Passed
Push — master ( ddb298...87c113 )
by Maurício
07:12 queued 10s
created

DatabasesController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
1
<?php
2
/**
3
 * Holds the PhpMyAdmin\Controllers\Server\DatabasesController
4
 *
5
 * @package PhpMyAdmin\Controllers
6
 */
7
declare(strict_types=1);
8
9
namespace PhpMyAdmin\Controllers\Server;
10
11
use PhpMyAdmin\Charsets;
12
use PhpMyAdmin\Charsets\Charset;
13
use PhpMyAdmin\Charsets\Collation;
14
use PhpMyAdmin\CheckUserPrivileges;
15
use PhpMyAdmin\Controllers\AbstractController;
16
use PhpMyAdmin\DatabaseInterface;
17
use PhpMyAdmin\Message;
18
use PhpMyAdmin\Response;
19
use PhpMyAdmin\Template;
20
use PhpMyAdmin\Url;
21
use PhpMyAdmin\Util;
22
23
/**
24
 * Handles viewing and creating and deleting databases
25
 *
26
 * @package PhpMyAdmin\Controllers
27
 */
28
class DatabasesController extends AbstractController
29
{
30
    /**
31
     * @var array array of database details
32
     */
33
    private $databases = [];
34
35
    /**
36
     * @var int number of databases
37
     */
38
    private $databaseCount = 0;
39
40
    /**
41
     * @var string sort by column
42
     */
43
    private $sortBy;
44
45
    /**
46
     * @var string sort order of databases
47
     */
48
    private $sortOrder;
49
50
    /**
51
     * @var boolean whether to show database statistics
52
     */
53
    private $hasStatistics;
54
55
    /**
56
     * @var int position in list navigation
57
     */
58
    private $position;
59
60
    /**
61
     * @param Response          $response Response object
62
     * @param DatabaseInterface $dbi      DatabaseInterface object
63
     * @param Template          $template Template that should be used (if provided, default one otherwise)
64
     */
65
    public function __construct($response, $dbi, Template $template)
66
    {
67
        parent::__construct($response, $dbi, $template);
68
69
        $checkUserPrivileges = new CheckUserPrivileges($dbi);
70
        $checkUserPrivileges->getPrivileges();
71
    }
72
73
    /**
74
     * Index action
75
     *
76
     * @param array $params Request parameters
77
     *
78
     * @return string HTML
79
     */
80
    public function index(array $params): string
81
    {
82
        global $cfg, $server, $dblist, $is_create_db_priv;
83
        global $replication_info, $db_to_create, $pmaThemeImage, $text_dir;
84
85
        $header = $this->response->getHeader();
86
        $scripts = $header->getScripts();
87
        $scripts->addFile('server/databases.js');
88
89
        include_once ROOT_PATH . 'libraries/replication.inc.php';
90
        include_once ROOT_PATH . 'libraries/server_common.inc.php';
91
92
        $this->setSortDetails($params['sort_by'], $params['sort_order']);
93
        $this->hasStatistics = ! empty($params['statistics']);
94
        $this->position = ! empty($params['pos']) ? (int) $params['pos'] : 0;
95
96
        /**
97
         * Gets the databases list
98
         */
99
        if ($server > 0) {
100
            $this->databases = $this->dbi->getDatabasesFull(
101
                null,
102
                $this->hasStatistics,
103
                DatabaseInterface::CONNECT_USER,
104
                $this->sortBy,
105
                $this->sortOrder,
106
                $this->position,
107
                true
108
            );
109
            $this->databaseCount = count($dblist->databases);
110
        }
111
112
        $urlParams = [
113
            'statistics' => $this->hasStatistics,
114
            'pos' => $this->position,
115
            'sort_by' => $this->sortBy,
116
            'sort_order' => $this->sortOrder,
117
        ];
118
119
        $databases = $this->getDatabases($replication_types ?? []);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $replication_types seems to never exist and therefore isset should always be false.
Loading history...
120
121
        $charsetsList = [];
122
        if ($cfg['ShowCreateDb'] && $is_create_db_priv) {
123
            $charsets = Charsets::getCharsets($this->dbi, $cfg['Server']['DisableIS']);
124
            $collations = Charsets::getCollations($this->dbi, $cfg['Server']['DisableIS']);
125
            $serverCollation = $this->dbi->getServerCollation();
126
            /** @var Charset $charset */
127
            foreach ($charsets as $charset) {
128
                $collationsList = [];
129
                /** @var Collation $collation */
130
                foreach ($collations[$charset->getName()] as $collation) {
131
                    $collationsList[] = [
132
                        'name' => $collation->getName(),
133
                        'description' => $collation->getDescription(),
134
                        'is_selected' => $serverCollation === $collation->getName(),
135
                    ];
136
                }
137
                $charsetsList[] = [
138
                    'name' => $charset->getName(),
139
                    'description' => $charset->getDescription(),
140
                    'collations' => $collationsList,
141
                ];
142
            }
143
        }
144
145
        $headerStatistics = $this->getStatisticsColumns();
146
147
        return $this->template->render('server/databases/index', [
148
            'is_create_database_shown' => $cfg['ShowCreateDb'],
149
            'has_create_database_privileges' => $is_create_db_priv,
150
            'has_statistics' => $this->hasStatistics,
151
            'database_to_create' => $db_to_create,
152
            'databases' => $databases['databases'],
153
            'total_statistics' => $databases['total_statistics'],
154
            'header_statistics' => $headerStatistics,
155
            'charsets' => $charsetsList,
156
            'database_count' => $this->databaseCount,
157
            'pos' => $this->position,
158
            'url_params' => $urlParams,
159
            'max_db_list' => $cfg['MaxDbList'],
160
            'has_master_replication' => $replication_info['master']['status'],
161
            'has_slave_replication' => $replication_info['slave']['status'],
162
            'is_drop_allowed' => $this->dbi->isSuperuser() || $cfg['AllowUserDropDatabase'],
163
            'pma_theme_image' => $pmaThemeImage,
164
            'text_dir' => $text_dir,
165
        ]);
166
    }
167
168
    /**
169
     * Handles creating a new database
170
     *
171
     * @param array $params Request parameters
172
     *
173
     * @return array JSON
174
     */
175
    public function create(array $params): array
176
    {
177
        global $cfg, $db;
178
179
        if (! isset($params['new_db']) || mb_strlen($params['new_db']) === 0 || ! $this->response->isAjax()) {
180
            return ['message' => Message::error()];
181
        }
182
183
        // lower_case_table_names=1 `DB` becomes `db`
184
        if ($this->dbi->getLowerCaseNames() === '1') {
185
            $params['new_db'] = mb_strtolower(
186
                $params['new_db']
187
            );
188
        }
189
190
        /**
191
         * Builds and executes the db creation sql query
192
         */
193
        $sqlQuery = 'CREATE DATABASE ' . Util::backquote($params['new_db']);
0 ignored issues
show
Bug introduced by
Are you sure PhpMyAdmin\Util::backquote($params['new_db']) of type array|mixed|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
        $sqlQuery = 'CREATE DATABASE ' . /** @scrutinizer ignore-type */ Util::backquote($params['new_db']);
Loading history...
194
        if (! empty($params['db_collation'])) {
195
            list($databaseCharset) = explode('_', $params['db_collation']);
196
            $charsets = Charsets::getCharsets(
197
                $this->dbi,
198
                $cfg['Server']['DisableIS']
199
            );
200
            $collations = Charsets::getCollations(
201
                $this->dbi,
202
                $cfg['Server']['DisableIS']
203
            );
204
            if (array_key_exists($databaseCharset, $charsets)
205
                && array_key_exists($params['db_collation'], $collations[$databaseCharset])
206
            ) {
207
                $sqlQuery .= ' DEFAULT'
208
                    . Util::getCharsetQueryPart($params['db_collation']);
209
            }
210
        }
211
        $sqlQuery .= ';';
212
213
        $result = $this->dbi->tryQuery($sqlQuery);
214
215
        if (! $result) {
216
            // avoid displaying the not-created db name in header or navi panel
217
            $db = '';
218
219
            $message = Message::rawError($this->dbi->getError());
0 ignored issues
show
Bug introduced by
It seems like $this->dbi->getError() can also be of type boolean; however, parameter $message of PhpMyAdmin\Message::rawError() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

219
            $message = Message::rawError(/** @scrutinizer ignore-type */ $this->dbi->getError());
Loading history...
220
            $json = ['message' => $message];
221
222
            $this->response->setRequestStatus(false);
223
        } else {
224
            $db = $params['new_db'];
225
226
            $message = Message::success(__('Database %1$s has been created.'));
227
            $message->addParam($params['new_db']);
228
229
            $scriptName = Util::getScriptNameForOption(
230
                $cfg['DefaultTabDatabase'],
231
                'database'
232
            );
233
234
            $json = [
235
                'message' => $message,
236
                'sql_query' => Util::getMessage(null, $sqlQuery, 'success'),
237
                'url_query' => $scriptName . Url::getCommon(
238
                    ['db' => $params['new_db']],
239
                    strpos($scriptName, '?') === false ? '?' : '&'
240
                ),
241
            ];
242
        }
243
244
        return $json;
245
    }
246
247
    /**
248
     * Handles dropping multiple databases
249
     *
250
     * @param array $params Request parameters
251
     *
252
     * @return array JSON
253
     */
254
    public function destroy(array $params): array
255
    {
256
        global $submit_mult, $mult_btn, $selected, $err_url, $cfg;
257
258
        if (! isset($params['drop_selected_dbs'])
259
            || ! $this->response->isAjax()
260
            || (! $this->dbi->isSuperuser() && ! $cfg['AllowUserDropDatabase'])
261
        ) {
262
            $message = Message::error();
263
        } elseif (! isset($params['selected_dbs'])) {
264
            $message = Message::error(__('No databases selected.'));
265
        } else {
266
            // for mult_submits.inc.php
267
            $action = Url::getFromRoute('/server/databases');
268
            $err_url = $action;
269
270
            $submit_mult = 'drop_db';
271
            $mult_btn = __('Yes');
272
273
            include ROOT_PATH . 'libraries/mult_submits.inc.php';
274
275
            if (empty($message)) { // no error message
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $message seems to never exist and therefore empty should always be true.
Loading history...
276
                $numberOfDatabases = count($selected);
277
                $message = Message::success(
278
                    _ngettext(
279
                        '%1$d database has been dropped successfully.',
280
                        '%1$d databases have been dropped successfully.',
281
                        $numberOfDatabases
282
                    )
283
                );
284
                $message->addParam($numberOfDatabases);
285
            }
286
        }
287
288
        $json = [];
289
        if ($message instanceof Message) {
290
            $json = ['message' => $message];
291
            $this->response->setRequestStatus($message->isSuccess());
292
        }
293
294
        return $json;
295
    }
296
297
    /**
298
     * Extracts parameters sort order and sort by
299
     *
300
     * @param string|null $sortBy    sort by
301
     * @param string|null $sortOrder sort order
302
     *
303
     * @return void
304
     */
305
    private function setSortDetails(?string $sortBy, ?string $sortOrder): void
306
    {
307
        if (empty($sortBy)) {
308
            $this->sortBy = 'SCHEMA_NAME';
309
        } else {
310
            $sortByWhitelist = [
311
                'SCHEMA_NAME',
312
                'DEFAULT_COLLATION_NAME',
313
                'SCHEMA_TABLES',
314
                'SCHEMA_TABLE_ROWS',
315
                'SCHEMA_DATA_LENGTH',
316
                'SCHEMA_INDEX_LENGTH',
317
                'SCHEMA_LENGTH',
318
                'SCHEMA_DATA_FREE',
319
            ];
320
            $this->sortBy = 'SCHEMA_NAME';
321
            if (in_array($sortBy, $sortByWhitelist)) {
322
                $this->sortBy = $sortBy;
323
            }
324
        }
325
326
        $this->sortOrder = 'asc';
327
        if (isset($sortOrder)
328
            && mb_strtolower($sortOrder) === 'desc'
329
        ) {
330
            $this->sortOrder = 'desc';
331
        }
332
    }
333
334
    /**
335
     * Returns database list
336
     *
337
     * @param array $replicationTypes replication types
338
     *
339
     * @return array
340
     */
341
    private function getDatabases(array $replicationTypes): array
342
    {
343
        global $cfg, $replication_info;
344
345
        $databases = [];
346
        $totalStatistics = $this->getStatisticsColumns();
347
        foreach ($this->databases as $database) {
348
            $replication = [
349
                'master' => [
350
                    'status' => $replication_info['master']['status'],
351
                ],
352
                'slave' => [
353
                    'status' => $replication_info['slave']['status'],
354
                ],
355
            ];
356
            foreach ($replicationTypes as $type) {
357
                if ($replication_info[$type]['status']) {
358
                    $key = array_search(
359
                        $database["SCHEMA_NAME"],
360
                        $replication_info[$type]['Ignore_DB']
361
                    );
362
                    if (strlen((string) $key) > 0) {
363
                        $replication[$type]['is_replicated'] = false;
364
                    } else {
365
                        $key = array_search(
366
                            $database["SCHEMA_NAME"],
367
                            $replication_info[$type]['Do_DB']
368
                        );
369
370
                        if (strlen((string) $key) > 0
371
                            || count($replication_info[$type]['Do_DB']) === 0
372
                        ) {
373
                            // if ($key != null) did not work for index "0"
374
                            $replication[$type]['is_replicated'] = true;
375
                        }
376
                    }
377
                }
378
            }
379
380
            $statistics = $this->getStatisticsColumns();
381
            if ($this->hasStatistics) {
382
                foreach (array_keys($statistics) as $key) {
383
                    $statistics[$key]['raw'] = $database[$key] ?? null;
384
                    $totalStatistics[$key]['raw'] += (int) $database[$key] ?? 0;
385
                }
386
            }
387
388
            $url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
389
            $url .= Url::getCommonRaw(
390
                ['db' => $database['SCHEMA_NAME']],
391
                strpos($url, '?') === false ? '?' : '&'
392
            );
393
            $databases[$database['SCHEMA_NAME']] = [
394
                'name' => $database['SCHEMA_NAME'],
395
                'collation' => [],
396
                'statistics' => $statistics,
397
                'replication' => $replication,
398
                'is_system_schema' => $this->dbi->isSystemSchema(
399
                    $database['SCHEMA_NAME'],
400
                    true
401
                ),
402
                'url' => $url,
403
            ];
404
            $collation = Charsets::findCollationByName(
405
                $this->dbi,
406
                $cfg['Server']['DisableIS'],
407
                $database['DEFAULT_COLLATION_NAME']
408
            );
409
            if ($collation !== null) {
410
                $databases[$database['SCHEMA_NAME']]['collation'] = [
411
                    'name' => $collation->getName(),
412
                    'description' => $collation->getDescription(),
413
                ];
414
            }
415
        }
416
417
        return [
418
            'databases' => $databases,
419
            'total_statistics' => $totalStatistics,
420
        ];
421
    }
422
423
    /**
424
     * Prepares the statistics columns
425
     *
426
     * @return array
427
     */
428
    private function getStatisticsColumns(): array
429
    {
430
        return [
431
            'SCHEMA_TABLES' => [
432
                'title' => __('Tables'),
433
                'format' => 'number',
434
                'raw' => 0,
435
            ],
436
            'SCHEMA_TABLE_ROWS' => [
437
                'title' => __('Rows'),
438
                'format' => 'number',
439
                'raw' => 0,
440
            ],
441
            'SCHEMA_DATA_LENGTH' => [
442
                'title' => __('Data'),
443
                'format' => 'byte',
444
                'raw' => 0,
445
            ],
446
            'SCHEMA_INDEX_LENGTH' => [
447
                'title' => __('Indexes'),
448
                'format' => 'byte',
449
                'raw' => 0,
450
            ],
451
            'SCHEMA_LENGTH' => [
452
                'title' => __('Total'),
453
                'format' => 'byte',
454
                'raw' => 0,
455
            ],
456
            'SCHEMA_DATA_FREE' => [
457
                'title' => __('Overhead'),
458
                'format' => 'byte',
459
                'raw' => 0,
460
            ],
461
        ];
462
    }
463
}
464