Node::getData()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.074

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 16
rs 10
ccs 5
cts 6
cp 0.8333
cc 4
nc 3
nop 5
crap 4.074
1
<?php
2
/**
3
 * Functionality for the navigation tree in the left frame
4
 */
5
6
declare(strict_types=1);
7
8
namespace PhpMyAdmin\Navigation\Nodes;
9
10
use PhpMyAdmin\Config;
11
use PhpMyAdmin\ConfigStorage\Features\NavigationItemsHidingFeature;
12
use PhpMyAdmin\ConfigStorage\RelationParameters;
13
use PhpMyAdmin\DatabaseInterface;
14
use PhpMyAdmin\Dbal\ConnectionType;
15
use PhpMyAdmin\Html\Generator;
16
use PhpMyAdmin\Navigation\NodeType;
17
use PhpMyAdmin\UserPrivileges;
18
use PhpMyAdmin\Util;
19
20
use function __;
21
use function array_keys;
22
use function array_reverse;
23
use function array_slice;
24
use function base64_encode;
25
use function count;
26
use function implode;
27
use function in_array;
28
use function is_string;
29
use function preg_match;
30
use function sort;
31
use function sprintf;
32
use function str_starts_with;
33
use function strstr;
34
35
/**
36
 * The Node is the building block for the collapsible navigation tree
37
 */
