diff --git a/application/src/Api/Adapter/AbstractResourceEntityAdapter.php b/application/src/Api/Adapter/AbstractResourceEntityAdapter.php index 12d3c2661..3f8e3e4b2 100644 --- a/application/src/Api/Adapter/AbstractResourceEntityAdapter.php +++ b/application/src/Api/Adapter/AbstractResourceEntityAdapter.php @@ -472,9 +472,11 @@ public function getPropertyByTerm($term) * 123 * - -: Query subject values of the specified property where * there is no corresponding resource template property, e.g. 123- - * - -: Query subject values of - * the specified property where there is a corresponding resource - * template property, e.g. 123-234 + * - -: Query subject values of + * the specified property where there are corresponding resource + * template properties, e.g. 123-234,345. Note that you can add subject + * values of the specified property where there is no corresponding + * resource template property by adding a zero ID, e.g. 123-0,234,345 * * @param Resource $resource * @param int|string|null $propertyId @@ -530,11 +532,17 @@ public function getSubjectValuesQueryBuilder(Resource $resource, $propertyId = n if (false !== strpos($propertyId, '-')) { $propertyIds = explode('-', $propertyId); $propertyId = $propertyIds[0]; - $resourceTemplatePropertyId = $propertyIds[1]; - $qb->andWhere($resourceTemplatePropertyId - ? $qb->expr()->eq('resource_template_property', $this->createNamedParameter($qb, $resourceTemplatePropertyId)) - : $qb->expr()->isNull('resource_template_property') - ); + $resourceTemplatePropertyIds = array_map('intval', explode(',', $propertyIds[1])); + if (in_array(0, $resourceTemplatePropertyIds)) { + // A zero ID means subject values of the specified property + // where there is no corresponding resource template property. + $qb->andWhere($qb->expr()->orX( + $qb->expr()->isNull('resource_template_property'), + $qb->expr()->in('resource_template_property', $this->createNamedParameter($qb, $resourceTemplatePropertyIds)) + )); + } else { + $qb->andWhere($qb->expr()->in('resource_template_property', $this->createNamedParameter($qb, $resourceTemplatePropertyIds))); + } } $qb->andWhere($qb->expr()->eq('value.property', $this->createNamedParameter($qb, $propertyId))); } @@ -570,11 +578,12 @@ public function getSubjectValues(Resource $resource, $page = null, $perPage = nu ->select([ 'value val', 'property.id property_id', - 'resource_template_property.id resource_template_property_id', 'property.label property_label', + 'resource_template_property.id resource_template_property_id', 'resource_template_property.alternateLabel property_alternate_label', + "CASE WHEN resource_template_property.alternateLabel IS NOT NULL AND resource_template_property.alternateLabel NOT LIKE '' THEN resource_template_property.alternateLabel ELSE property.label END order_by_label", ]) - ->orderBy('property.id, resource_template_property.alternateLabel, resource.title') + ->orderBy('property.id, order_by_label, resource.title') ->setMaxResults($perPage) ->setFirstResult($offset); $event = new Event('api.subject_values.query', $this, [ @@ -607,7 +616,11 @@ public function getSubjectValuesSimple(Resource $resource, $propertyId = null, $ $qb = $this->getSubjectValuesQueryBuilder($resource, $propertyId, $resourceType, $siteId) ->join('value.property', 'property') ->join('property.vocabulary', 'vocabulary') - ->select("CONCAT(vocabulary.prefix, ':', property.localName) term, IDENTITY(value.resource) id, resource.title title"); + ->select([ + "CONCAT(vocabulary.prefix, ':', property.localName) term", + 'IDENTITY(value.resource) id', + 'resource.title title', + ]); $event = new Event('api.subject_values_simple.query', $this, [ 'queryBuilder' => $qb, 'resource' => $resource, @@ -649,16 +662,56 @@ public function getSubjectValueProperties(Resource $resource, $resourceType = nu ->join('value.property', 'property') ->join('property.vocabulary', 'vocabulary') ->select([ - "DISTINCT CONCAT(property.id, '-', COALESCE(resource_template_property.id, '')) id_concat", - "CONCAT(vocabulary.prefix, ':', property.localName) term", 'property.id property_id', 'resource_template_property.id resource_template_property_id', 'property.label property_label', 'resource_template_property.alternateLabel property_alternate_label', + "CONCAT(vocabulary.prefix, ':', property.localName) term", ]) ->orderBy('property.id, resource_template_property.id'); - $results = $qb->getQuery()->getResult(); - return $results; + // Group the properties by property ID then label. We must use code to + // group instead of a SQL "GROUP BY" because of the special case where + // there is no resource template property. + $results = []; + foreach ($qb->getQuery()->getResult() as $result) { + if ($result['property_alternate_label']) { + $label = $result['property_alternate_label']; + $labelIsTranslatable = false; + $resourceTemplatePropertyId = $result['resource_template_property_id']; + } elseif ($result['resource_template_property_id']) { + $label = $result['property_label']; + $labelIsTranslatable = true; + $resourceTemplatePropertyId = $result['resource_template_property_id']; + } else { + $label = $result['property_label']; + $labelIsTranslatable = true; + $resourceTemplatePropertyId = 0; + } + $results[$result['property_id']][$label]['resource_template_property_ids'][] = $resourceTemplatePropertyId; + $results[$result['property_id']][$label]['term'] = $result['term']; + // The shared label is translatable if at least one of the individual + // labels is a property label. A shared label is not translatable if + // all the individual labels are alternate labels. + if ($labelIsTranslatable) { + $results[$result['property_id']][$label]['label_is_translatable'] = true; + } + } + // Build the properties array from grouped array. + $subjectValueProperties = []; + foreach ($results as $propertyId => $properties) { + foreach ($properties as $label => $data) { + $subjectValueProperties[] = [ + 'label' => $label, + 'property_id' => $propertyId, + 'term' => $data['term'], + 'label_is_translatable' => $data['label_is_translatable'] ?? false, + 'compound_id' => sprintf('%s:%s-%s', $resourceType, $propertyId, implode(',', array_unique($data['resource_template_property_ids']))), + ]; + } + } + // Sort the properties by property ID then label. + usort($subjectValueProperties, fn ($a, $b) => strcmp($a['property_id'] . $a['label'], $b['property_id'] . $b['label'])); + return $subjectValueProperties; } public function preprocessBatchUpdate(array $data, Request $request) diff --git a/application/src/Api/Representation/AbstractResourceEntityRepresentation.php b/application/src/Api/Representation/AbstractResourceEntityRepresentation.php index ec2bc60d3..ed6367c29 100644 --- a/application/src/Api/Representation/AbstractResourceEntityRepresentation.php +++ b/application/src/Api/Representation/AbstractResourceEntityRepresentation.php @@ -402,7 +402,7 @@ public function subjectValues($page = null, $perPage = null, $propertyId = null, $results = $this->getAdapter()->getSubjectValues($this->resource, $page, $perPage, $propertyId, $resourceType, $siteId); $subjectValues = []; foreach ($results as $result) { - $index = sprintf('%s-%s', $result['property_id'], $result['resource_template_property_id']); + $index = $result['property_alternate_label'] ?: $result['property_label']; $result['val'] = new ValueRepresentation($result['val'], $this->getServiceLocator()); $subjectValues[$index][] = $result; } diff --git a/application/view/common/linked-resources.phtml b/application/view/common/linked-resources.phtml index 72cf01cc2..2ae8b38dd 100644 --- a/application/view/common/linked-resources.phtml +++ b/application/view/common/linked-resources.phtml @@ -37,11 +37,16 @@ foreach ($resourcePropertiesAll as $type => $resourceProperties) { 'options' => [sprintf('%s:', $type) => sprintf('%s: All', $labelMap[$type])], ]; foreach ($resourceProperties as $resourceProperty) { - $key = sprintf('%s:%s', $type, $resourceProperty['id_concat']); - $value = $resourceProperty['property_alternate_label'] - ? $this->translate($resourceProperty['property_alternate_label']) - : $this->translate($resourceProperty['property_label']); - $valueOptions[$type]['options'][$key] = sprintf('%s: %s', $labelMap[$type], $value); + $label = $resourceProperty['label_is_translatable'] + ? $this->translate($resourceProperty['label']) + : $resourceProperty['label']; + $valueOptions[$type]['options'][] = [ + 'value' => $resourceProperty['compound_id'], + 'label' => sprintf('%s: %s', $labelMap[$type], $label), + 'attributes' => [ + 'title' => $resourceProperty['term'], + ], + ]; } } $resourcePropertiesSelect->setValueOptions($valueOptions);