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

Controllers/Table/TableStructureController.php (3 issues)

1
<?php
2
/* vim: set expandtab sw=4 ts=4 sts=4: */
3
/**
4
 * Holds the PhpMyAdmin\Controllers\Table\TableStructureController
5
 *
6
 * @package PhpMyAdmin\Controllers
7
 */
8
declare(strict_types=1);
9
10
namespace PhpMyAdmin\Controllers\Table;
11
12
use PhpMyAdmin\CentralColumns;
13
use PhpMyAdmin\Config\PageSettings;
14
use PhpMyAdmin\Controllers\TableController;
15
use PhpMyAdmin\Core;
16
use PhpMyAdmin\CreateAddField;
17
use PhpMyAdmin\Index;
18
use PhpMyAdmin\Message;
19
use PhpMyAdmin\ParseAnalyze;
20
use PhpMyAdmin\Partition;
21
use PhpMyAdmin\Relation;
22
use PhpMyAdmin\Sql;
23
use PhpMyAdmin\SqlParser\Context;
24
use PhpMyAdmin\SqlParser\Parser;
25
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
26
use PhpMyAdmin\Table;
27
use PhpMyAdmin\Template;
28
use PhpMyAdmin\Tracker;
29
use PhpMyAdmin\Transformations;
30
use PhpMyAdmin\Url;
31
use PhpMyAdmin\Util;
32
33
/**
34
 * Handles table structure logic
35
 *
36
 * @package PhpMyAdmin\Controllers
37
 */
