diff --git a/ChangeLog b/ChangeLog index be225c41ca..390e3779ba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,7 @@ phpMyAdmin - ChangeLog - issue #12196 Removed $cfg['ThemePath'] - issue #6274 Add support for export user settings as config.inc.php snippet - issue #5555 Better report query errors while generating SQL exports +- issue #12307 Produce valid JSON on export 4.6.3 (not yet released) - issue #12249 Fixed cookie path on Windows diff --git a/doc/import_export.rst b/doc/import_export.rst index 82549bb01c..e005be09e7 100644 --- a/doc/import_export.rst +++ b/doc/import_export.rst @@ -152,6 +152,88 @@ JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write and it is easy for machines to parse and generate. +.. versionchanged:: 4.7.0 + + The generated JSON structure has been changed in phpMyAdmin 4.7.0 to + produce valid JSON data. + +The generated JSON is list of objects with following attributes: + +.. js:data:: type + + Type of given object, can be one of: + + ``header`` + Export header containing comment and phpMyAdmin version. + ``database`` + Start of a database marker, containing name of database. + ``table`` + Table data export. + +.. js:data:: version + + Used in ``header`` :js:data:`type` and indicates phpMyAdmin version. + +.. js:data:: comment + + Optional textual comment. + +.. js:data:: name + + Object name - either table or database based on :js:data:`type`. + +.. js:data:: database + + Database name for ``table`` :js:data:`type`. + +.. js:data:: data + + Table content for ``table`` :js:data:`type`. + +Sample output: + +.. code-block:: json + + [ + { + "comment": "Export to JSON plugin for PHPMyAdmin", + "type": "header", + "version": "4.7.0-dev" + }, + { + "name": "cars", + "type": "database" + }, + { + "data": [ + { + "car_id": "1", + "description": "Green Chrysler 300", + "make_id": "5", + "mileage": "113688", + "price": "13545.00", + "transmission": "automatic", + "yearmade": "2007" + } + ], + "database": "cars", + "name": "cars", + "type": "table" + }, + { + "data": [ + { + "make": "Chrysler", + "make_id": "5" + } + ], + "database": "cars", + "name": "makes", + "type": "table" + } + ] + + LaTeX ----- diff --git a/libraries/plugins/export/ExportJson.php b/libraries/plugins/export/ExportJson.php index e26a761207..0747dd35b3 100644 --- a/libraries/plugins/export/ExportJson.php +++ b/libraries/plugins/export/ExportJson.php @@ -24,6 +24,8 @@ use PMA; */ class ExportJson extends ExportPlugin { + private $first = true; + /** * Constructor */ @@ -32,6 +34,24 @@ class ExportJson extends ExportPlugin $this->setProperties(); } + /** + * Encodes the data into JSON + * + * @param mixed $data Data to encode + * + * @return string + */ + public function encode($data) + { + if (isset($GLOBALS['json_pretty_print']) + && $GLOBALS['json_pretty_print'] + ) { + return json_encode($data, JSON_PRETTY_PRINT); + } else { + return json_encode($data); + } + } + /** * Sets the export JSON properties * @@ -79,14 +99,17 @@ class ExportJson extends ExportPlugin */ public function exportHeader() { - PMA_exportOutputHandler( - '/**' . $GLOBALS['crlf'] - . ' Export to JSON plugin for PHPMyAdmin' . $GLOBALS['crlf'] - . ' @version ' . PMA_VERSION . $GLOBALS['crlf'] - . ' */' . $GLOBALS['crlf'] . $GLOBALS['crlf'] + global $crlf; + + $meta = array( + 'type' => 'header', + 'version' => PMA_VERSION, + 'comment' => 'Export to JSON plugin for PHPMyAdmin', ); - return true; + return PMA_exportOutputHandler( + '[' . $crlf . $this->encode($meta) . ',' . $crlf + ); } /** @@ -96,7 +119,9 @@ class ExportJson extends ExportPlugin */ public function exportFooter() { - return true; + global $crlf; + + return PMA_exportOutputHandler(']' . $crlf); } /** @@ -109,14 +134,20 @@ class ExportJson extends ExportPlugin */ public function exportDBHeader($db, $db_alias = '') { + global $crlf; + if (empty($db_alias)) { $db_alias = $db; } - PMA_exportOutputHandler( - '// Database \'' . $db_alias . '\'' . $GLOBALS['crlf'] + + $meta = array( + 'type' => 'database', + 'name' => $db_alias ); - return true; + return PMA_exportOutputHandler( + $this->encode($meta) . ',' . $crlf + ); } /** @@ -169,6 +200,28 @@ class ExportJson extends ExportPlugin $table_alias = $table; $this->initAlias($aliases, $db_alias, $table_alias); + if (! $this->first) { + if (!PMA_exportOutputHandler(',')) { + return false; + } + } else { + $this->first = false; + } + + $buffer = $this->encode( + array( + 'type' => 'table', + 'name' => $table_alias, + 'database' => $db_alias, + 'data' => "@@DATA@@" + ) + ); + list($header, $footer) = explode('"@@DATA@@"', $buffer); + + if (!PMA_exportOutputHandler($header . $crlf . '[' . $crlf)) { + return false; + } + $result = $GLOBALS['dbi']->query( $sql_query, null, @@ -191,16 +244,10 @@ class ExportJson extends ExportPlugin $record_cnt++; // Output table name as comment if this is the first record of the table - if ($record_cnt == 1) { - $buffer = $crlf . '// ' . $db_alias . '.' . $table_alias - . $crlf . $crlf; - $buffer .= '['; - } else { - $buffer = ', '; - } - - if (!PMA_exportOutputHandler($buffer)) { - return false; + if ($record_cnt > 1) { + if (!PMA_exportOutputHandler(',' . $crlf)) { + return false; + } } $data = array(); @@ -209,23 +256,13 @@ class ExportJson extends ExportPlugin $data[$columns[$i]] = $record[$i]; } - if (isset($GLOBALS['json_pretty_print']) - && $GLOBALS['json_pretty_print'] - ) { - $encoded = json_encode($data, JSON_PRETTY_PRINT); - } else { - $encoded = json_encode($data); - } - - if (!PMA_exportOutputHandler($encoded)) { + if (!PMA_exportOutputHandler($this->encode($data))) { return false; } } - if ($record_cnt) { - if (!PMA_exportOutputHandler(']' . $crlf)) { - return false; - } + if (!PMA_exportOutputHandler($crlf . ']' . $crlf . $footer . $crlf)) { + return false; } $GLOBALS['dbi']->freeResult($result); diff --git a/test/classes/plugin/export/ExportJsonTest.php b/test/classes/plugin/export/ExportJsonTest.php index eccb4786c0..3bd3d9c7f1 100644 --- a/test/classes/plugin/export/ExportJsonTest.php +++ b/test/classes/plugin/export/ExportJsonTest.php @@ -139,10 +139,10 @@ class ExportJsonTest extends PMATestCase $GLOBALS['crlf'] = "\n"; $this->expectOutputString( - '/**' . "\n" - . ' Export to JSON plugin for PHPMyAdmin' . "\n" - . ' @version ' . PMA_VERSION . "\n" - . ' */' . "\n" . "\n" + "[\n" + . '{"type":"header","version":"' . PMA_VERSION + . '","comment":"Export to JSON plugin for PHPMyAdmin"},' + . "\n" ); $this->assertTrue( @@ -157,6 +157,10 @@ class ExportJsonTest extends PMATestCase */ public function testExportFooter() { + $this->expectOutputString( + ']' + ); + $this->assertTrue( $this->object->exportFooter() ); @@ -172,7 +176,7 @@ class ExportJsonTest extends PMATestCase $GLOBALS['crlf'] = "\n"; $this->expectOutputString( - "// Database 'testDB'\n" + '{"type":"database","name":"testDB"},' . "\n" ); $this->assertTrue( @@ -243,8 +247,12 @@ class ExportJsonTest extends PMATestCase $GLOBALS['dbi'] = $dbi; $this->expectOutputString( - "\n// db.tbl\n\n" . - "[{\"f1\":\"foo\"}, {\"f1\":\"bar\"}]\n" + '{"type":"table","name":"tbl","database":"db","data":' + . "\n[\n" + . '{"f1":"foo"},' + . "\n" + . '{"f1":"bar"}' + . "\n]\n}\n" ); $this->assertTrue(