Menu::getDbTabs()   F
last analyzed

Complexity

Conditions 15
Paths 1152

Size

Total Lines 108
Code Lines 77

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 59
CRAP Score 18.2524

Importance

Changes 0
Metric Value
eloc 77
nc 1152
nop 0
dl 0
loc 108
ccs 59
cts 78
cp 0.7564
c 0
b 0
f 0
cc 15
crap 18.2524
rs 1.9018

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
declare(strict_types=1);
4
5
namespace PhpMyAdmin;
6
7
use PhpMyAdmin\ConfigStorage\Relation;
8
use PhpMyAdmin\ConfigStorage\UserGroupLevel;
9
use PhpMyAdmin\Dbal\ConnectionType;
10
use PhpMyAdmin\Query\Utilities;
11
use PhpMyAdmin\Routing\Routing;
12
use PhpMyAdmin\Tracking\Tracker;
13
use PhpMyAdmin\Utils\SessionCache;
14
15
use function __;
16
use function array_intersect_key;
17
use function count;
18
use function in_array;
19
use function mb_strpos;
20
use function mb_substr;
21
use function preg_replace;
22
use function str_contains;
23
24
/**
25
 * Generates and renders the top menu
26
 */
27
class Menu
28
{
29
    /**
30
     * Creates a new instance of Menu
31
     *
32
     * @param string $db    Database name
33
     * @param string $table Table name
34
     */
35 16
    public function __construct(
36
        private readonly DatabaseInterface $dbi,
37
        private readonly Template $template,
38
        private readonly Config $config,
39
        private readonly Relation $relation,
40
        private string $db,
41
        private string $table,
42
    ) {
43 16
    }
44
45
    /**
46
     * Returns the menu and the breadcrumbs as a string
47
     */
48 16
    public function getDisplay(): string
49
    {
50 16
        $breadcrumbs = $this->getBreadcrumbs();
51 16
        $menu = $this->getMenu();
52
53 16
        return $this->template->render('menu/main', [
54 16
            'server' => $breadcrumbs['server'],
55 16
            'database' => $breadcrumbs['database'],
56 16
            'table' => $breadcrumbs['table'],
57 16
            'tabs' => $menu['tabs'],
58 16
            'url_params' => $menu['url_params'],
59 16
        ]);
60
    }
61
62
    /** @return array{tabs: mixed[], url_params: mixed[]} */
63 16
    private function getMenu(): array
64
    {
65 16
        $urlParams = [];
66
67
        // The URL will not work if the table is defined without a database
68 16
        if ($this->table !== '' && $this->db !== '') {
69 8
            $tabs = $this->getTableTabs();
70 8
            $urlParams['db'] = $this->db;
71 8
            $urlParams['table'] = $this->table;
72 8
            $level = UserGroupLevel::Table;
73 8
        } elseif ($this->db !== '') {
74 4
            $tabs = $this->getDbTabs();
75 4
            $urlParams['db'] = $this->db;
76 4
            $level = UserGroupLevel::Database;
77
        } else {
78 4
            $tabs = $this->getServerTabs();
79 4
            $level = UserGroupLevel::Server;
80
        }
81
82 16
        $allowedTabs = $this->getAllowedTabs($level);
83
        // Filter out any tabs that are not allowed
84 16
        $tabs = array_intersect_key($tabs, $allowedTabs);
85
86 16
        return ['tabs' => $tabs, 'url_params' => $urlParams];
87
    }
88
89
    /**
90
     * Returns a list of allowed tabs for the current user for the given level
91
     *
92
     * @return mixed[] list of allowed tabs
93
     */
94 16
    private function getAllowedTabs(UserGroupLevel $level): array
95
    {
96 16
        $cacheKey = 'menu-levels-' . $level->value;
97 16
        if (SessionCache::has($cacheKey)) {
98
            return SessionCache::get($cacheKey);
99
        }
100
101 16
        $allowedTabs = Util::getMenuTabList($level);
102 16
        $configurableMenusFeature = $this->relation->getRelationParameters()->configurableMenusFeature;
103 16
        if ($configurableMenusFeature !== null) {
104
            $groupTable = Util::backquote($configurableMenusFeature->database)
105
                . '.' . Util::backquote($configurableMenusFeature->userGroups);
106
            $userTable = Util::backquote($configurableMenusFeature->database)
107
                . '.' . Util::backquote($configurableMenusFeature->users);
108
109
            $sqlQuery = 'SELECT `tab` FROM ' . $groupTable
110
                . " WHERE `allowed` = 'N'"
111
                . " AND `tab` LIKE '" . $level->value . "%'"
112
                . ' AND `usergroup` = (SELECT usergroup FROM '
113
                . $userTable . ' WHERE `username` = '
114
                . $this->dbi->quoteString($this->config->selectedServer['user'], ConnectionType::ControlUser) . ')';
115
116
            $result = $this->dbi->tryQueryAsControlUser($sqlQuery);
117
            if ($result) {
118
                while ($row = $result->fetchAssoc()) {
119
                    $tab = (string) $row['tab'];
120
                    $tabName = mb_substr(
121
                        $tab,
122
                        mb_strpos($tab, '_') + 1,
123
                    );
124
                    unset($allowedTabs[$tabName]);
125
                }
126
            }
127
        }
128
129 16
        SessionCache::set($cacheKey, $allowedTabs);
130
131 16
        return $allowedTabs;
132
    }
133
134
    /** @return array{server: mixed[], database: mixed[], table: mixed[]} */
135 16
    private function getBreadcrumbs(): array
136
    {
137 16
        $server = [];
138 16
        $database = [];
139 16
        $table = [];
140
141 16
        if (empty($this->config->selectedServer['host'])) {
142
            $this->config->selectedServer['host'] = '';
143
        }
144
145 16
        $server['name'] = ! empty($this->config->selectedServer['verbose'])
146 16
            ? $this->config->selectedServer['verbose'] : $this->config->selectedServer['host'];
147 16
        $server['name'] .= empty($this->config->selectedServer['port'])
148 16
            ? '' : ':' . $this->config->selectedServer['port'];
149 16
        $server['url'] = Util::getUrlForOption($this->config->settings['DefaultTabServer'], 'server');
150
151 16
        if ($this->db !== '') {
152 12
            $database['name'] = $this->db;
153 12
            $database['url'] = Util::getUrlForOption($this->config->settings['DefaultTabDatabase'], 'database');
154 12
            if ($this->table !== '') {
155 8
                $table['name'] = $this->table;
156 8
                $table['url'] = Util::getUrlForOption($this->config->settings['DefaultTabTable'], 'table');
157 8
                $tableObj = $this->dbi->getTable($this->db, $this->table);
158 8
                $table['is_view'] = $tableObj->isView();
159 8
                $table['comment'] = '';
160 8
                if (! $table['is_view']) {
161 8
                    $table['comment'] = $tableObj->getComment();
162
                }
163
164 8
                if (str_contains($table['comment'], '; InnoDB free')) {
165 4
                    $table['comment'] = (string) preg_replace('@; InnoDB free:.*?$@', '', $table['comment']);
166
                }
167
            } else {
168
                // no table selected, display database comment if present
169 4
                $relationParameters = $this->relation->getRelationParameters();
170
171
                // Get additional information about tables for tooltip is done
172
                // in Util::getDbInfo() only once
173 4
                if ($relationParameters->columnCommentsFeature !== null) {
174
                    $database['comment'] = $this->relation->getDbComment($this->db);
175
                }
176
            }
177
        }
178
179 16
        return ['server' => $server, 'database' => $database, 'table' => $table];
180
    }
181
182
    /**
183
     * Returns the table tabs as an array
184
     *
185
     * @return mixed[] Data for generating table tabs
186
     */
187 8
    private function getTableTabs(): array
188
    {
189 8
        $route = Routing::$route;
190
191 8
        $isSystemSchema = Utilities::isSystemSchema($this->db);
192 8
        $tableIsView = $this->dbi->getTable($this->db, $this->table)
193 8
            ->isView();
194 8
        $updatableView = false;
195 8
        if ($tableIsView) {
196
            $updatableView = $this->dbi->getTable($this->db, $this->table)
197
                ->isUpdatableView();
198
        }
199
200 8
        $isSuperUser = $this->dbi->isSuperUser();
201 8
        $isCreateOrGrantUser = $this->dbi->isGrantUser() || $this->dbi->isCreateUser();
202
203 8
        $tabs = [];
204
205 8
        $tabs['browse']['icon'] = 'b_browse';
206 8
        $tabs['browse']['text'] = __('Browse');
207 8
        $tabs['browse']['route'] = '/sql';
208 8
        $tabs['browse']['args']['pos'] = 0;
209 8
        $tabs['browse']['active'] = $route === '/sql';
210
211 8
        $tabs['structure']['icon'] = 'b_props';
212 8
        $tabs['structure']['route'] = '/table/structure';
213 8
        $tabs['structure']['text'] = __('Structure');
214 8
        $tabs['structure']['active'] = in_array($route, ['/table/relation', '/table/structure'], true);
215
216 8
        $tabs['sql']['icon'] = 'b_sql';
217 8
        $tabs['sql']['route'] = '/table/sql';
218 8
        $tabs['sql']['text'] = __('SQL');
219 8
        $tabs['sql']['active'] = $route === '/table/sql';
220
221 8
        $tabs['search']['icon'] = 'b_search';
222 8
        $tabs['search']['text'] = __('Search');
223 8
        $tabs['search']['route'] = '/table/search';
224 8
        $tabs['search']['active'] = in_array($route, [
225 8
            '/table/find-replace',
226 8
            '/table/search',
227 8
            '/table/zoom-search',
228 8
        ], true);
229
230 8
        if (! $isSystemSchema && (! $tableIsView || $updatableView)) {
231 8
            $tabs['insert']['icon'] = 'b_insrow';
232 8
            $tabs['insert']['route'] = '/table/change';
233 8
            $tabs['insert']['text'] = __('Insert');
234 8
            $tabs['insert']['active'] = $route === '/table/change';
235
        }
236
237 8
        $tabs['export']['icon'] = 'b_tblexport';
238 8
        $tabs['export']['route'] = '/table/export';
239 8
        $tabs['export']['args']['single_table'] = 'true';
240 8
        $tabs['export']['text'] = __('Export');
241 8
        $tabs['export']['active'] = $route === '/table/export';
242
243
        /**
244
         * Don't display "Import" for views and information_schema
245
         */
246 8
        if (! $tableIsView && ! $isSystemSchema) {
247 8
            $tabs['import']['icon'] = 'b_tblimport';
248 8
            $tabs['import']['route'] = '/table/import';
249 8
            $tabs['import']['text'] = __('Import');
250 8
            $tabs['import']['active'] = $route === '/table/import';
251
        }
252
253 8
        if (($isSuperUser || $isCreateOrGrantUser) && ! $isSystemSchema) {
254 8
            $tabs['privileges']['route'] = '/table/privileges';
255
            // stay on table view
256 8
            $tabs['privileges']['text'] = __('Privileges');
257 8
            $tabs['privileges']['icon'] = 's_rights';
258 8
            $tabs['privileges']['active'] = $route === '/table/privileges';
259
        }
260
261
        /**
262
         * Don't display "Operations" for views and information_schema
263
         */
264 8
        if (! $tableIsView && ! $isSystemSchema) {
265 8
            $tabs['operation']['icon'] = 'b_tblops';
266 8
            $tabs['operation']['route'] = '/table/operations';
267 8
            $tabs['operation']['text'] = __('Operations');
268 8
            $tabs['operation']['active'] = $route === '/table/operations';
269
        }
270
271
        /**
272
         * Views support a limited number of operations
273
         */
274 8
        if ($tableIsView && ! $isSystemSchema) {
275
            $tabs['operation']['icon'] = 'b_tblops';
276
            $tabs['operation']['route'] = '/view/operations';
277
            $tabs['operation']['text'] = __('Operations');
278
            $tabs['operation']['active'] = $route === '/view/operations';
279
        }
280
281 8
        if (Tracker::isActive() && ! $isSystemSchema) {
282
            $tabs['tracking']['icon'] = 'eye';
283
            $tabs['tracking']['text'] = __('Tracking');
284
            $tabs['tracking']['route'] = '/table/tracking';
285
            $tabs['tracking']['active'] = $route === '/table/tracking';
286
        }
287
288 8
        if (! $isSystemSchema && Util::currentUserHasPrivilege('TRIGGER', $this->db, $this->table) && ! $tableIsView) {
289
            $tabs['triggers']['route'] = '/triggers';
290
            $tabs['triggers']['text'] = __('Triggers');
291
            $tabs['triggers']['icon'] = 'b_triggers';
292
            $tabs['triggers']['active'] = $route === '/triggers';
293
        }
294
295 8
        return $tabs;
296
    }
297
298
    /**
299
     * Returns the db tabs as an array
300
     *
301
     * @return mixed[] Data for generating db tabs
302
     */
303 4
    private function getDbTabs(): array
304
    {
305 4
        $route = Routing::$route;
306
307 4
        $isSystemSchema = Utilities::isSystemSchema($this->db);
308 4
        $numTables = count($this->dbi->getTables($this->db));
309 4
        $isSuperUser = $this->dbi->isSuperUser();
310 4
        $isCreateOrGrantUser = $this->dbi->isGrantUser() || $this->dbi->isCreateUser();
311
312 4
        $relationParameters = $this->relation->getRelationParameters();
313
314 4
        $tabs = [];
315
316 4
        $tabs['structure']['route'] = '/database/structure';
317 4
        $tabs['structure']['text'] = __('Structure');
318 4
        $tabs['structure']['icon'] = 'b_props';
319 4
        $tabs['structure']['active'] = $route === '/database/structure';
320
321 4
        $tabs['sql']['route'] = '/database/sql';
322 4
        $tabs['sql']['text'] = __('SQL');
323 4
        $tabs['sql']['icon'] = 'b_sql';
324 4
        $tabs['sql']['active'] = $route === '/database/sql';
325
326 4
        $tabs['search']['text'] = __('Search');
327 4
        $tabs['search']['icon'] = 'b_search';
328 4
        $tabs['search']['route'] = '/database/search';
329 4
        $tabs['search']['active'] = $route === '/database/search';
330 4
        if ($numTables == 0) {
331
            $tabs['search']['warning'] = __('Database seems to be empty!');
332
        }
333
334 4
        $tabs['query']['text'] = __('Query');
335 4
        $tabs['query']['icon'] = 's_db';
336 4
        $tabs['query']['route'] = '/database/multi-table-query';
337 4
        $tabs['query']['active'] = $route === '/database/multi-table-query';
338 4
        if ($numTables == 0) {
339
            $tabs['query']['warning'] = __('Database seems to be empty!');
340
        }
341
342 4
        $tabs['export']['text'] = __('Export');
343 4
        $tabs['export']['icon'] = 'b_export';
344 4
        $tabs['export']['route'] = '/database/export';
345 4
        $tabs['export']['active'] = $route === '/database/export';
346 4
        if ($numTables == 0) {
347
            $tabs['export']['warning'] = __('Database seems to be empty!');
348
        }
349
350 4
        if (! $isSystemSchema) {
351 4
            $tabs['import']['route'] = '/database/import';
352 4
            $tabs['import']['text'] = __('Import');
353 4
            $tabs['import']['icon'] = 'b_import';
354 4
            $tabs['import']['active'] = $route === '/database/import';
355
356 4
            $tabs['operation']['route'] = '/database/operations';
357 4
            $tabs['operation']['text'] = __('Operations');
358 4
            $tabs['operation']['icon'] = 'b_tblops';
359 4
            $tabs['operation']['active'] = $route === '/database/operations';
360
361 4
            if ($isSuperUser || $isCreateOrGrantUser) {
362 4
                $tabs['privileges']['route'] = '/database/privileges';
363
                // stay on database view
364 4
                $tabs['privileges']['text'] = __('Privileges');
365 4
                $tabs['privileges']['icon'] = 's_rights';
366 4
                $tabs['privileges']['active'] = $route === '/database/privileges';
367
            }
368
369 4
            $tabs['routines']['route'] = '/database/routines';
370 4
            $tabs['routines']['text'] = __('Routines');
371 4
            $tabs['routines']['icon'] = 'b_routines';
372 4
            $tabs['routines']['active'] = $route === '/database/routines';
373
374 4
            if (Util::currentUserHasPrivilege('EVENT', $this->db)) {
375
                $tabs['events']['route'] = '/database/events';
376
                $tabs['events']['text'] = __('Events');
377
                $tabs['events']['icon'] = 'b_events';
378
                $tabs['events']['active'] = $route === '/database/events';
379
            }
380
381 4
            if (Util::currentUserHasPrivilege('TRIGGER', $this->db)) {
382
                $tabs['triggers']['route'] = '/triggers';
383
                $tabs['triggers']['text'] = __('Triggers');
384
                $tabs['triggers']['icon'] = 'b_triggers';
385
                $tabs['triggers']['active'] = $route === '/triggers';
386
            }
387
        }
388
389 4
        if (Tracker::isActive() && ! $isSystemSchema) {
390
            $tabs['tracking']['text'] = __('Tracking');
391
            $tabs['tracking']['icon'] = 'eye';
392
            $tabs['tracking']['route'] = '/database/tracking';
393
            $tabs['tracking']['active'] = $route === '/database/tracking';
394
        }
395
396 4
        if (! $isSystemSchema) {
397 4
            $tabs['designer']['text'] = __('Designer');
398 4
            $tabs['designer']['icon'] = 'b_relations';
399 4
            $tabs['designer']['route'] = '/database/designer';
400 4
            $tabs['designer']['active'] = $route === '/database/designer';
401
        }
402
403 4
        if (! $isSystemSchema && $relationParameters->centralColumnsFeature !== null) {
404
            $tabs['central_columns']['text'] = __('Central columns');
405
            $tabs['central_columns']['icon'] = 'centralColumns';
406
            $tabs['central_columns']['route'] = '/database/central-columns';
407
            $tabs['central_columns']['active'] = $route === '/database/central-columns';
408
        }
409
410 4
        return $tabs;
411
    }
412
413
    /**
414
     * Returns the server tabs as an array
415
     *
416
     * @return mixed[] Data for generating server tabs
417
     */
418 4
    private function getServerTabs(): array
419
    {
420 4
        $route = Routing::$route;
421
422 4
        $isSuperUser = $this->dbi->isSuperUser();
423 4
        $isCreateOrGrantUser = $this->dbi->isGrantUser() || $this->dbi->isCreateUser();
424 4
        if (SessionCache::has('binary_logs')) {
425
            $binaryLogs = SessionCache::get('binary_logs');
426
        } else {
427 4
            $binaryLogs = $this->dbi->fetchResult('SHOW MASTER LOGS', 'Log_name');
428 4
            SessionCache::set('binary_logs', $binaryLogs);
429
        }
430
431 4
        $tabs = [];
432
433 4
        $tabs['databases']['icon'] = 's_db';
434 4
        $tabs['databases']['route'] = '/server/databases';
435 4
        $tabs['databases']['text'] = __('Databases');
436 4
        $tabs['databases']['active'] = $route === '/server/databases';
437
438 4
        $tabs['sql']['icon'] = 'b_sql';
439 4
        $tabs['sql']['route'] = '/server/sql';
440 4
        $tabs['sql']['text'] = __('SQL');
441 4
        $tabs['sql']['active'] = $route === '/server/sql';
442
443 4
        $tabs['status']['icon'] = 's_status';
444 4
        $tabs['status']['route'] = '/server/status';
445 4
        $tabs['status']['text'] = __('Status');
446 4
        $tabs['status']['active'] = in_array($route, [
447 4
            '/server/status',
448 4
            '/server/status/advisor',
449 4
            '/server/status/monitor',
450 4
            '/server/status/processes',
451 4
            '/server/status/queries',
452 4
            '/server/status/variables',
453 4
        ], true);
454
455 4
        if ($isSuperUser || $isCreateOrGrantUser) {
456 4
            $tabs['rights']['icon'] = 's_rights';
457 4
            $tabs['rights']['route'] = '/server/privileges';
458 4
            $tabs['rights']['text'] = __('User accounts');
459 4
            $tabs['rights']['active'] = in_array($route, ['/server/privileges', '/server/user-groups'], true);
460
        }
461
462 4
        $tabs['export']['icon'] = 'b_export';
463 4
        $tabs['export']['route'] = '/server/export';
464 4
        $tabs['export']['text'] = __('Export');
465 4
        $tabs['export']['active'] = $route === '/server/export';
466
467 4
        $tabs['import']['icon'] = 'b_import';
468 4
        $tabs['import']['route'] = '/server/import';
469 4
        $tabs['import']['text'] = __('Import');
470 4
        $tabs['import']['active'] = $route === '/server/import';
471
472 4
        $tabs['settings']['icon'] = 'b_tblops';
473 4
        $tabs['settings']['route'] = '/preferences/manage';
474 4
        $tabs['settings']['text'] = __('Settings');
475 4
        $tabs['settings']['active'] = in_array($route, [
476 4
            '/preferences/export',
477 4
            '/preferences/features',
478 4
            '/preferences/import',
479 4
            '/preferences/main-panel',
480 4
            '/preferences/manage',
481 4
            '/preferences/navigation',
482 4
            '/preferences/sql',
483 4
            '/preferences/two-factor',
484 4
        ], true);
485
486 4
        if (! empty($binaryLogs)) {
487 4
            $tabs['binlog']['icon'] = 's_tbl';
488 4
            $tabs['binlog']['route'] = '/server/binlog';
489 4
            $tabs['binlog']['text'] = __('Binary log');
490 4
            $tabs['binlog']['active'] = $route === '/server/binlog';
491
        }
492
493 4
        if ($isSuperUser) {
494 4
            $tabs['replication']['icon'] = 's_replication';
495 4
            $tabs['replication']['route'] = '/server/replication';
496 4
            $tabs['replication']['text'] = __('Replication');
497 4
            $tabs['replication']['active'] = $route === '/server/replication';
498
        }
499
500 4
        $tabs['vars']['icon'] = 's_vars';
501 4
        $tabs['vars']['route'] = '/server/variables';
502 4
        $tabs['vars']['text'] = __('Variables');
503 4
        $tabs['vars']['active'] = $route === '/server/variables';
504
505 4
        $tabs['charset']['icon'] = 's_asci';
506 4
        $tabs['charset']['route'] = '/server/collations';
507 4
        $tabs['charset']['text'] = __('Charsets');
508 4
        $tabs['charset']['active'] = $route === '/server/collations';
509
510 4
        $tabs['engine']['icon'] = 'b_engine';
511 4
        $tabs['engine']['route'] = '/server/engines';
512 4
        $tabs['engine']['text'] = __('Engines');
513 4
        $tabs['engine']['active'] = $route === '/server/engines';
514
515 4
        $tabs['plugins']['icon'] = 'b_plugin';
516 4
        $tabs['plugins']['route'] = '/server/plugins';
517 4
        $tabs['plugins']['text'] = __('Plugins');
518 4
        $tabs['plugins']['active'] = $route === '/server/plugins';
519
520 4
        return $tabs;
521
    }
522
523
    /**
524
     * Set current table
525
     *
526
     * @param string $table Current table
527
     */
528 4
    public function setTable(string $table): Menu
529
    {
530 4
        $this->table = $table;
531
532 4
        return $this;
533
    }
534
}
535