Removing old documentation
[openemr.git] / phpmyadmin / libraries / navigation / Nodes / Node.class.php
blobce49a818654615f966679d5b42137a163bcb43ac
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Functionality for the navigation tree in the left frame
6 * @package PhpMyAdmin-Navigation
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
12 /**
13 * The Node is the building block for the collapsible navigation tree
15 * @package PhpMyAdmin-Navigation
17 class Node
19 /**
20 * @var int Defines a possible node type
22 const CONTAINER = 0;
24 /**
25 * @var int Defines a possible node type
27 const OBJECT = 1;
29 /**
30 * @var string A non-unique identifier for the node
31 * This may be trimmed when grouping nodes
33 public $name = "";
35 /**
36 * @var string A non-unique identifier for the node
37 * This will never change after being assigned
39 public $real_name = "";
41 /**
42 * @var int May be one of CONTAINER or OBJECT
44 public $type = Node::OBJECT;
46 /**
47 * @var bool Whether this object has been created while grouping nodes
48 * Only relevant if the node is of type CONTAINER
50 public $is_group;
52 /**
53 * @var bool Whether to add a "display: none;" CSS
54 * rule to the node when rendering it
56 public $visible = false;
58 /**
59 * @var Node A reference to the parent object of
60 * this node, NULL for the root node.
62 public $parent;
64 /**
65 * @var Node[] An array of Node objects that are
66 * direct children of this node
68 public $children = array();
70 /**
71 * @var Mixed A string used to group nodes, or an array of strings
72 * Only relevant if the node is of type CONTAINER
74 public $separator = '';
76 /**
77 * @var int How many time to recursively apply the grouping function
78 * Only relevant if the node is of type CONTAINER
80 public $separator_depth = 1;
82 /**
83 * @var string An IMG tag, used when rendering the node
85 public $icon;
87 /**
88 * @var Array An array of A tags, used when rendering the node
89 * The indexes in the array may be 'icon' and 'text'
91 public $links;
93 /**
94 * @var string HTML title
96 public $title;
98 /**
99 * @var string Extra CSS classes for the node
101 public $classes = '';
104 * @var bool Whether this node is a link for creating new objects
106 public $isNew = false;
109 * @var int The position for the pagination of
110 * the branch at the second level of the tree
112 public $pos2 = 0;
115 * @var int The position for the pagination of
116 * the branch at the third level of the tree
118 public $pos3 = 0;
121 * Initialises the class by setting the mandatory variables
123 * @param string $name An identifier for the new node
124 * @param int $type Type of node, may be one of CONTAINER or OBJECT
125 * @param bool $is_group Whether this object has been created
126 * while grouping nodes
128 public function __construct($name, $type = Node::OBJECT, $is_group = false)
130 if (! empty($name)) {
131 $this->name = $name;
132 $this->real_name = $name;
134 if ($type === Node::CONTAINER) {
135 $this->type = Node::CONTAINER;
137 $this->is_group = (bool)$is_group;
141 * Adds a child node to this node
143 * @param Node $child A child node
145 * @return void
147 public function addChild($child)
149 $this->children[] = $child;
150 $child->parent = $this;
154 * Returns a child node given it's name
156 * @param string $name The name of requested child
157 * @param bool $real_name Whether to use the "real_name"
158 * instead of "name" in comparisons
160 * @return false|Node The requested child node or false,
161 * if the requested node cannot be found
163 public function getChild($name, $real_name = false)
165 if ($real_name) {
166 foreach ($this->children as $child) {
167 if ($child->real_name == $name) {
168 return $child;
171 } else {
172 foreach ($this->children as $child) {
173 if ($child->name == $name) {
174 return $child;
178 return false;
182 * Removes a child node from this node
184 * @param string $name The name of child to be removed
186 * @return void
188 public function removeChild($name)
190 foreach ($this->children as $key => $child) {
191 if ($child->name == $name) {
192 unset($this->children[$key]);
193 break;
199 * Retrieves the parents for a node
201 * @param bool $self Whether to include the Node itself in the results
202 * @param bool $containers Whether to include nodes of type CONTAINER
203 * @param bool $groups Whether to include nodes which have $group == true
205 * @return array An array of parent Nodes
207 public function parents($self = false, $containers = false, $groups = false)
209 $parents = array();
210 if ($self
211 && ($this->type != Node::CONTAINER || $containers)
212 && (!$this->is_group || $groups)
214 $parents[] = $this;
216 $parent = $this->parent;
217 while (isset($parent)) {
218 if (($parent->type != Node::CONTAINER || $containers)
219 && (!$parent->is_group || $groups)
221 $parents[] = $parent;
223 $parent = $parent->parent;
225 return $parents;
229 * Returns the actual parent of a node. If used twice on an index or columns
230 * node, it will return the table and database nodes. The names of the returned
231 * nodes can be used in SQL queries, etc...
233 * @return Node|false
235 public function realParent()
237 $retval = $this->parents();
238 if (count($retval) <= 0) {
239 return false;
242 return $retval[0];
246 * This function checks if the node has children nodes associated with it
248 * @param bool $count_empty_containers Whether to count empty child
249 * containers as valid children
251 * @return bool Whether the node has child nodes
253 public function hasChildren($count_empty_containers = true)
255 $retval = false;
256 if ($count_empty_containers) {
257 if (count($this->children)) {
258 $retval = true;
260 } else {
261 foreach ($this->children as $child) {
262 if ($child->type == Node::OBJECT || $child->hasChildren(false)) {
263 $retval = true;
264 break;
268 return $retval;
272 * Returns true if the node has some siblings (other nodes on the same tree
273 * level, in the same branch), false otherwise.
274 * The only exception is for nodes on
275 * the third level of the tree (columns and indexes), for which the function
276 * always returns true. This is because we want to render the containers
277 * for these nodes
279 * @return bool
281 public function hasSiblings()
283 $retval = false;
284 $paths = $this->getPaths();
285 if (count($paths['aPath_clean']) > 3) {
286 $retval = true;
287 return $retval;
290 foreach ($this->parent->children as $child) {
291 if ($child !== $this
292 && ($child->type == Node::OBJECT || $child->hasChildren(false))
294 $retval = true;
295 break;
298 return $retval;
302 * Returns the number of child nodes that a node has associated with it
304 * @return int The number of children nodes
306 public function numChildren()
308 $retval = 0;
309 foreach ($this->children as $child) {
310 if ($child->type == Node::OBJECT) {
311 $retval++;
312 } else {
313 $retval += $child->numChildren();
316 return $retval;
320 * Returns the actual path and the virtual paths for a node
321 * both as clean arrays and base64 encoded strings
323 * @return array
325 public function getPaths()
327 $aPath = array();
328 $aPath_clean = array();
329 foreach ($this->parents(true, true, false) as $parent) {
330 $aPath[] = base64_encode($parent->real_name);
331 $aPath_clean[] = $parent->real_name;
333 $aPath = implode('.', array_reverse($aPath));
334 $aPath_clean = array_reverse($aPath_clean);
336 $vPath = array();
337 $vPath_clean = array();
338 foreach ($this->parents(true, true, true) as $parent) {
339 $vPath[] = base64_encode($parent->name);
340 $vPath_clean[] = $parent->name;
342 $vPath = implode('.', array_reverse($vPath));
343 $vPath_clean = array_reverse($vPath_clean);
345 return array(
346 'aPath' => $aPath,
347 'aPath_clean' => $aPath_clean,
348 'vPath' => $vPath,
349 'vPath_clean' => $vPath_clean
354 * Returns the names of children of type $type present inside this container
355 * This method is overridden by the Node_Database and Node_Table classes
357 * @param string $type The type of item we are looking for
358 * ('tables', 'views', etc)
359 * @param int $pos The offset of the list within the results
360 * @param string $searchClause A string used to filter the results of the query
362 * @return array
364 public function getData($type, $pos, $searchClause = '')
366 $maxItems = $GLOBALS['cfg']['FirstLevelNavigationItems'];
367 if (!$GLOBALS['cfg']['NavigationTreeEnableGrouping']
368 || !$GLOBALS['cfg']['ShowDatabasesNavigationAsTree']
370 if (isset($GLOBALS['cfg']['Server']['DisableIS'])
371 && ! $GLOBALS['cfg']['Server']['DisableIS']
373 $query = "SELECT `SCHEMA_NAME` ";
374 $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA` ";
375 $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause);
376 $query .= "ORDER BY `SCHEMA_NAME` ";
377 $query .= "LIMIT $pos, $maxItems";
378 $retval = $GLOBALS['dbi']->fetchResult($query);
379 return $retval;
382 if ($GLOBALS['dbs_to_test'] === false) {
383 $retval = array();
384 $query = "SHOW DATABASES ";
385 $query .= $this->_getWhereClause('Database', $searchClause);
386 $handle = $GLOBALS['dbi']->tryQuery($query);
387 if ($handle === false) {
388 return $retval;
391 $count = 0;
392 if (!$GLOBALS['dbi']->dataSeek($handle, $pos)) {
393 return $retval;
396 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) {
397 if ($count < $maxItems) {
398 $retval[] = $arr[0];
399 $count++;
400 } else {
401 break;
405 return $retval;
408 $retval = array();
409 $count = 0;
410 foreach ($this->_getDatabasesToSearch($searchClause) as $db) {
411 $query = "SHOW DATABASES LIKE '" . $db . "'";
412 $handle = $GLOBALS['dbi']->tryQuery($query);
413 if ($handle === false) {
414 continue;
417 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) {
418 if ($this->_isHideDb($arr[0])) {
419 continue;
421 if (in_array($arr[0], $retval)) {
422 continue;
425 if ($pos <= 0 && $count < $maxItems) {
426 $retval[] = $arr[0];
427 $count++;
429 $pos--;
432 sort($retval);
433 return $retval;
436 $dbSeparator = $GLOBALS['cfg']['NavigationTreeDbSeparator'];
437 if (isset($GLOBALS['cfg']['Server']['DisableIS'])
438 && ! $GLOBALS['cfg']['Server']['DisableIS']
440 $query = "SELECT `SCHEMA_NAME` ";
441 $query .= "FROM `INFORMATION_SCHEMA`.`SCHEMATA`, ";
442 $query .= "(";
443 $query .= "SELECT DB_first_level ";
444 $query .= "FROM ( ";
445 $query .= "SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, ";
446 $query .= "'$dbSeparator', 1) ";
447 $query .= "DB_first_level ";
448 $query .= "FROM INFORMATION_SCHEMA.SCHEMATA ";
449 $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause);
450 $query .= ") t ";
451 $query .= "ORDER BY DB_first_level ASC ";
452 $query .= "LIMIT $pos, $maxItems";
453 $query .= ") t2 ";
454 $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause);
455 $query .= "AND 1 = LOCATE(CONCAT(DB_first_level, ";
456 $query .= "'$dbSeparator'), ";
457 $query .= "CONCAT(SCHEMA_NAME, ";
458 $query .= "'$dbSeparator')) ";
459 $query .= "ORDER BY SCHEMA_NAME ASC";
460 $retval = $GLOBALS['dbi']->fetchResult($query);
461 return $retval;
464 if ($GLOBALS['dbs_to_test'] === false) {
465 $query = "SHOW DATABASES ";
466 $query .= $this->_getWhereClause('Database', $searchClause);
467 $handle = $GLOBALS['dbi']->tryQuery($query);
468 $prefixes = array();
469 if ($handle !== false) {
470 $prefixMap = array();
471 $total = $pos + $maxItems;
472 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) {
473 $prefix = strstr($arr[0], $dbSeparator, true);
474 if ($prefix === false) {
475 $prefix = $arr[0];
477 $prefixMap[$prefix] = 1;
478 if (sizeof($prefixMap) == $total) {
479 break;
482 $prefixes = array_slice(array_keys($prefixMap), $pos);
485 $query = "SHOW DATABASES ";
486 $query .= $this->_getWhereClause('Database', $searchClause);
487 $query .= " AND (";
488 $subClauses = array();
489 foreach ($prefixes as $prefix) {
490 $subClauses[] = " LOCATE('"
491 . PMA_Util::sqlAddSlashes($prefix) . $dbSeparator . "', "
492 . "CONCAT(`Database`, '" . $dbSeparator . "')) = 1 ";
494 $query .= implode("OR", $subClauses) . ")";
495 $retval = $GLOBALS['dbi']->fetchResult($query);
496 return $retval;
499 $retval = array();
500 $prefixMap = array();
501 $total = $pos + $maxItems;
502 foreach ($this->_getDatabasesToSearch($searchClause) as $db) {
503 $query = "SHOW DATABASES LIKE '" . $db . "'";
504 $handle = $GLOBALS['dbi']->tryQuery($query);
505 if ($handle === false) {
506 continue;
509 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) {
510 if ($this->_isHideDb($arr[0])) {
511 continue;
513 $prefix = strstr($arr[0], $dbSeparator, true);
514 if ($prefix === false) {
515 $prefix = $arr[0];
517 $prefixMap[$prefix] = 1;
518 if (sizeof($prefixMap) == $total) {
519 break 2;
523 $prefixes = array_slice(array_keys($prefixMap), $pos);
525 foreach ($this->_getDatabasesToSearch($searchClause) as $db) {
526 $query = "SHOW DATABASES LIKE '" . $db . "'";
527 $handle = $GLOBALS['dbi']->tryQuery($query);
528 if ($handle === false) {
529 continue;
532 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) {
533 if ($this->_isHideDb($arr[0])) {
534 continue;
536 if (in_array($arr[0], $retval)) {
537 continue;
540 foreach ($prefixes as $prefix) {
541 $starts_with = strpos(
542 $arr[0] . $dbSeparator,
543 $prefix . $dbSeparator
544 ) === 0;
545 if ($starts_with) {
546 $retval[] = $arr[0];
547 break;
552 sort($retval);
554 return $retval;
558 * Returns the number of children of type $type present inside this container
559 * This method is overridden by the Node_Database and Node_Table classes
561 * @param string $type The type of item we are looking for
562 * ('tables', 'views', etc)
563 * @param string $searchClause A string used to filter the results of the query
565 * @return int
567 public function getPresence($type = '', $searchClause = '')
569 if (!$GLOBALS['cfg']['NavigationTreeEnableGrouping']
570 || !$GLOBALS['cfg']['ShowDatabasesNavigationAsTree']
572 if (isset($GLOBALS['cfg']['Server']['DisableIS'])
573 && ! $GLOBALS['cfg']['Server']['DisableIS']
575 $query = "SELECT COUNT(*) ";
576 $query .= "FROM INFORMATION_SCHEMA.SCHEMATA ";
577 $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause);
578 $retval = (int)$GLOBALS['dbi']->fetchValue($query);
579 return $retval;
582 if ($GLOBALS['dbs_to_test'] === false) {
583 $query = "SHOW DATABASES ";
584 $query .= $this->_getWhereClause('Database', $searchClause);
585 $retval = $GLOBALS['dbi']->numRows(
586 $GLOBALS['dbi']->tryQuery($query)
588 return $retval;
591 $retval = 0;
592 foreach ($this->_getDatabasesToSearch($searchClause) as $db) {
593 $query = "SHOW DATABASES LIKE '" . $db . "'";
594 $retval += $GLOBALS['dbi']->numRows(
595 $GLOBALS['dbi']->tryQuery($query)
598 return $retval;
601 $dbSeparator = $GLOBALS['cfg']['NavigationTreeDbSeparator'];
602 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
603 $query = "SELECT COUNT(*) ";
604 $query .= "FROM ( ";
605 $query .= "SELECT DISTINCT SUBSTRING_INDEX(SCHEMA_NAME, ";
606 $query .= "'$dbSeparator', 1) ";
607 $query .= "DB_first_level ";
608 $query .= "FROM INFORMATION_SCHEMA.SCHEMATA ";
609 $query .= $this->_getWhereClause('SCHEMA_NAME', $searchClause);
610 $query .= ") t ";
611 $retval = (int)$GLOBALS['dbi']->fetchValue($query);
612 return $retval;
615 if ($GLOBALS['dbs_to_test'] !== false) {
616 $prefixMap = array();
617 foreach ($this->_getDatabasesToSearch($searchClause) as $db) {
618 $query = "SHOW DATABASES LIKE '" . $db . "'";
619 $handle = $GLOBALS['dbi']->tryQuery($query);
620 if ($handle === false) {
621 continue;
624 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) {
625 if ($this->_isHideDb($arr[0])) {
626 continue;
628 $prefix = strstr($arr[0], $dbSeparator, true);
629 if ($prefix === false) {
630 $prefix = $arr[0];
632 $prefixMap[$prefix] = 1;
635 $retval = count($prefixMap);
636 return $retval;
639 $prefixMap = array();
640 $query = "SHOW DATABASES ";
641 $query .= $this->_getWhereClause('Database', $searchClause);
642 $handle = $GLOBALS['dbi']->tryQuery($query);
643 if ($handle !== false) {
644 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) {
645 $prefix = strstr($arr[0], $dbSeparator, true);
646 if ($prefix === false) {
647 $prefix = $arr[0];
649 $prefixMap[$prefix] = 1;
652 $retval = count($prefixMap);
654 return $retval;
658 * Detemines whether a given database should be hidden according to 'hide_db'
660 * @param string $db database name
662 * @return boolean whether to hide
664 private function _isHideDb($db)
666 if (! empty($GLOBALS['cfg']['Server']['hide_db'])
667 && preg_match('/' . $GLOBALS['cfg']['Server']['hide_db'] . '/', $db)
669 return true;
671 return false;
675 * Get the list of databases for 'SHOW DATABASES LIKE' queries.
676 * If a search clause is set it gets the highest priority while only_db gets
677 * the next priority. In case both are empty list of databases determined by
678 * GRANTs are used
680 * @param string $searchClause search clause
682 * @return array array of databases
684 private function _getDatabasesToSearch($searchClause)
686 if (! empty($searchClause)) {
687 $databases = array(
688 "%" . PMA_Util::sqlAddSlashes($searchClause, true) . "%"
690 } elseif (! empty($GLOBALS['cfg']['Server']['only_db'])) {
691 $databases = $GLOBALS['cfg']['Server']['only_db'];
692 } elseif (! empty($GLOBALS['dbs_to_test'])) {
693 $databases = $GLOBALS['dbs_to_test'];
695 sort($databases);
696 return $databases;
700 * Returns the WHERE clause depending on the $searchClause parameter
701 * and the hide_db directive
703 * @param string $columnName Column name of the column having database names
704 * @param string $searchClause A string used to filter the results of the query
706 * @return string
708 private function _getWhereClause($columnName, $searchClause = '')
710 $whereClause = "WHERE TRUE ";
711 if (! empty($searchClause)) {
712 $whereClause .= "AND " . PMA_Util::backquote($columnName) . " LIKE '%";
713 $whereClause .= PMA_Util::sqlAddSlashes(
714 $searchClause, true
716 $whereClause .= "%' ";
719 if (! empty($GLOBALS['cfg']['Server']['hide_db'])) {
720 $whereClause .= "AND " . PMA_Util::backquote($columnName)
721 . " NOT REGEXP '" . $GLOBALS['cfg']['Server']['hide_db'] . "' ";
724 if (! empty($GLOBALS['cfg']['Server']['only_db'])) {
725 if (is_string($GLOBALS['cfg']['Server']['only_db'])) {
726 $GLOBALS['cfg']['Server']['only_db'] = array(
727 $GLOBALS['cfg']['Server']['only_db']
730 $whereClause .= "AND (";
731 $subClauses = array();
732 foreach ($GLOBALS['cfg']['Server']['only_db'] as $each_only_db) {
733 $subClauses[] = " " . PMA_Util::backquote($columnName) . " LIKE '"
734 . $each_only_db . "' ";
736 $whereClause .= implode("OR", $subClauses) . ")";
738 return $whereClause;
742 * Returns HTML for control buttons displayed infront of a node
744 * @return String HTML for control buttons
746 public function getHtmlForControlButtons()
748 return '';
752 * Returns CSS classes for a node
754 * @param boolean $match Whether the node matched loaded tree
756 * @return String with html classes.
758 public function getCssClasses($match)
760 if (! $GLOBALS['cfg']['NavigationTreeEnableExpansion']
762 return '';
765 $result = array('expander');
767 if ($this->is_group || $match) {
768 $result[] = 'loaded';
770 if ($this->type == Node::CONTAINER) {
771 $result[] = 'container';
774 return implode(' ', $result);
778 * Returns icon for the node
780 * @param boolean $match Whether the node matched loaded tree
782 * @return String with image name
784 public function getIcon($match)
786 if (! $GLOBALS['cfg']['NavigationTreeEnableExpansion']
788 return '';
789 } elseif ($match && ! $this->is_group) {
790 $this->visible = true;
791 return PMA_Util::getImage('b_minus.png');
792 } else {
793 return PMA_Util::getImage('b_plus.png', __('Expand/Collapse'));
798 * Gets the count of hidden elements for each database
800 * @return array array containing the count of hidden elements for each database
802 public function getNavigationHidingData()
804 $cfgRelation = PMA_getRelationsParam();
805 if ($cfgRelation['navwork']) {
806 $navTable = PMA_Util::backquote($cfgRelation['db'])
807 . "." . PMA_Util::backquote($cfgRelation['navigationhiding']);
808 $sqlQuery = "SELECT `db_name`, COUNT(*) AS `count` FROM " . $navTable
809 . " WHERE `username`='"
810 . PMA_Util::sqlAddSlashes($GLOBALS['cfg']['Server']['user']) . "'"
811 . " GROUP BY `db_name`";
812 $counts = $GLOBALS['dbi']->fetchResult(
813 $sqlQuery, 'db_name', 'count', $GLOBALS['controllink']
815 return $counts;
817 return null;