diff options
Diffstat (limited to 'srcs/phpmyadmin/libraries/classes/Navigation')
24 files changed, 4814 insertions, 0 deletions
diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Navigation.php b/srcs/phpmyadmin/libraries/classes/Navigation/Navigation.php new file mode 100644 index 0000000..49d40df --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Navigation.php @@ -0,0 +1,280 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * This class is responsible for instantiating + * the various components of the navigation panel + * + * @package PhpMyAdmin-navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation; + +use PhpMyAdmin\Config\PageSettings; +use PhpMyAdmin\DatabaseInterface; +use PhpMyAdmin\Relation; +use PhpMyAdmin\Response; +use PhpMyAdmin\Sanitize; +use PhpMyAdmin\Server\Select; +use PhpMyAdmin\Template; +use PhpMyAdmin\Url; +use PhpMyAdmin\Util; + +/** + * The navigation panel - displays server, db and table selection tree + * + * @package PhpMyAdmin-Navigation + */ +class Navigation +{ + /** + * @var Template + */ + private $template; + + /** + * @var Relation + */ + private $relation; + + /** + * @var DatabaseInterface + */ + private $dbi; + + /** + * @var NavigationTree + */ + private $tree; + + /** + * Navigation constructor. + * @param Template $template Template instance + * @param Relation $relation Relation instance + * @param DatabaseInterface $dbi DatabaseInterface instance + */ + public function __construct($template, $relation, $dbi) + { + $this->template = $template; + $this->relation = $relation; + $this->dbi = $dbi; + $this->tree = new NavigationTree($this->template, $this->dbi); + } + + /** + * Renders the navigation tree, or part of it + * + * @return string The navigation tree + */ + public function getDisplay(): string + { + global $cfg; + + $logo = [ + 'is_displayed' => $cfg['NavigationDisplayLogo'], + 'has_link' => false, + 'link' => '#', + 'attributes' => ' target="_blank" rel="noopener noreferrer"', + 'source' => '', + ]; + + $response = Response::getInstance(); + if (! $response->isAjax()) { + $logo['source'] = $this->getLogoSource(); + $logo['has_link'] = (string) $cfg['NavigationLogoLink'] !== ''; + $logo['link'] = trim((string) $cfg['NavigationLogoLink']); + if (! Sanitize::checkLink($logo['link'], true)) { + $logo['link'] = 'index.php'; + } + if ($cfg['NavigationLogoLinkWindow'] === 'main') { + if (empty(parse_url($logo['link'], PHP_URL_HOST))) { + $hasStartChar = strpos($logo['link'], '?'); + $logo['link'] .= Url::getCommon( + [], + is_bool($hasStartChar) ? '?' : Url::getArgSeparator() + ); + } + $logo['attributes'] = ''; + } + + if ($cfg['NavigationDisplayServers'] && count($cfg['Servers']) > 1) { + $serverSelect = Select::render(true, true); + } + + if (! defined('PMA_DISABLE_NAVI_SETTINGS')) { + $navigationSettings = PageSettings::getNaviSettings(); + } + } + if (! $response->isAjax() + || ! empty($_POST['full']) + || ! empty($_POST['reload']) + ) { + if ($cfg['ShowDatabasesNavigationAsTree']) { + // provide database tree in navigation + $navRender = $this->tree->renderState(); + } else { + // provide legacy pre-4.0 navigation + $navRender = $this->tree->renderDbSelect(); + } + } else { + $navRender = $this->tree->renderPath(); + } + + return $this->template->render('navigation/main', [ + 'is_ajax' => $response->isAjax(), + 'logo' => $logo, + 'is_synced' => $cfg['NavigationLinkWithMainPanel'], + 'is_highlighted' => $cfg['NavigationTreePointerEnable'], + 'is_autoexpanded' => $cfg['NavigationTreeAutoexpandSingleDb'], + 'server' => $GLOBALS['server'], + 'auth_type' => $cfg['Server']['auth_type'], + 'is_servers_displayed' => $cfg['NavigationDisplayServers'], + 'servers' => $cfg['Servers'], + 'server_select' => $serverSelect ?? '', + 'navigation_tree' => $navRender, + 'is_navigation_settings_enabled' => ! defined('PMA_DISABLE_NAVI_SETTINGS'), + 'navigation_settings' => $navigationSettings ?? '', + 'is_drag_drop_import_enabled' => $cfg['enable_drag_drop_import'] === true, + ]); + } + + /** + * Add an item of navigation tree to the hidden items list in PMA database. + * + * @param string $itemName name of the navigation tree item + * @param string $itemType type of the navigation tree item + * @param string $dbName database name + * @param string $tableName table name if applicable + * + * @return void + */ + public function hideNavigationItem( + $itemName, + $itemType, + $dbName, + $tableName = null + ) { + $navTable = Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "INSERT INTO " . $navTable + . "(`username`, `item_name`, `item_type`, `db_name`, `table_name`)" + . " VALUES (" + . "'" . $this->dbi->escapeString($GLOBALS['cfg']['Server']['user']) . "'," + . "'" . $this->dbi->escapeString($itemName) . "'," + . "'" . $this->dbi->escapeString($itemType) . "'," + . "'" . $this->dbi->escapeString($dbName) . "'," + . "'" . (! empty($tableName) ? $this->dbi->escapeString($tableName) : "" ) + . "')"; + $this->relation->queryAsControlUser($sqlQuery, false); + } + + /** + * Remove a hidden item of navigation tree from the + * list of hidden items in PMA database. + * + * @param string $itemName name of the navigation tree item + * @param string $itemType type of the navigation tree item + * @param string $dbName database name + * @param string $tableName table name if applicable + * + * @return void + */ + public function unhideNavigationItem( + $itemName, + $itemType, + $dbName, + $tableName = null + ) { + $navTable = Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "DELETE FROM " . $navTable + . " WHERE" + . " `username`='" + . $this->dbi->escapeString($GLOBALS['cfg']['Server']['user']) . "'" + . " AND `item_name`='" . $this->dbi->escapeString($itemName) . "'" + . " AND `item_type`='" . $this->dbi->escapeString($itemType) . "'" + . " AND `db_name`='" . $this->dbi->escapeString($dbName) . "'" + . (! empty($tableName) + ? " AND `table_name`='" . $this->dbi->escapeString($tableName) . "'" + : "" + ); + $this->relation->queryAsControlUser($sqlQuery, false); + } + + /** + * Returns HTML for the dialog to show hidden navigation items. + * + * @param string $database database name + * @param string $itemType type of the items to include + * @param string $table table name + * + * @return string HTML for the dialog to show hidden navigation items + */ + public function getItemUnhideDialog($database, $itemType = null, $table = null) + { + $hidden = $this->getHiddenItems($database, $table); + + $typeMap = [ + 'group' => __('Groups:'), + 'event' => __('Events:'), + 'function' => __('Functions:'), + 'procedure' => __('Procedures:'), + 'table' => __('Tables:'), + 'view' => __('Views:'), + ]; + + return $this->template->render('navigation/item_unhide_dialog', [ + 'database' => $database, + 'table' => $table, + 'hidden' => $hidden, + 'types' => $typeMap, + 'item_type' => $itemType, + ]); + } + + /** + * @param string $database Database name + * @param string|null $table Table name + * @return array + */ + private function getHiddenItems(string $database, ?string $table): array + { + $navTable = Util::backquote($GLOBALS['cfgRelation']['db']) + . "." . Util::backquote($GLOBALS['cfgRelation']['navigationhiding']); + $sqlQuery = "SELECT `item_name`, `item_type` FROM " . $navTable + . " WHERE `username`='" + . $this->dbi->escapeString($GLOBALS['cfg']['Server']['user']) . "'" + . " AND `db_name`='" . $this->dbi->escapeString($database) . "'" + . " AND `table_name`='" + . (! empty($table) ? $this->dbi->escapeString($table) : '') . "'"; + $result = $this->relation->queryAsControlUser($sqlQuery, false); + + $hidden = []; + if ($result) { + while ($row = $this->dbi->fetchArray($result)) { + $type = $row['item_type']; + if (! isset($hidden[$type])) { + $hidden[$type] = []; + } + $hidden[$type][] = $row['item_name']; + } + } + $this->dbi->freeResult($result); + return $hidden; + } + + /** + * @return string Logo source + */ + private function getLogoSource(): string + { + global $pmaThemeImage; + + if (isset($pmaThemeImage) && @file_exists($pmaThemeImage . 'logo_left.png')) { + return $pmaThemeImage . 'logo_left.png'; + } elseif (isset($pmaThemeImage) && @file_exists($pmaThemeImage . 'pma_logo2.png')) { + return $pmaThemeImage . 'pma_logo2.png'; + } + return ''; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/NavigationTree.php b/srcs/phpmyadmin/libraries/classes/Navigation/NavigationTree.php new file mode 100644 index 0000000..289ed6e --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/NavigationTree.php @@ -0,0 +1,1581 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation; + +use PhpMyAdmin\CheckUserPrivileges; +use PhpMyAdmin\DatabaseInterface; +use PhpMyAdmin\Navigation\Nodes\Node; +use PhpMyAdmin\Navigation\Nodes\NodeDatabase; +use PhpMyAdmin\Navigation\Nodes\NodeTable; +use PhpMyAdmin\Navigation\Nodes\NodeTableContainer; +use PhpMyAdmin\Navigation\Nodes\NodeViewContainer; +use PhpMyAdmin\RecentFavoriteTable; +use PhpMyAdmin\Response; +use PhpMyAdmin\Template; +use PhpMyAdmin\Url; +use PhpMyAdmin\Util; + +/** + * Displays a collapsible of database objects in the navigation frame + * + * @package PhpMyAdmin-Navigation + */ +class NavigationTree +{ + /** + * @var Node Reference to the root node of the tree + */ + private $tree; + /** + * @var array The actual paths to all expanded nodes in the tree + * This does not include nodes created after the grouping + * of nodes has been performed + */ + private $aPath = []; + /** + * @var array The virtual paths to all expanded nodes in the tree + * This includes nodes created after the grouping of + * nodes has been performed + */ + private $vPath = []; + /** + * @var int Position in the list of databases, + * used for pagination + */ + private $pos; + /** + * @var array The names of the type of items that are being paginated on + * the second level of the navigation tree. These may be + * tables, views, functions, procedures or events. + */ + private $pos2Name = []; + /** + * @var array The positions of nodes in the lists of tables, views, + * routines or events used for pagination + */ + private $pos2Value = []; + /** + * @var array The names of the type of items that are being paginated + * on the second level of the navigation tree. + * These may be columns or indexes + */ + private $pos3Name = []; + /** + * @var array The positions of nodes in the lists of columns or indexes + * used for pagination + */ + private $pos3Value = []; + /** + * @var string The search clause to use in SQL queries for + * fetching databases + * Used by the asynchronous fast filter + */ + private $searchClause = ''; + /** + * @var string The search clause to use in SQL queries for + * fetching nodes + * Used by the asynchronous fast filter + */ + private $searchClause2 = ''; + /** + * @var bool Whether a warning was raised for large item groups + * which can affect performance. + */ + private $largeGroupWarning = false; + + /** + * @var Template + */ + private $template; + + /** + * @var DatabaseInterface + */ + private $dbi; + + /** + * NavigationTree constructor. + * @param Template $template Template instance + * @param DatabaseInterface $dbi DatabaseInterface instance + */ + public function __construct($template, $dbi) + { + $this->template = $template; + $this->dbi = $dbi; + + $checkUserPrivileges = new CheckUserPrivileges($this->dbi); + $checkUserPrivileges->getPrivileges(); + + // Save the position at which we are in the database list + if (isset($_POST['pos'])) { + $this->pos = (int) $_POST['pos']; + } elseif (isset($_GET['pos'])) { + $this->pos = (int) $_GET['pos']; + } + if (! isset($this->pos)) { + $this->pos = $this->getNavigationDbPos(); + } + // Get the active node + if (isset($_REQUEST['aPath'])) { + $this->aPath[0] = $this->parsePath($_REQUEST['aPath']); + $this->pos2Name[0] = $_REQUEST['pos2_name']; + $this->pos2Value[0] = $_REQUEST['pos2_value']; + if (isset($_REQUEST['pos3_name'])) { + $this->pos3Name[0] = $_REQUEST['pos3_name']; + $this->pos3Value[0] = $_REQUEST['pos3_value']; + } + } else { + if (isset($_POST['n0_aPath'])) { + $count = 0; + while (isset($_POST['n' . $count . '_aPath'])) { + $this->aPath[$count] = $this->parsePath( + $_POST['n' . $count . '_aPath'] + ); + $index = 'n' . $count . '_pos2_'; + $this->pos2Name[$count] = $_POST[$index . 'name']; + $this->pos2Value[$count] = $_POST[$index . 'value']; + $index = 'n' . $count . '_pos3_'; + if (isset($_POST[$index])) { + $this->pos3Name[$count] = $_POST[$index . 'name']; + $this->pos3Value[$count] = $_POST[$index . 'value']; + } + $count++; + } + } + } + if (isset($_REQUEST['vPath'])) { + $this->vPath[0] = $this->parsePath($_REQUEST['vPath']); + } else { + if (isset($_POST['n0_vPath'])) { + $count = 0; + while (isset($_POST['n' . $count . '_vPath'])) { + $this->vPath[$count] = $this->parsePath( + $_POST['n' . $count . '_vPath'] + ); + $count++; + } + } + } + if (isset($_REQUEST['searchClause'])) { + $this->searchClause = $_REQUEST['searchClause']; + } + if (isset($_REQUEST['searchClause2'])) { + $this->searchClause2 = $_REQUEST['searchClause2']; + } + // Initialise the tree by creating a root node + $node = NodeFactory::getInstance('NodeDatabaseContainer', 'root'); + $this->tree = $node; + if ($GLOBALS['cfg']['NavigationTreeEnableGrouping'] + && $GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + ) { + $this->tree->separator = $GLOBALS['cfg']['NavigationTreeDbSeparator']; + $this->tree->separatorDepth = 10000; + } + } + + /** + * Returns the database position for the page selector + * + * @return int + */ + private function getNavigationDbPos() + { + $retval = 0; + + if (strlen($GLOBALS['db']) == 0) { + return $retval; + } + + /* + * @todo describe a scenario where this code is executed + */ + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $dbSeparator = $this->dbi->escapeString( + $GLOBALS['cfg']['NavigationTreeDbSeparator'] + ); + $query = "SELECT (COUNT(DB_first_level) DIV %d) * %d "; + $query .= "from ( "; + $query .= " SELECT distinct SUBSTRING_INDEX(SCHEMA_NAME, "; + $query .= " '%s', 1) "; + $query .= " DB_first_level "; + $query .= " FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= " WHERE `SCHEMA_NAME` < '%s' "; + $query .= ") t "; + + $retval = $this->dbi->fetchValue( + sprintf( + $query, + (int) $GLOBALS['cfg']['FirstLevelNavigationItems'], + (int) $GLOBALS['cfg']['FirstLevelNavigationItems'], + $dbSeparator, + $this->dbi->escapeString($GLOBALS['db']) + ) + ); + + return $retval; + } + + $prefixMap = []; + if ($GLOBALS['dbs_to_test'] === false) { + $handle = $this->dbi->tryQuery("SHOW DATABASES"); + if ($handle !== false) { + while ($arr = $this->dbi->fetchArray($handle)) { + if (strcasecmp($arr[0], $GLOBALS['db']) >= 0) { + break; + } + + $prefix = strstr( + $arr[0], + $GLOBALS['cfg']['NavigationTreeDbSeparator'], + true + ); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + } + } + } else { + $databases = []; + foreach ($GLOBALS['dbs_to_test'] as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $this->dbi->tryQuery($query); + if ($handle === false) { + continue; + } + while ($arr = $this->dbi->fetchArray($handle)) { + $databases[] = $arr[0]; + } + } + sort($databases); + foreach ($databases as $database) { + if (strcasecmp($database, $GLOBALS['db']) >= 0) { + break; + } + + $prefix = strstr( + $database, + $GLOBALS['cfg']['NavigationTreeDbSeparator'], + true + ); + if ($prefix === false) { + $prefix = $database; + } + $prefixMap[$prefix] = 1; + } + } + + $navItems = (int) $GLOBALS['cfg']['FirstLevelNavigationItems']; + $retval = (int) floor(count($prefixMap) / $navItems) * $navItems; + + return $retval; + } + + /** + * Converts an encoded path to a node in string format to an array + * + * @param string $string The path to parse + * + * @return array + */ + private function parsePath($string) + { + $path = explode('.', $string); + foreach ($path as $key => $value) { + $path[$key] = base64_decode($value); + } + + return $path; + } + + /** + * Generates the tree structure so that it can be rendered later + * + * @return Node|false The active node or false in case of failure + */ + private function buildPath() + { + $retval = $this->tree; + + // Add all databases unconditionally + $data = $this->tree->getData( + 'databases', + $this->pos, + $this->searchClause + ); + $hiddenCounts = $this->tree->getNavigationHidingData(); + foreach ($data as $db) { + $node = NodeFactory::getInstance('NodeDatabase', $db); + if (isset($hiddenCounts[$db])) { + $node->setHiddenCount($hiddenCounts[$db]); + } + $this->tree->addChild($node); + } + + // Whether build other parts of the tree depends + // on whether we have any paths in $this->_aPath + foreach ($this->aPath as $key => $path) { + $retval = $this->buildPathPart( + $path, + $this->pos2Name[$key], + $this->pos2Value[$key], + isset($this->pos3Name[$key]) ? $this->pos3Name[$key] : '', + isset($this->pos3Value[$key]) ? $this->pos3Value[$key] : '' + ); + } + + return $retval; + } + + /** + * Builds a branch of the tree + * + * @param array $path A paths pointing to the branch + * of the tree that needs to be built + * @param string $type2 The type of item being paginated on + * the second level of the tree + * @param int $pos2 The position for the pagination of + * the branch at the second level of the tree + * @param string $type3 The type of item being paginated on + * the third level of the tree + * @param int $pos3 The position for the pagination of + * the branch at the third level of the tree + * + * @return Node|bool The active node or false in case of failure, true if the path contains <= 1 items + */ + private function buildPathPart(array $path, $type2, $pos2, $type3, $pos3) + { + if (empty($pos2)) { + $pos2 = 0; + } + if (empty($pos3)) { + $pos3 = 0; + } + + $retval = true; + if (count($path) <= 1) { + return $retval; + } + + array_shift($path); // remove 'root' + /** @var NodeDatabase $db */ + $db = $this->tree->getChild($path[0]); + $retval = $db; + + if ($db === false) { + return false; + } + + $containers = $this->addDbContainers($db, $type2, $pos2); + + array_shift($path); // remove db + + if ((count($path) <= 0 || ! array_key_exists($path[0], $containers)) + && count($containers) != 1 + ) { + return $retval; + } + + if (count($containers) === 1) { + $container = array_shift($containers); + } else { + $container = $db->getChild($path[0], true); + if ($container === false) { + return false; + } + } + $retval = $container; + + if (count($container->children) <= 1) { + $dbData = $db->getData( + $container->realName, + $pos2, + $this->searchClause2 + ); + foreach ($dbData as $item) { + switch ($container->realName) { + case 'events': + $node = NodeFactory::getInstance( + 'NodeEvent', + $item + ); + break; + case 'functions': + $node = NodeFactory::getInstance( + 'NodeFunction', + $item + ); + break; + case 'procedures': + $node = NodeFactory::getInstance( + 'NodeProcedure', + $item + ); + break; + case 'tables': + $node = NodeFactory::getInstance( + 'NodeTable', + $item + ); + break; + case 'views': + $node = NodeFactory::getInstance( + 'NodeView', + $item + ); + break; + default: + break; + } + if (isset($node)) { + if ($type2 == $container->realName) { + $node->pos2 = $pos2; + } + $container->addChild($node); + } + } + } + if (count($path) > 1 && $path[0] != 'tables') { + $retval = false; + + return $retval; + } + + array_shift($path); // remove container + if (count($path) <= 0) { + return $retval; + } + + /** @var NodeTable $table */ + $table = $container->getChild($path[0], true); + if ($table === false) { + if (! $db->getPresence('tables', $path[0])) { + return false; + } + + $node = NodeFactory::getInstance( + 'NodeTable', + $path[0] + ); + if ($type2 == $container->realName) { + $node->pos2 = $pos2; + } + $container->addChild($node); + $table = $container->getChild($path[0], true); + } + $retval = $table; + $containers = $this->addTableContainers( + $table, + $pos2, + $type3, + $pos3 + ); + array_shift($path); // remove table + if (count($path) <= 0 + || ! array_key_exists($path[0], $containers) + ) { + return $retval; + } + + $container = $table->getChild($path[0], true); + $retval = $container; + $tableData = $table->getData( + $container->realName, + $pos3 + ); + foreach ($tableData as $item) { + switch ($container->realName) { + case 'indexes': + $node = NodeFactory::getInstance( + 'NodeIndex', + $item + ); + break; + case 'columns': + $node = NodeFactory::getInstance( + 'NodeColumn', + $item + ); + break; + case 'triggers': + $node = NodeFactory::getInstance( + 'NodeTrigger', + $item + ); + break; + default: + break; + } + if (isset($node)) { + $node->pos2 = $container->parent->pos2; + if ($type3 == $container->realName) { + $node->pos3 = $pos3; + } + $container->addChild($node); + } + } + + return $retval; + } + + /** + * Adds containers to a node that is a table + * + * References to existing children are returned + * if this function is called twice on the same node + * + * @param NodeTable $table The table node, new containers will be + * attached to this node + * @param int $pos2 The position for the pagination of + * the branch at the second level of the tree + * @param string $type3 The type of item being paginated on + * the third level of the tree + * @param int $pos3 The position for the pagination of + * the branch at the third level of the tree + * + * @return array An array of new nodes + */ + private function addTableContainers($table, $pos2, $type3, $pos3) + { + $retval = []; + if ($table->hasChildren(true) == 0) { + if ($table->getPresence('columns')) { + $retval['columns'] = NodeFactory::getInstance( + 'NodeColumnContainer' + ); + } + if ($table->getPresence('indexes')) { + $retval['indexes'] = NodeFactory::getInstance( + 'NodeIndexContainer' + ); + } + if ($table->getPresence('triggers')) { + $retval['triggers'] = NodeFactory::getInstance( + 'NodeTriggerContainer' + ); + } + // Add all new Nodes to the tree + foreach ($retval as $node) { + $node->pos2 = $pos2; + if ($type3 == $node->realName) { + $node->pos3 = $pos3; + } + $table->addChild($node); + } + } else { + foreach ($table->children as $node) { + if ($type3 == $node->realName) { + $node->pos3 = $pos3; + } + $retval[$node->realName] = $node; + } + } + + return $retval; + } + + /** + * Adds containers to a node that is a database + * + * References to existing children are returned + * if this function is called twice on the same node + * + * @param NodeDatabase $db The database node, new containers will be + * attached to this node + * @param string $type The type of item being paginated on + * the second level of the tree + * @param int $pos2 The position for the pagination of + * the branch at the second level of the tree + * + * @return array An array of new nodes + */ + private function addDbContainers($db, $type, $pos2) + { + // Get items to hide + $hidden = $db->getHiddenItems('group'); + if (! $GLOBALS['cfg']['NavigationTreeShowTables'] + && ! in_array('tables', $hidden) + ) { + $hidden[] = 'tables'; + } + if (! $GLOBALS['cfg']['NavigationTreeShowViews'] + && ! in_array('views', $hidden) + ) { + $hidden[] = 'views'; + } + if (! $GLOBALS['cfg']['NavigationTreeShowFunctions'] + && ! in_array('functions', $hidden) + ) { + $hidden[] = 'functions'; + } + if (! $GLOBALS['cfg']['NavigationTreeShowProcedures'] + && ! in_array('procedures', $hidden) + ) { + $hidden[] = 'procedures'; + } + if (! $GLOBALS['cfg']['NavigationTreeShowEvents'] + && ! in_array('events', $hidden) + ) { + $hidden[] = 'events'; + } + + $retval = []; + if ($db->hasChildren(true) == 0) { + if (! in_array('tables', $hidden) && $db->getPresence('tables')) { + $retval['tables'] = NodeFactory::getInstance( + 'NodeTableContainer' + ); + } + if (! in_array('views', $hidden) && $db->getPresence('views')) { + $retval['views'] = NodeFactory::getInstance( + 'NodeViewContainer' + ); + } + if (! in_array('functions', $hidden) && $db->getPresence('functions')) { + $retval['functions'] = NodeFactory::getInstance( + 'NodeFunctionContainer' + ); + } + if (! in_array('procedures', $hidden) && $db->getPresence('procedures')) { + $retval['procedures'] = NodeFactory::getInstance( + 'NodeProcedureContainer' + ); + } + if (! in_array('events', $hidden) && $db->getPresence('events')) { + $retval['events'] = NodeFactory::getInstance( + 'NodeEventContainer' + ); + } + // Add all new Nodes to the tree + foreach ($retval as $node) { + if ($type == $node->realName) { + $node->pos2 = $pos2; + } + $db->addChild($node); + } + } else { + foreach ($db->children as $node) { + if ($type == $node->realName) { + $node->pos2 = $pos2; + } + $retval[$node->realName] = $node; + } + } + + return $retval; + } + + /** + * Recursively groups tree nodes given a separator + * + * @param mixed $node The node to group or null + * to group the whole tree. If + * passed as an argument, $node + * must be of type CONTAINER + * + * @return void + */ + public function groupTree($node = null) + { + if (! isset($node)) { + $node = $this->tree; + } + $this->groupNode($node); + foreach ($node->children as $child) { + $this->groupTree($child); + } + } + + /** + * Recursively groups tree nodes given a separator + * + * @param Node $node The node to group + * + * @return void + */ + public function groupNode($node) + { + if ($node->type != Node::CONTAINER + || ! $GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + return; + } + + $separators = []; + if (is_array($node->separator)) { + $separators = $node->separator; + } else { + if (strlen($node->separator)) { + $separators[] = $node->separator; + } + } + $prefixes = []; + if ($node->separatorDepth > 0) { + foreach ($node->children as $child) { + $prefixPos = false; + foreach ($separators as $separator) { + $sepPos = mb_strpos((string) $child->name, $separator); + if ($sepPos != false + && $sepPos != mb_strlen($child->name) + && $sepPos != 0 + && ($prefixPos === false || $sepPos < $prefixPos) + ) { + $prefixPos = $sepPos; + } + } + if ($prefixPos !== false) { + $prefix = mb_substr($child->name, 0, $prefixPos); + if (! isset($prefixes[$prefix])) { + $prefixes[$prefix] = 1; + } else { + $prefixes[$prefix]++; + } + } + //Bug #4375: Check if prefix is the name of a DB, to create a group. + foreach ($node->children as $otherChild) { + if (array_key_exists($otherChild->name, $prefixes)) { + $prefixes[$otherChild->name]++; + } + } + } + //Check if prefix is the name of a DB, to create a group. + foreach ($node->children as $child) { + if (array_key_exists($child->name, $prefixes)) { + $prefixes[$child->name]++; + } + } + } + // It is not a group if it has only one item + foreach ($prefixes as $key => $value) { + if ($value == 1) { + unset($prefixes[$key]); + } + } + // rfe #1634 Don't group if there's only one group and no other items + if (count($prefixes) === 1) { + $keys = array_keys($prefixes); + $key = $keys[0]; + if ($prefixes[$key] == count($node->children) - 1) { + unset($prefixes[$key]); + } + } + if (count($prefixes)) { + /** @var Node[] $groups */ + $groups = []; + foreach ($prefixes as $key => $value) { + // warn about large groups + if ($value > 500 && ! $this->largeGroupWarning) { + trigger_error( + __( + 'There are large item groups in navigation panel which ' + . 'may affect the performance. Consider disabling item ' + . 'grouping in the navigation panel.' + ), + E_USER_WARNING + ); + $this->largeGroupWarning = true; + } + + $groups[$key] = new Node( + htmlspecialchars((string) $key), + Node::CONTAINER, + true + ); + $groups[$key]->separator = $node->separator; + $groups[$key]->separatorDepth = $node->separatorDepth - 1; + $groups[$key]->icon = Util::getImage( + 'b_group', + __('Groups') + ); + $groups[$key]->pos2 = $node->pos2; + $groups[$key]->pos3 = $node->pos3; + if ($node instanceof NodeTableContainer + || $node instanceof NodeViewContainer + ) { + $tblGroup = '&tbl_group=' . urlencode($key); + $groups[$key]->links = [ + 'text' => $node->links['text'] . $tblGroup, + 'icon' => $node->links['icon'] . $tblGroup, + ]; + } + $node->addChild($groups[$key]); + foreach ($separators as $separator) { + $separatorLength = strlen($separator); + // FIXME: this could be more efficient + foreach ($node->children as $child) { + $keySeparatorLength = mb_strlen((string) $key) + $separatorLength; + $nameSubstring = mb_substr( + (string) $child->name, + 0, + $keySeparatorLength + ); + if (($nameSubstring != $key . $separator + && $child->name != $key) + || $child->type != Node::OBJECT + ) { + continue; + } + $class = get_class($child); + $className = substr($class, strrpos($class, '\\') + 1); + unset($class); + $newChild = NodeFactory::getInstance( + $className, + mb_substr( + $child->name, + $keySeparatorLength + ) + ); + + if ($newChild instanceof NodeDatabase + && $child->getHiddenCount() > 0 + ) { + $newChild->setHiddenCount($child->getHiddenCount()); + } + + $newChild->realName = $child->realName; + $newChild->icon = $child->icon; + $newChild->links = $child->links; + $newChild->pos2 = $child->pos2; + $newChild->pos3 = $child->pos3; + $groups[$key]->addChild($newChild); + foreach ($child->children as $elm) { + $newChild->addChild($elm); + } + $node->removeChild($child->name); + } + } + } + foreach ($prefixes as $key => $value) { + $this->groupNode($groups[$key]); + $groups[$key]->classes = "navGroup"; + } + } + } + + /** + * Renders a state of the tree, used in light mode when + * either JavaScript and/or Ajax are disabled + * + * @return string HTML code for the navigation tree + */ + public function renderState() + { + $this->buildPath(); + + $quickWarp = $this->quickWarp(); + $fastFilter = $this->fastFilterHtml($this->tree); + $controls = ''; + if ($GLOBALS['cfg']['NavigationTreeEnableExpansion']) { + $controls = $this->controls(); + } + $pageSelector = $this->getPageSelector($this->tree); + + $this->groupTree(); + $children = $this->tree->children; + usort($children, [ + NavigationTree::class, + 'sortNode', + ]); + $this->setVisibility(); + + $nodes = ''; + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i == 0) { + $nodes .= $this->renderNode($children[0], true, 'first'); + } else { + if ($i + 1 != $nbChildren) { + $nodes .= $this->renderNode($children[$i], true); + } else { + $nodes .= $this->renderNode($children[$i], true, 'last'); + } + } + } + + return $this->template->render('navigation/tree/state', [ + 'quick_warp' => $quickWarp, + 'fast_filter' => $fastFilter, + 'controls' => $controls, + 'page_selector' => $pageSelector, + 'nodes' => $nodes, + ]); + } + + /** + * Renders a part of the tree, used for Ajax requests in light mode + * + * @return string|false HTML code for the navigation tree + */ + public function renderPath() + { + $node = $this->buildPath(); + if ($node !== false) { + $this->groupTree(); + + $listContent = $this->fastFilterHtml($node); + $listContent .= $this->getPageSelector($node); + $children = $node->children; + usort($children, [ + NavigationTree::class, + 'sortNode', + ]); + + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i + 1 != $nbChildren) { + $listContent .= $this->renderNode($children[$i], true); + } else { + $listContent .= $this->renderNode($children[$i], true, 'last'); + } + } + + if (! $GLOBALS['cfg']['ShowDatabasesNavigationAsTree']) { + $parents = $node->parents(true); + $parentName = $parents[0]->realName; + } + } + + if (! empty($this->searchClause) || ! empty($this->searchClause2)) { + $results = 0; + if (! empty($this->searchClause2)) { + if (is_object($node->realParent())) { + $results = $node->realParent() + ->getPresence( + $node->realName, + $this->searchClause2 + ); + } + } else { + $results = $this->tree->getPresence( + 'databases', + $this->searchClause + ); + } + $results = sprintf( + _ngettext( + '%s result found', + '%s results found', + $results + ), + $results + ); + Response::getInstance() + ->addJSON( + 'results', + $results + ); + } + + if ($node !== false) { + return $this->template->render('navigation/tree/path', [ + 'has_search_results' => ! empty($this->searchClause) || ! empty($this->searchClause2), + 'list_content' => $listContent ?? '', + 'is_tree' => $GLOBALS['cfg']['ShowDatabasesNavigationAsTree'], + 'parent_name' => $parentName ?? '', + ]); + } + return false; + } + + /** + * Renders the parameters that are required on the client + * side to know which page(s) we will be requesting data from + * + * @param Node $node The node to create the pagination parameters for + * + * @return string + */ + private function getPaginationParamsHtml($node) + { + $retval = ''; + $paths = $node->getPaths(); + if (isset($paths['aPath_clean'][2])) { + $retval .= "<span class='hide pos2_name'>"; + $retval .= $paths['aPath_clean'][2]; + $retval .= "</span>"; + $retval .= "<span class='hide pos2_value'>"; + $retval .= htmlspecialchars((string) $node->pos2); + $retval .= "</span>"; + } + if (isset($paths['aPath_clean'][4])) { + $retval .= "<span class='hide pos3_name'>"; + $retval .= $paths['aPath_clean'][4]; + $retval .= "</span>"; + $retval .= "<span class='hide pos3_value'>"; + $retval .= htmlspecialchars((string) $node->pos3); + $retval .= "</span>"; + } + + return $retval; + } + + /** + * Finds whether given tree matches this tree. + * + * @param array $tree Tree to check + * @param array $paths Paths to check + * + * @return boolean + */ + private function findTreeMatch(array $tree, array $paths) + { + $match = false; + foreach ($tree as $path) { + $match = true; + foreach ($paths as $key => $part) { + if (! isset($path[$key]) || $part != $path[$key]) { + $match = false; + break; + } + } + if ($match) { + break; + } + } + + return $match; + } + + /** + * Renders a single node or a branch of the tree + * + * @param Node $node The node to render + * @param bool $recursive Bool: Whether to render a single node or a branch + * @param string $class An additional class for the list item + * + * @return string HTML code for the tree node or branch + */ + private function renderNode($node, $recursive, $class = '') + { + $retval = ''; + $paths = $node->getPaths(); + if ($node->hasSiblings() + || $node->realParent() === false + ) { + $response = Response::getInstance(); + if ($node->type == Node::CONTAINER + && count($node->children) === 0 + && ! $response->isAjax() + ) { + return ''; + } + $retval .= '<li class="' . trim($class . ' ' . $node->classes) . '">'; + $sterile = [ + 'events', + 'triggers', + 'functions', + 'procedures', + 'views', + 'columns', + 'indexes', + ]; + $parentName = ''; + $parents = $node->parents(false, true); + if (count($parents)) { + $parentName = $parents[0]->realName; + } + // if node name itself is in sterile, then allow + if ($node->isGroup + || (! in_array($parentName, $sterile) && ! $node->isNew) + || in_array($node->realName, $sterile) + ) { + $retval .= "<div class='block'>"; + $iClass = ''; + if ($class == 'first') { + $iClass = " class='first'"; + } + $retval .= "<i$iClass></i>"; + if (strpos($class, 'last') === false) { + $retval .= "<b></b>"; + } + + $match = $this->findTreeMatch( + $this->vPath, + $paths['vPath_clean'] + ); + + $retval .= '<a class="' . $node->getCssClasses($match) . '"'; + $retval .= " href='#'>"; + $retval .= "<span class='hide aPath'>"; + $retval .= $paths['aPath']; + $retval .= "</span>"; + $retval .= "<span class='hide vPath'>"; + $retval .= $paths['vPath']; + $retval .= "</span>"; + $retval .= "<span class='hide pos'>"; + $retval .= $this->pos; + $retval .= "</span>"; + $retval .= $this->getPaginationParamsHtml($node); + if ($GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + || $parentName != 'root' + ) { + $retval .= $node->getIcon($match); + } + + $retval .= "</a>"; + $retval .= "</div>"; + } else { + $retval .= "<div class='block'>"; + $iClass = ''; + if ($class == 'first') { + $iClass = " class='first'"; + } + $retval .= "<i$iClass></i>"; + $retval .= $this->getPaginationParamsHtml($node); + $retval .= "</div>"; + } + + $linkClass = ''; + $haveAjax = [ + 'functions', + 'procedures', + 'events', + 'triggers', + 'indexes', + ]; + $parent = $node->parents(false, true); + $isNewView = $parent[0]->realName == 'views' && $node->isNew === true; + if ($parent[0]->type == Node::CONTAINER + && (in_array($parent[0]->realName, $haveAjax) || $isNewView) + ) { + $linkClass = ' ajax'; + } + + if ($node->type == Node::CONTAINER) { + $retval .= "<i>"; + } + + $divClass = ''; + + if (isset($node->links['icon']) && ! empty($node->links['icon'])) { + $iconLinks = $node->links['icon']; + $icons = $node->icon; + if (! is_array($iconLinks)) { + $iconLinks = [$iconLinks]; + $icons = [$icons]; + } + + if (count($icons) > 1) { + $divClass = 'double'; + } + } + + $retval .= "<div class='block " . $divClass . "'>"; + + if (isset($node->links['icon']) && ! empty($node->links['icon'])) { + $args = []; + foreach ($node->parents(true) as $parent) { + $args[] = urlencode($parent->realName); + } + + foreach ($icons as $key => $icon) { + $link = vsprintf($iconLinks[$key], $args); + if ($linkClass != '') { + $retval .= "<a class='$linkClass' href='$link'>"; + $retval .= "{$icon}</a>"; + } else { + $retval .= "<a href='$link'>{$icon}</a>"; + } + } + } else { + $retval .= "<u>{$node->icon}</u>"; + } + $retval .= "</div>"; + + if (isset($node->links['text'])) { + $args = []; + foreach ($node->parents(true) as $parent) { + $args[] = urlencode($parent->realName); + } + $link = vsprintf($node->links['text'], $args); + $title = isset($node->links['title']) ? $node->links['title'] : ''; + if ($node->type == Node::CONTAINER) { + $retval .= " <a class='hover_show_full' href='$link'>"; + $retval .= htmlspecialchars($node->name); + $retval .= "</a>"; + } else { + $retval .= "<a class='hover_show_full$linkClass' href='$link'"; + $retval .= " title='$title'>"; + $retval .= htmlspecialchars($node->displayName ?? $node->realName); + $retval .= "</a>"; + } + } else { + $retval .= " {$node->name}"; + } + $retval .= $node->getHtmlForControlButtons(); + if ($node->type == Node::CONTAINER) { + $retval .= "</i>"; + } + $retval .= '<div class="clearfloat"></div>'; + $wrap = true; + } else { + $node->visible = true; + $wrap = false; + $retval .= $this->getPaginationParamsHtml($node); + } + + if ($recursive) { + $hide = ''; + if (! $node->visible) { + $hide = " style='display: none;'"; + } + $children = $node->children; + usort( + $children, + [ + NavigationTree::class, + 'sortNode', + ] + ); + $buffer = ''; + $extraClass = ''; + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i + 1 == $nbChildren) { + $extraClass = ' last'; + } + $buffer .= $this->renderNode( + $children[$i], + true, + $children[$i]->classes . $extraClass + ); + } + if (! empty($buffer)) { + if ($wrap) { + $retval .= "<div$hide class='list_container'><ul>"; + } + $retval .= $this->fastFilterHtml($node); + $retval .= $this->getPageSelector($node); + $retval .= $buffer; + if ($wrap) { + $retval .= "</ul></div>"; + } + } + } + if ($node->hasSiblings()) { + $retval .= "</li>"; + } + + return $retval; + } + + /** + * Renders a database select box like the pre-4.0 navigation panel + * + * @return string HTML code + */ + public function renderDbSelect() + { + $this->buildPath(); + + $quickWarp = $this->quickWarp(); + + $this->tree->isGroup = false; + + // Provide for pagination in database select + $listNavigator = Util::getListNavigator( + $this->tree->getPresence('databases', ''), + $this->pos, + ['server' => $GLOBALS['server']], + 'navigation.php', + 'frame_navigation', + $GLOBALS['cfg']['FirstLevelNavigationItems'], + 'pos', + ['dbselector'] + ); + + $children = $this->tree->children; + $selected = $GLOBALS['db']; + $options = ''; + foreach ($children as $node) { + if ($node->isNew) { + continue; + } + $paths = $node->getPaths(); + if (isset($node->links['text'])) { + $title = isset($node->links['title']) ? '' : $node->links['title']; + $options .= '<option value="' + . htmlspecialchars($node->realName) . '"' + . ' title="' . htmlspecialchars($title) . '"' + . ' apath="' . $paths['aPath'] . '"' + . ' vpath="' . $paths['vPath'] . '"' + . ' pos="' . $this->pos . '"'; + if ($node->realName == $selected) { + $options .= ' selected'; + } + $options .= '>' . htmlspecialchars($node->realName); + $options .= '</option>'; + } + } + + $children = $this->tree->children; + usort($children, [ + NavigationTree::class, + 'sortNode', + ]); + $this->setVisibility(); + + $nodes = ''; + for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { + if ($i == 0) { + $nodes .= $this->renderNode($children[0], true, 'first'); + } else { + if ($i + 1 != $nbChildren) { + $nodes .= $this->renderNode($children[$i], true); + } else { + $nodes .= $this->renderNode($children[$i], true, 'last'); + } + } + } + + return $this->template->render('navigation/tree/database_select', [ + 'quick_warp' => $quickWarp, + 'list_navigator' => $listNavigator, + 'server' => $GLOBALS['server'], + 'options' => $options, + 'nodes' => $nodes, + ]); + } + + /** + * Makes some nodes visible based on the which node is active + * + * @return void + */ + private function setVisibility() + { + foreach ($this->vPath as $path) { + $node = $this->tree; + foreach ($path as $value) { + $child = $node->getChild($value); + if ($child !== false) { + $child->visible = true; + $node = $child; + } + } + } + } + + /** + * Generates the HTML code for displaying the fast filter for tables + * + * @param Node $node The node for which to generate the fast filter html + * + * @return string LI element used for the fast filter + */ + private function fastFilterHtml($node) + { + $retval = ''; + $filterDbMin + = (int) $GLOBALS['cfg']['NavigationTreeDisplayDbFilterMinimum']; + $filterItemMin + = (int) $GLOBALS['cfg']['NavigationTreeDisplayItemFilterMinimum']; + if ($node === $this->tree + && $this->tree->getPresence() >= $filterDbMin + ) { + $urlParams = [ + 'pos' => 0, + ]; + $retval .= '<li class="fast_filter db_fast_filter">'; + $retval .= '<form class="ajax fast_filter">'; + $retval .= Url::getHiddenInputs($urlParams); + $retval .= '<input class="searchClause" type="text"'; + $retval .= ' name="searchClause" accesskey="q"'; + $retval .= " placeholder='" + . __("Type to filter these, Enter to search all"); + $retval .= "'>"; + $retval .= '<span title="' . __('Clear fast filter') . '">X</span>'; + $retval .= "</form>"; + $retval .= "</li>"; + + return $retval; + } + + if (($node->type == Node::CONTAINER + && ($node->realName == 'tables' + || $node->realName == 'views' + || $node->realName == 'functions' + || $node->realName == 'procedures' + || $node->realName == 'events')) + && method_exists($node->realParent(), 'getPresence') + && $node->realParent()->getPresence($node->realName) >= $filterItemMin + ) { + $paths = $node->getPaths(); + $urlParams = [ + 'pos' => $this->pos, + 'aPath' => $paths['aPath'], + 'vPath' => $paths['vPath'], + 'pos2_name' => $node->realName, + 'pos2_value' => 0, + ]; + $retval .= "<li class='fast_filter'>"; + $retval .= "<form class='ajax fast_filter'>"; + $retval .= Url::getHiddenFields($urlParams); + $retval .= "<input class='searchClause' type='text'"; + $retval .= " name='searchClause2'"; + $retval .= " placeholder='" + . __("Type to filter these, Enter to search all") . "'>"; + $retval .= "<span title='" . __('Clear fast filter') . "'>X</span>"; + $retval .= "</form>"; + $retval .= "</li>"; + } + + return $retval; + } + + /** + * Creates the code for displaying the controls + * at the top of the navigation tree + * + * @return string HTML code for the controls + */ + private function controls() + { + // always iconic + $showIcon = true; + $showText = false; + + $retval = '<!-- CONTROLS START -->'; + $retval .= '<li id="navigation_controls_outer">'; + $retval .= '<div id="navigation_controls">'; + $retval .= Util::getNavigationLink( + '#', + $showText, + __('Collapse all'), + $showIcon, + 's_collapseall', + 'pma_navigation_collapse' + ); + $syncImage = 's_unlink'; + $title = __('Link with main panel'); + if ($GLOBALS['cfg']['NavigationLinkWithMainPanel']) { + $syncImage = 's_link'; + $title = __('Unlink from main panel'); + } + $retval .= Util::getNavigationLink( + '#', + $showText, + $title, + $showIcon, + $syncImage, + 'pma_navigation_sync' + ); + $retval .= '</div>'; + $retval .= '</li>'; + $retval .= '<!-- CONTROLS ENDS -->'; + + return $retval; + } + + /** + * Generates the HTML code for displaying the list pagination + * + * @param Node $node The node for whose children the page + * selector will be created + * + * @return string + */ + private function getPageSelector($node) + { + $retval = ''; + if ($node === $this->tree) { + $retval .= Util::getListNavigator( + $this->tree->getPresence('databases', $this->searchClause), + $this->pos, + ['server' => $GLOBALS['server']], + 'navigation.php', + 'frame_navigation', + $GLOBALS['cfg']['FirstLevelNavigationItems'], + 'pos', + ['dbselector'] + ); + } else { + if ($node->type == Node::CONTAINER && ! $node->isGroup) { + $paths = $node->getPaths(); + + $level = isset($paths['aPath_clean'][4]) ? 3 : 2; + $urlParams = [ + 'aPath' => $paths['aPath'], + 'vPath' => $paths['vPath'], + 'pos' => $this->pos, + 'server' => $GLOBALS['server'], + 'pos2_name' => $paths['aPath_clean'][2], + ]; + if ($level == 3) { + $pos = $node->pos3; + $urlParams['pos2_value'] = $node->pos2; + $urlParams['pos3_name'] = $paths['aPath_clean'][4]; + } else { + $pos = $node->pos2; + } + $num = $node->realParent() + ->getPresence( + $node->realName, + $this->searchClause2 + ); + $retval .= Util::getListNavigator( + $num, + $pos, + $urlParams, + 'navigation.php', + 'frame_navigation', + $GLOBALS['cfg']['MaxNavigationItems'], + 'pos' . $level . '_value' + ); + } + } + + return $retval; + } + + /** + * Called by usort() for sorting the nodes in a container + * + * @param Node $a The first element used in the comparison + * @param Node $b The second element used in the comparison + * + * @return int See strnatcmp() and strcmp() + */ + public static function sortNode($a, $b) + { + if ($a->isNew) { + return -1; + } + + if ($b->isNew) { + return 1; + } + + if ($GLOBALS['cfg']['NaturalOrder']) { + return strnatcasecmp($a->name, $b->name); + } + + return strcasecmp($a->name, $b->name); + } + + /** + * Display quick warp links, contain Recents and Favorites + * + * @return string HTML code + */ + private function quickWarp() + { + $retval = '<div class="pma_quick_warp">'; + if ($GLOBALS['cfg']['NumRecentTables'] > 0) { + $retval .= RecentFavoriteTable::getInstance('recent') + ->getHtml(); + } + if ($GLOBALS['cfg']['NumFavoriteTables'] > 0) { + $retval .= RecentFavoriteTable::getInstance('favorite') + ->getHtml(); + } + $retval .= '<div class="clearfloat"></div>'; + $retval .= '</div>'; + + return $retval; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/NodeFactory.php b/srcs/phpmyadmin/libraries/classes/Navigation/NodeFactory.php new file mode 100644 index 0000000..3e751b2 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/NodeFactory.php @@ -0,0 +1,93 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * This class is responsible for creating Node objects + * + * @package PhpMyAdmin-navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation; + +use PhpMyAdmin\Navigation\Nodes\Node; + +/** + * Node factory - instantiates Node objects or objects derived from the Node class + * + * @package PhpMyAdmin-Navigation + */ +class NodeFactory +{ + protected static $namespace = 'PhpMyAdmin\\Navigation\\Nodes\\%s'; + /** + * Sanitizes the name of a Node class + * + * @param string $class The class name to be sanitized + * + * @return string + */ + private static function sanitizeClass($class) + { + if (! preg_match('@^Node\w*$@', $class)) { + $class = 'Node'; + trigger_error( + sprintf( + /* l10n: The word "Node" must not be translated here */ + __('Invalid class name "%1$s", using default of "Node"'), + $class + ), + E_USER_ERROR + ); + } + + return self::checkClass($class); + } + + /** + * Checks if a class exists and try to load it. + * Will return the default class name back if the + * file for some subclass is not available + * + * @param string $class The class name to check + * + * @return string + */ + private static function checkClass($class) + { + $class = sprintf(self::$namespace, $class); + + if (! class_exists($class)) { + $class = sprintf(self::$namespace, 'Node'); + trigger_error( + sprintf( + __('Could not load class "%1$s"'), + $class + ), + E_USER_ERROR + ); + } + + return $class; + } + + /** + * Instantiates a Node object + * + * @param string $class The name of the class to instantiate + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + * + * @return mixed + */ + public static function getInstance( + $class = 'Node', + $name = 'default', + $type = Node::OBJECT, + $isGroup = false + ) { + $class = self::sanitizeClass($class); + return new $class($name, $type, $isGroup); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/Node.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/Node.php new file mode 100644 index 0000000..3d68f65 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/Node.php @@ -0,0 +1,842 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree in the left frame + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\DatabaseInterface; +use PhpMyAdmin\Relation; +use PhpMyAdmin\Util; + +/** + * The Node is the building block for the collapsible navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class Node +{ + /** + * @var int Defines a possible node type + */ + public const CONTAINER = 0; + /** + * @var int Defines a possible node type + */ + public const OBJECT = 1; + /** + * @var string A non-unique identifier for the node + * This may be trimmed when grouping nodes + */ + public $name = ""; + /** + * @var string A non-unique identifier for the node + * This will never change after being assigned + */ + public $realName = ""; + /** + * @var int May be one of CONTAINER or OBJECT + */ + public $type = Node::OBJECT; + /** + * @var bool Whether this object has been created while grouping nodes + * Only relevant if the node is of type CONTAINER + */ + public $isGroup; + /** + * @var bool Whether to add a "display: none;" CSS + * rule to the node when rendering it + */ + public $visible = false; + /** + * @var Node A reference to the parent object of + * this node, NULL for the root node. + */ + public $parent; + /** + * @var Node[] An array of Node objects that are + * direct children of this node + */ + public $children = []; + /** + * @var Mixed A string used to group nodes, or an array of strings + * Only relevant if the node is of type CONTAINER + */ + public $separator = ''; + /** + * @var int How many time to recursively apply the grouping function + * Only relevant if the node is of type CONTAINER + */ + public $separatorDepth = 1; + /** + * @var string|array An IMG tag, used when rendering the node, an array for NodeTabl + */ + public $icon; + /** + * @var array An array of A tags, used when rendering the node + * The indexes in the array may be 'icon' and 'text' + */ + public $links; + /** + * @var string HTML title + */ + public $title; + /** + * @var string Extra CSS classes for the node + */ + public $classes = ''; + /** + * @var bool Whether this node is a link for creating new objects + */ + public $isNew = false; + /** + * @var int The position for the pagination of + * the branch at the second level of the tree + */ + public $pos2 = 0; + /** + * @var int The position for the pagination of + * the branch at the third level of the tree + */ + public $pos3 = 0; + + /** + * @var Relation + */ + protected $relation; + + /** + * @var string $displayName display name for the navigation tree + */ + public $displayName; + + /** + * Initialises the class by setting the mandatory variables + * + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + */ + public function __construct($name, $type = Node::OBJECT, $isGroup = false) + { + if (strlen((string) $name)) { + $this->name = $name; + $this->realName = $name; + } + if ($type === Node::CONTAINER) { + $this->type = Node::CONTAINER; + } + $this->isGroup = (bool) $isGroup; + $this->relation = new Relation($GLOBALS['dbi']); + } + + /** + * Adds a child node to this node + * + * @param Node $child A child node + * + * @return void + */ + public function addChild($child) + { + $this->children[] = $child; + $child->parent = $this; + } + + /** + * Returns a child node given it's name + * + * @param string $name The name of requested child + * @param bool $realName Whether to use the "realName" + * instead of "name" in comparisons + * + * @return false|Node The requested child node or false, + * if the requested node cannot be found + */ + public function getChild($name, $realName = false) + { + if ($realName) { + foreach ($this->children as $child) { + if ($child->realName == $name) { + return $child; + } + } + } else { + foreach ($this->children as $child) { + if ($child->name == $name) { + return $child; + } + } + } + + return false; + } + + /** + * Removes a child node from this node + * + * @param string $name The name of child to be removed + * + * @return void + */ + public function removeChild($name) + { + foreach ($this->children as $key => $child) { + if ($child->name == $name) { + unset($this->children[$key]); + break; + } + } + } + + /** + * Retrieves the parents for a node + * + * @param bool $self Whether to include the Node itself in the results + * @param bool $containers Whether to include nodes of type CONTAINER + * @param bool $groups Whether to include nodes which have $group == true + * + * @return array An array of parent Nodes + */ + public function parents($self = false, $containers = false, $groups = false) + { + $parents = []; + if ($self + && ($this->type != Node::CONTAINER || $containers) + && (! $this->isGroup || $groups) + ) { + $parents[] = $this; + } + $parent = $this->parent; + while ($parent !== null) { + if (($parent->type != Node::CONTAINER || $containers) + && (! $parent->isGroup || $groups) + ) { + $parents[] = $parent; + } + $parent = $parent->parent; + } + + return $parents; + } + + /** + * Returns the actual parent of a node. If used twice on an index or columns + * node, it will return the table and database nodes. The names of the returned + * nodes can be used in SQL queries, etc... + * + * @return Node|false + */ + public function realParent() + { + $retval = $this->parents(); + if (count($retval) <= 0) { + return false; + } + + return $retval[0]; + } + + /** + * This function checks if the node has children nodes associated with it + * + * @param bool $countEmptyContainers Whether to count empty child + * containers as valid children + * + * @return bool Whether the node has child nodes + */ + public function hasChildren($countEmptyContainers = true) + { + $retval = false; + if ($countEmptyContainers) { + if (count($this->children)) { + $retval = true; + } + } else { + foreach ($this->children as $child) { + if ($child->type == Node::OBJECT || $child->hasChildren(false)) { + $retval = true; + break; + } + } + } + + return $retval; + } + + /** + * Returns true if the node has some siblings (other nodes on the same tree + * level, in the same branch), false otherwise. + * The only exception is for nodes on + * the third level of the tree (columns and indexes), for which the function + * always returns true. This is because we want to render the containers + * for these nodes + * + * @return bool + */ + public function hasSiblings() + { + $retval = false; + $paths = $this->getPaths(); + if (count($paths['aPath_clean']) > 3) { + return true; + } + + foreach ($this->parent->children as $child) { + if ($child !== $this + && ($child->type == Node::OBJECT || $child->hasChildren(false)) + ) { + $retval = true; + break; + } + } + + return $retval; + } + + /** + * Returns the number of child nodes that a node has associated with it + * + * @return int The number of children nodes + */ + public function numChildren() + { + $retval = 0; + foreach ($this->children as $child) { + if ($child->type == Node::OBJECT) { + $retval++; + } else { + $retval += $child->numChildren(); + } + } + + return $retval; + } + + /** + * Returns the actual path and the virtual paths for a node + * both as clean arrays and base64 encoded strings + * + * @return array + */ + public function getPaths() + { + $aPath = []; + $aPathClean = []; + foreach ($this->parents(true, true, false) as $parent) { + $aPath[] = base64_encode($parent->realName); + $aPathClean[] = $parent->realName; + } + $aPath = implode('.', array_reverse($aPath)); + $aPathClean = array_reverse($aPathClean); + + $vPath = []; + $vPathClean = []; + foreach ($this->parents(true, true, true) as $parent) { + $vPath[] = base64_encode((string) $parent->name); + $vPathClean[] = $parent->name; + } + $vPath = implode('.', array_reverse($vPath)); + $vPathClean = array_reverse($vPathClean); + + return [ + 'aPath' => $aPath, + 'aPath_clean' => $aPathClean, + 'vPath' => $vPath, + 'vPath_clean' => $vPathClean, + ]; + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + public function getData($type, $pos, $searchClause = '') + { + $maxItems = $GLOBALS['cfg']['FirstLevelNavigationItems']; + if (! $GLOBALS['cfg']['NavigationTreeEnableGrouping'] + || ! $GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + ) { + if (isset($GLOBALS['cfg']['Server']['DisableIS']) + && ! $GLOBALS['cfg']['Server']['DisableIS'] + ) { + $query = "SELECT `SCHEMA_NAME` "; + $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA` "; + $query .= $this->getWhereClause('SCHEMA_NAME', $searchClause); + $query .= "ORDER BY `SCHEMA_NAME` "; + $query .= "LIMIT $pos, $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] === false) { + $retval = []; + $query = "SHOW DATABASES "; + $query .= $this->getWhereClause('Database', $searchClause); + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + return $retval; + } + + $count = 0; + if (! $GLOBALS['dbi']->dataSeek($handle, $pos)) { + return $retval; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr[0]; + $count++; + } else { + break; + } + } + + return $retval; + } + + $retval = []; + $count = 0; + foreach ($this->getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->isHideDb($arr[0])) { + continue; + } + if (in_array($arr[0], $retval)) { + continue; + } + + if ($pos <= 0 && $count < $maxItems) { + $retval[] = $arr[0]; + $count++; + } + $pos--; + } + } + sort($retval); + + return $retval; + } + + $dbSeparator = $GLOBALS['cfg']['NavigationTreeDbSeparator']; + if (isset($GLOBALS['cfg']['Server']['DisableIS']) + && ! $GLOBALS['cfg']['Server']['DisableIS'] + ) { + $query = "SELECT `SCHEMA_NAME` "; + $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA`, "; + $query .= "("; + $query .= "SELECT DB_first_level "; + $query .= "FROM ( "; + $query .= "SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, "; + $query .= "'" . $GLOBALS['dbi']->escapeString($dbSeparator) . "', 1) "; + $query .= "DB_first_level "; + $query .= "FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= $this->getWhereClause('SCHEMA_NAME', $searchClause); + $query .= ") t "; + $query .= "ORDER BY DB_first_level ASC "; + $query .= "LIMIT $pos, $maxItems"; + $query .= ") t2 "; + $query .= $this->getWhereClause('SCHEMA_NAME', $searchClause); + $query .= "AND 1 = LOCATE(CONCAT(DB_first_level, "; + $query .= "'" . $GLOBALS['dbi']->escapeString($dbSeparator) . "'), "; + $query .= "CONCAT(SCHEMA_NAME, "; + $query .= "'" . $GLOBALS['dbi']->escapeString($dbSeparator) . "')) "; + $query .= "ORDER BY SCHEMA_NAME ASC"; + $retval = $GLOBALS['dbi']->fetchResult($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] === false) { + $query = "SHOW DATABASES "; + $query .= $this->getWhereClause('Database', $searchClause); + $handle = $GLOBALS['dbi']->tryQuery($query); + $prefixes = []; + if ($handle !== false) { + $prefixMap = []; + $total = $pos + $maxItems; + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + if (count($prefixMap) == $total) { + break; + } + } + $prefixes = array_slice(array_keys($prefixMap), (int) $pos); + } + + $query = "SHOW DATABASES "; + $query .= $this->getWhereClause('Database', $searchClause); + $query .= "AND ("; + $subClauses = []; + foreach ($prefixes as $prefix) { + $subClauses[] = " LOCATE('" + . $GLOBALS['dbi']->escapeString((string) $prefix) . $dbSeparator + . "', " + . "CONCAT(`Database`, '" . $dbSeparator . "')) = 1 "; + } + $query .= implode("OR", $subClauses) . ")"; + $retval = $GLOBALS['dbi']->fetchResult($query); + + return $retval; + } + + $retval = []; + $prefixMap = []; + $total = $pos + $maxItems; + foreach ($this->getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->isHideDb($arr[0])) { + continue; + } + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + if (count($prefixMap) == $total) { + break 2; + } + } + } + $prefixes = array_slice(array_keys($prefixMap), $pos); + + foreach ($this->getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->isHideDb($arr[0])) { + continue; + } + if (in_array($arr[0], $retval)) { + continue; + } + + foreach ($prefixes as $prefix) { + $startsWith = strpos( + $arr[0] . $dbSeparator, + $prefix . $dbSeparator + ) === 0; + if ($startsWith) { + $retval[] = $arr[0]; + break; + } + } + } + } + sort($retval); + + return $retval; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param string $searchClause A string used to filter the results of the query + * + * @return int + */ + public function getPresence($type = '', $searchClause = '') + { + if (! $GLOBALS['cfg']['NavigationTreeEnableGrouping'] + || ! $GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] + ) { + if (isset($GLOBALS['cfg']['Server']['DisableIS']) + && ! $GLOBALS['cfg']['Server']['DisableIS'] + ) { + $query = "SELECT COUNT(*) "; + $query .= "FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= $this->getWhereClause('SCHEMA_NAME', $searchClause); + $retval = (int) $GLOBALS['dbi']->fetchValue($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] === false) { + $query = "SHOW DATABASES "; + $query .= $this->getWhereClause('Database', $searchClause); + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + + return $retval; + } + + $retval = 0; + foreach ($this->getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $retval += $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + $dbSeparator = $GLOBALS['cfg']['NavigationTreeDbSeparator']; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $query = "SELECT COUNT(*) "; + $query .= "FROM ( "; + $query .= "SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, "; + $query .= "'$dbSeparator', 1) "; + $query .= "DB_first_level "; + $query .= "FROM INFORMATION_SCHEMA.SCHEMATA "; + $query .= $this->getWhereClause('SCHEMA_NAME', $searchClause); + $query .= ") t "; + $retval = (int) $GLOBALS['dbi']->fetchValue($query); + + return $retval; + } + + if ($GLOBALS['dbs_to_test'] !== false) { + $prefixMap = []; + foreach ($this->getDatabasesToSearch($searchClause) as $db) { + $query = "SHOW DATABASES LIKE '" . $db . "'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + continue; + } + + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($this->isHideDb($arr[0])) { + continue; + } + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + } + } + $retval = count($prefixMap); + + return $retval; + } + + $prefixMap = []; + $query = "SHOW DATABASES "; + $query .= $this->getWhereClause('Database', $searchClause); + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + $prefix = strstr($arr[0], $dbSeparator, true); + if ($prefix === false) { + $prefix = $arr[0]; + } + $prefixMap[$prefix] = 1; + } + } + $retval = count($prefixMap); + + return $retval; + } + + /** + * Detemines whether a given database should be hidden according to 'hide_db' + * + * @param string $db database name + * + * @return boolean whether to hide + */ + private function isHideDb($db) + { + return ! empty($GLOBALS['cfg']['Server']['hide_db']) + && preg_match('/' . $GLOBALS['cfg']['Server']['hide_db'] . '/', $db); + } + + /** + * Get the list of databases for 'SHOW DATABASES LIKE' queries. + * If a search clause is set it gets the highest priority while only_db gets + * the next priority. In case both are empty list of databases determined by + * GRANTs are used + * + * @param string $searchClause search clause + * + * @return array array of databases + */ + private function getDatabasesToSearch($searchClause) + { + $databases = []; + if (! empty($searchClause)) { + $databases = [ + "%" . $GLOBALS['dbi']->escapeString($searchClause) . "%", + ]; + } elseif (! empty($GLOBALS['cfg']['Server']['only_db'])) { + $databases = $GLOBALS['cfg']['Server']['only_db']; + } elseif (! empty($GLOBALS['dbs_to_test'])) { + $databases = $GLOBALS['dbs_to_test']; + } + sort($databases); + + return $databases; + } + + /** + * Returns the WHERE clause depending on the $searchClause parameter + * and the hide_db directive + * + * @param string $columnName Column name of the column having database names + * @param string $searchClause A string used to filter the results of the query + * + * @return string + */ + private function getWhereClause($columnName, $searchClause = '') + { + $whereClause = "WHERE TRUE "; + if (! empty($searchClause)) { + $whereClause .= "AND " . Util::backquote($columnName) + . " LIKE '%"; + $whereClause .= $GLOBALS['dbi']->escapeString($searchClause); + $whereClause .= "%' "; + } + + if (! empty($GLOBALS['cfg']['Server']['hide_db'])) { + $whereClause .= "AND " . Util::backquote($columnName) + . " NOT REGEXP '" + . $GLOBALS['dbi']->escapeString($GLOBALS['cfg']['Server']['hide_db']) + . "' "; + } + + if (! empty($GLOBALS['cfg']['Server']['only_db'])) { + if (is_string($GLOBALS['cfg']['Server']['only_db'])) { + $GLOBALS['cfg']['Server']['only_db'] = [ + $GLOBALS['cfg']['Server']['only_db'], + ]; + } + $whereClause .= "AND ("; + $subClauses = []; + foreach ($GLOBALS['cfg']['Server']['only_db'] as $eachOnlyDb) { + $subClauses[] = " " . Util::backquote($columnName) + . " LIKE '" + . $GLOBALS['dbi']->escapeString($eachOnlyDb) . "' "; + } + $whereClause .= implode("OR", $subClauses) . ") "; + } + + return $whereClause; + } + + /** + * Returns HTML for control buttons displayed infront of a node + * + * @return String HTML for control buttons + */ + public function getHtmlForControlButtons() + { + return ''; + } + + /** + * Returns CSS classes for a node + * + * @param boolean $match Whether the node matched loaded tree + * + * @return String with html classes. + */ + public function getCssClasses($match) + { + if (! $GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + return ''; + } + + $result = ['expander']; + + if ($this->isGroup || $match) { + $result[] = 'loaded'; + } + if ($this->type == Node::CONTAINER) { + $result[] = 'container'; + } + + return implode(' ', $result); + } + + /** + * Returns icon for the node + * + * @param boolean $match Whether the node matched loaded tree + * + * @return String with image name + */ + public function getIcon($match) + { + if (! $GLOBALS['cfg']['NavigationTreeEnableExpansion'] + ) { + return ''; + } elseif ($match) { + $this->visible = true; + + return Util::getImage('b_minus'); + } + + return Util::getImage('b_plus', __('Expand/Collapse')); + } + + /** + * Gets the count of hidden elements for each database + * + * @return array|null array containing the count of hidden elements for each database + */ + public function getNavigationHidingData() + { + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + $navTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote( + $cfgRelation['navigationhiding'] + ); + $sqlQuery = "SELECT `db_name`, COUNT(*) AS `count` FROM " . $navTable + . " WHERE `username`='" + . $GLOBALS['dbi']->escapeString( + $GLOBALS['cfg']['Server']['user'] + ) . "'" + . " GROUP BY `db_name`"; + $counts = $GLOBALS['dbi']->fetchResult( + $sqlQuery, + 'db_name', + 'count', + DatabaseInterface::CONNECT_CONTROL + ); + + return $counts; + } + + return null; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeColumn.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeColumn.php new file mode 100644 index 0000000..9c9e605 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeColumn.php @@ -0,0 +1,116 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Util; + +/** + * Represents a columns node in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeColumn extends Node +{ + /** + * Initialises the class + * + * @param array $item array to identify the column node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + */ + public function __construct($item, $type = Node::OBJECT, $isGroup = false) + { + $this->displayName = $this->getDisplayName($item); + + parent::__construct($item['name'], $type, $isGroup); + $this->icon = Util::getImage($this->getColumnIcon($item['key']), __('Column')); + $this->links = [ + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&field=%1$s' + . '&change_column=1', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&field=%1$s' + . '&change_column=1', + 'title' => __('Structure'), + ]; + } + + /** + * Get customized Icon for columns in navigation tree + * + * @param string $key The key type - (primary, foreign etc.) + * + * @return string Icon name for required key. + */ + private function getColumnIcon($key) + { + switch ($key) { + case 'PRI': + $retval = 'b_primary'; + break; + case 'UNI': + $retval = 'bd_primary'; + break; + default: + $retval = 'pause'; + break; + } + return $retval; + } + + /** + * Get displayable name for navigation tree (key_type, data_type, default) + * + * @param array $item Item is array containing required info + * + * @return string Display name for navigation tree + */ + private function getDisplayName($item) + { + $retval = $item['name']; + $flag = 0; + foreach ($item as $key => $value) { + if (! empty($value) && $key != 'name') { + $flag == 0 ? $retval .= ' (' : $retval .= ', '; + $flag = 1; + $retval .= $this->getTruncateValue($key, $value); + } + } + $retval .= ')'; + return $retval; + } + + /** + * Get truncated value for display in node column view + * + * @param string $key key to identify default,datatype etc + * @param string $value value corresponding to key + * + * @return string truncated value + */ + public function getTruncateValue($key, $value) + { + $retval = ''; + + switch ($key) { + case 'default': + strlen($value) > 6 ? + $retval .= substr($value, 0, 6) . '...' : + $retval = $value; + break; + default: + $retval = $value; + break; + } + + return $retval; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeColumnContainer.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeColumnContainer.php new file mode 100644 index 0000000..22cba58 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeColumnContainer.php @@ -0,0 +1,55 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Navigation\NodeFactory; +use PhpMyAdmin\Util; + +/** + * Represents a container for column nodes in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeColumnContainer extends Node +{ + /** + * Initialises the class + */ + public function __construct() + { + parent::__construct(__('Columns'), Node::CONTAINER); + $this->icon = Util::getImage('pause', __('Columns')); + $this->links = [ + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ]; + $this->realName = 'columns'; + + $newLabel = _pgettext('Create new column', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $newLabel + ); + $new->isNew = true; + $new->icon = Util::getImage('b_column_add', $newLabel); + $new->links = [ + 'text' => 'tbl_addfield.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s' + . '&field_where=last&after_field=', + 'icon' => 'tbl_addfield.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s' + . '&field_where=last&after_field=', + ]; + $new->classes = 'new_column italics'; + $this->addChild($new); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabase.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabase.php new file mode 100644 index 0000000..33c93fb --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabase.php @@ -0,0 +1,717 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Relation; +use PhpMyAdmin\Url; +use PhpMyAdmin\Util; + +/** + * Represents a database node in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeDatabase extends Node +{ + /** + * The number of hidden items in this database + * + * @var int + */ + protected $hiddenCount = 0; + + /** + * Initialises the class + * + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + */ + public function __construct($name, $type = Node::OBJECT, $isGroup = false) + { + parent::__construct($name, $type, $isGroup); + $this->icon = Util::getImage( + 's_db', + __('Database operations') + ); + + $scriptName = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabDatabase'], + 'database' + ); + $this->links = [ + 'text' => $scriptName + . '?server=' . $GLOBALS['server'] + . '&db=%1$s', + 'icon' => 'db_operations.php?server=' . $GLOBALS['server'] + . '&db=%1$s&', + 'title' => __('Structure'), + ]; + $this->classes = 'database'; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + public function getPresence($type = '', $searchClause = '', $singleItem = false) + { + $retval = 0; + switch ($type) { + case 'tables': + $retval = $this->getTableCount($searchClause, $singleItem); + break; + case 'views': + $retval = $this->getViewCount($searchClause, $singleItem); + break; + case 'procedures': + $retval = $this->getProcedureCount($searchClause, $singleItem); + break; + case 'functions': + $retval = $this->getFunctionCount($searchClause, $singleItem); + break; + case 'events': + $retval = $this->getEventCount($searchClause, $singleItem); + break; + default: + break; + } + + return $retval; + } + + /** + * Returns the number of tables or views present inside this database + * + * @param string $which tables|views + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function getTableOrViewCount($which, $searchClause, $singleItem) + { + $db = $this->realName; + if ($which == 'tables') { + $condition = 'IN'; + } else { + $condition = 'NOT IN'; + } + + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='$db' "; + $query .= "AND `TABLE_TYPE`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (! empty($searchClause)) { + $query .= "AND " . $this->getWhereClauseForSearch( + $searchClause, + $singleItem, + 'TABLE_NAME' + ); + } + $retval = (int) $GLOBALS['dbi']->fetchValue($query); + } else { + $query = "SHOW FULL TABLES FROM "; + $query .= Util::backquote($db); + $query .= " WHERE `Table_type`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (! empty($searchClause)) { + $query .= "AND " . $this->getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Tables_in_' . $db + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the number of tables present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function getTableCount($searchClause, $singleItem) + { + return $this->getTableOrViewCount( + 'tables', + $searchClause, + $singleItem + ); + } + + /** + * Returns the number of views present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function getViewCount($searchClause, $singleItem) + { + return $this->getTableOrViewCount( + 'views', + $searchClause, + $singleItem + ); + } + + /** + * Returns the number of procedures present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function getProcedureCount($searchClause, $singleItem) + { + $db = $this->realName; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA` " + . Util::getCollateForIS() . "='$db'"; + $query .= "AND `ROUTINE_TYPE`='PROCEDURE' "; + if (! empty($searchClause)) { + $query .= "AND " . $this->getWhereClauseForSearch( + $searchClause, + $singleItem, + 'ROUTINE_NAME' + ); + } + $retval = (int) $GLOBALS['dbi']->fetchValue($query); + } else { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SHOW PROCEDURE STATUS WHERE `Db`='$db' "; + if (! empty($searchClause)) { + $query .= "AND " . $this->getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Name' + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the number of functions present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function getFunctionCount($searchClause, $singleItem) + { + $db = $this->realName; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + $query .= "AND `ROUTINE_TYPE`='FUNCTION' "; + if (! empty($searchClause)) { + $query .= "AND " . $this->getWhereClauseForSearch( + $searchClause, + $singleItem, + 'ROUTINE_NAME' + ); + } + $retval = (int) $GLOBALS['dbi']->fetchValue($query); + } else { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SHOW FUNCTION STATUS WHERE `Db`='$db' "; + if (! empty($searchClause)) { + $query .= "AND " . $this->getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Name' + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the number of events present inside this database + * + * @param string $searchClause A string used to filter the results of + * the query + * @param boolean $singleItem Whether to get presence of a single known + * item or false in none + * + * @return int + */ + private function getEventCount($searchClause, $singleItem) + { + $db = $this->realName; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`EVENTS` "; + $query .= "WHERE `EVENT_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + if (! empty($searchClause)) { + $query .= "AND " . $this->getWhereClauseForSearch( + $searchClause, + $singleItem, + 'EVENT_NAME' + ); + } + $retval = (int) $GLOBALS['dbi']->fetchValue($query); + } else { + $db = Util::backquote($db); + $query = "SHOW EVENTS FROM $db "; + if (! empty($searchClause)) { + $query .= "WHERE " . $this->getWhereClauseForSearch( + $searchClause, + $singleItem, + 'Name' + ); + } + $retval = $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + + return $retval; + } + + /** + * Returns the WHERE clause for searching inside a database + * + * @param string $searchClause A string used to filter the results of the query + * @param boolean $singleItem Whether to get presence of a single known item + * @param string $columnName Name of the column in the result set to match + * + * @return string WHERE clause for searching + */ + private function getWhereClauseForSearch( + $searchClause, + $singleItem, + $columnName + ) { + $query = ''; + if ($singleItem) { + $query .= Util::backquote($columnName) . " = "; + $query .= "'" . $GLOBALS['dbi']->escapeString($searchClause) . "'"; + } else { + $query .= Util::backquote($columnName) . " LIKE "; + $query .= "'%" . $GLOBALS['dbi']->escapeString($searchClause) + . "%'"; + } + + return $query; + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + public function getData($type, $pos, $searchClause = '') + { + $retval = []; + switch ($type) { + case 'tables': + $retval = $this->getTables($pos, $searchClause); + break; + case 'views': + $retval = $this->getViews($pos, $searchClause); + break; + case 'procedures': + $retval = $this->getProcedures($pos, $searchClause); + break; + case 'functions': + $retval = $this->getFunctions($pos, $searchClause); + break; + case 'events': + $retval = $this->getEvents($pos, $searchClause); + break; + default: + break; + } + + // Remove hidden items so that they are not displayed in navigation tree + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + $hiddenItems = $this->getHiddenItems(substr($type, 0, -1)); + foreach ($retval as $key => $item) { + if (in_array($item, $hiddenItems)) { + unset($retval[$key]); + } + } + } + + return $retval; + } + + /** + * Return list of hidden items of given type + * + * @param string $type The type of items we are looking for + * ('table', 'function', 'group', etc.) + * + * @return array Array containing hidden items of given type + */ + public function getHiddenItems($type) + { + $db = $this->realName; + $cfgRelation = $this->relation->getRelationsParam(); + if (! $cfgRelation['navwork']) { + return []; + } + $navTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['navigationhiding']); + $sqlQuery = "SELECT `item_name` FROM " . $navTable + . " WHERE `username`='" . $cfgRelation['user'] . "'" + . " AND `item_type`='" . $type + . "' AND `db_name`='" . $GLOBALS['dbi']->escapeString($db) + . "'"; + $result = $this->relation->queryAsControlUser($sqlQuery, false); + $hiddenItems = []; + if ($result) { + while ($row = $GLOBALS['dbi']->fetchArray($result)) { + $hiddenItems[] = $row[0]; + } + } + $GLOBALS['dbi']->freeResult($result); + + return $hiddenItems; + } + + /** + * Returns the list of tables or views inside this database + * + * @param string $which tables|views + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function getTablesOrViews($which, $pos, $searchClause) + { + if ($which == 'tables') { + $condition = 'IN'; + } else { + $condition = 'NOT IN'; + } + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = []; + $db = $this->realName; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT `TABLE_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`TABLES` "; + $query .= "WHERE `TABLE_SCHEMA`='$escdDb' "; + $query .= "AND `TABLE_TYPE`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (! empty($searchClause)) { + $query .= "AND `TABLE_NAME` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $query .= "ORDER BY `TABLE_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + } else { + $query = " SHOW FULL TABLES FROM "; + $query .= Util::backquote($db); + $query .= " WHERE `Table_type`" . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') "; + if (! empty($searchClause)) { + $query .= "AND " . Util::backquote( + "Tables_in_" . $db + ); + $query .= " LIKE '%" . $GLOBALS['dbi']->escapeString( + $searchClause + ); + $query .= "%'"; + } + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr[0]; + $count++; + } else { + break; + } + } + } + } + } + + return $retval; + } + + /** + * Returns the list of tables inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function getTables($pos, $searchClause) + { + return $this->getTablesOrViews('tables', $pos, $searchClause); + } + + /** + * Returns the list of views inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function getViews($pos, $searchClause) + { + return $this->getTablesOrViews('views', $pos, $searchClause); + } + + /** + * Returns the list of procedures or functions inside this database + * + * @param string $routineType PROCEDURE|FUNCTION + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function getRoutines($routineType, $pos, $searchClause) + { + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = []; + $db = $this->realName; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT `ROUTINE_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`ROUTINES` "; + $query .= "WHERE `ROUTINE_SCHEMA` " + . Util::getCollateForIS() . "='$escdDb'"; + $query .= "AND `ROUTINE_TYPE`='" . $routineType . "' "; + if (! empty($searchClause)) { + $query .= "AND `ROUTINE_NAME` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $query .= "ORDER BY `ROUTINE_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + } else { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SHOW " . $routineType . " STATUS WHERE `Db`='$escdDb' "; + if (! empty($searchClause)) { + $query .= "AND `Name` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Name']; + $count++; + } else { + break; + } + } + } + } + } + + return $retval; + } + + /** + * Returns the list of procedures inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function getProcedures($pos, $searchClause) + { + return $this->getRoutines('PROCEDURE', $pos, $searchClause); + } + + /** + * Returns the list of functions inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function getFunctions($pos, $searchClause) + { + return $this->getRoutines('FUNCTION', $pos, $searchClause); + } + + /** + * Returns the list of events inside this database + * + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + private function getEvents($pos, $searchClause) + { + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = []; + $db = $this->realName; + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $escdDb = $GLOBALS['dbi']->escapeString($db); + $query = "SELECT `EVENT_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`EVENTS` "; + $query .= "WHERE `EVENT_SCHEMA` " + . Util::getCollateForIS() . "='$escdDb' "; + if (! empty($searchClause)) { + $query .= "AND `EVENT_NAME` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $query .= "ORDER BY `EVENT_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + } else { + $escdDb = Util::backquote($db); + $query = "SHOW EVENTS FROM $escdDb "; + if (! empty($searchClause)) { + $query .= "WHERE `Name` LIKE '%"; + $query .= $GLOBALS['dbi']->escapeString($searchClause); + $query .= "%'"; + } + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle !== false) { + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Name']; + $count++; + } else { + break; + } + } + } + } + } + + return $retval; + } + + /** + * Returns HTML for control buttons displayed infront of a node + * + * @return String HTML for control buttons + */ + public function getHtmlForControlButtons() + { + $ret = ''; + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + if ($this->hiddenCount > 0) { + $params = [ + 'showUnhideDialog' => true, + 'dbName' => $this->realName, + ]; + $ret = '<span class="dbItemControls">' + . '<a href="navigation.php" data-post="' + . Url::getCommon($params, '') . '"' + . ' class="showUnhide ajax">' + . Util::getImage( + 'show', + __('Show hidden items') + ) + . '</a></span>'; + } + } + + return $ret; + } + + /** + * Sets the number of hidden items in this database + * + * @param int $count hidden item count + * + * @return void + */ + public function setHiddenCount($count) + { + $this->hiddenCount = $count; + } + + /** + * Returns the number of hidden items in this database + * + * @return int hidden item count + */ + public function getHiddenCount() + { + return $this->hiddenCount; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php new file mode 100644 index 0000000..0005915 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php @@ -0,0 +1,62 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Relation; +use PhpMyAdmin\Url; +use PhpMyAdmin\Util; + +/** + * Represents a node that is a child of a database node + * This may either be a concrete child such as table or a container + * such as table container + * + * @package PhpMyAdmin-Navigation + */ +abstract class NodeDatabaseChild extends Node +{ + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + abstract protected function getItemType(); + + /** + * Returns HTML for control buttons displayed infront of a node + * + * @return String HTML for control buttons + */ + public function getHtmlForControlButtons() + { + $ret = ''; + $cfgRelation = $this->relation->getRelationsParam(); + if ($cfgRelation['navwork']) { + $db = $this->realParent()->realName; + $item = $this->realName; + + $params = [ + 'hideNavItem' => true, + 'itemType' => $this->getItemType(), + 'itemName' => $item, + 'dbName' => $db, + ]; + + $ret = '<span class="navItemControls">' + . '<a href="navigation.php" data-post="' + . Url::getCommon($params, '') . '"' + . ' class="hideNavItem ajax">' + . Util::getImage('hide', __('Hide')) + . '</a></span>'; + } + + return $ret; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabaseChildContainer.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabaseChildContainer.php new file mode 100644 index 0000000..7c182c6 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabaseChildContainer.php @@ -0,0 +1,43 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Represents container node that carries children of a database + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +/** + * Represents container node that carries children of a database + * + * @package PhpMyAdmin-Navigation + */ +abstract class NodeDatabaseChildContainer extends NodeDatabaseChild +{ + /** + * Initialises the class by setting the common variables + * + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + */ + public function __construct($name, $type = Node::OBJECT) + { + parent::__construct($name, $type); + if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']) { + $this->separator = $GLOBALS['cfg']['NavigationTreeTableSeparator']; + $this->separatorDepth = (int) $GLOBALS['cfg']['NavigationTreeTableLevel']; + } + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'group'; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabaseContainer.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabaseContainer.php new file mode 100644 index 0000000..9b5746f --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeDatabaseContainer.php @@ -0,0 +1,52 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\CheckUserPrivileges; +use PhpMyAdmin\Navigation\NodeFactory; +use PhpMyAdmin\Util; + +/** + * Represents a container for database nodes in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeDatabaseContainer extends Node +{ + /** + * Initialises the class + * + * @param string $name An identifier for the new node + */ + public function __construct($name) + { + $checkUserPrivileges = new CheckUserPrivileges($GLOBALS['dbi']); + $checkUserPrivileges->getPrivileges(); + + parent::__construct($name, Node::CONTAINER); + + if ($GLOBALS['is_create_db_priv'] + && $GLOBALS['cfg']['ShowCreateDb'] !== false + ) { + $new = NodeFactory::getInstance( + 'Node', + _pgettext('Create new database', 'New') + ); + $new->isNew = true; + $new->icon = Util::getImage('b_newdb', ''); + $new->links = [ + 'text' => 'server_databases.php?server=' . $GLOBALS['server'], + 'icon' => 'server_databases.php?server=' . $GLOBALS['server'], + ]; + $new->classes = 'new_database italics'; + $this->addChild($new); + } + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeEvent.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeEvent.php new file mode 100644 index 0000000..91aa5ce --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeEvent.php @@ -0,0 +1,51 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Util; + +/** + * Represents a event node in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeEvent extends NodeDatabaseChild +{ + /** + * Initialises the class + * + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + */ + public function __construct($name, $type = Node::OBJECT, $isGroup = false) + { + parent::__construct($name, $type, $isGroup); + $this->icon = Util::getImage('b_events'); + $this->links = [ + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&edit_item=1', + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&export_item=1', + ]; + $this->classes = 'event'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'event'; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeEventContainer.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeEventContainer.php new file mode 100644 index 0000000..86c2937 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeEventContainer.php @@ -0,0 +1,52 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Navigation\NodeFactory; +use PhpMyAdmin\Util; + +/** + * Represents a container for events nodes in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeEventContainer extends NodeDatabaseChildContainer +{ + /** + * Initialises the class + */ + public function __construct() + { + parent::__construct(__('Events'), Node::CONTAINER); + $this->icon = Util::getImage('b_events', ''); + $this->links = [ + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%1$s', + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%1$s', + ]; + $this->realName = 'events'; + + $new = NodeFactory::getInstance( + 'Node', + _pgettext('Create new event', 'New') + ); + $new->isNew = true; + $new->icon = Util::getImage('b_event_add', ''); + $new->links = [ + 'text' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + 'icon' => 'db_events.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + ]; + $new->classes = 'new_event italics'; + $this->addChild($new); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeFunction.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeFunction.php new file mode 100644 index 0000000..ffd2afe --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeFunction.php @@ -0,0 +1,53 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Util; + +/** + * Represents a function node in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeFunction extends NodeDatabaseChild +{ + /** + * Initialises the class + * + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + */ + public function __construct($name, $type = Node::OBJECT, $isGroup = false) + { + parent::__construct($name, $type, $isGroup); + $this->icon = Util::getImage('b_routines', __('Function')); + $this->links = [ + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=FUNCTION' + . '&edit_item=1', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=FUNCTION' + . '&execute_dialog=1', + ]; + $this->classes = 'function'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'function'; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeFunctionContainer.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeFunctionContainer.php new file mode 100644 index 0000000..52715a3 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeFunctionContainer.php @@ -0,0 +1,53 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Navigation\NodeFactory; +use PhpMyAdmin\Util; + +/** + * Represents a container for functions nodes in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeFunctionContainer extends NodeDatabaseChildContainer +{ + /** + * Initialises the class + */ + public function __construct() + { + parent::__construct(__('Functions'), Node::CONTAINER); + $this->icon = Util::getImage('b_routines', __('Functions')); + $this->links = [ + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=FUNCTION', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=FUNCTION', + ]; + $this->realName = 'functions'; + + $newLabel = _pgettext('Create new function', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $newLabel + ); + $new->isNew = true; + $new->icon = Util::getImage('b_routine_add', $newLabel); + $new->links = [ + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1&item_type=FUNCTION', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1&item_type=FUNCTION', + ]; + $new->classes = 'new_function italics'; + $this->addChild($new); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeIndex.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeIndex.php new file mode 100644 index 0000000..c464114 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeIndex.php @@ -0,0 +1,41 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Util; + +/** + * Represents a index node in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeIndex extends Node +{ + /** + * Initialises the class + * + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + */ + public function __construct($name, $type = Node::OBJECT, $isGroup = false) + { + parent::__construct($name, $type, $isGroup); + $this->icon = Util::getImage('b_index', __('Index')); + $this->links = [ + 'text' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&index=%1$s', + 'icon' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&db=%3$s&table=%2$s&index=%1$s', + ]; + $this->classes = 'index'; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeIndexContainer.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeIndexContainer.php new file mode 100644 index 0000000..9be90d3 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeIndexContainer.php @@ -0,0 +1,55 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Navigation\NodeFactory; +use PhpMyAdmin\Util; + +/** + * Represents a container for index nodes in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeIndexContainer extends Node +{ + /** + * Initialises the class + */ + public function __construct() + { + parent::__construct(__('Indexes'), Node::CONTAINER); + $this->icon = Util::getImage('b_index', __('Indexes')); + $this->links = [ + 'text' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ]; + $this->realName = 'indexes'; + + $newLabel = _pgettext('Create new index', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $newLabel + ); + $new->isNew = true; + $new->icon = Util::getImage('b_index_add', $newLabel); + $new->links = [ + 'text' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&create_index=1&added_fields=2' + . '&db=%3$s&table=%2$s', + 'icon' => 'tbl_indexes.php?server=' . $GLOBALS['server'] + . '&create_index=1&added_fields=2' + . '&db=%3$s&table=%2$s', + ]; + $new->classes = 'new_index italics'; + $this->addChild($new); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeProcedure.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeProcedure.php new file mode 100644 index 0000000..fbced93 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeProcedure.php @@ -0,0 +1,53 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Util; + +/** + * Represents a procedure node in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeProcedure extends NodeDatabaseChild +{ + /** + * Initialises the class + * + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + */ + public function __construct($name, $type = Node::OBJECT, $isGroup = false) + { + parent::__construct($name, $type, $isGroup); + $this->icon = Util::getImage('b_routines', __('Procedure')); + $this->links = [ + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=PROCEDURE' + . '&edit_item=1', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&item_name=%1$s&item_type=PROCEDURE' + . '&execute_dialog=1', + ]; + $this->classes = 'procedure'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'procedure'; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeProcedureContainer.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeProcedureContainer.php new file mode 100644 index 0000000..1978ede --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeProcedureContainer.php @@ -0,0 +1,53 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Navigation\NodeFactory; +use PhpMyAdmin\Util; + +/** + * Represents a container for procedure nodes in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeProcedureContainer extends NodeDatabaseChildContainer +{ + /** + * Initialises the class + */ + public function __construct() + { + parent::__construct(__('Procedures'), Node::CONTAINER); + $this->icon = Util::getImage('b_routines', __('Procedures')); + $this->links = [ + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=PROCEDURE', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%1$s&type=PROCEDURE', + ]; + $this->realName = 'procedures'; + + $newLabel = _pgettext('Create new procedure', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $newLabel + ); + $new->isNew = true; + $new->icon = Util::getImage('b_routine_add', $newLabel); + $new->links = [ + 'text' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + 'icon' => 'db_routines.php?server=' . $GLOBALS['server'] + . '&db=%2$s&add_item=1', + ]; + $new->classes = 'new_procedure italics'; + $this->addChild($new); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTable.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTable.php new file mode 100644 index 0000000..89965eb --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTable.php @@ -0,0 +1,310 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Util; + +/** + * Represents a columns node in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeTable extends NodeDatabaseChild +{ + /** + * Initialises the class + * + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + */ + public function __construct($name, $type = Node::OBJECT, $isGroup = false) + { + parent::__construct($name, $type, $isGroup); + $this->icon = []; + $this->addIcon( + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable'], + 'table' + ) + ); + $this->addIcon( + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable2'], + 'table' + ) + ); + $title = Util::getTitleForTarget( + $GLOBALS['cfg']['DefaultTabTable'] + ); + $this->title = $title; + + $scriptName = Util::getScriptNameForOption( + $GLOBALS['cfg']['DefaultTabTable'], + 'table' + ); + $this->links = [ + 'text' => $scriptName + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s' + . '&pos=0', + 'icon' => [ + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable'], + 'table' + ) + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + Util::getScriptNameForOption( + $GLOBALS['cfg']['NavigationTreeDefaultTabTable2'], + 'table' + ) + . '?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ], + 'title' => $this->title, + ]; + $this->classes = 'table'; + } + + /** + * Returns the number of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('columns' or 'indexes') + * @param string $searchClause A string used to filter the results of the query + * + * @return int + */ + public function getPresence($type = '', $searchClause = '') + { + $retval = 0; + $db = $this->realParent()->realName; + $table = $this->realName; + switch ($type) { + case 'columns': + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`COLUMNS` "; + $query .= "WHERE `TABLE_NAME`='$table' "; + $query .= "AND `TABLE_SCHEMA`='$db'"; + $retval = (int) $GLOBALS['dbi']->fetchValue($query); + } else { + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW COLUMNS FROM $table FROM $db"; + $retval = (int) $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + break; + case 'indexes': + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW INDEXES FROM $table FROM $db"; + $retval = (int) $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + break; + case 'triggers': + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT COUNT(*) "; + $query .= "FROM `INFORMATION_SCHEMA`.`TRIGGERS` "; + $query .= "WHERE `EVENT_OBJECT_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + $query .= "AND `EVENT_OBJECT_TABLE` " + . Util::getCollateForIS() . "='$table'"; + $retval = (int) $GLOBALS['dbi']->fetchValue($query); + } else { + $db = Util::backquote($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SHOW TRIGGERS FROM $db WHERE `Table` = '$table'"; + $retval = (int) $GLOBALS['dbi']->numRows( + $GLOBALS['dbi']->tryQuery($query) + ); + } + break; + default: + break; + } + + return $retval; + } + + /** + * Returns the names of children of type $type present inside this container + * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase + * and PhpMyAdmin\Navigation\Nodes\NodeTable classes + * + * @param string $type The type of item we are looking for + * ('tables', 'views', etc) + * @param int $pos The offset of the list within the results + * @param string $searchClause A string used to filter the results of the query + * + * @return array + */ + public function getData($type, $pos, $searchClause = '') + { + $maxItems = $GLOBALS['cfg']['MaxNavigationItems']; + $retval = []; + $db = $this->realParent()->realName; + $table = $this->realName; + switch ($type) { + case 'columns': + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT `COLUMN_NAME` AS `name` "; + $query .= ",`COLUMN_KEY` AS `key` "; + $query .= ",`DATA_TYPE` AS `type` "; + $query .= ",`COLUMN_DEFAULT` AS `default` "; + $query .= ",IF (`IS_NULLABLE` = 'NO', '', 'nullable') AS `nullable` "; + $query .= "FROM `INFORMATION_SCHEMA`.`COLUMNS` "; + $query .= "WHERE `TABLE_NAME`='$table' "; + $query .= "AND `TABLE_SCHEMA`='$db' "; + $query .= "ORDER BY `COLUMN_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + } + + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW COLUMNS FROM $table FROM $db"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + break; + } + + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Field']; + $count++; + } else { + break; + } + } + } + break; + case 'indexes': + $db = Util::backquote($db); + $table = Util::backquote($table); + $query = "SHOW INDEXES FROM $table FROM $db"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + break; + } + + $count = 0; + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if (in_array($arr['Key_name'], $retval)) { + continue; + } + if ($pos <= 0 && $count < $maxItems) { + $retval[] = $arr['Key_name']; + $count++; + } + $pos--; + } + break; + case 'triggers': + if (! $GLOBALS['cfg']['Server']['DisableIS']) { + $db = $GLOBALS['dbi']->escapeString($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SELECT `TRIGGER_NAME` AS `name` "; + $query .= "FROM `INFORMATION_SCHEMA`.`TRIGGERS` "; + $query .= "WHERE `EVENT_OBJECT_SCHEMA` " + . Util::getCollateForIS() . "='$db' "; + $query .= "AND `EVENT_OBJECT_TABLE` " + . Util::getCollateForIS() . "='$table' "; + $query .= "ORDER BY `TRIGGER_NAME` ASC "; + $query .= "LIMIT " . intval($pos) . ", $maxItems"; + $retval = $GLOBALS['dbi']->fetchResult($query); + break; + } + + $db = Util::backquote($db); + $table = $GLOBALS['dbi']->escapeString($table); + $query = "SHOW TRIGGERS FROM $db WHERE `Table` = '$table'"; + $handle = $GLOBALS['dbi']->tryQuery($query); + if ($handle === false) { + break; + } + + $count = 0; + if ($GLOBALS['dbi']->dataSeek($handle, $pos)) { + while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { + if ($count < $maxItems) { + $retval[] = $arr['Trigger']; + $count++; + } else { + break; + } + } + } + break; + default: + break; + } + + return $retval; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'table'; + } + + /** + * Add an icon to navigation tree + * + * @param string $page Page name to redirect + * + * @return void + */ + private function addIcon($page) + { + if (empty($page)) { + return; + } + + switch ($page) { + case 'tbl_structure.php': + $this->icon[] = Util::getImage('b_props', __('Structure')); + break; + case 'tbl_select.php': + $this->icon[] = Util::getImage('b_search', __('Search')); + break; + case 'tbl_change.php': + $this->icon[] = Util::getImage('b_insrow', __('Insert')); + break; + case 'tbl_sql.php': + $this->icon[] = Util::getImage('b_sql', __('SQL')); + break; + case 'sql.php': + $this->icon[] = Util::getImage('b_browse', __('Browse')); + break; + } + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTableContainer.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTableContainer.php new file mode 100644 index 0000000..ec93203 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTableContainer.php @@ -0,0 +1,54 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Navigation\NodeFactory; +use PhpMyAdmin\Util; + +/** + * Represents a container for table nodes in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeTableContainer extends NodeDatabaseChildContainer +{ + /** + * Initialises the class + */ + public function __construct() + { + parent::__construct(__('Tables'), Node::CONTAINER); + $this->icon = Util::getImage('b_browse', __('Tables')); + $this->links = [ + 'text' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=table', + 'icon' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=table', + ]; + $this->realName = 'tables'; + $this->classes = 'tableContainer subContainer'; + + $newLabel = _pgettext('Create new table', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $newLabel + ); + $new->isNew = true; + $new->icon = Util::getImage('b_table_add', $newLabel); + $new->links = [ + 'text' => 'tbl_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + 'icon' => 'tbl_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + ]; + $new->classes = 'new_table italics'; + $this->addChild($new); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTrigger.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTrigger.php new file mode 100644 index 0000000..8ec4612 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTrigger.php @@ -0,0 +1,41 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Util; + +/** + * Represents a trigger node in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeTrigger extends Node +{ + /** + * Initialises the class + * + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + */ + public function __construct($name, $type = Node::OBJECT, $isGroup = false) + { + parent::__construct($name, $type, $isGroup); + $this->icon = Util::getImage('b_triggers'); + $this->links = [ + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&item_name=%1$s&edit_item=1', + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&item_name=%1$s&export_item=1', + ]; + $this->classes = 'trigger'; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php new file mode 100644 index 0000000..a5aa3b7 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php @@ -0,0 +1,52 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Navigation\NodeFactory; +use PhpMyAdmin\Util; + +/** + * Represents a container for trigger nodes in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeTriggerContainer extends Node +{ + /** + * Initialises the class + */ + public function __construct() + { + parent::__construct(__('Triggers'), Node::CONTAINER); + $this->icon = Util::getImage('b_triggers'); + $this->links = [ + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ]; + $this->realName = 'triggers'; + + $new = NodeFactory::getInstance( + 'Node', + _pgettext('Create new trigger', 'New') + ); + $new->isNew = true; + $new->icon = Util::getImage('b_trigger_add', ''); + $new->links = [ + 'text' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&add_item=1', + 'icon' => 'db_triggers.php?server=' . $GLOBALS['server'] + . '&db=%3$s&add_item=1', + ]; + $new->classes = 'new_trigger italics'; + $this->addChild($new); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeView.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeView.php new file mode 100644 index 0000000..1ded149 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeView.php @@ -0,0 +1,51 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Util; + +/** + * Represents a view node in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeView extends NodeDatabaseChild +{ + /** + * Initialises the class + * + * @param string $name An identifier for the new node + * @param int $type Type of node, may be one of CONTAINER or OBJECT + * @param bool $isGroup Whether this object has been created + * while grouping nodes + */ + public function __construct($name, $type = Node::OBJECT, $isGroup = false) + { + parent::__construct($name, $type, $isGroup); + $this->icon = Util::getImage('b_props', __('View')); + $this->links = [ + 'text' => 'sql.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s&pos=0', + 'icon' => 'tbl_structure.php?server=' . $GLOBALS['server'] + . '&db=%2$s&table=%1$s', + ]; + $this->classes = 'view'; + } + + /** + * Returns the type of the item represented by the node. + * + * @return string type of the item + */ + protected function getItemType() + { + return 'view'; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeViewContainer.php b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeViewContainer.php new file mode 100644 index 0000000..f202904 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Navigation/Nodes/NodeViewContainer.php @@ -0,0 +1,54 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Functionality for the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Navigation\Nodes; + +use PhpMyAdmin\Navigation\NodeFactory; +use PhpMyAdmin\Util; + +/** + * Represents a container for view nodes in the navigation tree + * + * @package PhpMyAdmin-Navigation + */ +class NodeViewContainer extends NodeDatabaseChildContainer +{ + /** + * Initialises the class + */ + public function __construct() + { + parent::__construct(__('Views'), Node::CONTAINER); + $this->icon = Util::getImage('b_views', __('Views')); + $this->links = [ + 'text' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=view', + 'icon' => 'db_structure.php?server=' . $GLOBALS['server'] + . '&db=%1$s&tbl_type=view', + ]; + $this->classes = 'viewContainer subContainer'; + $this->realName = 'views'; + + $newLabel = _pgettext('Create new view', 'New'); + $new = NodeFactory::getInstance( + 'Node', + $newLabel + ); + $new->isNew = true; + $new->icon = Util::getImage('b_view_add', $newLabel); + $new->links = [ + 'text' => 'view_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + 'icon' => 'view_create.php?server=' . $GLOBALS['server'] + . '&db=%2$s', + ]; + $new->classes = 'new_view italics'; + $this->addChild($new); + } +} |
