ExportSql::getTableDefForView()   F
last analyzed

Complexity

Conditions 12
Paths 388

Size

Total Lines 63
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 12.003

Importance

Changes 0
Metric Value
cc 12
eloc 35
nc 388
nop 3
dl 0
loc 63
rs 3.7833
c 0
b 0
f 0
ccs 35
cts 36
cp 0.9722
crap 12.003

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Set of functions used to build SQL dumps of tables
4
 */
5
6
declare(strict_types=1);
7
8
namespace PhpMyAdmin\Plugins\Export;
9
10
use PhpMyAdmin\Charsets;
11
use PhpMyAdmin\Config;
12
use PhpMyAdmin\Current;
13
use PhpMyAdmin\Database\Events;
14
use PhpMyAdmin\Database\Routines;
15
use PhpMyAdmin\DatabaseInterface;
16
use PhpMyAdmin\Dbal\ConnectionType;
17
use PhpMyAdmin\Plugins\ExportPlugin;
18
use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup;
19
use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup;
20
use PhpMyAdmin\Properties\Options\Groups\OptionsPropertySubgroup;
21
use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
22
use PhpMyAdmin\Properties\Options\Items\MessageOnlyPropertyItem;
23
use PhpMyAdmin\Properties\Options\Items\NumberPropertyItem;
24
use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem;
25
use PhpMyAdmin\Properties\Options\Items\SelectPropertyItem;
26
use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
27
use PhpMyAdmin\Properties\Plugins\ExportPluginProperties;
28
use PhpMyAdmin\SqlParser\Components\CreateDefinition;
29
use PhpMyAdmin\SqlParser\Context;
30
use PhpMyAdmin\SqlParser\Parser;
31
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
32
use PhpMyAdmin\SqlParser\Token;
33
use PhpMyAdmin\SqlParser\TokenType;
34
use PhpMyAdmin\Triggers\Triggers;
35
use PhpMyAdmin\UniqueCondition;
36
use PhpMyAdmin\Util;
37
use PhpMyAdmin\Version;
38
39
use function __;
40
use function array_keys;
41
use function bin2hex;
42
use function defined;
43
use function explode;
44
use function implode;
45
use function in_array;
46
use function is_array;
47
use function mb_strlen;
48
use function mb_strpos;
49
use function mb_substr;
50
use function preg_quote;
51
use function preg_replace;
52
use function preg_split;
53
use function sprintf;
54
use function str_contains;
55
use function str_repeat;
56
use function str_replace;
57
use function strtotime;
58
use function strtoupper;
59
use function trigger_error;
60
61
use const E_USER_ERROR;
62
use const PHP_VERSION;
63
64
/**
65
 * Handles the export for the SQL class
66
 */
