NavigationTree.php 53 KB

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