38
class TableStructureController extends TableController
39
{
40
    /**
41
     * @var Table  The table object
42
     */
43
    protected $table_obj;
44
    /**
45
     * @var string  The URL query string
46
     */
47
    protected $_url_query;
48
    /**
49
     * @var bool DB is information_schema
50
     */
51
    protected $_db_is_system_schema;
52
    /**
53
     * @var bool Table is a view
54
     */
55
    protected $_tbl_is_view;
56
    /**
57
     * @var string Table storage engine
58
     */
59
    protected $_tbl_storage_engine;
60
    /**
61
     * @var int Number of rows
62
     */
63
    protected $_table_info_num_rows;
64
    /**
65
     * @var string Table collation
66
     */
67
    protected $_tbl_collation;
68
    /**
69
     * @var array Show table info
70
     */
71
    protected $_showtable;
72
73
    /**
74
     * @var CreateAddField
75
     */
76
    private $createAddField;
77
78
    /**
79
     * @var Relation
80
     */
81
    private $relation;
82
83
    /**
84
     * @var Transformations
85
     */
86
    private $transformations;
87
88
    /**
89
     * TableStructureController constructor
90
     *
91
     * @param \PhpMyAdmin\Response          $response            Response object
92
     * @param \PhpMyAdmin\DatabaseInterface $dbi                 DatabaseInterface object
93
     * @param string                        $db                  Database name
94
     * @param string                        $table               Table name
95
     * @param string                        $type                Indicate the db_structure or tbl_structure
96
     * @param int                           $num_tables          Number of tables
97
     * @param int                           $pos                 Current position in the list
98
     * @param bool                          $db_is_system_schema DB is information_schema
99
     * @param int                           $total_num_tables    Number of tables
100
     * @param array                         $tables              Tables in the DB
101
     * @param bool                          $is_show_stats       Whether stats show or not
102
     * @param bool                          $tbl_is_view         Table is a view
103
     * @param string                        $tbl_storage_engine  Table storage engine
104
     * @param int                           $table_info_num_rows Number of rows
105
     * @param string                        $tbl_collation       Table collation
106
     * @param array                         $showtable           Show table info
107
     */
108
    public function __construct(
109
        $response,
110
        $dbi,
111
        $db,
112
        $table,
113
        $type,
114
        $num_tables,
115
        $pos,
116
        $db_is_system_schema,
117
        $total_num_tables,
118
        $tables,
119
        $is_show_stats,
120
        $tbl_is_view,
121
        $tbl_storage_engine,
122
        $table_info_num_rows,
123
        $tbl_collation,
124
        $showtable
125
    ) {
126
        parent::__construct($response, $dbi, $db, $table);
127
128
        $this->_db_is_system_schema = $db_is_system_schema;
129
        $this->_url_query = Url::getCommonRaw(['db' => $db, 'table' => $table]);
130
        $this->_tbl_is_view = $tbl_is_view;
131
        $this->_tbl_storage_engine = $tbl_storage_engine;
132
        $this->_table_info_num_rows = $table_info_num_rows;
133
        $this->_tbl_collation = $tbl_collation;
134
        $this->_showtable = $showtable;
135
        $this->table_obj = $this->dbi->getTable($this->db, $this->table);
136
137
        $this->createAddField = new CreateAddField($dbi);
138
        $this->relation = new Relation($dbi);
139
        $this->transformations = new Transformations();
140
    }
141
142
    /**
143
     * Index action
144
     *
145
     * @return void
146
     */
147
    public function indexAction()
148
    {
149
        PageSettings::showGroup('TableStructure');
150
151
        /**
152
         * Function implementations for this script
153
         */
154
        include_once 'libraries/check_user_privileges.inc.php';
155
156
        $this->response->getHeader()->getScripts()->addFiles(
157
            [
158
                'tbl_structure.js',
159
                'indexes.js'
160
            ]
161
        );
162
163
        /**
164
         * Handle column moving
165
         */
166
        if (isset($_REQUEST['move_columns'])
167
            && is_array($_REQUEST['move_columns'])
168
            && $this->response->isAjax()
169
        ) {
170
            $this->moveColumns();
171
            return;
172
        }
173
174
        /**
175
         * handle MySQL reserved words columns check
176
         */
177
        if (isset($_REQUEST['reserved_word_check'])) {
178
            if ($GLOBALS['cfg']['ReservedWordDisableWarning'] === false) {
179
                $columns_names = $_REQUEST['field_name'];
180
                $reserved_keywords_names = [];
181
                foreach ($columns_names as $column) {
182
                    if (Context::isKeyword(trim($column), true)) {
183
                        $reserved_keywords_names[] = trim($column);
184
                    }
185
                }
186
                if (Context::isKeyword(trim($this->table), true)) {
187
                    $reserved_keywords_names[] = trim($this->table);
188
                }
189
                if (count($reserved_keywords_names) == 0) {
190
                    $this->response->setRequestStatus(false);
191
                }
192
                $this->response->addJSON(
193
                    'message',
194
                    sprintf(
195
                        _ngettext(
196
                            'The name \'%s\' is a MySQL reserved keyword.',
197
                            'The names \'%s\' are MySQL reserved keywords.',
198
                            count($reserved_keywords_names)
199
                        ),
200
                        implode(',', $reserved_keywords_names)
201
                    )
202
                );
203
            } else {
204
                $this->response->setRequestStatus(false);
205
            }
206
            return;
207
        }
208
        /**
209
         * A click on Change has been made for one column
210
         */
211
        if (isset($_REQUEST['change_column'])) {
212
            $this->displayHtmlForColumnChange(null, 'tbl_structure.php');
213
            return;
214
        }
215
216
        /**
217
         * Adding or editing partitioning of the table
218
         */
219
        if (isset($_REQUEST['edit_partitioning'])
220
            && ! isset($_REQUEST['save_partitioning'])
221
        ) {
222
            $this->displayHtmlForPartitionChange();
223
            return;
224
        }
225
226
        /**
227
         * handle multiple field commands if required
228
         *
229
         * submit_mult_*_x comes from IE if <input type="img" ...> is used
230
         */
231
        $submit_mult = $this->getMultipleFieldCommandType();
232
233
        if (! empty($submit_mult)) {
234
            if (isset($_REQUEST['selected_fld'])) {
235
                if ($submit_mult == 'browse') {
236
                    // browsing the table displaying only selected columns
237
                    $this->displayTableBrowseForSelectedColumns(
238
                        $GLOBALS['goto'],
239
                        $GLOBALS['pmaThemeImage']
240
                    );
241
                } else {
242
                    // handle multiple field commands
243
                    // handle confirmation of deleting multiple columns
244
                    $action = 'tbl_structure.php';
245
                    $GLOBALS['selected'] = $_REQUEST['selected_fld'];
246
                    list(
247
                        $what_ret, $query_type_ret, $is_unset_submit_mult,
248
                        $mult_btn_ret, $centralColsError
249
                        )
250
                            = $this->getDataForSubmitMult(
251
                                $submit_mult,
252
                                $_REQUEST['selected_fld'],
253
                                $action
254
                            );
255
                    //update the existing variables
256
                    // todo: refactor mult_submits.inc.php such as
257
                    // below globals are not needed anymore
258
                    if (isset($what_ret)) {
259
                        $GLOBALS['what'] = $what_ret;
260
                        global $what;
261
                    }
262
                    if (isset($query_type_ret)) {
263
                        $GLOBALS['query_type'] = $query_type_ret;
264
                        global $query_type;
265
                    }
266
                    if ($is_unset_submit_mult) {
267
                        unset($submit_mult);
268
                    }
269
                    if (isset($mult_btn_ret)) {
270
                        $GLOBALS['mult_btn'] = $mult_btn_ret;
271
                        global $mult_btn;
272
                    }
273
                    include 'libraries/mult_submits.inc.php';
274
                    /**
275
                     * if $submit_mult == 'change', execution will have stopped
276
                     * at this point
277
                     */
278
                    if (empty($message)) {
279
                        $message = Message::success();
280
                    }
281
                    $this->response->addHTML(
282
                        Util::getMessage($message, $sql_query)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sql_query seems to be never defined.
Loading history...
283
                    );
284
                }
285
            } else {
286
                $this->response->setRequestStatus(false);
287
                $this->response->addJSON('message', __('No column selected.'));
288
            }
289
        }
290
291
        /**
292
         * Modifications have been submitted -> updates the table
293
         */
294
        if (isset($_REQUEST['do_save_data'])) {
295
            $regenerate = $this->updateColumns();
296
            if ($regenerate) {
297
                // This happens when updating failed
298
                // @todo: do something appropriate
299
            } else {
300
                // continue to show the table's structure
301
                unset($_REQUEST['selected']);
302
            }
303
        }
304
305
        /**
306
         * Modifications to the partitioning have been submitted -> updates the table
307
         */
308
        if (isset($_REQUEST['save_partitioning'])) {
309
            $this->updatePartitioning();
310
        }
311
312
        /**
313
         * Adding indexes
314
         */
315
        if (isset($_REQUEST['add_key'])
316
            || isset($_REQUEST['partition_maintenance'])
317
        ) {
318
            //todo: set some variables for sql.php include, to be eliminated
319
            //after refactoring sql.php
320
            $db = $this->db;
321
            $table = $this->table;
322
            $sql_query = $GLOBALS['sql_query'];
323
            $cfg = $GLOBALS['cfg'];
324
            $pmaThemeImage = $GLOBALS['pmaThemeImage'];
325
            include 'sql.php';
326
            $GLOBALS['reload'] = true;
327
        }
328
329
        /**
330
         * Gets the relation settings
331
         */
332
        $cfgRelation = $this->relation->getRelationsParam();
333
334
        /**
335
         * Runs common work
336
         */
337
        // set db, table references, for require_once that follows
338
        // got to be eliminated in long run
339
        $db = &$this->db;
340
        $table = &$this->table;
341
        $url_params = [];
342
        include_once 'libraries/tbl_common.inc.php';
343
        $this->_db_is_system_schema = $db_is_system_schema;
344
        $this->_url_query = Url::getCommonRaw([
345
            'db' => $db,
346
            'table' => $table,
347
            'goto' => 'tbl_structure.php',
348
            'back' => 'tbl_structure.php',
349
        ]);
350
        /* The url_params array is initialized in above include */
351
        $url_params['goto'] = 'tbl_structure.php';
352
        $url_params['back'] = 'tbl_structure.php';
353
354
        // 2. Gets table keys and retains them
355
        // @todo should be: $server->db($db)->table($table)->primary()
356
        $primary = Index::getPrimary($this->table, $this->db);
357
        $columns_with_index = $this->dbi
358
            ->getTable($this->db, $this->table)
359
            ->getColumnsWithIndex(
360
                Index::UNIQUE | Index::INDEX | Index::SPATIAL
361
                | Index::FULLTEXT
362
            );
363
        $columns_with_unique_index = $this->dbi
364
            ->getTable($this->db, $this->table)
365
            ->getColumnsWithIndex(Index::UNIQUE);
366
367
        // 3. Get fields
368
        $fields = (array)$this->dbi->getColumns(
369
            $this->db,
370
            $this->table,
371
            null,
372
            true
373
        );
374
375
        //display table structure
376
        $this->response->addHTML(
377
            $this->displayStructure(
378
                $cfgRelation,
379
                $columns_with_unique_index,
380
                $url_params,
381
                $primary,
382
                $fields,
383
                $columns_with_index
384
            )
385
        );
386
    }
387
388
    /**
389
     * Moves columns in the table's structure based on $_REQUEST
390
     *
391
     * @return void
392
     */
393
    protected function moveColumns()
394
    {
395
        $this->dbi->selectDb($this->db);
396
397
        /*
398
         * load the definitions for all columns
399
         */
400
        $columns = $this->dbi->getColumnsFull($this->db, $this->table);
401
        $column_names = array_keys($columns);
402
        $changes = [];
403
404
        // move columns from first to last
405
        for ($i = 0, $l = count($_REQUEST['move_columns']); $i < $l; $i++) {
406
            $column = $_REQUEST['move_columns'][$i];
407
            // is this column already correctly placed?
408
            if ($column_names[$i] == $column) {
409
                continue;
410
            }
411
412
            // it is not, let's move it to index $i
413
            $data = $columns[$column];
414
            $extracted_columnspec = Util::extractColumnSpec($data['Type']);
415
            if (isset($data['Extra'])
416
                && $data['Extra'] == 'on update CURRENT_TIMESTAMP'
417
            ) {
418
                $extracted_columnspec['attribute'] = $data['Extra'];
419
                unset($data['Extra']);
420
            }
421
            $current_timestamp = ($data['Type'] == 'timestamp'
422
                    || $data['Type'] == 'datetime')
423
                && ($data['Default'] == 'CURRENT_TIMESTAMP'
424
                    || $data['Default'] == 'current_timestamp()');
425
426
            if ($data['Null'] === 'YES' && $data['Default'] === null) {
427
                $default_type = 'NULL';
428
            } elseif ($current_timestamp) {
429
                $default_type = 'CURRENT_TIMESTAMP';
430
            } elseif ($data['Default'] === null) {
431
                $default_type = 'NONE';
432
            } else {
433
                $default_type = 'USER_DEFINED';
434
            }
435
436
            $virtual = [
437
                'VIRTUAL', 'PERSISTENT', 'VIRTUAL GENERATED', 'STORED GENERATED'
438
            ];
439
            $data['Virtuality'] = '';
440
            $data['Expression'] = '';
441
            if (isset($data['Extra']) && in_array($data['Extra'], $virtual)) {
442
                $data['Virtuality'] = str_replace(' GENERATED', '', $data['Extra']);
443
                $expressions = $this->table->getColumnGenerationExpression($column);
444
                $data['Expression'] = $expressions[$column];
445
            }
446
447
            $changes[] = 'CHANGE ' . Table::generateAlter(
448
                $column,
449
                $column,
450
                mb_strtoupper($extracted_columnspec['type']),
451
                $extracted_columnspec['spec_in_brackets'],
452
                $extracted_columnspec['attribute'],
453
                isset($data['Collation']) ? $data['Collation'] : '',
454
                $data['Null'] === 'YES' ? 'NULL' : 'NOT NULL',
455
                $default_type,
456
                $current_timestamp ? '' : $data['Default'],
457
                isset($data['Extra']) && $data['Extra'] !== '' ? $data['Extra']
458
                : false,
459
                isset($data['COLUMN_COMMENT']) && $data['COLUMN_COMMENT'] !== ''
460
                ? $data['COLUMN_COMMENT'] : false,
461
                $data['Virtuality'],
462
                $data['Expression'],
463
                $i === 0 ? '-first' : $column_names[$i - 1]
464
            );
465
            // update current column_names array, first delete old position
466
            for ($j = 0, $ll = count($column_names); $j < $ll; $j++) {
467
                if ($column_names[$j] == $column) {
468
                    unset($column_names[$j]);
469
                }
470
            }
471
            // insert moved column
472
            array_splice($column_names, $i, 0, $column);
473
        }
474
        if (empty($changes) && !isset($_REQUEST['preview_sql'])) { // should never happen
475
            $this->response->setRequestStatus(false);
476
            return;
477
        }
478
        // query for moving the columns
479
        $sql_query = sprintf(
480
            'ALTER TABLE %s %s',
481
            Util::backquote($this->table),
482
            implode(', ', $changes)
483
        );
484
485
        if (isset($_REQUEST['preview_sql'])) { // preview sql
486
            $this->response->addJSON(
487
                'sql_data',
488
                $this->template->render('preview_sql', [
489
                    'query_data' => $sql_query
490
                ])
491
            );
492
        } else { // move column
493
            $this->dbi->tryQuery($sql_query);
494
            $tmp_error = $this->dbi->getError();
495
            if ($tmp_error) {
496
                $this->response->setRequestStatus(false);
497
                $this->response->addJSON('message', Message::error($tmp_error));
498
            } else {
499
                $message = Message::success(
500
                    __('The columns have been moved successfully.')
501
                );
502
                $this->response->addJSON('message', $message);
503
                $this->response->addJSON('columns', $column_names);
504
            }
505
        }
506
    }
507
508
    /**
509
     * Displays HTML for changing one or more columns
510
     *
511
     * @param array  $selected the selected columns
512
     * @param string $action   target script to call
513
     *
514
     * @return boolean true if error occurred
515
     *
516
     */
517
    protected function displayHtmlForColumnChange($selected, $action)
518
    {
519
        // $selected comes from mult_submits.inc.php
520
        if (empty($selected)) {
521
            $selected[] = $_REQUEST['field'];
522
            $selected_cnt = 1;
523
        } else { // from a multiple submit
524
            $selected_cnt = count($selected);
525
        }
526
527
        /**
528
         * @todo optimize in case of multiple fields to modify
529
         */
530
        $fields_meta = [];
531
        for ($i = 0; $i < $selected_cnt; $i++) {
532
            $value = $this->dbi->getColumns(
533
                $this->db,
534
                $this->table,
535
                $selected[$i],
536
                true
537
            );
538
            if (count($value) == 0) {
539
                $message = Message::error(
540
                    __('Failed to get description of column %s!')
541
                );
542
                $message->addParam($selected[$i]);
543
                $this->response->addHTML($message);
544
            } else {
545
                $fields_meta[] = $value;
546
            }
547
        }
548
        $num_fields = count($fields_meta);
549
        // set these globals because tbl_columns_definition_form.inc.php
550
        // verifies them
551
        // @todo: refactor tbl_columns_definition_form.inc.php so that it uses
552
        // protected function params
553
        $GLOBALS['action'] = $action;
554
        $GLOBALS['num_fields'] = $num_fields;
555
556
        /**
557
         * Form for changing properties.
558
         */
559
        include_once 'libraries/check_user_privileges.inc.php';
560
        include 'libraries/tbl_columns_definition_form.inc.php';
561
    }
562
563
    /**
564
     * Displays HTML for partition change
565
     *
566
     * @return string HTML for partition change
567
     */
568
    protected function displayHtmlForPartitionChange()
569
    {
570
        $partitionDetails = null;
571
        if (! isset($_REQUEST['partition_by'])) {
572
            $partitionDetails = $this->_extractPartitionDetails();
573
        }
574
575
        include 'libraries/tbl_partition_definition.inc.php';
576
        $this->response->addHTML(
577
            $this->template->render('table/structure/partition_definition_form', [
578
                'db' => $this->db,
579
                'table' => $this->table,
580
                'partition_details' => $partitionDetails,
581
            ])
582
        );
583
    }
584
585
    /**
586
     * Extracts partition details from CREATE TABLE statement
587
     *
588
     * @return array[] array of partition details
589
     */
590
    private function _extractPartitionDetails()
591
    {
592
        $createTable = (new Table($this->table, $this->db))->showCreate();
593
        if (! $createTable) {
594
            return null;
595
        }
596
597
        $parser = new Parser($createTable);
598
        /**
599
         * @var CreateStatement $stmt
600
         */
601
        $stmt = $parser->statements[0];
602
603
        $partitionDetails = [];
604
605
        $partitionDetails['partition_by'] = '';
606
        $partitionDetails['partition_expr'] = '';
607
        $partitionDetails['partition_count'] = '';
608
609
        if (! empty($stmt->partitionBy)) {
610
            $openPos = strpos($stmt->partitionBy, "(");
611
            $closePos = strrpos($stmt->partitionBy, ")");
612
613
            $partitionDetails['partition_by']
614
                = trim(substr($stmt->partitionBy, 0, $openPos));
615
            $partitionDetails['partition_expr']
616
                = trim(substr($stmt->partitionBy, $openPos + 1, $closePos - ($openPos + 1)));
617
            if (isset($stmt->partitionsNum)) {
618
                $count = $stmt->partitionsNum;
619
            } else {
620
                $count = count($stmt->partitions);
621
            }
622
            $partitionDetails['partition_count'] = $count;
623
        }
624
625
        $partitionDetails['subpartition_by'] = '';
626
        $partitionDetails['subpartition_expr'] = '';
627
        $partitionDetails['subpartition_count'] = '';
628
629
        if (! empty($stmt->subpartitionBy)) {
630
            $openPos = strpos($stmt->subpartitionBy, "(");
631
            $closePos = strrpos($stmt->subpartitionBy, ")");
632
633
            $partitionDetails['subpartition_by']
634
                = trim(substr($stmt->subpartitionBy, 0, $openPos));
635
            $partitionDetails['subpartition_expr']
636
                = trim(substr($stmt->subpartitionBy, $openPos + 1, $closePos - ($openPos + 1)));
637
            if (isset($stmt->subpartitionsNum)) {
638
                $count = $stmt->subpartitionsNum;
639
            } else {
640
                $count = count($stmt->partitions[0]->subpartitions);
641
            }
642
            $partitionDetails['subpartition_count'] = $count;
643
        }
644
645
        // Only LIST and RANGE type parameters allow subpartitioning
646
        $partitionDetails['can_have_subpartitions']
647
            = $partitionDetails['partition_count'] > 1
648
                && ($partitionDetails['partition_by'] == 'RANGE'
649
                || $partitionDetails['partition_by'] == 'RANGE COLUMNS'
650
                || $partitionDetails['partition_by'] == 'LIST'
651
                || $partitionDetails['partition_by'] == 'LIST COLUMNS');
652
653
        // Values are specified only for LIST and RANGE type partitions
654
        $partitionDetails['value_enabled'] = isset($partitionDetails['partition_by'])
655
            && ($partitionDetails['partition_by'] == 'RANGE'
656
            || $partitionDetails['partition_by'] == 'RANGE COLUMNS'
657
            || $partitionDetails['partition_by'] == 'LIST'
658
            || $partitionDetails['partition_by'] == 'LIST COLUMNS');
659
660
        $partitionDetails['partitions'] = [];
661
662
        for ($i = 0; $i < intval($partitionDetails['partition_count']); $i++) {
663
            if (! isset($stmt->partitions[$i])) {
664
                $partitionDetails['partitions'][$i] = [
665
                    'name' => 'p' . $i,
666
                    'value_type' => '',
667
                    'value' => '',
668
                    'engine' => '',
669
                    'comment' => '',
670
                    'data_directory' => '',
671
                    'index_directory' => '',
672
                    'max_rows' => '',
673
                    'min_rows' => '',
674
                    'tablespace' => '',
675
                    'node_group' => '',
676
                ];
677
            } else {
678
                $p = $stmt->partitions[$i];
679
                $type = $p->type;
680
                $expr = trim((string) $p->expr, '()');
681
                if ($expr == 'MAXVALUE') {
682
                    $type .= ' MAXVALUE';
683
                    $expr = '';
684
                }
685
                $partitionDetails['partitions'][$i] = [
686
                    'name' => $p->name,
687
                    'value_type' => $type,
688
                    'value' => $expr,
689
                    'engine' => $p->options->has('ENGINE', true),
690
                    'comment' => trim((string) $p->options->has('COMMENT', true), "'"),
691
                    'data_directory' => trim((string) $p->options->has('DATA DIRECTORY', true), "'"),
692
                    'index_directory' => trim((string) $p->options->has('INDEX_DIRECTORY', true), "'"),
693
                    'max_rows' => $p->options->has('MAX_ROWS', true),
694
                    'min_rows' => $p->options->has('MIN_ROWS', true),
695
                    'tablespace' => $p->options->has('TABLESPACE', true),
696
                    'node_group' => $p->options->has('NODEGROUP', true),
697
                ];
698
            }
699
700
            $partition =& $partitionDetails['partitions'][$i];
701
            $partition['prefix'] = 'partitions[' . $i . ']';
702
703
            if ($partitionDetails['subpartition_count'] > 1) {
704
                $partition['subpartition_count'] = $partitionDetails['subpartition_count'];
705
                $partition['subpartitions'] = [];
706
707
                for ($j = 0; $j < intval($partitionDetails['subpartition_count']); $j++) {
708
                    if (! isset($stmt->partitions[$i]->subpartitions[$j])) {
709
                        $partition['subpartitions'][$j] = [
710
                            'name' => $partition['name'] . '_s' . $j,
711
                            'engine' => '',
712
                            'comment' => '',
713
                            'data_directory' => '',
714
                            'index_directory' => '',
715
                            'max_rows' => '',
716
                            'min_rows' => '',
717
                            'tablespace' => '',
718
                            'node_group' => '',
719
                        ];
720
                    } else {
721
                        $sp = $stmt->partitions[$i]->subpartitions[$j];
722
                        $partition['subpartitions'][$j] = [
723
                            'name' => $sp->name,
724
                            'engine' => $sp->options->has('ENGINE', true),
725
                            'comment' => trim($sp->options->has('COMMENT', true), "'"),
726
                            'data_directory' => trim($sp->options->has('DATA DIRECTORY', true), "'"),
727
                            'index_directory' => trim($sp->options->has('INDEX_DIRECTORY', true), "'"),
728
                            'max_rows' => $sp->options->has('MAX_ROWS', true),
729
                            'min_rows' => $sp->options->has('MIN_ROWS', true),
730
                            'tablespace' => $sp->options->has('TABLESPACE', true),
731
                            'node_group' => $sp->options->has('NODEGROUP', true),
732
                        ];
733
                    }
734
735
                    $subpartition =& $partition['subpartitions'][$j];
736
                    $subpartition['prefix'] = 'partitions[' . $i . ']'
737
                        . '[subpartitions][' . $j . ']';
738
                }
739
            }
740
        }
741
742
        return $partitionDetails;
743
    }
744
745
    /**
746
     * Update the table's partitioning based on $_REQUEST
747
     *
748
     * @return void
749
     */
750
    protected function updatePartitioning()
751
    {
752
        $sql_query = "ALTER TABLE " . Util::backquote($this->table) . " "
753
            . $this->createAddField->getPartitionsDefinition();
754
755
        // Execute alter query
756
        $result = $this->dbi->tryQuery($sql_query);
757
758
        if ($result !== false) {
759
            $message = Message::success(
760
                __('Table %1$s has been altered successfully.')
761
            );
762
            $message->addParam($this->table);
763
            $this->response->addHTML(
764
                Util::getMessage($message, $sql_query, 'success')
765
            );
766
        } else {
767
            $this->response->setRequestStatus(false);
768
            $this->response->addJSON(
769
                'message',
770
                Message::rawError(
771
                    __('Query error') . ':<br />' . $this->dbi->getError()
772
                )
773
            );
774
        }
775
    }
776
777
    /**
778
     * Function to get the type of command for multiple field handling
779
     *
780
     * @return string
781
     */
782
    protected function getMultipleFieldCommandType()
783
    {
784
        $types = [
785
            'change', 'drop', 'primary',
786
            'index', 'unique', 'spatial',
787
            'fulltext', 'browse'
788
        ];
789
790
        foreach ($types as $type) {
791
            if (isset($_REQUEST['submit_mult_' . $type . '_x'])) {
792
                return $type;
793
            }
794
        }
795
796
        if (isset($_REQUEST['submit_mult'])) {
797
            return $_REQUEST['submit_mult'];
798
        } elseif (isset($_REQUEST['mult_btn'])
799
            && $_REQUEST['mult_btn'] == __('Yes')
800
        ) {
801
            if (isset($_REQUEST['selected'])) {
802
                $_REQUEST['selected_fld'] = $_REQUEST['selected'];
803
            }
804
            return 'row_delete';
805
        }
806
807
        return null;
808
    }
809
810
    /**
811
     * Function to display table browse for selected columns
812
     *
813
     * @param string $goto          goto page url
814
     * @param string $pmaThemeImage URI of the pma theme image
815
     *
816
     * @return void
817
     */
818
    protected function displayTableBrowseForSelectedColumns($goto, $pmaThemeImage)
819
    {
820
        $GLOBALS['active_page'] = 'sql.php';
821
        $fields = [];
822
        foreach ($_REQUEST['selected_fld'] as $sval) {
823
            $fields[] = Util::backquote($sval);
824
        }
825
        $sql_query = sprintf(
826
            'SELECT %s FROM %s.%s',
827
            implode(', ', $fields),
828
            Util::backquote($this->db),
829
            Util::backquote($this->table)
830
        );
831
832
        // Parse and analyze the query
833
        $db = &$this->db;
834
        list(
835
            $analyzed_sql_results,
836
            $db,
837
        ) = ParseAnalyze::sqlQuery($sql_query, $db);
838
        // @todo: possibly refactor
839
        extract($analyzed_sql_results);
840
841
        $sql = new Sql();
842
        $this->response->addHTML(
843
            $sql->executeQueryAndGetQueryResponse(
844
                isset($analyzed_sql_results) ? $analyzed_sql_results : '',
845
                false, // is_gotofile
846
                $this->db, // db
847
                $this->table, // table
848
                null, // find_real_end
849
                null, // sql_query_for_bookmark
850
                null, // extra_data
851
                null, // message_to_show
852
                null, // message
853
                null, // sql_data
854
                $goto, // goto
855
                $pmaThemeImage, // pmaThemeImage
856
                null, // disp_query
857
                null, // disp_message
858
                null, // query_type
859
                $sql_query, // sql_query
860
                null, // selectedTables
861
                null // complete_query
862
            )
863
        );
864
    }
865
866
    /**
867
     * Update the table's structure based on $_REQUEST
868
     *
869
     * @return boolean              true if error occurred
870
     *
871
     */
872
    protected function updateColumns()
873
    {
874
        $err_url = 'tbl_structure.php' . Url::getCommon(
875
            [
876
                'db' => $this->db, 'table' => $this->table
877
            ]
878
        );
879
        $regenerate = false;
880
        $field_cnt = count($_REQUEST['field_name']);
881
        $changes = [];
882
        $adjust_privileges = [];
883
        $columns_with_index = $this->dbi
884
            ->getTable($this->db, $this->table)
885
            ->getColumnsWithIndex(
886
                Index::PRIMARY | Index::UNIQUE
887
            );
888
        for ($i = 0; $i < $field_cnt; $i++) {
889
            if (!$this->columnNeedsAlterTable($i)) {
890
                continue;
891
            }
892
893
            $changes[] = 'CHANGE ' . Table::generateAlter(
894
                Util::getValueByKey($_REQUEST, "field_orig.${i}", ''),
895
                $_REQUEST['field_name'][$i],
896
                $_REQUEST['field_type'][$i],
897
                $_REQUEST['field_length'][$i],
898
                $_REQUEST['field_attribute'][$i],
899
                Util::getValueByKey($_REQUEST, "field_collation.${i}", ''),
900
                Util::getValueByKey($_REQUEST, "field_null.${i}", 'NOT NULL'),
901
                $_REQUEST['field_default_type'][$i],
902
                $_REQUEST['field_default_value'][$i],
903
                Util::getValueByKey($_REQUEST, "field_extra.${i}", false),
904
                Util::getValueByKey($_REQUEST, "field_comments.${i}", ''),
905
                Util::getValueByKey($_REQUEST, "field_virtuality.${i}", ''),
906
                Util::getValueByKey($_REQUEST, "field_expression.${i}", ''),
907
                Util::getValueByKey($_REQUEST, "field_move_to.${i}", ''),
908
                $columns_with_index
909
            );
910
911
            // find the remembered sort expression
912
            $sorted_col = $this->table_obj->getUiProp(
913
                Table::PROP_SORTED_COLUMN
914
            );
915
            // if the old column name is part of the remembered sort expression
916
            if (mb_strpos(
917
                (string) $sorted_col,
918
                Util::backquote($_REQUEST['field_orig'][$i])
0 ignored issues
show
It seems like PhpMyAdmin\Util::backquo...UEST['field_orig'][$i]) can also be of type array; however, parameter $needle of mb_strpos() 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

918
                /** @scrutinizer ignore-type */ Util::backquote($_REQUEST['field_orig'][$i])
Loading history...
919
            ) !== false) {
920
                // delete the whole remembered sort expression
921
                $this->table_obj->removeUiProp(Table::PROP_SORTED_COLUMN);
922
            }
923
924
            if (isset($_REQUEST['field_adjust_privileges'][$i])
925
                && ! empty($_REQUEST['field_adjust_privileges'][$i])
926
                && $_REQUEST['field_orig'][$i] != $_REQUEST['field_name'][$i]
927
            ) {
928
                $adjust_privileges[$_REQUEST['field_orig'][$i]]
929
                    = $_REQUEST['field_name'][$i];
930
            }
931
        } // end for
932
933
        if (count($changes) > 0 || isset($_REQUEST['preview_sql'])) {
934
            // Builds the primary keys statements and updates the table
935
            $key_query = '';
936
            /**
937
             * this is a little bit more complex
938
             *
939
             * @todo if someone selects A_I when altering a column we need to check:
940
             *  - no other column with A_I
941
             *  - the column has an index, if not create one
942
             *
943
             */
944
945
            // To allow replication, we first select the db to use
946
            // and then run queries on this db.
947
            if (!$this->dbi->selectDb($this->db)) {
948
                Util::mysqlDie(
949
                    $this->dbi->getError(),
950
                    'USE ' . Util::backquote($this->db) . ';',
951
                    false,
952
                    $err_url
953
                );
954
            }
955
            $sql_query = 'ALTER TABLE ' . Util::backquote($this->table) . ' ';
956
            $sql_query .= implode(', ', $changes) . $key_query;
957
            $sql_query .= ';';
958
959
            // If there is a request for SQL previewing.
960
            if (isset($_REQUEST['preview_sql'])) {
961
                Core::previewSQL(count($changes) > 0 ? $sql_query : '');
962
            }
963
964
            $columns_with_index = $this->dbi
965
                ->getTable($this->db, $this->table)
966
                ->getColumnsWithIndex(
967
                    Index::PRIMARY | Index::UNIQUE | Index::INDEX
968
                    | Index::SPATIAL | Index::FULLTEXT
969
                );
970
971
            $changedToBlob = [];
972
            // While changing the Column Collation
973
            // First change to BLOB
974
            for ($i = 0; $i < $field_cnt; $i++) {
975
                if (isset($_REQUEST['field_collation'][$i])
976
                    && isset($_REQUEST['field_collation_orig'][$i])
977
                    && $_REQUEST['field_collation'][$i] !== $_REQUEST['field_collation_orig'][$i]
978
                    && ! in_array($_REQUEST['field_orig'][$i], $columns_with_index)
979
                ) {
980
                    $secondary_query = 'ALTER TABLE ' . Util::backquote(
981
                        $this->table
982
                    )
983
                    . ' CHANGE ' . Util::backquote(
0 ignored issues
show
Are you sure PhpMyAdmin\Util::backquo...UEST['field_orig'][$i]) 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

983
                    . ' CHANGE ' . /** @scrutinizer ignore-type */ Util::backquote(
Loading history...
984
                        $_REQUEST['field_orig'][$i]
985
                    )
986
                    . ' ' . Util::backquote($_REQUEST['field_orig'][$i])
987
                    . ' BLOB;';
988
                    $this->dbi->query($secondary_query);
989
                    $changedToBlob[$i] = true;
990
                } else {
991
                    $changedToBlob[$i] = false;
992
                }
993
            }
994
995
            // Then make the requested changes
996
            $result = $this->dbi->tryQuery($sql_query);
997
998
            if ($result !== false) {
999
                $changed_privileges = $this->adjustColumnPrivileges(
1000
                    $adjust_privileges
1001
                );
1002
1003
                if ($changed_privileges) {
1004
                    $message = Message::success(
1005
                        __(
1006
                            'Table %1$s has been altered successfully. Privileges ' .
1007
                            'have been adjusted.'
1008
                        )
1009
                    );
1010
                } else {
1011
                    $message = Message::success(
1012
                        __('Table %1$s has been altered successfully.')
1013
                    );
1014
                }
1015
                $message->addParam($this->table);
1016
1017
                $this->response->addHTML(
1018
                    Util::getMessage($message, $sql_query, 'success')
1019
                );
1020
            } else {
1021
                // An error happened while inserting/updating a table definition
1022
1023
                // Save the Original Error
1024
                $orig_error = $this->dbi->getError();
1025
                $changes_revert = [];
1026
1027
                // Change back to Original Collation and data type
1028
                for ($i = 0; $i < $field_cnt; $i++) {
1029
                    if ($changedToBlob[$i]) {
1030
                        $changes_revert[] = 'CHANGE ' . Table::generateAlter(
1031
                            Util::getValueByKey($_REQUEST, "field_orig.${i}", ''),
1032
                            $_REQUEST['field_name'][$i],
1033
                            $_REQUEST['field_type_orig'][$i],
1034
                            $_REQUEST['field_length_orig'][$i],
1035
                            $_REQUEST['field_attribute_orig'][$i],
1036
                            Util::getValueByKey($_REQUEST, "field_collation_orig.${i}", ''),
1037
                            Util::getValueByKey($_REQUEST, "field_null_orig.${i}", 'NOT NULL'),
1038
                            $_REQUEST['field_default_type_orig'][$i],
1039
                            $_REQUEST['field_default_value_orig'][$i],
1040
                            Util::getValueByKey($_REQUEST, "field_extra_orig.${i}", false),
1041
                            Util::getValueByKey($_REQUEST, "field_comments_orig.${i}", ''),
1042
                            Util::getValueByKey($_REQUEST, "field_virtuality_orig.${i}", ''),
1043
                            Util::getValueByKey($_REQUEST, "field_expression_orig.${i}", ''),
1044
                            Util::getValueByKey($_REQUEST, "field_move_to_orig.${i}", '')
1045
                        );
1046
                    }
1047
                }
1048
1049
                $revert_query = 'ALTER TABLE ' . Util::backquote($this->table)
1050
                    . ' ';
1051
                $revert_query .= implode(', ', $changes_revert) . '';
1052
                $revert_query .= ';';
1053
1054
                // Column reverted back to original
1055
                $this->dbi->query($revert_query);
1056
1057
                $this->response->setRequestStatus(false);
1058
                $this->response->addJSON(
1059
                    'message',
1060
                    Message::rawError(
1061
                        __('Query error') . ':<br />' . $orig_error
1062
                    )
1063
                );
1064
                $regenerate = true;
1065
            }
1066
        }
1067
1068
        // update field names in relation
1069
        if (isset($_REQUEST['field_orig']) && is_array($_REQUEST['field_orig'])) {
1070
            foreach ($_REQUEST['field_orig'] as $fieldindex => $fieldcontent) {
1071
                if ($_REQUEST['field_name'][$fieldindex] != $fieldcontent) {
1072
                    $this->relation->renameField(
1073
                        $this->db,
1074
                        $this->table,
1075
                        $fieldcontent,
1076
                        $_REQUEST['field_name'][$fieldindex]
1077
                    );
1078
                }
1079
            }
1080
        }
1081
1082
        // update mime types
1083
        if (isset($_REQUEST['field_mimetype'])
1084
            && is_array($_REQUEST['field_mimetype'])
1085
            && $GLOBALS['cfg']['BrowseMIME']
1086
        ) {
1087
            foreach ($_REQUEST['field_mimetype'] as $fieldindex => $mimetype) {
1088
                if (isset($_REQUEST['field_name'][$fieldindex])
1089
                    && strlen($_REQUEST['field_name'][$fieldindex]) > 0
1090
                ) {
1091
                    $this->transformations->setMime(
1092
                        $this->db,
1093
                        $this->table,
1094
                        $_REQUEST['field_name'][$fieldindex],
1095
                        $mimetype,
1096
                        $_REQUEST['field_transformation'][$fieldindex],
1097
                        $_REQUEST['field_transformation_options'][$fieldindex],
1098
                        $_REQUEST['field_input_transformation'][$fieldindex],
1099
                        $_REQUEST['field_input_transformation_options'][$fieldindex]
1100
                    );
1101
                }
1102
            }
1103
        }
1104
        return $regenerate;
1105
    }
