3 * Generates and renders the top menu
6 declare(strict_types
=1);
10 use PhpMyAdmin\ConfigStorage\Relation
;
11 use PhpMyAdmin\Query\Utilities
;
12 use PhpMyAdmin\Utils\SessionCache
;
15 use function array_intersect_key
;
17 use function in_array
;
18 use function mb_strpos
;
19 use function mb_strstr
;
20 use function mb_substr
;
21 use function preg_replace
;
24 * Class for generating the top menu
35 /** @var DatabaseInterface */
52 * Creates a new instance of Menu
54 * @param string $db Database name
55 * @param string $table Table name
57 public function __construct(DatabaseInterface
$dbi, string $db, string $table)
61 $this->table
= $table;
62 $this->relation
= new Relation($dbi);
63 $this->template
= new Template();
67 * Returns the menu and the breadcrumbs as a string
69 public function getDisplay(): string
71 $retval = $this->getBreadcrumbs();
72 $retval .= $this->getMenu();
78 * Returns the menu as HTML
80 * @return string HTML formatted menubar
82 private function getMenu(): string
86 // The URL will not work if the table is defined without a database
87 if ($this->table
!== '' && $this->db
!== '') {
88 $tabs = $this->getTableTabs();
89 $urlParams['db'] = $this->db
;
90 $urlParams['table'] = $this->table
;
92 } elseif ($this->db
!== '') {
93 $tabs = $this->getDbTabs();
94 $urlParams['db'] = $this->db
;
97 $tabs = $this->getServerTabs();
101 $allowedTabs = $this->getAllowedTabs($level);
102 // Filter out any tabs that are not allowed
103 $tabs = array_intersect_key($tabs, $allowedTabs);
105 return $this->template
->render('top_menu', [
107 'url_params' => $urlParams,
112 * Returns a list of allowed tabs for the current user for the given level
114 * @param string $level 'server', 'db' or 'table' level
116 * @return array list of allowed tabs
118 private function getAllowedTabs($level)
120 $cacheKey = 'menu-levels-' . $level;
121 if (SessionCache
::has($cacheKey)) {
122 return SessionCache
::get($cacheKey);
125 $allowedTabs = Util
::getMenuTabList($level) ??
[];
126 $relationParameters = $this->relation
->getRelationParameters();
127 if ($relationParameters->hasConfigurableMenusFeature()) {
128 $groupTable = Util
::backquote($relationParameters->db
)
130 . Util
::backquote($relationParameters->usergroups
);
131 $userTable = Util
::backquote($relationParameters->db
)
132 . '.' . Util
::backquote($relationParameters->users
);
134 $sqlQuery = 'SELECT `tab` FROM ' . $groupTable
135 . " WHERE `allowed` = 'N'"
136 . " AND `tab` LIKE '" . $level . "%'"
137 . ' AND `usergroup` = (SELECT usergroup FROM '
138 . $userTable . " WHERE `username` = '"
139 . $this->dbi
->escapeString($GLOBALS['cfg']['Server']['user']) . "')";
141 $result = $this->relation
->queryAsControlUser($sqlQuery, false);
143 while ($row = $this->dbi
->fetchAssoc($result)) {
144 $tab = (string) $row['tab'];
145 $tabName = mb_substr(
147 mb_strpos($tab, '_') +
1
149 unset($allowedTabs[$tabName]);
154 SessionCache
::set($cacheKey, $allowedTabs);
160 * Returns the breadcrumbs as HTML
162 * @return string HTML formatted breadcrumbs
164 private function getBreadcrumbs(): string
172 if (empty($cfg['Server']['host'])) {
173 $cfg['Server']['host'] = '';
176 $server['name'] = ! empty($cfg['Server']['verbose'])
177 ?
$cfg['Server']['verbose'] : $cfg['Server']['host'];
178 $server['name'] .= empty($cfg['Server']['port'])
179 ?
'' : ':' . $cfg['Server']['port'];
180 $server['url'] = Util
::getUrlForOption($cfg['DefaultTabServer'], 'server');
182 if ($this->db
!== '') {
183 $database['name'] = $this->db
;
184 $database['url'] = Util
::getUrlForOption($cfg['DefaultTabDatabase'], 'database');
185 if ($this->table
!== '') {
186 $table['name'] = $this->table
;
187 $table['url'] = Util
::getUrlForOption($cfg['DefaultTabTable'], 'table');
188 $tableObj = $this->dbi
->getTable($this->db
, $this->table
);
189 $table['is_view'] = $tableObj->isView();
190 $table['comment'] = '';
191 if (! $table['is_view']) {
192 $table['comment'] = $tableObj->getComment();
195 if (mb_strstr($table['comment'], '; InnoDB free')) {
196 $table['comment'] = preg_replace('@; InnoDB free:.*?$@', '', $table['comment']);
199 // no table selected, display database comment if present
200 $relationParameters = $this->relation
->getRelationParameters();
202 // Get additional information about tables for tooltip is done
203 // in Util::getDbInfo() only once
204 if ($relationParameters->hasColumnCommentsFeature()) {
205 $database['comment'] = $this->relation
->getDbComment($this->db
);
210 return $this->template
->render('menu/breadcrumbs', [
212 'database' => $database,
218 * Returns the table tabs as an array
220 * @return array Data for generating table tabs
222 private function getTableTabs(): array
226 $isSystemSchema = Utilities
::isSystemSchema($this->db
);
227 $tableIsView = $this->dbi
->getTable($this->db
, $this->table
)
229 $updatableView = false;
231 $updatableView = $this->dbi
->getTable($this->db
, $this->table
)
235 $isSuperUser = $this->dbi
->isSuperUser();
236 $isCreateOrGrantUser = $this->dbi
->isGrantUser() ||
$this->dbi
->isCreateUser();
240 $tabs['browse']['icon'] = 'b_browse';
241 $tabs['browse']['text'] = __('Browse');
242 $tabs['browse']['route'] = '/sql';
243 $tabs['browse']['args']['pos'] = 0;
244 $tabs['browse']['active'] = $route === '/sql';
246 $tabs['structure']['icon'] = 'b_props';
247 $tabs['structure']['route'] = '/table/structure';
248 $tabs['structure']['text'] = __('Structure');
249 $tabs['structure']['active'] = in_array($route, [
254 $tabs['sql']['icon'] = 'b_sql';
255 $tabs['sql']['route'] = '/table/sql';
256 $tabs['sql']['text'] = __('SQL');
257 $tabs['sql']['active'] = $route === '/table/sql';
259 $tabs['search']['icon'] = 'b_search';
260 $tabs['search']['text'] = __('Search');
261 $tabs['search']['route'] = '/table/search';
262 $tabs['search']['active'] = in_array($route, [
263 '/table/find-replace',
265 '/table/zoom-search',
268 if (! $isSystemSchema && (! $tableIsView ||
$updatableView)) {
269 $tabs['insert']['icon'] = 'b_insrow';
270 $tabs['insert']['route'] = '/table/change';
271 $tabs['insert']['text'] = __('Insert');
272 $tabs['insert']['active'] = $route === '/table/change';
275 $tabs['export']['icon'] = 'b_tblexport';
276 $tabs['export']['route'] = '/table/export';
277 $tabs['export']['args']['single_table'] = 'true';
278 $tabs['export']['text'] = __('Export');
279 $tabs['export']['active'] = $route === '/table/export';
282 * Don't display "Import" for views and information_schema
284 if (! $tableIsView && ! $isSystemSchema) {
285 $tabs['import']['icon'] = 'b_tblimport';
286 $tabs['import']['route'] = '/table/import';
287 $tabs['import']['text'] = __('Import');
288 $tabs['import']['active'] = $route === '/table/import';
291 if (($isSuperUser ||
$isCreateOrGrantUser) && ! $isSystemSchema) {
292 $tabs['privileges']['route'] = '/server/privileges';
293 $tabs['privileges']['args']['checkprivsdb'] = $this->db
;
294 $tabs['privileges']['args']['checkprivstable'] = $this->table
;
295 // stay on table view
296 $tabs['privileges']['args']['viewing_mode'] = 'table';
297 $tabs['privileges']['text'] = __('Privileges');
298 $tabs['privileges']['icon'] = 's_rights';
299 $tabs['privileges']['active'] = $route === '/server/privileges';
303 * Don't display "Operations" for views and information_schema
305 if (! $tableIsView && ! $isSystemSchema) {
306 $tabs['operation']['icon'] = 'b_tblops';
307 $tabs['operation']['route'] = '/table/operations';
308 $tabs['operation']['text'] = __('Operations');
309 $tabs['operation']['active'] = $route === '/table/operations';
313 * Views support a limited number of operations
315 if ($tableIsView && ! $isSystemSchema) {
316 $tabs['operation']['icon'] = 'b_tblops';
317 $tabs['operation']['route'] = '/view/operations';
318 $tabs['operation']['text'] = __('Operations');
319 $tabs['operation']['active'] = $route === '/view/operations';
322 if (Tracker
::isActive() && ! $isSystemSchema) {
323 $tabs['tracking']['icon'] = 'eye';
324 $tabs['tracking']['text'] = __('Tracking');
325 $tabs['tracking']['route'] = '/table/tracking';
326 $tabs['tracking']['active'] = $route === '/table/tracking';
329 if (! $isSystemSchema && Util
::currentUserHasPrivilege('TRIGGER', $this->db
, $this->table
) && ! $tableIsView) {
330 $tabs['triggers']['route'] = '/table/triggers';
331 $tabs['triggers']['text'] = __('Triggers');
332 $tabs['triggers']['icon'] = 'b_triggers';
333 $tabs['triggers']['active'] = $route === '/table/triggers';
340 * Returns the db tabs as an array
342 * @return array Data for generating db tabs
344 private function getDbTabs(): array
348 $isSystemSchema = Utilities
::isSystemSchema($this->db
);
349 $numTables = count($this->dbi
->getTables($this->db
));
350 $isSuperUser = $this->dbi
->isSuperUser();
351 $isCreateOrGrantUser = $this->dbi
->isGrantUser() ||
$this->dbi
->isCreateUser();
353 $relationParameters = $this->relation
->getRelationParameters();
357 $tabs['structure']['route'] = '/database/structure';
358 $tabs['structure']['text'] = __('Structure');
359 $tabs['structure']['icon'] = 'b_props';
360 $tabs['structure']['active'] = $route === '/database/structure';
362 $tabs['sql']['route'] = '/database/sql';
363 $tabs['sql']['text'] = __('SQL');
364 $tabs['sql']['icon'] = 'b_sql';
365 $tabs['sql']['active'] = $route === '/database/sql';
367 $tabs['search']['text'] = __('Search');
368 $tabs['search']['icon'] = 'b_search';
369 $tabs['search']['route'] = '/database/search';
370 $tabs['search']['active'] = $route === '/database/search';
371 if ($numTables == 0) {
372 $tabs['search']['warning'] = __('Database seems to be empty!');
375 $tabs['query']['text'] = __('Query');
376 $tabs['query']['icon'] = 's_db';
377 $tabs['query']['route'] = '/database/multi-table-query';
378 $tabs['query']['active'] = $route === '/database/multi-table-query' ||
$route === '/database/qbe';
379 if ($numTables == 0) {
380 $tabs['query']['warning'] = __('Database seems to be empty!');
383 $tabs['export']['text'] = __('Export');
384 $tabs['export']['icon'] = 'b_export';
385 $tabs['export']['route'] = '/database/export';
386 $tabs['export']['active'] = $route === '/database/export';
387 if ($numTables == 0) {
388 $tabs['export']['warning'] = __('Database seems to be empty!');
391 if (! $isSystemSchema) {
392 $tabs['import']['route'] = '/database/import';
393 $tabs['import']['text'] = __('Import');
394 $tabs['import']['icon'] = 'b_import';
395 $tabs['import']['active'] = $route === '/database/import';
397 $tabs['operation']['route'] = '/database/operations';
398 $tabs['operation']['text'] = __('Operations');
399 $tabs['operation']['icon'] = 'b_tblops';
400 $tabs['operation']['active'] = $route === '/database/operations';
402 if ($isSuperUser ||
$isCreateOrGrantUser) {
403 $tabs['privileges']['route'] = '/server/privileges';
404 $tabs['privileges']['args']['checkprivsdb'] = $this->db
;
405 // stay on database view
406 $tabs['privileges']['args']['viewing_mode'] = 'db';
407 $tabs['privileges']['text'] = __('Privileges');
408 $tabs['privileges']['icon'] = 's_rights';
409 $tabs['privileges']['active'] = $route === '/server/privileges';
412 $tabs['routines']['route'] = '/database/routines';
413 $tabs['routines']['text'] = __('Routines');
414 $tabs['routines']['icon'] = 'b_routines';
415 $tabs['routines']['active'] = $route === '/database/routines';
417 if (Util
::currentUserHasPrivilege('EVENT', $this->db
)) {
418 $tabs['events']['route'] = '/database/events';
419 $tabs['events']['text'] = __('Events');
420 $tabs['events']['icon'] = 'b_events';
421 $tabs['events']['active'] = $route === '/database/events';
424 if (Util
::currentUserHasPrivilege('TRIGGER', $this->db
)) {
425 $tabs['triggers']['route'] = '/database/triggers';
426 $tabs['triggers']['text'] = __('Triggers');
427 $tabs['triggers']['icon'] = 'b_triggers';
428 $tabs['triggers']['active'] = $route === '/database/triggers';
432 if (Tracker
::isActive() && ! $isSystemSchema) {
433 $tabs['tracking']['text'] = __('Tracking');
434 $tabs['tracking']['icon'] = 'eye';
435 $tabs['tracking']['route'] = '/database/tracking';
436 $tabs['tracking']['active'] = $route === '/database/tracking';
439 if (! $isSystemSchema) {
440 $tabs['designer']['text'] = __('Designer');
441 $tabs['designer']['icon'] = 'b_relations';
442 $tabs['designer']['route'] = '/database/designer';
443 $tabs['designer']['active'] = $route === '/database/designer';
446 if (! $isSystemSchema && $relationParameters->hasCentralColumnsFeature()) {
447 $tabs['central_columns']['text'] = __('Central columns');
448 $tabs['central_columns']['icon'] = 'centralColumns';
449 $tabs['central_columns']['route'] = '/database/central-columns';
450 $tabs['central_columns']['active'] = $route === '/database/central-columns';
457 * Returns the server tabs as an array
459 * @return array Data for generating server tabs
461 private function getServerTabs(): array
465 $isSuperUser = $this->dbi
->isSuperUser();
466 $isCreateOrGrantUser = $this->dbi
->isGrantUser() ||
$this->dbi
->isCreateUser();
467 if (SessionCache
::has('binary_logs')) {
468 $binaryLogs = SessionCache
::get('binary_logs');
470 $binaryLogs = $this->dbi
->fetchResult(
474 DatabaseInterface
::CONNECT_USER
,
475 DatabaseInterface
::QUERY_STORE
477 SessionCache
::set('binary_logs', $binaryLogs);
482 $tabs['databases']['icon'] = 's_db';
483 $tabs['databases']['route'] = '/server/databases';
484 $tabs['databases']['text'] = __('Databases');
485 $tabs['databases']['active'] = $route === '/server/databases';
487 $tabs['sql']['icon'] = 'b_sql';
488 $tabs['sql']['route'] = '/server/sql';
489 $tabs['sql']['text'] = __('SQL');
490 $tabs['sql']['active'] = $route === '/server/sql';
492 $tabs['status']['icon'] = 's_status';
493 $tabs['status']['route'] = '/server/status';
494 $tabs['status']['text'] = __('Status');
495 $tabs['status']['active'] = in_array($route, [
497 '/server/status/advisor',
498 '/server/status/monitor',
499 '/server/status/processes',
500 '/server/status/queries',
501 '/server/status/variables',
504 if ($isSuperUser ||
$isCreateOrGrantUser) {
505 $tabs['rights']['icon'] = 's_rights';
506 $tabs['rights']['route'] = '/server/privileges';
507 $tabs['rights']['text'] = __('User accounts');
508 $tabs['rights']['active'] = in_array($route, [
509 '/server/privileges',
510 '/server/user-groups',
512 $tabs['rights']['args']['viewing_mode'] = 'server';
515 $tabs['export']['icon'] = 'b_export';
516 $tabs['export']['route'] = '/server/export';
517 $tabs['export']['text'] = __('Export');
518 $tabs['export']['active'] = $route === '/server/export';
520 $tabs['import']['icon'] = 'b_import';
521 $tabs['import']['route'] = '/server/import';
522 $tabs['import']['text'] = __('Import');
523 $tabs['import']['active'] = $route === '/server/import';
525 $tabs['settings']['icon'] = 'b_tblops';
526 $tabs['settings']['route'] = '/preferences/manage';
527 $tabs['settings']['text'] = __('Settings');
528 $tabs['settings']['active'] = in_array($route, [
529 '/preferences/export',
530 '/preferences/features',
531 '/preferences/import',
532 '/preferences/main-panel',
533 '/preferences/manage',
534 '/preferences/navigation',
536 '/preferences/two-factor',
539 if (! empty($binaryLogs)) {
540 $tabs['binlog']['icon'] = 's_tbl';
541 $tabs['binlog']['route'] = '/server/binlog';
542 $tabs['binlog']['text'] = __('Binary log');
543 $tabs['binlog']['active'] = $route === '/server/binlog';
547 $tabs['replication']['icon'] = 's_replication';
548 $tabs['replication']['route'] = '/server/replication';
549 $tabs['replication']['text'] = __('Replication');
550 $tabs['replication']['active'] = $route === '/server/replication';
553 $tabs['vars']['icon'] = 's_vars';
554 $tabs['vars']['route'] = '/server/variables';
555 $tabs['vars']['text'] = __('Variables');
556 $tabs['vars']['active'] = $route === '/server/variables';
558 $tabs['charset']['icon'] = 's_asci';
559 $tabs['charset']['route'] = '/server/collations';
560 $tabs['charset']['text'] = __('Charsets');
561 $tabs['charset']['active'] = $route === '/server/collations';
563 $tabs['engine']['icon'] = 'b_engine';
564 $tabs['engine']['route'] = '/server/engines';
565 $tabs['engine']['text'] = __('Engines');
566 $tabs['engine']['active'] = $route === '/server/engines';
568 $tabs['plugins']['icon'] = 'b_plugin';
569 $tabs['plugins']['route'] = '/server/plugins';
570 $tabs['plugins']['text'] = __('Plugins');
571 $tabs['plugins']['active'] = $route === '/server/plugins';
579 * @param string $table Current table
583 public function setTable(string $table)
585 $this->table
= $table;