From cbd35c9fa8ff00e2913aaf33b4e461ddc58b744b Mon Sep 17 00:00:00 2001 From: William Desportes Date: Fri, 23 Apr 2021 17:48:14 +0200 Subject: [PATCH] Ref #16847 - Fix JSON export code duplicated and not following rules in raw query export mode Signed-off-by: William Desportes --- .../classes/Plugins/Export/ExportJson.php | 95 +++++++++--------- .../classes/Plugins/Export/ExportJsonTest.php | 99 +++++++++++++++++++ 2 files changed, 145 insertions(+), 49 deletions(-) diff --git a/libraries/classes/Plugins/Export/ExportJson.php b/libraries/classes/Plugins/Export/ExportJson.php index f76aa78014..efd56c15bc 100644 --- a/libraries/classes/Plugins/Export/ExportJson.php +++ b/libraries/classes/Plugins/Export/ExportJson.php @@ -231,6 +231,42 @@ class ExportJson extends ExportPlugin 'data' => '@@DATA@@', ] ); + + return $this->doExportForQuery( + $dbi, + $sql_query, + $buffer, + $crlf, + $aliases, + $db, + $table + ); + } + + /** + * Export to JSON + * + * @return bool False on export fail and true on export end success + * + * @phpstan-param array{ + * string: array{ + * 'tables': array{ + * string: array{ + * 'columns': array{string: string} + * } + * } + * } + * }|array|null $aliases + */ + protected function doExportForQuery( + DatabaseInterface $dbi, + string $sql_query, + string $buffer, + string $crlf, + ?array $aliases, + ?string $db, + ?string $table + ): bool { [$header, $footer] = explode('"@@DATA@@"', $buffer); if (! $this->export->outputHandler($header . $crlf . '[' . $crlf)) { @@ -248,7 +284,9 @@ class ExportJson extends ExportPlugin $columns = []; for ($i = 0; $i < $columns_cnt; $i++) { $col_as = $dbi->fieldName($result, $i); - if (! empty($aliases[$db]['tables'][$table]['columns'][$col_as])) { + if ($db !== null && $table !== null && $aliases !== null + && ! empty($aliases[$db]['tables'][$table]['columns'][$col_as]) + ) { $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as]; } $columns[$i] = stripslashes($col_as); @@ -323,56 +361,15 @@ class ExportJson extends ExportPlugin 'data' => '@@DATA@@', ] ); - [$header, $footer] = explode('"@@DATA@@"', $buffer); - if (! $this->export->outputHandler($header . $crlf . '[' . $crlf)) { - return false; - } - - $result = $dbi->query( + return $this->doExportForQuery( + $dbi, $sql_query, - DatabaseInterface::CONNECT_USER, - DatabaseInterface::QUERY_UNBUFFERED + $buffer, + $crlf, + null, + null, + null ); - $columns_cnt = $dbi->numFields($result); - - $columns = []; - for ($i = 0; $i < $columns_cnt; $i++) { - $col_as = $dbi->fieldName($result, $i); - $columns[$i] = stripslashes($col_as); - } - - $record_cnt = 0; - while ($record = $dbi->fetchRow($result)) { - $record_cnt++; - - if ($record_cnt > 1) { - if (! $this->export->outputHandler(',' . $crlf)) { - return false; - } - } - - $data = []; - - for ($i = 0; $i < $columns_cnt; $i++) { - $data[$columns[$i]] = $record[$i]; - } - - $encodedData = $this->encode($data); - if (! $encodedData) { - return false; - } - if (! $this->export->outputHandler($encodedData)) { - return false; - } - } - - if (! $this->export->outputHandler($crlf . ']' . $crlf . $footer . $crlf)) { - return false; - } - - $dbi->freeResult($result); - - return true; } } diff --git a/test/classes/Plugins/Export/ExportJsonTest.php b/test/classes/Plugins/Export/ExportJsonTest.php index 2d6edd1199..4c12f7451b 100644 --- a/test/classes/Plugins/Export/ExportJsonTest.php +++ b/test/classes/Plugins/Export/ExportJsonTest.php @@ -340,4 +340,103 @@ class ExportJsonTest extends AbstractTestCase $this->object->exportData('db', 'tbl', "\n", 'example.com', 'SELECT') ); } + + public function testExportRawComplexData(): void + { + $dbi = $this->getMockBuilder(DatabaseInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $flags = []; + $normalString = new stdClass(); + $normalString->blob = false; + $normalString->numeric = false; + $normalString->type = 'string'; + $normalString->name = 'f1'; + $normalString->charsetnr = 33; + $normalString->length = 20; + $flags[] = $normalString; + $binaryField = new stdClass(); + $binaryField->blob = false; + $binaryField->numeric = false; + $binaryField->type = 'string'; + $binaryField->name = 'f1'; + $binaryField->charsetnr = 63; + $binaryField->length = 20; + $flags[] = $binaryField; + $textField = new stdClass(); + $textField->blob = false; + $textField->numeric = false; + $textField->type = 'blob'; + $textField->name = 'f1'; + $textField->charsetnr = 23; + $textField->length = 20; + $flags[] = $textField; + $blobField = new stdClass(); + $blobField->blob = false; + $blobField->numeric = false; + $blobField->type = 'blob'; + $blobField->name = 'f1'; + $blobField->charsetnr = 63; + $blobField->length = 20; + $flags[] = $blobField; + + $dbi->expects($this->once()) + ->method('getFieldsMeta') + ->with(null) + ->will($this->returnValue($flags)); + + $dbi->expects($this->once()) + ->method('numFields') + ->with(null) + ->will($this->returnValue(4)); + + $dbi->expects($this->exactly(4)) + ->method('fieldName') + ->withConsecutive( + [null, 0], + [null, 1], + [null, 2], + [null, 3] + ) + ->willReturnOnConsecutiveCalls( + 'f1', + 'f2', + 'f3', + 'f4' + ); + + $dbi->expects($this->exactly(4)) + ->method('fetchRow') + ->withConsecutive( + [null], + [null], + [null], + [null] + ) + ->willReturnOnConsecutiveCalls( + // normalString binaryField textField blobField + ['"\'">