1106
1107
    /**
1108
     * Adjusts the Privileges for all the columns whose names have changed
1109
     *
1110
     * @param array $adjust_privileges assoc array of old col names mapped to new
1111
     *                                 cols
1112
     *
1113
     * @return boolean  boolean whether at least one column privileges
1114
     * adjusted
1115
     */
1116
    protected function adjustColumnPrivileges(array $adjust_privileges)
1117
    {
1118
        $changed = false;
1119
1120
        if (Util::getValueByKey($GLOBALS, 'col_priv', false)
1121
            && Util::getValueByKey($GLOBALS, 'is_reload_priv', false)
1122
        ) {
1123
            $this->dbi->selectDb('mysql');
1124
1125
            // For Column specific privileges
1126
            foreach ($adjust_privileges as $oldCol => $newCol) {
1127
                $this->dbi->query(
1128
                    sprintf(
1129
                        'UPDATE %s SET Column_name = "%s"
1130
                        WHERE Db = "%s"
1131
                        AND Table_name = "%s"
1132
                        AND Column_name = "%s";',
1133
                        Util::backquote('columns_priv'),
1134
                        $newCol,
1135
                        $this->db,
1136
                        $this->table,
1137
                        $oldCol
1138
                    )
1139
                );
1140
1141
                // i.e. if atleast one column privileges adjusted
1142
                $changed = true;
1143
            }
1144
1145
            if ($changed) {
1146
                // Finally FLUSH the new privileges
1147
                $this->dbi->query("FLUSH PRIVILEGES;");
1148
            }
1149
        }
1150
1151
        return $changed;
1152
    }
