diff --git a/Module.php b/Module.php
index f6b0c49f..02fbcd98 100644
--- a/Module.php
+++ b/Module.php
@@ -21,26 +21,34 @@ public function install(ServiceLocatorInterface $serviceLocator)
{
$connection = $serviceLocator->get('Omeka\Connection');
$sql = <<<'SQL'
+CREATE TABLE csvimport_mapping_model (
+ id INT AUTO_INCREMENT NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ created DATETIME NOT NULL,
+ mapping LONGTEXT NOT NULL COMMENT '(DC2Type:json)',
+ PRIMARY KEY(id),
+ UNIQUE INDEX UNIQ_B0D508235E237E06 (name))
+DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;
CREATE TABLE csvimport_import (
- id INT AUTO_INCREMENT NOT NULL,
- job_id INT NOT NULL,
- undo_job_id INT DEFAULT NULL,
- comment VARCHAR(255) DEFAULT NULL,
- resource_type VARCHAR(255) NOT NULL,
- has_err TINYINT(1) NOT NULL,
- stats LONGTEXT NOT NULL COMMENT '(DC2Type:json_array)',
- UNIQUE INDEX UNIQ_17B50881BE04EA9 (job_id),
- UNIQUE INDEX UNIQ_17B508814C276F75 (undo_job_id),
- PRIMARY KEY(id)
-) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB;
+ id INT AUTO_INCREMENT NOT NULL,
+ job_id INT NOT NULL,
+ undo_job_id INT DEFAULT NULL,
+ comment VARCHAR(255) DEFAULT NULL,
+ resource_type VARCHAR(255) NOT NULL,
+ has_err TINYINT(1) NOT NULL,
+ stats LONGTEXT NOT NULL COMMENT '(DC2Type:json_array)',
+ UNIQUE INDEX UNIQ_17B50881BE04EA9 (job_id),
+ UNIQUE INDEX UNIQ_17B508814C276F75 (undo_job_id),
+ PRIMARY KEY(id))
+DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;
CREATE TABLE csvimport_entity (
- id INT AUTO_INCREMENT NOT NULL,
- job_id INT NOT NULL,
- entity_id INT NOT NULL,
- resource_type VARCHAR(255) NOT NULL,
- INDEX IDX_84D382F4BE04EA9 (job_id),
- PRIMARY KEY(id)
-) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB;
+ id INT AUTO_INCREMENT NOT NULL,
+ job_id INT NOT NULL,
+ entity_id INT NOT NULL,
+ resource_type VARCHAR(255) NOT NULL,
+ INDEX IDX_84D382F4BE04EA9 (job_id),
+ PRIMARY KEY(id))
+DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;
ALTER TABLE csvimport_import ADD CONSTRAINT FK_17B50881BE04EA9 FOREIGN KEY (job_id) REFERENCES job (id);
ALTER TABLE csvimport_import ADD CONSTRAINT FK_17B508814C276F75 FOREIGN KEY (undo_job_id) REFERENCES job (id);
ALTER TABLE csvimport_entity ADD CONSTRAINT FK_84D382F4BE04EA9 FOREIGN KEY (job_id) REFERENCES job (id);
@@ -60,6 +68,7 @@ public function uninstall(ServiceLocatorInterface $serviceLocator)
ALTER TABLE csvimport_import DROP FOREIGN KEY FK_17B50881BE04EA9;
DROP TABLE IF EXISTS csvimport_entity;
DROP TABLE IF EXISTS csvimport_import;
+DROP TABLE IF EXISTS csvimport_mapping_model;
SQL;
$sqls = array_filter(array_map('trim', explode(';', $sql)));
foreach ($sqls as $sql) {
@@ -76,6 +85,24 @@ public function upgrade($oldVersion, $newVersion, ServiceLocatorInterface $servi
ALTER TABLE csvimport_import ADD stats LONGTEXT NOT NULL COMMENT '(DC2Type:json_array)';
UPDATE csvimport_import SET stats = CONCAT('{"processed":{"', resource_type, '":', added_count, '}}');
ALTER TABLE csvimport_import DROP added_count;
+SQL;
+ $sqls = array_filter(array_map('trim', explode(';', $sql)));
+ foreach ($sqls as $sql) {
+ $connection->exec($sql);
+ }
+ }
+
+ if (version_compare($oldVersion, '2.7.0', '<')) {
+ $connection = $serviceLocator->get('Omeka\Connection');
+ $sql = <<<'SQL'
+CREATE TABLE csvimport_mapping_model (
+ id INT AUTO_INCREMENT NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ created DATETIME NOT NULL,
+ mapping LONGTEXT NOT NULL COMMENT '(DC2Type:json)',
+ PRIMARY KEY(id),
+ UNIQUE INDEX UNIQ_B0D508235E237E06 (name))
+DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;
SQL;
$sqls = array_filter(array_map('trim', explode(';', $sql)));
foreach ($sqls as $sql) {
diff --git a/asset/js/csvimport.js b/asset/js/csvimport.js
index 86f0ba62..74bb8119 100644
--- a/asset/js/csvimport.js
+++ b/asset/js/csvimport.js
@@ -2,7 +2,6 @@
* Initially based on Omeka S omeka2importer.js and resource-core.js.
*/
(function ($) {
-
$(document).ready(function() {
/*
* Init.
@@ -14,12 +13,85 @@
var defaultSidebarHtml = null;
var actionsHtml = '
';
var batchEditCheckboxes = $('.column-select, .select-all');
var batchEditButton = $('#batch-edit-options');
+ $(document).on('mapping.updated', newTable);
+
+ newTable();
+
+ function newTable() {
+ resetActiveColumns();
+
+ /*
+ * Batch edit options.
+ */
+
+ $('.batch-edit input[type="checkbox"], .batch-edit .select-all').change(function() {
+ if ($('.column-select:checked').length > 0) {
+ batchEditButton.removeClass('inactive').addClass('active sidebar-content');
+ } else {
+ batchEditButton.addClass('inactive').removeClass('active sidebar-content');
+ }
+ });
+
+ /*
+ * Sidebar chooser (buttons on each mappable element).
+ */
+
+ $('.column-header + .actions a').on('click', function(e) {
+ e.preventDefault();
+ if (activeElement !== null) {
+ activeElement.removeClass('active');
+ }
+ if ($('.column-select:checked').length > 0) {
+ resetActiveColumns();
+ }
+ activeElement = $(e.target).closest('tr.mappable');
+ activeElement.addClass('active');
+
+ var actionElement = $(this);
+ $('.sidebar-chooser li').removeClass('active');
+ actionElement.parent().addClass('active');
+ var target = actionElement.data('sidebar-selector');
+
+ var sidebar = $(target);
+ if (!sidebar.hasClass('active') ) {
+ defaultSidebarHtml = sidebar.html();
+ }
+ var columnName = activeElement.data('column');
+ if (sidebar.find('.column-name').length > 0) {
+ $('.column-name').text(columnName);
+ } else {
+ sidebar.find('h3').append(' ' + columnName + '');
+ }
+
+ var currentSidebar = $('.sidebar.active');
+ if (currentSidebar.attr('id') != target) {
+ currentSidebar.removeClass('active');
+ sidebar.html(defaultSidebarHtml);
+ rebindInputs(sidebar);
+ }
+
+ Omeka.openSidebar(sidebar);
+ populateSidebar();
+ });
+
+ /*
+ * Actions on mapped columns.
+ */
+
+ // Remove mapping.
+ $('.section').on('click', 'a.remove-mapping', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ $(this).parents('li.mapping').remove();
+ });
+ };
+
/*
* Rebinding chosen selects and property selector after sidebar hydration.
*/
@@ -60,47 +132,6 @@
});
}
- /*
- * Sidebar chooser (buttons on each mappable element).
- */
-
- $('.column-header + .actions a').on('click', function(e) {
- e.preventDefault();
- if (activeElement !== null) {
- activeElement.removeClass('active');
- }
- if ($('.column-select:checked').length > 0) {
- resetActiveColumns();
- }
- activeElement = $(e.target).closest('tr.mappable');
- activeElement.addClass('active');
-
- var actionElement = $(this);
- $('.sidebar-chooser li').removeClass('active');
- actionElement.parent().addClass('active');
- var target = actionElement.data('sidebar-selector');
-
- var sidebar = $(target);
- if (!sidebar.hasClass('active') ) {
- defaultSidebarHtml = sidebar.html();
- }
- var columnName = activeElement.data('column');
- if (sidebar.find('.column-name').length > 0) {
- $('.column-name').text(columnName);
- } else {
- sidebar.find('h3').append(' ' + columnName + '');
- }
-
- var currentSidebar = $('.sidebar.active');
- if (currentSidebar.attr('id') != target) {
- currentSidebar.removeClass('active');
- sidebar.html(defaultSidebarHtml);
- rebindInputs(sidebar);
- }
-
- Omeka.openSidebar(sidebar);
- populateSidebar();
- });
function populateSidebar() {
$('.active.element .options :input:not(:disabled)').each(function() {
@@ -119,17 +150,6 @@
});
}
- /*
- * Batch edit options.
- */
-
- $('.batch-edit input[type="checkbox"], .batch-edit .select-all').change(function() {
- if ($('.column-select:checked').length > 0) {
- batchEditButton.removeClass('inactive').addClass('active sidebar-content');
- } else {
- batchEditButton.addClass('inactive').removeClass('active sidebar-content');
- }
- });
$(document).on('click', '#batch-edit-options.active', function() {
defaultSidebarHtml = $('#column-options').html();
@@ -341,17 +361,6 @@
batchEditButton.removeClass('active sidebar-content').addClass('inactive');
}
- /*
- * Actions on mapped columns.
- */
-
- // Remove mapping.
- $('.section').on('click', 'a.remove-mapping', function(e) {
- e.preventDefault();
- e.stopPropagation();
- $(this).parents('li.mapping').remove();
- });
-
function applyMappings(flagName, flagValue, flagLiClass, flagLabel) {
var hasFlag = activeElement.find('ul.mappings li.' + flagLiClass);
if (flagValue == 'default') {
diff --git a/asset/js/mappingselect.js b/asset/js/mappingselect.js
new file mode 100644
index 00000000..916a6ffa
--- /dev/null
+++ b/asset/js/mappingselect.js
@@ -0,0 +1,33 @@
+(function ($) {
+$(document).ready(function() {
+ $(document).on('click', '#mapping-select-button', function(e) {
+ e.preventDefault(); // Stop normal form submission
+
+ var form = $(this).closest('form');
+ var formData = form.serialize(); // Collect all form inputs
+
+ var sidebar = $(this).parents('.sidebar');
+ Omeka.closeSidebar(sidebar);
+
+ $.ajax({
+ url: form.attr('action'),
+ method: 'POST',
+ data: formData,
+ success: function(response, status, xhr) {
+ if (xhr.status === 200 && response.trim().length > 0) {
+ var newTable = $('').html(response).find('table'); // parse HTML
+ var currentTable = $('table');
+
+ // Replace only the inside of the table
+ currentTable.replaceWith(newTable);
+
+ $(document).trigger("enhance.tablesaw");
+ $(document).trigger('mapping.updated');
+ } else {
+ console.error('Received an empty or invalid response from the server.');
+ }
+ }
+ });
+});
+});
+})(jQuery)
diff --git a/config/module.config.php b/config/module.config.php
index e67a0e7b..9c85cf83 100644
--- a/config/module.config.php
+++ b/config/module.config.php
@@ -24,20 +24,29 @@
],
],
'form_elements' => [
+ 'invokables' => [
+ 'CSVImport\Form\MappingModelEditForm' => Form\MappingModelEditForm::class,
+ 'CSVImport\Form\MappingModelSelectForm' => Form\MappingModelSelectForm::class,
+ ],
'factories' => [
'CSVImport\Form\ImportForm' => Service\Form\ImportFormFactory::class,
- 'CSVImport\Form\MappingForm' => Service\Form\MappingFormFactory::class,
+ 'CSVImport\Form\MappingModelForm' => Service\Form\MappingModelFormFactory::class,
+ 'CSVImport\Form\Element\MappingModelSelect' => Service\Form\Element\MappingModelSelectFactory::class,
+ 'CSVImport\Form\MappingModelSaveForm' => Service\Form\MappingModelSaveFormFactory::class,
],
],
'controllers' => [
'factories' => [
'CSVImport\Controller\Index' => Service\Controller\IndexControllerFactory::class,
+ 'CSVImport\Controller\Admin\MappingModel' => Service\Controller\Admin\MappingModelControllerFactory::class,
],
],
'controller_plugins' => [
'factories' => [
'automapHeadersToMetadata' => Service\ControllerPlugin\AutomapHeadersToMetadataFactory::class,
'findResourcesFromIdentifiers' => Service\ControllerPlugin\FindResourcesFromIdentifiersFactory::class,
+ 'loadMappingModel' => Service\ControllerPlugin\LoadMappingModelFactory::class,
+ 'saveMappingModel' => Service\ControllerPlugin\SaveMappingModelFactory::class,
],
'aliases' => [
'findResourceFromIdentifier' => 'findResourcesFromIdentifiers',
@@ -47,6 +56,7 @@
'invokables' => [
'csvimport_entities' => Api\Adapter\EntityAdapter::class,
'csvimport_imports' => Api\Adapter\ImportAdapter::class,
+ 'csvimport_mapping_models' => Api\Adapter\MappingModelAdapter::class,
],
],
'service_manager' => [
@@ -92,6 +102,35 @@
],
],
],
+ 'mapping-model' => [
+ 'type' => 'Segment',
+ 'options' => [
+ 'route' => '/mapping-model[/:action]',
+ 'constraints' => [
+ 'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
+ ],
+ 'defaults' => [
+ '__NAMESPACE__' => 'CSVImport\Controller\Admin',
+ 'controller' => 'MappingModel',
+ 'action' => 'browse',
+ ],
+ ],
+ ],
+ 'mapping-model-id' => [
+ 'type' => 'Segment',
+ 'options' => [
+ 'route' => '/mapping-model/:id[/:action]',
+ 'constraints' => [
+ 'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
+ 'id' => '\d+',
+ ],
+ 'defaults' => [
+ '__NAMESPACE__' => 'CSVImport\Controller\Admin',
+ 'controller' => 'MappingModel',
+ 'action' => 'show',
+ ],
+ ],
+ ],
],
],
],
@@ -123,6 +162,10 @@
'action' => 'past-imports',
'resource' => 'CSVImport\Controller\Index',
],
+ [
+ 'label' => 'Mapping Models', // @translate
+ 'route' => 'admin/csvimport/mapping-model',
+ ],
],
],
],
@@ -138,7 +181,7 @@
],
],
'js_translate_strings' => [
- 'Remove mapping', // @translate
+ 'Remove mapping model', // @translate
],
'csv_import' => [
'sources' => [
diff --git a/config/module.ini b/config/module.ini
index 4a035b55..29922adc 100644
--- a/config/module.ini
+++ b/config/module.ini
@@ -8,5 +8,5 @@ author_link = "https://omeka.org/"
module_link = "https://omeka.org/s/docs/user-manual/modules/csvimport/"
support_link = "https://forum.omeka.org/c/omeka-s/modules"
configurable = false
-version = "2.6.2"
+version = "3.0.0"
omeka_version_constraint = "^4.0.0"
diff --git a/data/doctrine-proxies/__CG__CSVImportEntityCSVImportEntity.php b/data/doctrine-proxies/__CG__CSVImportEntityCSVImportEntity.php
index 7cbf4b84..85e4b184 100644
--- a/data/doctrine-proxies/__CG__CSVImportEntityCSVImportEntity.php
+++ b/data/doctrine-proxies/__CG__CSVImportEntityCSVImportEntity.php
@@ -2,6 +2,7 @@
namespace DoctrineProxies\__CG__\CSVImport\Entity;
+
/**
* DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR
*/
@@ -12,39 +13,41 @@ class CSVImportEntity extends \CSVImport\Entity\CSVImportEntity implements \Doct
* three parameters, being respectively the proxy object to be initialized, the method that triggered the
* initialization process and an array of ordered parameters that were passed to that method.
*
- * @see \Doctrine\Common\Persistence\Proxy::__setInitializer
+ * @see \Doctrine\Common\Proxy\Proxy::__setInitializer
*/
public $__initializer__;
/**
* @var \Closure the callback responsible of loading properties that need to be copied in the cloned object
*
- * @see \Doctrine\Common\Persistence\Proxy::__setCloner
+ * @see \Doctrine\Common\Proxy\Proxy::__setCloner
*/
public $__cloner__;
/**
* @var boolean flag indicating if this object was already initialized
*
- * @see \Doctrine\Common\Persistence\Proxy::__isInitialized
+ * @see \Doctrine\Persistence\Proxy::__isInitialized
*/
public $__isInitialized__ = false;
/**
- * @var array properties to be lazy loaded, with keys being the property
- * names and values being their default values
+ * @var array
properties to be lazy loaded, indexed by property name
+ */
+ public static $lazyPropertiesNames = array (
+);
+
+ /**
+ * @var array default values of properties to be lazy loaded, with keys being the property names
*
- * @see \Doctrine\Common\Persistence\Proxy::__getLazyProperties
+ * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties
*/
- public static $lazyPropertiesDefaults = [];
+ public static $lazyPropertiesDefaults = array (
+);
- /**
- * @param \Closure $initializer
- * @param \Closure $cloner
- */
- public function __construct($initializer = null, $cloner = null)
+ public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null)
{
$this->__initializer__ = $initializer;
@@ -82,7 +85,7 @@ public function __wakeup()
$existingProperties = get_object_vars($proxy);
- foreach ($proxy->__getLazyProperties() as $property => $defaultValue) {
+ foreach ($proxy::$lazyPropertiesDefaults as $property => $defaultValue) {
if ( ! array_key_exists($property, $existingProperties)) {
$proxy->$property = $defaultValue;
}
@@ -103,7 +106,7 @@ public function __clone()
/**
* Forces initialization of the proxy
*/
- public function __load()
+ public function __load(): void
{
$this->__initializer__ && $this->__initializer__->__invoke($this, '__load', []);
}
@@ -112,7 +115,7 @@ public function __load()
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
- public function __isInitialized()
+ public function __isInitialized(): bool
{
return $this->__isInitialized__;
}
@@ -121,7 +124,7 @@ public function __isInitialized()
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
- public function __setInitialized($initialized)
+ public function __setInitialized($initialized): void
{
$this->__isInitialized__ = $initialized;
}
@@ -130,7 +133,7 @@ public function __setInitialized($initialized)
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
- public function __setInitializer(\Closure $initializer = null)
+ public function __setInitializer(\Closure $initializer = null): void
{
$this->__initializer__ = $initializer;
}
@@ -139,7 +142,7 @@ public function __setInitializer(\Closure $initializer = null)
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
- public function __getInitializer()
+ public function __getInitializer(): ?\Closure
{
return $this->__initializer__;
}
@@ -148,7 +151,7 @@ public function __getInitializer()
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
- public function __setCloner(\Closure $cloner = null)
+ public function __setCloner(\Closure $cloner = null): void
{
$this->__cloner__ = $cloner;
}
@@ -157,7 +160,7 @@ public function __setCloner(\Closure $cloner = null)
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific cloning logic
*/
- public function __getCloner()
+ public function __getCloner(): ?\Closure
{
return $this->__cloner__;
}
@@ -165,9 +168,10 @@ public function __getCloner()
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
+ * @deprecated no longer in use - generated code now relies on internal components rather than generated public API
* @static
*/
- public function __getLazyProperties()
+ public function __getLazyProperties(): array
{
return self::$lazyPropertiesDefaults;
}
diff --git a/data/doctrine-proxies/__CG__CSVImportEntityCSVImportImport.php b/data/doctrine-proxies/__CG__CSVImportEntityCSVImportImport.php
index f985216e..31736f44 100644
--- a/data/doctrine-proxies/__CG__CSVImportEntityCSVImportImport.php
+++ b/data/doctrine-proxies/__CG__CSVImportEntityCSVImportImport.php
@@ -2,6 +2,7 @@
namespace DoctrineProxies\__CG__\CSVImport\Entity;
+
/**
* DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR
*/
@@ -12,39 +13,41 @@ class CSVImportImport extends \CSVImport\Entity\CSVImportImport implements \Doct
* three parameters, being respectively the proxy object to be initialized, the method that triggered the
* initialization process and an array of ordered parameters that were passed to that method.
*
- * @see \Doctrine\Common\Persistence\Proxy::__setInitializer
+ * @see \Doctrine\Common\Proxy\Proxy::__setInitializer
*/
public $__initializer__;
/**
* @var \Closure the callback responsible of loading properties that need to be copied in the cloned object
*
- * @see \Doctrine\Common\Persistence\Proxy::__setCloner
+ * @see \Doctrine\Common\Proxy\Proxy::__setCloner
*/
public $__cloner__;
/**
* @var boolean flag indicating if this object was already initialized
*
- * @see \Doctrine\Common\Persistence\Proxy::__isInitialized
+ * @see \Doctrine\Persistence\Proxy::__isInitialized
*/
public $__isInitialized__ = false;
/**
- * @var array properties to be lazy loaded, with keys being the property
- * names and values being their default values
+ * @var array properties to be lazy loaded, indexed by property name
+ */
+ public static $lazyPropertiesNames = array (
+);
+
+ /**
+ * @var array default values of properties to be lazy loaded, with keys being the property names
*
- * @see \Doctrine\Common\Persistence\Proxy::__getLazyProperties
+ * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties
*/
- public static $lazyPropertiesDefaults = [];
+ public static $lazyPropertiesDefaults = array (
+);
- /**
- * @param \Closure $initializer
- * @param \Closure $cloner
- */
- public function __construct($initializer = null, $cloner = null)
+ public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null)
{
$this->__initializer__ = $initializer;
@@ -82,7 +85,7 @@ public function __wakeup()
$existingProperties = get_object_vars($proxy);
- foreach ($proxy->__getLazyProperties() as $property => $defaultValue) {
+ foreach ($proxy::$lazyPropertiesDefaults as $property => $defaultValue) {
if ( ! array_key_exists($property, $existingProperties)) {
$proxy->$property = $defaultValue;
}
@@ -103,7 +106,7 @@ public function __clone()
/**
* Forces initialization of the proxy
*/
- public function __load()
+ public function __load(): void
{
$this->__initializer__ && $this->__initializer__->__invoke($this, '__load', []);
}
@@ -112,7 +115,7 @@ public function __load()
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
- public function __isInitialized()
+ public function __isInitialized(): bool
{
return $this->__isInitialized__;
}
@@ -121,7 +124,7 @@ public function __isInitialized()
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
- public function __setInitialized($initialized)
+ public function __setInitialized($initialized): void
{
$this->__isInitialized__ = $initialized;
}
@@ -130,7 +133,7 @@ public function __setInitialized($initialized)
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
- public function __setInitializer(\Closure $initializer = null)
+ public function __setInitializer(\Closure $initializer = null): void
{
$this->__initializer__ = $initializer;
}
@@ -139,7 +142,7 @@ public function __setInitializer(\Closure $initializer = null)
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
- public function __getInitializer()
+ public function __getInitializer(): ?\Closure
{
return $this->__initializer__;
}
@@ -148,7 +151,7 @@ public function __getInitializer()
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
- public function __setCloner(\Closure $cloner = null)
+ public function __setCloner(\Closure $cloner = null): void
{
$this->__cloner__ = $cloner;
}
@@ -157,7 +160,7 @@ public function __setCloner(\Closure $cloner = null)
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific cloning logic
*/
- public function __getCloner()
+ public function __getCloner(): ?\Closure
{
return $this->__cloner__;
}
@@ -165,9 +168,10 @@ public function __getCloner()
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
+ * @deprecated no longer in use - generated code now relies on internal components rather than generated public API
* @static
*/
- public function __getLazyProperties()
+ public function __getLazyProperties(): array
{
return self::$lazyPropertiesDefaults;
}
diff --git a/data/doctrine-proxies/__CG__CSVImportEntityCSVImportMapping.php b/data/doctrine-proxies/__CG__CSVImportEntityCSVImportMapping.php
new file mode 100644
index 00000000..11ca22aa
--- /dev/null
+++ b/data/doctrine-proxies/__CG__CSVImportEntityCSVImportMapping.php
@@ -0,0 +1,272 @@
+ properties to be lazy loaded, indexed by property name
+ */
+ public static $lazyPropertiesNames = array (
+);
+
+ /**
+ * @var array default values of properties to be lazy loaded, with keys being the property names
+ *
+ * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties
+ */
+ public static $lazyPropertiesDefaults = array (
+);
+
+
+
+ public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null)
+ {
+
+ $this->__initializer__ = $initializer;
+ $this->__cloner__ = $cloner;
+ }
+
+
+
+
+
+
+
+ /**
+ *
+ * @return array
+ */
+ public function __sleep()
+ {
+ if ($this->__isInitialized__) {
+ return ['__isInitialized__', 'id', 'name', 'created', 'mapping'];
+ }
+
+ return ['__isInitialized__', 'id', 'name', 'created', 'mapping'];
+ }
+
+ /**
+ *
+ */
+ public function __wakeup()
+ {
+ if ( ! $this->__isInitialized__) {
+ $this->__initializer__ = function (CSVImportMappingModel $proxy) {
+ $proxy->__setInitializer(null);
+ $proxy->__setCloner(null);
+
+ $existingProperties = get_object_vars($proxy);
+
+ foreach ($proxy::$lazyPropertiesDefaults as $property => $defaultValue) {
+ if ( ! array_key_exists($property, $existingProperties)) {
+ $proxy->$property = $defaultValue;
+ }
+ }
+ };
+
+ }
+ }
+
+ /**
+ *
+ */
+ public function __clone()
+ {
+ $this->__cloner__ && $this->__cloner__->__invoke($this, '__clone', []);
+ }
+
+ /**
+ * Forces initialization of the proxy
+ */
+ public function __load(): void
+ {
+ $this->__initializer__ && $this->__initializer__->__invoke($this, '__load', []);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @internal generated method: use only when explicitly handling proxy specific loading logic
+ */
+ public function __isInitialized(): bool
+ {
+ return $this->__isInitialized__;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @internal generated method: use only when explicitly handling proxy specific loading logic
+ */
+ public function __setInitialized($initialized): void
+ {
+ $this->__isInitialized__ = $initialized;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @internal generated method: use only when explicitly handling proxy specific loading logic
+ */
+ public function __setInitializer(\Closure $initializer = null): void
+ {
+ $this->__initializer__ = $initializer;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @internal generated method: use only when explicitly handling proxy specific loading logic
+ */
+ public function __getInitializer(): ?\Closure
+ {
+ return $this->__initializer__;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @internal generated method: use only when explicitly handling proxy specific loading logic
+ */
+ public function __setCloner(\Closure $cloner = null): void
+ {
+ $this->__cloner__ = $cloner;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @internal generated method: use only when explicitly handling proxy specific cloning logic
+ */
+ public function __getCloner(): ?\Closure
+ {
+ return $this->__cloner__;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @internal generated method: use only when explicitly handling proxy specific loading logic
+ * @deprecated no longer in use - generated code now relies on internal components rather than generated public API
+ * @static
+ */
+ public function __getLazyProperties(): array
+ {
+ return self::$lazyPropertiesDefaults;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getId()
+ {
+ if ($this->__isInitialized__ === false) {
+ return (int) parent::getId();
+ }
+
+
+ $this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []);
+
+ return parent::getId();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getName()
+ {
+
+ $this->__initializer__ && $this->__initializer__->__invoke($this, 'getName', []);
+
+ return parent::getName();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setName($name)
+ {
+
+ $this->__initializer__ && $this->__initializer__->__invoke($this, 'setName', [$name]);
+
+ return parent::setName($name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMapping()
+ {
+
+ $this->__initializer__ && $this->__initializer__->__invoke($this, 'getMapping', []);
+
+ return parent::getMapping();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setMapping($mapping)
+ {
+
+ $this->__initializer__ && $this->__initializer__->__invoke($this, 'setMapping', [$mapping]);
+
+ return parent::setMapping($mapping);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getCreated()
+ {
+
+ $this->__initializer__ && $this->__initializer__->__invoke($this, 'getCreated', []);
+
+ return parent::getCreated();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function prePersist(\Doctrine\ORM\Event\LifecycleEventArgs $eventArgs)
+ {
+
+ $this->__initializer__ && $this->__initializer__->__invoke($this, 'prePersist', [$eventArgs]);
+
+ return parent::prePersist($eventArgs);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getResourceId()
+ {
+
+ $this->__initializer__ && $this->__initializer__->__invoke($this, 'getResourceId', []);
+
+ return parent::getResourceId();
+ }
+
+}
diff --git a/src/Api/Adapter/MappingModelAdapter.php b/src/Api/Adapter/MappingModelAdapter.php
new file mode 100644
index 00000000..22f10dc3
--- /dev/null
+++ b/src/Api/Adapter/MappingModelAdapter.php
@@ -0,0 +1,62 @@
+getContent();
+
+ if (isset($data['name'])) {
+ $entity->setName($data['name']);
+ }
+
+ if (isset($data['mapping'])) {
+ $entity->setMapping($data['mapping']);
+ }
+ }
+
+ public function validateEntity(EntityInterface $entity, ErrorStore $errorStore)
+ {
+ if (!$entity->getName()) {
+ $errorStore->addError('o-module-csvimport-mappingmodel:name', 'A model must have a name to save it.'); // @translate
+ }
+
+ if (!$entity->getMapping()) {
+ $errorStore->addError('o-module-csvimport-mappingmodel:mapping', 'Mapping model must exists.'); // @translate
+ }
+ }
+
+ public function buildQuery(QueryBuilder $qb, array $query)
+ {
+ if (isset($query['name'])) {
+ $qb->andWhere($qb->expr()->eq(
+ 'omeka_root.name',
+ $this->createNamedParameter($qb, $query['name']))
+ );
+ }
+ }
+}
diff --git a/src/Api/Representation/MappingModelRepresentation.php b/src/Api/Representation/MappingModelRepresentation.php
new file mode 100644
index 00000000..748ab3af
--- /dev/null
+++ b/src/Api/Representation/MappingModelRepresentation.php
@@ -0,0 +1,55 @@
+ $this->name(),
+ 'mapping' => $this->mapping(),
+ 'created' => $this->created(),
+ ];
+ }
+
+ public function getJsonLdType()
+ {
+ return 'o:CSVimportMappingModel';
+ }
+
+ public function name()
+ {
+ return $this->resource->getName();
+ }
+
+ public function mapping()
+ {
+ return $this->resource->getMapping();
+ }
+
+ public function created()
+ {
+ return $this->resource->getCreated();
+ }
+
+ public function adminUrl($action = null, $canonical = false)
+ {
+ $url = $this->getViewHelper('Url');
+ return $url(
+ 'admin/csvimport/mapping-model-id',
+ [
+ 'controller' => $this->getControllerName(),
+ 'action' => $action,
+ 'id' => $this->id(),
+ ],
+ ['force_canonical' => $canonical]
+ );
+ }
+}
diff --git a/src/Controller/Admin/MappingModelController.php b/src/Controller/Admin/MappingModelController.php
new file mode 100644
index 00000000..e9671029
--- /dev/null
+++ b/src/Controller/Admin/MappingModelController.php
@@ -0,0 +1,243 @@
+setBrowseDefaults('created');
+ $response = $this->api()->search('csvimport_mapping_models');
+
+ $this->paginator($response->getTotalResults());
+
+ $formDeleteSelected = $this->getForm(ConfirmForm::class);
+ $formDeleteSelected->setAttribute('action', $this->url()->fromRoute(null, ['action' => 'batch-delete'], true));
+ $formDeleteSelected->setButtonLabel('Confirm Delete'); // @translate
+ $formDeleteSelected->setAttribute('id', 'confirm-delete-selected');
+
+ $view = new ViewModel;
+ $mappingModels = $response->getContent();
+ $view->setVariable('mappingModels', $mappingModels);
+ $view->setVariable('formDeleteSelected', $formDeleteSelected);
+ return $view;
+ }
+
+ public function showAction()
+ {
+ $propertiesMap = [];
+ $properties = $this->api()->search('properties')->getContent();
+ foreach ($properties as $property) {
+ $propertiesMap[$property->id()] = $property->term();
+ }
+
+ $mappingModel = $this->loadMappingModel($this->params('id'), []);
+
+ $view = new ViewModel;
+ $view->setVariable('propertiesMap', $propertiesMap);
+ $view->setVariable('columns', $mappingModel['columns']);
+ unset($mappingModel['columns']);
+ $this->logger()->debug(json_encode($mappingModel));
+ $view->setVariable('automaps', $mappingModel);
+ return $view;
+ }
+
+ public function saveAction()
+ {
+ $response = $this->api()->search('csvimport_mapping_models');
+ $mappings = $response->getContent();
+ $mappingNames = [];
+ foreach ($mappings as $mapping) {
+ $mappingNames[] = $mapping->name();
+ }
+
+ $query = $this->params()->fromQuery();
+
+ $jobId = null;
+ if ($this->getRequest()->isPost()) {
+ if (!empty($this->params()->fromPost()['job_id']))
+ $jobId = $this->params()->fromPost()['job_id'];
+ else {
+ $this->messenger()->addError('Job id not provided.'); // @translate;
+ return $this->redirect()->toRoute('admin/csvimport/past-imports', ['action' => 'browse'], true);
+ }
+ }
+ else {
+ if (!empty($query['job_id'])) {
+ $jobId = $query['job_id'];
+ }
+ else {
+ return $this->getResponse()->setStatusCode(404)->setContent('Job id not provided.'); // @translate
+ }
+ }
+
+ $view = new ViewModel;
+ $form = $this->getForm(MappingModelSaveForm::class, ['job_id' => $jobId]);
+ $view->setVariable('form', $form);
+ $view->setTerminal(true);
+ $view->setTemplate('csv-import/admin/mapping-model/save-mapping-model');
+ $view->setVariable('mappings', $mappingNames);
+
+ if ($this->getRequest()->isPost()) {
+ $data = $this->params()->fromPost();
+ $form->setData($data);
+
+ if ($form->isValid()) {
+
+ $job = null;
+ $job = $this->api()->read('jobs', $jobId)->getContent();
+ if (empty($job)) {
+ $this->messenger()->addError('Could not find job with id %s.', $jobId); // @translate;
+ return $this->redirect()->toRoute('admin/csvimport/past-imports', ['action' => 'browse'], true);
+ }
+
+ $args = $job->args();
+ $args['override_mapping'] = $data['override_mapping'] ?? null;
+ $args['mapping_name'] = $data['mapping_name'];
+ if (!$this->saveMappingModel($args)) {
+ // TODO Keep user variables when the form is invalid.
+ $this->messenger()->addError('A mapping model with that name already exists.'); // @translate
+ return $this->redirect()->toRoute('admin/csvimport/past-imports', ['action' => 'browse'], true);
+ }
+ else {
+ $this->messenger()->addSuccess(sprintf('Mapping successfully saved as %s.', // @translate
+ $data['mapping_name']));
+ return $this->redirect()->toRoute('admin/csvimport/past-imports', ['action' => 'browse'], true);
+ }
+ }
+ $this->messenger()->addFormErrors($form);
+ return $this->redirect()->toRoute('admin/csvimport/past-imports', ['action' => 'browse'], true);
+ }
+
+ return $view;
+ }
+
+ /*
+ * meant for JS
+ */
+ public function selectMappingModelAction()
+ {
+ $response = $this->api()->search('csvimport_mapping_models');
+ $mappings = $response->getContent();
+
+ $view = new ViewModel;
+ $form = $this->getForm(MappingModelSelectForm::class);
+ $view->setVariable('form', $form);
+ $view->setTerminal(true);
+ $view->setTemplate('csv-import/admin/mapping-model/select-mapping-model');
+
+ if ($this->getRequest()->isPost()) {
+ $data = $this->params()->fromPost();
+ $form->setData($data);
+ if ($form->isValid()) {
+ $mappingId = $form->get('mapping_id')->getValue();
+
+ $session = new \Laminas\Session\Container('CsvImport');
+
+ $columns = $session->columns;
+
+ $response = $this->api()->read('csvimport_mapping_models', $mappingId);
+ if ($response) {
+ $view = new ViewModel([
+ 'automaps' => $this->loadMappingModel($mappingId, $columns),
+ 'columns' => $columns,
+ 'resourceType' => $session->resourceType,
+ ]);
+ $view->setTemplate('common/mapping-table');
+ $view->setTerminal(true); // no layout
+ return $view;
+ } else {
+ return $this->getResponse()->setStatusCode(404)->setContent('Mapping model not found.'); // @translate
+ }
+ }
+ return $this->getResponse()->setStatusCode(400)->setContent('Mapping model selection form not valid.');
+ }
+
+ return $view;
+ }
+
+ public function editAction()
+ {
+ $response = $this->api()->read('csvimport_mapping_models', $this->params('id'));
+ $mapping = $response->getContent();
+
+ $view = new ViewModel;
+ $form = $this->getForm(MappingModelEditForm::class);
+ $form->setAttribute('action', $mapping->url('edit'));
+ $form->setData([
+ 'model_name' => $mapping->name(),
+ ]);
+
+ $view->setVariable('form', $form);
+ $view->setTerminal(true);
+ $view->setTemplate('csv-import/admin/mapping-model/edit');
+ $view->setVariable('mapping', $mapping);
+
+ if ($this->getRequest()->isPost()) {
+ $data = $this->params()->fromPost();
+ $form->setData($data);
+ if ($form->isValid()) {
+ $mappingName = $form->get('model_name')->getValue();
+
+ $response = $this->api($form)->update('csvimport_mapping_models', $this->params('id'), ['name' => $mappingName], [], ['isPartial' => true]);
+ if ($response) {
+ $this->messenger()->addSuccess('Mapping model successfully updated'); // @translate
+ return $this->redirect()->toRoute(
+ 'admin/csvimport/mapping-model',
+ ['action' => 'browse'],
+ true
+ );
+ }
+ } else {
+ $this->messenger()->addFormErrors($form);
+ return $this->redirect()->toRoute(
+ 'admin/csvimport/mapping-model',
+ ['action' => 'browse'],
+ true
+ );
+ }
+ }
+ return $view;
+ }
+
+ public function deleteConfirmAction()
+ {
+ $response = $this->api()->read('csvimport_mapping_models', $this->params('id'));
+ $mappingModel = $response->getContent();
+
+ $view = new ViewModel;
+ $view->setTerminal(true);
+ $view->setTemplate('common/delete-confirm-details');
+ $view->setVariable('resource', $mappingModel);
+ $view->setVariable('resourceLabel', 'Mapping'); // @translate
+ return $view;
+ }
+
+ public function deleteAction()
+ {
+ if ($this->getRequest()->isPost()) {
+ $form = $this->getForm(ConfirmForm::class);
+ $form->setData($this->getRequest()->getPost());
+ if ($form->isValid()) {
+ $response = $this->api($form)->delete('csvimport_mapping_models', $this->params('id'));
+ if ($response) {
+ $this->messenger()->addSuccess('Mapping model successfully deleted'); // @translate
+ }
+ } else {
+ $this->messenger()->addFormErrors($form);
+ }
+ }
+ return $this->redirect()->toRoute(
+ 'admin/csvimport/mapping-model',
+ ['action' => 'browse'],
+ true
+ );
+ }
+}
diff --git a/src/Controller/IndexController.php b/src/Controller/IndexController.php
index 3e3073b2..11608b97 100644
--- a/src/Controller/IndexController.php
+++ b/src/Controller/IndexController.php
@@ -2,7 +2,7 @@
namespace CSVImport\Controller;
use CSVImport\Form\ImportForm;
-use CSVImport\Form\MappingForm;
+use CSVImport\Form\MappingModelForm;
use CSVImport\Source\SourceInterface;
use CSVImport\Job\Import;
use finfo;
@@ -123,8 +123,13 @@ public function mapAction()
return $this->redirect()->toRoute('admin/csvimport');
}
+ $session = new \Laminas\Session\Container('CsvImport');
+
+ $session->columns = $columns;
+ $session->resourceType = $resourceType;
+
$mappingOptions['columns'] = $columns;
- $form = $this->getForm(MappingForm::class, $mappingOptions);
+ $form = $this->getForm(MappingModelForm::class, $mappingOptions);
$automapOptions = [];
$automapOptions['check_names_alone'] = $args['automap_check_names_alone'];
@@ -143,9 +148,10 @@ public function mapAction()
$view->setVariable('mappings', $this->getMappingsForResource($resourceType));
$view->setVariable('mediaForms', $this->getMediaForms());
$view->setVariable('dataTypes', $this->getDataTypes());
+
return $view;
} else {
- $form = $this->getForm(MappingForm::class, $mappingOptions);
+ $form = $this->getForm(MappingModelForm::class, $mappingOptions);
$form->setData($post);
if ($form->isValid()) {
if (isset($post['basic-settings']) || isset($post['advanced-settings'])) {
@@ -155,7 +161,10 @@ public function mapAction()
}
$args = $this->cleanArgs($post);
+ $session = new \Laminas\Session\Container('CsvImport');
+ $args['columns'] = $session->columns;
$this->saveUserSettings($args);
+
$dispatcher = $this->jobDispatcher();
$job = $dispatcher->dispatch('CSVImport\Job\Import', $args);
// The CsvImport record is created in the job, so it doesn't
diff --git a/src/Entity/CSVImportMappingModel.php b/src/Entity/CSVImportMappingModel.php
new file mode 100644
index 00000000..c247d567
--- /dev/null
+++ b/src/Entity/CSVImportMappingModel.php
@@ -0,0 +1,87 @@
+id;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ public function getMapping()
+ {
+ return $this->mapping;
+ }
+
+ public function setMapping($mapping)
+ {
+ $this->mapping = $mapping;
+
+ return $this;
+ }
+
+ public function getCreated()
+ {
+ return $this->created;
+ }
+
+ /**
+ * @PrePersist
+ */
+ public function prePersist(LifecycleEventArgs $eventArgs)
+ {
+ $this->created = new DateTime('now');
+ }
+}
diff --git a/src/Form/Element/MappingModelSelect.php b/src/Form/Element/MappingModelSelect.php
new file mode 100644
index 00000000..f929f395
--- /dev/null
+++ b/src/Form/Element/MappingModelSelect.php
@@ -0,0 +1,38 @@
+apiManager = $apiManager;
+ }
+
+ /**
+ * @return ApiManager
+ */
+ public function getApiManager()
+ {
+ return $this->apiManager;
+ }
+
+ public function getValueOptions(): array
+ {
+ $valueOptions = [];
+ $mappings = $this->apiManager->search("csvimport_mapping_models", [])->getContent();
+ foreach ($mappings as $mapping) {
+ $valueOptions[$mapping->id()] = $mapping->name();
+ }
+ return $valueOptions;
+ }
+}
diff --git a/src/Form/MappingModelEditForm.php b/src/Form/MappingModelEditForm.php
new file mode 100644
index 00000000..4dc5788b
--- /dev/null
+++ b/src/Form/MappingModelEditForm.php
@@ -0,0 +1,22 @@
+add([
+ 'name' => 'model_name',
+ 'type' => 'text',
+ 'options' => [
+ 'label' => 'Mapping model name', //@translate
+ ],
+ 'attributes' => [
+ 'required' => true,
+ ],
+ ]);
+ }
+}
diff --git a/src/Form/MappingForm.php b/src/Form/MappingModelForm.php
similarity index 97%
rename from src/Form/MappingForm.php
rename to src/Form/MappingModelForm.php
index 2db56d78..a5e4e4ce 100644
--- a/src/Form/MappingForm.php
+++ b/src/Form/MappingModelForm.php
@@ -9,7 +9,7 @@
use Omeka\Form\Element\SiteSelect;
use Laminas\Form\Form;
-class MappingForm extends Form
+class MappingModelForm extends Form
{
protected $serviceLocator;
@@ -84,7 +84,7 @@ public function init()
'type' => ResourceSelect::class,
'options' => [
'label' => 'Resource template', // @translate
- 'info' => 'Assign a resource template to all imported resources. Specific mappings can override this setting.', // @translate
+ 'info' => 'Assign a resource template to all imported resources. Specific mapping models can override this setting.', // @translate
'empty_option' => 'Select a template', // @translate
'resource_value_options' => [
'resource' => 'resource_templates',
@@ -107,7 +107,7 @@ public function init()
'type' => ResourceClassSelect::class,
'options' => [
'label' => 'Class', // @translate
- 'info' => 'Assign a resource class to all imported resources. Specific mappings can override this setting.', // @translate
+ 'info' => 'Assign a resource class to all imported resources. Specific mapping models can override this setting.', // @translate
'empty_option' => 'Select a class', // @translate
],
'attributes' => [
@@ -153,7 +153,7 @@ public function init()
'type' => 'radio',
'options' => [
'label' => 'Visibility', // @translate
- 'info' => 'Set visibility for all imported resources. Specific mappings can override this setting.', // @translate
+ 'info' => 'Set visibility for all imported resources. Specific mapping models can override this setting.', // @translate
'value_options' => [
'1' => 'Public', // @translate
'0' => 'Private', // @translate
@@ -171,7 +171,7 @@ public function init()
'type' => 'radio',
'options' => [
'label' => 'Open/closed to additions', // @translate
- 'info' => 'Set whether imported item sets are open to additions. Specific mappings can override this setting.', // @translate
+ 'info' => 'Set whether imported item sets are open to additions. Specific mapping models can override this setting.', // @translate
'value_options' => [
'1' => 'Open', // @translate
'0' => 'Closed', // @translate
@@ -221,7 +221,7 @@ public function init()
'type' => 'radio',
'options' => [
'label' => 'Item sets open/closed to additions', // @translate
- 'info' => 'Set whether imported item sets are open to additions. Specific mappings can override this setting.', // @translate
+ 'info' => 'Set whether imported item sets are open to additions. Specific mapping models can override this setting.', // @translate
'value_options' => [
'1' => 'Open', // @translate
'0' => 'Closed', // @translate
@@ -284,7 +284,7 @@ public function init()
'type' => 'text',
'options' => [
'label' => 'Language', // @translate
- 'info' => 'Language setting to apply to all imported literal data. Individual property mappings can override the setting here.', // @translate
+ 'info' => 'Language setting to apply to all imported literal data. Individual property mapping models can override the setting here.', // @translate
],
'attributes' => [
'id' => 'global_language',
diff --git a/src/Form/MappingModelSaveForm.php b/src/Form/MappingModelSaveForm.php
new file mode 100644
index 00000000..361e83e4
--- /dev/null
+++ b/src/Form/MappingModelSaveForm.php
@@ -0,0 +1,41 @@
+setAttribute('action', 'mapping-model/save');
+
+ $this->add([
+ 'name'=> 'job_id',
+ 'type'=> 'hidden',
+ 'attributes' => [
+ 'value' => $this->getOption('job_id'),
+ ]
+ ]);
+
+ $this->add([
+ 'name' => 'mapping_name',
+ 'type' => 'text',
+ 'options' => [
+ 'label' => 'Mapping model name', //@translate
+ ],
+ 'attributes' => [
+ 'required' => true,
+ ],
+ ]);
+ $this->add([
+ 'name' => 'override_mapping',
+ 'type' => Checkbox::class,
+ 'options' => [
+ 'label' => 'Override mapping model if it already exists?', //@translate
+ ],
+ ]);
+ }
+}
diff --git a/src/Form/MappingModelSelectForm.php b/src/Form/MappingModelSelectForm.php
new file mode 100644
index 00000000..467f49b9
--- /dev/null
+++ b/src/Form/MappingModelSelectForm.php
@@ -0,0 +1,31 @@
+setAttribute('action', '/admin/csvimport/mapping-model/selectMappingModel');
+
+ $this->add([
+ 'name' => 'mapping_id',
+ 'type' => MappingModelSelect::class,
+ 'attributes' => [
+ 'id' => 'mapping-id',
+ 'class' => 'chosen-select',
+ 'multiple' => false,
+ 'data-placeholder' => '',
+ ],
+ 'options' => [
+ 'label' => 'Select mapping model', // @translate
+ 'resource_value_options' => [
+ 'resource' => 'csvimport_mapping_models',
+ 'query' => [],
+ ],
+ ],
+ ]);
+ }
+}
diff --git a/src/Job/Import.php b/src/Job/Import.php
index 614b13ae..a8d68e33 100644
--- a/src/Job/Import.php
+++ b/src/Job/Import.php
@@ -504,7 +504,7 @@ protected function checkMedias(array $data)
$this->hasErr = true;
$this->logger->err(new Message('A media to create is not attached to an item (%s).', // @translate
empty($entityJson['o:source'])
- ? (isset($entityJson['o:ingester']) ? $entityJson['o:ingester'] : 'unknown ingester') // @translate
+ ? ($entityJson['o:ingester'] ?? 'unknown ingester') // @translate
: $entityJson['o:ingester'] . ': ' . $entityJson['o:source']));
}
}
diff --git a/src/Mapping/AbstractResourceMapping.php b/src/Mapping/AbstractResourceMapping.php
index b86bc92a..45d98cd3 100644
--- a/src/Mapping/AbstractResourceMapping.php
+++ b/src/Mapping/AbstractResourceMapping.php
@@ -49,7 +49,7 @@ public function processRow(array $row)
// First, pull in the global settings.
$this->processGlobalArgs();
- $multivalueMap = isset($this->args['column-multivalue']) ? $this->args['column-multivalue'] : [];
+ $multivalueMap = $this->args['column-multivalue'] ?? [];
$multivalueSeparator = $this->args['multivalue_separator'];
foreach ($row as $index => $values) {
if (empty($multivalueMap[$index])) {
diff --git a/src/Mapping/MediaSourceMapping.php b/src/Mapping/MediaSourceMapping.php
index 5a43e9fa..475d0d49 100644
--- a/src/Mapping/MediaSourceMapping.php
+++ b/src/Mapping/MediaSourceMapping.php
@@ -51,7 +51,7 @@ public function processRow(array $row)
$mediaAdapters = $config['media_ingester_adapter'];
$action = $this->args['action'];
- $multivalueMap = isset($this->args['column-multivalue']) ? $this->args['column-multivalue'] : [];
+ $multivalueMap = $this->args['column-multivalue'] ?? [];
$multivalueSeparator = $this->args['multivalue_separator'];
foreach ($row as $index => $values) {
if (isset($mediaMap[$index])) {
diff --git a/src/Mapping/PropertyMapping.php b/src/Mapping/PropertyMapping.php
index cf866be5..e1a6725b 100644
--- a/src/Mapping/PropertyMapping.php
+++ b/src/Mapping/PropertyMapping.php
@@ -58,12 +58,12 @@ public function processRow(array $row)
$dataTypeAdapters = $this->getDataTypeAdapters();
// Get default option values.
- $globalLanguage = isset($this->args['global_language']) ? $this->args['global_language'] : '';
+ $globalLanguage = $this->args['global_language'] ?? '';
- $multivalueMap = isset($this->args['column-multivalue']) ? $this->args['column-multivalue'] : [];
+ $multivalueMap = $this->args['column-multivalue'] ?? [];
$multivalueSeparator = $this->args['multivalue_separator'];
- $resourceIdentifierPropertyMap = isset($this->args['column-resource-identifier-property']) ? $this->args['column-resource-identifier-property'] : [];
+ $resourceIdentifierPropertyMap = $this->args['column-resource-identifier-property'] ?? [];
$findResourceFromIdentifier = $this->findResourceFromIdentifier;
foreach ($row as $index => $values) {
@@ -106,7 +106,7 @@ public function processRow(array $row)
// Check if a label is provided after the url.
// Note: A url has no space, but a uri may have.
if (strpos($value, ' ')) {
- list($valueId, $valueLabel) = explode(' ', $value, 2);
+ [$valueId, $valueLabel] = explode(' ', $value, 2);
$valueLabel = trim($valueLabel);
} else {
$valueId = $value;
diff --git a/src/Mvc/Controller/Plugin/LoadMappingModel.php b/src/Mvc/Controller/Plugin/LoadMappingModel.php
new file mode 100644
index 00000000..ad498c18
--- /dev/null
+++ b/src/Mvc/Controller/Plugin/LoadMappingModel.php
@@ -0,0 +1,174 @@
+connection = $connection;
+ $this->api = $apiManager;
+ $this->logger = $logger;
+ }
+
+ /**
+ *
+ */
+ public function __invoke(int $id, array $columns)
+ {
+ // Fetch the mapping
+ $mapping = $this->api->read('csvimport_mapping_models', $id)->getContent();
+
+ if (empty($mapping)) {
+ // $this->logger()->debug(sprintf('No mapping with id %s found.', $id));
+ return [];
+ }
+
+ $mappingValue = json_decode($mapping->mapping(), true);
+
+ $originalColumns = $mappingValue['columns'];
+ unset($mappingValue['columns']); // so we don't iterate over it after like it's an actual mapping
+
+ // $this->logger->debug(sprintf('Old columns : ' . PHP_EOL . '%s' . PHP_EOL . 'New columns:' . PHP_EOL . '%s' . PHP_EOL,
+ // json_encode($originalColumns), json_encode($columns)));
+
+ // Find the mapping between old column indexes and current columns, according to name matching
+ $oldToNewColumn = [];
+ if (!empty($columns)) {
+ foreach ($columns as $index => $column) {
+ $columnMapping = array_search($column, $originalColumns);
+
+ if ($columnMapping !== false) {
+ $oldToNewColumn[strval($columnMapping)] = $index;
+ }
+ }
+ } else {
+ foreach ($originalColumns as $index => $column) {
+ $oldToNewColumn[strval($index)] = $index;
+ }
+ }
+
+ // $this->logger->debug(sprintf('Column mapping: ' . PHP_EOL . '%s' . PHP_EOL, json_encode($oldToNewColumn)));
+ // $this->logger->debug(sprintf('Mapping in : ' . PHP_EOL . '%s' . PHP_EOL, json_encode($mappingValue)));
+
+ $automap = [];
+
+ // if no columns passed as argument it means there are no new columns, so we remind of the original ones
+ // used in MappingController
+ if (empty($columns)) {
+ $automap['columns'] = $originalColumns;
+ }
+
+ /*
+ * Reading each entry that was sent by the user in the form
+ * The structure is complicated and undocumented.
+ */
+ foreach ($mappingValue as $mappingValueColumnName => $mappingValueColumn) {
+ $name = "";
+ if (str_contains($mappingValueColumnName, 'column-')) {
+ $name = explode('column-', $mappingValueColumnName)[1];
+
+ // properties of type property
+ if ($name == 'property') {
+ foreach ($mappingValueColumn as $index => $property) {
+ if (array_key_exists($index, $oldToNewColumn)) {
+ foreach ($property as $subindex => $subproperty) {
+ $element = [];
+ $element["name"] = $name;
+ $element["class"] = $name;
+ $element["multiple"] = true;
+ $element["special"] = " data-property-id=\"" . $subproperty . "\"";
+ $element["label"] = explode(':', $subindex)[1] ?? $subindex;
+ $element["value"] = $subproperty;
+ $automap[$oldToNewColumn[$index]][] = $element;
+ }
+ }
+ }
+ }
+
+ // these are column options. Only one per column, for data simplicity we put it in $automap[ColumnIndex][0]
+ elseif ($name == "data-type"
+ || $name == "multivalue"
+ || $name == "language"
+ || $name == "private-values"
+ || $name == "resource-identifier-property") {
+ foreach ($mappingValueColumn as $index => $property) {
+ if (array_key_exists($index, $oldToNewColumn)) {
+ $automap[$oldToNewColumn[$index]][0][$name] = $property;
+ }
+ }
+ }
+
+ // these are mappings that are other than properties
+ else {
+ foreach ($mappingValueColumn as $index => $property) {
+ if (array_key_exists($index, $oldToNewColumn)) {
+ $element = [];
+ $element["name"] = $name;
+ $element["class"] = $name;
+ $element["multiple"] = false;
+ $element["special"] = "";
+ $element["value"] = $property;
+ $element["label"] = "label"; // @tdodo
+ $automap[$oldToNewColumn[$index]][] = $element;
+ }
+ }
+ }
+ }
+ }
+
+ return $automap;
+ }
+}
diff --git a/src/Mvc/Controller/Plugin/SaveMappingModel.php b/src/Mvc/Controller/Plugin/SaveMappingModel.php
new file mode 100644
index 00000000..a767372d
--- /dev/null
+++ b/src/Mvc/Controller/Plugin/SaveMappingModel.php
@@ -0,0 +1,146 @@
+connection = $connection;
+ $this->api = $apiManager;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Save mapping.
+ */
+ public function __invoke(array $args): bool
+ {
+ $shouldOverrideMapping = false;
+ $mappingName = $args['mapping_name'];
+
+ // $this->logger->debug('[CSVImport] Mapping model name.');
+ // $this->logger->debug(json_encode($mappingName));
+
+ $alreadyExistsContent = $this->api->search('csvimport_mapping_models', ['name' => $mappingName])->getContent();
+ if (count($alreadyExistsContent) > 0) {
+ if (!empty($args['override_mapping']))
+ {
+ $shouldOverrideMapping = true;
+ }
+ else
+ {
+ // $this->logger->debug('[CSVImport] Already existing mapping model.');
+ // $this->logger->debug(json_encode($alreadyExistsContent));
+ return false;
+ }
+ }
+
+ if (empty($args['columns'])) {
+ // We first need to read the file to get the column names
+ // Because we need to remember the column names
+ $filePath = $args['filepath'];
+ $fileName = $args['filename'];
+
+ // Check if file exists and is readable
+ if (!file_exists($filePath) || !is_readable($filePath)) {
+ // $this->logger->err(sprintf("[CSV Import]: File '%s' not found when saving mapping model.", $filePath)); // @translate
+ }
+
+ // Open the file for reading
+ if (($handle = fopen($filePath, 'r')) !== false) {
+ // Read the first line as CSV (header row)
+ $args['columns'] = fgetcsv($handle);
+
+ // Close file
+ fclose($handle);
+
+ // Output the column names
+ if (!$args['columns']) {
+ // $this->logger->err(sprintf("[CSV Import]: Unable to read columns when saving mapping model.")); // @translate
+ }
+ } else {
+ // $this->logger->err(sprintf("[CSV Import]: File '%s' could not be opened when saving mapping model.", $filePath)); // @translate
+ }
+
+ if (empty($args['columns'])) {
+ // $this->logger->err(sprintf("[CSV Import]: Unable to get columns from file '%s'.", $filePath)); // @translate
+ }
+ }
+
+ // $this->logger->debug(sprintf("[CSV Import] Column names: " . PHP_EOL . "%s" . PHP_EOL, json_encode($args["columns"])));
+
+ // don't save irrelevant data
+ unset($args['filename']);
+ unset($args['filesize']);
+ unset($args['filepath']);
+ unset($args['media_type']);
+ unset($args['resource_type']);
+ unset($args['automap_check_names_alone']);
+ unset($args['mapping_name']);
+ unset($args['override_mapping']);
+
+ // $this->logger->debug(sprintf('[CSV Import: Args to be saved my mapping model]' . PHP_EOL . '%s' . PHP_EOL, json_encode($args)));
+
+ if ($shouldOverrideMapping) {
+ $this->api->update('csvimport_mapping_models', ['name' => $mappingName], ['mapping' => json_encode($args)], [], ['isPartial' => true]);
+ }
+ else {
+ $this->api->create('csvimport_mapping_models', ['mapping' => json_encode($args), 'name' => $mappingName]);
+ }
+
+ return true;
+ }
+}
diff --git a/src/Service/Controller/Admin/MappingModelControllerFactory.php b/src/Service/Controller/Admin/MappingModelControllerFactory.php
new file mode 100644
index 00000000..07b98201
--- /dev/null
+++ b/src/Service/Controller/Admin/MappingModelControllerFactory.php
@@ -0,0 +1,17 @@
+get('Omeka\Connection'),
+ $services->get('Omeka\ApiManager'),
+ $services->get('Omeka\Logger')
+ );
+ }
+}
diff --git a/src/Service/ControllerPlugin/SaveMappingModelFactory.php b/src/Service/ControllerPlugin/SaveMappingModelFactory.php
new file mode 100644
index 00000000..5040ec98
--- /dev/null
+++ b/src/Service/ControllerPlugin/SaveMappingModelFactory.php
@@ -0,0 +1,18 @@
+get('Omeka\Connection'),
+ $services->get('Omeka\ApiManager'),
+ $services->get('Omeka\Logger')
+ );
+ }
+}
diff --git a/src/Service/Form/Element/MappingModelSelectFactory.php b/src/Service/Form/Element/MappingModelSelectFactory.php
new file mode 100644
index 00000000..3471fc5e
--- /dev/null
+++ b/src/Service/Form/Element/MappingModelSelectFactory.php
@@ -0,0 +1,16 @@
+setApiManager($services->get('Omeka\ApiManager'));
+ return $element;
+ }
+}
diff --git a/src/Service/Form/MappingFormFactory.php b/src/Service/Form/MappingModelFormFactory.php
similarity index 67%
rename from src/Service/Form/MappingFormFactory.php
rename to src/Service/Form/MappingModelFormFactory.php
index 38c3639d..766f994e 100644
--- a/src/Service/Form/MappingFormFactory.php
+++ b/src/Service/Form/MappingModelFormFactory.php
@@ -1,15 +1,15 @@
setServiceLocator($services);
return $form;
}
diff --git a/src/Service/Form/MappingModelSaveFormFactory.php b/src/Service/Form/MappingModelSaveFormFactory.php
new file mode 100644
index 00000000..8cb5892a
--- /dev/null
+++ b/src/Service/Form/MappingModelSaveFormFactory.php
@@ -0,0 +1,21 @@
+setOption("job_id", $options["job_id"]);
+ return $form;
+ }
+}
diff --git a/view/common/mapping-table.phtml b/view/common/mapping-table.phtml
new file mode 100644
index 00000000..2e94adb7
--- /dev/null
+++ b/view/common/mapping-table.phtml
@@ -0,0 +1,87 @@
+plugin('escapeHtml'); ?>
+
\ No newline at end of file
diff --git a/view/csv-import/admin/mapping-model/browse.phtml b/view/csv-import/admin/mapping-model/browse.phtml
new file mode 100644
index 00000000..c1f2670d
--- /dev/null
+++ b/view/csv-import/admin/mapping-model/browse.phtml
@@ -0,0 +1,117 @@
+plugin('translate');
+$escape = $this->plugin('escapeHtml');
+$this->htmlElement('body')->appendAttribute('class', 'mappings browse');
+?>
+
+pageTitle($translate('Mapping models'), 1, $this->translate('CSV Import')); ?>
+
+trigger('view.browse.before'); ?>
+
+
+
+trigger('view.browse.after'); ?>
+
+
+
+
+
+
+
+
diff --git a/view/csv-import/admin/mapping-model/edit.phtml b/view/csv-import/admin/mapping-model/edit.phtml
new file mode 100644
index 00000000..2b0a6331
--- /dev/null
+++ b/view/csv-import/admin/mapping-model/edit.phtml
@@ -0,0 +1,14 @@
+prepare(); ?>
+
+pageTitle($this->translate('Edit mapping model'), 3); ?>
+
+form()->openTag($form); ?>
+
+formCollection($form); ?>
+
+
+
+form()->closeTag($form); ?>
+
diff --git a/view/csv-import/admin/mapping-model/save-mapping-model.phtml b/view/csv-import/admin/mapping-model/save-mapping-model.phtml
new file mode 100644
index 00000000..88d7acd8
--- /dev/null
+++ b/view/csv-import/admin/mapping-model/save-mapping-model.phtml
@@ -0,0 +1,43 @@
+prepare(); ?>
+
+pageTitle($this->translate('Save mapping model'), 3); ?>
+
+form()->openTag($form); ?>
+
+formCollection($form); ?>
+
+
+
+form()->closeTag($form); ?>
+
+
\ No newline at end of file
diff --git a/view/csv-import/admin/mapping-model/select-mapping-model.phtml b/view/csv-import/admin/mapping-model/select-mapping-model.phtml
new file mode 100644
index 00000000..f513d01d
--- /dev/null
+++ b/view/csv-import/admin/mapping-model/select-mapping-model.phtml
@@ -0,0 +1,16 @@
+prepare(); ?>
+
+pageTitle($this->translate('Select mapping model'), 3); ?>
+
+form()->openTag($form); ?>
+
+formCollection($form); ?>
+
+
+
+form()->closeTag($form); ?>
+
diff --git a/view/csv-import/admin/mapping-model/show.phtml b/view/csv-import/admin/mapping-model/show.phtml
new file mode 100644
index 00000000..d7948293
--- /dev/null
+++ b/view/csv-import/admin/mapping-model/show.phtml
@@ -0,0 +1,65 @@
+plugin('escapeHtml');
+$this->htmlElement('body')->appendAttribute('class', 'no-section-hashes');
+$this->headLink()->appendStylesheet($this->assetUrl('css/csvimport.css', 'CSVImport'));
+?>
+
+
+
+
+ | translate("Column"); ?> |
+ translate("Mapping"); ?> |
+ translate("Options"); ?> |
+
+
+
+ $column): ?>
+
+ |
+
+ |
+
+
+
+
+
+ - >
+ translate($automaps[$index]['label']); ?>
+
+
+
+ - >
+ translate($automap['label']); ?>
+
+
+
+
+
+ |
+
+
+ -
+ translate('Data type:'); ?>
+
+
+ -
+ translate("Multivalue"); ?>
+
+ -
+ translate("Language:"); ?>
+
+
+ -
+ translate("Private values"); ?>
+
+ -
+ translate("Resource identifier property:"); ?>
+
+
+
+ |
+
+
+
+
diff --git a/view/csv-import/index/map.phtml b/view/csv-import/index/map.phtml
index f310f84d..f9837759 100644
--- a/view/csv-import/index/map.phtml
+++ b/view/csv-import/index/map.phtml
@@ -4,6 +4,7 @@ $escapeHtml = $this->plugin('escapeHtml');
$this->htmlElement('body')->appendAttribute('class', 'no-section-hashes');
$this->headLink()->appendStylesheet($this->assetUrl('css/csvimport.css', 'CSVImport'));
$this->headScript()->appendFile($this->assetUrl('js/csvimport.js', 'CSVImport'));
+$this->headScript()->appendFile($this->assetUrl('js/mappingselect.js', 'CSVImport'));
$resourceTypeLabels = [
'items' => $this->translate('items'),
@@ -56,85 +57,46 @@ $pageTitle = isset($resourceTypeLabels[$resourceType])
-
+ partial('common/mapping-table', ['resourceType' => $resourceType, 'columns' => $columns, 'automaps' => $automaps]); ?>
partial('common/mapping-sidebar'); ?>
partial('common/options-sidebar', ['dataTypes' => $dataTypes]); ?>
form()->closeTag($form); ?>
+
+
+
diff --git a/view/csv-import/index/past-imports.phtml b/view/csv-import/index/past-imports.phtml
index 8b7a80fe..9600a0e9 100644
--- a/view/csv-import/index/past-imports.phtml
+++ b/view/csv-import/index/past-imports.phtml
@@ -26,6 +26,7 @@ $this->headLink()->appendStylesheet($this->assetUrl('css/csvimport.css', 'CSVImp
translate('Result'));?> |
translate('Status'));?> |
translate('Owner'));?> |
+ translate('Save'));?> |
@@ -103,9 +104,43 @@ $this->headLink()->appendStylesheet($this->assetUrl('css/csvimport.css', 'CSVImp
echo $this->hyperlink($owner->name(), $this->url('admin/id', array('controller' => 'user', 'action' => 'show', 'id' => $owner->id())));
endif;
?>
+
+ hyperlink(
+ '',
+ '#',
+ [
+ 'data-sidebar-content-url' => $this->url(
+ 'admin/csvimport/mapping-model',
+ [
+ 'controller' => 'MappingModel',
+ 'action' => 'save',
+ ],
+ [
+ 'query' => [
+ 'job_id' => $job->id(),
+ ],
+ ]
+ ),
+ 'class' => 'fa fal fa-save sidebar-content',
+ 'title' => $escape($this->translate('Save')),
+ 'data-sidebar-selector' => '#sidebar',
+ ],
+ );
+ } ?>
+ |
+
+