ExportSql::exportConfigurationMetadata()   D
last analyzed

Complexity

Conditions 15
Paths 52

Size

Total Lines 152
Code Lines 97

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 240

Importance

Changes 0
Metric Value
cc 15
eloc 97
nc 52
nop 3
dl 0
loc 152
ccs 0
cts 102
cp 0
crap 240
rs 4.7987
c 0
b 0
f 0

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
    /** @psalm-return non-empty-lowercase-string */
77
    public function getName(): string
78
    {
79
        return 'sql';
80
    }
81
82 100
    public function useSqlBackquotes(bool $useSqlBackquotes): void
83
    {
84 100
        $this->useSqlBackquotes = $useSqlBackquotes;
85
    }
86
87 100
    protected function setProperties(): ExportPluginProperties
88
    {
89 100
        $GLOBALS['plugin_param'] ??= null;
90
91 100
        $hideSql = false;
92 100
        $hideStructure = false;
93 100
        if ($GLOBALS['plugin_param']['export_type'] === 'table' && ! $GLOBALS['plugin_param']['single_table']) {
94 100
            $hideStructure = true;
95 100
            $hideSql = true;
96
        }
97
98
        // In case we have `raw_query` parameter set,
99
        // we initialize SQL option
100 100
        if (isset($_REQUEST['raw_query'])) {
101
            $hideStructure = false;
102
            $hideSql = false;
103
        }
104
105 100
        $exportPluginProperties = new ExportPluginProperties();
106 100
        $exportPluginProperties->setText('SQL');
107 100
        $exportPluginProperties->setExtension('sql');
108 100
        $exportPluginProperties->setMimeType('text/x-sql');
109
110 100
        if ($hideSql) {
111 100
            return $exportPluginProperties;
112
        }
113
114 4
        $exportPluginProperties->setOptionsText(__('Options'));
115
116
        // create the root group that will be the options field for
117
        // $exportPluginProperties
118
        // this will be shown as "Format specific options"
119 4
        $exportSpecificOptions = new OptionsPropertyRootGroup('Format Specific Options');
120
121
        // general options main group
122 4
        $generalOptions = new OptionsPropertyMainGroup('general_opts');
123
124
        // comments
125 4
        $subgroup = new OptionsPropertySubgroup('include_comments');
126 4
        $leaf = new BoolPropertyItem(
127 4
            'include_comments',
128 4
            __(
129 4
                'Display comments <i>(includes info such as export timestamp, PHP version, and server version)</i>',
130 4
            ),
131 4
        );
132 4
        $subgroup->setSubgroupHeader($leaf);
133
134 4
        $leaf = new TextPropertyItem(
135 4
            'header_comment',
136 4
            __('Additional custom header comment (\n splits lines):'),
137 4
        );
138 4
        $subgroup->addProperty($leaf);
139 4
        $leaf = new BoolPropertyItem(
140 4
            'dates',
141 4
            __(
142 4
                'Include a timestamp of when databases were created, last updated, and last checked',
143 4
            ),
144 4
        );
145 4
        $subgroup->addProperty($leaf);
146 4
        $relationParameters = $this->relation->getRelationParameters();
147 4
        if ($relationParameters->relationFeature !== null) {
148 4
            $leaf = new BoolPropertyItem(
149 4
                'relation',
150 4
                __('Display foreign key relationships'),
151 4
            );
152 4
            $subgroup->addProperty($leaf);
153
        }
154
155 4
        if ($relationParameters->browserTransformationFeature !== null) {
156 4
            $leaf = new BoolPropertyItem(
157 4
                'mime',
158 4
                __('Display media types'),
159 4
            );
160 4
            $subgroup->addProperty($leaf);
161
        }
162
163 4
        $generalOptions->addProperty($subgroup);
164
165
        // enclose in a transaction
166 4
        $leaf = new BoolPropertyItem(
167 4
            'use_transaction',
168 4
            __('Enclose export in a transaction'),
169 4
        );
170 4
        $leaf->setDoc(
171 4
            ['programs', 'mysqldump', 'option_mysqldump_single-transaction'],
172 4
        );
173 4
        $generalOptions->addProperty($leaf);
174
175
        // disable foreign key checks
176 4
        $leaf = new BoolPropertyItem(
177 4
            'disable_fk',
178 4
            __('Disable foreign key checks'),
179 4
        );
180 4
        $leaf->setDoc(
181 4
            ['manual_MySQL_Database_Administration', 'server-system-variables', 'sysvar_foreign_key_checks'],
182 4
        );
183 4
        $generalOptions->addProperty($leaf);
184
185
        // export views as tables
186 4
        $leaf = new BoolPropertyItem(
187 4
            'views_as_tables',
188 4
            __('Export views as tables'),
189 4
        );
190 4
        $generalOptions->addProperty($leaf);
191
192
        // export metadata
193 4
        $leaf = new BoolPropertyItem(
194 4
            'metadata',
195 4
            __('Export metadata'),
196 4
        );
197 4
        $generalOptions->addProperty($leaf);
198
199 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

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

513
            $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...
514 4
            if ($type === 'FUNCTION') {
515 4
                $definition = Routines::getFunctionDefinition($dbi, $db, $routine);
516
            } else {
517 4
                $definition = Routines::getProcedureDefinition($dbi, $db, $routine);
518
            }
519
520 4
            $createQuery = $this->replaceWithAliases($delimiter, $definition, $aliases, $db, $flag);
521 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

521
            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...
522
                // Remove definer clause from routine definitions
523
                $parser = new Parser('DELIMITER ' . $delimiter . "\n" . $createQuery);
524
                $statement = $parser->statements[0];
525
                $statement->options->remove('DEFINER');
526
                $createQuery = $statement->build();
527
            }
