diff --git a/lib/Db/Column.php b/lib/Db/Column.php index 3710956208..56373c98ad 100644 --- a/lib/Db/Column.php +++ b/lib/Db/Column.php @@ -158,7 +158,7 @@ class Column extends EntitySuper implements JsonSerializable { protected ?string $lastEditByDisplayName = null; protected ?ViewColumnInformation $viewColumnInformation = null; - protected const VIRTUAL_PROPERTIES = ['createdByDisplayName', 'lastEditByDisplayName', 'viewColumnInformation']; + protected const VIRTUAL_PROPERTIES = ['createdByDisplayName', 'lastEditByDisplayName', 'viewColumnInformation', 'columnName']; public function __construct() { $this->addType('id', 'integer'); diff --git a/lib/Db/Row2.php b/lib/Db/Row2.php index dc24f7b335..d223b1dc74 100644 --- a/lib/Db/Row2.php +++ b/lib/Db/Row2.php @@ -23,6 +23,7 @@ class Row2 implements JsonSerializable { private ?string $lastEditBy = null; private ?string $lastEditAt = null; private ?array $data = []; + private array $cellMetadata = []; private array $changedColumnIds = []; // collect column ids that have changed after $loaded = true private bool $loaded = false; // set to true if model is loaded, after that changed column ids will be collected @@ -133,10 +134,29 @@ public function filterDataByColumns(array $columns): array { return $this->data; } + /** + * add response-only metadata for a specific column + */ + public function addCellMeta(int $columnId, array $meta): void { + if (!isset($this->cellMetadata[$columnId])) { + $this->cellMetadata[$columnId] = []; + } + $this->cellMetadata[$columnId] = array_merge($this->cellMetadata[$columnId], $meta); + } + /** * @psalm-return TablesRow */ public function jsonSerialize(): array { + $data = []; + foreach ($this->data as $cell) { + $colId = $cell['columnId']; + $merged = $cell; + if (isset($this->cellMetadata[$colId])) { + $merged = array_merge($merged, $this->cellMetadata[$colId]); + } + $data[] = $merged; + } return [ 'id' => $this->id, 'tableId' => $this->tableId, @@ -144,7 +164,7 @@ public function jsonSerialize(): array { 'createdAt' => $this->createdAt, 'lastEditBy' => $this->lastEditBy, 'lastEditAt' => $this->lastEditAt, - 'data' => $this->data, + 'data' => $data, ]; } @@ -191,4 +211,15 @@ public function markAsLoaded(): void { $this->loaded = true; } + /** + * attach columnName as metadata for each cell + */ + public function addColumnNames(array $fullRowData): void { + foreach ($fullRowData as $meta) { + if (isset($meta['columnId']) && array_key_exists('columnName', $meta)) { + $this->addCellMeta((int)$meta['columnId'], ['columnName' => $meta['columnName']]); + } + } + } + } diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 9111ba55f5..891d7b5e16 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -80,7 +80,7 @@ * createdAt: string, * lastEditBy: string, * lastEditAt: string, - * data: ?array{columnId: int, value: mixed}, + * data: ?array{columnId: int, columnName: string, value: mixed}, * } * * @psalm-type TablesShare = array{ diff --git a/lib/Service/RowService.php b/lib/Service/RowService.php index 34b7b401b3..972aeb6dc4 100644 --- a/lib/Service/RowService.php +++ b/lib/Service/RowService.php @@ -208,6 +208,28 @@ public function create(?int $tableId, ?int $viewId, RowDataInput|array $data): R throw new InternalError('Cannot create row without table or view in context'); } + $fullRowData = []; + $columnNames = []; + + foreach ($columns as $column) { + $columnNames[$column->getId()] = $column->getTitle(); + } + + $rows = $data instanceof RowDataInput ? iterator_to_array($data) : $data; + + foreach ($rows as $row) { + $colId = (int)$row['columnId']; + if (!isset($columnNames[$colId])) { + continue; + } + + $fullRowData[] = [ + 'columnId' => $colId, + 'value' => $row['value'], + 'columnName' => $columnNames[$colId], + ]; + } + $tableId = $tableId ?? $view->getTableId(); $data = $data instanceof RowDataInput ? $data : RowDataInput::fromArray($data); @@ -220,6 +242,8 @@ public function create(?int $tableId, ?int $viewId, RowDataInput|array $data): R $row2->setData($data); try { $insertedRow = $this->row2Mapper->insert($row2); + // attach columnName to returned row for the response only + $insertedRow->addColumnNames($fullRowData); $this->eventDispatcher->dispatchTyped(new RowAddedEvent($insertedRow)); $this->activityManager->triggerEvent( @@ -334,7 +358,7 @@ private function cleanupAndValidateData(RowDataInput $data, array $columns, ?int if (!$column && $viewId) { throw new InternalError('Column with id ' . $entry['columnId'] . ' is not part of view with id ' . $viewId); } elseif (!$column && $tableId) { - throw new InternalError('Column with id ' . $entry['columnId'] . ' is not part of table with id ' . $tableId); + throw new BadRequestError('Column with id ' . $entry['columnId'] . ' is not part of table with id ' . $tableId); } if (!$column) { diff --git a/phpunit.xml b/phpunit.xml index b2139fbe07..8acc16f660 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -3,7 +3,7 @@ - SPDX-FileCopyrightText: 2021 Florian Steffens - SPDX-License-Identifier: AGPL-3.0-or-later --> - + ./tests/Unit diff --git a/tests/unit/Db/Row2Test.php b/tests/unit/Db/Row2Test.php new file mode 100644 index 0000000000..c4782c79fa --- /dev/null +++ b/tests/unit/Db/Row2Test.php @@ -0,0 +1,33 @@ +setTableId(1); + + $row->setData([ + ['columnId' => 57, 'value' => 'foo'], + ['columnId' => 58, 'value' => 'bar'], + ]); + + $json = $row->jsonSerialize(); + $this->assertArrayNotHasKey('columnName', $json['data'][0]); + + $row->addCellMeta(57, ['columnName' => 'Title 57']); + $resp = $row->jsonSerialize(); + $this->assertArrayHasKey('columnName', $resp['data'][0]); + $this->assertSame('Title 57', $resp['data'][0]['columnName']); + } +}