Completed
Push — master ( 64741e...904a1b )
by Maurício
09:11
created

Database/DatabaseStructureController.php (1 issue)

1
<?php
2
/* vim: set expandtab sw=4 ts=4 sts=4: */
3
/**
4
 * Holds the PhpMyAdmin\Controllers\Database\DatabaseStructureController
5
 *
6
 * @package PhpMyAdmin\Controllers
7
 */
8
declare(strict_types=1);
9
10
namespace PhpMyAdmin\Controllers\Database;
11
12
use PhpMyAdmin\Charsets;
13
use PhpMyAdmin\Config\PageSettings;
14
use PhpMyAdmin\Controllers\DatabaseController;
15
use PhpMyAdmin\Core;
16
use PhpMyAdmin\DatabaseInterface;
17
use PhpMyAdmin\Display\CreateTable;
18
use PhpMyAdmin\Message;
19
use PhpMyAdmin\RecentFavoriteTable;
20
use PhpMyAdmin\Relation;
21
use PhpMyAdmin\Replication;
22
use PhpMyAdmin\Response;
23
use PhpMyAdmin\Sanitize;
24
use PhpMyAdmin\Template;
25
use PhpMyAdmin\Tracker;
26
use PhpMyAdmin\Util;
27
use PhpMyAdmin\Url;
28
29
/**
30
 * Handles database structure logic
31
 *
32
 * @package PhpMyAdmin\Controllers
33
 */
