Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mutliple datatypes by property in template, and multiple properties with different names (laminas) #1597

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8b90d89
Updated resource form to support extended settings.
Daniel-KM Jul 20, 2020
c6a2278
Stored the template settings in resource-form.js to simplify events.
Daniel-KM Aug 31, 2020
b01133a
Added event "o:template-applied".
Daniel-KM Aug 31, 2020
5a226d2
Updated display in resource template form when a property is title or…
Daniel-KM Aug 31, 2020
6f017f2
Cleaned dataType() view helper and template.
Daniel-KM Jul 20, 2020
6d99b92
Added a method to get the labels of data types of a resource template…
Daniel-KM Jul 20, 2020
3e723e2
Allowed resource templates to manage multiple datatypes and duplicate…
Daniel-KM Jul 20, 2020
ffd7cd7
Managed settings in resource template property form.
Daniel-KM Jul 20, 2020
a7b16b1
Added data type label to dataType() view helper and template.
Daniel-KM Jul 20, 2020
689d0bb
Allowed to get a template property by property and by data type.
Daniel-KM Jul 20, 2020
ba77536
Updated resource template representation to manage multiple data types.
Daniel-KM Jul 20, 2020
ac87f3f
Updated resource template adapter to manage multiple properties and d…
Daniel-KM Jul 20, 2020
ba86c4d
Fixed #1623 for remarks.
Sep 3, 2020
606222d
Updated views to manage multiple data types.
Daniel-KM Jul 20, 2020
ebde210
Managed duplicate properties during edition of resource template.
Daniel-KM Jul 20, 2020
a0c51a8
Added a form element select for datatypes.
Daniel-KM Jul 20, 2020
0c79827
Updated method representation->values() to manage duplicate propertie…
Daniel-KM Jul 20, 2020
d57bd38
Merge branch 'feature/template_datatypes_3_/multiple_datatypes' into …
Sep 3, 2020
6326149
Updated resource-form.js to manage templates with multiple properties…
Daniel-KM Jul 20, 2020
c572222
Merge branch 'feature/template_datatypes_3_/controller_view' into fea…
Sep 3, 2020
4848c59
Managed multiple data types during edition of resource template.
Daniel-KM Jul 20, 2020
4413604
Managed single/multiple data types with the view helper DataType.
Daniel-KM Jul 20, 2020
54e8baf
Merge branch 'feature/template_datatypes_3_/representation' into feat…
Sep 3, 2020
a86bf0f
Managed import/export of resource title and description for templates.
Daniel-KM Jul 27, 2020
532fe66
Merge branch 'feature/template_datatypes_3_/resource_edit' into featu…
Sep 3, 2020
bac3d3e
Updated views to manage multiple data types.
Daniel-KM Jul 20, 2020
acb20b1
Merge branch 'feature/template_datatypes_3_/forms' into feature/templ…
Sep 3, 2020
804765c
Updated export of resource templates to manage multiple data types.
Daniel-KM Jul 27, 2020
03690cb
Added an option to select default data types for resource forms.
Daniel-KM Jul 20, 2020
0ad7fd8
Merge branch 'feature/template_datatypes_3_/controller_view' into fea…
Sep 3, 2020
de3e69b
Updated import of resource templates to manage multiple data types.
Daniel-KM Jul 27, 2020
8391e20
Merge branch 'feature/template_datatypes_3_/default_datatypes' into f…
Sep 3, 2020
e017ed6
Merge branch 'feature/template_datatypes_3_/import_export' into featu…
Sep 3, 2020
fc2e739
Added a flag when an error exists in resource form.
Sep 7, 2020
4b488a0
Added a wrapper for the item set and media sidebars.
Sep 7, 2020
c0de88a
Simplified resource-form.js to check multiple datatypes.
Sep 7, 2020
189c018
Fixed resource template adapter when there is no property.
Sep 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
389 changes: 275 additions & 114 deletions application/asset/js/resource-form.js

Large diffs are not rendered by default.

