Passed
Push — master ( 9102b9...990f80 )
by Maurício
07:29
created

NavigationTree::addTableContainers()   B

Complexity

Conditions 9
Paths 11

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 22
nc 11
nop 4
dl 0
loc 37
rs 8.0555
c 0
b 0
f 0
1
<?php
2
/* vim: set expandtab sw=4 ts=4 sts=4: */
3
/**
4
 * Functionality for the navigation tree
5
 *
6
 * @package PhpMyAdmin-Navigation
7
 */
8
declare(strict_types=1);
9
10
namespace PhpMyAdmin\Navigation;
11
12
use PhpMyAdmin\CheckUserPrivileges;
13
use PhpMyAdmin\DatabaseInterface;
14
use PhpMyAdmin\Navigation\Nodes\Node;
15
use PhpMyAdmin\Navigation\Nodes\NodeDatabase;
16
use PhpMyAdmin\Navigation\Nodes\NodeTable;
17
use PhpMyAdmin\Navigation\Nodes\NodeTableContainer;
18
use PhpMyAdmin\Navigation\Nodes\NodeViewContainer;
19
use PhpMyAdmin\RecentFavoriteTable;
20
use PhpMyAdmin\Response;
21
use PhpMyAdmin\Template;
22
use PhpMyAdmin\Util;
23
use PhpMyAdmin\Url;
24
25
/**
26
 * Displays a collapsible of database objects in the navigation frame
27
 *
28
 * @package PhpMyAdmin-Navigation
29
 */
30
class NavigationTree
31
{
32
    /**
33
     * @var Node Reference to the root node of the tree
34
     */
35
    private $tree;
36
    /**
37
     * @var array The actual paths to all expanded nodes in the tree
38
     *            This does not include nodes created after the grouping
39
     *            of nodes has been performed
40
     */
41
    private $aPath = [];
42
    /**
43
     * @var array The virtual paths to all expanded nodes in the tree
44
     *            This includes nodes created after the grouping of
45
     *            nodes has been performed
46
     */
47
    private $vPath = [];
48
    /**
49
     * @var int Position in the list of databases,
50
     *          used for pagination
51
     */
52
    private $pos;
53
    /**
54
     * @var array The names of the type of items that are being paginated on
55
     *            the second level of the navigation tree. These may be
56
     *            tables, views, functions, procedures or events.
57
     */
58
    private $pos2Name = [];
59
    /**
60
     * @var array The positions of nodes in the lists of tables, views,
61
     *            routines or events used for pagination
62
     */
63
    private $pos2Value = [];
64
    /**
65
     * @var array The names of the type of items that are being paginated
66
     *            on the second level of the navigation tree.
67
     *            These may be columns or indexes
68
     */
69
    private $pos3Name = [];
70
    /**
71
     * @var array The positions of nodes in the lists of columns or indexes
72
     *            used for pagination
73
     */
74
    private $pos3Value = [];
75
    /**
76
     * @var string The search clause to use in SQL queries for
77
     *             fetching databases
78
     *             Used by the asynchronous fast filter
79
     */
80
    private $searchClause = '';
81
    /**
82
     * @var string The search clause to use in SQL queries for
83
     *             fetching nodes
84
     *             Used by the asynchronous fast filter
85
     */
86
    private $searchClause2 = '';
87
    /**
88
     * @var bool Whether a warning was raised for large item groups
89
     *           which can affect performance.
90
     */
91
    private $largeGroupWarning = false;
92
93
    /**
94
     * @var Template
95
     */
96
    private $template;
97
98
    /**
99
     * @var DatabaseInterface
100
     */
101
    private $dbi;
102
103
    /**
104
     * NavigationTree constructor.
105
     * @param Template          $template Template instance
106
     * @param DatabaseInterface $dbi      DatabaseInterface instance
107
     */
108
    public function __construct($template, $dbi)
109
    {
110
        $this->template = $template;
111
        $this->dbi = $dbi;
112
113
        $checkUserPrivileges = new CheckUserPrivileges($this->dbi);
114
        $checkUserPrivileges->getPrivileges();
115
116
        // Save the position at which we are in the database list
117
        if (isset($_POST['pos'])) {
118
            $this->pos = (int) $_POST['pos'];
119
        } elseif (isset($_GET['pos'])) {
120
            $this->pos = (int) $_GET['pos'];
121
        }
122
        if (! isset($this->pos)) {
123
            $this->pos = $this->getNavigationDbPos();
124
        }
125
        // Get the active node
126
        if (isset($_REQUEST['aPath'])) {
127
            $this->aPath[0] = $this->parsePath($_REQUEST['aPath']);
128
            $this->pos2Name[0] = $_REQUEST['pos2_name'];
129
            $this->pos2Value[0] = $_REQUEST['pos2_value'];
130
            if (isset($_REQUEST['pos3_name'])) {
131
                $this->pos3Name[0] = $_REQUEST['pos3_name'];
132
                $this->pos3Value[0] = $_REQUEST['pos3_value'];
133
            }
134
        } else {
135
            if (isset($_POST['n0_aPath'])) {
136
                $count = 0;
137
                while (isset($_POST['n' . $count . '_aPath'])) {
138
                    $this->aPath[$count] = $this->parsePath(
139
                        $_POST['n' . $count . '_aPath']
140
                    );
141
                    $index = 'n' . $count . '_pos2_';
142
                    $this->pos2Name[$count] = $_POST[$index . 'name'];
143
                    $this->pos2Value[$count] = $_POST[$index . 'value'];
144
                    $index = 'n' . $count . '_pos3_';
145
                    if (isset($_POST[$index])) {
146
                        $this->pos3Name[$count] = $_POST[$index . 'name'];
147
                        $this->pos3Value[$count] = $_POST[$index . 'value'];
148
                    }
149
                    $count++;
150
                }
151
            }
152
        }
153
        if (isset($_REQUEST['vPath'])) {
154
            $this->vPath[0] = $this->parsePath($_REQUEST['vPath']);
155
        } else {
156
            if (isset($_POST['n0_vPath'])) {
157
                $count = 0;
158
                while (isset($_POST['n' . $count . '_vPath'])) {
159
                    $this->vPath[$count] = $this->parsePath(
160
                        $_POST['n' . $count . '_vPath']
161
                    );
162
                    $count++;
163
                }
164
            }
165
        }
166
        if (isset($_REQUEST['searchClause'])) {
167
            $this->searchClause = $_REQUEST['searchClause'];
168
        }
169
        if (isset($_REQUEST['searchClause2'])) {
170
            $this->searchClause2 = $_REQUEST['searchClause2'];
171
        }
172
        // Initialise the tree by creating a root node
173
        $node = NodeFactory::getInstance('NodeDatabaseContainer', 'root');
174
        $this->tree = $node;
175
        if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']
176
            && $GLOBALS['cfg']['ShowDatabasesNavigationAsTree']
177
        ) {
178
            $this->tree->separator = $GLOBALS['cfg']['NavigationTreeDbSeparator'];
179
            $this->tree->separatorDepth = 10000;
180
        }
181
    }
