bug #2938492 information_schema sorting order
[phpmyadmin/dkf.git] / libraries / List_Database.class.php
blob5744f31010362c3de7cadcbb2143807ab8dad297
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * holds the PMA_List_Database class
6 * @version $Id$
7 * @package phpMyAdmin
8 */
10 /**
11 * the list base class
13 require_once './libraries/List.class.php';
15 /**
16 * handles database lists
18 * <code>
19 * $PMA_List_Database = new PMA_List_Database($userlink, $controllink);
20 * </code>
22 * @todo this object should be attached to the PMA_Server object
23 * @todo ? make use of INFORMATION_SCHEMA
24 * @todo ? support --skip-showdatabases and user has only global rights
25 * @access public
26 * @since phpMyAdmin 2.9.10
27 * @package phpMyAdmin
29 /*public*/ class PMA_List_Database extends PMA_List
31 /**
32 * @var mixed database link resource|object to be used
34 protected $_db_link = null;
36 /**
37 * @var mixed user database link resource|object
39 protected $_db_link_user = null;
41 /**
42 * @var mixed controluser database link resource|object
44 protected $_db_link_control = null;
46 /**
47 * @var boolean whether SHOW DATABASES is disabled or not
48 * @access protected
50 protected $_show_databases_disabled = false;
52 /**
53 * @var string command to retrieve databases from server
55 protected $_command = null;
57 /**
58 * Constructor
60 * @uses PMA_List_Database::$_db_link
61 * @uses PMA_List_Database::$_db_link_user
62 * @uses PMA_List_Database::$_db_link_control
63 * @uses PMA_List_Database::build()
64 * @param mixed $db_link_user user database link resource|object
65 * @param mixed $db_link_control control database link resource|object
67 public function __construct($db_link_user = null, $db_link_control = null)
69 $this->_db_link = $db_link_user;
70 $this->_db_link_user = $db_link_user;
71 $this->_db_link_control = $db_link_control;
73 parent::__construct();
74 $this->build();
77 /**
78 * checks if the configuration wants to hide some databases
80 * @todo temporaly use this docblock to test how to doc $GLOBALS
81 * @uses PMA_List_Database::$items
82 * @uses preg_match()
83 * @uses $cfg['Server']['hide_db']
85 protected function _checkHideDatabase()
87 if (empty($GLOBALS['cfg']['Server']['hide_db'])) {
88 return;
91 foreach ($this->getArrayCopy() as $key => $db) {
92 if (preg_match('/' . $GLOBALS['cfg']['Server']['hide_db'] . '/', $db)) {
93 $this->offsetUnset($key);
98 /**
99 * retrieves database list from server
101 * @todo we could also search mysql tables if all fail?
102 * @uses PMA_List_Database::$_show_databases_disabled for not retrying if SHOW DATABASES is disabled
103 * @uses PMA_List_Database::$_db_link
104 * @uses PMA_List_Database::$_db_link_control in case of SHOW DATABASES is disabled for userlink
105 * @uses PMA_DBI_fetch_result()
106 * @uses PMA_DBI_getError()
107 * @uses natsort()
108 * @uses sort()
109 * @uses $cfg['NaturalOrder']
110 * @uses $GLOBALS['error_showdatabases']
111 * @uses $GLOBALS['errno']
112 * @param string $like_db_name usally a db_name containing wildcards
114 protected function _retrieve($like_db_name = null)
116 if ($this->_show_databases_disabled) {
117 return array();
120 if (null !== $like_db_name) {
121 $command = "SHOW DATABASES LIKE '" . $like_db_name . "'";
122 } elseif (null === $this->_command) {
123 $command = str_replace('#user#', $GLOBALS['cfg']['Server']['user'],
124 $GLOBALS['cfg']['Server']['ShowDatabasesCommand']);
125 $this->_command = $command;
126 } else {
127 $command = $this->_command;
130 $database_list = PMA_DBI_fetch_result($command, null, null, $this->_db_link);
131 PMA_DBI_getError();
133 if ($GLOBALS['errno'] !== 0) {
134 // failed to get database list, try the control user
135 // (hopefully there is one and he has SHOW DATABASES right)
136 $this->_db_link = $this->_db_link_control;
137 $database_list = PMA_DBI_fetch_result($command, null, null, $this->_db_link);
139 PMA_DBI_getError();
141 if ($GLOBALS['errno'] !== 0) {
142 // failed! we will display a warning that phpMyAdmin could not safely
143 // retrieve database list, the admin has to setup a control user or
144 // allow SHOW DATABASES
145 $GLOBALS['error_showdatabases'] = true;
146 $this->_show_databases_disabled = true;
150 if ($GLOBALS['cfg']['NaturalOrder']) {
151 natsort($database_list);
152 } else {
153 // need to sort anyway, otherwise information_schema
154 // goes at the top
155 sort($database_list);
158 return $database_list;
162 * builds up the list
164 * @uses PMA_List_Database::$items to initialize it
165 * @uses PMA_List_Database::_checkOnlyDatabase()
166 * @uses PMA_List_Database::_retrieve()
167 * @uses PMA_List_Database::_checkHideDatabase()
168 * @uses exchangeArray()
170 public function build()
172 if (! $this->_checkOnlyDatabase()) {
173 $items = $this->_retrieve();
174 $this->exchangeArray($items);
177 $this->_checkHideDatabase();
181 * checks the only_db configuration
183 * @uses PMA_List_Database::$_show_databases_disabled
184 * @uses PMA_List_Database::$items
185 * @uses PMA_List_Database::_retrieve()
186 * @uses PMA_unescape_mysql_wildcards()
187 * @uses preg_match()
188 * @uses array_diff()
189 * @uses array_merge()
190 * @uses is_array()
191 * @uses strlen()
192 * @uses is_string()
193 * @uses $cfg['Server']['only_db']
194 * @return boolean false if there is no only_db, otherwise true
196 protected function _checkOnlyDatabase()
198 if (is_string($GLOBALS['cfg']['Server']['only_db'])
199 && strlen($GLOBALS['cfg']['Server']['only_db'])) {
200 $GLOBALS['cfg']['Server']['only_db'] = array(
201 $GLOBALS['cfg']['Server']['only_db']
205 if (! is_array($GLOBALS['cfg']['Server']['only_db'])) {
206 return false;
209 $items = array();
211 foreach ($GLOBALS['cfg']['Server']['only_db'] as $each_only_db) {
212 if ($each_only_db === '*' && ! $this->_show_databases_disabled) {
213 // append all not already listed dbs to the list
214 $items = array_merge($items,
215 array_diff($this->_retrieve(), $items));
216 // there can only be one '*', and this can only be last
217 break;
220 // check if the db name contains wildcard,
221 // thus containing not escaped _ or %
222 if (! preg_match('/(^|[^\\\\])(_|%)/', $each_only_db)) {
223 // ... not contains wildcard
224 $items[] = PMA_unescape_mysql_wildcards($each_only_db);
225 continue;
228 if (! $this->_show_databases_disabled) {
229 $items = array_merge($items, $this->_retrieve($each_only_db));
230 continue;
233 // @todo induce error, about not using wildcards with SHOW DATABASE disabled?
236 $this->exchangeArray($items);
238 return true;
242 * returns default item
244 * @uses PMA_List::getEmpty()
245 * @uses $GLOBALS['db']
246 * @uses strlen()
247 * @return string default item
249 public function getDefault()
251 if (strlen($GLOBALS['db'])) {
252 return $GLOBALS['db'];
255 return $this->getEmpty();
259 * returns array with dbs grouped with extended infos
261 * @uses $GLOBALS['PMA_List_Database']
262 * @uses $GLOBALS['cfgRelation']['commwork']
263 * @uses $cfg['ShowTooltip']
264 * @uses $cfg['LeftFrameDBTree']
265 * @uses $cfg['LeftFrameDBSeparator']
266 * @uses $cfg['ShowTooltipAliasDB']
267 * @uses PMA_getTableCount()
268 * @uses PMA_getDbComment()
269 * @uses is_array()
270 * @uses implode()
271 * @uses strstr()
272 * @uses explode()
273 * @param integer $offset
274 * @param integer $count
275 * @return array db list
277 public function getGroupedDetails($offset, $count)
279 $dbgroups = array();
280 $parts = array();
282 if ($GLOBALS['cfg']['ShowTooltip']
283 && $GLOBALS['cfgRelation']['commwork']) {
284 $db_tooltips = PMA_getDbComments();
287 if (!$GLOBALS['cfg']['LeftFrameDBTree']) {
288 $separators = array();
289 } elseif (is_array($GLOBALS['cfg']['LeftFrameDBSeparator'])) {
290 $separators = $GLOBALS['cfg']['LeftFrameDBSeparator'];
291 } elseif (!empty($GLOBALS['cfg']['LeftFrameDBSeparator'])) {
292 $separators = array($GLOBALS['cfg']['LeftFrameDBSeparator']);
293 } else {
294 $separators = array();
297 foreach ($this->getLimitedItems($offset, $count) as $key => $db) {
298 // garvin: Get comments from PMA comments table
299 $db_tooltip = '';
301 if (isset($db_tooltips[$db])) {
302 $db_tooltip = $db_tooltips[$db];
305 $pos = false;
307 foreach($separators as $separator) {
308 // use strpos instead of strrpos; it seems more common to
309 // have the db name, the separator, then the rest which
310 // might contain a separator
311 // like dbname_the_rest
312 $pos = strpos($db, $separator);
314 if ($pos !== false) {
315 break;
319 if ($pos !== false) {
320 $group = substr($db, 0, $pos);
321 $disp_name_cut = substr($db, $pos);
322 } else {
323 $group = $db;
324 $disp_name_cut = $db;
327 $disp_name = $db;
328 if ($db_tooltip && $GLOBALS['cfg']['ShowTooltipAliasDB']) {
329 $disp_name = $db_tooltip;
330 $disp_name_cut = $db_tooltip;
331 $db_tooltip = $db;
334 $dbgroups[$group][$db] = array(
335 'name' => $db,
336 'disp_name_cut' => $disp_name_cut,
337 'disp_name' => $disp_name,
338 'comment' => $db_tooltip,
341 if ($GLOBALS['cfg']['Server']['CountTables']) {
342 $dbgroups[$group][$db]['num_tables'] = PMA_getTableCount($db);
344 } // end foreach ($GLOBALS['PMA_List_Database']->items as $db)
345 return $dbgroups;
349 * returns a part of the items
351 * @uses array_slice()
352 * @param integer $offset
353 * @param integer $count
354 * @return array some items
356 public function getLimitedItems($offset, $count)
358 return array_slice($this->getArrayCopy(), $offset, $count);
362 * returns html code for list with dbs
364 * @return string html code list
366 public function getHtmlListGrouped($selected = '', $offset, $count)
368 if (true === $selected) {
369 $selected = $this->getDefault();
372 $return = '<ul id="databaseList" xml:lang="en" dir="ltr">' . "\n";
373 foreach ($this->getGroupedDetails($offset, $count) as $group => $dbs) {
374 if (count($dbs) > 1) {
375 $return .= '<li>' . htmlspecialchars($group) . '<ul>' . "\n";
376 // whether display db_name cut by the group part
377 $cut = true;
378 } else {
379 // .. or full
380 $cut = false;
382 foreach ($dbs as $db) {
383 $return .= '<li';
384 if ($db['name'] == $selected) {
385 $return .= ' class="selected"';
387 $return .= '><a';
388 if (! empty($db['comment'])) {
389 $return .= ' title="' . htmlspecialchars($db['comment']) . '"';
391 $return .= ' href="index.php?' . PMA_generate_common_url($db['name'])
392 . '" target="_parent">';
393 if ($cut) {
394 $return .= htmlspecialchars($db['disp_name_cut']);
395 } else {
396 $return .= htmlspecialchars($db['disp_name']);
399 if (! empty($db['num_tables'])) {
400 $return .= ' (' . $db['num_tables'] . ')';
402 $return .= '</a></li>' . "\n";
404 if (count($dbs) > 1) {
405 $return .= '</ul></li>' . "\n";
408 $return .= '</ul>';
410 return $return;
414 * returns html code for select form element with dbs
416 * @todo IE can not handle different text directions in select boxes so,
417 * as mostly names will be in english, we set the whole selectbox to LTR
418 * and EN
420 * @return string html code select
422 public function getHtmlSelectGrouped($selected = '', $offset, $count)
424 if (true === $selected) {
425 $selected = $this->getDefault();
428 $return = '<select name="db" id="lightm_db" xml:lang="en" dir="ltr"'
429 . ' onchange="if (this.value != \'\') window.parent.openDb(this.value);">' . "\n"
430 . '<option value="" dir="' . $GLOBALS['text_dir'] . '">'
431 . '(' . $GLOBALS['strDatabases'] . ') ...</option>' . "\n";
432 foreach ($this->getGroupedDetails($offset, $count) as $group => $dbs) {
433 if (count($dbs) > 1) {
434 $return .= '<optgroup label="' . htmlspecialchars($group)
435 . '">' . "\n";
436 // whether display db_name cuted by the group part
437 $cut = true;
438 } else {
439 // .. or full
440 $cut = false;
442 foreach ($dbs as $db) {
443 $return .= '<option value="' . htmlspecialchars($db['name']) . '"'
444 .' title="' . htmlspecialchars($db['comment']) . '"';
445 if ($db['name'] == $selected) {
446 $return .= ' selected="selected"';
448 $return .= '>' . htmlspecialchars($cut ? $db['disp_name_cut'] : $db['disp_name']);
449 if (! empty($db['num_tables'])) {
450 $return .= ' (' . $db['num_tables'] . ')';
452 $return .= '</option>' . "\n";
454 if (count($dbs) > 1) {
455 $return .= '</optgroup>' . "\n";
458 $return .= '</select>';
460 return $return;
464 * this is just a backup, if all is fine this can be deleted later
466 * @deprecated
468 protected function _checkAgainstPrivTables()
470 // 1. get allowed dbs from the "mysql.db" table
471 // lem9: User can be blank (anonymous user)
472 $local_query = "
473 SELECT DISTINCT `Db` FROM `mysql`.`db`
474 WHERE `Select_priv` = 'Y'
475 AND `User`
476 IN ('" . PMA_sqlAddslashes($GLOBALS['cfg']['Server']['user']) . "', '')";
477 $tmp_mydbs = PMA_DBI_fetch_result($local_query, null, null,
478 $GLOBALS['controllink']);
479 if ($tmp_mydbs) {
480 // Will use as associative array of the following 2 code
481 // lines:
482 // the 1st is the only line intact from before
483 // correction,
484 // the 2nd replaces $dblist[] = $row['Db'];
486 // Code following those 2 lines in correction continues
487 // populating $dblist[], as previous code did. But it is
488 // now populated with actual database names instead of
489 // with regular expressions.
490 $tmp_alldbs = PMA_DBI_query('SHOW DATABASES;', $GLOBALS['controllink']);
491 // loic1: all databases cases - part 2
492 if (isset($tmp_mydbs['%'])) {
493 while ($tmp_row = PMA_DBI_fetch_row($tmp_alldbs)) {
494 $dblist[] = $tmp_row[0];
495 } // end while
496 } else {
497 while ($tmp_row = PMA_DBI_fetch_row($tmp_alldbs)) {
498 $tmp_db = $tmp_row[0];
499 if (isset($tmp_mydbs[$tmp_db]) && $tmp_mydbs[$tmp_db] == 1) {
500 $dblist[] = $tmp_db;
501 $tmp_mydbs[$tmp_db] = 0;
502 } elseif (!isset($dblist[$tmp_db])) {
503 foreach ($tmp_mydbs as $tmp_matchpattern => $tmp_value) {
504 // loic1: fixed bad regexp
505 // TODO: db names may contain characters
506 // that are regexp instructions
507 $re = '(^|(\\\\\\\\)+|[^\])';
508 $tmp_regex = preg_replace('/' . addcslashes($re,'/') . '%/', '\\1.*', preg_replace('/' . addcslashes($re,'/') . '_/', '\\1.{1}', $tmp_matchpattern));
509 // Fixed db name matching
510 // 2000-08-28 -- Benjamin Gandon
511 if (preg_match('/^' . addcslashes($tmp_regex,'/') . '$/', $tmp_db)) {
512 $dblist[] = $tmp_db;
513 break;
515 } // end while
516 } // end if ... elseif ...
517 } // end while
518 } // end else
519 PMA_DBI_free_result($tmp_alldbs);
520 unset($tmp_mydbs);
521 } // end if
523 // 2. get allowed dbs from the "mysql.tables_priv" table
524 $local_query = 'SELECT DISTINCT Db FROM mysql.tables_priv WHERE Table_priv LIKE \'%Select%\' AND User = \'' . PMA_sqlAddslashes($GLOBALS['cfg']['Server']['user']) . '\'';
525 $rs = PMA_DBI_try_query($local_query, $GLOBALS['controllink']);
526 if ($rs && @PMA_DBI_num_rows($rs)) {
527 while ($row = PMA_DBI_fetch_assoc($rs)) {
528 if (!in_array($row['Db'], $dblist)) {
529 $dblist[] = $row['Db'];
531 } // end while
532 PMA_DBI_free_result($rs);
533 } // end if