NavigationTree::renderNode()   F
last analyzed

Complexity

Conditions 25
Paths 3052

Size

Total Lines 111
Code Lines 76

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 69
CRAP Score 25.4882

Importance

Changes 0
Metric Value
eloc 76
dl 0
loc 111
ccs 69
cts 76
cp 0.9079
rs 0
c 0
b 0
f 0
cc 25
nc 3052
nop 3
crap 25.4882

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Functionality for the navigation tree
4
 */
5
6
declare(strict_types=1);
7
8
namespace PhpMyAdmin\Navigation;
9
10
use PhpMyAdmin\Config;
11
use PhpMyAdmin\ConfigStorage\Relation;
12
use PhpMyAdmin\ConfigStorage\RelationParameters;
0 ignored issues
show
Bug introduced by
The type PhpMyAdmin\ConfigStorage\RelationParameters 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...
13
use PhpMyAdmin\Current;
14
use PhpMyAdmin\Dbal\DatabaseInterface;
15
use PhpMyAdmin\Error\ErrorHandler;
16
use PhpMyAdmin\Favorites\RecentFavoriteTables;
17
use PhpMyAdmin\Favorites\TableType;
18
use PhpMyAdmin\Html\Generator;
19
use PhpMyAdmin\Navigation\Nodes\Icon;
20
use PhpMyAdmin\Navigation\Nodes\Link;
21
use PhpMyAdmin\Navigation\Nodes\Node;
22
use PhpMyAdmin\Navigation\Nodes\NodeColumn;
23
use PhpMyAdmin\Navigation\Nodes\NodeColumnContainer;
24
use PhpMyAdmin\Navigation\Nodes\NodeDatabase;
25
use PhpMyAdmin\Navigation\Nodes\NodeDatabaseContainer;
26
use PhpMyAdmin\Navigation\Nodes\NodeEvent;
27
use PhpMyAdmin\Navigation\Nodes\NodeEventContainer;
28
use PhpMyAdmin\Navigation\Nodes\NodeFunction;
29
use PhpMyAdmin\Navigation\Nodes\NodeFunctionContainer;
30
use PhpMyAdmin\Navigation\Nodes\NodeIndex;
31
use PhpMyAdmin\Navigation\Nodes\NodeIndexContainer;
32
use PhpMyAdmin\Navigation\Nodes\NodeProcedure;
33
use PhpMyAdmin\Navigation\Nodes\NodeProcedureContainer;
34
use PhpMyAdmin\Navigation\Nodes\NodeTable;
35
use PhpMyAdmin\Navigation\Nodes\NodeTableContainer;
36
use PhpMyAdmin\Navigation\Nodes\NodeTrigger;
37
use PhpMyAdmin\Navigation\Nodes\NodeTriggerContainer;
38
use PhpMyAdmin\Navigation\Nodes\NodeView;
39
use PhpMyAdmin\Navigation\Nodes\NodeViewContainer;
40
use PhpMyAdmin\ResponseRenderer;
41
use PhpMyAdmin\Template;
42
use PhpMyAdmin\Url;
43
use PhpMyAdmin\UserPrivileges;
44
use PhpMyAdmin\UserPrivilegesFactory;
45
46
use function __;
47
use function _ngettext;
48
use function array_key_exists;
49
use function array_key_last;
50
use function array_keys;
51
use function array_map;
52
use function array_merge;
53
use function array_shift;
54
use function base64_decode;
55
use function count;
56
use function explode;
57
use function floor;
58
use function htmlspecialchars;
59
use function in_array;
60
use function is_bool;
61
use function mb_strlen;
62
use function mb_strpos;
63
use function mb_substr;
64
use function sort;
65
use function sprintf;
66
use function strcasecmp;
67
use function strlen;
68
use function strnatcasecmp;
69
use function strrpos;
70
use function strstr;
71
use function substr;
72
use function trim;
73
use function usort;
74
75
/**
76
 * Displays a collapsible of database objects in the navigation frame
77
 */
