NavigationTree::addDbContainers()   F
last analyzed

Complexity

Conditions 26
Paths 1120

Size

Total Lines 65
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 702

Importance

Changes 0
Metric Value
eloc 33
c 0
b 0
f 0
dl 0
loc 65
rs 0
ccs 0
cts 33
cp 0
cc 26
nc 1120
nop 4
crap 702

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