ExportXml.php 17 KB

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