67
class ExportSql extends ExportPlugin
68
{
69
    /**
70
     * Whether charset header was sent.
71
     */
72
    private bool $sentCharset = false;
73
74
    private bool $useSqlBackquotes = false;
75
76
    private string $sqlViews = '';
77
78
    public string|null $sqlAutoIncrements = null;
79
    public string|null $sqlIndexes = null;
80
    public string|null $sqlConstraints = null;
81
82
    public string $sqlConstraintsQuery = '';
83
84
    /** @psalm-return non-empty-lowercase-string */
85
    public function getName(): string
86
    {
87
        return 'sql';
88
    }
89
90 100
    public function useSqlBackquotes(bool $useSqlBackquotes): void
91
    {
92 100
        $this->useSqlBackquotes = $useSqlBackquotes;
93
    }
94
95 100
    protected function setProperties(): ExportPluginProperties
96
    {
97 100
        $GLOBALS['plugin_param'] ??= null;
98
99 100
        $hideSql = false;
100 100
        $hideStructure = false;
101 100
        if ($GLOBALS['plugin_param']['export_type'] === 'table' && ! $GLOBALS['plugin_param']['single_table']) {
102 100
            $hideStructure = true;
103 100
            $hideSql = true;
104
        }
105
106
        // In case we have `raw_query` parameter set,
107
        // we initialize SQL option
108 100
        if (isset($_REQUEST['raw_query'])) {
109
            $hideStructure = false;
110
            $hideSql = false;
111
        }
112
113 100
        $exportPluginProperties = new ExportPluginProperties();
114 100
        $exportPluginProperties->setText('SQL');
115 100
        $exportPluginProperties->setExtension('sql');
116 100
        $exportPluginProperties->setMimeType('text/x-sql');
117
118 100
        if ($hideSql) {
119 100
            return $exportPluginProperties;
120
        }
121
122 4
        $exportPluginProperties->setOptionsText(__('Options'));
123
124
        // create the root group that will be the options field for
125
        // $exportPluginProperties
126
        // this will be shown as "Format specific options"
127 4
        $exportSpecificOptions = new OptionsPropertyRootGroup('Format Specific Options');
128
129
        // general options main group
130 4
        $generalOptions = new OptionsPropertyMainGroup('general_opts');
131
132
        // comments
133 4
        $subgroup = new OptionsPropertySubgroup('include_comments');
134 4
        $leaf = new BoolPropertyItem(
135 4
            'include_comments',
136 4
            __(
137 4
                'Display comments <i>(includes info such as export timestamp, PHP version, and server version)</i>',
138 4
            ),
139 4
        );
140 4
        $subgroup->setSubgroupHeader($leaf);
141
142 4
        $leaf = new TextPropertyItem(
143 4
            'header_comment',
144 4
            __('Additional custom header comment (\n splits lines):'),
145 4
        );
146 4
        $subgroup->addProperty($leaf);
147 4
        $leaf = new BoolPropertyItem(
148 4
            'dates',
149 4
            __(
150 4
                'Include a timestamp of when databases were created, last updated, and last checked',
151 4
            ),
152 4
        );
153 4
        $subgroup->addProperty($leaf);
154 4
        $relationParameters = $this->relation->getRelationParameters();
155 4
        if ($relationParameters->relationFeature !== null) {
156 4
            $leaf = new BoolPropertyItem(
157 4
                'relation',
158 4
                __('Display foreign key relationships'),
159 4
            );
160 4
            $subgroup->addProperty($leaf);
161
        }
162
163 4
        if ($relationParameters->browserTransformationFeature !== null) {
164 4
            $leaf = new BoolPropertyItem(
165 4
                'mime',
166 4
                __('Display media types'),
167 4
            );
168 4
            $subgroup->addProperty($leaf);
169
        }
170
171 4
        $generalOptions->addProperty($subgroup);
172
173
        // enclose in a transaction
174 4
        $leaf = new BoolPropertyItem(
175 4
            'use_transaction',
176 4
            __('Enclose export in a transaction'),
177 4
        );
178 4
        $leaf->setDoc(
179 4
            ['programs', 'mysqldump', 'option_mysqldump_single-transaction'],
180 4
        );
181 4
        $generalOptions->addProperty($leaf);
182
183
        // disable foreign key checks
184 4
        $leaf = new BoolPropertyItem(
185 4
            'disable_fk',
186 4
            __('Disable foreign key checks'),
187 4
        );
188 4
        $leaf->setDoc(
189 4
            ['manual_MySQL_Database_Administration', 'server-system-variables', 'sysvar_foreign_key_checks'],
190 4
        );
191 4
        $generalOptions->addProperty($leaf);
192
193
        // export views as tables
194 4
        $leaf = new BoolPropertyItem(
195 4
            'views_as_tables',
196 4
            __('Export views as tables'),
197 4
        );
198 4
        $generalOptions->addProperty($leaf);
199
200
        // export metadata
201 4
        $leaf = new BoolPropertyItem(
202 4
            'metadata',
203 4
            __('Export metadata'),
204 4
        );
205 4
        $generalOptions->addProperty($leaf);
206
207 4
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

207
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

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

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

Loading history...
208
        // compatibility maximization
209 4
        $compats = $dbi->getCompatibilities();
210 4
        if ($compats !== []) {
211 4
            $this->addCompatOptions($compats, $generalOptions);
212
        }
213
214
        // what to dump (structure/data/both)
215 4
        $subgroup = new OptionsPropertySubgroup(
216 4
            'dump_table',
217 4
            __('Dump table'),
218 4
        );
219 4
        $leaf = new RadioPropertyItem('structure_or_data');
220 4
        $leaf->setValues(
221 4
            ['structure' => __('structure'), 'data' => __('data'), 'structure_and_data' => __('structure and data')],
222 4
        );
223 4
        $subgroup->setSubgroupHeader($leaf);
224 4
        $generalOptions->addProperty($subgroup);
225
226
        // add the main group to the root group
227 4
        $exportSpecificOptions->addProperty($generalOptions);
228
229
        // structure options main group
230 4
        if (! $hideStructure) {
231 4
            $structureOptions = new OptionsPropertyMainGroup(
232 4
                'structure',
233 4
                __('Object creation options'),
234 4
            );
235 4
            $structureOptions->setForce('data');
236
237
            // begin SQL Statements
238 4
            $subgroup = new OptionsPropertySubgroup();
239 4
            $leaf = new MessageOnlyPropertyItem(
240 4
                'add_statements',
241 4
                __('Add statements:'),
242 4
            );
243 4
            $subgroup->setSubgroupHeader($leaf);
244
245
            // server export options
246 4
            if ($GLOBALS['plugin_param']['export_type'] === 'server') {
247 4
                $leaf = new BoolPropertyItem(
248 4
                    'drop_database',
249 4
                    sprintf(__('Add %s statement'), '<code>DROP DATABASE IF EXISTS</code>'),
250 4
                );
251 4
                $subgroup->addProperty($leaf);
252
            }
253
254 4
            if ($GLOBALS['plugin_param']['export_type'] === 'database') {
255
                $createClause = '<code>CREATE DATABASE / USE</code>';
256
                $leaf = new BoolPropertyItem(
257
                    'create_database',
258
                    sprintf(__('Add %s statement'), $createClause),
259
                );
260
                $subgroup->addProperty($leaf);
261
            }
262
263 4
            if ($GLOBALS['plugin_param']['export_type'] === 'table') {
264
                $dropClause = $dbi->getTable(Current::$database, Current::$table)->isView()
265
                    ? '<code>DROP VIEW</code>'
266
                    : '<code>DROP TABLE</code>';
267
            } else {
268 4
                $dropClause = '<code>DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT</code>';
269
            }
270
271 4
            $dropClause .= '<code> / TRIGGER</code>';
272
273 4
            $leaf = new BoolPropertyItem(
274 4
                'drop_table',
275 4
                sprintf(__('Add %s statement'), $dropClause),
276 4
            );
277 4
            $subgroup->addProperty($leaf);
278
279 4
            $subgroupCreateTable = new OptionsPropertySubgroup();
280
281
            // Add table structure option
282 4
            $leaf = new BoolPropertyItem(
283 4
                'create_table',
284 4
                sprintf(__('Add %s statement'), '<code>CREATE TABLE</code>'),
285 4
            );
286 4
            $subgroupCreateTable->setSubgroupHeader($leaf);
287
288 4
            $leaf = new BoolPropertyItem(
289 4
                'if_not_exists',
290 4
                '<code>IF NOT EXISTS</code> ' . __(
291 4
                    '(less efficient as indexes will be generated during table creation)',
292 4
                ),
293 4
            );
294 4
            $subgroupCreateTable->addProperty($leaf);
295
296 4
            $leaf = new BoolPropertyItem(
297 4
                'auto_increment',
298 4
                sprintf(__('%s value'), '<code>AUTO_INCREMENT</code>'),
299 4
            );
300 4
            $subgroupCreateTable->addProperty($leaf);
301
302 4
            $subgroup->addProperty($subgroupCreateTable);
303
304
            // Add view option
305 4
            $subgroupCreateView = new OptionsPropertySubgroup();
306 4
            $leaf = new BoolPropertyItem(
307 4
                'create_view',
308 4
                sprintf(__('Add %s statement'), '<code>CREATE VIEW</code>'),
309 4
            );
310 4
            $subgroupCreateView->setSubgroupHeader($leaf);
311
312 4
            $leaf = new BoolPropertyItem(
313 4
                'simple_view_export',
314
                /* l10n: Allow simplifying exported view syntax to only "CREATE VIEW" */
315 4
                __('Use simple view export'),
316 4
            );
317 4
            $subgroupCreateView->addProperty($leaf);
318
319 4
            $leaf = new BoolPropertyItem(
320 4
                'view_current_user',
321 4
                __('Exclude definition of current user'),
322 4
            );
323 4
            $subgroupCreateView->addProperty($leaf);
324
325 4
            $leaf = new BoolPropertyItem(
326 4
                'or_replace_view',
327 4
                sprintf(__('%s view'), '<code>OR REPLACE</code>'),
328 4
            );
329 4
            $subgroupCreateView->addProperty($leaf);
330
331 4
            $subgroup->addProperty($subgroupCreateView);
332
333 4
            $leaf = new BoolPropertyItem(
334 4
                'procedure_function',
335 4
                sprintf(
336 4
                    __('Add %s statement'),
337 4
                    '<code>CREATE PROCEDURE / FUNCTION / EVENT</code>',
338 4
                ),
339 4
            );
340 4
            $subgroup->addProperty($leaf);
341
342
            // Add triggers option
343 4
            $leaf = new BoolPropertyItem(
344 4
                'create_trigger',
345 4
                sprintf(__('Add %s statement'), '<code>CREATE TRIGGER</code>'),
346 4
            );
347 4
            $subgroup->addProperty($leaf);
348
349 4
            $structureOptions->addProperty($subgroup);
350
351 4
            $leaf = new BoolPropertyItem(
352 4
                'backquotes',
353 4
                __(
354 4
                    'Enclose table and column names with backquotes '
355 4
                    . '<i>(Protects column and table names formed with'
356 4
                    . ' special characters or keywords)</i>',
357 4
                ),
358 4
            );
359
360 4
            $structureOptions->addProperty($leaf);
361
362
            // add the main group to the root group
363 4
            $exportSpecificOptions->addProperty($structureOptions);
364
        }
365
366
        // begin Data options
367 4
        $dataOptions = new OptionsPropertyMainGroup(
368 4
            'data',
369 4
            __('Data creation options'),
370 4
        );
371 4
        $dataOptions->setForce('structure');
372 4
        $leaf = new BoolPropertyItem(
373 4
            'truncate',
374 4
            __('Truncate table before insert'),
375 4
        );
376 4
        $dataOptions->addProperty($leaf);
377
378
        // begin SQL Statements
379 4
        $subgroup = new OptionsPropertySubgroup();
380 4
        $leaf = new MessageOnlyPropertyItem(
381 4
            __('Instead of <code>INSERT</code> statements, use:'),
382 4
        );
383 4
        $subgroup->setSubgroupHeader($leaf);
384
385 4
        $leaf = new BoolPropertyItem(
386 4
            'delayed',
387 4
            __('<code>INSERT DELAYED</code> statements'),
388 4
        );
389 4
        $leaf->setDoc(
390 4
            ['manual_MySQL_Database_Administration', 'insert_delayed'],
391 4
        );
392 4
        $subgroup->addProperty($leaf);
393
394 4
        $leaf = new BoolPropertyItem(
395 4
            'ignore',
396 4
            __('<code>INSERT IGNORE</code> statements'),
397 4
        );
398 4
        $leaf->setDoc(
399 4
            ['manual_MySQL_Database_Administration', 'insert'],
400 4
        );
401 4
        $subgroup->addProperty($leaf);
402 4
        $dataOptions->addProperty($subgroup);
403
404
        // Function to use when dumping dat
405 4
        $leaf = new SelectPropertyItem(
406 4
            'type',
407 4
            __('Function to use when dumping data:'),
408 4
        );
409 4
        $leaf->setValues(
410 4
            ['INSERT' => 'INSERT', 'UPDATE' => 'UPDATE', 'REPLACE' => 'REPLACE'],
411 4
        );
412 4
        $dataOptions->addProperty($leaf);
413
414
        /* Syntax to use when inserting data */
415 4
        $subgroup = new OptionsPropertySubgroup();
416 4
        $leaf = new MessageOnlyPropertyItem(
417 4
            null,
418 4
            __('Syntax to use when inserting data:'),
419 4
        );
420 4
        $subgroup->setSubgroupHeader($leaf);
421 4
        $leaf = new RadioPropertyItem(
422 4
            'insert_syntax',
423 4
            __('<code>INSERT IGNORE</code> statements'),
424 4
        );
425 4
        $leaf->setValues(
426 4
            [
427 4
                'complete' => __(
428 4
                    'include column names in every <code>INSERT</code> statement'
429 4
                    . ' <br> &nbsp; &nbsp; &nbsp; Example: <code>INSERT INTO'
430 4
                    . ' tbl_name (col_A,col_B,col_C) VALUES (1,2,3)</code>',
431 4
                ),
432 4
                'extended' => __(
433 4
                    'insert multiple rows in every <code>INSERT</code> statement'
434 4
                    . '<br> &nbsp; &nbsp; &nbsp; Example: <code>INSERT INTO'
435 4
                    . ' tbl_name VALUES (1,2,3), (4,5,6), (7,8,9)</code>',
436 4
                ),
437 4
                'both' => __(
438 4
                    'both of the above<br> &nbsp; &nbsp; &nbsp; Example:'
439 4
                    . ' <code>INSERT INTO tbl_name (col_A,col_B,col_C) VALUES'
440 4
                    . ' (1,2,3), (4,5,6), (7,8,9)</code>',
441 4
                ),
442 4
                'none' => __(
443 4
                    'neither of the above<br> &nbsp; &nbsp; &nbsp; Example:'
444 4
                    . ' <code>INSERT INTO tbl_name VALUES (1,2,3)</code>',
445 4
                ),
446 4
            ],
447 4
        );
448 4
        $subgroup->addProperty($leaf);
449 4
        $dataOptions->addProperty($subgroup);
450
451
        // Max length of query
452 4
        $leaf = new NumberPropertyItem(
453 4
            'max_query_size',
454 4
            __('Maximal length of created query'),
455 4
        );
456 4
        $dataOptions->addProperty($leaf);
457
458
        // Dump binary columns in hexadecimal
459 4
        $leaf = new BoolPropertyItem(
460 4
            'hex_for_binary',
461 4
            __(
462 4
                'Dump binary columns in hexadecimal notation <i>(for example, "abc" becomes 0x616263)</i>',
463 4
            ),
464 4
        );
465 4
        $dataOptions->addProperty($leaf);
466
467
        // Dump time in UTC
468 4
        $leaf = new BoolPropertyItem(
469 4
            'utc_time',
470 4
            __(
471 4
                'Dump TIMESTAMP columns in UTC <i>(enables TIMESTAMP columns'
472 4
                . ' to be dumped and reloaded between servers in different'
473 4
                . ' time zones)</i>',
474 4
            ),
475 4
        );
476 4
        $dataOptions->addProperty($leaf);
477
478
        // add the main group to the root group
479 4
        $exportSpecificOptions->addProperty($dataOptions);
480
481
        // set the options for the export plugin property item
482 4
        $exportPluginProperties->setOptions($exportSpecificOptions);
483
484 4
        return $exportPluginProperties;
485
    }
486
487
    /**
488
     * Generates SQL for routines export
489
     *
490
     * @param string   $db        Database
491
     * @param mixed[]  $aliases   Aliases of db/table/columns
492
     * @param string   $name      Verbose name of exported routine
493
     * @param string[] $routines  List of routines to export
494
     * @param string   $delimiter Delimiter to use in SQL
495
     * @psalm-param 'FUNCTION'|'PROCEDURE' $type
496
     *
497
     * @return string SQL query
498
     */
499 4
    protected function exportRoutineSQL(
500
        string $db,
501
        array $aliases,
502
        string $type,
503
        string $name,
504
        array $routines,
505
        string $delimiter,
506
    ): string {
507 4
        $text = $this->exportComment()
508 4
            . $this->exportComment($name)
509 4
            . $this->exportComment();
510
511 4
        $usedAlias = false;
512 4
        $procQuery = '';
513
514 4
        foreach ($routines as $routine) {
515 4
            if (! empty($GLOBALS['sql_drop_table'])) {
516 4
                $procQuery .= 'DROP ' . $type . ' IF EXISTS '
517 4
                    . Util::backquote($routine)
518 4
                    . $delimiter . "\n";
519
            }
520
521 4
            $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

521
            $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

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

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

Loading history...
522 4
            if ($type === 'FUNCTION') {
523 4
                $definition = Routines::getFunctionDefinition($dbi, $db, $routine);
524
            } else {
525 4
                $definition = Routines::getProcedureDefinition($dbi, $db, $routine);
526
            }
527
528 4
            $createQuery = $this->replaceWithAliases($delimiter, $definition, $aliases, $db, $flag);
529 4
            if ($createQuery !== '' && Config::getInstance()->settings['Export']['remove_definer_from_definitions']) {
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

529
            if ($createQuery !== '' && /** @scrutinizer ignore-deprecated */ Config::getInstance()->settings['Export']['remove_definer_from_definitions']) {

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

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

Loading history...
530
                // Remove definer clause from routine definitions
531
                $parser = new Parser('DELIMITER ' . $delimiter . "\n" . $createQuery);
532
                $statement = $parser->statements[0];
533
                $statement->options->remove('DEFINER');
534
                $createQuery = $statement->build();
535
            }
536
537
            // One warning per database
538 4
            if ($flag) {
539
                $usedAlias = true;
540
            }
541
542 4
            $procQuery .= $createQuery . $delimiter . "\n\n";
543
        }
544
545 4
        if ($usedAlias) {
546
            $text .= $this->exportComment(
547
                __('It appears your database uses routines;'),
548
            )
549
            . $this->exportComment(
550
                __('alias export may not work reliably in all cases.'),
551
            )
552
            . $this->exportComment();
553
        }
554
555 4
        $text .= $procQuery;
556
557 4
        return $text;
558
    }
559
560
    /**
561
     * Exports routines (procedures and functions)
562
     *
563
     * @param string  $db      Database
564
     * @param mixed[] $aliases Aliases of db/table/columns
565
     */
566 4
    public function exportRoutines(string $db, array $aliases = []): bool
567
    {
568 4
        $dbAlias = $db;
569 4
        $this->initAlias($aliases, $dbAlias);
570
571 4
        $text = '';
572 4
        $delimiter = '$$';
573
574 4
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

574
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

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

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

Loading history...
575 4
        $procedureNames = Routines::getProcedureNames($dbi, $db);
576 4
        $functionNames = Routines::getFunctionNames($dbi, $db);
577
578 4
        if ($procedureNames || $functionNames) {
579 4
            $text .= "\n"
580 4
                . 'DELIMITER ' . $delimiter . "\n";
581
582 4
            if ($procedureNames !== []) {
583 4
                $text .= $this->exportRoutineSQL(
584 4
                    $db,
585 4
                    $aliases,
586 4
                    'PROCEDURE',
587 4
                    __('Procedures'),
588 4
                    $procedureNames,
589 4
                    $delimiter,
590 4
                );
591
            }
592
593 4
            if ($functionNames !== []) {
594 4
                $text .= $this->exportRoutineSQL(
595 4
                    $db,
596 4
                    $aliases,
597 4
                    'FUNCTION',
598 4
                    __('Functions'),
599 4
                    $functionNames,
600 4
                    $delimiter,
601 4
                );
602
            }
603
604 4
            $text .= 'DELIMITER ;' . "\n";
605
        }
606
607 4
        if ($text !== '') {
608 4
            return $this->export->outputHandler($text);
609
        }
610
611
        return false;
612
    }
613
614
    /**
615
     * Possibly outputs comment
616
     *
617
     * @param string $text Text of comment
618
     *
619
     * @return string The formatted comment
620
     */
621 52
    private function exportComment(string $text = ''): string
622
    {
623 52
        if (isset($GLOBALS['sql_include_comments']) && $GLOBALS['sql_include_comments']) {
624
            // see https://dev.mysql.com/doc/refman/5.0/en/ansi-diff-comments.html
625 36
            if ($text === '') {
626 28
                return '--' . "\n";
627
            }
628
629 36
            $lines = preg_split("/\\r\\n|\\r|\\n/", $text);
630 36
            if ($lines === false) {
631
                return '--' . "\n";
632
            }
633
634 36
            $result = [];
635 36
            foreach ($lines as $line) {
636 36
                $result[] = '-- ' . $line . "\n";
637
            }
638
639 36
            return implode('', $result);
640
        }
641
642 20
        return '';
643
    }
644
645
    /**
646
     * Possibly outputs CRLF
647
     *
648
     * @return string crlf or nothing
649
     */
650 28
    private function possibleCRLF(): string
651
    {
652 28
        if (isset($GLOBALS['sql_include_comments']) && $GLOBALS['sql_include_comments']) {
653 20
            return "\n";
654
        }
655
656 12
        return '';
657
    }
658
659
    /**
660
     * Outputs export footer
661
     */
662 4
    public function exportFooter(): bool
663
    {
664 4
        $foot = '';
665
666 4
        if (isset($GLOBALS['sql_disable_fk'])) {
667 4
            $foot .= 'SET FOREIGN_KEY_CHECKS=1;' . "\n";
668
        }
669
670 4
        if (isset($GLOBALS['sql_use_transaction'])) {
671 4
            $foot .= 'COMMIT;' . "\n";
672
        }
673
674
        // restore connection settings
675 4
        if ($this->sentCharset) {
676
            $foot .= "\n"
677
                . '/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;'
678
                . "\n"
679
                . '/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;'
680
                . "\n"
681
                . '/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;'
682
                . "\n";
683
            $this->sentCharset = false;
684
        }
685
686
        /* Restore timezone */
687 4
        if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) {
688 4
            DatabaseInterface::getInstance()->query('SET time_zone = "' . $GLOBALS['old_tz'] . '"');
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

688
            /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance()->query('SET time_zone = "' . $GLOBALS['old_tz'] . '"');

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

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

Loading history...
689
        }
690
691 4
        return $this->export->outputHandler($foot);
692
    }
693
694
    /**
695
     * Outputs export header. It is the first method to be called, so all
696
     * the required variables are initialized here.
697
     */
698 4
    public function exportHeader(): bool
699
    {
700 4
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

700
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

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

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

Loading history...
701 4
        if (isset($GLOBALS['sql_compatibility'])) {
702 4
            $tmpCompat = $GLOBALS['sql_compatibility'];
703 4
            if ($tmpCompat === 'NONE') {
704 4
                $tmpCompat = '';
705
            }
706
707 4
            $dbi->tryQuery('SET SQL_MODE="' . $tmpCompat . '"');
708 4
            unset($tmpCompat);
709
        }
710
711 4
        $head = $this->exportComment('phpMyAdmin SQL Dump')
712 4
            . $this->exportComment('version ' . Version::VERSION)
713 4
            . $this->exportComment('https://www.phpmyadmin.net/')
714 4
            . $this->exportComment();
715 4
        $config = Config::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

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

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

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

Loading history...
716 4
        $hostString = __('Host:') . ' ' . $config->selectedServer['host'];
717 4
        if (! empty($config->selectedServer['port'])) {
718 4
            $hostString .= ':' . $config->selectedServer['port'];
719
        }
720
721 4
        $head .= $this->exportComment($hostString);
722 4
        $head .= $this->exportComment(
723 4
            __('Generation Time:') . ' '
724 4
            . Util::localisedDate(),
725 4
        )
726 4
        . $this->exportComment(
727 4
            __('Server version:') . ' ' . $dbi->getVersionString(),
728 4
        )
729 4
        . $this->exportComment(__('PHP Version:') . ' ' . PHP_VERSION)
730 4
        . $this->possibleCRLF();
731
732 4
        if (! empty($GLOBALS['sql_header_comment'])) {
733
            // '\n' is not a newline (like "\n" would be), it's the characters
734
            // backslash and n, as explained on the export interface
735 4
            $lines = explode('\n', $GLOBALS['sql_header_comment']);
736 4
            $head .= $this->exportComment();
737 4
            foreach ($lines as $oneLine) {
738 4
                $head .= $this->exportComment($oneLine);
739
            }
740
741 4
            $head .= $this->exportComment();
742
        }
743
744 4
        if (isset($GLOBALS['sql_disable_fk'])) {
745 4
            $head .= 'SET FOREIGN_KEY_CHECKS=0;' . "\n";
746
        }
747
748
        // We want exported AUTO_INCREMENT columns to have still same value,
749
        // do this only for recent MySQL exports
750 4
        if (! isset($GLOBALS['sql_compatibility']) || $GLOBALS['sql_compatibility'] === 'NONE') {
751 4
            $head .= 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' . "\n";
752
        }
753
754 4
        if (isset($GLOBALS['sql_use_transaction'])) {
755 4
            $head .= 'START TRANSACTION;' . "\n";
756
        }
757
758
        /* Change timezone if we should export timestamps in UTC */
759 4
        if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) {
760 4
            $head .= 'SET time_zone = "+00:00";' . "\n";
761 4
            $GLOBALS['old_tz'] = $dbi
762 4
                ->fetchValue('SELECT @@session.time_zone');
763 4
            $dbi->query('SET time_zone = "+00:00"');
764
        }
765
766 4
        $head .= $this->possibleCRLF();
767
768 4
        if (! empty($GLOBALS['asfile'])) {
769
            // we are saving as file, therefore we provide charset information
770
            // so that a utility like the mysql client can interpret
771
            // the file correctly
772 4
            if (isset($GLOBALS['charset'], Charsets::$mysqlCharsetMap[$GLOBALS['charset']])) {
773
                // we got a charset from the export dialog
774 4
                $setNames = Charsets::$mysqlCharsetMap[$GLOBALS['charset']];
775
            } else {
776
                // by default we use the connection charset
777
                $setNames = Charsets::$mysqlCharsetMap['utf-8'];
778
            }
779
780 4
            if ($setNames === 'utf8') {
781 4
                $setNames = $dbi->getDefaultCharset();
782
            }
783
784 4
            $head .= "\n"
785 4
                . '/*!40101 SET @OLD_CHARACTER_SET_CLIENT='
786 4
                . '@@CHARACTER_SET_CLIENT */;' . "\n"
787 4
                . '/*!40101 SET @OLD_CHARACTER_SET_RESULTS='
788 4
                . '@@CHARACTER_SET_RESULTS */;' . "\n"
789 4
                . '/*!40101 SET @OLD_COLLATION_CONNECTION='
790 4
                . '@@COLLATION_CONNECTION */;' . "\n"
791 4
                . '/*!40101 SET NAMES ' . $setNames . ' */;' . "\n\n";
792 4
            $this->sentCharset = true;
793
        }
794
795 4
        return $this->export->outputHandler($head);
796
    }
