You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
877 lines
26 KiB
877 lines
26 KiB
<?php |
|
/** |
|
* Functionality for the navigation tree in the left frame |
|
*/ |
|
|
|
declare(strict_types=1); |
|
|
|
namespace PhpMyAdmin\Navigation\Nodes; |
|
|
|
use PhpMyAdmin\ConfigStorage\Relation; |
|
use PhpMyAdmin\DatabaseInterface; |
|
use PhpMyAdmin\Html\Generator; |
|
use PhpMyAdmin\Util; |
|
|
|
use function __; |
|
use function array_keys; |
|
use function array_reverse; |
|
use function array_slice; |
|
use function base64_encode; |
|
use function count; |
|
use function implode; |
|
use function in_array; |
|
use function is_string; |
|
use function preg_match; |
|
use function sort; |
|
use function sprintf; |
|
use function strlen; |
|
use function strpos; |
|
use function strstr; |
|
|
|
/** |
|
* The Node is the building block for the collapsible navigation tree |
|
*/ |
|
class Node |
|
{ |
|
public const CONTAINER = 0; |
|
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 = self::OBJECT; |
|
/** |
|
* @var bool Whether this object has been created while grouping nodes |
|
* Only relevant if the node is of type CONTAINER |
|
*/ |
|
public $isGroup = false; |
|
/** |
|
* @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; |
|
|
|
/** |
|
* For the IMG tag, used when rendering the node. |
|
* |
|
* @var array<string, string> |
|
* @psalm-var array{image: string, title: string} |
|
*/ |
|
public $icon = ['image' => '', 'title' => '']; |
|
|
|
/** |
|
* An array of A tags, used when rendering the node. |
|
* |
|
* @var array<string, mixed> |
|
* @psalm-var array{ |
|
* text: array{route: string, params: array<string, mixed>}, |
|
* icon: array{route: string, params: array<string, mixed>}, |
|
* second_icon?: array{route: string, params: array<string, mixed>}, |
|
* title?: string |
|
* } |
|
*/ |
|
public $links = [ |
|
'text' => ['route' => '', 'params' => []], |
|
'icon' => ['route' => '', 'params' => []], |
|
]; |
|
|
|
/** @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; |
|
|
|
/** @var string|null */ |
|
public $urlParamName = null; |
|
|
|
/** |
|
* 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 = self::OBJECT, $isGroup = false) |
|
{ |
|
global $dbi; |
|
|
|
if (strlen((string) $name)) { |
|
$this->name = $name; |
|
$this->realName = $name; |
|
} |
|
|
|
if ($type === self::CONTAINER) { |
|
$this->type = self::CONTAINER; |
|
} |
|
|
|
$this->isGroup = (bool) $isGroup; |
|
$this->relation = new Relation($dbi); |
|
} |
|
|
|
/** |
|
* Adds a child node to this node |
|
* |
|
* @param Node $child A child node |
|
*/ |
|
public function addChild($child): void |
|
{ |
|
$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 Node|null The requested child node or null, |
|
* if the requested node cannot be found |
|
*/ |
|
public function getChild($name, $realName = false): ?Node |
|
{ |
|
if ($realName) { |
|
foreach ($this->children as $child) { |
|
if ($child->realName == $name) { |
|
return $child; |
|
} |
|
} |
|
} else { |
|
foreach ($this->children as $child) { |
|
if ($child->name == $name && $child->isNew === false) { |
|
return $child; |
|
} |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* Removes a child node from this node |
|
* |
|
* @param string $name The name of child to be removed |
|
*/ |
|
public function removeChild($name): void |
|
{ |
|
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 Node[] An array of parent Nodes |
|
*/ |
|
public function parents($self = false, $containers = false, $groups = false): array |
|
{ |
|
$parents = []; |
|
if ($self && ($this->type != self::CONTAINER || $containers) && (! $this->isGroup || $groups)) { |
|
$parents[] = $this; |
|
} |
|
|
|
$parent = $this->parent; |
|
while ($parent !== null) { |
|
if (($parent->type != self::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 |
|
*/ |
|
public function hasChildren($countEmptyContainers = true): bool |
|
{ |
|
$retval = false; |
|
if ($countEmptyContainers) { |
|
if (count($this->children)) { |
|
$retval = true; |
|
} |
|
} else { |
|
foreach ($this->children as $child) { |
|
if ($child->type == self::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 |
|
*/ |
|
public function hasSiblings(): bool |
|
{ |
|
$retval = false; |
|
$paths = $this->getPaths(); |
|
if (count($paths['aPath_clean']) > 3) { |
|
return true; |
|
} |
|
|
|
foreach ($this->parent->children as $child) { |
|
if ($child !== $this && ($child->type == self::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(): int |
|
{ |
|
$retval = 0; |
|
foreach ($this->children as $child) { |
|
if ($child->type == self::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(): array |
|
{ |
|
$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 = '') |
|
{ |
|
if (isset($GLOBALS['cfg']['Server']['DisableIS']) && ! $GLOBALS['cfg']['Server']['DisableIS']) { |
|
return $this->getDataFromInfoSchema($pos, $searchClause); |
|
} |
|
|
|
if ($GLOBALS['dbs_to_test'] === false) { |
|
return $this->getDataFromShowDatabases($pos, $searchClause); |
|
} |
|
|
|
return $this->getDataFromShowDatabasesLike($pos, $searchClause); |
|
} |
|
|
|
/** |
|
* 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 = '') |
|
{ |
|
global $dbi; |
|
|
|
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); |
|
|
|
return (int) $dbi->fetchValue($query); |
|
} |
|
|
|
if ($GLOBALS['dbs_to_test'] === false) { |
|
$query = 'SHOW DATABASES '; |
|
$query .= $this->getWhereClause('Database', $searchClause); |
|
|
|
return (int) $dbi->queryAndGetNumRows($query); |
|
} |
|
|
|
$retval = 0; |
|
foreach ($this->getDatabasesToSearch($searchClause) as $db) { |
|
$query = "SHOW DATABASES LIKE '" . $db . "'"; |
|
$retval += (int) $dbi->queryAndGetNumRows($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 '; |
|
|
|
return (int) $dbi->fetchValue($query); |
|
} |
|
|
|
if ($GLOBALS['dbs_to_test'] !== false) { |
|
$prefixMap = []; |
|
foreach ($this->getDatabasesToSearch($searchClause) as $db) { |
|
$query = "SHOW DATABASES LIKE '" . $db . "'"; |
|
$handle = $dbi->tryQuery($query); |
|
if ($handle === false) { |
|
continue; |
|
} |
|
|
|
while ($arr = $handle->fetchRow()) { |
|
if ($this->isHideDb($arr[0])) { |
|
continue; |
|
} |
|
|
|
$prefix = strstr($arr[0], $dbSeparator, true); |
|
if ($prefix === false) { |
|
$prefix = $arr[0]; |
|
} |
|
|
|
$prefixMap[$prefix] = 1; |
|
} |
|
} |
|
|
|
return count($prefixMap); |
|
} |
|
|
|
$prefixMap = []; |
|
$query = 'SHOW DATABASES '; |
|
$query .= $this->getWhereClause('Database', $searchClause); |
|
$handle = $dbi->tryQuery($query); |
|
if ($handle !== false) { |
|
while ($arr = $handle->fetchRow()) { |
|
$prefix = strstr($arr[0], $dbSeparator, true); |
|
if ($prefix === false) { |
|
$prefix = $arr[0]; |
|
} |
|
|
|
$prefixMap[$prefix] = 1; |
|
} |
|
} |
|
|
|
return count($prefixMap); |
|
} |
|
|
|
/** |
|
* Detemines whether a given database should be hidden according to 'hide_db' |
|
* |
|
* @param string $db database name |
|
*/ |
|
private function isHideDb($db): bool |
|
{ |
|
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) |
|
{ |
|
global $dbi; |
|
|
|
$databases = []; |
|
if (! empty($searchClause)) { |
|
$databases = [ |
|
'%' . $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 = '') |
|
{ |
|
global $dbi; |
|
|
|
$whereClause = 'WHERE TRUE '; |
|
if (! empty($searchClause)) { |
|
$whereClause .= 'AND ' . Util::backquote($columnName) |
|
. " LIKE '%"; |
|
$whereClause .= $dbi->escapeString($searchClause); |
|
$whereClause .= "%' "; |
|
} |
|
|
|
if (! empty($GLOBALS['cfg']['Server']['hide_db'])) { |
|
$whereClause .= 'AND ' . Util::backquote($columnName) |
|
. " NOT REGEXP '" |
|
. $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 '" |
|
. $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(): string |
|
{ |
|
return ''; |
|
} |
|
|
|
/** |
|
* Returns CSS classes for a node |
|
* |
|
* @param bool $match Whether the node matched loaded tree |
|
* |
|
* @return string with html classes. |
|
*/ |
|
public function getCssClasses($match): string |
|
{ |
|
if (! $GLOBALS['cfg']['NavigationTreeEnableExpansion']) { |
|
return ''; |
|
} |
|
|
|
$result = ['expander']; |
|
|
|
if ($this->isGroup || $match) { |
|
$result[] = 'loaded'; |
|
} |
|
|
|
if ($this->type == self::CONTAINER) { |
|
$result[] = 'container'; |
|
} |
|
|
|
return implode(' ', $result); |
|
} |
|
|
|
/** |
|
* Returns icon for the node |
|
* |
|
* @param bool $match Whether the node matched loaded tree |
|
* |
|
* @return string with image name |
|
*/ |
|
public function getIcon($match): string |
|
{ |
|
if (! $GLOBALS['cfg']['NavigationTreeEnableExpansion']) { |
|
return ''; |
|
} |
|
|
|
if ($match) { |
|
$this->visible = true; |
|
|
|
return Generator::getImage('b_minus'); |
|
} |
|
|
|
return Generator::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() |
|
{ |
|
global $dbi; |
|
|
|
$navigationItemsHidingFeature = $this->relation->getRelationParameters()->navigationItemsHidingFeature; |
|
if ($navigationItemsHidingFeature !== null) { |
|
$navTable = Util::backquote($navigationItemsHidingFeature->database) |
|
. '.' . Util::backquote($navigationItemsHidingFeature->navigationHiding); |
|
$sqlQuery = 'SELECT `db_name`, COUNT(*) AS `count` FROM ' . $navTable |
|
. " WHERE `username`='" |
|
. $dbi->escapeString($GLOBALS['cfg']['Server']['user']) . "'" |
|
. ' GROUP BY `db_name`'; |
|
|
|
return $dbi->fetchResult($sqlQuery, 'db_name', 'count', DatabaseInterface::CONNECT_CONTROL); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
/** |
|
* @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 getDataFromInfoSchema($pos, $searchClause) |
|
{ |
|
global $dbi, $cfg; |
|
|
|
$maxItems = $cfg['FirstLevelNavigationItems']; |
|
if (! $cfg['NavigationTreeEnableGrouping'] || ! $cfg['ShowDatabasesNavigationAsTree']) { |
|
$query = sprintf( |
|
'SELECT `SCHEMA_NAME` FROM `INFORMATION_SCHEMA`.`SCHEMATA` %sORDER BY `SCHEMA_NAME` LIMIT %d, %d', |
|
$this->getWhereClause('SCHEMA_NAME', $searchClause), |
|
$pos, |
|
$maxItems |
|
); |
|
|
|
return $dbi->fetchResult($query); |
|
} |
|
|
|
$dbSeparator = $cfg['NavigationTreeDbSeparator']; |
|
$query = sprintf( |
|
'SELECT `SCHEMA_NAME` FROM `INFORMATION_SCHEMA`.`SCHEMATA`, (SELECT DB_first_level' |
|
. ' FROM ( SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, \'%1$s\', 1) DB_first_level' |
|
. ' FROM INFORMATION_SCHEMA.SCHEMATA %2$s) t' |
|
. ' ORDER BY DB_first_level ASC LIMIT %3$d, %4$d) t2' |
|
. ' %2$sAND 1 = LOCATE(CONCAT(DB_first_level, \'%1$s\'),' |
|
. ' CONCAT(SCHEMA_NAME, \'%1$s\')) ORDER BY SCHEMA_NAME ASC', |
|
$dbi->escapeString($dbSeparator), |
|
$this->getWhereClause('SCHEMA_NAME', $searchClause), |
|
$pos, |
|
$maxItems |
|
); |
|
|
|
return $dbi->fetchResult($query); |
|
} |
|
|
|
/** |
|
* @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 getDataFromShowDatabases($pos, $searchClause) |
|
{ |
|
global $dbi, $cfg; |
|
|
|
$maxItems = $cfg['FirstLevelNavigationItems']; |
|
if (! $cfg['NavigationTreeEnableGrouping'] || ! $cfg['ShowDatabasesNavigationAsTree']) { |
|
$handle = $dbi->tryQuery(sprintf( |
|
'SHOW DATABASES %s', |
|
$this->getWhereClause('Database', $searchClause) |
|
)); |
|
if ($handle === false) { |
|
return []; |
|
} |
|
|
|
$count = 0; |
|
if (! $handle->seek($pos)) { |
|
return []; |
|
} |
|
|
|
$retval = []; |
|
while ($arr = $handle->fetchRow()) { |
|
if ($count >= $maxItems) { |
|
break; |
|
} |
|
|
|
$retval[] = $arr[0]; |
|
$count++; |
|
} |
|
|
|
return $retval; |
|
} |
|
|
|
$dbSeparator = $cfg['NavigationTreeDbSeparator']; |
|
$handle = $dbi->tryQuery(sprintf( |
|
'SHOW DATABASES %s', |
|
$this->getWhereClause('Database', $searchClause) |
|
)); |
|
$prefixes = []; |
|
if ($handle !== false) { |
|
$prefixMap = []; |
|
$total = $pos + $maxItems; |
|
while ($arr = $handle->fetchRow()) { |
|
$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); |
|
} |
|
|
|
$subClauses = []; |
|
foreach ($prefixes as $prefix) { |
|
$subClauses[] = sprintf( |
|
' LOCATE(\'%1$s%2$s\', CONCAT(`Database`, \'%2$s\')) = 1 ', |
|
$dbi->escapeString((string) $prefix), |
|
$dbSeparator |
|
); |
|
} |
|
|
|
$query = sprintf( |
|
'SHOW DATABASES %sAND (%s)', |
|
$this->getWhereClause('Database', $searchClause), |
|
implode('OR', $subClauses) |
|
); |
|
|
|
return $dbi->fetchResult($query); |
|
} |
|
|
|
/** |
|
* @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 getDataFromShowDatabasesLike($pos, $searchClause) |
|
{ |
|
global $dbi, $cfg; |
|
|
|
$maxItems = $cfg['FirstLevelNavigationItems']; |
|
if (! $cfg['NavigationTreeEnableGrouping'] || ! $cfg['ShowDatabasesNavigationAsTree']) { |
|
$retval = []; |
|
$count = 0; |
|
foreach ($this->getDatabasesToSearch($searchClause) as $db) { |
|
$handle = $dbi->tryQuery(sprintf('SHOW DATABASES LIKE \'%s\'', $db)); |
|
if ($handle === false) { |
|
continue; |
|
} |
|
|
|
while ($arr = $handle->fetchRow()) { |
|
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 = $cfg['NavigationTreeDbSeparator']; |
|
$retval = []; |
|
$prefixMap = []; |
|
$total = $pos + $maxItems; |
|
foreach ($this->getDatabasesToSearch($searchClause) as $db) { |
|
$handle = $dbi->tryQuery(sprintf('SHOW DATABASES LIKE \'%s\'', $db)); |
|
if ($handle === false) { |
|
continue; |
|
} |
|
|
|
while ($arr = $handle->fetchRow()) { |
|
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) { |
|
$handle = $dbi->tryQuery(sprintf('SHOW DATABASES LIKE \'%s\'', $db)); |
|
if ($handle === false) { |
|
continue; |
|
} |
|
|
|
while ($arr = $handle->fetchRow()) { |
|
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; |
|
} |
|
}
|
|
|