Translated using Weblate (Estonian)
[phpmyadmin.git] / libraries / classes / Export.php
blob1a5c495a3f86646a4083dedbaa2ff2bc0228d167
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * function for the main export logic
6 * @package PhpMyAdmin
7 */
8 declare(strict_types=1);
10 namespace PhpMyAdmin;
12 use PhpMyAdmin\Plugins\ExportPlugin;
13 use PhpMyAdmin\Plugins\SchemaPlugin;
15 /**
16 * PhpMyAdmin\Export class
18 * @package PhpMyAdmin
20 class Export
22 /**
23 * @var DatabaseInterface
25 private $dbi;
27 /**
28 * Export constructor.
29 * @param DatabaseInterface $dbi DatabaseInterface instance
31 public function __construct($dbi)
33 $this->dbi = $dbi;
36 /**
37 * Sets a session variable upon a possible fatal error during export
39 * @return void
41 public function shutdown(): void
43 $error = error_get_last();
44 if ($error != null && mb_strpos($error['message'], "execution time")) {
45 //set session variable to check if there was error while exporting
46 $_SESSION['pma_export_error'] = $error['message'];
50 /**
51 * Detect ob_gzhandler
53 * @return bool
55 public function isGzHandlerEnabled(): bool
57 return in_array('ob_gzhandler', ob_list_handlers());
60 /**
61 * Detect whether gzencode is needed; it might not be needed if
62 * the server is already compressing by itself
64 * @return bool Whether gzencode is needed
66 public function gzencodeNeeded(): bool
69 * We should gzencode only if the function exists
70 * but we don't want to compress twice, therefore
71 * gzencode only if transparent compression is not enabled
72 * and gz compression was not asked via $cfg['OBGzip']
73 * but transparent compression does not apply when saving to server
75 $chromeAndGreaterThan43 = PMA_USR_BROWSER_AGENT == 'CHROME'
76 && PMA_USR_BROWSER_VER >= 43; // see bug #4942
78 return function_exists('gzencode')
79 && ((! ini_get('zlib.output_compression')
80 && ! $this->isGzHandlerEnabled())
81 || $GLOBALS['save_on_server']
82 || $chromeAndGreaterThan43);
85 /**
86 * Output handler for all exports, if needed buffering, it stores data into
87 * $dump_buffer, otherwise it prints them out.
89 * @param string $line the insert statement
91 * @return bool Whether output succeeded
93 public function outputHandler(?string $line): bool
95 global $time_start, $dump_buffer, $dump_buffer_len, $save_filename;
97 // Kanji encoding convert feature
98 if ($GLOBALS['output_kanji_conversion']) {
99 $line = Encoding::kanjiStrConv(
100 $line,
101 $GLOBALS['knjenc'],
102 isset($GLOBALS['xkana']) ? $GLOBALS['xkana'] : ''
106 // If we have to buffer data, we will perform everything at once at the end
107 if ($GLOBALS['buffer_needed']) {
108 $dump_buffer .= $line;
109 if ($GLOBALS['onfly_compression']) {
110 $dump_buffer_len += strlen($line);
112 if ($dump_buffer_len > $GLOBALS['memory_limit']) {
113 if ($GLOBALS['output_charset_conversion']) {
114 $dump_buffer = Encoding::convertString(
115 'utf-8',
116 $GLOBALS['charset'],
117 $dump_buffer
120 if ($GLOBALS['compression'] == 'gzip'
121 && $this->gzencodeNeeded()
123 // as a gzipped file
124 // without the optional parameter level because it bugs
125 $dump_buffer = gzencode($dump_buffer);
127 if ($GLOBALS['save_on_server']) {
128 $write_result = @fwrite($GLOBALS['file_handle'], $dump_buffer);
129 // Here, use strlen rather than mb_strlen to get the length
130 // in bytes to compare against the number of bytes written.
131 if ($write_result != strlen($dump_buffer)) {
132 $GLOBALS['message'] = Message::error(
133 __('Insufficient space to save the file %s.')
135 $GLOBALS['message']->addParam($save_filename);
136 return false;
138 } else {
139 echo $dump_buffer;
141 $dump_buffer = '';
142 $dump_buffer_len = 0;
144 } else {
145 $time_now = time();
146 if ($time_start >= $time_now + 30) {
147 $time_start = $time_now;
148 header('X-pmaPing: Pong');
149 } // end if
151 } elseif ($GLOBALS['asfile']) {
152 if ($GLOBALS['output_charset_conversion']) {
153 $line = Encoding::convertString(
154 'utf-8',
155 $GLOBALS['charset'],
156 $line
159 if ($GLOBALS['save_on_server'] && mb_strlen($line) > 0) {
160 if ($GLOBALS['file_handle'] !== null) {
161 $write_result = @fwrite($GLOBALS['file_handle'], $line);
162 } else {
163 $write_result = false;
165 // Here, use strlen rather than mb_strlen to get the length
166 // in bytes to compare against the number of bytes written.
167 if (! $write_result
168 || $write_result != strlen($line)
170 $GLOBALS['message'] = Message::error(
171 __('Insufficient space to save the file %s.')
173 $GLOBALS['message']->addParam($save_filename);
174 return false;
176 $time_now = time();
177 if ($time_start >= $time_now + 30) {
178 $time_start = $time_now;
179 header('X-pmaPing: Pong');
180 } // end if
181 } else {
182 // We export as file - output normally
183 echo $line;
185 } else {
186 // We export as html - replace special chars
187 echo htmlspecialchars($line);
189 return true;
193 * Returns HTML containing the footer for a displayed export
195 * @param string $back_button the link for going Back
196 * @param string $refreshButton the link for refreshing page
198 * @return string the HTML output
200 public function getHtmlForDisplayedExportFooter(
201 string $back_button,
202 string $refreshButton
203 ): string {
205 * Close the html tags and add the footers for on-screen export
207 return '</textarea>'
208 . ' </form>'
209 . '<br>'
210 // bottom back button
211 . $back_button
212 . $refreshButton
213 . '</div>'
214 . '<script type="text/javascript">' . "\n"
215 . '//<![CDATA[' . "\n"
216 . 'var $body = $("body");' . "\n"
217 . '$("#textSQLDUMP")' . "\n"
218 . '.width($body.width() - 50)' . "\n"
219 . '.height($body.height() - 100);' . "\n"
220 . '//]]>' . "\n"
221 . '</script>' . "\n";
225 * Computes the memory limit for export
227 * @return int the memory limit
229 public function getMemoryLimit(): int
231 $memory_limit = trim(ini_get('memory_limit'));
232 $memory_limit_num = (int) substr($memory_limit, 0, -1);
233 $lowerLastChar = strtolower(substr($memory_limit, -1));
234 // 2 MB as default
235 if (empty($memory_limit) || '-1' == $memory_limit) {
236 $memory_limit = 2 * 1024 * 1024;
237 } elseif ($lowerLastChar == 'm') {
238 $memory_limit = $memory_limit_num * 1024 * 1024;
239 } elseif ($lowerLastChar == 'k') {
240 $memory_limit = $memory_limit_num * 1024;
241 } elseif ($lowerLastChar == 'g') {
242 $memory_limit = $memory_limit_num * 1024 * 1024 * 1024;
243 } else {
244 $memory_limit = (int) $memory_limit;
247 // Some of memory is needed for other things and as threshold.
248 // During export I had allocated (see memory_get_usage function)
249 // approx 1.2MB so this comes from that.
250 if ($memory_limit > 1500000) {
251 $memory_limit -= 1500000;
254 // Some memory is needed for compression, assume 1/3
255 $memory_limit /= 8;
256 return $memory_limit;
260 * Return the filename and MIME type for export file
262 * @param string $export_type type of export
263 * @param string $remember_template whether to remember template
264 * @param ExportPlugin $export_plugin the export plugin
265 * @param string $compression compression asked
266 * @param string $filename_template the filename template
268 * @return string[] the filename template and mime type
270 public function getFilenameAndMimetype(
271 string $export_type,
272 string $remember_template,
273 ExportPlugin $export_plugin,
274 string $compression,
275 string $filename_template
276 ): array {
277 if ($export_type == 'server') {
278 if (! empty($remember_template)) {
279 $GLOBALS['PMA_Config']->setUserValue(
280 'pma_server_filename_template',
281 'Export/file_template_server',
282 $filename_template
285 } elseif ($export_type == 'database') {
286 if (! empty($remember_template)) {
287 $GLOBALS['PMA_Config']->setUserValue(
288 'pma_db_filename_template',
289 'Export/file_template_database',
290 $filename_template
293 } else {
294 if (! empty($remember_template)) {
295 $GLOBALS['PMA_Config']->setUserValue(
296 'pma_table_filename_template',
297 'Export/file_template_table',
298 $filename_template
302 $filename = Util::expandUserString($filename_template);
303 // remove dots in filename (coming from either the template or already
304 // part of the filename) to avoid a remote code execution vulnerability
305 $filename = Sanitize::sanitizeFilename($filename, $replaceDots = true);
307 // Grab basic dump extension and mime type
308 // Check if the user already added extension;
309 // get the substring where the extension would be if it was included
310 $extension_start_pos = mb_strlen($filename) - mb_strlen(
311 $export_plugin->getProperties()->getExtension()
312 ) - 1;
313 $user_extension = mb_substr(
314 $filename,
315 $extension_start_pos,
316 mb_strlen($filename)
318 $required_extension = "." . $export_plugin->getProperties()->getExtension();
319 if (mb_strtolower($user_extension) != $required_extension) {
320 $filename .= $required_extension;
322 $mime_type = $export_plugin->getProperties()->getMimeType();
324 // If dump is going to be compressed, set correct mime_type and add
325 // compression to extension
326 if ($compression == 'gzip') {
327 $filename .= '.gz';
328 $mime_type = 'application/x-gzip';
329 } elseif ($compression == 'zip') {
330 $filename .= '.zip';
331 $mime_type = 'application/zip';
333 return [
334 $filename,
335 $mime_type,
340 * Open the export file
342 * @param string $filename the export filename
343 * @param boolean $quick_export whether it's a quick export or not
345 * @return array the full save filename, possible message and the file handle
347 public function openFile(string $filename, bool $quick_export): array
349 $file_handle = null;
350 $message = '';
351 $doNotSaveItOver = true;
353 if (isset($_POST['quick_export_onserver_overwrite'])) {
354 $doNotSaveItOver = $_POST['quick_export_onserver_overwrite'] != 'saveitover';
357 $save_filename = Util::userDir($GLOBALS['cfg']['SaveDir'])
358 . preg_replace('@[/\\\\]@', '_', $filename);
360 if (@file_exists($save_filename)
361 && ((! $quick_export && empty($_POST['onserver_overwrite']))
362 || ($quick_export
363 && $doNotSaveItOver))
365 $message = Message::error(
367 'File %s already exists on server, '
368 . 'change filename or check overwrite option.'
371 $message->addParam($save_filename);
372 } elseif (@is_file($save_filename) && ! @is_writable($save_filename)) {
373 $message = Message::error(
375 'The web server does not have permission '
376 . 'to save the file %s.'
379 $message->addParam($save_filename);
380 } elseif (! $file_handle = @fopen($save_filename, 'w')) {
381 $message = Message::error(
383 'The web server does not have permission '
384 . 'to save the file %s.'
387 $message->addParam($save_filename);
389 return [
390 $save_filename,
391 $message,
392 $file_handle,
397 * Close the export file
399 * @param resource $file_handle the export file handle
400 * @param string $dump_buffer the current dump buffer
401 * @param string $save_filename the export filename
403 * @return Message a message object (or empty string)
405 public function closeFile(
406 $file_handle,
407 string $dump_buffer,
408 string $save_filename
409 ): Message {
410 $write_result = @fwrite($file_handle, $dump_buffer);
411 fclose($file_handle);
412 // Here, use strlen rather than mb_strlen to get the length
413 // in bytes to compare against the number of bytes written.
414 if (strlen($dump_buffer) > 0
415 && (! $write_result || $write_result != strlen($dump_buffer))
417 $message = new Message(
418 __('Insufficient space to save the file %s.'),
419 Message::ERROR,
420 [$save_filename]
422 } else {
423 $message = new Message(
424 __('Dump has been saved to file %s.'),
425 Message::SUCCESS,
426 [$save_filename]
429 return $message;
433 * Compress the export buffer
435 * @param array|string $dump_buffer the current dump buffer
436 * @param string $compression the compression mode
437 * @param string $filename the filename
439 * @return array|string|bool
441 public function compress($dump_buffer, string $compression, string $filename)
443 if ($compression == 'zip' && function_exists('gzcompress')) {
444 $zipExtension = new ZipExtension();
445 $filename = substr($filename, 0, -4); // remove extension (.zip)
446 $dump_buffer = $zipExtension->createFile($dump_buffer, $filename);
447 } elseif ($compression == 'gzip' && $this->gzencodeNeeded()) {
448 // without the optional parameter level because it bugs
449 $dump_buffer = gzencode($dump_buffer);
451 return $dump_buffer;
455 * Saves the dump_buffer for a particular table in an array
456 * Used in separate files export
458 * @param string $object_name the name of current object to be stored
459 * @param boolean $append optional boolean to append to an existing index or not
461 * @return void
463 public function saveObjectInBuffer(string $object_name, bool $append = false): void
465 global $dump_buffer_objects, $dump_buffer, $dump_buffer_len;
467 if (! empty($dump_buffer)) {
468 if ($append && isset($dump_buffer_objects[$object_name])) {
469 $dump_buffer_objects[$object_name] .= $dump_buffer;
470 } else {
471 $dump_buffer_objects[$object_name] = $dump_buffer;
475 // Re - initialize
476 $dump_buffer = '';
477 $dump_buffer_len = 0;
481 * Returns HTML containing the header for a displayed export
483 * @param string $export_type the export type
484 * @param string $db the database name
485 * @param string $table the table name
487 * @return string[] the generated HTML and back button
489 public function getHtmlForDisplayedExportHeader(
490 string $export_type,
491 string $db,
492 string $table
493 ): array {
494 $html = '<div>';
497 * Displays a back button with all the $_POST data in the URL
498 * (store in a variable to also display after the textarea)
500 $back_button = '<p id="export_back_button">[ <a href="';
501 if ($export_type == 'server') {
502 $back_button .= 'server_export.php" data-post="' . Url::getCommon([], '');
503 } elseif ($export_type == 'database') {
504 $back_button .= 'db_export.php" data-post="' . Url::getCommon(['db' => $db], '');
505 } else {
506 $back_button .= 'tbl_export.php" data-post="' . Url::getCommon(
508 'db' => $db,
509 'table' => $table,
515 // Convert the multiple select elements from an array to a string
516 if ($export_type == 'database') {
517 $structOrDataForced = empty($_POST['structure_or_data_forced']);
518 if ($structOrDataForced && ! isset($_POST['table_structure'])) {
519 $_POST['table_structure'] = [];
521 if ($structOrDataForced && ! isset($_POST['table_data'])) {
522 $_POST['table_data'] = [];
526 foreach ($_POST as $name => $value) {
527 if (! is_array($value)) {
528 $back_button .= '&amp;' . urlencode((string) $name) . '=' . urlencode((string) $value);
531 $back_button .= '&amp;repopulate=1">' . __('Back') . '</a> ]</p>';
532 $html .= '<br>';
533 $html .= $back_button;
534 $refreshButton = '<form id="export_refresh_form" method="POST" action="export.php" class="disableAjax">';
535 $refreshButton .= '[ <a class="disableAjax" onclick="$(this).parent().submit()">' . __('Refresh') . '</a> ]';
536 foreach ($_POST as $name => $value) {
537 if (is_array($value)) {
538 foreach ($value as $val) {
539 $refreshButton .= '<input type="hidden" name="' . htmlentities((string) $name) . '[]" value="' . htmlentities((string) $val) . '">';
541 } else {
542 $refreshButton .= '<input type="hidden" name="' . htmlentities((string) $name) . '" value="' . htmlentities((string) $value) . '">';
545 $refreshButton .= '</form>';
546 $html .= $refreshButton
547 . '<br>'
548 . '<form name="nofunction">'
549 . '<textarea name="sqldump" cols="50" rows="30" '
550 . 'id="textSQLDUMP" wrap="OFF">';
552 return [
553 $html,
554 $back_button,
555 $refreshButton,
560 * Export at the server level
562 * @param string|array $db_select the selected databases to export
563 * @param string $whatStrucOrData structure or data or both
564 * @param ExportPlugin $export_plugin the selected export plugin
565 * @param string $crlf end of line character(s)
566 * @param string $err_url the URL in case of error
567 * @param string $export_type the export type
568 * @param bool $do_relation whether to export relation info
569 * @param bool $do_comments whether to add comments
570 * @param bool $do_mime whether to add MIME info
571 * @param bool $do_dates whether to add dates
572 * @param array $aliases alias information for db/table/column
573 * @param string $separate_files whether it is a separate-files export
575 * @return void
577 public function exportServer(
578 $db_select,
579 string $whatStrucOrData,
580 ExportPlugin $export_plugin,
581 string $crlf,
582 string $err_url,
583 string $export_type,
584 bool $do_relation,
585 bool $do_comments,
586 bool $do_mime,
587 bool $do_dates,
588 array $aliases,
589 string $separate_files
590 ): void {
591 if (! empty($db_select)) {
592 $tmp_select = implode('|', $db_select);
593 $tmp_select = '|' . $tmp_select . '|';
595 // Walk over databases
596 foreach ($GLOBALS['dblist']->databases as $current_db) {
597 if (isset($tmp_select)
598 && mb_strpos(' ' . $tmp_select, '|' . $current_db . '|')
600 $tables = $this->dbi->getTables($current_db);
601 $this->exportDatabase(
602 $current_db,
603 $tables,
604 $whatStrucOrData,
605 $tables,
606 $tables,
607 $export_plugin,
608 $crlf,
609 $err_url,
610 $export_type,
611 $do_relation,
612 $do_comments,
613 $do_mime,
614 $do_dates,
615 $aliases,
616 $separate_files == 'database' ? $separate_files : ''
618 if ($separate_files == 'server') {
619 $this->saveObjectInBuffer($current_db);
622 } // end foreach database
626 * Export at the database level
628 * @param string $db the database to export
629 * @param array $tables the tables to export
630 * @param string $whatStrucOrData structure or data or both
631 * @param array $table_structure whether to export structure for each table
632 * @param array $table_data whether to export data for each table
633 * @param ExportPlugin $export_plugin the selected export plugin
634 * @param string $crlf end of line character(s)
635 * @param string $err_url the URL in case of error
636 * @param string $export_type the export type
637 * @param bool $do_relation whether to export relation info
638 * @param bool $do_comments whether to add comments
639 * @param bool $do_mime whether to add MIME info
640 * @param bool $do_dates whether to add dates
641 * @param array $aliases Alias information for db/table/column
642 * @param string $separate_files whether it is a separate-files export
644 * @return void
646 public function exportDatabase(
647 string $db,
648 array $tables,
649 string $whatStrucOrData,
650 array $table_structure,
651 array $table_data,
652 ExportPlugin $export_plugin,
653 string $crlf,
654 string $err_url,
655 string $export_type,
656 bool $do_relation,
657 bool $do_comments,
658 bool $do_mime,
659 bool $do_dates,
660 array $aliases,
661 string $separate_files
662 ): void {
663 $db_alias = ! empty($aliases[$db]['alias'])
664 ? $aliases[$db]['alias'] : '';
666 if (! $export_plugin->exportDBHeader($db, $db_alias)) {
667 return;
669 if (! $export_plugin->exportDBCreate($db, $export_type, $db_alias)) {
670 return;
672 if ($separate_files == 'database') {
673 $this->saveObjectInBuffer('database', true);
676 if (($GLOBALS['sql_structure_or_data'] == 'structure'
677 || $GLOBALS['sql_structure_or_data'] == 'structure_and_data')
678 && isset($GLOBALS['sql_procedure_function'])
680 $export_plugin->exportRoutines($db, $aliases);
682 if ($separate_files == 'database') {
683 $this->saveObjectInBuffer('routines');
687 $views = [];
689 foreach ($tables as $table) {
690 $_table = new Table($table, $db);
691 // if this is a view, collect it for later;
692 // views must be exported after the tables
693 $is_view = $_table->isView();
694 if ($is_view) {
695 $views[] = $table;
697 if (($whatStrucOrData == 'structure'
698 || $whatStrucOrData == 'structure_and_data')
699 && in_array($table, $table_structure)
701 // for a view, export a stand-in definition of the table
702 // to resolve view dependencies (only when it's a single-file export)
703 if ($is_view) {
704 if ($separate_files == ''
705 && isset($GLOBALS['sql_create_view'])
706 && ! $export_plugin->exportStructure(
707 $db,
708 $table,
709 $crlf,
710 $err_url,
711 'stand_in',
712 $export_type,
713 $do_relation,
714 $do_comments,
715 $do_mime,
716 $do_dates,
717 $aliases
720 break;
722 } elseif (isset($GLOBALS['sql_create_table'])) {
723 $table_size = $GLOBALS['maxsize'];
724 // Checking if the maximum table size constrain has been set
725 // And if that constrain is a valid number or not
726 if ($table_size !== '' && is_numeric($table_size)) {
727 // This obtains the current table's size
728 $query = 'SELECT data_length + index_length
729 from information_schema.TABLES
730 WHERE table_schema = "' . $this->dbi->escapeString($db) . '"
731 AND table_name = "' . $this->dbi->escapeString($table) . '"';
733 $size = $this->dbi->fetchValue($query);
734 //Converting the size to MB
735 $size = ($size / 1024) / 1024;
736 if ($size > $table_size) {
737 continue;
741 if (! $export_plugin->exportStructure(
742 $db,
743 $table,
744 $crlf,
745 $err_url,
746 'create_table',
747 $export_type,
748 $do_relation,
749 $do_comments,
750 $do_mime,
751 $do_dates,
752 $aliases
753 )) {
754 break;
758 // if this is a view or a merge table, don't export data
759 if (($whatStrucOrData == 'data' || $whatStrucOrData == 'structure_and_data')
760 && in_array($table, $table_data)
761 && ! $is_view
763 $tableObj = new Table($table, $db);
764 $nonGeneratedCols = $tableObj->getNonGeneratedColumns(true);
766 $local_query = 'SELECT ' . implode(', ', $nonGeneratedCols)
767 . ' FROM ' . Util::backquote($db)
768 . '.' . Util::backquote($table);
770 if (! $export_plugin->exportData(
771 $db,
772 $table,
773 $crlf,
774 $err_url,
775 $local_query,
776 $aliases
777 )) {
778 break;
782 // this buffer was filled, we save it and go to the next one
783 if ($separate_files == 'database') {
784 $this->saveObjectInBuffer('table_' . $table);
787 // now export the triggers (needs to be done after the data because
788 // triggers can modify already imported tables)
789 if (isset($GLOBALS['sql_create_trigger']) && ($whatStrucOrData == 'structure'
790 || $whatStrucOrData == 'structure_and_data')
791 && in_array($table, $table_structure)
793 if (! $export_plugin->exportStructure(
794 $db,
795 $table,
796 $crlf,
797 $err_url,
798 'triggers',
799 $export_type,
800 $do_relation,
801 $do_comments,
802 $do_mime,
803 $do_dates,
804 $aliases
805 )) {
806 break;
809 if ($separate_files == 'database') {
810 $this->saveObjectInBuffer('table_' . $table, true);
815 if (isset($GLOBALS['sql_create_view'])) {
816 foreach ($views as $view) {
817 // no data export for a view
818 if ($whatStrucOrData == 'structure'
819 || $whatStrucOrData == 'structure_and_data'
821 if (! $export_plugin->exportStructure(
822 $db,
823 $view,
824 $crlf,
825 $err_url,
826 'create_view',
827 $export_type,
828 $do_relation,
829 $do_comments,
830 $do_mime,
831 $do_dates,
832 $aliases
833 )) {
834 break;
837 if ($separate_files == 'database') {
838 $this->saveObjectInBuffer('view_' . $view);
844 if (! $export_plugin->exportDBFooter($db)) {
845 return;
848 // export metadata related to this db
849 if (isset($GLOBALS['sql_metadata'])) {
850 // Types of metadata to export.
851 // In the future these can be allowed to be selected by the user
852 $metadataTypes = $this->getMetadataTypes();
853 $export_plugin->exportMetadata($db, $tables, $metadataTypes);
855 if ($separate_files == 'database') {
856 $this->saveObjectInBuffer('metadata');
860 if ($separate_files == 'database') {
861 $this->saveObjectInBuffer('extra');
864 if (($GLOBALS['sql_structure_or_data'] == 'structure'
865 || $GLOBALS['sql_structure_or_data'] == 'structure_and_data')
866 && isset($GLOBALS['sql_procedure_function'])
868 $export_plugin->exportEvents($db);
870 if ($separate_files == 'database') {
871 $this->saveObjectInBuffer('events');
877 * Export at the table level
879 * @param string $db the database to export
880 * @param string $table the table to export
881 * @param string $whatStrucOrData structure or data or both
882 * @param ExportPlugin $export_plugin the selected export plugin
883 * @param string $crlf end of line character(s)
884 * @param string $err_url the URL in case of error
885 * @param string $export_type the export type
886 * @param bool $do_relation whether to export relation info
887 * @param bool $do_comments whether to add comments
888 * @param bool $do_mime whether to add MIME info
889 * @param bool $do_dates whether to add dates
890 * @param string|null $allrows whether "dump all rows" was ticked
891 * @param string $limit_to upper limit
892 * @param string $limit_from starting limit
893 * @param string $sql_query query for which exporting is requested
894 * @param array $aliases Alias information for db/table/column
896 * @return void
898 public function exportTable(
899 string $db,
900 string $table,
901 string $whatStrucOrData,
902 ExportPlugin $export_plugin,
903 string $crlf,
904 string $err_url,
905 string $export_type,
906 bool $do_relation,
907 bool $do_comments,
908 bool $do_mime,
909 bool $do_dates,
910 ?string $allrows,
911 string $limit_to,
912 string $limit_from,
913 string $sql_query,
914 array $aliases
915 ): void {
916 $db_alias = ! empty($aliases[$db]['alias'])
917 ? $aliases[$db]['alias'] : '';
918 if (! $export_plugin->exportDBHeader($db, $db_alias)) {
919 return;
921 if (isset($allrows)
922 && $allrows == '0'
923 && $limit_to > 0
924 && $limit_from >= 0
926 $add_query = ' LIMIT '
927 . ($limit_from > 0 ? $limit_from . ', ' : '')
928 . $limit_to;
929 } else {
930 $add_query = '';
933 $_table = new Table($table, $db);
934 $is_view = $_table->isView();
935 if ($whatStrucOrData == 'structure'
936 || $whatStrucOrData == 'structure_and_data'
938 if ($is_view) {
939 if (isset($GLOBALS['sql_create_view'])) {
940 if (! $export_plugin->exportStructure(
941 $db,
942 $table,
943 $crlf,
944 $err_url,
945 'create_view',
946 $export_type,
947 $do_relation,
948 $do_comments,
949 $do_mime,
950 $do_dates,
951 $aliases
952 )) {
953 return;
956 } elseif (isset($GLOBALS['sql_create_table'])) {
957 if (! $export_plugin->exportStructure(
958 $db,
959 $table,
960 $crlf,
961 $err_url,
962 'create_table',
963 $export_type,
964 $do_relation,
965 $do_comments,
966 $do_mime,
967 $do_dates,
968 $aliases
969 )) {
970 return;
974 // If this is an export of a single view, we have to export data;
975 // for example, a PDF report
976 // if it is a merge table, no data is exported
977 if ($whatStrucOrData == 'data'
978 || $whatStrucOrData == 'structure_and_data'
980 if (! empty($sql_query)) {
981 // only preg_replace if needed
982 if (! empty($add_query)) {
983 // remove trailing semicolon before adding a LIMIT
984 $sql_query = preg_replace('%;\s*$%', '', $sql_query);
986 $local_query = $sql_query . $add_query;
987 $this->dbi->selectDb($db);
988 } else {
989 // Data is exported only for Non-generated columns
990 $tableObj = new Table($table, $db);
991 $nonGeneratedCols = $tableObj->getNonGeneratedColumns(true);
993 $local_query = 'SELECT ' . implode(', ', $nonGeneratedCols)
994 . ' FROM ' . Util::backquote($db)
995 . '.' . Util::backquote($table) . $add_query;
997 if (! $export_plugin->exportData(
998 $db,
999 $table,
1000 $crlf,
1001 $err_url,
1002 $local_query,
1003 $aliases
1004 )) {
1005 return;
1008 // now export the triggers (needs to be done after the data because
1009 // triggers can modify already imported tables)
1010 if (isset($GLOBALS['sql_create_trigger']) && ($whatStrucOrData == 'structure'
1011 || $whatStrucOrData == 'structure_and_data')
1013 if (! $export_plugin->exportStructure(
1014 $db,
1015 $table,
1016 $crlf,
1017 $err_url,
1018 'triggers',
1019 $export_type,
1020 $do_relation,
1021 $do_comments,
1022 $do_mime,
1023 $do_dates,
1024 $aliases
1025 )) {
1026 return;
1029 if (! $export_plugin->exportDBFooter($db)) {
1030 return;
1033 if (isset($GLOBALS['sql_metadata'])) {
1034 // Types of metadata to export.
1035 // In the future these can be allowed to be selected by the user
1036 $metadataTypes = $this->getMetadataTypes();
1037 $export_plugin->exportMetadata($db, $table, $metadataTypes);
1042 * Loads correct page after doing export
1044 * @param string $db the database name
1045 * @param string $table the table name
1046 * @param string $export_type Export type
1048 * @return void
1050 public function showPage(string $db, string $table, string $export_type): void
1052 global $cfg;
1053 if ($export_type == 'server') {
1054 $active_page = 'server_export.php';
1055 include_once ROOT_PATH . 'server_export.php';
1056 } elseif ($export_type == 'database') {
1057 $active_page = 'db_export.php';
1058 include_once ROOT_PATH . 'db_export.php';
1059 } else {
1060 $active_page = 'tbl_export.php';
1061 include_once ROOT_PATH . 'tbl_export.php';
1063 exit;
1067 * Merge two alias arrays, if array1 and array2 have
1068 * conflicting alias then array2 value is used if it
1069 * is non empty otherwise array1 value.
1071 * @param array $aliases1 first array of aliases
1072 * @param array $aliases2 second array of aliases
1074 * @return array resultant merged aliases info
1076 public function mergeAliases(array $aliases1, array $aliases2): array
1078 // First do a recursive array merge
1079 // on aliases arrays.
1080 $aliases = array_merge_recursive($aliases1, $aliases2);
1081 // Now, resolve conflicts in aliases, if any
1082 foreach ($aliases as $db_name => $db) {
1083 // If alias key is an array then
1084 // it is a merge conflict.
1085 if (isset($db['alias']) && is_array($db['alias'])) {
1086 $val1 = $db['alias'][0];
1087 $val2 = $db['alias'][1];
1088 // Use aliases2 alias if non empty
1089 $aliases[$db_name]['alias']
1090 = empty($val2) ? $val1 : $val2;
1092 if (! isset($db['tables'])) {
1093 continue;
1095 foreach ($db['tables'] as $tbl_name => $tbl) {
1096 if (isset($tbl['alias']) && is_array($tbl['alias'])) {
1097 $val1 = $tbl['alias'][0];
1098 $val2 = $tbl['alias'][1];
1099 // Use aliases2 alias if non empty
1100 $aliases[$db_name]['tables'][$tbl_name]['alias']
1101 = empty($val2) ? $val1 : $val2;
1103 if (! isset($tbl['columns'])) {
1104 continue;
1106 foreach ($tbl['columns'] as $col => $col_as) {
1107 if (isset($col_as) && is_array($col_as)) {
1108 $val1 = $col_as[0];
1109 $val2 = $col_as[1];
1110 // Use aliases2 alias if non empty
1111 $aliases[$db_name]['tables'][$tbl_name]['columns'][$col]
1112 = empty($val2) ? $val1 : $val2;
1117 return $aliases;
1121 * Locks tables
1123 * @param string $db database name
1124 * @param array $tables list of table names
1125 * @param string $lockType lock type; "[LOW_PRIORITY] WRITE" or "READ [LOCAL]"
1127 * @return mixed result of the query
1129 public function lockTables(string $db, array $tables, string $lockType = "WRITE")
1131 $locks = [];
1132 foreach ($tables as $table) {
1133 $locks[] = Util::backquote($db) . "."
1134 . Util::backquote($table) . " " . $lockType;
1137 $sql = "LOCK TABLES " . implode(", ", $locks);
1138 return $this->dbi->tryQuery($sql);
1142 * Releases table locks
1144 * @return mixed result of the query
1146 public function unlockTables()
1148 return $this->dbi->tryQuery("UNLOCK TABLES");
1152 * Returns all the metadata types that can be exported with a database or a table
1154 * @return string[] metadata types.
1156 public function getMetadataTypes(): array
1158 return [
1159 'column_info',
1160 'table_uiprefs',
1161 'tracking',
1162 'bookmark',
1163 'relation',
1164 'table_coords',
1165 'pdf_pages',
1166 'savedsearches',
1167 'central_columns',
1168 'export_templates',
1173 * Returns the checked clause, depending on the presence of key in array
1175 * @param string $key the key to look for
1176 * @param array $array array to verify
1178 * @return string the checked clause
1180 public function getCheckedClause(string $key, array $array): string
1182 if (in_array($key, $array)) {
1183 return ' checked="checked"';
1186 return '';
1190 * get all the export options and verify
1191 * call and include the appropriate Schema Class depending on $export_type
1193 * @param string|null $export_type format of the export
1195 * @return void
1197 public function processExportSchema(?string $export_type): void
1200 * default is PDF, otherwise validate it's only letters a-z
1202 if (! isset($export_type) || ! preg_match('/^[a-zA-Z]+$/', $export_type)) {
1203 $export_type = 'pdf';
1206 // sanitize this parameter which will be used below in a file inclusion
1207 $export_type = Core::securePath($export_type);
1209 // get the specific plugin
1210 /** @var SchemaPlugin $export_plugin */
1211 $export_plugin = Plugins::getPlugin(
1212 "schema",
1213 $export_type,
1214 'libraries/classes/Plugins/Schema/'
1217 // Check schema export type
1218 if ($export_plugin === null || ! is_object($export_plugin)) {
1219 Core::fatalError(__('Bad type!'));
1222 $this->dbi->selectDb($GLOBALS['db']);
1223 $export_plugin->exportSchema($GLOBALS['db']);