* array(
* {JSON-LD term} => array(
* 'property' => {property representation},
+ * 'alternate_label' => {label},
+ * 'alternate_comment' => {comment},
* 'values' => {
* {value representation},
* {value representation},
@@ -30,6 +32,7 @@ abstract class AbstractResourceEntityRepresentation extends AbstractEntityRepres
* @var array
*/
protected $values;
+ protected $valuesByTemplateProperty;
/**
* Get the internal members of this resource entity.
@@ -222,7 +225,11 @@ public function modified()
}
/**
- * Get all value representations of this resource.
+ * Get all value representations of this resource, by term or template row.
+ *
+ * The two outputs are the same when there are no duplicated property in the
+ * template. The key for template row are "term" for the first property, then
+ * "term-ResourceTemplateProperty position" when the property is duplicated.
*
*
* array(
@@ -233,40 +240,73 @@ public function modified()
* 'values' => array(
* {ValueRepresentation},
* {ValueRepresentation},
+ * {…},
* ),
* ),
* )
*
*
+ * @param $byTemplateProperty
* @return array
*/
- public function values()
+ public function values($byTemplateProperty = false)
{
if (isset($this->values)) {
- return $this->values;
+ return $byTemplateProperty
+ ? $this->valuesByTemplateProperty
+ : $this->values;
}
- // Set the default template info.
- $templateInfo = [
- 'dcterms:title' => [],
- 'dcterms:description' => [],
- ];
+ $values = [];
+ $valuesByTemplateProperty = [];
+ $dataTypesByProperty = [];
+ $hasDuplicate = false;
+ // Set the default template info one time.
$template = $this->resourceTemplate();
if ($template) {
- // Set the custom template info.
- $templateInfo = [];
foreach ($template->resourceTemplateProperties() as $templateProperty) {
- $term = $templateProperty->property()->term();
- $templateInfo[$term] = [
+ $property = $templateProperty->property();
+ $term = $property->term();
+ $dataTypes = $templateProperty->dataTypes();
+ // Manage an exception.
+ if (in_array('resource', $dataTypes)) {
+ $dataTypes = array_unique(array_merge($dataTypes, ['resource:item', 'resource:itemset', 'resource:media']));
+ }
+ $keyTemplateProperty = $term . '-' . $templateProperty->position();
+ // With duplicate properties, keep only the first label and
+ // comment.
+ if (isset($values[$term])) {
+ $hasDuplicate = true;
+ $valuesByTemplateProperty[$keyTemplateProperty] = [
+ 'property' => $property,
+ 'alternate_label' => $templateProperty->alternateLabel(),
+ 'alternate_comment' => $templateProperty->alternateComment(),
+ ];
+ $dataTypesByProperty[$term] += empty($dataTypes)
+ ? ['default' => $keyTemplateProperty]
+ : array_fill_keys($dataTypes, $keyTemplateProperty);
+ continue;
+ }
+ $values[$term] = [
+ 'property' => $property,
'alternate_label' => $templateProperty->alternateLabel(),
'alternate_comment' => $templateProperty->alternateComment(),
];
+ $valuesByTemplateProperty[$term] = $values[$term];
+ $dataTypesByProperty[$term] = empty($dataTypes)
+ ? ['default' => $term]
+ : array_fill_keys($dataTypes, $term);
}
+ } else {
+ // Force prepend title and description when there is no template.
+ $values = [
+ 'dcterms:title' => [],
+ 'dcterms:description' => [],
+ ];
}
// Get this resource's values.
- $values = [];
foreach ($this->resource->getValues() as $valueEntity) {
$value = new ValueRepresentation($valueEntity, $this->getServiceLocator());
if ($value->isHidden()) {
@@ -283,25 +323,50 @@ public function values()
$values[$term]['values'][] = $value;
}
- // Order this resource's properties according to the template order.
- $sortedValues = [];
- foreach ($values as $term => $valueInfo) {
- foreach ($templateInfo as $templateTerm => $templateAlternates) {
- if (array_key_exists($templateTerm, $values)) {
- $sortedValues[$templateTerm] =
- array_merge($values[$templateTerm], $templateAlternates);
- }
- }
- }
-
- $values = $sortedValues + $values;
+ // Remove terms without values.
+ $removeEmpty = function ($v) {
+ return !empty($v['values']);
+ };
+ $values = array_filter($values, $removeEmpty);
$eventManager = $this->getEventManager();
$args = $eventManager->prepareArgs(['values' => $values]);
$eventManager->trigger('rep.resource.values', $this, $args);
$this->values = $args['values'];
- return $this->values;
+
+ // Prepare the list for template with duplicated properties after the
+ // event above.
+ // Note: duplicated properties don't have duplicated data types, so
+ // values can be remapped directly.
+ if ($template && $hasDuplicate) {
+ foreach ($this->values as $term => $data) {
+ foreach ($data['values'] as $value) {
+ $dataType = $value->type();
+ if (isset($dataTypesByProperty[$term][$dataType])) {
+ $keyTemplateProperty = $dataTypesByProperty[$term][$dataType];
+ } elseif (isset($dataTypesByProperty[$term]['default'])) {
+ $keyTemplateProperty = $dataTypesByProperty[$term]['default'];
+ } else {
+ $keyTemplateProperty = $term;
+ }
+ if (!isset($valuesByTemplateProperty[$keyTemplateProperty]['property'])) {
+ $valuesByTemplateProperty[$keyTemplateProperty]['property'] = $value->property();
+ $valuesByTemplateProperty[$keyTemplateProperty]['alternate_label'] = null;
+ $valuesByTemplateProperty[$keyTemplateProperty]['alternate_comment'] = null;
+ }
+ $valuesByTemplateProperty[$keyTemplateProperty]['values'][] = $value;
+ }
+ }
+ // Remove keys without values.
+ $this->valuesByTemplateProperty = array_filter($valuesByTemplateProperty, $removeEmpty);
+ } else {
+ $this->valuesByTemplateProperty = $this->values;
+ }
+
+ return $byTemplateProperty
+ ? $this->valuesByTemplateProperty
+ : $this->values;
}
/**
@@ -309,7 +374,7 @@ public function values()
*
* @param string $term The prefix:local_part
* @param array $options
- * - type: (null) Get values of this type only. Valid types are "literal",
+ * - type: (null) Get values of this type only. Default types are "literal",
* "uri", and "resource". Returns all types by default.
* - all: (false) If true, returns all values that match criteria. If false,
* returns the first matching value.
@@ -322,18 +387,12 @@ public function values()
public function value($term, array $options = [])
{
// Set defaults.
- if (!isset($options['type'])) {
- $options['type'] = null;
- }
- if (!isset($options['all'])) {
- $options['all'] = false;
- }
- if (!isset($options['default'])) {
- $options['default'] = $options['all'] ? [] : null;
- }
- if (!isset($options['lang'])) {
- $options['lang'] = null;
- }
+ $options += [
+ 'type' => null,
+ 'all' => false,
+ 'default' => isset($options['all']) ? [] : null,
+ 'lang' => null,
+ ];
if (!$this->getAdapter()->isTerm($term)) {
return $options['default'];
@@ -449,14 +508,14 @@ public function displayValues(array $options = [])
$partial = $this->getViewHelper('partial');
$eventManager = $this->getEventManager();
- $args = $eventManager->prepareArgs(['values' => $this->values()]);
+ $args = $eventManager->prepareArgs(['values' => $this->values(true)]);
$eventManager->trigger('rep.resource.display_values', $this, $args);
$options['values'] = $args['values'];
$template = $this->resourceTemplate();
- if ($template) {
- $options['templateProperties'] = $template->resourceTemplateProperties();
- }
+ $options['templateProperties'] = $template
+ ? $template->resourceTemplateProperties()
+ : [];
return $partial($options['viewName'], $options);
}
diff --git a/application/src/Api/Representation/ResourceTemplatePropertyRepresentation.php b/application/src/Api/Representation/ResourceTemplatePropertyRepresentation.php
index 26f2cfdd59..13d9a78be1 100644
--- a/application/src/Api/Representation/ResourceTemplatePropertyRepresentation.php
+++ b/application/src/Api/Representation/ResourceTemplatePropertyRepresentation.php
@@ -2,7 +2,6 @@
namespace Omeka\Api\Representation;
use Omeka\Entity\ResourceTemplateProperty;
-use Laminas\ServiceManager\Exception\ServiceNotFoundException;
use Laminas\ServiceManager\ServiceLocatorInterface;
class ResourceTemplatePropertyRepresentation extends AbstractRepresentation
@@ -31,7 +30,7 @@ public function jsonSerialize()
'o:property' => $this->property()->getReference(),
'o:alternate_label' => $this->alternateLabel(),
'o:alternate_comment' => $this->alternateComment(),
- 'o:data_type' => $this->dataType(),
+ 'o:data_type' => $this->dataTypes(),
'o:is_required' => $this->isRequired(),
'o:is_private' => $this->isPrivate(),
];
@@ -80,22 +79,46 @@ public function position()
}
/**
+ * @deprecated Since version 3.0.0. Use dataTypes() instead.
* @return string|null
*/
public function dataType()
{
// Check the data type against the list of registered data types.
- $dataType = $this->templateProperty->getDataType();
- try {
- $this->getServiceLocator()->get('Omeka\DataTypeManager')->get($dataType);
- } catch (ServiceNotFoundException $e) {
- // Treat an unknown data type as "Default"
- $dataType = null;
+ $dataTypes = $this->templateProperty->getDataType();
+ if (empty($dataTypes)) {
+ return null;
}
- return $dataType;
+ $dataType = reset($dataTypes);
+ // Treat an unknown data type as "Default".
+ return $this->getServiceLocator()->get('Omeka\DataTypeManager')->has($dataType)
+ ? $dataType
+ : null;
}
/**
+ * @return string[]
+ */
+ public function dataTypes()
+ {
+ // Check the data type against the list of registered data types.
+ $dataTypes = $this->templateProperty->getDataType();
+ if (empty($dataTypes)) {
+ return [];
+ }
+ $dataTypeManager = $this->getServiceLocator()->get('Omeka\DataTypeManager');
+ $result = [];
+ foreach ($dataTypes as $dataType) {
+ // Treat an unknown data type as "Default".
+ if ($dataTypeManager->has($dataType)) {
+ $result[] = $dataType;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * @deprecated Since version 3.0.0. Use dataTypeLabels() instead.
* @return string
*/
public function dataTypeLabel()
@@ -108,6 +131,22 @@ public function dataTypeLabel()
->get($dataType)->getLabel();
}
+ /**
+ * @return array List of data type names and labels.
+ */
+ public function dataTypeLabels()
+ {
+ $result = [];
+ $dataTypeManager = $this->getServiceLocator()->get('Omeka\DataTypeManager');
+ foreach ($this->dataTypes() as $dataType) {
+ $result[] = [
+ 'name' => $dataType,
+ 'label' => $dataTypeManager->get($dataType)->getLabel(),
+ ];
+ }
+ return $result;
+ }
+
/**
* @return bool
*/
diff --git a/application/src/Api/Representation/ResourceTemplateRepresentation.php b/application/src/Api/Representation/ResourceTemplateRepresentation.php
index c46ad91f43..3bf69d5244 100644
--- a/application/src/Api/Representation/ResourceTemplateRepresentation.php
+++ b/application/src/Api/Representation/ResourceTemplateRepresentation.php
@@ -76,7 +76,7 @@ public function resourceClass()
/**
* Return the title property of this resource template.
*
- * @return ResourceClassRepresentation
+ * @return PropertyRepresentation
*/
public function titleProperty()
{
@@ -87,7 +87,7 @@ public function titleProperty()
/**
* Return the description property of this resource template.
*
- * @return ResourceClassRepresentation
+ * @return PropertyRepresentation
*/
public function descriptionProperty()
{
@@ -98,14 +98,15 @@ public function descriptionProperty()
/**
* Return the properties assigned to this resource template.
*
- * @return array
+ * @return ResourceTemplatePropertyRepresentation[]
*/
public function resourceTemplateProperties()
{
$resTemProps = [];
+ $services = $this->getServiceLocator();
foreach ($this->resource->getResourceTemplateProperties() as $resTemProp) {
$resTemProps[] = new ResourceTemplatePropertyRepresentation(
- $resTemProp, $this->getServiceLocator());
+ $resTemProp, $services);
}
return $resTemProps;
}
@@ -114,15 +115,44 @@ public function resourceTemplateProperties()
* Return the specified template property or null if it doesn't exist.
*
* @param int $propertyId
- * @mixed ResourceTemplatePropertyRepresentation
+ * @param string $dataType
+ * @param bool $all
+ * @mixed ResourceTemplatePropertyRepresentation|ResourceTemplatePropertyRepresentation[]|null
*/
- public function resourceTemplateProperty($propertyId)
+ public function resourceTemplateProperty($propertyId, $dataType = null, $all = false)
{
- $resTemProp = $this->resource->getResourceTemplateProperties()->get($propertyId);
- if ($resTemProp) {
- return new ResourceTemplatePropertyRepresentation($resTemProp, $this->getServiceLocator());
+ $propertyId = (int) $propertyId;
+ $resTemProps = $this->resource->getResourceTemplateProperties()
+ ->filter(function (\Omeka\Entity\ResourceTemplateProperty $resTemProp) use ($propertyId, $dataType, $all) {
+ if ($resTemProp->getProperty()->getId() !== $propertyId) {
+ return false;
+ }
+ if (empty($dataType)) {
+ return true;
+ }
+ $dataTypes = $resTemProp->getDataType();
+ return in_array($dataType, $dataTypes);
+ });
+ if (!count($resTemProps)) {
+ return $all ? [] : null;
+ }
+
+ $services = $this->getServiceLocator();
+ if ($all) {
+ return array_map(function ($resTemProp) use ($services) {
+ return new ResourceTemplatePropertyRepresentation($resTemProp, $services);
+ }, $resTemProps);
+ } else {
+ // Return the template property without data type, if any.
+ if (empty($dataType) && count($resTemProps) > 1) {
+ foreach ($resTemProps as $resTemProp) {
+ if (!$resTemProp->getDataType()) {
+ return new ResourceTemplatePropertyRepresentation($resTemProp, $services);
+ }
+ }
+ }
+ return new ResourceTemplatePropertyRepresentation($resTemProps->first(), $services);
}
- return null;
}
/**
diff --git a/application/src/Controller/Admin/ResourceTemplateController.php b/application/src/Controller/Admin/ResourceTemplateController.php
index 318a696562..1be4ebe0e4 100644
--- a/application/src/Controller/Admin/ResourceTemplateController.php
+++ b/application/src/Controller/Admin/ResourceTemplateController.php
@@ -79,12 +79,14 @@ public function importAction()
}
} else {
// Process review import form.
- $import = json_decode($this->params()->fromPost('import'), true);
- $dataTypes = $this->params()->fromPost('data_types', []);
-
+ $import = json_decode($form->getData()['import'], true);
$import['o:label'] = $this->params()->fromPost('label');
- foreach ($dataTypes as $key => $dataType) {
- $import['o:resource_template_property'][$key]['o:data_type'] = $dataType;
+
+ $dataTypes = $this->params()->fromPost('data_types');
+ if ($dataTypes) {
+ foreach ($dataTypes as $key => $dataTypeList) {
+ $import['o:resource_template_property'][$key]['o:data_type'] = $dataTypeList;
+ }
}
$response = $this->api($form)->create('resource_templates', $import);
@@ -121,6 +123,8 @@ public function importAction()
* the property. By design, the API will only hydrate members and data types
* that are flagged as valid.
*
+ * @todo Manage direct import of data types from Value Suggest and other modules.
+ *
* @param array $import
* @return array
*/
@@ -163,6 +167,21 @@ protected function flagValid(array $import)
}
}
+ foreach (['o:title_property', 'o:description_property'] as $property) {
+ if (isset($import[$property])) {
+ if ($vocab = $getVocab($import[$property]['vocabulary_namespace_uri'])) {
+ $import[$property]['vocabulary_prefix'] = $vocab->prefix();
+ $prop = $this->api()->searchOne('properties', [
+ 'vocabulary_namespace_uri' => $import[$property]['vocabulary_namespace_uri'],
+ 'local_name' => $import[$property]['local_name'],
+ ])->getContent();
+ if ($prop) {
+ $import[$property]['o:id'] = $prop->id();
+ }
+ }
+ }
+ }
+
foreach ($import['o:resource_template_property'] as $key => $property) {
if ($vocab = $getVocab($property['vocabulary_namespace_uri'])) {
$import['o:resource_template_property'][$key]['vocabulary_prefix'] = $vocab->prefix();
@@ -172,8 +191,25 @@ protected function flagValid(array $import)
])->getContent();
if ($prop) {
$import['o:resource_template_property'][$key]['o:property'] = ['o:id' => $prop->id()];
- if (in_array($import['o:resource_template_property'][$key]['data_type_name'], $dataTypes)) {
- $import['o:resource_template_property'][$key]['o:data_type'] = $import['o:resource_template_property'][$key]['data_type_name'];
+ // Check the deprecated "data_type_name" if needed and
+ // normalize it.
+ if (!array_key_exists('data_types', $import['o:resource_template_property'][$key])) {
+ $import['o:resource_template_property'][$key]['data_types'] = [[
+ 'name' => $import['o:resource_template_property'][$key]['data_type_name'],
+ 'label' => $import['o:resource_template_property'][$key]['data_type_label'],
+ ]];
+ }
+ $importDataTypes = [];
+ foreach ($import['o:resource_template_property'][$key]['data_types'] as $dataType) {
+ $importDataTypes[$dataType['name']] = $dataType;
+ }
+ $import['o:resource_template_property'][$key]['data_types'] = $importDataTypes;
+ // Prepare the list of standard data types.
+ $import['o:resource_template_property'][$key]['o:data_type'] = [];
+ foreach ($importDataTypes as $name => $importDataType) {
+ if (in_array($name, $dataTypes)) {
+ $import['o:resource_template_property'][$key]['o:data_type'][] = $importDataType['name'];
+ }
}
}
}
@@ -224,6 +260,32 @@ protected function importIsValid($import)
}
}
+ // Validate title and description.
+ foreach (['o:title_property', 'o:description_property'] as $property) {
+ if (isset($import[$property])) {
+ if (!is_array($import[$property])) {
+ // Invalid property.
+ return false;
+ }
+ if (!array_key_exists('vocabulary_namespace_uri', $import[$property])
+ || !array_key_exists('vocabulary_label', $import[$property])
+ || !array_key_exists('local_name', $import[$property])
+ || !array_key_exists('label', $import[$property])
+ ) {
+ // Missing a property info.
+ return false;
+ }
+ if (!is_string($import[$property]['vocabulary_namespace_uri'])
+ || !is_string($import[$property]['vocabulary_label'])
+ || !is_string($import[$property]['local_name'])
+ || !is_string($import[$property]['label'])
+ ) {
+ // Invalid property info.
+ return false;
+ }
+ }
+ }
+
// Validate properties.
if (!isset($import['o:resource_template_property']) || !is_array($import['o:resource_template_property'])) {
// missing or invalid o:resource_template_property
@@ -235,6 +297,11 @@ protected function importIsValid($import)
// invalid o:resource_template_property format
return false;
}
+
+ // Manage import from an export of Omeka < 3.0.
+ $oldExport = !array_key_exists('data_types', $property);
+
+ // Check missing o:resource_template_property info.
if (!array_key_exists('vocabulary_namespace_uri', $property)
|| !array_key_exists('vocabulary_label', $property)
|| !array_key_exists('local_name', $property)
@@ -243,12 +310,17 @@ protected function importIsValid($import)
|| !array_key_exists('o:alternate_comment', $property)
|| !array_key_exists('o:is_required', $property)
|| !array_key_exists('o:is_private', $property)
- || !array_key_exists('data_type_name', $property)
- || !array_key_exists('data_type_label', $property)
) {
- // missing o:resource_template_property info
return false;
}
+ if ($oldExport
+ && (!array_key_exists('data_type_name', $property)
+ || !array_key_exists('data_type_label', $property)
+ )) {
+ return false;
+ }
+
+ // Check invalid o:resource_template_property info.
if (!is_string($property['vocabulary_namespace_uri'])
|| !is_string($property['vocabulary_label'])
|| !is_string($property['local_name'])
@@ -257,10 +329,16 @@ protected function importIsValid($import)
|| (!is_string($property['o:alternate_comment']) && !is_null($property['o:alternate_comment']))
|| !is_bool($property['o:is_required'])
|| !is_bool($property['o:is_private'])
- || (!is_string($property['data_type_name']) && !is_null($property['data_type_name']))
- || (!is_string($property['data_type_label']) && !is_null($property['data_type_label']))
) {
- // invalid o:resource_template_property info
+ return false;
+ }
+ if ($oldExport) {
+ if ((!is_string($property['data_type_name']) && !is_null($property['data_type_name']))
+ || (!is_string($property['data_type_label']) && !is_null($property['data_type_label']))
+ ) {
+ return false;
+ }
+ } elseif (!is_array($property['data_types']) && !is_null($property['data_types'])) {
return false;
}
}
@@ -269,8 +347,11 @@ protected function importIsValid($import)
public function exportAction()
{
+ /** @var \Omeka\Api\Representation\ResourceTemplateRepresentation $template */
$template = $this->api()->read('resource_templates', $this->params('id'))->getContent();
$templateClass = $template->resourceClass();
+ $templateTitle = $template->titleProperty();
+ $templateDescription = $template->descriptionProperty();
$templateProperties = $template->resourceTemplateProperties();
$export = [
@@ -288,25 +369,37 @@ public function exportAction()
];
}
+ if ($templateTitle) {
+ $vocab = $templateTitle->vocabulary();
+ $export['o:title_property'] = [
+ 'vocabulary_namespace_uri' => $vocab->namespaceUri(),
+ 'vocabulary_label' => $vocab->label(),
+ 'local_name' => $templateTitle->localName(),
+ 'label' => $templateTitle->label(),
+ ];
+ }
+
+ if ($templateDescription) {
+ $vocab = $templateDescription->vocabulary();
+ $export['o:description_property'] = [
+ 'vocabulary_namespace_uri' => $vocab->namespaceUri(),
+ 'vocabulary_label' => $vocab->label(),
+ 'local_name' => $templateDescription->localName(),
+ 'label' => $templateDescription->label(),
+ ];
+ }
+
foreach ($templateProperties as $templateProperty) {
$property = $templateProperty->property();
$vocab = $property->vocabulary();
- $dataTypeName = $templateProperty->dataType();
- $dataTypeLabel = null;
- if ($dataTypeName) {
- $dataType = $this->dataTypeManager->get($dataTypeName);
- $dataTypeLabel = $dataType->getLabel();
- }
-
// Note that "position" is implied by array order.
$export['o:resource_template_property'][] = [
'o:alternate_label' => $templateProperty->alternateLabel(),
'o:alternate_comment' => $templateProperty->alternateComment(),
'o:is_required' => $templateProperty->isRequired(),
'o:is_private' => $templateProperty->isPrivate(),
- 'data_type_name' => $dataTypeName,
- 'data_type_label' => $dataTypeLabel,
+ 'data_types' => $templateProperty->dataTypeLabels(),
'vocabulary_namespace_uri' => $vocab->namespaceUri(),
'vocabulary_label' => $vocab->label(),
'local_name' => $property->localName(),
@@ -361,25 +454,25 @@ public function deleteAction()
public function addAction()
{
- return $this->getAddEditView();
+ return $this->getAddEditView(false);
}
public function editAction()
{
- return $this->getAddEditView();
+ return $this->getAddEditView(true);
}
/**
* Get the add/edit view.
*
+ * @param bool $isUpdate
* @return ViewModel
*/
- protected function getAddEditView()
+ protected function getAddEditView($isUpdate = false)
{
- $action = $this->params('action');
$form = $this->getForm(ResourceTemplateForm::class);
- if ('edit' == $action) {
+ if ($isUpdate) {
$resourceTemplate = $this->api()
->read('resource_templates', $this->params('id'))
->getContent();
@@ -394,17 +487,28 @@ protected function getAddEditView()
$data['o:description_property[o:id]'] = $data['o:description_property']->id();
}
$form->setData($data);
+ } else {
+ $resourceTemplate = null;
}
if ($this->getRequest()->isPost()) {
$data = $this->params()->fromPost();
$form->setData($data);
if ($form->isValid()) {
- $response = ('edit' === $action)
+ foreach ($data['o:resource_template_property'] as $key => $dataProperty) {
+ if (empty($dataProperty['o:data_type'])) {
+ $data['o:resource_template_property'][$key]['o:data_type'] = [];
+ } elseif (is_array($dataProperty['o:data_type'])) {
+ $data['o:resource_template_property'][$key]['o:data_type'] = $dataProperty['o:data_type'];
+ } else {
+ $data['o:resource_template_property'][$key]['o:data_type'] = explode(',', $dataProperty['o:data_type']);
+ }
+ }
+ $response = $isUpdate
? $this->api($form)->update('resource_templates', $resourceTemplate->id(), $data)
: $this->api($form)->create('resource_templates', $data);
if ($response) {
- if ('edit' === $action) {
+ if ($isUpdate) {
$successMessage = 'Resource template successfully updated'; // @translate
} else {
$successMessage = new Message(
@@ -420,16 +524,15 @@ protected function getAddEditView()
$this->messenger()->addSuccess($successMessage);
return $this->redirect()->toUrl($response->getContent()->url());
}
+ $this->messenger()->addFormErrors($form);
} else {
$this->messenger()->addFormErrors($form);
}
}
$view = new ViewModel;
- if ('edit' === $action) {
- $view->setVariable('resourceTemplate', $resourceTemplate);
- }
- $view->setVariable('propertyRows', $this->getPropertyRows());
+ $view->setVariable('resourceTemplate', $resourceTemplate);
+ $view->setVariable('propertyRows', $this->getPropertyRows($isUpdate));
$view->setVariable('form', $form);
return $view;
}
@@ -437,12 +540,11 @@ protected function getAddEditView()
/**
* Get the property rows for the add/edit form.
*
+ * @param bool $isUpdate
* @return array
*/
- protected function getPropertyRows()
+ protected function getPropertyRows($isUpdate)
{
- $action = $this->params('action');
-
if ($this->getRequest()->isPost()) {
// Set POSTed property rows
$data = $this->params()->fromPost();
@@ -456,22 +558,23 @@ protected function getPropertyRows()
$property = $this->api()->read(
'properties', $propertyRow['o:property']['o:id']
)->getContent();
- $propertyRows[$property->id()]['o:property'] = $property;
+ $propertyRows[$key]['o:property'] = $property;
}
} else {
// Set default property rows
$propertyRows = [];
- if ('edit' == $action) {
+ if ($isUpdate) {
+ /** @var \Omeka\Api\Representation\ResourceTemplateRepresentation $resourceTemplate */
$resourceTemplate = $this->api()
->read('resource_templates', $this->params('id'))
->getContent();
$resTemProps = $resourceTemplate->resourceTemplateProperties();
- foreach ($resTemProps as $key => $resTemProp) {
- $propertyRows[$key] = [
+ foreach ($resTemProps as $resTemProp) {
+ $propertyRows[] = [
'o:property' => $resTemProp->property(),
'o:alternate_label' => $resTemProp->alternateLabel(),
'o:alternate_comment' => $resTemProp->alternateComment(),
- 'o:data_type' => $resTemProp->dataType(),
+ 'o:data_type' => $resTemProp->dataTypes(),
'o:is_required' => $resTemProp->isRequired(),
'o:is_private' => $resTemProp->isPrivate(),
];
@@ -490,7 +593,7 @@ protected function getPropertyRows()
'o:property' => $titleProperty,
'o:alternate_label' => null,
'o:alternate_comment' => null,
- 'o:data_type' => null,
+ 'o:data_type' => [],
'o:is_required' => false,
'o:is_private' => false,
],
@@ -498,7 +601,7 @@ protected function getPropertyRows()
'o:property' => $descriptionProperty,
'o:alternate_label' => null,
'o:alternate_comment' => null,
- 'o:data_type' => null,
+ 'o:data_type' => [],
'o:is_required' => false,
'o:is_private' => false,
],
@@ -525,15 +628,19 @@ public function addNewPropertyRowAction()
'o:property' => $property,
'o:alternate_label' => null,
'o:alternate_comment' => null,
- 'o:data_type' => null,
+ 'o:data_type' => [],
'o:is_required' => false,
'o:is_private' => false,
];
+ $namePrefix = 'o:resource_template_property[' . rand(PHP_INT_MAX / 1000000, PHP_INT_MAX) . ']';
+
$view = new ViewModel;
$view->setTerminal(true);
$view->setTemplate('omeka/admin/resource-template/show-property-row');
+ $view->setVariable('resourceTemplate', null);
$view->setVariable('propertyRow', $propertyRow);
+ $view->setVariable('namePrefix', $namePrefix);
return $view;
}
}
diff --git a/application/src/Entity/ResourceTemplate.php b/application/src/Entity/ResourceTemplate.php
index 12a126ae6c..872953cc6d 100644
--- a/application/src/Entity/ResourceTemplate.php
+++ b/application/src/Entity/ResourceTemplate.php
@@ -49,8 +49,7 @@ class ResourceTemplate extends AbstractEntity
* targetEntity="ResourceTemplateProperty",
* mappedBy="resourceTemplate",
* orphanRemoval=true,
- * cascade={"persist", "remove", "detach"},
- * indexBy="property_id"
+ * cascade={"persist", "remove", "detach"}
* )
* @OrderBy({"position" = "ASC"})
*/
diff --git a/application/src/Entity/ResourceTemplateProperty.php b/application/src/Entity/ResourceTemplateProperty.php
index 4df4443f33..c0a1fb2588 100644
--- a/application/src/Entity/ResourceTemplateProperty.php
+++ b/application/src/Entity/ResourceTemplateProperty.php
@@ -4,8 +4,8 @@
/**
* @Entity
* @Table(
- * uniqueConstraints={
- * @UniqueConstraint(
+ * indexes={
+ * @Index(
* columns={"resource_template_id", "property_id"}
* )
* }
@@ -48,7 +48,7 @@ class ResourceTemplateProperty extends AbstractEntity
protected $position;
/**
- * @Column(nullable=true)
+ * @Column(type="json_array", nullable=true)
*/
protected $dataType;
@@ -117,7 +117,7 @@ public function setPosition($position)
$this->position = (int) $position;
}
- public function setDataType($dataType)
+ public function setDataType(array $dataType = null)
{
$this->dataType = $dataType;
}
diff --git a/application/src/Form/Element/DataTypeSelect.php b/application/src/Form/Element/DataTypeSelect.php
new file mode 100644
index 0000000000..7d3f91f013
--- /dev/null
+++ b/application/src/Form/Element/DataTypeSelect.php
@@ -0,0 +1,74 @@
+ 'select',
+ 'multiple' => false,
+ 'class' => 'chosen-select',
+ ];
+
+ /**
+ * @var DataTypeManager
+ */
+ protected $dataTypeManager;
+
+ /**
+ * @var array
+ */
+ protected $dataTypes = [];
+
+ public function getValueOptions()
+ {
+ $options = [];
+ $optgroupOptions = [];
+ foreach ($this->dataTypes as $dataTypeName) {
+ $dataType = $this->dataTypeManager->get($dataTypeName);
+ $label = $dataType->getLabel();
+ if ($optgroupLabel = $dataType->getOptgroupLabel()) {
+ // Hash the optgroup key to avoid collisions when merging with
+ // data types without an optgroup.
+ $optgroupKey = md5($optgroupLabel);
+ // Put resource data types before ones added by modules.
+ $optionsVal = in_array($dataTypeName, ['resource', 'resource:item', 'resource:itemset', 'resource:media'])
+ ? 'options'
+ : 'optgroupOptions';
+ if (!isset(${$optionsVal}[$optgroupKey])) {
+ ${$optionsVal}[$optgroupKey] = [
+ 'label' => $optgroupLabel,
+ 'options' => [],
+ ];
+ }
+ ${$optionsVal}[$optgroupKey]['options'][$dataTypeName] = $label;
+ } else {
+ $options[$dataTypeName] = $label;
+ }
+ }
+ // Always put data types not organized in option groups before data
+ // types organized within option groups.
+ return array_merge($options, $optgroupOptions);
+ }
+
+ /**
+ * @param DataTypeManager $dataTypeManager
+ * @return self
+ */
+ public function setDataTypeManager(DataTypeManager $dataTypeManager)
+ {
+ $this->dataTypeManager = $dataTypeManager;
+ $this->dataTypes = $dataTypeManager->getRegisteredNames();
+ return $this;
+ }
+
+ /**
+ * @return DataTypeManager
+ */
+ public function getDataTypeManager()
+ {
+ return $this->dataTypeManager;
+ }
+}
diff --git a/application/src/Form/SettingForm.php b/application/src/Form/SettingForm.php
index 8a9931e712..18855ded92 100644
--- a/application/src/Form/SettingForm.php
+++ b/application/src/Form/SettingForm.php
@@ -2,6 +2,7 @@
namespace Omeka\Form;
use DateTimeZone;
+use Omeka\Form\Element\DataTypeSelect;
use Omeka\Form\Element\SiteSelect;
use Omeka\Form\Element\RestoreTextarea;
use Omeka\Settings\Settings;
@@ -246,8 +247,8 @@ public function init()
'name' => 'default_to_private',
'type' => 'Checkbox',
'options' => [
- 'label' => 'Default content visibility to Private', // @translate
- 'info' => 'If checked, all items, item sets and sites newly created will have their visibility set to private by default.', // @translate
+ 'label' => 'Default content visibility to Private', // @translate
+ 'info' => 'If checked, all items, item sets and sites newly created will have their visibility set to private by default.', // @translate
],
'attributes' => [
'value' => $this->settings->get('default_to_private'),
@@ -255,6 +256,18 @@ public function init()
],
]);
+ $generalFieldset->add([
+ 'name' => 'resource_default_datatypes',
+ 'type' => DataTypeSelect::class,
+ 'options' => [
+ 'label' => 'Default datatypes for properties in resources forms', // @translate
+ ],
+ 'attributes' => [
+ 'value' => $this->settings->get('resource_default_datatypes', ['literal', 'resource', 'uri']),
+ 'id' => 'resource_default_datatypes',
+ ],
+ ]);
+
$generalFieldset->add([
'name' => 'index_fulltext_search',
'type' => 'Checkbox',
diff --git a/application/src/Service/Form/Element/DataTypeSelectFactory.php b/application/src/Service/Form/Element/DataTypeSelectFactory.php
new file mode 100644
index 0000000000..f39d26d539
--- /dev/null
+++ b/application/src/Service/Form/Element/DataTypeSelectFactory.php
@@ -0,0 +1,16 @@
+setDataTypeManager($services->get('Omeka\DataTypeManager'));
+ }
+}
diff --git a/application/src/View/Helper/DataType.php b/application/src/View/Helper/DataType.php
index d3c21e9f30..6e5cab5f7a 100644
--- a/application/src/View/Helper/DataType.php
+++ b/application/src/View/Helper/DataType.php
@@ -38,7 +38,7 @@ public function __construct(DataTypeManager $dataTypeManager)
* - Data types organized in option groups
*
* @param string $name
- * @param string $value
+ * @param string|array $value
* @param array $attributes
*/
public function getSelect($name, $value = null, $attributes = [])
@@ -74,6 +74,9 @@ public function getSelect($name, $value = null, $attributes = [])
$element->setEmptyOption('Default')
->setValueOptions($options)
->setAttributes($attributes);
+ if (!$element->getAttribute('multiple') && is_array($value)) {
+ $value = reset($value);
+ }
$element->setValue($value);
return $this->getView()->formSelect($element);
}
@@ -82,10 +85,12 @@ public function getTemplates()
{
$view = $this->getView();
$templates = '';
+ $resource = isset($view->resource) ? $view->resource : null;
+ $partial = $view->plugin('partial');
foreach ($this->dataTypes as $dataType) {
- $templates .= $view->partial('common/data-type-wrapper', [
+ $templates .= $partial('common/data-type-wrapper', [
'dataType' => $dataType,
- 'resource' => isset($view->resource) ? $view->resource : null,
+ 'resource' => $resource,
]);
}
return $templates;
@@ -96,6 +101,22 @@ public function getTemplate($dataType)
return $this->manager->get($dataType)->form($this->getView());
}
+ public function getLabel($dataType)
+ {
+ return $this->manager->get($dataType)->getLabel();
+ }
+
+ /**
+ * @param string $dataType
+ * @return \Omeka\DataType\DataTypeInterface|null
+ */
+ public function getDataType($dataType)
+ {
+ return $this->manager->has($dataType)
+ ? $this->manager->get($dataType)
+ : null;
+ }
+
/**
* Prepare the view to enable the data types.
*/
diff --git a/application/view/common/data-type-wrapper.phtml b/application/view/common/data-type-wrapper.phtml
index 0498c4ac99..ffbaede872 100644
--- a/application/view/common/data-type-wrapper.phtml
+++ b/application/view/common/data-type-wrapper.phtml
@@ -1,17 +1,27 @@
plugin('translate');
$escape = $this->plugin('escapeHtml');
+$escapeAttr = $this->plugin('escapeHtmlAttr');
+$hyperlink = $this->plugin('hyperlink');
+$dataTypeHelper = $this->plugin('dataType');
+
+$icons = [
+ 'resource:item' => 'items',
+ 'resource:itemset' => 'item-sets',
+ 'resource:media' => 'media',
+];
?>
-
+
+
- dataType()->getTemplate($dataType); ?>
+ getTemplate($dataType); ?>
-
+
propertySelector(); ?>
diff --git a/application/view/common/resource-form-templates.phtml b/application/view/common/resource-form-templates.phtml
index f6b5a6b473..8359c067d9 100644
--- a/application/view/common/resource-form-templates.phtml
+++ b/application/view/common/resource-form-templates.phtml
@@ -2,9 +2,17 @@
$translate = $this->plugin('translate');
$escape = $this->plugin('escapeHtml');
$this->dataType()->prepareForm();
+
+$defaultDataTypes = $this->setting('resource_default_datatypes') ?: ['literal', 'resource', 'uri'];
+
+$icons = [
+ 'resource:item' => 'items',
+ 'resource:itemset' => 'item-sets',
+ 'resource:media' => 'media',
+];
?>
-
+
+