182
183
    /**
184
     * Returns the database position for the page selector
185
     *
186
     * @return int
187
     */
188
    private function getNavigationDbPos()
189
    {
190
        $retval = 0;
191
192
        if (strlen($GLOBALS['db']) == 0) {
193
            return $retval;
194
        }
195
196
        /*
197
         * @todo describe a scenario where this code is executed
198
         */
199
        if (! $GLOBALS['cfg']['Server']['DisableIS']) {
200
            $dbSeparator = $this->dbi->escapeString(
201
                $GLOBALS['cfg']['NavigationTreeDbSeparator']
202
            );
203
            $query = "SELECT (COUNT(DB_first_level) DIV %d) * %d ";
204
            $query .= "from ( ";
205
            $query .= " SELECT distinct SUBSTRING_INDEX(SCHEMA_NAME, ";
206
            $query .= " '%s', 1) ";
207
            $query .= " DB_first_level ";
208
            $query .= " FROM INFORMATION_SCHEMA.SCHEMATA ";
209
            $query .= " WHERE `SCHEMA_NAME` < '%s' ";
210
            $query .= ") t ";
211
212
            $retval = $this->dbi->fetchValue(
213
                sprintf(
214
                    $query,
215
                    (int) $GLOBALS['cfg']['FirstLevelNavigationItems'],
216
                    (int) $GLOBALS['cfg']['FirstLevelNavigationItems'],
217
                    $dbSeparator,
218
                    $this->dbi->escapeString($GLOBALS['db'])
219
                )
220
            );
221
222
            return $retval;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $retval could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
223
        }
224
225
        $prefixMap = [];
226
        if ($GLOBALS['dbs_to_test'] === false) {
227
            $handle = $this->dbi->tryQuery("SHOW DATABASES");
228
            if ($handle !== false) {
229
                while ($arr = $this->dbi->fetchArray($handle)) {
230
                    if (strcasecmp($arr[0], $GLOBALS['db']) >= 0) {
231
                        break;
232
                    }
233
234
                    $prefix = strstr(
235
                        $arr[0],
236
                        $GLOBALS['cfg']['NavigationTreeDbSeparator'],
237
                        true
238
                    );
239
                    if ($prefix === false) {
240
                        $prefix = $arr[0];
241
                    }
242
                    $prefixMap[$prefix] = 1;
243
                }
244
            }
245
        } else {
246
            $databases = [];
247
            foreach ($GLOBALS['dbs_to_test'] as $db) {
248
                $query = "SHOW DATABASES LIKE '" . $db . "'";
249
                $handle = $this->dbi->tryQuery($query);
250
                if ($handle === false) {
251
                    continue;
252
                }
253
                while ($arr = $this->dbi->fetchArray($handle)) {
254
                    $databases[] = $arr[0];
255
                }
256
            }
257
            sort($databases);
258
            foreach ($databases as $database) {
259
                if (strcasecmp($database, $GLOBALS['db']) >= 0) {
260
                    break;
261
                }
262
263
                $prefix = strstr(
264
                    $database,
265
                    $GLOBALS['cfg']['NavigationTreeDbSeparator'],
266
                    true
267
                );
268
                if ($prefix === false) {
269
                    $prefix = $database;
270
                }
271
                $prefixMap[$prefix] = 1;
272
            }
273
        }
274
275
        $navItems = (int) $GLOBALS['cfg']['FirstLevelNavigationItems'];
276
        $retval = floor(count($prefixMap) / $navItems) * $navItems;
277
278
        return $retval;
279
    }
280
281
    /**
282
     * Converts an encoded path to a node in string format to an array
283
     *
284
     * @param string $string The path to parse
285
     *
286
     * @return array
287
     */
288
    private function parsePath($string)
289
    {
290
        $path = explode('.', $string);
291
        foreach ($path as $key => $value) {
292
            $path[$key] = base64_decode($value);
293
        }
294
295
        return $path;
296
    }
297
298
    /**
299
     * Generates the tree structure so that it can be rendered later
300
     *
301
     * @return Node|false The active node or false in case of failure
302
     */
303
    private function buildPath()
304
    {
305
        $retval = $this->tree;
306
307
        // Add all databases unconditionally
308
        $data = $this->tree->getData(
309
            'databases',
310
            $this->pos,
311
            $this->searchClause
312
        );
313
        $hiddenCounts = $this->tree->getNavigationHidingData();
314
        foreach ($data as $db) {
315
            $node = NodeFactory::getInstance('NodeDatabase', $db);
316
            if (isset($hiddenCounts[$db])) {
317
                $node->setHiddenCount($hiddenCounts[$db]);
318
            }
319
            $this->tree->addChild($node);
320
        }
321
322
        // Whether build other parts of the tree depends
323
        // on whether we have any paths in $this->_aPath
324
        foreach ($this->aPath as $key => $path) {
325
            $retval = $this->buildPathPart(
326
                $path,
327
                $this->pos2Name[$key],
328
                $this->pos2Value[$key],
329
                isset($this->pos3Name[$key]) ? $this->pos3Name[$key] : '',
330
                isset($this->pos3Value[$key]) ? $this->pos3Value[$key] : ''
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? $this->pos3Value[$key] : '' can also be of type string; however, parameter $pos3 of PhpMyAdmin\Navigation\Na...onTree::buildPathPart() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

330
                /** @scrutinizer ignore-type */ isset($this->pos3Value[$key]) ? $this->pos3Value[$key] : ''
Loading history...
331
            );
332
        }
333
334
        return $retval;
335
    }
336
337
    /**
338
     * Builds a branch of the tree
339
     *
340
     * @param array  $path  A paths pointing to the branch
341
     *                      of the tree that needs to be built
342
     * @param string $type2 The type of item being paginated on
343
     *                      the second level of the tree
344
     * @param int    $pos2  The position for the pagination of
345
     *                      the branch at the second level of the tree
346
     * @param string $type3 The type of item being paginated on
347
     *                      the third level of the tree
348
     * @param int    $pos3  The position for the pagination of
349
     *                      the branch at the third level of the tree
350
     *
351
     * @return Node|false The active node or false in case of failure
352
     */
353
    private function buildPathPart(array $path, $type2, $pos2, $type3, $pos3)