528
529
            // One warning per database
530 4
            if ($flag) {
531
                $usedAlias = true;
532
            }
533
534 4
            $procQuery .= $createQuery . $delimiter . "\n\n";
535
        }
536
537 4
        if ($usedAlias) {
538
            $text .= $this->exportComment(
539
                __('It appears your database uses routines;'),
540
            )
541
            . $this->exportComment(
542
                __('alias export may not work reliably in all cases.'),
543
            )
544
            . $this->exportComment();
545
        }
546
547 4
        $text .= $procQuery;
548
549 4
        return $text;
550
    }
551
552
    /**
553
     * Exports routines (procedures and functions)
554
     *
555
     * @param string  $db      Database
556
     * @param mixed[] $aliases Aliases of db/table/columns
557
     */
558 4
    public function exportRoutines(string $db, array $aliases = []): bool
559
    {
560 4
        $dbAlias = $db;
561 4
        $this->initAlias($aliases, $dbAlias);
562
563 4
        $text = '';
564 4
        $delimiter = '$$';
565
566 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

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

680
            /** @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...
681
        }
682
683 4
        return $this->export->outputHandler($foot);
684
    }
685
686
    /**
687
     * Outputs export header. It is the first method to be called, so all
688
     * the required variables are initialized here.
689
     */
690 4
    public function exportHeader(): bool
691
    {
692 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

692
        $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...
693 4
        if (isset($GLOBALS['sql_compatibility'])) {
694 4
            $tmpCompat = $GLOBALS['sql_compatibility'];
695 4
            if ($tmpCompat === 'NONE') {
696 4
                $tmpCompat = '';
697
            }
698
699 4
            $dbi->tryQuery('SET SQL_MODE="' . $tmpCompat . '"');
700 4
            unset($tmpCompat);
701
        }
702
703 4
        $head = $this->exportComment('phpMyAdmin SQL Dump')
704 4
            . $this->exportComment('version ' . Version::VERSION)
705 4
            . $this->exportComment('https://www.phpmyadmin.net/')
706 4
            . $this->exportComment();
707 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

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

829
        $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...
830 4
        if (str_contains($collation, '_')) {
831 4
            $createQuery .= ' DEFAULT CHARACTER SET '
832 4
                . mb_substr(
833 4
                    $collation,
834 4
                    0,
835 4
                    (int) mb_strpos($collation, '_'),
836 4
                )
837 4
                . ' COLLATE ' . $collation;
838
        } else {
839 4
            $createQuery .= ' DEFAULT CHARACTER SET ' . $collation;
840
        }
841
842 4
        $createQuery .= ';' . "\n";
843 4
        if (! $this->export->outputHandler($createQuery)) {
844
            return false;
845
        }
846
847 4
        return $this->exportUseStatement($dbAlias, $compat);
848
    }
849
850
    /**
851
     * Outputs USE statement
852
     *
853
     * @param string $db     db to use
854
     * @param string $compat sql compatibility
855
     */
856 4
    private function exportUseStatement(string $db, string $compat): bool
857
    {
858 4
        if ($compat === 'NONE') {
859 4
            return $this->export->outputHandler(
860 4
                'USE '
861 4
                . Util::backquoteCompat(
862 4
                    $db,
863 4
                    $compat,
864 4
                    $this->useSqlBackquotes,
865 4
                )
866 4
                . ';' . "\n",
867 4
            );
868
        }
869
870
        return $this->export->outputHandler('USE ' . $db . ';' . "\n");
871
    }
872
873
    /**
874
     * Outputs database header
875
     *
876
     * @param string $db      Database name
877
     * @param string $dbAlias Alias of db
878
     */
879 4
    public function exportDBHeader(string $db, string $dbAlias = ''): bool
880
    {
881 4
        if ($dbAlias === '') {
882 4
            $dbAlias = $db;
883
        }
884
885 4
        $compat = $GLOBALS['sql_compatibility'] ?? 'NONE';
886
887 4
        $head = $this->exportComment()
888 4
            . $this->exportComment(
889 4
                __('Database:') . ' '
890 4
                . Util::backquoteCompat(
891 4
                    $dbAlias,
892 4
                    $compat,
893 4
                    $this->useSqlBackquotes,
894 4
                ),
895 4
            )
896 4
            . $this->exportComment();
897
898 4
        return $this->export->outputHandler($head);
899
    }
900
901
    /**
902
     * Outputs database footer
903
     *
904
     * @param string $db Database name
905
     */
906 4
    public function exportDBFooter(string $db): bool
907
    {
908 4
        $result = true;
909
910
        //add indexes to the sql dump file
911 4
        if (isset($GLOBALS['sql_indexes'])) {
912
            $result = $this->export->outputHandler($GLOBALS['sql_indexes']);
913
            unset($GLOBALS['sql_indexes']);
914
        }
915
916
        //add auto increments to the sql dump file
917 4
        if (isset($GLOBALS['sql_auto_increments'])) {
918
            $result = $this->export->outputHandler($GLOBALS['sql_auto_increments']);
919
            unset($GLOBALS['sql_auto_increments']);
920
        }
921
922
        //add constraints to the sql dump file
923 4
        if (isset($GLOBALS['sql_constraints'])) {
924 4
            $result = $this->export->outputHandler($GLOBALS['sql_constraints']);
925 4
            unset($GLOBALS['sql_constraints']);
926
        }
927
928 4
        return $result;
929
    }
930
931
    /**
932
     * Exports events
933
     *
934
     * @param string $db Database
935
     */
936 4
    public function exportEvents(string $db): bool
937
    {
938 4
        $text = '';
939 4
        $delimiter = '$$';
940
941 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

941
        $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...
942 4
        $eventNames = $dbi->fetchResult(
943 4
            'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE'
944 4
            . ' EVENT_SCHEMA= ' . $dbi->quoteString($db),
945 4
        );
946
947 4
        if ($eventNames !== []) {
948 4
            $text .= "\n"
949 4
                . 'DELIMITER ' . $delimiter . "\n";
950
951 4
            $text .= $this->exportComment()
952 4
                . $this->exportComment(__('Events'))
953 4
                . $this->exportComment();
954
955 4
            foreach ($eventNames as $eventName) {
956 4
                if (! empty($GLOBALS['sql_drop_table'])) {
957
                    $text .= 'DROP EVENT IF EXISTS '
958
                        . Util::backquote($eventName)
959
                        . $delimiter . "\n";
960
                }
961
962 4
                $eventDef = Events::getDefinition($dbi, $db, $eventName);
963
                if (
964 4
                    $eventDef !== null
965 4
                    && $eventDef !== ''
966 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

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

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

1225
        $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...
1226 8
        foreach ($columns as $columnName => $definition) {
1227 8
            $colAlias = $columnName;
1228 8
            if (! empty($aliases[$db]['tables'][$view]['columns'][$colAlias])) {
1229
                $colAlias = $aliases[$db]['tables'][$view]['columns'][$colAlias];
1230
            }
1231
1232 8
            $tmp[] = Util::backquote($colAlias) . ' ' . $definition['Type'] . "\n";
1233
        }
1234
1235 8
        return $createQuery . implode(',', $tmp) . ');' . "\n";
1236
    }
1237
1238
    /**
1239
     * Returns CREATE definition that matches $view's structure
1240
     *
1241
     * @param string  $db      the database name
1242
     * @param string  $view    the view name
1243
     * @param mixed[] $aliases Aliases of db/table/columns
1244
     *
1245
     * @return string resulting schema
1246
     */
1247 8
    private function getTableDefForView(
1248
        string $db,
1249
        string $view,
1250
        array $aliases = [],
1251
    ): string {
1252 8
        $dbAlias = $db;
1253 8
        $viewAlias = $view;
1254 8
        $this->initAlias($aliases, $dbAlias, $viewAlias);
1255 8
        $createQuery = 'CREATE TABLE';
1256 8
        if (isset($GLOBALS['sql_if_not_exists'])) {
1257 4
            $createQuery .= ' IF NOT EXISTS ';
1258
        }
1259
1260 8
        $createQuery .= Util::backquote($viewAlias) . '(' . "\n";
1261
1262 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

1262
        $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...
1263 8
        $columns = $dbi->getColumns($db, $view, true);
1264
1265 8
        $firstCol = true;
1266 8
        foreach ($columns as $column) {
1267 8
            $colAlias = $column->field;
1268 8
            if (! empty($aliases[$db]['tables'][$view]['columns'][$colAlias])) {
1269
                $colAlias = $aliases[$db]['tables'][$view]['columns'][$colAlias];
1270
            }
1271
1272 8
            $extractedColumnspec = Util::extractColumnSpec($column->type);
1273
1274 8
            if (! $firstCol) {
1275 4
                $createQuery .= ',' . "\n";
1276
            }
1277
1278 8
            $createQuery .= '    ' . Util::backquote($colAlias);
1279 8
            $createQuery .= ' ' . $column->type;
1280 8
            if ($extractedColumnspec['can_contain_collation'] && ! empty($column->collation)) {
1281 4
                $createQuery .= ' COLLATE ' . $column->collation;
1282
            }
1283
1284 8
            if (! $column->isNull) {
1285 8
                $createQuery .= ' NOT NULL';
1286
            }
1287
1288 8
            if ($column->default !== null) {
1289 8
                $createQuery .= ' DEFAULT ' . $dbi->quoteString($column->default);
1290 4
            } elseif ($column->isNull) {
1291 4
                $createQuery .= ' DEFAULT NULL';
1292
            }
1293
1294 8
            if ($column->comment !== '') {
1295 4
                $createQuery .= ' COMMENT ' . $dbi->quoteString($column->comment);
1296
            }
1297
1298 8
            $firstCol = false;
1299
        }
1300
1301 8
        $createQuery .= "\n" . ');' . "\n";
1302
1303 8
        $compat = $GLOBALS['sql_compatibility'] ?? 'NONE';
1304
1305 8
        if ($compat === 'MSSQL') {
1306 4
            return $this->makeCreateTableMSSQLCompatible($createQuery);
1307
        }
1308
1309 8
        return $createQuery;
1310
    }
1311
1312
    /**
1313
     * Returns $table's CREATE definition
1314
     *
1315
     * @param string  $db                      the database name
1316
     * @param string  $table                   the table name
1317
     * @param bool    $showDates               whether to include creation/
1318
     *                                          update/check dates
1319
     * @param bool    $addSemicolon            whether to add semicolon and
1320
     *                                          end-of-line at the end
1321
     * @param bool    $view                    whether we're handling a view
1322
     * @param bool    $updateIndexesIncrements whether we need to update
1323
     *                                           two global variables
1324
     * @param mixed[] $aliases                 Aliases of db/table/columns
1325
     *
1326
     * @return string resulting schema
1327
     */
1328 12
    public function getTableDef(
1329
        string $db,
1330
        string $table,
1331
        bool $showDates = false,
1332
        bool $addSemicolon = true,
1333
        bool $view = false,
1334
        bool $updateIndexesIncrements = true,
1335
        array $aliases = [],
1336
    ): string {
1337 12
        $GLOBALS['sql_drop_table'] ??= null;
1338 12
        $GLOBALS['sql_constraints'] ??= null;
1339 12
        $GLOBALS['sql_constraints_query'] ??= null;
1340 12
        $GLOBALS['sql_indexes'] ??= null;
1341 12
        $GLOBALS['sql_indexes_query'] ??= null;
1342 12
        $GLOBALS['sql_auto_increments'] ??= null;
1343 12
        $GLOBALS['sql_drop_foreign_keys'] ??= null;
1344
1345 12
        $dbAlias = $db;
1346 12
        $tableAlias = $table;
1347 12
        $this->initAlias($aliases, $dbAlias, $tableAlias);
1348
1349 12
        $compat = $GLOBALS['sql_compatibility'] ?? 'NONE';
1350
1351 12
        $schemaCreate = $this->getTableStatus($db, $table, $showDates);
1352
1353 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

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

1450
                    /** @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...
1451 4
                    || isset($GLOBALS['sql_view_current_user'])
1452
                ) {
1453
                    $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

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

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

1863
            /** @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...
1864
        }
1865
1866
        return $this->exportData($db ?? '', '', $errorUrl, $sqlQuery);
1867
    }
1868
1869
    /**
1870
     * Outputs table's structure
1871
     *
1872
     * @param string  $db         database name
1873
     * @param string  $table      table name
1874
     * @param string  $exportMode 'create_table', 'triggers', 'create_view', 'stand_in'
1875
     * @param string  $exportType 'server', 'database', 'table'
1876
     * @param bool    $doRelation whether to include relation comments
1877
     * @param bool    $doComments whether to include the pmadb-style column
1878
     *                            comments as comments in the structure; this is
1879
     *                            deprecated but the parameter is left here
1880
     *                            because /export calls exportStructure()
1881
     *                            also for other export types which use this
1882
     *                            parameter
1883
     * @param bool    $doMime     whether to include mime comments
1884
     * @param bool    $dates      whether to include creation/update/check dates
1885
     * @param mixed[] $aliases    Aliases of db/table/columns
1886
     */
1887 4
    public function exportStructure(
1888
        string $db,
1889
        string $table,
1890
        string $exportMode,
1891
        string $exportType,
1892
        bool $doRelation = false,
1893
        bool $doComments = false,
1894
        bool $doMime = false,
1895
        bool $dates = false,
1896
        array $aliases = [],
1897
    ): bool {
1898 4
        $dbAlias = $db;
1899 4
        $tableAlias = $table;
1900 4
        $this->initAlias($aliases, $dbAlias, $tableAlias);
1901 4
        $compat = $GLOBALS['sql_compatibility'] ?? 'NONE';
1902
1903 4
        $formattedTableName = Util::backquoteCompat($tableAlias, $compat, $this->useSqlBackquotes);
1904 4
        $dump = $this->possibleCRLF()
1905 4
            . $this->exportComment(str_repeat('-', 56))
1906 4
            . $this->possibleCRLF()
1907 4
            . $this->exportComment();
1908
1909
        switch ($exportMode) {
1910 4
            case 'create_table':
1911 4
                $dump .= $this->exportComment(
1912 4
                    __('Table structure for table') . ' ' . $formattedTableName,
1913 4
                );
1914 4
                $dump .= $this->exportComment();
1915 4
                $dump .= $this->getTableDef($db, $table, $dates, true, false, true, $aliases);
1916 4
                $dump .= $this->getTableComments($db, $table, $doRelation, $doMime, $aliases);
1917 4
                break;
1918 4
            case 'triggers':
1919 4
                $dump = '';
1920 4
                $delimiter = '$$';
1921 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

1921
                $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...
1922 4
                if ($triggers !== []) {
1923 4
                    $dump .= $this->possibleCRLF()
1924 4
                    . $this->exportComment()
1925 4
                    . $this->exportComment(
1926 4
                        __('Triggers') . ' ' . $formattedTableName,
1927 4
                    )
1928 4
                        . $this->exportComment();
1929 4
                    $usedAlias = false;
1930 4
                    $triggerQuery = '';
1931 4
                    foreach ($triggers as $trigger) {
1932 4
                        if (! empty($GLOBALS['sql_drop_table'])) {
1933 4
                            $triggerQuery .= $trigger->getDropSql() . ';' . "\n";
1934
                        }
1935
1936 4
                        $triggerQuery .= 'DELIMITER ' . $delimiter . "\n";
1937 4
                        $triggerQuery .= $this->replaceWithAliases(
1938 4
                            $delimiter,
1939 4
                            $trigger->getCreateSql($delimiter),
1940 4
                            $aliases,
1941 4
                            $db,
1942 4
                            $flag,
1943 4
                        );
1944 4
                        if ($flag) {
1945
                            $usedAlias = true;
1946
                        }
1947
1948 4
                        $triggerQuery .= $delimiter . "\n" . 'DELIMITER ;' . "\n";
1949
                    }
1950
1951
                    // One warning per table.
1952 4
                    if ($usedAlias) {
1953
                        $dump .= $this->exportComment(
1954
                            __('It appears your table uses triggers;'),
1955
                        )
1956
                        . $this->exportComment(
1957
                            __('alias export may not work reliably in all cases.'),
1958
                        )
1959
                        . $this->exportComment();
1960
                    }
1961
1962 4
                    $dump .= $triggerQuery;
1963
                }
1964
1965 4
                break;
1966 4
            case 'create_view':
1967 4
                if (empty($GLOBALS['sql_views_as_tables'])) {
1968 4
                    $dump .= $this->exportComment(
1969 4
                        __('Structure for view')
1970 4
                        . ' '
1971 4
                        . $formattedTableName,
1972 4
                    )
1973 4
                    . $this->exportComment();
1974
                    // delete the stand-in table previously created (if any)
1975 4
                    if ($exportType !== 'table') {
1976 4
                        $dump .= 'DROP TABLE IF EXISTS '
1977 4
                            . Util::backquote($tableAlias) . ';' . "\n";
1978
                    }
1979
1980 4
                    $dump .= $this->getTableDef($db, $table, $dates, true, true, true, $aliases);
1981
                } else {
1982 4
                    $dump .= $this->exportComment(
1983 4
                        sprintf(
1984 4
                            __('Structure for view %s exported as a table'),
1985 4
                            $formattedTableName,
1986 4
                        ),
1987 4
                    )
1988 4
                    . $this->exportComment();
1989
                    // delete the stand-in table previously created (if any)
1990 4
                    if ($exportType !== 'table') {
1991 4
                        $dump .= 'DROP TABLE IF EXISTS '
1992 4
                        . Util::backquote($tableAlias) . ';' . "\n";
1993
                    }
1994
1995 4
                    $dump .= $this->getTableDefForView($db, $table, $aliases);
1996
                }
1997
1998 4
                break;
1999 4
            case 'stand_in':
2000 4
                $dump .= $this->exportComment(
2001 4
                    __('Stand-in structure for view') . ' ' . $formattedTableName,
2002 4
                )
2003 4
                    . $this->exportComment(
2004 4
                        __('(See below for the actual view)'),
2005 4
                    )
2006 4
                    . $this->exportComment();
2007
                // export a stand-in definition to resolve view dependencies
2008 4
                $dump .= $this->getTableDefStandIn($db, $table, $aliases);
2009
        }
2010
2011
        // this one is built by getTableDef() to use in table copy/move
2012
        // but not in the case of export
2013 4
        unset($GLOBALS['sql_constraints_query']);
2014
2015 4
        return $this->export->outputHandler($dump);
2016
    }
2017
2018
    /**
2019
     * Outputs the content of a table in SQL format
2020
     *
2021
     * @param string  $db       database name
2022
     * @param string  $table    table name
2023
     * @param string  $errorUrl the url to go back in case of error
2024
     * @param string  $sqlQuery SQL query for obtaining data
2025
     * @param mixed[] $aliases  Aliases of db/table/columns
2026
     */
2027 16
    public function exportData(
2028
        string $db,
2029
        string $table,
2030
        string $errorUrl,
2031
        string $sqlQuery,
2032
        array $aliases = [],
2033
    ): bool {
2034 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

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

2236
                    $values[] = '0x' . bin2hex(/** @scrutinizer ignore-type */ $row[$j]);
Loading history...
2237 4
                } elseif (! empty($GLOBALS['exporting_metadata']) && $row[$j] === '@LAST_PAGE') {
2238
                    $values[] = '@LAST_PAGE';
2239 4
                } elseif ($row[$j] === '') {
2240
                    $values[] = "''";
2241
                } else {
2242
                    // something else -> treat as a string
2243 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

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

2650
        $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...
2651 12
        $result = $dbi->tryQuery(
2652 12
            'SHOW TABLE STATUS FROM ' . Util::backquote($db)
2653 12
            . ' WHERE Name = ' . $dbi->quoteString($table),
2654 12
        );
2655 12
        if ($result !== false && $result->numRows() > 0) {
2656 8
            $tmpres = $result->fetchAssoc();
2657
2658 8
            if ($showDates && ! empty($tmpres['Create_time'])) {
2659 4
                $schemaCreate .= $this->exportComment(
2660 4
                    __('Creation:') . ' '
2661 4
                    . Util::localisedDate(
2662 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

2662
                        strtotime(/** @scrutinizer ignore-type */ $tmpres['Create_time']),
Loading history...
2663 4
                    ),
2664 4
                );
2665 4
                $newCrlf = $this->exportComment() . "\n";
2666
            }
2667
2668 8
            if ($showDates && ! empty($tmpres['Update_time'])) {
2669 4
                $schemaCreate .= $this->exportComment(
2670 4
                    __('Last update:') . ' '
2671 4
                    . Util::localisedDate(
2672 4
                        strtotime($tmpres['Update_time']),
2673 4
                    ),
2674 4
                );
2675 4
                $newCrlf = $this->exportComment() . "\n";
2676
            }
2677
2678 8
            if ($showDates && ! empty($tmpres['Check_time'])) {
2679 4
                $schemaCreate .= $this->exportComment(
2680 4
                    __('Last check:') . ' '
2681 4
                    . Util::localisedDate(
2682 4
                        strtotime($tmpres['Check_time']),
2683 4
                    ),
2684 4
                );
2685 4
                $newCrlf = $this->exportComment() . "\n";
2686
            }
2687
        }
2688
2689 12
        return $schemaCreate . $newCrlf;
2690
    }
2691
2692
    /** @param string[] $compats */
2693 4
    private function addCompatOptions(array $compats, OptionsPropertyMainGroup $generalOptions): void
2694
    {
2695 4
        $values = [];
2696 4
        foreach ($compats as $val) {
2697 4
            $values[$val] = $val;
2698
        }
2699
2700 4
        $leaf = new SelectPropertyItem(
2701 4
            'compatibility',
2702 4
            __(
2703 4
                'Database system or older MySQL server to maximize output compatibility with:',
2704 4
            ),
2705 4
        );
2706 4
        $leaf->setValues($values);
2707 4
        $leaf->setDoc(
2708 4
            ['manual_MySQL_Database_Administration', 'Server_SQL_mode'],
2709 4
        );
2710 4
        $generalOptions->addProperty($leaf);
2711
    }
2712
}
2713