78
class NavigationTree
79
{
80
    private const SPECIAL_NODE_NAMES = ['tables', 'views', 'functions', 'procedures', 'events'];
81
82
    /** @var NodeDatabaseContainer Reference to the root node of the tree */
83
    private NodeDatabaseContainer $tree;
84
    /**
85
     * @var mixed[] The actual paths to all expanded nodes in the tree
86
     *            This does not include nodes created after the grouping
87
     *            of nodes has been performed
88
     */
89
    private array $aPath = [];
90
    /**
91
     * @var mixed[] The virtual paths to all expanded nodes in the tree
92
     *            This includes nodes created after the grouping of
93
     *            nodes has been performed
94
     */
95
    private array $vPath = [];
96
    /**
97
     * @var int Position in the list of databases,
98
     *          used for pagination
99
     */
100
    private int $pos = 0;
101
    /**
102
     * @var string[] The names of the type of items that are being paginated on
103
     *               the second level of the navigation tree. These may be
104
     *               tables, views, functions, procedures or events.
105
     */
106
    private array $pos2Name = [];
107
    /**
108
     * @var int[] The positions of nodes in the lists of tables, views,
109
     *            routines or events used for pagination
110
     */
111
    private array $pos2Value = [];
112
    /**
113
     * @var string[] The names of the type of items that are being paginated
114
     *               on the second level of the navigation tree.
115
     *               These may be columns or indexes
116
     */
117
    private array $pos3Name = [];
118
    /**
119
     * @var int[] The positions of nodes in the lists of columns or indexes
120
     *            used for pagination
121
     */
122
    private array $pos3Value = [];
123
    /**
124
     * @var string The search clause to use in SQL queries for
125
     *             fetching databases
126
     *             Used by the asynchronous fast filter
127
     */
128
    private string $searchClause = '';
129
    /**
130
     * @var string The search clause to use in SQL queries for
131
     *             fetching nodes
132
     *             Used by the asynchronous fast filter
133
     */
134
    private string $searchClause2 = '';
135
    /**
136
     * @var bool Whether a warning was raised for large item groups
137
     *           which can affect performance.
138
     */
139
    private bool $largeGroupWarning = false;
140
141
    private RelationParameters $relationParameters;
142
143 16
    public function __construct(
144
        private Template $template,
145
        private DatabaseInterface $dbi,
146
        Relation $relation,
147
        private readonly Config $config,
148
    ) {
149 16
        $this->relationParameters = $relation->getRelationParameters();
150 16
        $userPrivilegesFactory = new UserPrivilegesFactory($this->dbi);
151 16
        $userPrivileges = $userPrivilegesFactory->getPrivileges();
152
153
        // Save the position at which we are in the database list
154 16
        if (isset($_POST['pos'])) {
155
            $this->pos = (int) $_POST['pos'];
156 16
        } elseif (isset($_GET['pos'])) {
157
            $this->pos = (int) $_GET['pos'];
158
        } else {
159 16
            $this->pos = $this->getNavigationDbPos($userPrivileges);
160
        }
161
162
        // Get the active node
163 16
        if (isset($_POST['aPath'])) {
164
            $this->aPath[0] = $this->parsePath($_POST['aPath']);
165
            $this->pos2Name[0] = $_POST['pos2_name'] ?? '';
166
            $this->pos2Value[0] = (int) ($_POST['pos2_value'] ?? 0);
167
            if (isset($_POST['pos3_name'])) {
168
                $this->pos3Name[0] = $_POST['pos3_name'];
169
                $this->pos3Value[0] = (int) $_POST['pos3_value'];
170
            }
171 16
        } elseif (isset($_POST['n0_aPath'])) {
172
            $count = 0;
173
            while (isset($_POST['n' . $count . '_aPath'])) {
174
                $this->aPath[$count] = $this->parsePath($_POST['n' . $count . '_aPath']);
175
                if (isset($_POST['n' . $count . '_pos2_name'])) {
176
                    $this->pos2Name[$count] = $_POST['n' . $count . '_pos2_name'];
177
                    $this->pos2Value[$count] = (int) $_POST['n' . $count . '_pos2_value'];
178
                }
179
180
                if (isset($_POST['n' . $count . '_pos3_name'])) {
181
                    $this->pos3Name[$count] = $_POST['n' . $count . '_pos3_name'];
182
                    $this->pos3Value[$count] = (int) $_POST['n' . $count . '_pos3_value'];
183
                }
184
185
                $count++;
186
            }
187
        }
188
189 16
        if (isset($_POST['vPath'])) {
190
            $this->vPath[0] = $this->parsePath($_POST['vPath']);
191 16
        } elseif (isset($_POST['n0_vPath'])) {
192
            $count = 0;
193
            while (isset($_POST['n' . $count . '_vPath'])) {
194
                $this->vPath[$count] = $this->parsePath($_POST['n' . $count . '_vPath']);
195
                $count++;
196
            }
197
        }
198
199 16
        if (isset($_POST['searchClause'])) {
200
            $this->searchClause = $_POST['searchClause'];
201
        }
202
203 16
        if (isset($_POST['searchClause2'])) {
204
            $this->searchClause2 = $_POST['searchClause2'];
205
        }
206
207
        // Initialize the tree by creating a root node
208 16
        $this->tree = new NodeDatabaseContainer($this->config, 'root');
209
    }
210
211
    /**
212
     * Returns the database position for the page selector
213
     */
214 16
    private function getNavigationDbPos(UserPrivileges $userPrivileges): int
215
    {
216 16
        if (Current::$database === '') {
217 4
            return 0;
218
        }
219
220
        /** @todo describe a scenario where this code is executed */
221 16
        if (! $this->config->selectedServer['DisableIS']) {
222 16
            $query = 'SELECT (COUNT(DB_first_level) DIV %d) * %d ';
223 16
            $query .= 'from ( ';
224 16
            $query .= ' SELECT distinct SUBSTRING_INDEX(SCHEMA_NAME, ';
225 16
            $query .= ' %s, 1) ';
226 16
            $query .= ' DB_first_level ';
227 16
            $query .= ' FROM INFORMATION_SCHEMA.SCHEMATA ';
228 16
            $query .= ' WHERE `SCHEMA_NAME` < %s ';
229 16
            $query .= ') t ';
230
231 16
            return (int) $this->dbi->fetchValue(
232 16
                sprintf(
233 16
                    $query,
234 16
                    $this->config->settings['FirstLevelNavigationItems'],
235 16
                    $this->config->settings['FirstLevelNavigationItems'],
236 16
                    $this->dbi->quoteString($this->config->settings['NavigationTreeDbSeparator']),
237 16
                    $this->dbi->quoteString(Current::$database),
238 16
                ),
239 16
            );
240
        }
241
242
        $prefixMap = [];
243
        if ($userPrivileges->databasesToTest === false) {
244
            $handle = $this->dbi->tryQuery('SHOW DATABASES');
245
            if ($handle !== false) {
246
                while ($database = $handle->fetchValue()) {
247
                    if (strcasecmp($database, Current::$database) >= 0) {
248
                        break;
249
                    }
250
251
                    $prefix = strstr($database, $this->config->settings['NavigationTreeDbSeparator'], true);
252
                    if ($prefix === false) {
253
                        $prefix = $database;
254
                    }
255
256
                    $prefixMap[$prefix] = 1;
257
                }
258
            }
259
        } else {
260
            $databases = [];
261
            foreach ($userPrivileges->databasesToTest as $db) {
262
                $query = "SHOW DATABASES LIKE '" . $db . "'";
263
                $handle = $this->dbi->tryQuery($query);
264
                if ($handle === false) {
265
                    continue;
266
                }
267
268
                $databases = array_merge($databases, $handle->fetchAllColumn());
269
            }
270
271
            sort($databases);
272
            foreach ($databases as $database) {
273
                if (strcasecmp($database, Current::$database) >= 0) {
274
                    break;
275
                }
276
277
                $prefix = strstr($database, $this->config->settings['NavigationTreeDbSeparator'], true);
278
                if ($prefix === false) {
279
                    $prefix = $database;
280
                }
281
282
                $prefixMap[$prefix] = 1;
283
            }
284
        }
285
286
        $navItems = $this->config->settings['FirstLevelNavigationItems'];
287
288
        return (int) floor(count($prefixMap) / $navItems) * $navItems;
289
    }
290
291
    /**
292
     * Converts an encoded path to a node in string format to an array
293
     *
294
     * @param string $string The path to parse
295
     *
296
     * @return non-empty-list<string>
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-list<string> at position 0 could not be parsed: Unknown type name 'non-empty-list' at position 0 in non-empty-list<string>.
Loading history...
297
     */
298
    private function parsePath(string $string): array
299
    {
300
        return array_map(base64_decode(...), explode('.', $string));
0 ignored issues
show
Bug introduced by
The type base64_decode 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...
301
    }
302
303
    /**
304
     * Generates the tree structure so that it can be rendered later
305
     *
306
     * @return Node|bool The active node or false in case of failure, or true: (@see buildPathPart())
307
     */
308 16
    private function buildPath(UserPrivileges $userPrivileges): Node|bool
309
    {
310 16
        $retval = $this->tree;
311
312
        // Add all databases unconditionally
313 16
        $data = $this->tree->getData(
314 16
            $userPrivileges,
315 16
            $this->relationParameters,
316 16
            'databases',
317 16
            $this->pos,
318 16
            $this->searchClause,
319 16
        );
320 16
        $hiddenCounts = $this->tree->getNavigationHidingData($this->relationParameters->navigationItemsHidingFeature);
321 16
        foreach ($data as $db) {
322 16
            $node = new NodeDatabase($this->config, $db);
323 16
            if (isset($hiddenCounts[$db])) {
324
                $node->setHiddenCount((int) $hiddenCounts[$db]);
325
            }
326
327 16
            $this->tree->addChild($node);
328
        }
329
330
        // Whether build other parts of the tree depends
331
        // on whether we have any paths in $this->aPath
332 16
        foreach ($this->aPath as $key => $path) {
333
            $retval = $this->buildPathPart(
334
                $userPrivileges,
335
                $path,
336
                $this->pos2Name[$key] ?? '',
337
                $this->pos2Value[$key] ?? 0,
338
                $this->pos3Name[$key] ?? '',
339
                $this->pos3Value[$key] ?? 0,
340
            );
341
        }
342
343 16
        return $retval;
344
    }
345
346
    /**
347
     * Builds a branch of the tree
348
     *
349
     * @param mixed[] $path  A paths pointing to the branch
350
     *                     of the tree that needs to be built
351
     * @param string  $type2 The type of item being paginated on
352
     *                       the second level of the tree
353
     * @param int     $pos2  The position for the pagination of
354
     *                       the branch at the second level of the tree
355
     * @param string  $type3 The type of item being paginated on
356
     *                       the third level of the tree
357
     * @param int     $pos3  The position for the pagination of
358
     *                       the branch at the third level of the tree
359
     *
360
     * @return Node|bool    The active node or false in case of failure, true if the path contains <= 1 items
361
     */
362
    private function buildPathPart(
363
        UserPrivileges $userPrivileges,
364
        array $path,
365
        string $type2,
366
        int $pos2,
367
        string $type3,
368
        int $pos3,
369
    ): Node|bool {
370
        if (count($path) <= 1) {
371
            return true;
372
        }
373
374
        array_shift($path); // remove 'root'
375
        /** @var NodeDatabase|null $db */
376
        $db = $this->tree->getChild($path[0]);
377
378
        if ($db === null) {
379
            return false;
380
        }
381
382
        $containers = $this->addDbContainers($userPrivileges, $db, $type2, $pos2);
383
384
        array_shift($path); // remove db
385
386
        if (($path === [] || ! array_key_exists($path[0], $containers)) && count($containers) != 1) {
387
            return $db;
388
        }
389
390
        if (count($containers) === 1) {
391
            $container = array_shift($containers);
392
        } else {
393
            $container = $db->getChild($path[0], true);
394
            if ($container === null) {
395
                return false;
396
            }
397
        }
398
399
        if (count($container->children) <= 1) {
400
            $dbData = $db->getData(
401
                $userPrivileges,
402
                $this->relationParameters,
403
                $container->realName,
404
                $pos2,
405
                $this->searchClause2,
406
            );
407
            foreach ($dbData as $item) {
408
                $node = match ($container->realName) {
409
                    'events' => new NodeEvent($this->config, $item),
410
                    'functions' => new NodeFunction($this->config, $item),
411
                    'procedures' => new NodeProcedure($this->config, $item),
412
                    'tables' => new NodeTable($this->config, $item),
413
                    'views' => new NodeView($this->config, $item),
414
                    default => null,
415
                };
416
417
                if ($node === null) {
418
                    continue;
419
                }
420
421
                if ($type2 === $container->realName) {
422
                    $node->pos2 = $pos2;
423
                }
424
425
                $container->addChild($node);
426
            }
427
        }
428
429
        if (count($path) > 1 && $path[0] !== 'tables') {
430
            return false;
431
        }
432
433
        array_shift($path); // remove container
434
        if ($path === []) {
435
            return $container;
436
        }
437
438
        /** @var NodeTable|null $table */
439
        $table = $container->getChild($path[0], true);
440
        if ($table === null) {
441
            if ($db->getPresence($userPrivileges, 'tables', $path[0]) === 0) {
442
                return false;
443
            }
444
445
            $node = new NodeTable($this->config, $path[0]);
446
            if ($type2 === $container->realName) {
447
                $node->pos2 = $pos2;
448
            }
449
450
            $container->addChild($node);
451
            $table = $container->getChild($path[0], true);
452
            if ($table === null) {
453
                return false;
454
            }
455
        }
456
457
        $containers = $this->addTableContainers($userPrivileges, $table, $pos2, $type3, $pos3);
458
        array_shift($path); // remove table
459
        if ($path === [] || ! array_key_exists($path[0], $containers)) {
460
            return $table;
461
        }
462
463
        $container = $table->getChild($path[0], true);
464
        if ($container === null) {
465
            return false;
466
        }
467
468
        $tableData = $table->getData($userPrivileges, $this->relationParameters, $container->realName, $pos3);
469
        foreach ($tableData as $item) {
470
            $node = match ($container->realName) {
471
                'indexes' => new NodeIndex($this->config, $item),
472
                'columns' => new NodeColumn($this->config, $item),
473
                'triggers' => new NodeTrigger($this->config, $item),
474
                default => null,
475
            };
476
477
            if ($node === null) {
478
                continue;
479
            }
480
481
            $node->pos2 = $container->parent->pos2;
482
            if ($type3 === $container->realName) {
483
                $node->pos3 = $pos3;
484
            }
485
486
            $container->addChild($node);
487
        }
488
489
        return $container;
490
    }
491
492
    /**
493
     * Adds containers to a node that is a table
494
     *
495
     * References to existing children are returned
496
     * if this function is called twice on the same node
497
     *
498
     * @param NodeTable $table The table node, new containers will be
499
     *                         attached to this node
500
     * @param int       $pos2  The position for the pagination of
501
     *                         the branch at the second level of the tree
502
     * @param string    $type3 The type of item being paginated on
503
     *                         the third level of the tree
504
     * @param int       $pos3  The position for the pagination of
505
     *                         the branch at the third level of the tree
506
     *
507
     * @return Node[] An array of new nodes
508
     */
509
    private function addTableContainers(
510
        UserPrivileges $userPrivileges,
511
        NodeTable $table,
512
        int $pos2,
513
        string $type3,
514
        int $pos3,
515
    ): array {
516
        $retval = [];
517
        if (! $table->hasChildren()) {
518
            if ($table->getPresence($userPrivileges, 'columns') !== 0) {
519
                $retval['columns'] = new NodeColumnContainer($this->config);
520
            }
521
522
            if ($table->getPresence($userPrivileges, 'indexes') !== 0) {
523
                $retval['indexes'] = new NodeIndexContainer($this->config);
524
            }
525
526
            if ($table->getPresence($userPrivileges, 'triggers') !== 0) {
527
                $retval['triggers'] = new NodeTriggerContainer($this->config);
528
            }
529
530
            // Add all new Nodes to the tree
531
            foreach ($retval as $node) {
532
                $node->pos2 = $pos2;
533
                if ($type3 === $node->realName) {
534
                    $node->pos3 = $pos3;
535
                }
536
537
                $table->addChild($node);
538
            }
539
        } else {
540
            foreach ($table->children as $node) {
541
                if ($type3 === $node->realName) {
542
                    $node->pos3 = $pos3;
543
                }
544
545
                $retval[$node->realName] = $node;
546
            }
547
        }
548
549
        return $retval;
550
    }
551
552
    /**
553
     * Adds containers to a node that is a database
554
     *
555
     * References to existing children are returned
556
     * if this function is called twice on the same node
557
     *
558
     * @param NodeDatabase $db   The database node, new containers will be
559
     *                           attached to this node
560
     * @param string       $type The type of item being paginated on
561
     *                           the second level of the tree
562
     * @param int          $pos2 The position for the pagination of
563
     *                           the branch at the second level of the tree
564
     *
565
     * @return Node[] An array of new nodes
566
     */
567
    private function addDbContainers(UserPrivileges $userPrivileges, NodeDatabase $db, string $type, int $pos2): array
568
    {
569
        // Get items to hide
570
        $hidden = $db->getHiddenItems($this->relationParameters, 'group');
571
        if (! $this->config->settings['NavigationTreeShowTables'] && ! in_array('tables', $hidden, true)) {
572
            $hidden[] = 'tables';
573
        }
574
575
        if (! $this->config->settings['NavigationTreeShowViews'] && ! in_array('views', $hidden, true)) {
576
            $hidden[] = 'views';
577
        }
578
579
        if (! $this->config->settings['NavigationTreeShowFunctions'] && ! in_array('functions', $hidden, true)) {
580
            $hidden[] = 'functions';
581
        }
582
583
        if (! $this->config->settings['NavigationTreeShowProcedures'] && ! in_array('procedures', $hidden, true)) {
584
            $hidden[] = 'procedures';
585
        }
586
587
        if (! $this->config->settings['NavigationTreeShowEvents'] && ! in_array('events', $hidden, true)) {
588
            $hidden[] = 'events';
589
        }
590
591
        $retval = [];
592
        if (! $db->hasChildren()) {
593
            if (! in_array('tables', $hidden, true) && $db->getPresence($userPrivileges, 'tables')) {
594
                $retval['tables'] = new NodeTableContainer($this->config);
595
            }
596
597
            if (! in_array('views', $hidden, true) && $db->getPresence($userPrivileges, 'views')) {
598
                $retval['views'] = new NodeViewContainer($this->config);
599
            }
600
601
            if (! in_array('functions', $hidden, true) && $db->getPresence($userPrivileges, 'functions')) {
602
                $retval['functions'] = new NodeFunctionContainer($this->config);
603
            }
604
605
            if (! in_array('procedures', $hidden, true) && $db->getPresence($userPrivileges, 'procedures')) {
606
                $retval['procedures'] = new NodeProcedureContainer($this->config);
607
            }
608
609
            if (! in_array('events', $hidden, true) && $db->getPresence($userPrivileges, 'events')) {
610
                $retval['events'] = new NodeEventContainer($this->config);
611
            }
612
613
            // Add all new Nodes to the tree
614
            foreach ($retval as $node) {
615
                if ($type === $node->realName) {
616
                    $node->pos2 = $pos2;
617
                }
618
619
                $db->addChild($node);
620
            }
621
        } else {
622
            foreach ($db->children as $node) {
623
                if ($type === $node->realName) {
624
                    $node->pos2 = $pos2;
625
                }
626
627
                $retval[$node->realName] = $node;
628
            }
629
        }
630
631
        return $retval;
632
    }
633
634
    /**
635
     * Recursively groups tree nodes given a separator
636
     *
637
     * @param Node|null $node The node to group or null
638
     *                   to group the whole tree. If
639
     *                   passed as an argument, $node
640
     *                   must be of type CONTAINER
641
     */
642 12
    public function groupTree(Node|null $node = null): void
643
    {
644 12
        if ($node === null) {
645 12
            $node = $this->tree;
646
        }
647
648 12
        $this->groupNode($node);
649 12
        foreach ($node->children as $child) {
650 12
            $this->groupTree($child);
651
        }
652
    }
653
654
    /**
655
     * Recursively groups tree nodes given a separator
656
     *
657
     * @param Node $node The node to group
658
     */
659 12
    public function groupNode(Node $node): void
660
    {
661 12
        if ($node->type !== NodeType::Container || ! $this->config->settings['NavigationTreeEnableExpansion']) {
662 12
            return;
663
        }
664
665 12
        $prefixes = [];
666 12
        if ($node->separatorDepth > 0) {
667 12
            foreach ($node->children as $child) {
668 12
                $prefixPos = false;
669 12
                foreach ($node->separators as $separator) {
670 12
                    $sepPos = mb_strpos($child->name, $separator);
671
                    if (
672 12
                        $sepPos === false
673 4
                        || $sepPos === 0
674 4
                        || ($prefixPos !== false && $sepPos >= $prefixPos)
675 12
                        || $sepPos === mb_strlen($child->name)
676
                    ) {
677 12
                        continue;
678
                    }
679
680 4
                    $prefixPos = $sepPos;
681
                }
682
683 12
                if ($prefixPos !== false) {
684 4
                    $prefix = mb_substr($child->name, 0, $prefixPos);
685 4
                    if (! isset($prefixes[$prefix])) {
686 4
                        $prefixes[$prefix] = 1;
687
                    } else {
688 4
                        $prefixes[$prefix]++;
689
                    }
690
                }
691
692
                //Bug #4375: Check if prefix is the name of a DB, to create a group.
693 12
                foreach ($node->children as $otherChild) {
694 12
                    if (! array_key_exists($otherChild->name, $prefixes)) {
695 12
                        continue;
696
                    }
697
698
                    $prefixes[$otherChild->name]++;
699
                }
700
            }
701
702
            //Check if prefix is the name of a DB, to create a group.
703 12
            foreach ($node->children as $child) {
704 12
                if (! array_key_exists($child->name, $prefixes)) {
705 12
                    continue;
706
                }
707
708
                $prefixes[$child->name]++;
709
            }
710
        }
711
712
        // It is not a group if it has only one item
713 12
        foreach ($prefixes as $key => $value) {
714 4
            if ($value > 1) {
715 4
                continue;
716
            }
717
718
            unset($prefixes[$key]);
719
        }
720
721 12
        $numChildren = count($node->children);
722
723
        // rfe #1634 Don't group if there's only one group and no other items
724 12
        if (count($prefixes) === 1) {
725 4
            $keys = array_keys($prefixes);
726 4
            $key = $keys[0];
727 4
            if ($prefixes[$key] == $numChildren - 1) {
728
                unset($prefixes[$key]);
729
            }
730
        }
731
732 12
        if ($prefixes === []) {
733 12
            return;
734
        }
735
736
        /** @var Node[] $groups */
737 4
        $groups = [];
738 4
        foreach ($prefixes as $key => $value) {
739
            // warn about large groups
740 4
            if ($value > 500 && ! $this->largeGroupWarning) {
741
                ErrorHandler::getInstance()->addUserError(
742
                    __(
743
                        'There are large item groups in navigation panel which '
744
                        . 'may affect the performance. Consider disabling item '
745
                        . 'grouping in the navigation panel.',
746
                    ),
747
                );
748
                $this->largeGroupWarning = true;
749
            }
750
751 4
            $newChildren = [];
752 4
            foreach ($node->separators as $separator) {
753 4
                $separatorLength = strlen($separator);
754
                // FIXME: this could be more efficient
755 4
                foreach ($node->children as $child) {
756 4
                    $keySeparatorLength = mb_strlen((string) $key) + $separatorLength;
757 4
                    $nameSubstring = mb_substr($child->name, 0, $keySeparatorLength);
758
                    if (
759 4
                        ($nameSubstring !== $key . $separator && $child->name !== $key)
760 4
                        || $child->type !== NodeType::Object
761
                    ) {
762
                        continue;
763
                    }
764
765 4
                    $newChild = new $child($this->config, mb_substr($child->name, $keySeparatorLength));
766
                    if (
767 4
                        $child instanceof NodeDatabase
768 4
                        && $newChild instanceof NodeDatabase
769 4
                        && $child->getHiddenCount() > 0
770
                    ) {
771
                        $newChild->setHiddenCount($child->getHiddenCount());
772
                    }
773
774 4
                    $newChild->realName = $child->realName;
775 4
                    $newChild->icon = $child->icon;
776 4
                    $newChild->link = $child->link;
777 4
                    $newChild->pos2 = $child->pos2;
778 4
                    $newChild->pos3 = $child->pos3;
779 4
                    foreach ($child->children as $elm) {
780
                        $newChild->addChild($elm);
781
                    }
782
783 4
                    $newChildren[] = ['node' => $newChild, 'replaces_name' => $child->name];
784
                }
785
            }
786
787 4
            if ($newChildren === []) {
788
                continue;
789
            }
790
791
            // If the current node is a standard group (not NodeTableContainer, etc.)
792
            // and the new group contains all of the current node's children, combine them
793 4
            $class = $node::class;
794 4
            if (count($newChildren) === $numChildren && substr($class, strrpos($class, '\\') + 1) === 'Node') {
795
                $node->name .= $node->separators[0] . htmlspecialchars((string) $key);
796
                $node->realName .= $node->separators[0] . htmlspecialchars((string) $key);
797
                $node->separatorDepth--;
798
                foreach ($newChildren as $newChild) {
799
                    $node->removeChild($newChild['replaces_name']);
800
                    $node->addChild($newChild['node']);
801
                }
802
            } else {
803 4
                $groups[$key] = new Node($this->config, (string) $key, NodeType::Container, true);
804 4
                $groups[$key]->separators = $node->separators;
805 4
                $groups[$key]->separatorDepth = $node->separatorDepth - 1;
806 4
                $groups[$key]->icon = new Icon(
807 4
                    'b_group',
808 4
                    __('Groups'),
809 4
                    $node->icon->route,
810 4
                    array_merge($node->icon->params, ['tbl_group' => $key]),
811 4
                );
812 4
                $groups[$key]->pos2 = $node->pos2;
813 4
                $groups[$key]->pos3 = $node->pos3;
814 4
                if ($node instanceof NodeTableContainer || $node instanceof NodeViewContainer) {
815
                    $groups[$key]->link = new Link(
816
                        '',
817
                        $node->link->route,
818
                        array_merge($node->link->params, ['tbl_group' => $key]),
819
                    );
820
                }
821
822 4
                foreach ($newChildren as $newChild) {
823 4
                    $node->removeChild($newChild['replaces_name']);
824 4
                    $groups[$key]->addChild($newChild['node']);
825
                }
826
            }
827
        }
828
829 4
        foreach ($groups as $group) {
830 4
            if ($group->children === []) {
831
                continue;
832
            }
833
834 4
            $node->addChild($group);
835 4
            $this->groupNode($group);
836 4
            $group->classes = 'navGroup';
837
        }
838
    }
839
840
    /**
841
     * Renders a state of the tree, used in light mode when
842
     * either JavaScript and/or Ajax are disabled
843
     *
844
     * @return string HTML code for the navigation tree
845
     */
846 8
    public function renderState(UserPrivileges $userPrivileges): string
847
    {
848 8
        $this->buildPath($userPrivileges);
849
850 8
        $quickWarp = $this->quickWarp();
851 8
        $fastFilter = $this->fastFilterHtml($userPrivileges, $this->tree);
852 8
        $controls = '';
853 8
        if ($this->config->settings['NavigationTreeEnableExpansion']) {
854 8
            $controls = $this->controls();
855
        }
856
857 8
        $pageSelector = $this->getPageSelector($userPrivileges, $this->tree);
858
859 8
        $this->groupTree();
860 8
        $children = $this->tree->children;
861 8
        usort($children, $this->sortNode(...));
862 8
        $this->setVisibility();
863
864 8
        $nodes = $this->renderNodes($userPrivileges, $children);
865
866 8
        return $this->template->render('navigation/tree/state', [
867 8
            'quick_warp' => $quickWarp,
868 8
            'fast_filter' => $fastFilter,
869 8
            'controls' => $controls,
870 8
            'page_selector' => $pageSelector,
871 8
            'nodes' => $nodes,
872 8
        ]);
873
    }
874
875
    /**
876
     * Renders a part of the tree, used for Ajax requests in light mode
877
     *
878
     * @return string|false HTML code for the navigation tree
879
     */
880 4
    public function renderPath(UserPrivileges $userPrivileges): string|false
881
    {
882 4
        $node = $this->buildPath($userPrivileges);
883 4
        if (! is_bool($node)) {
0 ignored issues
show
introduced by
The condition is_bool($node) is always false.
Loading history...
884 4
            $this->groupTree();
885
886 4
            $listContent = $this->fastFilterHtml($userPrivileges, $node);
887 4
            $listContent .= $this->getPageSelector($userPrivileges, $node);
888 4
            $children = $node->children;
889 4
            usort($children, $this->sortNode(...));
890
891 4
            $listContent .= $this->renderNodes($userPrivileges, $children, false);
892
893 4
            if (! $this->config->settings['ShowDatabasesNavigationAsTree']) {
894
                $parents = $node->parents(true);
895
                $parentName = $parents[0]->realName;
896
            }
897
        }
898
899 4
        $hasSearchClause = $this->searchClause !== '' || $this->searchClause2 !== '';
900 4
        if ($hasSearchClause && ! is_bool($node)) {
901
            $results = 0;
902
            if ($this->searchClause2 !== '') {
903
                $parent = $node->getRealParent();
904
                if ($parent instanceof Node) {
905
                    $results = $parent->getPresence($userPrivileges, $node->realName, $this->searchClause2);
906
                }
907
            } else {
908
                $results = $this->tree->getPresence($userPrivileges, 'databases', $this->searchClause);
909
            }
910
911
            $results = sprintf(
912
                _ngettext(
913
                    '%s result found',
914
                    '%s results found',
915
                    $results,
916
                ),
917
                $results,
918
            );
919
            ResponseRenderer::getInstance()
920
                ->addJSON('results', $results);
921
        }
922
923 4
        if ($node !== false) {
0 ignored issues
show
introduced by
The condition $node !== false is always true.
Loading history...
924 4
            return $this->template->render('navigation/tree/path', [
925 4
                'has_search_results' => $this->searchClause !== '' || $this->searchClause2 !== '',
926 4
                'list_content' => $listContent ?? '',
927 4
                'is_tree' => $this->config->settings['ShowDatabasesNavigationAsTree'],
928 4
                'parent_name' => $parentName ?? '',
929 4
            ]);
930
        }
931
932
        return false;
933
    }
934
935
    /**
936
     * Renders the parameters that are required on the client
937
     * side to know which page(s) we will be requesting data from
938
     *
939
     * @param Node $node The node to create the pagination parameters for
940
     *
941
     * @return array<string, string>
942
     */
943 16
    private function getPaginationParamsHtml(Node $node): array
944
    {
945 16
        $renderDetails = [];
946 16
        $paths = $node->getPaths();
947 16
        if (isset($paths['aPath_clean'][2])) {
948
            $renderDetails['position'] = 'pos2_nav';
949
            $renderDetails['data_name'] = $paths['aPath_clean'][2];
950
            $renderDetails['data_value'] = (string) $node->pos2;
951
        }
952
953 16
        if (isset($paths['aPath_clean'][4])) {
954
            $renderDetails['position'] = 'pos3_nav';
955
            $renderDetails['data_name'] = $paths['aPath_clean'][4];
956
            $renderDetails['data_value'] = (string) $node->pos3;
957
        }
958
959 16
        return $renderDetails;
960
    }
961
962
    /**
963
     * Finds whether given tree matches this tree.
964
     *
965
     * @param mixed[] $tree  Tree to check
966
     * @param mixed[] $paths Paths to check
967
     */
968 16
    private function findTreeMatch(array $tree, array $paths): bool
969
    {
970 16
        $match = false;
971 16
        foreach ($tree as $path) {
972
            $match = true;
973
            foreach ($paths as $key => $part) {
974
                if (! isset($path[$key]) || $part != $path[$key]) {
975
                    $match = false;
976
                    break;
977
                }
978
            }
979
980
            if ($match) {
981
                break;
982
            }
983
        }
984
985 16
        return $match;
986
    }
987
988
    /** @param Node[] $children */
989 16
    private function renderNodes(UserPrivileges $userPrivileges, array $children, bool $hasFirstClass = true): string
990
    {
991 16
        $nodes = '';
992 16
        $lastKey = array_key_last($children);
993 16
        foreach ($children as $i => $child) {
994 16
            if ($i === 0) {
995 16
                $nodes .= $this->renderNode($userPrivileges, $child, $hasFirstClass ? 'first' : '');
996
            } elseif ($i !== $lastKey) {
997
                $nodes .= $this->renderNode($userPrivileges, $child);
998
            } else {
999
                $nodes .= $this->renderNode($userPrivileges, $child, 'last');
1000
            }
1001
        }
1002
1003 16
        return $nodes;
1004
    }
1005
1006
    /**
1007
     * Renders a single node or a branch of the tree
1008
     *
1009
     * @param Node   $node  The node to render
1010
     * @param string $class An additional class for the list item
1011
     *
1012
     * @return string HTML code for the tree node or branch
1013
     */
1014 16
    private function renderNode(UserPrivileges $userPrivileges, Node $node, string $class = ''): string
1015
    {
1016 16
        $controlButtons = '';
1017 16
        $paths = $node->getPaths();
1018 16
        $nodeIsContainer = $node->type === NodeType::Container;
1019 16
        $liClasses = '';
1020 16
        $iconLinks = [];
1021 16
        $textLink = [];
1022
1023
        // Whether to show the node in the tree (true for all nodes but root)
1024
        // If false, the node's children will still be shown, but as children of the node's parent
1025 16
        $showNode = $node->hasSiblings() || $node->parents(false, true) !== [];
1026
1027
        // Don't show the 'Tables' node under each database unless it has 'Views', etc. as a sibling
1028 16
        if ($node instanceof NodeTableContainer && ! $node->hasSiblings()) {
1029
            $showNode = false;
1030
        }
1031
1032 16
        if ($showNode) {
1033 16
            $response = ResponseRenderer::getInstance();
1034 16
            if ($nodeIsContainer && $node->children === [] && ! $response->isAjax()) {
1035
                return '';
1036
            }
1037
1038 16
            $liClasses = trim($class . ' ' . $node->classes);
1039 16
            $sterile = ['events', 'triggers', 'functions', 'procedures', 'views', 'columns', 'indexes'];
1040 16
            $parentName = '';
1041 16
            $parents = $node->parents(false, true);
1042 16
            if ($parents !== []) {
1043 16
                $parentName = $parents[0]->realName;
1044
            }
1045
1046
            // if node name itself is in sterile, then allow
1047 16
            $nodeIsGroup = $node->isGroup
1048 16
                || (! in_array($parentName, $sterile, true) && ! $node->isNew)
1049 16
                || (in_array($node->realName, $sterile, true) && $node->children !== []);
1050 16
            if ($nodeIsGroup) {
1051 16
                $match = $this->findTreeMatch($this->vPath, $paths['vPath_clean']);
1052 16
                $linkClasses = $node->getCssClasses($match);
1053 16
                if ($this->config->settings['ShowDatabasesNavigationAsTree'] || $parentName !== 'root') {
1054 16
                    $nodeIcon = $node->getIcon($match);
1055
                }
1056
            }
1057
1058 16
            $paginationParams = $this->getPaginationParamsHtml($node);
1059
1060 16
            if (! $node->isGroup) {
1061 16
                $args = [];
1062 16
                $parents = $node->parents(true);
1063 16
                foreach ($parents as $parent) {
1064 16
                    if ($parent->urlParamName === null) {
1065
                        continue;
1066
                    }
1067
1068 16
                    $args[$parent->urlParamName] = $parent->realName;
1069
                }
1070
1071 16
                $iconLinks[] = $node->icon->withDifferentParams($args);
1072
1073 16
                if ($node instanceof NodeTable && $node->secondIcon !== null) {
1074
                    $iconLinks[] = $node->secondIcon->withDifferentParams($args);
1075
                }
1076
1077 16
                $textLink = $node->link->withDifferentParams($args);
1078
            }
1079
1080 16
            $controlButtons .= $node->getHtmlForControlButtons($this->relationParameters->navigationItemsHidingFeature);
1081 16
            $wrap = true;
1082
        } else {
1083
            $node->visible = true;
1084
            $wrap = false;
1085
            $paginationParams = $this->getPaginationParamsHtml($node);
1086
        }
1087
1088 16
        $children = $node->children;
1089 16
        usort($children, $this->sortNode(...));
1090 16
        $buffer = '';
1091 16
        $extraClass = '';
1092 16
        $lastKey = array_key_last($children);
1093 16
        foreach ($children as $i => $child) {
1094 4
            if ($i === $lastKey) {
1095 4
                $extraClass = ' last';
1096
            }
1097
1098 4
            $buffer .= $this->renderNode($userPrivileges, $child, $child->classes . $extraClass);
1099
        }
1100
1101 16
        if ($buffer !== '') {
1102 4
            $recursiveHtml = $this->fastFilterHtml($userPrivileges, $node);
1103 4
            $recursiveHtml .= $this->getPageSelector($userPrivileges, $node);
1104 4
            $recursiveHtml .= $buffer;
1105
        }
1106
1107 16
        return $this->template->render('navigation/tree/node', [
1108 16
            'node' => $node,
1109 16
            'displayName' => $node instanceof NodeColumn ? $node->displayName : $node->realName,
1110 16
            'class' => $class,
1111 16
            'show_node' => $showNode,
1112 16
            'has_siblings' => $node->hasSiblings(),
1113 16
            'li_classes' => $liClasses,
1114 16
            'control_buttons' => $controlButtons,
1115 16
            'node_is_container' => $nodeIsContainer,
1116 16
            'has_second_icon' => isset($node->secondIcon),
1117 16
            'recursive' => ['html' => $recursiveHtml ?? '', 'has_wrapper' => $wrap, 'is_hidden' => ! $node->visible],
1118 16
            'icon_links' => $iconLinks,
1119 16
            'text_link' => $textLink,
1120 16
            'pagination_params' => $paginationParams,
1121 16
            'node_is_group' => $nodeIsGroup ?? false,
1122 16
            'link_classes' => $linkClasses ?? '',
1123 16
            'paths' => ['a_path' => $paths['aPath'], 'v_path' => $paths['vPath'], 'pos' => $this->pos],
1124 16
            'node_icon' => $nodeIcon ?? '',
1125 16
        ]);
1126
    }
1127
1128
    /**
1129
     * Renders a database select box like the pre-4.0 navigation panel
1130
     *
1131
     * @return string HTML code
1132
     */
1133 4
    public function renderDbSelect(UserPrivileges $userPrivileges): string
1134
    {
1135 4
        $this->buildPath($userPrivileges);
1136
1137 4
        $quickWarp = $this->quickWarp();
1138
1139 4
        $this->tree->isGroup = false;
1140
1141
        // Provide for pagination in database select
1142 4
        $listNavigator = Generator::getListNavigator(
1143 4
            $this->tree->getPresence($userPrivileges, 'databases'),
1144 4
            $this->pos,
1145 4
            ['server' => Current::$server],
1146 4
            Url::getFromRoute('/navigation'),
1147 4
            'frame_navigation',
1148 4
            $this->config->settings['FirstLevelNavigationItems'],
1149 4
            'pos',
1150 4
            ['dbselector'],
1151 4
        );
1152
1153 4
        $children = $this->tree->children;
1154 4
        $selected = Current::$database;
1155 4
        $options = [];
1156 4
        foreach ($children as $node) {
1157 4
            if ($node->isNew) {
1158
                continue;
1159
            }
1160
1161 4
            $paths = $node->getPaths();
1162
1163 4
            $options[] = [
1164 4
                'title' => $node->link->title,
1165 4
                'name' => $node->realName,
1166 4
                'data' => ['apath' => $paths['aPath'], 'vpath' => $paths['vPath'], 'pos' => $this->pos],
1167 4
                'isSelected' => $node->realName === $selected,
1168 4
            ];
1169
        }
1170
1171 4
        $children = $this->tree->children;
1172 4
        usort($children, $this->sortNode(...));
1173 4
        $this->setVisibility();
1174
1175 4
        $nodes = $this->renderNodes($userPrivileges, $children);
1176
1177 4
        $databaseUrl = Url::getFromRoute(Config::getInstance()->settings['DefaultTabDatabase']);
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::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

1177
        $databaseUrl = Url::getFromRoute(/** @scrutinizer ignore-deprecated */ Config::getInstance()->settings['DefaultTabDatabase']);

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...
1178
1179 4
        return $this->template->render('navigation/tree/database_select', [
1180 4
            'quick_warp' => $quickWarp,
1181 4
            'list_navigator' => $listNavigator,
1182 4
            'server' => Current::$server,
1183 4
            'options' => $options,
1184 4
            'nodes' => $nodes,
1185 4
            'database_url' => $databaseUrl,
1186 4
        ]);
1187
    }
1188
1189
    /**
1190
     * Makes some nodes visible based on the which node is active
1191
     */
1192 12
    private function setVisibility(): void
1193
    {
1194 12
        foreach ($this->vPath as $path) {
1195
            $node = $this->tree;
1196
            foreach ($path as $value) {
1197
                $child = $node->getChild($value);
1198
                if ($child === null) {
1199
                    continue;
1200
                }
1201
1202
                $child->visible = true;
1203
                $node = $child;
1204
            }
1205
        }
1206
    }
1207
1208
    /**
1209
     * Generates the HTML code for displaying the fast filter for tables
1210
     *
1211
     * @param Node $node The node for which to generate the fast filter html
1212
     *
1213
     * @return string LI element used for the fast filter
1214
     */
1215 12
    private function fastFilterHtml(UserPrivileges $userPrivileges, Node $node): string
1216
    {
1217 12
        $filterDbMin = $this->config->settings['NavigationTreeDisplayDbFilterMinimum'];
1218 12
        $filterItemMin = $this->config->settings['NavigationTreeDisplayItemFilterMinimum'];
1219 12
        $urlParams = [];
1220
1221 12
        $isRootNode = $node === $this->tree && $this->tree->getPresence($userPrivileges) >= $filterDbMin;
0 ignored issues
show
introduced by
The condition $node === $this->tree is always false.
Loading history...
1222 12
        if ($isRootNode) {
0 ignored issues
show
introduced by
The condition $isRootNode is always false.
Loading history...
1223
            $urlParams = ['pos' => 0];
1224
        } else {
1225 12
            $nodeIsContainer = $node->type === NodeType::Container;
1226
1227 12
            $nodeIsSpecial = in_array($node->realName, self::SPECIAL_NODE_NAMES, true);
1228
1229 12
            $realParent = $node->getRealParent();
1230
            if (
1231 12
                $nodeIsContainer && $nodeIsSpecial
1232 12
                && $realParent instanceof Node
1233 12
                && $realParent->getPresence($userPrivileges, $node->realName) >= $filterItemMin
1234
            ) {
1235
                $paths = $node->getPaths();
1236
                $urlParams = [
1237
                    'pos' => $this->pos,
1238
                    'aPath' => $paths['aPath'],
1239
                    'vPath' => $paths['vPath'],
1240
                    'pos2_name' => $node->realName,
1241
                    'pos2_value' => 0,
1242
                ];
1243
            }
1244
        }
1245
1246 12
        return $this->template->render('navigation/tree/fast_filter', [
1247 12
            'url_params' => $urlParams,
1248 12
            'is_root_node' => $isRootNode,
1249 12
        ]);
1250
    }
1251
1252
    /**
1253
     * Creates the code for displaying the controls
1254
     * at the top of the navigation tree
1255
     *
1256
     * @return string HTML code for the controls
1257
     */
1258 8
    private function controls(): string
1259
    {
1260 8
        $collapseAll = Generator::getNavigationLink(
1261 8
            '#',
1262 8
            __('Collapse all'),
1263 8
            's_collapseall',
1264 8
            'pma_navigation_collapse',
1265 8
        );
1266 8
        $syncImage = 's_unlink';
1267 8
        $title = __('Link with main panel');
1268 8
        if ($this->config->settings['NavigationLinkWithMainPanel']) {
1269 8
            $syncImage = 's_link';
1270 8
            $title = __('Unlink from main panel');
1271
        }
1272
1273 8
        $unlink = Generator::getNavigationLink('#', $title, $syncImage, 'pma_navigation_sync');
1274
1275 8
        return $this->template->render('navigation/tree/controls', [
1276 8
            'collapse_all' => $collapseAll,
1277 8
            'unlink' => $unlink,
1278 8
        ]);
1279
    }
1280
1281
    /**
1282
     * Generates the HTML code for displaying the list pagination
1283
     *
1284
     * @param Node $node The node for whose children the page
1285
     *                   selector will be created
1286
     */
1287 12
    private function getPageSelector(UserPrivileges $userPrivileges, Node $node): string
1288
    {
1289 12
        $retval = '';
1290 12
        if ($node === $this->tree) {
0 ignored issues
show
introduced by
The condition $node === $this->tree is always false.
Loading history...
1291 12
            $retval .= Generator::getListNavigator(
1292 12
                $this->tree->getPresence($userPrivileges, 'databases', $this->searchClause),
1293 12
                $this->pos,
1294 12
                ['server' => Current::$server],
1295 12
                Url::getFromRoute('/navigation'),
1296 12
                'frame_navigation',
1297 12
                $this->config->settings['FirstLevelNavigationItems'],
1298 12
                'pos',
1299 12
                ['dbselector'],
1300 12
            );
1301 4
        } elseif ($node->type === NodeType::Container && ! $node->isGroup) {
1302
            $paths = $node->getPaths();
1303
            $level = isset($paths['aPath_clean'][4]) ? 3 : 2;
1304
            $urlParams = [
1305
                'aPath' => $paths['aPath'],
1306
                'vPath' => $paths['vPath'],
1307
                'pos' => $this->pos,
1308
                'server' => Current::$server,
1309
                'pos2_name' => $paths['aPath_clean'][2],
1310
            ];
1311
            if ($level === 3) {
1312
                $pos = $node->pos3;
1313
                $urlParams['pos2_value'] = $node->pos2;
1314
                $urlParams['pos3_name'] = $paths['aPath_clean'][4];
1315
            } else {
1316
                $pos = $node->pos2;
1317
            }
1318
1319
            /** @var Node $realParent */
1320
            $realParent = $node->getRealParent();
1321
            $num = $realParent->getPresence($userPrivileges, $node->realName, $this->searchClause2);
1322
            $retval .= Generator::getListNavigator(
1323
                $num,
1324
                $pos,
1325
                $urlParams,
1326
                Url::getFromRoute('/navigation'),
1327
                'frame_navigation',
1328
                $this->config->settings['MaxNavigationItems'],
1329
                'pos' . $level . '_value',
1330
            );
1331
        }
1332
1333 12
        return $retval;
1334
    }
1335
1336
    /**
1337
     * Called by usort() for sorting the nodes in a container
1338
     *
1339
     * @param Node $a The first element used in the comparison
1340
     * @param Node $b The second element used in the comparison
1341
     *
1342
     * @return int See strnatcmp() and strcmp()
1343
     */
1344 4
    private function sortNode(Node $a, Node $b): int
0 ignored issues
show
Unused Code introduced by
The method sortNode() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
1345
    {
1346 4
        if ($a->isNew) {
1347
            return -1;
1348
        }
1349
1350 4
        if ($b->isNew) {
1351
            return 1;
1352
        }
1353
1354 4
        if ($this->config->settings['NaturalOrder']) {
1355 4
            return strnatcasecmp($a->name, $b->name);
1356
        }
1357
1358
        return strcasecmp($a->name, $b->name);
1359
    }
1360
1361
    /**
1362
     * Display quick warp links, contain Recents and Favorites
1363
     *
1364
     * @return string HTML code
1365
     */
1366 12
    private function quickWarp(): string
1367
    {
1368 12
        $recent = '';
1369 12
        if ($this->config->settings['NumRecentTables'] > 0) {
1370 12
            $recent = RecentFavoriteTables::getInstance(TableType::Recent)->getHtmlList();
1371
        }
1372
1373 12
        $favorite = '';
1374 12
        if ($this->config->settings['NumFavoriteTables'] > 0) {
1375 12
            $favorite = RecentFavoriteTables::getInstance(TableType::Favorite)->getHtmlList();
1376
        }
1377
1378 12
        return $this->template->render('navigation/tree/quick_warp', [
1379 12
            'recent' => $recent,
1380 12
            'favorite' => $favorite,
1381 12
        ]);
1382
    }
1383
}
1384