354
    {
355
        if (empty($pos2)) {
356
            $pos2 = 0;
357
        }
358
        if (empty($pos3)) {
359
            $pos3 = 0;
360
        }
361
362
        $retval = true;
363
        if (count($path) <= 1) {
364
            return $retval;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $retval returns the type true which is incompatible with the documented return type PhpMyAdmin\Navigation\Nodes\Node|false.
Loading history...
365
        }
366
367
        array_shift($path); // remove 'root'
368
        /** @var NodeDatabase $db */
369
        $db = $this->tree->getChild($path[0]);
370
        $retval = $db;
371
372
        if ($db === false) {
0 ignored issues
show
introduced by
The condition $db === false is always false.
Loading history...
373
            return false;
374
        }
375
376
        $containers = $this->addDbContainers($db, $type2, $pos2);
377
378
        array_shift($path); // remove db
379
380
        if ((count($path) <= 0 || ! array_key_exists($path[0], $containers))
381
            && count($containers) != 1
382
        ) {
383
            return $retval;
384
        }
385
386
        if (count($containers) == 1) {
387
            $container = array_shift($containers);
388
        } else {
389
            $container = $db->getChild($path[0], true);
390
            if ($container === false) {
391
                return false;
392
            }
393
        }
394
        $retval = $container;
395
396
        if (count($container->children) <= 1) {
397
            $dbData = $db->getData(
398
                $container->realName,
399
                $pos2,
400
                $this->searchClause2
401
            );
402
            foreach ($dbData as $item) {
403
                switch ($container->realName) {
404
                    case 'events':
405
                        $node = NodeFactory::getInstance(
406
                            'NodeEvent',
407
                            $item
408
                        );
409
                        break;
410
                    case 'functions':
411
                        $node = NodeFactory::getInstance(
412
                            'NodeFunction',
413
                            $item
414
                        );
415
                        break;
416
                    case 'procedures':
417
                        $node = NodeFactory::getInstance(
418
                            'NodeProcedure',
419
                            $item
420
                        );
421
                        break;
422
                    case 'tables':
423
                        $node = NodeFactory::getInstance(
424
                            'NodeTable',
425
                            $item
426
                        );
427
                        break;
428
                    case 'views':
429
                        $node = NodeFactory::getInstance(
430
                            'NodeView',
431
                            $item
432
                        );
433
                        break;
434
                    default:
435
                        break;
436
                }
437
                if (isset($node)) {
438
                    if ($type2 == $container->realName) {
439
                        $node->pos2 = $pos2;
440
                    }
441
                    $container->addChild($node);
442
                }
443
            }
444
        }
445
        if (count($path) > 1 && $path[0] != 'tables') {
446
            $retval = false;
447
448
            return $retval;
449
        }
450
451
        array_shift($path); // remove container
452
        if (count($path) <= 0) {
453
            return $retval;
454
        }
455
456
        /** @var NodeTable $table */
457
        $table = $container->getChild($path[0], true);
458
        if ($table === false) {
0 ignored issues
show
introduced by
The condition $table === false is always false.
Loading history...
459
            if (! $db->getPresence('tables', $path[0])) {
460
                return false;
461
            }
462
463
            $node = NodeFactory::getInstance(
464
                'NodeTable',
465
                $path[0]
466
            );
467
            if ($type2 == $container->realName) {
468
                $node->pos2 = $pos2;
469
            }
470
            $container->addChild($node);
471
            $table = $container->getChild($path[0], true);
472
        }
473
        $retval = $table;
474
        $containers = $this->addTableContainers(
475
            $table,
476
            $pos2,
477
            $type3,
478
            $pos3
479
        );
480
        array_shift($path); // remove table
481
        if (count($path) <= 0
482
            || ! array_key_exists($path[0], $containers)
483
        ) {
484
            return $retval;
485
        }
486
487
        $container = $table->getChild($path[0], true);
488
        $retval = $container;
489
        $tableData = $table->getData(
490
            $container->realName,
491
            $pos3
492
        );
493
        foreach ($tableData as $item) {
494
            switch ($container->realName) {
495
                case 'indexes':
496
                    $node = NodeFactory::getInstance(
497
                        'NodeIndex',
498
                        $item
499
                    );
500
                    break;
501
                case 'columns':
502
                    $node = NodeFactory::getInstance(
503
                        'NodeColumn',
504
                        $item
505
                    );
506
                    break;
507
                case 'triggers':
508
                    $node = NodeFactory::getInstance(
509
                        'NodeTrigger',
510
                        $item
511
                    );
512
                    break;
513
                default:
514
                    break;
515
            }
516
            if (isset($node)) {
517
                $node->pos2 = $container->parent->pos2;
518
                if ($type3 == $container->realName) {
519
                    $node->pos3 = $pos3;
520
                }
521
                $container->addChild($node);
522
            }
523
        }
524
525
        return $retval;
526
    }
527
528
    /**
529
     * Adds containers to a node that is a table
530
     *
531
     * References to existing children are returned
532
     * if this function is called twice on the same node
533
     *
534
     * @param NodeTable $table The table node, new containers will be
535
     *                         attached to this node
536
     * @param int       $pos2  The position for the pagination of
537
     *                         the branch at the second level of the tree
538
     * @param string    $type3 The type of item being paginated on
539
     *                         the third level of the tree
540
     * @param int       $pos3  The position for the pagination of
541
     *                         the branch at the third level of the tree
542
     *
543
     * @return array An array of new nodes
544
     */
545
    private function addTableContainers($table, $pos2, $type3, $pos3)
546
    {
547
        $retval = [];
548
        if ($table->hasChildren(true) == 0) {
549
            if ($table->getPresence('columns')) {
550
                $retval['columns'] = NodeFactory::getInstance(
551
                    'NodeColumnContainer'
552
                );
553
            }
554
            if ($table->getPresence('indexes')) {
555
                $retval['indexes'] = NodeFactory::getInstance(
556
                    'NodeIndexContainer'
557
                );
558
            }
559
            if ($table->getPresence('triggers')) {
560
                $retval['triggers'] = NodeFactory::getInstance(
561
                    'NodeTriggerContainer'
562
                );
563
            }
564
            // Add all new Nodes to the tree
565
            foreach ($retval as $node) {
566
                $node->pos2 = $pos2;
567
                if ($type3 == $node->realName) {
568
                    $node->pos3 = $pos3;
569
                }
570
                $table->addChild($node);
571
            }
572
        } else {
573
            foreach ($table->children as $node) {
574
                if ($type3 == $node->realName) {
575
                    $node->pos3 = $pos3;
576
                }
577
                $retval[$node->realName] = $node;
578
            }
579
        }
580
581
        return $retval;
582
    }
583
584
    /**
585
     * Adds containers to a node that is a database
586
     *
587
     * References to existing children are returned
588
     * if this function is called twice on the same node
589
     *
590
     * @param NodeDatabase $db   The database node, new containers will be
591
     *                           attached to this node
592
     * @param string       $type The type of item being paginated on
593
     *                           the second level of the tree
594
     * @param int          $pos2 The position for the pagination of
595
     *                           the branch at the second level of the tree
596
     *
597
     * @return array An array of new nodes
598
     */
599
    private function addDbContainers($db, $type, $pos2)
