Skip to content

Commit 77650d9

Browse files
committed
issue #601 upload fieldnotes
1 parent 34975e3 commit 77650d9

File tree

3 files changed

+249
-0
lines changed

3 files changed

+249
-0
lines changed

okapi/core/OkapiServiceRunner.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class OkapiServiceRunner
4343
'services/caches/formatters/garmin',
4444
'services/caches/formatters/ggz',
4545
'services/caches/map/tile',
46+
'services/draftlogs/upload_fieldnotes_file',
4647
'services/logs/capabilities',
4748
'services/logs/delete',
4849
'services/logs/edit',
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
3+
namespace okapi\services\draftlogs\upload_fieldnotes_file;
4+
5+
use okapi\core\Exception\InvalidParam;
6+
use okapi\core\Exception\ParamMissing;
7+
use okapi\core\Db;
8+
use okapi\core\Okapi;
9+
use okapi\core\OkapiServiceRunner;
10+
use okapi\core\Request\OkapiInternalRequest;
11+
use okapi\core\Request\OkapiRequest;
12+
use okapi\services\logs\LogsCommon;
13+
use okapi\Settings;
14+
15+
class WebService
16+
{
17+
public static function options()
18+
{
19+
return array(
20+
'min_auth_level' => 3
21+
);
22+
}
23+
24+
public static function call(OkapiRequest $request)
25+
{
26+
if (Settings::get('OC_BRANCH') != 'oc.de')
27+
throw new BadRequest('This method is not supported in this OKAPI installation. See the has_draftlogs field in services/apisrv/installation method.');
28+
29+
$field_notes = $request->get_parameter('field_notes');
30+
if (!$field_notes) throw new ParamMissing('field_notes');
31+
32+
$notes = self::parse_notes($field_notes);
33+
34+
foreach ($notes['records'] as $n)
35+
{
36+
$geocache = OkapiServiceRunner::call(
37+
'services/caches/geocache',
38+
new OkapiInternalRequest($request->consumer, $request->token, array(
39+
'cache_code' => $n['code'],
40+
'fields' => 'internal_id'
41+
))
42+
);
43+
44+
try {
45+
$type = Okapi::logtypename2id($n['type']);
46+
} catch (\Exception $e) {
47+
throw new InvalidParam('Type', 'Invalid log type provided.');
48+
}
49+
50+
$dateString = strtotime($n['date']);
51+
if ($dateString === false) {
52+
throw new InvalidParam('`Date` field in log record', "Input data not recognized.");
53+
} else {
54+
$date = date("Y-m-d H:i:s", $dateString);
55+
}
56+
57+
$user_id = $request->token->user_id;
58+
$geocache_id = $geocache['internal_id'];
59+
$text = $n['log'];
60+
61+
Db::query("
62+
insert into field_note (
63+
user_id, geocache_id, type, date, text
64+
) values (
65+
'".Db::escape_string($user_id)."',
66+
'".Db::escape_string($geocache_id)."',
67+
'".Db::escape_string($type)."',
68+
'".Db::escape_string($date)."',
69+
'".Db::escape_string($text)."'
70+
)
71+
");
72+
73+
}
74+
$result = array(
75+
'success' => true,
76+
'totalRecords' => $notes['totalRecords'],
77+
'processedRecords' => $notes['processedRecords']
78+
);
79+
return Okapi::formatted_response($request, $result);
80+
}
81+
82+
// ------------------------------------------------------------------
83+
84+
private static function parse_notes($field_notes)
85+
{
86+
$decoded_field_notes = base64_decode($field_notes, true);
87+
if ($decoded_field_notes === false) throw new InvalidParam('field_notes', "Input data is not properly base64 encoded.");
88+
89+
$multiline = self::fieldNotesTxtArea2Array($decoded_field_notes);
90+
$submittable_logtype_names = Okapi::get_submittable_logtype_names();
91+
$records = [];
92+
$totalRecords = 0;
93+
$processedRecords = 0;
94+
95+
foreach ($multiline as $line) {
96+
$totalRecords++;
97+
$line = trim($line);
98+
$fields = self::CSVtoArray($line);
99+
100+
$code = $fields[0];
101+
$date = $fields[1];
102+
$type = $fields[2];
103+
104+
if (!in_array($type, $submittable_logtype_names)) continue;
105+
106+
$log = nl2br($fields[3]);
107+
108+
$records[] = [
109+
'code' => $code,
110+
'date' => $date,
111+
'type' => $type,
112+
'log' => $log,
113+
];
114+
$processedRecords++;
115+
}
116+
return ['success' => true, 'records' => $records, 'totalRecords' => $totalRecords, 'processedRecords' => $processedRecords];
117+
}
118+
119+
120+
// ------------------------------------------------------------------
121+
122+
private static function fieldNotesTxtArea2Array($fieldnotes)
123+
{
124+
$output = [];
125+
$buffer = '';
126+
$start = true;
127+
128+
$lines = explode("\n", $fieldnotes);
129+
$lines = array_filter($lines); // Drop empty lines
130+
131+
foreach ($lines as $line) {
132+
if ($start) {
133+
$buffer = $line;
134+
$start = false;
135+
} else {
136+
if (strpos($line, 'OC') !== 0) {
137+
$buffer .= "\n" . $line;
138+
} else {
139+
$output[] = trim($buffer);
140+
$buffer = $line;
141+
}
142+
}
143+
}
144+
145+
if (!$start) {
146+
$output[] = trim($buffer);
147+
}
148+
149+
return $output;
150+
}
151+
152+
// ------------------------------------------------------------------
153+
154+
private static function CSVtoArray($text)
155+
{
156+
$result = [''];
157+
$inQuotes = true;
158+
$i = 0;
159+
160+
foreach (str_split($text) as $char) {
161+
if ($char === '"') {
162+
$inQuotes = !$inQuotes;
163+
if ($p === '"') {
164+
$result[$i] .= '"';
165+
}
166+
} elseif ($inQuotes && $char === ',') {
167+
$char = $result[++$i] = '';
168+
} else {
169+
$result[$i] .= $char;
170+
}
171+
$p = $char;
172+
}
173+
174+
return $result;
175+
}
176+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<xml>
2+
<brief>Upload Fieldnotes</brief>
3+
<issue-id>630</issue-id>
4+
<desc>
5+
<p>Upload a set of one or more fieldnotes records.</p>
6+
</desc>
7+
<req name='field_notes' infotags='ocde-specific'>
8+
<p>This method allows you to upload a series of fieldnotes in the CSV format.
9+
Fieldnotes are draft versions of log entries. Once uploaded, users will be able
10+
to review, edit, and submit them via the Opencaching site.</p>
11+
12+
<p>Fieldnotes do not have a CSV header in the file that might describe the columns.</p>
13+
<p>Each record describes a geocache log consisting of four fields:</p>
14+
<ol>
15+
<li>Geocache Code</li>
16+
<li>Date</li>
17+
<li>Log Type</li>
18+
<li>Log Text</li>
19+
</ol>
20+
<p>The first three fields are string entities that don't have line control characters in them,
21+
the Log Text field is different and a bit difficult as it may spread over muliple lines
22+
and it may contain quote characters. In order to preserve the structure
23+
of the records, the <i>field_notes</i> parameter must be passed as a
24+
<b>base64 encoded utf8 string</b>. UTF-16LE, UTF-16BE with or without
25+
BOM are not supported.
26+
</p>
27+
<p>The second field <i>Date</i> should be in ISO 8601 format (currently any format
28+
acceptable by PHP's strtotime function also will do, but most of them don't handle
29+
time zones properly, try to use ISO 8601!).</p>
30+
<p>Since the log type is passed as a string, its value must match the
31+
values supported by the platform (case sensitive!):</p>
32+
<pre>
33+
[
34+
"Found it",
35+
"Didn't find it",
36+
"Comment",
37+
"Attended",
38+
"Will attend",
39+
"Archived",
40+
"Ready to search",
41+
"Temporarily unavailable"
42+
]
43+
</pre>
44+
<p>Note: This service method is not supported on all installations</p>
45+
</req>
46+
<common-format-params/>
47+
<returns>
48+
<p>A dictionary of the following structure:</p>
49+
<ul>
50+
<li>success - true</li>
51+
<li>totalRecords - number of records in <i>field_notes</i></li>
52+
<li>processedRecords - number of records inserted into the database</li>
53+
</ul>
54+
<p>processedRecords may be less than totalRecords (it may even be zero) and that
55+
is the case for the following reason: Fieldnotes files are created from
56+
Geocaching client applications on Smartphones. These applications support multiple
57+
geocaching platforms from which opencaching is only one of them. Conseqently the
58+
Fieldnotes file is a "hybrid file" which contains records for multiple different
59+
platforms. For instance for geocaching.com logs, the records start with <b>GC....</b>
60+
while on opencaching the log records start with <b>OC....</b>. The client application
61+
uploads one and the same Fielnotes file to all platforms and it is the platform's
62+
task to filter out what matches their objects. Geocaching.com will discard "OC logs"
63+
and opencaching must discard "GC logs" in that file.</p>
64+
<p>In addition, in that hybrid file there will be <i>Log Type</i>, a string that
65+
inevitably has a different definition for the various platforms. For instance, what
66+
is called a "Write note" log on geocaching.com is recognized as "Comments" on
67+
opencaching platforms. Consequently fieldnotes records which have a Log Type which
68+
is not understood by the opencaching platform will be discarded without notice.</p>
69+
<p>It is the responsibility of the client application to assign the correct Log Type
70+
string when the offline log is created</p>
71+
</returns>
72+
</xml>

0 commit comments

Comments
 (0)