Completed
Push — master ( e6ae49...a7a749 )
by Maurício
01:42 queued 26s
created

Export::getHTMLForBackButton()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 18
nc 3
nop 3
dl 0
loc 27
ccs 0
cts 7
cp 0
crap 12
rs 9.6666
c 1
b 0
f 0
1
<?php
2
/**
3
 * function for the main export logic
4
 */
5
6
declare(strict_types=1);
7
8
namespace PhpMyAdmin;
9
10
use PhpMyAdmin\Controllers\Database\ExportController as DatabaseExportController;
11
use PhpMyAdmin\Controllers\Server\ExportController as ServerExportController;
12
use PhpMyAdmin\Controllers\Table\ExportController as TableExportController;
13
use PhpMyAdmin\Plugins\ExportPlugin;
14
use PhpMyAdmin\Plugins\SchemaPlugin;
15
16
use function __;
17
use function array_filter;
18
use function array_merge_recursive;
19
use function error_get_last;
20
use function fclose;
21
use function file_exists;
22
use function fopen;
23
use function function_exists;
24
use function fwrite;
25
use function gzencode;
26
use function header;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, PhpMyAdmin\header. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
27
use function htmlentities;
28
use function htmlspecialchars;
29
use function http_build_query;
30
use function implode;
31
use function in_array;
32
use function ini_get;
33
use function is_array;
34
use function is_file;
35
use function is_numeric;
36
use function is_string;
37
use function is_writable;
38
use function mb_strlen;
39
use function mb_strpos;
40
use function mb_strtolower;
41
use function mb_substr;
42
use function ob_list_handlers;
43
use function preg_match;
44
use function preg_replace;
45
use function strlen;
46
use function strtolower;
47
use function substr;
48
use function time;
49
use function trim;
50
51
/**
52
 * PhpMyAdmin\Export class
53
 */
