ExportJson.php 9.8 KB

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