34
class DatabaseStructureController extends DatabaseController
35
{
36
    /**
37
     * @var int Number of tables
38
     */
39
    protected $_num_tables;
40
    /**
41
     * @var int Current position in the list
42
     */
43
    protected $_pos;
44
    /**
45
     * @var bool DB is information_schema
46
     */
47
    protected $_db_is_system_schema;
48
    /**
49
     * @var int Number of tables
50
     */
51
    protected $_total_num_tables;
52
    /**
53
     * @var array Tables in the database
54
     */
55
    protected $_tables;
56
    /**
57
     * @var bool whether stats show or not
58
     */
59
    protected $_is_show_stats;
60
61
    /**
62
     * @var Relation
63
     */
64
    private $relation;
65
66
    /**
67
     * @var Replication
68
     */
69
    private $replication;
70
71
    /**
72
     * Constructor
73
     *
74
     * @param Response          $response Response object
75
     * @param DatabaseInterface $dbi      DatabaseInterface object
76
     * @param string            $db       Database name
77
     */
78
    public function __construct($response, $dbi, $db)
79
    {
80
        parent::__construct($response, $dbi, $db);
81
        $this->relation = new Relation($dbi);
82
        $this->replication = new Replication();
83
    }
84
85
    /**
86
     * Retrieves databse information for further use
87
     *
88
     * @param string $sub_part Page part name
89
     *
90
     * @return void
91
     */
92
    private function _getDbInfo($sub_part)
93
    {
94
        list(
95
            $tables,
96
            $num_tables,
97
            $total_num_tables,
98
            ,
99
            $is_show_stats,
100
            $db_is_system_schema,
101
            ,
102
            ,
103
            $pos
104
        ) = Util::getDbInfo($this->db, $sub_part);
105
106
        $this->_tables = $tables;
107
        $this->_num_tables = $num_tables;
108
        $this->_pos = $pos;
109
        $this->_db_is_system_schema = $db_is_system_schema;
110
        $this->_total_num_tables = $total_num_tables;
111
        $this->_is_show_stats = $is_show_stats;
112
    }
113
114
    /**
115
     * Index action
116
     *
117
     * @return void
118
     */
119
    public function indexAction()
120
    {
121
        $response = Response::getInstance();
122
123
        // Add/Remove favorite tables using Ajax request.
124
        if ($response->isAjax() && !empty($_REQUEST['favorite_table'])) {
125
            $this->addRemoveFavoriteTablesAction();
126
            return;
127
        }
128
129
        // If there is an Ajax request for real row count of a table.
130
        if ($response->isAjax()
131
            && isset($_REQUEST['real_row_count'])
132
            && $_REQUEST['real_row_count'] == true
133
        ) {
134
            $this->handleRealRowCountRequestAction();
135
            return;
136
        }
137
138
        // Drops/deletes/etc. multiple tables if required
139
        if ((! empty($_POST['submit_mult']) && isset($_POST['selected_tbl']))
140
            || isset($_POST['mult_btn'])
141
        ) {
142
            $this->multiSubmitAction();
143
        }
144
145
        $this->response->getHeader()->getScripts()->addFiles(
146
            [
147
                'db_structure.js',
148
                'tbl_change.js',
149
            ]
150
        );
151
152
        // Gets the database structure
153
        $this->_getDbInfo('_structure');
154
155
        // Checks if there are any tables to be shown on current page.
156
        // If there are no tables, the user is redirected to the last page
157
        // having any.
158
        if ($this->_total_num_tables > 0 && $this->_pos > $this->_total_num_tables) {
159
            $uri = './db_structure.php' . Url::getCommonRaw([
160
                'db' => $this->db,
161
                'pos' => max(0, $this->_total_num_tables - $GLOBALS['cfg']['MaxTableList']),
162
                'reload' => 1
163
            ]);
164
            Core::sendHeaderLocation($uri);
165
        }
166
167
        include_once 'libraries/replication.inc.php';
168
169
        PageSettings::showGroup('DbStructure');
170
171
        // 1. No tables
172
        if ($this->_num_tables == 0) {
173
            $this->response->addHTML(
174
                Message::notice(__('No tables found in database.'))
175
            );
176
            if (empty($this->_db_is_system_schema)) {
177
                $this->response->addHTML(CreateTable::getHtml($this->db));
178
            }
179
            return;
180
        }
181
182
        // else
183
        // 2. Shows table information
184
        /**
185
         * Displays the tables list
186
         */
187
        $this->response->addHTML('<div id="tableslistcontainer">');
188
        $_url_params = [
189
            'pos' => $this->_pos,
190
            'db'  => $this->db];
191
192
        // Add the sort options if they exists
193
        if (isset($_REQUEST['sort'])) {
194
            $_url_params['sort'] = $_REQUEST['sort'];
195
        }
196
197
        if (isset($_REQUEST['sort_order'])) {
198
            $_url_params['sort_order'] = $_REQUEST['sort_order'];
199
        }
200
201
        $this->response->addHTML(
202
            Util::getListNavigator(
203
                $this->_total_num_tables,
204
                $this->_pos,
205
                $_url_params,
206
                'db_structure.php',
207
                'frame_content',
208
                $GLOBALS['cfg']['MaxTableList']
209
            )
210
        );
211
212
        $this->displayTableList();
213
214
        // display again the table list navigator
215
        $this->response->addHTML(
216
            Util::getListNavigator(
217
                $this->_total_num_tables,
218
                $this->_pos,
219
                $_url_params,
220
                'db_structure.php',
221
                'frame_content',
222
                $GLOBALS['cfg']['MaxTableList']
223
            )
224
        );
225
226
        $this->response->addHTML('</div><hr />');
227
228
        /**
229
         * Work on the database
230
         */
231
        /* DATABASE WORK */
232
        /* Printable view of a table */
233
        $this->response->addHTML(
234
            $this->template->render('database/structure/print_view_data_dictionary_link', [
235
                'url_query' => Url::getCommon([
236
                    'db' => $this->db,
237
                    'goto' => 'db_structure.php',
238
                ])
239
            ])
240
        );
241
242
        if (empty($this->_db_is_system_schema)) {
243
            $this->response->addHTML(CreateTable::getHtml($this->db));
244
        }
245
    }
246
247
    /**
248
     * Add or remove favorite tables
249
     *
250
     * @return void
251
     */
252
    public function addRemoveFavoriteTablesAction()
253
    {
254
        $fav_instance = RecentFavoriteTable::getInstance('favorite');
255
        if (isset($_REQUEST['favorite_tables'])) {
256
            $favorite_tables = json_decode($_REQUEST['favorite_tables'], true);
257
        } else {
258
            $favorite_tables = [];
259
        }
260
        // Required to keep each user's preferences separate.
261
        $user = sha1($GLOBALS['cfg']['Server']['user']);
262
263
        // Request for Synchronization of favorite tables.
264
        if (isset($_REQUEST['sync_favorite_tables'])) {
265
            $cfgRelation = $this->relation->getRelationsParam();
266
            if ($cfgRelation['favoritework']) {
267
                $this->synchronizeFavoriteTables($fav_instance, $user, $favorite_tables);
268
            }
269
            return;
270
        }
271
        $changes = true;
272
        $titles = Util::buildActionTitles();
273
        $favorite_table = $_REQUEST['favorite_table'];
274
        $already_favorite = $this->checkFavoriteTable($favorite_table);
275
276
        if (isset($_REQUEST['remove_favorite'])) {
277
            if ($already_favorite) {
278
                // If already in favorite list, remove it.
279
                $fav_instance->remove($this->db, $favorite_table);
280
                $already_favorite = false; // for favorite_anchor template
281
            }
282
        } elseif (isset($_REQUEST['add_favorite'])) {
283
            if (!$already_favorite) {
284
                $nbTables = count($fav_instance->getTables());
285
                if ($nbTables == $GLOBALS['cfg']['NumFavoriteTables']) {
286
                    $changes = false;
287
                } else {
288
                    // Otherwise add to favorite list.
289
                    $fav_instance->add($this->db, $favorite_table);
290
                    $already_favorite = true;  // for favorite_anchor template
291
                }
292
            }
293
        }
294
295
        $favorite_tables[$user] = $fav_instance->getTables();
296
        $this->response->addJSON('changes', $changes);
297
        if (!$changes) {
298
            $this->response->addJSON(
299
                'message',
300
                $this->template->render('components/error_message', [
301
                    'msg' => __("Favorite List is full!"),
302
                ])
303
            );
304
            return;
305
        }
306
        // Check if current table is already in favorite list.
307
        $favParams = ['db' => $this->db,
308
            'ajax_request' => true,
309
            'favorite_table' => $favorite_table,
310
            (($already_favorite ? 'remove' : 'add') . '_favorite') => true
311
        ];
312
        $this->response->addJSON([
313
            'user' => $user,
314
            'favorite_tables' => json_encode($favorite_tables),
315
            'list' => $fav_instance->getHtmlList(),
316
            'anchor' => $this->template->render('database/structure/favorite_anchor', [
317
                'table_name_hash' => md5($favorite_table),
318
                'db_table_name_hash' => md5($this->db . "." . $favorite_table),
319
                'fav_params' => $favParams,
320
                'already_favorite' => $already_favorite,
321
                'titles' => $titles,
322
            ]),
323
        ]);
324
    }
325
326
    /**
327
     * Handles request for real row count on database level view page.
328
     *
329
     * @return boolean true
330
     */
331
    public function handleRealRowCountRequestAction()
332
    {
333
        $ajax_response = $this->response;
334
        // If there is a request to update all table's row count.
335
        if (!isset($_REQUEST['real_row_count_all'])) {
336
            // Get the real row count for the table.
337
            $real_row_count = $this->dbi
338
                ->getTable($this->db, $_REQUEST['table'])
339
                ->getRealRowCountTable();
340
            // Format the number.
341
            $real_row_count = Util::formatNumber($real_row_count, 0);
342
            $ajax_response->addJSON('real_row_count', $real_row_count);
343
            return;
344
        }
345
346
        // Array to store the results.
347
        $real_row_count_all = [];
348
        // Iterate over each table and fetch real row count.
349
        foreach ($this->_tables as $table) {
350
            $row_count = $this->dbi
351
                ->getTable($this->db, $table['TABLE_NAME'])
352
                ->getRealRowCountTable();
353
            $real_row_count_all[] = [
354
                'table' => $table['TABLE_NAME'],
355
                'row_count' => $row_count
356
            ];
357
        }
358
359
        $ajax_response->addJSON(
360
            'real_row_count_all',
361
            json_encode($real_row_count_all)
362
        );
363
    }
364
365
    /**
366
     * Handles actions related to multiple tables
367
     *
368
     * @return void
369
     */
370
    public function multiSubmitAction()
371
    {
372
        $action = 'db_structure.php';
373
        $err_url = 'db_structure.php' . Url::getCommon(
374
            ['db' => $this->db]
375
        );
376
377
        // see bug #2794840; in this case, code path is:
378
        // db_structure.php -> libraries/mult_submits.inc.php -> sql.php
379
        // -> db_structure.php and if we got an error on the multi submit,
380
        // we must display it here and not call again mult_submits.inc.php
381
        if (! isset($_POST['error']) || false === $_POST['error']) {
382
            include 'libraries/mult_submits.inc.php';
383
        }
384
        if (empty($_POST['message'])) {
385
            $_POST['message'] = Message::success();
386
        }
387
    }
388
389
    /**
390
     * Displays the list of tables
391
     *
392
     * @return void
393
     */
394
    protected function displayTableList()
395
    {
396
        // filtering
397
        $this->response->addHTML(
398
            $this->template->render('filter', ['filter_value' => ''])
399
        );
400
        // table form
401
        $this->response->addHTML(
402
            $this->template->render('database/structure/table_header', [
403
                'db' => $this->db,
404
                'db_is_system_schema' => $this->_db_is_system_schema,
405
                'replication' => $GLOBALS['replication_info']['slave']['status'],
406
                'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
407
                'is_show_stats' => $GLOBALS['is_show_stats'],
408
                'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
409
                'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
410
                'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
411
                'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
412
                'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
413
                'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
414
            ])
415
        );
416
417
        $i = $sum_entries = 0;
418
        $overhead_check = false;
419
        $create_time_all = '';
420
        $update_time_all = '';
421
        $check_time_all = '';
422
        $num_columns = $GLOBALS['cfg']['PropertiesNumColumns'] > 1
423
            ? ceil($this->_num_tables / $GLOBALS['cfg']['PropertiesNumColumns']) + 1
424
            : 0;
425
        $row_count      = 0;
426
        $sum_size       = 0;
427
        $overhead_size  = 0;
428
429
        $hidden_fields = [];
430
        $overall_approx_rows = false;
431
        foreach ($this->_tables as $keyname => $current_table) {
432
            // Get valid statistics whatever is the table type
433
434
            $drop_query = '';
435
            $drop_message = '';
436
            $overhead = '';
437
            $input_class = ['checkall'];
438
439
            $table_is_view = false;
440
            // Sets parameters for links
441
            $tbl_url_query = Url::getCommon(
442
                ['db' => $this->db, 'table' => $current_table['TABLE_NAME']]
443
            );
444
            // do not list the previous table's size info for a view
445
446
            list($current_table, $formatted_size, $unit, $formatted_overhead,
447
                $overhead_unit, $overhead_size, $table_is_view, $sum_size)
448
                    = $this->getStuffForEngineTypeTable(
449
                        $current_table,
450
                        $sum_size,
451
                        $overhead_size
452
                    );
453
454
            $curTable = $this->dbi
455
                ->getTable($this->db, $current_table['TABLE_NAME']);
456
            if (!$curTable->isMerge()) {
457
                $sum_entries += $current_table['TABLE_ROWS'];
458
            }
459
460
            if (isset($current_table['Collation'])) {
461
                $collation = '<dfn title="'
462
                    . Charsets::getCollationDescr($current_table['Collation']) . '">'
463
                    . $current_table['Collation'] . '</dfn>';
464
            } else {
465
                $collation = '---';
466
            }
467
468
            if ($this->_is_show_stats) {
469
                if ($formatted_overhead != '') {
470
                    $overhead = '<a href="tbl_structure.php'
471
                        . $tbl_url_query . '#showusage">'
472
                        . '<span>' . $formatted_overhead . '</span>&nbsp;'
473
                        . '<span class="unit">' . $overhead_unit . '</span>'
474
                        . '</a>' . "\n";
475
                    $overhead_check = true;
476
                    $input_class[] = 'tbl-overhead';
477
                } else {
478
                    $overhead = '-';
479
                }
480
            } // end if
481
482
            if ($GLOBALS['cfg']['ShowDbStructureCharset']) {
483
                if (isset($current_table['Collation'])) {
484
                    $charset = mb_substr($collation, 0, mb_strpos($collation, "_"));
485
                } else {
486
                    $charset = '';
487
                }
488
            }
489
490
            if ($GLOBALS['cfg']['ShowDbStructureCreation']) {
491
                $create_time = isset($current_table['Create_time'])
492
                    ? $current_table['Create_time'] : '';
493
                if ($create_time
494
                    && (!$create_time_all
495
                    || $create_time < $create_time_all)
496
                ) {
497
                    $create_time_all = $create_time;
498
                }
499
            }
500
501
            if ($GLOBALS['cfg']['ShowDbStructureLastUpdate']) {
502
                $update_time = isset($current_table['Update_time'])
503
                    ? $current_table['Update_time'] : '';
504
                if ($update_time
505
                    && (!$update_time_all
506
                    || $update_time < $update_time_all)
507
                ) {
508
                    $update_time_all = $update_time;
509
                }
510
            }
511
512
            if ($GLOBALS['cfg']['ShowDbStructureLastCheck']) {
513
                $check_time = isset($current_table['Check_time'])
514
                    ? $current_table['Check_time'] : '';
515
                if ($check_time
516
                    && (!$check_time_all
517
                    || $check_time < $check_time_all)
518
                ) {
519
                    $check_time_all = $check_time;
520
                }
521
            }
522
523
            $truename = (!empty($tooltip_truename)
524
                    && isset($tooltip_truename[$current_table['TABLE_NAME']]))
525
                ? $tooltip_truename[$current_table['TABLE_NAME']]
526
                : $current_table['TABLE_NAME'];
527
528
            $i++;
529
530
            $row_count++;
531
            if ($table_is_view) {
532
                $hidden_fields[] = '<input type="hidden" name="views[]" value="'
533
                    . htmlspecialchars($current_table['TABLE_NAME']) . '" />';
534
            }
535
536
            /*
537
             * Always activate links for Browse, Search and Empty, even if
538
             * the icons are greyed, because
539
             * 1. for views, we don't know the number of rows at this point
540
             * 2. for tables, another source could have populated them since the
541
             *    page was generated
542
             *
543
             * I could have used the PHP ternary conditional operator but I find
544
             * the code easier to read without this operator.
545
             */
546
            $may_have_rows = $current_table['TABLE_ROWS'] > 0 || $table_is_view;
547
            $titles = Util::buildActionTitles();
548
549
            if (!$this->_db_is_system_schema) {
550
                $drop_query = sprintf(
551
                    'DROP %s %s',
552
                    ($table_is_view || $current_table['ENGINE'] == null) ? 'VIEW'
553
                    : 'TABLE',
554
                    Util::backquote(
0 ignored issues
show
It seems like PhpMyAdmin\Util::backquo...nt_table['TABLE_NAME']) can also be of type array; however, parameter $args of sprintf() 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

554
                    /** @scrutinizer ignore-type */ Util::backquote(
Loading history...
555
                        $current_table['TABLE_NAME']
556
                    )
557
                );
558
                $drop_message = sprintf(
559
                    (($table_is_view || $current_table['ENGINE'] == null)
560
                        ? __('View %s has been dropped.')
561
                        : __('Table %s has been dropped.')),
562
                    str_replace(
563
                        ' ',
564
                        '&nbsp;',
565
                        htmlspecialchars($current_table['TABLE_NAME'])
566
                    )
567
                );
568
            }
569
570
            if ($num_columns > 0
571
                && $this->_num_tables > $num_columns
572
                && ($row_count % $num_columns) == 0
573
            ) {
574
                $row_count = 1;
575
576
                $this->response->addHTML(
577
                    '</tr></tbody></table></div></form>'
578
                );
579
580
                $this->response->addHTML(
581
                    $this->template->render('database/structure/table_header', [
582
                        'db' => $this->db,
583
                        'db_is_system_schema' => $this->_db_is_system_schema,
584
                        'replication' => $GLOBALS['replication_info']['slave']['status'],
585
                        'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
586
                        'is_show_stats' => $GLOBALS['is_show_stats'],
587
                        'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
588
                        'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
589
                        'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
590
                        'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
591
                        'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
592
                        'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
593
                    ])
594
                );
595
            }
596
597
            list($approx_rows, $show_superscript) = $this->isRowCountApproximated(
598
                $current_table,
599
                $table_is_view
600
            );
601
602
            list($do, $ignored) = $this->getReplicationStatus($truename);
603
604
            $this->response->addHTML(
605
                $this->template->render('database/structure/structure_table_row', [
606
                    'table_name_hash' => md5($current_table['TABLE_NAME']),
607
                    'db_table_name_hash' => md5($this->db . '.' . $current_table['TABLE_NAME']),
608
                    'db' => $this->db,
609
                    'curr' => $i,
610
                    'input_class' => implode(' ', $input_class),
611
                    'table_is_view' => $table_is_view,
612
                    'current_table' => $current_table,
613
                    'browse_table_title' => $may_have_rows ? $titles['Browse'] : $titles['NoBrowse'],
614
                    'search_table_title' => $may_have_rows ? $titles['Search'] : $titles['NoSearch'],
615
                    'browse_table_label_title' => htmlspecialchars($current_table['TABLE_COMMENT']),
616
                    'browse_table_label_truename' => $truename,
617
                    'empty_table_sql_query' => urlencode(
618
                        'TRUNCATE ' . Util::backquote(
619
                            $current_table['TABLE_NAME']
620
                        )
621
                    ),
622
                    'empty_table_message_to_show' => urlencode(
623
                        sprintf(
624
                            __('Table %s has been emptied.'),
625
                            htmlspecialchars(
626
                                $current_table['TABLE_NAME']
627
                            )
628
                        )
629
                    ),
630
                    'empty_table_title' => $may_have_rows ? $titles['Empty'] : $titles['NoEmpty'],
631
                    'tracking_icon' => $this->getTrackingIcon($truename),
632
                    'server_slave_status' => $GLOBALS['replication_info']['slave']['status'],
633
                    'tbl_url_query' => $tbl_url_query,
634
                    'db_is_system_schema' => $this->_db_is_system_schema,
635
                    'titles' => $titles,
636
                    'drop_query' => $drop_query,
637
                    'drop_message' => $drop_message,
638
                    'collation' => $collation,
639
                    'formatted_size' => $formatted_size,
640
                    'unit' => $unit,
641
                    'overhead' => $overhead,
642
                    'create_time' => (isset($create_time) && $create_time
643
                        ? Util::localisedDate(strtotime($create_time)) : '-'),
644
                    'update_time' => (isset($update_time) && $update_time
645
                        ? Util::localisedDate(strtotime($update_time)) : '-'),
646
                    'check_time' => (isset($check_time) && $check_time
647
                        ? Util::localisedDate(strtotime($check_time)) : '-'),
648
                    'charset' => isset($charset)
649
                        ? $charset : '',
650
                    'is_show_stats' => $this->_is_show_stats,
651
                    'ignored' => $ignored,
652
                    'do' => $do,
653
                    'approx_rows' => $approx_rows,
654
                    'show_superscript' => $show_superscript,
655
                    'already_favorite' => $this->checkFavoriteTable(
656
                        $current_table['TABLE_NAME']
657
                    ),
658
                    'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
659
                    'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
660
                    'limit_chars' => $GLOBALS['cfg']['LimitChars'],
661
                    'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
662
                    'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
663
                    'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
664
                    'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
665
                    'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
666
                ])
667
            );
668
669
            $overall_approx_rows = $overall_approx_rows || $approx_rows;
670
        } // end foreach
671
672
        $this->response->addHTML('</tbody>');
673
674
        $db_collation = $this->dbi->getDbCollation($this->db);
675
        $db_charset = mb_substr($db_collation, 0, mb_strpos($db_collation, "_"));
676
677
        // Show Summary
678
        $this->response->addHTML(
679
            $this->template->render('database/structure/body_for_table_summary', [
680
                'num_tables' => $this->_num_tables,
681
                'server_slave_status' => $GLOBALS['replication_info']['slave']['status'],
682
                'db_is_system_schema' => $this->_db_is_system_schema,
683
                'sum_entries' => $sum_entries,
684
                'db_collation' => $db_collation,
685
                'is_show_stats' => $this->_is_show_stats,
686
                'db_charset' => $db_charset,
687
                'sum_size' => $sum_size,
688
                'overhead_size' => $overhead_size,
689
                'create_time_all' => ($create_time_all ? Util::localisedDate(strtotime($create_time_all)) : '-'),
690
                'update_time_all' => ($update_time_all ? Util::localisedDate(strtotime($update_time_all)) : '-'),
691
                'check_time_all' => ($check_time_all ? Util::localisedDate(strtotime($check_time_all)) : '-'),
692
                'approx_rows' => $overall_approx_rows,
693
                'num_favorite_tables' => $GLOBALS['cfg']['NumFavoriteTables'],
694
                'db' => $GLOBALS['db'],
695
                'properties_num_columns' => $GLOBALS['cfg']['PropertiesNumColumns'],
696
                'dbi' => $this->dbi,
697
                'show_charset' => $GLOBALS['cfg']['ShowDbStructureCharset'],
698
                'show_comment' => $GLOBALS['cfg']['ShowDbStructureComment'],
699
                'show_creation' => $GLOBALS['cfg']['ShowDbStructureCreation'],
700
                'show_last_update' => $GLOBALS['cfg']['ShowDbStructureLastUpdate'],
701
                'show_last_check' => $GLOBALS['cfg']['ShowDbStructureLastCheck'],
702
            ])
703
        );
704
        $this->response->addHTML('</table>');
705
706
        //check all
707
        $this->response->addHTML(
708
            $this->template->render('database/structure/check_all_tables', [
709
                'pma_theme_image' => $GLOBALS['pmaThemeImage'],
710
                'text_dir' => $GLOBALS['text_dir'],
711
                'overhead_check' => $overhead_check,
712
                'db_is_system_schema' => $this->_db_is_system_schema,
713
                'hidden_fields' => $hidden_fields,
714
                'disable_multi_table' => $GLOBALS['cfg']['DisableMultiTableMaintenance'],
715
                'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'],
716
            ])
717
        );
718
        $this->response->addHTML('</form>'); //end of form
719
    }