54
class Export
55
{
56
    /** @var DatabaseInterface */
57
    private $dbi;
58
59
    /** @var mixed */
60
    public $dumpBuffer = '';
61
62
    /** @var int */
63
    public $dumpBufferLength = 0;
64
65
    /** @var array */
66
    public $dumpBufferObjects = [];
67
68
    /**
69
     * @param DatabaseInterface $dbi DatabaseInterface instance
70 8
     */
71
    public function __construct($dbi)
72 8
    {
73 2
        $this->dbi = $dbi;
74
    }
75
76
    /**
77
     * Sets a session variable upon a possible fatal error during export
78
     */
79
    public function shutdown(): void
80
    {
81
        $error = error_get_last();
82
        if ($error == null || ! mb_strpos($error['message'], 'execution time')) {
83
            return;
84
        }
85
86
        //set session variable to check if there was error while exporting
87
        $_SESSION['pma_export_error'] = $error['message'];
88
    }
89
90
    /**
91
     * Detect ob_gzhandler
92
     */
93
    public function isGzHandlerEnabled(): bool
94
    {
95
        /** @var string[] $handlers */
96
        $handlers = ob_list_handlers();
97
98
        return in_array('ob_gzhandler', $handlers);
99
    }
100
101
    /**
102
     * Detect whether gzencode is needed; it might not be needed if
103
     * the server is already compressing by itself
104
     */
105
    public function gzencodeNeeded(): bool
106
    {
107
        /*
108
         * We should gzencode only if the function exists
109
         * but we don't want to compress twice, therefore
110
         * gzencode only if transparent compression is not enabled
111
         * and gz compression was not asked via $cfg['OBGzip']
112
         * but transparent compression does not apply when saving to server
113
         */
114
        return function_exists('gzencode')
115
            && ((! ini_get('zlib.output_compression')
116
                    && ! $this->isGzHandlerEnabled())
117
                || $GLOBALS['save_on_server']
118
                || $GLOBALS['config']->get('PMA_USR_BROWSER_AGENT') === 'CHROME');
119
    }
120
121
    /**
122
     * Output handler for all exports, if needed buffering, it stores data into
123
     * $this->dumpBuffer, otherwise it prints them out.
124
     *
125
     * @param string $line the insert statement
126
     */
127
    public function outputHandler(?string $line): bool
128
    {
129
        $GLOBALS['time_start'] = $GLOBALS['time_start'] ?? null;
130
        $GLOBALS['save_filename'] = $GLOBALS['save_filename'] ?? null;
131
132
        // Kanji encoding convert feature
133
        if ($GLOBALS['output_kanji_conversion']) {
134
            $line = Encoding::kanjiStrConv($line, $GLOBALS['knjenc'], $GLOBALS['xkana'] ?? '');
0 ignored issues
show
Bug introduced by
It seems like $line can also be of type null; however, parameter $str of PhpMyAdmin\Encoding::kanjiStrConv() 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

134
            $line = Encoding::kanjiStrConv(/** @scrutinizer ignore-type */ $line, $GLOBALS['knjenc'], $GLOBALS['xkana'] ?? '');
Loading history...
135
        }
136
137
        // If we have to buffer data, we will perform everything at once at the end
138
        if ($GLOBALS['buffer_needed']) {
139
            $this->dumpBuffer .= $line;
140
            if ($GLOBALS['onfly_compression']) {
141
                $this->dumpBufferLength += strlen((string) $line);
142
143
                if ($this->dumpBufferLength > $GLOBALS['memory_limit']) {
144
                    if ($GLOBALS['output_charset_conversion']) {
145
                        $this->dumpBuffer = Encoding::convertString('utf-8', $GLOBALS['charset'], $this->dumpBuffer);
146
                    }
147
148
                    if ($GLOBALS['compression'] === 'gzip' && $this->gzencodeNeeded()) {
149
                        // as a gzipped file
150
                        // without the optional parameter level because it bugs
151
                        $this->dumpBuffer = gzencode($this->dumpBuffer);
152
                    }
153
154
                    if ($GLOBALS['save_on_server']) {
155
                        $writeResult = @fwrite($GLOBALS['file_handle'], (string) $this->dumpBuffer);
156
                        // Here, use strlen rather than mb_strlen to get the length
157
                        // in bytes to compare against the number of bytes written.
158
                        if ($writeResult != strlen((string) $this->dumpBuffer)) {
159
                            $GLOBALS['message'] = Message::error(
160
                                __('Insufficient space to save the file %s.')
161
                            );
162
                            $GLOBALS['message']->addParam($GLOBALS['save_filename']);
163
164
                            return false;
165
                        }
166
                    } else {
167
                        echo $this->dumpBuffer;
168
                    }
169
170
                    $this->dumpBuffer = '';
171
                    $this->dumpBufferLength = 0;
172
                }
173
            } else {
174
                $timeNow = time();
175
                if ($GLOBALS['time_start'] >= $timeNow + 30) {
176
                    $GLOBALS['time_start'] = $timeNow;
177
                    header('X-pmaPing: Pong');
178
                }
179
            }
180
        } elseif ($GLOBALS['asfile']) {
181
            if ($GLOBALS['output_charset_conversion']) {
182
                $line = Encoding::convertString('utf-8', $GLOBALS['charset'], $line);
0 ignored issues
show
Bug introduced by
It seems like $line can also be of type null; however, parameter $what of PhpMyAdmin\Encoding::convertString() 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

182
                $line = Encoding::convertString('utf-8', $GLOBALS['charset'], /** @scrutinizer ignore-type */ $line);
Loading history...
183
            }
184
185
            if ($GLOBALS['save_on_server'] && mb_strlen((string) $line) > 0) {
186
                if ($GLOBALS['file_handle'] !== null) {
187
                    $writeResult = @fwrite($GLOBALS['file_handle'], (string) $line);
188
                } else {
189
                    $writeResult = false;
190
                }
191
192
                // Here, use strlen rather than mb_strlen to get the length
193
                // in bytes to compare against the number of bytes written.
194
                if (! $writeResult || $writeResult != strlen((string) $line)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $writeResult of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
195
                    $GLOBALS['message'] = Message::error(
196
                        __('Insufficient space to save the file %s.')
197
                    );
198
                    $GLOBALS['message']->addParam($GLOBALS['save_filename']);
199
200
                    return false;
201
                }
202
203
                $timeNow = time();
204
                if ($GLOBALS['time_start'] >= $timeNow + 30) {
205
                    $GLOBALS['time_start'] = $timeNow;
206
                    header('X-pmaPing: Pong');
207
                }
208
            } else {
209
                // We export as file - output normally
210
                echo $line;
211
            }
212
        } else {
213
            // We export as html - replace special chars
214
            echo htmlspecialchars((string) $line);
215
        }
216
217
        return true;
218
    }
219
220
    /**
221
     * Returns HTML containing the footer for a displayed export
222
     *
223
     * @param string $exportType the export type
224
     * @param string $db         the database name
225
     * @param string $table      the table name
226
     *
227
     * @return string the HTML output
228
     */
229
    public function getHtmlForDisplayedExportFooter(
230
        string $exportType,
231
        string $db,
232
        string $table
233
    ): string {
234
        /**
235
         * Close the html tags and add the footers for on-screen export
236
         */
237
        return '</textarea>'
238
            . '    </form>'
239
            . '<br>'
240
            // bottom back button
241
            . $this->getHTMLForBackButton($exportType, $db, $table)
242
            . $this->getHTMLForRefreshButton($exportType)
243
            . '</div>'
244
            . '<script type="text/javascript">' . "\n"
245
            . '//<![CDATA[' . "\n"
246
            . 'var $body = $("body");' . "\n"
247
            . '$("#textSQLDUMP")' . "\n"
248
            . '.width($body.width() - 50)' . "\n"
249
            . '.height($body.height() - 100);' . "\n"
250
            . '//]]>' . "\n"
251
            . '</script>' . "\n";
252
    }
253
254
    /**
255
     * Computes the memory limit for export
256
     *
257
     * @return int the memory limit
258
     */
259
    public function getMemoryLimit(): int
260
    {
261
        $memoryLimit = trim((string) ini_get('memory_limit'));
262
        $memoryLimitNumber = (int) substr($memoryLimit, 0, -1);
263
        $lowerLastChar = strtolower(substr($memoryLimit, -1));
264
        // 2 MB as default
265
        if (empty($memoryLimit) || $memoryLimit == '-1') {
266
            $memoryLimit = 2 * 1024 * 1024;
267
        } elseif ($lowerLastChar === 'm') {
268
            $memoryLimit = $memoryLimitNumber * 1024 * 1024;
269
        } elseif ($lowerLastChar === 'k') {
270
            $memoryLimit = $memoryLimitNumber * 1024;
271
        } elseif ($lowerLastChar === 'g') {
272
            $memoryLimit = $memoryLimitNumber * 1024 * 1024 * 1024;
273
        } else {
274
            $memoryLimit = (int) $memoryLimit;
275
        }
276
277
        // Some of memory is needed for other things and as threshold.
278
        // During export I had allocated (see memory_get_usage function)
279
        // approx 1.2MB so this comes from that.
280
        if ($memoryLimit > 1500000) {
281
            $memoryLimit -= 1500000;
282
        }
283
284
        // Some memory is needed for compression, assume 1/3
285
        $memoryLimit /= 8;
286
287
        return $memoryLimit;
288
    }
289
290
    /**
291
     * Returns the filename and MIME type for a compression and an export plugin
292
     *
293
     * @param ExportPlugin $exportPlugin the export plugin
294
     * @param string       $compression  compression asked
295
     * @param string       $filename     the filename
296 4
     *
297
     * @return string[]    the filename and mime type
298
     */
299
    public function getFinalFilenameAndMimetypeForFilename(
300
        ExportPlugin $exportPlugin,
301
        string $compression,
302
        string $filename
303
    ): array {
304 4
        // Grab basic dump extension and mime type
305 4
        // Check if the user already added extension;
306 4
        // get the substring where the extension would be if it was included
307 4
        $requiredExtension = '.' . $exportPlugin->getProperties()->getExtension();
308 4
        $extensionLength = mb_strlen($requiredExtension);
309
        $userExtension = mb_substr($filename, -$extensionLength);
310
        if (mb_strtolower($userExtension) != $requiredExtension) {
311 4
            $filename .= $requiredExtension;
312
        }
313
314
        $mediaType = $exportPlugin->getProperties()->getMimeType();
315 4
316 4
        // If dump is going to be compressed, set correct mime_type and add
317 4
        // compression to extension
318 4
        if ($compression === 'gzip') {
319 4
            $filename .= '.gz';
320 4
            $mediaType = 'application/x-gzip';
321
        } elseif ($compression === 'zip') {
322
            $filename .= '.zip';
323
            $mediaType = 'application/zip';
324 4
        }
325 1
326
        return [
327
            $filename,
328
            $mediaType,
329
        ];
330
    }
331
332
    /**
333
     * Return the filename and MIME type for export file
334
     *
335
     * @param string       $exportType       type of export
336
     * @param string       $rememberTemplate whether to remember template
337
     * @param ExportPlugin $exportPlugin     the export plugin
338
     * @param string       $compression      compression asked
339
     * @param string       $filenameTemplate the filename template
340
     *
341
     * @return string[] the filename template and mime type
342
     */
343
    public function getFilenameAndMimetype(
344
        string $exportType,
345
        string $rememberTemplate,
346
        ExportPlugin $exportPlugin,
347
        string $compression,
348
        string $filenameTemplate
349
    ): array {
350
        if ($exportType === 'server') {
351
            if (! empty($rememberTemplate)) {
352
                $GLOBALS['config']->setUserValue(
353
                    'pma_server_filename_template',
354
                    'Export/file_template_server',
355
                    $filenameTemplate
356
                );
357
            }
358
        } elseif ($exportType === 'database') {
359
            if (! empty($rememberTemplate)) {
360
                $GLOBALS['config']->setUserValue(
361
                    'pma_db_filename_template',
362
                    'Export/file_template_database',
363
                    $filenameTemplate
364
                );
365
            }
366
        } elseif ($exportType === 'raw') {
367
            if (! empty($rememberTemplate)) {
368
                $GLOBALS['config']->setUserValue(
369
                    'pma_raw_filename_template',
370
                    'Export/file_template_raw',
371
                    $filenameTemplate
372
                );
373
            }
374
        } else {
375
            if (! empty($rememberTemplate)) {
376
                $GLOBALS['config']->setUserValue(
377
                    'pma_table_filename_template',
378
                    'Export/file_template_table',
379
                    $filenameTemplate
380
                );
381
            }
382
        }
383
384
        $filename = Util::expandUserString($filenameTemplate);
385
        // remove dots in filename (coming from either the template or already
386
        // part of the filename) to avoid a remote code execution vulnerability
387
        $filename = Sanitize::sanitizeFilename($filename, true);
388
389
        return $this->getFinalFilenameAndMimetypeForFilename($exportPlugin, $compression, $filename);
390
    }
391
392
    /**
393
     * Open the export file
394
     *
395
     * @param string $filename    the export filename
396
     * @param bool   $quickExport whether it's a quick export or not
397
     *
398
     * @return array the full save filename, possible message and the file handle
399
     */
400
    public function openFile(string $filename, bool $quickExport): array
401
    {
402
        $fileHandle = null;
403
        $message = '';
404
        $doNotSaveItOver = true;
405
406
        if (isset($_POST['quick_export_onserver_overwrite'])) {
407
            $doNotSaveItOver = $_POST['quick_export_onserver_overwrite'] !== 'saveitover';
408
        }
409
410
        $saveFilename = Util::userDir((string) ($GLOBALS['cfg']['SaveDir'] ?? ''))
411
            . preg_replace('@[/\\\\]@', '_', $filename);
412
413
        if (
414
            @file_exists($saveFilename)
415
            && ((! $quickExport && empty($_POST['onserver_overwrite']))
416
            || ($quickExport
417
            && $doNotSaveItOver))
418
        ) {
419
            $message = Message::error(
420
                __(
421
                    'File %s already exists on server, change filename or check overwrite option.'
422
                )
423
            );
424
            $message->addParam($saveFilename);
425
        } elseif (@is_file($saveFilename) && ! @is_writable($saveFilename)) {
426
            $message = Message::error(
427
                __(
428
                    'The web server does not have permission to save the file %s.'
429
                )
430
            );
431
            $message->addParam($saveFilename);
432
        } else {
433
            $fileHandle = @fopen($saveFilename, 'w');
434
435
            if ($fileHandle === false) {
436
                $message = Message::error(
437
                    __(
438
                        'The web server does not have permission to save the file %s.'
439
                    )
440
                );
441
                $message->addParam($saveFilename);
442
            }
443
        }
444
445
        return [
446
            $saveFilename,
447
            $message,
448
            $fileHandle,
449
        ];
450
    }
451
452
    /**
453
     * Close the export file
454
     *
455
     * @param resource $fileHandle   the export file handle
456
     * @param string   $dumpBuffer   the current dump buffer
457
     * @param string   $saveFilename the export filename
458
     *
459
     * @return Message a message object (or empty string)
460
     */
461
    public function closeFile(
462
        $fileHandle,
463
        string $dumpBuffer,
464
        string $saveFilename
465
    ): Message {
466
        $writeResult = @fwrite($fileHandle, $dumpBuffer);
467
        fclose($fileHandle);
468
        // Here, use strlen rather than mb_strlen to get the length
469
        // in bytes to compare against the number of bytes written.
470
        if (strlen($dumpBuffer) > 0 && (! $writeResult || $writeResult != strlen($dumpBuffer))) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $writeResult of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
471
            $message = new Message(
472
                __('Insufficient space to save the file %s.'),
473
                Message::ERROR,
474
                [$saveFilename]
475
            );
476
        } else {
477
            $message = new Message(
478
                __('Dump has been saved to file %s.'),
479
                Message::SUCCESS,
480
                [$saveFilename]
481
            );
482
        }
483
484
        return $message;
485
    }
