ExportJson.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <?php
  2. /**
  3. * Set of methods used to build dumps of tables as JSON
  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 const JSON_PRETTY_PRINT;
  15. use const JSON_UNESCAPED_UNICODE;
  16. use function bin2hex;
  17. use function explode;
  18. use function json_encode;
  19. use function stripslashes;
  20. /**
  21. * Handles the export for the JSON format
  22. */
  23. class ExportJson extends ExportPlugin
  24. {
  25. /** @var bool */
  26. private $first = true;
  27. public function __construct()
  28. {
  29. parent::__construct();
  30. $this->setProperties();
  31. }
  32. /**
  33. * Encodes the data into JSON
  34. *
  35. * @param mixed $data Data to encode
  36. *
  37. * @return string
  38. */
  39. public function encode($data)
  40. {
  41. $options = 0;
  42. if (isset($GLOBALS['json_pretty_print'])
  43. && $GLOBALS['json_pretty_print']
  44. ) {
  45. $options |= JSON_PRETTY_PRINT;
  46. }
  47. if (isset($GLOBALS['json_unicode'])
  48. && $GLOBALS['json_unicode']
  49. ) {
  50. $options |= JSON_UNESCAPED_UNICODE;
  51. }
  52. return json_encode($data, $options);
  53. }
  54. /**
  55. * Sets the export JSON properties
  56. *
  57. * @return void
  58. */
  59. protected function setProperties()
  60. {
  61. $exportPluginProperties = new ExportPluginProperties();
  62. $exportPluginProperties->setText('JSON');
  63. $exportPluginProperties->setExtension('json');
  64. $exportPluginProperties->setMimeType('text/plain');
  65. $exportPluginProperties->setOptionsText(__('Options'));
  66. // create the root group that will be the options field for
  67. // $exportPluginProperties
  68. // this will be shown as "Format specific options"
  69. $exportSpecificOptions = new OptionsPropertyRootGroup(
  70. 'Format Specific Options'
  71. );
  72. // general options main group
  73. $generalOptions = new OptionsPropertyMainGroup('general_opts');
  74. // create primary items and add them to the group
  75. $leaf = new HiddenPropertyItem('structure_or_data');
  76. $generalOptions->addProperty($leaf);
  77. $leaf = new BoolPropertyItem(
  78. 'pretty_print',
  79. __('Output pretty-printed JSON (Use human-readable formatting)')
  80. );
  81. $generalOptions->addProperty($leaf);
  82. $leaf = new BoolPropertyItem(
  83. 'unicode',
  84. __('Output unicode characters unescaped')
  85. );
  86. $generalOptions->addProperty($leaf);
  87. // add the main group to the root group
  88. $exportSpecificOptions->addProperty($generalOptions);
  89. // set the options for the export plugin property item
  90. $exportPluginProperties->setOptions($exportSpecificOptions);
  91. $this->properties = $exportPluginProperties;
  92. }
  93. /**
  94. * Outputs export header
  95. *
  96. * @return bool Whether it succeeded
  97. */
  98. public function exportHeader()
  99. {
  100. global $crlf;
  101. $meta = [
  102. 'type' => 'header',
  103. 'version' => PMA_VERSION,
  104. 'comment' => 'Export to JSON plugin for PHPMyAdmin',
  105. ];
  106. return $this->export->outputHandler(
  107. '[' . $crlf . $this->encode($meta) . ',' . $crlf
  108. );
  109. }
  110. /**
  111. * Outputs export footer
  112. *
  113. * @return bool Whether it succeeded
  114. */
  115. public function exportFooter()
  116. {
  117. global $crlf;
  118. return $this->export->outputHandler(']' . $crlf);
  119. }
  120. /**
  121. * Outputs database header
  122. *
  123. * @param string $db Database name
  124. * @param string $db_alias Aliases of db
  125. *
  126. * @return bool Whether it succeeded
  127. */
  128. public function exportDBHeader($db, $db_alias = '')
  129. {
  130. global $crlf;
  131. if (empty($db_alias)) {
  132. $db_alias = $db;
  133. }
  134. $meta = [
  135. 'type' => 'database',
  136. 'name' => $db_alias,
  137. ];
  138. return $this->export->outputHandler(
  139. $this->encode($meta) . ',' . $crlf
  140. );
  141. }
  142. /**
  143. * Outputs database footer
  144. *
  145. * @param string $db Database name
  146. *
  147. * @return bool Whether it succeeded
  148. */
  149. public function exportDBFooter($db)
  150. {
  151. return true;
  152. }
  153. /**
  154. * Outputs CREATE DATABASE statement
  155. *
  156. * @param string $db Database name
  157. * @param string $export_type 'server', 'database', 'table'
  158. * @param string $db_alias Aliases of db
  159. *
  160. * @return bool Whether it succeeded
  161. */
  162. public function exportDBCreate($db, $export_type, $db_alias = '')
  163. {
  164. return true;
  165. }
  166. /**
  167. * Outputs the content of a table in JSON format
  168. *
  169. * @param string $db database name
  170. * @param string $table table name
  171. * @param string $crlf the end of line sequence
  172. * @param string $error_url the url to go back in case of error
  173. * @param string $sql_query SQL query for obtaining data
  174. * @param array $aliases Aliases of db/table/columns
  175. *
  176. * @return bool Whether it succeeded
  177. */
  178. public function exportData(
  179. $db,
  180. $table,
  181. $crlf,
  182. $error_url,
  183. $sql_query,
  184. array $aliases = []
  185. ) {
  186. global $dbi;
  187. $db_alias = $db;
  188. $table_alias = $table;
  189. $this->initAlias($aliases, $db_alias, $table_alias);
  190. if (! $this->first) {
  191. if (! $this->export->outputHandler(',')) {
  192. return false;
  193. }
  194. } else {
  195. $this->first = false;
  196. }
  197. $buffer = $this->encode(
  198. [
  199. 'type' => 'table',
  200. 'name' => $table_alias,
  201. 'database' => $db_alias,
  202. 'data' => '@@DATA@@',
  203. ]
  204. );
  205. return $this->doExportForQuery(
  206. $dbi,
  207. $sql_query,
  208. $buffer,
  209. $crlf,
  210. $aliases,
  211. $db,
  212. $table
  213. );
  214. }
  215. /**
  216. * Export to JSON
  217. *
  218. * @return bool False on export fail and true on export end success
  219. *
  220. * @phpstan-param array{
  221. * string: array{
  222. * 'tables': array{
  223. * string: array{
  224. * 'columns': array{string: string}
  225. * }
  226. * }
  227. * }
  228. * }|array|null $aliases
  229. */
  230. protected function doExportForQuery(
  231. DatabaseInterface $dbi,
  232. string $sql_query,
  233. string $buffer,
  234. string $crlf,
  235. ?array $aliases,
  236. ?string $db,
  237. ?string $table
  238. ): bool {
  239. [$header, $footer] = explode('"@@DATA@@"', $buffer);
  240. if (! $this->export->outputHandler($header . $crlf . '[' . $crlf)) {
  241. return false;
  242. }
  243. $result = $dbi->query(
  244. $sql_query,
  245. DatabaseInterface::CONNECT_USER,
  246. DatabaseInterface::QUERY_UNBUFFERED
  247. );
  248. $columns_cnt = $dbi->numFields($result);
  249. $fieldsMeta = $dbi->getFieldsMeta($result);
  250. $columns = [];
  251. for ($i = 0; $i < $columns_cnt; $i++) {
  252. $col_as = $dbi->fieldName($result, $i);
  253. if ($db !== null && $table !== null && $aliases !== null
  254. && ! empty($aliases[$db]['tables'][$table]['columns'][$col_as])
  255. ) {
  256. $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as];
  257. }
  258. $columns[$i] = stripslashes($col_as);
  259. }
  260. $record_cnt = 0;
  261. while ($record = $dbi->fetchRow($result)) {
  262. $record_cnt++;
  263. // Output table name as comment if this is the first record of the table
  264. if ($record_cnt > 1) {
  265. if (! $this->export->outputHandler(',' . $crlf)) {
  266. return false;
  267. }
  268. }
  269. $data = [];
  270. for ($i = 0; $i < $columns_cnt; $i++) {
  271. // 63 is the binary charset, see: https://dev.mysql.com/doc/internals/en/charsets.html
  272. $isBlobAndIsBinaryCharset = $fieldsMeta[$i]->type === 'blob' && $fieldsMeta[$i]->charsetnr === 63;
  273. // This can occur for binary fields
  274. $isBinaryString = $fieldsMeta[$i]->type === 'string' && $fieldsMeta[$i]->charsetnr === 63;
  275. if (
  276. (
  277. $fieldsMeta[$i]->type === 'geometry'
  278. || $isBlobAndIsBinaryCharset
  279. || $isBinaryString
  280. )
  281. && $record[$i] !== null
  282. ) {
  283. // export GIS and blob types as hex
  284. $record[$i] = '0x' . bin2hex($record[$i]);
  285. }
  286. $data[$columns[$i]] = $record[$i];
  287. }
  288. $encodedData = $this->encode($data);
  289. if (! $encodedData) {
  290. return false;
  291. }
  292. if (! $this->export->outputHandler($encodedData)) {
  293. return false;
  294. }
  295. }
  296. if (! $this->export->outputHandler($crlf . ']' . $crlf . $footer . $crlf)) {
  297. return false;
  298. }
  299. $dbi->freeResult($result);
  300. return true;
  301. }
  302. /**
  303. * Outputs result raw query in JSON format
  304. *
  305. * @param string $err_url the url to go back in case of error
  306. * @param string $sql_query the rawquery to output
  307. * @param string $crlf the end of line sequence
  308. *
  309. * @return bool if succeeded
  310. */
  311. public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
  312. {
  313. global $dbi;
  314. $buffer = $this->encode(
  315. [
  316. 'type' => 'raw',
  317. 'data' => '@@DATA@@',
  318. ]
  319. );
  320. return $this->doExportForQuery(
  321. $dbi,
  322. $sql_query,
  323. $buffer,
  324. $crlf,
  325. null,
  326. null,
  327. null
  328. );
  329. }
  330. }