StructureController::displayTableList()   F
last analyzed

Complexity

Conditions 46
Paths > 20000

Size

Total Lines 306
Code Lines 222

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 197
CRAP Score 58.883

Importance

Changes 0
Metric Value
eloc 222
c 0
b 0
f 0
dl 0
loc 306
ccs 197
cts 241
cp 0.8174
rs 0
cc 46
nc 35831820
nop 1
crap 58.883

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\Controllers\Database;
6
7
use PhpMyAdmin\Charsets;
8
use PhpMyAdmin\Config;
9
use PhpMyAdmin\Config\PageSettings;
10
use PhpMyAdmin\ConfigStorage\Relation;
11
use PhpMyAdmin\Controllers\InvocableController;
12
use PhpMyAdmin\Current;
13
use PhpMyAdmin\DatabaseInterface;
14
use PhpMyAdmin\DbTableExists;
15
use PhpMyAdmin\Favorites\RecentFavoriteTable;
16
use PhpMyAdmin\Favorites\RecentFavoriteTables;
17
use PhpMyAdmin\Favorites\TableType;
18
use PhpMyAdmin\Html\Generator;
19
use PhpMyAdmin\Http\Response;
20
use PhpMyAdmin\Http\ServerRequest;
21
use PhpMyAdmin\Identifiers\DatabaseName;
0 ignored issues
show
Bug introduced by
The type PhpMyAdmin\Identifiers\DatabaseName was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use PhpMyAdmin\Identifiers\TableName;
0 ignored issues
show
Bug introduced by
The type PhpMyAdmin\Identifiers\TableName was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
use PhpMyAdmin\LanguageManager;
24
use PhpMyAdmin\Message;
25
use PhpMyAdmin\Query\Utilities;
26
use PhpMyAdmin\Replication\Replication;
27
use PhpMyAdmin\Replication\ReplicationInfo;
28
use PhpMyAdmin\ResponseRenderer;
29
use PhpMyAdmin\Sanitize;
30
use PhpMyAdmin\StorageEngine;
31
use PhpMyAdmin\Template;
32
use PhpMyAdmin\Tracking\TrackedTable;
33
use PhpMyAdmin\Tracking\Tracker;
34
use PhpMyAdmin\Tracking\TrackingChecker;
35
use PhpMyAdmin\Url;
36
use PhpMyAdmin\Util;
37
38
use function __;
39
use function array_search;
40
use function ceil;
41
use function count;
42
use function htmlspecialchars;
43
use function implode;
44
use function in_array;
45
use function is_string;
46
use function max;
47
use function mb_substr;
48
use function md5;
49
use function preg_match;
50
use function preg_quote;
51
use function sprintf;
52
use function str_replace;
53
use function strtotime;
54
use function urlencode;
55
56
/**
57
 * Handles database structure logic
58
 */