797
798
    /**
799
     * Outputs CREATE DATABASE statement
800
     *
801
     * @param string $db         Database name
802
     * @param string $exportType 'server', 'database', 'table'
803
     * @param string $dbAlias    Aliases of db
804
     */
805 4
    public function exportDBCreate(string $db, string $exportType, string $dbAlias = ''): bool
806
    {
807 4
        if ($dbAlias === '') {
808 4
            $dbAlias = $db;
809
        }
810
811 4
        $compat = $GLOBALS['sql_compatibility'] ?? 'NONE';
812
813 4
        $exportStructure = ! isset($GLOBALS['sql_structure_or_data'])
814 4
            || in_array($GLOBALS['sql_structure_or_data'], ['structure', 'structure_and_data'], true);
815 4
        if ($exportStructure && isset($GLOBALS['sql_drop_database'])) {
816
            if (
817 4
                ! $this->export->outputHandler(
818 4
                    'DROP DATABASE IF EXISTS '
819 4
                    . Util::backquoteCompat(
820 4
                        $dbAlias,
821 4
                        $compat,
822 4
                        $this->useSqlBackquotes,
823 4
                    )
824 4
                    . ';' . "\n",
825 4
                )
826
            ) {
827
                return false;
828
            }
829
        }
830
831 4
        if ($exportType === 'database' && ! isset($GLOBALS['sql_create_database'])) {
832
            return true;
833
        }
834
835 4
        $createQuery = 'CREATE DATABASE IF NOT EXISTS '
836 4
            . Util::backquoteCompat($dbAlias, $compat, $this->useSqlBackquotes);
837 4
        $collation = DatabaseInterface::getInstance()->getDbCollation($db);
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

837
        $collation = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance()->getDbCollation($db);

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

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

Loading history...
838 4
        if (str_contains($collation, '_')) {
839 4
            $createQuery .= ' DEFAULT CHARACTER SET '
840 4
                . mb_substr(
841 4
                    $collation,
842 4
                    0,
843 4
                    (int) mb_strpos($collation, '_'),
844 4
                )
845 4
                . ' COLLATE ' . $collation;
846
        } else {
847 4
            $createQuery .= ' DEFAULT CHARACTER SET ' . $collation;
848
        }
849
850 4
        $createQuery .= ';' . "\n";
851 4
        if (! $this->export->outputHandler($createQuery)) {
852
            return false;
853
        }
854
855 4
        return $this->exportUseStatement($dbAlias, $compat);
856
    }
857
858
    /**
859
     * Outputs USE statement
860
     *
861
     * @param string $db     db to use
862
     * @param string $compat sql compatibility
863
     */
864 4
    private function exportUseStatement(string $db, string $compat): bool
865
    {
866 4
        if ($compat === 'NONE') {
867 4
            return $this->export->outputHandler(
868 4
                'USE '
869 4
                . Util::backquoteCompat(
870 4
                    $db,
871 4
                    $compat,
872 4
                    $this->useSqlBackquotes,
873 4
                )
874 4
                . ';' . "\n",
875 4
            );
876
        }
877
878
        return $this->export->outputHandler('USE ' . $db . ';' . "\n");
879
    }
880
881
    /**
882
     * Outputs database header
883
     *
884
     * @param string $db      Database name
885
     * @param string $dbAlias Alias of db
886
     */
887 4
    public function exportDBHeader(string $db, string $dbAlias = ''): bool
888
    {
889 4
        if ($dbAlias === '') {
890 4
            $dbAlias = $db;
891
        }
892
893 4
        $compat = $GLOBALS['sql_compatibility'] ?? 'NONE';
894
895 4
        $head = $this->exportComment()
896 4
            . $this->exportComment(
897 4
                __('Database:') . ' '
898 4
                . Util::backquoteCompat(
899 4
                    $dbAlias,
900 4
                    $compat,
901 4
                    $this->useSqlBackquotes,
902 4
                ),
903 4
            )
904 4
            . $this->exportComment();
905
906 4
        return $this->export->outputHandler($head);
907
    }
908
909
    /**
910
     * Outputs database footer
911
     *
912
     * @param string $db Database name
913
     */
914 4
    public function exportDBFooter(string $db): bool
915
    {
916 4
        $result = true;
917
918
        //add indexes to the sql dump file
919 4
        if ($this->sqlIndexes !== null) {
920
            $result = $this->export->outputHandler($this->sqlIndexes);
921
            $this->sqlIndexes = null;
922
        }
923
924
        //add auto increments to the sql dump file
925 4
        if ($this->sqlAutoIncrements !== null) {
926
            $result = $this->export->outputHandler($this->sqlAutoIncrements);
927
            $this->sqlAutoIncrements = null;
928
        }
929
930
        //add views to the sql dump file
931 4
        if ($this->sqlViews !== '') {
932
            $result = $this->export->outputHandler($this->sqlViews);
933
            $this->sqlViews = '';
934
        }
935
936
        //add constraints to the sql dump file
937 4
        if ($this->sqlConstraints !== null) {
938 4
            $result = $this->export->outputHandler($this->sqlConstraints);
939 4
            $this->sqlConstraints = null;
940
        }
941
942 4
        return $result;
943
    }
944
945
    /**
946
     * Exports events
947
     *
948
     * @param string $db Database
949
     */
950 4
    public function exportEvents(string $db): bool
951
    {
952 4
        $text = '';
953 4
        $delimiter = '$$';
954
955 4
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

955
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

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

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

Loading history...
956 4
        $eventNames = $dbi->fetchResult(
957 4
            'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE'
958 4
            . ' EVENT_SCHEMA= ' . $dbi->quoteString($db),
959 4
        );
960
961 4
        if ($eventNames !== []) {
962 4
            $text .= "\n"
963 4
                . 'DELIMITER ' . $delimiter . "\n";
964
965 4
            $text .= $this->exportComment()
966 4
                . $this->exportComment(__('Events'))
967 4
                . $this->exportComment();
968
969 4
            foreach ($eventNames as $eventName) {
970 4
                if (! empty($GLOBALS['sql_drop_table'])) {
971
                    $text .= 'DROP EVENT IF EXISTS '
972
                        . Util::backquote($eventName)
973
                        . $delimiter . "\n";
974
                }
975
976 4
                $eventDef = Events::getDefinition($dbi, $db, $eventName);
977
                if (
978 4
                    $eventDef !== null
979 4
                    && $eventDef !== ''
980 4
                    && Config::getInstance()->settings['Export']['remove_definer_from_definitions']
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

980
                    && /** @scrutinizer ignore-deprecated */ Config::getInstance()->settings['Export']['remove_definer_from_definitions']

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

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

Loading history...
981
                ) {
982
                    // remove definer clause from the event definition
983
                    $parser = new Parser('DELIMITER ' . $delimiter . "\n" . $eventDef);
984
                    $statement = $parser->statements[0];
985
                    $statement->options->remove('DEFINER');
986
                    $eventDef = $statement->build();
987
                }
988
989 4
                $text .= $eventDef . $delimiter . "\n\n";
990
            }
991
992 4
            $text .= 'DELIMITER ;' . "\n";
993
        }
994
995 4
        if ($text !== '') {
996 4
            return $this->export->outputHandler($text);
997
        }
998
999
        return false;
1000
    }
1001
1002
    /**
1003
     * Exports metadata from Configuration Storage
1004
     *
1005
     * @param string          $db            database being exported
1006
     * @param string|string[] $tables        table(s) being exported
1007
     * @param string[]        $metadataTypes types of metadata to export
1008
     */
1009
    public function exportMetadata(
1010
        string $db,
1011
        string|array $tables,
1012
        array $metadataTypes,
1013
    ): bool {
1014
        $relationParameters = $this->relation->getRelationParameters();
1015
        if ($relationParameters->db === null) {
1016
            return true;
1017
        }
1018
1019
        $comment = $this->possibleCRLF()
1020
            . $this->possibleCRLF()
1021
            . $this->exportComment()
1022
            . $this->exportComment(__('Metadata'))
1023
            . $this->exportComment();
1024
        if (! $this->export->outputHandler($comment)) {
1025
            return false;
1026
        }
1027
1028
        if (! $this->exportUseStatement((string) $relationParameters->db, $GLOBALS['sql_compatibility'])) {
1029
            return false;
1030
        }
1031
1032
        $r = 1;
1033
        if (is_array($tables)) {
0 ignored issues
show
introduced by
The condition is_array($tables) is always true.
Loading history...
1034
            // export metadata for each table
1035
            foreach ($tables as $table) {
1036
                $r &= (int) $this->exportConfigurationMetadata($db, $table, $metadataTypes);
1037
            }
1038
1039
            // export metadata for the database
1040
            $r &= (int) $this->exportConfigurationMetadata($db, null, $metadataTypes);
1041
        } else {
1042
            // export metadata for single table
1043
            $r &= (int) $this->exportConfigurationMetadata($db, $tables, $metadataTypes);
1044
        }
1045
1046
        return (bool) $r;
1047
    }
1048
1049
    /**
1050
     * Exports metadata from Configuration Storage
1051
     *
1052
     * @param string      $db            database being exported
1053
     * @param string|null $table         table being exported
1054
     * @param string[]    $metadataTypes types of metadata to export
1055
     */