38
class Node
39
{
40
    /**
41
     * @var string A non-unique identifier for the node
42
     *             This will never change after being assigned
43
     */
44
    public string $realName = '';
45
46
    /**
47
     * @var bool Whether to add a "display: none;" CSS
48
     *           rule to the node when rendering it
49
     */
50
    public bool $visible = false;
51
    /**
52
     * @var Node|null A reference to the parent object of
53
     *           this node, NULL for the root node.
54
     */
55
    public Node|null $parent = null;
56
    /**
57
     * @var Node[] An array of Node objects that are
58
     *             direct children of this node
59
     */
60
    public array $children = [];
61
    /**
62
     * @var mixed A string used to group nodes, or an array of strings
63
     *            Only relevant if the node is of type CONTAINER
64
     */
65
    public mixed $separator = '';
66
    /**
67
     * @var int How many time to recursively apply the grouping function
68
     *          Only relevant if the node is of type CONTAINER
69
     */
70
    public int $separatorDepth = 1;
71
72
    /**
73
     * For the IMG tag, used when rendering the node.
74
     *
75
     * @var array<string, string>
76
     * @psalm-var array{image: string, title: string}
77
     */
78
    public array $icon = ['image' => '', 'title' => ''];
79
80
    /**
81
     * An array of A tags, used when rendering the node.
82
     *
83
     * @var array<string, mixed>
84
     * @psalm-var array{
85
     *   text: array{route: string, params: array<string, mixed>},
86
     *   icon: array{route: string, params: array<string, mixed>},
87
     *   second_icon?: array{route: string, params: array<string, mixed>},
88
     *   title?: string
89
     * }
90
     */
91
    public array $links = ['text' => ['route' => '', 'params' => []], 'icon' => ['route' => '', 'params' => []]];
92
93
    /** @var string HTML title */
94
    public string $title = '';
95
    /** @var string Extra CSS classes for the node */
96
    public string $classes = '';
97
    /** @var bool Whether this node is a link for creating new objects */
98
    public bool $isNew = false;
99
    /**
100
     * @var int The position for the pagination of
101
     *          the branch at the second level of the tree
102
     */
103
    public int $pos2 = 0;
104
    /**
105
     * @var int The position for the pagination of
106
     *          the branch at the third level of the tree
107
     */
108
    public int $pos3 = 0;
109
110
    /** @var string $displayName  display name for the navigation tree */
111
    public string|null $displayName = null;
112
113
    public string|null $urlParamName = null;
114
115
    /**
116
     * Initialises the class by setting the mandatory variables
117
     *
118
     * @param string   $name    A non-unique identifier for the node
119
     *                          This may be trimmed when grouping nodes
120
     * @param NodeType $type    Type of node, may be one of CONTAINER or OBJECT
121
     * @param bool     $isGroup Whether this object has been created while grouping nodes
122
     *                          Only relevant if the node is of type CONTAINER
123
     */
124 100
    public function __construct(
125
        protected readonly Config $config,
126
        public string $name = '',
127
        public readonly NodeType $type = NodeType::Object,
128
        public bool $isGroup = false,
129
    ) {
130 100
        $this->realName = $name;
131
    }
132
133
    /**
134
     * Instantiates a Node object that will be used only for "New db/table/etc.." objects
135
     *
136
     * @param string $name    An identifier for the new node
137
     * @param string $classes Extra CSS classes for the node
138
     */
139 4
    public function getInstanceForNewNode(
140
        string $name,
141
        string $classes,
142
    ): Node {
143 4
        $node = new Node($this->config, $name);
144 4
        $node->title = $name;
145 4
        $node->isNew = true;
146 4
        $node->classes = $classes;
147
148 4
        return $node;
149
    }
150
151
    /**
152
     * Adds a child node to this node
153
     *
154
     * @param Node $child A child node
155
     */
156 52
    public function addChild(Node $child): void
157
    {
158 52
        $this->children[] = $child;
159 52
        $child->parent = $this;
160
    }
161
162
    /**
163
     * Returns a child node given it's name
164
     *
165
     * @param string $name     The name of requested child
166
     * @param bool   $realName Whether to use the "realName"
167
     *                         instead of "name" in comparisons
168
     *
169
     * @return Node|null The requested child node or null,
170
     *                   if the requested node cannot be found
171
     */
172 8
    public function getChild(string $name, bool $realName = false): Node|null
173
    {
174 8
        if ($realName) {
175 8
            foreach ($this->children as $child) {
176 8
                if ($child->realName === $name) {
177 8
                    return $child;
178
                }
179
            }
180
        } else {
181 8
            foreach ($this->children as $child) {
182 8
                if ($child->name === $name && ! $child->isNew) {
183 8
                    return $child;
184
                }
185
            }
186
        }
187
188 4
        return null;
189
    }
190
191
    /**
192
     * Removes a child node from this node
193
     *
194
     * @param string $name The name of child to be removed
195
     */
196 4
    public function removeChild(string $name): void
197
    {
198 4
        foreach ($this->children as $key => $child) {
199 4
            if ($child->name === $name) {
200 4
                unset($this->children[$key]);
201 4
                break;
202
            }
203
        }
204
    }
205
206
    /**
207
     * Retrieves the parents for a node
208
     *
209
     * @param bool $self       Whether to include the Node itself in the results
210
     * @param bool $containers Whether to include nodes of type CONTAINER
211
     * @param bool $groups     Whether to include nodes which have $group == true
212
     *
213
     * @return Node[] An array of parent Nodes
214
     */
215 24
    public function parents(bool $self = false, bool $containers = false, bool $groups = false): array
216
    {
217 24
        $parents = [];
218 24
        if ($self && ($this->type !== NodeType::Container || $containers) && (! $this->isGroup || $groups)) {
219 20
            $parents[] = $this;
220
        }
221
222 24
        $parent = $this->parent;
223 24
        if ($parent === null) {
224
            /** @infection-ignore-all */
225 8
            return $parents;
226
        }
227
228 24
        while ($parent !== null) {
229 24
            if (($parent->type !== NodeType::Container || $containers) && (! $parent->isGroup || $groups)) {
230 24
                $parents[] = $parent;
231
            }
232
233 24
            $parent = $parent->parent;
234
        }
235
236 24
        return $parents;
237
    }
238
239
    /**
240
     * Returns the actual parent of a node. If used twice on an index or columns
241
     * node, it will return the table and database nodes. The names of the returned
242
     * nodes can be used in SQL queries, etc...
243
     */
244 4
    public function realParent(): Node|false
245
    {
246 4
        $retval = $this->parents();
247 4
        if (count($retval) <= 0) {
248 4
            return false;
249
        }
250
251 4
        return $retval[0];
252
    }
253
254
    /**
255
     * This function checks if the node has children nodes associated with it
256
     *
257
     * @param bool $countEmptyContainers Whether to count empty child
258
     *                                   containers as valid children
259
     */
260 12
    public function hasChildren(bool $countEmptyContainers = true): bool
261
    {
262 12
        if ($countEmptyContainers) {
263 8
            return $this->children !== [];
264
        }
265
266 12
        foreach ($this->children as $child) {
267 12
            if ($child->type === NodeType::Object || $child->hasChildren(false)) {
268 12
                return true;
269
            }
270
        }
271
272 12
        return false;
273
    }
274
275
    /**
276
     * Returns true if the node has some siblings (other nodes on the same tree
277
     * level, in the same branch), false otherwise.
278
     * The only exception is for nodes on
279
     * the third level of the tree (columns and indexes), for which the function
280
     * always returns true. This is because we want to render the containers
281
     * for these nodes
282
     */
283 12
    public function hasSiblings(): bool
284
    {
285 12
        if ($this->parent === null) {
286 4
            return false;
287
        }
288
289 12
        $paths = $this->getPaths();
290 12
        if (count($paths['aPath_clean']) > 3) {
291 4
            return true;
292
        }
293
294 12
        foreach ($this->parent->children as $child) {
295 12
            if ($child !== $this && ($child->type === NodeType::Object || $child->hasChildren(false))) {
296 8
                return true;
297
            }
298
        }
299
300 12
        return false;
301
    }
302
303
    /**
304
     * Returns the number of child nodes that a node has associated with it
305
     *
306
     * @return int The number of children nodes
307
     */
308 4
    public function numChildren(): int
309
    {
310 4
        $retval = 0;
311 4
        foreach ($this->children as $child) {
312 4
            if ($child->type === NodeType::Object) {
313 4
                $retval++;
314
            } else {
315 4
                $retval += $child->numChildren();
316
            }
317
        }
318
319 4
        return $retval;
320
    }
321
322
    /**
323
     * Returns the actual path and the virtual paths for a node
324
     * both as clean arrays and base64 encoded strings
325
     *
326
     * @return array{aPath: string, aPath_clean: string[], vPath: string, vPath_clean: string[]}
327
     */
328 16
    public function getPaths(): array
329
    {
330 16
        $aPath = [];
331 16
        $aPathClean = [];
332 16
        foreach ($this->parents(true, true) as $parent) {
333 16
            $aPath[] = base64_encode($parent->realName);
334 16
            $aPathClean[] = $parent->realName;
335
        }
336
337 16
        $aPath = implode('.', array_reverse($aPath));
338 16
        $aPathClean = array_reverse($aPathClean);
339
340 16
        $vPath = [];
341 16
        $vPathClean = [];
342 16
        foreach ($this->parents(true, true, true) as $parent) {
343 16
            $vPath[] = base64_encode($parent->name);
344 16
            $vPathClean[] = $parent->name;
345
        }
346
347 16
        $vPath = implode('.', array_reverse($vPath));
348 16
        $vPathClean = array_reverse($vPathClean);
349
350 16
        return ['aPath' => $aPath, 'aPath_clean' => $aPathClean, 'vPath' => $vPath, 'vPath_clean' => $vPathClean];
351
    }
352
353
    /**
354
     * Returns the names of children of type $type present inside this container
355
     * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase
356
     * and PhpMyAdmin\Navigation\Nodes\NodeTable classes
357
     *
358
     * @param string $type         The type of item we are looking for
359
     *                             ('tables', 'views', etc)
360
     * @param int    $pos          The offset of the list within the results
361
     * @param string $searchClause A string used to filter the results of the query
362
     *
363
     * @return mixed[]
364
     */
365 12
    public function getData(
366
        UserPrivileges $userPrivileges,
367
        RelationParameters $relationParameters,
0 ignored issues
show
Unused Code introduced by
The parameter $relationParameters is not used and could be removed. ( Ignorable by Annotation )

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

367
        /** @scrutinizer ignore-unused */ RelationParameters $relationParameters,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
368
        string $type,
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

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

368
        /** @scrutinizer ignore-unused */ string $type,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
369
        int $pos,
370
        string $searchClause = '',
371
    ): array {
372 12
        if (isset($this->config->selectedServer['DisableIS']) && ! $this->config->selectedServer['DisableIS']) {
373 8
            return $this->getDataFromInfoSchema($pos, $searchClause);
374
        }
375
376 4
        if ($userPrivileges->databasesToTest === false) {
377 4
            return $this->getDataFromShowDatabases($pos, $searchClause);
378
        }
379
380
        return $this->getDataFromShowDatabasesLike($userPrivileges, $pos, $searchClause);
381
    }
382
383
    /**
384
     * Returns the number of children of type $type present inside this container
385
     * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase
386
     * and PhpMyAdmin\Navigation\Nodes\NodeTable classes
387
     *
388
     * @param string $type         The type of item we are looking for
389
     *                             ('tables', 'views', etc)
390
     * @param string $searchClause A string used to filter the results of the query
391
     */
392 12
    public function getPresence(UserPrivileges $userPrivileges, string $type = '', string $searchClause = ''): int
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

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

392
    public function getPresence(UserPrivileges $userPrivileges, /** @scrutinizer ignore-unused */ string $type = '', string $searchClause = ''): int

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
393
    {
394 12
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

394
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
395
        if (
396 12
            ! $this->config->settings['NavigationTreeEnableGrouping']
397 12
            || ! $this->config->settings['ShowDatabasesNavigationAsTree']
398
        ) {
399 4
            if (isset($this->config->selectedServer['DisableIS']) && ! $this->config->selectedServer['DisableIS']) {
400 4
                $query = 'SELECT COUNT(*) ';
401 4
                $query .= 'FROM INFORMATION_SCHEMA.SCHEMATA ';
402 4
                $query .= $this->getWhereClause('SCHEMA_NAME', $searchClause);
403
404 4
                return (int) $dbi->fetchValue($query);
405
            }
406
407
            if ($userPrivileges->databasesToTest === false) {
408
                $query = 'SHOW DATABASES ';
409
                $query .= $this->getWhereClause('Database', $searchClause);
410
411
                return (int) $dbi->queryAndGetNumRows($query);
412
            }
413
414
            $retval = 0;
415
            foreach ($this->getDatabasesToSearch($userPrivileges, $searchClause) as $db) {
416
                $query = 'SHOW DATABASES LIKE ' . $dbi->quoteString($db);
417
                $retval += (int) $dbi->queryAndGetNumRows($query);
418
            }
419
420
            return $retval;
421
        }
422
423 8
        $dbSeparator = $this->config->settings['NavigationTreeDbSeparator'];
424 8
        if (! $this->config->selectedServer['DisableIS']) {
425 4
            $query = 'SELECT COUNT(*) ';
426 4
            $query .= 'FROM ( ';
427 4
            $query .= 'SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, ';
428 4
            $query .= "'" . $dbSeparator . "', 1) ";
429 4
            $query .= 'DB_first_level ';
430 4
            $query .= 'FROM INFORMATION_SCHEMA.SCHEMATA ';
431 4
            $query .= $this->getWhereClause('SCHEMA_NAME', $searchClause);
432 4
            $query .= ') t ';
433
434 4
            return (int) $dbi->fetchValue($query);
435
        }
436
437 4
        if ($userPrivileges->databasesToTest !== false) {
438
            $prefixMap = [];
439
            foreach ($this->getDatabasesToSearch($userPrivileges, $searchClause) as $db) {
440
                $query = 'SHOW DATABASES LIKE ' . $dbi->quoteString($db);
441
                $handle = $dbi->tryQuery($query);
442
                if ($handle === false) {
443
                    continue;
444
                }
445
446
                while ($arr = $handle->fetchRow()) {
447
                    if ($this->isHideDb($arr[0])) {
448
                        continue;
449
                    }
450
451
                    $prefix = strstr($arr[0], $dbSeparator, true);
452
                    if ($prefix === false) {
453
                        $prefix = $arr[0];
454
                    }
455
456
                    $prefixMap[$prefix] = 1;
457
                }
458
            }
459
460
            return count($prefixMap);
461
        }
462
463 4
        $prefixMap = [];
464 4
        $query = 'SHOW DATABASES ';
465 4
        $query .= $this->getWhereClause('Database', $searchClause);
466 4
        $handle = $dbi->tryQuery($query);
467 4
        if ($handle !== false) {
468 4
            while ($arr = $handle->fetchRow()) {
469
                $prefix = strstr($arr[0], $dbSeparator, true);
0 ignored issues
show
Bug introduced by
It seems like $arr[0] can also be of type null; however, parameter $haystack of strstr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

469
                $prefix = strstr(/** @scrutinizer ignore-type */ $arr[0], $dbSeparator, true);
Loading history...
470
                if ($prefix === false) {
471
                    $prefix = $arr[0];
472
                }
473
474
                $prefixMap[$prefix] = 1;
475
            }
476
        }
477
478 4
        return count($prefixMap);
479
    }
480
481
    /**
482
     * Detemines whether a given database should be hidden according to 'hide_db'
483
     *
484
     * @param string $db database name
485
     */
486
    private function isHideDb(string $db): bool
487
    {
488
        return ! empty($this->config->selectedServer['hide_db'])
489
            && preg_match('/' . $this->config->selectedServer['hide_db'] . '/', $db);
490
    }
491
492
    /**
493
     * Get the list of databases for 'SHOW DATABASES LIKE' queries.
494
     * If a search clause is set it gets the highest priority while only_db gets
495
     * the next priority. In case both are empty list of databases determined by
496
     * GRANTs are used
497
     *
498
     * @param string $searchClause search clause
499
     *
500
     * @return mixed[] array of databases
501
     */
502
    private function getDatabasesToSearch(UserPrivileges $userPrivileges, string $searchClause): array
503
    {
504
        $databases = [];
505
        if ($searchClause !== '') {
506
            $databases = ['%' . DatabaseInterface::getInstance()->escapeMysqlWildcards($searchClause) . '%'];
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

506
            $databases = ['%' . /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance()->escapeMysqlWildcards($searchClause) . '%'];

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
507
        } elseif (! empty($this->config->selectedServer['only_db'])) {
508
            $databases = $this->config->selectedServer['only_db'];
509
        } elseif ($userPrivileges->databasesToTest !== false && $userPrivileges->databasesToTest !== []) {
510
            $databases = $userPrivileges->databasesToTest;
511
        }
512
513
        sort($databases);
514
515
        return $databases;
516
    }
517
518
    /**
519
     * Returns the WHERE clause depending on the $searchClause parameter
520
     * and the hide_db directive
521
     *
522
     * @param string $columnName   Column name of the column having database names
523
     * @param string $searchClause A string used to filter the results of the query
524
     */
525 28
    private function getWhereClause(string $columnName, string $searchClause = ''): string
526
    {
527 28
        $whereClause = 'WHERE TRUE ';
528 28
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

528
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
529 28
        if ($searchClause !== '') {
530 12
            $whereClause .= 'AND ' . Util::backquote($columnName)
531 12
                . ' LIKE ' . $dbi->quoteString('%' . $dbi->escapeMysqlWildcards($searchClause) . '%') . ' ';
532
        }
533
534 28
        if (! empty($this->config->selectedServer['hide_db'])) {
535 4
            $whereClause .= 'AND ' . Util::backquote($columnName)
536 4
                . ' NOT REGEXP ' . $dbi->quoteString($this->config->selectedServer['hide_db']) . ' ';
537
        }
538
539 28
        if (! empty($this->config->selectedServer['only_db'])) {
540 4
            if (is_string($this->config->selectedServer['only_db'])) {
541 4
                $this->config->selectedServer['only_db'] = [$this->config->selectedServer['only_db']];
542
            }
543
544 4
            $whereClause .= 'AND (';
545 4
            $subClauses = [];
546 4
            foreach ($this->config->selectedServer['only_db'] as $eachOnlyDb) {
547 4
                $subClauses[] = ' ' . Util::backquote($columnName)
548 4
                    . ' LIKE ' . $dbi->quoteString($eachOnlyDb) . ' ';
549
            }
550
551 4
            $whereClause .= implode('OR', $subClauses) . ') ';
552
        }
553
554 28
        return $whereClause;
555
    }
556
557
    /**
558
     * Returns HTML for control buttons displayed infront of a node
559
     *
560
     * @return string HTML for control buttons
561
     */
562
    public function getHtmlForControlButtons(NavigationItemsHidingFeature|null $navigationItemsHidingFeature): string
563
    {
564
        return '';
565
    }
566
567
    /**
568
     * Returns CSS classes for a node
569
     *
570
     * @param bool $match Whether the node matched loaded tree
571
     *
572
     * @return string with html classes.
573
     */
574
    public function getCssClasses(bool $match): string
575
    {
576
        if (! $this->config->settings['NavigationTreeEnableExpansion']) {
577
            return '';
578
        }
579
580
        $result = ['expander'];
581
582
        if ($this->isGroup || $match) {
583
            $result[] = 'loaded';
584
        }
585
586
        if ($this->type === NodeType::Container) {
587
            $result[] = 'container';
588
        }
589
590
        return implode(' ', $result);
591
    }
592
593
    /**
594
     * Returns icon for the node
595
     *
596
     * @param bool $match Whether the node matched loaded tree
597
     *
598
     * @return string with image name
599
     */
600
    public function getIcon(bool $match): string
601
    {
602
        if (! $this->config->settings['NavigationTreeEnableExpansion']) {
603
            return '';
604
        }
605
606
        if ($match) {
607
            $this->visible = true;
608
609
            return Generator::getImage('b_minus');
610
        }
611
612
        return Generator::getImage('b_plus', __('Expand/Collapse'));
613
    }
614
615
    /**
616
     * Gets the count of hidden elements for each database
617
     *
618
     * @return mixed[]|null array containing the count of hidden elements for each database
619
     */
620
    public function getNavigationHidingData(NavigationItemsHidingFeature|null $navigationItemsHidingFeature): array|null
621
    {
622
        if ($navigationItemsHidingFeature !== null) {
623
            $navTable = Util::backquote($navigationItemsHidingFeature->database)
624
                . '.' . Util::backquote($navigationItemsHidingFeature->navigationHiding);
625
            $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

625
            $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
626
            $sqlQuery = 'SELECT `db_name`, COUNT(*) AS `count` FROM ' . $navTable
627
                . ' WHERE `username`='
628
                . $dbi->quoteString($this->config->selectedServer['user'])
629
                . ' GROUP BY `db_name`';
630
631
            return $dbi->fetchResult($sqlQuery, 'db_name', 'count', ConnectionType::ControlUser);
632
        }
633
634
        return null;
635
    }
636
637
    /**
638
     * @param int    $pos          The offset of the list within the results.
639
     * @param string $searchClause A string used to filter the results of the query.
640
     *
641
     * @return mixed[]
642
     */
643 8
    private function getDataFromInfoSchema(int $pos, string $searchClause): array
644
    {
645 8
        $maxItems = $this->config->settings['FirstLevelNavigationItems'];
646 8
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

646
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
647
        if (
648 8
            ! $this->config->settings['NavigationTreeEnableGrouping']
649 8
            || ! $this->config->settings['ShowDatabasesNavigationAsTree']
650
        ) {
651 4
            $query = sprintf(
652 4
                'SELECT `SCHEMA_NAME` FROM `INFORMATION_SCHEMA`.`SCHEMATA` %sORDER BY `SCHEMA_NAME` LIMIT %d, %d',
653 4
                $this->getWhereClause('SCHEMA_NAME', $searchClause),
654 4
                $pos,
655 4
                $maxItems,
656 4
            );
657
658 4
            return $dbi->fetchResult($query);
659
        }
660
661 4
        $dbSeparator = $this->config->settings['NavigationTreeDbSeparator'];
662 4
        $query = sprintf(
663 4
            'SELECT `SCHEMA_NAME` FROM `INFORMATION_SCHEMA`.`SCHEMATA`, (SELECT DB_first_level'
664 4
                . ' FROM ( SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, %1$s, 1) DB_first_level'
665 4
                . ' FROM INFORMATION_SCHEMA.SCHEMATA %2$s) t'
666 4
                . ' ORDER BY DB_first_level ASC LIMIT %3$d, %4$d) t2'
667 4
                . ' %2$sAND 1 = LOCATE(CONCAT(DB_first_level, %1$s),'
668 4
                . ' CONCAT(SCHEMA_NAME, %1$s)) ORDER BY SCHEMA_NAME ASC',
669 4
            $dbi->quoteString($dbSeparator),
670 4
            $this->getWhereClause('SCHEMA_NAME', $searchClause),
671 4
            $pos,
672 4
            $maxItems,
673 4
        );
674
675 4
        return $dbi->fetchResult($query);
676
    }
677
678
    /**
679
     * @param int    $pos          The offset of the list within the results.
680
     * @param string $searchClause A string used to filter the results of the query.
681
     *
682
     * @return mixed[]
683
     */
684 4
    private function getDataFromShowDatabases(int $pos, string $searchClause): array
685
    {
686 4
        $maxItems = $this->config->settings['FirstLevelNavigationItems'];
687 4
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

687
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
688
        if (
689 4
            ! $this->config->settings['NavigationTreeEnableGrouping']
690 4
            || ! $this->config->settings['ShowDatabasesNavigationAsTree']
691
        ) {
692
            $handle = $dbi->tryQuery(sprintf(
693
                'SHOW DATABASES %s',
694
                $this->getWhereClause('Database', $searchClause),
695
            ));
696
            if ($handle === false) {
697
                return [];
698
            }
699
700
            $count = 0;
701
            if (! $handle->seek($pos)) {
702
                return [];
703
            }
704
705
            $retval = [];
706
            while ($arr = $handle->fetchRow()) {
707
                if ($count >= $maxItems) {
708
                    break;
709
                }
710
711
                $retval[] = $arr[0];
712
                $count++;
713
            }
714
715
            return $retval;
716
        }
717
718 4
        $dbSeparator = $this->config->settings['NavigationTreeDbSeparator'];
719 4
        $handle = $dbi->tryQuery(sprintf(
720 4
            'SHOW DATABASES %s',
721 4
            $this->getWhereClause('Database', $searchClause),
722 4
        ));
723 4
        $prefixes = [];
724 4
        if ($handle !== false) {
725 4
            $prefixMap = [];
726 4
            $total = $pos + $maxItems;
727 4
            while ($arr = $handle->fetchRow()) {
728 4
                $prefix = strstr($arr[0], $dbSeparator, true);
0 ignored issues
show
Bug introduced by
It seems like $arr[0] can also be of type null; however, parameter $haystack of strstr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

728
                $prefix = strstr(/** @scrutinizer ignore-type */ $arr[0], $dbSeparator, true);
Loading history...
729 4
                if ($prefix === false) {
730 4
                    $prefix = $arr[0];
731
                }
732
733 4
                $prefixMap[$prefix] = 1;
734 4
                if (count($prefixMap) == $total) {
735
                    break;
736
                }
737
            }
738
739 4
            $prefixes = array_slice(array_keys($prefixMap), $pos);
740
        }
741
742 4
        $subClauses = [];
743 4
        foreach ($prefixes as $prefix) {
744 4
            $subClauses[] = sprintf(
745 4
                ' LOCATE(%s, CONCAT(`Database`, %s)) = 1 ',
746 4
                $dbi->quoteString($prefix . $dbSeparator),
747 4
                $dbi->quoteString($dbSeparator),
748 4
            );
749
        }
750
751 4
        $query = sprintf(
752 4
            'SHOW DATABASES %sAND (%s)',
753 4
            $this->getWhereClause('Database', $searchClause),
754 4
            implode('OR', $subClauses),
755 4
        );
756
757 4
        return $dbi->fetchResult($query);
758
    }
759
760
    /**
761
     * @param int    $pos          The offset of the list within the results.
762
     * @param string $searchClause A string used to filter the results of the query.
763
     *
764
     * @return mixed[]
765
     */
766
    private function getDataFromShowDatabasesLike(UserPrivileges $userPrivileges, int $pos, string $searchClause): array
767
    {
768
        $maxItems = $this->config->settings['FirstLevelNavigationItems'];
769
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

769
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
770
        if (
771
            ! $this->config->settings['NavigationTreeEnableGrouping']
772
            || ! $this->config->settings['ShowDatabasesNavigationAsTree']
773
        ) {
774
            $retval = [];
775
            $count = 0;
776
            foreach ($this->getDatabasesToSearch($userPrivileges, $searchClause) as $db) {
777
                $handle = $dbi->tryQuery(sprintf('SHOW DATABASES LIKE %s', $dbi->quoteString($db)));
778
                if ($handle === false) {
779
                    continue;
780
                }
781
782
                while ($arr = $handle->fetchRow()) {
783
                    if ($this->isHideDb($arr[0])) {
784
                        continue;
785
                    }
786
787
                    if (in_array($arr[0], $retval, true)) {
788
                        continue;
789
                    }
790
791
                    if ($pos <= 0 && $count < $maxItems) {
792
                        $retval[] = $arr[0];
793
                        $count++;
794
                    }
795
796
                    $pos--;
797
                }
798
            }
799
800
            sort($retval);
801
802
            return $retval;
803
        }
804
805
        $dbSeparator = $this->config->settings['NavigationTreeDbSeparator'];
806
        $retval = [];
807
        $prefixMap = [];
808
        $total = $pos + $maxItems;
809
        foreach ($this->getDatabasesToSearch($userPrivileges, $searchClause) as $db) {
810
            $handle = $dbi->tryQuery(sprintf('SHOW DATABASES LIKE %s', $dbi->quoteString($db)));
811
            if ($handle === false) {
812
                continue;
813
            }
814
815
            while ($arr = $handle->fetchRow()) {
816
                if ($this->isHideDb($arr[0])) {
817
                    continue;
818
                }
819
820
                $prefix = strstr($arr[0], $dbSeparator, true);
821
                if ($prefix === false) {
822
                    $prefix = $arr[0];
823
                }
824
825
                $prefixMap[$prefix] = 1;
826
                if (count($prefixMap) == $total) {
827
                    break 2;
828
                }
829
            }
830
        }
831
832
        $prefixes = array_slice(array_keys($prefixMap), $pos);
833
834
        foreach ($this->getDatabasesToSearch($userPrivileges, $searchClause) as $db) {
835
            $handle = $dbi->tryQuery(sprintf('SHOW DATABASES LIKE %s', $dbi->quoteString($db)));
836
            if ($handle === false) {
837
                continue;
838
            }
839
840
            while ($arr = $handle->fetchRow()) {
841
                if ($this->isHideDb($arr[0])) {
842
                    continue;
843
                }
844
845
                if (in_array($arr[0], $retval, true)) {
846
                    continue;
847
                }
848
849
                foreach ($prefixes as $prefix) {
850
                    $startsWith = str_starts_with($arr[0] . $dbSeparator, $prefix . $dbSeparator);
851
                    if ($startsWith) {
852
                        $retval[] = $arr[0];
853
                        break;
854
                    }
855
                }
856
            }
857
        }
858
859
        sort($retval);
860
861
        return $retval;
862
    }
863
}
864