600
    {
601
        // Get items to hide
602
        $hidden = $db->getHiddenItems('group');
603
        if (! $GLOBALS['cfg']['NavigationTreeShowTables']
604
            && ! in_array('tables', $hidden)
605
        ) {
606
            $hidden[] = 'tables';
607
        }
608
        if (! $GLOBALS['cfg']['NavigationTreeShowViews']
609
            && ! in_array('views', $hidden)
610
        ) {
611
            $hidden[] = 'views';
612
        }
613
        if (! $GLOBALS['cfg']['NavigationTreeShowFunctions']
614
            && ! in_array('functions', $hidden)
615
        ) {
616
            $hidden[] = 'functions';
617
        }
618
        if (! $GLOBALS['cfg']['NavigationTreeShowProcedures']
619
            && ! in_array('procedures', $hidden)
620
        ) {
621
            $hidden[] = 'procedures';
622
        }
623
        if (! $GLOBALS['cfg']['NavigationTreeShowEvents']
624
            && ! in_array('events', $hidden)
625
        ) {
626
            $hidden[] = 'events';
627
        }
628
629
        $retval = [];
630
        if ($db->hasChildren(true) == 0) {
631
            if (! in_array('tables', $hidden) && $db->getPresence('tables')) {
632
                $retval['tables'] = NodeFactory::getInstance(
633
                    'NodeTableContainer'
634
                );
635
            }
636
            if (! in_array('views', $hidden) && $db->getPresence('views')) {
637
                $retval['views'] = NodeFactory::getInstance(
638
                    'NodeViewContainer'
639
                );
640
            }
641
            if (! in_array('functions', $hidden) && $db->getPresence('functions')) {
642
                $retval['functions'] = NodeFactory::getInstance(
643
                    'NodeFunctionContainer'
644
                );
645
            }
646
            if (! in_array('procedures', $hidden) && $db->getPresence('procedures')) {
647
                $retval['procedures'] = NodeFactory::getInstance(
648
                    'NodeProcedureContainer'
649
                );
650
            }
651
            if (! in_array('events', $hidden) && $db->getPresence('events')) {
652
                $retval['events'] = NodeFactory::getInstance(
653
                    'NodeEventContainer'
654
                );
655
            }
656
            // Add all new Nodes to the tree
657
            foreach ($retval as $node) {
658
                if ($type == $node->realName) {
659
                    $node->pos2 = $pos2;
660
                }
661
                $db->addChild($node);
662
            }
663
        } else {
664
            foreach ($db->children as $node) {
665
                if ($type == $node->realName) {
666
                    $node->pos2 = $pos2;
667
                }
668
                $retval[$node->realName] = $node;
669
            }
670
        }
671
672
        return $retval;
673
    }
674
675
    /**
676
     * Recursively groups tree nodes given a separator
677
     *
678
     * @param mixed $node The node to group or null
679
     *                    to group the whole tree. If
680
     *                    passed as an argument, $node
681
     *                    must be of type CONTAINER
682
     *
683
     * @return void
684
     */
685
    public function groupTree($node = null)
686
    {
687
        if (! isset($node)) {
688
            $node = $this->tree;
689
        }
690
        $this->groupNode($node);
691
        foreach ($node->children as $child) {
692
            $this->groupTree($child);
693
        }
694
    }
695
696
    /**
697
     * Recursively groups tree nodes given a separator
698
     *
699
     * @param Node $node The node to group
700
     *
701
     * @return void
702
     */
703
    public function groupNode($node)
704
    {
705
        if ($node->type != Node::CONTAINER
706
            || ! $GLOBALS['cfg']['NavigationTreeEnableExpansion']
707
        ) {
708
            return;
709
        }
710
711
        $separators = [];
712
        if (is_array($node->separator)) {
713
            $separators = $node->separator;
714
        } else {
715
            if (strlen($node->separator)) {
716
                $separators[] = $node->separator;
717
            }
718
        }
719
        $prefixes = [];
720
        if ($node->separatorDepth > 0) {
721
            foreach ($node->children as $child) {
722
                $prefixPos = false;
723
                foreach ($separators as $separator) {
724
                    $sepPos = mb_strpos((string) $child->name, $separator);
725
                    if ($sepPos != false
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $sepPos of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
726
                        && $sepPos != mb_strlen($child->name)
727
                        && $sepPos != 0
728
                        && ($prefixPos == false || $sepPos < $prefixPos)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
729
                    ) {
730
                        $prefixPos = $sepPos;
731
                    }
732
                }
733
                if ($prefixPos !== false) {
734
                    $prefix = mb_substr($child->name, 0, $prefixPos);
735
                    if (! isset($prefixes[$prefix])) {
736
                        $prefixes[$prefix] = 1;
737
                    } else {
738
                        $prefixes[$prefix]++;
739
                    }
740
                }
741
                //Bug #4375: Check if prefix is the name of a DB, to create a group.
742
                foreach ($node->children as $otherChild) {
743
                    if (array_key_exists($otherChild->name, $prefixes)) {
744
                        $prefixes[$otherChild->name]++;
745
                    }
746
                }
747
            }
748
            //Check if prefix is the name of a DB, to create a group.
749
            foreach ($node->children as $child) {
750
                if (array_key_exists($child->name, $prefixes)) {
751
                    $prefixes[$child->name]++;
752
                }
753
            }
754
        }
755
        // It is not a group if it has only one item
756
        foreach ($prefixes as $key => $value) {
757
            if ($value == 1) {
758
                unset($prefixes[$key]);
759
            }
760
        }
761
        // rfe #1634 Don't group if there's only one group and no other items
762
        if (count($prefixes) == 1) {
763
            $keys = array_keys($prefixes);
764
            $key = $keys[0];
765
            if ($prefixes[$key] == count($node->children) - 1) {
766
                unset($prefixes[$key]);
767
            }
768
        }
769
        if (count($prefixes)) {
770
            /** @var Node[] $groups */
771
            $groups = [];
772
            foreach ($prefixes as $key => $value) {
773
                // warn about large groups
774
                if ($value > 500 && ! $this->largeGroupWarning) {
775
                    trigger_error(
776
                        __(
777
                            'There are large item groups in navigation panel which '
778
                            . 'may affect the performance. Consider disabling item '
779
                            . 'grouping in the navigation panel.'
780
                        ),
781
                        E_USER_WARNING
782
                    );
783
                    $this->largeGroupWarning = true;
784
                }
785
786
                $groups[$key] = new Node(
787
                    htmlspecialchars($key),
788
                    Node::CONTAINER,
789
                    true
790
                );
791
                $groups[$key]->separator = $node->separator;
792
                $groups[$key]->separatorDepth = $node->separatorDepth - 1;
793
                $groups[$key]->icon = Util::getImage(
794
                    'b_group',
795
                    __('Groups')
796
                );
797
                $groups[$key]->pos2 = $node->pos2;
798
                $groups[$key]->pos3 = $node->pos3;
799
                if ($node instanceof NodeTableContainer
800
                    || $node instanceof NodeViewContainer
801
                ) {
802
                    $tblGroup = '&amp;tbl_group=' . urlencode($key);
803
                    $groups[$key]->links = [
804
                        'text' => $node->links['text'] . $tblGroup,
805
                        'icon' => $node->links['icon'] . $tblGroup,
806
                    ];
807
                }
808
                $node->addChild($groups[$key]);
809
                foreach ($separators as $separator) {
810
                    $separatorLength = strlen($separator);
811
                    // FIXME: this could be more efficient
812
                    foreach ($node->children as $child) {
813
                        $keySeparatorLength = mb_strlen((string) $key) + $separatorLength;
814
                        $nameSubstring = mb_substr(
815
                            (string) $child->name,
816
                            0,
817
                            $keySeparatorLength
818
                        );
819
                        if (($nameSubstring != $key . $separator
820
                            && $child->name != $key)
821
                            || $child->type != Node::OBJECT
822
                        ) {
823
                            continue;
824
                        }
825
                        $class = get_class($child);
826
                        $className = substr($class, strrpos($class, '\\') + 1);
827
                        unset($class);
828
                        $newChild = NodeFactory::getInstance(
829
                            $className,
830
                            mb_substr(
831
                                $child->name,
832
                                $keySeparatorLength
833
                            )
834
                        );
835
836
                        if ($newChild instanceof NodeDatabase
837
                            && $child->getHiddenCount() > 0
0 ignored issues
show
Bug introduced by
The method getHiddenCount() does not exist on PhpMyAdmin\Navigation\Nodes\Node. It seems like you code against a sub-type of PhpMyAdmin\Navigation\Nodes\Node such as PhpMyAdmin\Navigation\Nodes\NodeDatabase. ( Ignorable by Annotation )

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

837
                            && $child->/** @scrutinizer ignore-call */ getHiddenCount() > 0
Loading history...
838
                        ) {
839
                            $newChild->setHiddenCount($child->getHiddenCount());
840
                        }
841
842
                        $newChild->realName = $child->realName;
843
                        $newChild->icon = $child->icon;
844
                        $newChild->links = $child->links;
845
                        $newChild->pos2 = $child->pos2;
846
                        $newChild->pos3 = $child->pos3;
847
                        $groups[$key]->addChild($newChild);
848
                        foreach ($child->children as $elm) {
849
                            $newChild->addChild($elm);
850
                        }
851
                        $node->removeChild($child->name);
852
                    }
853
                }
854
            }
855
            foreach ($prefixes as $key => $value) {
856
                $this->groupNode($groups[$key]);
857
                $groups[$key]->classes = "navGroup";
858
            }
859
        }
860
    }
