Passed
Push — master ( 3bb133...b97460 )
by Maurício
07:10
created

StructureController   F

Complexity

Total Complexity 182

Size/Duplication

Total Lines 1593
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 182
eloc 919
dl 0
loc 1593
rs 1.681
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A adjustColumnPrivileges() 0 36 5
F _extractPartitionDetails() 0 153 20
F indexAction() 0 232 26
A displayTableBrowseForSelectedColumns() 0 44 3
A displayHtmlForColumnChange() 0 46 4
C getDataForSubmitMult() 0 72 12
F displayStructure() 0 192 16
F updateColumns() 0 234 31
A displayHtmlForPartitionChange() 0 13 2
D getTableStats() 0 107 16
A getKeyForTablePrimary() 0 16 3
B getMultipleFieldCommandType() 0 31 7
F moveColumns() 0 114 28
A __construct() 0 32 1
B columnNeedsAlterTable() 0 33 6
A updatePartitioning() 0 22 2

How to fix   Complexity   

Complex Class

Complex classes like StructureController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StructureController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* vim: set expandtab sw=4 ts=4 sts=4: */
3
/**
4
 * Holds the PhpMyAdmin\Controllers\Table\StructureController
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\CheckUserPrivileges;
14
use PhpMyAdmin\Config\PageSettings;
15
use PhpMyAdmin\Controllers\TableController;
16
use PhpMyAdmin\Core;
17
use PhpMyAdmin\CreateAddField;
18
use PhpMyAdmin\Index;
19
use PhpMyAdmin\Message;
20
use PhpMyAdmin\ParseAnalyze;
21
use PhpMyAdmin\Partition;
22
use PhpMyAdmin\Relation;
23
use PhpMyAdmin\Sql;
24
use PhpMyAdmin\SqlParser\Context;
25
use PhpMyAdmin\SqlParser\Parser;
26
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
27
use PhpMyAdmin\Table;
28
use PhpMyAdmin\Template;
29
use PhpMyAdmin\Tracker;
30
use PhpMyAdmin\Transformations;
31
use PhpMyAdmin\Url;
32
use PhpMyAdmin\Util;
33
34
/**
35
 * Handles table structure logic
36
 *
37
 * @package PhpMyAdmin\Controllers
38
 */