106 changes: 74 additions & 32 deletions application/asset/js/resource-template-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ new Sortable(propertyList[0], {
$('#property-selector .selector-child').click(function(e) {
e.preventDefault();
var propertyId = $(this).closest('li').data('property-id');
if ($('#properties li[data-property-id="' + propertyId + '"]').length) {
// Resource templates cannot be assigned duplicate properties.
return;
}
$.get(propertyList.data('addNewPropertyRowUrl'), {property_id: propertyId})
.done(function(data) {
// Check if the property is the template title or description.
propertyList.append(data);
if (propertyId == titleProperty.val()) {
$('.title-property-cell').remove();
$('#properties .property[data-property-id=' + propertyId + ']').find('.actions').before(titlePropertyTemplate);
}
if (propertyId == descriptionProperty.val()) {
$('.description-property-cell').remove();
$('#properties .property[data-property-id=' + propertyId + ']').find('.actions').before(descriptionPropertyTemplate);
}
});
});

Expand All @@ -53,50 +58,87 @@ propertyList.on('click', '.property-restore', function(e) {

propertyList.on('click', '.property-edit', function(e) {
e.preventDefault();
// Get values stored in the row.
var prop = $(this).closest('.property');
var propId = prop.data('property-id');
var propertyId = prop.data('property-id');
var oriLabel = prop.find('.original-label');
var altLabel = prop.find('.alternate-label');
var altLabel = prop.find('[data-property-key="o:alternate_label"]');
var oriComment = prop.find('.original-comment');
var altComment = prop.find('.alternate-comment');
var isRequired = prop.find('.is-required');
var isPrivate = prop.find('.is-private');
var dataType = prop.find('.data-type');
var altComment = prop.find('[data-property-key="o:alternate_comment"]');
var isRequired = prop.find('[data-property-key="o:is_required"]');
var isPrivate = prop.find('[data-property-key="o:is_private"]');
var dataTypes = prop.find('[data-property-key="o:data_type"]');
var settings = {};
prop.find('[data-setting-key]').each(function(index, hiddenElement) {
settings[index] = $(hiddenElement);
});

$('#original-label').text(oriLabel.val());
$('#alternate-label').val(altLabel.val());
$('#original-comment').text(oriComment.val());
$('#alternate-comment').val(altComment.val());
$('#is-title-property').prop('checked', propId == titleProperty.val());
$('#is-description-property').prop('checked', propId == descriptionProperty.val());
$('#is-required').prop('checked', isRequired.val());
$('#is-private').prop('checked', isPrivate.val());
$('#data-type option[value="' + dataType.val() + '"]').prop('selected', true);
$('#data-type').trigger('chosen:updated');
// Copy values into the sidebar.
$('#edit-sidebar #original-label').text(oriLabel.val());
$('#edit-sidebar #alternate-label').val(altLabel.val());
$('#edit-sidebar #original-comment').text(oriComment.val());
$('#edit-sidebar #alternate-comment').val(altComment.val());
$('#edit-sidebar #is-title-property').prop('checked', propertyId == titleProperty.val());
$('#edit-sidebar #is-description-property').prop('checked', propertyId == descriptionProperty.val());
$('#edit-sidebar #is-required').prop('checked', isRequired.val() === '1');
$('#edit-sidebar #is-private').prop('checked', isPrivate.val() === '1');
$('#edit-sidebar #data-type').val(dataTypes.val().replace(/^,+|,+$/g, '').split(','));
$('#edit-sidebar #data-type').trigger('chosen:updated');
$.each(settings, function(index, hiddenElement) {
var settingKey = hiddenElement.data('setting-key');
var sidebarElement = $('#edit-sidebar [data-setting-key="' + settingKey + '"]');
var sidebarElementType = sidebarElement.prop('type') ? sidebarElement.prop('type') : sidebarElement.prop('nodeName').toLowerCase();
if (sidebarElementType === 'checkbox') {
sidebarElement.prop('checked', hiddenElement.prop('checked'));
} else if (sidebarElementType === 'radio') {
$('#edit-sidebar [data-setting-key="' + hiddenElement.data('setting-key') + '"]')
.val([prop.find('[data-setting-key="' + hiddenElement.data('setting-key') + '"]:checked').val()]);
} else if (sidebarElementType === 'select' || sidebarElementType === 'select-multiple' ) {
sidebarElement.val(hiddenElement.val());
sidebarElement.trigger('chosen:updated');
} else { // Text, textarea, number…
sidebarElement.val(hiddenElement.val());
}
});

// When the sidebar fieldset is applied, store new values in the row.
$('#set-changes').off('click.setchanges').on('click.setchanges', function(e) {
altLabel.val($('#alternate-label').val());
prop.find('.alternate-label-cell').text($('#alternate-label').val());
altComment.val($('#alternate-comment').val());
if ($('#is-title-property').prop('checked')) {
titleProperty.val(propId);
altLabel.val($('#edit-sidebar #alternate-label').val());
prop.find('.alternate-label-cell').text($('#edit-sidebar #alternate-label').val());
altComment.val($('#edit-sidebar #alternate-comment').val());
if ($('#edit-sidebar #is-title-property').prop('checked')) {
titleProperty.val(propertyId);
$('.title-property-cell').remove();
prop.find('.actions').before(titlePropertyTemplate);
} else if (propId == titleProperty.val()) {
} else if (propertyId == titleProperty.val()) {
titleProperty.val(null);
$('.title-property-cell').remove();
}
if ($('#is-description-property').prop('checked')) {
descriptionProperty.val(propId);
if ($('#edit-sidebar #is-description-property').prop('checked')) {
descriptionProperty.val(propertyId);
$('.description-property-cell').remove();
prop.find('.actions').before(descriptionPropertyTemplate);
} else if (propId == descriptionProperty.val()) {
} else if (propertyId == descriptionProperty.val()) {
descriptionProperty.val(null);
$('.description-property-cell').remove();
}
$('#is-required').prop('checked') ? isRequired.val(1) : isRequired.val(null);
$('#is-private').prop('checked') ? isPrivate.val(1) : isPrivate.val(null);
dataType.val($('#data-type').val());
isRequired.val($('#edit-sidebar #is-required').prop('checked') ? '1' : '');
isPrivate.val($('#edit-sidebar #is-private').prop('checked') ? '1' : '');
dataTypes.val($('#edit-sidebar #data-type').val());
// New fields are not yet stored in the row.
$('#edit-sidebar [data-setting-key]').each(function(index, sidebarElement) {
sidebarElement = $(sidebarElement);
var sidebarElementType = sidebarElement.prop('type') ? sidebarElement.prop('type') : sidebarElement.prop('nodeName').toLowerCase();
var hiddenElement = prop.find('[data-setting-key="' + sidebarElement.data('setting-key') + '"]');
if (sidebarElementType === 'checkbox') {
hiddenElement.prop('checked', sidebarElement.prop('checked'));
} else if (sidebarElementType === 'radio') {
prop.find('[data-setting-key="' + sidebarElement.data('setting-key') + '"]')
.val([$('#edit-sidebar [data-setting-key="' + sidebarElement.data('setting-key') + '"]:checked').val()]);
} else {
hiddenElement.val(sidebarElement.val());
}
});
Omeka.closeSidebar($('#edit-sidebar'));
});

Expand Down
1 change: 1 addition & 0 deletions application/config/module.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@
'Omeka\Form\SiteForm' => Service\Form\SiteFormFactory::class,
'Omeka\Form\SiteSettingsForm' => Service\Form\SiteSettingsFormFactory::class,
'Omeka\Form\UserBatchUpdateForm' => Service\Form\UserBatchUpdateFormFactory::class,
'Omeka\Form\Element\DataTypeSelect' => Service\Form\Element\DataTypeSelectFactory::class,
'Omeka\Form\Element\ResourceSelect' => Service\Form\Element\ResourceSelectFactory::class,
'Omeka\Form\Element\ResourceClassSelect' => Service\Form\Element\ResourceClassSelectFactory::class,
'Omeka\Form\Element\ResourceTemplateSelect' => Service\Form\Element\ResourceTemplateSelectFactory::class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ public function setPosition($position)
/**
* {@inheritDoc}
*/
public function setDataType($dataType)
public function setDataType(array $dataType = NULL)
{

$this->__initializer__ && $this->__initializer__->__invoke($this, 'setDataType', [$dataType]);
Expand Down
4 changes: 2 additions & 2 deletions application/data/install/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,13 @@ CREATE TABLE `resource_template_property` (
`alternate_label` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`alternate_comment` longtext COLLATE utf8mb4_unicode_ci,
`position` int(11) DEFAULT NULL,
`data_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`data_type` longtext COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '(DC2Type:json_array)',
`is_required` tinyint(1) NOT NULL,
`is_private` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_4689E2F116131EA549213EC` (`resource_template_id`,`property_id`),
KEY `IDX_4689E2F116131EA` (`resource_template_id`),
KEY `IDX_4689E2F1549213EC` (`property_id`),
KEY `IDX_4689E2F116131EA549213EC` (`resource_template_id`, `property_id`),
CONSTRAINT `FK_4689E2F116131EA` FOREIGN KEY (`resource_template_id`) REFERENCES `resource_template` (`id`),
CONSTRAINT `FK_4689E2F1549213EC` FOREIGN KEY (`property_id`) REFERENCES `property` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Expand Down
54 changes: 54 additions & 0 deletions application/data/migrations/20200831000000_MutlipleDataTypes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
namespace Omeka\Db\Migrations;

use Doctrine\DBAL\Connection;
use Omeka\Db\Migration\MigrationInterface;

class MutlipleDataTypes implements MigrationInterface
{
public function up(Connection $conn)
{
// Remove previous indexes if any.
$indexes = [
'UNIQ_4689E2F116131EA549213EC',
'UNIQ_4689E2F116131EA549213ECA633250B',
'UNIQ_4689E2F116131EA549213EC37919CCB',
'IDX_4689E2F116131EA549213EC',
];
foreach ($indexes as $index) {
$sql = <<<SQL
SHOW INDEX FROM `resource_template_property` WHERE Key_name = "$index";
SQL;
if ($conn->fetchAll($sql)) {
$sql = <<<SQL
ALTER TABLE `resource_template_property` DROP INDEX $index;
SQL;
$conn->exec($sql);
}
}

$sql = <<<SQL
ALTER TABLE `resource_template_property` CHANGE `data_type` `data_type` LONGTEXT COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '(DC2Type:json_array)';
SQL;
$conn->exec($sql);

$sql = <<<SQL
CREATE INDEX IDX_4689E2F116131EA549213EC ON resource_template_property (resource_template_id, property_id);
SQL;
$conn->exec($sql);

$sql = <<<SQL
UPDATE `resource_template_property`
SET `data_type` = NULL
WHERE `data_type` IS NULL OR TRIM(`data_type`) = "";
SQL;
$conn->exec($sql);

$sql = <<<SQL
UPDATE `resource_template_property`
SET `data_type` = CONCAT('["', REPLACE(TRIM(`data_type`), "\n", '","'), '"]')
WHERE `data_type` IS NOT NULL AND SUBSTRING(`data_type`, 1, 1) != "[";
SQL;
$conn->exec($sql);
}
}
99 changes: 53 additions & 46 deletions application/src/Api/Adapter/ResourceTemplateAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,45 @@ public function validateRequest(Request $request, ErrorStore $errorStore)
{
$data = $request->getContent();

// A resource template may not have duplicate properties.
// A resource template must not have duplicate properties with the same
// data types. Each data type is checked separately for each property,
// so a data type cannot be used multiple times with the same property.
if (isset($data['o:resource_template_property'])
&& is_array($data['o:resource_template_property'])
) {
$propertyIds = [];
$checkDataTypes = [];
$checkDataTypesByProperty = [];
foreach ($data['o:resource_template_property'] as $resTemPropData) {
if (!isset($resTemPropData['o:property']['o:id'])) {
continue; // skip when no property ID
if (empty($resTemPropData['o:property']['o:id'])) {
// Skip when no property ID.
continue;
}
$propertyId = $resTemPropData['o:property']['o:id'];
if (in_array($propertyId, $propertyIds)) {

$dataTypes = empty($resTemPropData['o:data_type']) ? [] : $resTemPropData['o:data_type'];
sort($dataTypes);
$dataTypes = array_unique(array_filter(array_map('trim', $dataTypes)));
$dataTypesString = implode('|', $dataTypes);
$check = $propertyId . '-' . $dataTypesString;
if (isset($checkDataTypes[$check])) {
$errorStore->addError('o:property', new Message(
'Attempting to add duplicate property with ID %s', // @translate
$propertyId
'Attempting to add duplicate property "%s" (ID %s) with the same data types', // @translate
$resTemPropData['o:original_label'] ?? '', $propertyId
));
}
$propertyIds[] = $propertyId;
$checkDataTypes[$check] = true;

if (empty($checkDataTypesByProperty[$propertyId])) {
$checkDataTypesByProperty[$propertyId] = $dataTypes;
} elseif ($dataTypes) {
if (array_intersect($dataTypes, $checkDataTypesByProperty[$propertyId])) {
$errorStore->addError('o:property', new Message(
'Attempting to add the same data types to the same property "%s" (ID %s)', // @translate
$resTemPropData['o:original_label'] ?? '', $propertyId
));
}
$checkDataTypesByProperty[$propertyId] = array_merge($checkDataTypesByProperty[$propertyId], $dataTypes);
}
}
}
}
Expand All @@ -96,6 +118,7 @@ public function validateEntity(EntityInterface $entity,
public function hydrate(Request $request, EntityInterface $entity,
ErrorStore $errorStore
) {
/** @var \Omeka\Entity\ResourceTemplate $entity */
$data = $request->getContent();
$this->hydrateOwner($request, $entity);
$this->hydrateResourceClass($request, $entity);
Expand Down Expand Up @@ -128,27 +151,19 @@ public function hydrate(Request $request, EntityInterface $entity,
&& isset($data['o:resource_template_property'])
&& is_array($data['o:resource_template_property'])
) {

// Get a resource template property by property ID.
$getResTemProp = function ($propertyId, $resTemProps) {
foreach ($resTemProps as $resTemProp) {
if ($propertyId == $resTemProp->getProperty()->getId()) {
return $resTemProp;
}
}
return null;
};

$propertyAdapter = $this->getAdapter('properties');
$resTemProps = $entity->getResourceTemplateProperties();
$resTemPropsToRetain = [];
$resTemProps->first();
$totalExisting = count($resTemProps);
// Position is one-based.
$position = 1;
foreach ($data['o:resource_template_property'] as $resTemPropData) {
if (!isset($resTemPropData['o:property']['o:id'])) {
if (empty($resTemPropData['o:property']['o:id'])) {
continue; // skip when no property ID
}

$propertyId = $resTemPropData['o:property']['o:id'];
$propertyId = (int) $resTemPropData['o:property']['o:id'];

$altLabel = null;
if (isset($resTemPropData['o:alternate_label'])
&& '' !== trim($resTemPropData['o:alternate_label'])
Expand All @@ -161,11 +176,9 @@ public function hydrate(Request $request, EntityInterface $entity,
) {
$altComment = $resTemPropData['o:alternate_comment'];
}
$dataType = null;
if (isset($resTemPropData['o:data_type'])
&& '' !== trim($resTemPropData['o:data_type'])
) {
$dataType = $resTemPropData['o:data_type'];
$dataTypes = null;
if (!empty($resTemPropData['o:data_type'])) {
$dataTypes = array_values(array_unique(array_filter(array_map('trim', $resTemPropData['o:data_type']))));
}
$isRequired = false;
if (isset($resTemPropData['o:is_required'])) {
Expand All @@ -176,36 +189,30 @@ public function hydrate(Request $request, EntityInterface $entity,
$isPrivate = (bool) $resTemPropData['o:is_private'];
}

// Check whether a passed property is already assigned to this
// resource template.
$resTemProp = $getResTemProp($propertyId, $resTemProps);
if (!$resTemProp) {
// It is not assigned. Add a new resource template property.
// No need to explicitly add it to the collection since it
// is added implicitly when setting the resource template.
$property = $propertyAdapter->findEntity($propertyId);
// Reuse existing records, because id has no meaning.
if ($position <= $totalExisting) {
$resTemProp = $resTemProps[$position - 1];
} else {
$resTemProp = new ResourceTemplateProperty;
$resTemProp->setResourceTemplate($entity);
$resTemProp->setProperty($property);
$entity->getResourceTemplateProperties()->add($resTemProp);
$resTemProps->add($resTemProp);
}

$resTemProp->setResourceTemplate($entity);
$resTemProp->setProperty($propertyAdapter->findEntity($propertyId));
$resTemProp->setAlternateLabel($altLabel);
$resTemProp->setAlternateComment($altComment);
$resTemProp->setDataType($dataType);
$resTemProp->setDataType($dataTypes);
$resTemProp->setIsRequired($isRequired);
$resTemProp->setIsPrivate($isPrivate);
// Set the position of the property to its intrinsic order
// within the passed array.
$resTemProp->setPosition($position++);
$resTemPropsToRetain[] = $resTemProp;
}

// Remove resource template properties that were not included in the
// passed data.
foreach ($resTemProps as $resTemPropId => $resTemProp) {
if (!in_array($resTemProp, $resTemPropsToRetain)) {
$resTemProps->remove($resTemPropId);
}
// Remove remaining resource template properties that were not
// included in the passed data.
for (; $position <= $totalExisting; $position++) {
$resTemProps->remove($position - 1);
}
}
}
Expand Down
Loading