486
487
    /**
488
     * Compress the export buffer
489
     *
490
     * @param array|string $dumpBuffer  the current dump buffer
491
     * @param string       $compression the compression mode
492
     * @param string       $filename    the filename
493
     *
494
     * @return array|string|bool
495
     */
496
    public function compress($dumpBuffer, string $compression, string $filename)
497
    {
498
        if ($compression === 'zip' && function_exists('gzcompress')) {
499
            $zipExtension = new ZipExtension();
500
            $filename = substr($filename, 0, -4); // remove extension (.zip)
501
            $dumpBuffer = $zipExtension->createFile($dumpBuffer, $filename);
502
        } elseif ($compression === 'gzip' && $this->gzencodeNeeded() && is_string($dumpBuffer)) {
503
            // without the optional parameter level because it bugs
504
            $dumpBuffer = gzencode($dumpBuffer);
505
        }
506
507
        return $dumpBuffer;
508
    }
509
510
    /**
511
     * Saves the dump buffer for a particular table in an array
512
     * Used in separate files export
513
     *
514
     * @param string $objectName the name of current object to be stored
515
     * @param bool   $append     optional boolean to append to an existing index or not
516
     */
517
    public function saveObjectInBuffer(string $objectName, bool $append = false): void
518
    {
519
        if (! empty($this->dumpBuffer)) {
520
            if ($append && isset($this->dumpBufferObjects[$objectName])) {
521
                $this->dumpBufferObjects[$objectName] .= $this->dumpBuffer;
522
            } else {
523
                $this->dumpBufferObjects[$objectName] = $this->dumpBuffer;
524
            }
525
        }
526
527
        // Re - initialize
528
        $this->dumpBuffer = '';
529
        $this->dumpBufferLength = 0;
530
    }
