3 declare(strict_types
=1);
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
;
16 use function array_intersect_key
;
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
;
25 * Generates and renders the top menu
30 * Creates a new instance of Menu
32 * @param string $db Database name
33 * @param string $table Table name
35 public function __construct(
36 private readonly DatabaseInterface
$dbi,
37 private readonly Template
$template,
38 private readonly Config
$config,
39 private readonly Relation
$relation,
41 private string $table,
46 * Returns the menu and the breadcrumbs as a string
48 public function getDisplay(): string
50 $breadcrumbs = $this->getBreadcrumbs();
51 $menu = $this->getMenu();
53 return $this->template
->render('menu/main', [
54 'server' => $breadcrumbs['server'],
55 'database' => $breadcrumbs['database'],
56 'table' => $breadcrumbs['table'],
57 'tabs' => $menu['tabs'],
58 'url_params' => $menu['url_params'],
62 /** @return array{tabs: mixed[], url_params: mixed[]} */
63 private function getMenu(): array
67 // The URL will not work if the table is defined without a database
68 if ($this->table
!== '' && $this->db
!== '') {
69 $tabs = $this->getTableTabs();
70 $urlParams['db'] = $this->db
;
71 $urlParams['table'] = $this->table
;
72 $level = UserGroupLevel
::Table
;
73 } elseif ($this->db
!== '') {
74 $tabs = $this->getDbTabs();
75 $urlParams['db'] = $this->db
;
76 $level = UserGroupLevel
::Database
;
78 $tabs = $this->getServerTabs();
79 $level = UserGroupLevel
::Server
;
82 $allowedTabs = $this->getAllowedTabs($level);
83 // Filter out any tabs that are not allowed
84 $tabs = array_intersect_key($tabs, $allowedTabs);
86 return ['tabs' => $tabs, 'url_params' => $urlParams];
90 * Returns a list of allowed tabs for the current user for the given level
92 * @return mixed[] list of allowed tabs
94 private function getAllowedTabs(UserGroupLevel
$level): array
96 $cacheKey = 'menu-levels-' . $level->value
;
97 if (SessionCache
::has($cacheKey)) {
98 return SessionCache
::get($cacheKey);
101 $allowedTabs = Util
::getMenuTabList($level);
102 $configurableMenusFeature = $this->relation
->getRelationParameters()->configurableMenusFeature
;
103 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
);
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
) . ')';
116 $result = $this->dbi
->tryQueryAsControlUser($sqlQuery);
118 while ($row = $result->fetchAssoc()) {
119 $tab = (string) $row['tab'];
120 $tabName = mb_substr(
122 mb_strpos($tab, '_') +
1,
124 unset($allowedTabs[$tabName]);
129 SessionCache
::set($cacheKey, $allowedTabs);
134 /** @return array{server: mixed[], database: mixed[], table: mixed[]} */
135 private function getBreadcrumbs(): array
141 if (empty($this->config
->selectedServer
['host'])) {
142 $this->config
->selectedServer
['host'] = '';
145 $server['name'] = ! empty($this->config
->selectedServer
['verbose'])
146 ?
$this->config
->selectedServer
['verbose'] : $this->config
->selectedServer
['host'];
147 $server['name'] .= empty($this->config
->selectedServer
['port'])
148 ?
'' : ':' . $this->config
->selectedServer
['port'];
149 $server['url'] = Util
::getUrlForOption($this->config
->settings
['DefaultTabServer'], 'server');
151 if ($this->db
!== '') {
152 $database['name'] = $this->db
;
153 $database['url'] = Util
::getUrlForOption($this->config
->settings
['DefaultTabDatabase'], 'database');
154 if ($this->table
!== '') {
155 $table['name'] = $this->table
;
156 $table['url'] = Util
::getUrlForOption($this->config
->settings
['DefaultTabTable'], 'table');
157 $tableObj = $this->dbi
->getTable($this->db
, $this->table
);
158 $table['is_view'] = $tableObj->isView();
159 $table['comment'] = '';
160 if (! $table['is_view']) {
161 $table['comment'] = $tableObj->getComment();
164 if (str_contains($table['comment'], '; InnoDB free')) {
165 $table['comment'] = (string) preg_replace('@; InnoDB free:.*?$@', '', $table['comment']);
168 // no table selected, display database comment if present
169 $relationParameters = $this->relation
->getRelationParameters();
171 // Get additional information about tables for tooltip is done
172 // in Util::getDbInfo() only once
173 if ($relationParameters->columnCommentsFeature
!== null) {
174 $database['comment'] = $this->relation
->getDbComment($this->db
);
179 return ['server' => $server, 'database' => $database, 'table' => $table];
183 * Returns the table tabs as an array
185 * @return mixed[] Data for generating table tabs
187 private function getTableTabs(): array
189 $route = Routing
::$route;
191 $isSystemSchema = Utilities
::isSystemSchema($this->db
);
192 $tableIsView = $this->dbi
->getTable($this->db
, $this->table
)
194 $updatableView = false;
196 $updatableView = $this->dbi
->getTable($this->db
, $this->table
)
200 $isSuperUser = $this->dbi
->isSuperUser();
201 $isCreateOrGrantUser = $this->dbi
->isGrantUser() ||
$this->dbi
->isCreateUser();
205 $tabs['browse']['icon'] = 'b_browse';
206 $tabs['browse']['text'] = __('Browse');
207 $tabs['browse']['route'] = '/sql';
208 $tabs['browse']['args']['pos'] = 0;
209 $tabs['browse']['active'] = $route === '/sql';
211 $tabs['structure']['icon'] = 'b_props';
212 $tabs['structure']['route'] = '/table/structure';
213 $tabs['structure']['text'] = __('Structure');
214 $tabs['structure']['active'] = in_array($route, ['/table/relation', '/table/structure'], true);
216 $tabs['sql']['icon'] = 'b_sql';
217 $tabs['sql']['route'] = '/table/sql';
218 $tabs['sql']['text'] = __('SQL');
219 $tabs['sql']['active'] = $route === '/table/sql';
221 $tabs['search']['icon'] = 'b_search';
222 $tabs['search']['text'] = __('Search');
223 $tabs['search']['route'] = '/table/search';
224 $tabs['search']['active'] = in_array($route, [
225 '/table/find-replace',
227 '/table/zoom-search',
230 if (! $isSystemSchema && (! $tableIsView ||
$updatableView)) {
231 $tabs['insert']['icon'] = 'b_insrow';
232 $tabs['insert']['route'] = '/table/change';
233 $tabs['insert']['text'] = __('Insert');
234 $tabs['insert']['active'] = $route === '/table/change';
237 $tabs['export']['icon'] = 'b_tblexport';
238 $tabs['export']['route'] = '/table/export';
239 $tabs['export']['args']['single_table'] = 'true';
240 $tabs['export']['text'] = __('Export');
241 $tabs['export']['active'] = $route === '/table/export';
244 * Don't display "Import" for views and information_schema
246 if (! $tableIsView && ! $isSystemSchema) {
247 $tabs['import']['icon'] = 'b_tblimport';
248 $tabs['import']['route'] = '/table/import';
249 $tabs['import']['text'] = __('Import');
250 $tabs['import']['active'] = $route === '/table/import';
253 if (($isSuperUser ||
$isCreateOrGrantUser) && ! $isSystemSchema) {
254 $tabs['privileges']['route'] = '/table/privileges';
255 // stay on table view
256 $tabs['privileges']['text'] = __('Privileges');
257 $tabs['privileges']['icon'] = 's_rights';
258 $tabs['privileges']['active'] = $route === '/table/privileges';
262 * Don't display "Operations" for views and information_schema
264 if (! $tableIsView && ! $isSystemSchema) {
265 $tabs['operation']['icon'] = 'b_tblops';
266 $tabs['operation']['route'] = '/table/operations';
267 $tabs['operation']['text'] = __('Operations');
268 $tabs['operation']['active'] = $route === '/table/operations';
272 * Views support a limited number of operations
274 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';
281 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';
288 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';
299 * Returns the db tabs as an array
301 * @return mixed[] Data for generating db tabs
303 private function getDbTabs(): array
305 $route = Routing
::$route;
307 $isSystemSchema = Utilities
::isSystemSchema($this->db
);
308 $numTables = count($this->dbi
->getTables($this->db
));
309 $isSuperUser = $this->dbi
->isSuperUser();
310 $isCreateOrGrantUser = $this->dbi
->isGrantUser() ||
$this->dbi
->isCreateUser();
312 $relationParameters = $this->relation
->getRelationParameters();
316 $tabs['structure']['route'] = '/database/structure';
317 $tabs['structure']['text'] = __('Structure');
318 $tabs['structure']['icon'] = 'b_props';
319 $tabs['structure']['active'] = $route === '/database/structure';
321 $tabs['sql']['route'] = '/database/sql';
322 $tabs['sql']['text'] = __('SQL');
323 $tabs['sql']['icon'] = 'b_sql';
324 $tabs['sql']['active'] = $route === '/database/sql';
326 $tabs['search']['text'] = __('Search');
327 $tabs['search']['icon'] = 'b_search';
328 $tabs['search']['route'] = '/database/search';
329 $tabs['search']['active'] = $route === '/database/search';
330 if ($numTables == 0) {
331 $tabs['search']['warning'] = __('Database seems to be empty!');
334 $tabs['query']['text'] = __('Query');
335 $tabs['query']['icon'] = 's_db';
336 $tabs['query']['route'] = '/database/multi-table-query';
337 $tabs['query']['active'] = $route === '/database/multi-table-query';
338 if ($numTables == 0) {
339 $tabs['query']['warning'] = __('Database seems to be empty!');
342 $tabs['export']['text'] = __('Export');
343 $tabs['export']['icon'] = 'b_export';
344 $tabs['export']['route'] = '/database/export';
345 $tabs['export']['active'] = $route === '/database/export';
346 if ($numTables == 0) {
347 $tabs['export']['warning'] = __('Database seems to be empty!');
350 if (! $isSystemSchema) {
351 $tabs['import']['route'] = '/database/import';
352 $tabs['import']['text'] = __('Import');
353 $tabs['import']['icon'] = 'b_import';
354 $tabs['import']['active'] = $route === '/database/import';
356 $tabs['operation']['route'] = '/database/operations';
357 $tabs['operation']['text'] = __('Operations');
358 $tabs['operation']['icon'] = 'b_tblops';
359 $tabs['operation']['active'] = $route === '/database/operations';
361 if ($isSuperUser ||
$isCreateOrGrantUser) {
362 $tabs['privileges']['route'] = '/database/privileges';
363 // stay on database view
364 $tabs['privileges']['text'] = __('Privileges');
365 $tabs['privileges']['icon'] = 's_rights';
366 $tabs['privileges']['active'] = $route === '/database/privileges';
369 $tabs['routines']['route'] = '/database/routines';
370 $tabs['routines']['text'] = __('Routines');
371 $tabs['routines']['icon'] = 'b_routines';
372 $tabs['routines']['active'] = $route === '/database/routines';
374 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';
381 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';
389 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';
396 if (! $isSystemSchema) {
397 $tabs['designer']['text'] = __('Designer');
398 $tabs['designer']['icon'] = 'b_relations';
399 $tabs['designer']['route'] = '/database/designer';
400 $tabs['designer']['active'] = $route === '/database/designer';
403 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';
414 * Returns the server tabs as an array
416 * @return mixed[] Data for generating server tabs
418 private function getServerTabs(): array
420 $route = Routing
::$route;
422 $isSuperUser = $this->dbi
->isSuperUser();
423 $isCreateOrGrantUser = $this->dbi
->isGrantUser() ||
$this->dbi
->isCreateUser();
424 if (SessionCache
::has('binary_logs')) {
425 $binaryLogs = SessionCache
::get('binary_logs');
427 $binaryLogs = $this->dbi
->fetchResult('SHOW MASTER LOGS', 'Log_name');
428 SessionCache
::set('binary_logs', $binaryLogs);
433 $tabs['databases']['icon'] = 's_db';
434 $tabs['databases']['route'] = '/server/databases';
435 $tabs['databases']['text'] = __('Databases');
436 $tabs['databases']['active'] = $route === '/server/databases';
438 $tabs['sql']['icon'] = 'b_sql';
439 $tabs['sql']['route'] = '/server/sql';
440 $tabs['sql']['text'] = __('SQL');
441 $tabs['sql']['active'] = $route === '/server/sql';
443 $tabs['status']['icon'] = 's_status';
444 $tabs['status']['route'] = '/server/status';
445 $tabs['status']['text'] = __('Status');
446 $tabs['status']['active'] = in_array($route, [
448 '/server/status/advisor',
449 '/server/status/monitor',
450 '/server/status/processes',
451 '/server/status/queries',
452 '/server/status/variables',
455 if ($isSuperUser ||
$isCreateOrGrantUser) {
456 $tabs['rights']['icon'] = 's_rights';
457 $tabs['rights']['route'] = '/server/privileges';
458 $tabs['rights']['text'] = __('User accounts');
459 $tabs['rights']['active'] = in_array($route, ['/server/privileges', '/server/user-groups'], true);
462 $tabs['export']['icon'] = 'b_export';
463 $tabs['export']['route'] = '/server/export';
464 $tabs['export']['text'] = __('Export');
465 $tabs['export']['active'] = $route === '/server/export';
467 $tabs['import']['icon'] = 'b_import';
468 $tabs['import']['route'] = '/server/import';
469 $tabs['import']['text'] = __('Import');
470 $tabs['import']['active'] = $route === '/server/import';
472 $tabs['settings']['icon'] = 'b_tblops';
473 $tabs['settings']['route'] = '/preferences/manage';
474 $tabs['settings']['text'] = __('Settings');
475 $tabs['settings']['active'] = in_array($route, [
476 '/preferences/export',
477 '/preferences/features',
478 '/preferences/import',
479 '/preferences/main-panel',
480 '/preferences/manage',
481 '/preferences/navigation',
483 '/preferences/two-factor',
486 if (! empty($binaryLogs)) {
487 $tabs['binlog']['icon'] = 's_tbl';
488 $tabs['binlog']['route'] = '/server/binlog';
489 $tabs['binlog']['text'] = __('Binary log');
490 $tabs['binlog']['active'] = $route === '/server/binlog';
494 $tabs['replication']['icon'] = 's_replication';
495 $tabs['replication']['route'] = '/server/replication';
496 $tabs['replication']['text'] = __('Replication');
497 $tabs['replication']['active'] = $route === '/server/replication';
500 $tabs['vars']['icon'] = 's_vars';
501 $tabs['vars']['route'] = '/server/variables';
502 $tabs['vars']['text'] = __('Variables');
503 $tabs['vars']['active'] = $route === '/server/variables';
505 $tabs['charset']['icon'] = 's_asci';
506 $tabs['charset']['route'] = '/server/collations';
507 $tabs['charset']['text'] = __('Charsets');
508 $tabs['charset']['active'] = $route === '/server/collations';
510 $tabs['engine']['icon'] = 'b_engine';
511 $tabs['engine']['route'] = '/server/engines';
512 $tabs['engine']['text'] = __('Engines');
513 $tabs['engine']['active'] = $route === '/server/engines';
515 $tabs['plugins']['icon'] = 'b_plugin';
516 $tabs['plugins']['route'] = '/server/plugins';
517 $tabs['plugins']['text'] = __('Plugins');
518 $tabs['plugins']['active'] = $route === '/server/plugins';
526 * @param string $table Current table
528 public function setTable(string $table): Menu
530 $this->table
= $table;