720
721
    /**
722
     * Returns the tracking icon if the table is tracked
723
     *
724
     * @param string $table table name
725
     *
726
     * @return string HTML for tracking icon
727
     */
728
    protected function getTrackingIcon($table)
729
    {
730
        $tracking_icon = '';
731
        if (Tracker::isActive()) {
732
            $is_tracked = Tracker::isTracked($this->db, $table);
733
            if ($is_tracked
734
                || Tracker::getVersion($this->db, $table) > 0
735
            ) {
736
                $tracking_icon = $this->template->render('database/structure/tracking_icon', [
737
                    'db' => $this->db,
738
                    'table' => $table,
739
                    'is_tracked' => $is_tracked,
740
                ]);
741
            }
742
        }
743
        return $tracking_icon;
744
    }
745
746
    /**
747
     * Returns whether the row count is approximated
748
     *
749
     * @param array   $current_table array containing details about the table
750
     * @param boolean $table_is_view whether the table is a view
751
     *
752
     * @return array
753
     */
754
    protected function isRowCountApproximated(array $current_table, $table_is_view)
755
    {
756
        $approx_rows = false;
757
        $show_superscript = '';
758
759
        // there is a null value in the ENGINE
760
        // - when the table needs to be repaired, or
761
        // - when it's a view
762
        //  so ensure that we'll display "in use" below for a table
763
        //  that needs to be repaired
764
        if (isset($current_table['TABLE_ROWS'])
765
            && ($current_table['ENGINE'] != null || $table_is_view)
766
        ) {
767
            // InnoDB/TokuDB table: we did not get an accurate row count
768
            $approx_rows = !$table_is_view
769
                && in_array($current_table['ENGINE'], ['InnoDB', 'TokuDB'])
770
                && !$current_table['COUNTED'];
771
772
            if ($table_is_view
773
                && $current_table['TABLE_ROWS'] >= $GLOBALS['cfg']['MaxExactCountViews']
774
            ) {
775
                $approx_rows = true;
776
                $show_superscript = Util::showHint(
777
                    Sanitize::sanitize(
778
                        sprintf(
779
                            __(
780
                                'This view has at least this number of '
781
                                . 'rows. Please refer to %sdocumentation%s.'
782
                            ),
783
                            '[doc@cfg_MaxExactCountViews]',
784
                            '[/doc]'
785
                        )
786
                    )
787
                );
788
            }
789
        }
790
791
        return [$approx_rows, $show_superscript];
792
    }