531
532
    /**
533
     * Returns HTML containing the header for a displayed export
534
     *
535
     * @param string $exportType the export type
536
     * @param string $db         the database name
537
     * @param string $table      the table name
538
     *
539
     * @return string the generated HTML and back button
540
     */
541
    public function getHtmlForDisplayedExportHeader(
542
        string $exportType,
543
        string $db,
544
        string $table
545
    ): string {
546
        /**
547
         * Displays a back button with all the $_POST data in the URL
548
         */
549
        return '<div>'
550
            . '<br>'
551
            . $this->getHTMLForBackButton($exportType, $db, $table)
552
            . $this->getHTMLForRefreshButton($exportType)
553
            . '<br>'
554
            . '<form name="nofunction">'
555
            . '<textarea name="sqldump" cols="50" rows="30" '
556
            . 'id="textSQLDUMP" wrap="OFF">';
557
    }
558
559
    /**
560
     * Export at the server level
561
     *
562
     * @param string|array $dbSelect        the selected databases to export
563
     * @param string       $whatStrucOrData structure or data or both
564
     * @param ExportPlugin $exportPlugin    the selected export plugin
565
     * @param string       $crlf            end of line character(s)
566
     * @param string       $errorUrl        the URL in case of error
567
     * @param string       $exportType      the export type
568
     * @param bool         $doRelation      whether to export relation info
569
     * @param bool         $doComments      whether to add comments
570
     * @param bool         $doMime          whether to add MIME info
571
     * @param bool         $doDates         whether to add dates
572
     * @param array        $aliases         alias information for db/table/column
573
     * @param string       $separateFiles   whether it is a separate-files export
574
     */
575
    public function exportServer(
576
        $dbSelect,
577
        string $whatStrucOrData,
578
        ExportPlugin $exportPlugin,
579
        string $crlf,
580
        string $errorUrl,
581
        string $exportType,
582
        bool $doRelation,
583
        bool $doComments,
584
        bool $doMime,
585
        bool $doDates,
586
        array $aliases,
587
        string $separateFiles
588
    ): void {
589
        if (! empty($dbSelect) && is_array($dbSelect)) {
590
            $tmpSelect = implode('|', $dbSelect);
591
            $tmpSelect = '|' . $tmpSelect . '|';
592
        }
593
594
        // Walk over databases
595
        foreach ($GLOBALS['dblist']->databases as $currentDb) {
596
            if (! isset($tmpSelect) || ! mb_strpos(' ' . $tmpSelect, '|' . $currentDb . '|')) {
597
                continue;
598
            }
599
600
            $tables = $this->dbi->getTables($currentDb);
601
            $this->exportDatabase(
602
                $currentDb,
603
                $tables,
604
                $whatStrucOrData,
605
                $tables,
606
                $tables,
607
                $exportPlugin,
608
                $crlf,
609
                $errorUrl,
610
                $exportType,
611
                $doRelation,
612
                $doComments,
613
                $doMime,
614
                $doDates,
615
                $aliases,
616
                $separateFiles === 'database' ? $separateFiles : ''
617
            );
618
            if ($separateFiles !== 'server') {
619
                continue;
620
            }
621
622
            $this->saveObjectInBuffer($currentDb);
623
        }
624
    }
625
626
    /**
627
     * Export at the database level
628
     *
629
     * @param string       $db              the database to export
630
     * @param array        $tables          the tables to export
631
     * @param string       $whatStrucOrData structure or data or both
632
     * @param array        $tableStructure  whether to export structure for each table
633
     * @param array        $tableData       whether to export data for each table
634
     * @param ExportPlugin $exportPlugin    the selected export plugin
635
     * @param string       $crlf            end of line character(s)
636
     * @param string       $errorUrl        the URL in case of error
637
     * @param string       $exportType      the export type
638
     * @param bool         $doRelation      whether to export relation info
639
     * @param bool         $doComments      whether to add comments
640
     * @param bool         $doMime          whether to add MIME info
641
     * @param bool         $doDates         whether to add dates
642
     * @param array        $aliases         Alias information for db/table/column
643
     * @param string       $separateFiles   whether it is a separate-files export
644
     */