1056
    private function exportConfigurationMetadata(
1057
        string $db,
1058
        string|null $table,
1059
        array $metadataTypes,
1060
    ): bool {
1061
        $relationParameters = $this->relation->getRelationParameters();
1062
        $relationParams = $relationParameters->toArray();
1063
1064
        if (isset($table)) {
1065
            $types = ['column_info' => 'db_name', 'table_uiprefs' => 'db_name', 'tracking' => 'db_name'];
1066
        } else {
1067
            $types = [
1068
                'bookmark' => 'dbase',
1069
                'relation' => 'master_db',
1070
                'pdf_pages' => 'db_name',
1071
                'savedsearches' => 'db_name',
1072
                'central_columns' => 'db_name',
1073
            ];
1074
        }
1075
1076
        $aliases = [];
1077
1078
        $comment = $this->possibleCRLF() . $this->exportComment();
1079
1080
        if ($table !== null) {
1081
            $comment .= $this->exportComment(
1082
                sprintf(
1083
                    __('Metadata for table %s'),
1084
                    $table,
1085
                ),
1086
            );
1087
        } else {
1088
            $comment .= $this->exportComment(
1089
                sprintf(
1090
                    __('Metadata for database %s'),
1091
                    $db,
1092
                ),
1093
            );
1094
        }
1095
1096
        $comment .= $this->exportComment();
1097
1098
        if (! $this->export->outputHandler($comment)) {
1099
            return false;
1100
        }
1101
1102
        foreach ($types as $type => $dbNameColumn) {
1103
            if (! in_array($type, $metadataTypes) || ! isset($relationParams[$type])) {
1104
                continue;
1105
            }
1106
1107
            $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

1107
            $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

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

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

Loading history...
1108
            // special case, designer pages and their coordinates
1109
            if ($type === 'pdf_pages') {
1110
                if ($relationParameters->pdfFeature === null) {
1111
                    continue;
1112
                }
1113
1114
                $sqlQuery = 'SELECT `page_nr`, `page_descr` FROM '
1115
                    . Util::backquote($relationParameters->pdfFeature->database)
1116
                    . '.' . Util::backquote($relationParameters->pdfFeature->pdfPages)
1117
                    . ' WHERE `db_name` = ' . $dbi->quoteString($db);
1118
1119
                $result = $dbi->fetchResult($sqlQuery, 'page_nr', 'page_descr');
1120
1121
                foreach (array_keys($result) as $page) {
1122
                    // insert row for pdf_page
1123
                    $sqlQueryRow = 'SELECT `db_name`, `page_descr` FROM '
1124
                        . Util::backquote($relationParameters->pdfFeature->database)
1125
                        . '.' . Util::backquote($relationParameters->pdfFeature->pdfPages)
1126
                        . ' WHERE `db_name` = ' . $dbi->quoteString($db)
1127
                        . ' AND `page_nr` = ' . (int) $page;
1128
1129
                    if (
1130
                        ! $this->exportData(
1131
                            $relationParameters->pdfFeature->database->getName(),
1132
                            $relationParameters->pdfFeature->pdfPages->getName(),
1133
                            '',
1134
                            $sqlQueryRow,
1135
                            $aliases,
1136
                        )
1137
                    ) {
1138
                        return false;
1139
                    }
1140
1141
                    $lastPage = "\n"
1142
                        . 'SET @LAST_PAGE = LAST_INSERT_ID();'
1143
                        . "\n";
1144
                    if (! $this->export->outputHandler($lastPage)) {
1145
                        return false;
1146
                    }
1147
1148
                    $sqlQueryCoords = 'SELECT `db_name`, `table_name`, '
1149
                        . "'@LAST_PAGE' AS `pdf_page_number`, `x`, `y` FROM "
1150
                        . Util::backquote($relationParameters->pdfFeature->database)
1151
                        . '.' . Util::backquote($relationParameters->pdfFeature->tableCoords)
1152
                        . " WHERE `pdf_page_number` = '" . $page . "'";
1153
1154
                    $GLOBALS['exporting_metadata'] = true;
1155
                    if (
1156
                        ! $this->exportData(
1157
                            $relationParameters->pdfFeature->database->getName(),
1158
                            $relationParameters->pdfFeature->tableCoords->getName(),
1159
                            '',
1160
                            $sqlQueryCoords,
1161
                            $aliases,
1162
                        )
1163
                    ) {
1164
                        $GLOBALS['exporting_metadata'] = false;
1165
1166
                        return false;
1167
                    }
1168
1169
                    $GLOBALS['exporting_metadata'] = false;
1170
                }
1171
1172
                continue;
1173
            }
1174
1175
            // remove auto_incrementing id field for some tables
1176
            $sqlQuery = match ($type) {
1177
                'bookmark' => 'SELECT `dbase`, `user`, `label`, `query` FROM ',
1178
                'column_info' => 'SELECT `db_name`, `table_name`, `column_name`,'
1179
                    . ' `comment`, `mimetype`, `transformation`,'
1180
                    . ' `transformation_options`, `input_transformation`,'
1181
                    . ' `input_transformation_options` FROM',
1182
                'savedsearches' => 'SELECT `username`, `db_name`, `search_name`, `search_data` FROM',
1183
                default => 'SELECT * FROM ',
1184
            };
1185
1186
            $sqlQuery .= Util::backquote($relationParameters->db)
1187
                . '.' . Util::backquote((string) $relationParams[$type])
1188
                . ' WHERE ' . Util::backquote($dbNameColumn)
1189
                . ' = ' . $dbi->quoteString($db);
1190
            if (isset($table)) {
1191
                $sqlQuery .= ' AND `table_name` = ' . $dbi->quoteString($table);
1192
            }
1193
1194
            if (
1195
                ! $this->exportData(
1196
                    (string) $relationParameters->db,
1197
                    (string) $relationParams[$type],
1198
                    '',
1199
                    $sqlQuery,
1200
                    $aliases,
1201
                )
1202
            ) {
1203
                return false;
1204
            }
1205
        }
1206
1207
        return true;
1208
    }
1209
1210
    /**
1211
     * Returns a stand-in CREATE definition to resolve view dependencies
1212
     *
1213
     * @param string  $db      the database name
1214
     * @param string  $view    the view name
1215
     * @param mixed[] $aliases Aliases of db/table/columns
1216
     *
1217
     * @return string resulting definition
1218
     */
1219 8
    public function getTableDefStandIn(string $db, string $view, array $aliases = []): string
1220
    {
1221 8
        $dbAlias = $db;
1222 8
        $viewAlias = $view;
1223 8
        $this->initAlias($aliases, $dbAlias, $viewAlias);
1224 8
        $createQuery = '';
1225 8
        if (! empty($GLOBALS['sql_drop_table'])) {
1226 4
            $createQuery .= 'DROP VIEW IF EXISTS '
1227 4
                . Util::backquote($viewAlias)
1228 4
                . ';' . "\n";
1229
        }
1230
1231 8
        $createQuery .= 'CREATE TABLE ';
1232
1233 8
        if (isset($GLOBALS['sql_if_not_exists']) && $GLOBALS['sql_if_not_exists']) {
1234 4
            $createQuery .= 'IF NOT EXISTS ';
1235
        }
1236
1237 8
        $createQuery .= Util::backquote($viewAlias) . ' (' . "\n";
1238 8
        $tmp = [];
1239 8
        $columns = DatabaseInterface::getInstance()->getColumnsFull($db, $view);
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

1239
        $columns = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance()->getColumnsFull($db, $view);

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

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

Loading history...
1240 8
        foreach ($columns as $columnName => $definition) {
1241 8
            $colAlias = $columnName;
1242 8
            if (! empty($aliases[$db]['tables'][$view]['columns'][$colAlias])) {
1243
                $colAlias = $aliases[$db]['tables'][$view]['columns'][$colAlias];
1244
            }
1245
1246 8
            $tmp[] = Util::backquote($colAlias) . ' ' . $definition['Type'] . "\n";
1247
        }
1248
1249 8
        return $createQuery . implode(',', $tmp) . ');' . "\n";
1250
    }
1251
1252
    /**
1253
     * Returns CREATE definition that matches $view's structure
1254
     *
1255
     * @param string  $db      the database name
1256
     * @param string  $view    the view name
1257
     * @param mixed[] $aliases Aliases of db/table/columns
1258
     *
1259
     * @return string resulting schema
1260
     */
1261 8
    private function getTableDefForView(
1262
        string $db,
1263
        string $view,
1264
        array $aliases = [],
1265
    ): string {
1266 8
        $dbAlias = $db;
1267 8
        $viewAlias = $view;
1268 8
        $this->initAlias($aliases, $dbAlias, $viewAlias);
1269 8
        $createQuery = 'CREATE TABLE';
1270 8
        if (isset($GLOBALS['sql_if_not_exists'])) {
1271 4
            $createQuery .= ' IF NOT EXISTS ';
1272
        }
1273
1274 8
        $createQuery .= Util::backquote($viewAlias) . '(' . "\n";
1275
1276 8
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

1276
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

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

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

Loading history...
1277 8
        $columns = $dbi->getColumns($db, $view, true);
1278
1279 8
        $firstCol = true;
1280 8
        foreach ($columns as $column) {
1281 8
            $colAlias = $column->field;
1282 8
            if (! empty($aliases[$db]['tables'][$view]['columns'][$colAlias])) {
1283
                $colAlias = $aliases[$db]['tables'][$view]['columns'][$colAlias];
1284
            }
1285
1286 8
            $extractedColumnspec = Util::extractColumnSpec($column->type);
1287
1288 8
            if (! $firstCol) {
1289 4
                $createQuery .= ',' . "\n";
1290
            }
1291
1292 8
            $createQuery .= '    ' . Util::backquote($colAlias);
1293 8
            $createQuery .= ' ' . $column->type;
1294 8
            if ($extractedColumnspec['can_contain_collation'] && ! empty($column->collation)) {
1295 4
                $createQuery .= ' COLLATE ' . $column->collation;
1296
            }
1297
1298 8
            if (! $column->isNull) {
1299 8
                $createQuery .= ' NOT NULL';
1300
            }
1301
1302 8
            if ($column->default !== null) {
1303 8
                $createQuery .= ' DEFAULT ' . $dbi->quoteString($column->default);
1304 4
            } elseif ($column->isNull) {
1305 4
                $createQuery .= ' DEFAULT NULL';
1306
            }
1307
1308 8
            if ($column->comment !== '') {
1309 4
                $createQuery .= ' COMMENT ' . $dbi->quoteString($column->comment);
1310
            }
1311
1312 8
            $firstCol = false;
1313
        }
1314
1315 8
        $createQuery .= "\n" . ');' . "\n";
1316
1317 8
        $compat = $GLOBALS['sql_compatibility'] ?? 'NONE';
1318
1319 8
        if ($compat === 'MSSQL') {
1320 4
            return $this->makeCreateTableMSSQLCompatible($createQuery);
1321
        }
1322
1323 8
        return $createQuery;
1324
    }
1325
1326
    /**
1327
     * Returns $table's CREATE definition
1328
     *
1329
     * @param string  $db                      the database name
1330
     * @param string  $table                   the table name
1331
     * @param bool    $showDates               whether to include creation/
1332
     *                                          update/check dates
1333
     * @param bool    $addSemicolon            whether to add semicolon and
1334
     *                                          end-of-line at the end
1335
     * @param bool    $view                    whether we're handling a view
1336
     * @param bool    $updateIndexesIncrements whether we need to update
1337
     *                                           two global variables
1338
     * @param mixed[] $aliases                 Aliases of db/table/columns
1339
     *
1340
     * @return string resulting schema
1341
     */