793
794
    /**
795
     * Returns the replication status of the table.
796
     *
797
     * @param string $table table name
798
     *
799
     * @return array
800
     */
801
    protected function getReplicationStatus($table)
802
    {
803
        $do = $ignored = false;
804
        if ($GLOBALS['replication_info']['slave']['status']) {
805
            $nbServSlaveDoDb = count(
806
                $GLOBALS['replication_info']['slave']['Do_DB']
807
            );
808
            $nbServSlaveIgnoreDb = count(
809
                $GLOBALS['replication_info']['slave']['Ignore_DB']
810
            );
811
            $searchDoDBInTruename = array_search(
812
                $table,
813
                $GLOBALS['replication_info']['slave']['Do_DB']
814
            );
815
            $searchDoDBInDB = array_search(
816
                $this->db,
817
                $GLOBALS['replication_info']['slave']['Do_DB']
818
            );
819
820
            $do = strlen($searchDoDBInTruename) > 0
821
                || strlen($searchDoDBInDB) > 0
822
                || ($nbServSlaveDoDb == 0 && $nbServSlaveIgnoreDb == 0)
823
                || $this->hasTable(
824
                    $GLOBALS['replication_info']['slave']['Wild_Do_Table'],
825
                    $table
826
                );
827
828
            $searchDb = array_search(
829
                $this->db,
830
                $GLOBALS['replication_info']['slave']['Ignore_DB']
831
            );
832
            $searchTable = array_search(
833
                $table,
834
                $GLOBALS['replication_info']['slave']['Ignore_Table']
835
            );
836
            $ignored = strlen($searchTable) > 0
837
                || strlen($searchDb) > 0
838
                || $this->hasTable(
839
                    $GLOBALS['replication_info']['slave']['Wild_Ignore_Table'],
840
                    $table
841
                );
842
        }
843
844
        return [$do, $ignored];
845
    }
