From d7e9dd4dbce8b3050382407727dc502478011e6b Mon Sep 17 00:00:00 2001
From: Hugo Dimpfelmoser
Date: Sat, 25 Nov 2023 10:39:29 +0100
Subject: [PATCH] issue #601 upload fieldnotes
---
okapi/core/OkapiServiceRunner.php | 1 +
.../services/fieldnotes/upload/WebService.php | 170 ++++++++++++++++++
okapi/services/fieldnotes/upload/docs.xml | 47 +++++
3 files changed, 218 insertions(+)
create mode 100644 okapi/services/fieldnotes/upload/WebService.php
create mode 100644 okapi/services/fieldnotes/upload/docs.xml
diff --git a/okapi/core/OkapiServiceRunner.php b/okapi/core/OkapiServiceRunner.php
index 5433e5d9..823c99fe 100644
--- a/okapi/core/OkapiServiceRunner.php
+++ b/okapi/core/OkapiServiceRunner.php
@@ -43,6 +43,7 @@ class OkapiServiceRunner
'services/caches/formatters/gpx',
'services/caches/formatters/garmin',
'services/caches/formatters/ggz',
+ 'services/fieldnotes/upload',
'services/caches/map/tile',
'services/logs/capabilities',
'services/logs/delete',
diff --git a/okapi/services/fieldnotes/upload/WebService.php b/okapi/services/fieldnotes/upload/WebService.php
new file mode 100644
index 00000000..600e5b64
--- /dev/null
+++ b/okapi/services/fieldnotes/upload/WebService.php
@@ -0,0 +1,170 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false // if the installation doesn't support it
+ );
+
+ if (Settings::get('OC_BRANCH') == 'oc.de')
+ {
+
+ $field_notes = $request->get_parameter('field_notes');
+ if (!$field_notes) throw new ParamMissing('field_notes');
+
+ $result = Okapi::get_submittable_logtype_names();
+ $result = json_encode($result, JSON_PRETTY_PRINT); // debug
+ return Okapi::formatted_response($request, $result);
+
+ $notes = self::parse_notes($field_notes);
+ if ($notes === false) throw new InvalidParam('field_notes', "Input data not recognized.");
+
+ foreach ($notes as $n)
+ {
+ $geocache = OkapiServiceRunner::call(
+ 'services/caches/geocache',
+ new OkapiInternalRequest($request->consumer, $request->token, array(
+ 'cache_code' => $n['code'],
+ 'fields' => 'internal_id'
+ ))
+ );
+ $user_id = $request->token->user_id;
+ $geocache_id = $geocache['internal_id'];
+ $type = Okapi::logtypename2id($n['type']);
+ $date = date("Y-m-d H:i:s", strtotime($n['date']));
+ $text = $n['log'];
+
+ Db::query("
+ insert into field_note (
+ user_id, geocache_id, type, date, text
+ ) values (
+ '".Db::escape_string($user_id)."',
+ '".Db::escape_string($geocache_id)."',
+ '".Db::escape_string($type)."',
+ '".Db::escape_string($date)."',
+ '".Db::escape_string($text)."'
+ )
+ ");
+
+ }
+ $result = array(
+ 'success' => true
+ );
+ //$result = json_encode($notes, JSON_PRETTY_PRINT); // debug
+ }
+ return Okapi::formatted_response($request, $result);
+ }
+
+ // ------------------------------------------------------------------
+
+ private static function parse_notes($field_notes)
+ {
+ $decoded_field_notes = base64_decode($field_notes, true);
+ if ($decoded_field_notes === false) return false;
+
+ $multiline = self::fieldNotesTxtArea2Array($decoded_field_notes);
+
+ $parsed_records = [];
+
+ foreach ($multiline as $line) {
+ $line = trim($line);
+ $fields = self::CSVtoArray($line);
+
+ $code = $fields[0];
+ $date = $fields[1];
+ $type = $fields[2];
+ $log = nl2br($fields[3]);
+
+ $parsed_records[] = [
+ 'code' => $code,
+ 'date' => $date,
+ 'type' => $type,
+ 'log' => $log,
+ ];
+ }
+ return $parsed_records;
+ }
+
+
+ // ------------------------------------------------------------------
+
+ private static function fieldNotesTxtArea2Array($fieldnotes)
+ {
+ $output = [];
+ $buffer = '';
+ $start = true;
+
+ $lines = explode("\n", $fieldnotes);
+ $lines = array_filter($lines); // Drop empty lines
+
+ foreach ($lines as $line) {
+ if ($start) {
+ $buffer = $line;
+ $start = false;
+ } else {
+ if (strpos($line, 'OC') !== 0) {
+ $buffer .= "\n" . $line;
+ } else {
+ $output[] = trim($buffer);
+ $buffer = $line;
+ }
+ }
+ }
+
+ if (!$start) {
+ $output[] = trim($buffer);
+ }
+
+ return $output;
+ }
+
+ // ------------------------------------------------------------------
+
+ private static function CSVtoArray($text)
+ {
+ $ret = [''];
+ $i = 0;
+ $p = '';
+ $s = true;
+
+ foreach (str_split($text) as $l) {
+ if ('"' === $l) {
+ $s = !$s;
+ if ('"' === $p) {
+ $ret[$i] .= '"';
+ $l = '-';
+ } elseif ('' === $p) {
+ $l = '-';
+ }
+ } elseif ($s && ',' === $l) {
+ $l = $ret[++$i] = '';
+ } else {
+ $ret[$i] .= $l;
+ }
+ $p = $l;
+ }
+
+ return $ret;
+ }
+}
diff --git a/okapi/services/fieldnotes/upload/docs.xml b/okapi/services/fieldnotes/upload/docs.xml
new file mode 100644
index 00000000..acbc4ac0
--- /dev/null
+++ b/okapi/services/fieldnotes/upload/docs.xml
@@ -0,0 +1,47 @@
+
+ Upload Fieldnotes
+ 630
+
+ Upload a set of one or more fieldnotes records.
+
+
+ Fieldnotes consist of one or more records in CSV format. Each
+ record comprises a geocache log consisting of four fields:
+
+ - Geocache Code
+ - Date
+ - Log Type
+ - Log Text
+
+ The first three fields are simple entities, the Log Text field is
+ different and a bit difficult as it may spread over muliple lines
+ and it may contain quote characters. In order to preserve the structure
+ of the records, the field_notes parameter must be passed as a
+ base64 encoded utf8 string. UTF-16LE, UTF-16BE with or without
+ BOM are not supported.
+
+ Since the log type is passed as a string, its value must match the
+ values supported by the platform (case sensitive!):
+
+[
+ "Found it",
+ "Didn't find it",
+ "Comment",
+ "Attended",
+ "Will attend",
+ "Archived",
+ "Ready to search",
+ "Temporarily unavailable"
+]
+
+
+ Note: This service method is not supported on all installations
+
+
+
+ A dictionary of the following structure:
+
+
+