1<?php
2/* vim: set expandtab sw=4 ts=4 sts=4: */
3
4/**
5 * Holds the PhpMyAdmin\Controllers\Server\ServerDatabasesController
6 *
7 * @package PhpMyAdmin\Controllers
8 */
9
10namespace PhpMyAdmin\Controllers\Server;
11
12use PhpMyAdmin\Controllers\Controller;
13use PhpMyAdmin\Charsets;
14use PhpMyAdmin\DatabaseInterface;
15use PhpMyAdmin\Message;
16use PhpMyAdmin\Response;
17use PhpMyAdmin\Server\Common;
18use PhpMyAdmin\Template;
19use PhpMyAdmin\Url;
20use PhpMyAdmin\Util;
21
22/**
23 * Handles viewing and creating and deleting databases
24 *
25 * @package PhpMyAdmin\Controllers
26 */
27class ServerDatabasesController extends Controller
28{
29    /**
30     * @var array array of database details
31     */
32    private $_databases;
33    /**
34     * @var int number of databases
35     */
36    private $_database_count;
37    /**
38     * @var string sort by column
39     */
40    private $_sort_by;
41    /**
42     * @var string sort order of databases
43     */
44    private $_sort_order;
45    /**
46     * @var boolean whether to show database statistics
47     */
48    private $_dbstats;
49    /**
50     * @var int position in list navigation
51     */
52    private $_pos;
53
54    /**
55     * Index action
56     *
57     * @return void
58     */
59    public function indexAction()
60    {
61        include_once 'libraries/check_user_privileges.inc.php';
62
63        $response = Response::getInstance();
64
65        if (isset($_POST['drop_selected_dbs'])
66            && $response->isAjax()
67            && ($GLOBALS['dbi']->isSuperuser() || $GLOBALS['cfg']['AllowUserDropDatabase'])
68        ) {
69            $this->dropDatabasesAction();
70            return;
71        }
72
73        include_once 'libraries/replication.inc.php';
74
75        if (isset($_POST['new_db'])
76            && $response->isAjax()
77        ) {
78            $this->createDatabaseAction();
79            return;
80        }
81
82        include_once 'libraries/server_common.inc.php';
83
84        $header  = $this->response->getHeader();
85        $scripts = $header->getScripts();
86        $scripts->addFile('server_databases.js');
87
88        $this->_setSortDetails();
89        $this->_dbstats = ! empty($_POST['dbstats']);
90        $this->_pos     = empty($_REQUEST['pos']) ? 0 : (int) $_REQUEST['pos'];
91
92        /**
93         * Gets the databases list
94         */
95        if ($GLOBALS['server'] > 0) {
96            $this->_databases = $this->dbi->getDatabasesFull(
97                null, $this->_dbstats, DatabaseInterface::CONNECT_USER, $this->_sort_by,
98                $this->_sort_order, $this->_pos, true
99            );
100            $this->_database_count = count($GLOBALS['dblist']->databases);
101        } else {
102            $this->_database_count = 0;
103        }
104
105        if ($this->_database_count > 0 && ! empty($this->_databases)) {
106            $databases = $this->_getHtmlForDatabases($replication_types);
107        }
108
109        $this->response->addHTML(Template::get('server/databases/index')->render([
110            'show_create_db' => $GLOBALS['cfg']['ShowCreateDb'],
111            'is_create_db_priv' => $GLOBALS['is_create_db_priv'],
112            'dbstats' => $this->_dbstats,
113            'db_to_create' => $GLOBALS['db_to_create'],
114            'server_collation' => $GLOBALS['dbi']->getServerCollation(),
115            'databases' => isset($databases) ? $databases : null,
116            'dbi' => $GLOBALS['dbi'],
117            'disable_is' => $GLOBALS['cfg']['Server']['DisableIS'],
118        ]));
119    }
120
121    /**
122     * Handles creating a new database
123     *
124     * @return void
125     */
126    public function createDatabaseAction()
127    {
128        // lower_case_table_names=1 `DB` becomes `db`
129        if ($GLOBALS['dbi']->getLowerCaseNames() === '1') {
130            $_POST['new_db'] = mb_strtolower(
131                $_POST['new_db']
132            );
133        }
134        /**
135         * Builds and executes the db creation sql query
136         */
137        $sql_query = 'CREATE DATABASE ' . Util::backquote($_POST['new_db']);
138        if (! empty($_POST['db_collation'])) {
139            list($db_charset) = explode('_', $_POST['db_collation']);
140            $charsets = Charsets::getMySQLCharsets(
141                $GLOBALS['dbi'],
142                $GLOBALS['cfg']['Server']['DisableIS']
143            );
144            $collations = Charsets::getMySQLCollations(
145                $GLOBALS['dbi'],
146                $GLOBALS['cfg']['Server']['DisableIS']
147            );
148            if (in_array($db_charset, $charsets)
149                && in_array($_POST['db_collation'], $collations[$db_charset])
150            ) {
151                $sql_query .= ' DEFAULT'
152                    . Util::getCharsetQueryPart($_POST['db_collation']);
153            }
154        }
155        $sql_query .= ';';
156
157        $result = $GLOBALS['dbi']->tryQuery($sql_query);
158
159        if (! $result) {
160            // avoid displaying the not-created db name in header or navi panel
161            $GLOBALS['db'] = '';
162
163            $message = Message::rawError($GLOBALS['dbi']->getError());
164            $this->response->setRequestStatus(false);
165            $this->response->addJSON('message', $message);
166        } else {
167            $GLOBALS['db'] = $_POST['new_db'];
168
169            $message = Message::success(__('Database %1$s has been created.'));
170            $message->addParam($_POST['new_db']);
171            $this->response->addJSON('message', $message);
172            $this->response->addJSON(
173                'sql_query', Util::getMessage(null, $sql_query, 'success')
174            );
175
176            $this->response->addJSON(
177                'url_query',
178                Util::getScriptNameForOption(
179                    $GLOBALS['cfg']['DefaultTabDatabase'], 'database'
180                )
181                . Url::getCommon(array('db' => $_POST['new_db']))
182            );
183        }
184    }
185
186    /**
187     * Handles dropping multiple databases
188     *
189     * @return void
190     */
191    public function dropDatabasesAction()
192    {
193        if (! isset($_POST['selected_dbs'])) {
194            $message = Message::error(__('No databases selected.'));
195        } else {
196            $action = 'server_databases.php';
197            $err_url = $action . Url::getCommon();
198
199            $GLOBALS['submit_mult'] = 'drop_db';
200            $GLOBALS['mult_btn'] = __('Yes');
201
202            include 'libraries/mult_submits.inc.php';
203
204            if (empty($message)) { // no error message
205                $number_of_databases = count($selected);
206                $message = Message::success(
207                    _ngettext(
208                        '%1$d database has been dropped successfully.',
209                        '%1$d databases have been dropped successfully.',
210                        $number_of_databases
211                    )
212                );
213                $message->addParam($number_of_databases);
214            }
215        }
216
217        if ($message instanceof Message) {
218            $this->response->setRequestStatus($message->isSuccess());
219            $this->response->addJSON('message', $message);
220        }
221    }
222
223    /**
224     * Extracts parameters $sort_order and $sort_by
225     *
226     * @return void
227     */
228    private function _setSortDetails()
229    {
230        if (empty($_REQUEST['sort_by'])) {
231            $this->_sort_by = 'SCHEMA_NAME';
232        } else {
233            $sort_by_whitelist = array(
234                'SCHEMA_NAME',
235                'DEFAULT_COLLATION_NAME',
236                'SCHEMA_TABLES',
237                'SCHEMA_TABLE_ROWS',
238                'SCHEMA_DATA_LENGTH',
239                'SCHEMA_INDEX_LENGTH',
240                'SCHEMA_LENGTH',
241                'SCHEMA_DATA_FREE'
242            );
243            if (in_array($_REQUEST['sort_by'], $sort_by_whitelist)) {
244                $this->_sort_by = $_REQUEST['sort_by'];
245            } else {
246                $this->_sort_by = 'SCHEMA_NAME';
247            }
248        }
249
250        if (isset($_REQUEST['sort_order'])
251            && mb_strtolower($_REQUEST['sort_order']) == 'desc'
252        ) {
253            $this->_sort_order = 'desc';
254        } else {
255            $this->_sort_order = 'asc';
256        }
257    }
258
259    /**
260     * Returns the html for Database List
261     *
262     * @param array $replication_types replication types
263     *
264     * @return string
265     */
266    private function _getHtmlForDatabases(array $replication_types)
267    {
268        $first_database = reset($this->_databases);
269        // table col order
270        $column_order = $this->_getColumnOrder();
271
272        // calculate aggregate stats to display in footer
273        foreach ($this->_databases as $current) {
274            foreach ($column_order as $stat_name => $stat) {
275                if (array_key_exists($stat_name, $current)
276                    && is_numeric($stat['footer'])
277                ) {
278                    $column_order[$stat_name]['footer'] += $current[$stat_name];
279                }
280            }
281        }
282
283        $_url_params = array(
284            'pos' => $this->_pos,
285            'dbstats' => $this->_dbstats,
286            'sort_by' => $this->_sort_by,
287            'sort_order' => $this->_sort_order,
288        );
289
290        $html = Template::get('server/databases/databases_header')->render([
291            'database_count' => $this->_database_count,
292            'pos' => $this->_pos,
293            'url_params' => $_url_params,
294            'max_db_list' => $GLOBALS['cfg']['MaxDbList'],
295            'sort_by' => $this->_sort_by,
296            'sort_order' => $this->_sort_order,
297            'column_order' => $column_order,
298            'first_database' => $first_database,
299            'master_replication' => $GLOBALS['replication_info']['master']['status'],
300            'slave_replication' => $GLOBALS['replication_info']['slave']['status'],
301            'is_superuser' => $GLOBALS['dbi']->isSuperuser(),
302            'allow_user_drop_database' => $GLOBALS['cfg']['AllowUserDropDatabase'],
303        ]);
304
305        $html .= $this->_getHtmlForTableBody($column_order, $replication_types);
306
307        $html .= Template::get('server/databases/databases_footer')->render([
308            'column_order' => $column_order,
309            'first_database' => $first_database,
310            'master_replication' => $GLOBALS['replication_info']['master']['status'],
311            'slave_replication' => $GLOBALS['replication_info']['slave']['status'],
312            'database_count' => $this->_database_count,
313            'is_superuser' => $GLOBALS['dbi']->isSuperuser(),
314            'allow_user_drop_database' => $GLOBALS['cfg']['AllowUserDropDatabase'],
315            'pma_theme_image' => $GLOBALS['pmaThemeImage'],
316            'text_dir' => $GLOBALS['text_dir'],
317            'dbstats' => $this->_dbstats,
318        ]);
319
320        return $html;
321    }
322
323    /**
324     * Prepares the $column_order array
325     *
326     * @return array
327     */
328    private function _getColumnOrder()
329    {
330        $column_order = array();
331        $column_order['DEFAULT_COLLATION_NAME'] = array(
332            'disp_name' => __('Collation'),
333            'description_function' => array(Charsets::class, 'getCollationDescr'),
334            'format'    => 'string',
335            'footer'    => $this->dbi->getServerCollation(),
336        );
337        $column_order['SCHEMA_TABLES'] = array(
338            'disp_name' => __('Tables'),
339            'format'    => 'number',
340            'footer'    => 0,
341        );
342        $column_order['SCHEMA_TABLE_ROWS'] = array(
343            'disp_name' => __('Rows'),
344            'format'    => 'number',
345            'footer'    => 0,
346        );
347        $column_order['SCHEMA_DATA_LENGTH'] = array(
348            'disp_name' => __('Data'),
349            'format'    => 'byte',
350            'footer'    => 0,
351        );
352        $column_order['SCHEMA_INDEX_LENGTH'] = array(
353            'disp_name' => __('Indexes'),
354            'format'    => 'byte',
355            'footer'    => 0,
356        );
357        $column_order['SCHEMA_LENGTH'] = array(
358            'disp_name' => __('Total'),
359            'format'    => 'byte',
360            'footer'    => 0,
361        );
362        $column_order['SCHEMA_DATA_FREE'] = array(
363            'disp_name' => __('Overhead'),
364            'format'    => 'byte',
365            'footer'    => 0,
366        );
367
368        return $column_order;
369    }
370
371    /**
372     * Returns the html for Database List
373     *
374     * @param array $column_order      column order
375     * @param array $replication_types replication types
376     *
377     * @return string
378     */
379    private function _getHtmlForTableBody(array $column_order, array $replication_types)
380    {
381        $html = '<tbody>' . "\n";
382
383        foreach ($this->_databases as $current) {
384            $tr_class = ' db-row';
385            if ($this->dbi->isSystemSchema($current['SCHEMA_NAME'], true)) {
386                $tr_class .= ' noclick';
387            }
388
389            $generated_html = $this->_buildHtmlForDb(
390                $current,
391                $column_order,
392                $replication_types,
393                $GLOBALS['replication_info'],
394                $tr_class
395            );
396            $html .= $generated_html;
397        } // end foreach ($this->_databases as $key => $current)
398        $html .= '</tbody>';
399
400        return $html;
401    }
402
403    /**
404     * Builds the HTML for one database to display in the list
405     * of databases from server_databases.php
406     *
407     * @param array  $current           current database
408     * @param array  $column_order      column order
409     * @param array  $replication_types replication types
410     * @param array  $replication_info  replication info
411     * @param string $tr_class          HTMl class for the row
412     *
413     * @return array $column_order, $out
414     */
415    function _buildHtmlForDb(
416        array $current, array $column_order,
417        array $replication_types, array $replication_info, $tr_class = ''
418    ) {
419        $master_replication = $slave_replication = '';
420        foreach ($replication_types as $type) {
421            if ($replication_info[$type]['status']) {
422                $out = '';
423                $key = array_search(
424                    $current["SCHEMA_NAME"],
425                    $replication_info[$type]['Ignore_DB']
426                );
427                if (strlen($key) > 0) {
428                    $out = Util::getIcon(
429                        's_cancel',
430                        __('Not replicated')
431                    );
432                } else {
433                    $key = array_search(
434                        $current["SCHEMA_NAME"], $replication_info[$type]['Do_DB']
435                    );
436
437                    if (strlen($key) > 0
438                        || count($replication_info[$type]['Do_DB']) == 0
439                    ) {
440                        // if ($key != null) did not work for index "0"
441                        $out = Util::getIcon(
442                            's_success',
443                            __('Replicated')
444                        );
445                    }
446                }
447
448                if ($type == 'master') {
449                    $master_replication = $out;
450                } elseif ($type == 'slave') {
451                    $slave_replication = $out;
452                }
453            }
454        }
455
456        return Template::get('server/databases/table_row')->render([
457            'current' => $current,
458            'tr_class' => $tr_class,
459            'column_order' => $column_order,
460            'master_replication_status' => $GLOBALS['replication_info']['master']['status'],
461            'master_replication' => $master_replication,
462            'slave_replication_status' => $GLOBALS['replication_info']['slave']['status'],
463            'slave_replication' => $slave_replication,
464            'is_superuser' => $GLOBALS['dbi']->isSuperuser(),
465            'allow_user_drop_database' => $GLOBALS['cfg']['AllowUserDropDatabase'],
466            'is_system_schema' => $GLOBALS['dbi']->isSystemSchema($current['SCHEMA_NAME'], true),
467            'default_tab_database' => $GLOBALS['cfg']['DefaultTabDatabase'],
468        ]);
469    }
470}
471