861
862
    /**
863
     * Renders a state of the tree, used in light mode when
864
     * either JavaScript and/or Ajax are disabled
865
     *
866
     * @return string HTML code for the navigation tree
867
     */
868
    public function renderState()
869
    {
870
        $this->buildPath();
871
872
        $quickWarp = $this->quickWarp();
873
        $fastFilter = $this->fastFilterHtml($this->tree);
874
        $controls = '';
875
        if ($GLOBALS['cfg']['NavigationTreeEnableExpansion']) {
876
            $controls = $this->controls();
877
        }
878
        $pageSelector = $this->getPageSelector($this->tree);
879
880
        $this->groupTree();
881
        $children = $this->tree->children;
882
        usort($children, [
883
            NavigationTree::class,
884
            'sortNode',
885
        ]);
886
        $this->setVisibility();
887
888
        $nodes = '';
889
        for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) {
890
            if ($i == 0) {
891
                $nodes .= $this->renderNode($children[0], true, 'first');
892
            } else {
893
                if ($i + 1 != $nbChildren) {
894
                    $nodes .= $this->renderNode($children[$i], true);
895
                } else {
896
                    $nodes .= $this->renderNode($children[$i], true, 'last');
897
                }
898
            }
899
        }
900
901
        return $this->template->render('navigation/tree/state', [
902
            'quick_warp' => $quickWarp,
903
            'fast_filter' => $fastFilter,
904
            'controls' => $controls,
905
            'page_selector' => $pageSelector,
906
            'nodes' => $nodes,
907
        ]);
908
    }
909
910
    /**
911
     * Renders a part of the tree, used for Ajax requests in light mode
912
     *
913
     * @return string|false HTML code for the navigation tree
914
     */
915
    public function renderPath()
916
    {
917
        $node = $this->buildPath();
918
        if ($node !== false) {
919
            $this->groupTree();
920
921
            $listContent = $this->fastFilterHtml($node);
922
            $listContent .= $this->getPageSelector($node);
923
            $children = $node->children;
924
            usort($children, [
925
                NavigationTree::class,
926
                'sortNode',
927
            ]);
928
929
            for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) {
930
                if ($i + 1 != $nbChildren) {
931
                    $listContent .= $this->renderNode($children[$i], true);
932
                } else {
933
                    $listContent .= $this->renderNode($children[$i], true, 'last');
934
                }
935
            }
936
937
            if (! $GLOBALS['cfg']['ShowDatabasesNavigationAsTree']) {
938
                $parents = $node->parents(true);
939
                $parentName = $parents[0]->realName;
940
            }
941
        }
942
943
        if (! empty($this->searchClause) || ! empty($this->searchClause2)) {
944
            $results = 0;
945
            if (! empty($this->searchClause2)) {
946
                if (is_object($node->realParent())) {
947
                    $results = $node->realParent()
948
                        ->getPresence(
949
                            $node->realName,
950
                            $this->searchClause2
951
                        );
952
                }
953
            } else {
954
                $results = $this->tree->getPresence(
955
                    'databases',
956
                    $this->searchClause
957
                );
958
            }
959
            $results = sprintf(
960
                _ngettext(
961
                    '%s result found',
962
                    '%s results found',
963
                    $results
964
                ),
965
                $results
966
            );
967
            Response::getInstance()
968
                ->addJSON(
969
                    'results',
970
                    $results
971
                );
972
        }
973
974
        if ($node !== false) {
975
            return $this->template->render('navigation/tree/path', [
976
                'has_search_results' => ! empty($this->searchClause) || ! empty($this->searchClause2),
977
                'list_content' => $listContent ?? '',
978
                'is_tree' => $GLOBALS['cfg']['ShowDatabasesNavigationAsTree'],
979
                'parent_name' => $parentName ?? '',
980
            ]);
981
        }
982
        return false;
983
    }
984
985
    /**
986
     * Renders the parameters that are required on the client
987
     * side to know which page(s) we will be requesting data from
988
     *
989
     * @param Node $node The node to create the pagination parameters for
990
     *
991
     * @return string
992
     */
993
    private function getPaginationParamsHtml($node)
994
    {
995
        $retval = '';
996
        $paths = $node->getPaths();
997
        if (isset($paths['aPath_clean'][2])) {
998
            $retval .= "<span class='hide pos2_name'>";
999
            $retval .= $paths['aPath_clean'][2];
1000
            $retval .= "</span>";
1001
            $retval .= "<span class='hide pos2_value'>";
1002
            $retval .= htmlspecialchars((string) $node->pos2);
1003
            $retval .= "</span>";
1004
        }
1005
        if (isset($paths['aPath_clean'][4])) {
1006
            $retval .= "<span class='hide pos3_name'>";
1007
            $retval .= $paths['aPath_clean'][4];
1008
            $retval .= "</span>";
1009
            $retval .= "<span class='hide pos3_value'>";
1010
            $retval .= htmlspecialchars((string) $node->pos3);
1011
            $retval .= "</span>";
1012
        }
1013
1014
        return $retval;
1015
    }
