1<?php 2/* vim: set expandtab sw=4 ts=4 sts=4: */ 3/** 4 * Functionality for the navigation tree 5 * 6 * @package PhpMyAdmin-Navigation 7 */ 8namespace PhpMyAdmin\Navigation; 9 10use PhpMyAdmin\Navigation\Nodes\Node; 11use PhpMyAdmin\Navigation\Nodes\NodeDatabase; 12use PhpMyAdmin\Navigation\Nodes\NodeTable; 13use PhpMyAdmin\Navigation\Nodes\NodeTableContainer; 14use PhpMyAdmin\Navigation\Nodes\NodeViewContainer; 15use PhpMyAdmin\RecentFavoriteTable; 16use PhpMyAdmin\Response; 17use PhpMyAdmin\Util; 18use PhpMyAdmin\Url; 19 20require_once 'libraries/check_user_privileges.inc.php'; 21 22/** 23 * Displays a collapsible of database objects in the navigation frame 24 * 25 * @package PhpMyAdmin-Navigation 26 */ 27class NavigationTree 28{ 29 /** 30 * @var Node Reference to the root node of the tree 31 */ 32 private $_tree; 33 /** 34 * @var array The actual paths to all expanded nodes in the tree 35 * This does not include nodes created after the grouping 36 * of nodes has been performed 37 */ 38 private $_aPath = array(); 39 /** 40 * @var array The virtual paths to all expanded nodes in the tree 41 * This includes nodes created after the grouping of 42 * nodes has been performed 43 */ 44 private $_vPath = array(); 45 /** 46 * @var int Position in the list of databases, 47 * used for pagination 48 */ 49 private $_pos; 50 /** 51 * @var array The names of the type of items that are being paginated on 52 * the second level of the navigation tree. These may be 53 * tables, views, functions, procedures or events. 54 */ 55 private $_pos2_name = array(); 56 /** 57 * @var array The positions of nodes in the lists of tables, views, 58 * routines or events used for pagination 59 */ 60 private $_pos2_value = array(); 61 /** 62 * @var array The names of the type of items that are being paginated 63 * on the second level of the navigation tree. 64 * These may be columns or indexes 65 */ 66 private $_pos3_name = array(); 67 /** 68 * @var array The positions of nodes in the lists of columns or indexes 69 * used for pagination 70 */ 71 private $_pos3_value = array(); 72 /** 73 * @var string The search clause to use in SQL queries for 74 * fetching databases 75 * Used by the asynchronous fast filter 76 */ 77 private $_searchClause = ''; 78 /** 79 * @var string The search clause to use in SQL queries for 80 * fetching nodes 81 * Used by the asynchronous fast filter 82 */ 83 private $_searchClause2 = ''; 84 /** 85 * @var bool Whether a warning was raised for large item groups 86 * which can affect performance. 87 */ 88 private $_largeGroupWarning = false; 89 90 /** 91 * Initialises the class 92 */ 93 public function __construct() 94 { 95 // Save the position at which we are in the database list 96 if (isset($_POST['pos'])) { 97 $this->_pos = (int) $_POST['pos']; 98 } elseif (isset($_GET['pos'])) { 99 $this->_pos = (int) $_GET['pos']; 100 } 101 if (!isset($this->_pos)) { 102 $this->_pos = $this->_getNavigationDbPos(); 103 } 104 // Get the active node 105 if (isset($_REQUEST['aPath'])) { 106 $this->_aPath[0] = $this->_parsePath($_REQUEST['aPath']); 107 $this->_pos2_name[0] = $_REQUEST['pos2_name']; 108 $this->_pos2_value[0] = $_REQUEST['pos2_value']; 109 if (isset($_REQUEST['pos3_name'])) { 110 $this->_pos3_name[0] = $_REQUEST['pos3_name']; 111 $this->_pos3_value[0] = $_REQUEST['pos3_value']; 112 } 113 } else { 114 if (isset($_POST['n0_aPath'])) { 115 $count = 0; 116 while (isset($_POST['n' . $count . '_aPath'])) { 117 $this->_aPath[$count] = $this->_parsePath( 118 $_POST['n' . $count . '_aPath'] 119 ); 120 $index = 'n' . $count . '_pos2_'; 121 $this->_pos2_name[$count] = $_POST[$index . 'name']; 122 $this->_pos2_value[$count] = $_POST[$index . 'value']; 123 $index = 'n' . $count . '_pos3_'; 124 if (isset($_POST[$index])) { 125 $this->_pos3_name[$count] = $_POST[$index . 'name']; 126 $this->_pos3_value[$count] = $_POST[$index . 'value']; 127 } 128 $count++; 129 } 130 } 131 } 132 if (isset($_REQUEST['vPath'])) { 133 $this->_vPath[0] = $this->_parsePath($_REQUEST['vPath']); 134 } else { 135 if (isset($_POST['n0_vPath'])) { 136 $count = 0; 137 while (isset($_POST['n' . $count . '_vPath'])) { 138 $this->_vPath[$count] = $this->_parsePath( 139 $_POST['n' . $count . '_vPath'] 140 ); 141 $count++; 142 } 143 } 144 } 145 if (isset($_REQUEST['searchClause'])) { 146 $this->_searchClause = $_REQUEST['searchClause']; 147 } 148 if (isset($_REQUEST['searchClause2'])) { 149 $this->_searchClause2 = $_REQUEST['searchClause2']; 150 } 151 // Initialise the tree by creating a root node 152 $node = NodeFactory::getInstance('NodeDatabaseContainer', 'root'); 153 $this->_tree = $node; 154 if ($GLOBALS['cfg']['NavigationTreeEnableGrouping'] 155 && $GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] 156 ) { 157 $this->_tree->separator = $GLOBALS['cfg']['NavigationTreeDbSeparator']; 158 $this->_tree->separator_depth = 10000; 159 } 160 } 161 162 /** 163 * Returns the database position for the page selector 164 * 165 * @return int 166 */ 167 private function _getNavigationDbPos() 168 { 169 $retval = 0; 170 171 if (strlen($GLOBALS['db']) == 0) { 172 return $retval; 173 } 174 175 /* 176 * @todo describe a scenario where this code is executed 177 */ 178 if (!$GLOBALS['cfg']['Server']['DisableIS']) { 179 $dbSeparator = $GLOBALS['dbi']->escapeString( 180 $GLOBALS['cfg']['NavigationTreeDbSeparator'] 181 ); 182 $query = "SELECT (COUNT(DB_first_level) DIV %d) * %d "; 183 $query .= "from ( "; 184 $query .= " SELECT distinct SUBSTRING_INDEX(SCHEMA_NAME, "; 185 $query .= " '%s', 1) "; 186 $query .= " DB_first_level "; 187 $query .= " FROM INFORMATION_SCHEMA.SCHEMATA "; 188 $query .= " WHERE `SCHEMA_NAME` < '%s' "; 189 $query .= ") t "; 190 191 $retval = $GLOBALS['dbi']->fetchValue( 192 sprintf( 193 $query, 194 (int)$GLOBALS['cfg']['FirstLevelNavigationItems'], 195 (int)$GLOBALS['cfg']['FirstLevelNavigationItems'], 196 $dbSeparator, 197 $GLOBALS['dbi']->escapeString($GLOBALS['db']) 198 ) 199 ); 200 201 return $retval; 202 } 203 204 $prefixMap = array(); 205 if ($GLOBALS['dbs_to_test'] === false) { 206 $handle = $GLOBALS['dbi']->tryQuery("SHOW DATABASES"); 207 if ($handle !== false) { 208 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { 209 if (strcasecmp($arr[0], $GLOBALS['db']) >= 0) { 210 break; 211 } 212 213 $prefix = strstr( 214 $arr[0], 215 $GLOBALS['cfg']['NavigationTreeDbSeparator'], 216 true 217 ); 218 if ($prefix === false) { 219 $prefix = $arr[0]; 220 } 221 $prefixMap[$prefix] = 1; 222 } 223 } 224 } else { 225 $databases = array(); 226 foreach ($GLOBALS['dbs_to_test'] as $db) { 227 $query = "SHOW DATABASES LIKE '" . $db . "'"; 228 $handle = $GLOBALS['dbi']->tryQuery($query); 229 if ($handle === false) { 230 continue; 231 } 232 while ($arr = $GLOBALS['dbi']->fetchArray($handle)) { 233 $databases[] = $arr[0]; 234 } 235 } 236 sort($databases); 237 foreach ($databases as $database) { 238 if (strcasecmp($database, $GLOBALS['db']) >= 0) { 239 break; 240 } 241 242 $prefix = strstr( 243 $database, 244 $GLOBALS['cfg']['NavigationTreeDbSeparator'], 245 true 246 ); 247 if ($prefix === false) { 248 $prefix = $database; 249 } 250 $prefixMap[$prefix] = 1; 251 } 252 } 253 254 $navItems = (int)$GLOBALS['cfg']['FirstLevelNavigationItems']; 255 $retval = floor((count($prefixMap) / $navItems)) * $navItems; 256 257 return $retval; 258 } 259 260 /** 261 * Converts an encoded path to a node in string format to an array 262 * 263 * @param string $string The path to parse 264 * 265 * @return array 266 */ 267 private function _parsePath($string) 268 { 269 $path = explode('.', $string); 270 foreach ($path as $key => $value) { 271 $path[$key] = base64_decode($value); 272 } 273 274 return $path; 275 } 276 277 /** 278 * Generates the tree structure so that it can be rendered later 279 * 280 * @return Node|false The active node or false in case of failure 281 */ 282 private function _buildPath() 283 { 284 $retval = $this->_tree; 285 286 // Add all databases unconditionally 287 $data = $this->_tree->getData( 288 'databases', 289 $this->_pos, 290 $this->_searchClause 291 ); 292 $hiddenCounts = $this->_tree->getNavigationHidingData(); 293 foreach ($data as $db) { 294 $node = NodeFactory::getInstance('NodeDatabase', $db); 295 if (isset($hiddenCounts[$db])) { 296 $node->setHiddenCount($hiddenCounts[$db]); 297 } 298 $this->_tree->addChild($node); 299 } 300 301 // Whether build other parts of the tree depends 302 // on whether we have any paths in $this->_aPath 303 foreach ($this->_aPath as $key => $path) { 304 $retval = $this->_buildPathPart( 305 $path, 306 $this->_pos2_name[$key], 307 $this->_pos2_value[$key], 308 isset($this->_pos3_name[$key]) ? $this->_pos3_name[$key] : '', 309 isset($this->_pos3_value[$key]) ? $this->_pos3_value[$key] : '' 310 ); 311 } 312 313 return $retval; 314 } 315 316 /** 317 * Builds a branch of the tree 318 * 319 * @param array $path A paths pointing to the branch 320 * of the tree that needs to be built 321 * @param string $type2 The type of item being paginated on 322 * the second level of the tree 323 * @param int $pos2 The position for the pagination of 324 * the branch at the second level of the tree 325 * @param string $type3 The type of item being paginated on 326 * the third level of the tree 327 * @param int $pos3 The position for the pagination of 328 * the branch at the third level of the tree 329 * 330 * @return Node|false The active node or false in case of failure 331 */ 332 private function _buildPathPart(array $path, $type2, $pos2, $type3, $pos3) 333 { 334 if (empty($pos2)) { 335 $pos2 = 0; 336 } 337 if (empty($pos3)) { 338 $pos3 = 0; 339 } 340 341 $retval = true; 342 if (count($path) <= 1) { 343 return $retval; 344 } 345 346 array_shift($path); // remove 'root' 347 /* @var $db NodeDatabase */ 348 $db = $this->_tree->getChild($path[0]); 349 $retval = $db; 350 351 if ($db === false) { 352 return false; 353 } 354 355 $containers = $this->_addDbContainers($db, $type2, $pos2); 356 357 array_shift($path); // remove db 358 359 if ((count($path) <= 0 || !array_key_exists($path[0], $containers)) 360 && count($containers) != 1 361 ) { 362 return $retval; 363 } 364 365 if (count($containers) == 1) { 366 $container = array_shift($containers); 367 } else { 368 $container = $db->getChild($path[0], true); 369 if ($container === false) { 370 return false; 371 } 372 } 373 $retval = $container; 374 375 if (count($container->children) <= 1) { 376 $dbData = $db->getData( 377 $container->real_name, 378 $pos2, 379 $this->_searchClause2 380 ); 381 foreach ($dbData as $item) { 382 switch ($container->real_name) { 383 case 'events': 384 $node = NodeFactory::getInstance( 385 'NodeEvent', 386 $item 387 ); 388 break; 389 case 'functions': 390 $node = NodeFactory::getInstance( 391 'NodeFunction', 392 $item 393 ); 394 break; 395 case 'procedures': 396 $node = NodeFactory::getInstance( 397 'NodeProcedure', 398 $item 399 ); 400 break; 401 case 'tables': 402 $node = NodeFactory::getInstance( 403 'NodeTable', 404 $item 405 ); 406 break; 407 case 'views': 408 $node = NodeFactory::getInstance( 409 'NodeView', 410 $item 411 ); 412 break; 413 default: 414 break; 415 } 416 if (isset($node)) { 417 if ($type2 == $container->real_name) { 418 $node->pos2 = $pos2; 419 } 420 $container->addChild($node); 421 } 422 } 423 } 424 if (count($path) > 1 && $path[0] != 'tables') { 425 $retval = false; 426 427 return $retval; 428 } 429 430 array_shift($path); // remove container 431 if (count($path) <= 0) { 432 return $retval; 433 } 434 435 /* @var $table NodeTable */ 436 $table = $container->getChild($path[0], true); 437 if ($table === false) { 438 if (!$db->getPresence('tables', $path[0])) { 439 return false; 440 } 441 442 $node = NodeFactory::getInstance( 443 'NodeTable', 444 $path[0] 445 ); 446 if ($type2 == $container->real_name) { 447 $node->pos2 = $pos2; 448 } 449 $container->addChild($node); 450 $table = $container->getChild($path[0], true); 451 } 452 $retval = $table; 453 $containers = $this->_addTableContainers( 454 $table, 455 $pos2, 456 $type3, 457 $pos3 458 ); 459 array_shift($path); // remove table 460 if (count($path) <= 0 461 || !array_key_exists($path[0], $containers) 462 ) { 463 return $retval; 464 } 465 466 $container = $table->getChild($path[0], true); 467 $retval = $container; 468 $tableData = $table->getData( 469 $container->real_name, 470 $pos3 471 ); 472 foreach ($tableData as $item) { 473 switch ($container->real_name) { 474 case 'indexes': 475 $node = NodeFactory::getInstance( 476 'NodeIndex', 477 $item 478 ); 479 break; 480 case 'columns': 481 $node = NodeFactory::getInstance( 482 'NodeColumn', 483 $item 484 ); 485 break; 486 case 'triggers': 487 $node = NodeFactory::getInstance( 488 'NodeTrigger', 489 $item 490 ); 491 break; 492 default: 493 break; 494 } 495 if (isset($node)) { 496 $node->pos2 = $container->parent->pos2; 497 if ($type3 == $container->real_name) { 498 $node->pos3 = $pos3; 499 } 500 $container->addChild($node); 501 } 502 } 503 504 return $retval; 505 } 506 507 /** 508 * Adds containers to a node that is a table 509 * 510 * References to existing children are returned 511 * if this function is called twice on the same node 512 * 513 * @param NodeTable $table The table node, new containers will be 514 * attached to this node 515 * @param int $pos2 The position for the pagination of 516 * the branch at the second level of the tree 517 * @param string $type3 The type of item being paginated on 518 * the third level of the tree 519 * @param int $pos3 The position for the pagination of 520 * the branch at the third level of the tree 521 * 522 * @return array An array of new nodes 523 */ 524 private function _addTableContainers($table, $pos2, $type3, $pos3) 525 { 526 $retval = array(); 527 if ($table->hasChildren(true) == 0) { 528 if ($table->getPresence('columns')) { 529 $retval['columns'] = NodeFactory::getInstance( 530 'NodeColumnContainer' 531 ); 532 } 533 if ($table->getPresence('indexes')) { 534 $retval['indexes'] = NodeFactory::getInstance( 535 'NodeIndexContainer' 536 ); 537 } 538 if ($table->getPresence('triggers')) { 539 $retval['triggers'] = NodeFactory::getInstance( 540 'NodeTriggerContainer' 541 ); 542 } 543 // Add all new Nodes to the tree 544 foreach ($retval as $node) { 545 $node->pos2 = $pos2; 546 if ($type3 == $node->real_name) { 547 $node->pos3 = $pos3; 548 } 549 $table->addChild($node); 550 } 551 } else { 552 foreach ($table->children as $node) { 553 if ($type3 == $node->real_name) { 554 $node->pos3 = $pos3; 555 } 556 $retval[$node->real_name] = $node; 557 } 558 } 559 560 return $retval; 561 } 562 563 /** 564 * Adds containers to a node that is a database 565 * 566 * References to existing children are returned 567 * if this function is called twice on the same node 568 * 569 * @param NodeDatabase $db The database node, new containers will be 570 * attached to this node 571 * @param string $type The type of item being paginated on 572 * the second level of the tree 573 * @param int $pos2 The position for the pagination of 574 * the branch at the second level of the tree 575 * 576 * @return array An array of new nodes 577 */ 578 private function _addDbContainers($db, $type, $pos2) 579 { 580 // Get items to hide 581 $hidden = $db->getHiddenItems('group'); 582 if (!$GLOBALS['cfg']['NavigationTreeShowTables'] 583 && !in_array('tables', $hidden) 584 ) { 585 $hidden[] = 'tables'; 586 } 587 if (!$GLOBALS['cfg']['NavigationTreeShowViews'] 588 && !in_array('views', $hidden) 589 ) { 590 $hidden[] = 'views'; 591 } 592 if (!$GLOBALS['cfg']['NavigationTreeShowFunctions'] 593 && !in_array('functions', $hidden) 594 ) { 595 $hidden[] = 'functions'; 596 } 597 if (!$GLOBALS['cfg']['NavigationTreeShowProcedures'] 598 && !in_array('procedures', $hidden) 599 ) { 600 $hidden[] = 'procedures'; 601 } 602 if (!$GLOBALS['cfg']['NavigationTreeShowEvents'] 603 && !in_array('events', $hidden) 604 ) { 605 $hidden[] = 'events'; 606 } 607 608 $retval = array(); 609 if ($db->hasChildren(true) == 0) { 610 if (!in_array('tables', $hidden) && $db->getPresence('tables')) { 611 $retval['tables'] = NodeFactory::getInstance( 612 'NodeTableContainer' 613 ); 614 } 615 if (!in_array('views', $hidden) && $db->getPresence('views')) { 616 $retval['views'] = NodeFactory::getInstance( 617 'NodeViewContainer' 618 ); 619 } 620 if (!in_array('functions', $hidden) && $db->getPresence('functions')) { 621 $retval['functions'] = NodeFactory::getInstance( 622 'NodeFunctionContainer' 623 ); 624 } 625 if (!in_array('procedures', $hidden) && $db->getPresence('procedures')) { 626 $retval['procedures'] = NodeFactory::getInstance( 627 'NodeProcedureContainer' 628 ); 629 } 630 if (!in_array('events', $hidden) && $db->getPresence('events')) { 631 $retval['events'] = NodeFactory::getInstance( 632 'NodeEventContainer' 633 ); 634 } 635 // Add all new Nodes to the tree 636 foreach ($retval as $node) { 637 if ($type == $node->real_name) { 638 $node->pos2 = $pos2; 639 } 640 $db->addChild($node); 641 } 642 } else { 643 foreach ($db->children as $node) { 644 if ($type == $node->real_name) { 645 $node->pos2 = $pos2; 646 } 647 $retval[$node->real_name] = $node; 648 } 649 } 650 651 return $retval; 652 } 653 654 /** 655 * Recursively groups tree nodes given a separator 656 * 657 * @param mixed $node The node to group or null 658 * to group the whole tree. If 659 * passed as an argument, $node 660 * must be of type CONTAINER 661 * 662 * @return void 663 */ 664 public function groupTree($node = null) 665 { 666 if (!isset($node)) { 667 $node = $this->_tree; 668 } 669 $this->groupNode($node); 670 foreach ($node->children as $child) { 671 $this->groupTree($child); 672 } 673 } 674 675 /** 676 * Recursively groups tree nodes given a separator 677 * 678 * @param Node $node The node to group 679 * 680 * @return void 681 */ 682 public function groupNode($node) 683 { 684 if ($node->type != Node::CONTAINER 685 || !$GLOBALS['cfg']['NavigationTreeEnableExpansion'] 686 ) { 687 return; 688 } 689 690 $separators = array(); 691 if (is_array($node->separator)) { 692 $separators = $node->separator; 693 } else { 694 if (strlen($node->separator)) { 695 $separators[] = $node->separator; 696 } 697 } 698 $prefixes = array(); 699 if ($node->separator_depth > 0) { 700 foreach ($node->children as $child) { 701 $prefix_pos = false; 702 foreach ($separators as $separator) { 703 $sep_pos = mb_strpos($child->name, $separator); 704 if ($sep_pos != false 705 && $sep_pos != mb_strlen($child->name) 706 && $sep_pos != 0 707 && ($prefix_pos == false || $sep_pos < $prefix_pos) 708 ) { 709 $prefix_pos = $sep_pos; 710 } 711 } 712 if ($prefix_pos !== false) { 713 $prefix = mb_substr($child->name, 0, $prefix_pos); 714 if (!isset($prefixes[$prefix])) { 715 $prefixes[$prefix] = 1; 716 } else { 717 $prefixes[$prefix]++; 718 } 719 } 720 //Bug #4375: Check if prefix is the name of a DB, to create a group. 721 foreach ($node->children as $otherChild) { 722 if (array_key_exists($otherChild->name, $prefixes)) { 723 $prefixes[$otherChild->name]++; 724 } 725 } 726 } 727 //Check if prefix is the name of a DB, to create a group. 728 foreach ($node->children as $child) { 729 if (array_key_exists($child->name, $prefixes)) { 730 $prefixes[$child->name]++; 731 } 732 } 733 } 734 // It is not a group if it has only one item 735 foreach ($prefixes as $key => $value) { 736 if ($value == 1) { 737 unset($prefixes[$key]); 738 } 739 } 740 // rfe #1634 Don't group if there's only one group and no other items 741 if (count($prefixes) == 1) { 742 $keys = array_keys($prefixes); 743 $key = $keys[0]; 744 if ($prefixes[$key] == count($node->children) - 1) { 745 unset($prefixes[$key]); 746 } 747 } 748 if (count($prefixes)) { 749 /** @var Node[] $groups */ 750 $groups = array(); 751 foreach ($prefixes as $key => $value) { 752 753 // warn about large groups 754 if ($value > 500 && !$this->_largeGroupWarning) { 755 trigger_error( 756 __( 757 'There are large item groups in navigation panel which ' 758 . 'may affect the performance. Consider disabling item ' 759 . 'grouping in the navigation panel.' 760 ), 761 E_USER_WARNING 762 ); 763 $this->_largeGroupWarning = true; 764 } 765 766 $groups[$key] = new Node( 767 htmlspecialchars((string) $key), 768 Node::CONTAINER, 769 true 770 ); 771 $groups[$key]->separator = $node->separator; 772 $groups[$key]->separator_depth = $node->separator_depth - 1; 773 $groups[$key]->icon = Util::getImage( 774 'b_group' 775 ); 776 $groups[$key]->pos2 = $node->pos2; 777 $groups[$key]->pos3 = $node->pos3; 778 if ($node instanceof NodeTableContainer 779 || $node instanceof NodeViewContainer 780 ) { 781 $tblGroup = '&tbl_group=' . urlencode($key); 782 $groups[$key]->links = array( 783 'text' => $node->links['text'] . $tblGroup, 784 'icon' => $node->links['icon'] . $tblGroup, 785 ); 786 } 787 $node->addChild($groups[$key]); 788 foreach ($separators as $separator) { 789 $separatorLength = strlen($separator); 790 // FIXME: this could be more efficient 791 foreach ($node->children as $child) { 792 $keySeparatorLength = mb_strlen($key) + $separatorLength; 793 $name_substring = mb_substr( 794 $child->name, 795 0, 796 $keySeparatorLength 797 ); 798 if (($name_substring != $key . $separator 799 && $child->name != $key) 800 || $child->type != Node::OBJECT 801 ) { 802 continue; 803 } 804 $class = get_class($child); 805 $className = substr($class, strrpos($class, '\\') + 1); 806 unset($class); 807 $new_child = NodeFactory::getInstance( 808 $className, 809 mb_substr( 810 $child->name, 811 $keySeparatorLength 812 ) 813 ); 814 815 if ($new_child instanceof NodeDatabase 816 && $child->getHiddenCount() > 0 817 ) { 818 $new_child->setHiddenCount($child->getHiddenCount()); 819 } 820 821 $new_child->real_name = $child->real_name; 822 $new_child->icon = $child->icon; 823 $new_child->links = $child->links; 824 $new_child->pos2 = $child->pos2; 825 $new_child->pos3 = $child->pos3; 826 $groups[$key]->addChild($new_child); 827 foreach ($child->children as $elm) { 828 $new_child->addChild($elm); 829 } 830 $node->removeChild($child->name); 831 } 832 } 833 } 834 foreach ($prefixes as $key => $value) { 835 $this->groupNode($groups[$key]); 836 $groups[$key]->classes = "navGroup"; 837 } 838 } 839 } 840 841 /** 842 * Renders a state of the tree, used in light mode when 843 * either JavaScript and/or Ajax are disabled 844 * 845 * @return string HTML code for the navigation tree 846 */ 847 public function renderState() 848 { 849 $this->_buildPath(); 850 $retval = $this->_quickWarp(); 851 $retval .= '<div class="clearfloat"></div>'; 852 $retval .= '<ul>'; 853 $retval .= $this->_fastFilterHtml($this->_tree); 854 if ($GLOBALS['cfg']['NavigationTreeEnableExpansion'] 855 ) { 856 $retval .= $this->_controls(); 857 } 858 $retval .= '</ul>'; 859 $retval .= $this->_getPageSelector($this->_tree); 860 $this->groupTree(); 861 $retval .= "<div id='pma_navigation_tree_content'><ul>"; 862 $children = $this->_tree->children; 863 usort( 864 $children, 865 array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') 866 ); 867 $this->_setVisibility(); 868 for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { 869 if ($i == 0) { 870 $retval .= $this->_renderNode($children[0], true, 'first'); 871 } else { 872 if ($i + 1 != $nbChildren) { 873 $retval .= $this->_renderNode($children[$i], true); 874 } else { 875 $retval .= $this->_renderNode($children[$i], true, 'last'); 876 } 877 } 878 } 879 $retval .= "</ul></div>"; 880 881 return $retval; 882 } 883 884 /** 885 * Renders a part of the tree, used for Ajax 886 * requests in light mode 887 * 888 * @return string HTML code for the navigation tree 889 */ 890 public function renderPath() 891 { 892 $node = $this->_buildPath(); 893 if ($node === false) { 894 $retval = false; 895 } else { 896 $this->groupTree(); 897 $retval = "<div class='list_container hide'>"; 898 if (!empty($this->_searchClause) || !empty($this->_searchClause2)) { 899 $retval .= "<ul class='search_results'>"; 900 } else { 901 $retval .= "<ul>"; 902 } 903 $listContent = $this->_fastFilterHtml($node); 904 $listContent .= $this->_getPageSelector($node); 905 $children = $node->children; 906 usort( 907 $children, 908 array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') 909 ); 910 for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { 911 if ($i + 1 != $nbChildren) { 912 $listContent .= $this->_renderNode($children[$i], true); 913 } else { 914 $listContent .= $this->_renderNode($children[$i], true, 'last'); 915 } 916 } 917 $retval .= $listContent; 918 $retval .= "</ul>"; 919 if (!$GLOBALS['cfg']['ShowDatabasesNavigationAsTree']) { 920 $retval .= "<span class='hide loaded_db'>"; 921 $parents = $node->parents(true); 922 $retval .= urlencode($parents[0]->real_name); 923 $retval .= "</span>"; 924 if (empty($listContent)) { 925 $retval .= "<div style='margin:0.75em'>"; 926 $retval .= __('No tables found in database.'); 927 $retval .= "</div>"; 928 } 929 } 930 $retval .= "</div>"; 931 } 932 933 if (!empty($this->_searchClause) || !empty($this->_searchClause2)) { 934 $results = 0; 935 if (!empty($this->_searchClause2)) { 936 if (is_object($node->realParent())) { 937 $results = $node->realParent() 938 ->getPresence( 939 $node->real_name, 940 $this->_searchClause2 941 ); 942 } 943 } else { 944 $results = $this->_tree->getPresence( 945 'databases', 946 $this->_searchClause 947 ); 948 } 949 $results = sprintf( 950 _ngettext( 951 '%s result found', 952 '%s results found', 953 $results 954 ), 955 $results 956 ); 957 Response::getInstance() 958 ->addJSON( 959 'results', 960 $results 961 ); 962 } 963 964 return $retval; 965 } 966 967 /** 968 * Renders the parameters that are required on the client 969 * side to know which page(s) we will be requesting data from 970 * 971 * @param Node $node The node to create the pagination parameters for 972 * 973 * @return string 974 */ 975 private function _getPaginationParamsHtml($node) 976 { 977 $retval = ''; 978 $paths = $node->getPaths(); 979 if (isset($paths['aPath_clean'][2])) { 980 $retval .= "<span class='hide pos2_name'>"; 981 $retval .= $paths['aPath_clean'][2]; 982 $retval .= "</span>"; 983 $retval .= "<span class='hide pos2_value'>"; 984 $retval .= htmlspecialchars($node->pos2); 985 $retval .= "</span>"; 986 } 987 if (isset($paths['aPath_clean'][4])) { 988 $retval .= "<span class='hide pos3_name'>"; 989 $retval .= $paths['aPath_clean'][4]; 990 $retval .= "</span>"; 991 $retval .= "<span class='hide pos3_value'>"; 992 $retval .= htmlspecialchars($node->pos3); 993 $retval .= "</span>"; 994 } 995 996 return $retval; 997 } 998 999 /** 1000 * Finds whether given tree matches this tree. 1001 * 1002 * @param array $tree Tree to check 1003 * @param array $paths Paths to check 1004 * 1005 * @return boolean 1006 */ 1007 private function _findTreeMatch(array $tree, array $paths) 1008 { 1009 $match = false; 1010 foreach ($tree as $path) { 1011 $match = true; 1012 foreach ($paths as $key => $part) { 1013 if (!isset($path[$key]) || $part != $path[$key]) { 1014 $match = false; 1015 break; 1016 } 1017 } 1018 if ($match) { 1019 break; 1020 } 1021 } 1022 1023 return $match; 1024 } 1025 1026 /** 1027 * Renders a single node or a branch of the tree 1028 * 1029 * @param Node $node The node to render 1030 * @param bool $recursive Bool: Whether to render a single node or a branch 1031 * @param string $class An additional class for the list item 1032 * 1033 * @return string HTML code for the tree node or branch 1034 */ 1035 private function _renderNode($node, $recursive, $class = '') 1036 { 1037 $retval = ''; 1038 $paths = $node->getPaths(); 1039 if ($node->hasSiblings() 1040 || $node->realParent() === false 1041 ) { 1042 $response = Response::getInstance(); 1043 if ($node->type == Node::CONTAINER 1044 && count($node->children) == 0 1045 && ! $response->isAjax() 1046 ) { 1047 return ''; 1048 } 1049 $retval .= '<li class="' . trim($class . ' ' . $node->classes) . '">'; 1050 $sterile = array( 1051 'events', 1052 'triggers', 1053 'functions', 1054 'procedures', 1055 'views', 1056 'columns', 1057 'indexes', 1058 ); 1059 $parentName = ''; 1060 $parents = $node->parents(false, true); 1061 if (count($parents)) { 1062 $parentName = $parents[0]->real_name; 1063 } 1064 // if node name itself is in sterile, then allow 1065 if ($node->is_group 1066 || (!in_array($parentName, $sterile) && !$node->isNew) 1067 || (in_array($node->real_name, $sterile)) 1068 ) { 1069 $retval .= "<div class='block'>"; 1070 $iClass = ''; 1071 if ($class == 'first') { 1072 $iClass = " class='first'"; 1073 } 1074 $retval .= "<i$iClass></i>"; 1075 if (strpos($class, 'last') === false) { 1076 $retval .= "<b></b>"; 1077 } 1078 1079 $match = $this->_findTreeMatch( 1080 $this->_vPath, 1081 $paths['vPath_clean'] 1082 ); 1083 1084 $retval .= '<a class="' . $node->getCssClasses($match) . '"'; 1085 $retval .= " href='#'>"; 1086 $retval .= "<span class='hide aPath'>"; 1087 $retval .= $paths['aPath']; 1088 $retval .= "</span>"; 1089 $retval .= "<span class='hide vPath'>"; 1090 $retval .= $paths['vPath']; 1091 $retval .= "</span>"; 1092 $retval .= "<span class='hide pos'>"; 1093 $retval .= $this->_pos; 1094 $retval .= "</span>"; 1095 $retval .= $this->_getPaginationParamsHtml($node); 1096 if ($GLOBALS['cfg']['ShowDatabasesNavigationAsTree'] 1097 || $parentName != 'root' 1098 ) { 1099 $retval .= $node->getIcon($match); 1100 } 1101 1102 $retval .= "</a>"; 1103 $retval .= "</div>"; 1104 } else { 1105 $retval .= "<div class='block'>"; 1106 $iClass = ''; 1107 if ($class == 'first') { 1108 $iClass = " class='first'"; 1109 } 1110 $retval .= "<i$iClass></i>"; 1111 $retval .= $this->_getPaginationParamsHtml($node); 1112 $retval .= "</div>"; 1113 } 1114 1115 $linkClass = ''; 1116 $haveAjax = array( 1117 'functions', 1118 'procedures', 1119 'events', 1120 'triggers', 1121 'indexes', 1122 ); 1123 $parent = $node->parents(false, true); 1124 $isNewView = $parent[0]->real_name == 'views' && $node->isNew === true; 1125 if ($parent[0]->type == Node::CONTAINER 1126 && (in_array($parent[0]->real_name, $haveAjax) || $isNewView) 1127 ) { 1128 $linkClass = ' ajax'; 1129 } 1130 1131 if ($node->type == Node::CONTAINER) { 1132 $retval .= "<i>"; 1133 } 1134 1135 $divClass = ''; 1136 1137 if (isset($node->links['icon']) && !empty($node->links['icon'])) { 1138 $iconLinks = $node->links['icon']; 1139 $icons = $node->icon; 1140 if (!is_array($iconLinks)) { 1141 $iconLinks = array($iconLinks); 1142 $icons = array($icons); 1143 } 1144 1145 if (count($icons) > 1) { 1146 $divClass = 'double'; 1147 } 1148 } 1149 1150 $retval .= "<div class='block " . $divClass . "'>"; 1151 1152 if (isset($node->links['icon']) && !empty($node->links['icon'])) { 1153 $args = array(); 1154 foreach ($node->parents(true) as $parent) { 1155 $args[] = urlencode($parent->real_name); 1156 } 1157 1158 foreach ($icons as $key => $icon) { 1159 $link = vsprintf($iconLinks[$key], $args); 1160 if ($linkClass != '') { 1161 $retval .= "<a class='$linkClass' href='$link'>"; 1162 $retval .= "{$icon}</a>"; 1163 } else { 1164 $retval .= "<a href='$link'>{$icon}</a>"; 1165 } 1166 } 1167 } else { 1168 $retval .= "<u>{$node->icon}</u>"; 1169 } 1170 $retval .= "</div>"; 1171 1172 if (isset($node->links['text'])) { 1173 $args = array(); 1174 foreach ($node->parents(true) as $parent) {; 1175 $args[] = urlencode($parent->real_name); 1176 } 1177 $link = vsprintf($node->links['text'], $args); 1178 $title = isset($node->links['title']) ? $node->links['title'] : ''; 1179 if ($node->type == Node::CONTAINER) { 1180 $retval .= " <a class='hover_show_full' href='$link'>"; 1181 $retval .= htmlspecialchars($node->name); 1182 $retval .= "</a>"; 1183 } else { 1184 $retval .= "<a class='hover_show_full$linkClass' href='$link'"; 1185 $retval .= " title='$title'>"; 1186 $retval .= htmlspecialchars($node->real_name); 1187 $retval .= "</a>"; 1188 } 1189 } else { 1190 $retval .= " {$node->name}"; 1191 } 1192 $retval .= $node->getHtmlForControlButtons(); 1193 if ($node->type == Node::CONTAINER) { 1194 $retval .= "</i>"; 1195 } 1196 $retval .= '<div class="clearfloat"></div>'; 1197 $wrap = true; 1198 } else { 1199 $node->visible = true; 1200 $wrap = false; 1201 $retval .= $this->_getPaginationParamsHtml($node); 1202 } 1203 1204 if ($recursive) { 1205 $hide = ''; 1206 if (!$node->visible) { 1207 $hide = " style='display: none;'"; 1208 } 1209 $children = $node->children; 1210 usort( 1211 $children, 1212 array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') 1213 ); 1214 $buffer = ''; 1215 $extra_class = ''; 1216 for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { 1217 if ($i + 1 == $nbChildren) { 1218 $extra_class = ' last'; 1219 } 1220 $buffer .= $this->_renderNode( 1221 $children[$i], 1222 true, 1223 $children[$i]->classes . $extra_class 1224 ); 1225 } 1226 if (!empty($buffer)) { 1227 if ($wrap) { 1228 $retval .= "<div$hide class='list_container'><ul>"; 1229 } 1230 $retval .= $this->_fastFilterHtml($node); 1231 $retval .= $this->_getPageSelector($node); 1232 $retval .= $buffer; 1233 if ($wrap) { 1234 $retval .= "</ul></div>"; 1235 } 1236 } 1237 } 1238 if ($node->hasSiblings()) { 1239 $retval .= "</li>"; 1240 } 1241 1242 return $retval; 1243 } 1244 1245 /** 1246 * Renders a database select box like the pre-4.0 navigation panel 1247 * 1248 * @return string HTML code 1249 */ 1250 public function renderDbSelect() 1251 { 1252 $this->_buildPath(); 1253 $retval = $this->_quickWarp(); 1254 $this->_tree->is_group = false; 1255 $retval .= '<div id="pma_navigation_select_database">'; 1256 // Provide for pagination in database select 1257 $retval .= Util::getListNavigator( 1258 $this->_tree->getPresence('databases', ''), 1259 $this->_pos, 1260 array('server' => $GLOBALS['server']), 1261 'navigation.php', 1262 'frame_navigation', 1263 $GLOBALS['cfg']['FirstLevelNavigationItems'], 1264 'pos', 1265 array('dbselector') 1266 ); 1267 $children = $this->_tree->children; 1268 $url_params = array( 1269 'server' => $GLOBALS['server'], 1270 ); 1271 $retval .= '<div id="pma_navigation_db_select">'; 1272 $retval .= '<form action="index.php">'; 1273 $retval .= Url::getHiddenFields($url_params); 1274 $retval .= '<select name="db" class="hide" id="navi_db_select">' 1275 . '<option value="" dir="' . $GLOBALS['text_dir'] . '">' 1276 . '(' . __('Databases') . ') ...</option>' . "\n"; 1277 $selected = $GLOBALS['db']; 1278 foreach ($children as $node) { 1279 if ($node->isNew) { 1280 continue; 1281 } 1282 $paths = $node->getPaths(); 1283 if (isset($node->links['text'])) { 1284 $title = isset($node->links['title']) ? '' : $node->links['title']; 1285 $retval .= '<option value="' 1286 . htmlspecialchars($node->real_name) . '"' 1287 . ' title="' . htmlspecialchars($title) . '"' 1288 . ' apath="' . $paths['aPath'] . '"' 1289 . ' vpath="' . $paths['vPath'] . '"' 1290 . ' pos="' . $this->_pos . '"'; 1291 if ($node->real_name == $selected) { 1292 $retval .= ' selected="selected"'; 1293 } 1294 $retval .= '>' . htmlspecialchars($node->real_name); 1295 $retval .= '</option>'; 1296 } 1297 } 1298 $retval .= '</select></form>'; 1299 $retval .= '</div></div>'; 1300 $retval .= '<div id="pma_navigation_tree_content"><ul>'; 1301 $children = $this->_tree->children; 1302 usort( 1303 $children, 1304 array('PhpMyAdmin\\Navigation\\NavigationTree', 'sortNode') 1305 ); 1306 $this->_setVisibility(); 1307 for ($i = 0, $nbChildren = count($children); $i < $nbChildren; $i++) { 1308 if ($i == 0) { 1309 $retval .= $this->_renderNode($children[0], true, 'first'); 1310 } else { 1311 if ($i + 1 != $nbChildren) { 1312 $retval .= $this->_renderNode($children[$i], true); 1313 } else { 1314 $retval .= $this->_renderNode($children[$i], true, 'last'); 1315 } 1316 } 1317 } 1318 $retval .= '</ul></div>'; 1319 1320 return $retval; 1321 } 1322 1323 /** 1324 * Makes some nodes visible based on the which node is active 1325 * 1326 * @return void 1327 */ 1328 private function _setVisibility() 1329 { 1330 foreach ($this->_vPath as $path) { 1331 $node = $this->_tree; 1332 foreach ($path as $value) { 1333 $child = $node->getChild($value); 1334 if ($child !== false) { 1335 $child->visible = true; 1336 $node = $child; 1337 } 1338 } 1339 } 1340 } 1341 1342 /** 1343 * Generates the HTML code for displaying the fast filter for tables 1344 * 1345 * @param Node $node The node for which to generate the fast filter html 1346 * 1347 * @return string LI element used for the fast filter 1348 */ 1349 private function _fastFilterHtml($node) 1350 { 1351 $retval = ''; 1352 $filter_db_min 1353 = (int)$GLOBALS['cfg']['NavigationTreeDisplayDbFilterMinimum']; 1354 $filter_item_min 1355 = (int)$GLOBALS['cfg']['NavigationTreeDisplayItemFilterMinimum']; 1356 if ($node === $this->_tree 1357 && $this->_tree->getPresence() >= $filter_db_min 1358 ) { 1359 $url_params = array( 1360 'pos' => 0, 1361 ); 1362 $retval .= '<li class="fast_filter db_fast_filter">'; 1363 $retval .= '<form class="ajax fast_filter">'; 1364 $retval .= Url::getHiddenInputs($url_params); 1365 $retval .= '<input class="searchClause" type="text"'; 1366 $retval .= ' name="searchClause" accesskey="q"'; 1367 $retval .= " placeholder='" 1368 . __("Type to filter these, Enter to search all"); 1369 $retval .= "' />"; 1370 $retval .= '<span title="' . __('Clear fast filter') . '">X</span>'; 1371 $retval .= "</form>"; 1372 $retval .= "</li>"; 1373 1374 return $retval; 1375 } 1376 1377 if (($node->type == Node::CONTAINER 1378 && ($node->real_name == 'tables' 1379 || $node->real_name == 'views' 1380 || $node->real_name == 'functions' 1381 || $node->real_name == 'procedures' 1382 || $node->real_name == 'events')) 1383 && method_exists($node->realParent(), 'getPresence') 1384 && $node->realParent()->getPresence($node->real_name) >= $filter_item_min 1385 ) { 1386 $paths = $node->getPaths(); 1387 $url_params = array( 1388 'pos' => $this->_pos, 1389 'aPath' => $paths['aPath'], 1390 'vPath' => $paths['vPath'], 1391 'pos2_name' => $node->real_name, 1392 'pos2_value' => 0, 1393 ); 1394 $retval .= "<li class='fast_filter'>"; 1395 $retval .= "<form class='ajax fast_filter'>"; 1396 $retval .= Url::getHiddenFields($url_params); 1397 $retval .= "<input class='searchClause' type='text'"; 1398 $retval .= " name='searchClause2'"; 1399 $retval .= " placeholder='" 1400 . __("Type to filter these, Enter to search all") . "' />"; 1401 $retval .= "<span title='" . __('Clear fast filter') . "'>X</span>"; 1402 $retval .= "</form>"; 1403 $retval .= "</li>"; 1404 } 1405 1406 return $retval; 1407 } 1408 1409 /** 1410 * Creates the code for displaying the controls 1411 * at the top of the navigation tree 1412 * 1413 * @return string HTML code for the controls 1414 */ 1415 private function _controls() 1416 { 1417 // always iconic 1418 $showIcon = true; 1419 $showText = false; 1420 1421 $retval = '<!-- CONTROLS START -->'; 1422 $retval .= '<li id="navigation_controls_outer">'; 1423 $retval .= '<div id="navigation_controls">'; 1424 $retval .= Util::getNavigationLink( 1425 '#', 1426 $showText, 1427 __('Collapse all'), 1428 $showIcon, 1429 's_collapseall', 1430 'pma_navigation_collapse' 1431 ); 1432 $syncImage = 's_unlink'; 1433 $title = __('Link with main panel'); 1434 if ($GLOBALS['cfg']['NavigationLinkWithMainPanel']) { 1435 $syncImage = 's_link'; 1436 $title = __('Unlink from main panel'); 1437 } 1438 $retval .= Util::getNavigationLink( 1439 '#', 1440 $showText, 1441 $title, 1442 $showIcon, 1443 $syncImage, 1444 'pma_navigation_sync' 1445 ); 1446 $retval .= '</div>'; 1447 $retval .= '</li>'; 1448 $retval .= '<!-- CONTROLS ENDS -->'; 1449 1450 return $retval; 1451 } 1452 1453 /** 1454 * Generates the HTML code for displaying the list pagination 1455 * 1456 * @param Node $node The node for whose children the page 1457 * selector will be created 1458 * 1459 * @return string 1460 */ 1461 private function _getPageSelector($node) 1462 { 1463 $retval = ''; 1464 if ($node === $this->_tree) { 1465 $retval .= Util::getListNavigator( 1466 $this->_tree->getPresence('databases', $this->_searchClause), 1467 $this->_pos, 1468 array('server' => $GLOBALS['server']), 1469 'navigation.php', 1470 'frame_navigation', 1471 $GLOBALS['cfg']['FirstLevelNavigationItems'], 1472 'pos', 1473 array('dbselector') 1474 ); 1475 } else { 1476 if ($node->type == Node::CONTAINER && !$node->is_group) { 1477 $paths = $node->getPaths(); 1478 1479 $level = isset($paths['aPath_clean'][4]) ? 3 : 2; 1480 $_url_params = array( 1481 'aPath' => $paths['aPath'], 1482 'vPath' => $paths['vPath'], 1483 'pos' => $this->_pos, 1484 'server' => $GLOBALS['server'], 1485 'pos2_name' => $paths['aPath_clean'][2], 1486 ); 1487 if ($level == 3) { 1488 $pos = $node->pos3; 1489 $_url_params['pos2_value'] = $node->pos2; 1490 $_url_params['pos3_name'] = $paths['aPath_clean'][4]; 1491 } else { 1492 $pos = $node->pos2; 1493 } 1494 $num = $node->realParent() 1495 ->getPresence( 1496 $node->real_name, 1497 $this->_searchClause2 1498 ); 1499 $retval .= Util::getListNavigator( 1500 $num, 1501 $pos, 1502 $_url_params, 1503 'navigation.php', 1504 'frame_navigation', 1505 $GLOBALS['cfg']['MaxNavigationItems'], 1506 'pos' . $level . '_value' 1507 ); 1508 } 1509 } 1510 1511 return $retval; 1512 } 1513 1514 /** 1515 * Called by usort() for sorting the nodes in a container 1516 * 1517 * @param Node $a The first element used in the comparison 1518 * @param Node $b The second element used in the comparison 1519 * 1520 * @return int See strnatcmp() and strcmp() 1521 */ 1522 static public function sortNode($a, $b) 1523 { 1524 if ($a->isNew) { 1525 return -1; 1526 } 1527 1528 if ($b->isNew) { 1529 return 1; 1530 } 1531 1532 if ($GLOBALS['cfg']['NaturalOrder']) { 1533 return strnatcasecmp($a->name, $b->name); 1534 } 1535 1536 return strcasecmp($a->name, $b->name); 1537 } 1538 1539 /** 1540 * Display quick warp links, contain Recents and Favorites 1541 * 1542 * @return string HTML code 1543 */ 1544 private function _quickWarp() 1545 { 1546 $retval = '<div class="pma_quick_warp">'; 1547 if ($GLOBALS['cfg']['NumRecentTables'] > 0) { 1548 $retval .= RecentFavoriteTable::getInstance('recent') 1549 ->getHtml(); 1550 } 1551 if ($GLOBALS['cfg']['NumFavoriteTables'] > 0) { 1552 $retval .= RecentFavoriteTable::getInstance('favorite') 1553 ->getHtml(); 1554 } 1555 $retval .= '<div class="clearfloat"></div>'; 1556 $retval .= '</div>'; 1557 1558 return $retval; 1559 } 1560} 1561