From 85994aee6b98ec5000f282dffc827ee0c0f3ff1c Mon Sep 17 00:00:00 2001 From: John van Breda Date: Fri, 12 Jan 2024 14:10:22 +0000 Subject: [PATCH 01/27] Remove debug code --- ElasticsearchProxyHelper.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ElasticsearchProxyHelper.php b/ElasticsearchProxyHelper.php index 928340ee..35cb405f 100644 --- a/ElasticsearchProxyHelper.php +++ b/ElasticsearchProxyHelper.php @@ -2495,7 +2495,6 @@ private static function applyUserFiltersLicences(array $definition, array &$bool */ private static function applyUserFiltersCoordinatePrecision(array $definition, array &$bool) { $filter = self::getDefinitionFilter($definition, ['coordinate_precision']); - \Drupal::logger('iform')->notice(var_export($filter, TRUE)); if (!empty($filter)) { // Default is same as or better than. $filter['op'] = $filter['op'] ?? '<='; From 7eb08c0d0500c27a93f75d2c1b60590c1ffdefd1 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Mon, 15 Jan 2024 11:10:46 +0000 Subject: [PATCH 02/27] Sref system select support for blankText Allows it to initially have no value for warehouse location edit. --- data_entry_helper.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data_entry_helper.php b/data_entry_helper.php index 5b703cfa..701f3b9a 100644 --- a/data_entry_helper.php +++ b/data_entry_helper.php @@ -2838,6 +2838,8 @@ public static function sref_and_system(array $options) { * Associative array with the key being the EPSG code for the system or * the notation abbreviation (e.g. OSGB), and the value being the * description to display. + * * blankText - adds an item to the start of the list for when there is no + * value. * * @return string * HTML to insert into the page for the spatial reference systems selection @@ -2856,6 +2858,13 @@ public static function sref_system_select(array $options) { ], $options); $options = self::check_options($options); $opts = ''; + if ($options['blankText']) { + $opts .= str_replace( + ['{value}', '{caption}', '{selected}', '{attribute_list}'], + ['', $options['blankText']], + $indicia_templates['select_item'] + ); + } foreach ($options['systems'] as $system => $caption) { $selected = ($options['default'] == $system ? 'selected' : ''); $opts .= str_replace( From d9d763ebbccda1993ad278d8c132d934260d0d21 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Wed, 17 Jan 2024 13:49:51 +0000 Subject: [PATCH 03/27] Not in filter should not be applied if quality provided Otherwise a quality filter without op gets inverted (e.g. pending on verification_5) --- prebuilt_forms/includes/report_filters.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/prebuilt_forms/includes/report_filters.php b/prebuilt_forms/includes/report_filters.php index e79e3fa3..188ff2d0 100644 --- a/prebuilt_forms/includes/report_filters.php +++ b/prebuilt_forms/includes/report_filters.php @@ -1951,7 +1951,11 @@ function report_filter_panel(array $readAuth, $options, $website_id, &$hiddenStu $optionParams[substr($key, 7)] = $value; } } - $allParams = array_merge(['quality' => 'R', 'quality_op' => 'not in'], $optionParams, $getParams); + $allParams = array_merge($optionParams, $getParams); + if (!isset($allParams['quality'])) { + $allParams['quality'] = 'R'; + $allParams['quality_op'] = 'not in'; + } if (!empty($allParams)) { report_helper::$initialFilterParamsToApply = array_merge(report_helper::$initialFilterParamsToApply, $allParams); $json = json_encode($allParams); From 5669ad3061232b821c54e8c8db0825574518dacf Mon Sep 17 00:00:00 2001 From: John van Breda Date: Wed, 17 Jan 2024 14:24:56 +0000 Subject: [PATCH 04/27] Fixes an issue with Source panel Only one website available - surveys did not get populated. --- prebuilt_forms/includes/report_filters.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/prebuilt_forms/includes/report_filters.php b/prebuilt_forms/includes/report_filters.php index 188ff2d0..1d74364d 100644 --- a/prebuilt_forms/includes/report_filters.php +++ b/prebuilt_forms/includes/report_filters.php @@ -1202,6 +1202,12 @@ public function getControls(array $readAuth, array $options) { HTML; + $selectWebsiteFirstInfo = "

$lang[selectWebsiteToLoadSurveys]

"; + } + else { + $website = $websites[0]; + $r .= "
"; + $selectWebsiteFirstInfo = ''; } } else { @@ -1209,7 +1215,8 @@ public function getControls(array $readAuth, array $options) { // is on the website details->milestones tab for a single website) so // supply this as a param, we don't need to worry about sharing as other // websites will have their own milestones. - $r .= ''; + $r .= "
"; + $selectWebsiteFirstInfo = ''; } // Put a cached list of all available surveys in indiciaData to save hits // when building source pane descriptions. @@ -1232,6 +1239,7 @@ public function getControls(array $readAuth, array $options) {

$lang[surveyDatasets]

$surveysFilterInput $surveysOpSelect + $selectWebsiteFirstInfo
From df36e24d987718ab0f3ab111a97bbe44b2d83770 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Thu, 18 Jan 2024 12:02:44 +0000 Subject: [PATCH 05/27] Deprecated code tidy --- prebuilt_forms/extensions/misc_extensions.php | 2 +- prebuilt_forms/extensions/taxon_input_extras.php | 2 -- prebuilt_forms/seasearch_survey.php | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/prebuilt_forms/extensions/misc_extensions.php b/prebuilt_forms/extensions/misc_extensions.php index e1559721..534447fa 100644 --- a/prebuilt_forms/extensions/misc_extensions.php +++ b/prebuilt_forms/extensions/misc_extensions.php @@ -520,7 +520,7 @@ public static function set_page_title($auth, $args, $tabalias, $options, $path) * Helper method to enable jQuery tooltips. */ public static function enable_tooltips() { - drupal_add_library('system', 'ui.tooltip', TRUE); + data_entry_helper::add_resource('jquery_ui'); data_entry_helper::$javascript .= " $('form#entry_form').tooltip({ open: function(event, ui) { diff --git a/prebuilt_forms/extensions/taxon_input_extras.php b/prebuilt_forms/extensions/taxon_input_extras.php index f37d18df..ba96c0a2 100644 --- a/prebuilt_forms/extensions/taxon_input_extras.php +++ b/prebuilt_forms/extensions/taxon_input_extras.php @@ -37,8 +37,6 @@ class extension_taxon_input_extras { * * @title - Override the title of the box displaying the hints. */ public static function add_species_hints($auth, $args, $tabalias, $options, $path) { - // enable nice tooltips - //drupal_add_library('system', 'ui.tooltip', true); $options = array_merge([ 'title' => 'Hints relating to species names entered', ], $options); diff --git a/prebuilt_forms/seasearch_survey.php b/prebuilt_forms/seasearch_survey.php index 8d1c306d..a1c96d4d 100644 --- a/prebuilt_forms/seasearch_survey.php +++ b/prebuilt_forms/seasearch_survey.php @@ -94,7 +94,7 @@ protected static function get_form_html($args, $auth, $attributes) { // toggle the checkboxes to after the label to match the form. $indicia_templates['check_or_radio_group_item'] = '
  • '; - drupal_add_library('system', 'ui.tooltip', true); + data_entry_helper::add_resource('jquery_ui'); // Create an array of custom attributes keyed by caption for easy lookup later foreach($attributes as $attr) self::$attrsByCaption[strtolower($attr['caption'])] = $attr; From e6f0296360d37170c98c90cbb52f97519a1425f6 Mon Sep 17 00:00:00 2001 From: Rich Burkmar Date: Thu, 18 Jan 2024 12:19:12 +0000 Subject: [PATCH 06/27] Added extra filters to ebms_atlas_map --- prebuilt_forms/ebms_atlas_map.php | 18 ++++++++++++++++++ prebuilt_forms/js/ebms_atlas_map.js | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/prebuilt_forms/ebms_atlas_map.php b/prebuilt_forms/ebms_atlas_map.php index 2a36fa7d..5b02acad 100644 --- a/prebuilt_forms/ebms_atlas_map.php +++ b/prebuilt_forms/ebms_atlas_map.php @@ -280,11 +280,29 @@ protected static function get_form_html($args, $auth, $attributes) { $esFilter = self::createEsFilterHtml('taxon.accepted_taxon_id', self::$externalKey, 'match_phrase', 'must'); // Exclude rejected records in ES queries. $esFilter .= self::createEsFilterHtml('identification.verification_status', 'R', 'term', 'must_not'); + // Only those that pass auto-checks should be included. + $esFilter .= self::createEsFilterHtml('identification.auto_checks.result', 'true', 'term', 'must'); + // Do not include any immature life stages + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'egg', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'Egg', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'larva', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'Larva', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'larvae', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'Larvae', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'pupa', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'Pupa', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'pupae', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'Pupae', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'cocoon', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'Cocoon', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'caterpillar', 'term', 'must_not'); + $esFilter .= self::createEsFilterHtml('occurrence.life_stage', 'Caterpillar', 'term', 'must_not'); } else { self::$taxaTaxonListId = ''; $esFilter = ''; } + if (self::$dataYearFilter != 0) { $esFilter .= self::createEsFilterHtml('event.year', self::$dataYearFilter, 'match_phrase', 'must'); } diff --git a/prebuilt_forms/js/ebms_atlas_map.js b/prebuilt_forms/js/ebms_atlas_map.js index 9023e4de..b9d48a80 100644 --- a/prebuilt_forms/js/ebms_atlas_map.js +++ b/prebuilt_forms/js/ebms_atlas_map.js @@ -46,6 +46,10 @@ jQuery(document).ready(function($) { indiciaFns.processTraceMapData2 = function (el, sourceSettings, response) { console.log(response.aggregations.aggs.buckets) + + const total=response.aggregations.aggs.buckets.reduce((t,d) => t=t+d.doc_count, 0) + console.log('total:', total) + const mapData = [] response.aggregations.aggs.buckets.forEach(wb => { const week = Number(wb.key) From a69ded8572481554e46b399f0e7220350a48698d Mon Sep 17 00:00:00 2001 From: Rich Burkmar Date: Thu, 18 Jan 2024 12:53:06 +0000 Subject: [PATCH 07/27] Removed dpm call from ebms_atlas_map --- prebuilt_forms/ebms_atlas_map.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/prebuilt_forms/ebms_atlas_map.php b/prebuilt_forms/ebms_atlas_map.php index 5b02acad..3328f531 100644 --- a/prebuilt_forms/ebms_atlas_map.php +++ b/prebuilt_forms/ebms_atlas_map.php @@ -320,7 +320,6 @@ protected static function get_form_html($args, $auth, $attributes) { * Empty string as no HTML required. */ protected static function get_control_source($auth, $args, $tabalias, $options) { - dpm($options); return ElasticsearchReportHelper::source($options); } @@ -601,10 +600,6 @@ protected static function getArgDefaults($args) { * Hidden input HTML. */ protected static function createEsFilterHtml($field, $value, $queryType, $boolClause) { - // dpm([ - // 'field' => $field, - // 'value' => $value - // ]); $r = << @@ -639,8 +634,6 @@ protected static function getNames($auth) { self::$externalKey = $species_details[0]['external_key']; } - //dpm($species_details[0]); - //self::$preferred = $species_details[0]['taxon_plain']; self::$preferred = $species_details[0]['taxon']; } } From a77980dbc9a80968bc0cf2a845c9048103c0d5b4 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Tue, 23 Jan 2024 11:23:24 +0000 Subject: [PATCH 08/27] Fix potential PHP warnings in Drupal logs --- data_entry_helper.php | 2 +- prebuilt_forms/includes/dynamic.php | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/data_entry_helper.php b/data_entry_helper.php index 701f3b9a..833a642e 100644 --- a/data_entry_helper.php +++ b/data_entry_helper.php @@ -2858,7 +2858,7 @@ public static function sref_system_select(array $options) { ], $options); $options = self::check_options($options); $opts = ''; - if ($options['blankText']) { + if (!empty($options['blankText'])) { $opts .= str_replace( ['{value}', '{caption}', '{selected}', '{attribute_list}'], ['', $options['blankText']], diff --git a/prebuilt_forms/includes/dynamic.php b/prebuilt_forms/includes/dynamic.php index 9a739bb2..3f0b5998 100644 --- a/prebuilt_forms/includes/dynamic.php +++ b/prebuilt_forms/includes/dynamic.php @@ -265,14 +265,17 @@ public static function get_form($args, $nid) { } // Get authorisation tokens to update and read from the Warehouse. We allow - // child classes to generate this first if subclassed. - if (self::$auth) { - $auth = self::$auth; - } - else { + // child classes to generate this first if subclassed. So, if not already + // generated, or we are on a data entry form without write auth (which can + // happen when loading multiple pages using a view), then fetch read write + // auth. + if (!self::$auth || (empty(self::$auth['write']) && call_user_func([self::$called_class, 'isDataEntryForm']))) { $auth = data_entry_helper::get_read_write_auth($args['website_id'], $args['password']); self::$auth = $auth; } + else { + $auth = self::$auth; + } // Determine how the form was requested and therefore what to output. $mode = (method_exists(self::$called_class, 'getMode')) ? call_user_func([self::$called_class, 'getMode'], $args, $nid) : ''; From 7b7dd0c674fb64358edf8a61643dc30109257db6 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Wed, 24 Jan 2024 15:10:00 +0000 Subject: [PATCH 09/27] Make function public So can be used from warehouse REST API. --- ElasticsearchProxyHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ElasticsearchProxyHelper.php b/ElasticsearchProxyHelper.php index 35cb405f..43bf85b9 100644 --- a/ElasticsearchProxyHelper.php +++ b/ElasticsearchProxyHelper.php @@ -1355,7 +1355,7 @@ private static function applyGroupFilter(array $readAuth, $groupFilter, array &$ * @param array $bool * ES bool query definintion. */ - private static function applyFilterDef(array $readAuth, array $definition, array &$bool) { + public static function applyFilterDef(array $readAuth, array $definition, array &$bool) { self::convertLocationListToSearchArea($definition, $readAuth); self::applyUserFiltersTaxonGroupList($definition, $bool); self::applyUserFiltersTaxaTaxonList($definition, $bool, $readAuth); From e6bec443ab8b40d9835c72b90db4f9826b1dbb2c Mon Sep 17 00:00:00 2001 From: John van Breda Date: Fri, 26 Jan 2024 13:28:22 +0000 Subject: [PATCH 10/27] Uses class on warehouse when appropriate To avoid indirect data access via web services --- ElasticsearchProxyHelper.php | 123 +++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 43 deletions(-) diff --git a/ElasticsearchProxyHelper.php b/ElasticsearchProxyHelper.php index 8bc7b021..60d4986f 100644 --- a/ElasticsearchProxyHelper.php +++ b/ElasticsearchProxyHelper.php @@ -1417,16 +1417,25 @@ private static function convertLocationListToSearchArea(array &$definition, arra 'location_id', ]); if (!empty($filter)) { - require_once 'report_helper.php'; - $boundaryData = report_helper::get_report_data([ - 'dataSource' => '/library/locations/locations_combined_boundary_transformed', - 'extraParams' => [ - 'location_ids' => $filter['value'], - ], - 'readAuth' => $readAuth, - 'caching' => TRUE, - ]); - $definition['searchArea'] = $boundaryData[0]['geom']; + if (defined('KOHANA')) { + // Use direct access as on warehouse. + $polygon = warehouseEsFilters::getCombinedBoundaryData($filter['value']); + } + else { + // API access as on client. + require_once 'report_helper.php'; + $boundaryData = report_helper::get_report_data([ + 'dataSource' => '/library/locations/locations_combined_boundary_transformed', + 'extraParams' => [ + 'location_ids' => $filter['value'], + ], + 'readAuth' => $readAuth, + 'caching' => TRUE, + 'cachePerUser' => FALSE, + ]); + $polygon = $boundaryData[0]['geom']; + } + $definition['searchArea'] = $polygon; } } @@ -1464,14 +1473,21 @@ private static function applyUserFiltersTaxonGroupList(array $definition, array */ private static function applyTaxonomyFilter(array &$bool, array $readAuth, $filterField, $filterValues) { // Convert the IDs to external keys, stored in ES as taxon_ids. - $taxonData = helper_base::get_population_data([ - 'report' => 'library/taxa/convert_ids_to_external_keys', - 'extraParams' => [ - $filterField => $filterValues, - 'master_checklist_id' => hostsite_get_config_value('iform', 'master_checklist_id', 0), - ] + $readAuth, - 'cachePerUser' => FALSE, - ]); + if (defined('KOHANA')) { + // Use direct access as on warehouse. + $taxonData = warehouseEsFilters::taxonIdsToExternalKeys($filterField, $filterValues); + } + else { + // API access as on client. + $taxonData = helper_base::get_population_data([ + 'report' => 'library/taxa/convert_ids_to_external_keys', + 'extraParams' => [ + $filterField => $filterValues, + 'master_checklist_id' => hostsite_get_config_value('iform', 'master_checklist_id', 0), + ] + $readAuth, + 'cachePerUser' => FALSE, + ]); + } $keys = []; foreach ($taxonData as $taxon) { $keys[] = $taxon['external_key']; @@ -1677,16 +1693,23 @@ private static function applyUserFiltersIndexedLocationTypeList(array $definitio if (!empty($filter)) { // Convert the location type IDs to terms that are used in the ES // document. - $typeRows = helper_base::get_population_data([ - 'table' => 'termlists_term', - 'extraParams' => [ - 'id' => $filter, - 'view' => 'cache', - ] + $readAuth, - ]); + if (defined('KOHANA')) { + // Use direct access as on warehouse. + $typeTerms = warehouseEsFilters::getTermsFromIds($filter); + } + else { + // API access as on client. + $typeTerms = helper_base::get_population_data([ + 'table' => 'termlists_term', + 'extraParams' => [ + 'id' => $filter, + 'view' => 'cache', + ] + $readAuth, + ]); + } $types = []; - foreach ($typeRows as $typeRow) { - $types[] = $typeRow['term']; + foreach ($typeTerms as $typeTermRow) { + $types[] = $typeTermRow['term']; } if (count($types) > 0) { $bool['must'][] = [ @@ -2368,12 +2391,19 @@ private static function getLicenceCodes(array $readAuth) { 'open' => [], 'restricted' => [], ]; - $licences = helper_base::get_population_data([ - 'table' => 'licence', - 'extraParams' => $readAuth, - 'columns' => 'code,open', - 'cachePerUser' => FALSE, - ]); + if (defined('KOHANA')) { + // Direct access as on warehouse. + $licences = warehouseEsFilters::getLicences(); + } + else { + // API access as on client. + $licences = helper_base::get_population_data([ + 'table' => 'licence', + 'extraParams' => $readAuth, + 'columns' => 'code,open', + 'cachePerUser' => FALSE, + ]); + } foreach ($licences as $licence) { $r[$licence['open'] === 't' ? 'open' : 'restricted'][] = $licence['code']; } @@ -2650,16 +2680,23 @@ private static function applyUserFiltersTaxaScratchpadList(array $definition, ar 'taxa_scratchpad_list_id', ]); if (!empty($filter)) { - require_once 'report_helper.php'; - // Convert the IDs to external keys, stored in ES as taxon_ids. - $taxonData = report_helper::get_report_data([ - 'dataSource' => '/library/taxa/external_keys_for_scratchpad', - 'extraParams' => [ - 'scratchpad_list_id' => $filter['value'], - ], - 'readAuth' => $readAuth, - 'caching' => TRUE, - ]); + if (defined('KOHANA')) { + // Direct access as on warehouse. + $taxonData = warehouseEsFilters::getExternalKeysForTaxonScratchpad($filter['value']); + } + else { + require_once 'report_helper.php'; + // Convert the IDs to external keys, stored in ES as taxon_ids. + $taxonData = report_helper::get_report_data([ + 'dataSource' => '/library/taxa/external_keys_for_scratchpad', + 'extraParams' => [ + 'scratchpad_list_id' => $filter['value'], + ], + 'readAuth' => $readAuth, + 'caching' => TRUE, + 'cachePerUser' => FALSE, + ]); + } $keys = []; foreach ($taxonData as $taxon) { $keys[] = $taxon['external_key']; From 4c2cd6b94c88c77876e5ba21726bdd3cc935d7cf Mon Sep 17 00:00:00 2001 From: Andrew van Breda Date: Mon, 29 Jan 2024 08:43:25 +0000 Subject: [PATCH 11/27] 1. Prepare some forms for changes to handling of Ajax Forms in iForm. 2. Remove some UKBMS code I do not think needs to be in main Indicia repository --- .../css/ukbms_timed_observations.css | 67 --- prebuilt_forms/js/ukbms_timed_observations.js | 467 ------------------ .../lang/ukbms_timed_observations.en.php | 28 -- prebuilt_forms/mnhnl_bird_transect_walks.php | 1 + prebuilt_forms/pollenator_gallery.php | 1 + prebuilt_forms/pollenator_reidentify.php | 1 + prebuilt_forms/pollenators.php | 1 + 7 files changed, 4 insertions(+), 562 deletions(-) delete mode 100644 prebuilt_forms/css/ukbms_timed_observations.css delete mode 100644 prebuilt_forms/js/ukbms_timed_observations.js delete mode 100644 prebuilt_forms/lang/ukbms_timed_observations.en.php diff --git a/prebuilt_forms/css/ukbms_timed_observations.css b/prebuilt_forms/css/ukbms_timed_observations.css deleted file mode 100644 index e9b93b7e..00000000 --- a/prebuilt_forms/css/ukbms_timed_observations.css +++ /dev/null @@ -1,67 +0,0 @@ -label { - width: 140px; -} - -.helpText { - margin-left: 144px; -} - -table#observation-input1,table#observation-input2,table#observation-input3,table#observation-input4 { - width: auto; -} - -table#observation-input1 td, table#observation-input1 input, table#observation-input2 td, table#observation-input2 input, table#observation-input3 td, table#observation-input3 input, table#observation-input4 td, table#observation-input4 input{ - margin: 1px; - padding: 1px; -} - -table#observation-input1 input,table#observation-input2 input,table#observation-input3 input,table#observation-input4 input { - width: 50px; - border: solid silver 1px; -} - -table th, table td { - text-align: center; -} - -table .alt-row { - background-color: #e7e7e7; -} - -table tr.table-selected { - border-top: solid blue 1px; - border-bottom: solid blue 1px; -} - -table td.table-selected { - border-left: solid blue 1px; - border-right: solid blue 1px; -} - -table thead.table-header { - display: table-header-group; -} - -table td.first { - border-left: dashed grey 2px; -} - -input.edited[type="text"], input.edited[type="password"], select.edited { - border: solid green 1px; -} - -input.saving[type="text"], input.saving[type="password"], select.saving { - background: url(../../../media/images/ajax-loader.gif) no-repeat right; - font-style: italic; -} - -.ui-widget-header { - font-weight: normal; - padding-left: 3px; - padding-right: 3px; - -} -table.species-grid thead th, table.species-grid tbody td, table.sticky-header thead th { - font-size: 80%; - font-family: Tahoma, Geneva, sans-serif; -} \ No newline at end of file diff --git a/prebuilt_forms/js/ukbms_timed_observations.js b/prebuilt_forms/js/ukbms_timed_observations.js deleted file mode 100644 index 9b214b3e..00000000 --- a/prebuilt_forms/js/ukbms_timed_observations.js +++ /dev/null @@ -1,467 +0,0 @@ - -function getTotal(cell) { - var row=jQuery(cell).parents('tr:first')[0]; - var table=jQuery(cell).closest('table')[0]; - // get the total for the row - var total=0, cellValue; - jQuery.each(jQuery(row).find('.count-input'), function(idx, cell) { - cellValue = parseInt(jQuery(cell).val()); - if (!isNaN(cellValue)) { - total += cellValue; - } - }); - jQuery(row).find('.row-total').html(total); - // get the total for the column - var matches = jQuery(cell).parents('td:first')[0].className.match(/col\-\d+/); - var colidx = matches[0].substr(4); - total = 0; - jQuery.each(jQuery(cell).closest('table').find('.occs-body').find('.col-'+colidx+' .count-input'), function(idx, collCell) { - cellValue = parseInt(jQuery(collCell).val()); - if (!isNaN(cellValue)) { - total += cellValue; - } - }); - jQuery(table).find('td.col-total.col-'+colidx).html(total); -} - -function addSpeciesToGrid(occurrenceSpecies, taxonList, speciesTableSelector, force, tabIDX){ - // this function is given a list of species from the occurrences and if they are in the taxon list - // adds them to a table in the order they are in that taxon list - // any that are left are swept up by another function. - jQuery.each(taxonList, function(idx, species) { - var found = force; - if(!found){ - jQuery.each(occurrenceSpecies, function(idx, occ){ - // taxonList may or may not be preferred - // Occ has both a ttl_id and a preferred - if(occ['processed']!==true && (occ['ttl_id']==species['id'] || occ['preferred_ttl_id']==species['id'])) - found=true; - }); - } - if(found) - addGridRow(species, speciesTableSelector, true, tabIDX); - }); -} - -function addGridRow(species, speciesTableSelector, end, tabIDX){ - var name, val, existing = false; - if (species.default_common_name!==null) { - name = species.default_common_name - } else if (species.preferred_language_iso==='lat') { - name = ''+species.taxon+''; - } else { - name = species.taxon; - } - var row = jQuery(''); - if(end) - jQuery(speciesTableSelector+' tbody.occs-body').append(row); - else - jQuery(speciesTableSelector+' tbody.occs-body').prepend(row); - if (typeof indiciaData.existingOccurrences[species.taxon_meaning_id]!=="undefined") { - existing = true; - indiciaData.existingOccurrences[species.taxon_meaning_id]['processed']=true; - jQuery(''+name+'').appendTo(row); - } else jQuery(''+name+'').appendTo(row); - var rowTotal = 0; - for(var idx = 0; idx < indiciaData.occurrence_attribute.length; idx++) { - var isNumber = indiciaData.occurrence_attribute_ctrl[idx].attr('class').indexOf('number:true')>=0, - id = 'value:'+species.id+':occAttr:'+indiciaData.occurrence_attribute[idx], - name = 'value:'+species.id+':occAttr:'+indiciaData.occurrence_attribute[idx]+':', - val = '', valId; - if (typeof indiciaData.occurrence_totals[idx] === "undefined") - indiciaData.occurrence_totals[idx] = {}; - if (typeof indiciaData.occurrence_totals[idx][speciesTableSelector] === "undefined") - indiciaData.occurrence_totals[idx][speciesTableSelector]=0; - // find current value if there is one - var cell = jQuery('').appendTo(row); - if (existing) { - val = indiciaData.existingOccurrences[species.taxon_meaning_id]['value_'+indiciaData.occurrence_attribute[idx]] === null ? '' : - indiciaData.existingOccurrences[species.taxon_meaning_id]['value_'+indiciaData.occurrence_attribute[idx]]; - if (isNumber && val!=='') { - rowTotal += parseInt(val); - indiciaData.occurrence_totals[idx][speciesTableSelector] += parseInt(val); - } - valId=indiciaData.existingOccurrences[species.taxon_meaning_id]['a_id_'+indiciaData.occurrence_attribute[idx]]; - if (valId!==null) { - name = name+indiciaData.existingOccurrences[species.taxon_meaning_id]['a_id_'+indiciaData.occurrence_attribute[idx]]; - } - } - indiciaData.occurrence_attribute_ctrl[idx].clone().attr('id', id).attr('name', name).val(val).addClass(isNumber ? 'count-input' : 'non-count-input').appendTo(cell); - } - jQuery(''+rowTotal+'').appendTo(row); - row.find('.count-input').keydown(count_keydown).focus(count_focus).change(input_change).blur(input_blur); - row.find('.non-count-input').focus(count_focus).change(select_change); -} - -// Not all events can be bound using live() - which is deprecated for later versions of jQuery anyway. -// Define event handlers. -// TBC this should be OK to use as is. -function count_keydown (evt) { - var targetRow = [], targetInput=[], code, attrID, parts=evt.target.id.split(':'), type='value'; - code=parts[2]; - attrID=parts[3]; - - // down arrow or enter key - if (evt.keyCode===13 || evt.keyCode===40) { - targetRow = jQuery(evt.target).parents('tr').next('tr'); - } - // up arrow - if (evt.keyCode===38) { - targetRow = jQuery(evt.target).parents('tr').prev('tr'); - } - if (targetRow.length>0) { - targetInput = targetRow.find('input[id^='+type+'\\:][id$=\\:'+code+'\\:'+attrID+']'); - } - // right arrow - move to next cell if at end of text - if (evt.keyCode===39 && evt.target.selectionEnd >= evt.target.value.length) { - targetInput = jQuery(evt.target).parents('td').next('td').find('input'); - if (targetInput.length===0) { - // end of row, so move down to next if there is one - targetRow = jQuery(evt.target).parents('tr').next('tr'); - if (targetRow.length>0) { - targetInput = targetRow.find('input:visible:first'); - } - } - } - // left arrow - move to previous cell if at start of text - if (evt.keyCode===37 && evt.target.selectionStart === 0) { - targetInput = jQuery(evt.target).parents('td').prev('td').find('input'); - if (targetInput.length===0) { - // before start of row, so move up to previous if there is one - targetRow = jQuery(evt.target).parents('tr').prev('tr'); - if (targetRow.length>0) { - targetInput = targetRow.find('input:visible:last'); - } - } - } - if (targetInput.length > 0) { - jQuery(targetInput).get()[0].focus(); - return false; - } -}; - -function count_focus (evt) { - // select the row - var matches = jQuery(evt.target).parents('td:first')[0].className.match(/col\-\d+/); - var colidx = matches[0].substr(4); - jQuery(evt.target).parents('table:first').find('.table-selected').removeClass('table-selected'); - jQuery(evt.target).parents('table:first').find('.ui-state-active').removeClass('ui-state-active'); - jQuery(evt.target).parents('tr:first').addClass('table-selected'); - jQuery(evt.target).parents('table:first').find('tbody .col-'+colidx).addClass('table-selected'); - jQuery(evt.target).parents('table:first').find('thead .col-'+colidx).addClass('ui-state-active'); -}; - -function input_change (evt) { - jQuery(evt.target).addClass('edited'); -}; - -function select_change (evt) { - jQuery(evt.target).addClass('edited'); - input_blur(evt); -}; - -function input_blur (evt) { - var selector = '#'+evt.target.id.replace(/:/g, '\\:'); - indiciaData.currentCell = evt.target.id; - getTotal(evt.target); - if (jQuery(selector).hasClass('edited')) { - jQuery(selector).addClass('saving'); - if (jQuery(selector).hasClass('count-input')) { - // check for number input - don't post if not a number - if (!jQuery(selector).val().match(/^[0-9]*$/)) { - alert('Please enter a valid number - '+evt.target.id); - // use a timer, as refocus during blur not reliable. - setTimeout("jQuery('#"+evt.target.id+"').focus(); jQuery('#"+evt.target.id+"').select()", 100); - return; - } - } - if (jQuery(selector).hasClass('count-input') || jQuery(selector).hasClass('non-count-input')) { - // need to save the occurrence for the current cell - // set the taxa_taxon_list_id, which we can extract from part of the id of the input. - var parts=evt.target.id.split(':'); - jQuery('#ttlid').val(parts[1]); - // store the actual abundance value we want to save. Use 0 instead of blank - // since required for deletions - if (jQuery(selector).hasClass('count-input') && jQuery(selector).val()==='') { - jQuery(selector).val('0'); - } - jQuery('#occattr').val(jQuery(selector).val()); - // multiple attributes per occurrence: never delete the occurrence - // does this cell already have an occurrence? - if (jQuery('#value\\:'+parts[1] +'\\:occid').length>0) { - jQuery('#occid').val(jQuery('#value\\:'+parts[1] +'\\:occid').val()); - jQuery('#occid').attr('disabled', false); - } else { - // if no existing occurrence, we must not post the occurrence:id field. - jQuery('#occid').attr('disabled', true); - } - parts=jQuery(selector).attr('name').split(':'); - if (parts[4]=='') { - // by setting the attribute field name to occAttr:n where n is the occurrence attribute id, we will get a new one - jQuery('#occattr').attr('name', 'occAttr:' + parts[3]); - } else { - // by setting the attribute field name to occAttr:n:m where m is the occurrence attribute value id, we will update the existing one - jQuery('#occattr').attr('name', 'occAttr:' + parts[3] + ':' + parts[4]); - } - // store the current cell's ID as a transaction ID, so we know which cell we were updating. - jQuery('#transaction_id').val(evt.target.id); - jQuery('#occ-form').submit(); - if (jQuery(selector).val()==='0' || jQuery(selector).val()==='') { - jQuery(selector).attr('name', evt.target.id+':'); - } - } - } -}; - -function loadSpeciesList() { - // redo alt-row classes - var redo_alt_row = function (table) { - var rowCount = 0; - jQuery(table + ' tbody').find('tr').each(function(){ - if(rowCount%2===0) - jQuery(this).removeClass('alt-row'); - else - jQuery(this).addClass('alt-row'); - rowCount++; - }); - } - - indiciaData.currentCell=null; - // first add any data recorded, then populate the tables with any blank rows required. There is a heirarchy: if data is in more than one species list, it is added - // to the first grid it appears in. - // note that when added from the list, the ttlid is the preferred one, but if added from the autocomplete it may/probably - // will not be. - addSpeciesToGrid(indiciaData.existingOccurrences, indiciaData.speciesList1List, 'table#observation-input1', true, 1); - var process2 = function () { - var process3 = function () { - var process4 = function () { - if(indiciaData.speciesList4>0){ - var TaxonData = { - 'taxon_list_id': indiciaData.speciesList4, - 'preferred': 't', - 'auth_token': indiciaData.readAuth.auth_token, - 'nonce': indiciaData.readAuth.nonce, - 'mode': 'json', - 'allow_data_entry': 't', - 'view': 'cache', - 'orderby': 'taxonomic_sort_order' - }; - var query = {"in":{"taxon_meaning_id":indiciaData.allTaxonMeaningIdsAtSample}}; - if(typeof indiciaData.speciesList4FilterField != "undefined") - query['in'][indiciaData.speciesList4FilterField] = indiciaData.speciesList4FilterValues; - TaxonData.query = JSON.stringify(query); - jQuery.ajax({ - 'url': indiciaData.indiciaSvc+'index.php/services/data/taxa_taxon_list', - 'data': TaxonData, - 'dataType': 'jsonp', - 'success': function(data) { - addSpeciesToGrid(indiciaData.existingOccurrences, data, 'table#observation-input4', false, 4); - // copy across the col totals - for(var idx = 0; idx < indiciaData.occurrence_attribute.length; idx++) { - jQuery('table#observation-input4 tfoot .col-total.col-'+(idx+1)).html(typeof indiciaData.occurrence_totals[idx]['table#observation-input4']==="undefined" ? 0 : indiciaData.occurrence_totals[idx]['table#observation-input4']); - } - jQuery('#grid4-loading').remove(); - } - }); - } - }; // end of function process4 - if(indiciaData.speciesList3>0){ - var TaxonData = { - 'taxon_list_id': indiciaData.speciesList3, - 'preferred': 't', - 'auth_token': indiciaData.readAuth.auth_token, - 'nonce': indiciaData.readAuth.nonce, - 'mode': 'json', - 'allow_data_entry': 't', - 'view': 'cache', - 'orderby': 'taxonomic_sort_order' - }; - var query = {}; - var q = indiciaData.speciesList3FilterField; - if(typeof q != "undefined") - query = {"in":{q : indiciaData.speciesList3FilterValues}}; - TaxonData.query = JSON.stringify(query); - jQuery.ajax({ - 'url': indiciaData.indiciaSvc+'index.php/services/data/taxa_taxon_list', - 'data': TaxonData, - 'dataType': 'jsonp', - 'success': function(data) { - addSpeciesToGrid(indiciaData.existingOccurrences, data, 'table#observation-input3', true, 3); - // copy across the col totals - for(var idx = 0; idx < indiciaData.occurrence_attribute.length; idx++) { - jQuery('table#observation-input3 tfoot .col-total.col-'+(idx+1)).html(typeof indiciaData.occurrence_totals[idx]['table#observation-input3']==="undefined" ? 0 : indiciaData.occurrence_totals[idx]['table#observation-input3']); - } - jQuery('#grid3-loading').remove(); - process4(); - } - }); - } else process4(); - }; // end of function process3 - if(indiciaData.speciesList2>0){ - var TaxonData = { - 'taxon_list_id': indiciaData.speciesList2, - 'preferred': 't', - 'auth_token': indiciaData.readAuth.auth_token, - 'nonce': indiciaData.readAuth.nonce, - 'mode': 'json', - 'allow_data_entry': 't', - 'view': 'cache', - 'orderby': 'taxonomic_sort_order' - }; - var query = {}; - var q = indiciaData.speciesList2FilterField; - if(typeof q != "undefined") - query = {"in":{q : indiciaData.speciesList2FilterValues}}; - TaxonData.query = JSON.stringify(query); - jQuery.ajax({ - 'url': indiciaData.indiciaSvc+'index.php/services/data/taxa_taxon_list', - 'data': TaxonData, - 'dataType': 'jsonp', - 'success': function(data) { - addSpeciesToGrid(indiciaData.existingOccurrences, data, 'table#observation-input2', true, 2); - // copy across the col totals - for(var idx = 0; idx < indiciaData.occurrence_attribute.length; idx++) { - jQuery('table#observation-input2 tfoot .col-total.col-'+(idx+1)).html(typeof indiciaData.occurrence_totals[idx]['table#observation-input2']==="undefined" ? 0 : indiciaData.occurrence_totals[idx]['table#observation-input2']); - } - jQuery('#grid2-loading').remove(); - process3(); - } - }); - } else process3(); - }; // end of function process2 - - // copy across the col totals - for(var idx = 0; idx < indiciaData.occurrence_attribute.length; idx++) { - jQuery('table#observation-input1 tfoot .col-total.col-'+(idx+1)).html(typeof indiciaData.occurrence_totals[idx]['table#observation-input1']==="undefined" ? 0 : indiciaData.occurrence_totals[idx]['table#observation-input1']); - } - // the main grid is populated with blank rows by the full list by default (the true in the addSpeciesToGrid call) - // the other 2 grids are to be populated with rows (blank or otherwise) by whatever has been recorded at this site previously. - // Have to have existing meaning ids rloaded and list 1 completed first. - process2(); - - jQuery('#taxonLookupControlContainer').hide(); - - function checkErrors(data) { - if (typeof data.error!=="undefined") { - if (typeof data.errors!=="undefined") { - jQuery.each(data.errors, function(idx, error) { - alert(error); - }); - } else { - alert('An error occured when trying to save the data'); - } - // data.transaction_id stores the last cell at the time of the post. - var selector = '#'+data.transaction_id.replace(/:/g, '\\:'); - jQuery(selector).focus(); - jQuery(selector).select(); - return false; - } else { - return true; - } - } - - jQuery('#occ-form').ajaxForm({ - async: true, - dataType: 'json', - success: function(data, status, form){ - if (checkErrors(data)) { - var selector = '#'+data.transaction_id.replace(/:/g, '\\:'); - jQuery(selector).removeClass('saving'); - // skip deletions - if (jQuery(selector).val()!=='0' && jQuery(selector).val()!=='') { - var parts = data.transaction_id.split(':'); - jQuery(selector).attr('name', parts[0]+':'+parts[1]+':'+parts[2]+':'+parts[3]+':'+data.struct.children[0].id); - if(jQuery('#value\\:'+parts[1]+'\\:occid').length==0) - jQuery(selector).after(''); - } - jQuery(selector).removeClass('edited'); - } - } - }); - -}; -// autocompletes assume ID -function bindSpeciesAutocomplete(selectorID, tableSelectorID, url, lookupListId, lookupListFilterField, lookupListFilterValues, readAuth, duplicateMsg, max, tabIDX) { - // inner function to handle a selection of a taxon from the autocomplete - var handleSelectedTaxon = function(event, data) { - if(jQuery('#row-'+data.taxon_meaning_id).length>0){ - alert(duplicateMsg); - jQuery(event.target).val(''); - return; - } - addGridRow(data, tableSelectorID, true, tabIDX); - jQuery(event.target).val(''); - var table = jQuery(tableSelectorID); - table.parent().find('.sticky-header').remove(); - table.find('thead.tableHeader-processed').removeClass('tableHeader-processed'); - table.removeClass('tableheader-processed'); - table.addClass('sticky-enabled'); - if(typeof Drupal.behaviors.tableHeader == 'object') // Drupal 7 - Drupal.behaviors.tableHeader.attach(table.parent()); - else // Drupal6 : it is a function - Drupal.behaviors.tableHeader(table.parent()); - }; - var extra_params = { - view : 'cache', - orderby : 'taxon', - mode : 'json', - qfield : 'taxon', - auth_token: readAuth.auth_token, - nonce: readAuth.nonce, - taxon_list_id: lookupListId - }; - if(typeof lookupListFilterField != "undefined"){ - extra_params.query = '{"in":{"'+lookupListFilterField+'":'+JSON.stringify(lookupListFilterValues)+"}}"; - }; - - // Attach auto-complete code to the input - var ctrl = jQuery('#' + selectorID).autocomplete(url+'/taxa_taxon_list', { - extraParams : extra_params, - max : max, - parse: function(data) { - var results = []; - jQuery.each(data, function(i, item) { - results[results.length] = - { - 'data' : item, - 'result' : item.taxon, - 'value' : item.taxa_taxon_list_id - }; - }); - return results; - }, - formatItem: function(item) { - return item.taxon; - } - }); - ctrl.bind('result', handleSelectedTaxon); - setTimeout(function() { jQuery('#' + ctrl.attr('id')).focus(); }); -} - -function bindRecorderNameAutocomplete(attrID, userID, baseurl, surveyID, token, nonce) { - jQuery('#smpAttr\\:'+attrID).autocomplete(baseurl+'/index.php/services/report/requestReport', { - extraParams : { - mode : 'json', - report : 'reports_for_prebuilt_forms/UKBMS/ukbms_recorder_names.xml', - reportSource : 'local', - qfield : 'name', - auth_token: token, - attr_id : attrID, - survey_id : surveyID, - user_id : userID, - nonce: nonce - }, - max: 50, - mustMatch : false, - parse: function(data) { - var results = []; - jQuery.each(data, function(i, item) { - results[results.length] = {'data' : item,'result' : item.name,'value' : item.name}; - }); - return results; - }, - formatItem: function(item) {return item.name;} - }); -}; - - diff --git a/prebuilt_forms/lang/ukbms_timed_observations.en.php b/prebuilt_forms/lang/ukbms_timed_observations.en.php deleted file mode 100644 index a304425f..00000000 --- a/prebuilt_forms/lang/ukbms_timed_observations.en.php +++ /dev/null @@ -1,28 +0,0 @@ - 'This taxon is already included in one of the lists on this form' -); \ No newline at end of file diff --git a/prebuilt_forms/mnhnl_bird_transect_walks.php b/prebuilt_forms/mnhnl_bird_transect_walks.php index 925bc67d..b8eee11e 100644 --- a/prebuilt_forms/mnhnl_bird_transect_walks.php +++ b/prebuilt_forms/mnhnl_bird_transect_walks.php @@ -218,6 +218,7 @@ public static function get_form($args, $nid, $response = NULL) { drupal_add_js(\Drupal::service('extension.path.resolver')->getPath('module', 'iform') . '/media/js/jquery.form.js', 'module'); data_entry_helper::link_default_stylesheet(); + data_entry_helper::add_resource('jquery_form'); data_entry_helper::add_resource('jquery_ui'); $language = iform_lang_iso_639_2($args['language']); if ($args['language'] != 'en') { diff --git a/prebuilt_forms/pollenator_gallery.php b/prebuilt_forms/pollenator_gallery.php index ef4b7558..68e4528a 100644 --- a/prebuilt_forms/pollenator_gallery.php +++ b/prebuilt_forms/pollenator_gallery.php @@ -477,6 +477,7 @@ public static function get_form($args, $nid) { drupal_add_js(\Drupal::service('extension.path.resolver')->getPath('module', 'iform') .'/media/js/jquery.form.js', 'module'); data_entry_helper::link_default_stylesheet(); + data_entry_helper::add_resource('jquery_form'); data_entry_helper::add_resource('jquery_ui'); if($args['language'] != 'en') data_entry_helper::add_resource('jquery_ui_'.$args['language']); diff --git a/prebuilt_forms/pollenator_reidentify.php b/prebuilt_forms/pollenator_reidentify.php index 231011b1..9700bfd0 100644 --- a/prebuilt_forms/pollenator_reidentify.php +++ b/prebuilt_forms/pollenator_reidentify.php @@ -117,6 +117,7 @@ public static function get_form($args, $nid) { $r = ''; drupal_add_js(\Drupal::service('extension.path.resolver')->getPath('module', 'iform') .'/media/js/jquery.form.js', 'module'); data_entry_helper::link_default_stylesheet(); + data_entry_helper::add_resource('jquery_form'); data_entry_helper::add_resource('jquery_ui'); data_entry_helper::add_resource('openlayers'); data_entry_helper::enable_validation('new-comments-form'); // don't care about ID itself, just want resources diff --git a/prebuilt_forms/pollenators.php b/prebuilt_forms/pollenators.php index aa0a41d6..f4bfc85e 100644 --- a/prebuilt_forms/pollenators.php +++ b/prebuilt_forms/pollenators.php @@ -474,6 +474,7 @@ public static function get_form($args, $nid) { drupal_add_js(\Drupal::service('extension.path.resolver')->getPath('module', 'iform') .'/media/js/jquery.form.js', 'module'); data_entry_helper::link_default_stylesheet(); + data_entry_helper::add_resource('jquery_form'); data_entry_helper::add_resource('jquery_ui'); data_entry_helper::add_resource('autocomplete'); From b085a603cb31f98192741a9e8857275dfcd50f7f Mon Sep 17 00:00:00 2001 From: John van Breda Date: Tue, 30 Jan 2024 11:43:41 +0000 Subject: [PATCH 12/27] Use Indicia translation Should not use t() in prebuilt form code. --- prebuilt_forms/sectioned_transects_edit_transect.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prebuilt_forms/sectioned_transects_edit_transect.php b/prebuilt_forms/sectioned_transects_edit_transect.php index 466a2956..dcab8706 100644 --- a/prebuilt_forms/sectioned_transects_edit_transect.php +++ b/prebuilt_forms/sectioned_transects_edit_transect.php @@ -578,14 +578,14 @@ private static function get_site_tab($auth, $args, $settings) { } // Other blocks to go at the top, next to the map. if (isset($args['site_help']) && $args['site_help'] != '') { - $r .= '

    ' . t($args['site_help']) . '

    '; + $r .= '

    ' . lang::get($args['site_help']) . '

    '; } $r .= get_attribute_html($settings['attributes'], $args, ['extraParams' => $auth['read']]); $r .= ''; $r .= ""; // left $r .= '
    '; if (!$settings['locationId']) { - $help = t('Use the search box to find a nearby town or village, then drag the map to pan and click on the map to set the centre grid reference of the transect. '. + $help = lang::get('Use the search box to find a nearby town or village, then drag the map to pan and click on the map to set the centre grid reference of the transect. '. 'Alternatively if you know the grid reference you can enter it in the Grid Ref box on the left.'); $r .= '

    ' . $help . '

    '; $r .= data_entry_helper::georeference_lookup([ From ef9bfdd44704cb46352cfd7043bfc05525f3e960 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Thu, 8 Feb 2024 13:52:03 +0000 Subject: [PATCH 13/27] Changing a transect name updates section names to match --- .../sectioned_transects_edit_transect.php | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/prebuilt_forms/sectioned_transects_edit_transect.php b/prebuilt_forms/sectioned_transects_edit_transect.php index dcab8706..a881ac5f 100644 --- a/prebuilt_forms/sectioned_transects_edit_transect.php +++ b/prebuilt_forms/sectioned_transects_edit_transect.php @@ -350,6 +350,7 @@ public static function get_form($args, $nid, $response=NULL) { $settings['autocalcSectionLengthAttrId'] = empty($args['autocalc_section_length_attr_id']) ? 0 : $args['autocalc_section_length_attr_id']; $settings['autocalcTransectLengthAttrId'] = empty($args['autocalc_transect_length_attr_id']) ? 0 : $args['autocalc_transect_length_attr_id']; $settings['defaultSectionGridRef'] = empty($args['default_section_grid_ref']) ? 'parent' : $args['default_section_grid_ref']; + $sectionIds = []; if ($settings['locationId']) { data_entry_helper::load_existing_record($auth['read'], 'location', $settings['locationId']); $settings['walks'] = data_entry_helper::get_population_data(array( @@ -397,7 +398,7 @@ public static function get_form($args, $nid, $response=NULL) { if ($attr['caption']==='No. of sections') { $settings['numSectionsAttr'] = $attr['fieldname']; for ($i = 1; $i <= $attr['displayValue']; $i++) { - $settings['sections']["S$i"]=NULL; + $settings['sections']["S$i"] = NULL; } $existingSectionCount = empty($attr['displayValue']) ? 1 : $attr['displayValue']; data_entry_helper::$javascript .= "$('#".str_replace(':','\\\\:',$attr['id'])."').attr('min',$existingSectionCount).attr('max',".$args['maxSectionCount'].");\n"; @@ -420,6 +421,7 @@ public static function get_form($args, $nid, $response=NULL) { $code = $section['code']; data_entry_helper::$javascript .= "indiciaData.sections.$code = {'geom':'".$section['boundary_geom']."','id':'".$section['id']."','sref':'".$section['centroid_sref']."','system':'".$section['centroid_sref_system']."'};\n"; $settings['sections'][$code] = $section; + $sectionIds[$section['code']] = $section['id']; } } else { @@ -452,7 +454,7 @@ public static function get_form($args, $nid, $response=NULL) { 'progressBar' => isset($args['tabProgress']) && $args['tabProgress']==TRUE )); } - $r .= self::get_site_tab($auth, $args, $settings); + $r .= self::getSiteTab($auth, $args, $settings, $sectionIds); if ($settings['locationId']) { $r .= self::get_your_route_tab($auth, $args, $settings); if ($args['always_show_section_details'] || count($settings['section_attributes']) > 0) @@ -514,7 +516,22 @@ protected static function check_prerequisites() { return $ok; } - private static function get_site_tab($auth, $args, $settings) { + /** + * Retrieve the HTML for the site tab. + * + * @param array $auth + * Read and write auth tokens. + * @param array $args + * Form arguments. + * @param array $settings + * Settings data. + * @param array $sectionIds + * List of child section IDs, keyed by code. + * + * @return string + * Tab HTML. + */ + private static function getSiteTab($auth, $args, $settings, array $sectionIds) { $r = '
    '; $r .= '
    '; $r .= $auth['write']; @@ -525,6 +542,11 @@ private static function get_site_tab($auth, $args, $settings) { $r .= "\n"; if ($settings['locationId']) { $r .= "\n"; + // Enable detecting changes to the site name. + $r .= "\n"; + // Include section IDs to make name update propogation simpler. + $sectionIdsJson = htmlspecialchars(json_encode($sectionIds)); + $r .= "\n"; } // Pass through the group_id if set in URL parameters, so we can save the // location against the group. @@ -774,15 +796,18 @@ protected static function get_section_details_tab($auth, $args, $settings) { */ protected static function section_selector($settings, $id) { $sectionArr = []; - foreach ($settings['sections'] as $code=>$section) + foreach ($settings['sections'] as $code => $section) { $sectionArr[$code] = $code; - $selector = '
      '; - foreach ($sectionArr as $key=>$value) { + } + $selector = '
        '; + foreach ($sectionArr as $key => $value) { $classes = []; - if ($key=='S1') + if ($key === 'S1') { $classes[] = 'selected'; - if (!isset($settings['sections'][$key])) + } + if (!isset($settings['sections'][$key])) { $classes[] = 'missing'; + } $class = count($classes) ? ' class="'.implode(' ', $classes).'"' : ''; $selector .= "
      1. $value
      2. "; } @@ -956,6 +981,24 @@ public static function get_submission($values, $args) { ]; } } + elseif (isset($values['previous_name']) && $values['previous_name'] !== $values['location:name'] && !empty($values['section_ids'])) { + // Not the first save and the transect name has been updated. So, update + // the section names to match the new transect name. + $sectionIds = json_decode($values['section_ids'], TRUE); + foreach ($sectionIds as $code => $sectionId) { + // Add a sub-model for each section just to update the name. + $s['subModels'][] = [ + 'fkId' => 'parent_id', + 'model' => [ + 'id' => 'location', + 'fields' => [ + 'id' => $sectionId, + 'name' => $values['location:name'] . ' - ' . $code, + ], + ], + ]; + } + } return $s; } From ed24b96444f63dc7c0bd862e2673cc9c3ba5ec26 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Fri, 16 Feb 2024 12:01:26 +0000 Subject: [PATCH 14/27] Minor tidy --- data_entry_helper.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/data_entry_helper.php b/data_entry_helper.php index f18c58d5..1b1265af 100644 --- a/data_entry_helper.php +++ b/data_entry_helper.php @@ -4823,7 +4823,7 @@ public static function species_checklist_filter_popup($options, $nameFilter) { * @param string $spatialRefPrecisionAttrId * Provide the ID of the attribute which defines the spatial ref precision * of each subSample where relevant. - * @param string $subSampleAttrs + * @param array $subSampleAttrs * Array of attribute IDs to load for the sub-samples. * * @return array @@ -4831,7 +4831,7 @@ public static function species_checklist_filter_popup($options, $nameFilter) { */ public static function preload_species_checklist_occurrences($sampleId, $readAuth, array $loadMedia, $extraParams, &$subSamples, $useSubSamples, $subSampleMethodID='', - $spatialRefPrecisionAttrId = NULL, $subSampleAttrs = []) { + $spatialRefPrecisionAttrId = NULL, array $subSampleAttrs = []) { $occurrenceIds = []; // don't load from the db if there are validation errors, since the $_POST will already contain all the // data we need. @@ -4857,9 +4857,16 @@ public static function preload_species_checklist_occurrences($sampleId, $readAut } } if ($useSubSamples) { - $extraParams += $readAuth + ['view' => 'detail','parent_id'=>$sampleId,'deleted' => 'f', 'orderby' => 'id', 'sortdir' => 'ASC']; - if($subSampleMethodID != '') + $extraParams += $readAuth + [ + 'view' => 'detail', + 'parent_id'=>$sampleId, + 'deleted' => 'f', + 'orderby' => 'id', + 'sortdir' => 'ASC', + ]; + if($subSampleMethodID != '') { $extraParams['sample_method_id'] = $subSampleMethodID; + } $params = array( 'table' => 'sample', 'extraParams' => $extraParams, @@ -6826,7 +6833,7 @@ protected static function check_options($options) { * ID of the database record to load. * @param string $view * Name of the view to load attributes from, normally 'list' or 'detail'. - * @param bool $sharing + * @param bool|string $sharing * Defaults to false. If set to the name of a sharing task (reporting, * peer_review, verification, data_flow, moderation or editing), then the * record can be loaded from another client website if a sharing agreement @@ -6885,7 +6892,7 @@ private static function getControlFieldKeyMappings() { * ID of the database record to load. * @param string $view * Name of the view to load attributes from, normally 'list' or 'detail'. - * @param boolean $sharing + * @param boolean|string $sharing * Defaults to false. If set to the name of a sharing task (reporting, * peer_review, verification, data_flow, moderation or editing), then the * record can be loaded from another client website if a sharing agreement From b066ed6207b74e9f18b45cfb9986825e9cf099e9 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Tue, 20 Feb 2024 10:15:58 +0000 Subject: [PATCH 15/27] File path corrected --- prebuilt_forms/extensions/taxon_input_extras.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prebuilt_forms/extensions/taxon_input_extras.php b/prebuilt_forms/extensions/taxon_input_extras.php index ba96c0a2..a337fa62 100644 --- a/prebuilt_forms/extensions/taxon_input_extras.php +++ b/prebuilt_forms/extensions/taxon_input_extras.php @@ -41,7 +41,7 @@ public static function add_species_hints($auth, $args, $tabalias, $options, $pat 'title' => 'Hints relating to species names entered', ], $options); $filePath = hostsite_get_public_file_path(); - data_entry_helper::$javascript .= "initSpeciesHints('$filePath/indicia/speciesHints.json');\n"; + data_entry_helper::$javascript .= "initSpeciesHints('/$filePath/indicia/speciesHints.json');\n"; return '

        ' . lang::get($options['title']) . '

        ' . "
        "; } From 0980e4b77762126397e319462fdbe3574d912191 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Wed, 28 Feb 2024 21:11:03 -0400 Subject: [PATCH 16/27] Fix potential error on draft iform layout form --- data_entry_helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_entry_helper.php b/data_entry_helper.php index 1b1265af..a2ec9f3c 100644 --- a/data_entry_helper.php +++ b/data_entry_helper.php @@ -9124,7 +9124,7 @@ public static function getAttributes($options, $indexedArray = TRUE, $sharing = } elseif ($options['attrtable'] !== 'person_attribute' && $options['attrtable'] !== 'taxa_taxon_list_attribute') { $surveys = array(NULL); - if (isset($options['survey_id'])) { + if (!empty($options['survey_id'])) { $surveys[] = $options['survey_id']; $survey = data_entry_helper::get_population_data(array( 'table' => 'survey', From f4b0a0213cf9051accbffd7c6ba6dce383138d7e Mon Sep 17 00:00:00 2001 From: John van Breda Date: Mon, 11 Mar 2024 19:37:48 +0000 Subject: [PATCH 17/27] A little bit of groups code tidying --- ElasticsearchReportHelper.php | 32 +++++++++++++---------- prebuilt_forms/includes/groups.php | 41 +++++++++++++++++------------- report_helper.php | 8 +++--- 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/ElasticsearchReportHelper.php b/ElasticsearchReportHelper.php index b663f82c..0ac8b66a 100644 --- a/ElasticsearchReportHelper.php +++ b/ElasticsearchReportHelper.php @@ -668,12 +668,16 @@ public static function download(array $options) { /** * Integrates the page with groups (activities). * + * @param bool $checkPage + * Set to false to disable checking that the current page path is an iform + * page linked to the group. + * * @link https://indicia-docs.readthedocs.io/en/latest/site-building/iform/helpers/elasticsearch-report-helper.html#elasticsearchreporthelper-groupintegration * * @return string * Control HTML */ - public static function groupIntegration(array $options) { + public static function groupIntegration(array $options, $checkPage = TRUE) { $options = array_merge([ 'missingGroupIdBehaviour' => 'error', 'showGroupSummary' => FALSE, @@ -693,7 +697,7 @@ public static function groupIntegration(array $options) { return ''; } require_once 'prebuilt_forms/includes/groups.php'; - $member = group_authorise_group_id($group_id, $options['readAuth']); + $member = group_authorise_group_id($group_id, $options['readAuth'], $checkPage); $output = ''; if (!empty($group_id)) { // Apply filtering by group. @@ -723,21 +727,21 @@ public static function groupIntegration(array $options) { $output .= self::getGroupPageLinks($group, $options, $member); } } - } - $filterBoundaries = helper_base::get_population_data([ - 'report' => 'library/groups/group_boundary_transformed', - 'extraParams' => $options['readAuth'] + ['group_id' => $group_id], - 'cachePerUser' => FALSE, - ]); - if (count($filterBoundaries) > 0) { - helper_base::$indiciaData['reportBoundaries'] = []; - foreach ($filterBoundaries as $boundary) { - helper_base::$indiciaData['reportBoundaries'][] = $boundary['boundary']; - } - helper_base::$late_javascript .= << 'library/groups/group_boundary_transformed', + 'extraParams' => $options['readAuth'] + ['group_id' => $group_id], + 'cachePerUser' => FALSE, + ]); + if (count($filterBoundaries) > 0) { + helper_base::$indiciaData['reportBoundaries'] = []; + foreach ($filterBoundaries as $boundary) { + helper_base::$indiciaData['reportBoundaries'][] = $boundary['boundary']; + } + helper_base::$late_javascript .= << 'groups_user', - 'extraParams' => $readAuth + array( + 'extraParams' => $readAuth + [ 'group_id' => $group_id, 'user_id' => hostsite_get_user_field('indicia_user_id'), 'pending' => 'f', - ), - 'nocache' => true - )); + ], + 'nocache' => TRUE, + ]); } - $gp = data_entry_helper::get_population_data(array( - 'table' => 'group_page', - 'extraParams' => $readAuth + array('group_id' => $group_id, 'path' => hostsite_get_current_page_path()) - )); - if (count($gp) === 0) { - hostsite_show_message(lang::get('You are trying to access a page which is not available for this group.'), 'warning', TRUE); - hostsite_goto_page(''); - return FALSE; + if ($checkPage) { + $gp = helper_base::get_population_data([ + 'table' => 'group_page', + 'extraParams' => $readAuth + [ + 'group_id' => $group_id, + 'path' => hostsite_get_current_page_path(), + ] + ]); + if (count($gp) === 0) { + hostsite_show_message(lang::get('You are trying to access a page which is not available for this group.'), 'warning', TRUE); + hostsite_goto_page(''); + return FALSE; + } } - elseif (count($gu) === 0 && $gp[0]['administrator'] !== NULL) { + if (count($gu) === 0 && $gp[0]['administrator'] !== NULL) { // Administrator field is null if the page is fully public. Else if not // a group member, then throw them out. hostsite_show_message(lang::get('You are trying to access a page for a group you do not belong to.'), 'warning', TRUE); @@ -118,7 +125,7 @@ function group_authorise_group_id($group_id, $readAuth) { * Group field values loaded from the database. */ function group_apply_report_limits(array &$args, $readAuth, $nid, $isMember) { - $group = data_entry_helper::get_population_data([ + $group = helper_base::get_population_data([ 'table' => 'group', 'extraParams' => $readAuth + ['id' => $_GET['group_id'], 'view' => 'detail'] ]); diff --git a/report_helper.php b/report_helper.php index 21a39ff4..4a73b02d 100644 --- a/report_helper.php +++ b/report_helper.php @@ -3093,8 +3093,8 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { setlocale (LC_TIME, $lang); for($i=0; $i<7; $i++){ - $r .= "" . - lang::get(mb_convert_encoding(date('D', $header_date->getTimestamp()), 'UTF-8', 'ISO-8859-1')) . + $r .= "" . + lang::get(mb_convert_encoding(date('D', $header_date->getTimestamp()), 'UTF-8', 'ISO-8859-1')) . ""; // i8n $header_date->modify('+1 day'); } @@ -3136,8 +3136,8 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { $weekno++; $r .= "" . ($options['includeWeekNumber'] ? "" . $weekno . "" : "") . - "" . - t(mb_convert_encoding(date('M', $consider_date->getTimestamp()), 'UTF-8', 'ISO-8859-1')) . + "" . + t(mb_convert_encoding(date('M', $consider_date->getTimestamp()), 'UTF-8', 'ISO-8859-1')) . ""; } $cellContents=$consider_date->format('j'); // day in month. From d3d2aa98d2919de209d7b27e5d690f7fe8b2774f Mon Sep 17 00:00:00 2001 From: Andrew van Breda Date: Tue, 12 Mar 2024 18:42:44 +0000 Subject: [PATCH 18/27] Forms were not working, however the UKBMS version of the forms do work. Copy differences across. An example of it not working before was the Year field that was supposed to auto-fill did not work, the UKBMS version does work. Note: the form does still include lots of old syntax such as array() instead of [], I will make a note to fix those separately. --- prebuilt_forms/timed_count.php | 50 ++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/prebuilt_forms/timed_count.php b/prebuilt_forms/timed_count.php index 6d456fc8..627aeb7d 100644 --- a/prebuilt_forms/timed_count.php +++ b/prebuilt_forms/timed_count.php @@ -216,6 +216,7 @@ public static function get_form($args, $nid, $response=null) { } public static function get_sample_form($args, $nid, $response) { + global $user; iform_load_helpers(array('map_helper')); $auth = data_entry_helper::get_read_write_auth($args['website_id'], $args['password']); // either looking at existing, creating a new one, or an error occurred: no successful posts... @@ -554,7 +555,8 @@ function(data){ editControl.activate(); nav.activate(); "). -" mapdiv.map.events.triggerEvent('zoomend'); +" delete indiciaData['zoomToAfterFetchingGoogleApiScript-' + mapdiv.map.id]; + mapdiv.map.events.triggerEvent('zoomend'); }); "; @@ -576,9 +578,9 @@ function(data){ 'attrtable' => 'sample_attribute', 'key' => 'sample_id', 'fieldprefix' => 'smpAttr', - 'extraParams'=>$auth['read'], - 'survey_id'=>$args['survey_id'], - 'sample_method_id'=>$sampleMethods[0]['id'] + 'extraParams' => $auth['read'], + 'survey_id' => $args['survey_id'], + 'sample_method_id' => $sampleMethods[0]['id'] )); $r .= get_user_profile_hidden_inputs($attributes, $args, isset(data_entry_helper::$entity_to_load['sample:id']), $auth['read']). data_entry_helper::text_input(array('label' => lang::get('Site Name'), 'fieldname' => 'sample:location_name', 'validation' => array('required') /*, 'class' => 'control-width-5' */ )) @@ -587,6 +589,22 @@ function(data){ if ($sampleId == null){ if(isset($_GET['date'])) data_entry_helper::$entity_to_load['C1:sample:date'] = $_GET['date']; $r .= data_entry_helper::date_picker(array('label' => lang::get('Date of first count'), 'fieldname' => 'C1:sample:date', 'validation' => array('required','date'))); + data_entry_helper::$javascript .= " +indiciaFns.on('change', '.precise-date-picker', {}, function(e) { + var dateToSave = $(e.currentTarget).val(); + var result; + if (result = dateToSave.trim().match(/^\\d{4}/)) { + // Date given year first, so ISO format. That's how HTML5 date input + // values are formatted. + $('#sample\\\\:date').val(result); + } else if (result = dateToSave.trim().match(/\\d{4}$/)) { + // Date given year last + $('#sample\\\\:date').val(result); + } +}); +if($('#C1\\\\:sample\\\\:date').val() != '') { + $('.precise-date-picker').change(); +}\n"; } unset(data_entry_helper::$default_validation_rules['sample:date']); $help = lang::get('The Year field is read-only, and is calculated automatically from the date(s) of the Counts.'); @@ -663,6 +681,7 @@ function(data){ } public static function get_occurrences_form($args, $nid, $response) { + global $user; data_entry_helper::add_resource('jquery_form'); data_entry_helper::add_resource('jquery_ui'); data_entry_helper::add_resource('autocomplete'); @@ -779,21 +798,24 @@ public static function get_occurrences_form($args, $nid, $response) { for($i = 0; $i < max($args['numberOfCounts'], count($subSamples)+1); $i++){ $subSampleId = (isset($subSamples[$i]) ? $subSamples[$i]['sample_id'] : null); $r .= "
        ".lang::get('Count ').($i+1).""; - if($subSampleId) $r .= ""; + if ($subSampleId) { + $r .= ""; + } $r .= ''; - if($subSampleId || (isset(data_entry_helper::$entity_to_load['C'.($i+1).':sample:date']) && data_entry_helper::$entity_to_load['C'.($i+1).':sample:date'] != '')) - $dateValidation = array('required','date'); + if ($subSampleId || (isset(data_entry_helper::$entity_to_load['C'.($i+1).':sample:date']) && data_entry_helper::$entity_to_load['C'.($i+1).':sample:date'] != '')) + $dateValidation = ['required', 'date']; else - $dateValidation = array('date'); + $dateValidation = ['date']; // The sample dates are restrained to the requisite year, but there is already a restriction on future dates, so only // change the upper limit if not this year. the date in data_entry_helper::$entity_to_load['sample:date'] is a vague date year - $r .= data_entry_helper::date_picker(array('label' => lang::get('Date'), 'fieldname' => 'C'.($i+1).':sample:date', 'validation' => $dateValidation)); - data_entry_helper::$javascript .= "$('#C".($i+1)."\\\\:sample\\\\:date' ).datepicker( 'option', 'minDate', new Date(".data_entry_helper::$entity_to_load['sample:date'].", 1 - 1, 1) );\n"; - if(date('Y') > data_entry_helper::$entity_to_load['sample:date']) - data_entry_helper::$javascript .= "$('#C".($i+1)."\\\\:sample\\\\:date' ).datepicker( 'option', 'maxDate', new Date(".data_entry_helper::$entity_to_load['sample:date'].", 12 - 1, 31) );\n"; - if(!$subSampleId && $i) { + $r .= data_entry_helper::date_picker(['label' => lang::get('Date'), 'fieldname' => 'C' . ($i+1) . ':sample:date', 'validation' => $dateValidation]); + data_entry_helper::$javascript .= "$('#ctrl-wrap-C" . ($i+1) . "-sample-date .precise-date-picker' ).prop( 'min', '".data_entry_helper::$entity_to_load['sample:date']."-01-01' );\n"; + if (date('Y') > data_entry_helper::$entity_to_load['sample:date']) { + data_entry_helper::$javascript .= "$('#ctrl-wrap-C" . ($i+1) . "-sample-date .precise-date-picker' ).prop( 'max', '".data_entry_helper::$entity_to_load['sample:date']."-12-31' );\n"; + } + if (!$subSampleId && $i) { $r .= "

        ".lang::get('You must enter the date before you can enter any further information.').'

        '; - data_entry_helper::$javascript .= "$('#C".($i+1)."\\\\:sample\\\\:date' ).change(function(){ + data_entry_helper::$javascript .= "$('#ctrl-wrap-C" . ($i+1) . "-sample-date .precise-date-picker' ).change(function(){ myFieldset = $(this).addClass('required').closest('fieldset'); myFieldset.find('.smp-input,[name=taxonLookupControl]').removeAttr('disabled'); // leave the count fields as are. });\n"; From 40926cc3e3b9dc7470d1a3e119b07cff27e2bd30 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Tue, 12 Mar 2024 20:41:30 +0000 Subject: [PATCH 19/27] Allow group label override in English (e.g. activities, projects) --- prebuilt_forms/group_edit.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/prebuilt_forms/group_edit.php b/prebuilt_forms/group_edit.php index 3f8fa822..a6368a09 100644 --- a/prebuilt_forms/group_edit.php +++ b/prebuilt_forms/group_edit.php @@ -74,6 +74,13 @@ public static function get_parameters() { 'captionField' => 'term', 'extraParams' => ['termlist_external_key' => 'indicia:group_types'], ], + [ + 'name' => 'default_group_label', + 'caption' => 'Default group label', + 'description' => 'What should a group be referred to as? E.g. project, activity etc. This will be overriden with the selected group\'s type when editing.', + 'type' => 'text_input', + 'default' => 'group', + ], [ 'name' => 'parent_group_type', 'caption' => 'Parent group type', @@ -364,6 +371,9 @@ public static function get_form($args, $nid) { $args['filter_types'] = json_decode($args['filter_types'], TRUE); $reloadPath = self::getReloadPath(); data_entry_helper::$website_id = $args['website_id']; + if (!empty($args['default_group_label'])) { + self::$groupType = strtolower($args['default_group_label']); + } // Maintain compatibility with form settings from before group type became // multiselect. if (empty($args['group_type'])) { From d3e9b8fce7be4ec855a9836fc86ca2314e349c49 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Wed, 13 Mar 2024 16:50:12 +0000 Subject: [PATCH 20/27] Changes so samples quality filters compatible with new occs code --- prebuilt_forms/includes/report_filters.php | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/prebuilt_forms/includes/report_filters.php b/prebuilt_forms/includes/report_filters.php index 1d74364d..3bc0e219 100644 --- a/prebuilt_forms/includes/report_filters.php +++ b/prebuilt_forms/includes/report_filters.php @@ -1072,17 +1072,25 @@ public function getTitle() { */ public function getControls() { $r = '
        ' . lang::get('Please note, your options for quality filtering are restricted by your access permissions in this context.') . '
        '; + $r .= data_entry_helper::radio_group([ + 'label' => lang::get('Samples to include or exclude'), + 'fieldname' => 'quality_op', + 'id' => 'quality_op' . (empty($options['standalone']) ? '' : '--standalone'), + 'lookupValues' => [ + 'in' => lang::get('Include'), + 'not in' => lang::get('Exclude'), + ], + 'default' => 'in', + ]); $r .= data_entry_helper::select([ - 'label' => lang::get('Samples to include'), 'fieldname' => 'quality', 'class' => 'quality-filter', 'lookupValues' => [ - 'V' => lang::get('Accepted records only'), - 'P' => lang::get('Not reviewed'), - '!R' => lang::get('Exclude not accepted records'), - '!D' => lang::get('Exclude queried or not accepted records'), + 'P' => lang::get('Pending'), + 'V' => lang::get('Accepted'), + 'R' => lang::get('Not accepted'), + 'D' => lang::get('Queried or rejected'), 'all' => lang::get('All records'), - 'R' => lang::get('Not accepted records only'), ], ]); $r .= data_entry_helper::select([ @@ -1440,6 +1448,7 @@ function report_filter_panel(array $readAuth, $options, $website_id, &$hiddenStu unset($options['presets']['my-groups']); unset($options['presets']['my-groups-locality']); } + data_entry_helper::$indiciaData['filterEntity'] = $options['entity']; // If in the warehouse we don't need to worry about the iform master list. if (function_exists('hostsite_get_config_value')) { $options = array_merge( From 8b5099ce1116a618f75f5114c4c0ebc986cf590b Mon Sep 17 00:00:00 2001 From: Andrew van Breda Date: Sat, 16 Mar 2024 14:37:50 +0000 Subject: [PATCH 21/27] When loading a static species list in edit mode (sample_id or occurrence_id provided). If the recorded name is not available, then load another name with the same meaning. Makes sure only one selected, with preferred prioritised. The original recording name is display as "Previously recorded as xxxxx". The original taxa_taxon_list_id is injected back into the row, so redetermination is never fired. Can be used if the preferred name has changed. Could also apply in situation such as a preferred latin recording is made, but grid is changed to show common only (or similar situation). Possibly in multi-lingual situations also but untested. Doesn't apply to free text selection (lookupListId provided on grid), or single species selection, in those modes the recorded name was always shown in text box anyway. Have tested various scenarios, such as multiple occurrences on grid, and also using 2 species grids. --- data_entry_helper.php | 208 +++++++++++++++++++ prebuilt_forms/dynamic_sample_occurrence.php | 6 + 2 files changed, 214 insertions(+) diff --git a/data_entry_helper.php b/data_entry_helper.php index a2ec9f3c..77b73f45 100644 --- a/data_entry_helper.php +++ b/data_entry_helper.php @@ -3846,6 +3846,20 @@ public static function species_checklist(array $options) { // Load the full list of species for the grid, including the main checklist // plus any additional species in the reloaded occurrences. $taxalist = self::get_species_checklist_taxa_list($options, $taxonRows); + $shiftedTtlIds = []; + if ((!empty($_GET['sample_id']) || !empty($_GET['occurrence_id'])) && + !empty($options['listId'])) { + // If using a static species list, load occurrences using a name with + // same meaning if original name not available. + $fullDetailsOfTaxonListFormatted = self::createFormattedTaxaListForProcessing($options['readAuth'], $taxalist); + $shiftTaxaOntoNamesWithSameMeaningResults = self::shiftTaxaOntoNamesWithSameMeaning($options['readAuth'], $taxonRows, $fullDetailsOfTaxonListFormatted); + $taxonRows = $shiftTaxaOntoNamesWithSameMeaningResults['taxonRows']; + $shiftedTtlIds = $shiftTaxaOntoNamesWithSameMeaningResults['shiftedTtlIds']; + } + // If we are use species grid name that wasn't original recorded name, inform the user of the recorded name as well. + // Run this code even when sample_id, occurrence_id not supplied otherwise {previously_recorded_as} replacement tokens + // appear on the screen. + $taxalist = self::addPreviousNameWarningToList($options['readAuth'], $shiftedTtlIds, $taxalist); // If we managed to read the species list data we can proceed. if (!array_key_exists('error', $taxalist)) { $attrOptions = [ @@ -4031,6 +4045,12 @@ public static function species_checklist(array $options) { } $row .= "\n"; $fieldname = "sc:$options[id]-$txIdx:$existingRecordId:present"; + // If an occurrence does not have its original name available, then an alternative name + // with same meaning can be used. + // Switch back to the old taxa_taxon_list_id (but leave the updated name) so we don't fire a redetermination. + if (!empty($shiftedTtlIds) && array_key_exists($taxon['taxa_taxon_list_id'], $shiftedTtlIds)) { + $taxon['taxa_taxon_list_id'] = $shiftedTtlIds[$taxon['taxa_taxon_list_id']]; + } if ($options['rowInclusionCheck'] === 'hasData') { $row .= ""; } @@ -4377,6 +4397,194 @@ public static function species_checklist(array $options) { } } + /** + * Display occurrence against a name with the same meaning if original name not available. + * + * When taxa are loaded onto data entry page in edit mode, we need to display the occurrence + * against a different name if we can find one with the same meaning and the original is not available. + * + * @param array @readAuth + * Authentication tokens for the Warehouse. + * @param array $taxaInOccDetail + * List of taxa taxon list ids and occurrence information if applicable (might include taxa not actually on the page) + * @param array $taxonListItems + * All the available species items displayed on the page. + * @return array; + */ + function shiftTaxaOntoNamesWithSameMeaning($readAuth, $taxaInOccDetail, $fullDetailsOfTaxonListFormatted) { + // Cycle through all taxa relating to occurrences for the sample. + // This can include names that don't actually appear on the page. + foreach ($taxaInOccDetail as $taxaInOccDetailRow) { + $ttlIdsOfTaxaInOccDetail[] = $taxaInOccDetailRow['ttlId']; + } + // Collect full details as we need the meaning IDs of these + $fullDetailsOfTaxaInOccDetail = data_entry_helper::get_population_data([ + 'table' => 'taxa_taxon_list', + 'extraParams' => $readAuth + [ + 'query' => json_encode(['in' => ['id' => $ttlIdsOfTaxaInOccDetail]]), + 'view' => 'detail', + ], + ]); + // Format so we have the taxa taxon list ID as a key + $fullDetailsOfTaxaInOccDetailFormatted = []; + foreach ($fullDetailsOfTaxaInOccDetail as $fullRowDetail) { + $fullDetailsOfTaxaInOccDetailFormatted[$fullRowDetail['id']] = $fullRowDetail; + } + // Keep track of processed rows + $shiftedTtlIds = []; + // Cycle through all taxa relating to occurences so we can find ones without a taxa_taxon_list on the page + // then find a taxa_taxon_list_id we can actually use + foreach ($taxaInOccDetail as &$possibleRowToShiftFrom) { + // If the taxon is pointed to by an occurrence + if (isset($possibleRowToShiftFrom['occId'])) { + // If the taxon name is not displayed on the page, we need to find one to use + if (!array_key_exists($possibleRowToShiftFrom['ttlId'], $fullDetailsOfTaxonListFormatted)) { + // Cycle through all taxa again to find a name with the same meaning but isn't the same name + // The first time we do this, only look for preferred names as we want to use that if available + foreach ($taxaInOccDetail as &$possibleRowToShiftTo) { + // If row to shift has already been processed then don't do anymore work + if (!in_array($possibleRowToShiftFrom['ttlId'], $shiftedTtlIds)) { + // If the meaning is a match between the two rows but not the same taxa_taxon_list_id + if ($fullDetailsOfTaxaInOccDetailFormatted[$possibleRowToShiftFrom['ttlId']]['taxon_meaning_id'] == $fullDetailsOfTaxaInOccDetailFormatted[$possibleRowToShiftTo['ttlId']]['taxon_meaning_id'] && + $possibleRowToShiftFrom['ttlId'] != $possibleRowToShiftTo['ttlId'] && + array_key_exists($possibleRowToShiftTo['ttlId'], $fullDetailsOfTaxonListFormatted) && + // Note cannot use TRUE, as boolean is supplied as string + $fullDetailsOfTaxonListFormatted[$possibleRowToShiftTo['ttlId']]['preferred'] == 't') { + self::applyChangesToSwitchedRows($possibleRowToShiftFrom, $possibleRowToShiftTo, $shiftedTtlIds); + } + } + } + // If we need didn't find a preferred name, then continue to find a common name/synonym in similar way. + foreach ($taxaInOccDetail as &$possibleRowToShiftTo) { + // If row to shift already processed then don't do anymore + if (!in_array($possibleRowToShiftFrom['ttlId'], $shiftedTtlIds)) { + if ($fullDetailsOfTaxaInOccDetailFormatted[$possibleRowToShiftFrom['ttlId']]['taxon_meaning_id'] == $fullDetailsOfTaxaInOccDetailFormatted[$possibleRowToShiftTo['ttlId']]['taxon_meaning_id'] && + $possibleRowToShiftFrom['ttlId'] != $possibleRowToShiftTo['ttlId'] && + array_key_exists($possibleRowToShiftTo['ttlId'], $fullDetailsOfTaxonListFormatted) && + // Note cannot use TRUE as boolean is supplied as string + // Use != 't' instead of == 't' as it will catch all other cases if this result is corrupted for some reason + $fullDetailsOfTaxonListFormatted[$possibleRowToShiftTo['ttlId']]['preferred'] != 't') { + self::applyChangesToSwitchedRows($possibleRowToShiftFrom, $possibleRowToShiftTo, $shiftedTtlIds); + } + } + } + } + } + } + $shiftTaxaOntoNamesWithSameMeaningResults=[]; + $shiftTaxaOntoNamesWithSameMeaningResults['taxonRows'] = $taxaInOccDetail; + $shiftTaxaOntoNamesWithSameMeaningResults['shiftedTtlIds'] = $shiftedTtlIds; + return $shiftTaxaOntoNamesWithSameMeaningResults; + } + + /** + * Get formatted full details of the species list. + * + * Get species list with full details, + * using taxa_taxon_list_id as array key. + * + * @param array @readAuth + * Authentication tokens for the Warehouse. + * @param array $taxonListItems + * The details of the taxa available on screen. + * + * @return array + */ + private static function createFormattedTaxaListForProcessing($readAuth, $taxonListItems) { + // Put the taxa_taxon_list IDS into a flat array so can be passed as query param. + $idsOfAllAvailableTtlRows = []; + foreach ($taxonListItems as $listRow) { + if (!empty($listRow['taxa_taxon_list_id'])) { + $idsOfAllAvailableTtlRows[] = $listRow['taxa_taxon_list_id']; + } + } + // Get the details of every item in the available species list + $fullDetailsOfTaxonList = data_entry_helper::get_population_data([ + 'table' => 'taxa_taxon_list', + 'extraParams' => $readAuth + [ + 'query' => json_encode(['in' => ['id' => $idsOfAllAvailableTtlRows]]), + 'view' => 'detail', + ], + ]); + $fullDetailsOfTaxonListFormatted = []; + // Create array where the taxa_taxon_list_id is the key to make processing easier. + foreach ($fullDetailsOfTaxonList as $taxonListRowDetail) { + $fullDetailsOfTaxonListFormatted[$taxonListRowDetail['id']] = $taxonListRowDetail; + } + return $fullDetailsOfTaxonListFormatted; + } + + /** + * Move occurrence onto another name with same meaning. + * + * Add occurrence data to the new row, unset from old row. + * + * @param array $rowToShiftFrom + * The original row we are moving the occurrence data from. + * @param array $rowToShiftTo + * The original row we are moving the occurrence data to. + * @param array $shiftedTtlIds + * Taxa_taxon_list_ids of species that have already been processed. + * + * @return array + */ + public static function applyChangesToSwitchedRows(&$rowToShiftFrom, &$rowToShiftTo, &$shiftedTtlIds) { + // Add the occurrence detail to the name we have found + $rowToShiftTo['loadedTxIdx'] = $rowToShiftFrom['loadedTxIdx']; + $rowToShiftTo['occId'] = $rowToShiftFrom['occId']; + $rowToShiftTo['smpIdx'] = $rowToShiftFrom['smpIdx']; + // Then remove the data relating to the occurrence from original row + unset($rowToShiftFrom['loadedTxIdx']); + unset($rowToShiftFrom['occId']); + unset($rowToShiftFrom['smpIdx']); + // Note that this row has now been processed. + $shiftedTtlIds[$rowToShiftTo['ttlId']] = $rowToShiftFrom['ttlId']; + } + + /** + * Add recorded name to species list if needed. + * + * If the species name available on screen is not the same as the recorded name, + * then inform the user of the recorded name. + * + * @param array @readAuth + * Authentication tokens for the Warehouse. + * @param array $shiftedTtlIds + * Array The array key is the displayed taxon's ttl ID, the value is the original recorded ttl ID). + * @param array $taxonListItems + * The details of the taxa available on screen. + * + * @return array + */ + private static function addPreviousNameWarningToList($readAuth, $shiftedTtlIds, $taxonListItems) { + // Get details of the original recorded taxa + $fullDetailsRecordedTaxa = data_entry_helper::get_population_data([ + 'table' => 'taxa_taxon_list', + 'extraParams' => $readAuth + [ + 'query' => json_encode(['in' => ['id' => $shiftedTtlIds]]), + 'view' => 'detail', + ], + ]); + // Format the output so that the ttl_id is the array key + $fullDetailsRecordedTaxaFormatted = []; + foreach ($fullDetailsRecordedTaxa as $fullDetailsRecordedTaxonRow) { + $fullDetailsRecordedTaxaFormatted[$fullDetailsRecordedTaxonRow['id']] = $fullDetailsRecordedTaxonRow; + } + foreach ($taxonListItems as &$taxonListItem) { + // If we find a name we are using that is not the original name on the occurrence + if (array_key_exists($taxonListItem['taxa_taxon_list_id'], $shiftedTtlIds)) { + // Then we are going make a label displaying the recorded name to the user. + // To get the name, we need to get the recorded ttl from the $shiftedTtlIds, + // then simply collect the taxon name from the array containing the taxon details. + $taxonListItem['previously_recorded_as'] = + $fullDetailsRecordedTaxaFormatted[$shiftedTtlIds[$taxonListItem['taxa_taxon_list_id']]]['taxon']; + } else { + $taxonListItem['previously_recorded_as'] = ''; + } + } + return $taxonListItems; + } + /** * Checks if a list of mediaTypes only contains images. * diff --git a/prebuilt_forms/dynamic_sample_occurrence.php b/prebuilt_forms/dynamic_sample_occurrence.php index fbea6115..86e8fdab 100644 --- a/prebuilt_forms/dynamic_sample_occurrence.php +++ b/prebuilt_forms/dynamic_sample_occurrence.php @@ -2248,6 +2248,12 @@ protected static function build_grid_taxon_label_function($args, $options) { '} else {' . "\n" . ' $r = "{taxon}";' . "\n" . '}' . "\n"; + // If the species name available on screen is not the same as the recorded name, + // then inform the user of the recorded name. + $php .= + 'if ("{previously_recorded_as}"!="" && "{previously_recorded_as}"!=NULL) {' . "\n" . + ' $r .= "
        Previously recorded as {previously_recorded_as}";' . "\n" . + '}' . "\n"; // This bit optionally adds '- common' or '- latin' depending on what was // being searched. if (isset($args['species_include_both_names']) && $args['species_include_both_names']) { From 41432bd9d1ecddf7749ec9b767e9b66326aeb46e Mon Sep 17 00:00:00 2001 From: John van Breda Date: Mon, 18 Mar 2024 14:22:38 +0000 Subject: [PATCH 22/27] Prevents a PHP warning --- ElasticsearchReportHelper.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ElasticsearchReportHelper.php b/ElasticsearchReportHelper.php index 13b124ee..d4526d76 100644 --- a/ElasticsearchReportHelper.php +++ b/ElasticsearchReportHelper.php @@ -923,7 +923,7 @@ public static function leafletMap(array $options) { } } } - // Override map position from URL if needed. + // Override map position from URL if needed. // (e.g. this allows control via an iFrame). if (!empty($_GET['initialLat'])) { $options['initialLat'] = $_GET['initialLat']; @@ -2364,9 +2364,11 @@ private static function applySourceModeDefaultsTermAggregation(array &$options) * Options passed to the [source]. Will be modified as appropriate. */ private static function applySourceModeDefaults(array &$options) { - $method = 'applySourceModeDefaults' . ucfirst($options['mode']); - if (method_exists('ElasticsearchReportHelper', $method)) { - self::$method($options); + if (!empty($options['mode'])) { + $method = 'applySourceModeDefaults' . ucfirst($options['mode']); + if (method_exists('ElasticsearchReportHelper', $method)) { + self::$method($options); + } } } From fac6e784b34adec3cd3d99119862225413f8bdf9 Mon Sep 17 00:00:00 2001 From: Andrew van Breda Date: Tue, 19 Mar 2024 16:54:34 +0000 Subject: [PATCH 23/27] Fix problem where on EBMS the Country/Region filter on the Explore Data page became broken because the countries ID list that was intended for the parent drop-down was also being applied to the child region drop-downs. --- ElasticsearchReportHelper.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ElasticsearchReportHelper.php b/ElasticsearchReportHelper.php index d4526d76..971796f1 100644 --- a/ElasticsearchReportHelper.php +++ b/ElasticsearchReportHelper.php @@ -843,6 +843,8 @@ private static function internalLocationSelect(array $options, $addGeomHiddenInp } if ($idx > 0) { $options['parentControlId'] = "$baseId-" . ($idx - 1); + // We don't want the query to apply to the child drop-downs ($idx > 0), as the query can't apply to both parent/child, as they are very different. + unset($options['extraParams']['query']); if ($idx === 1) { $options['parentControlLabel'] = $options['label']; $options['filterField'] = 'parent_id'; From 9d541675e12e15265879485fe30b1e3e9db68e78 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Wed, 20 Mar 2024 16:20:10 +0000 Subject: [PATCH 24/27] Code tidy Made the new code for shifting species list names less database chatty. --- data_entry_helper.php | 190 +++++++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 96 deletions(-) diff --git a/data_entry_helper.php b/data_entry_helper.php index 77b73f45..9a99114e 100644 --- a/data_entry_helper.php +++ b/data_entry_helper.php @@ -3845,14 +3845,14 @@ public static function species_checklist(array $options) { } // Load the full list of species for the grid, including the main checklist // plus any additional species in the reloaded occurrences. - $taxalist = self::get_species_checklist_taxa_list($options, $taxonRows); + $taxalist = self::getSpeciesChecklistTaxaList($options, $taxonRows); $shiftedTtlIds = []; if ((!empty($_GET['sample_id']) || !empty($_GET['occurrence_id'])) && !empty($options['listId'])) { // If using a static species list, load occurrences using a name with // same meaning if original name not available. - $fullDetailsOfTaxonListFormatted = self::createFormattedTaxaListForProcessing($options['readAuth'], $taxalist); - $shiftTaxaOntoNamesWithSameMeaningResults = self::shiftTaxaOntoNamesWithSameMeaning($options['readAuth'], $taxonRows, $fullDetailsOfTaxonListFormatted); + $preferredFlagsByTtlId = self::getPreferredFlagsByTtlId($taxalist); + $shiftTaxaOntoNamesWithSameMeaningResults = self::shiftTaxaOntoNamesWithSameMeaning($options['readAuth'], $taxonRows, $preferredFlagsByTtlId); $taxonRows = $shiftTaxaOntoNamesWithSameMeaningResults['taxonRows']; $shiftedTtlIds = $shiftTaxaOntoNamesWithSameMeaningResults['shiftedTtlIds']; } @@ -4398,120 +4398,112 @@ public static function species_checklist(array $options) { } /** - * Display occurrence against a name with the same meaning if original name not available. + * Display occurrence against a name with the same meaning. * - * When taxa are loaded onto data entry page in edit mode, we need to display the occurrence - * against a different name if we can find one with the same meaning and the original is not available. + * When taxa are loaded onto data entry page in edit mode, we need to display + * the occurrence against a different name if we can find one with the same + * meaning and the original is not available. * - * @param array @readAuth - * Authentication tokens for the Warehouse. - * @param array $taxaInOccDetail - * List of taxa taxon list ids and occurrence information if applicable (might include taxa not actually on the page) - * @param array $taxonListItems - * All the available species items displayed on the page. - * @return array; + * @param array $readAuth + * Read authorisation tokens. + * @param array $allRequestedTaxa + * List of taxa taxon list ids and occurrence information if applicable + * for all taxa that are being requested by the grid (including preloaded + * plus taxa for existing occurrences). + * @param array $preferredByTtlId + * Preferred flag for all the taxa listed, keyed by taxa_taxon_list_id. + * + * @return array */ - function shiftTaxaOntoNamesWithSameMeaning($readAuth, $taxaInOccDetail, $fullDetailsOfTaxonListFormatted) { - // Cycle through all taxa relating to occurrences for the sample. - // This can include names that don't actually appear on the page. - foreach ($taxaInOccDetail as $taxaInOccDetailRow) { - $ttlIdsOfTaxaInOccDetail[] = $taxaInOccDetailRow['ttlId']; - } - // Collect full details as we need the meaning IDs of these - $fullDetailsOfTaxaInOccDetail = data_entry_helper::get_population_data([ - 'table' => 'taxa_taxon_list', - 'extraParams' => $readAuth + [ - 'query' => json_encode(['in' => ['id' => $ttlIdsOfTaxaInOccDetail]]), - 'view' => 'detail', - ], - ]); - // Format so we have the taxa taxon list ID as a key - $fullDetailsOfTaxaInOccDetailFormatted = []; - foreach ($fullDetailsOfTaxaInOccDetail as $fullRowDetail) { - $fullDetailsOfTaxaInOccDetailFormatted[$fullRowDetail['id']] = $fullRowDetail; + private static function shiftTaxaOntoNamesWithSameMeaning(array $readAuth, array $allRequestedTaxa, array $preferredFlagsByTtlId) { + $taxaWhichNeedTaxonMeaningIds = []; + // Reformat requested taxon details so they are keyed by taxa taxon list ID. + $allRequestedTaxaByTtlId = []; + foreach ($allRequestedTaxa as $requestedTaxonDetail) { + $allRequestedTaxaByTtlId[$requestedTaxonDetail['ttlId']] = $requestedTaxonDetail; + // If the row needs shifting, it will need a taxon meaning ID for comparison later. + if (!isset($requestedTaxonDetail['taxon_meaning_id']) && isset($requestedTaxonDetail['occId']) && !array_key_exists($requestedTaxonDetail['ttlId'], $preferredFlagsByTtlId)) { + $taxaWhichNeedTaxonMeaningIds[] = $requestedTaxonDetail['ttlId']; + } + } + // Taxa added to the list for occurrences which are not in the preloaded + // list and need shifting won't have a taxon meaning ID. + if (!empty($taxaWhichNeedTaxonMeaningIds)) { + $fetchTaxonMeaningIds = data_entry_helper::get_population_data([ + 'table' => 'taxa_taxon_list', + 'extraParams' => $readAuth + [ + 'query' => json_encode(['in' => ['id' => $taxaWhichNeedTaxonMeaningIds]]), + 'view' => 'detail', + ], + ]); + foreach ($fetchTaxonMeaningIds as $taxonMeaningInfo) { + $allRequestedTaxaByTtlId[$taxonMeaningInfo['id']]['taxon_meaning_id'] = $taxonMeaningInfo['taxon_meaning_id']; + } } - // Keep track of processed rows + // Keep track of processed rows. $shiftedTtlIds = []; - // Cycle through all taxa relating to occurences so we can find ones without a taxa_taxon_list on the page - // then find a taxa_taxon_list_id we can actually use - foreach ($taxaInOccDetail as &$possibleRowToShiftFrom) { - // If the taxon is pointed to by an occurrence - if (isset($possibleRowToShiftFrom['occId'])) { - // If the taxon name is not displayed on the page, we need to find one to use - if (!array_key_exists($possibleRowToShiftFrom['ttlId'], $fullDetailsOfTaxonListFormatted)) { - // Cycle through all taxa again to find a name with the same meaning but isn't the same name - // The first time we do this, only look for preferred names as we want to use that if available - foreach ($taxaInOccDetail as &$possibleRowToShiftTo) { - // If row to shift has already been processed then don't do anymore work - if (!in_array($possibleRowToShiftFrom['ttlId'], $shiftedTtlIds)) { - // If the meaning is a match between the two rows but not the same taxa_taxon_list_id - if ($fullDetailsOfTaxaInOccDetailFormatted[$possibleRowToShiftFrom['ttlId']]['taxon_meaning_id'] == $fullDetailsOfTaxaInOccDetailFormatted[$possibleRowToShiftTo['ttlId']]['taxon_meaning_id'] && - $possibleRowToShiftFrom['ttlId'] != $possibleRowToShiftTo['ttlId'] && - array_key_exists($possibleRowToShiftTo['ttlId'], $fullDetailsOfTaxonListFormatted) && - // Note cannot use TRUE, as boolean is supplied as string - $fullDetailsOfTaxonListFormatted[$possibleRowToShiftTo['ttlId']]['preferred'] == 't') { - self::applyChangesToSwitchedRows($possibleRowToShiftFrom, $possibleRowToShiftTo, $shiftedTtlIds); - } + // Cycle through all taxa relating to occurences so we can find ones + // without a taxa_taxon_list on the page then find a taxa_taxon_list_id we + // can actually use. + foreach ($allRequestedTaxa as &$possibleRowToShiftFrom) { + // Will need to shift if an existing occurrence and not in the list of + // taxa taxon lists available in the grid. + if (isset($possibleRowToShiftFrom['occId']) && !array_key_exists($possibleRowToShiftFrom['ttlId'], $preferredFlagsByTtlId)) { + // Cycle through all taxa again to find a name with the same meaning but isn't the same name + // The first time we do this, only look for preferred names as we want to use that if available + foreach ($allRequestedTaxa as &$possibleRowToShiftTo) { + // If row to shift has already been processed then don't do anymore + // work. This can break the loop when a hit found. + if (!in_array($possibleRowToShiftFrom['ttlId'], $shiftedTtlIds)) { + // If the meaning is a match between the two rows but not the same taxa_taxon_list_id + if ($allRequestedTaxaByTtlId[$possibleRowToShiftFrom['ttlId']]['taxon_meaning_id'] == $allRequestedTaxaByTtlId[$possibleRowToShiftTo['ttlId']]['taxon_meaning_id'] && + $possibleRowToShiftFrom['ttlId'] != $possibleRowToShiftTo['ttlId'] && + array_key_exists($possibleRowToShiftTo['ttlId'], $preferredFlagsByTtlId) && + // Note cannot use TRUE, as boolean is supplied as string + $preferredFlagsByTtlId[$possibleRowToShiftTo['ttlId']] == 't') { + self::applyChangesToSwitchedRows($possibleRowToShiftFrom, $possibleRowToShiftTo, $shiftedTtlIds); } } - // If we need didn't find a preferred name, then continue to find a common name/synonym in similar way. - foreach ($taxaInOccDetail as &$possibleRowToShiftTo) { - // If row to shift already processed then don't do anymore - if (!in_array($possibleRowToShiftFrom['ttlId'], $shiftedTtlIds)) { - if ($fullDetailsOfTaxaInOccDetailFormatted[$possibleRowToShiftFrom['ttlId']]['taxon_meaning_id'] == $fullDetailsOfTaxaInOccDetailFormatted[$possibleRowToShiftTo['ttlId']]['taxon_meaning_id'] && - $possibleRowToShiftFrom['ttlId'] != $possibleRowToShiftTo['ttlId'] && - array_key_exists($possibleRowToShiftTo['ttlId'], $fullDetailsOfTaxonListFormatted) && - // Note cannot use TRUE as boolean is supplied as string - // Use != 't' instead of == 't' as it will catch all other cases if this result is corrupted for some reason - $fullDetailsOfTaxonListFormatted[$possibleRowToShiftTo['ttlId']]['preferred'] != 't') { - self::applyChangesToSwitchedRows($possibleRowToShiftFrom, $possibleRowToShiftTo, $shiftedTtlIds); - } + } + // If we need didn't find a preferred name, then continue to find a common name/synonym in similar way. + foreach ($allRequestedTaxa as &$possibleRowToShiftTo) { + // If row to shift has already been processed then don't do anymore + // work. This can break the loop when a hit found. + if (!in_array($possibleRowToShiftFrom['ttlId'], $shiftedTtlIds)) { + if ($allRequestedTaxaByTtlId[$possibleRowToShiftFrom['ttlId']]['taxon_meaning_id'] == $allRequestedTaxaByTtlId[$possibleRowToShiftTo['ttlId']]['taxon_meaning_id'] && + $possibleRowToShiftFrom['ttlId'] != $possibleRowToShiftTo['ttlId'] && + array_key_exists($possibleRowToShiftTo['ttlId'], $preferredFlagsByTtlId) && + // Note cannot use TRUE as boolean is supplied as string + // Use != 't' instead of == 't' as it will catch all other cases if this result is corrupted for some reason + $preferredFlagsByTtlId[$possibleRowToShiftTo['ttlId']] != 't') { + self::applyChangesToSwitchedRows($possibleRowToShiftFrom, $possibleRowToShiftTo, $shiftedTtlIds); } } } } } $shiftTaxaOntoNamesWithSameMeaningResults=[]; - $shiftTaxaOntoNamesWithSameMeaningResults['taxonRows'] = $taxaInOccDetail; + $shiftTaxaOntoNamesWithSameMeaningResults['taxonRows'] = $allRequestedTaxa; $shiftTaxaOntoNamesWithSameMeaningResults['shiftedTtlIds'] = $shiftedTtlIds; return $shiftTaxaOntoNamesWithSameMeaningResults; } /** - * Get formatted full details of the species list. - * - * Get species list with full details, - * using taxa_taxon_list_id as array key. + * Get's the preferred flag for each taxon, keyed by taxa taxon list ID. * - * @param array @readAuth - * Authentication tokens for the Warehouse. * @param array $taxonListItems - * The details of the taxa available on screen. + * The details of the taxa available to choose from. * * @return array + * Associative array of preferred flag values. */ - private static function createFormattedTaxaListForProcessing($readAuth, $taxonListItems) { - // Put the taxa_taxon_list IDS into a flat array so can be passed as query param. - $idsOfAllAvailableTtlRows = []; - foreach ($taxonListItems as $listRow) { - if (!empty($listRow['taxa_taxon_list_id'])) { - $idsOfAllAvailableTtlRows[] = $listRow['taxa_taxon_list_id']; - } - } - // Get the details of every item in the available species list - $fullDetailsOfTaxonList = data_entry_helper::get_population_data([ - 'table' => 'taxa_taxon_list', - 'extraParams' => $readAuth + [ - 'query' => json_encode(['in' => ['id' => $idsOfAllAvailableTtlRows]]), - 'view' => 'detail', - ], - ]); - $fullDetailsOfTaxonListFormatted = []; + private static function getPreferredFlagsByTtlId($taxonListItems) { + $preferredFlagsByTtlId = []; // Create array where the taxa_taxon_list_id is the key to make processing easier. - foreach ($fullDetailsOfTaxonList as $taxonListRowDetail) { - $fullDetailsOfTaxonListFormatted[$taxonListRowDetail['id']] = $taxonListRowDetail; + foreach ($taxonListItems as $taxonListRowDetail) { + $preferredFlagsByTtlId[$taxonListRowDetail['taxa_taxon_list_id']] = $taxonListRowDetail['preferred']; } - return $fullDetailsOfTaxonListFormatted; + return $preferredFlagsByTtlId; } /** @@ -4528,7 +4520,7 @@ private static function createFormattedTaxaListForProcessing($readAuth, $taxonLi * * @return array */ - public static function applyChangesToSwitchedRows(&$rowToShiftFrom, &$rowToShiftTo, &$shiftedTtlIds) { + private static function applyChangesToSwitchedRows(&$rowToShiftFrom, &$rowToShiftTo, &$shiftedTtlIds) { // Add the occurrence detail to the name we have found $rowToShiftTo['loadedTxIdx'] = $rowToShiftFrom['loadedTxIdx']; $rowToShiftTo['occId'] = $rowToShiftFrom['occId']; @@ -5468,7 +5460,7 @@ private static function getSpeciesChecklistColResponsive($options, $column) { * @return array * The taxon list to use in the grid. */ - public static function get_species_checklist_taxa_list($options, &$taxonRows) { + public static function getSpeciesChecklistTaxaList($options, &$taxonRows) { // Get the list of species that are always added to the grid, by first // building a filter or using preloaded ones. if (!empty($options['preloadTaxa'])) { @@ -5496,7 +5488,10 @@ public static function get_species_checklist_taxa_list($options, &$taxonRows) { // Create a list of the rows we are going to add to the grid, with // the preloaded species names linked to them. if ($taxonFilter == $taxon['taxa_taxon_list_id']) { - $taxonRows[] = array('ttlId'=>$taxon['taxa_taxon_list_id']); + $taxonRows[] = [ + 'ttlId' => $taxon['taxa_taxon_list_id'], + 'taxon_meaning_id' => $taxon['taxon_meaning_id'], + ]; } } } @@ -5504,7 +5499,10 @@ public static function get_species_checklist_taxa_list($options, &$taxonRows) { else { foreach ($taxalist as $taxon) { // create a list of the rows we are going to add to the grid, with the preloaded species names linked to them - $taxonRows[] = array('ttlId' => $taxon['taxa_taxon_list_id']); + $taxonRows[] = [ + 'ttlId' => $taxon['taxa_taxon_list_id'], + 'taxon_meaning_id' => $taxon['taxon_meaning_id'], + ]; } } // If there are any existing records to add to the list from the lookup From 61fd06414e2e0ee8db1e062d3272899603f79ee3 Mon Sep 17 00:00:00 2001 From: John van Breda Date: Fri, 22 Mar 2024 12:12:36 +0000 Subject: [PATCH 25/27] Report filters - vertical layout option --- prebuilt_forms/includes/report_filters.php | 84 ++++++++++++++++------ 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/prebuilt_forms/includes/report_filters.php b/prebuilt_forms/includes/report_filters.php index 3bc0e219..df27eb61 100644 --- a/prebuilt_forms/includes/report_filters.php +++ b/prebuilt_forms/includes/report_filters.php @@ -1378,6 +1378,10 @@ function status_filter_control($readAuth, $options) { * verification rule tools are available for the user to apply their own * data checks. Enables options for filtering on the outcome of custom * rule checks. + * * layout - defaults to horizontal, but can be set to vertical so that the + * panel buttons are arranged vertically. + * * initialState - defaults to closed, but can be set to open if you want + * the panel buttons to be always visible. Works well with vertical layout. * @param int $website_id * The current website's warehouse ID. * @param string $hiddenStuff @@ -1434,6 +1438,8 @@ function report_filter_panel(array $readAuth, $options, $website_id, &$hiddenStu 'period_within_year', 'without_polygon', ], + 'layout' => 'horizontal', + 'initialState' => 'closed', ], $options); // Introduce some extra quick filters useful for verifiers. if ($options['sharing'] === 'verification') { @@ -1470,6 +1476,7 @@ function report_filter_panel(array $readAuth, $options, $website_id, &$hiddenStu hostsite_add_library('collapse'); } $lang = [ + 'closeFilterBuilder' => lang::get('Close Filter Builder'), 'context' => lang::get('Context'), 'deleteFilter' => lang::get('Delete filter'), 'filter' => lang::get('Filter'), @@ -1477,6 +1484,7 @@ function report_filter_panel(array $readAuth, $options, $website_id, &$hiddenStu 'filterSaveInstruct' => lang::get('To save these filter settings for future use, give the filter a name then click Save Filter.'), 'filterName' => lang::get('Filter name'), 'saveFilter' => lang::get('Save filter'), + 'saveFilterAs' => lang::get('Save filter as'), 'selectStoredFilter' => lang::get('Select stored filter'), 'storedFilters' => lang::get('Stored filters'), ]; @@ -1651,7 +1659,7 @@ function report_filter_panel(array $readAuth, $options, $website_id, &$hiddenStu } } } - $r = '
        '; + $r = "
        "; if ($options['allowSave'] && $options['admin']) { if (empty($_GET['filters_user_id'])) { // New filter to create, so sharing type can be edited. @@ -1677,11 +1685,15 @@ function report_filter_panel(array $readAuth, $options, $website_id, &$hiddenStu $r .= data_entry_helper::hidden_text(['fieldname' => 'filter:sharing']); } } + $formInline = $options['layout'] === 'horizontal' ? ' form-inline' : ''; if ($options['allowLoad']) { + // Position of changed indicator asterisk depends on space available due to + // layout. + $changed = $options['layout'] === 'horizontal' ? "*" : ''; $r .= << +
        $lang[storedFilters] - + $changed HTML; $r .= '
        '; if ($customDefs) { @@ -1745,16 +1757,21 @@ function report_filter_panel(array $readAuth, $options, $website_id, &$hiddenStu 'class' => ' class="' . $indicia_templates['buttonDefaultClass'] . '"', 'caption' => lang::get('Reset'), ]); - $r .= helper_base::apply_static_template('button', [ - 'id' => 'filter-build', - 'title' => lang::get('Create a custom filter'), - 'class' => ' class="' . $indicia_templates['buttonDefaultClass'] . '"', - 'caption' => lang::get('Create a filter'), - ]); + if ($options['initialState'] === 'closed') { + $r .= helper_base::apply_static_template('button', [ + 'id' => 'filter-build', + 'title' => lang::get('Create a custom filter'), + 'class' => ' class="' . $indicia_templates['buttonDefaultClass'] . '"', + 'caption' => lang::get('Create a filter'), + ]); + } $r .= '
        '; - $r .= '