39
class StructureController extends TableController
40
{
41
    /**
42
     * @var Table  The table object
43
     */
44
    protected $table_obj;
45
    /**
46
     * @var string  The URL query string
47
     */
48
    protected $_url_query;
49
    /**
50
     * @var bool DB is information_schema
51
     */
52
    protected $_db_is_system_schema;
53
    /**
54
     * @var bool Table is a view
55
     */
56
    protected $_tbl_is_view;
57
    /**
58
     * @var string Table storage engine
59
     */
60
    protected $_tbl_storage_engine;
61
    /**
62
     * @var int Number of rows
63
     */
64
    protected $_table_info_num_rows;
65
    /**
66
     * @var string Table collation
67
     */
68
    protected $_tbl_collation;
69
    /**
70
     * @var array Show table info
71
     */
72
    protected $_showtable;
73
74
    /**
75
     * @var CreateAddField
76
     */
77
    private $createAddField;
78
79
    /**
80
     * @var Relation
81
     */
82
    private $relation;
83
84
    /**
85
     * @var Transformations
86
     */
87
    private $transformations;
88
89
    /**
90
     * StructureController constructor
91
     *
92
     * @param \PhpMyAdmin\Response          $response            Response object
93
     * @param \PhpMyAdmin\DatabaseInterface $dbi                 DatabaseInterface object
94
     * @param string                        $db                  Database name
95
     * @param string                        $table               Table name
96
     * @param string                        $type                Indicate the db_structure or tbl_structure
97
     * @param int                           $num_tables          Number of tables
98
     * @param int                           $pos                 Current position in the list
99
     * @param bool                          $db_is_system_schema DB is information_schema
100
     * @param int                           $total_num_tables    Number of tables
101
     * @param array                         $tables              Tables in the DB
102
     * @param bool                          $is_show_stats       Whether stats show or not
103
     * @param bool                          $tbl_is_view         Table is a view
104
     * @param string                        $tbl_storage_engine  Table storage engine
105
     * @param int                           $table_info_num_rows Number of rows
106
     * @param string                        $tbl_collation       Table collation
107
     * @param array                         $showtable           Show table info
108
     */
109
    public function __construct(
110
        $response,
111
        $dbi,
112
        $db,
113
        $table,
114
        $type,
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

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

114
        /** @scrutinizer ignore-unused */ $type,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
115
        $num_tables,
0 ignored issues
show
Unused Code introduced by
The parameter $num_tables is not used and could be removed. ( Ignorable by Annotation )

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

115
        /** @scrutinizer ignore-unused */ $num_tables,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
116
        $pos,
0 ignored issues
show
Unused Code introduced by
The parameter $pos is not used and could be removed. ( Ignorable by Annotation )

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

116
        /** @scrutinizer ignore-unused */ $pos,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
117
        $db_is_system_schema,
118
        $total_num_tables,
0 ignored issues
show
Unused Code introduced by
The parameter $total_num_tables is not used and could be removed. ( Ignorable by Annotation )

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

118
        /** @scrutinizer ignore-unused */ $total_num_tables,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
119
        $tables,
0 ignored issues
show
Unused Code introduced by
The parameter $tables is not used and could be removed. ( Ignorable by Annotation )

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

119
        /** @scrutinizer ignore-unused */ $tables,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
120
        $is_show_stats,
0 ignored issues
show
Unused Code introduced by
The parameter $is_show_stats is not used and could be removed. ( Ignorable by Annotation )

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

120
        /** @scrutinizer ignore-unused */ $is_show_stats,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
121
        $tbl_is_view,
122
        $tbl_storage_engine,
123
        $table_info_num_rows,
124
        $tbl_collation,
125
        $showtable
126
    ) {
127
        parent::__construct($response, $dbi, $db, $table);
128
129
        $this->_db_is_system_schema = $db_is_system_schema;
130
        $this->_url_query = Url::getCommonRaw(['db' => $db, 'table' => $table]);
131
        $this->_tbl_is_view = $tbl_is_view;
132
        $this->_tbl_storage_engine = $tbl_storage_engine;
133
        $this->_table_info_num_rows = $table_info_num_rows;
134
        $this->_tbl_collation = $tbl_collation;
135
        $this->_showtable = $showtable;
136
        $this->table_obj = $this->dbi->getTable($this->db, $this->table);
137
138
        $this->createAddField = new CreateAddField($dbi);
139
        $this->relation = new Relation($dbi);
140
        $this->transformations = new Transformations();
141
    }
142
143
    /**
144
     * Index action
145
     *
146
     * @return void
147
     */
148
    public function indexAction()
149
    {
150
        PageSettings::showGroup('TableStructure');
151
152
        $checkUserPrivileges = new CheckUserPrivileges($this->dbi);
153
        $checkUserPrivileges->getPrivileges();
154
155
        $this->response->getHeader()->getScripts()->addFiles(
156
            [
157
                'tbl_structure.js',
158
                'indexes.js',
159
            ]
160
        );
161
162
        /**
163
         * Handle column moving
164
         */
165
        if (isset($_POST['move_columns'])
166
            && is_array($_POST['move_columns'])
167
            && $this->response->isAjax()
168
        ) {
169
            $this->moveColumns();
170
            return;
171
        }
172
173
        /**
174
         * handle MySQL reserved words columns check
175
         */
176
        if (isset($_POST['reserved_word_check'])) {
177
            if ($GLOBALS['cfg']['ReservedWordDisableWarning'] === false) {
178
                $columns_names = $_POST['field_name'];
179
                $reserved_keywords_names = [];
180
                foreach ($columns_names as $column) {
181
                    if (Context::isKeyword(trim($column), true)) {
182
                        $reserved_keywords_names[] = trim($column);
183
                    }
184
                }
185
                if (Context::isKeyword(trim($this->table), true)) {
186
                    $reserved_keywords_names[] = trim($this->table);
187
                }
188
                if (count($reserved_keywords_names) == 0) {
189
                    $this->response->setRequestStatus(false);
190
                }
191
                $this->response->addJSON(
192
                    'message',
193
                    sprintf(
194
                        _ngettext(
195
                            'The name \'%s\' is a MySQL reserved keyword.',
196
                            'The names \'%s\' are MySQL reserved keywords.',
197
                            count($reserved_keywords_names)
198
                        ),
199
                        implode(',', $reserved_keywords_names)
200
                    )
201
                );
202
            } else {
203
                $this->response->setRequestStatus(false);
204
            }
205
            return;
206
        }
207
        /**
208
         * A click on Change has been made for one column
209
         */
210
        if (isset($_GET['change_column'])) {
211
            $this->displayHtmlForColumnChange(null, 'tbl_structure.php');
212
            return;
213
        }
214
215
        /**
216
         * Adding or editing partitioning of the table
217
         */
218
        if (isset($_POST['edit_partitioning'])
219
            && ! isset($_POST['save_partitioning'])
220
        ) {
221
            $this->displayHtmlForPartitionChange();
222
            return;
223
        }
224
225
        /**
226
         * handle multiple field commands if required
227
         *
228
         * submit_mult_*_x comes from IE if <input type="img" ...> is used
229
         */
230
        $submit_mult = $this->getMultipleFieldCommandType();
231
232
        if (! empty($submit_mult)) {
233
            if (isset($_POST['selected_fld'])) {
234
                if ($submit_mult == 'browse') {
235
                    // browsing the table displaying only selected columns
236
                    $this->displayTableBrowseForSelectedColumns(
237
                        $GLOBALS['goto'],
238
                        $GLOBALS['pmaThemeImage']
239
                    );
240
                } else {
241
                    // handle multiple field commands
242
                    // handle confirmation of deleting multiple columns
243
                    $action = 'tbl_structure.php';
244
                    $GLOBALS['selected'] = $_POST['selected_fld'];
245
                    list(
246
                        $what_ret, $query_type_ret, $is_unset_submit_mult,
247
                        $mult_btn_ret, $centralColsError
248
                        )
249
                            = $this->getDataForSubmitMult(
250
                                $submit_mult,
251
                                $_POST['selected_fld'],
252
                                $action
253
                            );
254
                    //update the existing variables
255
                    // todo: refactor mult_submits.inc.php such as
256
                    // below globals are not needed anymore
257
                    if (isset($what_ret)) {
258
                        $GLOBALS['what'] = $what_ret;
259
                        global $what;
260
                    }
261
                    if (isset($query_type_ret)) {
262
                        $GLOBALS['query_type'] = $query_type_ret;
263
                        global $query_type;
264
                    }
265
                    if ($is_unset_submit_mult) {
266
                        unset($submit_mult);
267
                    }
268
                    if (isset($mult_btn_ret)) {
269
                        $GLOBALS['mult_btn'] = $mult_btn_ret;
270
                        global $mult_btn;
271
                    }
272
                    include ROOT_PATH . 'libraries/mult_submits.inc.php';
273
                    /**
274
                     * if $submit_mult == 'change', execution will have stopped
275
                     * at this point
276
                     */
277
                    if (empty($message)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $message seems to never exist and therefore empty should always be true.
Loading history...
278
                        $message = Message::success();
279
                    }
280
                    $this->response->addHTML(
281
                        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...
282
                    );
283
                }
284
            } else {
285
                $this->response->setRequestStatus(false);
286
                $this->response->addJSON('message', __('No column selected.'));
287
            }
288
        }
289
290
        /**
291
         * Modifications have been submitted -> updates the table
292
         */
293
        if (isset($_POST['do_save_data'])) {
294
            $regenerate = $this->updateColumns();
295
            if (! $regenerate) {
296
                // continue to show the table's structure
297
                unset($_POST['selected']);
298
            }
299
        }
300
301
        /**
302
         * Modifications to the partitioning have been submitted -> updates the table
303
         */
304
        if (isset($_POST['save_partitioning'])) {
305
            $this->updatePartitioning();
306
        }
307
308
        /**
309
         * Adding indexes
310
         */
311
        if (isset($_POST['add_key'])
312
            || isset($_POST['partition_maintenance'])
313
        ) {
314
            //todo: set some variables for sql.php include, to be eliminated
315
            //after refactoring sql.php
316
            $db = $this->db;
317
            $table = $this->table;
318
            $sql_query = $GLOBALS['sql_query'];
319
            $cfg = $GLOBALS['cfg'];
320
            $pmaThemeImage = $GLOBALS['pmaThemeImage'];
321
            include ROOT_PATH . 'sql.php';
322
            $GLOBALS['reload'] = true;
323
        }
324
325
        /**
326
         * Gets the relation settings
327
         */
328
        $cfgRelation = $this->relation->getRelationsParam();
329
330
        /**
331
         * Runs common work
332
         */
333
        // set db, table references, for require_once that follows
334
        // got to be eliminated in long run
335
        $db = &$this->db;
336
        $table = &$this->table;
337
        $url_params = [];
338
        include_once ROOT_PATH . 'libraries/tbl_common.inc.php';
339
        $this->_db_is_system_schema = $db_is_system_schema;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $db_is_system_schema seems to be never defined.
Loading history...
340
        $this->_url_query = Url::getCommonRaw([
341
            'db' => $db,
342
            'table' => $table,
343
            'goto' => 'tbl_structure.php',
344
            'back' => 'tbl_structure.php',
345
        ]);
346
        /* The url_params array is initialized in above include */
347
        $url_params['goto'] = 'tbl_structure.php';
348
        $url_params['back'] = 'tbl_structure.php';
349
350
        // 2. Gets table keys and retains them
351
        // @todo should be: $server->db($db)->table($table)->primary()
352
        $primary = Index::getPrimary($this->table, $this->db);
353
        $columns_with_index = $this->dbi
354
            ->getTable($this->db, $this->table)
355
            ->getColumnsWithIndex(
356
                Index::UNIQUE | Index::INDEX | Index::SPATIAL
357
                | Index::FULLTEXT
358
            );
359
        $columns_with_unique_index = $this->dbi
360
            ->getTable($this->db, $this->table)
361
            ->getColumnsWithIndex(Index::UNIQUE);
362
363
        // 3. Get fields
364
        $fields = (array) $this->dbi->getColumns(
365
            $this->db,
366
            $this->table,
367
            null,
368
            true
369
        );
370
371
        //display table structure
372
        $this->response->addHTML(
373
            $this->displayStructure(
374
                $cfgRelation,
375
                $columns_with_unique_index,
376
                $url_params,
377
                $primary,
378
                $fields,
379
                $columns_with_index
380
            )
381
        );
382
    }
383
384
    /**
385
     * Moves columns in the table's structure based on $_REQUEST
386
     *
387
     * @return void
388
     */
389
    protected function moveColumns()
390
    {
391
        $this->dbi->selectDb($this->db);
392
393
        /*
394
         * load the definitions for all columns
395
         */
396
        $columns = $this->dbi->getColumnsFull($this->db, $this->table);
397
        $column_names = array_keys($columns);
398
        $changes = [];
399
400
        // move columns from first to last
401
        for ($i = 0, $l = count($_POST['move_columns']); $i < $l; $i++) {
402
            $column = $_POST['move_columns'][$i];
403
            // is this column already correctly placed?
404
            if ($column_names[$i] == $column) {
405
                continue;
406
            }
407
408
            // it is not, let's move it to index $i
409
            $data = $columns[$column];
410
            $extracted_columnspec = Util::extractColumnSpec($data['Type']);
411
            if (isset($data['Extra'])
412
                && $data['Extra'] == 'on update CURRENT_TIMESTAMP'
413
            ) {
414
                $extracted_columnspec['attribute'] = $data['Extra'];
415
                unset($data['Extra']);
416
            }
417
            $current_timestamp = ($data['Type'] == 'timestamp'
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $current_timestamp = ($d... 'current_timestamp()'), Probably Intended Meaning: $current_timestamp = $da... 'current_timestamp()')
Loading history...
418
                    || $data['Type'] == 'datetime')
419
                && ($data['Default'] == 'CURRENT_TIMESTAMP'
420
                    || $data['Default'] == 'current_timestamp()');
421
422
            if ($data['Null'] === 'YES' && $data['Default'] === null) {
423
                $default_type = 'NULL';
424
            } elseif ($current_timestamp) {
425
                $default_type = 'CURRENT_TIMESTAMP';
426
            } elseif ($data['Default'] === null) {
427
                $default_type = 'NONE';
428
            } else {
429
                $default_type = 'USER_DEFINED';
430
            }
431
432
            $virtual = [
433
                'VIRTUAL',
434
                'PERSISTENT',
435
                'VIRTUAL GENERATED',
436
                'STORED GENERATED',
437
            ];
438
            $data['Virtuality'] = '';
439
            $data['Expression'] = '';
440
            if (isset($data['Extra']) && in_array($data['Extra'], $virtual)) {
441
                $data['Virtuality'] = str_replace(' GENERATED', '', $data['Extra']);
442
                $expressions = $this->table->getColumnGenerationExpression($column);
443
                $data['Expression'] = $expressions[$column];
444
            }
445
446
            $changes[] = 'CHANGE ' . Table::generateAlter(
447
                $column,
448
                $column,
449
                mb_strtoupper($extracted_columnspec['type']),
450
                $extracted_columnspec['spec_in_brackets'],
451
                $extracted_columnspec['attribute'],
452
                isset($data['Collation']) ? $data['Collation'] : '',
453
                $data['Null'] === 'YES' ? 'NULL' : 'NOT NULL',
454
                $default_type,
455
                $current_timestamp ? '' : $data['Default'],
456
                isset($data['Extra']) && $data['Extra'] !== '' ? $data['Extra']
0 ignored issues
show
Bug introduced by
It seems like IssetNode && $data['Extr... $data['Extra'] : false can also be of type false; however, parameter $extra of PhpMyAdmin\Table::generateAlter() 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

456
                /** @scrutinizer ignore-type */ isset($data['Extra']) && $data['Extra'] !== '' ? $data['Extra']
Loading history...
457
                : false,
458
                isset($data['COLUMN_COMMENT']) && $data['COLUMN_COMMENT'] !== ''
0 ignored issues
show
Bug introduced by
It seems like IssetNode && $data['COLU...OLUMN_COMMENT'] : false can also be of type false; however, parameter $comment of PhpMyAdmin\Table::generateAlter() 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

458
                /** @scrutinizer ignore-type */ isset($data['COLUMN_COMMENT']) && $data['COLUMN_COMMENT'] !== ''
Loading history...
459
                ? $data['COLUMN_COMMENT'] : false,
460
                $data['Virtuality'],
461
                $data['Expression'],
462
                $i === 0 ? '-first' : $column_names[$i - 1]
463
            );
464
            // update current column_names array, first delete old position
465
            for ($j = 0, $ll = count($column_names); $j < $ll; $j++) {
466
                if ($column_names[$j] == $column) {
467
                    unset($column_names[$j]);
468
                }
469
            }
470
            // insert moved column
471
            array_splice($column_names, $i, 0, $column);
472
        }
473
        if (empty($changes) && ! isset($_REQUEST['preview_sql'])) { // should never happen
474
            $this->response->setRequestStatus(false);
475
            return;
476
        }
477
        // query for moving the columns
478
        $sql_query = sprintf(
479
            'ALTER TABLE %s %s',
480
            Util::backquote($this->table),
481
            implode(', ', $changes)
482
        );
483
484
        if (isset($_REQUEST['preview_sql'])) { // preview sql
485
            $this->response->addJSON(
486
                'sql_data',
487
                $this->template->render('preview_sql', [
488
                    'query_data' => $sql_query,
489
                ])
490
            );
491
        } else { // move column
492
            $this->dbi->tryQuery($sql_query);
493
            $tmp_error = $this->dbi->getError();
494
            if ($tmp_error) {
495
                $this->response->setRequestStatus(false);
496
                $this->response->addJSON('message', Message::error($tmp_error));
0 ignored issues
show
Bug introduced by
It seems like $tmp_error can also be of type true; however, parameter $string of PhpMyAdmin\Message::error() 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

496
                $this->response->addJSON('message', Message::error(/** @scrutinizer ignore-type */ $tmp_error));
Loading history...
497
            } else {
498
                $message = Message::success(
499
                    __('The columns have been moved successfully.')
500
                );
501
                $this->response->addJSON('message', $message);
502
                $this->response->addJSON('columns', $column_names);
503
            }
504
        }
505
    }
506
507
    /**
508
     * Displays HTML for changing one or more columns
509
     *
510
     * @param array  $selected the selected columns
511
     * @param string $action   target script to call
512
     *
513
     * @return void
514
     *
515
     */
516
    protected function displayHtmlForColumnChange($selected, $action)
517
    {
518
        // $selected comes from mult_submits.inc.php
519
        if (empty($selected)) {
520
            $selected[] = $_REQUEST['field'];
521
            $selected_cnt = 1;
522
        } else { // from a multiple submit
523
            $selected_cnt = count($selected);
524
        }
525
526
        /**
527
         * @todo optimize in case of multiple fields to modify
528
         */
529
        $fields_meta = [];
530
        for ($i = 0; $i < $selected_cnt; $i++) {
531
            $value = $this->dbi->getColumns(
532
                $this->db,
533
                $this->table,
534
                $selected[$i],
535
                true
536
            );
537
            if (count($value) == 0) {
538
                $message = Message::error(
539
                    __('Failed to get description of column %s!')
540
                );
541
                $message->addParam($selected[$i]);
542
                $this->response->addHTML($message);
543
            } else {
544
                $fields_meta[] = $value;
545
            }
546
        }
547
        $num_fields = count($fields_meta);
548
        // set these globals because tbl_columns_definition_form.inc.php
549
        // verifies them
550
        // @todo: refactor tbl_columns_definition_form.inc.php so that it uses
551
        // protected function params
552
        $GLOBALS['action'] = $action;
553
        $GLOBALS['num_fields'] = $num_fields;
554
555
        /**
556
         * Form for changing properties.
557
         */
558
        $checkUserPrivileges = new CheckUserPrivileges($this->dbi);
559
        $checkUserPrivileges->getPrivileges();
560
561
        include ROOT_PATH . 'libraries/tbl_columns_definition_form.inc.php';
562
    }
563
564
    /**
565
     * Displays HTML for partition change
566
     *
567
     * @return void
568
     */
569
    protected function displayHtmlForPartitionChange()
570
    {
571
        $partitionDetails = null;
572
        if (! isset($_POST['partition_by'])) {
573
            $partitionDetails = $this->_extractPartitionDetails();
574
        }
575
576
        include ROOT_PATH . 'libraries/tbl_partition_definition.inc.php';
577
        $this->response->addHTML(
578
            $this->template->render('table/structure/partition_definition_form', [
579
                'db' => $this->db,
580
                'table' => $this->table,
581
                'partition_details' => $partitionDetails,
582
            ])
583
        );
584
    }
585
586
    /**
587
     * Extracts partition details from CREATE TABLE statement
588
     *
589
     * @return array[] array of partition details
590
     */
591
    private function _extractPartitionDetails()
592
    {
593
        $createTable = (new Table($this->table, $this->db))->showCreate();
594
        if (! $createTable) {
595
            return null;
596
        }
597
598
        $parser = new Parser($createTable);
599
        /**
600
         * @var CreateStatement $stmt
601
         */
602
        $stmt = $parser->statements[0];
603
604
        $partitionDetails = [];
605
606
        $partitionDetails['partition_by'] = '';
607
        $partitionDetails['partition_expr'] = '';
608
        $partitionDetails['partition_count'] = '';
609
610
        if (! empty($stmt->partitionBy)) {
611
            $openPos = strpos($stmt->partitionBy, "(");
612
            $closePos = strrpos($stmt->partitionBy, ")");
613
614
            $partitionDetails['partition_by']
615
                = trim(substr($stmt->partitionBy, 0, $openPos));
616
            $partitionDetails['partition_expr']
617
                = trim(substr($stmt->partitionBy, $openPos + 1, $closePos - ($openPos + 1)));
618
            if (isset($stmt->partitionsNum)) {
619
                $count = $stmt->partitionsNum;
620
            } else {
621
                $count = count($stmt->partitions);
622
            }
623
            $partitionDetails['partition_count'] = $count;
624
        }
625
626
        $partitionDetails['subpartition_by'] = '';
627
        $partitionDetails['subpartition_expr'] = '';
628
        $partitionDetails['subpartition_count'] = '';
629
630
        if (! empty($stmt->subpartitionBy)) {
631
            $openPos = strpos($stmt->subpartitionBy, "(");
632
            $closePos = strrpos($stmt->subpartitionBy, ")");
633
634
            $partitionDetails['subpartition_by']
635
                = trim(substr($stmt->subpartitionBy, 0, $openPos));
636
            $partitionDetails['subpartition_expr']
637
                = trim(substr($stmt->subpartitionBy, $openPos + 1, $closePos - ($openPos + 1)));
638
            if (isset($stmt->subpartitionsNum)) {
639
                $count = $stmt->subpartitionsNum;
640
            } else {
641
                $count = count($stmt->partitions[0]->subpartitions);
642
            }
643
            $partitionDetails['subpartition_count'] = $count;
644
        }
645
646
        // Only LIST and RANGE type parameters allow subpartitioning
647
        $partitionDetails['can_have_subpartitions']
648
            = $partitionDetails['partition_count'] > 1
649
                && ($partitionDetails['partition_by'] == 'RANGE'
650
                || $partitionDetails['partition_by'] == 'RANGE COLUMNS'
651
                || $partitionDetails['partition_by'] == 'LIST'
652
                || $partitionDetails['partition_by'] == 'LIST COLUMNS');
653
654
        // Values are specified only for LIST and RANGE type partitions
655
        $partitionDetails['value_enabled'] = isset($partitionDetails['partition_by'])
656
            && ($partitionDetails['partition_by'] == 'RANGE'
657
            || $partitionDetails['partition_by'] == 'RANGE COLUMNS'
658
            || $partitionDetails['partition_by'] == 'LIST'
659
            || $partitionDetails['partition_by'] == 'LIST COLUMNS');
660
661
        $partitionDetails['partitions'] = [];
662
663
        for ($i = 0, $iMax = (int) $partitionDetails['partition_count']; $i < $iMax; $i++) {
664
            if (! isset($stmt->partitions[$i])) {
665
                $partitionDetails['partitions'][$i] = [
666
                    'name' => 'p' . $i,
667
                    'value_type' => '',
668
                    'value' => '',
669
                    'engine' => '',
670
                    'comment' => '',
671
                    'data_directory' => '',
672
                    'index_directory' => '',
673
                    'max_rows' => '',
674
                    'min_rows' => '',
675
                    'tablespace' => '',
676
                    'node_group' => '',
677
                ];
678
            } else {
679
                $p = $stmt->partitions[$i];
680
                $type = $p->type;
681
                $expr = trim((string) $p->expr, '()');
682
                if ($expr == 'MAXVALUE') {
683
                    $type .= ' MAXVALUE';
684
                    $expr = '';
685
                }
686
                $partitionDetails['partitions'][$i] = [
687
                    'name' => $p->name,
688
                    'value_type' => $type,
689
                    'value' => $expr,
690
                    'engine' => $p->options->has('ENGINE', true),
691
                    'comment' => trim((string) $p->options->has('COMMENT', true), "'"),
692
                    'data_directory' => trim((string) $p->options->has('DATA DIRECTORY', true), "'"),
693
                    'index_directory' => trim((string) $p->options->has('INDEX_DIRECTORY', true), "'"),
694
                    'max_rows' => $p->options->has('MAX_ROWS', true),
695
                    'min_rows' => $p->options->has('MIN_ROWS', true),
696
                    'tablespace' => $p->options->has('TABLESPACE', true),
697
                    'node_group' => $p->options->has('NODEGROUP', true),
698
                ];
699
            }
700
701
            $partition =& $partitionDetails['partitions'][$i];
702
            $partition['prefix'] = 'partitions[' . $i . ']';
703
704
            if ($partitionDetails['subpartition_count'] > 1) {
705
                $partition['subpartition_count'] = $partitionDetails['subpartition_count'];
706
                $partition['subpartitions'] = [];
707
708
                for ($j = 0, $jMax = (int) $partitionDetails['subpartition_count']; $j < $jMax; $j++) {
709
                    if (! isset($stmt->partitions[$i]->subpartitions[$j])) {
710
                        $partition['subpartitions'][$j] = [
711
                            'name' => $partition['name'] . '_s' . $j,
712
                            'engine' => '',
713
                            'comment' => '',
714
                            'data_directory' => '',
715
                            'index_directory' => '',
716
                            'max_rows' => '',
717
                            'min_rows' => '',
718
                            'tablespace' => '',
719
                            'node_group' => '',
720
                        ];
721
                    } else {
722
                        $sp = $stmt->partitions[$i]->subpartitions[$j];
723
                        $partition['subpartitions'][$j] = [
724
                            'name' => $sp->name,
725
                            'engine' => $sp->options->has('ENGINE', true),
726
                            'comment' => trim($sp->options->has('COMMENT', true), "'"),
727
                            'data_directory' => trim($sp->options->has('DATA DIRECTORY', true), "'"),
728
                            'index_directory' => trim($sp->options->has('INDEX_DIRECTORY', true), "'"),
729
                            'max_rows' => $sp->options->has('MAX_ROWS', true),
730
                            'min_rows' => $sp->options->has('MIN_ROWS', true),
731
                            'tablespace' => $sp->options->has('TABLESPACE', true),
732
                            'node_group' => $sp->options->has('NODEGROUP', true),
733
                        ];
734
                    }
735
736
                    $subpartition =& $partition['subpartitions'][$j];
737
                    $subpartition['prefix'] = 'partitions[' . $i . ']'
738
                        . '[subpartitions][' . $j . ']';
739
                }
740
            }
741
        }
742
743
        return $partitionDetails;
744
    }
745
746
    /**
747
     * Update the table's partitioning based on $_REQUEST
748
     *
749
     * @return void
750
     */
751
    protected function updatePartitioning()
752
    {
753
        $sql_query = "ALTER TABLE " . Util::backquote($this->table) . " "
754
            . $this->createAddField->getPartitionsDefinition();
755
756
        // Execute alter query
757
        $result = $this->dbi->tryQuery($sql_query);
758
759
        if ($result !== false) {
760
            $message = Message::success(
761
                __('Table %1$s has been altered successfully.')
762
            );
763
            $message->addParam($this->table);
764
            $this->response->addHTML(
765
                Util::getMessage($message, $sql_query, 'success')
766
            );
767
        } else {
768
            $this->response->setRequestStatus(false);
769
            $this->response->addJSON(
770
                'message',
771
                Message::rawError(
772
                    __('Query error') . ':<br>' . $this->dbi->getError()
773
                )
774
            );
775
        }
776
    }
777
778
    /**
779
     * Function to get the type of command for multiple field handling
780
     *
781
     * @return string
782
     */
783
    protected function getMultipleFieldCommandType()
784
    {
785
        $types = [
786
            'change',
787
            'drop',
788
            'primary',
789
            'index',
790
            'unique',
791
            'spatial',
792
            'fulltext',
793
            'browse',
794
        ];
795
796
        foreach ($types as $type) {
797
            if (isset($_POST['submit_mult_' . $type . '_x'])) {
798
                return $type;
799
            }
800
        }
801
802
        if (isset($_POST['submit_mult'])) {
803
            return $_POST['submit_mult'];
804
        } elseif (isset($_POST['mult_btn'])
805
            && $_POST['mult_btn'] == __('Yes')
806
        ) {
807
            if (isset($_POST['selected'])) {
808
                $_POST['selected_fld'] = $_POST['selected'];
809
            }
810
            return 'row_delete';
811
        }
812
813
        return null;
814
    }
815
816
    /**
817
     * Function to display table browse for selected columns
818
     *
819
     * @param string $goto          goto page url
820
     * @param string $pmaThemeImage URI of the pma theme image
821
     *
822
     * @return void
823
     */
824
    protected function displayTableBrowseForSelectedColumns($goto, $pmaThemeImage)
825
    {
826
        $GLOBALS['active_page'] = 'sql.php';
827
        $fields = [];
828
        foreach ($_POST['selected_fld'] as $sval) {
829
            $fields[] = Util::backquote($sval);
830
        }
831
        $sql_query = sprintf(
832
            'SELECT %s FROM %s.%s',
833
            implode(', ', $fields),
834
            Util::backquote($this->db),
835
            Util::backquote($this->table)
836
        );
837
838
        // Parse and analyze the query
839
        $db = &$this->db;
840
        list(
841
            $analyzed_sql_results,
842
            $db,
843
        ) = ParseAnalyze::sqlQuery($sql_query, $db);
844
        // @todo: possibly refactor
845
        extract($analyzed_sql_results);
846
847
        $sql = new Sql();
848
        $this->response->addHTML(
849
            $sql->executeQueryAndGetQueryResponse(
850
                isset($analyzed_sql_results) ? $analyzed_sql_results : '',
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? $analyzed_sql_results : '' can also be of type string; however, parameter $analyzed_sql_results of PhpMyAdmin\Sql::executeQueryAndGetQueryResponse() does only seem to accept array, 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

850
                /** @scrutinizer ignore-type */ isset($analyzed_sql_results) ? $analyzed_sql_results : '',
Loading history...
851
                false, // is_gotofile
852
                $this->db, // db
853
                $this->table, // table
854
                null, // find_real_end
855
                null, // sql_query_for_bookmark
856
                null, // extra_data
857
                null, // message_to_show
858
                null, // message
859
                null, // sql_data
860
                $goto, // goto
861
                $pmaThemeImage, // pmaThemeImage
862
                null, // disp_query
863
                null, // disp_message
864
                null, // query_type
865
                $sql_query, // sql_query
866
                null, // selectedTables
867
                null // complete_query
868
            )
869
        );
870
    }
871
872
    /**
873
     * Update the table's structure based on $_REQUEST
874
     *
875
     * @return boolean              true if error occurred
876
     *
877
     */
878
    protected function updateColumns()
879
    {
880
        $err_url = 'tbl_structure.php' . Url::getCommon(
881
            [
882
                'db' => $this->db,
883
                'table' => $this->table,
884
            ]
885
        );
886
        $regenerate = false;
887
        $field_cnt = count($_POST['field_name']);
888
        $changes = [];
889
        $adjust_privileges = [];
890
        $columns_with_index = $this->dbi
891
            ->getTable($this->db, $this->table)
892
            ->getColumnsWithIndex(
893
                Index::PRIMARY | Index::UNIQUE
894
            );
895
        for ($i = 0; $i < $field_cnt; $i++) {
896
            if (! $this->columnNeedsAlterTable($i)) {
897
                continue;
898
            }
899
900
            $changes[] = 'CHANGE ' . Table::generateAlter(
901
                Util::getValueByKey($_POST, "field_orig.${i}", ''),
902
                $_POST['field_name'][$i],
903
                $_POST['field_type'][$i],
904
                $_POST['field_length'][$i],
905
                $_POST['field_attribute'][$i],
906
                Util::getValueByKey($_POST, "field_collation.${i}", ''),
907
                Util::getValueByKey($_POST, "field_null.${i}", 'NOT NULL'),
908
                $_POST['field_default_type'][$i],
909
                $_POST['field_default_value'][$i],
910
                Util::getValueByKey($_POST, "field_extra.${i}", false),
0 ignored issues
show
Bug introduced by
It seems like PhpMyAdmin\Util::getValu...ield_extra.'.$i, false) can also be of type false; however, parameter $extra of PhpMyAdmin\Table::generateAlter() 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

910
                /** @scrutinizer ignore-type */ Util::getValueByKey($_POST, "field_extra.${i}", false),
Loading history...
911
                Util::getValueByKey($_POST, "field_comments.${i}", ''),
912
                Util::getValueByKey($_POST, "field_virtuality.${i}", ''),
913
                Util::getValueByKey($_POST, "field_expression.${i}", ''),
914
                Util::getValueByKey($_POST, "field_move_to.${i}", ''),
915
                $columns_with_index
916
            );
917
918
            // find the remembered sort expression
919
            $sorted_col = $this->table_obj->getUiProp(
920
                Table::PROP_SORTED_COLUMN
921
            );
922
            // if the old column name is part of the remembered sort expression
923
            if (mb_strpos(
924
                (string) $sorted_col,
925
                Util::backquote($_POST['field_orig'][$i])
0 ignored issues
show
Bug introduced by
It seems like PhpMyAdmin\Util::backquo...POST['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

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

990
                    . ' CHANGE ' . /** @scrutinizer ignore-type */ Util::backquote(
Loading history...
991
                        $_POST['field_orig'][$i]
992
                    )
993
                    . ' ' . Util::backquote($_POST['field_orig'][$i])
994
                    . ' BLOB;';
995
                    $this->dbi->query($secondary_query);
996
                    $changedToBlob[$i] = true;
997
                } else {
998
                    $changedToBlob[$i] = false;
999
                }
1000
            }
1001
1002
            // Then make the requested changes
1003
            $result = $this->dbi->tryQuery($sql_query);
1004
1005
            if ($result !== false) {
1006
                $changed_privileges = $this->adjustColumnPrivileges(
1007
                    $adjust_privileges
1008
                );
1009
1010
                if ($changed_privileges) {
1011
                    $message = Message::success(
1012
                        __(
1013
                            'Table %1$s has been altered successfully. Privileges ' .
1014
                            'have been adjusted.'
1015
                        )
1016
                    );
1017
                } else {
1018
                    $message = Message::success(
1019
                        __('Table %1$s has been altered successfully.')
1020
                    );
1021
                }
1022
                $message->addParam($this->table);
1023
1024
                $this->response->addHTML(
1025
                    Util::getMessage($message, $sql_query, 'success')
1026
                );
1027
            } else {
1028
                // An error happened while inserting/updating a table definition
1029
1030
                // Save the Original Error
1031
                $orig_error = $this->dbi->getError();
1032
                $changes_revert = [];
1033
1034
                // Change back to Original Collation and data type
1035
                for ($i = 0; $i < $field_cnt; $i++) {
1036
                    if ($changedToBlob[$i]) {
1037
                        $changes_revert[] = 'CHANGE ' . Table::generateAlter(
1038
                            Util::getValueByKey($_POST, "field_orig.${i}", ''),
1039
                            $_POST['field_name'][$i],
1040
                            $_POST['field_type_orig'][$i],
1041
                            $_POST['field_length_orig'][$i],
1042
                            $_POST['field_attribute_orig'][$i],
1043
                            Util::getValueByKey($_POST, "field_collation_orig.${i}", ''),
1044
                            Util::getValueByKey($_POST, "field_null_orig.${i}", 'NOT NULL'),
1045
                            $_POST['field_default_type_orig'][$i],
1046
                            $_POST['field_default_value_orig'][$i],
1047
                            Util::getValueByKey($_POST, "field_extra_orig.${i}", false),
1048
                            Util::getValueByKey($_POST, "field_comments_orig.${i}", ''),
1049
                            Util::getValueByKey($_POST, "field_virtuality_orig.${i}", ''),
1050
                            Util::getValueByKey($_POST, "field_expression_orig.${i}", ''),
1051
                            Util::getValueByKey($_POST, "field_move_to_orig.${i}", '')
1052
                        );
1053
                    }
1054
                }
1055
1056
                $revert_query = 'ALTER TABLE ' . Util::backquote($this->table)
1057
                    . ' ';
1058
                $revert_query .= implode(', ', $changes_revert) . '';
1059
                $revert_query .= ';';
1060
1061
                // Column reverted back to original
1062
                $this->dbi->query($revert_query);
1063
1064
                $this->response->setRequestStatus(false);
1065
                $this->response->addJSON(
1066
                    'message',
1067
                    Message::rawError(
1068
                        __('Query error') . ':<br>' . $orig_error
1069
                    )
1070
                );
1071
                $regenerate = true;
1072
            }
1073
        }
1074
1075
        // update field names in relation
1076
        if (isset($_POST['field_orig']) && is_array($_POST['field_orig'])) {
1077
            foreach ($_POST['field_orig'] as $fieldindex => $fieldcontent) {
1078
                if ($_POST['field_name'][$fieldindex] != $fieldcontent) {
1079
                    $this->relation->renameField(
1080
                        $this->db,
1081
                        $this->table,
1082
                        $fieldcontent,
1083
                        $_POST['field_name'][$fieldindex]
1084
                    );
1085
                }
1086
            }
1087
        }
1088
1089
        // update mime types
1090
        if (isset($_POST['field_mimetype'])
1091
            && is_array($_POST['field_mimetype'])
1092
            && $GLOBALS['cfg']['BrowseMIME']
1093
        ) {
1094
            foreach ($_POST['field_mimetype'] as $fieldindex => $mimetype) {
1095
                if (isset($_POST['field_name'][$fieldindex])
1096
                    && strlen($_POST['field_name'][$fieldindex]) > 0
1097
                ) {
1098
                    $this->transformations->setMime(
1099
                        $this->db,
1100
                        $this->table,
1101
                        $_POST['field_name'][$fieldindex],
1102
                        $mimetype,
1103
                        $_POST['field_transformation'][$fieldindex],
1104
                        $_POST['field_transformation_options'][$fieldindex],
1105
                        $_POST['field_input_transformation'][$fieldindex],
1106
                        $_POST['field_input_transformation_options'][$fieldindex]
1107
                    );
1108
                }
1109
            }
1110
        }
1111
        return $regenerate;
1112
    }
1113
1114
    /**
1115
     * Adjusts the Privileges for all the columns whose names have changed
1116
     *
1117
     * @param array $adjust_privileges assoc array of old col names mapped to new
1118
     *                                 cols
1119
     *
1120
     * @return boolean  boolean whether at least one column privileges
1121
     * adjusted
1122
     */
1123
    protected function adjustColumnPrivileges(array $adjust_privileges)
1124
    {
1125
        $changed = false;
1126
1127
        if (Util::getValueByKey($GLOBALS, 'col_priv', false)
1128
            && Util::getValueByKey($GLOBALS, 'is_reload_priv', false)
1129
        ) {
1130
            $this->dbi->selectDb('mysql');
1131
1132
            // For Column specific privileges
1133
            foreach ($adjust_privileges as $oldCol => $newCol) {
1134
                $this->dbi->query(
1135
                    sprintf(
1136
                        'UPDATE %s SET Column_name = "%s"
1137
                        WHERE Db = "%s"
1138
                        AND Table_name = "%s"
1139
                        AND Column_name = "%s";',
1140
                        Util::backquote('columns_priv'),
1141
                        $newCol,
1142
                        $this->db,
1143
                        $this->table,
1144
                        $oldCol
1145
                    )
1146
                );
1147
1148
                // i.e. if atleast one column privileges adjusted
1149
                $changed = true;
1150
            }
1151
1152
            if ($changed) {
1153
                // Finally FLUSH the new privileges
1154
                $this->dbi->query("FLUSH PRIVILEGES;");
1155
            }
1156
        }
1157
1158
        return $changed;
1159
    }
1160
1161
    /**
1162
     * Verifies if some elements of a column have changed
1163
     *
1164
     * @param integer $i column index in the request
1165
     *
1166
     * @return boolean true if we need to generate ALTER TABLE
1167
     *
1168
     */
1169
    protected function columnNeedsAlterTable($i)
1170
    {
1171
        // these two fields are checkboxes so might not be part of the
1172
        // request; therefore we define them to avoid notices below
1173
        if (! isset($_POST['field_null'][$i])) {
1174
            $_POST['field_null'][$i] = 'NO';
1175
        }
1176
        if (! isset($_POST['field_extra'][$i])) {
1177
            $_POST['field_extra'][$i] = '';
1178
        }
1179
1180
        // field_name does not follow the convention (corresponds to field_orig)
1181
        if ($_POST['field_name'][$i] != $_POST['field_orig'][$i]) {
1182
            return true;
1183
        }
1184
1185
        $fields = [
1186
            'field_attribute',
1187
            'field_collation',
1188
            'field_comments',
1189
            'field_default_value',
1190
            'field_default_type',
1191
            'field_extra',
1192
            'field_length',
1193
            'field_null',
1194
            'field_type',
1195
        ];
1196
        foreach ($fields as $field) {
1197
            if ($_POST[$field][$i] != $_POST[$field . '_orig'][$i]) {
1198
                return true;
1199
            }
1200
        }
1201
        return ! empty($_POST['field_move_to'][$i]);
1202
    }
1203
1204
    /**
1205
     * Displays the table structure ('show table' works correct since 3.23.03)
1206
     *
1207
     * @param array       $cfgRelation               current relation parameters
1208
     * @param array       $columns_with_unique_index Columns with unique index
1209
     * @param mixed       $url_params                Contains an associative
1210
     *                                               array with url params
1211
     * @param Index|false $primary_index             primary index or false if
1212
     *                                               no one exists
1213
     * @param array       $fields                    Fields
1214
     * @param array       $columns_with_index        Columns with index
1215
     *
1216
     * @return string
1217
     */
1218
    protected function displayStructure(
1219
        array $cfgRelation,
1220
        array $columns_with_unique_index,
1221
        $url_params,
1222
        $primary_index,
1223
        array $fields,
1224
        array $columns_with_index
1225
    ) {
1226
        // prepare comments
1227
        $comments_map = [];
1228
        $mime_map = [];
1229
1230
        if ($GLOBALS['cfg']['ShowPropertyComments']) {
1231
            $comments_map = $this->relation->getComments($this->db, $this->table);
1232
            if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) {
1233
                $mime_map = $this->transformations->getMime($this->db, $this->table, true);
1234
            }
1235
        }
1236
        $centralColumns = new CentralColumns($this->dbi);
1237
        $central_list = $centralColumns->getFromTable(
1238
            $this->db,
1239
            $this->table
1240
        );
1241
        $columns_list = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $columns_list is dead and can be removed.
Loading history...
1242
1243
        $titles = [
1244
            'Change' => Util::getIcon('b_edit', __('Change')),
1245
            'Drop' => Util::getIcon('b_drop', __('Drop')),
1246
            'NoDrop' => Util::getIcon('b_drop', __('Drop')),
1247
            'Primary' => Util::getIcon('b_primary', __('Primary')),
1248
            'Index' => Util::getIcon('b_index', __('Index')),
1249
            'Unique' => Util::getIcon('b_unique', __('Unique')),
1250
            'Spatial' => Util::getIcon('b_spatial', __('Spatial')),
1251
            'IdxFulltext' => Util::getIcon('b_ftext', __('Fulltext')),
1252
            'NoPrimary' => Util::getIcon('bd_primary', __('Primary')),
1253
            'NoIndex' => Util::getIcon('bd_index', __('Index')),
1254
            'NoUnique' => Util::getIcon('bd_unique', __('Unique')),
1255
            'NoSpatial' => Util::getIcon('bd_spatial', __('Spatial')),
1256
            'NoIdxFulltext' => Util::getIcon('bd_ftext', __('Fulltext')),
1257
            'DistinctValues' => Util::getIcon('b_browse', __('Distinct values')),
1258
        ];
1259
1260
        /**
1261
         * Work on the table
1262
         */
1263
        if ($this->_tbl_is_view && ! $this->_db_is_system_schema) {
1264
            $item = $this->dbi->fetchSingleRow(
1265
                sprintf(
1266
                    "SELECT `VIEW_DEFINITION`, `CHECK_OPTION`, `DEFINER`,
1267
                      `SECURITY_TYPE`
1268
                    FROM `INFORMATION_SCHEMA`.`VIEWS`
1269
                    WHERE TABLE_SCHEMA='%s'
1270
                    AND TABLE_NAME='%s';",
1271
                    $this->dbi->escapeString($this->db),
1272
                    $this->dbi->escapeString($this->table)
1273
                )
1274
            );
1275
1276
            $createView = $this->dbi->getTable($this->db, $this->table)
1277
                ->showCreate();
1278
            // get algorithm from $createView of the form
1279
            // CREATE ALGORITHM=<ALGORITHM> DE...
1280
            $parts = explode(" ", substr($createView, 17));
0 ignored issues
show
Bug introduced by
It seems like $createView can also be of type false; however, parameter $string of substr() 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

1280
            $parts = explode(" ", substr(/** @scrutinizer ignore-type */ $createView, 17));
Loading history...
1281
            $item['ALGORITHM'] = $parts[0];
1282
1283
            $view = [
1284
                'operation' => 'alter',
1285
                'definer' => $item['DEFINER'],
1286
                'sql_security' => $item['SECURITY_TYPE'],
1287
                'name' => $this->table,
1288
                'as' => $item['VIEW_DEFINITION'],
1289
                'with' => $item['CHECK_OPTION'],
1290
                'algorithm' => $item['ALGORITHM'],
1291
            ];
1292
1293
            $edit_view_url = 'view_create.php'
1294
                . Url::getCommon($url_params) . '&amp;'
1295
                . implode(
1296
                    '&amp;',
1297
                    array_map(
1298
                        function ($key, $val) {
1299
                            return 'view[' . urlencode($key) . ']=' . urlencode(
1300
                                $val
1301
                            );
1302
                        },
1303
                        array_keys($view),
1304
                        $view
1305
                    )
1306
                );
1307
        }
1308
1309
        /**
1310
         * Displays Space usage and row statistics
1311
         */
1312
        // BEGIN - Calc Table Space
1313
        // Get valid statistics whatever is the table type
1314
        if ($GLOBALS['cfg']['ShowStats']) {
1315
            //get table stats in HTML format
1316
            $tablestats = $this->getTableStats();
1317
            //returning the response in JSON format to be used by Ajax
1318
            $this->response->addJSON('tableStat', $tablestats);
1319
        }
1320
        // END - Calc Table Space
1321
1322
        $hideStructureActions = false;
1323
        if ($GLOBALS['cfg']['HideStructureActions'] === true) {
1324
            $hideStructureActions = true;
1325
        }
1326
1327
        // logic removed from Template
1328
        $rownum = 0;
1329
        $columns_list = [];
1330
        $attributes = [];
1331
        $displayed_fields = [];
1332
        $row_comments = [];
1333
        $extracted_columnspecs = [];
1334
        foreach ($fields as &$field) {
1335
            $rownum += 1;
1336
            $columns_list[] = $field['Field'];
1337
1338
            $extracted_columnspecs[$rownum] = Util::extractColumnSpec($field['Type']);
1339
            $attributes[$rownum] = $extracted_columnspecs[$rownum]['attribute'];
1340
            if (strpos($field['Extra'], 'on update CURRENT_TIMESTAMP') !== false) {
1341
                $attributes[$rownum] = 'on update CURRENT_TIMESTAMP';
1342
            }
1343
1344
            $displayed_fields[$rownum] = new \stdClass();
1345
            $displayed_fields[$rownum]->text = $field['Field'];
1346
            $displayed_fields[$rownum]->icon = "";
1347
            $row_comments[$rownum] = '';
1348
1349
            if (isset($comments_map[$field['Field']])) {
1350
                $displayed_fields[$rownum]->comment = $comments_map[$field['Field']];
1351
                $row_comments[$rownum] = $comments_map[$field['Field']];
1352
            }
1353
1354
            if ($primary_index && $primary_index->hasColumn($field['Field'])) {
1355
                $displayed_fields[$rownum]->icon .=
1356
                Util::getImage('b_primary', __('Primary'));
1357
            }
1358
1359
            if (in_array($field['Field'], $columns_with_index)) {
1360
                $displayed_fields[$rownum]->icon .=
1361
                Util::getImage('bd_primary', __('Index'));
1362
            }
1363
        }
1364
1365
        $engine = $this->table_obj->getStorageEngine();
1366
        return $this->template->render('table/structure/display_structure', [
1367
            'url_params' => [
1368
                'db' => $this->db,
1369
                'table' => $this->table,
1370
            ],
1371
            'is_foreign_key_supported' => Util::isForeignKeySupported($engine),
1372
            'displayIndexesHtml' => Index::getHtmlForDisplayIndexes(),
1373
            'cfg_relation' => $this->relation->getRelationsParam(),
1374
            'hide_structure_actions' => $hideStructureActions,
1375
            'db' => $this->db,
1376
            'table' => $this->table,
1377
            'db_is_system_schema' => $this->_db_is_system_schema,
1378
            'tbl_is_view' => $this->_tbl_is_view,
1379
            'mime_map' => $mime_map,
1380
            'url_query' => $this->_url_query,
1381
            'titles' => $titles,
1382
            'tbl_storage_engine' => $this->_tbl_storage_engine,
1383
            'primary' => $primary_index,
1384
            'columns_with_unique_index' => $columns_with_unique_index,
1385
            'edit_view_url' => isset($edit_view_url) ? $edit_view_url : null,
1386
            'columns_list' => $columns_list,
1387
            'table_stats' => isset($tablestats) ? $tablestats : null,
1388
            'fields' => $fields,
1389
            'extracted_columnspecs' => $extracted_columnspecs,
1390
            'columns_with_index' => $columns_with_index,
1391
            'central_list' => $central_list,
1392
            'comments_map' => $comments_map,
1393
            'browse_mime' => $GLOBALS['cfg']['BrowseMIME'],
1394
            'show_column_comments' => $GLOBALS['cfg']['ShowColumnComments'],
1395
            'show_stats' => $GLOBALS['cfg']['ShowStats'],
1396
            'relation_commwork' => $GLOBALS['cfgRelation']['commwork'],
1397
            'relation_mimework' => $GLOBALS['cfgRelation']['mimework'],
1398
            'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'],
1399
            'mysql_int_version' => $this->dbi->getVersion(),
1400
            'pma_theme_image' => $GLOBALS['pmaThemeImage'],
1401
            'text_dir' => $GLOBALS['text_dir'],
1402
            'is_active' => Tracker::isActive(),
1403
            'have_partitioning' => Partition::havePartitioning(),
1404
            'partitions' => Partition::getPartitions($this->db, $this->table),
1405
            'partition_names' => Partition::getPartitionNames($this->db, $this->table),
1406
            'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
1407
            'attributes' => $attributes,
1408
            'displayed_fields' => $displayed_fields,
1409
            'row_comments' => $row_comments,
1410
        ]);
1411
    }
