Skip to content

Commit

Permalink
issue opencaching#601 upload fieldnotes
Browse files Browse the repository at this point in the history
  • Loading branch information
hxdimpf committed Nov 26, 2023
1 parent 34975e3 commit 033d168
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 0 deletions.
1 change: 1 addition & 0 deletions okapi/core/OkapiServiceRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class OkapiServiceRunner
'services/caches/formatters/garmin',
'services/caches/formatters/ggz',
'services/caches/map/tile',
'services/draftlogs/upload_fieldnotes_file',
'services/logs/capabilities',
'services/logs/delete',
'services/logs/edit',
Expand Down
182 changes: 182 additions & 0 deletions okapi/services/draftlogs/upload_fieldnotes_file/WebService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?php

namespace okapi\services\draftlogs\upload_fieldnotes_file;

use okapi\core\Exception\InvalidParam;
use okapi\core\Exception\ParamMissing;
use okapi\core\Db;
use okapi\core\Okapi;
use okapi\core\OkapiServiceRunner;
use okapi\core\Request\OkapiInternalRequest;
use okapi\core\Request\OkapiRequest;
use okapi\services\logs\LogsCommon;
use okapi\Settings;

class WebService
{
public static function options()
{
return array(
'min_auth_level' => 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');

$notes = self::parse_notes($field_notes);
if ($notes['success'] === false) throw new InvalidParam('field_notes', "Input data not recognized.");

foreach ($notes['records'] 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']);
$text = $n['log'];
$dateString = strtotime($n['date']);
if ($dateString === false)
{
throw new InvalidParam('Date field in logi record', "Input data not recognized.");
}
else
{
$date = date("Y-m-d H:i:s", $dateString);
}
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,
'totalRecords' => $notes['totalRecords'],
'processedRecords' => $notes['processedRecords']
);
//$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);
$submittable_logtype_names = Okapi::get_submittable_logtype_names();
$records = [];
$totalRecords = 0;
$processedRecords = 0;

foreach ($multiline as $line) {
$totalRecords++;
$line = trim($line);
$fields = self::CSVtoArray($line);

$code = $fields[0];
$date = $fields[1];
$type = $fields[2];

if (!in_array($type, $submittable_logtype_names)) continue;

$log = nl2br($fields[3]);

$records[] = [
'code' => $code,
'date' => $date,
'type' => $type,
'log' => $log,
];
$processedRecords++;
}
return ['success' => true, 'records' => $records, 'totalRecords' => $totalRecords, 'processedRecords' => $processedRecords];
}


// ------------------------------------------------------------------

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;
}
}
72 changes: 72 additions & 0 deletions okapi/services/draftlogs/upload_fieldnotes_file/docs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<xml>
<brief>Upload Fieldnotes</brief>
<issue-id>630</issue-id>
<desc>
<p>Upload a set of one or more fieldnotes records.</p>
</desc>
<req name='field_notes' infotags='ocde-specific'>
<p>This method allows you to upload a series of fieldnotes in the CSV format.
Fieldnotes are draft versions of log entries. Once uploaded, users will be able
to review, edit, and submit them via the Opencaching site.</p>

<p>Fieldnotes do not have a CSV header in the file that might describe the columns.</p>
<p>Each record describes a geocache log consisting of four fields:</p>
<ol>
<li>Geocache Code</li>
<li>Date</li>
<li>Log Type</li>
<li>Log Text</li>
</ol>
<p>The first three fields are string entities that don't have line control characters in them,
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 <i>field_notes</i> parameter must be passed as a
<b>base64 encoded utf8 string</b>. UTF-16LE, UTF-16BE with or without
BOM are not supported.
</p>
<p>The second field <i>Date</i> should be in ISO 8601 format (currently any format
acceptable by PHP's strtotime function also will do, but most of them don't handle
time zones properly, try to use ISO 8601!).</p>
<p>Since the log type is passed as a string, its value must match the
values supported by the platform (case sensitive!):</p>
<pre>
[
"Found it",
"Didn't find it",
"Comment",
"Attended",
"Will attend",
"Archived",
"Ready to search",
"Temporarily unavailable"
]
</pre>
<p>Note: This service method is not supported on all installations</p>
</req>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li>success - true</li>
<li>totalRecords - number of records in <i>field_notes</i></li>
<li>processedRecords - number of records inserted into the database</li>
</ul>
<p>processedRecords may be less than totalRecords (it may even be zero) and that
is the case for the following reason: Fieldnotes files are created from
Geocaching client applications on Smartphones. These applications support multiple
geocaching platforms from which opencaching is only one of them. Conseqently the
Fieldnotes file is a "hybrid file" which contains records for multiple different
platforms. For instance for geocaching.com logs the records start with <b>GC....</b>
while of opencaching the log records start with <b>OC....</b>. The client application
uploads one and the same Fielnotes file to all platforms and it is the platform's
task to filter out what matches their objects. Geocaching.com will discard "OC logs"
and opencaching must discard "GC logs" in that file.</p>
<p>In addition, in that hybrid file there will be <i>Log Type</i>, a string that
inevitably has a different definition for the various platforms. For instance, what
is called a "Write note" log on geocaching.com is recognized s "Comments" on
opencaching platforms. Consequently fieldnotes records which have a log type which
is not understood by the opencaching platform will be discarded without notice.</p>
<p>It is the responsibility of the client application to assign the correct log type
string when the offline log is created</p>
</returns>
</xml>

0 comments on commit 033d168

Please sign in to comment.