1016
1017
    /**
1018
     * Finds whether given tree matches this tree.
1019
     *
1020
     * @param array $tree  Tree to check
1021
     * @param array $paths Paths to check
1022
     *
1023
     * @return boolean
1024
     */
1025
    private function findTreeMatch(array $tree, array $paths)
1026
    {
1027
        $match = false;
1028
        foreach ($tree as $path) {
1029
            $match = true;
1030
            foreach ($paths as $key => $part) {
1031
                if (! isset($path[$key]) || $part != $path[$key]) {
1032
                    $match = false;
1033
                    break;
1034
                }
1035
            }
1036
            if ($match) {
1037
                break;
1038
            }
1039
        }
1040
1041
        return $match;
1042
    }
1043
1044
    /**
1045
     * Renders a single node or a branch of the tree
1046
     *
1047
     * @param Node   $node      The node to render
1048
     * @param bool   $recursive Bool: Whether to render a single node or a branch
1049
     * @param string $class     An additional class for the list item
1050
     *
1051
     * @return string HTML code for the tree node or branch
1052
     */
1053
    private function renderNode($node, $recursive, $class = '')
1054
    {
1055
        $retval = '';
1056
        $paths = $node->getPaths();
1057
        if ($node->hasSiblings()
1058
            || $node->realParent() === false
1059
        ) {
1060
            $response = Response::getInstance();
1061
            if ($node->type == Node::CONTAINER
1062
                && count($node->children) == 0
1063
                && ! $response->isAjax()
1064
            ) {
1065
                return '';
1066
            }
1067
            $retval .= '<li class="' . trim($class . ' ' . $node->classes) . '">';
1068
            $sterile = [
1069
                'events',
1070
                'triggers',
1071
                'functions',
1072
                'procedures',
1073
                'views',
1074
                'columns',
1075
                'indexes',
1076
            ];
1077
            $parentName = '';
1078
            $parents = $node->parents(false, true);
1079
            if (count($parents)) {
1080
                $parentName = $parents[0]->realName;
1081
            }
1082
            // if node name itself is in sterile, then allow
1083
            if ($node->isGroup
1084
                || (! in_array($parentName, $sterile) && ! $node->isNew)
1085
                || in_array($node->realName, $sterile)
1086
            ) {
1087
                $retval .= "<div class='block'>";
1088
                $iClass = '';
1089
                if ($class == 'first') {
1090
                    $iClass = " class='first'";
1091
                }
1092
                $retval .= "<i$iClass></i>";
1093
                if (strpos($class, 'last') === false) {
1094
                    $retval .= "<b></b>";
1095
                }
1096
1097
                $match = $this->findTreeMatch(
1098
                    $this->vPath,
1099
                    $paths['vPath_clean']
1100
                );
1101
1102
                $retval .= '<a class="' . $node->getCssClasses($match) . '"';
1103
                $retval .= " href='#'>";
1104
                $retval .= "<span class='hide aPath'>";
1105
                $retval .= $paths['aPath'];
1106
                $retval .= "</span>";
1107
                $retval .= "<span class='hide vPath'>";
1108
                $retval .= $paths['vPath'];
1109
                $retval .= "</span>";
1110
                $retval .= "<span class='hide pos'>";
1111
                $retval .= $this->pos;
1112
                $retval .= "</span>";
1113
                $retval .= $this->getPaginationParamsHtml($node);
1114
                if ($GLOBALS['cfg']['ShowDatabasesNavigationAsTree']
1115
                    || $parentName != 'root'
1116
                ) {
1117
                    $retval .= $node->getIcon($match);
1118
                }
1119
1120
                $retval .= "</a>";
1121
                $retval .= "</div>";
1122
            } else {
1123
                $retval .= "<div class='block'>";
1124
                $iClass = '';
1125
                if ($class == 'first') {
1126
                    $iClass = " class='first'";
1127
                }
1128
                $retval .= "<i$iClass></i>";
1129
                $retval .= $this->getPaginationParamsHtml($node);
1130
                $retval .= "</div>";
1131
            }
1132
1133
            $linkClass = '';
1134
            $haveAjax = [
1135
                'functions',
1136
                'procedures',
1137
                'events',
1138
                'triggers',
1139
                'indexes',
1140
            ];
1141
            $parent = $node->parents(false, true);
1142
            $isNewView = $parent[0]->realName == 'views' && $node->isNew === true;
1143
            if ($parent[0]->type == Node::CONTAINER
1144
                && (in_array($parent[0]->realName, $haveAjax) || $isNewView)
1145
            ) {
1146
                $linkClass = ' ajax';
1147
            }
1148
1149
            if ($node->type == Node::CONTAINER) {
1150
                $retval .= "<i>";
1151
            }
1152
1153
            $divClass = '';
1154
1155
            if (isset($node->links['icon']) && ! empty($node->links['icon'])) {
1156
                $iconLinks = $node->links['icon'];
1157
                $icons = $node->icon;
1158
                if (! is_array($iconLinks)) {
1159
                    $iconLinks = [$iconLinks];
1160
                    $icons = [$icons];
1161
                }
1162
1163
                if (count($icons) > 1) {
0 ignored issues
show
Bug introduced by
It seems like $icons can also be of type string; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

1163
                if (count(/** @scrutinizer ignore-type */ $icons) > 1) {
Loading history...
1164
                    $divClass = 'double';
1165
                }
1166
            }
1167
1168
            $retval .= "<div class='block " . $divClass . "'>";
1169
1170
            if (isset($node->links['icon']) && ! empty($node->links['icon'])) {
1171
                $args = [];
1172
                foreach ($node->parents(true) as $parent) {
1173
                    $args[] = urlencode($parent->realName);
1174
                }
1175
1176
                foreach ($icons as $key => $icon) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $icons does not seem to be defined for all execution paths leading up to this point.
Loading history...
1177
                    $link = vsprintf($iconLinks[$key], $args);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $iconLinks does not seem to be defined for all execution paths leading up to this point.
Loading history...
1178
                    if ($linkClass != '') {
1179
                        $retval .= "<a class='$linkClass' href='$link'>";
1180
                        $retval .= "{$icon}</a>";
1181
                    } else {
1182
                        $retval .= "<a href='$link'>{$icon}</a>";
1183
                    }
1184
                }
1185
            } else {
1186
                $retval .= "<u>{$node->icon}</u>";
1187
            }
1188
            $retval .= "</div>";
1189
1190
            if (isset($node->links['text'])) {
1191
                $args = [];
1192
                foreach ($node->parents(true) as $parent) {
1193
                    $args[] = urlencode($parent->realName);
1194
                }
1195
                $link = vsprintf($node->links['text'], $args);
1196
                $title = isset($node->links['title']) ? $node->links['title'] : '';
1197
                if ($node->type == Node::CONTAINER) {
1198
                    $retval .= "&nbsp;<a class='hover_show_full' href='$link'>";
1199
                    $retval .= htmlspecialchars($node->name);
1200
                    $retval .= "</a>";
1201
                } else {
1202
                    $retval .= "<a class='hover_show_full$linkClass' href='$link'";
1203
                    $retval .= " title='$title'>";
1204
                    $retval .= htmlspecialchars($node->displayName ?? $node->realName);
1205
                    $retval .= "</a>";
1206
                }
1207
            } else {
1208
                $retval .= "&nbsp;{$node->name}";
1209
            }
1210
            $retval .= $node->getHtmlForControlButtons();
1211
            if ($node->type == Node::CONTAINER) {
1212
                $retval .= "</i>";
1213
            }
1214
            $retval .= '<div class="clearfloat"></div>';
1215
            $wrap = true;
1216
        } else {
1217
            $node->visible = true;
1218
            $wrap = false;
1219
            $retval .= $this->getPaginationParamsHtml($node);
1220
        }
1221
1222
        if ($recursive) {
1223
            $hide = '';
1224
            if (! $node->visible) {
1225
                $hide = " style='display: none;'";
1226
            }
1227
            $children = $node->children;
1228
            usort(
1229
                $children,
1230
                [
1231
                    NavigationTree::class,
1232
                    'sortNode',
1233
                ]
1234
            );
1235
            $buffer = '';
1236
            $extraClass = '';
1237
            for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) {
1238
                if ($i + 1 == $nbChildren) {
1239
                    $extraClass = ' last';
1240
                }
1241
                $buffer .= $this->renderNode(
1242
                    $children[$i],
1243
                    true,
1244
                    $children[$i]->classes . $extraClass
1245
                );
1246
            }
1247
            if (! empty($buffer)) {
1248
                if ($wrap) {
1249
                    $retval .= "<div$hide class='list_container'><ul>";
1250
                }
1251
                $retval .= $this->fastFilterHtml($node);
1252
                $retval .= $this->getPageSelector($node);
1253
                $retval .= $buffer;
1254
                if ($wrap) {
1255
                    $retval .= "</ul></div>";
1256
                }
1257
            }
1258
        }
