Removing old documentation
[openemr.git] / phpmyadmin / libraries / navigation / NavigationTree.class.php
blob1ad23cb2b764f8fa6c2f3a81688c585a4b9a5c13
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Functionality for the navigation tree
6 * @package PhpMyAdmin-Navigation
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
12 /**
13 * Displays a collapsible of database objects in the navigation frame
15 * @package PhpMyAdmin-Navigation
17 class PMA_NavigationTree
19 /**
20 * @var Node Reference to the root node of the tree
22 private $_tree;
24 /**
25 * @var array The actual paths to all expanded nodes in the tree
26 * This does not include nodes created after the grouping
27 * of nodes has been performed
29 private $_aPath = array();
31 /**
32 * @var array The virtual paths to all expanded nodes in the tree
33 * This includes nodes created after the grouping of
34 * nodes has been performed
36 private $_vPath = array();
38 /**
39 * @var int Position in the list of databases,
40 * used for pagination
42 private $_pos;
44 /**
45 * @var array The names of the type of items that are being paginated on
46 * the second level of the navigation tree. These may be
47 * tables, views, functions, procedures or events.
49 private $_pos2_name = array();
51 /**
52 * @var array The positions of nodes in the lists of tables, views,
53 * routines or events used for pagination
55 private $_pos2_value = array();
57 /**
58 * @var array The names of the type of items that are being paginated
59 * on the second level of the navigation tree.
60 * These may be columns or indexes
62 private $_pos3_name = array();
64 /**
65 * @var array The positions of nodes in the lists of columns or indexes
66 * used for pagination
68 private $_pos3_value = array();
70 /**
71 * @var string The search clause to use in SQL queries for
72 * fetching databases
73 * Used by the asynchronous fast filter
75 private $_searchClause = '';
77 /**
78 * @var string The search clause to use in SQL queries for
79 * fetching nodes
80 * Used by the asynchronous fast filter
82 private $_searchClause2 = '';
84 /**
85 * @var bool Whether a warning was raised for large item groups
86 * which can affect performance.
88 private $_largeGroupWarning = false;
90 /**
91 * Initialises the class
93 public function __construct()
95 // Save the position at which we are in the database list
96 if (isset($_REQUEST['pos'])) {
97 $this->_pos = (int) $_REQUEST['pos'];
99 if (! isset($this->_pos)) {
100 $this->_pos = $this->_getNavigationDbPos();
102 // Get the active node
103 if (isset($_REQUEST['aPath'])) {
104 $this->_aPath[0] = $this->_parsePath($_REQUEST['aPath']);
105 $this->_pos2_name[0] = $_REQUEST['pos2_name'];
106 $this->_pos2_value[0] = $_REQUEST['pos2_value'];
107 if (isset($_REQUEST['pos3_name'])) {
108 $this->_pos3_name[0] = $_REQUEST['pos3_name'];
109 $this->_pos3_value[0] = $_REQUEST['pos3_value'];
111 } else if (isset($_REQUEST['n0_aPath'])) {
112 $count = 0;
113 while (isset($_REQUEST['n' . $count . '_aPath'])) {
114 $this->_aPath[$count] = $this->_parsePath(
115 $_REQUEST['n' . $count . '_aPath']
117 $index = 'n' . $count . '_pos2_';
118 $this->_pos2_name[$count] = $_REQUEST[$index . 'name'];
119 $this->_pos2_value[$count] = $_REQUEST[$index . 'value'];
120 $index = 'n' . $count . '_pos3_';
121 if (isset($_REQUEST[$index])) {
122 $this->_pos3_name[$count] = $_REQUEST[$index . 'name'];
123 $this->_pos3_value[$count] = $_REQUEST[$index . 'value'];
125 $count++;
128 if (isset($_REQUEST['vPath'])) {
129 $this->_vPath[0] = $this->_parsePath($_REQUEST['vPath']);
130 } else if (isset($_REQUEST['n0_vPath'])) {
131 $count = 0;
132 while (isset($_REQUEST['n' . $count . '_vPath'])) {
133 $this->_vPath[$count] = $this->_parsePath(
134 $_REQUEST['n' . $count . '_vPath']
136 $count++;
139 if (isset($_REQUEST['searchClause'])) {
140 $this->_searchClause = $_REQUEST['searchClause'];
142 if (isset($_REQUEST['searchClause2'])) {
143 $this->_searchClause2 = $_REQUEST['searchClause2'];
145 // Initialise the tree by creating a root node
146 $node = PMA_NodeFactory::getInstance('Node_Database_Container', 'root');
147 $this->_tree = $node;
148 if ($GLOBALS['cfg']['NavigationTreeEnableGrouping']
149 && $GLOBALS['cfg']['ShowDatabasesNavigationAsTree']
151 $this->_tree->separator = $GLOBALS['cfg']['NavigationTreeDbSeparator'];
152 $this->_tree->separator_depth = 10000;
157 * Returns the database position for the page selector
159 * @return int
161 private function _getNavigationDbPos()
163 $retval = 0;
165 if (empty($GLOBALS['db'])) {
166 return $retval;
170 * @todo describe a scenario where this code is executed
172 if (! $GLOBALS['cfg']['Server']['DisableIS']) {
173 $query = "SELECT (COUNT(DB_first_level) DIV %d) * %d ";
174 $query .= "from ( ";
175 $query .= " SELECT distinct SUBSTRING_INDEX(SCHEMA_NAME, ";
176 $query .= " '{$GLOBALS['cfg']['NavigationTreeDbSeparator']}', 1) ";
177 $query .= " DB_first_level ";
178 $query .= " FROM INFORMATION_SCHEMA.SCHEMATA ";
179 $query .= " WHERE `SCHEMA_NAME` < '%s' ";
180 $query .= ") t ";
182 $retval = $GLOBALS['dbi']->fetchValue(
183 sprintf(
184 $query,
185 (int)$GLOBALS['cfg']['FirstLevelNavigationItems'],
186 (int)$GLOBALS['cfg']['FirstLevelNavigationItems'],
187 PMA_Util::sqlAddSlashes($GLOBALS['db'])
191 return $retval;
194 $prefixMap = array();
195 if ($GLOBALS['dbs_to_test'] === false) {
196 $handle = $GLOBALS['dbi']->tryQuery("SHOW DATABASES");
197 if ($handle !== false) {
198 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) {
199 if (strcasecmp($arr[0], $GLOBALS['db']) >= 0) {
200 break;
203 $prefix = strstr(
204 $arr[0],
205 $GLOBALS['cfg']['NavigationTreeDbSeparator'],
206 true
208 if ($prefix === false) {
209 $prefix = $arr[0];
211 $prefixMap[$prefix] = 1;
214 } else {
215 $databases = array();
216 foreach ($GLOBALS['dbs_to_test'] as $db) {
217 $query = "SHOW DATABASES LIKE '" . $db . "'";
218 $handle = $GLOBALS['dbi']->tryQuery($query);
219 if ($handle === false) {
220 continue;
222 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) {
223 $databases[] = $arr[0];
226 sort($databases);
227 foreach ($databases as $database) {
228 if (strcasecmp($database, $GLOBALS['db']) >= 0) {
229 break;
232 $prefix = strstr(
233 $database,
234 $GLOBALS['cfg']['NavigationTreeDbSeparator'],
235 true
237 if ($prefix === false) {
238 $prefix = $database;
240 $prefixMap[$prefix] = 1;
244 $navItems = (int) $GLOBALS['cfg']['FirstLevelNavigationItems'];
245 $retval = floor((count($prefixMap) / $navItems)) * $navItems;
246 return $retval;
250 * Converts an encoded path to a node in string format to an array
252 * @param string $string The path to parse
254 * @return array
256 private function _parsePath($string)
258 $path = explode('.', $string);
259 foreach ($path as $key => $value) {
260 $path[$key] = base64_decode($value);
262 return $path;
266 * Generates the tree structure so that it can be rendered later
268 * @return Node|false The active node or false in case of failure
270 private function _buildPath()
272 $retval = $this->_tree;
274 // Add all databases unconditionally
275 $data = $this->_tree->getData(
276 'databases',
277 $this->_pos,
278 $this->_searchClause
280 $hiddenCounts = $this->_tree->getNavigationHidingData();
281 foreach ($data as $db) {
282 $node = PMA_NodeFactory::getInstance('Node_Database', $db);
283 if (isset($hiddenCounts[$db])) {
284 $node->setHiddenCount($hiddenCounts[$db]);
286 $this->_tree->addChild($node);
289 // Whether build other parts of the tree depends
290 // on whether we have any paths in $this->_aPath
291 foreach ($this->_aPath as $key => $path) {
292 $retval = $this->_buildPathPart(
293 $path,
294 $this->_pos2_name[$key],
295 $this->_pos2_value[$key],
296 isset($this->_pos3_name[$key]) ? $this->_pos3_name[$key] : '',
297 isset($this->_pos3_value[$key]) ? $this->_pos3_value[$key] : ''
300 return $retval;
304 * Builds a branch of the tree
306 * @param array $path A paths pointing to the branch
307 * of the tree that needs to be built
308 * @param string $type2 The type of item being paginated on
309 * the second level of the tree
310 * @param int $pos2 The position for the pagination of
311 * the branch at the second level of the tree
312 * @param string $type3 The type of item being paginated on
313 * the third level of the tree
314 * @param int $pos3 The position for the pagination of
315 * the branch at the third level of the tree
317 * @return Node|false The active node or false in case of failure
319 private function _buildPathPart($path, $type2, $pos2, $type3, $pos3)
321 if (empty($pos2)) {
322 $pos2 = 0;
324 if (empty($pos3)) {
325 $pos3 = 0;
328 $retval = true;
329 if (count($path) <= 1) {
330 return $retval;
333 array_shift($path); // remove 'root'
334 /* @var $db Node_Database */
335 $db = $this->_tree->getChild($path[0]);
336 $retval = $db;
338 if ($db === false) {
339 return false;
342 $containers = $this->_addDbContainers($db, $type2, $pos2);
344 array_shift($path); // remove db
346 if ((count($path) <= 0
347 || !array_key_exists($path[0], $containers))
348 && count($containers) != 1
350 return $retval;
353 if (count($containers) == 1) {
354 $container = array_shift($containers);
355 } else {
356 $container = $db->getChild($path[0], true);
357 if ($container === false) {
358 return false;
361 $retval = $container;
363 if (count($container->children) <= 1) {
364 $dbData = $db->getData(
365 $container->real_name,
366 $pos2,
367 $this->_searchClause2
369 foreach ($dbData as $item) {
370 switch ($container->real_name) {
371 case 'events':
372 $node = PMA_NodeFactory::getInstance(
373 'Node_Event',
374 $item
376 break;
377 case 'functions':
378 $node = PMA_NodeFactory::getInstance(
379 'Node_Function',
380 $item
382 break;
383 case 'procedures':
384 $node = PMA_NodeFactory::getInstance(
385 'Node_Procedure',
386 $item
388 break;
389 case 'tables':
390 $node = PMA_NodeFactory::getInstance(
391 'Node_Table',
392 $item
394 break;
395 case 'views':
396 $node = PMA_NodeFactory::getInstance(
397 'Node_View',
398 $item
400 break;
401 default:
402 break;
404 if (isset($node)) {
405 if ($type2 == $container->real_name) {
406 $node->pos2 = $pos2;
408 $container->addChild($node);
412 if (count($path) > 1 && $path[0] != 'tables') {
413 $retval = false;
414 return $retval;
417 array_shift($path); // remove container
418 if (count($path) <= 0) {
419 return $retval;
422 /* @var $table Node_Table */
423 $table = $container->getChild($path[0], true);
424 if ($table === false) {
425 if (!$db->getPresence('tables', $path[0])) {
426 return false;
429 $node = PMA_NodeFactory::getInstance(
430 'Node_Table',
431 $path[0]
433 if ($type2 == $container->real_name) {
434 $node->pos2 = $pos2;
436 $container->addChild($node);
437 $table = $container->getChild($path[0], true);
439 $retval = $table;
440 $containers = $this->_addTableContainers(
441 $table,
442 $pos2,
443 $type3,
444 $pos3
446 array_shift($path); // remove table
447 if (count($path) <= 0
448 || !array_key_exists($path[0], $containers)
450 return $retval;
453 $container = $table->getChild($path[0], true);
454 $retval = $container;
455 $tableData = $table->getData(
456 $container->real_name,
457 $pos3
459 foreach ($tableData as $item) {
460 switch ($container->real_name) {
461 case 'indexes':
462 $node = PMA_NodeFactory::getInstance(
463 'Node_Index',
464 $item
466 break;
467 case 'columns':
468 $node = PMA_NodeFactory::getInstance(
469 'Node_Column',
470 $item
472 break;
473 case 'triggers':
474 $node = PMA_NodeFactory::getInstance(
475 'Node_Trigger',
476 $item
478 break;
479 default:
480 break;
482 if (isset($node)) {
483 $node->pos2 = $container->parent->pos2;
484 if ($type3 == $container->real_name) {
485 $node->pos3 = $pos3;
487 $container->addChild($node);
490 return $retval;
494 * Adds containers to a node that is a table
496 * References to existing children are returned
497 * if this function is called twice on the same node
499 * @param Node_Table $table The table node, new containers will be
500 * attached to this node
501 * @param int $pos2 The position for the pagination of
502 * the branch at the second level of the tree
503 * @param string $type3 The type of item being paginated on
504 * the third level of the tree
505 * @param int $pos3 The position for the pagination of
506 * the branch at the third level of the tree
508 * @return array An array of new nodes
510 private function _addTableContainers($table, $pos2, $type3, $pos3)
512 $retval = array();
513 if ($table->hasChildren(true) == 0) {
514 if ($table->getPresence('columns')) {
515 $retval['columns'] = PMA_NodeFactory::getInstance(
516 'Node_Column_Container'
519 if ($table->getPresence('indexes')) {
520 $retval['indexes'] = PMA_NodeFactory::getInstance(
521 'Node_Index_Container'
524 if ($table->getPresence('triggers')) {
525 $retval['triggers'] = PMA_NodeFactory::getInstance(
526 'Node_Trigger_Container'
529 // Add all new Nodes to the tree
530 foreach ($retval as $node) {
531 $node->pos2 = $pos2;
532 if ($type3 == $node->real_name) {
533 $node->pos3 = $pos3;
535 $table->addChild($node);
537 } else {
538 foreach ($table->children as $node) {
539 if ($type3 == $node->real_name) {
540 $node->pos3 = $pos3;
542 $retval[$node->real_name] = $node;
545 return $retval;
549 * Adds containers to a node that is a database
551 * References to existing children are returned
552 * if this function is called twice on the same node
554 * @param Node_Database $db The database node, new containers will be
555 * attached to this node
556 * @param string $type The type of item being paginated on
557 * the second level of the tree
558 * @param int $pos2 The position for the pagination of
559 * the branch at the second level of the tree
561 * @return array An array of new nodes
563 private function _addDbContainers($db, $type, $pos2)
565 // Get items to hide
566 $hidden = $db->getHiddenItems('group');
567 if (!$GLOBALS['cfg']['NavigationTreeShowTables']
568 && !in_array('tables', $hidden)
570 $hidden[] = 'tables';
572 if (!$GLOBALS['cfg']['NavigationTreeShowViews']
573 && !in_array('views', $hidden)
575 $hidden[] = 'views';
577 if (!$GLOBALS['cfg']['NavigationTreeShowFunctions']
578 && !in_array('functions', $hidden)
580 $hidden[] = 'functions';
582 if (!$GLOBALS['cfg']['NavigationTreeShowProcedures']
583 && !in_array('procedures', $hidden)
585 $hidden[] = 'procedures';
587 if (!$GLOBALS['cfg']['NavigationTreeShowEvents']
588 && !in_array('events', $hidden)
590 $hidden[] = 'events';
593 $retval = array();
594 if ($db->hasChildren(true) == 0) {
595 if (!in_array('tables', $hidden) && $db->getPresence('tables')) {
596 $retval['tables'] = PMA_NodeFactory::getInstance(
597 'Node_Table_Container'
600 if (!in_array('views', $hidden) && $db->getPresence('views')) {
601 $retval['views'] = PMA_NodeFactory::getInstance(
602 'Node_View_Container'
605 if (!in_array('functions', $hidden) && $db->getPresence('functions')) {
606 $retval['functions'] = PMA_NodeFactory::getInstance(
607 'Node_Function_Container'
610 if (!in_array('procedures', $hidden) && $db->getPresence('procedures')) {
611 $retval['procedures'] = PMA_NodeFactory::getInstance(
612 'Node_Procedure_Container'
615 if (!in_array('events', $hidden) && $db->getPresence('events')) {
616 $retval['events'] = PMA_NodeFactory::getInstance(
617 'Node_Event_Container'
620 // Add all new Nodes to the tree
621 foreach ($retval as $node) {
622 if ($type == $node->real_name) {
623 $node->pos2 = $pos2;
625 $db->addChild($node);
627 } else {
628 foreach ($db->children as $node) {
629 if ($type == $node->real_name) {
630 $node->pos2 = $pos2;
632 $retval[$node->real_name] = $node;
635 return $retval;
639 * Recursively groups tree nodes given a separator
641 * @param mixed $node The node to group or null
642 * to group the whole tree. If
643 * passed as an argument, $node
644 * must be of type CONTAINER
646 * @return void
648 public function groupTree($node = null)
650 if (! isset($node)) {
651 $node = $this->_tree;
653 $this->groupNode($node);
654 foreach ($node->children as $child) {
655 $this->groupTree($child);
660 * Recursively groups tree nodes given a separator
662 * @param Node $node The node to group
664 * @return void
666 public function groupNode($node)
668 if ($node->type != Node::CONTAINER
669 || ! $GLOBALS['cfg']['NavigationTreeEnableExpansion']
671 return;
674 $separators = array();
675 if (is_array($node->separator)) {
676 $separators = $node->separator;
677 } else if (strlen($node->separator)) {
678 $separators[] = $node->separator;
680 $prefixes = array();
681 if ($node->separator_depth > 0) {
682 foreach ($node->children as $child) {
683 $prefix_pos = false;
684 foreach ($separators as $separator) {
685 $sep_pos = /*overload*/mb_strpos($child->name, $separator);
686 if ($sep_pos != false
687 && $sep_pos != /*overload*/mb_strlen($child->name)
688 && $sep_pos != 0
689 && ($prefix_pos == false || $sep_pos < $prefix_pos)
691 $prefix_pos = $sep_pos;
694 if ($prefix_pos !== false) {
695 $prefix = /*overload*/mb_substr($child->name, 0, $prefix_pos);
696 if (! isset($prefixes[$prefix])) {
697 $prefixes[$prefix] = 1;
698 } else {
699 $prefixes[$prefix]++;
702 //Bug #4375: Check if prefix is the name of a DB, to create a group.
703 foreach ($node->children as $otherChild) {
704 if (array_key_exists($otherChild->name, $prefixes)) {
705 $prefixes[$otherChild->name]++;
709 //Check if prefix is the name of a DB, to create a group.
710 foreach ($node->children as $child) {
711 if (array_key_exists($child->name, $prefixes)) {
712 $prefixes[$child->name]++;
716 // It is not a group if it has only one item
717 foreach ($prefixes as $key => $value) {
718 if ($value == 1) {
719 unset($prefixes[$key]);
722 // rfe #1634 Don't group if there's only one group and no other items
723 if (count($prefixes) == 1) {
724 $keys = array_keys($prefixes);
725 $key = $keys[0];
726 if ($prefixes[$key] == count($node->children) - 1) {
727 unset($prefixes[$key]);
730 if (count($prefixes)) {
731 /** @var Node[] $groups */
732 $groups = array();
733 foreach ($prefixes as $key => $value) {
735 // warn about large groups
736 if ($value > 500 && ! $this->_largeGroupWarning) {
737 trigger_error(
739 'There are large item groups in navigation panel which '
740 . 'may affect the performance. Consider disabling item '
741 . 'grouping in the navigation panel.'
743 E_USER_WARNING
745 $this->_largeGroupWarning = true;
748 $groups[$key] = new Node(
749 $key,
750 Node::CONTAINER,
751 true
753 $groups[$key]->separator = $node->separator;
754 $groups[$key]->separator_depth = $node->separator_depth - 1;
755 $groups[$key]->icon = PMA_Util::getImage(
756 'b_group.png'
758 $groups[$key]->pos2 = $node->pos2;
759 $groups[$key]->pos3 = $node->pos3;
760 if ($node instanceof Node_Table_Container
761 || $node instanceof Node_View_Container
763 $tblGroup = '&amp;tbl_group=' . urlencode($key);
764 $groups[$key]->links = array(
765 'text' => $node->links['text'] . $tblGroup,
766 'icon' => $node->links['icon'] . $tblGroup
769 $node->addChild($groups[$key]);
770 foreach ($separators as $separator) {
771 $separatorLength = strlen($separator);
772 // FIXME: this could be more efficient
773 foreach ($node->children as $child) {
774 $keySeparatorLength = /*overload*/mb_strlen($key)
775 + $separatorLength;
776 $name_substring = /*overload*/mb_substr(
777 $child->name,
779 $keySeparatorLength
781 if (($name_substring != $key . $separator
782 && $child->name != $key)
783 || $child->type != Node::OBJECT
785 continue;
787 $class = get_class($child);
788 $new_child = PMA_NodeFactory::getInstance(
789 $class,
790 /*overload*/mb_substr(
791 $child->name,
792 $keySeparatorLength
796 if ($new_child instanceof Node_Database
797 && $child->getHiddenCount() > 0
799 $new_child->setHiddenCount($child->getHiddenCount());
802 $new_child->real_name = $child->real_name;
803 $new_child->icon = $child->icon;
804 $new_child->links = $child->links;
805 $new_child->pos2 = $child->pos2;
806 $new_child->pos3 = $child->pos3;
807 $groups[$key]->addChild($new_child);
808 foreach ($child->children as $elm) {
809 $new_child->addChild($elm);
811 $node->removeChild($child->name);
815 foreach ($prefixes as $key => $value) {
816 $this->groupNode($groups[$key]);
817 $groups[$key]->classes = "navGroup";
823 * Renders a state of the tree, used in light mode when
824 * either JavaScript and/or Ajax are disabled
826 * @return string HTML code for the navigation tree
828 public function renderState()
830 $this->_buildPath();
831 $retval = $this->_quickWarp();
832 $retval .= '<div class="clearfloat"></div>';
833 $retval .= '<ul>';
834 $retval .= $this->_fastFilterHtml($this->_tree);
835 if ($GLOBALS['cfg']['NavigationTreeEnableExpansion']
837 $retval .= $this->_controls();
839 $retval .= '</ul>';
840 $retval .= $this->_getPageSelector($this->_tree);
841 $this->groupTree();
842 $retval .= "<div id='pma_navigation_tree_content'><ul>";
843 $children = $this->_tree->children;
844 usort($children, array('PMA_NavigationTree', 'sortNode'));
845 $this->_setVisibility();
846 for ($i=0, $nbChildren = count($children); $i < $nbChildren; $i++) {
847 if ($i == 0) {
848 $retval .= $this->_renderNode($children[0], true, 'first');
849 } else if ($i + 1 != $nbChildren) {
850 $retval .= $this->_renderNode($children[$i], true);
851 } else {
852 $retval .= $this->_renderNode($children[$i], true, 'last');
855 $retval .= "</ul></div>";
856 return $retval;
860 * Renders a part of the tree, used for Ajax
861 * requests in light mode
863 * @return string HTML code for the navigation tree
865 public function renderPath()
867 $node = $this->_buildPath();
868 if ($node === false) {
869 $retval = false;
870 } else {
871 $this->groupTree();
872 $retval = "<div class='list_container' style='display: none;'>";
873 if (! empty($this->_searchClause) || ! empty($this->_searchClause2)) {
874 $retval .= "<ul class='search_results'>";
875 } else {
876 $retval .= "<ul>";
878 $listContent = $this->_fastFilterHtml($node);
879 $listContent .= $this->_getPageSelector($node);
880 $children = $node->children;
881 usort($children, array('PMA_NavigationTree', 'sortNode'));
882 for ($i=0, $nbChildren = count($children); $i < $nbChildren; $i++) {
883 if ($i + 1 != $nbChildren) {
884 $listContent .= $this->_renderNode($children[$i], true);
885 } else {
886 $listContent .= $this->_renderNode($children[$i], true, 'last');
889 $retval .= $listContent;
890 $retval .= "</ul>";
891 if (! $GLOBALS['cfg']['ShowDatabasesNavigationAsTree']) {
892 $retval .= "<span class='hide loaded_db'>";
893 $parents = $node->parents(true);
894 $retval .= urlencode($parents[0]->real_name);
895 $retval .= "</span>";
896 if (empty($listContent)) {
897 $retval .= "<div style='margin:0.75em'>";
898 $retval .= __('No tables found in database.');
899 $retval .= "</div>";
902 $retval .= "</div>";
905 if (! empty($this->_searchClause) || ! empty($this->_searchClause2)) {
906 $results = 0;
907 if (! empty($this->_searchClause2)) {
908 if (is_object($node->realParent())) {
909 $results = $node->realParent()->getPresence(
910 $node->real_name,
911 $this->_searchClause2
914 } else {
915 $results = $this->_tree->getPresence(
916 'databases',
917 $this->_searchClause
920 $results = sprintf(
921 _ngettext(
922 '%s result found',
923 '%s results found',
924 $results
926 $results
928 PMA_Response::getInstance()->addJSON(
929 'results',
930 $results
933 return $retval;
937 * Renders the parameters that are required on the client
938 * side to know which page(s) we will be requesting data from
940 * @param Node $node The node to create the pagination parameters for
942 * @return string
944 private function _getPaginationParamsHtml($node)
946 $retval = '';
947 $paths = $node->getPaths();
948 if (isset($paths['aPath_clean'][2])) {
949 $retval .= "<span class='hide pos2_name'>";
950 $retval .= $paths['aPath_clean'][2];
951 $retval .= "</span>";
952 $retval .= "<span class='hide pos2_value'>";
953 $retval .= $node->pos2;
954 $retval .= "</span>";
956 if (isset($paths['aPath_clean'][4])) {
957 $retval .= "<span class='hide pos3_name'>";
958 $retval .= $paths['aPath_clean'][4];
959 $retval .= "</span>";
960 $retval .= "<span class='hide pos3_value'>";
961 $retval .= $node->pos3;
962 $retval .= "</span>";
964 return $retval;
968 * Finds whether given tree matches this tree.
970 * @param array $tree Tree to check
971 * @param array $paths Paths to check
973 * @return boolean
975 private function _findTreeMatch($tree, $paths)
977 $match = false;
978 foreach ($tree as $path) {
979 $match = true;
980 foreach ($paths as $key => $part) {
981 if (! isset($path[$key]) || $part != $path[$key]) {
982 $match = false;
983 break;
986 if ($match) {
987 break;
990 return $match;
994 * Renders a single node or a branch of the tree
996 * @param Node $node The node to render
997 * @param bool $recursive Bool: Whether to render a single node or a branch
998 * @param string $class An additional class for the list item
1000 * @return string HTML code for the tree node or branch
1002 private function _renderNode($node, $recursive, $class = '')
1004 $retval = '';
1005 $paths = $node->getPaths();
1006 if ($node->hasSiblings()
1007 || $node->realParent() === false
1009 if ($node->type == Node::CONTAINER
1010 && count($node->children) == 0
1011 && $GLOBALS['is_ajax_request'] != true
1013 return '';
1015 $retval .= '<li class="' . trim($class . ' ' . $node->classes) . '">';
1016 $sterile = array(
1017 'events',
1018 'triggers',
1019 'functions',
1020 'procedures',
1021 'views',
1022 'columns',
1023 'indexes'
1025 $parentName = '';
1026 $parents = $node->parents(false, true);
1027 if (count($parents)) {
1028 $parentName = $parents[0]->real_name;
1030 // if node name itself is in sterile, then allow
1031 if ($node->is_group
1032 || (! in_array($parentName, $sterile) && ! $node->isNew)
1033 || (in_array($node->real_name, $sterile))
1035 $retval .= "<div class='block'>";
1036 $iClass = '';
1037 if ($class == 'first') {
1038 $iClass = " class='first'";
1040 $retval .= "<i$iClass></i>";
1041 if (strpos($class, 'last') === false) {
1042 $retval .= "<b></b>";
1045 $match = $this->_findTreeMatch(
1046 $this->_vPath,
1047 $paths['vPath_clean']
1050 $retval .= '<a class="' . $node->getCssClasses($match) . '"';
1051 $retval .= " href='#'>";
1052 $retval .= "<span class='hide aPath'>";
1053 $retval .= $paths['aPath'];
1054 $retval .= "</span>";
1055 $retval .= "<span class='hide vPath'>";
1056 $retval .= $paths['vPath'];
1057 $retval .= "</span>";
1058 $retval .= "<span class='hide pos'>";
1059 $retval .= $this->_pos;
1060 $retval .= "</span>";
1061 $retval .= $this->_getPaginationParamsHtml($node);
1062 if ($GLOBALS['cfg']['ShowDatabasesNavigationAsTree']
1063 || $parentName != 'root'
1065 $retval .= $node->getIcon($match);
1068 $retval .= "</a>";
1069 $retval .= "</div>";
1070 } else {
1071 $retval .= "<div class='block'>";
1072 $iClass = '';
1073 if ($class == 'first') {
1074 $iClass = " class='first'";
1076 $retval .= "<i$iClass></i>";
1077 $retval .= $this->_getPaginationParamsHtml($node);
1078 $retval .= "</div>";
1081 $linkClass = '';
1082 $haveAjax = array(
1083 'functions',
1084 'procedures',
1085 'events',
1086 'triggers',
1087 'indexes'
1089 $parent = $node->parents(false, true);
1090 $isNewView = $parent[0]->real_name == 'views' && $node->isNew === true;
1091 if ($parent[0]->type == Node::CONTAINER
1092 && (in_array($parent[0]->real_name, $haveAjax) || $isNewView)
1094 $linkClass = ' ajax';
1097 if ($node->type == Node::CONTAINER) {
1098 $retval .= "<i>";
1101 $divClass = '';
1103 if (isset($node->links['icon']) && !empty($node->links['icon'])) {
1104 $iconLinks = $node->links['icon'];
1105 $icons = $node->icon;
1106 if (!is_array($iconLinks)) {
1107 $iconLinks = array($iconLinks);
1108 $icons = array($icons);
1111 if (count($icons) > 1) {
1112 $divClass = 'double';
1116 $retval .= "<div class='block " . $divClass . "'>";
1118 if (isset($node->links['icon']) && !empty($node->links['icon'])) {
1119 $args = array();
1120 foreach ($node->parents(true) as $parent) {
1121 $args[] = urlencode($parent->real_name);
1124 foreach ($icons as $key => $icon) {
1125 $link = vsprintf($iconLinks[$key], $args);
1126 if ($linkClass != '') {
1127 $retval .= "<a class='$linkClass' href='$link'>";
1128 $retval .= "{$icon}</a>";
1129 } else {
1130 $retval .= "<a href='$link'>{$icon}</a>";
1133 } else {
1134 $retval .= "<u>{$node->icon}</u>";
1136 $retval .= "</div>";
1138 if (isset($node->links['text'])) {
1139 $args = array();
1140 foreach ($node->parents(true) as $parent) {
1141 $args[] = urlencode($parent->real_name);
1143 $link = vsprintf($node->links['text'], $args);
1144 $title = empty($node->links['title']) ? '' : $node->links['title'];
1145 if ($node->type == Node::CONTAINER) {
1146 $retval .= "&nbsp;<a class='hover_show_full' href='$link'>";
1147 $retval .= htmlspecialchars($node->name);
1148 $retval .= "</a>";
1149 } else {
1150 $retval .= "<a class='hover_show_full$linkClass' href='$link'";
1151 $retval .= " title='$title'>";
1152 $retval .= htmlspecialchars($node->real_name);
1153 $retval .= "</a>";
1155 } else {
1156 $retval .= "&nbsp;{$node->name}";
1158 $retval .= $node->getHtmlForControlButtons();
1159 if ($node->type == Node::CONTAINER) {
1160 $retval .= "</i>";
1162 $retval .= '<div class="clearfloat"></div>';
1163 $wrap = true;
1164 } else {
1165 $node->visible = true;
1166 $wrap = false;
1167 $retval .= $this->_getPaginationParamsHtml($node);
1170 if ($recursive) {
1171 $hide = '';
1172 if (!$node->visible) {
1173 $hide = " style='display: none;'";
1175 $children = $node->children;
1176 usort($children, array('PMA_NavigationTree', 'sortNode'));
1177 $buffer = '';
1178 $extra_class = '';
1179 for ($i=0, $nbChildren = count($children); $i < $nbChildren; $i++) {
1180 if ($i + 1 == $nbChildren) {
1181 $extra_class = ' last';
1183 $buffer .= $this->_renderNode(
1184 $children[$i],
1185 true,
1186 $children[$i]->classes . $extra_class
1189 if (! empty($buffer)) {
1190 if ($wrap) {
1191 $retval .= "<div$hide class='list_container'><ul>";
1193 $retval .= $this->_fastFilterHtml($node);
1194 $retval .= $this->_getPageSelector($node);
1195 $retval .= $buffer;
1196 if ($wrap) {
1197 $retval .= "</ul></div>";
1201 if ($node->hasSiblings()) {
1202 $retval .= "</li>";
1204 return $retval;
1208 * Renders a database select box like the pre-4.0 navigation panel
1210 * @return string HTML code
1212 public function renderDbSelect()
1214 $this->_buildPath();
1215 $retval = $this->_quickWarp();
1216 $this->_tree->is_group = false;
1217 $retval .= '<div id="pma_navigation_select_database">';
1218 // Provide for pagination in database select
1219 $retval .= PMA_Util::getListNavigator(
1220 $this->_tree->getPresence('databases', ''),
1221 $this->_pos,
1222 array('server' => $GLOBALS['server']),
1223 'navigation.php',
1224 'frame_navigation',
1225 $GLOBALS['cfg']['FirstLevelNavigationItems'],
1226 'pos',
1227 array('dbselector')
1229 $children = $this->_tree->children;
1230 array_shift($children);
1231 $url_params = array(
1232 'token' => $_SESSION[' PMA_token '],
1233 'server' => $GLOBALS['server']
1235 $retval .= '<div id="pma_navigation_db_select">';
1236 $retval .= '<form action="index.php">';
1237 $retval .= PMA_getHiddenFields($url_params);
1238 $retval .= '<select name="db" class="hide" id="navi_db_select">'
1239 . '<option value="" dir="' . $GLOBALS['text_dir'] . '">'
1240 . '(' . __('Databases') . ') ...</option>' . "\n";
1241 $selected = $GLOBALS['db'];
1242 foreach ($children as $node) {
1243 $paths = $node->getPaths();
1244 if (isset($node->links['text'])) {
1245 $title = empty($node->links['title']) ? '' : $node->links['title'];
1246 $retval .= '<option value="'
1247 . htmlspecialchars($node->real_name) . '"'
1248 . ' title="' . htmlspecialchars($title) . '"'
1249 . ' apath="' . $paths['aPath'] . '"'
1250 . ' vpath="' . $paths['vPath'] . '"'
1251 . ' pos="' . $this->_pos . '"';
1252 if ($node->real_name == $selected
1253 || (PMA_DRIZZLE && strtolower($node->real_name) == strtolower($selected))
1255 $retval .= ' selected="selected"';
1257 $retval .= '>' . htmlspecialchars($node->real_name);
1258 $retval .= '</option>';
1261 $retval .= '</select></form>';
1262 $retval .= '</div></div>';
1263 $retval .= '<div id="pma_navigation_tree_content"><ul>';
1264 $children = $this->_tree->children;
1265 usort($children, array('PMA_NavigationTree', 'sortNode'));
1266 $this->_setVisibility();
1267 for ($i=0, $nbChildren = count($children); $i < $nbChildren; $i++) {
1268 if ($i == 0) {
1269 $retval .= $this->_renderNode($children[0], true, 'first');
1270 } else if ($i + 1 != $nbChildren) {
1271 $retval .= $this->_renderNode($children[$i], true);
1272 } else {
1273 $retval .= $this->_renderNode($children[$i], true, 'last');
1276 $retval .= '</ul></div>';
1277 return $retval;
1281 * Makes some nodes visible based on the which node is active
1283 * @return void
1285 private function _setVisibility()
1287 foreach ($this->_vPath as $path) {
1288 $node = $this->_tree;
1289 foreach ($path as $value) {
1290 $child = $node->getChild($value);
1291 if ($child !== false) {
1292 $child->visible = true;
1293 $node = $child;
1300 * Generates the HTML code for displaying the fast filter for tables
1302 * @param Node $node The node for which to generate the fast filter html
1304 * @return string LI element used for the fast filter
1306 private function _fastFilterHtml($node)
1308 $retval = '';
1309 $filter_db_min
1310 = (int) $GLOBALS['cfg']['NavigationTreeDisplayDbFilterMinimum'];
1311 $filter_item_min
1312 = (int) $GLOBALS['cfg']['NavigationTreeDisplayItemFilterMinimum'];
1313 if ($node === $this->_tree
1314 && $this->_tree->getPresence() >= $filter_db_min
1316 $url_params = array(
1317 'pos' => 0
1319 $retval .= '<li class="fast_filter db_fast_filter">';
1320 $retval .= '<form class="ajax fast_filter">';
1321 $retval .= PMA_getHiddenFields($url_params);
1322 $retval .= '<input class="searchClause" type="text"';
1323 $retval .= ' name="searchClause" accesskey="q"';
1324 // allow html5 placeholder attribute
1325 $placeholder_key = 'value';
1326 if (PMA_USR_BROWSER_AGENT !== 'IE'
1327 || PMA_USR_BROWSER_VER > 9
1329 $placeholder_key = 'placeholder';
1331 $retval .= " $placeholder_key='"
1332 . __('Filter databases by name or regex');
1333 $retval .= "' />";
1334 $retval .= '<span title="' . __('Clear fast filter') . '">X</span>';
1335 $retval .= "</form>";
1336 $retval .= "</li>";
1337 } else if (($node->type == Node::CONTAINER
1338 && ( $node->real_name == 'tables'
1339 || $node->real_name == 'views'
1340 || $node->real_name == 'functions'
1341 || $node->real_name == 'procedures'
1342 || $node->real_name == 'events'))
1343 && method_exists($node->realParent(), 'getPresence')
1344 && $node->realParent()->getPresence($node->real_name) >= $filter_item_min
1346 $paths = $node->getPaths();
1347 $url_params = array(
1348 'pos' => $this->_pos,
1349 'aPath' => $paths['aPath'],
1350 'vPath' => $paths['vPath'],
1351 'pos2_name' => $node->real_name,
1352 'pos2_value' => 0
1354 $retval .= "<li class='fast_filter'>";
1355 $retval .= "<form class='ajax fast_filter'>";
1356 $retval .= PMA_getHiddenFields($url_params);
1357 $retval .= "<input class='searchClause' type='text'";
1358 $retval .= " name='searchClause2'";
1359 // allow html5 placeholder attribute
1360 $placeholder_key = 'value';
1361 if (PMA_USR_BROWSER_AGENT !== 'IE'
1362 || PMA_USR_BROWSER_VER > 9
1364 $placeholder_key = 'placeholder';
1366 $retval .= " $placeholder_key='"
1367 . __('Filter by name or regex') . "' />";
1368 $retval .= "<span title='" . __('Clear fast filter') . "'>X</span>";
1369 $retval .= "</form>";
1370 $retval .= "</li>";
1372 return $retval;
1376 * Creates the code for displaying the controls
1377 * at the top of the navigation tree
1379 * @return string HTML code for the controls
1381 private function _controls()
1383 // always iconic
1384 $showIcon = true;
1385 $showText = false;
1387 $retval = '<!-- CONTROLS START -->';
1388 $retval .= '<li id="navigation_controls_outer">';
1389 $retval .= '<div id="navigation_controls">';
1390 $retval .= PMA_Util::getNavigationLink(
1391 '#',
1392 $showText,
1393 __('Collapse all'),
1394 $showIcon,
1395 's_collapseall.png',
1396 'pma_navigation_collapse'
1398 $syncImage = 's_unlink.png';
1399 $title = __('Link with main panel');
1400 if ($GLOBALS['cfg']['NavigationLinkWithMainPanel']) {
1401 $syncImage = 's_link.png';
1402 $title = __('Unlink from main panel');
1404 $retval .= PMA_Util::getNavigationLink(
1405 '#',
1406 $showText,
1407 $title,
1408 $showIcon,
1409 $syncImage,
1410 'pma_navigation_sync'
1412 $retval .= '</div>';
1413 $retval .= '</li>';
1414 $retval .= '<!-- CONTROLS ENDS -->';
1415 return $retval;
1419 * Generates the HTML code for displaying the list pagination
1421 * @param Node $node The node for whose children the page
1422 * selector will be created
1424 * @return string
1426 private function _getPageSelector($node)
1428 $retval = '';
1429 if ($node === $this->_tree) {
1430 $retval .= PMA_Util::getListNavigator(
1431 $this->_tree->getPresence('databases', $this->_searchClause),
1432 $this->_pos,
1433 array('server' => $GLOBALS['server']),
1434 'navigation.php',
1435 'frame_navigation',
1436 $GLOBALS['cfg']['FirstLevelNavigationItems'],
1437 'pos',
1438 array('dbselector')
1440 } else if ($node->type == Node::CONTAINER && ! $node->is_group) {
1441 $paths = $node->getPaths();
1443 $level = isset($paths['aPath_clean'][4]) ? 3 : 2;
1444 $_url_params = array(
1445 'aPath' => $paths['aPath'],
1446 'vPath' => $paths['vPath'],
1447 'pos' => $this->_pos,
1448 'server' => $GLOBALS['server'],
1449 'pos2_name' => $paths['aPath_clean'][2]
1451 if ($level == 3) {
1452 $pos = $node->pos3;
1453 $_url_params['pos2_value'] = $node->pos2;
1454 $_url_params['pos3_name'] = $paths['aPath_clean'][4];
1455 } else {
1456 $pos = $node->pos2;
1458 $num = $node->realParent()->getPresence(
1459 $node->real_name,
1460 $this->_searchClause2
1462 $retval .= PMA_Util::getListNavigator(
1463 $num,
1464 $pos,
1465 $_url_params,
1466 'navigation.php',
1467 'frame_navigation',
1468 $GLOBALS['cfg']['MaxNavigationItems'],
1469 'pos' . $level . '_value'
1472 return $retval;
1476 * Called by usort() for sorting the nodes in a container
1478 * @param Node $a The first element used in the comparison
1479 * @param Node $b The second element used in the comparison
1481 * @return int See strnatcmp() and strcmp()
1483 static public function sortNode($a, $b)
1485 if ($a->isNew) {
1486 return -1;
1487 } else if ($b->isNew) {
1488 return 1;
1490 if ($GLOBALS['cfg']['NaturalOrder']) {
1491 return strnatcasecmp($a->name, $b->name);
1492 } else {
1493 return strcasecmp($a->name, $b->name);
1498 * Display quick warp links, contain Recents and Favorites
1500 * @return string HTML code
1502 private function _quickWarp()
1504 $retval = '<div class="pma_quick_warp">';
1505 if ($GLOBALS['cfg']['NumRecentTables'] > 0) {
1506 $retval .= PMA_RecentFavoriteTable::getInstance('recent')->getHtml();
1508 if ($GLOBALS['cfg']['NumFavoriteTables'] > 0) {
1509 $retval .= PMA_RecentFavoriteTable::getInstance('favorite')->getHtml();
1511 $retval .= '<div class="clearfloat"></div>';
1512 $retval .= '</div>';
1513 return $retval;