forked from fa/breadcrumb-the-shire
605 lines
24 KiB
PHP
605 lines
24 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Service\CustomField;
|
|
|
|
use MintyPHP\Repository\CustomField\TenantCustomFieldDefinitionRepository;
|
|
use MintyPHP\Repository\CustomField\TenantCustomFieldOptionRepository;
|
|
use MintyPHP\Repository\CustomField\UserCustomFieldValueOptionRepository;
|
|
use MintyPHP\Repository\CustomField\UserCustomFieldValueRepository;
|
|
use MintyPHP\Repository\Support\RepoQuery;
|
|
use MintyPHP\Service\User\UserScopeGateway;
|
|
|
|
class UserCustomFieldValueService
|
|
{
|
|
public function __construct(
|
|
private readonly UserScopeGateway $userScopeGateway
|
|
) {
|
|
}
|
|
|
|
public function validateForTenants(array $tenantIds, array $post, bool $canEdit): array
|
|
{
|
|
if (!$canEdit) {
|
|
return ['ok' => true, 'errors' => []];
|
|
}
|
|
$tenantIds = self::normalizeTenantIds($tenantIds);
|
|
if (!$tenantIds) {
|
|
return ['ok' => true, 'errors' => []];
|
|
}
|
|
$prepared = $this->prepareForSync($tenantIds, $post);
|
|
if ($prepared['errors']) {
|
|
return ['ok' => false, 'errors' => $prepared['errors']];
|
|
}
|
|
return ['ok' => true, 'errors' => []];
|
|
}
|
|
|
|
public function buildDefinitionsByTenant(array $tenantIds, bool $includeInactive = false): array
|
|
{
|
|
$tenantIds = self::normalizeTenantIds($tenantIds);
|
|
if (!$tenantIds) {
|
|
return [];
|
|
}
|
|
|
|
$definitions = TenantCustomFieldDefinitionRepository::listByTenantIds($tenantIds, !$includeInactive);
|
|
if (!$definitions) {
|
|
return [];
|
|
}
|
|
|
|
$definitionIds = array_values(array_filter(array_map(
|
|
static fn (array $definition): int => (int) ($definition['id'] ?? 0),
|
|
$definitions
|
|
), static fn (int $id): bool => $id > 0));
|
|
$options = TenantCustomFieldOptionRepository::listByDefinitionIds($definitionIds, !$includeInactive);
|
|
$optionsByDefinition = [];
|
|
foreach ($options as $option) {
|
|
$definitionId = (int) ($option['definition_id'] ?? 0);
|
|
if ($definitionId <= 0) {
|
|
continue;
|
|
}
|
|
$optionsByDefinition[$definitionId] ??= [];
|
|
$optionsByDefinition[$definitionId][] = $option;
|
|
}
|
|
|
|
$grouped = [];
|
|
foreach ($definitions as $definition) {
|
|
$tenantId = (int) ($definition['tenant_id'] ?? 0);
|
|
$definitionId = (int) ($definition['id'] ?? 0);
|
|
if ($tenantId <= 0 || $definitionId <= 0) {
|
|
continue;
|
|
}
|
|
$definition['options'] = $optionsByDefinition[$definitionId] ?? [];
|
|
$grouped[$tenantId] ??= [];
|
|
$grouped[$tenantId][] = $definition;
|
|
}
|
|
|
|
return $grouped;
|
|
}
|
|
|
|
public function buildUserValueMap(int $userId, array $definitionIds): array
|
|
{
|
|
if ($userId <= 0) {
|
|
return [];
|
|
}
|
|
$definitionIds = array_values(array_unique(array_map('intval', $definitionIds)));
|
|
$definitionIds = array_values(array_filter($definitionIds, static fn ($id) => $id > 0));
|
|
if (!$definitionIds) {
|
|
return [];
|
|
}
|
|
|
|
$rows = UserCustomFieldValueRepository::listByUserAndDefinitionIds($userId, $definitionIds);
|
|
if (!$rows) {
|
|
return [];
|
|
}
|
|
|
|
$valueIds = array_values(array_filter(array_map(
|
|
static fn (array $row): int => (int) ($row['id'] ?? 0),
|
|
$rows
|
|
), static fn (int $id): bool => $id > 0));
|
|
$optionIdsByValue = UserCustomFieldValueOptionRepository::listOptionIdsByValueIds($valueIds);
|
|
|
|
$map = [];
|
|
foreach ($rows as $row) {
|
|
$definitionId = (int) ($row['definition_id'] ?? 0);
|
|
$valueId = (int) ($row['id'] ?? 0);
|
|
if ($definitionId <= 0 || $valueId <= 0) {
|
|
continue;
|
|
}
|
|
$map[$definitionId] = [
|
|
'id' => $valueId,
|
|
'value_text' => array_key_exists('value_text', $row) ? (string) ($row['value_text'] ?? '') : '',
|
|
'value_bool' => array_key_exists('value_bool', $row) && $row['value_bool'] !== null
|
|
? (int) $row['value_bool']
|
|
: null,
|
|
'value_date' => array_key_exists('value_date', $row) ? (string) ($row['value_date'] ?? '') : '',
|
|
'option_id' => array_key_exists('option_id', $row) && $row['option_id'] !== null
|
|
? (int) $row['option_id']
|
|
: null,
|
|
'option_ids' => $optionIdsByValue[$valueId] ?? [],
|
|
];
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
/**
|
|
* Build API-safe custom field payload grouped by tenant.
|
|
*
|
|
* @param array<int, array<string, mixed>> $tenantSummaries Assignment tenant rows
|
|
* from user assignment context, each with at least id/uuid/description/status.
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function buildPublicValuesByTenant(int $userId, array $tenantSummaries, ?int $tenantScopeId = null): array
|
|
{
|
|
if ($userId <= 0) {
|
|
return [];
|
|
}
|
|
|
|
$tenantScopeId = (int) ($tenantScopeId ?? 0);
|
|
$orderedTenants = [];
|
|
foreach ($tenantSummaries as $tenantSummary) {
|
|
if (!is_array($tenantSummary)) {
|
|
continue;
|
|
}
|
|
$tenantId = (int) ($tenantSummary['id'] ?? 0);
|
|
if ($tenantId <= 0) {
|
|
continue;
|
|
}
|
|
if ($tenantScopeId > 0 && $tenantId !== $tenantScopeId) {
|
|
continue;
|
|
}
|
|
$orderedTenants[$tenantId] = [
|
|
'id' => $tenantId,
|
|
'uuid' => (string) ($tenantSummary['uuid'] ?? ''),
|
|
'description' => (string) ($tenantSummary['description'] ?? ''),
|
|
'status' => (string) ($tenantSummary['status'] ?? ''),
|
|
];
|
|
}
|
|
|
|
if (!$orderedTenants) {
|
|
return [];
|
|
}
|
|
|
|
$tenantIds = array_keys($orderedTenants);
|
|
$definitionsByTenant = $this->buildDefinitionsByTenant($tenantIds, false);
|
|
if (!$definitionsByTenant) {
|
|
return [];
|
|
}
|
|
|
|
$definitionIds = [];
|
|
foreach ($tenantIds as $tenantId) {
|
|
foreach (($definitionsByTenant[$tenantId] ?? []) as $definition) {
|
|
$definitionId = (int) ($definition['id'] ?? 0);
|
|
if ($definitionId > 0) {
|
|
$definitionIds[] = $definitionId;
|
|
}
|
|
}
|
|
}
|
|
$definitionIds = array_values(array_unique($definitionIds));
|
|
$valueMap = $this->buildUserValueMap($userId, $definitionIds);
|
|
|
|
$result = [];
|
|
foreach ($tenantIds as $tenantId) {
|
|
$definitions = $definitionsByTenant[$tenantId] ?? [];
|
|
if (!$definitions) {
|
|
continue;
|
|
}
|
|
|
|
$fields = [];
|
|
foreach ($definitions as $definition) {
|
|
$definitionId = (int) ($definition['id'] ?? 0);
|
|
if ($definitionId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$type = strtolower(trim((string) ($definition['type'] ?? 'text')));
|
|
$optionsRaw = is_array($definition['options'] ?? null) ? $definition['options'] : [];
|
|
$optionsById = [];
|
|
$optionsPublic = [];
|
|
foreach ($optionsRaw as $option) {
|
|
if (!is_array($option)) {
|
|
continue;
|
|
}
|
|
$optionId = (int) ($option['id'] ?? 0);
|
|
$optionKey = trim((string) ($option['option_key'] ?? ''));
|
|
if ($optionId <= 0 || $optionKey === '') {
|
|
continue;
|
|
}
|
|
$optionsById[$optionId] = $optionKey;
|
|
$optionsPublic[] = [
|
|
'option_key' => $optionKey,
|
|
'label' => (string) ($option['label'] ?? ''),
|
|
];
|
|
}
|
|
|
|
$field = [
|
|
'uuid' => (string) ($definition['uuid'] ?? ''),
|
|
'field_key' => (string) ($definition['field_key'] ?? ''),
|
|
'label' => (string) ($definition['label'] ?? ''),
|
|
'type' => $type,
|
|
'is_filterable' => !empty($definition['is_filterable']),
|
|
'value' => self::mapPublicValueByType($type, $valueMap[$definitionId] ?? null, $optionsById),
|
|
];
|
|
|
|
if (in_array($type, ['select', 'multiselect'], true)) {
|
|
$field['options'] = $optionsPublic;
|
|
}
|
|
|
|
$fields[] = $field;
|
|
}
|
|
|
|
if (!$fields) {
|
|
continue;
|
|
}
|
|
|
|
$tenant = $orderedTenants[$tenantId];
|
|
$result[] = [
|
|
'tenant' => [
|
|
'uuid' => (string) $tenant['uuid'],
|
|
'description' => (string) $tenant['description'],
|
|
'status' => (string) $tenant['status'],
|
|
],
|
|
'fields' => $fields,
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function syncForUser(int $userId, array $tenantIds, array $post, bool $canEdit): array
|
|
{
|
|
if ($userId <= 0) {
|
|
return ['ok' => false, 'errors' => [t('User not found')]];
|
|
}
|
|
|
|
$tenantIds = self::normalizeTenantIds($tenantIds);
|
|
// Always clean values for tenants no longer assigned — happens even when !$canEdit,
|
|
// because orphaned custom field data should not persist after tenant reassignment.
|
|
if (!UserCustomFieldValueRepository::deleteByUserOutsideTenantIds($userId, $tenantIds)) {
|
|
return ['ok' => false, 'errors' => [t('Custom field values can not be saved')]];
|
|
}
|
|
if (!$canEdit) {
|
|
return ['ok' => true, 'errors' => []];
|
|
}
|
|
|
|
if (!$tenantIds) {
|
|
return ['ok' => true, 'errors' => []];
|
|
}
|
|
|
|
$prepared = $this->prepareForSync($tenantIds, $post);
|
|
if ($prepared['errors']) {
|
|
return ['ok' => false, 'errors' => $prepared['errors']];
|
|
}
|
|
if (!$prepared['prepared']) {
|
|
return ['ok' => true, 'errors' => []];
|
|
}
|
|
foreach ($prepared['prepared'] as $definitionId => $valueData) {
|
|
if (!empty($valueData['empty'])) {
|
|
if (!UserCustomFieldValueRepository::deleteByUserAndDefinitionIds($userId, [(int) $definitionId])) {
|
|
return ['ok' => false, 'errors' => [t('Custom field values can not be saved')]];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!empty($valueData['option_ids'])) {
|
|
$valueId = UserCustomFieldValueRepository::upsertScalarValue($userId, (int) $definitionId, [
|
|
'value_text' => null,
|
|
'value_bool' => null,
|
|
'value_date' => null,
|
|
'option_id' => null,
|
|
]);
|
|
if (!$valueId) {
|
|
return ['ok' => false, 'errors' => [t('Custom field values can not be saved')]];
|
|
}
|
|
if (!UserCustomFieldValueOptionRepository::replaceForValueId((int) $valueId, $valueData['option_ids'])) {
|
|
return ['ok' => false, 'errors' => [t('Custom field values can not be saved')]];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$valueId = UserCustomFieldValueRepository::upsertScalarValue($userId, (int) $definitionId, [
|
|
'value_text' => $valueData['value_text'],
|
|
'value_bool' => $valueData['value_bool'],
|
|
'value_date' => $valueData['value_date'],
|
|
'option_id' => $valueData['option_id'],
|
|
]);
|
|
if (!$valueId) {
|
|
return ['ok' => false, 'errors' => [t('Custom field values can not be saved')]];
|
|
}
|
|
if (!UserCustomFieldValueOptionRepository::replaceForValueId((int) $valueId, [])) {
|
|
return ['ok' => false, 'errors' => [t('Custom field values can not be saved')]];
|
|
}
|
|
}
|
|
|
|
return ['ok' => true, 'errors' => []];
|
|
}
|
|
|
|
private function prepareForSync(array $tenantIds, array $post): array
|
|
{
|
|
$definitionsByTenant = $this->buildDefinitionsByTenant($tenantIds, false);
|
|
$definitions = [];
|
|
foreach ($definitionsByTenant as $tenantDefinitionList) {
|
|
foreach ($tenantDefinitionList as $definition) {
|
|
$definitionId = (int) ($definition['id'] ?? 0);
|
|
if ($definitionId > 0) {
|
|
$definitions[$definitionId] = $definition;
|
|
}
|
|
}
|
|
}
|
|
if (!$definitions) {
|
|
return ['prepared' => [], 'errors' => []];
|
|
}
|
|
|
|
$rawValues = is_array($post['custom_field_values'] ?? null) ? $post['custom_field_values'] : [];
|
|
$rawMultiValues = is_array($post['custom_field_values_multi'] ?? null) ? $post['custom_field_values_multi'] : [];
|
|
|
|
$errors = [];
|
|
$prepared = [];
|
|
foreach ($definitions as $definitionId => $definition) {
|
|
$type = strtolower((string) ($definition['type'] ?? ''));
|
|
$label = (string) ($definition['label'] ?? ('#' . $definitionId));
|
|
$options = is_array($definition['options'] ?? null) ? $definition['options'] : [];
|
|
$activeOptionIds = array_values(array_filter(array_map(
|
|
static fn (array $option): int => !empty($option['active']) ? (int) ($option['id'] ?? 0) : 0,
|
|
$options
|
|
), static fn (int $id): bool => $id > 0));
|
|
$activeOptionMap = $activeOptionIds ? array_fill_keys($activeOptionIds, true) : [];
|
|
|
|
$rawScalar = $rawValues[(string) $definitionId] ?? null;
|
|
$rawMulti = $rawMultiValues[(string) $definitionId] ?? [];
|
|
|
|
$valueData = [
|
|
'value_text' => null,
|
|
'value_bool' => null,
|
|
'value_date' => null,
|
|
'option_id' => null,
|
|
'option_ids' => [],
|
|
'empty' => true,
|
|
];
|
|
|
|
if (in_array($type, ['text', 'textarea'], true)) {
|
|
$text = trim((string) $rawScalar);
|
|
if ($text !== '') {
|
|
$valueData['value_text'] = $text;
|
|
$valueData['empty'] = false;
|
|
}
|
|
} elseif ($type === 'boolean') {
|
|
$bool = self::normalizeBool($rawScalar);
|
|
if ($bool === null && trim((string) $rawScalar) !== '') {
|
|
$errors[] = sprintf(t('Invalid value for %s'), $label);
|
|
continue;
|
|
}
|
|
if ($bool !== null) {
|
|
$valueData['value_bool'] = $bool;
|
|
$valueData['empty'] = false;
|
|
}
|
|
} elseif ($type === 'date') {
|
|
$date = self::normalizeDate($rawScalar);
|
|
if ($date === null && trim((string) $rawScalar) !== '') {
|
|
$errors[] = sprintf(t('Invalid value for %s'), $label);
|
|
continue;
|
|
}
|
|
if ($date !== null) {
|
|
$valueData['value_date'] = $date;
|
|
$valueData['empty'] = false;
|
|
}
|
|
} elseif ($type === 'select') {
|
|
$optionId = (int) $rawScalar;
|
|
if ($optionId > 0 && !isset($activeOptionMap[$optionId])) {
|
|
$errors[] = sprintf(t('Invalid value for %s'), $label);
|
|
continue;
|
|
}
|
|
if ($optionId > 0) {
|
|
$valueData['option_id'] = $optionId;
|
|
$valueData['empty'] = false;
|
|
}
|
|
} elseif ($type === 'multiselect') {
|
|
$optionIds = RepoQuery::normalizeIdList($rawMulti);
|
|
$invalid = array_values(array_filter($optionIds, static fn (int $id): bool => !isset($activeOptionMap[$id])));
|
|
if ($invalid) {
|
|
$errors[] = sprintf(t('Invalid value for %s'), $label);
|
|
continue;
|
|
}
|
|
if ($optionIds) {
|
|
$valueData['option_ids'] = $optionIds;
|
|
$valueData['empty'] = false;
|
|
}
|
|
}
|
|
|
|
$prepared[$definitionId] = $valueData;
|
|
}
|
|
|
|
return ['prepared' => $prepared, 'errors' => $errors];
|
|
}
|
|
|
|
public function extractAddressBookFilterSpec(array $query, int $currentUserId): array
|
|
{
|
|
$tenantIds = $this->userScopeGateway()->getUserTenantIds($currentUserId);
|
|
$definitions = TenantCustomFieldDefinitionRepository::listFilterableByTenantIds($tenantIds);
|
|
if (!$definitions) {
|
|
return ['definitions' => [], 'filters' => [], 'query' => []];
|
|
}
|
|
|
|
$definitionMapByUuid = [];
|
|
$definitionIds = [];
|
|
foreach ($definitions as $definition) {
|
|
$uuid = strtolower(trim((string) ($definition['uuid'] ?? '')));
|
|
$definitionId = (int) ($definition['id'] ?? 0);
|
|
if ($uuid === '' || $definitionId <= 0) {
|
|
continue;
|
|
}
|
|
$definitionMapByUuid[$uuid] = $definition;
|
|
$definitionIds[] = $definitionId;
|
|
}
|
|
$definitionIds = array_values(array_unique($definitionIds));
|
|
$options = TenantCustomFieldOptionRepository::listByDefinitionIds($definitionIds, true);
|
|
$optionMapByDefinition = [];
|
|
foreach ($options as $option) {
|
|
$definitionId = (int) ($option['definition_id'] ?? 0);
|
|
$optionId = (int) ($option['id'] ?? 0);
|
|
if ($definitionId <= 0 || $optionId <= 0) {
|
|
continue;
|
|
}
|
|
$optionMapByDefinition[$definitionId] ??= [];
|
|
$optionMapByDefinition[$definitionId][$optionId] = true;
|
|
}
|
|
|
|
$filters = [
|
|
'select' => [],
|
|
'boolean' => [],
|
|
'multiselect' => [],
|
|
'date' => [],
|
|
];
|
|
$normalizedQuery = [];
|
|
foreach ($query as $rawKey => $rawValue) {
|
|
$key = strtolower(trim((string) $rawKey));
|
|
if ($key === '') {
|
|
continue;
|
|
}
|
|
|
|
// URL key prefixes: cf_ = single-value (select/boolean), cfm_ = multiselect, cfd_ = date range.
|
|
if (preg_match('/^cf_([a-f0-9-]{36})$/', $key, $match)) {
|
|
$uuid = $match[1];
|
|
$definition = $definitionMapByUuid[$uuid] ?? null;
|
|
if (!$definition) {
|
|
continue;
|
|
}
|
|
$definitionId = (int) ($definition['id'] ?? 0);
|
|
$type = strtolower((string) ($definition['type'] ?? ''));
|
|
if ($type === 'select') {
|
|
$optionId = (int) $rawValue;
|
|
if ($optionId > 0 && !empty($optionMapByDefinition[$definitionId][$optionId])) {
|
|
$filters['select'][$definitionId] = $optionId;
|
|
$normalizedQuery[$key] = (string) $optionId;
|
|
}
|
|
} elseif ($type === 'boolean') {
|
|
$bool = self::normalizeBool($rawValue);
|
|
if ($bool !== null) {
|
|
$filters['boolean'][$definitionId] = $bool;
|
|
$normalizedQuery[$key] = (string) $bool;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (preg_match('/^cfm_([a-f0-9-]{36})$/', $key, $match)) {
|
|
$uuid = $match[1];
|
|
$definition = $definitionMapByUuid[$uuid] ?? null;
|
|
if (!$definition || strtolower((string) ($definition['type'] ?? '')) !== 'multiselect') {
|
|
continue;
|
|
}
|
|
$definitionId = (int) ($definition['id'] ?? 0);
|
|
$optionIds = RepoQuery::normalizeIdList($rawValue);
|
|
if (!$optionIds) {
|
|
continue;
|
|
}
|
|
$allowedMap = $optionMapByDefinition[$definitionId] ?? [];
|
|
$optionIds = array_values(array_filter($optionIds, static fn (int $id): bool => isset($allowedMap[$id])));
|
|
if (!$optionIds) {
|
|
continue;
|
|
}
|
|
$filters['multiselect'][$definitionId] = $optionIds;
|
|
$normalizedQuery[$key] = implode(',', $optionIds);
|
|
continue;
|
|
}
|
|
|
|
if (preg_match('/^cfd_([a-f0-9-]{36})_(from|to)$/', $key, $match)) {
|
|
$uuid = $match[1];
|
|
$bound = $match[2];
|
|
$definition = $definitionMapByUuid[$uuid] ?? null;
|
|
if (!$definition || strtolower((string) ($definition['type'] ?? '')) !== 'date') {
|
|
continue;
|
|
}
|
|
$date = self::normalizeDate($rawValue);
|
|
if ($date === null) {
|
|
continue;
|
|
}
|
|
$definitionId = (int) ($definition['id'] ?? 0);
|
|
$filters['date'][$definitionId] ??= ['from' => null, 'to' => null];
|
|
$filters['date'][$definitionId][$bound] = $date;
|
|
$normalizedQuery[$key] = $date;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'definitions' => array_values($definitionMapByUuid),
|
|
'filters' => $filters,
|
|
'query' => $normalizedQuery,
|
|
];
|
|
}
|
|
|
|
private function userScopeGateway(): UserScopeGateway
|
|
{
|
|
return $this->userScopeGateway;
|
|
}
|
|
|
|
private static function normalizeTenantIds(array $tenantIds): array
|
|
{
|
|
$tenantIds = array_values(array_unique(array_map('intval', $tenantIds)));
|
|
return array_values(array_filter($tenantIds, static fn ($id) => $id > 0));
|
|
}
|
|
|
|
private static function normalizeBool($value): ?int
|
|
{
|
|
if (is_bool($value)) {
|
|
return $value ? 1 : 0;
|
|
}
|
|
$value = strtolower(trim((string) $value));
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
if (in_array($value, ['1', 'true', 'yes', 'on'], true)) {
|
|
return 1;
|
|
}
|
|
if (in_array($value, ['0', 'false', 'no', 'off'], true)) {
|
|
return 0;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static function normalizeDate($value): ?string
|
|
{
|
|
$value = trim((string) $value);
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
$dt = \DateTimeImmutable::createFromFormat('Y-m-d', $value);
|
|
if (!$dt || $dt->format('Y-m-d') !== $value) {
|
|
return null;
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
private static function mapPublicValueByType(string $type, ?array $valueData, array $optionsById)
|
|
{
|
|
$hasValue = is_array($valueData);
|
|
if ($type === 'multiselect') {
|
|
if (!$hasValue) {
|
|
return [];
|
|
}
|
|
$optionIds = is_array($valueData['option_ids'] ?? null) ? $valueData['option_ids'] : [];
|
|
$keys = [];
|
|
foreach ($optionIds as $optionIdRaw) {
|
|
$optionId = (int) $optionIdRaw;
|
|
if ($optionId > 0 && isset($optionsById[$optionId])) {
|
|
$keys[] = (string) $optionsById[$optionId];
|
|
}
|
|
}
|
|
return array_values(array_unique($keys));
|
|
}
|
|
|
|
if (!$hasValue) {
|
|
return null;
|
|
}
|
|
|
|
return match ($type) {
|
|
'boolean' => array_key_exists('value_bool', $valueData) && $valueData['value_bool'] !== null
|
|
? ((int) $valueData['value_bool'] === 1)
|
|
: null,
|
|
'date' => (($date = trim((string) ($valueData['value_date'] ?? ''))) !== '' ? $date : null),
|
|
'select' => (($optionId = (int) ($valueData['option_id'] ?? 0)) > 0 && isset($optionsById[$optionId]))
|
|
? (string) $optionsById[$optionId]
|
|
: null,
|
|
default => (($text = (string) ($valueData['value_text'] ?? '')) !== '' ? $text : null),
|
|
};
|
|
}
|
|
}
|