645
    public function exportDatabase(
646
        string $db,
647
        array $tables,
648
        string $whatStrucOrData,
649
        array $tableStructure,
650
        array $tableData,
651
        ExportPlugin $exportPlugin,
652
        string $crlf,
653
        string $errorUrl,
654
        string $exportType,
655
        bool $doRelation,
656
        bool $doComments,
657
        bool $doMime,
658
        bool $doDates,
659
        array $aliases,
660
        string $separateFiles
661
    ): void {
662
        $dbAlias = ! empty($aliases[$db]['alias'])
663
            ? $aliases[$db]['alias'] : '';
664
665
        if (! $exportPlugin->exportDBHeader($db, $dbAlias)) {
666
            return;
667
        }
668
669
        if (! $exportPlugin->exportDBCreate($db, $exportType, $dbAlias)) {
670
            return;
671
        }
672
673
        if ($separateFiles === 'database') {
674
            $this->saveObjectInBuffer('database', true);
675
        }
676
677
        if (
678
            ($GLOBALS['sql_structure_or_data'] === 'structure'
679
            || $GLOBALS['sql_structure_or_data'] === 'structure_and_data')
680
            && isset($GLOBALS['sql_procedure_function'])
681
        ) {
682
            $exportPlugin->exportRoutines($db, $aliases);
683
684
            if ($separateFiles === 'database') {
685
                $this->saveObjectInBuffer('routines');
686
            }
687
        }
688
689
        $views = [];
690
691
        foreach ($tables as $table) {
692
            $tableObject = new Table($table, $db);
693
            // if this is a view, collect it for later;
694
            // views must be exported after the tables
695
            $isView = $tableObject->isView();
696
            if ($isView) {
697
                $views[] = $table;
698
            }
699
700
            if (
701
                ($whatStrucOrData === 'structure'
702
                || $whatStrucOrData === 'structure_and_data')
703
                && in_array($table, $tableStructure)
704
            ) {
705
                // for a view, export a stand-in definition of the table
706
                // to resolve view dependencies (only when it's a single-file export)
707
                if ($isView) {
708
                    if (
709
                        $separateFiles == ''
710
                        && isset($GLOBALS['sql_create_view'])
711
                        && ! $exportPlugin->exportStructure(
712
                            $db,
713
                            $table,
714
                            $crlf,
715
                            $errorUrl,
716
                            'stand_in',
717
                            $exportType,
718
                            $doRelation,
719
                            $doComments,
720
                            $doMime,
721
                            $doDates,
722
                            $aliases
723
                        )
724
                    ) {
725
                        break;
726
                    }
727
                } elseif (isset($GLOBALS['sql_create_table'])) {
728
                    $tableSize = $GLOBALS['maxsize'];
729
                    // Checking if the maximum table size constrain has been set
730
                    // And if that constrain is a valid number or not
731
                    if ($tableSize !== '' && is_numeric($tableSize)) {
732
                        // This obtains the current table's size
733
                        $query = 'SELECT data_length + index_length
734
                              from information_schema.TABLES
735
                              WHERE table_schema = "' . $this->dbi->escapeString($db) . '"
736
                              AND table_name = "' . $this->dbi->escapeString($table) . '"';
737
738
                        $size = (int) $this->dbi->fetchValue($query);
739
                        //Converting the size to MB
740
                        $size /= 1024 * 1024;
741
                        if ($size > $tableSize) {
742
                            continue;
743
                        }
744
                    }
745
746
                    if (
747
                        ! $exportPlugin->exportStructure(
748
                            $db,
749
                            $table,
750
                            $crlf,
751
                            $errorUrl,
752
                            'create_table',
753
                            $exportType,
754
                            $doRelation,
755
                            $doComments,
756
                            $doMime,
757
                            $doDates,
758
                            $aliases
759
                        )
760
                    ) {
761
                        break;
762
                    }
763
                }
764
            }
765
766
            // if this is a view or a merge table, don't export data
767
            if (
768
                ($whatStrucOrData === 'data' || $whatStrucOrData === 'structure_and_data')
769
                && in_array($table, $tableData)
770
                && ! $isView
771
            ) {
772
                $tableObj = new Table($table, $db);
773
                $nonGeneratedCols = $tableObj->getNonGeneratedColumns(true);
774
775
                $localQuery = 'SELECT ' . implode(', ', $nonGeneratedCols)
776
                    . ' FROM ' . Util::backquote($db)
777
                    . '.' . Util::backquote($table);
778
779
                if (! $exportPlugin->exportData($db, $table, $crlf, $errorUrl, $localQuery, $aliases)) {
780
                    break;
781
                }
782
            }
783
784
            // this buffer was filled, we save it and go to the next one
785
            if ($separateFiles === 'database') {
786
                $this->saveObjectInBuffer('table_' . $table);
787
            }
788
789
            // now export the triggers (needs to be done after the data because
790
            // triggers can modify already imported tables)
791
            if (
792
                ! isset($GLOBALS['sql_create_trigger']) || ($whatStrucOrData !== 'structure'
793
                && $whatStrucOrData !== 'structure_and_data')
794
                || ! in_array($table, $tableStructure)
795
            ) {
796
                continue;
797
            }
798
799
            if (
800
                ! $exportPlugin->exportStructure(
801
                    $db,
802
                    $table,
803
                    $crlf,
804
                    $errorUrl,
805
                    'triggers',
806
                    $exportType,
807
                    $doRelation,
808
                    $doComments,
809
                    $doMime,
810
                    $doDates,
811
                    $aliases
812
                )
813
            ) {
814
                break;
815
            }
816
817
            if ($separateFiles !== 'database') {
818
                continue;
819
            }
820
821
            $this->saveObjectInBuffer('table_' . $table, true);
822
        }
823
824
        if (isset($GLOBALS['sql_create_view'])) {
825
            foreach ($views as $view) {
826
                // no data export for a view
827
                if ($whatStrucOrData !== 'structure' && $whatStrucOrData !== 'structure_and_data') {
828
                    continue;
829
                }
830
831
                if (
832
                    ! $exportPlugin->exportStructure(
833
                        $db,
834
                        $view,
835
                        $crlf,
836
                        $errorUrl,
837
                        'create_view',
838
                        $exportType,
839
                        $doRelation,
840
                        $doComments,
841
                        $doMime,
842
                        $doDates,
843
                        $aliases
844
                    )
845
                ) {
846
                    break;
847
                }
848
849
                if ($separateFiles !== 'database') {
850
                    continue;
851
                }
852
853
                $this->saveObjectInBuffer('view_' . $view);
854
            }
855
        }
856
857
        if (! $exportPlugin->exportDBFooter($db)) {
858
            return;
859
        }
860
861
        // export metadata related to this db
862
        if (isset($GLOBALS['sql_metadata'])) {
863
            // Types of metadata to export.
864
            // In the future these can be allowed to be selected by the user
865
            $metadataTypes = $this->getMetadataTypes();
866
            $exportPlugin->exportMetadata($db, $tables, $metadataTypes);
867
868
            if ($separateFiles === 'database') {
869
                $this->saveObjectInBuffer('metadata');
870
            }
871
        }
872
873
        if ($separateFiles === 'database') {
874
            $this->saveObjectInBuffer('extra');
875
        }
876
877
        if (
878
            ($GLOBALS['sql_structure_or_data'] !== 'structure'
879
            && $GLOBALS['sql_structure_or_data'] !== 'structure_and_data')
880
            || ! isset($GLOBALS['sql_procedure_function'])
881
        ) {
882
            return;
883
        }
884
885
        $exportPlugin->exportEvents($db);
886
887
        if ($separateFiles !== 'database') {
888
            return;
889
        }
890
891
        $this->saveObjectInBuffer('events');
892
    }
