diff --git a/.scoper-production-dependencies b/.scoper-production-dependencies index e29620a89e..336650b97b 100644 --- a/.scoper-production-dependencies +++ b/.scoper-production-dependencies @@ -7,3 +7,5 @@ psr/http-client psr/http-factory psr/http-message psr/simple-cache +symfony/uid +symfony/polyfill-uuid diff --git a/appinfo/info.xml b/appinfo/info.xml index 4f05ced258..94e609d38b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -26,7 +26,7 @@ Share your tables and views with users and groups within your cloud. Have a good time and manage whatever you want. ]]> - 2.2.0 + 2.2.1-dev.0 AGPL-3.0-or-later Nextcloud GmbH and Nextcloud contributors Tables diff --git a/composer.json b/composer.json index e79266588d..e7566fcfc4 100644 --- a/composer.json +++ b/composer.json @@ -66,6 +66,7 @@ "require": { "phpoffice/phpspreadsheet": "^5.1", "ext-json": "*", - "bamarni/composer-bin-plugin": "^1.9.1" + "bamarni/composer-bin-plugin": "^1.9.1", + "symfony/uid": "^6.4" } } diff --git a/composer.lock b/composer.lock index 9bef24e1ed..1dd11e0a8b 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": "22888eddc0a1d9e115a52e0366dd5401", + "content-hash": "a23ff2aecc750aa400068e3f79dc35b8", "packages": [ { "name": "bamarni/composer-bin-plugin", @@ -642,6 +642,167 @@ "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/uid", + "version": "v6.4.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "6b973c385f00341b246f697d82dc01a09107acdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/6b973c385f00341b246f697d82dc01a09107acdd", + "reference": "6b973c385f00341b246f697d82dc01a09107acdd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v6.4.32" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-23T15:07:59+00:00" } ], "packages-dev": [ diff --git a/lib/Analytics/AnalyticsDatasource.php b/lib/Analytics/AnalyticsDatasource.php index 5a61416fd0..80081b5ade 100644 --- a/lib/Analytics/AnalyticsDatasource.php +++ b/lib/Analytics/AnalyticsDatasource.php @@ -231,9 +231,9 @@ private function getData(int $nodeId, ?int $limit, ?int $offset, ?string $nodeTy if ($datum['columnId'] === $column->getId()) { // if column type selection, the corresponding labels need to be fetched if ($column->getType() === 'selection') { - foreach ($column->getSelectionOptionsArray() as $option) { - if ($option['id'] === $datum['value']) { - $value = $option['label']; + foreach ($column->getSelectionOptionsCollection() as $option) { + if ($option->key() === $datum['value']) { + $value = $option->label(); } } } else { diff --git a/lib/Controller/Api1Controller.php b/lib/Controller/Api1Controller.php index c7fc1c9375..bfb7410a23 100644 --- a/lib/Controller/Api1Controller.php +++ b/lib/Controller/Api1Controller.php @@ -21,6 +21,7 @@ use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Errors\PermissionError; use OCA\Tables\Middleware\Attribute\RequirePermission; +use OCA\Tables\Model\SelectionOptions; use OCA\Tables\Model\ViewUpdateInput; use OCA\Tables\ResponseDefinitions; use OCA\Tables\Service\ColumnService; diff --git a/lib/Db/Column.php b/lib/Db/Column.php index 5f7518d4c9..295cc5b973 100644 --- a/lib/Db/Column.php +++ b/lib/Db/Column.php @@ -10,8 +10,10 @@ use JsonSerializable; use OCA\Tables\Constants\ColumnType; use OCA\Tables\Dto\Column as ColumnDto; +use OCA\Tables\Model\SelectionOptions; use OCA\Tables\ResponseDefinitions; use OCA\Tables\Service\ValueObject\ViewColumnInformation; +use OCA\Tables\Vendor\Symfony\Component\Uid\Uuid; use ValueError; /** @@ -19,6 +21,8 @@ * * @psalm-import-type TablesColumn from ResponseDefinitions * + * @method string|null getUuid() + * @method setUuid(?string $uuid) * @method getTitle(): string * @method setTitle(string $title) * @method getTableId(): int @@ -58,14 +62,12 @@ * @method setTextDefault(?string $textDefault) * @method getTextAllowedPattern(): string * @method setTextAllowedPattern(?string $textAllowedPattern) - * @method getTextAllowedPattern(): ?string * @method getTextMaxLength(): int * @method setTextMaxLength(?int $textMaxLength) * @method getTextUnique(): bool * @method setTextUnique(?bool $textUnique) - * @method getSelectionOptions(): string - * @method getSelectionDefault(): string - * @method setSelectionOptions(?string $selectionOptionsArray) + * @method string getSelectionOptions() + * @method setSelectionOptions(?string $selectionOptions) * @method setSelectionDefault(?string $selectionDefault) * @method getSelectionDefault(): ?string * @method getDatetimeDefault(): string @@ -113,6 +115,7 @@ class Column extends EntitySuper implements JsonSerializable { public const META_ID_TITLE = 'id'; + protected ?string $uuid = null; protected ?string $title = null; protected ?int $tableId = null; protected ?string $createdBy = null; @@ -164,6 +167,7 @@ class Column extends EntitySuper implements JsonSerializable { public function __construct() { $this->addType('id', 'integer'); + $this->addType('uuid', 'string'); $this->addType('tableId', 'integer'); $this->addType('mandatory', 'boolean'); @@ -185,6 +189,8 @@ public function __construct() { $this->addType('showUserStatus', 'boolean'); $this->addType('customSettings', 'string'); + + $this->addType('selectionOptions', 'string'); } public static function isValidMetaTypeId(int $metaTypeId): bool { @@ -197,8 +203,41 @@ public static function isValidMetaTypeId(int $metaTypeId): bool { ], true); } + public function setter (string $name, array $args): void { + if ($name === 'uuid') { + $this->setOrAssignUuid((string)$args[0]); + return; + } + + parent::setter($name, $args); + } + + private function assignUuid(): void { + if ($this->uuid !== null) { + throw new \RuntimeException('This column already has a UUID, they are immutable'); + } + $this->applyUuid(Uuid::v7()->toRfc4122()); + } + + private function setOrAssignUuid(?string $uuid): void { + if ($this->uuid !== null) { + throw new \RuntimeException('This column already has a UUID, they are immutable'); + } + if ($uuid === null) { + $this->assignUuid(); + return; + } + $this->applyUuid($uuid); + } + + private function applyUuid(string $uuid): void { + $this->uuid = $uuid; + $this->markFieldUpdated('uuid'); + } + public static function fromDto(ColumnDto $data): self { $column = new self(); + $column->assignUuid(); $column->setTitle($data->getTitle()); $column->setType($data->getType()); $column->setSubtype($data->getSubtype() ?? ''); @@ -214,8 +253,7 @@ public static function fromDto(ColumnDto $data): self { $column->setNumberDecimals($data->getNumberDecimals()); $column->setNumberPrefix($data->getNumberPrefix() ?? ''); $column->setNumberSuffix($data->getNumberSuffix() ?? ''); - $column->setSelectionOptions($data->getSelectionOptions()); - $column->setSelectionDefault($data->getSelectionDefault()); + $column->setSelectionOptionsCollection(SelectionOptions::createFromInputJsonString($data->getSelectionOptions() ?? '[]', $data->getSelectionDefault())); $column->setDatetimeDefault($data->getDatetimeDefault()); $column->setUsergroupDefault($data->getUsergroupDefault()); $column->setUsergroupMultipleItems($data->getUsergroupMultipleItems()); @@ -241,18 +279,17 @@ public function setUsergroupDefaultArray(array $array):void { $this->setUsergroup($json); } - public function getSelectionOptionsArray(): array { - $options = $this->getSelectionOptions(); - if ($options !== '' && $options !== null && $options !== 'null') { - return \json_decode($options, true); - } else { - return []; - } + public function getSelectionOptionsCollection(): SelectionOptions { + return SelectionOptions::createFromInputJsonString($this->getSelectionOptions() ?? '[]', $this->getSelectionDefault()); } - public function setSelectionOptionsArray(array $array):void { - $json = \json_encode($array); - $this->setSelectionOptions($json); + public function setSelectionOptionsCollection(SelectionOptions $selectionOptions): void { + $this->setSelectionOptions(json_encode($selectionOptions->jsonSerialize())); + $this->setSelectionDefault($selectionOptions->defaultSerialized()); + } + + public function getSelectionOptionsArray(): ?array { + return $this->getSelectionOptionsCollection()->jsonSerialize(); } /** @@ -261,6 +298,7 @@ public function setSelectionOptionsArray(array $array):void { public function jsonSerialize(): array { return [ 'id' => $this->id, + 'uuid' => $this->uuid, 'tableId' => $this->tableId, 'title' => $this->title, 'createdBy' => $this->createdBy, diff --git a/lib/Migration/Version2020Date20260513185340.php b/lib/Migration/Version2020Date20260513185340.php new file mode 100644 index 0000000000..32766789b2 --- /dev/null +++ b/lib/Migration/Version2020Date20260513185340.php @@ -0,0 +1,96 @@ +hasTable(self::TARGET_TABLE)) { + return null; + } + + $columnsTable = $schema->getTable(self::TARGET_TABLE); + if (!$columnsTable->hasColumn(self::COL_UUID)) { + $columnsTable->addColumn(self::COL_UUID, Types::STRING, [ + 'notnull' => false, + 'default' => null, + 'length' => 36, + 'comment' => 'UUIDv7 identifier to support structural updates across instances', + ]); + } + + return $schema; + } + + #[Override] + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + + $qbUpdate = $this->db->getQueryBuilder(); + $qbUpdate->update(self::TARGET_TABLE) + ->set(self::COL_UUID, $qbUpdate->createParameter('columnUuid')) + ->where($qbUpdate->expr()->eq(self::COL_ID, $qbUpdate->createParameter('columnLocalId'))); + + $qbSelect = $this->db->getQueryBuilder(); + $qbSelect->select(self::COL_ID) + ->from(self::TARGET_TABLE); + $select = $qbSelect->executeQuery(); + + $writeBatches = 250; + $updates = 0; + + try { + $this->db->beginTransaction(); + while (($columnId = $select->fetchOne()) !== false) { + $qbUpdate->setParameters( + [ + 'columnLocalId' => (int)$columnId, + 'columnUuid' => Uuid::v7()->toRfc4122(), + ], + [ + Types::INTEGER, + Types::STRING, + ] + ); + $qbUpdate->executeStatement(); + $updates++; + if ($updates % $writeBatches === 0) { + $this->db->commit(); + $this->db->beginTransaction(); + } + } + $this->db->commit(); + } catch (\Exception $e) { + $this->db->rollBack(); + throw $e; + } + + $select->closeCursor(); + } +} diff --git a/lib/Model/SelectionOption.php b/lib/Model/SelectionOption.php new file mode 100644 index 0000000000..d85aa94420 --- /dev/null +++ b/lib/Model/SelectionOption.php @@ -0,0 +1,51 @@ +key; + } + + public function label(): string { + return $this->label; + } + + #[\Override] + public function jsonSerialize(): array { + return [ + 'id' => $this->key, + 'label' => $this->label, + ]; + } +} diff --git a/lib/Model/SelectionOptions.php b/lib/Model/SelectionOptions.php new file mode 100644 index 0000000000..4a860d11b9 --- /dev/null +++ b/lib/Model/SelectionOptions.php @@ -0,0 +1,132 @@ +selectionOptions !== null) { + // `check` subtype has options set to null + foreach ($this->selectionOptions as $selectionOption) { + if (!$selectionOption instanceof SelectionOption) { + throw new \InvalidArgumentException('Provided selectionOption must be an instance of SelectionOption'); + } + } + } + if (is_int($this->default)) { + $this->applyIntDefault(); + } else if (is_string($this->default)) { + $this->applyStringDefault(); + } + } + + private function applyIntDefault(): void { + // default value targets a specific key + foreach ($this->selectionOptions as $selectionOption) { + if ($selectionOption->key() === $this->default) { + return; + } + } + // if the default is not available anymore, we pragmatically unset it. + $this->default = null; + } + + private function applyStringDefault(): void { + // default value is a JSON string targeting multiple keys + + $workDefault = \json_decode($this->default(), true); + if (!is_array($workDefault)) { + $this->default = null; + return; + } + + $confirmedOptions = []; + foreach ($workDefault as $defaultOption) { + $normalizedDefaultOption = (int)$defaultOption; + foreach ($this->selectionOptions as $selectionOption) { + if ($selectionOption->key() === $normalizedDefaultOption) { + $confirmedOptions[] = $normalizedDefaultOption; + continue 2; + } + // if the default is not available anymore, we pragmatically ignore it. + } + } + $this->default = $confirmedOptions; + } + + public static function createFromInputArray(?array $data, null|bool|int|string $default): self { + if ($data !== null) { + $selectionOptions = []; + foreach ($data as $inputSelectionOption) { + $selectionOptions[] = SelectionOption::createFromInputArray($inputSelectionOption); + } + } + // `check` subtype has null as options + return new self($selectionOptions ?? null, $default); + } + + public static function createFromInputJsonString(?string $data, null|bool|int|string $default): self { + if ($data !== null && $data !== 'null') { + $inputArray = \json_decode($data === '' ? '[]' : $data, true); + if (!is_array($inputArray)) { + throw new \InvalidArgumentException('Provided selectionOption is not a valid JSON string'); + } + } else { + // `check` subtype has "null" as options + $inputArray = null; + } + return self::createFromInputArray($inputArray, $default); + } + + public function default(): mixed { + return $this->default; + } + + public function defaultSerialized(): string { + return \json_encode($this->default()); + } + + #[\Override] + public function jsonSerialize(): ?array { + if ($this->selectionOptions === null) { + return null; + } + return array_map(static fn (SelectionOption $so) => $so->jsonSerialize(), $this->selectionOptions); + } + + #[\Override] + public function current(): SelectionOption { + return current($this->selectionOptions); + } + + #[\Override] + public function next(): void { + next($this->selectionOptions); + } + + #[\Override] + public function key(): ?int { + return key($this->selectionOptions); + } + + #[\Override] + public function valid(): bool { + return $this->key() !== null; + } + + #[\Override] + public function rewind(): void { + reset($this->selectionOptions); + } +} diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 1dd4d5f6a8..c006a46ccb 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -117,6 +117,7 @@ * * @psalm-type TablesColumn = array{ * id: int, + * uuid: string, * title: string, * tableId: int, * createdBy: string, diff --git a/lib/Service/ColumnService.php b/lib/Service/ColumnService.php index 1f7322e8f1..2d02a7931e 100644 --- a/lib/Service/ColumnService.php +++ b/lib/Service/ColumnService.php @@ -21,14 +21,17 @@ use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Errors\PermissionError; use OCA\Tables\Helper\UserHelper; +use OCA\Tables\Model\SelectionOptions; use OCA\Tables\ResponseDefinitions; use OCA\Tables\Service\ValueObject\Title; use OCA\Tables\Service\ValueObject\ViewColumnInformation; use OCA\Tables\Validation\ColumnDtoValidator; +use OCA\Tables\Vendor\Symfony\Component\Uid\Uuid; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\IL10N; use Psr\Log\LoggerInterface; +use Symfony\Component\Uid\NilUlid; /** * @psalm-import-type TablesColumn from ResponseDefinitions @@ -74,6 +77,7 @@ public function __construct( } /** + * @return Column[] * @throws InternalError * @throws PermissionError */ @@ -141,7 +145,7 @@ public function findAllByManagedView(View $view, string $userId): array { /** * @param int $viewId * @param string|null $userId - * @return array + * @return Column[] * @throws NotFoundError * @throws PermissionError * @throws InternalError @@ -391,11 +395,10 @@ public function update( $item->setNumberMin($columnDto->getNumberMin()); $item->setNumberMax($columnDto->getNumberMax()); $item->setNumberDecimals($columnDto->getNumberDecimals()); - if ($columnDto->getSelectionOptions() !== null) { - $item->setSelectionOptions($columnDto->getSelectionOptions()); - } - if ($columnDto->getSelectionDefault() !== null) { - $item->setSelectionDefault($columnDto->getSelectionDefault()); + if ($columnDto->getSelectionOptions() !== null || $columnDto->getSelectionDefault() !== null) { + $item->setSelectionOptionsCollection(SelectionOptions::createFromInputJsonString( + $columnDto->getSelectionOptions(), $columnDto->getSelectionDefault()) + ); } $item->setDatetimeDefault($columnDto->getDatetimeDefault()); @@ -672,6 +675,15 @@ private function enhanceColumns(?array $columns, ?View $view = null): array { */ public function importColumn(Table $table, array $column): int { $item = new Column(); + if (isset($column['uuid'])) { + $uuid = (string)$column['uuid']; + if ($uuid === '') { + $uuid = null; + } elseif (!Uuid::isValid($uuid)) { + throw new \InvalidArgumentException('Invalid UUID provided'); + } + } + $item->setUuid($uuid ?? null); $item->setTableId($table->getId()); $item->setTitle($column['title']); $item->setCreatedBy($table->getOwnership()); @@ -692,8 +704,7 @@ public function importColumn(Table $table, array $column): int { $item->setTextAllowedPattern($column['textAllowedPattern']); $item->setTextMaxLength($column['textMaxLength']); $item->setTextUnique($column['textUnique']); - $item->setSelectionOptions(json_encode($column['selectionOptions'])); - $item->setSelectionDefault($column['selectionDefault']); + $item->setSelectionOptionsCollection(SelectionOptions::createFromInputArray($column['selectionOptions'], $column['selectionDefault'])); $item->setDatetimeDefault($column['datetimeDefault']); $item->setUsergroupDefault(json_encode($column['usergroupDefault'])); $item->setUsergroupMultipleItems($column['usergroupMultipleItems']); diff --git a/lib/Service/ColumnTypes/SelectionBusiness.php b/lib/Service/ColumnTypes/SelectionBusiness.php index 3fd6f19c76..423478d6ec 100644 --- a/lib/Service/ColumnTypes/SelectionBusiness.php +++ b/lib/Service/ColumnTypes/SelectionBusiness.php @@ -22,9 +22,9 @@ public function parseValue($value, Column $column): string { return ''; } - foreach ($column->getSelectionOptionsArray() as $option) { - if ($option['id'] === $intValue) { - return json_encode((string)$option['id']); + foreach ($column->getSelectionOptionsCollection() as $option) { + if ($option->key() === $intValue) { + return json_encode((string)$option->key()); } } @@ -32,9 +32,9 @@ public function parseValue($value, Column $column): string { } public function parseDisplayValue($value, Column $column): string { - foreach ($column->getSelectionOptionsArray() as $option) { - if ($option['label'] === $value) { - return json_encode($option['id']); + foreach ($column->getSelectionOptionsCollection() as $option) { + if ($option->label() === $value) { + return json_encode($option->key()); } } @@ -46,7 +46,7 @@ public function parseDisplayValue($value, Column $column): string { * @param Column $column * @return bool */ - public function canBeParsed($value, Column $column): bool { + public function canBeParsed(mixed $value, Column $column): bool { if ($value === null) { return true; } @@ -56,8 +56,8 @@ public function canBeParsed($value, Column $column): bool { return false; } - foreach ($column->getSelectionOptionsArray() as $option) { - if ($option['id'] === $intValue) { + foreach ($column->getSelectionOptionsCollection() as $option) { + if ($option->key() === $intValue) { return true; } } @@ -70,8 +70,8 @@ public function canBeParsedDisplayValue($value, Column $column): bool { return true; } - foreach ($column->getSelectionOptionsArray() as $option) { - if ($option['label'] === $value) { + foreach ($column->getSelectionOptionsCollection() as $option) { + if ($option->label() === $value) { return true; } } diff --git a/lib/Service/ColumnTypes/SelectionMultiBusiness.php b/lib/Service/ColumnTypes/SelectionMultiBusiness.php index b8799a6613..629a9808f4 100644 --- a/lib/Service/ColumnTypes/SelectionMultiBusiness.php +++ b/lib/Service/ColumnTypes/SelectionMultiBusiness.php @@ -8,10 +8,11 @@ namespace OCA\Tables\Service\ColumnTypes; use OCA\Tables\Db\Column; +use OCA\Tables\Model\SelectionOptions; class SelectionMultiBusiness extends SuperBusiness { - private array $options = []; + private SelectionOptions $options; /** * @param mixed $value (array|string|null) @@ -23,7 +24,7 @@ public function parseValue($value, Column $column): string { return json_encode([]); } - $this->options = $column->getSelectionOptionsArray(); + $this->options = $column->getSelectionOptionsCollection(); $wasString = false; if (is_string($value)) { @@ -48,20 +49,18 @@ public function parseValue($value, Column $column): string { * @param int|string|null $value int assume as option ID, string assumes a label * @return int|null return always the option ID or null */ - private function getOptionIdForValue($value): ?int { - if ($value === null) { + private function getOptionIdForValue(mixed $value): ?int { + if ($value === null || !isset($this->options)) { return null; } foreach ($this->options as $option) { if (is_int($value)) { - if ($option['id'] === $value) { - return $option['id']; - } - } else { - if ($option['label'] === $value) { - return $option['id']; + if ($option->key() === $value) { + return $option->key(); } + } else if ($option->label() === $value) { + return $option->key(); } } return null; @@ -77,7 +76,7 @@ public function canBeParsed($value, Column $column): bool { return true; } - $this->options = $column->getSelectionOptionsArray(); + $this->options = $column->getSelectionOptionsCollection(); $wasString = false; if (is_string($value)) {