59
final class StructureController implements InvocableController
60
{
61
    /** @var int Number of tables */
62
    private int $numTables = 0;
63
64
    /** @var int Current position in the list */
65
    private int $position = 0;
66
67
    /** @var bool DB is information_schema */
68
    private bool $dbIsSystemSchema = false;
69
70
    /** @var int Number of tables */
71
    private int $totalNumTables = 0;
72
73
    /** @var mixed[] Tables in the database */
74
    private array $tables = [];
75
76
    /** @var bool whether stats show or not */
77
    private bool $isShowStats = false;
78
79
    private ReplicationInfo $replicationInfo;
80
81 20
    public function __construct(
82
        private readonly ResponseRenderer $response,
83
        private readonly Template $template,
84
        private readonly Relation $relation,
85
        private readonly Replication $replication,
86
        private readonly DatabaseInterface $dbi,
87
        private readonly TrackingChecker $trackingChecker,
88
        private readonly PageSettings $pageSettings,
89
        private readonly DbTableExists $dbTableExists,
90
    ) {
91 20
        $this->replicationInfo = new ReplicationInfo($this->dbi);
92
    }
93
94
    /**
95
     * Retrieves database information for further use.
96
     */
97 4
    private function getDatabaseInfo(ServerRequest $request): void
98
    {
99 4
        [$tables, $totalNumTables] = Util::getDbInfo($request, Current::$database);
100
101 4
        $this->tables = $tables;
102 4
        $this->numTables = count($tables);
103 4
        $this->position = Util::getTableListPosition($request, Current::$database);
104 4
        $this->totalNumTables = $totalNumTables;
105
106
        /**
107
         * whether to display extended stats
108
         */
109 4
        $this->isShowStats = Config::getInstance()->settings['ShowStats'];
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

109
        $this->isShowStats = /** @scrutinizer ignore-deprecated */ Config::getInstance()->settings['ShowStats'];

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
110
111
        /**
112
         * whether selected db is information_schema
113
         */
114 4
        $this->dbIsSystemSchema = false;
115
116 4
        if (! Utilities::isSystemSchema(Current::$database)) {
117 4
            return;
118
        }
119
120
        $this->isShowStats = false;
121
        $this->dbIsSystemSchema = true;
122
    }
123
124
    public function __invoke(ServerRequest $request): Response|null
125
    {
126
        $GLOBALS['errorUrl'] ??= null;
127
128
        $parameters = ['sort' => $_REQUEST['sort'] ?? null, 'sort_order' => $_REQUEST['sort_order'] ?? null];
129
130
        if (! $this->response->checkParameters(['db'])) {
131
            return null;
132
        }
133
134
        $config = Config::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

134
        $config = /** @scrutinizer ignore-deprecated */ Config::getInstance();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
135
        $GLOBALS['errorUrl'] = Util::getScriptNameForOption($config->settings['DefaultTabDatabase'], 'database');
136
        $GLOBALS['errorUrl'] .= Url::getCommon(['db' => Current::$database], '&');
137
138
        $databaseName = DatabaseName::tryFrom($request->getParam('db'));
139
        if ($databaseName === null || ! $this->dbTableExists->selectDatabase($databaseName)) {
140
            if ($request->isAjax()) {
141
                $this->response->setRequestStatus(false);
142
                $this->response->addJSON('message', Message::error(__('No databases selected.')));
143
144
                return null;
145
            }
146
147
            $this->response->redirectToRoute('/', ['reload' => true, 'message' => __('No databases selected.')]);
148
149
            return null;
150
        }
151
152
        $this->response->addScriptFiles(['database/structure.js', 'table/change.js']);
153
154
        // Gets the database structure
155
        $this->getDatabaseInfo($request);
156
157
        // Checks if there are any tables to be shown on current page.
158
        // If there are no tables, the user is redirected to the last page
159
        // having any.
160
        if ($this->totalNumTables > 0 && $this->position > $this->totalNumTables) {
161
            $this->response->redirectToRoute('/database/structure', [
162
                'db' => Current::$database,
163
                'pos' => max(0, $this->totalNumTables - $config->settings['MaxTableList']),
164
                'reload' => 1,
165
            ]);
166
        }
167
168
        $this->replicationInfo->load($request->getParsedBodyParam('primary_connection'));
169
        $replicaInfo = $this->replicationInfo->getReplicaInfo();
170
171
        $this->pageSettings->init('DbStructure');
172
        $this->response->addHTML($this->pageSettings->getErrorHTML());
173
        $this->response->addHTML($this->pageSettings->getHTML());
174
175
        if ($this->numTables > 0) {
176
            $urlParams = ['pos' => $this->position, 'db' => Current::$database];
177
            if (isset($parameters['sort'])) {
178
                $urlParams['sort'] = $parameters['sort'];
179
            }
180
181
            if (isset($parameters['sort_order'])) {
182
                $urlParams['sort_order'] = $parameters['sort_order'];
183
            }
184
185
            $listNavigator = Generator::getListNavigator(
186
                $this->totalNumTables,
187
                $this->position,
188
                $urlParams,
189
                Url::getFromRoute('/database/structure'),
190
                'frame_content',
191
                $config->settings['MaxTableList'],
192
            );
193
194
            $tableList = $this->displayTableList($replicaInfo);
195
        }
196
197
        $createTable = '';
198
        if (! $this->dbIsSystemSchema) {
199
            $createTable = $this->template->render('database/create_table', ['db' => Current::$database]);
200
        }
201
202
        $this->response->render('database/structure/index', [
203
            'database' => Current::$database,
204
            'has_tables' => $this->numTables > 0,
205
            'list_navigator_html' => $listNavigator ?? '',
206
            'table_list_html' => $tableList ?? '',
207
            'is_system_schema' => $this->dbIsSystemSchema,
208
            'create_table_html' => $createTable,
209
        ]);
210
211
        return null;
212
    }
213
214
    /** @param mixed[] $replicaInfo */
215 4
    private function displayTableList(array $replicaInfo): string
216
    {
217 4
        $html = '';
218
219
        // filtering
220 4
        $html .= $this->template->render('filter', ['filter_value' => '']);
221
222 4
        $i = $sumEntries = 0;
223 4
        $overheadCheck = false;
224 4
        $createTimeAll = '';
225 4
        $updateTimeAll = '';
226 4
        $checkTimeAll = '';
227 4
        $config = Config::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

227
        $config = /** @scrutinizer ignore-deprecated */ Config::getInstance();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
228 4
        $numColumns = $config->settings['PropertiesNumColumns'] > 1
229
            ? ceil($this->numTables / $config->settings['PropertiesNumColumns']) + 1
230 4
            : 0;
231 4
        $rowCount = 0;
232 4
        $sumSize = 0;
233 4
        $overheadSize = 0;
234
235 4
        $hiddenFields = [];
236 4
        $overallApproxRows = false;
237 4
        $structureTableRows = [];
238 4
        $trackedTables = $this->trackingChecker->getTrackedTables(Current::$database);
239 4
        $recentFavoriteTables = RecentFavoriteTables::getInstance(TableType::Favorite);
240 4
        foreach ($this->tables as $currentTable) {
241
            // Get valid statistics whatever is the table type
242
243 4
            $dropQuery = '';
244 4
            $dropMessage = '';
245 4
            $overhead = '';
246 4
            $inputClass = ['checkall'];
247
248
            // Sets parameters for links
249 4
            $tableUrlParams = ['db' => Current::$database, 'table' => $currentTable['TABLE_NAME']];
250
            // do not list the previous table's size info for a view
251
252 4
            [
253 4
                $currentTable,
254 4
                $formattedSize,
255 4
                $unit,
256 4
                $formattedOverhead,
257 4
                $overheadUnit,
258 4
                $overheadSize,
259 4
                $tableIsView,
260 4
                $sumSize,
261 4
            ] = $this->getStuffForEngineTypeTable($currentTable, $sumSize, $overheadSize);
262
263 4
            $curTable = $this->dbi
264 4
                ->getTable(Current::$database, $currentTable['TABLE_NAME']);
265 4
            if (! $curTable->isMerge()) {
266 4
                $sumEntries += $currentTable['TABLE_ROWS'];
267
            }
268
269 4
            $collationDefinition = '---';
270 4
            if (isset($currentTable['Collation'])) {
271
                $tableCollation = Charsets::findCollationByName(
272
                    $this->dbi,
273
                    $config->selectedServer['DisableIS'],
274
                    $currentTable['Collation'],
275
                );
276
                if ($tableCollation !== null) {
277
                    $collationDefinition = $this->template->render('database/structure/collation_definition', [
278
                        'valueTitle' => $tableCollation->getDescription(),
279
                        'value' => $tableCollation->getName(),
280
                    ]);
281
                }
282
            }
283
284 4
            if ($this->isShowStats) {
285 4
                $overhead = '-';
286 4
                if ($formattedOverhead != '') {
287 4
                    $overhead = $this->template->render('database/structure/overhead', [
288 4
                        'table_url_params' => $tableUrlParams,
289 4
                        'formatted_overhead' => $formattedOverhead,
290 4
                        'overhead_unit' => $overheadUnit,
291 4
                    ]);
292 4
                    $overheadCheck = true;
293 4
                    $inputClass[] = 'tbl-overhead';
294
                }
295
            }
296
297 4
            if ($config->settings['ShowDbStructureCharset']) {
298
                $charset = '';
299
                if (isset($tableCollation)) {
300
                    $charset = $tableCollation->getCharset();
301
                }
302
            }
303
304 4
            if ($config->settings['ShowDbStructureCreation']) {
305
                $createTime = $currentTable['Create_time'] ?? '';
306
                if ($createTime && (! $createTimeAll || $createTime < $createTimeAll)) {
307
                    $createTimeAll = $createTime;
308
                }
309
            }
310
311 4
            if ($config->settings['ShowDbStructureLastUpdate']) {
312
                $updateTime = $currentTable['Update_time'] ?? '';
313
                if ($updateTime && (! $updateTimeAll || $updateTime < $updateTimeAll)) {
314
                    $updateTimeAll = $updateTime;
315
                }
316
            }
317
318 4
            if ($config->settings['ShowDbStructureLastCheck']) {
319
                $checkTime = $currentTable['Check_time'] ?? '';
320
                if ($checkTime && (! $checkTimeAll || $checkTime < $checkTimeAll)) {
321
                    $checkTimeAll = $checkTime;
322
                }
323
            }
324
325 4
            $truename = $currentTable['TABLE_NAME'];
326
327 4
            $i++;
328
329 4
            $rowCount++;
330 4
            if ($tableIsView) {
331
                $hiddenFields[] = '<input type="hidden" name="views[]" value="'
332
                    . htmlspecialchars($currentTable['TABLE_NAME']) . '">';
333
            }
334
335
            /**
336
             * Always activate links for Browse, Search and Empty, even if
337
             * the icons are greyed, because
338
             * 1. for views, we don't know the number of rows at this point
339
             * 2. for tables, another source could have populated them since the
340
             *    page was generated
341
             *
342
             * I could have used the PHP ternary conditional operator but I find
343
             * the code easier to read without this operator.
344
             */
345 4
            $mayHaveRows = $currentTable['TABLE_ROWS'] > 0 || $tableIsView;
0 ignored issues
show
introduced by
The condition $tableIsView is always true.
Loading history...
346
347 4
            if (! $this->dbIsSystemSchema) {
348 4
                $dropQuery = sprintf(
349 4
                    'DROP %s %s',
350 4
                    $tableIsView || $currentTable['ENGINE'] == null ? 'VIEW'
351 4
                    : 'TABLE',
352 4
                    Util::backquote(
353 4
                        $currentTable['TABLE_NAME'],
354 4
                    ),
355 4
                );
356 4
                $dropMessage = sprintf(
357 4
                    ($tableIsView || $currentTable['ENGINE'] == null
358
                        ? __('View %s has been dropped.')
359 4
                        : __('Table %s has been dropped.')),
360 4
                    str_replace(
361 4
                        ' ',
362 4
                        '&nbsp;',
363 4
                        htmlspecialchars($currentTable['TABLE_NAME']),
364 4
                    ),
365 4
                );
366
            }
367
368 4
            if ($numColumns > 0 && $this->numTables > $numColumns && ($rowCount % $numColumns) === 0) {
369
                $rowCount = 1;
370
371
                $html .= $this->template->render('database/structure/table_header', [
372
                    'db' => Current::$database,
373
                    'db_is_system_schema' => $this->dbIsSystemSchema,
374
                    'replication' => $replicaInfo['status'],
375
                    'properties_num_columns' => $config->settings['PropertiesNumColumns'],
376
                    'is_show_stats' => $this->isShowStats,
377
                    'show_charset' => $config->settings['ShowDbStructureCharset'],
378
                    'show_comment' => $config->settings['ShowDbStructureComment'],
379
                    'show_creation' => $config->settings['ShowDbStructureCreation'],
380
                    'show_last_update' => $config->settings['ShowDbStructureLastUpdate'],
381
                    'show_last_check' => $config->settings['ShowDbStructureLastCheck'],
382
                    'num_favorite_tables' => $config->settings['NumFavoriteTables'],
383
                    'structure_table_rows' => $structureTableRows,
384
                ]);
385
                $structureTableRows = [];
386
            }
387
388 4
            [$approxRows, $showSuperscript] = $this->isRowCountApproximated($currentTable, $tableIsView);
389
390 4
            [$do, $ignored] = $this->getReplicationStatus($replicaInfo, $truename);
391
392 4
            $structureTableRows[] = [
393 4
                'table_name_hash' => md5($currentTable['TABLE_NAME']),
394 4
                'db_table_name_hash' => md5(Current::$database . '.' . $currentTable['TABLE_NAME']),
395 4
                'db' => Current::$database,
396 4
                'curr' => $i,
397 4
                'input_class' => implode(' ', $inputClass),
398 4
                'table_is_view' => $tableIsView,
399 4
                'current_table' => $currentTable,
400 4
                'may_have_rows' => $mayHaveRows,
401 4
                'browse_table_label_title' => htmlspecialchars($currentTable['TABLE_COMMENT']),
402 4
                'browse_table_label_truename' => $truename,
403 4
                'empty_table_sql_query' => 'TRUNCATE ' . Util::backquote($currentTable['TABLE_NAME']),
404 4
                'empty_table_message_to_show' => urlencode(
405 4
                    sprintf(
406 4
                        __('Table %s has been emptied.'),
407 4
                        htmlspecialchars(
408 4
                            $currentTable['TABLE_NAME'],
409 4
                        ),
410 4
                    ),
411 4
                ),
412 4
                'tracking_icon' => $this->getTrackingIcon($truename, $trackedTables[$truename] ?? null),
413 4
                'server_replica_status' => $replicaInfo['status'],
414 4
                'table_url_params' => $tableUrlParams,
415 4
                'db_is_system_schema' => $this->dbIsSystemSchema,
416 4
                'drop_query' => $dropQuery,
417 4
                'drop_message' => $dropMessage,
418 4
                'collation' => $collationDefinition,
419 4
                'formatted_size' => $formattedSize,
420 4
                'unit' => $unit,
421 4
                'overhead' => $overhead,
422 4
                'create_time' => isset($createTime) && $createTime
423 4
                        ? Util::localisedDate(strtotime($createTime)) : '-',
424 4
                'update_time' => isset($updateTime) && $updateTime
425 4
                        ? Util::localisedDate(strtotime($updateTime)) : '-',
426 4
                'check_time' => isset($checkTime) && $checkTime
427 4
                        ? Util::localisedDate(strtotime($checkTime)) : '-',
428 4
                'charset' => $charset ?? '',
429 4
                'is_show_stats' => $this->isShowStats,
430 4
                'ignored' => $ignored,
431 4
                'do' => $do,
432 4
                'approx_rows' => $approxRows,
433 4
                'show_superscript' => $showSuperscript,
434 4
                'already_favorite' => $recentFavoriteTables->contains(
435 4
                    new RecentFavoriteTable(
436 4
                        DatabaseName::from(Current::$database),
437 4
                        TableName::from($currentTable['TABLE_NAME']),
438 4
                    ),
439 4
                ),
440 4
                'num_favorite_tables' => $config->settings['NumFavoriteTables'],
441 4
                'properties_num_columns' => $config->settings['PropertiesNumColumns'],
442 4
                'limit_chars' => $config->settings['LimitChars'],
443 4
                'show_charset' => $config->settings['ShowDbStructureCharset'],
444 4
                'show_comment' => $config->settings['ShowDbStructureComment'],
445 4
                'show_creation' => $config->settings['ShowDbStructureCreation'],
446 4
                'show_last_update' => $config->settings['ShowDbStructureLastUpdate'],
447 4
                'show_last_check' => $config->settings['ShowDbStructureLastCheck'],
448 4
            ];
449
450 4
            $overallApproxRows = $overallApproxRows || $approxRows;
451
        }
452
453 4
        $databaseCollation = [];
454 4
        $databaseCharset = '';
455 4
        $collation = Charsets::findCollationByName(
456 4
            $this->dbi,
457 4
            $config->selectedServer['DisableIS'],
458 4
            $this->dbi->getDbCollation(Current::$database),
459 4
        );
460 4
        if ($collation !== null) {
461
            $databaseCollation = ['name' => $collation->getName(), 'description' => $collation->getDescription()];
462
            $databaseCharset = $collation->getCharset();
463
        }
464
465 4
        $relationParameters = $this->relation->getRelationParameters();
466
467 4
        $defaultStorageEngine = '';
468 4
        if ($config->settings['PropertiesNumColumns'] < 2) {
469
            // MySQL <= 5.5.2
470 4
            $defaultStorageEngine = $this->dbi->fetchValue('SELECT @@storage_engine;');
471 4
            if (! is_string($defaultStorageEngine) || $defaultStorageEngine === '') {
472
                // MySQL >= 5.5.3
473 4
                $defaultStorageEngine = $this->dbi->fetchValue('SELECT @@default_storage_engine;');
474
            }
475
        }
476
477 4
        return $html . $this->template->render('database/structure/table_header', [
478 4
            'db' => Current::$database,
479 4
            'db_is_system_schema' => $this->dbIsSystemSchema,
480 4
            'replication' => $replicaInfo['status'],
481 4
            'properties_num_columns' => $config->settings['PropertiesNumColumns'],
482 4
            'is_show_stats' => $this->isShowStats,
483 4
            'show_charset' => $config->settings['ShowDbStructureCharset'],
484 4
            'show_comment' => $config->settings['ShowDbStructureComment'],
485 4
            'show_creation' => $config->settings['ShowDbStructureCreation'],
486 4
            'show_last_update' => $config->settings['ShowDbStructureLastUpdate'],
487 4
            'show_last_check' => $config->settings['ShowDbStructureLastCheck'],
488 4
            'num_favorite_tables' => $config->settings['NumFavoriteTables'],
489 4
            'structure_table_rows' => $structureTableRows,
490 4
            'body_for_table_summary' => [
491 4
                'num_tables' => $this->numTables,
492 4
                'server_replica_status' => $replicaInfo['status'],
493 4
                'db_is_system_schema' => $this->dbIsSystemSchema,
494 4
                'sum_entries' => $sumEntries,
495 4
                'database_collation' => $databaseCollation,
496 4
                'is_show_stats' => $this->isShowStats,
497 4
                'database_charset' => $databaseCharset,
498 4
                'sum_size' => $sumSize,
499 4
                'overhead_size' => $overheadSize,
500 4
                'create_time_all' => $createTimeAll ? Util::localisedDate(strtotime($createTimeAll)) : '-',
501 4
                'update_time_all' => $updateTimeAll ? Util::localisedDate(strtotime($updateTimeAll)) : '-',
502 4
                'check_time_all' => $checkTimeAll ? Util::localisedDate(strtotime($checkTimeAll)) : '-',
503 4
                'approx_rows' => $overallApproxRows,
504 4
                'num_favorite_tables' => $config->settings['NumFavoriteTables'],
505 4
                'db' => Current::$database,
506 4
                'properties_num_columns' => $config->settings['PropertiesNumColumns'],
507 4
                'default_storage_engine' => $defaultStorageEngine,
508 4
                'show_charset' => $config->settings['ShowDbStructureCharset'],
509 4
                'show_comment' => $config->settings['ShowDbStructureComment'],
510 4
                'show_creation' => $config->settings['ShowDbStructureCreation'],
511 4
                'show_last_update' => $config->settings['ShowDbStructureLastUpdate'],
512 4
                'show_last_check' => $config->settings['ShowDbStructureLastCheck'],
513 4
            ],
514 4
            'check_all_tables' => [
515 4
                'text_dir' => LanguageManager::$textDir,
516 4
                'overhead_check' => $overheadCheck,
517 4
                'db_is_system_schema' => $this->dbIsSystemSchema,
518 4
                'hidden_fields' => $hiddenFields,
519 4
                'disable_multi_table' => $config->settings['DisableMultiTableMaintenance'],
520 4
                'central_columns_work' => $relationParameters->centralColumnsFeature !== null,
521 4
            ],
522 4
        ]);
523
    }
524
525
    /**
526
     * Returns the tracking icon if the table is tracked
527
     *
528
     * @return string HTML for tracking icon
529
     */
530 4
    private function getTrackingIcon(string $table, TrackedTable|null $trackedTable): string
531
    {
532 4
        $trackingIcon = '';
533 4
        if (Tracker::isActive() && $trackedTable !== null) {
534
            $trackingIcon = $this->template->render('database/structure/tracking_icon', [
535
                'db' => Current::$database,
536
                'table' => $table,
537
                'is_tracked' => $trackedTable->active,
538
            ]);
539
        }
540
541 4
        return $trackingIcon;
542
    }
543
544
    /**
545
     * Returns whether the row count is approximated
546
     *
547
     * @param mixed[] $currentTable array containing details about the table
548
     * @param bool    $tableIsView  whether the table is a view
549
     *
550
     * @return array{bool, string}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{bool, string} at position 2 could not be parsed: Expected ':' at position 2, but found 'bool'.
Loading history...
551
     */
552 4
    private function isRowCountApproximated(
553
        array $currentTable,
554
        bool $tableIsView,
555
    ): array {
556 4
        $approxRows = false;
557 4
        $showSuperscript = '';
558
559
        // there is a null value in the ENGINE
560
        // - when the table needs to be repaired, or
561
        // - when it's a view
562
        //  so ensure that we'll display "in use" below for a table
563
        //  that needs to be repaired
564 4
        if (isset($currentTable['TABLE_ROWS']) && ($currentTable['ENGINE'] != null || $tableIsView)) {
565
            // InnoDB/TokuDB table: we did not get an accurate row count
566 4
            $approxRows = ! $tableIsView
567 4
                && in_array($currentTable['ENGINE'], ['InnoDB', 'TokuDB'], true)
568 4
                && ! $currentTable['COUNTED'];
569
570 4
            if ($tableIsView && $currentTable['TABLE_ROWS'] >= Config::getInstance()->settings['MaxExactCountViews']) {
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

570
            if ($tableIsView && $currentTable['TABLE_ROWS'] >= /** @scrutinizer ignore-deprecated */ Config::getInstance()->settings['MaxExactCountViews']) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
571
                $approxRows = true;
572
                $showSuperscript = Generator::showHint(
573
                    Sanitize::convertBBCode(
574
                        sprintf(
575
                            __(
576
                                'This view has at least this number of rows. Please refer to %sdocumentation%s.',
577
                            ),
578
                            '[doc@cfg_MaxExactCountViews]',
579
                            '[/doc]',
580
                        ),
581
                    ),
582
                );
583
            }
584
        }
585
586 4
        return [$approxRows, $showSuperscript];
587
    }
588
589
    /**
590
     * Returns the replication status of the table.
591
     *
592
     * @param mixed[] $replicaInfo
593
     * @param string  $table       table name
594
     *
595
     * @return array{bool, bool}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{bool, bool} at position 2 could not be parsed: Expected ':' at position 2, but found 'bool'.
Loading history...
596
     */
597 4
    private function getReplicationStatus(array $replicaInfo, string $table): array
598
    {
599 4
        $do = $ignored = false;
600 4
        if ($replicaInfo['status']) {
601
            $nbServReplicaDoDb = count($replicaInfo['Do_DB']);
602
            $nbServReplicaIgnoreDb = count($replicaInfo['Ignore_DB']);
603
            $searchDoDBInTruename = array_search($table, $replicaInfo['Do_DB']);
604
            $searchDoDBInDB = array_search(Current::$database, $replicaInfo['Do_DB']);
605
606
            $do = (is_string($searchDoDBInTruename) && $searchDoDBInTruename !== '')
607
                || (is_string($searchDoDBInDB) && $searchDoDBInDB !== '')
608
                || ($nbServReplicaDoDb == 0 && $nbServReplicaIgnoreDb == 0)
609
                || $this->hasTable($replicaInfo['Wild_Do_Table'], $table);
610
611
            $searchDb = array_search(Current::$database, $replicaInfo['Ignore_DB']);
612
            $searchTable = array_search($table, $replicaInfo['Ignore_Table']);
613
            $ignored = (is_string($searchTable) && $searchTable !== '')
614
                || (is_string($searchDb) && $searchDb !== '')
615
                || $this->hasTable($replicaInfo['Wild_Ignore_Table'], $table);
616
        }
617
618 4
        return [$do, $ignored];
619
    }
620
621
    /**
622
     * Find table with truename
623
     *
624
     * @param mixed[] $db       DB to look into
625
     * @param string  $truename Table name
626
     */
627 4
    private function hasTable(array $db, string $truename): bool
628
    {
629 4
        foreach ($db as $dbTable) {
630
            if (
631 4
                Current::$database == $this->replication->extractDbOrTable($dbTable)
632 4
                && preg_match(
633 4
                    '@^' .
634 4
                    preg_quote(mb_substr($this->replication->extractDbOrTable($dbTable, 'table'), 0, -1), '@') . '@',
635 4
                    $truename,
636 4
                )
637
            ) {
638 4
                return true;
639
            }
640
        }
641
642 4
        return false;
643
    }
644
645
    /**
646
     * Get the value set for ENGINE table,
647
     *
648
     * @internal param bool $table_is_view whether table is view or not
649
     *
650
     * @param mixed[] $currentTable current table
651
     * @param int     $sumSize      total table size
652
     * @param int     $overheadSize overhead size
653
     *
654
     * @return mixed[]
655
     */
656 4
    private function getStuffForEngineTypeTable(
657
        array $currentTable,
658
        int $sumSize,
659
        int $overheadSize,
660
    ): array {
661 4
        $formattedSize = '-';
662 4
        $unit = '';
663 4
        $formattedOverhead = '';
664 4
        $overheadUnit = '';
665 4
        $tableIsView = false;
666
667 4
        switch ($currentTable['ENGINE']) {
668
            // MyISAM, ISAM or Heap table: Row count, data size and index size
669
            // are accurate; data size is accurate for ARCHIVE
670 4
            case 'MyISAM':
671 4
            case 'ISAM':
672 4
            case 'HEAP':
673 4
            case 'MEMORY':
674 4
            case 'ARCHIVE':
675 4
            case 'Aria':
676 4
            case 'Maria':
677 4
                [
678 4
                    $currentTable,
679 4
                    $formattedSize,
680 4
                    $unit,
681 4
                    $formattedOverhead,
682 4
                    $overheadUnit,
683 4
                    $overheadSize,
684 4
                    $sumSize,
685 4
                ] = $this->getValuesForAriaTable(
686 4
                    $currentTable,
687 4
                    $sumSize,
688 4
                    $overheadSize,
689 4
                    $formattedSize,
690 4
                    $unit,
691 4
                    $formattedOverhead,
692 4
                    $overheadUnit,
693 4
                );
694 4
                break;
695
            case 'InnoDB':
696
            case 'PBMS':
697
            case 'TokuDB':
698
            case 'ROCKSDB':
699
                // InnoDB table: Row count is not accurate but data and index sizes are.
700
                // PBMS table in Drizzle: TABLE_ROWS is taken from table cache,
701
                // so it may be unavailable
702
                [$currentTable, $formattedSize, $unit, $sumSize] = $this->getValuesForInnodbTable(
703
                    $currentTable,
704
                    $sumSize,
705
                );
706
                break;
707
            // Mysql 5.0.x (and lower) uses MRG_MyISAM
708
            // and MySQL 5.1.x (and higher) uses MRG_MYISAM
709
            // Both are aliases for MERGE
710
            case 'MRG_MyISAM':
711
            case 'MRG_MYISAM':
712
            case 'MERGE':
713
            case 'BerkeleyDB':
714
                // Merge or BerkleyDB table: Only row count is accurate.
715
                if ($this->isShowStats) {
716
                    $formattedSize = ' - ';
717
                }
718
719
                break;
720
            // for a view, the ENGINE is sometimes reported as null,
721
            // or on some servers it's reported as "SYSTEM VIEW"
722
            case null:
723
            case 'SYSTEM VIEW':
724
                // possibly a view, do nothing
725
                break;
726
            case 'Mroonga':
727
                // The idea is to show the size only if Mroonga is available,
728
                // in other case the old unknown message will appear
729
                if (StorageEngine::hasMroongaEngine()) {
730
                    [$currentTable, $formattedSize, $unit, $sumSize] = $this->getValuesForMroongaTable(
731
                        $currentTable,
732
                        $sumSize,
733
                    );
734
                    break;
735
                }
736
                // no break, go to default case
737
            default:
738
                // Unknown table type.
739
                if ($this->isShowStats) {
740
                    $formattedSize = __('unknown');
741
                }
742
        }
743
744 4
        if ($currentTable['TABLE_TYPE'] === 'VIEW' || $currentTable['TABLE_TYPE'] === 'SYSTEM VIEW') {
745
            // countRecords() takes care of $cfg['MaxExactCountViews']
746
            $currentTable['TABLE_ROWS'] = $this->dbi
747
                ->getTable(Current::$database, $currentTable['TABLE_NAME'])
748
                ->countRecords(true);
749
            $tableIsView = true;
750
        }
751
752 4
        return [
753 4
            $currentTable,
754 4
            $formattedSize,
755 4
            $unit,
756 4
            $formattedOverhead,
757 4
            $overheadUnit,
758 4
            $overheadSize,
759 4
            $tableIsView,
760 4
            $sumSize,
761 4
        ];
762
    }
763
764
    /**
765
     * Get values for ARIA/MARIA tables
766
     *
767
     * @param mixed[] $currentTable      current table
768
     * @param int     $sumSize           sum size
769
     * @param int     $overheadSize      overhead size
770
     * @param string  $formattedSize     formatted size
771
     * @param string  $unit              unit
772
     * @param string  $formattedOverhead overhead formatted
773
     * @param string  $overheadUnit      overhead unit
774
     *
775
     * @return mixed[]
776
     */
777 8
    private function getValuesForAriaTable(
778
        array $currentTable,
779
        int $sumSize,
780
        int $overheadSize,
781
        string $formattedSize,
782
        string $unit,
783
        string $formattedOverhead,
784
        string $overheadUnit,
785
    ): array {
786 8
        if ($this->dbIsSystemSchema) {
787 4
            $currentTable['Rows'] = $this->dbi
788 4
                ->getTable(Current::$database, $currentTable['Name'])
789 4
                ->countRecords();
790
        }
791
792 8
        if ($this->isShowStats) {
793
            /** @var int $tblsize */
794 8
            $tblsize = $currentTable['Data_length']
795 8
                + $currentTable['Index_length'];
796 8
            $sumSize += $tblsize;
797 8
            [$formattedSize, $unit] = Util::formatByteDown($tblsize, 3, $tblsize > 0 ? 1 : 0);
798 8
            if (isset($currentTable['Data_free']) && $currentTable['Data_free'] > 0) {
799 8
                [$formattedOverhead, $overheadUnit] = Util::formatByteDown($currentTable['Data_free'], 3, 1);
800 8
                $overheadSize += $currentTable['Data_free'];
801
            }
802
        }
803
804 8
        return [$currentTable, $formattedSize, $unit, $formattedOverhead, $overheadUnit, $overheadSize, $sumSize];
805
    }
806
807
    /**
808
     * Get values for InnoDB table
809
     *
810
     * @param mixed[] $currentTable current table
811
     * @param int     $sumSize      sum size
812
     *
813
     * @return mixed[]
814
     */
815 4
    private function getValuesForInnodbTable(
816
        array $currentTable,
817
        int $sumSize,
818
    ): array {
819 4
        $formattedSize = $unit = '';
820
821
        if (
822 4
            (in_array($currentTable['ENGINE'], ['InnoDB', 'TokuDB'], true)
823 4
            && $currentTable['TABLE_ROWS'] < Config::getInstance()->settings['MaxExactCount'])
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

823
            && $currentTable['TABLE_ROWS'] < /** @scrutinizer ignore-deprecated */ Config::getInstance()->settings['MaxExactCount'])

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
824 4
            || ! isset($currentTable['TABLE_ROWS'])
825
        ) {
826 4
            $currentTable['COUNTED'] = true;
827 4
            $currentTable['TABLE_ROWS'] = $this->dbi
828 4
                ->getTable(Current::$database, $currentTable['TABLE_NAME'])
829 4
                ->countRecords(true);
830
        } else {
831 4
            $currentTable['COUNTED'] = false;
832
        }
833
834 4
        if ($this->isShowStats) {
835
            /** @var int $tblsize */
836 4
            $tblsize = $currentTable['Data_length']
837 4
                + $currentTable['Index_length'];
838 4
            $sumSize += $tblsize;
839 4
            [$formattedSize, $unit] = Util::formatByteDown($tblsize, 3, $tblsize > 0 ? 1 : 0);
840
        }
841
842 4
        return [$currentTable, $formattedSize, $unit, $sumSize];
843
    }
844
845
    /**
846
     * Get values for Mroonga table
847
     *
848
     * @param mixed[] $currentTable current table
849
     * @param int     $sumSize      sum size
850
     *
851
     * @return mixed[]
852
     */
853 4
    private function getValuesForMroongaTable(
854
        array $currentTable,
855
        int $sumSize,
856
    ): array {
857 4
        $formattedSize = '';
858 4
        $unit = '';
859
860 4
        if ($this->isShowStats) {
861
            /** @var int $tblsize */
862 4
            $tblsize = $currentTable['Data_length'] + $currentTable['Index_length'];
863 4
            $sumSize += $tblsize;
864 4
            [$formattedSize, $unit] = Util::formatByteDown($tblsize, 3, $tblsize > 0 ? 1 : 0);
865
        }
866
867 4
        return [$currentTable, $formattedSize, $unit, $sumSize];
868
    }
869
}
870