1259
        if ($node->hasSiblings()) {
1260
            $retval .= "</li>";
1261
        }
1262
1263
        return $retval;
1264
    }
1265
1266
    /**
1267
     * Renders a database select box like the pre-4.0 navigation panel
1268
     *
1269
     * @return string HTML code
1270
     */
1271
    public function renderDbSelect()
1272
    {
1273
        $this->buildPath();
1274
1275
        $quickWarp = $this->quickWarp();
1276
1277
        $this->tree->isGroup = false;
1278
1279
        // Provide for pagination in database select
1280
        $listNavigator = Util::getListNavigator(
1281
            $this->tree->getPresence('databases', ''),
1282
            $this->pos,
1283
            ['server' => $GLOBALS['server']],
1284
            'navigation.php',
1285
            'frame_navigation',
1286
            $GLOBALS['cfg']['FirstLevelNavigationItems'],
1287
            'pos',
1288
            ['dbselector']
1289
        );
1290
1291
        $children = $this->tree->children;
1292
        $selected = $GLOBALS['db'];
1293
        $options = '';
1294
        foreach ($children as $node) {
1295
            if ($node->isNew) {
1296
                continue;
1297
            }
1298
            $paths = $node->getPaths();
1299
            if (isset($node->links['text'])) {
1300
                $title = isset($node->links['title']) ? '' : $node->links['title'];
1301
                $options .= '<option value="'
1302
                    . htmlspecialchars($node->realName) . '"'
1303
                    . ' title="' . htmlspecialchars($title) . '"'
1304
                    . ' apath="' . $paths['aPath'] . '"'
1305
                    . ' vpath="' . $paths['vPath'] . '"'
1306
                    . ' pos="' . $this->pos . '"';
1307
                if ($node->realName == $selected) {
1308
                    $options .= ' selected';
1309
                }
1310
                $options .= '>' . htmlspecialchars($node->realName);
1311
                $options .= '</option>';
1312
            }
1313
        }
1314
1315
        $children = $this->tree->children;
1316
        usort($children, [
1317
            NavigationTree::class,
1318
            'sortNode',
1319
        ]);
1320
        $this->setVisibility();
1321
1322
        $nodes = '';
1323
        for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) {
1324
            if ($i == 0) {
1325
                $nodes .= $this->renderNode($children[0], true, 'first');
1326
            } else {
1327
                if ($i + 1 != $nbChildren) {
1328
                    $nodes .= $this->renderNode($children[$i], true);
1329
                } else {
1330
                    $nodes .= $this->renderNode($children[$i], true, 'last');
1331
                }
1332
            }
1333
        }
1334
1335
        return $this->template->render('navigation/tree/database_select', [
1336
            'quick_warp' => $quickWarp,
1337
            'list_navigator' => $listNavigator,
1338
            'server' => $GLOBALS['server'],
1339
            'options' => $options,
1340
            'nodes' => $nodes,
1341
        ]);
1342
    }
1343
1344
    /**
1345
     * Makes some nodes visible based on the which node is active
1346
     *
1347
     * @return void
1348
     */
1349
    private function setVisibility()
1350
    {
1351
        foreach ($this->vPath as $path) {
1352
            $node = $this->tree;
1353
            foreach ($path as $value) {
1354
                $child = $node->getChild($value);
1355
                if ($child !== false) {
1356
                    $child->visible = true;
1357
                    $node = $child;
1358
                }
1359
            }
1360
        }
1361
    }
1362
1363
    /**
1364
     * Generates the HTML code for displaying the fast filter for tables
1365
     *
1366
     * @param Node $node The node for which to generate the fast filter html
1367
     *
1368
     * @return string LI element used for the fast filter
1369
     */
1370
    private function fastFilterHtml($node)