893
894
    /**
895
     * Export a raw query
896
     *
897
     * @param string       $whatStrucOrData whether to export structure for each table or raw
898
     * @param ExportPlugin $exportPlugin    the selected export plugin
899
     * @param string       $crlf            end of line character(s)
900
     * @param string       $errorUrl        the URL in case of error
901
     * @param string       $sqlQuery        the query to be executed
902
     * @param string       $exportType      the export type
903
     */
904
    public static function exportRaw(
905
        string $whatStrucOrData,
906
        ExportPlugin $exportPlugin,
907
        string $crlf,
908
        string $errorUrl,
909
        string $sqlQuery,
910
        string $exportType
0 ignored issues
show
Unused Code introduced by
The parameter $exportType is not used and could be removed. ( Ignorable by Annotation )

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

910
        /** @scrutinizer ignore-unused */ string $exportType

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

Loading history...
911
    ): void {
912
        // In case the we need to dump just the raw query
913
        if ($whatStrucOrData !== 'raw') {
914
            return;
915
        }
916
917
        if (! $exportPlugin->exportRawQuery($errorUrl, $sqlQuery, $crlf)) {
918
            $GLOBALS['message'] = Message::error(
919
                // phpcs:disable Generic.Files.LineLength.TooLong
920
                /* l10n: A query written by the user is a "raw query" that could be using no tables or databases in particular */
921
                __('Exporting a raw query is not supported for this export method.')
922
            );
923
924
            return;
925
        }
926
    }
927
928
    /**
929
     * Export at the table level
930
     *
931
     * @param string       $db              the database to export
932
     * @param string       $table           the table to export
933
     * @param string       $whatStrucOrData structure or data or both
934
     * @param ExportPlugin $exportPlugin    the selected export plugin
935
     * @param string       $crlf            end of line character(s)
936
     * @param string       $errorUrl        the URL in case of error
937
     * @param string       $exportType      the export type
938
     * @param bool         $doRelation      whether to export relation info
939
     * @param bool         $doComments      whether to add comments
940
     * @param bool         $doMime          whether to add MIME info
941
     * @param bool         $doDates         whether to add dates
942
     * @param string|null  $allrows         whether "dump all rows" was ticked
943
     * @param string       $limitTo         upper limit
944
     * @param string       $limitFrom       starting limit
945
     * @param string       $sqlQuery        query for which exporting is requested
946
     * @param array        $aliases         Alias information for db/table/column
947
     */
948
    public function exportTable(
949
        string $db,
950
        string $table,
951
        string $whatStrucOrData,
952
        ExportPlugin $exportPlugin,
953
        string $crlf,
954
        string $errorUrl,
955
        string $exportType,
956
        bool $doRelation,
957
        bool $doComments,
958
        bool $doMime,
959
        bool $doDates,
960
        ?string $allrows,
961
        string $limitTo,
962
        string $limitFrom,
963
        string $sqlQuery,
964
        array $aliases
965
    ): void {
966
        $dbAlias = ! empty($aliases[$db]['alias'])
967
            ? $aliases[$db]['alias'] : '';
968
        if (! $exportPlugin->exportDBHeader($db, $dbAlias)) {
969
            return;
970
        }
971
972
        if (isset($allrows) && $allrows == '0' && $limitTo > 0 && $limitFrom >= 0) {
973
            $addQuery = ' LIMIT '
974
                        . ($limitFrom > 0 ? $limitFrom . ', ' : '')
975
                        . $limitTo;
976
        } else {
977
            $addQuery = '';
978
        }
979
980
        $tableObject = new Table($table, $db);
981
        $isView = $tableObject->isView();
982
        if ($whatStrucOrData === 'structure' || $whatStrucOrData === 'structure_and_data') {
983
            if ($isView) {
984
                if (isset($GLOBALS['sql_create_view'])) {
985
                    if (
986
                        ! $exportPlugin->exportStructure(
987
                            $db,
988
                            $table,
989
                            $crlf,
990
                            $errorUrl,
991
                            'create_view',
992
                            $exportType,
993
                            $doRelation,
994
                            $doComments,
995
                            $doMime,
996
                            $doDates,
997
                            $aliases
998
                        )
999
                    ) {
1000
                        return;
1001
                    }
1002
                }
1003
            } elseif (isset($GLOBALS['sql_create_table'])) {
1004
                if (
1005
                    ! $exportPlugin->exportStructure(
1006
                        $db,
1007
                        $table,
1008
                        $crlf,
1009
                        $errorUrl,
1010
                        'create_table',
1011
                        $exportType,
1012
                        $doRelation,
1013
                        $doComments,
1014
                        $doMime,
1015
                        $doDates,
1016
                        $aliases
1017
                    )
1018
                ) {
1019
                    return;
1020
                }
1021
            }
1022
        }
1023
1024
        // If this is an export of a single view, we have to export data;
1025
        // for example, a PDF report
1026
        // if it is a merge table, no data is exported
1027
        if ($whatStrucOrData === 'data' || $whatStrucOrData === 'structure_and_data') {
1028
            if (! empty($sqlQuery)) {
1029
                // only preg_replace if needed
1030
                if (! empty($addQuery)) {
1031
                    // remove trailing semicolon before adding a LIMIT
1032
                    $sqlQuery = preg_replace('%;\s*$%', '', $sqlQuery);
1033
                }
1034
1035
                $localQuery = $sqlQuery . $addQuery;
1036
                $this->dbi->selectDb($db);
1037
            } else {
1038
                // Data is exported only for Non-generated columns
1039
                $tableObj = new Table($table, $db);
1040
                $nonGeneratedCols = $tableObj->getNonGeneratedColumns(true);
1041
1042
                $localQuery = 'SELECT ' . implode(', ', $nonGeneratedCols)
1043
                    . ' FROM ' . Util::backquote($db)
1044
                    . '.' . Util::backquote($table) . $addQuery;
1045
            }
1046
1047
            if (! $exportPlugin->exportData($db, $table, $crlf, $errorUrl, $localQuery, $aliases)) {
1048
                return;
1049
            }
1050
        }
1051
1052
        // now export the triggers (needs to be done after the data because
1053
        // triggers can modify already imported tables)
1054
        if (
1055
            isset($GLOBALS['sql_create_trigger']) && ($whatStrucOrData === 'structure'
1056
            || $whatStrucOrData === 'structure_and_data')
1057
        ) {
1058
            if (
1059
                ! $exportPlugin->exportStructure(
1060
                    $db,
1061
                    $table,
1062
                    $crlf,
1063
                    $errorUrl,
1064
                    'triggers',
1065
                    $exportType,
1066
                    $doRelation,
1067
                    $doComments,
1068
                    $doMime,
1069
                    $doDates,
1070
                    $aliases
1071
                )
1072
            ) {
1073
                return;
1074
            }
1075
        }
1076
1077
        if (! $exportPlugin->exportDBFooter($db)) {
1078
            return;
1079
        }
1080
1081
        if (! isset($GLOBALS['sql_metadata'])) {
1082
            return;
1083
        }
1084
1085
        // Types of metadata to export.
1086
        // In the future these can be allowed to be selected by the user
1087
        $metadataTypes = $this->getMetadataTypes();
1088
        $exportPlugin->exportMetadata($db, $table, $metadataTypes);
1089
    }