1342 12
    public function getTableDef(
1343
        string $db,
1344
        string $table,
1345
        bool $showDates = false,
1346
        bool $addSemicolon = true,
1347
        bool $view = false,
1348
        bool $updateIndexesIncrements = true,
1349
        array $aliases = [],
1350
    ): string {
1351 12
        $GLOBALS['sql_drop_table'] ??= null;
1352 12
        $GLOBALS['sql_indexes_query'] ??= null;
1353 12
        $GLOBALS['sql_drop_foreign_keys'] ??= null;
1354
1355 12
        $dbAlias = $db;
1356 12
        $tableAlias = $table;
1357 12
        $this->initAlias($aliases, $dbAlias, $tableAlias);
1358
1359 12
        $compat = $GLOBALS['sql_compatibility'] ?? 'NONE';
1360
1361 12
        $schemaCreate = $this->getTableStatus($db, $table, $showDates);
1362
1363 12
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

1363
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

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

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

Loading history...
1364 12
        if (! empty($GLOBALS['sql_drop_table']) && $dbi->getTable($db, $table)->isView()) {
1365
            $schemaCreate .= 'DROP VIEW IF EXISTS '
1366
                . Util::backquoteCompat($tableAlias, 'NONE', $this->useSqlBackquotes) . ';'
1367
                . "\n";
1368
        }
1369
1370
        // no need to generate a DROP VIEW here, it was done earlier
1371 12
        if (! empty($GLOBALS['sql_drop_table']) && ! $dbi->getTable($db, $table)->isView()) {
1372 8
            $schemaCreate .= 'DROP TABLE IF EXISTS '
1373 8
                . Util::backquoteCompat($tableAlias, 'NONE', $this->useSqlBackquotes) . ';'
1374 8
                . "\n";
1375
        }
1376
1377
        // Complete table dump,
1378
        // Whether to quote table and column names or not
1379 12
        if ($this->useSqlBackquotes) {
1380 8
            $dbi->query('SET SQL_QUOTE_SHOW_CREATE = 1');
1381
        } else {
1382 8
            $dbi->query('SET SQL_QUOTE_SHOW_CREATE = 0');
1383
        }
1384
1385
        // I don't see the reason why this unbuffered query could cause problems,
1386
        // because SHOW CREATE TABLE returns only one row, and we free the
1387
        // results below. Nonetheless, we got 2 user reports about this
1388
        // (see bug 1562533) so I removed the unbuffered mode.
1389
        // $result = $dbi->query('SHOW CREATE TABLE ' . backquote($db)
1390
        // . '.' . backquote($table), null, DatabaseInterface::QUERY_UNBUFFERED);
1391
        //
1392
        // Note: SHOW CREATE TABLE, at least in MySQL 5.1.23, does not
1393
        // produce a displayable result for the default value of a BIT
1394
        // column, nor does the mysqldump command. See MySQL bug 35796
1395 12
        $dbi->tryQuery('USE ' . Util::backquote($db));
1396 12
        $result = $dbi->tryQuery(
1397 12
            'SHOW CREATE TABLE ' . Util::backquote($db) . '.'
1398 12
            . Util::backquote($table),
1399 12
        );
1400
        // an error can happen, for example the table is crashed
1401 12
        $tmpError = $dbi->getError();
1402 12
        if ($tmpError !== '') {
1403 4
            $message = sprintf(__('Error reading structure for table %s:'), $db . '.' . $table);
1404 4
            $message .= ' ' . $tmpError;
1405 4
            if (! defined('TESTSUITE')) {
1406
                trigger_error($message, E_USER_ERROR);
1407
            }
1408
1409 4
            return $this->exportComment($message);
1410
        }
1411
1412
        // Old mode is stored so it can be restored once exporting is done.
1413 8
        $oldMode = Context::getMode();
1414
1415 8
        $warning = '';
1416
1417 8
        $row = [];
1418 8
        if ($result !== false) {
1419 8
            $row = $result->fetchRow();
1420
        }
1421
1422 8
        if ($row !== []) {
1423 8
            $createQuery = $row[1];
1424 8
            unset($row);
1425
1426
            // Convert end of line chars to one that we want (note that MySQL
1427
            // doesn't return query it will accept in all cases)
1428 8
            if (str_contains($createQuery, "(\r\n ")) {
1429
                $createQuery = str_replace("\r\n", "\n", $createQuery);
1430 8
            } elseif (str_contains($createQuery, "(\n ")) {
1431 8
                $createQuery = str_replace("\n", "\n", $createQuery);
1432
            } elseif (str_contains($createQuery, "(\r ")) {
1433
                $createQuery = str_replace("\r", "\n", $createQuery);
1434
            }
1435
1436
            /**
1437
             * Drop database name from VIEW creation.
1438
             *
1439
             * This is a bit tricky, but we need to issue SHOW CREATE TABLE with
1440
             * database name, but we don't want name to show up in CREATE VIEW
1441
             * statement.
1442
             */
1443 8
            if ($view) {
1444
                //TODO: use parser
1445 4
                $createQuery = preg_replace(
1446 4
                    '/' . preg_quote(Util::backquote($db), '/') . '\./',
1447 4
                    '',
1448 4
                    $createQuery,
1449 4
                );
1450 4
                $parser = new Parser($createQuery);
1451
                /**
1452
                 * `CREATE TABLE` statement.
1453
                 *
1454
                 * @var CreateStatement $statement
1455
                 */
1456 4
                $statement = $parser->statements[0];
1457
1458
                // exclude definition of current user
1459
                if (
1460 4
                    Config::getInstance()->settings['Export']['remove_definer_from_definitions']
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

1460
                    /** @scrutinizer ignore-deprecated */ Config::getInstance()->settings['Export']['remove_definer_from_definitions']

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

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

Loading history...
1461 4
                    || isset($GLOBALS['sql_view_current_user'])
1462
                ) {
1463
                    $statement->options->remove('DEFINER');
0 ignored issues
show
Bug introduced by
The method remove() does not exist on null. ( Ignorable by Annotation )

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

1463
                    $statement->options->/** @scrutinizer ignore-call */ 
1464
                                         remove('DEFINER');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1464
                }
1465
1466 4
                if (isset($GLOBALS['sql_simple_view_export'])) {
1467
                    $statement->options->remove('SQL SECURITY');
1468
                    $statement->options->remove('INVOKER');
1469
                    $statement->options->remove('ALGORITHM');
1470
                    $statement->options->remove('DEFINER');
1471
                }
1472
1473 4
                $createQuery = $statement->build();
1474
1475
                // whether to replace existing view or not
1476 4
                if (isset($GLOBALS['sql_or_replace_view'])) {
1477
                    $createQuery = preg_replace('/^CREATE/', 'CREATE OR REPLACE', $createQuery);
1478
                }
1479
            }
1480
1481
            // Substitute aliases in `CREATE` query.
1482 8
            $createQuery = $this->replaceWithAliases(null, $createQuery, $aliases, $db, $flag);
1483
1484
            // One warning per view.
1485 8
            if ($flag && $view) {
1486
                $warning = $this->exportComment()
1487
                    . $this->exportComment(
1488
                        __('It appears your database uses views;'),
1489
                    )
1490
                    . $this->exportComment(
1491
                        __('alias export may not work reliably in all cases.'),
1492
                    )
1493
                    . $this->exportComment();
1494
            }
1495
1496
            // Adding IF NOT EXISTS, if required.
1497 8
            if (isset($GLOBALS['sql_if_not_exists'])) {
1498 4
                $createQuery = (string) preg_replace('/^CREATE TABLE/', 'CREATE TABLE IF NOT EXISTS', $createQuery);
1499
            }
1500
1501
            // Making the query MSSQL compatible.
1502 8
            if ($compat === 'MSSQL') {
1503 8
                $createQuery = $this->makeCreateTableMSSQLCompatible($createQuery);
1504
            }
1505
1506
            // Views have no constraints, indexes, etc. They do not require any
1507
            // analysis.
1508 8
            if (! $view) {
1509 8
                if (! $this->useSqlBackquotes) {
1510
                    // Option "Enclose table and column names with backquotes"
1511
                    // was checked.
1512
                    Context::setMode(Context::getMode() | Context::SQL_MODE_NO_ENCLOSING_QUOTES);
1513
                }
1514
1515
                // Using appropriate quotes.
1516 8
                if ($compat === 'MSSQL') {
1517 8
                    Context::setMode(Context::getMode() | Context::SQL_MODE_ANSI_QUOTES);
1518
                }
1519
            }
1520
1521
            /**
1522
             * Parser used for analysis.
1523
             */
1524 8
            $parser = new Parser($createQuery);
1525
1526
            /**
1527
             * `CREATE TABLE` statement.
1528
             *
1529
             * @var CreateStatement $statement
1530
             */
1531 8
            $statement = $parser->statements[0];
1532
1533 8
            if (! empty($statement->entityOptions)) {
1534 8
                $engine = $statement->entityOptions->has('ENGINE');
1535
            } else {
1536
                $engine = '';
1537
            }
1538
1539
            /* Avoid operation on ARCHIVE tables as those can not be altered */
1540
            if (
1541 8
                ! empty($statement->fields) && is_array($statement->fields)
1542 8
                && (empty($engine) || strtoupper($engine) !== 'ARCHIVE')
1543
            ) {
1544
1545
                /**
1546
                 * Fragments containing definition of each constraint.
1547
                 */
1548 8
                $constraints = [];
1549
1550
                /**
1551
                 * Fragments containing definition of each index.
1552
                 */
1553 8
                $indexes = [];
1554
1555
                /**
1556
                 * Fragments containing definition of each FULLTEXT index.
1557
                 */
1558 8
                $indexesFulltext = [];
1559
1560
                /**
1561
                 * Fragments containing definition of each foreign key that will be dropped.
1562
                 */
1563 8
                $dropped = [];
1564
1565
                /**
1566
                 * Fragment containing definition of the `AUTO_INCREMENT`.
1567
                 */
1568 8
                $autoIncrement = [];
1569
1570
                // Scanning each field of the `CREATE` statement to fill the arrays
1571
                // above.
1572
                // If the field is used in any of the arrays above, it is removed
1573
                // from the original definition.
1574
                // Also, AUTO_INCREMENT attribute is removed.
1575
                /** @var CreateDefinition $field */
1576 8
                foreach ($statement->fields as $key => $field) {
1577 8
                    if ($field->isConstraint) {
1578
                        // Creating the parts that add constraints.
1579 4
                        $constraints[] = $field->build();
1580 4
                        unset($statement->fields[$key]);
1581 8
                    } elseif ($field->key !== null) {
1582
                        // Creating the parts that add indexes (must not be
1583
                        // constraints).
1584 8
                        if ($field->key->type === 'FULLTEXT KEY') {
1585
                            $indexesFulltext[] = $field->build();
1586
                            unset($statement->fields[$key]);
1587 8
                        } elseif (empty($GLOBALS['sql_if_not_exists'])) {
1588 4
                            $indexes[] = str_replace(
1589 4
                                'COMMENT=\'',
1590 4
                                'COMMENT \'',
1591 4
                                $field->build(),
1592 4
                            );
1593 4
                            unset($statement->fields[$key]);
1594
                        }
1595
                    }
1596
1597
                    // Creating the parts that drop foreign keys.
1598 8
                    if ($field->key !== null && $field->key->type === 'FOREIGN KEY' && $field->name !== null) {
1599 4
                        $dropped[] = 'FOREIGN KEY ' . Context::escape($field->name);
1600 4
                        unset($statement->fields[$key]);
1601
                    }
1602
1603
                    // Dropping AUTO_INCREMENT.
1604 8
                    if ($field->options === null) {
1605 8
                        continue;
1606
                    }
1607
1608 8
                    if (! $field->options->has('AUTO_INCREMENT') || ! empty($GLOBALS['sql_if_not_exists'])) {
1609 8
                        continue;
1610
                    }
1611
1612 4
                    $autoIncrement[] = $field->build();
1613 4
                    $field->options->remove('AUTO_INCREMENT');
1614
                }
1615
1616
                /**
1617
                 * The header of the `ALTER` statement (`ALTER TABLE tbl`).
1618
                 */
1619 8
                $alterHeader = 'ALTER TABLE ' . Util::backquoteCompat($tableAlias, $compat, $this->useSqlBackquotes);
1620
1621
                /**
1622
                 * The footer of the `ALTER` statement (usually ';')
1623
                 */
1624 8
                $alterFooter = ';' . "\n";
1625
1626
                // Generating constraints-related query.
1627 8
                if ($constraints !== []) {
1628 4
                    $this->sqlConstraintsQuery = $alterHeader . "\n" . '  ADD '
1629 4
                        . implode(',' . "\n" . '  ADD ', $constraints)
1630 4
                        . $alterFooter;
1631
1632 4
                    $this->sqlConstraints = $this->generateComment(
1633 4
                        $this->sqlConstraints,
1634 4
                        __('Constraints for dumped tables'),
1635 4
                        __('Constraints for table'),
1636 4
                        $tableAlias,
1637 4
                        $compat,
1638 4
                    ) . $this->sqlConstraintsQuery;
1639
                }
1640
1641
                // Generating indexes-related query.
1642 8
                $GLOBALS['sql_indexes_query'] = '';
1643
1644 8
                if ($indexes !== []) {
1645 4
                    $GLOBALS['sql_indexes_query'] .= $alterHeader . "\n" . '  ADD '
1646 4
                        . implode(',' . "\n" . '  ADD ', $indexes)
1647 4
                        . $alterFooter;
1648
                }
1649
1650 8
                if ($indexesFulltext !== []) {
1651
                    // InnoDB supports one FULLTEXT index creation at a time.
1652
                    // So FULLTEXT indexes are created one-by-one after other
1653
                    // indexes where created.
1654
                    $GLOBALS['sql_indexes_query'] .= $alterHeader
1655
                        . ' ADD ' . implode($alterFooter . $alterHeader . ' ADD ', $indexesFulltext)
1656
                        . $alterFooter;
1657
                }
1658
1659 8
                if ($indexes !== [] || $indexesFulltext !== []) {
1660 4
                    $this->sqlIndexes = $this->generateComment(
1661 4
                        $this->sqlIndexes,
1662 4
                        __('Indexes for dumped tables'),
1663 4
                        __('Indexes for table'),
1664 4
                        $tableAlias,
1665 4
                        $compat,
1666 4
                    ) . $GLOBALS['sql_indexes_query'];
1667
                }
1668
1669
                // Generating drop foreign keys-related query.
1670 8
                if ($dropped !== []) {
1671 4
                    $GLOBALS['sql_drop_foreign_keys'] = $alterHeader . "\n" . '  DROP '
1672 4
                        . implode(',' . "\n" . '  DROP ', $dropped)
1673 4
                        . $alterFooter;
1674
                }
1675
1676
                // Generating auto-increment-related query.
1677 8
                if ($autoIncrement !== [] && $updateIndexesIncrements) {
1678 4
                    $sqlAutoIncrementsQuery = $alterHeader . "\n" . '  MODIFY '
1679 4
                        . implode(',' . "\n" . '  MODIFY ', $autoIncrement);
1680
                    if (
1681 4
                        isset($GLOBALS['sql_auto_increment'])
1682 4
                        && $statement->entityOptions->has('AUTO_INCREMENT') !== false
0 ignored issues
show
Bug introduced by
The method has() does not exist on null. ( Ignorable by Annotation )

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

1682
                        && $statement->entityOptions->/** @scrutinizer ignore-call */ has('AUTO_INCREMENT') !== false

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1683 4
                        && (! isset($GLOBALS['table_data']) || in_array($table, $GLOBALS['table_data']))
1684
                    ) {
1685
                        $sqlAutoIncrementsQuery .= ', AUTO_INCREMENT='
1686
                            . $statement->entityOptions->has('AUTO_INCREMENT');
1687
                    }
1688
1689 4
                    $sqlAutoIncrementsQuery .= ';' . "\n";
1690
1691 4
                    $this->sqlAutoIncrements = $this->generateComment(
1692 4
                        $this->sqlAutoIncrements,
1693 4
                        __('AUTO_INCREMENT for dumped tables'),
1694 4
                        __('AUTO_INCREMENT for table'),
1695 4
                        $tableAlias,
1696 4
                        $compat,
1697 4
                    ) . $sqlAutoIncrementsQuery;
1698
                }
1699
1700
                // Removing the `AUTO_INCREMENT` attribute from the `CREATE TABLE`
1701
                // too.
1702
                if (
1703 8
                    $statement->entityOptions !== null
1704 8
                    && (empty($GLOBALS['sql_if_not_exists'])
1705 8
                    || empty($GLOBALS['sql_auto_increment']))
1706
                ) {
1707 4
                    $statement->entityOptions->remove('AUTO_INCREMENT');
1708
                }
1709
1710
                // Rebuilding the query.
1711 8
                $createQuery = $statement->build();
1712
            }
1713
1714 8
            $schemaCreate .= $createQuery;
1715
        }
1716
1717
        // Restoring old mode.
1718 8
        Context::setMode($oldMode);
1719
1720 8
        return $warning . $schemaCreate . ($addSemicolon ? ';' . "\n" : '');
1721
    }
1722
1723
    /**
1724
     * Returns $table's comments, relations etc.
1725
     *
1726
     * @param string  $db         database name
1727
     * @param string  $table      table name
1728
     * @param bool    $doRelation whether to include relation comments
1729
     * @param bool    $doMime     whether to include mime comments
1730
     * @param mixed[] $aliases    Aliases of db/table/columns
1731
     *
1732
     * @return string resulting comments
1733
     */