846
847
    /**
848
     * Synchronize favorite tables
849
     *
850
     *
851
     * @param RecentFavoriteTable $fav_instance    Instance of this class
852
     * @param string              $user            The user hash
853
     * @param array               $favorite_tables Existing favorites
854
     *
855
     * @return void
856
     */
857
    protected function synchronizeFavoriteTables(
858
        $fav_instance,
859
        $user,
860
        array $favorite_tables
861
    ) {
862
        $fav_instance_tables = $fav_instance->getTables();
863
864
        if (empty($fav_instance_tables)
865
            && isset($favorite_tables[$user])
866
        ) {
867
            foreach ($favorite_tables[$user] as $key => $value) {
868
                $fav_instance->add($value['db'], $value['table']);
869
            }
870
        }
871
        $favorite_tables[$user] = $fav_instance->getTables();
872
873
        $this->response->addJSON(
874
            [
875
                'favorite_tables' => json_encode($favorite_tables),
876
                'list' => $fav_instance->getHtmlList()
877
            ]
878
        );
879
        $server_id = $GLOBALS['server'];
880
        // Set flag when localStorage and pmadb(if present) are in sync.
881
        $_SESSION['tmpval']['favorites_synced'][$server_id] = true;
882
    }
883
884
    /**
885
     * Function to check if a table is already in favorite list.
886
     *
887
     * @param string $current_table current table
888
     *
889
     * @return true|false
890
     */
