From 5fdc1773ff33c78059463eba22d8c7759ef65e1e Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Thu, 26 Sep 2024 09:22:04 +0100
Subject: [PATCH 01/19] Syntax fix and code tidy
---
prebuilt_forms/species_details_2.php | 24 +++++++++++-------------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/prebuilt_forms/species_details_2.php b/prebuilt_forms/species_details_2.php
index d086be83..e949e563 100644
--- a/prebuilt_forms/species_details_2.php
+++ b/prebuilt_forms/species_details_2.php
@@ -43,70 +43,70 @@ class iform_species_details_2 extends BaseDynamicDetails {
/**
* Stores a value to indicate of no taxon identified.
*
- * @var notaxon
+ * @var bool
*/
private static $notaxon;
/**
* Stores the preferred name of the taxon with markup and authority.
*
- * @var preferred
+ * @var string
*/
private static $preferred;
/**
* Stores the preferred name of the taxon.
*
- * @var preferredPlain
+ * @var string
*/
private static $preferredPlain;
/**
* Stores the default common name of the taxon.
*
- * @var defaultCommonName
+ * @var string
*/
private static $defaultCommonName;
/**
* Stores the synonyms of the taxon.
*
- * @var synonyms
+ * @var array
*/
private static $synonyms = [];
/**
* Stores the common names of the taxon.
*
- * @var commonNames
+ * @var array
*/
private static $commonNames = [];
/**
* Stores the taxonomy of the taxon.
*
- * @var taxonomy
+ * @var array
*/
private static $taxonomy = [];
/**
* Stores the taxa_taxon_list_id of the taxon.
*
- * @var taxaTaxonListId
+ * @var int
*/
private static $taxaTaxonListId;
/**
* Stores the taxon_meaning_id of the taxon.
*
- * @var taxonMeaningId
+ * @var int
*/
private static $taxonMeaningId;
/**
* Stores the exter_key of the taxon.
*
- * @var externalKey
+ * @var string
*/
private static $externalKey;
@@ -1093,7 +1093,7 @@ protected static function get_control_recsthroughyear($auth, $args, $tabalias, $
'source' => 'recsthroughyearSource',
'functionName' => 'populateRecsThroughYearChart',
];
- $customScript .= ElasticsearchReportHelper::customScript($optionsCustomScript);
+ $customScript = ElasticsearchReportHelper::customScript($optionsCustomScript);
$r = <<
$title
@@ -1595,7 +1595,6 @@ protected static function mapWithoutGeoserver($auth, $args, $tabalias, $options)
$params = [
'taxa_taxon_list_id' => empty($_GET['taxa_taxon_list_id']) ? '' : $_GET['taxa_taxon_list_id'],
'taxon_meaning_id' => empty($_GET['taxon_meaning_id']) ? '' : $_GET['taxon_meaning_id'],
- 'sharing' => 'reporting',
'reportGroup' => 'dynamic',
'autoParamsForm' => FALSE,
'sharing' => $sharing,
@@ -1760,7 +1759,6 @@ protected static function get_control_speciesphotos($auth, $args, $tabalias, $op
/**
* Returns a control for picking a species.
*
- * @global type $indicia_templates
* @param array $auth
* Read authorisation tokens.
* @param array $args
From 38c18490f2fd8fb365d81fd68a54c265efc0d22b Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Thu, 26 Sep 2024 12:41:51 +0100
Subject: [PATCH 02/19] Remove unused parameter
---
prebuilt_forms/group_discovery.php | 7 -------
1 file changed, 7 deletions(-)
diff --git a/prebuilt_forms/group_discovery.php b/prebuilt_forms/group_discovery.php
index 42d0a250..e195740a 100644
--- a/prebuilt_forms/group_discovery.php
+++ b/prebuilt_forms/group_discovery.php
@@ -50,13 +50,6 @@ public static function getPageType(): PageType {
public static function get_parameters() {
return [
- [
- 'name' => 'group_home_path',
- 'caption' => 'Path to the group home page',
- 'description' => 'Path to the Drupal page which hosts group home pages.',
- 'type' => 'text_input',
- 'required' => FALSE,
- ],
[
'name' => 'default_group_label_plural',
'caption' => 'Default group label (plural)',
From 1efe6c5a542aa88d2d4cc3964989880a5daf0516 Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Mon, 30 Sep 2024 15:58:39 +0100
Subject: [PATCH 03/19] Adds boundaryLocationId option to leafletMap
---
ElasticsearchProxyHelper.php | 45 +++++++++++++++++++++++++++++++++++
ElasticsearchReportHelper.php | 1 +
2 files changed, 46 insertions(+)
diff --git a/ElasticsearchProxyHelper.php b/ElasticsearchProxyHelper.php
index c420b8fd..61520e3d 100644
--- a/ElasticsearchProxyHelper.php
+++ b/ElasticsearchProxyHelper.php
@@ -149,6 +149,9 @@ public static function callMethod($method, $nid) {
case 'runcustomruleset':
return self::proxyRunCustomRuleset($nid);
+ case 'getLocationBoundaryGeom':
+ return self::getLocationBoundaryGeom($nid);
+
default:
throw new ElasticsearchProxyAbort('Method not found', 404);
}
@@ -3222,4 +3225,46 @@ private static function proxyRunCustomRuleset($nid) {
return self::curlPost($url, $query);
}
+ /**
+ * Proxy method for fetching a locations' boundary geom.
+ *
+ * Used when the `locationBoundaryId` option is set for a `leafletMap`
+ * control. The response is cached.
+ *
+ * @param int $nid
+ * Node ID.
+ *
+ * @return array
+ * Array containing HTTP status and response message, plus boundary_geom
+ * if it worked.
+ */
+ private static function getLocationBoundaryGeom($nid) {
+ if (empty($_GET['location_id']) || !preg_match('/^\d+$/', $_GET['location_id'])) {
+ http_response_code(400);
+ return ['status' => 400, 'msg' => 'Bad Request'];
+ }
+ iform_load_helpers(['report_helper']);
+ $conn = iform_get_connection_details($nid);
+ $readAuth = helper_base::get_read_auth($conn['website_id'], $conn['password']);
+
+ $response = report_helper::get_report_data([
+ 'dataSource' => '/library/locations/locations_combined_boundary_transformed',
+ 'extraParams' => [
+ 'location_ids' => $_GET['location_id'],
+ ],
+ 'readAuth' => $readAuth,
+ 'caching' => TRUE,
+ 'cachePerUser' => FALSE,
+ ]);
+ if (empty($response)) {
+ http_response_code(404);
+ return ['status' => 404, 'msg' => 'Not Found'];
+ }
+ return [
+ 'status' => 200,
+ 'msg' => 'OK',
+ 'boundary_geom' => $response[0]['geom'],
+ ];
+ }
+
}
diff --git a/ElasticsearchReportHelper.php b/ElasticsearchReportHelper.php
index 7e082222..0b76658f 100644
--- a/ElasticsearchReportHelper.php
+++ b/ElasticsearchReportHelper.php
@@ -1180,6 +1180,7 @@ public static function leafletMap(array $options) {
}
$dataOptions = helper_base::getOptionsForJs($options, [
'baseLayerConfig',
+ 'boundaryLocationId',
'cookies',
'initialLat',
'initialLng',
From dd02bd7ea6456693af46d0b1c63b6fb23aaeccd0 Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Mon, 30 Sep 2024 15:58:56 +0100
Subject: [PATCH 04/19] cachePerUser option fix
---
helper_base.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/helper_base.php b/helper_base.php
index f4743d0e..f24eefc0 100644
--- a/helper_base.php
+++ b/helper_base.php
@@ -3596,6 +3596,7 @@ public static function getCachedGenericCall($url, array $get, array $post, array
];
if (isset($options['cachePerUser']) && !$options['cachePerUser']) {
$excludedParams[] = 'user_id';
+ $excludedParams[] = 'currentUser';
}
$cacheOpts = array_diff_key(array_merge($get, $post), array_combine($excludedParams, $excludedParams));
$cacheOpts['serviceCallPath'] = self::$base_url . $url;
From d227e4a37638074982b75faddb1b72eeea1ed37e Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Tue, 1 Oct 2024 10:56:51 +0100
Subject: [PATCH 05/19] Consistency of ES AJAX Proxy response format
---
ElasticsearchProxyHelper.php | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/ElasticsearchProxyHelper.php b/ElasticsearchProxyHelper.php
index 61520e3d..fa2bc634 100644
--- a/ElasticsearchProxyHelper.php
+++ b/ElasticsearchProxyHelper.php
@@ -286,7 +286,10 @@ private static function proxyDoesUserSeeNotifications($nid) {
iform_load_helpers(['VerificationHelper']);
$conn = iform_get_connection_details($nid);
$readAuth = helper_base::get_read_auth($conn['website_id'], $conn['password']);
- return ['msg' => VerificationHelper::doesUserSeeNotifications($readAuth, $_GET['user_id'])];
+ return [
+ 'message' => 'OK',
+ 'result' => VerificationHelper::doesUserSeeNotifications($readAuth, $_GET['user_id']),
+ ];
}
/**
@@ -660,7 +663,8 @@ private static function proxyVerificationQueryEmail() {
$success = hostsite_send_email($_POST['to'], $_POST['subject'], $emailBody);
return [
- 'status' => $success ? 'OK' : 'Fail',
+ 'message' => $success ? 'OK' : 'Fail',
+ 'code' => $success ? 200 : 500,
];
}
@@ -2867,6 +2871,12 @@ private static function bulkEditIds($nid, array $ids, array $updates, array $opt
* batch to fetch if paging.
*/
private static function proxyBulkEditAll($nid) {
+ return [
+ 'code' => 409,
+ 'message' => 'Conflict',
+ 'info' => 'Shared sample',
+ 'errorCode' => 'SAMPLES_CONTAIN_OTHER_OCCURRENCES',
+ ];
$batchInfo = self::getOccurrenceIdPageFromFilter(
$nid,
$_POST['occurrence:idsFromElasticFilter'],
@@ -3239,9 +3249,10 @@ private static function proxyRunCustomRuleset($nid) {
* if it worked.
*/
private static function getLocationBoundaryGeom($nid) {
+ return ['code' => 400, 'message' => 'Bad Request'];
if (empty($_GET['location_id']) || !preg_match('/^\d+$/', $_GET['location_id'])) {
http_response_code(400);
- return ['status' => 400, 'msg' => 'Bad Request'];
+ return ['code' => 400, 'message' => 'Bad Request'];
}
iform_load_helpers(['report_helper']);
$conn = iform_get_connection_details($nid);
@@ -3258,11 +3269,10 @@ private static function getLocationBoundaryGeom($nid) {
]);
if (empty($response)) {
http_response_code(404);
- return ['status' => 404, 'msg' => 'Not Found'];
+ return ['code' => 404, 'message' => 'Not Found'];
}
return [
- 'status' => 200,
- 'msg' => 'OK',
+ 'message' => 'OK',
'boundary_geom' => $response[0]['geom'],
];
}
From 143ebcfe420d6189fd6f8a037cedc0f3a576896d Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Tue, 1 Oct 2024 11:05:30 +0100
Subject: [PATCH 06/19] Removed debug code
---
ElasticsearchProxyHelper.php | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/ElasticsearchProxyHelper.php b/ElasticsearchProxyHelper.php
index fa2bc634..31a6456f 100644
--- a/ElasticsearchProxyHelper.php
+++ b/ElasticsearchProxyHelper.php
@@ -2871,12 +2871,6 @@ private static function bulkEditIds($nid, array $ids, array $updates, array $opt
* batch to fetch if paging.
*/
private static function proxyBulkEditAll($nid) {
- return [
- 'code' => 409,
- 'message' => 'Conflict',
- 'info' => 'Shared sample',
- 'errorCode' => 'SAMPLES_CONTAIN_OTHER_OCCURRENCES',
- ];
$batchInfo = self::getOccurrenceIdPageFromFilter(
$nid,
$_POST['occurrence:idsFromElasticFilter'],
@@ -2898,7 +2892,7 @@ private static function proxyBulkEditAll($nid) {
return $response;
}
-/**
+ /**
* Bulk edit a list of selected records.
*
* @param int $nid
@@ -3249,7 +3243,6 @@ private static function proxyRunCustomRuleset($nid) {
* if it worked.
*/
private static function getLocationBoundaryGeom($nid) {
- return ['code' => 400, 'message' => 'Bad Request'];
if (empty($_GET['location_id']) || !preg_match('/^\d+$/', $_GET['location_id'])) {
http_response_code(400);
return ['code' => 400, 'message' => 'Bad Request'];
From 023eacf2f396ae7e61920a64073126037fd8fcbb Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Wed, 2 Oct 2024 10:21:02 +0100
Subject: [PATCH 07/19] Use Drupal cache for location boundary if possible
---
ElasticsearchProxyHelper.php | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/ElasticsearchProxyHelper.php b/ElasticsearchProxyHelper.php
index 31a6456f..4e62901e 100644
--- a/ElasticsearchProxyHelper.php
+++ b/ElasticsearchProxyHelper.php
@@ -3267,6 +3267,10 @@ private static function getLocationBoundaryGeom($nid) {
return [
'message' => 'OK',
'boundary_geom' => $response[0]['geom'],
+ '#cache' => [
+ 'max-age' => 3600,
+ 'contexts' => ['route'],
+ ],
];
}
From 95644c46985729517f70f05229c7342e684b2a65 Mon Sep 17 00:00:00 2001
From: Rich Burkmar
Date: Thu, 10 Oct 2024 15:21:31 +0100
Subject: [PATCH 08/19] Fixes to problems with species_details_2 incl iRec iss
#1727
---
prebuilt_forms/js/species_details_2.js | 2 +-
prebuilt_forms/species_details_2.php | 30 ++++++++++++++++----------
2 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/prebuilt_forms/js/species_details_2.js b/prebuilt_forms/js/species_details_2.js
index 8b87b6ed..d81197fd 100644
--- a/prebuilt_forms/js/species_details_2.js
+++ b/prebuilt_forms/js/species_details_2.js
@@ -223,7 +223,7 @@ jQuery(document).ready(function($) {
if (existing) {
existing.recs += h.recs;
existing.minYear = h.minYear < existing.minYear ? h.minYear : existing.minYear;
- existing.maxYear = h.maxYear < existing.maxYear ? h.maxYear : existing.maxYear;
+ existing.maxYear = h.maxYear > existing.maxYear ? h.maxYear : existing.maxYear;
} else {
a.push({gr: h.gr, recs: h.recs, minYear: h.minYear, maxYear: h.maxYear});
}
diff --git a/prebuilt_forms/species_details_2.php b/prebuilt_forms/species_details_2.php
index 50b8d56a..c54f7055 100644
--- a/prebuilt_forms/species_details_2.php
+++ b/prebuilt_forms/species_details_2.php
@@ -43,70 +43,70 @@ class iform_species_details_2 extends BaseDynamicDetails {
/**
* Stores a value to indicate of no taxon identified.
*
- * @var bool
+ * @var notaxon
*/
private static $notaxon;
/**
* Stores the preferred name of the taxon with markup and authority.
*
- * @var string
+ * @var preferred
*/
private static $preferred;
/**
* Stores the preferred name of the taxon.
*
- * @var string
+ * @var preferredPlain
*/
private static $preferredPlain;
/**
* Stores the default common name of the taxon.
*
- * @var string
+ * @var defaultCommonName
*/
private static $defaultCommonName;
/**
* Stores the synonyms of the taxon.
*
- * @var array
+ * @var synonyms
*/
private static $synonyms = [];
/**
* Stores the common names of the taxon.
*
- * @var array
+ * @var commonNames
*/
private static $commonNames = [];
/**
* Stores the taxonomy of the taxon.
*
- * @var array
+ * @var taxonomy
*/
private static $taxonomy = [];
/**
* Stores the taxa_taxon_list_id of the taxon.
*
- * @var int
+ * @var taxaTaxonListId
*/
private static $taxaTaxonListId;
/**
* Stores the taxon_meaning_id of the taxon.
*
- * @var int
+ * @var taxonMeaningId
*/
private static $taxonMeaningId;
/**
* Stores the exter_key of the taxon.
*
- * @var string
+ * @var externalKey
*/
private static $externalKey;
@@ -527,7 +527,13 @@ protected static function get_form_html($args, $auth, $attributes) {
} else {
// Get information on species names
self::getNames($auth);
-
+ if (is_null(self::$externalKey)){
+ // Some lists have no TVKs
+ self::$notaxon = TRUE;
+ \Drupal::messenger()->addMessage('There is no external key corresponding to this taxon.');
+ return parent::get_form_html($args, $auth, $attributes);
+ }
+
// In Drupal 9, markup cannot be used in page title, so remove em tags.
$repArray = ['', ''];
$preferredClean = str_replace($repArray, '', self::$preferred);
@@ -1596,6 +1602,7 @@ protected static function mapWithoutGeoserver($auth, $args, $tabalias, $options)
$params = [
'taxa_taxon_list_id' => empty($_GET['taxa_taxon_list_id']) ? '' : $_GET['taxa_taxon_list_id'],
'taxon_meaning_id' => empty($_GET['taxon_meaning_id']) ? '' : $_GET['taxon_meaning_id'],
+ 'sharing' => 'reporting',
'reportGroup' => 'dynamic',
'autoParamsForm' => FALSE,
'sharing' => $sharing,
@@ -1766,6 +1773,7 @@ protected static function get_control_speciesphotos($auth, $args, $tabalias, $op
/**
* Returns a control for picking a species.
*
+ * @global type $indicia_templates
* @param array $auth
* Read authorisation tokens.
* @param array $args
From 54a062cd92cb3d41171cc08eec610444c5556a97 Mon Sep 17 00:00:00 2001
From: Andrew van Breda
Date: Sun, 27 Oct 2024 19:04:24 +0000
Subject: [PATCH 09/19] Import reverser code
---
import_helper_2.php | 214 +++++++++++++++++++++++++++++++++-
prebuilt_forms/importer_2.php | 9 ++
2 files changed, 222 insertions(+), 1 deletion(-)
diff --git a/import_helper_2.php b/import_helper_2.php
index ce4505a1..64b16944 100644
--- a/import_helper_2.php
+++ b/import_helper_2.php
@@ -130,7 +130,13 @@ public static function importer($options) {
self::$indiciaData['step'] = $nextImportStep;
switch ($nextImportStep) {
case 'fileSelectForm':
- return self::fileSelectForm($options);
+ $r = self::fileSelectForm($options);
+ // The reverser currently assumes occurrence entity.
+ if ($options['entity'] === 'occurrence' &&
+ (!empty($options['allow_import_reverse']) && $options['allow_import_reverse'] == TRUE)) {
+ $r .= self::importToReverseDropDown($options);
+ }
+ return $r;
case 'globalValuesForm':
return self::globalValuesForm($options);
@@ -150,6 +156,12 @@ public static function importer($options) {
case 'doImportPage':
return self::doImportPage($options);
+ case 'reversalModeWarning':
+ return self::reversalModeWarning($options);
+
+ case 'reversalResult':
+ return self::reversalResult($options);
+
default:
throw new exception('Invalid next-import-step parameter');
}
@@ -496,6 +508,206 @@ private static function fileSelectForm(array $options) {
return $r;
}
+ /**
+ * Fetch the HTML for the import reversal drop-down.
+ *
+ * @param array $options
+ * Options array for the control.
+ *
+ * @return string
+ * HTML.
+ */
+ private static function importToReverseDropDown(array $options) {
+ iform_load_helpers(['report_helper']);
+ if (!function_exists('hostsite_get_user_field') || !hostsite_get_user_field('indicia_user_id')) {
+ return '';
+ }
+ // We only show user their own imports.
+ $indiciaUserID = hostsite_get_user_field('indicia_user_id');
+ $extraParams = [
+ 'currentUser' => $indiciaUserID,
+ ];
+ // This will be empty if importer is running on Warehouse.
+ // In that case all imports for the logged-in user will be shown.
+ if (!empty($options['website_id'])) {
+ $extraParams = array_merge(
+ $extraParams, ['website_id' => $options['website_id']]
+ );
+ }
+ // Get a list of imports that are reversible.
+ $lookupData = report_helper::get_report_data([
+ 'dataSource' => 'library/imports/reversible_imports_list',
+ 'extraParams' => $options['readAuth'] + $extraParams,
+ ]);
+ // Construct label for the drop-down from the date/time and import_guid.
+ $reversableImports = [];
+ foreach ($lookupData as $importRow) {
+ $reversableImports[$importRow['import_guid']] = 'Date: ' . $importRow['import_date_time'] . ' (Import ID: ' . $importRow['import_guid'] . ')';
+ }
+ $r = '';
+ $r .= '
Or select a previous import to reverse
';
+ if (empty($reversableImports)) {
+ $r .= '
There are no previous imports available for you to reverse
';
+ }
+ // If there are some reversible imports, show user a drop-down of imports
+ // and a run the reverse button.
+ else {
+ $r .= '';
+ }
+ // Don't allow reverse to be run until an import has been selected.
+ data_entry_helper::$javascript = "
+ $('#reverse-guid').on('change', function() {
+ if ($(this).val()) {
+ $('#run-reverse').prop('disabled', false);
+ $('.reverse-instructions-1').show();
+ }
+ else {
+ $('#run-reverse').prop('disabled', true);
+ $('.reverse-instructions-1').hide();
+ }
+ });
+
+ $('#run-reverse').on('click', function() {
+ if (confirm('Are you sure you want to reverse the selected import?') == true) {
+ return true;
+ } else {
+ return false;
+ }
+ });";
+ return $r;
+ }
+
+ /**
+ * Allow user to select import reverse options.
+ *
+ * If applicable, allow the user to select whether to reverse all data,
+ * or just data that has not been changed since importing.
+ *
+ * @param array $options
+ * Options array for the control.
+ *
+ * @return string
+ * HTML.
+ */
+ private static function reversalModeWarning(array $options) {
+ iform_load_helpers(['report_helper']);
+ $extraParams = [];
+ if (!empty($_POST['reverse-guid'])) {
+ $extraParams['import_guid'] = $_POST['reverse-guid'];
+ }
+ else {
+ $extraParams['import_guid'] = '';
+ }
+ // Has any of the data been changed since the import was done.
+ $smpsChangedSinceImport = report_helper::get_report_data([
+ 'dataSource' => 'library/imports/changed_smps_since_import',
+ 'extraParams' => $options['readAuth'] + $extraParams,
+ ]);
+ $occsChangedSinceImport = report_helper::get_report_data([
+ 'dataSource' => 'library/imports/changed_occs_since_import',
+ 'extraParams' => $options['readAuth'] + $extraParams,
+ ]);
+ // If no changes have been made to the imported data,
+ // then we can skip straight to the result page.
+ if (empty($smpsChangedSinceImport) && empty($occsChangedSinceImport)) {
+ return self::reversalResult($options);
+ }
+ // If import data has been changed since import, then give the user
+ // the following options.
+ // Reverse all data, reverse only unchanged data, or abort reverse.
+ $r .= '';
+ // Change the button label depending if user has chosen to abort.
+ // Also change the next import step to be the first step again.
+ data_entry_helper::$javascript .= "
+ $('[name=\"reverse-mode\"').on('change', function() {
+ if ($(this).val() == 'abort_reverse') {
+ $('[name=\"next-import-step\"').val('fileSelectForm');
+ $('#run-reverse').prop('value','Abort');
+ }
+ else {
+ $('[name=\"next-import-step\"').val('reversalResult');
+ $('#run-reverse').prop('value','Continue');
+ };
+ });
+ ";
+ return $r;
+ }
+
+ /**
+ * Send reversal information to the Warehouse, and display result.
+ *
+ * @param array $options
+ * Options array for the control.
+ *
+ * @return string
+ * HTML.
+ */
+ private static function reversalResult(array $options) {
+ $indiciaUserID = hostsite_get_user_field('indicia_user_id');
+ $extraParams = [
+ 'currentUser' => $indiciaUserID,
+ ];
+ $data['warehouse_user_id'] = $indiciaUserID;
+ $serviceUrl = self ::$base_url . 'index.php/services/import_2/importreverse';
+ if (!empty($_POST['reverse-guid'])) {
+ $data['guid_to_reverse'] = $_POST['reverse-guid'];
+ }
+ if (!empty($_POST['reverse-mode'])) {
+ $data['reverse_mode'] = $_POST['reverse-mode'];
+ }
+ $response = self::http_post($serviceUrl, $data, FALSE);
+ $output = json_decode($response['output'], TRUE);
+ $r .= '';
+ return $r;
+ }
+
/**
* Fetch the HTML form for the settings form that captures global values.
*
diff --git a/prebuilt_forms/importer_2.php b/prebuilt_forms/importer_2.php
index 2d9ccdb1..8b202f8b 100644
--- a/prebuilt_forms/importer_2.php
+++ b/prebuilt_forms/importer_2.php
@@ -128,6 +128,7 @@ public static function get_parameters(array $readAuth) {
sample:fk_location:id
sample:input_form
sample:privacy_precision
+sample:import_guid
sample:record_status
sample:sensitivity_precision
TXT;
@@ -235,6 +236,14 @@ public static function get_parameters(array $readAuth) {
'default' => $default_terms['import2requiredFieldsIntro'],
'required' => FALSE,
],
+ [
+ 'name' => 'allow_import_reverse',
+ 'caption' => 'Allow import reversals?',
+ 'group' => 'Import reverser',
+ 'type' => 'boolean',
+ 'default' => TRUE,
+ 'required' => FALSE,
+ ],
];
$requestParams = $readAuth + ['entity' => 'occurrence'];
$request = helper_base::$base_url . 'index.php/services/import_2/get_plugins?' . http_build_query($requestParams);
From a8c8959e4733d159e84c44cbd803c008c7604a30 Mon Sep 17 00:00:00 2001
From: Andrew van Breda
Date: Tue, 29 Oct 2024 21:16:22 +0000
Subject: [PATCH 10/19] More elegant html, and JS moved to uploader.js
---
import_helper_2.php | 207 ++++++++++++++++++++++++++++----------------
1 file changed, 131 insertions(+), 76 deletions(-)
diff --git a/import_helper_2.php b/import_helper_2.php
index 64b16944..16c8b518 100644
--- a/import_helper_2.php
+++ b/import_helper_2.php
@@ -539,20 +539,45 @@ private static function importToReverseDropDown(array $options) {
'dataSource' => 'library/imports/reversible_imports_list',
'extraParams' => $options['readAuth'] + $extraParams,
]);
+ self::addLanguageStringsToJs('import_helper_2', [
+ 'are_you_sure_reverse' => 'Are you sure you want to reverse the selected import?',
+ ]);
+ $lang = [
+ 'select_a_previous_import_to_reverse' => lang::get('Or select a previous import to reverse'),
+ 'no_previous_imports_available_for_you_to_reverse' => lang::get('There are no previous imports available for you to reverse'),
+ 'imports_are_not_always_reversible' => lang::get('Please note that imports are not always reversible.'),
+ 'non_reversible_import_reasons' => lang::get('This may include old imports, imports that have been updated by another import,
+ or imports where a reversal has already been attempted.'),
+ 'reverse_import' => lang::get('Reverse import'),
+ 'new_records_will_be_reversed' => lang::get('Only new records created by the original import will be reversed.'),
+ 'updated_records_will_not_be_reversed' => lang::get('Any records that were selected for an update or deletion during that import will not be reversed.'),
+ ];
// Construct label for the drop-down from the date/time and import_guid.
$reversableImports = [];
foreach ($lookupData as $importRow) {
$reversableImports[$importRow['import_guid']] = 'Date: ' . $importRow['import_date_time'] . ' (Import ID: ' . $importRow['import_guid'] . ')';
}
- $r = '';
- $r .= '
Or select a previous import to reverse
';
+ $r .= <<
+
+ $lang[select_a_previous_import_to_reverse]
+
+ HTML;
if (empty($reversableImports)) {
- $r .= '
There are no previous imports available for you to reverse
';
+ $r .= <<
+
+ $lang[no_previous_imports_available_for_you_to_reverse]
+
+
+ HTML;
}
// If there are some reversible imports, show user a drop-down of imports
// and a run the reverse button.
else {
- $r .= '';
- }
- // Don't allow reverse to be run until an import has been selected.
- data_entry_helper::$javascript = "
- $('#reverse-guid').on('change', function() {
- if ($(this).val()) {
- $('#run-reverse').prop('disabled', false);
- $('.reverse-instructions-1').show();
- }
- else {
- $('#run-reverse').prop('disabled', true);
- $('.reverse-instructions-1').hide();
- }
- });
-
- $('#run-reverse').on('click', function() {
- if (confirm('Are you sure you want to reverse the selected import?') == true) {
- return true;
- } else {
- return false;
- }
- });";
+ $r .= <<
+
+
+ $lang[imports_are_not_always_reversible]
+
+ $lang[non_reversible_import_reasons]
+
+
+
+
+
+ HTML;
+ }
return $r;
}
@@ -607,6 +624,11 @@ private static function importToReverseDropDown(array $options) {
*/
private static function reversalModeWarning(array $options) {
iform_load_helpers(['report_helper']);
+ self::addLanguageStringsToJs('import_helper_2', [
+ 'abort' => 'Abort',
+ 'continue' => 'Continue',
+ 'are_you_sure_reverse' => 'Are you sure you wish to continue?',
+ ]);
$extraParams = [];
if (!empty($_POST['reverse-guid'])) {
$extraParams['import_guid'] = $_POST['reverse-guid'];
@@ -628,41 +650,58 @@ private static function reversalModeWarning(array $options) {
if (empty($smpsChangedSinceImport) && empty($occsChangedSinceImport)) {
return self::reversalResult($options);
}
+ $lang = [
+ 'changes_have_been_made' => lang::get('Changes have been made to the data since it was imported.'),
+ 'how_do_you_wish_to_proceed' => lang::get('How do you wish to proceed?'),
+ 'reverse_all_rows' => lang::get('Reverse all rows'),
+ 'reverse_unchanged_rows' => lang::get('Reverse unchanged rows'),
+ 'abort_the_reverse' => lang::get('Abort the reverse'),
+ 'continue' => lang::get('Continue'),
+ ];
// If import data has been changed since import, then give the user
// the following options.
// Reverse all data, reverse only unchanged data, or abort reverse.
- $r .= '';
- // Change the button label depending if user has chosen to abort.
- // Also change the next import step to be the first step again.
- data_entry_helper::$javascript .= "
- $('[name=\"reverse-mode\"').on('change', function() {
- if ($(this).val() == 'abort_reverse') {
- $('[name=\"next-import-step\"').val('fileSelectForm');
- $('#run-reverse').prop('value','Abort');
- }
- else {
- $('[name=\"next-import-step\"').val('reversalResult');
- $('#run-reverse').prop('value','Continue');
- };
- });
- ";
+ $reverseGuid = $_POST['reverse-guid'];
+ $r = <<
+
";
}
}
return $globalRows;
From e2e02150e2b7ef1b84c15383fdeba5308bf41305 Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Thu, 31 Oct 2024 11:49:39 +0000
Subject: [PATCH 14/19] Documented new feature.
---
import_helper_2.php | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/import_helper_2.php b/import_helper_2.php
index 7d87b0f3..1c1e8ca1 100644
--- a/import_helper_2.php
+++ b/import_helper_2.php
@@ -96,6 +96,8 @@ class import_helper_2 extends helper_base {
* ID or external key field mapping. Only affects the user's own data.
* * allowDeletes = set to true to enable mapping to a deleted flag for the
* user's own data. Requires the allowUpdates option to be set.
+ * * allowImportReverse - adds a control to the file upload page which allows
+ * a previous export to be selected and reversed.
*/
public static function importer($options) {
if (empty($options['entity'])) {
@@ -588,7 +590,7 @@ private static function importToReverseDropDown(array $options) {
$r .= <<
-
+
$lang[imports_are_not_always_reversible]
$lang[non_reversible_import_reasons]
From 68b9234a907752aaf41620e9b5f9e7ca9c79e33f Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Thu, 31 Oct 2024 12:33:45 +0000
Subject: [PATCH 15/19] Change list lookup icon
https://github.com/BiologicalRecordsCentre/iRecord/issues/1747
---
import_helper_2.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/import_helper_2.php b/import_helper_2.php
index 1c1e8ca1..1db9803e 100644
--- a/import_helper_2.php
+++ b/import_helper_2.php
@@ -1498,7 +1498,7 @@ private static function getSummaryColumnArrow(array $info) {
$arrow = "";
}
elseif (!empty($info['isFkField'])) {
- $arrow = "";
+ $arrow = "";
}
else {
$arrow = "";
From 6c63785ecec98cfd7a256538ae98a47a38209a65 Mon Sep 17 00:00:00 2001
From: Andrew van Breda
Date: Thu, 31 Oct 2024 14:48:53 +0000
Subject: [PATCH 16/19] Fix warnings appearing in Drupal log, not functionality
differences
---
import_helper_2.php | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/import_helper_2.php b/import_helper_2.php
index 1db9803e..77c7d6f4 100644
--- a/import_helper_2.php
+++ b/import_helper_2.php
@@ -539,7 +539,8 @@ private static function importToReverseDropDown(array $options) {
// Get a list of imports that are reversible.
$lookupData = report_helper::get_report_data([
'dataSource' => 'library/imports/reversible_imports_list',
- 'extraParams' => $options['readAuth'] + $extraParams,
+ 'readAuth' => $options['readAuth'],
+ 'extraParams' => $extraParams,
]);
self::addLanguageStringsToJs('import_helper_2', [
'are_you_sure_reverse' => 'Are you sure you want to reverse the selected import?',
@@ -559,7 +560,7 @@ private static function importToReverseDropDown(array $options) {
foreach ($lookupData as $importRow) {
$reversableImports[$importRow['import_guid']] = 'Date: ' . $importRow['import_date_time'] . ' (Import ID: ' . $importRow['import_guid'] . ')';
}
- $r .= <<
$lang[select_a_previous_import_to_reverse]
@@ -642,11 +643,13 @@ private static function reversalModeWarningOrSkipToResult(array $options) {
// Has any of the data been changed since the import was done.
$smpsChangedSinceImport = report_helper::get_report_data([
'dataSource' => 'library/imports/changed_smps_since_import',
- 'extraParams' => $options['readAuth'] + $extraParams,
+ 'readAuth' => $options['readAuth'],
+ 'extraParams' => $extraParams,
]);
$occsChangedSinceImport = report_helper::get_report_data([
'dataSource' => 'library/imports/changed_occs_since_import',
- 'extraParams' => $options['readAuth'] + $extraParams,
+ 'readAuth' => $options['readAuth'],
+ 'extraParams' => $extraParams,
]);
// If no changes have been made to the imported data,
// then we can skip straight to the result page.
@@ -733,7 +736,7 @@ private static function reversalResult(array $options) {
$response = self::http_post($serviceUrl, $data, FALSE);
$output = json_decode($response['output'], TRUE);
$reverseGuid = $_POST['reverse-guid'];
- $r .= <<
HTML;
if (!$response['result']) {
From 52fe24fddf2c98a301a6e6e9fed3f5392b6fc373 Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Fri, 8 Nov 2024 14:27:43 +0000
Subject: [PATCH 17/19] Minor bug fixes for new import reversal code
Also a minor text string improvement.
---
import_helper_2.php | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/import_helper_2.php b/import_helper_2.php
index 1db9803e..a757afe4 100644
--- a/import_helper_2.php
+++ b/import_helper_2.php
@@ -538,8 +538,9 @@ private static function importToReverseDropDown(array $options) {
}
// Get a list of imports that are reversible.
$lookupData = report_helper::get_report_data([
+ 'readAuth' => $options['readAuth'],
'dataSource' => 'library/imports/reversible_imports_list',
- 'extraParams' => $options['readAuth'] + $extraParams,
+ 'extraParams' => $extraParams,
]);
self::addLanguageStringsToJs('import_helper_2', [
'are_you_sure_reverse' => 'Are you sure you want to reverse the selected import?',
@@ -559,7 +560,7 @@ private static function importToReverseDropDown(array $options) {
foreach ($lookupData as $importRow) {
$reversableImports[$importRow['import_guid']] = 'Date: ' . $importRow['import_date_time'] . ' (Import ID: ' . $importRow['import_guid'] . ')';
}
- $r .= <<
$lang[select_a_previous_import_to_reverse]
@@ -1401,7 +1402,7 @@ private static function lookupMatchingForm(array $options) {
'matchesToLocation' => 'Matches to location',
'matchesToTaxon' => 'Matches to species or taxon name',
'matchesToTerm' => 'Matches to term',
- 'matchingPanelFor' => 'List of values to match for {1}',
+ 'matchingPanelFor' => 'List of values to match for {1} column',
'pleaseMatchAllValues' => 'Matches saved, but there are more matches required for {1}.',
'pleaseMatchValues' => 'Please match the values in the list before saving them.',
'pleaseSelect' => '- Please select -',
From 843da9b88cd3c9968f1ba866b6b4cb7fad2314a1 Mon Sep 17 00:00:00 2001
From: John van Breda
Date: Fri, 8 Nov 2024 15:19:00 +0000
Subject: [PATCH 18/19] Add support for location name in multiple place form
sub-samples
---
data_entry_helper.php | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/data_entry_helper.php b/data_entry_helper.php
index 032e928c..b4535a08 100644
--- a/data_entry_helper.php
+++ b/data_entry_helper.php
@@ -5066,6 +5066,7 @@ public static function preload_species_checklist_occurrences($sampleId, $readAut
data_entry_helper::$entity_to_load['sc:' . $idx . ':' . $subSample['id'] . ':sample:geom'] = $subSample['wkt'];
data_entry_helper::$entity_to_load['sc:' . $idx . ':' . $subSample['id'] . ':sample:wkt'] = $subSample['wkt'];
data_entry_helper::$entity_to_load['sc:' . $idx . ':' . $subSample['id'] . ':sample:location_id'] = $subSample['location_id'];
+ data_entry_helper::$entity_to_load['sc:' . $idx . ':' . $subSample['id'] . ':sample:location_name'] = $subSample['location_name'];
data_entry_helper::$entity_to_load['sc:' . $idx . ':' . $subSample['id'] . ':sample:entered_sref'] = $subSample['entered_sref'];
data_entry_helper::$entity_to_load['sc:' . $idx . ':' . $subSample['id'] . ':sample:entered_sref_system'] = $subSample['entered_sref_system'];
if ($spatialRefPrecisionAttrId) {
@@ -6042,6 +6043,8 @@ public static function get_species_checklist_clonable_row(array $options, array
* the default. Options same as sampleOnClusterButtonContents.
* * **samplePhotos** - set to true to add a photos upload control for each
* sub-sample.
+ * * **location_name** - set to true to add a location name input control for
+ * each sub-sample.
*/
public static function multiple_places_species_checklist($options) {
if (empty($options['spatialSystem'])) {
@@ -6074,7 +6077,14 @@ public static function multiple_places_species_checklist($options) {
$attr['fieldname'] = "sc:n::$attr[fieldname]";
$attr['id'] = "sc:n::$attr[id]";
}
- $sampleCtrls = get_attribute_html($sampleAttrs, [], ['extraParams' => $options['readAuth']], NULL, $attrOptions);
+ $sampleCtrls = '';
+ if (!empty($options['locationName'])) {
+ $sampleCtrls .= self::text_input([
+ 'label' => lang::get('Location name at this position'),
+ 'fieldname' => "sc:n::sample:location_name",
+ ]);
+ }
+ $sampleCtrls .= get_attribute_html($sampleAttrs, [], ['extraParams' => $options['readAuth']], NULL, $attrOptions);
// Add a template for the form section for a new subsample.
$r .= "
HTML;
}
else {
- $printedResponseOutput = print_r($response['output'], TRUE);
- // Result includes links to files which contain
- // changed and unchanged sample/occurrence rows.
+ // Result includes links to files which contain changed and unchanged
+ // sample/occurrence rows.
+ $samplesDetails = '