diff --git a/ElasticsearchProxyHelper.php b/ElasticsearchProxyHelper.php index af4d40cd..eaaf7a11 100644 --- a/ElasticsearchProxyHelper.php +++ b/ElasticsearchProxyHelper.php @@ -133,6 +133,12 @@ public static function callMethod($method, $nid) { case 'bulkmoveids': return self::proxyBulkMoveIds($nid); + case 'bulkeditall': + return self::proxyBulkEditAll($nid); + + case 'bulkeditids': + return self::proxyBulkEditIds($nid); + case 'clearcustomresults': return self::proxyClearCustomResults($nid); @@ -826,7 +832,11 @@ public static function getHttpRequestHeaders($config, $contentType = 'applicatio $headers[] = 'Authorization: ' . implode(':', $tokens); } else { - $keyFile = \Drupal::service('file_system')->realpath("private://") . '/rsa_private.pem'; + $keyFile = \Drupal::service('file_system')->realpath("private://") . '/private.key'; + if (!file_exists($keyFile)) { + // Fall back on legacy setup. + $keyFile = \Drupal::service('file_system')->realpath("private://") . '/rsa_private.pem'; + } if (!file_exists($keyFile)) { \Drupal::logger('iform')->error('Missing private key file for jwtUser Elasticsearch authentication.'); throw new ElasticsearchProxyAbort('Method not allowed as server configuration incomplete', 405); @@ -1602,11 +1612,11 @@ private static function applyFlagFilter($flag, array $definition, array &$bool) * Bool clauses that filters can be added to (e.g. $bool['must']). */ private static function applyUserFiltersSearchArea($definition, array &$bool) { - if (!empty($definition['searchArea'])) { + if (!empty($definition['searchArea']) || !empty($definition['bufferedSearchArea'])) { $bool['must'][] = [ 'geo_shape' => [ 'location.geom' => [ - 'shape' => $definition['searchArea'], + 'shape' => $definition['bufferedSearchArea'] ?? $definition['searchArea'], 'relation' => 'intersects', ], ], @@ -2700,36 +2710,47 @@ private static function getDefinitionFilter($definition, array $params) { } /** - * Receives a list of IDs to move between websites/datasets. - * - * Used by the recordsMover button. + * Either bulk move or edit a list of IDs. * * @return string - * Response from the data_utils/bulk_move service. + * Response from the data_utils/bulk_move or bulk_edit service. */ - private static function bulkMoveIds($nid, array $ids, $datasetMappings, $precheck) { + private static function bulkProcessIds($nid, array $ids, $service, array $data) { // Now do the move on the warehouse. iform_load_helpers(['helper_base']); - $request = helper_base::$base_url . "index.php/services/data_utils/bulk_move"; + $request = helper_base::$base_url . "index.php/services/data_utils/$service"; $conn = iform_get_connection_details($nid); $auth = helper_base::get_read_write_auth($conn['website_id'], $conn['password']); $postargs = helper_base::array_to_query_string(array_merge([ 'occurrence:ids' => implode(',', $ids), - 'datasetMappings' => $datasetMappings, - 'precheck' => $precheck ? 't' : 'f', - ], $auth['write_tokens']), TRUE); + ], $data, $auth['write_tokens']), TRUE); $response = helper_base::http_post($request, $postargs, FALSE); // The response should be in JSON. header('Content-type: application/json'); $output = json_decode($response['output']); - if (!$precheck && $output->code === 200) { + if (!($data['precheck'] ?? 'f' === 't') && isset($output->code) && $output->code === 200) { // Set website ID to 0, basically disabling the ES copy of the record - // until a proper update with correct taxonomy information comes through. + // until a proper update with correct information comes through. self::internalModifyListOnEs($ids, [], 0); } return $response['output']; } + /** + * Receives a list of IDs to move between websites/datasets. + * + * Used by the recordsMover button. + * + * @return string + * Response from the data_utils/bulk_move service. + */ + private static function bulkMoveIds($nid, array $ids, $datasetMappings, $precheck) { + return self::bulkProcessIds($nid, $ids, 'bulk_move', [ + 'datasetMappings' => $datasetMappings, + 'precheck' => $precheck ? 't' : 'f', + ]); + } + /** * Receives a filter defining records to move between websites/datasets. * @@ -2768,6 +2789,75 @@ private static function proxyBulkMoveIds($nid) { return self::bulkMoveIds($nid, explode(',', $_POST['occurrence:ids']), $_POST['datasetMappings'], !empty($_POST['precheck'])); } + /** + * Bulk edit a list of occurrence IDs. + * + * @param int $nid + * Node ID. + * @param array $ids + * List of occurrence IDs to edit. + * @param array $updates + * Key/value pairs for fields to change. Currently supports recorder_name, + * location_name, date and sref + sref_system. + * @param array $options + * Key/value pairs for any options to pass through. + * + * @return array + * Response data containing information about affected records. + */ + private static function bulkEditIds($nid, array $ids, array $updates, array $options) { + $response = self::bulkProcessIds($nid, $ids, 'bulk_edit', [ + 'updates' => json_encode($updates), + 'options' => json_encode($options), + ]); + return json_decode($response, TRUE); + } + + /** + * Bulk edit all records in the current filter. + * + * @param int $nid + * Node ID. + * + * @return array + * Response data containing information about affected records or next + * batch to fetch if paging. + */ + private static function proxyBulkEditAll($nid) { + $batchInfo = self::getOccurrenceIdPageFromFilter( + $nid, + $_POST['occurrence:idsFromElasticFilter'], + $_POST['search_after'] ?? NULL, + ); + if (empty($batchInfo['ids'])) { + return [ + 'code' => 204, + 'message' => 'No Content', + ]; + } + $response = self::bulkEditIds($nid, $batchInfo['ids'], $_POST['updates'], $_POST['options'] ?? []); + // Attach the search_after pagination info to the response. + if ($response['code'] === 200 && !empty($batchInfo['search_after'])) { + // Set pagination info, but not if empty array returned (which implies + // all done). + $response['search_after'] = $batchInfo['search_after']; + } + return $response; + } + +/** + * Bulk edit a list of selected records. + * + * @param int $nid + * Node ID. + * + * @return array + * Response data containing information about affected records. + */ + private static function proxyBulkEditIds($nid) { + return self::bulkEditIds($nid, explode(',', $_POST['occurrence:ids']), $_POST['updates'], $_POST['options'] ?? []); + } + /** * Proxy method that receives a filter and clears the user's custom flags. * diff --git a/ElasticsearchReportHelper.php b/ElasticsearchReportHelper.php index 5205a8fd..f09d06d3 100644 --- a/ElasticsearchReportHelper.php +++ b/ElasticsearchReportHelper.php @@ -349,11 +349,114 @@ public static function enableElasticsearchProxy($nid = NULL) { } catch (Exception $e) { self::$proxyEnableFailed = TRUE; + \Drupal::logger('iform')->error('Elasticsearch proxy enable failed: ' . $e->getMessage()); } } return self::$proxyEnabled; } + /** + * An Elasticsearch bulk editor tool. + * + * @return string + * Button container HTML. + * + * @link https://indicia-docs.readthedocs.io/en/latest/site-building/iform/helpers/elasticsearch-report-helper.html#elasticsearchreporthelper-bulkeditor + */ + public static function bulkEditor(array $options) { + self::checkOptions( + 'bulkEditor', + $options, + ['linkToDataControl'], + [] + ); + $options = array_merge([ + 'caption' => 'Bulk edit records', + 'restrictToOwnData' => TRUE, + ], $options); + $dataOptions = helper_base::getOptionsForJs($options, [ + 'id', + 'linkToDataControl', + 'restrictToOwnData', + ], TRUE); + helper_base::addLanguageStringsToJs('bulkEditor', [ + 'allowSampleSplitting' => 'Allow sample splitting?', + 'bulkEditorDialogMessageAll' => 'You are about to edit the entire list of {1} records.', + 'bulkEditorDialogMessageSelected' => 'You are about to edit {1} selected records.', + 'bulkEditProgress' => 'Edited {samples} samples and {occurrences} occurrences.', + 'cannotProceed' => 'Cannot proceed', + 'confirm' => 'Confirm', + 'done' => 'Records successfully edited. They will now be processed so they are available with their new values shortly.', + 'error' => 'An error occurred whilst trying to edit the records.', + 'errorEditNotFilteredToCurrentUser' => 'The records cannot be edited because the current page is not filtered to limit the records to only your data.', + 'preparing' => 'Preparing to edit the records...', + 'promptAllowSampleSplit' => '

The list of records to update contains occurrences which belong to samples that contain other occurrences which are not being updated. ' . + 'For example, sample {1} contains an occurrence {2} which is being updated, but it also contains occurrence {3} which is not being updated.

' . + '

Please confirm that you would like to split the samples so that the data values for the list of records you are editing can be updated without affecting other occurrences in the same samples.

', + 'warningNoChanges' => 'Please define at least one field value that you would like to change when bulk editing the records.', + 'warningNothingToDo' => 'There are no selected records to edit.', + ]); + $lang = [ + 'bulkEditRecords' => lang::get($options['caption']), + 'cancel' => lang::get('Cancel'), + 'close' => lang::get('Close'), + 'editing' => lang::get('Editing records'), + 'editInstructions' => 'Specify values to apply to all the edited records in the following controls, or leave blank for the data values to remain unchanged.', + 'proceed' => lang::get('Proceed'), + ]; + helper_base::add_resource('fancybox'); + $recorderNameControl = data_entry_helper::text_input([ + 'fieldname' => 'edit-recorder-name', + 'label' => lang::get('Recorder name'), + 'attributes' => ['placeholder' => lang::get('value not changed')], + ]); + $dateControl = data_entry_helper::text_input([ + 'fieldname' => 'edit-date', + 'label' => lang::get('Date'), + 'attributes' => ['placeholder' => lang::get('value not changed')], + ]); + $locationNameControl = data_entry_helper::text_input([ + 'fieldname' => 'edit-location-name', + 'label' => lang::get('Location name'), + 'attributes' => ['placeholder' => lang::get('value not changed')], + ]); + $srefControl = data_entry_helper::sref_and_system([ + 'fieldname' => 'edit-sref', + 'label' => lang::get('Spatial reference'), + 'attributes' => ['placeholder' => lang::get('value not changed')], + 'findMeButton' => FALSE, + ]); + global $indicia_templates; + $html = <<$lang[bulkEditRecords] +
+
+
+

$lang[bulkEditRecords]

+

+

$lang[editInstructions]

+ $recorderNameControl + $dateControl + $locationNameControl + $srefControl +
+ + +
+
+
+

$lang[editing]

+
+
+ +
+
+
+
+HTML; + return self::getControlContainer('bulkEditor', $options, $dataOptions, $html); + } + /** * An Elasticsearch records card gallery. * @@ -665,9 +768,40 @@ public static function download(array $options) { return self::getControlContainer('esDownload', $options, $dataOptions, $html); } + /** + * A scale for grid square opacity. + * + * Showing the number of records for each level. + * + * @param array $options + * Control options. + * + * @return string + * Scale container HTML. + * + * @link https://indicia-docs.readthedocs.io/en/latest/site-building/iform/helpers/elasticsearch-report-helper.html#elasticsearchreporthelper-gridsquareopacityscale + */ + public static function gridSquareOpacityScale(array $options) { + self::checkOptions('gridSquareOpacityScale', $options, + ['linkToDataControl', 'layer'], + [] + ); + helper_base::addLanguageStringsToJs('gridSquareOpacityScale', [ + 'noOfRecords' => 'No. of records', + ]); + $dataOptions = helper_base::getOptionsForJs($options, [ + 'id', + 'linkToDataControl', + 'layer', + ], TRUE); + return self::getControlContainer('gridSquareOpacityScale', $options, $dataOptions); + } + /** * Integrates the page with groups (activities). * + * @param array $options + * Control options. * @param bool $checkPage * Set to false to disable checking that the current page path is an iform * page linked to the group. @@ -835,6 +969,7 @@ private static function internalLocationSelect(array $options, $addGeomHiddenInp 'orderby' => 'name', ], $options['extraParams'], $options['readAuth']); $baseId = $options['id']; + $selectClass = $options['class']; foreach ($typeIds as $idx => $typeId) { $options['extraParams']['location_type_id'] = $typeId; if (count($typeIds) > 1) { @@ -843,7 +978,9 @@ 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. + // 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']; @@ -855,7 +992,7 @@ private static function internalLocationSelect(array $options, $addGeomHiddenInp // If locations are unindexed we need a place to store the geometry for // filtering. if ($addGeomHiddenInput) { - $r .= ""; + $r .= ""; } } return "
$r
"; @@ -1257,6 +1394,7 @@ public static function recordDetails(array $options) { helper_base::addLanguageStringsToJs('recordDetails', [ 'actionByPersonOnDate' => '{1} by {2} on {3}', 'comment' => 'Comment', + 'recordEntered' => 'Record entered', 'redetermination' => 'Redetermination', 'verificationDecision' => 'Verification', ]); @@ -1325,15 +1463,15 @@ public static function recordsMover(array $options) { ], TRUE); helper_base::addLanguageStringsToJs('recordsMover', [ 'cannotProceed' => 'Cannot proceed', - 'done' => 'Records successfully moved. They will be processed so they are available in their new location shortly.', + 'done' => 'Records successfully moved. They will now be processed so they are available in their new location shortly.', 'error' => 'An error occurred whilst trying to move the records.', 'errorNotFilteredToCurrentUser' => 'The records cannot be moved because the current page is not filtered to limit the records to only your data.', 'moveProgress' => 'Moved {samples} samples and {occurrences} occurrences.', 'moving' => 'Moving the records...', 'precheckProgress' => 'Checked {samples} samples and {occurrences} occurrences.', 'preparing' => 'Preparing to move the records...', - 'recordsMoverDialogMessageSelected' => 'You are about to move {1} selected records.', 'recordsMoverDialogMessageAll' => 'You are about to move the entire list of {1} records.', + 'recordsMoverDialogMessageSelected' => 'You are about to move {1} selected records.', 'warningNothingToDo' => 'There are no selected records to move.', ]); $lang = [ @@ -1348,7 +1486,7 @@ public static function recordsMover(array $options) { $html = <<$lang[moveRecords]
-
+

$lang[moveRecords]

diff --git a/helper_base.php b/helper_base.php index a7a82331..51b50f32 100644 --- a/helper_base.php +++ b/helper_base.php @@ -427,7 +427,10 @@ class helper_base { * * @var array */ - public static $indiciaData = []; + public static $indiciaData = [ + 'lang' => [], + 'templates' => [], + ]; /** * Inline JavaScript to be added to the page. @@ -730,9 +733,6 @@ public static function addLanguageStringsToJs($group, array $strings) { foreach ($strings as $key => $text) { $translations[$key] = lang::get($text); } - if (!isset(self::$indiciaData['lang'])) { - self::$indiciaData['lang'] = []; - } self::$indiciaData['lang'][$group] = $translations; } @@ -808,6 +808,7 @@ public static function getTranslations(array $strings) { * * jqplot_canvas_axis_label_renderer * * jqplot_trendline * * reportgrid + * * freeformReport * * tabs * * wizardprogress * * spatialReports @@ -1089,6 +1090,11 @@ public static function get_resources() { self::$js_path . 'jquery.reportgrid.js', ] ], + 'freeformReport' => [ + 'javascript' => [ + self::$js_path . 'jquery.freeformReport.js', + ] + ], 'reportfilters' => [ 'deps' => ['reportgrid'], 'stylesheets' => [self::$css_path . 'report-filters.css'], @@ -1225,9 +1231,11 @@ public static function get_resources() { self::$js_path . 'indicia.datacomponents/idc.pager.js', self::$js_path . 'indicia.datacomponents/jquery.idc.customScript.js', self::$js_path . 'indicia.datacomponents/jquery.idc.runCustomVerificationRulesets.js', + self::$js_path . 'indicia.datacomponents/jquery.idc.bulkEditor.js', self::$js_path . 'indicia.datacomponents/jquery.idc.cardGallery.js', self::$js_path . 'indicia.datacomponents/jquery.idc.dataGrid.js', self::$js_path . 'indicia.datacomponents/jquery.idc.esDownload.js', + self::$js_path . 'indicia.datacomponents/jquery.idc.gridSquareOpacityScale.js', self::$js_path . 'indicia.datacomponents/jquery.idc.leafletMap.js', self::$js_path . 'indicia.datacomponents/jquery.idc.recordsMover.js', self::$js_path . 'indicia.datacomponents/jquery.idc.recordDetailsPane.js', @@ -1235,6 +1243,7 @@ public static function get_resources() { self::$js_path . 'indicia.datacomponents/jquery.idc.verificationButtons.js', self::$js_path . 'indicia.datacomponents/jquery.idc.filterSummary.js', self::$js_path . 'indicia.datacomponents/jquery.idc.permissionFilters.js', + self::$js_path . 'proj4js.js', 'https://unpkg.com/@ungap/url-search-params', ], ], @@ -1254,7 +1263,7 @@ public static function get_resources() { 'leaflet', ], 'stylesheets' => [ - 'https://cdn.jsdelivr.net/gh/biologicalrecordscentre/brc-atlas@0.25.1/dist/brcatlas.umd.css', + 'https://cdn.jsdelivr.net/gh/biologicalrecordscentre/brc-atlas@0.25.1/dist/brcatlas.umd.min.css', ], 'javascript' => [ 'https://cdn.jsdelivr.net/gh/biologicalrecordscentre/brc-atlas@0.25.1/dist/brcatlas.umd.min.js', @@ -1265,10 +1274,10 @@ public static function get_resources() { 'd3', ], 'stylesheets' => [ - 'https://cdn.jsdelivr.net/gh/biologicalrecordscentre/brc-charts@0.15.0/dist/brccharts.umd.css', + 'https://cdn.jsdelivr.net/gh/biologicalrecordscentre/brc-charts/dist/brccharts.umd.min.css', ], 'javascript' => [ - 'https://cdn.jsdelivr.net/gh/biologicalrecordscentre/brc-charts@0.15.0/dist/brccharts.umd.min.js', + 'https://cdn.jsdelivr.net/gh/biologicalrecordscentre/brc-charts/dist/brccharts.umd.min.js', ], ], 'd3' => [ @@ -2020,6 +2029,7 @@ public static function mergeParamsIntoTemplate($params, $template, $useTemplateA array_push($replaceTags, '{' . $param . '-escape-dblquote}'); array_push($replaceTags, '{' . $param . '-escape-htmlquote}'); array_push($replaceTags, '{' . $param . '-escape-htmldblquote}'); + array_push($replaceTags, '{' . $param . '-escape-urlpath}'); } // Allow sep to have
. $value = ($param == 'sep' || $allowHtml) ? $value : htmlspecialchars($value ?? '', ENT_QUOTES, "UTF-8"); @@ -2033,6 +2043,7 @@ public static function mergeParamsIntoTemplate($params, $template, $useTemplateA array_push($replaceValues, str_replace('"', '\"', $value ?? '')); array_push($replaceValues, str_replace("'", "'", $value ?? '')); array_push($replaceValues, str_replace('"', '"', $value ?? '')); + array_push($replaceValues, trim(preg_replace('/[^a-z0-9\-]/', '', str_replace(' ', '-', strtolower($value ?? ''))), '-')); } } } @@ -2384,13 +2395,13 @@ public static function getIndiciaData() { require_once 'prebuilt_forms/includes/language_utils.php'; global $indicia_templates; // Add some useful templates. - self::$indiciaData['templates'] = [ + self::$indiciaData['templates'] = array_merge([ 'warningBox' => $indicia_templates['warningBox'], 'buttonDefaultClass' => $indicia_templates['buttonDefaultClass'], 'buttonHighlightedClass' => $indicia_templates['buttonHighlightedClass'], 'buttonSmallClass' => 'btn-xs', 'jQueryValidateErrorClass' => $indicia_templates['error_class'], - ]; + ], self::$indiciaData['templates']); self::$indiciaData['formControlClass'] = $indicia_templates['formControlClass']; self::$indiciaData['inlineErrorClass'] = $indicia_templates['error_class']; self::$indiciaData['dateFormat'] = self::$date_format; diff --git a/import_helper_2.php b/import_helper_2.php index 4fcbe06b..a64002e0 100644 --- a/import_helper_2.php +++ b/import_helper_2.php @@ -243,15 +243,18 @@ public static function extractFileOnWarehouse($fileName, array $writeAuth) { * Template ID if one was selected. * @param array $writeAuth * Write authorisation tokens. + * @param array $plugins + * List of enabled plugins as keys, with parameter arrays as values. * * @return array * Output of the web service request. */ - public static function initServerConfig($fileName, $importTemplateId, array $writeAuth) { + public static function initServerConfig($fileName, $importTemplateId, array $writeAuth, array $plugins = []) { $serviceUrl = self ::$base_url . 'index.php/services/import_2/init_server_config'; $data = $writeAuth + [ 'data-file' => $fileName, 'import_template_id' => $importTemplateId, + 'plugins' => json_encode($plugins), ]; $response = self::http_post($serviceUrl, $data, FALSE); $output = json_decode($response['output'], TRUE); @@ -530,7 +533,7 @@ private static function globalValuesForm(array $options) { 'value' => lang::get('Value'), ]; $template = self::loadSelectedTemplate($options); - if ($template && !empty($template['global_values'])) { + if (!empty($template) && !empty($template['global_values'])) { // Merge the template global values into the configuration's fixed // values. $globalValuesFromTemplate = json_decode($template['global_values'], TRUE); @@ -769,9 +772,10 @@ private static function mappingsForm(array $options) { 'incompleteFieldGroupSelected' => 'You have selected a mapping for the following field(s): {1}', 'suggestions' => 'Suggestions', ]); + $fileName = $_POST['data-file']; // Load the config for this import. $request = parent::$base_url . "index.php/services/import_2/get_config"; - $request .= '?' . http_build_query($options['readAuth'] + ['data-file' => $_POST['data-file']]); + $request .= '?' . http_build_query($options['readAuth'] + ['data-file' => $fileName]); $response = self::http_post($request, []); $config = json_decode($response['output'], TRUE); @@ -783,9 +787,9 @@ private static function mappingsForm(array $options) { throw new Exception('Service call to get_config failed.'); } self::$indiciaData['globalValues'] = $globalValues; - $availableFields = self::getAvailableDbFields($options, $globalValues); + $availableFields = self::getAvailableDbFields($options, $fileName, $globalValues); self::hideCoreFieldsIfReplaced($options, $globalValues['survey_id'], $availableFields); - $requiredFields = self::getAvailableDbFields($options, $globalValues, TRUE); + $requiredFields = self::getAvailableDbFields($options, $fileName, $globalValues, TRUE); // Only include required fields that are available for selection. Others // get populated by some other means. $requiredFields = array_intersect_key($requiredFields, $availableFields); @@ -1172,6 +1176,7 @@ private static function lookupMatchingForm(array $options) { */ private static function preprocessPage($options) { self::addLanguageStringsToJs('import_helper_2', [ + 'downloadPreprocessingErrorsExplanationsFile' => 'Download preprocessing errors explanations', 'importCannotProceed' => 'The import cannot proceed due to problems found in the data:', 'preprocessingError' => 'Preprocessing error', 'preprocessingErrorInfo' => 'An error occurred on the server whilst preprocessing your data:', @@ -1300,15 +1305,16 @@ private static function summaryPage(array $options) { 'title' => lang::get('Import summary'), 'value' => lang::get('Value'), ]; + $fileName = $_POST['data-file']; $request = parent::$base_url . "index.php/services/import_2/get_config"; - $request .= '?' . http_build_query($options['readAuth'] + ['data-file' => $_POST['data-file']]); + $request .= '?' . http_build_query($options['readAuth'] + ['data-file' => $fileName]); $response = self::http_post($request, []); $config = json_decode($response['output'], TRUE); if (!is_array($config)) { throw new Exception('Service call to get_config failed.'); } $ext = pathinfo($config['fileName'], PATHINFO_EXTENSION); - $availableFields = self::getAvailableDbFields($options, $config['global-values']); + $availableFields = self::getAvailableDbFields($options, $fileName, $config['global-values']); $mappingRows = []; $existingMatchFields = []; foreach ($config['columns'] as $columnLabel => $info) { @@ -1471,11 +1477,11 @@ private static function doImportPage($options) { ]); $lang = [ 'checkingData' => lang::get('Checking data'), + 'importAnother' => lang::get('Import another file'), 'importingDone' => lang::get('Import complete'), 'importingTitle' => lang::get('Importing the data...'), 'precheckDone' => 'Checking complete', 'precheckTitle' => 'Checking the import data for validation errors...', - 'precheckDone' => 'Checking complete', 'specifyUniqueTemplateName' => 'Please specify a unique name for the import template', ]; self::$indiciaData['readyToImport'] = TRUE; @@ -1487,6 +1493,8 @@ private static function doImportPage($options) { // Force a cache reload so the new template is instantly available. self::clearTemplateCache($options); } + $urlInfo = self::get_reload_link_parts(); + $restartUrl = $urlInfo['path'] . '?' . self::array_to_query_string($urlInfo['params']); return <<$lang[checkingData] @@ -1501,6 +1509,7 @@ private static function doImportPage($options) { +
+HTML; + } + else { + self::request_report($response, $options, $currentParamValues, false); + if (isset($response['error'])) return $response['error']; + $r = self::paramsFormIfRequired($response, $options, $currentParamValues); + // return the params form, if that is all that is being requested, or the parameters are not complete. + if ($options['paramsOnly'] || !isset($response['records'])) return $r; + $records = $response['records']; - $options = array_merge(array( - 'header' => '', - 'footer' => '', - 'bands' => [] - ), $options); + $options = array_merge(array( + 'header' => '', + 'footer' => '', + 'bands' => [] + ), $options); - if (!isset($records) || count($records)===0) { - return $r . $options['emptyText']; - } - // add a header - $r .= "
$options[header]"; - $rootFolder = self::getRootfolder(true); - $sep = strpos($rootFolder, '?')===FALSE ? '?' : '&'; - // output each row - foreach ($records as $row) { - // add some extra replacements for handling links - $row['rootFolder'] = $rootFolder; - $row['sep'] = $sep; - // for each row, check through the list of report bands - foreach ($options['bands'] as &$band) { - // default is to output a band - $outputBand = true; - // if the band has fields which trigger it to be output when they change value between rows, - // we need to check for changes to see if the band is to be output - if (isset($band['triggerFields'])) { - $outputBand = false; - // Make sure we have somewhere to store the current field values for checking against - if (!isset($band['triggerValues'])) - $band['triggerValues']=[]; - // look for changes in each trigger field - foreach ($band['triggerFields'] as $triggerField) { - if (!isset($band['triggerValues'][$triggerField]) || $band['triggerValues'][$triggerField]!=$row[$triggerField]) - // one of the trigger fields has changed value, so it means the band gets output - $outputBand=true; - // store the last value to compare against next time - $band['triggerValues'][$triggerField] = $row[$triggerField]; + if (!isset($records) || count($records)===0) { + return $r . $options['emptyText']; + } + // add a header + $r .= "
$options[header]"; + $rootFolder = self::getRootfolder(true); + $sep = strpos($rootFolder, '?')===FALSE ? '?' : '&'; + // output each row + foreach ($records as $row) { + // Add some extra replacements for handling links. + $row['rootFolder'] = $rootFolder; + $row['sep'] = $sep; + // For each row, check through the list of report bands. + foreach ($options['bands'] as &$band) { + // default is to output a band + $outputBand = true; + // if the band has fields which trigger it to be output when they change value between rows, + // we need to check for changes to see if the band is to be output + if (isset($band['triggerFields'])) { + $outputBand = false; + // Make sure we have somewhere to store the current field values for checking against + if (!isset($band['triggerValues'])) { + $band['triggerValues'] = []; + } + // look for changes in each trigger field + foreach ($band['triggerFields'] as $triggerField) { + if (!isset($band['triggerValues'][$triggerField]) || $band['triggerValues'][$triggerField]!=$row[$triggerField]) + // one of the trigger fields has changed value, so it means the band gets output + $outputBand=true; + // store the last value to compare against next time + $band['triggerValues'][$triggerField] = $row[$triggerField]; + } + } + // output the band only if it has been triggered, or has no trigger fields specified. + if ($outputBand) { + $row['imageFolder'] = self::get_uploaded_image_folder(); + $r .= self::apply_replacements_to_template($band['content'], $row); } - } - // output the band only if it has been triggered, or has no trigger fields specified. - if ($outputBand) { - $row['imageFolder'] = self::get_uploaded_image_folder(); - $r .= self::apply_replacements_to_template($band['content'], $row); } } + // add a footer + $r .= $options['footer'].'
'; + return $r; } - // add a footer - $r .= $options['footer'].'
'; - return $r; } /** @@ -2719,6 +2758,7 @@ private static function get_report_grid_actions($actions, $row, $pathParam = '') isset($value) ? str_replace("'", "'", $value) : NULL; $jsReplacements["$key-escape-htmldblquote"] = isset($value) ? str_replace('"', '"', $value) : NULL; + $jsReplacements["$key-escape-urlpath"] = trim(preg_replace('/[^a-z0-9\-]/', '', str_replace(' ', '-', strtolower($value))), '-'); } $links = []; $currentUrl = self::get_reload_link_parts(); // needed for params @@ -2990,13 +3030,13 @@ public static function report_calendar_grid($options) { $options = self::get_report_calendar_grid_options($options); $extras = ''; $currentParamValues = self::getReportGridCurrentParamValues($options); - self::request_report($response, $options, $currentParamValues, false, $extras); + self::request_report($response, $options, $currentParamValues, FALSE, $extras); if (isset($response['error'])) { return "ERROR RETURNED FROM request_report:".$response['error']; } // We're not even going to bother with asking the user to populate a partially filled in report parameter set. if (isset($response['parameterRequest'])) { - return '

Internal Error: Report request parameters not set up correctly.
'.(print_r($response,true)).'

'; + return '

Internal Error: Report request parameters not set up correctly.
'.(print_r($response,TRUE)).'

'; } self::$javascript .= " var pageURI = \"".$_SERVER['REQUEST_URI']."\"; @@ -3023,29 +3063,90 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { if(isset($dateRecords[$record['date']])) { $dateRecords[$record['date']][] = $record; } else { - $dateRecords[$record['date']] = array($record); + $dateRecords[$record['date']] = [$record]; } } $pageUrlParams = self::get_report_calendar_grid_page_url_params(); $pageUrl = self::report_calendar_grid_get_reload_url($pageUrlParams); - $pageUrl .= (strpos($pageUrl , '?')===false) ? '?' : '&'; + $pageUrl .= (strpos($pageUrl , '?') === FALSE) ? '?' : '&'; $thClass = $options['thClass']; - $r = "\n"; - $r .= "\n".($options['includeWeekNumber'] ? "" : "")."\n"; + $r = PHP_EOL . '
".lang::get("Week Number").""; - if(!isset($options["first_year"]) || $options["year"]>$options["first_year"]){ - $r .= "Prev"; - } - $r .= "".$options["year"].""; - if($options["year"] - Next"; - } - $r .= "
'; + $r .= PHP_EOL . '' . + '' . + ($options['includeWeekNumber'] ? + '' : + ''); + + $baseTheme = hostsite_get_config_value('iform.settings', 'base_theme', 'generic'); + $reloadURL = $pageUrl . $pageUrlParams['year']['name']."="; + $firstYear = (!empty($options["first_year"]) && $options["year"] >= $options["first_year"] ? $options["first_year"] : $options["year"]); + if ($baseTheme === 'generic') { + $template = '' . + '' . lang::get('Prev') . '' . + '' . + '{control}' . + ($options["year"] < date('Y') ? + '' . + '' . lang::get('Next') . '' . + '' : '') . + '' . + PHP_EOL; + } else { + $template = '
' . + '
' . + '
' . + '' . + '' . + '' . + '
' . + '{control}' . + ($options["year"] < date('Y') ? + '
' . + '' . + '' . + '' . + '
' : '') . + '
' . + '
' . PHP_EOL; + } + + global $indicia_templates; + $r .= ''; + $r .= "\n"; + // don't need a separate "Add survey" button as they just need to click the day.... // Not implementing a download. $r .= "\n"; - $date_from = array('year'=>$options["year"], 'month'=>1, 'day'=>1); + $date_from = ['year'=>$options["year"], 'month'=>1, 'day'=>1]; $weekno=0; // ISO Date - Mon=1, Sun=7 // Week 1 = the week with date_from in @@ -3059,7 +3160,7 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { $weeknumberfilter=explode(':',$options['weekNumberFilter']); if(count($weeknumberfilter)!=2){ $warnings .= "Week number filter unrecognised {".$options['weekNumberFilter']."} defaulting to all
"; - $weeknumberfilter=array('',''); + $weeknumberfilter = ['','']; } else { if($weeknumberfilter[0] != '' && (intval($weeknumberfilter[0])!=$weeknumberfilter[0] || $weeknumberfilter[0]>52)){ $warnings .= "Week number filter start unrecognised or out of range {".$weeknumberfilter[0]."} defaulting to year start
"; @@ -3075,7 +3176,9 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { if(!$weekstart_date){ $warnings .= "Weekstart month-day combination unrecognised {".$weekstart[1]."} defaulting to weekday=7 - Sunday
"; $weekstart[1]=7; - } else $weekstart[1]=$weekstart_date->format('N'); + } else { + $weekstart[1] = $weekstart_date->format('N'); + } } if(intval($weekstart[1])!=$weekstart[1] || $weekstart[1]<1 || $weekstart[1]>7) { $warnings .= "Weekstart unrecognised or out of range {".$weekstart[1]."} defaulting to 7 - Sunday
"; @@ -3088,7 +3191,7 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { $header_date=clone $consider_date; $r .= "".($options['includeWeekNumber'] ? "" : "").""; - require_once 'prebuilt_forms/includes/language_utils.php'; + require_once('prebuilt_forms/includes/language_utils.php'); $lang = iform_lang_iso_639_2(hostsite_get_user_field('language')); setlocale (LC_TIME, $lang); @@ -3105,8 +3208,9 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { $warnings .= "Week one month-day combination unrecognised {".$options['weekOneContains']."} defaulting to Jan-01
"; $weekOne_date = date_create($date_from['year'].'-Jan-01'); } - } else + } else { $weekOne_date = date_create($date_from['year'].'-Jan-01'); + } while($weekOne_date->format('N')!=$weekstart[1]){ $weekOne_date->modify('-1 day'); // scan back to start of week } @@ -3123,13 +3227,13 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { $now = new DateTime(); if($now < $consider_date && $options["viewPreviousIfTooEarly"]){ $options["year"]--; - $options["viewPreviousIfTooEarly"]=false; + $options["viewPreviousIfTooEarly"] = FALSE; unset($options['extraParams']['date_from']); unset($options['extraParams']['date_to']); return self::report_calendar_grid($options); } - $options["newURL"] .= (strpos($options["newURL"] , '?')===false) ? '?' : '&'; - $options["existingURL"] .= (strpos($options["existingURL"] , '?')===false) ? '?' : '&'; + $options["newURL"] .= (strpos($options["newURL"] , '?') === FALSE) ? '?' : '&'; + $options["existingURL"] .= (strpos($options["existingURL"] , '?') === FALSE) ? '?' : '&'; while($consider_date->format('Y') <= $options["year"] && ($weeknumberfilter[1]=='' || $consider_date->format('N')!=$weekstart[1] || $weekno < $weeknumberfilter[1])){ if($consider_date->format('N')==$weekstart[1]) { @@ -3148,15 +3252,16 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { if(isset($options['buildLinkFunc'])){ $options['consider_date'] = $consider_date->format('d/m/Y'); $callbackVal = call_user_func_array($options['buildLinkFunc'], - array(isset($dateRecords[$consider_date->format('d/m/Y')]) ? $dateRecords[$consider_date->format('d/m/Y')] : [], - $options, $cellContents)); + [isset($dateRecords[$consider_date->format('d/m/Y')]) ? $dateRecords[$consider_date->format('d/m/Y')] : [], + $options, $cellContents]); $cellclass=$callbackVal['cellclass']; $cellContents=$callbackVal['cellContents']; } else if(isset($dateRecords[$consider_date->format('d/m/Y')])){ // check if there is a record on this date $cellclass="existingLink"; $cellContents .= '
 
'; - foreach($dateRecords[$consider_date->format('d/m/Y')] as $record) + foreach ($dateRecords[$consider_date->format('d/m/Y')] as $record) { $cellContents.='format('d/m/Y').'" >
 
'; + } } else { $cellclass="newLink"; $cellContents .= '
 
'; @@ -3178,7 +3283,7 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { if (isset($options['footer']) && !empty($options['footer'])) { $rootFolder = self::getRootfolder(); $currentUrl = self::get_reload_link_parts(); - $footer = str_replace(array('{rootFolder}', + $footer = str_replace(['{rootFolder}', '{currentUrl}', '{sep}', '{warehouseRoot}', @@ -3188,8 +3293,8 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { '{iUserID}', '{website_id}', '{startDate}', - '{endDate}'), - array($rootFolder, + '{endDate}'], + [$rootFolder, $currentUrl['path'], strpos($rootFolder, '?')===FALSE ? '?' : '&', self::$base_url, @@ -3200,10 +3305,10 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { self::$website_id, $options['extraParams']['date_from'], $options['extraParams']['date_to'] - ), $options['footer']); + ], $options['footer']); // Merge in any references to the parameters sent to the report: could extend this in the future to pass in the extraParams foreach($currentParamValues as $key=>$param){ - $footer = str_replace(array('{'.$key.'}'), array($param), $footer); + $footer = str_replace(['{'.$key.'}'], [$param], $footer); } $extraFooter .= '
'.$footer.'
'; } @@ -3218,26 +3323,32 @@ function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { * @param array $options Options array passed to the control. */ private static function get_report_calendar_grid_options($options) { + if (function_exists('hostsite_get_config_value')) { + $baseTheme = hostsite_get_config_value('iform.settings', 'base_theme', 'generic'); + } else { + $baseTheme = 'generic'; + } + $userId = hostsite_get_user_field('id'); - $options = array_merge(array( + $options = array_merge([ 'mode' => 'report', 'id' => 'calendar-report-output', // this needs to be set explicitly when more than one report on a page - 'class' => 'ui-widget ui-widget-content report-grid', - 'thClass' => 'ui-widget-header', + 'class' => $baseTheme === 'generic' ? 'ui-widget ui-widget-content report-grid' : 'table report-grid', + 'thClass' => $baseTheme === 'generic' ? 'ui-widget-header' : '', 'extraParams' => [], 'year' => date('Y'), - 'viewPreviousIfTooEarly' => true, // if today is before the start of the calendar, display last year. + 'viewPreviousIfTooEarly' => TRUE, // if today is before the start of the calendar, display last year. // it is possible to create a partial calendar. - 'includeWeekNumber' => false, + 'includeWeekNumber' => FALSE, 'weekstart' => 'weekday=7', // Default Sunday 'weekNumberFilter' => ':' - ), $options); - $options["extraParams"] = array_merge(array( + ], $options); + $options["extraParams"] = array_merge([ 'date_from' => $options["year"].'-01-01', 'date_to' => $options["year"].'-12-31', 'user_id' => $userId, // Initially CMS User, changed to Indicia User later if in Easy Login mode. 'cms_user_id' => $userId, // CMS User, not Indicia User. - 'smpattrs' => ''), $options["extraParams"]); + 'smpattrs' => ''], $options["extraParams"]); $options['my_user_id'] = $userId; // Initially CMS User, changed to Indicia User later if in Easy Login mode. // Note for the calendar reports, the user_id is assumed to be the CMS user id as recorded in the CMS User ID attribute, // not the Indicia user id. @@ -3247,7 +3358,7 @@ private static function get_report_calendar_grid_options($options) { $options["extraParams"]['user_id'] = $indicia_user_id; } if ($options['my_user_id']) { // false switches this off. - $user_id = hostsite_get_user_field('indicia_user_id', false, false, $options['my_user_id']); + $user_id = hostsite_get_user_field('indicia_user_id', FALSE, FALSE, $options['my_user_id']); if(!empty($user_id)) { $options['my_user_id'] = $user_id; } @@ -3263,12 +3374,12 @@ private static function get_report_calendar_grid_options($options) { */ private static function get_report_calendar_grid_page_url_params() { $yearKey = 'year'; - return array( - 'year' => array( + return [ + 'year' => [ 'name' => $yearKey, 'value' => isset($_GET[$yearKey]) ? $_GET[$yearKey] : null - ) - ); + ] + ]; } /** * Build a url suitable for inclusion in the links for the report calendar grid column pagination @@ -3290,7 +3401,7 @@ private static function report_calendar_grid_get_reload_url($pageUrlParams) { } foreach ($reloadUrl['params'] as $key => $value) { if (!in_array($key, $excludedParams)){ - $reloadUrl['path'] .= (strpos($reloadUrl['path'],'?')===false ? '?' : '&')."$key=$value"; + $reloadUrl['path'] .= (strpos($reloadUrl['path'],'?') === FALSE ? '?' : '&')."$key=$value"; } } return $reloadUrl['path']; @@ -4140,14 +4251,14 @@ function replot(){ } $r .= "
' . lang::get('Week Number') . ''; + $indicia_templates['rcg_controlWrap'] = $template; + $ctrlid = 'year-select'; + $lookUpValues = range(date('Y'), $firstYear, -1); + $lookUpValues = array_combine($lookUpValues, $lookUpValues); + if ($baseTheme === 'generic') { + $r .= str_replace(['{control}', '{id}'], ['' . $options["year"] . '', $ctrlid], $template); + } else { + if(empty($options["first_year"]) && $options["year"] == date('Y')) { + $r .= data_entry_helper::text_input([ + 'id' => $ctrlid, + 'class' => 'year-select', + 'disabled' => ' disabled="disabled" ', + 'fieldname' => 'year-select', + 'default' => $options["year"], + 'controlWrapTemplate' => 'rcg_controlWrap' + ]); + } else { + $r .= data_entry_helper::select([ + 'id' => $ctrlid, + 'class' => 'year-select', + 'fieldname' => 'year-select', + 'lookupValues' => $lookUpValues, + 'default' => $options["year"], + 'controlWrapTemplate' => 'rcg_controlWrap' + ]); + report_helper::$javascript .= "$('#".$ctrlid."').on('input', function() { window.location.href= '" . $reloadURL . "'+$(this).val(); });" . PHP_EOL; + } + } + $r .= '
\n"; } - $summaryDataDownloadGrid=""; + $summaryDataDownloadGrid = ""; $r .= "\n"; $r .= "\n"; if($options['tableHeaders'] == 'both' || $options['tableHeaders'] == 'number'){ $r .= ''.$tableNumberHeaderRow.($options['includeTableTotalColumn'] - ?($options['includeSummaryData'] ? '' : ''). - ($options['includeEstimatesData'] ? '' : '') - :'').''; + ? ($options['includeSummaryData'] ? '' : '') . + ($options['includeEstimatesData'] ? '' : '') + : '') . ''; $summaryDataDownloadGrid .= 'Week'.$downloadNumberHeaderRow.($options['includeTableTotalColumn'] ?($options['includeSummaryData'] ? ',Total' : '') :'')."\n"; @@ -4530,20 +4641,22 @@ private static function report_calendar_summary_processEstimates(&$summaryArray, * @return array The processed options array. */ private static function get_report_calendar_summary_options($options) { - $options = array_merge(array( + $options = array_merge([ 'mode' => 'report', 'id' => 'calendar-report-output', // this needs to be set explicitly when more than one report on a page 'tableContainerID' => 'tablediv-container', 'tableID' => 'report-table', 'tableClass' => 'ui-widget ui-widget-content report-grid', + 'theadClass' => 'ui-widget-header', 'thClass' => 'ui-widget-header', 'altRowClass' => 'odd', 'extraParams' => [], - 'viewPreviousIfTooEarly' => true, // if today is before the start of the calendar, display last year. + 'viewPreviousIfTooEarly' => TRUE, // if today is before the start of the calendar, display last year. // it is possible to create a partial calendar. - 'includeWeekNumber' => false, + 'includeWeekNumber' => FALSE, 'weekstart' => 'weekday=7', // Default Sunday 'weekNumberFilter' => ':', + 'inSeasonFilter' => '', 'rowGroupColumn' => 'taxon', 'rowGroupID' => 'taxa_taxon_list_id', 'chartContainerID' => 'chartdiv-container', @@ -4556,11 +4669,11 @@ private static function get_report_calendar_summary_options($options) { 'rendererOptions' => [], 'legendOptions' => [], 'axesOptions' => [], - 'includeRawData' => true, - 'includeSummaryData' => true, - 'includeEstimatesData' => false, - 'includeTableTotalColumn' => true, - 'includeTableTotalRow' => true, + 'includeRawData' => TRUE, + 'includeSummaryData' => TRUE, + 'includeEstimatesData' => FALSE, + 'includeTableTotalColumn' => TRUE, + 'includeTableTotalRow' => TRUE, 'tableHeaders' => 'date', 'rawDataCombining' => 'add', 'dataRound' => 'nearest', @@ -4570,31 +4683,41 @@ private static function get_report_calendar_summary_options($options) { 'interpolation' => 'linear', 'firstValue' => 'none', 'lastValue' => 'none', - 'highlightEstimates' => false, - 'includeRawGridDownload' => false, - 'includeRawListDownload' => true, - 'includeSummaryGridDownload' => false, - 'includeEstimatesGridDownload' => false, - 'includeListDownload' => true, - 'downloadFilePrefix' => '' - ), $options); - $options["extraParams"] = array_merge(array( - 'date_from' => $options['date_start'], - 'date_to' => $options['date_end'], + 'highlightEstimates' => FALSE, + 'includeRawGridDownload' => FALSE, + 'includeRawListDownload' => TRUE, + 'includeSummaryGridDownload' => FALSE, + 'includeEstimatesGridDownload' => FALSE, + 'includeListDownload' => TRUE, + 'downloadFilePrefix' => '', + 'training' => FALSE + ], $options); + $options["extraParams"] = array_merge([ + 'date_from' => $options['date_start'], + 'date_to' => $options['date_end'], // 'user_id' => '', // CMS User, not Indicia User. // 'smpattrs' => '', - 'occattrs' => ''), $options["extraParams"]); + 'occattrs' => '', + 'training' => 'f' + ], + $options["extraParams"] + ); // Note for the calendar reports, the user_id is initially assumed to be the CMS user id as recorded in the CMS User ID attribute, // not the Indicia user id: we do the conversion here. if (isset($options["extraParams"]['user_id'])) { $options["extraParams"]['cms_user_id'] = $options["extraParams"]['user_id']; if (function_exists('hostsite_get_user_field') && $options["extraParams"]['user_id']!='') { - $user_id = hostsite_get_user_field('indicia_user_id', false, false, $options["extraParams"]['user_id']); - if(!empty($user_id)) + $user_id = hostsite_get_user_field('indicia_user_id', FALSE, FALSE, $options["extraParams"]['user_id']); + if (!empty($user_id)) { $options["extraParams"]['user_id'] = $user_id; + } } } + if (function_exists('hostsite_get_user_field') && hostsite_get_user_field('training', FALSE)) { + $options['training'] = TRUE; + $options['extraParams']['training'] = 't'; + } // Note for the calendar reports, the user_id is assumed to be the CMS user id as recorded in the CMS User ID attribute, // not the Indicia user id. @@ -4638,6 +4761,10 @@ private static function get_report_calendar_summary_options($options) { * Examples: "1:30" - Weeks one to thirty inclusive. * "4:" - Week four onwards. * ":5" - Upto and including week five. + *
  • inSeasonFilter + * Optional colon separated number pair. Used to produce an additional In-season total column in Estimates grid. + * Leave blank to omit the column. If provided, both numbers must be given. + * Examples: "1:26" - Weeks one to twemty-six inclusive.
  • *
  • rowGroupColumn * The column in the report which is used as the label for the vertical axis on the grid.
  • *
  • rowGroupID @@ -4646,383 +4773,468 @@ private static function get_report_calendar_summary_options($options) { * OPTIONAL: The column in the report which contains the count for this occurrence. If omitted then the default * is to assume one occurrence = count of 1
  • *
  • includeChartItemSeries - * Defaults to true. Include a series for each item in the report output. + * Defaults to TRUE. Include a series for each item in the report output. *
  • *
  • includeChartTotalSeries - * Defaults to true. Include a series for the total of each item in the report output. + * Defaults to TRUE. Include a series for the total of each item in the report output. *
  • * * @todo: Future Enhancements? Allow restriction to month. */ public static function report_calendar_summary2($options) { - $r=""; - // I know that there are better ways to approach some of the date manipulation, but they are PHP 5.3+. - // We support back to PHP 5.2 - // TODO put following JS into a control JS file. + $r = ""; + // I know that there are better ways to approach some of the date manipulation, but they are PHP 5.3+. + // We support back to PHP 5.2 + // TODO put following JS into a control JS file. - self::add_resource('jquery_ui'); + self::add_resource('jquery_ui'); - $extraParams = $options['readAuth'] + array('survey_id'=>$options['survey_id']); - $definition = data_entry_helper::get_population_data(array( - 'table' => 'summariser_definition', - 'extraParams'=>$extraParams, - )); - if (isset($records['error'])) return $records['error']; - if(count($definition) != 1) { - return 'ERROR: could not find a single summariser_definition records for survey_id ' . $options['survey_id'] . "\n".print_r($definition,true); + $definition = data_entry_helper::get_population_data([ + 'table' => 'summariser_definition', + 'extraParams' => $options['readAuth'] + ['survey_id' => $options['survey_id']], + ]); + if (isset($records['error'])) { + return $records['error']; + } + if (count($definition) != 1) { + return 'ERROR: could not find a single summariser_definition records for survey_id ' . + $options['survey_id'] . PHP_EOL . + print_r($definition, TRUE); } - $options = self::get_report_calendar_summary_options($options); // don't use all of these now, eg. extraParams: this is used later for raw data - $extraParams = $options['readAuth'] + array('year'=>$options['year'], 'survey_id'=>$options['survey_id']); - // at the moment the summary_builder module indexes the user_id on the created_by_id field on the parent sample. - // this effectively means that it assumes easy_login. - // Also means we have to use the converted Indicia user_id, stored by options function above in the extraParams. + $options = self::get_report_calendar_summary_options($options); + $options['caching'] = isset($options['caching']) ? $options['caching'] : TRUE; + // Don't use all of these now, eg. extraParams: this is used later for raw data + // At the moment the summary_builder module indexes the user_id on the created_by_id field on the parent sample. + // This effectively means that it assumes easy_login. // user_id and location_ids values of '0' imply "all" - $extraParams['user_id'] = $options['extraParams']['user_id']; - if(isset($options['taxon_list_id']) && $options['taxon_list_id']!="") - $extraParams['taxon_list_id'] = $options['taxon_list_id']; - - if(!empty($options['location_id'])) - $extraParams['location_id'] = $options['location_id']; - else if(!empty($options['location_list'])) - $extraParams['query'] = urlencode(json_encode(array('in'=>array('location_id', $options['location_list'])))); - else $extraParams['location_id'] = 0; // default to 'all' + // set up extra params for the summary_occurrence fetch: options extra params is for Raw data report + $extraParams = $options['readAuth'] + [ + 'year' => $options['year'], + 'survey_id' => $options['survey_id'], + 'user_id' => $options['summary_user_id'], + 'training' => $options['training'] + ]; + if (!empty($options['extraParams']['taxon_list_id'])) { + $extraParams['taxon_list_id'] = $options['extraParams']['taxon_list_id']; + } + if (isset($options['summary_location_id'])) { + $extraParams['location_id'] = $options['summary_location_id']; + $options['extraParams']['location_id'] = $options['summary_location_id']; + } + else if (isset($options['extraParams']['location_list'])) { + $extraParams['query'] = urlencode(json_encode(['in' => ['location_id', explode(',', $options['extraParams']['location_list'])]])); + } + else { + $options['valid'] = FALSE; // default to none + } $extraParams['columns'] = 'type,taxa_taxon_list_id,taxonomic_sort_order,taxon,preferred_taxon,' . 'default_common_name,taxon_meaning_id,summarised_data'; - $options['caching'] = isset($options['caching']) ? $options['caching'] : true; - $records = data_entry_helper::get_population_data(array( - 'table' => 'summary_occurrence', - 'extraParams'=>$extraParams, - 'caching'=> $options['caching'] - )); - if (isset($records['error'])) return $records['error']; - self::$javascript .= " -var pageURI = \"".$_SERVER['REQUEST_URI']."\"; -function rebuild_page_url(oldURL, overrideparam, overridevalue) { + + $records = $options['valid'] ? + data_entry_helper::get_population_data([ + 'table' => 'summary_occurrence', + 'extraParams'=>$extraParams, + 'caching'=> $options['caching'] + ]) : + []; + if (isset($records['error'])) { + hostsite_show_message(print_r($records,true)); + return $records['error']; + } + + self::$javascript .= " +var pageURI = '" . $_SERVER['REQUEST_URI'] . "'; +function rebuild_page_url(oldURL, overrideparam, overridevalue, removeparam) { var parts = oldURL.split('?'); var params = []; - if(overridevalue!=='') params.push(overrideparam+'='+overridevalue); + if (overridevalue!=='') { + params.push(overrideparam+'='+overridevalue); + } if(parts.length > 1) { var oldparams = parts[1].split('&'); for(var i = 0; i < oldparams.length; i++){ var bits = oldparams[i].split('='); - if(bits[0] != overrideparam) params.push(oldparams[i]); + if (bits[0] != overrideparam && removeparam.indexOf(bits[0])<0) { + params.push(oldparams[i]); + } } } - return parts[0]+(params.length > 0 ? '?'+params.join('&') : ''); + return parts[0] + (params.length > 0 ? '?' + (params.join('&')) : ''); }; - $('#year-control-previous').attr('href',rebuild_page_url(pageURI,'year',".$options['year']."-1)); - $('#year-control-next').attr('href',rebuild_page_url(pageURI,'year',".$options['year']."+1)); +$('#year-control-previous').attr('href',rebuild_page_url(pageURI,'year',".$options['year']."-1, [])); +$('#year-control-next').attr('href',rebuild_page_url(pageURI,'year',".$options['year']."+1, [])); // user and location ids are dealt with in the main form. Their change functions look at pageURI "; - // ISO Date - Mon=1, Sun=7 - // Week 1 = the week with date_from in + // ISO Date - Mon=1, Sun=7 + // Week 1 = the week with date_from in // The summariser_definition period_start is mandatory $options['weekNumberFilter'] = empty($options['weekNumberFilter']) ? ':' : $options['weekNumberFilter']; $periodNumberFilter=explode(':',$options['weekNumberFilter']); - if(count($periodNumberFilter)!=2){ + if (count($periodNumberFilter)!=2) { return "Period number filter unrecognised {".$options['weekNumberFilter']."}"; - } - if($periodNumberFilter[0] != '' && (intval($periodNumberFilter[0])!=$periodNumberFilter[0] || $periodNumberFilter[0]>52)){ - return "Period number filter start unrecognised or out of range {".$periodNumberFilter[0]."}"; - } - if($periodNumberFilter[1] != '' && (intval($periodNumberFilter[1])!=$periodNumberFilter[1] || $periodNumberFilter[1]<$periodNumberFilter[0] || $periodNumberFilter[1]>52)){ - return "Period number filter end unrecognised or out of range {".$periodNumberFilter[1]."}"; - } - $periodStart=explode('=',$definition[0]['period_start']); - if($periodStart[0] == 'date'){ - if(!($periodStartDate = date_create($options['year']."-".$periodStart[1]))){ - return "Period start unrecognised {".$definition[0]['period_start']."}"; - } + } + if ($periodNumberFilter[0] != '' && (intval($periodNumberFilter[0]) != $periodNumberFilter[0] || $periodNumberFilter[0] > 52)) { + return "Period number filter start unrecognised or out of range {" . $periodNumberFilter[0] . "}"; + } + if ($periodNumberFilter[1] != '' && (intval($periodNumberFilter[1]) != $periodNumberFilter[1] || $periodNumberFilter[1] < $periodNumberFilter[0] || $periodNumberFilter[1] > 52)) { + return "Period number filter end unrecognised or out of range {" . $periodNumberFilter[1] . "}"; + } + + if (empty($options['inSeasonFilter'])) { + $inSeason = FALSE; + } else { + $inSeason = explode(':', $options['inSeasonFilter']); + if (count($inSeason) != 2) { + return "In-season specification format unrecognised {".$options['weekNumberFilter']."}"; + } + if ($inSeason[0] != '' && (intval($inSeason[0]) != $inSeason[0] || $inSeason[0] > 52)) { + return "In-season specification start unrecognised or out of range {" . $inSeason[0] . "}"; + } + if ($inSeason[1] != '' && (intval($inSeason[1]) != $inSeason[1] || $inSeason[1] < $inSeason[0] || $inSeason[1] > 52)) { + return "In-season specification end unrecognised or out of range {" . $periodNumberFilter[1] . "}"; + } + } + + $periodStart = explode('=',$definition[0]['period_start']); + if ($periodStart[0] == 'date') { + if(!($periodStartDate = date_create($options['year'] . "-" . $periodStart[1]))){ + return "Period start unrecognised {" . $definition[0]['period_start'] ."}"; + } $periodStart = $periodStartDate->format('N'); - } else $periodStart = $periodStart[1]; - if(intval($periodStart)!=$periodStart || $periodStart<1 || $periodStart>7) { - return "Period start unrecognised or out of range {".$periodStart."}"; - } - if(!($periodOneDate = date_create($options['year'].'-'.$definition[0]['period_one_contains']))) { - return "Period one unrecognised {".$definition[0]['period_one_contains']."}"; - } + } else { + $periodStart = $periodStart[1]; + } + if (intval($periodStart) != $periodStart || $periodStart < 1 || $periodStart > 7) { + return "Period start unrecognised or out of range {" . $periodStart . "}"; + } + if (!($periodOneDate = date_create($options['year'] . '-' . $definition[0]['period_one_contains']))) { + return "Period one unrecognised {" . $definition[0]['period_one_contains'] . "}"; + } $periodOneDateWeekday = $periodOneDate->format('N'); - if($periodOneDateWeekday > $periodStart) // scan back to start of week - $periodOneDate->modify('-'.($periodOneDateWeekday-$periodStart).' day'); - else if($periodOneDateWeekday < $periodStart) - $periodOneDate->modify('-'.(7+$periodOneDateWeekday-$periodStart).' day'); + if ($periodOneDateWeekday > $periodStart) { // scan back to start of week + $periodOneDate->modify('-' . ($periodOneDateWeekday-$periodStart) . ' day'); + } + else if ($periodOneDateWeekday < $periodStart) { + $periodOneDate->modify('-' . (7+$periodOneDateWeekday-$periodStart) . ' day'); + } $firstPeriodDate = clone $periodOneDate; // date we start providing data for $periodOneDateYearDay = $periodOneDate->format('z'); // day within year note year_start_yearDay is by definition 0 - $minPeriodNo = $periodNumberFilter[0]!='' ? $periodNumberFilter[0] : 1; + $minPeriodNo = $periodNumberFilter[0] != '' ? $periodNumberFilter[0] : 1; $numPeriods = ceil($periodOneDateYearDay/7); // number of periods in year prior to $periodOneDate - 1st Jan gives zero, 2nd-8th Jan gives 1, etc - if($minPeriodNo-1 < (-1 * $numPeriods)) $minPeriodNo=(-1 * $numPeriods)+1; // have to allow for period zero - if($minPeriodNo < 1) + if ($minPeriodNo-1 < (-1 * $numPeriods)) { + $minPeriodNo=(-1 * $numPeriods)+1; // have to allow for period zero + } + if ($minPeriodNo < 1) { $firstPeriodDate->modify((($minPeriodNo-1)*7).' days'); // have to allow for period zero - else if($minPeriodNo > 1) + } + else if ($minPeriodNo > 1) { $firstPeriodDate->modify('+'.(($minPeriodNo-1)*7).' days'); + } - if($periodNumberFilter[1]!=''){ + if ($periodNumberFilter[1]!='') { $maxPeriodNo = $periodNumberFilter[1]; - } else { - $yearEnd = date_create($options['year'].'-Dec-25'); // don't want to go beyond the end of year: this is 1st Jan minus 1 week: it is the start of the last full week + } else { + $yearEnd = date_create($options['year'] . '-Dec-25'); // don't want to go beyond the end of year: this is 1st Jan minus 1 week: it is the start of the last full week $yearEndYearDay = $yearEnd->format('z'); // day within year $maxPeriodNo = 1+ceil(($yearEndYearDay-$periodOneDateYearDay)/7); - } + } // Initialise data $tableNumberHeaderRow = $tableDateHeaderRow = $downloadNumberHeaderRow = $downloadDateHeaderRow = ""; $summaryDataDownloadGrid = $estimateDataDownloadGrid = $rawDataDownloadGrid = ''; - $chartNumberLabels=[]; - $chartDateLabels=[]; - $fullDates=[]; - $summaryArray=[]; // this is used for the table output format - $rawArray=[]; // this is used for the table output format - // In order to apply the data combination and estmation processing, we assume that the the records are in taxon, location_id, sample_id order. - $locationArray=[]; // this is for a single species at a time. - $lastLocation=false; - $seriesLabels=[]; - $lastTaxonID=false; - $lastSample=false; - $locationSamples = []; + $chartNumberLabels = []; + $chartDateLabels = []; + $fullDates = []; + $summaryArray = []; // this is used for the table output format + $rawArray = []; // this is used for the table output format + // In order to apply the data combination and estmation processing, we assume that the the records are in taxon, location_id, sample_id order. + $locationArray = []; // this is for a single species at a time. + $lastLocation = FALSE; + $seriesLabels = []; + $lastTaxonID = FALSE; + $lastSample = FALSE; + $locationSamples = []; $periodList = []; $grandTotal=0; $totalRow = []; - $estimatesGrandTotal=0; + $estimatesGrandTotal = $inSeasonEstimatesGrandTotal = 0; $totalEstimatesRow = []; - $seriesIDs=[]; - $summarySeriesData=[]; - $estimatesSeriesData=[]; - $seriesOptions=[]; - - for($i= $minPeriodNo; $i <= $maxPeriodNo; $i++){ - $tableNumberHeaderRow.= ''; - $tableDateHeaderRow.= ''; - $downloadNumberHeaderRow.= ','.$i; - $downloadDateHeaderRow.= ','.$firstPeriodDate->format('d/m/Y'); - $chartNumberLabels[] = "".$i; - $chartDateLabels[] = $firstPeriodDate->format('M-d'); - $fullDates[$i] = $firstPeriodDate->format('d/m/Y'); - $firstPeriodDate->modify('+7 days'); - } - - $sampleFieldList = !empty($options['sampleFields']) ? explode(',',$options['sampleFields']) : false; - if(empty($sampleFieldList)) - $sampleFields = false; - else { - $sampleFields = []; - foreach($sampleFieldList as $sampleField) { - $parts = explode(':',$sampleField); - $field = array('caption'=>$parts[0], 'field'=>$parts[1], 'attr'=>false); - if(count($parts)==3 && $parts[1]='smpattr') { - $smpAttribute=data_entry_helper::get_population_data(array( - 'table' => 'sample_attribute', - 'extraParams'=>$options['readAuth'] + array('view' => 'list', 'id'=>$parts[2]) - )); - if(count($smpAttribute)>=1){ // may be assigned to more than one survey on this website. This is not relevant to info we want. - $field['id'] = $parts[2]; - $field['attr'] = $smpAttribute[0]; - } - } - $sampleFields[] = $field; - } - } - - $count = count($records); - $sortData=[]; - foreach($records as $index => $record){ - $taxonMeaningID=$record['taxon_meaning_id']; - if(empty($seriesLabels[$taxonMeaningID])) { + $seriesIDs = []; + $summarySeriesData = []; + $estimatesSeriesData = []; + $seriesOptions = []; + + for ($i= $minPeriodNo; $i <= $maxPeriodNo; $i++) { + $tableNumberHeaderRow.= ''; + $tableDateHeaderRow.= ''; + $downloadNumberHeaderRow.= ',' . $i; + $downloadDateHeaderRow.= ',' . $firstPeriodDate->format('d/m/Y'); + $chartNumberLabels[] = "" . $i; + $chartDateLabels[] = $firstPeriodDate->format('M-d'); + $fullDates[$i] = $firstPeriodDate->format('d/m/Y'); + $firstPeriodDate->modify('+7 days'); + } + + $sampleFieldList = !empty($options['sampleFields']) ? explode(',',$options['sampleFields']) : FALSE; + if (empty($sampleFieldList)) { + $sampleFields = FALSE; + } else { + $sampleFields = []; + foreach ($sampleFieldList as $sampleField) { + $parts = explode(':', $sampleField); + $field = ['caption' => $parts[0], 'field' => $parts[1], 'attr' => FALSE]; + if (count($parts) == 3 && $parts[1] == 'smpattr') { + $smpAttribute=data_entry_helper::get_population_data([ + 'table' => 'sample_attribute', + 'extraParams' => $options['readAuth'] + ['view' => 'list', 'id' => $parts[2]] + ]); + if (count($smpAttribute) >= 1) { // may be assigned to more than one survey on this website. This is not relevant to info we want. + $field['id'] = $parts[2]; + $field['attr'] = $smpAttribute[0]; + } + } + $sampleFields[] = $field; + } + } + + $count = count($records); + $sortData=[]; + // EBMS Only. Override taxon name display depending on scheme + if (\Drupal::moduleHandler()->moduleExists('ebms_scheme') && + !empty($options['taxon_column_overrides'])) { + $records = self::injectLanguageIntoDefaultCommonName($options['readAuth'], $records, $options['taxon_column_overrides']); + } + foreach ($records as $index => $record) { + $taxonMeaningID = $record['taxon_meaning_id']; + if (empty($seriesLabels[$taxonMeaningID])) { if($options['taxon_column'] === 'common_name' && !empty($record['default_common_name'])) { - $seriesLabels[$taxonMeaningID]=array('label'=>$record['default_common_name']); - if(!empty($record['preferred_taxon'])) $seriesLabels[$taxonMeaningID]['tip']=$record['preferred_taxon']; + $seriesLabels[$taxonMeaningID] = ['label'=>$record['default_common_name']]; + if (!empty($record['preferred_taxon'])) { + $seriesLabels[$taxonMeaningID]['tip'] = $record['preferred_taxon']; + } } else if(!empty($record['preferred_taxon'])) { - $seriesLabels[$taxonMeaningID]=array('label'=>$record['preferred_taxon']); - if(!empty($record['default_common_name'])) $seriesLabels[$taxonMeaningID]['tip']=$record['default_common_name']; + $seriesLabels[$taxonMeaningID] = ['label'=>$record['preferred_taxon']]; + if(!empty($record['default_common_name'])) { + $seriesLabels[$taxonMeaningID]['tip'] = $record['default_common_name']; + } } else if(!empty($record['taxon'])) { - $seriesLabels[$taxonMeaningID]=array('label'=>$record['taxon']); // various fall backs. + $seriesLabels[$taxonMeaningID] = ['label' => $record['taxon']]; // various fall backs. } else { - $seriesLabels[$taxonMeaningID]=array('label' => '['.$record['taxa_taxon_list_id'].']'); + $seriesLabels[$taxonMeaningID] = ['label' => '[' . $record['taxa_taxon_list_id'] . ']']; } - $summaryArray[$taxonMeaningID]=[]; - $sortData[$taxonMeaningID]=array($record['taxonomic_sort_order'],$taxonMeaningID); + $summaryArray[$taxonMeaningID] = []; + $sortData[] = [ + 'order' => $record['taxonomic_sort_order'], + 'label' => $seriesLabels[$taxonMeaningID]['label'], + 'meaning' => $taxonMeaningID + ]; } - $summarisedData = json_decode($record['summarised_data'], false); - foreach($summarisedData as $summary) { + $summarisedData = json_decode($record['summarised_data'], FALSE); + foreach ($summarisedData as $summary) { $periodNo = $summary->period; - if($periodNo >= $minPeriodNo && $periodNo <= $maxPeriodNo){ - if(!isset($summaryArray[$taxonMeaningID][$periodNo])) { - $summaryArray[$taxonMeaningID][$periodNo] = array('total'=>null,'estimate'=>0); - } - if($summary->summary !== null && $summary->summary !== "NULL") { - $summaryArray[$taxonMeaningID][$periodNo]['total'] = ($summaryArray[$taxonMeaningID][$periodNo]['total'] == null + if ($periodNo >= $minPeriodNo && $periodNo <= $maxPeriodNo) { + if (!isset($summaryArray[$taxonMeaningID][$periodNo])) { + $summaryArray[$taxonMeaningID][$periodNo] = ['total' => NULL,'estimate' => 0]; + } + if ($summary->summary !== NULL && $summary->summary !== "NULL") { + $summaryArray[$taxonMeaningID][$periodNo]['total'] = ($summaryArray[$taxonMeaningID][$periodNo]['total'] == NULL ? 0 : $summaryArray[$taxonMeaningID][$periodNo]['total']) + $summary->summary; - } + } $summaryArray[$taxonMeaningID][$periodNo]['estimate'] += $summary->estimate; - } - } - } - usort($sortData, array('report_helper', 'report_calendar_summary_sort1')); - // will storedata in an array[Y][X] - self::add_resource('jqplot'); - switch ($options['chartType']) { - case 'bar' : - self::add_resource('jqplot_bar'); - $renderer='$.jqplot.BarRenderer'; - break; - case 'pie' : - self::add_resource('jqplot_pie'); - $renderer='$.jqplot.PieRenderer'; - break; - default : // default is line - $renderer='$.jqplot.LineRenderer'; - break; - } - self::add_resource('jqplot_category_axis_renderer'); - $opts = ["seriesDefaults:{\n".(isset($renderer) ? " renderer:$renderer,\n" : '') . - " rendererOptions:".json_encode($options['rendererOptions'])."}"]; - $seriesToDisplay=(isset($options['outputSeries']) ? explode(',', $options['outputSeries']) : 'all'); - $summaryTab = '
    WeekTotalTotal with
    estimates
    TotalTotal with
    estimates
    '.$i.''.$firstPeriodDate->format('M').'
    '.$firstPeriodDate->format('d').'
    ' . $i . '' . $firstPeriodDate->format('M') . '
    ' . $firstPeriodDate->format('d') . '
    ' . - '' . + } + } + } + usort($sortData, ['report_helper', 'report_calendar_summary_sort1']); + // will storedata in an array[Y][X] + self::add_resource('jqplot'); + switch ($options['chartType']) { + case 'bar' : + self::add_resource('jqplot_bar'); + $renderer='$.jqplot.BarRenderer'; + break; + case 'pie' : + self::add_resource('jqplot_pie'); + $renderer='$.jqplot.PieRenderer'; + break; + default : // default is line + $renderer='$.jqplot.LineRenderer'; + break; + } + self::add_resource('jqplot_category_axis_renderer'); + $opts = ["seriesDefaults:{\n" . (isset($renderer) ? " renderer:$renderer,\n" : '') . + " rendererOptions:" . json_encode($options['rendererOptions']) . "}"]; + $seriesToDisplay = (isset($options['outputSeries']) ? explode(',', $options['outputSeries']) : (empty($options['includeChartTotalSeries']) ? 'all' : ['0'])); + $summaryTab = + '' . + '
    ' . + '' . '' . - ''. - $tableNumberHeaderRow. - '' . + '' . + $tableNumberHeaderRow . + '' . '' . '' . - ''. - $tableDateHeaderRow. - '' . + '' . + $tableDateHeaderRow . + '' . '' . '' . ''; - $estimateTab = '
    '.lang::get('Week').''.lang::get('Total').'' . lang::get('Week') . '' . lang::get('Total') . '
    '.lang::get('Date').'' . lang::get('Date') . '
    ' . - '' . + $estimateTab = + '' . + '
    ' . + '' . '' . - ''. - $tableNumberHeaderRow. - '' . + '' . + $tableNumberHeaderRow . + ($inSeason ? '' : '') . + '' . '' . '' . - ''. - $tableDateHeaderRow. - '' . + '' . + $tableDateHeaderRow . + ($inSeason ? '' : '') . + '' . '' . '' . ''; - $summaryDataDownloadGrid .= lang::get('Week').','.$downloadNumberHeaderRow.','.lang::get('Total')."\n". - lang::get('Date').','.$downloadDateHeaderRow.",\n"; - $estimateDataDownloadGrid .= lang::get('Week').','.$downloadNumberHeaderRow.','.lang::get('Estimates Total')."\n". - lang::get('Date').','.$downloadDateHeaderRow.",\n"; - $altRow=false; + $summaryDataDownloadGrid .= lang::get('Week') . ',' . $downloadNumberHeaderRow . ',' . lang::get('Total') . "\n" . + lang::get('Date') . ',' . $downloadDateHeaderRow . ",\n"; + $estimateDataDownloadGrid .= lang::get('Week') . ',' . $downloadNumberHeaderRow . ',' . + ($inSeason ? lang::get('In-season estimates total') . ',' : '') . + lang::get('Estimates Total') . "\n" . + lang::get('Date') . ',' . $downloadDateHeaderRow . ($inSeason ? ',' : '') . ",\n"; + $altRow = FALSE; for($i = $minPeriodNo; $i <= $maxPeriodNo; $i++) { $totalRow[$i] = $totalEstimatesRow[$i] = 0; - } - foreach($sortData as $sortedTaxon){ - $seriesID=$sortedTaxon[1]; - $summaryRow=$summaryArray[$seriesID]; - $summaryValues=[]; - $estimatesValues=[]; - if (!empty($seriesLabels[$seriesID])) { - $total = $estimatesTotal = 0; // row totals + } + foreach ($sortData as $sortedTaxon) { + $seriesID = $sortedTaxon['meaning']; + $summaryRow = $summaryArray[$seriesID]; + $summaryValues = []; + $estimatesValues = []; + if (!empty($seriesLabels[$seriesID])) { + $total = $estimatesTotal = $inSeasonEstimatesTotal = 0; // row totals $summaryTab .= '' . - '' . - $seriesLabels[$seriesID]['label'].''; - $estimateTab .= '' . - '' . - $seriesLabels[$seriesID]['label'].''; + '' . + $seriesLabels[$seriesID]['label'] . ''; + $estimateTab .= '' . + '' . + $seriesLabels[$seriesID]['label'] . ''; $summaryDataDownloadGrid .= '"' . $seriesLabels[$seriesID]['label'] . '","' . (isset($seriesLabels[$seriesID]['tip']) ? $seriesLabels[$seriesID]['tip'] : '') . '"'; $estimateDataDownloadGrid .= '"' . $seriesLabels[$seriesID]['label'] . '","' . (isset($seriesLabels[$seriesID]['tip']) ? $seriesLabels[$seriesID]['tip'] : '') . '"'; - for($i = $minPeriodNo; $i <= $maxPeriodNo; $i++){ - $summaryDataDownloadGrid .= ','; - $estimateDataDownloadGrid .= ','; - if(isset($summaryRow[$i])){ - $summaryValue = $summaryRow[$i]['total']; - $estimateValue = $summaryRow[$i]['estimate']; + for ($i = $minPeriodNo; $i <= $maxPeriodNo; $i++) { + $summaryDataDownloadGrid .= ','; + $estimateDataDownloadGrid .= ','; + if (isset($summaryRow[$i])) { + $summaryValue = $summaryRow[$i]['total']; + $estimateValue = $summaryRow[$i]['estimate']; $class = ($summaryValue===0 ? 'forcedZero' : ''); - if($summaryValue === 0 && $estimateValue === 0) + if ($summaryValue === 0 && $estimateValue === 0) { $estimatesClass='forcedZero'; - else - $estimatesClass = ($summaryValue===null || $summaryValue!=$estimateValue ? 'highlight-estimates' : ''); - $summaryDataDownloadGrid .= $summaryValue; - $estimateDataDownloadGrid .= $estimateValue; - $summaryTab .= ''; - $estimateTab .= ''; - if($summaryValue !== null) { - $total += $summaryValue; - $totalRow[$i] += $summaryValue; // = $summaryTotalRow - $grandTotal += $summaryValue; - $summaryValues[]=$summaryValue; - } else { - $summaryValues[]=0; - } - $estimatesValues[] = $estimateValue; - $estimatesTotal += $estimateValue; - $totalEstimatesRow[$i] += $estimateValue; // = $estimatesTotalRow - $estimatesGrandTotal += $estimateValue; - } else { - $summaryTab .= ''; - $estimateTab .= ''; - $summaryValues[]=0; - $estimatesValues[]=0; - } - } - if ($options['includeChartItemSeries']) { - $seriesIDs[] = $seriesID; - $summarySeriesData[] = '['.implode(',', $summaryValues).']'; - $estimatesSeriesData[] = '['.implode(',', $estimatesValues).']'; - $seriesOptions[] = '{"show":'.($seriesToDisplay == 'all' || in_array($seriesID, $seriesToDisplay) ? 'true' : 'false') . - ',"label":"'.$seriesLabels[$seriesID]['label'].'","showlabel":true}'; - } - $summaryTab .= ''; - $summaryDataDownloadGrid .= ','.$total."\n"; - $estimateTab .= ''; - $estimateDataDownloadGrid .= ','.$estimatesTotal."\n"; - $altRow=!$altRow; - } - } - if(!empty($options['includeChartTotalSeries'])){ // totals are put at the start - array_unshift($seriesIDs,0); // Total has ID 0 - array_unshift($summarySeriesData, '['.implode(',', $totalRow).']'); - array_unshift($estimatesSeriesData, '['.implode(',', $totalEstimatesRow).']'); - array_unshift($seriesOptions, '{"show":'.($seriesToDisplay == 'all' || in_array(0, $seriesToDisplay) ? 'true' : 'false') . - ',"label":"'.lang::get('Total').'","showlabel":true}'); - } - $opts[] = 'series:['.implode(',', $seriesOptions).']'; - $options['axesOptions']['xaxis']['renderer'] = '$.jqplot.CategoryAxisRenderer'; - if(isset($options['chartLabels']) && $options['chartLabels'] == 'number') - $options['axesOptions']['xaxis']['ticks'] = $chartNumberLabels; - else - $options['axesOptions']['xaxis']['ticks'] = $chartDateLabels; - // We need to fudge the json so the renderer class is not a string - $axesOpts = str_replace('"$.jqplot.CategoryAxisRenderer"', '$.jqplot.CategoryAxisRenderer', - 'axes:'.json_encode($options['axesOptions'])); - $opts[] = $axesOpts; + } else { + $estimatesClass = ($summaryValue===null || $summaryValue!=$estimateValue ? 'highlight-estimates' : ''); + } + $summaryDataDownloadGrid .= $summaryValue; + $estimateDataDownloadGrid .= $estimateValue; + $summaryTab .= ''; + $estimateTab .= ''; + if ($summaryValue !== NULL) { + $total += $summaryValue; + $totalRow[$i] += $summaryValue; // = $summaryTotalRow + $grandTotal += $summaryValue; + $summaryValues[]=$summaryValue; + } else { + $summaryValues[]=0; + } + $estimatesValues[] = $estimateValue; + $estimatesTotal += $estimateValue; + if ($inSeason && $i >= $inSeason[0] && $i <= $inSeason[1]) { + $inSeasonEstimatesTotal += $estimateValue; + $inSeasonEstimatesGrandTotal += $estimateValue; + } + $totalEstimatesRow[$i] += $estimateValue; // = $estimatesTotalRow + $estimatesGrandTotal += $estimateValue; + } else { + $summaryTab .= ''; + $estimateTab .= ''; + $summaryValues[] = 0; + $estimatesValues[] = 0; + } + } + if ($options['includeChartItemSeries']) { + $seriesIDs[] = $seriesID; + $summarySeriesData[] = '[' . implode(',', $summaryValues) . ']'; + $estimatesSeriesData[] = '[' . implode(',', $estimatesValues) . ']'; + $seriesOptions[] = '{"show":' . ($seriesToDisplay == 'all' || in_array($seriesID, $seriesToDisplay) ? 'true' : 'false') . + ',"label":"' . htmlspecialchars ($seriesLabels[$seriesID]['label']) . '","showlabel":true}'; + } + $summaryTab .= ''; + $summaryDataDownloadGrid .= ',' . $total."\n"; + if ($inSeason) { + $estimateTab .= ''; + $estimateDataDownloadGrid .= ',' . $inSeasonEstimatesTotal; + } + $estimateTab .= ''; + $estimateDataDownloadGrid .= ',' . $estimatesTotal . "\n"; + $altRow = !$altRow; + } + } + if (!empty($options['includeChartTotalSeries'])) { // totals are put at the start + array_unshift($seriesIDs,0); // Total has ID 0 + array_unshift($summarySeriesData, '[' . implode(',', $totalRow) . ']'); + array_unshift($estimatesSeriesData, '[' . implode(',', $totalEstimatesRow) . ']'); + array_unshift($seriesOptions, '{"show":' . ($seriesToDisplay == 'all' || in_array(0, $seriesToDisplay) ? 'true' : 'false') . + ',"label":"' . lang::get('Total') . '","showlabel":true}'); + } + $opts[] = 'series:[' . implode(',', $seriesOptions) . ']'; + $options['axesOptions']['xaxis']['renderer'] = '$.jqplot.CategoryAxisRenderer'; + if(isset($options['chartLabels']) && $options['chartLabels'] == 'number') { + $options['axesOptions']['xaxis']['ticks'] = $chartNumberLabels; + } else { + $options['axesOptions']['xaxis']['ticks'] = $chartDateLabels; + } + // We need to fudge the json so the renderer class is not a string + $axesOpts = str_replace('"$.jqplot.CategoryAxisRenderer"', '$.jqplot.CategoryAxisRenderer', + 'axes:'.json_encode($options['axesOptions'])); + $opts[] = $axesOpts; $summaryTab .= ''; $estimateTab .= ''; - $summaryDataDownloadGrid .= '"'.lang::get('Total (Summary)').'",'; - $estimateDataDownloadGrid .= '"'.lang::get('Total').'",'; - for($i= $minPeriodNo; $i <= $maxPeriodNo; $i++) { - $summaryTab .= ''; - $estimateTab.= ''; - $estimateDataDownloadGrid .= ','.$totalEstimatesRow[$i]; - $summaryDataDownloadGrid .= ','.$totalRow[$i]; - } - $summaryTab .= ''; - $summaryDataDownloadGrid .= ','.$grandTotal."\n"; - $estimateTab .= ''; - $estimateDataDownloadGrid .= ','.$estimatesGrandTotal."\n"; - $summaryTab .= "
    '.lang::get('Week').''.lang::get('Total').'' . lang::get('Week') . '' . lang::get('In-season total') . '' . lang::get('Total') . '
    '.lang::get('Date').''.lang::get('(with
    estimates)').'
    ' . lang::get('Date') . '(wks ' . $inSeason[0] . ' > '. $inSeason[1] . ')' . lang::get('(with
    estimates)') . '
    '.($summaryValue !== null ? $summaryValue : '').''.$estimateValue.''.$total.'
    '.$estimatesTotal.'
    ' . ($summaryValue !== NULL ? $summaryValue : '') . '' . $estimateValue . '' . $total . '
    ' . $inSeasonEstimatesTotal . '' . $estimatesTotal . '
    ' . lang::get('Total (Summary)') . '
    ' . lang::get('Total inc Estimates') . ''.$totalRow[$i].''.$totalEstimatesRow[$i].''.$grandTotal.'
    '.$estimatesGrandTotal.'
    \n"; - $estimateTab .= "\n"; - self::$javascript .= " -var seriesData = {ids: [".implode(',', $seriesIDs)."], summary: [".implode(',', $summarySeriesData)."], estimates: [".implode(',', $estimatesSeriesData)."]}; + $summaryDataDownloadGrid .= '"' . lang::get('Total (Summary)') . '",'; + $estimateDataDownloadGrid .= '"' . lang::get('Total') . '",'; + for ($i= $minPeriodNo; $i <= $maxPeriodNo; $i++) { + $summaryTab .= '' . $totalRow[$i] . ''; + $estimateTab.= '' . $totalEstimatesRow[$i] . ''; + $estimateDataDownloadGrid .= ',' . $totalEstimatesRow[$i]; + $summaryDataDownloadGrid .= ',' . $totalRow[$i]; + } + $summaryTab .= '' . $grandTotal . ''; + $summaryDataDownloadGrid .= ',' . $grandTotal . "\n"; + if ($inSeason) { + $estimateTab .= '' . $inSeasonEstimatesGrandTotal . ''; + $estimateDataDownloadGrid .= ',' . $inSeasonEstimatesGrandTotal; + } + $estimateTab .= '' . $estimatesGrandTotal . ''; + $estimateDataDownloadGrid .= ',' . $estimatesGrandTotal . "\n"; + $summaryTab .= "\n"; + $estimateTab .= "\n"; + self::$javascript .= " +var seriesData = {ids: [" . implode(',', $seriesIDs) . "], summary: [" . implode(',', $summarySeriesData) . "], estimates: [" . implode(',', $estimatesSeriesData) . "]}; function replot(type){ // there are problems with the coloring of series when added to a plot: easiest just to completely redraw. var max=0; $('#{$options['chartID']}-' + type).empty(); -". -(!isset($options['width']) || $options['width'] == '' ? " jQuery('#{$options['chartID']}-'+type).width(jQuery('#{$options['chartID']}-'+type).width());\n" : ''). -" var opts = {".implode(",\n", $opts)."}; +" . +(!isset($options['width']) || $options['width'] == '' ? " jQuery('#{$options['chartID']}-'+type).width(jQuery('#{$options['chartID']}-'+type).width());\n" : '') . +" var opts = {" . implode(",\n", $opts) . "}; // copy series from checkboxes. $('#{$options['chartID']}-'+type).parent().find('[name={$options['chartID']}-series]').each(function(idx, elem){ opts.series[idx].show = (jQuery(elem).filter(':checked').length > 0); @@ -5038,10 +5250,10 @@ function replot(type){ if($('#{$options['chartID']}-'+type).parent().find('[name={$options['chartID']}-series]').filter(':checked').length == 0) return; var plot = $.jqplot('{$options['chartID']}-'+type, seriesData[type], opts); for(var i=0; i

     
    '); - } + elem.after('
     
    '); + } } }; indiciaFns.bindTabsActivate($('#controls'), function(event, ui) { @@ -5050,250 +5262,285 @@ function replot(type){ if (panel.id==='estimateChart') { replot('estimates'); } }); "; - $summarySeriesPanel=""; - if(!empty($options['disableableSeries']) && + $summarySeriesPanel=""; + if (!empty($options['disableableSeries']) && (count($summaryArray)>(!empty($options['includeChartTotalSeries']) ? 0 : 1)) && !empty($options['includeChartItemSeries'])) { - $class='series-fieldset'; - if (function_exists('hostsite_add_library') && (!defined('DRUPAL_CORE_COMPATIBILITY') || DRUPAL_CORE_COMPATIBILITY!=='7.x')) { - hostsite_add_library('collapse'); - $class.=' collapsible collapsed'; - } - $summarySeriesPanel .= '
    ' . - ''.lang::get('Display Series')."" . - '\n"; - $idx=0; - if(!empty($options['includeChartTotalSeries'])){ - // use series ID = 0 for Total - $summarySeriesPanel .= ''. - '\n"; - $idx++; + $class = 'series-fieldset'; + if (function_exists('hostsite_add_library') && (!defined('DRUPAL_CORE_COMPATIBILITY') || DRUPAL_CORE_COMPATIBILITY!=='7.x')) { + hostsite_add_library('collapse'); + $class.=' collapsible collapsed'; + } + $summarySeriesPanel .= '
    ' . + '' . lang::get('Display Series') . "" . + '\n"; + $idx = 0; + if (!empty($options['includeChartTotalSeries'])) { + // use series ID = 0 for Total + $summarySeriesPanel .= ''. + '\n"; + $idx++; self::$javascript .= "jQuery('[name={$options['chartID']}-series]').filter('[value=0]')." . - ($seriesToDisplay == 'all' || in_array(0, $seriesToDisplay) ? 'attr("checked","checked");' : 'removeAttr("checked");') . + ($seriesToDisplay == 'all' || in_array(0, $seriesToDisplay) ? 'prop("checked","checked");' : 'removeProp("checked");') . "\n"; - } - foreach($sortData as $sortedTaxon){ - $seriesID=$sortedTaxon[1]; - $summaryRow=$summaryArray[$seriesID]; + } + foreach ($sortData as $sortedTaxon) { + $seriesID = $sortedTaxon['meaning']; + $summaryRow = $summaryArray[$seriesID]; $summarySeriesPanel .= '' . - '' . - '\n"; - $idx++; - self::$javascript .= "jQuery('[name=".$options['chartID']."-series]').filter('[value=".$seriesID."]').". - ($seriesToDisplay == 'all' || in_array($seriesID, $seriesToDisplay) ? 'attr("checked","checked");' : 'removeAttr("checked");') . + '' . + '\n"; + $idx++; + self::$javascript .= "jQuery('[name=" . $options['chartID'] ."-series]').filter('[value=" . $seriesID . "]').". + ($seriesToDisplay == 'all' || in_array($seriesID, $seriesToDisplay) ? 'prop("checked","checked");' : 'removeProp("checked");') . "\n"; - } - $summarySeriesPanel .= "
    \n"; - // Known issue: jqplot considers the min and max of all series when drawing on the screen, even those which are not displayed - // so replotting doesn't scale to the displayed series! - // Note we are keeping the 2 charts in sync. - self::$javascript .= " + } + $summarySeriesPanel .= "
    \n"; + // Known issue: jqplot considers the min and max of all series when drawing on the screen, even those which are not displayed + // so replotting doesn't scale to the displayed series! + // Note we are keeping the 2 charts in sync. + self::$javascript .= " jQuery('#summaryChart [name={$options['chartID']}-series]').change(function(){ - $('#estimateChart [name={$options['chartID']}-series]').filter('[value='+$(this).val()+']').attr('checked',$(this).attr('checked')); + $('#estimateChart [name={$options['chartID']}-series]').filter('[value='+$(this).val()+']').prop('checked',!!$(this).prop('checked')); replot('summary'); }); jQuery('#estimateChart [name={$options['chartID']}-series]').change(function(){ - $('#summaryChart [name={$options['chartID']}-series]').filter('[value='+$(this).val()+']').attr('checked',$(this).attr('checked')); + $('#summaryChart [name={$options['chartID']}-series]').filter('[value='+$(this).val()+']').prop('checked',!!$(this).prop('checked')); replot('estimates'); }); jQuery('#summaryChart .disable-button').click(function(){ if(jQuery(this).is('.cleared')){ // button is to show all - jQuery('[name={$options['chartID']}-series]').not('[value=0]').attr('checked','checked'); - jQuery('.disable-button').removeClass('cleared').val(\"".lang::get('Hide all '.$options['rowGroupColumn'])."\"); + jQuery('[name={$options['chartID']}-series]').not('[value=0]').prop('checked','checked'); + jQuery('.disable-button').removeClass('cleared').val(\"" . lang::get('Hide all') . "\"); } else { - jQuery('[name={$options['chartID']}-series]').not('[value=0]').removeAttr('checked'); - jQuery('.disable-button').addClass('cleared').val(\"".lang::get('Show all '.$options['rowGroupColumn'])."\"); + jQuery('[name={$options['chartID']}-series]').not('[value=0]').prop('checked',false); + jQuery('.disable-button').addClass('cleared').val(\"" . lang::get('Show all') . "\"); } replot('summary'); }); jQuery('#estimateChart .disable-button').click(function(){ if(jQuery(this).is('.cleared')){ // button is to show all - jQuery('[name={$options['chartID']}-series]').not('[value=0]').attr('checked','checked'); - jQuery('.disable-button').removeClass('cleared').val(\"".lang::get('Hide all '.$options['rowGroupColumn'])."\"); + jQuery('[name={$options['chartID']}-series]').not('[value=0]').prop('checked','checked'); + jQuery('.disable-button').removeClass('cleared').val(\"" . lang::get('Hide all') . "\"); } else { - jQuery('[name={$options['chartID']}-series]').not('[value=0]').removeAttr('checked'); - jQuery('.disable-button').addClass('cleared').val(\"".lang::get('Show all '.$options['rowGroupColumn'])."\"); + jQuery('[name={$options['chartID']}-series]').not('[value=0]').prop('checked',false); + jQuery('.disable-button').addClass('cleared').val(\"" . lang::get('Show all') . "\"); } replot('estimates'); }); "; - } - $hasRawData = false; - if(isset($options['location_id']) && $options['location_id']!=""){ - // get the raw data for a single location. - $options['extraParams']['orderby'] = 'date'; - self::request_report($response, $options, $currentParamValues, false, ''); - if (isset($response['error'])) { - $rawTab = "ERROR RETURNED FROM request_report:
    ".(print_r($response,true)); - } else if (isset($response['parameterRequest'])) { - // We're not even going to bother with asking the user to populate a partially filled in report parameter set. - $rawTab = '

    INTERNAL ERROR: Report request parameters not set up correctly.
    '.(print_r($response,true)).'

    '; - } else { - // convert records to a date based array so it can be used when generating the grid. - $altRow=false; - $records = $response['records']; - $rawTab = (isset($options['linkMessage']) ? $options['linkMessage'] : ''); - $rawDataDownloadGrid = lang::get('Week').','; - $rawArray = []; - $sampleList=[]; - $sampleDateList=[]; - $smpIdx=0; - $hasRawData = (count($records) > 0); - if(!$hasRawData) - $rawTab .= '

    '.lang::get('No raw data available for this location/period/user combination.').'

    '; - else { - foreach($records as $occurrence){ - if(!in_array($occurrence['sample_id'], $sampleList)) { - $sampleList[] = $occurrence['sample_id']; - $sampleData = array('id'=>$occurrence['sample_id'], 'date'=>$occurrence['date'], 'location'=>$occurrence['location_name']); - $rawArray[$occurrence['sample_id']] = []; - if($sampleFields){ - foreach($sampleFields as $sampleField) { - if($sampleField['attr'] === false) - $sampleData[$sampleField['caption']] = $occurrence[$sampleField['field']]; - else if($sampleField['attr']['data_type']=='L') - $sampleData[$sampleField['caption']] = $occurrence['attr_sample_term_'.$sampleField['id']]; - else - $sampleData[$sampleField['caption']] = $occurrence['attr_sample_'.$sampleField['id']]; - } - } - $sampleDateList[] = $sampleData; - } - if($occurrence['taxon_meaning_id']!==null && $occurrence['taxon_meaning_id']!=''){ - $count = (isset($options['countColumn']) && $options['countColumn']!='') ? + } + $hasRawData = FALSE; + if (!empty($options['extraParams']['location_id'])) { + // only get the raw data if a single location is specified. + $options['extraParams']['orderby'] = 'date'; + if (!isset($options['extraParams']['user_id'])) { + $options['extraParams']['user_id'] = 0; + } + self::request_report($response, $options, $currentParamValues, FALSE, ''); + if (isset($response['error'])) { + $rawTab = "ERROR RETURNED FROM request_report:
    " . (print_r($response, TRUE)); + } else if (isset($response['parameterRequest'])) { + // We're not even going to bother with asking the user to populate a partially filled in report parameter set. + $rawTab = '

    INTERNAL ERROR: Report request parameters not set up correctly.
    ' . (print_r($response, TRUE)) . '

    '; + } else { + // convert records to a date based array so it can be used when generating the grid. + $altRow = FALSE; + $records = $response['records']; + $rawTab = (isset($options['linkMessage']) ? $options['linkMessage'] : ''); + $rawDataDownloadGrid = lang::get('Week') . ','; + $rawArray = []; + $sampleList = []; + $sampleDateList = []; + $smpIdx = 0; + $hasRawData = (count($records) > 0); + if (!$hasRawData) { + $rawTab .= '

    ' . lang::get('No raw data available for this location/period/user combination.') . '

    '; + } else { + foreach ($records as $occurrence) { + if (!in_array($occurrence['sample_id'], $sampleList)) { + $sampleList[] = $occurrence['sample_id']; + $sampleData = ['id' => $occurrence['sample_id'], 'date' => $occurrence['date'], 'location' => $occurrence['location_name']]; + $rawArray[$occurrence['sample_id']] = []; + if ($sampleFields) { + foreach ($sampleFields as $sampleField) { + if ($sampleField['attr'] === FALSE) { + $sampleData[$sampleField['caption']] = $occurrence[$sampleField['field']]; + } else if ($sampleField['attr']['data_type']=='L') { + $sampleData[$sampleField['caption']] = $occurrence['attr_sample_term_'.$sampleField['id']]; + } else { + $sampleData[$sampleField['caption']] = $occurrence['attr_sample_'.$sampleField['id']]; + } + } + } + $sampleDateList[] = $sampleData; + } + if ($occurrence['taxon_meaning_id'] !== NULL && $occurrence['taxon_meaning_id'] != '') { + $count = (isset($options['countColumn']) && $options['countColumn'] != '') ? (isset($occurrence[$options['countColumn']]) ? $occurrence[$options['countColumn']] : 0) : 1; - if(!isset($rawArray[$occurrence['sample_id']][$occurrence['taxon_meaning_id']])) + if (!isset($rawArray[$occurrence['sample_id']][$occurrence['taxon_meaning_id']])) { $rawArray[$occurrence['sample_id']][$occurrence['taxon_meaning_id']] = $count; - else $rawArray[$occurrence['sample_id']][$occurrence['taxon_meaning_id']] += $count; - } - } - $rawTab .= '' . - ''; - foreach($sampleDateList as $sample){ + } else { + $rawArray[$occurrence['sample_id']][$occurrence['taxon_meaning_id']] += $count; + } + } + } + $rawTab .= '
    ' . + lang::get('Copy raw table to clipboard') . '
    ' . + '
    '.lang::get('Week').'
    ' . + ''; + foreach ($sampleDateList as $sample) { $sampleDate = date_create($sample['date']); -// $this_index = $this_date->format('z'); +// $this_index = $this_date->format('z'); $thisYearDay = $sampleDate->format('N'); - if($thisYearDay > $periodStart) // scan back to start of week - $sampleDate->modify('-'.($thisYearDay-$periodStart).' day'); - else if($thisYearDay < $periodStart) - $sampleDate->modify('-'.(7+$thisYearDay-$periodStart).' day'); + if ($thisYearDay > $periodStart) { // scan back to start of week + $sampleDate->modify('-' . ($thisYearDay-$periodStart) . ' day'); + } else if($thisYearDay < $periodStart) { + $sampleDate->modify('-' . (7+$thisYearDay-$periodStart) . ' day'); + } $thisYearDay = $sampleDate->format('z'); $periodNo = (int)floor(($thisYearDay-$periodOneDateYearDay)/7)+1; - $rawTab .= ''; - $rawDataDownloadGrid .= ','.$periodNo; - } - $rawTab .= ''; - $rawDataDownloadGrid .= "\n".lang::get('Date').','; - foreach($sampleDateList as $sample){ - $sample_date = date_create($sample['date']); - $rawTab .= ''; - $rawDataDownloadGrid .= ','.$sample['date']; - } + $rawTab .= ''; + $rawDataDownloadGrid .= ',' . $periodNo; + } + $rawTab .= ''; + $rawDataDownloadGrid .= "\n" . lang::get('Date') . ','; + foreach ($sampleDateList as $sample) { + $sample_date = date_create($sample['date']); + $rawTab .= ''; + $rawDataDownloadGrid .= ',' . $sample['date']; + } $rawTab .= ''; - $rawDataDownloadGrid .= "\n"; - if($sampleFields){ - foreach($sampleFields as $sampleField) { // last-sample-datarow - $rawTab .= ''; - $rawDataDownloadGrid .= '"'.$sampleField['caption'].'",'; - foreach($sampleDateList as $sample){ - $rawTab .= ''; - $rawDataDownloadGrid .= ','.$sample[$sampleField['caption']]; - } - $rawTab .= ''; - $rawDataDownloadGrid .= "\n"; - $altRow=!$altRow; - } + $rawDataDownloadGrid .= "\n"; + if ($sampleFields) { + foreach ($sampleFields as $sampleField) { // last-sample-datarow + $rawTab .= ''; + $rawDataDownloadGrid .= '"' . $sampleField['caption'] . '",'; + foreach ($sampleDateList as $sample) { + $rawTab .= ''; + $rawDataDownloadGrid .= ',' . $sample[$sampleField['caption']]; + } + $rawTab .= ''; + $rawDataDownloadGrid .= "\n"; + $altRow = !$altRow; + } self::$javascript .= " var sampleDatarows = $('#rawData .sample-datarow').length; $('#rawData .sample-datarow').eq(sampleDatarows-1).addClass('last-sample-datarow');\n"; - } - foreach($sortData as $sortedTaxon){ - $seriesID=$sortedTaxon[1]; // this is the meaning id - if (!empty($seriesLabels[$seriesID])) { - $rawTab .= ''.$seriesLabels[$seriesID]['label'].''; - $rawDataDownloadGrid .= '"'.$seriesLabels[$seriesID]['label'].'","'.(isset($seriesLabels[$seriesID]['tip']) ? $seriesLabels[$seriesID]['tip'] : '').'"'; - foreach($sampleList as $sampleID){ - $rawTab .= ''; - $rawDataDownloadGrid .= ','.(isset($rawArray[$sampleID][$seriesID]) ? $rawArray[$sampleID][$seriesID] : ''); - } - $rawTab .= ''; - $rawDataDownloadGrid .= "\n"; - $altRow=!$altRow; - } - } - $rawTab .= '
    ' . lang::get('Week') . ''.$periodNo.'
    '.lang::get('Date').''. - (isset($options['linkURL']) && $options['linkURL']!= '' ? '' : ''). - $sample_date->format('M').'
    '.$sample_date->format('d'). - (isset($options['linkURL']) && $options['linkURL']!= '' ? '
    ' : ''). - '
    ' . $periodNo . '
    ' . lang::get('Date') . '' . + (isset($options['linkURL']) && $options['linkURL']!= '' ? '' : ''). + $sample_date->format('M') . '
    ' . $sample_date->format('d') . + (isset($options['linkURL']) && $options['linkURL'] != '' ? '
    ' : '') . + '
    '.$sampleField['caption'].''.($sample[$sampleField['caption']]===null || $sample[$sampleField['caption']]=='' ? ' ' : $sample[$sampleField['caption']]).'
    ' . $sampleField['caption'] . '' . ($sample[$sampleField['caption']] === NULL || $sample[$sampleField['caption']]=='' ? ' ' : $sample[$sampleField['caption']]) . '
    '.(isset($rawArray[$sampleID][$seriesID]) ? $rawArray[$sampleID][$seriesID] : ' ').'
    '; - } - } - } else $rawTab = '

    '.lang::get('Raw Data is only available when a location is specified.').'

    '; - $hasData = (count($summaryArray)>0); - - if($hasData) { - $tabs = array( - '#summaryData'=>lang::get('Summary Table'), - '#summaryChart'=>lang::get('Summary Chart'), - '#estimateData'=>lang::get('Estimate Table'), - '#estimateChart'=>lang::get('Estimate Chart')); + } + foreach ($sortData as $sortedTaxon) { + $seriesID = $sortedTaxon['meaning']; + if (!empty($seriesLabels[$seriesID])) { + $rawTab .= '' . $seriesLabels[$seriesID]['label'] . ''; + $rawDataDownloadGrid .= '"'.$seriesLabels[$seriesID]['label'] . '","' . (isset($seriesLabels[$seriesID]['tip']) ? $seriesLabels[$seriesID]['tip'] : '') . '"'; + foreach ($sampleList as $sampleID) { + $rawTab .= '' . (isset($rawArray[$sampleID][$seriesID]) ? $rawArray[$sampleID][$seriesID] : ' ') . ''; + $rawDataDownloadGrid .= ',' . (isset($rawArray[$sampleID][$seriesID]) ? $rawArray[$sampleID][$seriesID] : ''); + } + $rawTab .= ''; + $rawDataDownloadGrid .= "\n"; + $altRow = !$altRow; + } + } + $rawTab .= ''; + } + } + } else { + $rawTab = '

    ' . lang::get('Raw Data is only available when a location is specified.') . '

    '; + } + $hasData = (count($summaryArray)>0); + + if ($hasData) { + $tabs = [ + '#summaryData' => lang::get('Summary Table'), + '#summaryChart' => lang::get('Summary Chart'), + '#estimateData' => lang::get('Estimate Table'), + '#estimateChart' => lang::get('Estimate Chart') + ]; } else { - $tabs = array('#summaryData'=>lang::get('No Summary Data')); + $tabs = ['#summaryData' => lang::get('No Summary Data')]; } $tabs['#rawData'] = lang::get('Raw Data'); - $downloadTab=""; - $timestamp = (isset($options['includeReportTimeStamp']) && $options['includeReportTimeStamp'] ? '_'.date('YmdHis') : ''); - unset($options['extraParams']['orderby']); // may have been set for raw data + $downloadTab = ""; + $timestamp = (isset($options['includeReportTimeStamp']) && $options['includeReportTimeStamp'] ? '_' . date('YmdHis') : ''); // No need for saved reports to be atomic events. // purging?? global $base_url; - $downloadsFolder = hostsite_get_public_file_path(). '/reportsDownloads/'; - if (!is_dir($downloadsFolder) || !is_writable($downloadsFolder)) { - $downloadTab .= ''.lang::get('Internal Config error: directory does not exist or is not writeable '.$downloadsFolder).''."\n"; - } else { - if ($hasData && $options['includeSummaryGridDownload']) { - $cacheFile = $options['downloadFilePrefix'].'summaryDataGrid'.$timestamp.'.csv'; - $handle = fopen($downloadsFolder.$cacheFile, 'wb'); - fwrite($handle, $summaryDataDownloadGrid); - fclose($handle); - $downloadTab .= ''.lang::get('Download Summary Grid (CSV Format)').' : '."\n"; + $downloadsFolder = hostsite_get_public_file_path() . '/reportsDownloads/'; + if ($hasData && $options['includeSummaryGridDownload']) { + if (!is_dir($downloadsFolder) || !is_writable($downloadsFolder)) { + return lang::get('Internal Config error: directory {1} does not exist or is not writeable', $downloadsFolder); } - if ($hasData && $options['includeEstimatesGridDownload']) { - $cacheFile = $options['downloadFilePrefix'].'estimateDataGrid'.$timestamp.'.csv'; - $handle = fopen($downloadsFolder.$cacheFile, 'wb'); - fwrite($handle, $estimateDataDownloadGrid); - fclose($handle); - $downloadTab .= ''.lang::get('Download Estimates Grid (CSV Format)').' : '."\n"; + $cacheFile = $options['downloadFilePrefix'] . 'summaryDataGrid' . $timestamp . '.csv'; + $handle = fopen($downloadsFolder.$cacheFile, 'wb'); + fwrite($handle, $summaryDataDownloadGrid); + fclose($handle); + $downloadTab .= '' . lang::get('Download Summary Grid (CSV Format)') . ' : ' . "\n"; + } + if ($hasData && $options['includeEstimatesGridDownload']) { + if (!is_dir($downloadsFolder) || !is_writable($downloadsFolder)) { + return lang::get('Internal Config error: directory {1} does not exist or is not writeable', $downloadsFolder); } - if ($hasRawData && $options['includeRawGridDownload']) { - $cacheFile = $options['downloadFilePrefix'].'rawDataGrid'.$timestamp.'.csv'; - $handle = fopen($downloadsFolder.$cacheFile, 'wb'); - fwrite($handle, $rawDataDownloadGrid); - fclose($handle); - $downloadTab .= ''.lang::get('Download Raw Data Grid (CSV Format)').' : '."\n"; + $cacheFile = $options['downloadFilePrefix'] . 'estimateDataGrid' . $timestamp.'.csv'; + $handle = fopen($downloadsFolder.$cacheFile, 'wb'); + fwrite($handle, $estimateDataDownloadGrid); + fclose($handle); + $downloadTab .= '' . lang::get('Download Estimates Grid (CSV Format)') . ' : ' . "\n"; + } + if ($hasRawData && $options['includeRawGridDownload']) { + if (!is_dir($downloadsFolder) || !is_writable($downloadsFolder)) { + return lang::get('Internal Config error: directory {1} does not exist or is not writeable', $downloadsFolder); } + $cacheFile = $options['downloadFilePrefix'].'rawDataGrid' . $timestamp . '.csv'; + $handle = fopen($downloadsFolder.$cacheFile, 'wb'); + fwrite($handle, $rawDataDownloadGrid); + fclose($handle); + $downloadTab .= '' . lang::get('Download Raw Data Grid (CSV Format)') .' : ' . "\n"; } if ($hasData && count($options['downloads'])>0) { // format is assumed to be CSV global $indicia_templates; $indicia_templates['report_download_link'] = ''; + $downloadExtraParams = []; + // copy if set + foreach(['survey_id', 'user_id', 'taxon_list_id', 'location_id', 'location_list', 'location_type_id', 'occattrs'] as $parameter) { + if (isset($options['extraParams'][$parameter])) { + $downloadExtraParams[$parameter] = $options['extraParams'][$parameter]; + } + } + foreach(['year', 'summary_user_id', 'summary_location_id'] as $parameter) { + if (isset($options[$parameter])) { + $downloadExtraParams[$parameter] = $options[$parameter]; + } + } + // 'date_to' and 'date_from' Deprecated + foreach(['date_start' => 'date_from', 'date_end' => 'date_to'] as $key => $parameter) { + if (isset($options[$key])) { + $downloadExtraParams[$parameter] = $options[$key]; + } + } + // locattrs and smpattrs etc for download reports are all set in download specific presets + $downloadOptions = [ + 'readAuth' => $options['readAuth'], + 'itemsPerPage' => FALSE + ]; + if (!isset($downloadExtraParams['user_id'])) { + $downloadExtraParams['user_id'] = ''; + } foreach ($options['downloads'] as $download) { - $downloadOptions = array('readAuth'=>$options['readAuth'], - 'extraParams'=>array_merge($options['extraParams'], array('date_from' => $options['date_start'], 'date_to' => $options['date_end'])), - 'itemsPerPage' => false); - // there are problems dealing with location_list as an array if empty, so connvert - if ($downloadOptions['extraParams']['location_list'] == "") - $downloadOptions['extraParams']['location_list'] = "(-1)"; - else $downloadOptions['extraParams']['location_list'] = '(' . $downloadOptions['extraParams']['location_list'] . ')'; - - if (isset($download['param_presets'])) - $downloadOptions['extraParams'] = array_merge($downloadOptions['extraParams'], $download['param_presets']); - + $downloadOptions['extraParams'] = array_merge( + $downloadExtraParams, + (isset($download['param_presets']) ? $download['param_presets'] : []) + ); $downloadOptions['dataSource'] = $download['dataSource']; $downloadOptions['filename'] = $download['filename']; $downloadTab .= '' . $download['caption'] . ' : ' . @@ -5301,27 +5548,106 @@ function replot(type){ } } - if($downloadTab!="") + if ($downloadTab !== "") { $tabs['#dataDownloads'] = lang::get('Downloads'); - $r .= '
    '. - data_entry_helper::tab_header(array('tabs'=>$tabs)). + } + $r .= '
    ' . + data_entry_helper::tab_header(['tabs' => $tabs]) . ($hasData ? - '
    '.$summaryTab.'
    '. - '
    '.$summarySeriesPanel.'
    '. - '
    '.$estimateTab.'
    '. - '
    '.$summarySeriesPanel.'
    ' - : '

    '.lang::get('No data available for this period with these filter values.').'

    '). - '
    '.$rawTab.'
    '. - ($downloadTab!="" ? '
    '.$downloadTab.'
    ' : ''). + '
    ' . $summaryTab . '
    ' . + '
    ' . + '' . + '
    ' . + $summarySeriesPanel . + '
    ' . + '
    ' . $estimateTab . '
    '. + '
    ' . + '' . + '
    ' . + $summarySeriesPanel . + '
    ' + : '

    ' . lang::get('No data available for this period with these filter values.') . '

    ') . + '
    ' . $rawTab . '
    '. + ($downloadTab !== "" ? '
    ' . $downloadTab . '
    ' : ''). '
    '; - data_entry_helper::enable_tabs(array('divId' => 'controls')); + data_entry_helper::enable_tabs(['divId' => 'controls']); return $r; } + /** + * Override the type of taxon label for some EBMS schemes + * + * EBMS ONLY. If user is member of scheme that specifies it should use common names + * then override the taxon names to use common names in the specified language. + * + * @param array $readAuth Authorisation tokens. + * @param array $records Records for report to display. + * @param array $taxonColumnOverrides Configuration for which EBMS schemes should show + * names in which language. + * @return array Records array with altered names. + */ + private static function injectLanguageIntoDefaultCommonName($readAuth, $records, $taxonColumnOverrides) { + $taxonColumnOverrides = json_decode($taxonColumnOverrides, TRUE); + $myId = hostsite_get_user_field('id'); + $schemes = ebms_scheme_list_user_schemes($myId); + $mySchemeOverrideLangId = FALSE; + if (count($schemes) > 0) { + $userSchemeId = $schemes[0]['id']; + // If we identify user as a scheme member then get the Warehouse language ID specified for the scheme + if (!empty($userSchemeId) && !empty($taxonColumnOverrides[$userSchemeId])) { + $mySchemeOverrideLangId = $taxonColumnOverrides[$userSchemeId]; + } + } + // Only override taxon labels if there is configuration for my scheme to do so + if ($mySchemeOverrideLangId) { + $recordsTaxonMeaningIds = []; + // For all the records shown on the report, create an taxon meaning id array to pass to the Warehouse + foreach ($records as $record) { + $recordsTaxonMeaningIds[] = $record['taxon_meaning_id']; + } + if (!empty($recordsTaxonMeaningIds)) { + // Get taxon rows associated with the report in the language we want + $taxonRowsInDetail = data_entry_helper::get_population_data([ + 'table' => 'taxa_taxon_list', + 'nocache' => TRUE, + 'extraParams' => $readAuth + [ + 'query' => json_encode(['in' => [ + 'taxon_meaning_id' => $recordsTaxonMeaningIds, + 'language_id' => [$mySchemeOverrideLangId] + ]]), + 'view' => 'detail' + ] + ]); + // Process the data so that the meaning id is the array key, and the name + // in the language we want is the value + foreach($taxonRowsInDetail as $taxonRowInDetail) { + $meaningsWithLangCommonNames[$taxonRowInDetail['taxon_meaning_id']] = $taxonRowInDetail['taxon']; + } + // Now simply ovewrite the names in the report + foreach ($records as $recordIdx => $record) { + $records[$recordIdx]['preferred_taxon'] = $meaningsWithLangCommonNames[$record['taxon_meaning_id']]; + $records[$recordIdx]['default_common_name'] = $meaningsWithLangCommonNames[$record['taxon_meaning_id']]; + } + } + } + return $records; + } + static function report_calendar_summary_sort1($a, $b) { - return ($a[0] > $b[0]) ; + // No sort order > end of list, sorted by label + if (empty($a['order'])) { + return empty($b['order']) ? strnatcasecmp($a['label'], $b['label']) : 1; + } + return empty($b['order']) ? -1 : ($a['order'] <= $b['order'] ? -1 : 1); } - } \ No newline at end of file diff --git a/submission_builder.php b/submission_builder.php index 7b20326c..6bebae07 100644 --- a/submission_builder.php +++ b/submission_builder.php @@ -313,7 +313,7 @@ public static function wrap($array, $entity, $field_prefix = NULL) { $sa['fields']['training'] = ['value' => 'on']; } // UseLocationName is a special flag to indicate that an unmatched location - // can go in the locaiton_name field. + // can go in the location_name field. if (isset($array['useLocationName'])) { if ($entity === 'sample') { if ((empty($sa['fields']['location_id']) || empty($sa['fields']['location_id']['value']))