1371
    {
1372
        $retval = '';
1373
        $filterDbMin
1374
            = (int) $GLOBALS['cfg']['NavigationTreeDisplayDbFilterMinimum'];
1375
        $filterItemMin
1376
            = (int) $GLOBALS['cfg']['NavigationTreeDisplayItemFilterMinimum'];
1377
        if ($node === $this->tree
1378
            && $this->tree->getPresence() >= $filterDbMin
1379
        ) {
1380
            $urlParams = [
1381
                'pos' => 0,
1382
            ];
1383
            $retval .= '<li class="fast_filter db_fast_filter">';
1384
            $retval .= '<form class="ajax fast_filter">';
1385
            $retval .= Url::getHiddenInputs($urlParams);
1386
            $retval .= '<input class="searchClause" type="text"';
1387
            $retval .= ' name="searchClause" accesskey="q"';
1388
            $retval .= " placeholder='"
1389
                . __("Type to filter these, Enter to search all");
1390
            $retval .= "'>";
1391
            $retval .= '<span title="' . __('Clear fast filter') . '">X</span>';
1392
            $retval .= "</form>";
1393
            $retval .= "</li>";
1394
1395
            return $retval;
1396
        }
1397
1398
        if (($node->type == Node::CONTAINER
1399
            && ($node->realName == 'tables'
1400
            || $node->realName == 'views'
1401
            || $node->realName == 'functions'
1402
            || $node->realName == 'procedures'
1403
            || $node->realName == 'events'))
1404
            && method_exists($node->realParent(), 'getPresence')
1405
            && $node->realParent()->getPresence($node->realName) >= $filterItemMin
1406
        ) {
1407
            $paths = $node->getPaths();
1408
            $urlParams = [
1409
                'pos'        => $this->pos,
1410
                'aPath'      => $paths['aPath'],
1411
                'vPath'      => $paths['vPath'],
1412
                'pos2_name'  => $node->realName,
1413
                'pos2_value' => 0,
1414
            ];
1415
            $retval .= "<li class='fast_filter'>";
1416
            $retval .= "<form class='ajax fast_filter'>";
1417
            $retval .= Url::getHiddenFields($urlParams);
1418
            $retval .= "<input class='searchClause' type='text'";
1419
            $retval .= " name='searchClause2'";
1420
            $retval .= " placeholder='"
1421
                . __("Type to filter these, Enter to search all") . "'>";
1422
            $retval .= "<span title='" . __('Clear fast filter') . "'>X</span>";
1423
            $retval .= "</form>";
1424
            $retval .= "</li>";
1425
        }
1426
1427
        return $retval;
1428
    }
1429
1430
    /**
1431
     * Creates the code for displaying the controls
1432
     * at the top of the navigation tree
1433
     *
1434
     * @return string HTML code for the controls
1435
     */
1436
    private function controls()
1437
    {
1438
        // always iconic
1439
        $showIcon = true;
1440
        $showText = false;
1441
1442
        $retval = '<!-- CONTROLS START -->';
1443
        $retval .= '<li id="navigation_controls_outer">';
1444
        $retval .= '<div id="navigation_controls">';
1445
        $retval .= Util::getNavigationLink(
1446
            '#',
1447
            $showText,
1448
            __('Collapse all'),
1449
            $showIcon,
1450
            's_collapseall',
1451
            'pma_navigation_collapse'
1452
        );
1453
        $syncImage = 's_unlink';
1454
        $title = __('Link with main panel');
1455
        if ($GLOBALS['cfg']['NavigationLinkWithMainPanel']) {
1456
            $syncImage = 's_link';
1457
            $title = __('Unlink from main panel');
1458
        }
1459
        $retval .= Util::getNavigationLink(
1460
            '#',
1461
            $showText,
1462
            $title,
1463
            $showIcon,
1464
            $syncImage,
1465
            'pma_navigation_sync'
1466
        );
1467
        $retval .= '</div>';
1468
        $retval .= '</li>';
1469
        $retval .= '<!-- CONTROLS ENDS -->';
1470
1471
        return $retval;
1472
    }
1473
1474
    /**
1475
     * Generates the HTML code for displaying the list pagination
1476
     *
1477
     * @param Node $node The node for whose children the page
1478
     *                   selector will be created
1479
     *
1480
     * @return string
1481
     */
1482
    private function getPageSelector($node)
1483
    {
1484
        $retval = '';
1485
        if ($node === $this->tree) {
1486
            $retval .= Util::getListNavigator(
1487
                $this->tree->getPresence('databases', $this->searchClause),
1488
                $this->pos,
1489
                ['server' => $GLOBALS['server']],
1490
                'navigation.php',
1491
                'frame_navigation',
1492
                $GLOBALS['cfg']['FirstLevelNavigationItems'],
1493
                'pos',
1494
                ['dbselector']
1495
            );
1496
        } else {
1497
            if ($node->type == Node::CONTAINER && ! $node->isGroup) {
1498
                $paths = $node->getPaths();
1499
1500
                $level = isset($paths['aPath_clean'][4]) ? 3 : 2;
1501
                $urlParams = [
1502
                    'aPath'     => $paths['aPath'],
1503
                    'vPath'     => $paths['vPath'],
1504
                    'pos'       => $this->pos,
1505
                    'server'    => $GLOBALS['server'],
1506
                    'pos2_name' => $paths['aPath_clean'][2],
1507
                ];
1508
                if ($level == 3) {
1509
                    $pos = $node->pos3;
1510
                    $urlParams['pos2_value'] = $node->pos2;
1511
                    $urlParams['pos3_name'] = $paths['aPath_clean'][4];
1512
                } else {
1513
                    $pos = $node->pos2;
1514
                }
1515
                $num = $node->realParent()
1516
                    ->getPresence(
1517
                        $node->realName,
1518
                        $this->searchClause2
1519
                    );
1520
                $retval .= Util::getListNavigator(
1521
                    $num,
1522
                    $pos,
1523
                    $urlParams,
1524
                    'navigation.php',
1525
                    'frame_navigation',
1526
                    $GLOBALS['cfg']['MaxNavigationItems'],
1527
                    'pos' . $level . '_value'
1528
                );
1529
            }
1530
        }
1531
1532
        return $retval;
1533
    }
1534
1535
    /**
1536
     * Called by usort() for sorting the nodes in a container
1537
     *
1538
     * @param Node $a The first element used in the comparison
1539
     * @param Node $b The second element used in the comparison
1540
     *
1541
     * @return int See strnatcmp() and strcmp()
1542
     */
1543
    public static function sortNode($a, $b)
1544
    {
1545
        if ($a->isNew) {
1546
            return -1;
1547
        }
1548
1549
        if ($b->isNew) {
1550
            return 1;
1551
        }
1552
1553
        if ($GLOBALS['cfg']['NaturalOrder']) {
1554
            return strnatcasecmp($a->name, $b->name);
1555
        }
1556
1557
        return strcasecmp($a->name, $b->name);
1558
    }
1559
1560
    /**
1561
     * Display quick warp links, contain Recents and Favorites
1562
     *
1563
     * @return string HTML code
1564
     */
1565
    private function quickWarp()
1566
    {
1567
        $retval = '<div class="pma_quick_warp">';
1568
        if ($GLOBALS['cfg']['NumRecentTables'] > 0) {
1569
            $retval .= RecentFavoriteTable::getInstance('recent')
1570
                ->getHtml();
1571
        }
1572
        if ($GLOBALS['cfg']['NumFavoriteTables'] > 0) {
1573
            $retval .= RecentFavoriteTable::getInstance('favorite')
1574
                ->getHtml();
1575
        }
1576
        $retval .= '<div class="clearfloat"></div>';
1577
        $retval .= '</div>';
1578
1579
        return $retval;
1580
    }
1581
}
1582