1734 8
    private function getTableComments(
1735
        string $db,
1736
        string $table,
1737
        bool $doRelation = false,
1738
        bool $doMime = false,
1739
        array $aliases = [],
1740
    ): string {
1741 8
        $dbAlias = $db;
1742 8
        $tableAlias = $table;
1743 8
        $this->initAlias($aliases, $dbAlias, $tableAlias);
1744
1745 8
        $relationParameters = $this->relation->getRelationParameters();
1746
1747 8
        $schemaCreate = '';
1748
1749
        // Check if we can use Relations
1750 8
        $foreigners = $this->relation->getRelationsAndStatus(
1751 8
            $doRelation && $relationParameters->relationFeature !== null,
1752 8
            $db,
1753 8
            $table,
1754 8
        );
1755
1756 8
        $mimeMap = null;
1757 8
        if ($doMime && $relationParameters->browserTransformationFeature !== null) {
1758 4
            $mimeMap = $this->transformations->getMime($db, $table, true);
1759
        }
1760
1761 8
        if ($mimeMap !== null && $mimeMap !== []) {
1762 4
            $schemaCreate .= $this->possibleCRLF()
1763 4
                . $this->exportComment()
1764 4
                . $this->exportComment(
1765 4
                    __('MEDIA TYPES FOR TABLE') . ' '
1766 4
                    . Util::backquoteCompat($table, 'NONE', $this->useSqlBackquotes) . ':',
1767 4
                );
1768 4
            foreach ($mimeMap as $mimeField => $mime) {
1769 4
                $schemaCreate .= $this->exportComment(
1770 4
                    '  '
1771 4
                    . Util::backquoteCompat($mimeField, 'NONE', $this->useSqlBackquotes),
1772 4
                )
1773 4
                . $this->exportComment(
1774 4
                    '      '
1775 4
                    . Util::backquoteCompat(
1776 4
                        $mime['mimetype'],
1777 4
                        'NONE',
1778 4
                        $this->useSqlBackquotes,
1779 4
                    ),
1780 4
                );
1781
            }
1782
1783 4
            $schemaCreate .= $this->exportComment();
1784
        }
1785
1786 8
        if ($foreigners !== []) {
1787 4
            $schemaCreate .= $this->possibleCRLF()
1788 4
                . $this->exportComment()
1789 4
                . $this->exportComment(
1790 4
                    __('RELATIONSHIPS FOR TABLE') . ' '
1791 4
                    . Util::backquoteCompat($tableAlias, 'NONE', $this->useSqlBackquotes)
1792 4
                    . ':',
1793 4
                );
1794
1795 4
            foreach ($foreigners as $relField => $rel) {
1796 4
                if ($relField !== 'foreign_keys_data') {
1797 4
                    $relFieldAlias = ! empty(
1798 4
                        $aliases[$db]['tables'][$table]['columns'][$relField]
1799
                    ) ? $aliases[$db]['tables'][$table]['columns'][$relField]
1800 4
                        : $relField;
1801 4
                    $schemaCreate .= $this->exportComment(
1802 4
                        '  '
1803 4
                        . Util::backquoteCompat(
1804 4
                            $relFieldAlias,
1805 4
                            'NONE',
1806 4
                            $this->useSqlBackquotes,
1807 4
                        ),
1808 4
                    )
1809 4
                    . $this->exportComment(
1810 4
                        '      '
1811 4
                        . Util::backquoteCompat(
1812 4
                            $rel['foreign_table'],
1813 4
                            'NONE',
1814 4
                            $this->useSqlBackquotes,
1815 4
                        )
1816 4
                        . ' -> '
1817 4
                        . Util::backquoteCompat(
1818 4
                            $rel['foreign_field'],
1819 4
                            'NONE',
1820 4
                            $this->useSqlBackquotes,
1821 4
                        ),
1822 4
                    );
1823
                } else {
1824
                    foreach ($rel as $oneKey) {
1825
                        foreach ($oneKey['index_list'] as $index => $field) {
1826
                            $relFieldAlias = ! empty(
1827
                                $aliases[$db]['tables'][$table]['columns'][$field]
1828
                            ) ? $aliases[$db]['tables'][$table]['columns'][$field]
1829
                                : $field;
1830
                            $schemaCreate .= $this->exportComment(
1831
                                '  '
1832
                                . Util::backquoteCompat(
1833
                                    $relFieldAlias,
1834
                                    'NONE',
1835
                                    $this->useSqlBackquotes,
1836
                                ),
1837
                            )
1838
                            . $this->exportComment(
1839
                                '      '
1840
                                . Util::backquoteCompat(
1841
                                    $oneKey['ref_table_name'],
1842
                                    'NONE',
1843
                                    $this->useSqlBackquotes,
1844
                                )
1845
                                . ' -> '
1846
                                . Util::backquoteCompat(
1847
                                    $oneKey['ref_index_list'][$index],
1848
                                    'NONE',
1849
                                    $this->useSqlBackquotes,
1850
                                ),
1851
                            );
1852
                        }
1853
                    }
1854
                }
1855
            }
1856
1857 4
            $schemaCreate .= $this->exportComment();
1858
        }
1859
1860 8
        return $schemaCreate;
1861
    }
1862
1863
    /**
1864
     * Outputs a raw query
1865
     *
1866
     * @param string      $errorUrl the url to go back in case of error
1867
     * @param string|null $db       the database where the query is executed
1868
     * @param string      $sqlQuery the rawquery to output
1869
     */
1870
    public function exportRawQuery(string $errorUrl, string|null $db, string $sqlQuery): bool
1871
    {
1872
        if ($db !== null) {
1873
            DatabaseInterface::getInstance()->selectDb($db);
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

1873
            /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance()->selectDb($db);

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

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

Loading history...
1874
        }
1875
1876
        return $this->exportData($db ?? '', '', $errorUrl, $sqlQuery);
1877
    }
1878
1879
    /**
1880
     * Outputs table's structure
1881
     *
1882
     * @param string  $db         database name
1883
     * @param string  $table      table name
1884
     * @param string  $exportMode 'create_table', 'triggers', 'create_view', 'stand_in'
1885
     * @param string  $exportType 'server', 'database', 'table'
1886
     * @param bool    $doRelation whether to include relation comments
1887
     * @param bool    $doComments whether to include the pmadb-style column
1888
     *                            comments as comments in the structure; this is
1889
     *                            deprecated but the parameter is left here
1890
     *                            because /export calls exportStructure()
1891
     *                            also for other export types which use this
1892
     *                            parameter
1893
     * @param bool    $doMime     whether to include mime comments
1894
     * @param bool    $dates      whether to include creation/update/check dates
1895
     * @param mixed[] $aliases    Aliases of db/table/columns
1896
     */