1153
1154
    /**
1155
     * Verifies if some elements of a column have changed
1156
     *
1157
     * @param integer $i column index in the request
1158
     *
1159
     * @return boolean true if we need to generate ALTER TABLE
1160
     *
1161
     */
1162
    protected function columnNeedsAlterTable($i)
1163
    {
1164
        // these two fields are checkboxes so might not be part of the
1165
        // request; therefore we define them to avoid notices below
1166
        if (! isset($_REQUEST['field_null'][$i])) {
1167
            $_REQUEST['field_null'][$i] = 'NO';
1168
        }
1169
        if (! isset($_REQUEST['field_extra'][$i])) {
1170
            $_REQUEST['field_extra'][$i] = '';
1171
        }
1172
1173
        // field_name does not follow the convention (corresponds to field_orig)
1174
        if ($_REQUEST['field_name'][$i] != $_REQUEST['field_orig'][$i]) {
1175
            return true;
1176
        }
1177
1178
        $fields = [
1179
            'field_attribute', 'field_collation', 'field_comments',
1180
            'field_default_value', 'field_default_type', 'field_extra',
1181
            'field_length', 'field_null', 'field_type'
1182
        ];
1183
        foreach ($fields as $field) {
1184
            if ($_REQUEST[$field][$i] != $_REQUEST[$field . '_orig'][$i]) {
1185
                return true;
1186
            }
1187
        }
1188
        return !empty($_REQUEST['field_move_to'][$i]);
1189
    }
