Export.php 43 KB

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