891
    protected function checkFavoriteTable($current_table)
892
    {
893
        // ensure $_SESSION['tmpval']['favorite_tables'] is initialized
894
        RecentFavoriteTable::getInstance('favorite');
895
        foreach ($_SESSION['tmpval']['favorite_tables'][$GLOBALS['server']] as $value) {
896
            if ($value['db'] == $this->db && $value['table'] == $current_table) {
897
                return true;
898
            }
899
        }
900
        return false;
901
    }
902
903
    /**
904
     * Find table with truename
905
     *
906
     * @param array  $db       DB to look into
907
     * @param string $truename Table name
908
     *
909
     * @return bool
910
     */
911
    protected function hasTable(array $db, $truename)
912
    {
913
        foreach ($db as $db_table) {
914
            if ($this->db == $this->replication->extractDbOrTable($db_table)
915
                && preg_match(
916
                    "@^" .
917
                    preg_quote(mb_substr($this->replication->extractDbOrTable($db_table, 'table'), 0, -1)) . "@",
918
                    $truename
919
                )
920
            ) {
921
                return true;
922
            }
923
        }
924
        return false;
925
    }
926
927
    /**
928
     * Get the value set for ENGINE table,
929
     *
930
     * @param array   $current_table current table
931
     * @param integer $sum_size      total table size
932
     * @param integer $overhead_size overhead size
933
     *
934
     * @return array
935
     * @internal param bool $table_is_view whether table is view or not
936
     */