1190
1191
    /**
1192
     * Displays the table structure ('show table' works correct since 3.23.03)
1193
     *
1194
     * @param array       $cfgRelation               current relation parameters
1195
     * @param array       $columns_with_unique_index Columns with unique index
1196
     * @param mixed       $url_params                Contains an associative
1197
     *                                               array with url params
1198
     * @param Index|false $primary_index             primary index or false if
1199
     *                                               no one exists
1200
     * @param array       $fields                    Fields
1201
     * @param array       $columns_with_index        Columns with index
1202
     *
1203
     * @return string
1204
     */
1205
    protected function displayStructure(
1206
        array $cfgRelation,
1207
        array $columns_with_unique_index,
1208
        $url_params,
1209
        $primary_index,
1210
        array $fields,
1211
        array $columns_with_index
1212
    ) {
1213
        // prepare comments
1214
        $comments_map = [];
1215
        $mime_map = [];
1216
1217
        if ($GLOBALS['cfg']['ShowPropertyComments']) {
1218
            $comments_map = $this->relation->getComments($this->db, $this->table);
1219
            if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) {
1220
                $mime_map = $this->transformations->getMime($this->db, $this->table, true);
1221
            }
1222
        }
1223
        $centralColumns = new CentralColumns($this->dbi);
1224
        $central_list = $centralColumns->getFromTable(
1225
            $this->db,
1226
            $this->table
1227
        );
