From b728ba22b37a4f8361206267bb7472f768d29d83 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Fri, 27 Dec 2024 16:41:03 +0100 Subject: [PATCH 01/24] Add maxDepth and storing subobjects --- lib/Db/Schema.php | 3 ++ lib/Migration/Version1Date20241227153853.php | 55 +++++++++++++++++++ lib/Service/ObjectService.php | 57 ++++++++++++++------ 3 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 lib/Migration/Version1Date20241227153853.php diff --git a/lib/Db/Schema.php b/lib/Db/Schema.php index db08bf7745..407d918e67 100644 --- a/lib/Db/Schema.php +++ b/lib/Db/Schema.php @@ -23,6 +23,7 @@ class Schema extends Entity implements JsonSerializable protected bool $hardValidation = false; protected ?DateTime $updated = null; protected ?DateTime $created = null; + protected int $maxDepth = 0; public function __construct() { $this->addType(fieldName: 'uuid', type: 'string'); @@ -37,6 +38,7 @@ public function __construct() { $this->addType(fieldName: 'hardValidation', type: Types::BOOLEAN); $this->addType(fieldName: 'updated', type: 'datetime'); $this->addType(fieldName: 'created', type: 'datetime'); + $this->addType(fieldName: 'maxDepth', type: Types::INTEGER); } public function getJsonFields(): array @@ -107,6 +109,7 @@ public function jsonSerialize(): array 'hardValidation' => $this->hardValidation, 'updated' => isset($this->updated) ? $this->updated->format('c') : null, 'created' => isset($this->created) ? $this->created->format('c') : null, + 'maxDepth' => $this->maxDepth, ]; $jsonFields = $this->getJsonFields(); diff --git a/lib/Migration/Version1Date20241227153853.php b/lib/Migration/Version1Date20241227153853.php new file mode 100644 index 0000000000..2125bf8bc0 --- /dev/null +++ b/lib/Migration/Version1Date20241227153853.php @@ -0,0 +1,55 @@ +getTable('openregister_schemas'); + if ($table->hasColumn('max_depth') === false) { + $table->addColumn(name: 'max_depth', typeName: Types::INTEGER, options: ['notnull' => true])->setDefault(default: 0); + } + } + + /** + * @param IOutput $output + * @param Closure(): ISchemaWrapper $schemaClosure + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + } +} diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index ce40d370d5..9d67581437 100755 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -425,6 +425,8 @@ public function findAllPaginated(array $requestParams): array unset($filters['_extend'], $filters['_limit'], $filters['_offset'], $filters['_order'], $filters['_page'], $filters['_search']); unset($filters['extend'], $filters['limit'], $filters['offset'], $filters['order'], $filters['page']); +// var_dump($filters); + $objects = $this->findAll(limit: $limit, offset: $offset, filters: $filters, sort: $order, search: $search, extend: $extend); $total = $this->count($filters); $pages = $limit !== null ? ceil($total/$limit) : 1; @@ -499,8 +501,14 @@ public function getObjects( * @throws ValidationException If the object fails validation. * @throws Exception|GuzzleException If an error occurs during object saving or file handling. */ - public function saveObject(int $register, int $schema, array $object): ObjectEntity + public function saveObject(int $register, int $schema, array $object, ?int $depth = null): ObjectEntity { + + if ($depth === null) { + //@TODO fetch depth from schema + $depth = 3; + } + // Convert register and schema to their respective objects if they are strings // @todo ??? if (is_string($register)) { $register = $this->registerMapper->find($register); @@ -552,7 +560,7 @@ public function saveObject(int $register, int $schema, array $object): ObjectEnt // Handle object properties that are either nested objects or files if ($schemaObject->getProperties() !== null && is_array($schemaObject->getProperties()) === true) { - $objectEntity = $this->handleObjectRelations($objectEntity, $object, $schemaObject->getProperties(), $register, $schema); + $objectEntity = $this->handleObjectRelations($objectEntity, $object, $schemaObject->getProperties(), $register, $schema, depth: $depth); } $objectEntity->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openregister.Objects.show', ['id' => $objectEntity->getUuid()]))); @@ -645,8 +653,9 @@ private function addObject( ObjectEntity $objectEntity, int $register, int $schema, - ?int $index = null - ): string + ?int $index = null, + int $depth = 0, + ): string|array { $subSchema = $schema; if (is_int($property['$ref']) === true) { @@ -661,7 +670,8 @@ private function addObject( $nestedObject = $this->saveObject( register: $register, schema: $subSchema, - object: $item + object: $item, + depth: $depth-1 ); if ($index === null) { @@ -675,6 +685,9 @@ private function addObject( $objectEntity->setRelations($relations); } + if ($depth !== 0) { + return $nestedObject->jsonSerialize(); + } return $nestedObject->getUuid(); } @@ -698,7 +711,8 @@ private function handleObjectProperty( array $item, ObjectEntity $objectEntity, int $register, - int $schema + int $schema, + int $depth = 0 ): string { return $this->addObject( @@ -707,7 +721,8 @@ private function handleObjectProperty( item: $item, objectEntity: $objectEntity, register: $register, - schema: $schema + schema: $schema, + depth: $depth ); } @@ -733,7 +748,8 @@ private function handleArrayProperty( array $items, ObjectEntity $objectEntity, int $register, - int $schema + int $schema, + int $depth = 0 ): array { if (isset($property['items']) === false) { @@ -749,7 +765,8 @@ private function handleArrayProperty( objectEntity: $objectEntity, register: $register, schema: $schema, - index: $index + index: $index, + depth: $depth ); } return $items; @@ -781,7 +798,8 @@ private function handleArrayProperty( objectEntity: $objectEntity, register: $register, schema: $schema, - index: $index + index: $index, + depth: $depth ); } @@ -812,7 +830,8 @@ private function handleOneOfProperty( ObjectEntity $objectEntity, int $register, int $schema, - ?int $index = null + ?int $index = null, + int $depth = 0 ): string|array { if (array_is_list($property) === false) { @@ -864,7 +883,8 @@ private function handleOneOfProperty( objectEntity: $objectEntity, register: $register, schema: $schema, - index: $index + index: $index, + depth: $depth ); } @@ -890,7 +910,8 @@ private function handleProperty( int $register, int $schema, array $object, - ObjectEntity $objectEntity + ObjectEntity $objectEntity, + int $depth = 0 ): array { switch($property['type']) { @@ -902,6 +923,7 @@ private function handleProperty( objectEntity: $objectEntity, register: $register, schema: $schema, + depth: $depth ); break; case 'array': @@ -912,6 +934,7 @@ private function handleProperty( objectEntity: $objectEntity, register: $register, schema: $schema, + depth: $depth ); break; case 'oneOf': @@ -921,7 +944,9 @@ private function handleProperty( item: $object[$propertyName], objectEntity: $objectEntity, register: $register, - schema: $schema); + schema: $schema, + depth: $depth + ); break; case 'file': $object[$propertyName] = $this->handleFileProperty( @@ -958,7 +983,8 @@ private function handleObjectRelations( array $object, array $properties, int $register, - int $schema + int $schema, + int $depth = 0 ): ObjectEntity { // @todo: Multidimensional support should be added @@ -975,6 +1001,7 @@ private function handleObjectRelations( schema: $schema, object: $object, objectEntity: $objectEntity, + depth: $depth ); } From e332d44904a3ef759f6e2bf78ab0458bfae81fe5 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Fri, 27 Dec 2024 16:41:10 +0100 Subject: [PATCH 02/24] Filter on subobjects --- lib/Service/MySQLJsonService.php | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/Service/MySQLJsonService.php b/lib/Service/MySQLJsonService.php index 43074209b6..dccbdd3a9b 100644 --- a/lib/Service/MySQLJsonService.php +++ b/lib/Service/MySQLJsonService.php @@ -23,13 +23,13 @@ class MySQLJsonService implements IDatabaseJsonService public function orderJson(IQueryBuilder $builder, array $order = []): IQueryBuilder { // Loop through each ordering field and direction - foreach ($order as $item=>$direction) { + foreach ($order as $item => $direction) { // Create parameters for the JSON path and sort direction $builder->createNamedParameter(value: "$.$item", placeHolder: ":path$item"); $builder->createNamedParameter(value: $direction, placeHolder: ":direction$item"); // Add ORDER BY clause using JSON_UNQUOTE and JSON_EXTRACT - $builder->orderBy($builder->createFunction("json_unquote(json_extract(object, :path$item))"),$direction); + $builder->orderBy($builder->createFunction("json_unquote(json_extract(object, :path$item))"), $direction); } return $builder; @@ -67,7 +67,7 @@ public function searchJson(IQueryBuilder $builder, ?string $search = null): IQue */ private function jsonFilterArray(IQueryBuilder $builder, string $filter, array $values): IQueryBuilder { - foreach ($values as $key=>$value) { + foreach ($values as $key => $value) { switch ($key) { case 'after': // Add >= filter for dates after specified value @@ -105,11 +105,10 @@ private function jsonFilterArray(IQueryBuilder $builder, string $filter, array $ * @param IQueryBuilder $builder The query builder instance * @return string The resulting OR conditions as a string */ - private function getMultipleContains (array $values, string $filter, IQueryBuilder $builder): string + private function getMultipleContains(array $values, string $filter, IQueryBuilder $builder): string { $orString = ''; - foreach ($values as $key=>$value) - { + foreach ($values as $key => $value) { // Create parameter for each value $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR, placeHolder: ":value$filter$key"); // Add OR condition checking if value exists in JSON array @@ -119,6 +118,21 @@ private function getMultipleContains (array $values, string $filter, IQueryBuild return $orString; } + private function parseFilter(string $filter): string + { + $explodedFilter = explode( + separator: '_', + string: $filter + ); + + $explodedFilter = array_map(function($field) {return "\"$field\"";}, $explodedFilter); + + return implode( + separator: '**.', + array: $explodedFilter + ); + } + /** * Add JSON filtering to a query * @@ -137,8 +151,10 @@ public function filterJson(IQueryBuilder $builder, array $filters): IQueryBuilde unset($filters['register'], $filters['schema'], $filters['updated'], $filters['created'], $filters['_queries']); foreach ($filters as $filter=>$value) { + $parsedFilter = $this->parseFilter($filter); + // Create parameter for JSON path - $builder->createNamedParameter(value: "$.$filter", placeHolder: ":path$filter"); + $builder->createNamedParameter(value: "$.$parsedFilter", placeHolder: ":path$filter"); if (is_array($value) === true && array_is_list($value) === false) { // Handle complex filters (after/before) @@ -155,7 +171,7 @@ public function filterJson(IQueryBuilder $builder, array $filters): IQueryBuilde // Handle simple equality filter $builder->createNamedParameter(value: $value, placeHolder: ":value$filter"); $builder - ->andWhere("json_extract(object, :path$filter) = :value$filter OR json_contains(object, json_quote(:value$filter), :path$filter)"); + ->andWhere("json_extract(object, :path$filter) = :value$filter OR json_contains(json_extract(object, :path$filter), json_quote(:value$filter))"); } return $builder; From 118fb062bb253ecea0bdef4be4aaff3295134516 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Mon, 30 Dec 2024 13:32:36 +0100 Subject: [PATCH 03/24] Fix migration --- lib/Migration/Version1Date20241227153853.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Migration/Version1Date20241227153853.php b/lib/Migration/Version1Date20241227153853.php index 2125bf8bc0..2b5e51b7b0 100644 --- a/lib/Migration/Version1Date20241227153853.php +++ b/lib/Migration/Version1Date20241227153853.php @@ -43,6 +43,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt if ($table->hasColumn('max_depth') === false) { $table->addColumn(name: 'max_depth', typeName: Types::INTEGER, options: ['notnull' => true])->setDefault(default: 0); } + + return $schema; } /** From 3d30dbe683b865f570a4950b440679b7fb8db5f7 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Tue, 31 Dec 2024 14:21:15 +0100 Subject: [PATCH 04/24] Add filteroptions --- appinfo/info.xml | 2 +- lib/Service/MySQLJsonService.php | 20 ++++++++++++++++++++ lib/Service/ObjectService.php | 2 -- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index b11b60225c..c97a9bc2e0 100755 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -22,7 +22,7 @@ Create a [bug report](https://github.com/OpenRegister/.github/issues/new/choose) Create a [feature request](https://github.com/OpenRegister/.github/issues/new/choose) ]]> - 0.1.22 + 0.1.23 agpl Conduction OpenRegister diff --git a/lib/Service/MySQLJsonService.php b/lib/Service/MySQLJsonService.php index dccbdd3a9b..03d693c173 100644 --- a/lib/Service/MySQLJsonService.php +++ b/lib/Service/MySQLJsonService.php @@ -70,17 +70,37 @@ private function jsonFilterArray(IQueryBuilder $builder, string $filter, array $ foreach ($values as $key => $value) { switch ($key) { case 'after': + case 'gte': + case '>=': // Add >= filter for dates after specified value $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR, placeHolder: ":value{$filter}after"); $builder ->andWhere("json_unquote(json_extract(object, :path$filter)) >= (:value{$filter}after)"); break; case 'before': + case 'lte': + case '<=': // Add <= filter for dates before specified value $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR, placeHolder: ":value{$filter}before"); $builder ->andWhere("json_unquote(json_extract(object, :path$filter)) <= (:value{$filter}before)"); break; + case 'strictly_after': + case 'gt': + case '>': + // Add >= filter for dates after specified value + $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR, placeHolder: ":value{$filter}after"); + $builder + ->andWhere("json_unquote(json_extract(object, :path$filter)) > (:value{$filter}after)"); + break; + case 'strictly_before': + case 'lt': + case '<': + // Add <= filter for dates before specified value + $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR, placeHolder: ":value{$filter}before"); + $builder + ->andWhere("json_unquote(json_extract(object, :path$filter)) < (:value{$filter}before)"); + break; default: // Add IN clause for array of values $builder->createNamedParameter(value: $value, type: IQueryBuilder::PARAM_STR_ARRAY, placeHolder: ":value{$filter}"); diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index 9d67581437..628a0d7b80 100755 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -425,8 +425,6 @@ public function findAllPaginated(array $requestParams): array unset($filters['_extend'], $filters['_limit'], $filters['_offset'], $filters['_order'], $filters['_page'], $filters['_search']); unset($filters['extend'], $filters['limit'], $filters['offset'], $filters['order'], $filters['page']); -// var_dump($filters); - $objects = $this->findAll(limit: $limit, offset: $offset, filters: $filters, sort: $order, search: $search, extend: $extend); $total = $this->count($filters); $pages = $limit !== null ? ceil($total/$limit) : 1; From 5623672186917175b6b9adcb19328665f5bd1975 Mon Sep 17 00:00:00 2001 From: Thijn Date: Fri, 3 Jan 2025 17:22:46 +0100 Subject: [PATCH 05/24] only show schemas of a specific register --- src/modals/object/EditObject.vue | 54 +++++++++++++++++++------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/modals/object/EditObject.vue b/src/modals/object/EditObject.vue index 094d3725db..16506dff22 100644 --- a/src/modals/object/EditObject.vue +++ b/src/modals/object/EditObject.vue @@ -133,7 +133,13 @@ export default { object: '', }, schemasLoading: false, - schemas: {}, + schemasData: [], + schemas: { + multiple: false, + closeOnSelect: true, + options: [], + value: null, + }, registersLoading: false, registers: {}, success: null, @@ -143,13 +149,31 @@ export default { closeModalTimeout: null, } }, + watch: { + 'registers.value': { + handler(newVal) { + if (newVal) { + if (!newVal.id) return + + const currentRegister = registerStore.registerList.find((register) => register.id === newVal.id) + const filteredSchemas = this.schemasData.filter((schema) => currentRegister.schemas.includes(schema.id)) + + this.schemas.options = filteredSchemas.map((schema) => ({ + id: schema.id, + label: schema.title, + })) + } + }, + deep: true, + }, + }, mounted() { this.initializeObjectItem() }, updated() { if (navigationStore.modal === 'editObject' && !this.hasUpdated) { this.initializeObjectItem() - this.initializeSchemas() + this.fetchSchemas() this.initializeRegisters() this.hasUpdated = true } @@ -165,30 +189,18 @@ export default { } } }, - initializeSchemas() { + fetchSchemas() { this.schemasLoading = true schemaStore.refreshSchemaList() .then(() => { - const activeSchemas = objectStore.objectItem?.id - ? schemaStore.schemaList.find((schema) => schema.id.toString() === objectStore.objectItem.schema) - : null - - this.schemas = { - multiple: false, - closeOnSelect: true, - options: schemaStore.schemaList.map((schema) => ({ - id: schema.id, - label: schema.title, - })), - value: activeSchemas - ? { - id: activeSchemas.id, - label: activeSchemas.title, - } - : null, - } + this.schemasData = schemaStore.schemaList + this.schemas.value = objectStore.objectItem?.id + ? this.schemasData.find((schema) => schema.id.toString() === objectStore.objectItem.schema.toString()) + : null + }) + .finally(() => { this.schemasLoading = false }) }, From e8dcbbf0d03e15a7cb5df68f26ead1c8f54caf77 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Mon, 6 Jan 2025 13:00:46 +0100 Subject: [PATCH 06/24] get maxdepth from schema --- lib/Service/ObjectService.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index ad9ddb6450..4277c6203f 100755 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -208,12 +208,12 @@ public function createFromArray(array $object, ?array $extend = []): array register: $this->getRegister(), schema: $this->getSchema(), object: $object - ); + ); // Lets turn the whole thing into an array $objectEntity = $objectEntity->jsonSerialize(); - // Extend object with properties if requested + // Extend object with properties if requested if (empty($extend) === false) { $objectEntity = $this->extendEntity(entity: $objectEntity, extend: $extend); } @@ -257,7 +257,7 @@ public function updateFromArray(string $id, array $object, bool $updateVersion, // Lets turn the whole thing into an array $objectEntity = $objectEntity->jsonSerialize(); - // Extend object with properties if requested + // Extend object with properties if requested if (empty($extend) === false) { $objectEntity = $this->extendEntity(entity: $objectEntity, extend: $extend); } @@ -546,24 +546,26 @@ public function getObjects( public function saveObject(int $register, int $schema, array $object, ?int $depth = null): ObjectEntity { - if ($depth === null) { - //@TODO fetch depth from schema - $depth = 3; - } - // Remove system properties (starting with _) $object = array_filter($object, function($key) { return !str_starts_with($key, '_'); }, ARRAY_FILTER_USE_KEY); // Convert register and schema to their respective objects if they are strings // @todo ??? - if (is_string($register)) { + if (is_string($register) === true) { $register = $this->registerMapper->find($register); } - if (is_string($schema)) { + if (is_string($schema) === true) { $schema = $this->schemaMapper->find($schema); } + + if ($depth === null && $schema instanceof Schema) { + $depth = $schema->getMaxDepth();; + } else if ($depth === null) { + $schemaObject = $this->schemaMapper->find($schema); + $depth = $schemaObject->getMaxDepth(); + } // Check if object already exists if (isset($object['id']) === true) { @@ -1628,7 +1630,7 @@ public function getRegisters(): array // Convert to arrays and extend schemas $registers = array_map(function($register) { $registerArray = is_array($register) ? $register : $register->jsonSerialize(); - + // Replace schema IDs with actual schema objects if schemas property exists if (isset($registerArray['schemas']) && is_array($registerArray['schemas'])) { $registerArray['schemas'] = array_map( From 22da30f6fd608c6842882ec4ea141b5c16123de0 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Mon, 6 Jan 2025 15:18:35 +0100 Subject: [PATCH 07/24] Also allow array subobjects --- lib/Service/ObjectService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index 19c345d3a2..1d10d9925d 100755 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -557,7 +557,7 @@ public function saveObject(int $register, int $schema, array $object, ?int $dept if (is_string($schema) === true) { $schema = $this->schemaMapper->find($schema); } - + if ($depth === null && $schema instanceof Schema) { $depth = $schema->getMaxDepth();; } else if ($depth === null) { @@ -760,7 +760,7 @@ private function handleObjectProperty( int $register, int $schema, int $depth = 0 - ): string + ): string|array { return $this->addObject( property: $property, From 8288a130d4fd735d631a09b7da1b4e64f1e4f907 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Mon, 6 Jan 2025 15:30:31 +0100 Subject: [PATCH 08/24] Add missing docblock --- lib/Service/MySQLJsonService.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/Service/MySQLJsonService.php b/lib/Service/MySQLJsonService.php index 03d693c173..669a939fb2 100644 --- a/lib/Service/MySQLJsonService.php +++ b/lib/Service/MySQLJsonService.php @@ -138,6 +138,12 @@ private function getMultipleContains(array $values, string $filter, IQueryBuilde return $orString; } + /** + * Parse filter in PHP style to MySQL style filter + * + * @param string $filter The original filter + * @return string The parsed filter for MySQL + */ private function parseFilter(string $filter): string { $explodedFilter = explode( From d9d8732f786d13f5d2fed98ae7691c79a1b30efc Mon Sep 17 00:00:00 2001 From: Thijn Date: Mon, 6 Jan 2025 17:00:02 +0100 Subject: [PATCH 09/24] fixed schemas not loading on register edit --- src/modals/register/EditRegister.vue | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/modals/register/EditRegister.vue b/src/modals/register/EditRegister.vue index 7d9c3edf79..df5840e9cf 100644 --- a/src/modals/register/EditRegister.vue +++ b/src/modals/register/EditRegister.vue @@ -100,7 +100,12 @@ export default { updated: '', }, schemasLoading: false, - schemas: {}, + schemas: { + options: [], + value: [], + multiple: true, + closeOnSelect: false, + }, sourcesLoading: false, sources: {}, success: false, @@ -147,20 +152,15 @@ export default { }) : null - this.schemas = { - multiple: true, - closeOnSelect: false, - options: schemaStore.schemaList.map((schema) => ({ - id: schema.id, - label: schema.title, - })), - value: activeSchemas - ? activeSchemas.map((schema) => ({ - id: schema.id, - label: schema.title, - })) - : null, - } + this.schemas.options = schemaStore.schemaList.map((schema) => ({ + id: schema.id, + label: schema.title, + })) + + this.schemas.value = activeSchemas.map((schema) => ({ + id: schema.id, + label: schema.title, + })) this.schemasLoading = false }) From c40859e3cc929c8b90dcbca96eaed55c3ef1d3a5 Mon Sep 17 00:00:00 2001 From: Remko Date: Tue, 7 Jan 2025 13:20:44 +0100 Subject: [PATCH 10/24] small fix --- src/modals/object/EditObject.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modals/object/EditObject.vue b/src/modals/object/EditObject.vue index 16506dff22..4c165a4b81 100644 --- a/src/modals/object/EditObject.vue +++ b/src/modals/object/EditObject.vue @@ -54,7 +54,7 @@ import { objectStore, schemaStore, registerStore, navigationStore } from '../../
Register: {{ registers.value.label }} - + Edit Register
From 1438f83d9770a7ce5f6505177b043463265d55a8 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 7 Jan 2025 20:44:36 +0100 Subject: [PATCH 11/24] Add events --- docs/events.md | 135 +++++++++++++++++++++++++++++ lib/Db/ObjectEntityMapper.php | 62 +++++++++++-- lib/Db/RegisterMapper.php | 65 +++++++++++--- lib/Db/SchemaMapper.php | 64 +++++++++++--- lib/Event/ObjectCreatedEvent.php | 34 ++++++++ lib/Event/ObjectDeletedEvent.php | 34 ++++++++ lib/Event/ObjectUpdatedEvent.php | 48 ++++++++++ lib/Event/RegisterCreatedEvent.php | 34 ++++++++ lib/Event/RegisterDeletedEvent.php | 34 ++++++++ lib/Event/RegisterUpdatedEvent.php | 48 ++++++++++ lib/Event/SchemaCreatedEvent.php | 34 ++++++++ lib/Event/SchemaDeletedEvent.php | 34 ++++++++ lib/Event/SchemaUpdatedEvent.php | 48 ++++++++++ 13 files changed, 645 insertions(+), 29 deletions(-) create mode 100644 docs/events.md create mode 100644 lib/Event/ObjectCreatedEvent.php create mode 100644 lib/Event/ObjectDeletedEvent.php create mode 100644 lib/Event/ObjectUpdatedEvent.php create mode 100644 lib/Event/RegisterCreatedEvent.php create mode 100644 lib/Event/RegisterDeletedEvent.php create mode 100644 lib/Event/RegisterUpdatedEvent.php create mode 100644 lib/Event/SchemaCreatedEvent.php create mode 100644 lib/Event/SchemaDeletedEvent.php create mode 100644 lib/Event/SchemaUpdatedEvent.php diff --git a/docs/events.md b/docs/events.md new file mode 100644 index 0000000000..abeec18682 --- /dev/null +++ b/docs/events.md @@ -0,0 +1,135 @@ +# Events Documentation + +## Overview + +This document provides a comprehensive overview of all events in the OpenRegister application. Events are a crucial part of OpenRegister's component-based architecture, enabling seamless integration with other Nextcloud applications. In Nextcloud's ecosystem, each application functions as an independent component - events provide a standardized way for these components to communicate and interact with OpenRegister. + +These events can be used to hook into various actions and extend functionality, allowing other applications to respond to changes in OpenRegister's data and workflow without tight coupling. This event-driven approach promotes loose coupling between components while enabling rich integration possibilities. + +## Available Events + +### Schema Events + +#### SchemaCreatedEvent +- **Class**: `OCA\OpenRegister\Event\SchemaCreatedEvent` +- **Triggered**: When a new schema is created in the system +- **Data Provided**: + - `getSchema()`: Returns the Schema object that was created +- **Usage**: Can be used to perform additional setup or trigger notifications when new schemas are created + +#### SchemaUpdatedEvent +- **Class**: `OCA\OpenRegister\Event\SchemaUpdatedEvent` +- **Triggered**: When a schema is updated +- **Data Provided**: + - `getSchema()`: Returns the updated Schema object + - `getOldSchema()`: Returns the Schema object before updates +- **Usage**: Useful for tracking changes to schemas and triggering related actions + +#### SchemaDeletedEvent +- **Class**: `OCA\OpenRegister\Event\SchemaDeletedEvent` +- **Triggered**: When a schema is deleted from the system +- **Data Provided**: + - `getSchema()`: Returns the Schema object that was deleted +- **Usage**: Can be used to perform cleanup or trigger additional actions when schemas are removed + +### Register Events + +#### RegisterCreatedEvent +- **Class**: `OCA\OpenRegister\Event\RegisterCreatedEvent` +- **Triggered**: When a new register is created +- **Data Provided**: + - `getRegister()`: Returns the Register object that was created +- **Usage**: Can be used to perform additional setup or trigger notifications when new registers are created + +#### RegisterUpdatedEvent +- **Class**: `OCA\OpenRegister\Event\RegisterUpdatedEvent` +- **Triggered**: When a register is updated +- **Data Provided**: + - `getRegister()`: Returns the updated Register object + - `getOldRegister()`: Returns the Register object before updates +- **Usage**: Useful for tracking changes to registers and triggering related actions + +#### RegisterDeletedEvent +- **Class**: `OCA\OpenRegister\Event\RegisterDeletedEvent` +- **Triggered**: When a register is deleted +- **Data Provided**: + - `getRegister()`: Returns the Register object that was deleted +- **Usage**: Can be used for cleanup operations or notifications when registers are removed + +### Object Events + +#### ObjectCreatedEvent +- **Class**: `OCA\OpenRegister\Event\ObjectCreatedEvent` +- **Triggered**: When a new object is created in a register +- **Data Provided**: + - `getObject()`: Returns the ObjectEntity that was created +- **Usage**: Useful for tracking new entries, triggering notifications, or performing additional processing on new objects + +#### ObjectUpdatedEvent +- **Class**: `OCA\OpenRegister\Event\ObjectUpdatedEvent` +- **Triggered**: When an existing object is updated in a register +- **Data Provided**: + - `getObject()`: Returns the updated ObjectEntity + - `getOldObject()`: Returns the ObjectEntity before updates +- **Usage**: Useful for tracking changes to objects, auditing modifications, or triggering follow-up actions + +#### ObjectDeletedEvent +- **Class**: `OCA\OpenRegister\Event\ObjectDeletedEvent` +- **Triggered**: When an object is deleted from a register +- **Data Provided**: + - `getObject()`: Returns the ObjectEntity that was deleted +- **Usage**: Can be used for cleanup operations, maintaining related data integrity, or sending notifications about deletions + +## Using Events + +Events are a powerful way to decouple different parts of your application and respond to changes in the system. The OpenRegister app uses Nextcloud's event dispatcher system to broadcast various events that you can listen to. + +### Event Handling Overview + +Events are dispatched at key points in the application lifecycle, such as when objects are created, updated, or deleted. By implementing event listeners, you can: + +- Perform additional actions when changes occur +- Maintain data consistency across different parts of the system +- Send notifications or trigger external integrations +- Add custom business logic without modifying core code +- Create audit trails and logs + +### Implementation Steps + +To start handling events in your application, follow these steps: + +### 1. Create an Event Listener Class + +First, create a class that will handle the event. Your listener class should: + +- Implement the `IEventListener` interface +- Define a `handle()` method that receives the event object +- Be placed in an appropriate namespace in your application + +Here's a basic example structure: + +```php +namespace OCA\MyApp\Listener; + +use OCA\OpenRegister\Event\SchemaCreatedEvent; +use OCA\OpenRegister\Event\SchemaUpdatedEvent; +use OCA\OpenRegister\Event\SchemaDeletedEvent; + +class SchemaListener implements IEventListener { + public function handle(SchemaCreatedEvent $event) { + // Handle the event + } +} +``` + +### 2. Register the Event Listener + +After creating your listener class, you need to register it with Nextcloud's event dispatcher system. This is done in your application's `lib/AppInfo/Application.php` file by registering the listener in the `register()` method: + +```php +$container->query(EventDispatcherInterface::class)->addListener(SchemaCreatedEvent::class, [SchemaListener::class, 'handle']); +``` + +This line registers the `SchemaListener` class to handle `SchemaCreatedEvent` events. You can add similar lines for other events to register your listener for those specific events. + +You can read more about event handling in the [Nextcloud documentation](https://docs.nextcloud.com/server/latest/developer_manual/basics/events.html). diff --git a/lib/Db/ObjectEntityMapper.php b/lib/Db/ObjectEntityMapper.php index 1b3f0a8933..f9bbbd5cef 100644 --- a/lib/Db/ObjectEntityMapper.php +++ b/lib/Db/ObjectEntityMapper.php @@ -13,6 +13,10 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use Symfony\Component\Uid\Uuid; +use OCP\EventDispatcher\IEventDispatcher; +use OCA\OpenRegister\Event\ObjectCreatedEvent; +use OCA\OpenRegister\Event\ObjectUpdatedEvent; +use OCA\OpenRegister\Event\ObjectDeletedEvent; /** * The ObjectEntityMapper class @@ -22,6 +26,7 @@ class ObjectEntityMapper extends QBMapper { private IDatabaseJsonService $databaseJsonService; + private IEventDispatcher $eventDispatcher; public const MAIN_FILTERS = ['register', 'schema', 'uuid', 'created', 'updated']; @@ -30,14 +35,19 @@ class ObjectEntityMapper extends QBMapper * * @param IDBConnection $db The database connection * @param MySQLJsonService $mySQLJsonService The MySQL JSON service + * @param IEventDispatcher $eventDispatcher The event dispatcher */ - public function __construct(IDBConnection $db, MySQLJsonService $mySQLJsonService) - { + public function __construct( + IDBConnection $db, + MySQLJsonService $mySQLJsonService, + IEventDispatcher $eventDispatcher + ) { parent::__construct($db, 'openregister_objects'); if ($db->getDatabasePlatform() instanceof MySQLPlatform === true) { $this->databaseJsonService = $mySQLJsonService; } + $this->eventDispatcher = $eventDispatcher; } /** @@ -251,7 +261,16 @@ public function createFromArray(array $object): ObjectEntity if ($obj->getUuid() === null) { $obj->setUuid(Uuid::v4()); } - return $this->insert($obj); + + $obj = $this->insert($obj); + + // Dispatch creation event + $this->eventDispatcher->dispatch( + ObjectCreatedEvent::class, + new ObjectCreatedEvent($obj) + ); + + return $obj; } /** @@ -263,18 +282,45 @@ public function createFromArray(array $object): ObjectEntity */ public function updateFromArray(int $id, array $object): ObjectEntity { - $obj = $this->find($id); - $obj->hydrate($object); + $oldObject = $this->find($id); + $newObject = clone $oldObject; + $newObject->hydrate($object); // Set or update the version if (isset($object['version']) === false) { - $version = explode('.', $obj->getVersion()); + $version = explode('.', $newObject->getVersion()); $version[2] = (int) $version[2] + 1; - $obj->setVersion(implode('.', $version)); + $newObject->setVersion(implode('.', $version)); } + $newObject = $this->update($newObject); + + // Dispatch update event + $this->eventDispatcher->dispatch( + ObjectUpdatedEvent::class, + new ObjectUpdatedEvent($newObject, $oldObject) + ); + + return $newObject; + } + + /** + * Delete an object + * + * @param ObjectEntity $object The object to delete + * @return ObjectEntity The deleted object + */ + public function delete(Entity $object): ObjectEntity + { + $result = parent::delete($object); + + // Dispatch deletion event + $this->eventDispatcher->dispatch( + ObjectDeletedEvent::class, + new ObjectDeletedEvent($object) + ); - return $this->update($obj); + return $result; } /** diff --git a/lib/Db/RegisterMapper.php b/lib/Db/RegisterMapper.php index 1f573d8fff..44ba9c6862 100644 --- a/lib/Db/RegisterMapper.php +++ b/lib/Db/RegisterMapper.php @@ -10,6 +10,10 @@ use OCA\OpenRegister\Db\Schema; use OCP\IDBConnection; use Symfony\Component\Uid\Uuid; +use OCP\EventDispatcher\IEventDispatcher; +use OCA\OpenRegister\Event\RegisterCreatedEvent; +use OCA\OpenRegister\Event\RegisterUpdatedEvent; +use OCA\OpenRegister\Event\RegisterDeletedEvent; /** * The RegisterMapper class @@ -19,17 +23,23 @@ class RegisterMapper extends QBMapper { private $schemaMapper; + private $eventDispatcher; /** * Constructor for RegisterMapper * * @param IDBConnection $db The database connection * @param SchemaMapper $schemaMapper The schema mapper + * @param IEventDispatcher $eventDispatcher The event dispatcher */ - public function __construct(IDBConnection $db, SchemaMapper $schemaMapper) - { + public function __construct( + IDBConnection $db, + SchemaMapper $schemaMapper, + IEventDispatcher $eventDispatcher + ) { parent::__construct($db, 'openregister_registers'); $this->schemaMapper = $schemaMapper; + $this->eventDispatcher = $eventDispatcher; } /** @@ -107,12 +117,19 @@ public function createFromArray(array $object): Register $register = new Register(); $register->hydrate(object: $object); - // Set uuid if not provided if ($register->getUuid() === null) { $register->setUuid(Uuid::v4()); } - return $this->insert(entity: $register); + $register = $this->insert(entity: $register); + + // Dispatch creation event + $this->eventDispatcher->dispatch( + RegisterCreatedEvent::class, + new RegisterCreatedEvent($register) + ); + + return $register; } /** @@ -124,18 +141,44 @@ public function createFromArray(array $object): Register */ public function updateFromArray(int $id, array $object): Register { - $obj = $this->find($id); - $obj->hydrate($object); + $oldRegister = $this->find($id); + $newRegister = clone $oldRegister; + $newRegister->hydrate($object); - // Update the version if (isset($object['version']) === false) { - $version = explode('.', $obj->getVersion()); + $version = explode('.', $newRegister->getVersion()); $version[2] = (int) $version[2] + 1; - $obj->setVersion(implode('.', $version)); + $newRegister->setVersion(implode('.', $version)); } - // Update the register and return it - return $this->update($obj); + $newRegister = $this->update($newRegister); + + // Dispatch update event + $this->eventDispatcher->dispatch( + RegisterUpdatedEvent::class, + new RegisterUpdatedEvent($newRegister, $oldRegister) + ); + + return $newRegister; + } + + /** + * Delete a register + * + * @param Register $register The register to delete + * @return Register The deleted register + */ + public function delete(Entity $register): Register + { + $result = parent::delete($register); + + // Dispatch deletion event + $this->eventDispatcher->dispatch( + RegisterDeletedEvent::class, + new RegisterDeletedEvent($register) + ); + + return $result; } /** diff --git a/lib/Db/SchemaMapper.php b/lib/Db/SchemaMapper.php index 11f3953405..f62e9ff1d0 100644 --- a/lib/Db/SchemaMapper.php +++ b/lib/Db/SchemaMapper.php @@ -8,6 +8,10 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use Symfony\Component\Uid\Uuid; +use OCP\EventDispatcher\IEventDispatcher; +use OCA\OpenRegister\Event\SchemaCreatedEvent; +use OCA\OpenRegister\Event\SchemaUpdatedEvent; +use OCA\OpenRegister\Event\SchemaDeletedEvent; /** * The SchemaMapper class @@ -16,14 +20,20 @@ */ class SchemaMapper extends QBMapper { + private $eventDispatcher; + /** * Constructor for the SchemaMapper * * @param IDBConnection $db The database connection + * @param IEventDispatcher $eventDispatcher The event dispatcher */ - public function __construct(IDBConnection $db) - { + public function __construct( + IDBConnection $db, + IEventDispatcher $eventDispatcher + ) { parent::__construct($db, 'openregister_schemas'); + $this->eventDispatcher = $eventDispatcher; } /** @@ -112,12 +122,19 @@ public function createFromArray(array $object): Schema $schema = new Schema(); $schema->hydrate(object: $object); - // Set uuid if not provided if ($schema->getUuid() === null) { $schema->setUuid(Uuid::v4()); } - return $this->insert(entity: $schema); + $schema = $this->insert(entity: $schema); + + // Dispatch creation event + $this->eventDispatcher->dispatch( + SchemaCreatedEvent::class, + new SchemaCreatedEvent($schema) + ); + + return $schema; } /** @@ -129,16 +146,43 @@ public function createFromArray(array $object): Schema */ public function updateFromArray(int $id, array $object): Schema { - $obj = $this->find($id); - $obj->hydrate($object); + $oldSchema = $this->find($id); + $newSchema = clone $oldSchema; + $newSchema->hydrate($object); - // Set or update the version if (isset($object['version']) === false) { - $version = explode('.', $obj->getVersion()); + $version = explode('.', $newSchema->getVersion()); $version[2] = (int) $version[2] + 1; - $obj->setVersion(implode('.', $version)); + $newSchema->setVersion(implode('.', $version)); } - return $this->update($obj); + $newSchema = $this->update($newSchema); + + // Dispatch update event + $this->eventDispatcher->dispatch( + SchemaUpdatedEvent::class, + new SchemaUpdatedEvent($newSchema, $oldSchema) + ); + + return $newSchema; + } + + /** + * Delete a schema + * + * @param Schema $schema The schema to delete + * @return Schema The deleted schema + */ + public function delete(Entity $schema): Schema + { + $result = parent::delete($schema); + + // Dispatch deletion event + $this->eventDispatcher->dispatch( + SchemaDeletedEvent::class, + new SchemaDeletedEvent($schema) + ); + + return $result; } } diff --git a/lib/Event/ObjectCreatedEvent.php b/lib/Event/ObjectCreatedEvent.php new file mode 100644 index 0000000000..357ac589d4 --- /dev/null +++ b/lib/Event/ObjectCreatedEvent.php @@ -0,0 +1,34 @@ +object = $object; + } + + /** + * Get the created object entity + * + * @return ObjectEntity The object entity that was created + */ + public function getObject(): ObjectEntity { + return $this->object; + } +} diff --git a/lib/Event/ObjectDeletedEvent.php b/lib/Event/ObjectDeletedEvent.php new file mode 100644 index 0000000000..fb0f3d4c64 --- /dev/null +++ b/lib/Event/ObjectDeletedEvent.php @@ -0,0 +1,34 @@ +object = $object; + } + + /** + * Get the deleted object entity + * + * @return ObjectEntity The object entity that was deleted + */ + public function getObject(): ObjectEntity { + return $this->object; + } +} \ No newline at end of file diff --git a/lib/Event/ObjectUpdatedEvent.php b/lib/Event/ObjectUpdatedEvent.php new file mode 100644 index 0000000000..264f723a42 --- /dev/null +++ b/lib/Event/ObjectUpdatedEvent.php @@ -0,0 +1,48 @@ +newObject = $newObject; + $this->oldObject = $oldObject; + } + + /** + * Get the updated object entity + * + * @return ObjectEntity The object entity after update + */ + public function getNewObject(): ObjectEntity { + return $this->newObject; + } + + /** + * Get the original object entity + * + * @return ObjectEntity The object entity before update + */ + public function getOldObject(): ObjectEntity { + return $this->oldObject; + } +} \ No newline at end of file diff --git a/lib/Event/RegisterCreatedEvent.php b/lib/Event/RegisterCreatedEvent.php new file mode 100644 index 0000000000..e5ba94d91b --- /dev/null +++ b/lib/Event/RegisterCreatedEvent.php @@ -0,0 +1,34 @@ +register = $register; + } + + /** + * Get the created register + * + * @return Register The register that was created + */ + public function getRegister(): Register { + return $this->register; + } +} \ No newline at end of file diff --git a/lib/Event/RegisterDeletedEvent.php b/lib/Event/RegisterDeletedEvent.php new file mode 100644 index 0000000000..0eb3c90ffd --- /dev/null +++ b/lib/Event/RegisterDeletedEvent.php @@ -0,0 +1,34 @@ +register = $register; + } + + /** + * Get the deleted register + * + * @return Register The register that was deleted + */ + public function getRegister(): Register { + return $this->register; + } +} \ No newline at end of file diff --git a/lib/Event/RegisterUpdatedEvent.php b/lib/Event/RegisterUpdatedEvent.php new file mode 100644 index 0000000000..fe9b660539 --- /dev/null +++ b/lib/Event/RegisterUpdatedEvent.php @@ -0,0 +1,48 @@ +newRegister = $newRegister; + $this->oldRegister = $oldRegister; + } + + /** + * Get the updated register + * + * @return Register The register after update + */ + public function getNewRegister(): Register { + return $this->newRegister; + } + + /** + * Get the original register + * + * @return Register The register before update + */ + public function getOldRegister(): Register { + return $this->oldRegister; + } +} \ No newline at end of file diff --git a/lib/Event/SchemaCreatedEvent.php b/lib/Event/SchemaCreatedEvent.php new file mode 100644 index 0000000000..522c758a68 --- /dev/null +++ b/lib/Event/SchemaCreatedEvent.php @@ -0,0 +1,34 @@ +schema = $schema; + } + + /** + * Get the created schema + * + * @return Schema The schema that was created + */ + public function getSchema(): Schema { + return $this->schema; + } +} \ No newline at end of file diff --git a/lib/Event/SchemaDeletedEvent.php b/lib/Event/SchemaDeletedEvent.php new file mode 100644 index 0000000000..041f1993ea --- /dev/null +++ b/lib/Event/SchemaDeletedEvent.php @@ -0,0 +1,34 @@ +schema = $schema; + } + + /** + * Get the deleted schema + * + * @return Schema The schema that was deleted + */ + public function getSchema(): Schema { + return $this->schema; + } +} \ No newline at end of file diff --git a/lib/Event/SchemaUpdatedEvent.php b/lib/Event/SchemaUpdatedEvent.php new file mode 100644 index 0000000000..397d9c81b0 --- /dev/null +++ b/lib/Event/SchemaUpdatedEvent.php @@ -0,0 +1,48 @@ +newSchema = $newSchema; + $this->oldSchema = $oldSchema; + } + + /** + * Get the updated schema + * + * @return Schema The schema after update + */ + public function getNewSchema(): Schema { + return $this->newSchema; + } + + /** + * Get the original schema + * + * @return Schema The schema before update + */ + public function getOldSchema(): Schema { + return $this->oldSchema; + } +} \ No newline at end of file From e595d3f17270070dfeb9a811e904eea91cdcd877 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 8 Jan 2025 16:52:51 +0100 Subject: [PATCH 12/24] First stages on fixing the event dispatcher calls The way events are dispatched now is deprecated, this fix updates it to the most recent version --- lib/Controller/ObjectsController.php | 22 +++++++-------- lib/Db/ObjectEntityMapper.php | 41 ++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/lib/Controller/ObjectsController.php b/lib/Controller/ObjectsController.php index bc8c25f8ce..1e14c331fc 100644 --- a/lib/Controller/ObjectsController.php +++ b/lib/Controller/ObjectsController.php @@ -13,7 +13,7 @@ use OCP\AppFramework\Http\JSONResponse; use OCP\DB\Exception; use OCP\IAppConfig; -use OCP\IRequest; +use OCP\IRequest; use OCP\App\IAppManager; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -111,7 +111,7 @@ public function index(ObjectService $objectService, SearchService $searchService public function show(string $id): JSONResponse { try { - return new JSONResponse($this->objectEntityMapper->find(idOrUuid: (int) $id)->getObjectArray()); + return new JSONResponse($this->objectEntityMapper->find((int) $id)->getObjectArray()); } catch (DoesNotExistException $exception) { return new JSONResponse(data: ['error' => 'Not Found'], statusCode: 404); } @@ -146,7 +146,7 @@ public function create(ObjectService $objectService): JSONResponse unset($data['id']); } - // If mapping ID is provided, transform the object using the mapping + // If mapping ID is provided, transform the object using the mapping $mappingService = $this->getOpenConnectorMappingService(); if ($mapping !== null && $mappingService !== null) { @@ -195,7 +195,7 @@ public function update(int $id, ObjectService $objectService): JSONResponse unset($data['id']); } - // If mapping ID is provided, transform the object using the mapping + // If mapping ID is provided, transform the object using the mapping $mappingService = $this->getOpenConnectorMappingService(); if ($mapping !== null && $mappingService !== null) { @@ -211,9 +211,9 @@ public function update(int $id, ObjectService $objectService): JSONResponse return new JSONResponse(['message' => $exception->getMessage(), 'validationErrors' => $formatter->format($exception->getErrors())], 400); } - $this->auditTrailMapper->createAuditTrail(new: $objectEntity, old: $oldObject); +// $this->auditTrailMapper->createAuditTrail(new: $objectEntity, old: $oldObject); - return new JSONResponse($objectEntity->getOBjectArray()); + return new JSONResponse($objectEntity->getObjectArray()); } /** @@ -330,11 +330,11 @@ public function logs(int $id): JSONResponse } } - + /** * Retrieves all available mappings - * + * * This method returns a JSON response containing all available mappings in the system. * * @NoAdminRequired @@ -346,15 +346,15 @@ public function mappings(): JSONResponse { // Get mapping service, which will return null based on implementation $mappingService = $this->getOpenConnectorMappingService(); - + // Initialize results array $results = []; - + // If mapping service exists, get all mappings using find() method if ($mappingService !== null) { $results = $mappingService->getMappings(); } - + // Return response with results array and total count return new JSONResponse([ 'results' => $results, diff --git a/lib/Db/ObjectEntityMapper.php b/lib/Db/ObjectEntityMapper.php index f9bbbd5cef..b62bc2a431 100644 --- a/lib/Db/ObjectEntityMapper.php +++ b/lib/Db/ObjectEntityMapper.php @@ -38,7 +38,7 @@ class ObjectEntityMapper extends QBMapper * @param IEventDispatcher $eventDispatcher The event dispatcher */ public function __construct( - IDBConnection $db, + IDBConnection $db, MySQLJsonService $mySQLJsonService, IEventDispatcher $eventDispatcher ) { @@ -248,6 +248,19 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters return $this->findEntities(query: $qb); } + /** + * @inheritDoc + */ + public function insert(Entity $entity): Entity + { + $entity = parent::insert($entity); + // Dispatch creation event + $this->eventDispatcher->dispatchTyped(new ObjectCreatedEvent($entity)); + + return $entity; + + } + /** * Creates an object from an array * @@ -264,15 +277,25 @@ public function createFromArray(array $object): ObjectEntity $obj = $this->insert($obj); - // Dispatch creation event - $this->eventDispatcher->dispatch( - ObjectCreatedEvent::class, - new ObjectCreatedEvent($obj) - ); return $obj; } + /** + * @inheritDoc + */ + public function update(Entity $entity): Entity + { + $oldObject = $this->find($entity->getId()); + + $entity = parent::update($entity); + // Dispatch creation event + $this->eventDispatcher->dispatchTyped(new ObjectUpdatedEvent($entity, $oldObject)); + + return $entity; + + } + /** * Updates an object from an array * @@ -295,12 +318,6 @@ public function updateFromArray(int $id, array $object): ObjectEntity $newObject = $this->update($newObject); - // Dispatch update event - $this->eventDispatcher->dispatch( - ObjectUpdatedEvent::class, - new ObjectUpdatedEvent($newObject, $oldObject) - ); - return $newObject; } From c497df77cdc1245e9cc2545d32233524c0a3b84b Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Thu, 9 Jan 2025 09:46:53 +0100 Subject: [PATCH 13/24] Document new registration route for event listeners --- docs/events.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/events.md b/docs/events.md index abeec18682..934fa18c78 100644 --- a/docs/events.md +++ b/docs/events.md @@ -127,7 +127,9 @@ class SchemaListener implements IEventListener { After creating your listener class, you need to register it with Nextcloud's event dispatcher system. This is done in your application's `lib/AppInfo/Application.php` file by registering the listener in the `register()` method: ```php -$container->query(EventDispatcherInterface::class)->addListener(SchemaCreatedEvent::class, [SchemaListener::class, 'handle']); +/* @var IEventDispatcher $dispatcher */ +$dispatcher = $this->getContainer()->get(IEventDispatcher::class); +$dispatcher->addServiceListener(eventName: SchemaCreatedEvent::class, className: SchemaCreatedEvent::class); ``` This line registers the `SchemaListener` class to handle `SchemaCreatedEvent` events. You can add similar lines for other events to register your listener for those specific events. From 025570b7368c82db150db3df9d2091aff058754e Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Thu, 9 Jan 2025 09:54:41 +0100 Subject: [PATCH 14/24] Fix documentation in line with latest Nextcloud documentation --- docs/events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/events.md b/docs/events.md index 934fa18c78..2eb8b3b356 100644 --- a/docs/events.md +++ b/docs/events.md @@ -120,7 +120,7 @@ class SchemaListener implements IEventListener { // Handle the event } } -``` +``` ### 2. Register the Event Listener From 04956fcce5cc7ed8afebe679674fcebb651f2ca9 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Thu, 9 Jan 2025 09:55:00 +0100 Subject: [PATCH 15/24] Dispatch events one layer deeper --- lib/Db/RegisterMapper.php | 52 +++++++++++++++++++++++++-------------- lib/Db/SchemaMapper.php | 49 ++++++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/lib/Db/RegisterMapper.php b/lib/Db/RegisterMapper.php index 44ba9c6862..dee62fa75a 100644 --- a/lib/Db/RegisterMapper.php +++ b/lib/Db/RegisterMapper.php @@ -3,6 +3,7 @@ namespace OCA\OpenRegister\Db; use OCA\OpenRegister\Db\Register; +use OCA\OpenRegister\Event\SchemaCreatedEvent; use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -33,7 +34,7 @@ class RegisterMapper extends QBMapper * @param IEventDispatcher $eventDispatcher The event dispatcher */ public function __construct( - IDBConnection $db, + IDBConnection $db, SchemaMapper $schemaMapper, IEventDispatcher $eventDispatcher ) { @@ -106,6 +107,19 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters return $this->findEntities(query: $qb); } + /** + * @inheritdoc + */ + public function insert(Entity $entity): Entity + { + $entity = parent::insert($entity); + + // Dispatch creation event + $this->eventDispatcher->dispatchTyped(new SchemaCreatedEvent($entity)); + + return $entity; + } + /** * Create a new register from an array of data * @@ -122,16 +136,24 @@ public function createFromArray(array $object): Register } $register = $this->insert(entity: $register); - - // Dispatch creation event - $this->eventDispatcher->dispatch( - RegisterCreatedEvent::class, - new RegisterCreatedEvent($register) - ); return $register; } + /** + * @inheritdoc + */ + public function update(Entity $entity): Entity + { + $oldSchema = $this->find($entity->getId()); + $entity = parent::update($entity); + + // Dispatch update event + $this->eventDispatcher->dispatchTyped(new RegisterUpdatedEvent($entity, $oldSchema)); + + return $entity; + } + /** * Update an existing register from an array of data * @@ -141,8 +163,7 @@ public function createFromArray(array $object): Register */ public function updateFromArray(int $id, array $object): Register { - $oldRegister = $this->find($id); - $newRegister = clone $oldRegister; + $newRegister = $this->find($id); $newRegister->hydrate($object); if (isset($object['version']) === false) { @@ -152,12 +173,6 @@ public function updateFromArray(int $id, array $object): Register } $newRegister = $this->update($newRegister); - - // Dispatch update event - $this->eventDispatcher->dispatch( - RegisterUpdatedEvent::class, - new RegisterUpdatedEvent($newRegister, $oldRegister) - ); return $newRegister; } @@ -168,13 +183,12 @@ public function updateFromArray(int $id, array $object): Register * @param Register $register The register to delete * @return Register The deleted register */ - public function delete(Entity $register): Register + public function delete(Entity $register): Register { $result = parent::delete($register); - + // Dispatch deletion event - $this->eventDispatcher->dispatch( - RegisterDeletedEvent::class, + $this->eventDispatcher->dispatchTyped( new RegisterDeletedEvent($register) ); diff --git a/lib/Db/SchemaMapper.php b/lib/Db/SchemaMapper.php index f62e9ff1d0..9884ec6bb3 100644 --- a/lib/Db/SchemaMapper.php +++ b/lib/Db/SchemaMapper.php @@ -111,6 +111,19 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters return $this->findEntities(query: $qb); } + /** + * @inheritdoc + */ + public function insert(Entity $entity): Entity + { + $entity = parent::insert($entity); + + // Dispatch creation event + $this->eventDispatcher->dispatchTyped(new SchemaCreatedEvent($entity)); + + return $entity; + } + /** * Creates a schema from an array * @@ -127,16 +140,24 @@ public function createFromArray(array $object): Schema } $schema = $this->insert(entity: $schema); - - // Dispatch creation event - $this->eventDispatcher->dispatch( - SchemaCreatedEvent::class, - new SchemaCreatedEvent($schema) - ); return $schema; } + /** + * @inheritdoc + */ + public function update(Entity $entity): Entity + { + $oldSchema = $this->find($entity->getId()); + $entity = parent::update($entity); + + // Dispatch update event + $this->eventDispatcher->dispatchTyped(new SchemaUpdatedEvent($entity, $oldSchema)); + + return $entity; + } + /** * Updates a schema from an array * @@ -146,8 +167,7 @@ public function createFromArray(array $object): Schema */ public function updateFromArray(int $id, array $object): Schema { - $oldSchema = $this->find($id); - $newSchema = clone $oldSchema; + $newSchema = $this->find($id); $newSchema->hydrate($object); if (isset($object['version']) === false) { @@ -157,12 +177,6 @@ public function updateFromArray(int $id, array $object): Schema } $newSchema = $this->update($newSchema); - - // Dispatch update event - $this->eventDispatcher->dispatch( - SchemaUpdatedEvent::class, - new SchemaUpdatedEvent($newSchema, $oldSchema) - ); return $newSchema; } @@ -173,13 +187,12 @@ public function updateFromArray(int $id, array $object): Schema * @param Schema $schema The schema to delete * @return Schema The deleted schema */ - public function delete(Entity $schema): Schema + public function delete(Entity $schema): Schema { $result = parent::delete($schema); - + // Dispatch deletion event - $this->eventDispatcher->dispatch( - SchemaDeletedEvent::class, + $this->eventDispatcher->dispatchTyped( new SchemaDeletedEvent($schema) ); From 23476dfb3ead3061f9d23c8212d81814bd0fcfb8 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Thu, 9 Jan 2025 13:23:45 +0100 Subject: [PATCH 16/24] Small copy fix --- lib/Db/RegisterMapper.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/Db/RegisterMapper.php b/lib/Db/RegisterMapper.php index dee62fa75a..b68bfa9b3b 100644 --- a/lib/Db/RegisterMapper.php +++ b/lib/Db/RegisterMapper.php @@ -7,8 +7,6 @@ use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; -use OCA\OpenRegister\Db\SchemaMapper; -use OCA\OpenRegister\Db\Schema; use OCP\IDBConnection; use Symfony\Component\Uid\Uuid; use OCP\EventDispatcher\IEventDispatcher; @@ -115,7 +113,7 @@ public function insert(Entity $entity): Entity $entity = parent::insert($entity); // Dispatch creation event - $this->eventDispatcher->dispatchTyped(new SchemaCreatedEvent($entity)); + $this->eventDispatcher->dispatchTyped(new RegisterCreatedEvent($entity)); return $entity; } @@ -180,16 +178,16 @@ public function updateFromArray(int $id, array $object): Register /** * Delete a register * - * @param Register $register The register to delete + * @param Register $entity The register to delete * @return Register The deleted register */ - public function delete(Entity $register): Register + public function delete(Entity $entity): Register { - $result = parent::delete($register); + $result = parent::delete($entity); // Dispatch deletion event $this->eventDispatcher->dispatchTyped( - new RegisterDeletedEvent($register) + new RegisterDeletedEvent($entity) ); return $result; From 2798dcee407ece1dec2087bcc9b4f85abb93db75 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Thu, 9 Jan 2025 16:10:43 +0100 Subject: [PATCH 17/24] Remove creating audit trail in controller as this is also done in the objectService --- lib/Controller/ObjectsController.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Controller/ObjectsController.php b/lib/Controller/ObjectsController.php index 1e14c331fc..42aeeed55b 100644 --- a/lib/Controller/ObjectsController.php +++ b/lib/Controller/ObjectsController.php @@ -210,9 +210,7 @@ public function update(int $id, ObjectService $objectService): JSONResponse $formatter = new ErrorFormatter(); return new JSONResponse(['message' => $exception->getMessage(), 'validationErrors' => $formatter->format($exception->getErrors())], 400); } - -// $this->auditTrailMapper->createAuditTrail(new: $objectEntity, old: $oldObject); - + return new JSONResponse($objectEntity->getObjectArray()); } From ef95b6bf251c09eda3a5666ed77235d20391a1b6 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Thu, 9 Jan 2025 16:34:22 +0100 Subject: [PATCH 18/24] Fix copy paste error --- docs/events.md | 2 +- lib/Controller/ObjectsController.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/events.md b/docs/events.md index 2eb8b3b356..1ea7a2a923 100644 --- a/docs/events.md +++ b/docs/events.md @@ -129,7 +129,7 @@ After creating your listener class, you need to register it with Nextcloud's eve ```php /* @var IEventDispatcher $dispatcher */ $dispatcher = $this->getContainer()->get(IEventDispatcher::class); -$dispatcher->addServiceListener(eventName: SchemaCreatedEvent::class, className: SchemaCreatedEvent::class); +$dispatcher->addServiceListener(eventName: SchemaCreatedEvent::class, className: SchemaCreatedListener::class); ``` This line registers the `SchemaListener` class to handle `SchemaCreatedEvent` events. You can add similar lines for other events to register your listener for those specific events. diff --git a/lib/Controller/ObjectsController.php b/lib/Controller/ObjectsController.php index 42aeeed55b..6556d33aac 100644 --- a/lib/Controller/ObjectsController.php +++ b/lib/Controller/ObjectsController.php @@ -210,7 +210,7 @@ public function update(int $id, ObjectService $objectService): JSONResponse $formatter = new ErrorFormatter(); return new JSONResponse(['message' => $exception->getMessage(), 'validationErrors' => $formatter->format($exception->getErrors())], 400); } - + return new JSONResponse($objectEntity->getObjectArray()); } From 7e63891103e4cec455dda732c270ec715f27669d Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Thu, 9 Jan 2025 16:42:21 +0100 Subject: [PATCH 19/24] Clearify one piece of text --- docs/events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/events.md b/docs/events.md index 1ea7a2a923..08a836d40d 100644 --- a/docs/events.md +++ b/docs/events.md @@ -132,6 +132,6 @@ $dispatcher = $this->getContainer()->get(IEventDispatcher::class); $dispatcher->addServiceListener(eventName: SchemaCreatedEvent::class, className: SchemaCreatedListener::class); ``` -This line registers the `SchemaListener` class to handle `SchemaCreatedEvent` events. You can add similar lines for other events to register your listener for those specific events. +This line registers the `SchemaListener` class to handle `SchemaCreatedEvent` events. You can register your listener to other events by adding similar lines. You can read more about event handling in the [Nextcloud documentation](https://docs.nextcloud.com/server/latest/developer_manual/basics/events.html). From db8aa723c972d42c362566c2cfea0ee9c1e2c8f9 Mon Sep 17 00:00:00 2001 From: Thijn Date: Fri, 10 Jan 2025 12:32:02 +0100 Subject: [PATCH 20/24] fixed object upload --- src/views/object/ObjectsList.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/object/ObjectsList.vue b/src/views/object/ObjectsList.vue index e1221d3f8d..184b869be5 100644 --- a/src/views/object/ObjectsList.vue +++ b/src/views/object/ObjectsList.vue @@ -22,7 +22,7 @@ import { objectStore, navigationStore, searchStore } from '../../store/store.js' Refresh - + From 6c73eaa03451c659e7fe5447ee54f991f088c2d0 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Mon, 13 Jan 2025 10:56:30 +0100 Subject: [PATCH 21/24] Add setting default values --- composer.json | 3 +- composer.lock | 124 ++++++++++++++++++++++++++++------ lib/Service/ObjectService.php | 27 +++++++- 3 files changed, 130 insertions(+), 24 deletions(-) diff --git a/composer.json b/composer.json index 7e07700c81..48c0e0e437 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ "guzzlehttp/guzzle": "^7.0", "opis/json-schema": "^2.3", "symfony/uid": "^6.4", - "symfony/yaml": "^6.4" + "symfony/yaml": "^6.4", + "twig/twig": "^3.18" }, "require-dev": { "nextcloud/ocp": "dev-stable29", diff --git a/composer.lock b/composer.lock index 64ff9c404d..a59137f8dd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d4ee34c9190d5b1b8eb1a1841807244e", + "content-hash": "065490421925be3ad6cb1128af7a5e1c", "packages": [ { "name": "adbario/php-dot-notation", @@ -1466,27 +1466,33 @@ "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1494,21 +1500,14 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -1518,16 +1517,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -1543,7 +1543,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php81", @@ -1921,6 +1921,86 @@ } ], "time": "2024-09-17T12:47:12+00:00" + }, + { + "name": "twig/twig", + "version": "v3.18.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", + "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php81": "^1.29" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.18.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-12-29T10:51:50+00:00" } ], "packages-dev": [ diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index 1d10d9925d..998b3d567c 100755 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -8,6 +8,8 @@ use GuzzleHttp\Exception\GuzzleException; use InvalidArgumentException; use JsonSerializable; +use OCA\OpenConnector\Twig\MappingExtension; +use OCA\OpenConnector\Twig\MappingRuntimeLoader; use OCA\OpenRegister\Db\File; use OCA\OpenRegister\Db\FileMapper; use OCA\OpenRegister\Db\Source; @@ -34,6 +36,8 @@ use stdClass; use Symfony\Component\Uid\Uuid; use GuzzleHttp\Client; +use Twig\Environment; +use Twig\Loader\ArrayLoader; /** * Service class for handling object operations. @@ -81,8 +85,11 @@ public function __construct( private readonly IAppManager $appManager, private readonly IAppConfig $config, private readonly FileMapper $fileMapper, + ArrayLoader $loader ) { + $this->twig = new Environment($loader); + $this->twig->addExtension(new MappingExtension()); } /** @@ -600,6 +607,8 @@ public function saveObject(int $register, int $schema, array $object, ?int $dept $objectEntity->setUuid(Uuid::v4()); } + $objectEntity->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openregister.Objects.show', ['id' => $objectEntity->getUuid()]))); + // Let grap any links that we can $objectEntity = $this->handleLinkRelations($objectEntity, $object); @@ -610,7 +619,7 @@ public function saveObject(int $register, int $schema, array $object, ?int $dept $objectEntity = $this->handleObjectRelations($objectEntity, $object, $schemaObject->getProperties(), $register, $schema, depth: $depth); } - $objectEntity->setUri($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('openregister.Objects.show', ['id' => $objectEntity->getUuid()]))); + $this->setDefaults($objectEntity); if ($objectEntity->getId() && ($schemaObject->getHardValidation() === false || $validationResult->isValid() === true)) { $objectEntity = $this->objectEntityMapper->update($objectEntity); @@ -1765,4 +1774,20 @@ public function getUses(string $id, ?int $register = null, ?int $schema = null): return $referencedObjects; } + + public function setDefaults(ObjectEntity $objectEntity): ObjectEntity + { + $data = $objectEntity->jsonSerialize(); + $schema = $this->schemaMapper->find($objectEntity->getSchema()); + + foreach ($schema->getProperties() as $name=>$property) { + if(isset($data[$name]) === false && isset($property['default']) === true) { + $data[$name] = $this->twig->createTemplate($property['default'], "{$schema->getTitle()}.$name")->render($objectEntity->getObjectArray()); + } + } + + $objectEntity->setObject($data); + + return $objectEntity; + } } From 9660a0feb0518741f13d1a3a146faa856b6c07ee Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Mon, 13 Jan 2025 13:45:42 +0100 Subject: [PATCH 22/24] Add docblock --- lib/Service/ObjectService.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index 998b3d567c..33c44dba18 100755 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -1775,6 +1775,15 @@ public function getUses(string $id, ?int $register = null, ?int $schema = null): return $referencedObjects; } + /** + * Sets default values for an object based upon its schema + * + * @param ObjectEntity $objectEntity The object to set default values in. + * + * @return ObjectEntity The resulting objectEntity. + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\SyntaxError + */ public function setDefaults(ObjectEntity $objectEntity): ObjectEntity { $data = $objectEntity->jsonSerialize(); From f73bccd14c80e13f5d5e630cb4d9dd45f9fdd719 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Mon, 13 Jan 2025 16:14:46 +0100 Subject: [PATCH 23/24] Style fix --- lib/Service/ObjectService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index 33c44dba18..6c609164eb 100755 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -1790,7 +1790,7 @@ public function setDefaults(ObjectEntity $objectEntity): ObjectEntity $schema = $this->schemaMapper->find($objectEntity->getSchema()); foreach ($schema->getProperties() as $name=>$property) { - if(isset($data[$name]) === false && isset($property['default']) === true) { + if (isset($data[$name]) === false && isset($property['default']) === true) { $data[$name] = $this->twig->createTemplate($property['default'], "{$schema->getTitle()}.$name")->render($objectEntity->getObjectArray()); } } From f5ce9bd340f561909575756a98eb0e4f09c4249d Mon Sep 17 00:00:00 2001 From: Barry Brands Date: Thu, 16 Jan 2025 01:54:25 +0100 Subject: [PATCH 24/24] fix extend for sub objects with maxDepth --- lib/Service/ObjectService.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index 6c609164eb..909bff769d 100755 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -1550,8 +1550,12 @@ public function renderEntity(array $entity, ?array $extend = []): array * @return array The extended entity as an array * @throws Exception If property not found */ - public function extendEntity(array $entity, array $extend): array + public function extendEntity(array $entity, array $extend, ?int $depth = 0): array { + if ($depth > 3) { + return $entity; + } + // Convert entity to array if needed if (is_array($entity)) { $result = $entity; @@ -1559,8 +1563,12 @@ public function extendEntity(array $entity, array $extend): array $result = $entity->jsonSerialize(); } + if (in_array('all', $extend)) { + $extendProperties = array_keys($result); + } + // Process each property to extend - foreach ($extend as $property) { + foreach ($extendProperties as $property) { $singularProperty = rtrim($property, 's'); // Check if property exists @@ -1597,7 +1605,8 @@ public function extendEntity(array $entity, array $extend): array try { $found = $this->objectEntityMapper->find($val); if ($found) { - $extendedValues[] = $found; + $extendedFound = $this->extendEntity($found->jsonSerialize(), $extend, $depth + 1); + $extendedValues[] = $extendedFound; } } catch (Exception $e) { continue; @@ -1610,7 +1619,8 @@ public function extendEntity(array $entity, array $extend): array // Handle single value $found = $this->objectEntityMapper->find($value); if ($found) { - $result[$property] = $found; + // Serialize and recursively extend the found object (apply depth tracking here) + $result[$property] = $this->extendEntity($found->jsonSerialize(), $extend, $depth + 1); // Start with depth 1 } } } catch (Exception $e2) {