937
    protected function getStuffForEngineTypeTable(
938
        array $current_table,
939
        $sum_size,
940
        $overhead_size
941
    ) {
942
        $formatted_size = '-';
943
        $unit = '';
944
        $formatted_overhead = '';
945
        $overhead_unit = '';
946
        $table_is_view = false;
947
948
        switch ($current_table['ENGINE']) {
949
        // MyISAM, ISAM or Heap table: Row count, data size and index size
950
        // are accurate; data size is accurate for ARCHIVE
951
            case 'MyISAM':
952
            case 'ISAM':
953
            case 'HEAP':
954
            case 'MEMORY':
955
            case 'ARCHIVE':
956
            case 'Aria':
957
            case 'Maria':
958
                list($current_table, $formatted_size, $unit, $formatted_overhead,
959
                $overhead_unit, $overhead_size, $sum_size)
960
                    = $this->getValuesForAriaTable(
961
                        $current_table,
962
                        $sum_size,
963
                        $overhead_size,
964
                        $formatted_size,
965
                        $unit,
966
                        $formatted_overhead,
967
                        $overhead_unit
968
                    );
969
                break;
970
            case 'InnoDB':
971
            case 'PBMS':
972
            case 'TokuDB':
973
                // InnoDB table: Row count is not accurate but data and index sizes are.
974
                // PBMS table in Drizzle: TABLE_ROWS is taken from table cache,
975
                // so it may be unavailable
976
                list($current_table, $formatted_size, $unit, $sum_size)
977
                = $this->getValuesForInnodbTable(
978
                    $current_table,
979
                    $sum_size
980
                );
981
                break;
982
        // Mysql 5.0.x (and lower) uses MRG_MyISAM
983
        // and MySQL 5.1.x (and higher) uses MRG_MYISAM
984
        // Both are aliases for MERGE
985
            case 'MRG_MyISAM':
986
            case 'MRG_MYISAM':
987
            case 'MERGE':
988
            case 'BerkeleyDB':
989
                // Merge or BerkleyDB table: Only row count is accurate.
990
                if ($this->_is_show_stats) {
991
                    $formatted_size =  ' - ';
992
                    $unit          =  '';
993
                }
994
                break;
995
        // for a view, the ENGINE is sometimes reported as null,
996
        // or on some servers it's reported as "SYSTEM VIEW"
997
            case null:
998
            case 'SYSTEM VIEW':
999
                // possibly a view, do nothing
1000
                break;
1001
            default:
1002
                // Unknown table type.
1003
                if ($this->_is_show_stats) {
1004
                    $formatted_size =  __('unknown');
1005
                    $unit          =  '';
1006
                }
1007
        } // end switch
1008
1009
        if ($current_table['TABLE_TYPE'] == 'VIEW'
1010
            || $current_table['TABLE_TYPE'] == 'SYSTEM VIEW'
1011
        ) {
1012
            // countRecords() takes care of $cfg['MaxExactCountViews']
1013
            $current_table['TABLE_ROWS'] = $this->dbi
1014
                ->getTable($this->db, $current_table['TABLE_NAME'])
1015
                ->countRecords(true);
1016
            $table_is_view = true;
1017
        }
1018
1019
        return [$current_table, $formatted_size, $unit, $formatted_overhead,
1020
            $overhead_unit, $overhead_size, $table_is_view, $sum_size
1021
        ];
1022
    }