1228
        $columns_list = [];
1229
1230
        $titles = [
1231
            'Change' => Util::getIcon('b_edit', __('Change')),
1232
            'Drop' => Util::getIcon('b_drop', __('Drop')),
1233
            'NoDrop' => Util::getIcon('b_drop', __('Drop')),
1234
            'Primary' => Util::getIcon('b_primary', __('Primary')),
1235
            'Index' => Util::getIcon('b_index', __('Index')),
1236
            'Unique' => Util::getIcon('b_unique', __('Unique')),
1237
            'Spatial' => Util::getIcon('b_spatial', __('Spatial')),
1238
            'IdxFulltext' => Util::getIcon('b_ftext', __('Fulltext')),
1239
            'NoPrimary' => Util::getIcon('bd_primary', __('Primary')),
1240
            'NoIndex' => Util::getIcon('bd_index', __('Index')),
1241
            'NoUnique' => Util::getIcon('bd_unique', __('Unique')),
1242
            'NoSpatial' => Util::getIcon('bd_spatial', __('Spatial')),
1243
            'NoIdxFulltext' => Util::getIcon('bd_ftext', __('Fulltext')),
1244
            'DistinctValues' => Util::getIcon('b_browse', __('Distinct values')),
1245
        ];
1246
1247
        /**
1248
         * Work on the table
1249
         */