1090
1091
    /**
1092
     * Loads correct page after doing export
1093
     */
1094
    public function showPage(string $exportType): void
1095
    {
1096
        $GLOBALS['active_page'] = $GLOBALS['active_page'] ?? null;
1097
        $GLOBALS['containerBuilder'] = $GLOBALS['containerBuilder'] ?? null;
1098
1099
        if ($exportType === 'server') {
1100
            $GLOBALS['active_page'] = Url::getFromRoute('/server/export');
1101
            /** @var ServerExportController $controller */
1102
            $controller = $GLOBALS['containerBuilder']->get(ServerExportController::class);
1103
            $controller();
1104
1105
            return;
1106
        }
1107
1108
        if ($exportType === 'database') {
1109
            $GLOBALS['active_page'] = Url::getFromRoute('/database/export');
1110
            /** @var DatabaseExportController $controller */
1111
            $controller = $GLOBALS['containerBuilder']->get(DatabaseExportController::class);
1112
            $controller();
1113
1114
            return;
1115
        }
1116
1117
        $GLOBALS['active_page'] = Url::getFromRoute('/table/export');
1118
        /** @var TableExportController $controller */
1119
        $controller = $GLOBALS['containerBuilder']->get(TableExportController::class);
1120
        $controller();
1121
    }
1122
1123
    /**
1124
     * Merge two alias arrays, if array1 and array2 have
1125
     * conflicting alias then array2 value is used if it
1126
     * is non empty otherwise array1 value.
1127
     *
1128
     * @param array $aliases1 first array of aliases
1129
     * @param array $aliases2 second array of aliases
1130
     *
1131
     * @return array resultant merged aliases info
1132
     */
1133
    public function mergeAliases(array $aliases1, array $aliases2): array
1134
    {
1135
        // First do a recursive array merge
1136
        // on aliases arrays.
1137
        $aliases = array_merge_recursive($aliases1, $aliases2);
1138
        // Now, resolve conflicts in aliases, if any
1139
        foreach ($aliases as $dbName => $db) {
1140
            // If alias key is an array then
1141
            // it is a merge conflict.
1142
            if (isset($db['alias']) && is_array($db['alias'])) {
1143
                $val1 = $db['alias'][0];
1144
                $val2 = $db['alias'][1];
1145
                // Use aliases2 alias if non empty
1146
                $aliases[$dbName]['alias'] = empty($val2) ? $val1 : $val2;
1147
            }
1148
1149
            if (! isset($db['tables'])) {
1150
                continue;
1151
            }
1152
1153
            foreach ($db['tables'] as $tableName => $tbl) {
1154
                if (isset($tbl['alias']) && is_array($tbl['alias'])) {
1155
                    $val1 = $tbl['alias'][0];
1156
                    $val2 = $tbl['alias'][1];
1157
                    // Use aliases2 alias if non empty
1158
                    $aliases[$dbName]['tables'][$tableName]['alias'] = empty($val2) ? $val1 : $val2;
1159
                }
1160
1161
                if (! isset($tbl['columns'])) {
1162
                    continue;
1163
                }
1164
1165
                foreach ($tbl['columns'] as $col => $colAs) {
1166
                    if (! isset($colAs) || ! is_array($colAs)) {
1167
                        continue;
1168
                    }
1169
1170
                    $val1 = $colAs[0];
1171
                    $val2 = $colAs[1];
1172
                    // Use aliases2 alias if non empty
1173
                    $aliases[$dbName]['tables'][$tableName]['columns'][$col] = empty($val2) ? $val1 : $val2;
1174
                }
1175
            }
1176
        }
1177
1178
        return $aliases;
1179
    }
