NodeDatabase.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. <?php
  2. /**
  3. * Functionality for the navigation tree
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Navigation\Nodes;
  7. use PhpMyAdmin\Html\Generator;
  8. use PhpMyAdmin\Url;
  9. use PhpMyAdmin\Util;
  10. use function __;
  11. use function array_slice;
  12. use function in_array;
  13. use function substr;
  14. use function usort;
  15. /**
  16. * Represents a database node in the navigation tree
  17. */
  18. class NodeDatabase extends Node
  19. {
  20. /**
  21. * The number of hidden items in this database
  22. *
  23. * @var int
  24. */
  25. protected $hiddenCount = 0;
  26. /**
  27. * Initialises the class
  28. *
  29. * @param string $name An identifier for the new node
  30. * @param int $type Type of node, may be one of CONTAINER or OBJECT
  31. * @param bool $isGroup Whether this object has been created
  32. * while grouping nodes
  33. */
  34. public function __construct($name, $type = Node::OBJECT, $isGroup = false)
  35. {
  36. parent::__construct($name, $type, $isGroup);
  37. $this->icon = ['image' => 's_db', 'title' => __('Database operations')];
  38. $this->links = [
  39. 'text' => [
  40. 'route' => Util::getUrlForOption($GLOBALS['cfg']['DefaultTabDatabase'], 'database'),
  41. 'params' => ['db' => null],
  42. ],
  43. 'icon' => ['route' => '/database/operations', 'params' => ['db' => null]],
  44. 'title' => __('Structure'),
  45. ];
  46. $this->classes = 'database';
  47. $this->urlParamName = 'db';
  48. }
  49. /**
  50. * Returns the number of children of type $type present inside this container
  51. * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase
  52. * and PhpMyAdmin\Navigation\Nodes\NodeTable classes
  53. *
  54. * @param string $type The type of item we are looking for
  55. * ('tables', 'views', etc)
  56. * @param string $searchClause A string used to filter the results of
  57. * the query
  58. * @param bool $singleItem Whether to get presence of a single known
  59. * item or false in none
  60. *
  61. * @return int
  62. */
  63. public function getPresence($type = '', $searchClause = '', $singleItem = false)
  64. {
  65. $retval = 0;
  66. switch ($type) {
  67. case 'tables':
  68. $retval = $this->getTableCount($searchClause, $singleItem);
  69. break;
  70. case 'views':
  71. $retval = $this->getViewCount($searchClause, $singleItem);
  72. break;
  73. case 'procedures':
  74. $retval = $this->getProcedureCount($searchClause, $singleItem);
  75. break;
  76. case 'functions':
  77. $retval = $this->getFunctionCount($searchClause, $singleItem);
  78. break;
  79. case 'events':
  80. $retval = $this->getEventCount($searchClause, $singleItem);
  81. break;
  82. default:
  83. break;
  84. }
  85. return $retval;
  86. }
  87. /**
  88. * Returns the number of tables or views present inside this database
  89. *
  90. * @param string $which tables|views
  91. * @param string $searchClause A string used to filter the results of
  92. * the query
  93. * @param bool $singleItem Whether to get presence of a single known
  94. * item or false in none
  95. *
  96. * @return int
  97. */
  98. private function getTableOrViewCount($which, $searchClause, $singleItem)
  99. {
  100. global $dbi;
  101. $db = $this->realName;
  102. if ($which === 'tables') {
  103. $condition = 'IN';
  104. } else {
  105. $condition = 'NOT IN';
  106. }
  107. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  108. $db = $dbi->escapeString($db);
  109. $query = 'SELECT COUNT(*) ';
  110. $query .= 'FROM `INFORMATION_SCHEMA`.`TABLES` ';
  111. $query .= "WHERE `TABLE_SCHEMA`='" . $db . "' ";
  112. $query .= 'AND `TABLE_TYPE` ' . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') ";
  113. if (! empty($searchClause)) {
  114. $query .= 'AND ' . $this->getWhereClauseForSearch($searchClause, $singleItem, 'TABLE_NAME');
  115. }
  116. $retval = (int) $dbi->fetchValue($query);
  117. } else {
  118. $query = 'SHOW FULL TABLES FROM ';
  119. $query .= Util::backquote($db);
  120. $query .= ' WHERE `Table_type` ' . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') ";
  121. if (! empty($searchClause)) {
  122. $query .= 'AND ' . $this->getWhereClauseForSearch($searchClause, $singleItem, 'Tables_in_' . $db);
  123. }
  124. $retval = $dbi->queryAndGetNumRows($query);
  125. }
  126. return $retval;
  127. }
  128. /**
  129. * Returns the number of tables present inside this database
  130. *
  131. * @param string $searchClause A string used to filter the results of
  132. * the query
  133. * @param bool $singleItem Whether to get presence of a single known
  134. * item or false in none
  135. *
  136. * @return int
  137. */
  138. private function getTableCount($searchClause, $singleItem)
  139. {
  140. return $this->getTableOrViewCount('tables', $searchClause, $singleItem);
  141. }
  142. /**
  143. * Returns the number of views present inside this database
  144. *
  145. * @param string $searchClause A string used to filter the results of
  146. * the query
  147. * @param bool $singleItem Whether to get presence of a single known
  148. * item or false in none
  149. *
  150. * @return int
  151. */
  152. private function getViewCount($searchClause, $singleItem)
  153. {
  154. return $this->getTableOrViewCount('views', $searchClause, $singleItem);
  155. }
  156. /**
  157. * Returns the number of procedures present inside this database
  158. *
  159. * @param string $searchClause A string used to filter the results of
  160. * the query
  161. * @param bool $singleItem Whether to get presence of a single known
  162. * item or false in none
  163. *
  164. * @return int
  165. */
  166. private function getProcedureCount($searchClause, $singleItem)
  167. {
  168. global $dbi;
  169. $db = $this->realName;
  170. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  171. $db = $dbi->escapeString($db);
  172. $query = 'SELECT COUNT(*) ';
  173. $query .= 'FROM `INFORMATION_SCHEMA`.`ROUTINES` ';
  174. $query .= 'WHERE `ROUTINE_SCHEMA` '
  175. . Util::getCollateForIS() . "='" . $db . "'";
  176. $query .= "AND `ROUTINE_TYPE`='PROCEDURE' ";
  177. if (! empty($searchClause)) {
  178. $query .= 'AND ' . $this->getWhereClauseForSearch($searchClause, $singleItem, 'ROUTINE_NAME');
  179. }
  180. $retval = (int) $dbi->fetchValue($query);
  181. } else {
  182. $db = $dbi->escapeString($db);
  183. $query = "SHOW PROCEDURE STATUS WHERE `Db`='" . $db . "' ";
  184. if (! empty($searchClause)) {
  185. $query .= 'AND ' . $this->getWhereClauseForSearch($searchClause, $singleItem, 'Name');
  186. }
  187. $retval = $dbi->queryAndGetNumRows($query);
  188. }
  189. return $retval;
  190. }
  191. /**
  192. * Returns the number of functions present inside this database
  193. *
  194. * @param string $searchClause A string used to filter the results of
  195. * the query
  196. * @param bool $singleItem Whether to get presence of a single known
  197. * item or false in none
  198. *
  199. * @return int
  200. */
  201. private function getFunctionCount($searchClause, $singleItem)
  202. {
  203. global $dbi;
  204. $db = $this->realName;
  205. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  206. $db = $dbi->escapeString($db);
  207. $query = 'SELECT COUNT(*) ';
  208. $query .= 'FROM `INFORMATION_SCHEMA`.`ROUTINES` ';
  209. $query .= 'WHERE `ROUTINE_SCHEMA` '
  210. . Util::getCollateForIS() . "='" . $db . "' ";
  211. $query .= "AND `ROUTINE_TYPE`='FUNCTION' ";
  212. if (! empty($searchClause)) {
  213. $query .= 'AND ' . $this->getWhereClauseForSearch($searchClause, $singleItem, 'ROUTINE_NAME');
  214. }
  215. $retval = (int) $dbi->fetchValue($query);
  216. } else {
  217. $db = $dbi->escapeString($db);
  218. $query = "SHOW FUNCTION STATUS WHERE `Db`='" . $db . "' ";
  219. if (! empty($searchClause)) {
  220. $query .= 'AND ' . $this->getWhereClauseForSearch($searchClause, $singleItem, 'Name');
  221. }
  222. $retval = $dbi->queryAndGetNumRows($query);
  223. }
  224. return $retval;
  225. }
  226. /**
  227. * Returns the number of events present inside this database
  228. *
  229. * @param string $searchClause A string used to filter the results of
  230. * the query
  231. * @param bool $singleItem Whether to get presence of a single known
  232. * item or false in none
  233. *
  234. * @return int
  235. */
  236. private function getEventCount($searchClause, $singleItem)
  237. {
  238. global $dbi;
  239. $db = $this->realName;
  240. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  241. $db = $dbi->escapeString($db);
  242. $query = 'SELECT COUNT(*) ';
  243. $query .= 'FROM `INFORMATION_SCHEMA`.`EVENTS` ';
  244. $query .= 'WHERE `EVENT_SCHEMA` '
  245. . Util::getCollateForIS() . "='" . $db . "' ";
  246. if (! empty($searchClause)) {
  247. $query .= 'AND ' . $this->getWhereClauseForSearch($searchClause, $singleItem, 'EVENT_NAME');
  248. }
  249. $retval = (int) $dbi->fetchValue($query);
  250. } else {
  251. $db = Util::backquote($db);
  252. $query = 'SHOW EVENTS FROM ' . $db . ' ';
  253. if (! empty($searchClause)) {
  254. $query .= 'WHERE ' . $this->getWhereClauseForSearch($searchClause, $singleItem, 'Name');
  255. }
  256. $retval = $dbi->queryAndGetNumRows($query);
  257. }
  258. return $retval;
  259. }
  260. /**
  261. * Returns the WHERE clause for searching inside a database
  262. *
  263. * @param string $searchClause A string used to filter the results of the query
  264. * @param bool $singleItem Whether to get presence of a single known item
  265. * @param string $columnName Name of the column in the result set to match
  266. *
  267. * @return string WHERE clause for searching
  268. */
  269. private function getWhereClauseForSearch(
  270. $searchClause,
  271. $singleItem,
  272. $columnName
  273. ) {
  274. global $dbi;
  275. $query = '';
  276. if ($singleItem) {
  277. $query .= Util::backquote($columnName) . ' = ';
  278. $query .= "'" . $dbi->escapeString($searchClause) . "'";
  279. } else {
  280. $query .= Util::backquote($columnName) . ' LIKE ';
  281. $query .= "'%" . $dbi->escapeString($searchClause)
  282. . "%'";
  283. }
  284. return $query;
  285. }
  286. /**
  287. * Returns the names of children of type $type present inside this container
  288. * This method is overridden by the PhpMyAdmin\Navigation\Nodes\NodeDatabase
  289. * and PhpMyAdmin\Navigation\Nodes\NodeTable classes
  290. *
  291. * @param string $type The type of item we are looking for
  292. * ('tables', 'views', etc)
  293. * @param int $pos The offset of the list within the results
  294. * @param string $searchClause A string used to filter the results of the query
  295. *
  296. * @return array
  297. */
  298. public function getData($type, $pos, $searchClause = '')
  299. {
  300. $pos = (int) $pos;
  301. $retval = [];
  302. switch ($type) {
  303. case 'tables':
  304. $retval = $this->getTables($pos, $searchClause);
  305. break;
  306. case 'views':
  307. $retval = $this->getViews($pos, $searchClause);
  308. break;
  309. case 'procedures':
  310. $retval = $this->getProcedures($pos, $searchClause);
  311. break;
  312. case 'functions':
  313. $retval = $this->getFunctions($pos, $searchClause);
  314. break;
  315. case 'events':
  316. $retval = $this->getEvents($pos, $searchClause);
  317. break;
  318. default:
  319. break;
  320. }
  321. // Remove hidden items so that they are not displayed in navigation tree
  322. $relationParameters = $this->relation->getRelationParameters();
  323. if ($relationParameters->navigationItemsHidingFeature !== null) {
  324. $hiddenItems = $this->getHiddenItems(substr($type, 0, -1));
  325. foreach ($retval as $key => $item) {
  326. if (! in_array($item, $hiddenItems)) {
  327. continue;
  328. }
  329. unset($retval[$key]);
  330. }
  331. }
  332. return $retval;
  333. }
  334. /**
  335. * Return list of hidden items of given type
  336. *
  337. * @param string $type The type of items we are looking for
  338. * ('table', 'function', 'group', etc.)
  339. *
  340. * @return array Array containing hidden items of given type
  341. */
  342. public function getHiddenItems($type)
  343. {
  344. global $dbi;
  345. $db = $this->realName;
  346. $relationParameters = $this->relation->getRelationParameters();
  347. if ($relationParameters->navigationItemsHidingFeature === null || $relationParameters->user === null) {
  348. return [];
  349. }
  350. $navTable = Util::backquote($relationParameters->navigationItemsHidingFeature->database)
  351. . '.' . Util::backquote($relationParameters->navigationItemsHidingFeature->navigationHiding);
  352. $sqlQuery = 'SELECT `item_name` FROM ' . $navTable
  353. . " WHERE `username`='" . $relationParameters->user . "'"
  354. . " AND `item_type`='" . $type
  355. . "' AND `db_name`='" . $dbi->escapeString($db)
  356. . "'";
  357. $result = $dbi->tryQueryAsControlUser($sqlQuery);
  358. if ($result) {
  359. return $result->fetchAllColumn();
  360. }
  361. return [];
  362. }
  363. /**
  364. * Returns the list of tables or views inside this database
  365. *
  366. * @param string $which tables|views
  367. * @param int $pos The offset of the list within the results
  368. * @param string $searchClause A string used to filter the results of the query
  369. *
  370. * @return array
  371. */
  372. private function getTablesOrViews($which, int $pos, $searchClause)
  373. {
  374. global $dbi;
  375. if ($which === 'tables') {
  376. $condition = 'IN';
  377. } else {
  378. $condition = 'NOT IN';
  379. }
  380. $maxItems = $GLOBALS['cfg']['MaxNavigationItems'];
  381. $retval = [];
  382. $db = $this->realName;
  383. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  384. $escdDb = $dbi->escapeString($db);
  385. $query = 'SELECT `TABLE_NAME` AS `name` ';
  386. $query .= 'FROM `INFORMATION_SCHEMA`.`TABLES` ';
  387. $query .= "WHERE `TABLE_SCHEMA`='" . $escdDb . "' ";
  388. $query .= 'AND `TABLE_TYPE` ' . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') ";
  389. if (! empty($searchClause)) {
  390. $query .= "AND `TABLE_NAME` LIKE '%";
  391. $query .= $dbi->escapeString($searchClause);
  392. $query .= "%'";
  393. }
  394. $query .= 'ORDER BY `TABLE_NAME` ASC ';
  395. $retval = $dbi->fetchResult($query);
  396. } else {
  397. $query = ' SHOW FULL TABLES FROM ';
  398. $query .= Util::backquote($db);
  399. $query .= ' WHERE `Table_type` ' . $condition . "('BASE TABLE', 'SYSTEM VERSIONED') ";
  400. if (! empty($searchClause)) {
  401. $query .= 'AND ' . Util::backquote('Tables_in_' . $db);
  402. $query .= " LIKE '%" . $dbi->escapeString($searchClause);
  403. $query .= "%'";
  404. }
  405. $handle = $dbi->tryQuery($query);
  406. if ($handle !== false) {
  407. $retval = $handle->fetchAllColumn();
  408. }
  409. }
  410. if ($GLOBALS['cfg']['NaturalOrder']) {
  411. usort($retval, 'strnatcasecmp');
  412. }
  413. return array_slice($retval, $pos, $maxItems);
  414. }
  415. /**
  416. * Returns the list of tables inside this database
  417. *
  418. * @param int $pos The offset of the list within the results
  419. * @param string $searchClause A string used to filter the results of the query
  420. *
  421. * @return array
  422. */
  423. private function getTables(int $pos, $searchClause)
  424. {
  425. return $this->getTablesOrViews('tables', $pos, $searchClause);
  426. }
  427. /**
  428. * Returns the list of views inside this database
  429. *
  430. * @param int $pos The offset of the list within the results
  431. * @param string $searchClause A string used to filter the results of the query
  432. *
  433. * @return array
  434. */
  435. private function getViews(int $pos, $searchClause)
  436. {
  437. return $this->getTablesOrViews('views', $pos, $searchClause);
  438. }
  439. /**
  440. * Returns the list of procedures or functions inside this database
  441. *
  442. * @param string $routineType PROCEDURE|FUNCTION
  443. * @param int $pos The offset of the list within the results
  444. * @param string $searchClause A string used to filter the results of the query
  445. *
  446. * @return array
  447. */
  448. private function getRoutines($routineType, $pos, $searchClause)
  449. {
  450. global $dbi;
  451. $maxItems = $GLOBALS['cfg']['MaxNavigationItems'];
  452. $retval = [];
  453. $db = $this->realName;
  454. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  455. $escdDb = $dbi->escapeString($db);
  456. $query = 'SELECT `ROUTINE_NAME` AS `name` ';
  457. $query .= 'FROM `INFORMATION_SCHEMA`.`ROUTINES` ';
  458. $query .= 'WHERE `ROUTINE_SCHEMA` '
  459. . Util::getCollateForIS() . "='" . $escdDb . "'";
  460. $query .= "AND `ROUTINE_TYPE`='" . $routineType . "' ";
  461. if (! empty($searchClause)) {
  462. $query .= "AND `ROUTINE_NAME` LIKE '%";
  463. $query .= $dbi->escapeString($searchClause);
  464. $query .= "%'";
  465. }
  466. $query .= 'ORDER BY `ROUTINE_NAME` ASC ';
  467. $retval = $dbi->fetchResult($query);
  468. } else {
  469. $escdDb = $dbi->escapeString($db);
  470. $query = 'SHOW ' . $routineType . " STATUS WHERE `Db`='" . $escdDb . "' ";
  471. if (! empty($searchClause)) {
  472. $query .= "AND `Name` LIKE '%";
  473. $query .= $dbi->escapeString($searchClause);
  474. $query .= "%'";
  475. }
  476. $handle = $dbi->tryQuery($query);
  477. if ($handle !== false) {
  478. while ($arr = $handle->fetchAssoc()) {
  479. $retval[] = $arr['Name'];
  480. }
  481. }
  482. }
  483. if ($GLOBALS['cfg']['NaturalOrder']) {
  484. usort($retval, 'strnatcasecmp');
  485. }
  486. return array_slice($retval, $pos, $maxItems);
  487. }
  488. /**
  489. * Returns the list of procedures inside this database
  490. *
  491. * @param int $pos The offset of the list within the results
  492. * @param string $searchClause A string used to filter the results of the query
  493. *
  494. * @return array
  495. */
  496. private function getProcedures($pos, $searchClause)
  497. {
  498. return $this->getRoutines('PROCEDURE', $pos, $searchClause);
  499. }
  500. /**
  501. * Returns the list of functions inside this database
  502. *
  503. * @param int $pos The offset of the list within the results
  504. * @param string $searchClause A string used to filter the results of the query
  505. *
  506. * @return array
  507. */
  508. private function getFunctions($pos, $searchClause)
  509. {
  510. return $this->getRoutines('FUNCTION', $pos, $searchClause);
  511. }
  512. /**
  513. * Returns the list of events inside this database
  514. *
  515. * @param int $pos The offset of the list within the results
  516. * @param string $searchClause A string used to filter the results of the query
  517. *
  518. * @return array
  519. */
  520. private function getEvents($pos, $searchClause)
  521. {
  522. global $dbi;
  523. $maxItems = $GLOBALS['cfg']['MaxNavigationItems'];
  524. $retval = [];
  525. $db = $this->realName;
  526. if (! $GLOBALS['cfg']['Server']['DisableIS']) {
  527. $escdDb = $dbi->escapeString($db);
  528. $query = 'SELECT `EVENT_NAME` AS `name` ';
  529. $query .= 'FROM `INFORMATION_SCHEMA`.`EVENTS` ';
  530. $query .= 'WHERE `EVENT_SCHEMA` '
  531. . Util::getCollateForIS() . "='" . $escdDb . "' ";
  532. if (! empty($searchClause)) {
  533. $query .= "AND `EVENT_NAME` LIKE '%";
  534. $query .= $dbi->escapeString($searchClause);
  535. $query .= "%'";
  536. }
  537. $query .= 'ORDER BY `EVENT_NAME` ASC ';
  538. $retval = $dbi->fetchResult($query);
  539. } else {
  540. $escdDb = Util::backquote($db);
  541. $query = 'SHOW EVENTS FROM ' . $escdDb . ' ';
  542. if (! empty($searchClause)) {
  543. $query .= "WHERE `Name` LIKE '%";
  544. $query .= $dbi->escapeString($searchClause);
  545. $query .= "%'";
  546. }
  547. $handle = $dbi->tryQuery($query);
  548. if ($handle !== false) {
  549. while ($arr = $handle->fetchAssoc()) {
  550. $retval[] = $arr['Name'];
  551. }
  552. }
  553. }
  554. if ($GLOBALS['cfg']['NaturalOrder']) {
  555. usort($retval, 'strnatcasecmp');
  556. }
  557. return array_slice($retval, $pos, $maxItems);
  558. }
  559. /**
  560. * Returns HTML for control buttons displayed infront of a node
  561. *
  562. * @return string HTML for control buttons
  563. */
  564. public function getHtmlForControlButtons(): string
  565. {
  566. $ret = '';
  567. $relationParameters = $this->relation->getRelationParameters();
  568. if ($relationParameters->navigationItemsHidingFeature !== null) {
  569. if ($this->hiddenCount > 0) {
  570. $params = [
  571. 'showUnhideDialog' => true,
  572. 'dbName' => $this->realName,
  573. ];
  574. $ret = '<span class="dbItemControls">'
  575. . '<a href="' . Url::getFromRoute('/navigation') . '" data-post="'
  576. . Url::getCommon($params, '', false) . '"'
  577. . ' class="showUnhide ajax">'
  578. . Generator::getImage(
  579. 'show',
  580. __('Show hidden items')
  581. )
  582. . '</a></span>';
  583. }
  584. }
  585. return $ret;
  586. }
  587. /**
  588. * Sets the number of hidden items in this database
  589. *
  590. * @param int $count hidden item count
  591. */
  592. public function setHiddenCount($count): void
  593. {
  594. $this->hiddenCount = $count;
  595. }
  596. /**
  597. * Returns the number of hidden items in this database
  598. *
  599. * @return int hidden item count
  600. */
  601. public function getHiddenCount()
  602. {
  603. return $this->hiddenCount;
  604. }
  605. }