1250
        if ($this->_tbl_is_view && ! $this->_db_is_system_schema) {
1251
            $item = $this->dbi->fetchSingleRow(
1252
                sprintf(
1253
                    "SELECT `VIEW_DEFINITION`, `CHECK_OPTION`, `DEFINER`,
1254
                      `SECURITY_TYPE`
1255
                    FROM `INFORMATION_SCHEMA`.`VIEWS`
1256
                    WHERE TABLE_SCHEMA='%s'
1257
                    AND TABLE_NAME='%s';",
1258
                    $this->dbi->escapeString($this->db),
1259
                    $this->dbi->escapeString($this->table)
1260
                )
1261
            );
1262
1263
            $createView = $this->dbi->getTable($this->db, $this->table)
1264
                ->showCreate();
1265
            // get algorithm from $createView of the form
1266
            // CREATE ALGORITHM=<ALGORITHM> DE...
1267
            $parts = explode(" ", substr($createView, 17));
1268
            $item['ALGORITHM'] = $parts[0];
1269
1270
            $view = [
1271
                'operation' => 'alter',
1272
                'definer' => $item['DEFINER'],
1273
                'sql_security' => $item['SECURITY_TYPE'],
1274
                'name' => $this->table,
1275
                'as' => $item['VIEW_DEFINITION'],
1276
                'with' => $item['CHECK_OPTION'],
1277
                'algorithm' => $item['ALGORITHM'],
1278
            ];
1279
1280
            $edit_view_url = 'view_create.php'
1281
                . Url::getCommon($url_params) . '&amp;'
1282
                . implode(
1283
                    '&amp;',
1284
                    array_map(
1285
                        function ($key, $val) {
1286
                            return 'view[' . urlencode($key) . ']=' . urlencode(
1287
                                $val
1288
                            );
1289
                        },
1290
                        array_keys($view),
1291
                        $view
1292
                    )
1293
                );
1294
        }
1295
1296
        /**
1297
         * Displays Space usage and row statistics
1298
         */
1299
        // BEGIN - Calc Table Space
1300
        // Get valid statistics whatever is the table type
1301
        if ($GLOBALS['cfg']['ShowStats']) {
1302
            //get table stats in HTML format
1303
            $tablestats = $this->getTableStats();
1304
            //returning the response in JSON format to be used by Ajax
1305
            $this->response->addJSON('tableStat', $tablestats);
1306
        }
1307
        // END - Calc Table Space
1308
1309
        $hideStructureActions = false;
1310
        if ($GLOBALS['cfg']['HideStructureActions'] === true) {
1311
            $hideStructureActions = true;
1312
        }
1313
1314
        // logic removed from Template
1315
        $rownum = 0;
1316
        $columns_list = [];
1317
        $attributes = [];
1318
        $displayed_fields = [];
1319
        $row_comments = [];
1320
        $extracted_columnspecs = [];
1321
        foreach ($fields as &$field) {
1322
            $rownum += 1;
1323
            $columns_list[] = $field['Field'];
1324
1325
            $extracted_columnspecs[$rownum] = Util::extractColumnSpec($field['Type']);
1326
            $attributes[$rownum] = $extracted_columnspecs[$rownum]['attribute'];
1327
            if (strpos($field['Extra'], 'on update CURRENT_TIMESTAMP') !== false) {
1328
                $attributes[$rownum] = 'on update CURRENT_TIMESTAMP';
1329
            }
1330
1331
            $displayed_fields[$rownum] = new \stdClass();
1332
            $displayed_fields[$rownum]->text = $field['Field'];
1333
            $displayed_fields[$rownum]->icon = "";
1334
            $row_comments[$rownum] = '';
1335
1336
            if (isset($comments_map[$field['Field']])) {
1337
                $displayed_fields[$rownum]->comment = $comments_map[$field['Field']];
1338
                $row_comments[$rownum] = $comments_map[$field['Field']];
1339
            }
1340
1341
            if ($primary_index && $primary_index->hasColumn($field['Field'])) {
1342
                $displayed_fields[$rownum]->icon .=
1343
                Util::getImage('b_primary', __('Primary'));
1344
            }
1345
1346
            if (in_array($field['Field'], $columns_with_index)) {
1347
                $displayed_fields[$rownum]->icon .=
1348
                Util::getImage('bd_primary', __('Index'));
1349
            }
1350
        }
1351
1352
        $engine = $this->table_obj->getStorageEngine();
1353
        return $this->template->render('table/structure/display_structure', [
1354
            'url_params' => [
1355
                'db' => $this->db,
1356
                'table' => $this->table,
1357
            ],
1358
            'is_foreign_key_supported' => Util::isForeignKeySupported($engine),
1359
            'displayIndexesHtml' => Index::getHtmlForDisplayIndexes(),
1360
            'cfg_relation' => $this->relation->getRelationsParam(),
1361
            'hide_structure_actions' => $hideStructureActions,
1362
            'db' => $this->db,
1363
            'table' => $this->table,
1364
            'db_is_system_schema' => $this->_db_is_system_schema,
1365
            'tbl_is_view' => $this->_tbl_is_view,
1366
            'mime_map' => $mime_map,
1367
            'url_query' => $this->_url_query,
1368
            'titles' => $titles,
1369
            'tbl_storage_engine' => $this->_tbl_storage_engine,
1370
            'primary' => $primary_index,
1371
            'columns_with_unique_index' => $columns_with_unique_index,
1372
            'edit_view_url' => isset($edit_view_url) ? $edit_view_url : null,
1373
            'columns_list' => $columns_list,
1374
            'table_stats' => isset($tablestats) ? $tablestats : null,
1375
            'fields' => $fields,
1376
            'extracted_columnspecs' => $extracted_columnspecs,
1377
            'columns_with_index' => $columns_with_index,
1378
            'central_list' => $central_list,
1379
            'comments_map' => $comments_map,
1380
            'browse_mime' => $GLOBALS['cfg']['BrowseMIME'],
1381
            'show_column_comments' => $GLOBALS['cfg']['ShowColumnComments'],
1382
            'show_stats' => $GLOBALS['cfg']['ShowStats'],
1383
            'relation_commwork' => $GLOBALS['cfgRelation']['commwork'],
1384
            'relation_mimework' => $GLOBALS['cfgRelation']['mimework'],
1385
            'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'],
1386
            'mysql_int_version' => $this->dbi->getVersion(),
1387
            'pma_theme_image' => $GLOBALS['pmaThemeImage'],
1388
            'text_dir' => $GLOBALS['text_dir'],
1389
            'is_active' => Tracker::isActive(),
1390
            'have_partitioning' => Partition::havePartitioning(),
1391
            'partitions' => Partition::getPartitions($this->db, $this->table),
1392
            'partition_names' => Partition::getPartitionNames($this->db, $this->table),
1393
            'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
1394
            'attributes' => $attributes,
1395
            'displayed_fields' => $displayed_fields,
1396
            'row_comments' => $row_comments,
1397
        ]);
1398
    }
1399
1400
    /**
1401
     * Get HTML snippet for display table statistics
1402
     *
1403
     * @return string
1404
     */
1405
    protected function getTableStats()