1180
1181
    /**
1182
     * Locks tables
1183
     *
1184
     * @param string $db       database name
1185
     * @param array  $tables   list of table names
1186
     * @param string $lockType lock type; "[LOW_PRIORITY] WRITE" or "READ [LOCAL]"
1187
     *
1188
     * @return mixed result of the query
1189
     */
1190
    public function lockTables(string $db, array $tables, string $lockType = 'WRITE')
1191
    {
1192
        $locks = [];
1193
        foreach ($tables as $table) {
1194 4
            $locks[] = Util::backquote($db) . '.'
1195
                . Util::backquote($table) . ' ' . $lockType;
1196
        }
1197
1198 4
        $sql = 'LOCK TABLES ' . implode(', ', $locks);
1199
1200 4
        return $this->dbi->tryQuery($sql);
1201
    }
1202
1203 4
    /**
1204 4
     * Releases table locks
1205 4
     *
1206
     * @return mixed result of the query
1207 4
     */
1208
    public function unlockTables()
1209
    {
1210 4
        return $this->dbi->tryQuery('UNLOCK TABLES');
1211
    }
1212
1213
    /**
1214 4
     * Returns all the metadata types that can be exported with a database or a table
1215 4
     *
1216
     * @return string[] metadata types.
1217
     */
1218
    public function getMetadataTypes(): array
1219
    {
1220
        return [
1221
            'column_info',
1222 4
            'table_uiprefs',
1223
            'tracking',
1224
            'bookmark',
1225
            'relation',
1226 4
            'table_coords',
1227 4
            'pdf_pages',
1228 4
            'savedsearches',
1229
            'central_columns',
1230
            'export_templates',
1231 4
        ];
1232 4
    }
1233
1234 4
    /**
1235
     * Returns the checked clause, depending on the presence of key in array
1236
     *
1237
     * @param string $key   the key to look for
1238
     * @param array  $array array to verify
1239 4
     *
1240
     * @return string the checked clause
1241
     */
1242
    public function getCheckedClause(string $key, array $array): string
1243
    {
1244
        if (in_array($key, $array)) {
1245
            return ' checked="checked"';
1246
        }
1247
1248
        return '';
1249
    }
1250
1251
    /**
1252
     * get all the export options and verify
1253
     * call and include the appropriate Schema Class depending on $export_type
1254
     *
1255
     * @param string|null $exportType format of the export
1256
     */
1257
    public function processExportSchema(?string $exportType): void
1258
    {
1259
        /**
1260
         * default is PDF, otherwise validate it's only letters a-z
1261
         */
1262
        if (! isset($exportType) || ! preg_match('/^[a-zA-Z]+$/', $exportType)) {
1263
            $exportType = 'pdf';
1264
        }
1265
1266
        // sanitize this parameter which will be used below in a file inclusion
1267
        $exportType = Core::securePath($exportType);
1268
1269
        // get the specific plugin
1270
        /** @var SchemaPlugin $exportPlugin */
1271
        $exportPlugin = Plugins::getPlugin('schema', $exportType);
1272
1273
        // Check schema export type
1274
        if ($exportPlugin === null) {
1275
            Core::fatalError(__('Bad type!'));
1276
        }
1277
1278
        $this->dbi->selectDb($_POST['db']);
1279
        $exportPlugin->exportSchema($_POST['db']);
1280
    }
1281
1282
    private function getHTMLForRefreshButton(string $exportType): string
1283
    {
1284
        $postParams = $this->getPostParams($exportType);
1285
1286
        $refreshButton = '<form id="export_refresh_form" method="POST" action="'
1287
            . Url::getFromRoute('/export') . '" class="disableAjax">';
1288
        $refreshButton .= '[ <a class="disableAjax export_refresh_btn">' . __('Refresh') . '</a> ]';
1289
        foreach ($postParams as $name => $value) {
1290
            if (is_array($value)) {
1291
                foreach ($value as $val) {
1292
                    $refreshButton .= '<input type="hidden" name="' . htmlentities((string) $name)
1293
                        . '[]" value="' . htmlentities((string) $val) . '">';
1294
                }
1295
            } else {
1296
                $refreshButton .= '<input type="hidden" name="' . htmlentities((string) $name)
1297
                    . '" value="' . htmlentities((string) $value) . '">';
1298
            }
1299
        }
1300
1301
        return $refreshButton . '</form>';
1302
    }
1303
1304
    private function getHTMLForBackButton(string $exportType, string $db, string $table): string
1305
    {
1306
        $backButton = '<p>[ <a href="';
1307
        if ($exportType === 'server') {
1308
            $backButton .= Url::getFromRoute('/server/export') . '" data-post="' . Url::getCommon([], '', false);
1309
        } elseif ($exportType === 'database') {
1310
            $backButton .= Url::getFromRoute('/database/export') . '" data-post="' . Url::getCommon(
1311
                ['db' => $db],
1312
                '',
1313
                false
1314
            );
1315
        } else {
1316
            $backButton .= Url::getFromRoute('/table/export') . '" data-post="' . Url::getCommon(
1317
                ['db' => $db, 'table' => $table],
1318
                '',
1319
                false
1320
            );
1321
        }
1322
1323
        $postParams = array_filter($this->getPostParams($exportType), static function ($value) {
1324
            return ! is_array($value);
1325
        });
1326
        $backButton .= '&amp;' . http_build_query($postParams);
1327
1328
        $backButton .= '&amp;repopulate=1">' . __('Back') . '</a> ]</p>';
1329
1330
        return $backButton;
1331
    }
1332
1333
    private function getPostParams(string $exportType): array
1334
    {
1335
        $postParams = $_POST;
1336
1337
        // Convert the multiple select elements from an array to a string
1338
        if ($exportType === 'database') {
1339
            $structOrDataForced = empty($postParams['structure_or_data_forced']);
1340
            if ($structOrDataForced && ! isset($postParams['table_structure'])) {
1341
                $postParams['table_structure'] = [];
1342
            }
1343
1344
            if ($structOrDataForced && ! isset($postParams['table_data'])) {
1345
                $postParams['table_data'] = [];
1346
            }
1347
        }
1348
1349
        return $postParams;
1350
    }
1351
}
1352