ExportXml.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. /**
  3. * Set of functions used to build XML dumps of tables
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Plugins\Export;
  7. use PhpMyAdmin\DatabaseInterface;
  8. use PhpMyAdmin\Plugins\ExportPlugin;
  9. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup;
  10. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup;
  11. use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
  12. use PhpMyAdmin\Properties\Options\Items\HiddenPropertyItem;
  13. use PhpMyAdmin\Properties\Plugins\ExportPluginProperties;
  14. use PhpMyAdmin\Util;
  15. use const PHP_VERSION;
  16. use function count;
  17. use function htmlspecialchars;
  18. use function is_array;
  19. use function mb_substr;
  20. use function rtrim;
  21. use function str_replace;
  22. use function stripslashes;
  23. use function strlen;
  24. // phpcs:disable PSR1.Files.SideEffects
  25. /* Can't do server export */
  26. if (! isset($GLOBALS['db']) || strlen($GLOBALS['db']) === 0) {
  27. $GLOBALS['skip_import'] = true;
  28. return;
  29. }
  30. // phpcs:enable
  31. /**
  32. * Handles the export for the XML class
  33. */
  34. class ExportXml extends ExportPlugin
  35. {
  36. /**
  37. * Table name
  38. *
  39. * @var string
  40. */
  41. private $table;
  42. /**
  43. * Table names
  44. *
  45. * @var array
  46. */
  47. private $tables;
  48. public function __construct()
  49. {
  50. parent::__construct();
  51. $this->setProperties();
  52. }
  53. /**
  54. * Initialize the local variables that are used for export XML
  55. *
  56. * @return void
  57. */
  58. protected function initSpecificVariables()
  59. {
  60. global $table, $tables;
  61. $this->setTable($table);
  62. if (! is_array($tables)) {
  63. return;
  64. }
  65. $this->setTables($tables);
  66. }
  67. /**
  68. * Sets the export XML properties
  69. *
  70. * @return void
  71. */
  72. protected function setProperties()
  73. {
  74. // create the export plugin property item
  75. $exportPluginProperties = new ExportPluginProperties();
  76. $exportPluginProperties->setText('XML');
  77. $exportPluginProperties->setExtension('xml');
  78. $exportPluginProperties->setMimeType('text/xml');
  79. $exportPluginProperties->setOptionsText(__('Options'));
  80. // create the root group that will be the options field for
  81. // $exportPluginProperties
  82. // this will be shown as "Format specific options"
  83. $exportSpecificOptions = new OptionsPropertyRootGroup(
  84. 'Format Specific Options'
  85. );
  86. // general options main group
  87. $generalOptions = new OptionsPropertyMainGroup('general_opts');
  88. // create primary items and add them to the group
  89. $leaf = new HiddenPropertyItem('structure_or_data');
  90. $generalOptions->addProperty($leaf);
  91. // add the main group to the root group
  92. $exportSpecificOptions->addProperty($generalOptions);
  93. // export structure main group
  94. $structure = new OptionsPropertyMainGroup(
  95. 'structure',
  96. __('Object creation options (all are recommended)')
  97. );
  98. // create primary items and add them to the group
  99. $leaf = new BoolPropertyItem(
  100. 'export_events',
  101. __('Events')
  102. );
  103. $structure->addProperty($leaf);
  104. $leaf = new BoolPropertyItem(
  105. 'export_functions',
  106. __('Functions')
  107. );
  108. $structure->addProperty($leaf);
  109. $leaf = new BoolPropertyItem(
  110. 'export_procedures',
  111. __('Procedures')
  112. );
  113. $structure->addProperty($leaf);
  114. $leaf = new BoolPropertyItem(
  115. 'export_tables',
  116. __('Tables')
  117. );
  118. $structure->addProperty($leaf);
  119. $leaf = new BoolPropertyItem(
  120. 'export_triggers',
  121. __('Triggers')
  122. );
  123. $structure->addProperty($leaf);
  124. $leaf = new BoolPropertyItem(
  125. 'export_views',
  126. __('Views')
  127. );
  128. $structure->addProperty($leaf);
  129. $exportSpecificOptions->addProperty($structure);
  130. // data main group
  131. $data = new OptionsPropertyMainGroup(
  132. 'data',
  133. __('Data dump options')
  134. );
  135. // create primary items and add them to the group
  136. $leaf = new BoolPropertyItem(
  137. 'export_contents',
  138. __('Export contents')
  139. );
  140. $data->addProperty($leaf);
  141. $exportSpecificOptions->addProperty($data);
  142. // set the options for the export plugin property item
  143. $exportPluginProperties->setOptions($exportSpecificOptions);
  144. $this->properties = $exportPluginProperties;
  145. }
  146. /**
  147. * Generates output for SQL defintions of routines
  148. *
  149. * @param string $db Database name
  150. * @param string $type Item type to be used in XML output
  151. * @param string $dbitype Item type used in DBI queries
  152. *
  153. * @return string XML with definitions
  154. */
  155. private function exportRoutinesDefinition($db, $type, $dbitype)
  156. {
  157. global $dbi;
  158. // Export routines
  159. $routines = $dbi->getProceduresOrFunctions(
  160. $db,
  161. $dbitype
  162. );
  163. return $this->exportDefinitions($db, $type, $dbitype, $routines);
  164. }
  165. /**
  166. * Generates output for SQL defintions
  167. *
  168. * @param string $db Database name
  169. * @param string $type Item type to be used in XML output
  170. * @param string $dbitype Item type used in DBI queries
  171. * @param array $names Names of items to export
  172. *
  173. * @return string XML with definitions
  174. */
  175. private function exportDefinitions($db, $type, $dbitype, array $names)
  176. {
  177. global $crlf, $dbi;
  178. $head = '';
  179. if ($names) {
  180. foreach ($names as $name) {
  181. $head .= ' <pma:' . $type . ' name="'
  182. . htmlspecialchars($name) . '">' . $crlf;
  183. // Do some formatting
  184. $sql = $dbi->getDefinition($db, $dbitype, $name);
  185. $sql = htmlspecialchars(rtrim($sql));
  186. $sql = str_replace("\n", "\n ", $sql);
  187. $head .= ' ' . $sql . $crlf;
  188. $head .= ' </pma:' . $type . '>' . $crlf;
  189. }
  190. }
  191. return $head;
  192. }
  193. /**
  194. * Outputs export header. It is the first method to be called, so all
  195. * the required variables are initialized here.
  196. *
  197. * @return bool Whether it succeeded
  198. */
  199. public function exportHeader()
  200. {
  201. $this->initSpecificVariables();
  202. global $crlf, $cfg, $db, $dbi;
  203. $table = $this->getTable();
  204. $tables = $this->getTables();
  205. $export_struct = isset($GLOBALS['xml_export_functions'])
  206. || isset($GLOBALS['xml_export_procedures'])
  207. || isset($GLOBALS['xml_export_tables'])
  208. || isset($GLOBALS['xml_export_triggers'])
  209. || isset($GLOBALS['xml_export_views']);
  210. $export_data = isset($GLOBALS['xml_export_contents']);
  211. if ($GLOBALS['output_charset_conversion']) {
  212. $charset = $GLOBALS['charset'];
  213. } else {
  214. $charset = 'utf-8';
  215. }
  216. $head = '<?xml version="1.0" encoding="' . $charset . '"?>' . $crlf
  217. . '<!--' . $crlf
  218. . '- phpMyAdmin XML Dump' . $crlf
  219. . '- version ' . PMA_VERSION . $crlf
  220. . '- https://www.phpmyadmin.net' . $crlf
  221. . '-' . $crlf
  222. . '- ' . __('Host:') . ' ' . htmlspecialchars($cfg['Server']['host']);
  223. if (! empty($cfg['Server']['port'])) {
  224. $head .= ':' . $cfg['Server']['port'];
  225. }
  226. $head .= $crlf
  227. . '- ' . __('Generation Time:') . ' '
  228. . Util::localisedDate() . $crlf
  229. . '- ' . __('Server version:') . ' ' . $dbi->getVersionString() . $crlf
  230. . '- ' . __('PHP Version:') . ' ' . PHP_VERSION . $crlf
  231. . '-->' . $crlf . $crlf;
  232. $head .= '<pma_xml_export version="1.0"'
  233. . ($export_struct
  234. ? ' xmlns:pma="https://www.phpmyadmin.net/some_doc_url/"'
  235. : '')
  236. . '>' . $crlf;
  237. if ($export_struct) {
  238. $result = $dbi->fetchResult(
  239. 'SELECT `DEFAULT_CHARACTER_SET_NAME`, `DEFAULT_COLLATION_NAME`'
  240. . ' FROM `information_schema`.`SCHEMATA` WHERE `SCHEMA_NAME`'
  241. . ' = \'' . $dbi->escapeString($db) . '\' LIMIT 1'
  242. );
  243. $db_collation = $result[0]['DEFAULT_COLLATION_NAME'];
  244. $db_charset = $result[0]['DEFAULT_CHARACTER_SET_NAME'];
  245. $head .= ' <!--' . $crlf;
  246. $head .= ' - Structure schemas' . $crlf;
  247. $head .= ' -->' . $crlf;
  248. $head .= ' <pma:structure_schemas>' . $crlf;
  249. $head .= ' <pma:database name="' . htmlspecialchars($db)
  250. . '" collation="' . htmlspecialchars($db_collation) . '" charset="' . htmlspecialchars($db_charset)
  251. . '">' . $crlf;
  252. if ($tables === null) {
  253. $tables = [];
  254. }
  255. if (count($tables) === 0) {
  256. $tables[] = $table;
  257. }
  258. foreach ($tables as $table) {
  259. // Export tables and views
  260. $result = $dbi->fetchResult(
  261. 'SHOW CREATE TABLE ' . Util::backquote($db) . '.'
  262. . Util::backquote($table),
  263. 0
  264. );
  265. $tbl = (string) $result[$table][1];
  266. $is_view = $dbi->getTable($db, $table)
  267. ->isView();
  268. if ($is_view) {
  269. $type = 'view';
  270. } else {
  271. $type = 'table';
  272. }
  273. if ($is_view && ! isset($GLOBALS['xml_export_views'])) {
  274. continue;
  275. }
  276. if (! $is_view && ! isset($GLOBALS['xml_export_tables'])) {
  277. continue;
  278. }
  279. $head .= ' <pma:' . $type . ' name="' . htmlspecialchars($table) . '">'
  280. . $crlf;
  281. $tbl = ' ' . htmlspecialchars($tbl);
  282. $tbl = str_replace("\n", "\n ", $tbl);
  283. $head .= $tbl . ';' . $crlf;
  284. $head .= ' </pma:' . $type . '>' . $crlf;
  285. if (! isset($GLOBALS['xml_export_triggers'])
  286. || ! $GLOBALS['xml_export_triggers']
  287. ) {
  288. continue;
  289. }
  290. // Export triggers
  291. $triggers = $dbi->getTriggers($db, $table);
  292. if (! $triggers) {
  293. continue;
  294. }
  295. foreach ($triggers as $trigger) {
  296. $code = $trigger['create'];
  297. $head .= ' <pma:trigger name="'
  298. . htmlspecialchars($trigger['name']) . '">' . $crlf;
  299. // Do some formatting
  300. $code = mb_substr(rtrim($code), 0, -3);
  301. $code = ' ' . htmlspecialchars($code);
  302. $code = str_replace("\n", "\n ", $code);
  303. $head .= $code . $crlf;
  304. $head .= ' </pma:trigger>' . $crlf;
  305. }
  306. unset($trigger, $triggers);
  307. }
  308. if (isset($GLOBALS['xml_export_functions'])
  309. && $GLOBALS['xml_export_functions']
  310. ) {
  311. $head .= $this->exportRoutinesDefinition($db, 'function', 'FUNCTION');
  312. }
  313. if (isset($GLOBALS['xml_export_procedures'])
  314. && $GLOBALS['xml_export_procedures']
  315. ) {
  316. $head .= $this->exportRoutinesDefinition($db, 'procedure', 'PROCEDURE');
  317. }
  318. if (isset($GLOBALS['xml_export_events'])
  319. && $GLOBALS['xml_export_events']
  320. ) {
  321. // Export events
  322. $events = $dbi->fetchResult(
  323. 'SELECT EVENT_NAME FROM information_schema.EVENTS '
  324. . "WHERE EVENT_SCHEMA='" . $dbi->escapeString($db)
  325. . "'"
  326. );
  327. $head .= $this->exportDefinitions(
  328. $db,
  329. 'event',
  330. 'EVENT',
  331. $events
  332. );
  333. }
  334. unset($result);
  335. $head .= ' </pma:database>' . $crlf;
  336. $head .= ' </pma:structure_schemas>' . $crlf;
  337. if ($export_data) {
  338. $head .= $crlf;
  339. }
  340. }
  341. return $this->export->outputHandler($head);
  342. }
  343. /**
  344. * Outputs export footer
  345. *
  346. * @return bool Whether it succeeded
  347. */
  348. public function exportFooter()
  349. {
  350. $foot = '</pma_xml_export>';
  351. return $this->export->outputHandler($foot);
  352. }
  353. /**
  354. * Outputs database header
  355. *
  356. * @param string $db Database name
  357. * @param string $db_alias Aliases of db
  358. *
  359. * @return bool Whether it succeeded
  360. */
  361. public function exportDBHeader($db, $db_alias = '')
  362. {
  363. global $crlf;
  364. if (empty($db_alias)) {
  365. $db_alias = $db;
  366. }
  367. if (isset($GLOBALS['xml_export_contents'])
  368. && $GLOBALS['xml_export_contents']
  369. ) {
  370. $head = ' <!--' . $crlf
  371. . ' - ' . __('Database:') . ' \''
  372. . htmlspecialchars($db_alias) . '\'' . $crlf
  373. . ' -->' . $crlf . ' <database name="'
  374. . htmlspecialchars($db_alias) . '">' . $crlf;
  375. return $this->export->outputHandler($head);
  376. }
  377. return true;
  378. }
  379. /**
  380. * Outputs database footer
  381. *
  382. * @param string $db Database name
  383. *
  384. * @return bool Whether it succeeded
  385. */
  386. public function exportDBFooter($db)
  387. {
  388. global $crlf;
  389. if (isset($GLOBALS['xml_export_contents'])
  390. && $GLOBALS['xml_export_contents']
  391. ) {
  392. return $this->export->outputHandler(' </database>' . $crlf);
  393. }
  394. return true;
  395. }
  396. /**
  397. * Outputs CREATE DATABASE statement
  398. *
  399. * @param string $db Database name
  400. * @param string $export_type 'server', 'database', 'table'
  401. * @param string $db_alias Aliases of db
  402. *
  403. * @return bool Whether it succeeded
  404. */
  405. public function exportDBCreate($db, $export_type, $db_alias = '')
  406. {
  407. return true;
  408. }
  409. /**
  410. * Outputs the content of a table in XML format
  411. *
  412. * @param string $db database name
  413. * @param string $table table name
  414. * @param string $crlf the end of line sequence
  415. * @param string $error_url the url to go back in case of error
  416. * @param string $sql_query SQL query for obtaining data
  417. * @param array $aliases Aliases of db/table/columns
  418. *
  419. * @return bool Whether it succeeded
  420. */
  421. public function exportData(
  422. $db,
  423. $table,
  424. $crlf,
  425. $error_url,
  426. $sql_query,
  427. array $aliases = []
  428. ) {
  429. global $dbi;
  430. // Do not export data for merge tables
  431. if ($dbi->getTable($db, $table)->isMerge()) {
  432. return true;
  433. }
  434. $db_alias = $db;
  435. $table_alias = $table;
  436. $this->initAlias($aliases, $db_alias, $table_alias);
  437. if (isset($GLOBALS['xml_export_contents'])
  438. && $GLOBALS['xml_export_contents']
  439. ) {
  440. $result = $dbi->query(
  441. $sql_query,
  442. DatabaseInterface::CONNECT_USER,
  443. DatabaseInterface::QUERY_UNBUFFERED
  444. );
  445. $columns_cnt = $dbi->numFields($result);
  446. $columns = [];
  447. for ($i = 0; $i < $columns_cnt; $i++) {
  448. $columns[$i] = stripslashes($dbi->fieldName($result, $i));
  449. }
  450. unset($i);
  451. $buffer = ' <!-- ' . __('Table') . ' '
  452. . htmlspecialchars($table_alias) . ' -->' . $crlf;
  453. if (! $this->export->outputHandler($buffer)) {
  454. return false;
  455. }
  456. while ($record = $dbi->fetchRow($result)) {
  457. $buffer = ' <table name="'
  458. . htmlspecialchars($table_alias) . '">' . $crlf;
  459. for ($i = 0; $i < $columns_cnt; $i++) {
  460. $col_as = $columns[$i];
  461. if (! empty($aliases[$db]['tables'][$table]['columns'][$col_as])
  462. ) {
  463. $col_as
  464. = $aliases[$db]['tables'][$table]['columns'][$col_as];
  465. }
  466. // If a cell is NULL, still export it to preserve
  467. // the XML structure
  468. if (! isset($record[$i]) || $record[$i] === null) {
  469. $record[$i] = 'NULL';
  470. }
  471. $buffer .= ' <column name="'
  472. . htmlspecialchars($col_as) . '">'
  473. . htmlspecialchars((string) $record[$i])
  474. . '</column>' . $crlf;
  475. }
  476. $buffer .= ' </table>' . $crlf;
  477. if (! $this->export->outputHandler($buffer)) {
  478. return false;
  479. }
  480. }
  481. $dbi->freeResult($result);
  482. }
  483. return true;
  484. }
  485. /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
  486. /**
  487. * Gets the table name
  488. *
  489. * @return string
  490. */
  491. private function getTable()
  492. {
  493. return $this->table;
  494. }
  495. /**
  496. * Sets the table name
  497. *
  498. * @param string $table table name
  499. *
  500. * @return void
  501. */
  502. private function setTable($table)
  503. {
  504. $this->table = $table;
  505. }
  506. /**
  507. * Gets the table names
  508. *
  509. * @return array
  510. */
  511. private function getTables()
  512. {
  513. return $this->tables;
  514. }
  515. /**
  516. * Sets the table names
  517. *
  518. * @param array $tables table names
  519. *
  520. * @return void
  521. */
  522. private function setTables(array $tables)
  523. {
  524. $this->tables = $tables;
  525. }
  526. }