1412
1413
    /**
1414
     * Get HTML snippet for display table statistics
1415
     *
1416
     * @return string
1417
     */
1418
    protected function getTableStats()
1419
    {
1420
        if (empty($this->_showtable)) {
1421
            $this->_showtable = $this->dbi->getTable(
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->dbi->getTable($th...tStatusInfo(null, true) can also be of type string. However, the property $_showtable is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1422
                $this->db,
1423
                $this->table
1424
            )->getStatusInfo(null, true);
1425
        }
1426
1427
        if (empty($this->_showtable['Data_length'])) {
1428
            $this->_showtable['Data_length'] = 0;
1429
        }
1430
        if (empty($this->_showtable['Index_length'])) {
1431
            $this->_showtable['Index_length'] = 0;
1432
        }
1433
1434
        $is_innodb = (isset($this->_showtable['Type'])
1435
            && $this->_showtable['Type'] == 'InnoDB');
1436
1437
        $mergetable = $this->table_obj->isMerge();
1438
1439
        // this is to display for example 261.2 MiB instead of 268k KiB
1440
        $max_digits = 3;
1441
        $decimals = 1;
1442
        list($data_size, $data_unit) = Util::formatByteDown(
1443
            $this->_showtable['Data_length'],
1444
            $max_digits,
1445
            $decimals
1446
        );
1447
        if ($mergetable == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1448
            list($index_size, $index_unit) = Util::formatByteDown(
1449
                $this->_showtable['Index_length'],
1450
                $max_digits,
1451
                $decimals
1452
            );
1453
        }
1454
        // InnoDB returns a huge value in Data_free, do not use it
1455
        if (! $is_innodb && isset($this->_showtable['Data_free'])
1456
            && $this->_showtable['Data_free'] > 0
1457
        ) {
1458
            list($free_size, $free_unit) = Util::formatByteDown(
1459
                $this->_showtable['Data_free'],
1460
                $max_digits,
1461
                $decimals
1462
            );
1463
            list($effect_size, $effect_unit) = Util::formatByteDown(
1464
                $this->_showtable['Data_length']
1465
                + $this->_showtable['Index_length']
1466
                - $this->_showtable['Data_free'],
1467
                $max_digits,
1468
                $decimals
1469
            );
1470
        } else {
1471
            list($effect_size, $effect_unit) = Util::formatByteDown(
1472
                $this->_showtable['Data_length']
1473
                + $this->_showtable['Index_length'],
1474
                $max_digits,
1475
                $decimals
1476
            );
1477
        }
1478
        list($tot_size, $tot_unit) = Util::formatByteDown(
1479
            $this->_showtable['Data_length'] + $this->_showtable['Index_length'],
1480
            $max_digits,
1481
            $decimals
1482
        );
1483
        if ($this->_table_info_num_rows > 0) {
1484
            list($avg_size, $avg_unit) = Util::formatByteDown(
1485
                ($this->_showtable['Data_length']
1486
                + $this->_showtable['Index_length'])
1487
                / $this->_showtable['Rows'],
1488
                6,
1489
                1
1490
            );
1491
        } else {
1492
            $avg_size = $avg_unit = '';
1493
        }
1494
1495
        $engine = $this->dbi->getTable($this->db, $this->table)->getStorageEngine();
1496
        return $this->template->render('table/structure/display_table_stats', [
1497
            'url_params' => [
1498
                'db' => $GLOBALS['db'],
1499
                'table' => $GLOBALS['table'],
1500
            ],
1501
            'is_foreign_key_supported' => Util::isForeignKeySupported($engine),
1502
            'cfg_relation' => $this->relation->getRelationsParam(),
1503
            'showtable' => $this->_showtable,
1504
            'table_info_num_rows' => $this->_table_info_num_rows,
1505
            'tbl_is_view' => $this->_tbl_is_view,
1506
            'db_is_system_schema' => $this->_db_is_system_schema,
1507
            'tbl_storage_engine' => $this->_tbl_storage_engine,
1508
            'url_query' => $this->_url_query,
1509
            'tbl_collation' => $this->_tbl_collation,
1510
            'is_innodb' => $is_innodb,
1511
            'mergetable' => $mergetable,
1512
            'avg_size' => isset($avg_size) ? $avg_size : null,
1513
            'avg_unit' => isset($avg_unit) ? $avg_unit : null,
1514
            'data_size' => $data_size,
1515
            'data_unit' => $data_unit,
1516
            'index_size' => isset($index_size) ? $index_size : null,
1517
            'index_unit' => isset($index_unit) ? $index_unit : null,
1518
            'free_size' => isset($free_size) ? $free_size : null,
1519
            'free_unit' => isset($free_unit) ? $free_unit : null,
1520
            'effect_size' => $effect_size,
1521
            'effect_unit' => $effect_unit,
1522
            'tot_size' => $tot_size,
1523
            'tot_unit' => $tot_unit,
1524
            'table' => $GLOBALS['table'],
1525
        ]);
1526
    }
1527
1528
    /**
1529
     * Gets table primary key
1530
     *
1531
     * @return string
1532
     */
1533
    protected function getKeyForTablePrimary()
1534
    {
1535
        $this->dbi->selectDb($this->db);
1536
        $result = $this->dbi->query(
1537
            'SHOW KEYS FROM ' . Util::backquote($this->table) . ';'
1538
        );
1539
        $primary = '';
1540
        while ($row = $this->dbi->fetchAssoc($result)) {
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type false; however, parameter $result of PhpMyAdmin\DatabaseInterface::fetchAssoc() does only seem to accept object, 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

1540
        while ($row = $this->dbi->fetchAssoc(/** @scrutinizer ignore-type */ $result)) {
Loading history...
1541
            // Backups the list of primary keys
1542
            if ($row['Key_name'] == 'PRIMARY') {
1543
                $primary .= $row['Column_name'] . ', ';
1544
            }
1545
        } // end while
1546
        $this->dbi->freeResult($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type false; however, parameter $result of PhpMyAdmin\DatabaseInterface::freeResult() does only seem to accept object, 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

1546
        $this->dbi->freeResult(/** @scrutinizer ignore-type */ $result);
Loading history...
1547
1548
        return $primary;
1549
    }
1550
1551
    /**
1552
     * Get List of information for Submit Mult
1553
     *
1554
     * @param string $submit_mult mult_submit type
1555
     * @param array  $selected    the selected columns
1556
     * @param string $action      action type
1557
     *
1558
     * @return array
1559
     */
1560
    protected function getDataForSubmitMult($submit_mult, $selected, $action)
1561
    {
1562
        $centralColumns = new CentralColumns($this->dbi);
1563
        $what = null;
1564
        $query_type = null;
1565
        $is_unset_submit_mult = false;
1566
        $mult_btn = null;
1567
        $centralColsError = null;
1568
        switch ($submit_mult) {
1569
            case 'drop':
1570
                $what     = 'drop_fld';
1571
                break;
1572
            case 'primary':
1573
                // Gets table primary key
1574
                $primary = $this->getKeyForTablePrimary();
1575
                if (empty($primary)) {
1576
                    // no primary key, so we can safely create new
1577
                    $is_unset_submit_mult = true;
1578
                    $query_type = 'primary_fld';
1579
                    $mult_btn   = __('Yes');
1580
                } else {
1581
                    // primary key exists, so lets as user
1582
                    $what = 'primary_fld';
1583
                }
1584
                break;
1585
            case 'index':
1586
                $is_unset_submit_mult = true;
1587
                $query_type = 'index_fld';
1588
                $mult_btn   = __('Yes');
1589
                break;
1590
            case 'unique':
1591
                $is_unset_submit_mult = true;
1592
                $query_type = 'unique_fld';
1593
                $mult_btn   = __('Yes');
1594
                break;
1595
            case 'spatial':
1596
                $is_unset_submit_mult = true;
1597
                $query_type = 'spatial_fld';
1598
                $mult_btn   = __('Yes');
1599
                break;
1600
            case 'ftext':
1601
                $is_unset_submit_mult = true;
1602
                $query_type = 'fulltext_fld';
1603
                $mult_btn   = __('Yes');
1604
                break;
1605
            case 'add_to_central_columns':
1606
                $centralColsError = $centralColumns->syncUniqueColumns(
1607
                    $selected,
1608
                    false
1609
                );
1610
                break;
1611
            case 'remove_from_central_columns':
1612
                $centralColsError = $centralColumns->deleteColumnsFromList(
1613
                    $selected,
1614
                    false
1615
                );
1616
                break;
1617
            case 'change':
1618
                $this->displayHtmlForColumnChange($selected, $action);
1619
                // execution stops here but PhpMyAdmin\Response correctly finishes
1620
                // the rendering
1621
                exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1622
            case 'browse':
1623
                // this should already be handled by tbl_structure.php
1624
        }
1625
1626
        return [
1627
            $what,
1628
            $query_type,
1629
            $is_unset_submit_mult,
1630
            $mult_btn,
1631
            $centralColsError,
1632
        ];
1633
    }
1634
}
1635