1023
1024
    /**
1025
     * Get values for ARIA/MARIA tables
1026
     *
1027
     * @param array   $current_table      current table
1028
     * @param integer $sum_size           sum size
1029
     * @param integer $overhead_size      overhead size
1030
     * @param integer $formatted_size     formatted size
1031
     * @param string  $unit               unit
1032
     * @param integer $formatted_overhead overhead formatted
1033
     * @param string  $overhead_unit      overhead unit
1034
     *
1035
     * @return array
1036
     */
1037
    protected function getValuesForAriaTable(
1038
        array $current_table,
1039
        $sum_size,
1040
        $overhead_size,
1041
        $formatted_size,
1042
        $unit,
1043
        $formatted_overhead,
1044
        $overhead_unit
1045
    ) {
1046
        if ($this->_db_is_system_schema) {
1047
            $current_table['Rows'] = $this->dbi
1048
                ->getTable($this->db, $current_table['Name'])
1049
                ->countRecords();
1050
        }
1051
1052
        if ($this->_is_show_stats) {
1053
            $tblsize = $current_table['Data_length']
1054
                + $current_table['Index_length'];
1055
            $sum_size += $tblsize;
1056
            list($formatted_size, $unit) = Util::formatByteDown(
1057
                $tblsize,
1058
                3,
1059
                ($tblsize > 0) ? 1 : 0
1060
            );
1061
            if (isset($current_table['Data_free'])
1062
                && $current_table['Data_free'] > 0
1063
            ) {
1064
                list($formatted_overhead, $overhead_unit)
1065
                    = Util::formatByteDown(
1066
                        $current_table['Data_free'],
1067
                        3,
1068
                        (($current_table['Data_free'] > 0) ? 1 : 0)
1069
                    );
1070
                $overhead_size += $current_table['Data_free'];
1071
            }
1072
        }
1073
        return [$current_table, $formatted_size, $unit, $formatted_overhead,
1074
            $overhead_unit, $overhead_size, $sum_size
1075
        ];
1076
    }
1077
1078
    /**
1079
     * Get values for InnoDB table
1080
     *
1081
     * @param array   $current_table current table
1082
     * @param integer $sum_size      sum size
1083
     *
1084
     * @return array
1085
     */
1086
    protected function getValuesForInnodbTable(
1087
        array $current_table,
1088
        $sum_size
1089
    ) {
1090
        $formatted_size = $unit = '';
1091
1092
        if ((in_array($current_table['ENGINE'], ['InnoDB', 'TokuDB'])
1093
            && $current_table['TABLE_ROWS'] < $GLOBALS['cfg']['MaxExactCount'])
1094
            || !isset($current_table['TABLE_ROWS'])
1095
        ) {
1096
            $current_table['COUNTED'] = true;
1097
            $current_table['TABLE_ROWS'] = $this->dbi
1098
                ->getTable($this->db, $current_table['TABLE_NAME'])
1099
                ->countRecords(true);
1100
        } else {
1101
            $current_table['COUNTED'] = false;
1102
        }
1103
1104
        if ($this->_is_show_stats) {
1105
            $tblsize = $current_table['Data_length']
1106
                + $current_table['Index_length'];
1107
            $sum_size += $tblsize;
1108
            list($formatted_size, $unit) = Util::formatByteDown(
1109
                $tblsize,
1110
                3,
1111
                (($tblsize > 0) ? 1 : 0)
1112
            );
1113
        }
1114
1115
        return [$current_table, $formatted_size, $unit, $sum_size];
1116
    }
1117
}
1118