1406
    {
1407
        if (empty($this->_showtable)) {
1408
            $this->_showtable = $this->dbi->getTable(
1409
                $this->db,
1410
                $this->table
1411
            )->getStatusInfo(null, true);
1412
        }
1413
1414
        if (empty($this->_showtable['Data_length'])) {
1415
            $this->_showtable['Data_length'] = 0;
1416
        }
1417
        if (empty($this->_showtable['Index_length'])) {
1418
            $this->_showtable['Index_length'] = 0;
1419
        }
1420
1421
        $is_innodb = (isset($this->_showtable['Type'])
1422
            && $this->_showtable['Type'] == 'InnoDB');
1423
1424
        $mergetable = $this->table_obj->isMerge();
1425
1426
        // this is to display for example 261.2 MiB instead of 268k KiB
1427
        $max_digits = 3;
1428
        $decimals = 1;
1429
        list($data_size, $data_unit) = Util::formatByteDown(
1430
            $this->_showtable['Data_length'],
1431
            $max_digits,
1432
            $decimals
1433
        );
1434
        if ($mergetable == false) {
1435
            list($index_size, $index_unit) = Util::formatByteDown(
1436
                $this->_showtable['Index_length'],
1437
                $max_digits,
1438
                $decimals
1439
            );
1440
        }
1441
        // InnoDB returns a huge value in Data_free, do not use it
1442
        if (! $is_innodb && isset($this->_showtable['Data_free'])
1443
            && $this->_showtable['Data_free'] > 0
1444
        ) {
1445
            list($free_size, $free_unit) = Util::formatByteDown(
1446
                $this->_showtable['Data_free'],
1447
                $max_digits,
1448
                $decimals
1449
            );
1450
            list($effect_size, $effect_unit) = Util::formatByteDown(
1451
                $this->_showtable['Data_length']
1452
                + $this->_showtable['Index_length']
1453
                - $this->_showtable['Data_free'],
1454
                $max_digits,
1455
                $decimals
1456
            );
1457
        } else {
1458
            list($effect_size, $effect_unit) = Util::formatByteDown(
1459
                $this->_showtable['Data_length']
1460
                + $this->_showtable['Index_length'],
1461
                $max_digits,
1462
                $decimals
1463
            );
1464
        }
1465
        list($tot_size, $tot_unit) = Util::formatByteDown(
1466
            $this->_showtable['Data_length'] + $this->_showtable['Index_length'],
1467
            $max_digits,
1468
            $decimals
1469
        );
1470
        if ($this->_table_info_num_rows > 0) {
1471
            list($avg_size, $avg_unit) = Util::formatByteDown(
1472
                ($this->_showtable['Data_length']
1473
                + $this->_showtable['Index_length'])
1474
                / $this->_showtable['Rows'],
1475
                6,
1476
                1
1477
            );
1478
        } else {
1479
            $avg_size = $avg_unit = '';
1480
        }
1481
1482
        $engine = $this->dbi->getTable($this->db, $this->table)->getStorageEngine();
1483
        return $this->template->render('table/structure/display_table_stats', [
1484
            'url_params' => [
1485
                'db' => $GLOBALS['db'],
1486
                'table' => $GLOBALS['table'],
1487
            ],
1488
            'is_foreign_key_supported' => Util::isForeignKeySupported($engine),
1489
            'cfg_relation' => $this->relation->getRelationsParam(),
1490
            'showtable' => $this->_showtable,
1491
            'table_info_num_rows' => $this->_table_info_num_rows,
1492
            'tbl_is_view' => $this->_tbl_is_view,
1493
            'db_is_system_schema' => $this->_db_is_system_schema,
1494
            'tbl_storage_engine' => $this->_tbl_storage_engine,
1495
            'url_query' => $this->_url_query,
1496
            'tbl_collation' => $this->_tbl_collation,
1497
            'is_innodb' => $is_innodb,
1498
            'mergetable' => $mergetable,
1499
            'avg_size' => isset($avg_size) ? $avg_size : null,
1500
            'avg_unit' => isset($avg_unit) ? $avg_unit : null,
1501
            'data_size' => $data_size,
1502
            'data_unit' => $data_unit,
1503
            'index_size' => isset($index_size) ? $index_size : null,
1504
            'index_unit' => isset($index_unit) ? $index_unit : null,
1505
            'free_size' => isset($free_size) ? $free_size : null,
1506
            'free_unit' => isset($free_unit) ? $free_unit : null,
1507
            'effect_size' => $effect_size,
1508
            'effect_unit' => $effect_unit,
1509
            'tot_size' => $tot_size,
1510
            'tot_unit' => $tot_unit,
1511
            'table' => $GLOBALS['table'],
1512
        ]);
1513
    }
1514
1515
    /**
1516
     * Gets table primary key
1517
     *
1518
     * @return string
1519
     */
1520
    protected function getKeyForTablePrimary()
1521
    {
1522
        $this->dbi->selectDb($this->db);
1523
        $result = $this->dbi->query(
1524
            'SHOW KEYS FROM ' . Util::backquote($this->table) . ';'
1525
        );
1526
        $primary = '';
1527
        while ($row = $this->dbi->fetchAssoc($result)) {
1528
            // Backups the list of primary keys
1529
            if ($row['Key_name'] == 'PRIMARY') {
1530
                $primary .= $row['Column_name'] . ', ';
1531
            }
1532
        } // end while
1533
        $this->dbi->freeResult($result);
1534
1535
        return $primary;
1536
    }
1537
1538
    /**
1539
     * Get List of information for Submit Mult
1540
     *
1541
     * @param string $submit_mult mult_submit type
1542
     * @param array  $selected    the selected columns
1543
     * @param string $action      action type
1544
     *
1545
     * @return array
1546
     */
1547
    protected function getDataForSubmitMult($submit_mult, $selected, $action)
1548
    {
1549
        $centralColumns = new CentralColumns($this->dbi);
1550
        $what = null;
1551
        $query_type = null;
1552
        $is_unset_submit_mult = false;
1553
        $mult_btn = null;
1554
        $centralColsError = null;
1555
        switch ($submit_mult) {
1556
            case 'drop':
1557
                $what     = 'drop_fld';
1558
                break;
1559
            case 'primary':
1560
                // Gets table primary key
1561
                $primary = $this->getKeyForTablePrimary();
1562
                if (empty($primary)) {
1563
                    // no primary key, so we can safely create new
1564
                    $is_unset_submit_mult = true;
1565
                    $query_type = 'primary_fld';
1566
                    $mult_btn   = __('Yes');
1567
                } else {
1568
                    // primary key exists, so lets as user
1569
                    $what = 'primary_fld';
1570
                }
1571
                break;
1572
            case 'index':
1573
                $is_unset_submit_mult = true;
1574
                $query_type = 'index_fld';
1575
                $mult_btn   = __('Yes');
1576
                break;
1577
            case 'unique':
1578
                $is_unset_submit_mult = true;
1579
                $query_type = 'unique_fld';
1580
                $mult_btn   = __('Yes');
1581
                break;
1582
            case 'spatial':
1583
                $is_unset_submit_mult = true;
1584
                $query_type = 'spatial_fld';
1585
                $mult_btn   = __('Yes');
1586
                break;
1587
            case 'ftext':
1588
                $is_unset_submit_mult = true;
1589
                $query_type = 'fulltext_fld';
1590
                $mult_btn   = __('Yes');
1591
                break;
1592
            case 'add_to_central_columns':
1593
                $centralColsError = $centralColumns->syncUniqueColumns(
1594
                    $selected,
1595
                    false
1596
                );
1597
                break;
1598
            case 'remove_from_central_columns':
1599
                $centralColsError = $centralColumns->deleteColumnsFromList(
1600
                    $selected,
1601
                    false
1602
                );
1603
                break;
1604
            case 'change':
1605
                $this->displayHtmlForColumnChange($selected, $action);
1606
                // execution stops here but PhpMyAdmin\Response correctly finishes
1607
                // the rendering
1608
                exit;
1609
            case 'browse':
1610
                // this should already be handled by tbl_structure.php
1611
        }
1612
1613
        return [
1614
            $what, $query_type, $is_unset_submit_mult, $mult_btn,
1615
            $centralColsError
1616
        ];
1617
    }
1618
}
1619