1897 4
    public function exportStructure(
1898
        string $db,
1899
        string $table,
1900
        string $exportMode,
1901
        string $exportType,
1902
        bool $doRelation = false,
1903
        bool $doComments = false,
1904
        bool $doMime = false,
1905
        bool $dates = false,
1906
        array $aliases = [],
1907
    ): bool {
1908 4
        $dbAlias = $db;
1909 4
        $tableAlias = $table;
1910 4
        $this->initAlias($aliases, $dbAlias, $tableAlias);
1911 4
        $compat = $GLOBALS['sql_compatibility'] ?? 'NONE';
1912
1913 4
        $formattedTableName = Util::backquoteCompat($tableAlias, $compat, $this->useSqlBackquotes);
1914 4
        $dump = $this->possibleCRLF()
1915 4
            . $this->exportComment(str_repeat('-', 56))
1916 4
            . $this->possibleCRLF()
1917 4
            . $this->exportComment();
1918
1919
        switch ($exportMode) {
1920 4
            case 'create_table':
1921 4
                $dump .= $this->exportComment(
1922 4
                    __('Table structure for table') . ' ' . $formattedTableName,
1923 4
                );
1924 4
                $dump .= $this->exportComment();
1925 4
                $dump .= $this->getTableDef($db, $table, $dates, true, false, true, $aliases);
1926 4
                $dump .= $this->getTableComments($db, $table, $doRelation, $doMime, $aliases);
1927 4
                break;
1928 4
            case 'triggers':
1929 4
                $dump = '';
1930 4
                $delimiter = '$$';
1931 4
                $triggers = Triggers::getDetails(DatabaseInterface::getInstance(), $db, $table);
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

1931
                $triggers = Triggers::getDetails(/** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance(), $db, $table);

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

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

Loading history...
1932 4
                if ($triggers !== []) {
1933 4
                    $dump .= $this->possibleCRLF()
1934 4
                    . $this->exportComment()
1935 4
                    . $this->exportComment(
1936 4
                        __('Triggers') . ' ' . $formattedTableName,
1937 4
                    )
1938 4
                        . $this->exportComment();
1939 4
                    $usedAlias = false;
1940 4
                    $triggerQuery = '';
1941 4
                    foreach ($triggers as $trigger) {
1942 4
                        if (! empty($GLOBALS['sql_drop_table'])) {
1943 4
                            $triggerQuery .= $trigger->getDropSql() . ';' . "\n";
1944
                        }
1945
1946 4
                        $triggerQuery .= 'DELIMITER ' . $delimiter . "\n";
1947 4
                        $triggerQuery .= $this->replaceWithAliases(
1948 4
                            $delimiter,
1949 4
                            $trigger->getCreateSql($delimiter),
1950 4
                            $aliases,
1951 4
                            $db,
1952 4
                            $flag,
1953 4
                        );
1954 4
                        if ($flag) {
1955
                            $usedAlias = true;
1956
                        }
1957
1958 4
                        $triggerQuery .= $delimiter . "\n" . 'DELIMITER ;' . "\n";
1959
                    }
1960
1961
                    // One warning per table.
1962 4
                    if ($usedAlias) {
1963
                        $dump .= $this->exportComment(
1964
                            __('It appears your table uses triggers;'),
1965
                        )
1966
                        . $this->exportComment(
1967
                            __('alias export may not work reliably in all cases.'),
1968
                        )
1969
                        . $this->exportComment();
1970
                    }
1971
1972 4
                    $dump .= $triggerQuery;
1973
                }
1974
1975 4
                break;
1976 4
            case 'create_view':
1977 4
                if (empty($GLOBALS['sql_views_as_tables'])) {
1978 4
                    $dump .= $this->exportComment(
1979 4
                        __('Structure for view')
1980 4
                        . ' '
1981 4
                        . $formattedTableName,
1982 4
                    )
1983 4
                    . $this->exportComment();
1984
                    // delete the stand-in table previously created (if any)
1985 4
                    if ($exportType !== 'table') {
1986 4
                        $dump .= 'DROP TABLE IF EXISTS '
1987 4
                            . Util::backquote($tableAlias) . ';' . "\n";
1988
                    }
1989
1990 4
                    $dump .= $this->getTableDef($db, $table, $dates, true, true, true, $aliases);
1991
                } else {
1992 4
                    $dump .= $this->exportComment(
1993 4
                        sprintf(
1994 4
                            __('Structure for view %s exported as a table'),
1995 4
                            $formattedTableName,
1996 4
                        ),
1997 4
                    )
1998 4
                    . $this->exportComment();
1999
                    // delete the stand-in table previously created (if any)
2000 4
                    if ($exportType !== 'table') {
2001 4
                        $dump .= 'DROP TABLE IF EXISTS '
2002 4
                        . Util::backquote($tableAlias) . ';' . "\n";
2003
                    }
2004
2005 4
                    $dump .= $this->getTableDefForView($db, $table, $aliases);
2006
                }
2007
2008 4
                if (empty($GLOBALS['sql_views_as_tables'])) {
2009
                    // Save views, to be inserted after indexes
2010
                    // in case the view uses USE INDEX syntax
2011 4
                    $this->sqlViews .= $dump;
2012 4
                    $dump = '';
2013
                }
2014
2015 4
                break;
2016 4
            case 'stand_in':
2017 4
                $dump .= $this->exportComment(
2018 4
                    __('Stand-in structure for view') . ' ' . $formattedTableName,
2019 4
                )
2020 4
                    . $this->exportComment(
2021 4
                        __('(See below for the actual view)'),
2022 4
                    )
2023 4
                    . $this->exportComment();
2024
                // export a stand-in definition to resolve view dependencies
2025 4
                $dump .= $this->getTableDefStandIn($db, $table, $aliases);
2026
        }
2027
2028
        // this one is built by getTableDef() to use in table copy/move
2029
        // but not in the case of export
2030 4
        $this->sqlConstraintsQuery = '';
2031
2032 4
        return $this->export->outputHandler($dump);
2033
    }
2034
2035
    /**
2036
     * Outputs the content of a table in SQL format
2037
     *
2038
     * @param string  $db       database name
2039
     * @param string  $table    table name
2040
     * @param string  $errorUrl the url to go back in case of error
2041
     * @param string  $sqlQuery SQL query for obtaining data
2042
     * @param mixed[] $aliases  Aliases of db/table/columns
2043
     */
2044 16
    public function exportData(
2045
        string $db,
2046
        string $table,
2047
        string $errorUrl,
2048
        string $sqlQuery,
2049
        array $aliases = [],
2050
    ): bool {
2051 16
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

2051
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

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

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

Loading history...
2052
        // Do not export data for merge tables
2053 16
        if ($dbi->getTable($db, $table)->isMerge()) {
2054
            return true;
2055
        }
2056
2057 16
        $dbAlias = $db;
2058 16
        $tableAlias = $table;
2059 16
        $this->initAlias($aliases, $dbAlias, $tableAlias);
2060
2061 16
        $compat = $GLOBALS['sql_compatibility'] ?? 'NONE';
2062
2063 16
        $formattedTableName = Util::backquoteCompat($tableAlias, $compat, $this->useSqlBackquotes);
2064
2065
        // Do not export data for a VIEW, unless asked to export the view as a table
2066
        // (For a VIEW, this is called only when exporting a single VIEW)
2067 16
        if ($dbi->getTable($db, $table)->isView() && empty($GLOBALS['sql_views_as_tables'])) {
2068 4
            $head = $this->possibleCRLF()
2069 4
                . $this->exportComment()
2070 4
                . $this->exportComment('VIEW ' . $formattedTableName)
2071 4
                . $this->exportComment(__('Data:') . ' ' . __('None'))
2072 4
                . $this->exportComment()
2073 4
                . $this->possibleCRLF();
2074
2075 4
            return $this->export->outputHandler($head);
2076
        }
2077
2078 12
        $result = $dbi->tryQuery($sqlQuery, ConnectionType::User, DatabaseInterface::QUERY_UNBUFFERED);
2079
        // a possible error: the table has crashed
2080 12
        $tmpError = $dbi->getError();
2081 12
        if ($tmpError !== '') {
2082 4
            $message = sprintf(__('Error reading data for table %s:'), $db . '.' . $table);
2083 4
            $message .= ' ' . $tmpError;
2084 4
            if (! defined('TESTSUITE')) {
2085
                trigger_error($message, E_USER_ERROR);
2086
            }
2087
2088 4
            return $this->export->outputHandler(
2089 4
                $this->exportComment($message),
2090 4
            );
2091
        }
2092
2093 8
        if ($result === false) {
2094
            return true;
2095
        }
2096
2097 8
        $fieldsCnt = $result->numFields();
2098
2099
        // Get field information
2100 8
        $fieldsMeta = $dbi->getFieldsMeta($result);
2101
2102 8
        $fieldSet = [];
2103
        /** @infection-ignore-all */
2104 8
        for ($j = 0; $j < $fieldsCnt; $j++) {
2105 8
            $colAs = $fieldsMeta[$j]->name;
2106 8
            if (! empty($aliases[$db]['tables'][$table]['columns'][$colAs])) {
2107
                $colAs = $aliases[$db]['tables'][$table]['columns'][$colAs];
2108
            }
2109
2110 8
            $fieldSet[$j] = Util::backquoteCompat($colAs, $compat, $this->useSqlBackquotes);
2111
        }
2112
2113 8
        if (isset($GLOBALS['sql_type']) && $GLOBALS['sql_type'] === 'UPDATE') {
2114
            // update
2115 4
            $schemaInsert = 'UPDATE ';
2116 4
            if (isset($GLOBALS['sql_ignore'])) {
2117 4
                $schemaInsert .= 'IGNORE ';
2118
            }
2119
2120
            // avoid EOL blank
2121 4
            $schemaInsert .= Util::backquoteCompat($tableAlias, $compat, $this->useSqlBackquotes) . ' SET';
2122
        } else {
2123
            // insert or replace
2124 4
            if (isset($GLOBALS['sql_type']) && $GLOBALS['sql_type'] === 'REPLACE') {
2125
                $sqlCommand = 'REPLACE';
2126
            } else {
2127 4
                $sqlCommand = 'INSERT';
2128
            }
2129
2130
            // delayed inserts?
2131 4
            if (isset($GLOBALS['sql_delayed'])) {
2132 4
                $insertDelayed = ' DELAYED';
2133
            } else {
2134
                $insertDelayed = '';
2135
            }
2136
2137
            // insert ignore?
2138 4
            if (isset($GLOBALS['sql_type'], $GLOBALS['sql_ignore']) && $GLOBALS['sql_type'] === 'INSERT') {
2139 4
                $insertDelayed .= ' IGNORE';
2140
            }
2141
2142
            //truncate table before insert
2143 4
            if (isset($GLOBALS['sql_truncate']) && $GLOBALS['sql_truncate'] && $sqlCommand === 'INSERT') {
2144 4
                $truncate = 'TRUNCATE TABLE '
2145 4
                    . Util::backquoteCompat($tableAlias, $compat, $this->useSqlBackquotes) . ';';
2146 4
                $truncatehead = $this->possibleCRLF()
2147 4
                    . $this->exportComment()
2148 4
                    . $this->exportComment(
2149 4
                        __('Truncate table before insert') . ' '
2150 4
                        . $formattedTableName,
2151 4
                    )
2152 4
                    . $this->exportComment()
2153 4
                    . "\n";
2154 4
                $this->export->outputHandler($truncatehead);
2155 4
                $this->export->outputHandler($truncate);
2156
            }
2157
2158
            // scheme for inserting fields
2159 4
            if ($GLOBALS['sql_insert_syntax'] === 'complete' || $GLOBALS['sql_insert_syntax'] === 'both') {
2160 4
                $fields = implode(', ', $fieldSet);
2161 4
                $schemaInsert = $sqlCommand . $insertDelayed . ' INTO '
2162 4
                    . Util::backquoteCompat($tableAlias, $compat, $this->useSqlBackquotes)
2163 4
                    . ' (' . $fields . ') VALUES'; // avoid EOL blank
2164
            } else {
2165
                $schemaInsert = $sqlCommand . $insertDelayed . ' INTO '
2166
                    . Util::backquoteCompat($tableAlias, $compat, $this->useSqlBackquotes)
2167
                    . ' VALUES';
2168
            }
2169
        }
2170
2171
        //\x08\\x09, not required
2172 8
        $currentRow = 0;
2173 8
        $querySize = 0;
2174
        if (
2175 8
            ($GLOBALS['sql_insert_syntax'] === 'extended'
2176 8
            || $GLOBALS['sql_insert_syntax'] === 'both')
2177 8
            && (! isset($GLOBALS['sql_type'])
2178 8
            || $GLOBALS['sql_type'] !== 'UPDATE')
2179
        ) {
2180 4
            $separator = ',';
2181 4
            $schemaInsert .= "\n";
2182
        } else {
2183 4
            $separator = ';';
2184
        }
2185
2186 8
        while ($row = $result->fetchRow()) {
2187 8
            if ($currentRow === 0) {
2188 8
                $head = $this->possibleCRLF()
2189 8
                    . $this->exportComment()
2190 8
                    . $this->exportComment(
2191 8
                        __('Dumping data for table') . ' '
2192 8
                        . $formattedTableName,
2193 8
                    )
2194 8
                    . $this->exportComment()
2195 8
                    . "\n";
2196 8
                if (! $this->export->outputHandler($head)) {
2197
                    return false;
2198
                }
2199
            }
2200
2201
            // We need to SET IDENTITY_INSERT ON for MSSQL
2202
            if (
2203 8
                $currentRow === 0
2204 8
                && isset($GLOBALS['sql_compatibility'])
2205 8
                && $GLOBALS['sql_compatibility'] === 'MSSQL'
2206
            ) {
2207
                if (
2208 8
                    ! $this->export->outputHandler(
2209 8
                        'SET IDENTITY_INSERT '
2210 8
                        . Util::backquoteCompat(
2211 8
                            $tableAlias,
2212 8
                            $compat,
2213 8
                            $this->useSqlBackquotes,
2214 8
                        )
2215 8
                        . ' ON ;' . "\n",
2216 8
                    )
2217
                ) {
2218
                    return false;
2219
                }
2220
            }
2221
2222 8
            $currentRow++;
2223 8
            $values = [];
2224 8
            foreach ($fieldsMeta as $j => $metaInfo) {
2225
                // NULL
2226 8
                if ($row[$j] === null) {
2227 8
                    $values[] = 'NULL';
2228
                } elseif (
2229 4
                    $metaInfo->isNumeric
2230
                ) {
2231
                    // a number
2232
                    $values[] = $row[$j];
2233 4
                } elseif ($metaInfo->isBinary && isset($GLOBALS['sql_hex_for_binary'])) {
2234
                    // a true BLOB
2235
                    // - mysqldump only generates hex data when the --hex-blob
2236
                    //   option is used, for fields having the binary attribute
2237
                    //   no hex is generated
2238
                    // - a TEXT field returns type blob but a real blob
2239
                    //   returns also the 'binary' flag
2240
2241
                    // empty blobs need to be different, but '0' is also empty
2242
                    // :-(
2243 4
                    if (empty($row[$j]) && $row[$j] != '0') {
2244
                        $values[] = '\'\'';
2245
                    } else {
2246 4
                        $values[] = '0x' . bin2hex($row[$j]);
2247
                    }
2248 4
                } elseif ($metaInfo->isMappedTypeBit) {
2249
                    // detection of 'bit' works only on mysqli extension
2250
                    $values[] = "b'" . Util::printableBitValue((int) $row[$j], $metaInfo->length) . "'";
2251 4
                } elseif ($metaInfo->isMappedTypeGeometry) {
2252
                    // export GIS types as hex
2253
                    $values[] = '0x' . bin2hex($row[$j]);
0 ignored issues
show
Bug introduced by
It seems like $row[$j] can also be of type null; however, parameter $string of bin2hex() 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

2253
                    $values[] = '0x' . bin2hex(/** @scrutinizer ignore-type */ $row[$j]);
Loading history...
2254 4
                } elseif (! empty($GLOBALS['exporting_metadata']) && $row[$j] === '@LAST_PAGE') {
2255
                    $values[] = '@LAST_PAGE';
2256 4
                } elseif ($row[$j] === '') {
2257
                    $values[] = "''";
2258
                } else {
2259
                    // something else -> treat as a string
2260 4
                    $values[] = $dbi->quoteString($row[$j]);
0 ignored issues
show
Bug introduced by
It seems like $row[$j] can also be of type null; however, parameter $str of PhpMyAdmin\DatabaseInterface::quoteString() 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

2260
                    $values[] = $dbi->quoteString(/** @scrutinizer ignore-type */ $row[$j]);
Loading history...
2261
                }
2262
            }
2263
2264
            // should we make update?
2265 8
            if (isset($GLOBALS['sql_type']) && $GLOBALS['sql_type'] === 'UPDATE') {
2266 4
                $insertLine = $schemaInsert;
2267
                /** @infection-ignore-all */
2268 4
                for ($i = 0; $i < $fieldsCnt; $i++) {
2269 4
                    if ($i === 0) {
2270 4
                        $insertLine .= ' ';
2271
                    }
2272
2273 4
                    if ($i > 0) {
2274
                        // avoid EOL blank
2275 4
                        $insertLine .= ',';
2276
                    }
2277
2278 4
                    $insertLine .= $fieldSet[$i] . ' = ' . $values[$i];
2279
                }
2280
2281 4
                $insertLine .= ' WHERE ' . (new UniqueCondition($fieldsMeta, $row))->getWhereClause();
2282 4
            } elseif ($GLOBALS['sql_insert_syntax'] === 'extended' || $GLOBALS['sql_insert_syntax'] === 'both') {
2283
                // Extended inserts case
2284 4
                if ($currentRow === 1) {
2285 4
                    $insertLine = $schemaInsert . '('
2286 4
                        . implode(', ', $values) . ')';
2287
                } else {
2288
                    $insertLine = '(' . implode(', ', $values) . ')';
2289
                    $insertLineSize = mb_strlen($insertLine);
2290
                    $sqlMaxSize = $GLOBALS['sql_max_query_size'];
2291
                    if ($sqlMaxSize > 0 && $querySize + $insertLineSize > $sqlMaxSize) {
2292
                        if (! $this->export->outputHandler(';' . "\n")) {
2293
                            return false;
2294
                        }
2295
2296
                        $querySize = 0;
2297
                        $currentRow = 1;
2298
                        $insertLine = $schemaInsert . $insertLine;
2299
                    }
2300
                }
2301
2302 4
                $querySize += mb_strlen($insertLine);
2303
            } else {
2304
                // Other inserts case
2305
                $insertLine = $schemaInsert . '(' . implode(', ', $values) . ')';
2306
            }
2307
2308 8
            if (! $this->export->outputHandler(($currentRow === 1 ? '' : $separator . "\n") . $insertLine)) {
2309
                return false;
2310
            }
2311
        }
2312
2313 8
        if ($currentRow > 0) {
2314 8
            if (! $this->export->outputHandler(';' . "\n")) {
2315
                return false;
2316
            }
2317
        }
2318
2319
        // We need to SET IDENTITY_INSERT OFF for MSSQL
2320
        if (
2321 8
            isset($GLOBALS['sql_compatibility'])
2322 8
            && $GLOBALS['sql_compatibility'] === 'MSSQL'
2323 8
            && $currentRow > 0
2324
        ) {
2325 8
            $outputSucceeded = $this->export->outputHandler(
2326 8
                "\n" . 'SET IDENTITY_INSERT '
2327 8
                . Util::backquoteCompat(
2328 8
                    $tableAlias,
2329 8
                    $compat,
2330 8
                    $this->useSqlBackquotes,
2331 8
                )
2332 8
                . ' OFF;' . "\n",
2333 8
            );
2334 8
            if (! $outputSucceeded) {
2335
                return false;
2336
            }
2337
        }
2338
2339 8
        return true;
2340
    }
2341
2342
    /**
2343
     * Make a create table statement compatible with MSSQL
2344
     *
2345
     * @param string $createQuery MySQL create table statement
2346
     *
2347
     * @return string MSSQL compatible create table statement
2348
     */
2349 16
    private function makeCreateTableMSSQLCompatible(string $createQuery): string
2350
    {
2351
        // In MSSQL
2352
        // 1. No 'IF NOT EXISTS' in CREATE TABLE
2353
        // 2. DATE field doesn't exists, we will use DATETIME instead
2354
        // 3. UNSIGNED attribute doesn't exist
2355
        // 4. No length on INT, TINYINT, SMALLINT, BIGINT and no precision on
2356
        //    FLOAT fields
2357
        // 5. No KEY and INDEX inside CREATE TABLE
2358
        // 6. DOUBLE field doesn't exists, we will use FLOAT instead
2359
2360 16
        $createQuery = (string) preg_replace('/^CREATE TABLE IF NOT EXISTS/', 'CREATE TABLE', $createQuery);
2361
        // first we need  to replace all lines ended with '" DATE ...,\n'
2362
        // last preg_replace preserve us from situation with date text
2363
        // inside DEFAULT field value
2364 16
        $createQuery = (string) preg_replace(
2365 16
            "/\" date DEFAULT NULL(,)?\n/",
2366 16
            '" datetime DEFAULT NULL$1' . "\n",
2367 16
            $createQuery,
2368 16
        );
2369 16
        $createQuery = (string) preg_replace("/\" date NOT NULL(,)?\n/", '" datetime NOT NULL$1' . "\n", $createQuery);
2370 16
        $createQuery = (string) preg_replace(
2371 16
            '/" date NOT NULL DEFAULT \'([^\'])/',
2372 16
            '" datetime NOT NULL DEFAULT \'$1',
2373 16
            $createQuery,
2374 16
        );
2375
2376
        // next we need to replace all lines ended with ') UNSIGNED ...,'
2377
        // last preg_replace preserve us from situation with unsigned text
2378
        // inside DEFAULT field value
2379 16
        $createQuery = (string) preg_replace("/\) unsigned NOT NULL(,)?\n/", ') NOT NULL$1' . "\n", $createQuery);
2380 16
        $createQuery = (string) preg_replace(
2381 16
            "/\) unsigned DEFAULT NULL(,)?\n/",
2382 16
            ') DEFAULT NULL$1' . "\n",
2383 16
            $createQuery,
2384 16
        );
2385 16
        $createQuery = (string) preg_replace(
2386 16
            '/\) unsigned NOT NULL DEFAULT \'([^\'])/',
2387 16
            ') NOT NULL DEFAULT \'$1',
2388 16
            $createQuery,
2389 16
        );
2390
2391
        // we need to replace all lines ended with
2392
        // '" INT|TINYINT([0-9]{1,}) ...,' last preg_replace preserve us
2393
        // from situation with int([0-9]{1,}) text inside DEFAULT field
2394
        // value
2395 16
        $createQuery = (string) preg_replace(
2396 16
            '/" (int|tinyint|smallint|bigint)\([0-9]+\) DEFAULT NULL(,)?\n/',
2397 16
            '" $1 DEFAULT NULL$2' . "\n",
2398 16
            $createQuery,
2399 16
        );
2400 16
        $createQuery = (string) preg_replace(
2401 16
            '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL(,)?\n/',
2402 16
            '" $1 NOT NULL$2' . "\n",
2403 16
            $createQuery,
2404 16
        );
2405 16
        $createQuery = (string) preg_replace(
2406 16
            '/" (int|tinyint|smallint|bigint)\([0-9]+\) NOT NULL DEFAULT \'([^\'])/',
2407 16
            '" $1 NOT NULL DEFAULT \'$2',
2408 16
            $createQuery,
2409 16
        );
2410
2411
        // we need to replace all lines ended with
2412
        // '" FLOAT|DOUBLE([0-9,]{1,}) ...,'
2413
        // last preg_replace preserve us from situation with
2414
        // float([0-9,]{1,}) text inside DEFAULT field value
2415 16
        $createQuery = (string) preg_replace(
2416 16
            '/" (float|double)(\([0-9]+,[0-9,]+\))? DEFAULT NULL(,)?\n/',
2417 16
            '" float DEFAULT NULL$3' . "\n",
2418 16
            $createQuery,
2419 16
        );
2420 16
        $createQuery = (string) preg_replace(
2421 16
            '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL(,)?\n/',
2422 16
            '" float NOT NULL$3' . "\n",
2423 16
            $createQuery,
2424 16
        );
2425
2426 16
        return (string) preg_replace(
2427 16
            '/" (float|double)(\([0-9,]+,[0-9,]+\))? NOT NULL DEFAULT \'([^\'])/',
2428 16
            '" float NOT NULL DEFAULT \'$3',
2429 16
            $createQuery,
2430 16
        );
2431
2432
        // @todo remove indexes from CREATE TABLE
2433
    }
2434
2435
    /**
2436
     * replaces db/table/column names with their aliases
2437
     *
2438
     * @param string|null $delimiter The delimiter for the parser (";" or "$$")
2439
     * @param string      $sqlQuery  SQL query in which aliases are to be substituted
2440
     * @param mixed[]     $aliases   Alias information for db/table/column
2441
     * @param string      $db        the database name
2442
     * @param bool|null   $flag      the flag denoting whether any replacement was done
2443
     *
2444
     * @return string query replaced with aliases
2445
     */
2446 16
    public function replaceWithAliases(
2447
        string|null $delimiter,
2448
        string $sqlQuery,
2449
        array $aliases,
2450
        string $db,
2451
        bool|null &$flag = null,
2452
    ): string {
2453 16
        $flag = false;
2454
2455
        /**
2456
         * The parser of this query.
2457
         */
2458 16
        $parser = new Parser(empty($delimiter) ? $sqlQuery : 'DELIMITER ' . $delimiter . "\n" . $sqlQuery);
2459
2460 16
        if (empty($parser->statements[0])) {
2461
            return $sqlQuery;
2462
        }
2463
2464
        /**
2465
         * The statement that represents the query.
2466
         *
2467
         * @var CreateStatement $statement
2468
         */
2469 16
        $statement = $parser->statements[0];
2470
2471
        /**
2472
         * Old database name.
2473
         */
2474 16
        $oldDatabase = $db;
2475
2476
        // Replacing aliases in `CREATE TABLE` statement.
2477 16
        if ($statement->options->has('TABLE')) {
2478
            // Extracting the name of the old database and table from the
2479
            // statement to make sure the parameters are correct.
2480 12
            if (! empty($statement->name->database)) {
2481
                $oldDatabase = $statement->name->database;
2482
            }
2483
2484
            /**
2485
             * Old table name.
2486
             */
2487 12
            $oldTable = $statement->name->table;
2488
2489
            // Finding the aliased database name.
2490
            // The database might be empty so we have to add a few checks.
2491 12
            $newDatabase = null;
2492 12
            if (! empty($statement->name->database)) {
2493
                $newDatabase = $statement->name->database;
2494
                if (! empty($aliases[$oldDatabase]['alias'])) {
2495
                    $newDatabase = $aliases[$oldDatabase]['alias'];
2496
                }
2497
            }
2498
2499
            // Finding the aliases table name.
2500 12
            $newTable = $oldTable;
2501 12
            if (! empty($aliases[$oldDatabase]['tables'][$oldTable]['alias'])) {
2502 4
                $newTable = $aliases[$oldDatabase]['tables'][$oldTable]['alias'];
2503
            }
2504
2505
            // Replacing new values.
2506 12
            if ($statement->name->database !== $newDatabase || $statement->name->table !== $newTable) {
2507 4
                $statement->name->database = $newDatabase;
2508 4
                $statement->name->table = $newTable;
2509 4
                $statement->name->expr = ''; // Force rebuild.
2510 4
                $flag = true;
2511
            }
2512
2513
            /** @var CreateDefinition[] $fields */
2514 12
            $fields = $statement->fields;
2515 12
            foreach ($fields as $field) {
2516
                // Column name.
2517
                if (
2518 12
                    $field->type !== null
2519 12
                    && ! empty($aliases[$oldDatabase]['tables'][$oldTable]['columns'][$field->name])
2520
                ) {
2521 4
                    $field->name = $aliases[$oldDatabase]['tables'][$oldTable]['columns'][$field->name];
2522 4
                    $flag = true;
2523
                }
2524
2525
                // Key's columns.
2526 12
                if ($field->key !== null) {
2527 12
                    foreach ($field->key->columns as $key => $column) {
2528 12
                        if (! isset($column['name'])) {
2529
                            // In case the column has no name field
2530
                            continue;
2531
                        }
2532
2533 12
                        if (empty($aliases[$oldDatabase]['tables'][$oldTable]['columns'][$column['name']])) {
2534 12
                            continue;
2535
                        }
2536
2537 4
                        $columnAliases = $aliases[$oldDatabase]['tables'][$oldTable]['columns'];
2538 4
                        $field->key->columns[$key]['name'] = $columnAliases[$column['name']];
2539 4
                        $flag = true;
2540
                    }
2541
                }
2542
2543
                // References.
2544 12
                if ($field->references === null) {
2545 12
                    continue;
2546
                }
2547
2548 8
                $refTable = $field->references->table->table;
2549
                // Replacing table.
2550 8
                if (! empty($aliases[$oldDatabase]['tables'][$refTable]['alias'])) {
2551
                    $field->references->table->table = $aliases[$oldDatabase]['tables'][$refTable]['alias'];
2552
                    $field->references->table->expr = '';
2553
                    $flag = true;
2554
                }
2555
2556
                // Replacing column names.
2557 8
                foreach ($field->references->columns as $key => $column) {
2558 8
                    if (empty($aliases[$oldDatabase]['tables'][$refTable]['columns'][$column])) {
2559 8
                        continue;
2560
                    }
2561
2562
                    $field->references->columns[$key] = $aliases[$oldDatabase]['tables'][$refTable]['columns'][$column];
2563
                    $flag = true;
2564
                }
2565
            }
2566 12
        } elseif ($statement->options->has('TRIGGER')) {
2567
            // Extracting the name of the old database and table from the
2568
            // statement to make sure the parameters are correct.
2569 8
            if (! empty($statement->table->database)) {
2570
                $oldDatabase = $statement->table->database;
2571
            }
2572
2573
            /**
2574
             * Old table name.
2575
             */
2576 8
            $oldTable = $statement->table->table;
2577
2578 8
            if (! empty($aliases[$oldDatabase]['tables'][$oldTable]['alias'])) {
2579 4
                $statement->table->table = $aliases[$oldDatabase]['tables'][$oldTable]['alias'];
2580 4
                $statement->table->expr = ''; // Force rebuild.
2581 4
                $flag = true;
2582
            }
2583
        }
2584
2585
        if (
2586 16
            $statement->options->has('TRIGGER')
2587 16
            || $statement->options->has('PROCEDURE')
2588 16
            || $statement->options->has('FUNCTION')
2589 16
            || $statement->options->has('VIEW')
2590
        ) {
2591
            // Replacing the body.
2592 12
            foreach ($statement->body as $token) {
2593
                // Replacing only symbols (that are not variables) and unknown identifiers.
2594 12
                $isSymbol = $token->type === TokenType::Symbol;
2595 12
                $isKeyword = $token->type === TokenType::Keyword;
2596 12
                $isNone = $token->type === TokenType::None;
2597 12
                $replaceToken = $isSymbol && ($token->flags & Token::FLAG_SYMBOL_VARIABLE) === 0
2598 12
                    || $isKeyword && ($token->flags & Token::FLAG_KEYWORD_RESERVED) === 0
2599 12
                    || $isNone;
2600 12
                if (! $replaceToken) {
2601 12
                    continue;
2602
                }
2603
2604 12
                $alias = $this->getAlias($aliases, $token->value);
2605 12
                if ($alias === '') {
2606 12
                    continue;
2607
                }
2608
2609
                // Replacing the token.
2610 4
                $token->token = Context::escape($alias);
2611 4
                $flag = true;
2612
            }
2613
        }
2614
2615 16
        return $statement->build();
2616
    }
2617
2618
    /**
2619
     * Generate comment
2620
     *
2621
     * @param string|null $sqlStatement SQL statement
2622
     * @param string      $comment1     Comment for dumped table
2623
     * @param string      $comment2     Comment for current table
2624
     * @param string      $tableAlias   Table alias
2625
     * @param string      $compat       Compatibility mode
2626
     */
2627 8
    protected function generateComment(
2628
        string|null $sqlStatement,
2629
        string $comment1,
2630
        string $comment2,
2631
        string $tableAlias,
2632
        string $compat,
2633
    ): string {
2634 8
        if ($sqlStatement === null) {
2635 8
            if (isset($GLOBALS['no_constraints_comments'])) {
2636
                $sqlStatement = '';
2637
            } else {
2638 8
                $sqlStatement = "\n"
2639 8
                    . $this->exportComment()
2640 8
                    . $this->exportComment($comment1)
2641 8
                    . $this->exportComment();
2642
            }
2643
        }
2644
2645
        // comments for current table
2646 8
        if (! isset($GLOBALS['no_constraints_comments'])) {
2647 8
            $sqlStatement .= "\n"
2648 8
                . $this->exportComment()
2649 8
                . $this->exportComment(
2650 8
                    $comment2 . ' ' . Util::backquoteCompat(
2651 8
                        $tableAlias,
2652 8
                        $compat,
2653 8
                        $this->useSqlBackquotes,
2654 8
                    ),
2655 8
                )
2656 8
                . $this->exportComment();
2657
        }
2658
2659 8
        return $sqlStatement;
2660
    }
2661
2662 12
    private function getTableStatus(string $db, string $table, bool $showDates): string
2663
    {
2664 12
        $newCrlf = "\n";
2665 12
        $schemaCreate = '';
2666
2667 12
        $dbi = DatabaseInterface::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\DatabaseInterface::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

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

2667
        $dbi = /** @scrutinizer ignore-deprecated */ DatabaseInterface::getInstance();

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

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

Loading history...
2668 12
        $result = $dbi->tryQuery(
2669 12
            'SHOW TABLE STATUS FROM ' . Util::backquote($db)
2670 12
            . ' WHERE Name = ' . $dbi->quoteString($table),
2671 12
        );
2672 12
        if ($result !== false && $result->numRows() > 0) {
2673 8
            $tmpres = $result->fetchAssoc();
2674
2675 8
            if ($showDates && ! empty($tmpres['Create_time'])) {
2676 4
                $schemaCreate .= $this->exportComment(
2677 4
                    __('Creation:') . ' '
2678 4
                    . Util::localisedDate(
2679 4
                        strtotime($tmpres['Create_time']),
0 ignored issues
show
Bug introduced by
It seems like $tmpres['Create_time'] can also be of type null; however, parameter $datetime of strtotime() 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

2679
                        strtotime(/** @scrutinizer ignore-type */ $tmpres['Create_time']),
Loading history...
2680 4
                    ),
2681 4
                );
2682 4
                $newCrlf = $this->exportComment() . "\n";
2683
            }
2684
2685 8
            if ($showDates && ! empty($tmpres['Update_time'])) {
2686 4
                $schemaCreate .= $this->exportComment(
2687 4
                    __('Last update:') . ' '
2688 4
                    . Util::localisedDate(
2689 4
                        strtotime($tmpres['Update_time']),
2690 4
                    ),
2691 4
                );
2692 4
                $newCrlf = $this->exportComment() . "\n";
2693
            }
2694
2695 8
            if ($showDates && ! empty($tmpres['Check_time'])) {
2696 4
                $schemaCreate .= $this->exportComment(
2697 4
                    __('Last check:') . ' '
2698 4
                    . Util::localisedDate(
2699 4
                        strtotime($tmpres['Check_time']),
2700 4
                    ),
2701 4
                );
2702 4
                $newCrlf = $this->exportComment() . "\n";
2703
            }
2704
        }
2705
2706 12
        return $schemaCreate . $newCrlf;
2707
    }
2708
2709
    /** @param string[] $compats */
2710 4
    private function addCompatOptions(array $compats, OptionsPropertyMainGroup $generalOptions): void
2711
    {
2712 4
        $values = [];
2713 4
        foreach ($compats as $val) {
2714 4
            $values[$val] = $val;
2715
        }
2716
2717 4
        $leaf = new SelectPropertyItem(
2718 4
            'compatibility',
2719 4
            __(
2720 4
                'Database system or older MySQL server to maximize output compatibility with:',
2721 4
            ),
2722 4
        );
2723 4
        $leaf->setValues($values);
2724 4
        $leaf->setDoc(
2725 4
            ['manual_MySQL_Database_Administration', 'Server_SQL_mode'],
2726 4
        );
2727 4
        $generalOptions->addProperty($leaf);
2728
    }
2729
}
2730