Skip to content

Commit 376d223

Browse files
committed
issue #601 upload fieldnotes
1 parent 34975e3 commit 376d223

File tree

3 files changed

+245
-0
lines changed

3 files changed

+245
-0
lines changed

okapi/core/OkapiServiceRunner.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class OkapiServiceRunner
4242
'services/caches/formatters/gpx',
4343
'services/caches/formatters/garmin',
4444
'services/caches/formatters/ggz',
45+
'services/fieldnotes/upload',
4546
'services/caches/map/tile',
4647
'services/logs/capabilities',
4748
'services/logs/delete',
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php
2+
3+
namespace okapi\services\fieldnotes\upload;
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+
$result = array(
27+
'success' => false // if the installation doesn't support it
28+
);
29+
30+
if (Settings::get('OC_BRANCH') == 'oc.de')
31+
{
32+
33+
$field_notes = $request->get_parameter('field_notes');
34+
if (!$field_notes) throw new ParamMissing('field_notes');
35+
36+
$notes = self::parse_notes($field_notes);
37+
if ($notes['success'] === false) throw new InvalidParam('field_notes', "Input data not recognized.");
38+
39+
foreach ($notes['records'] as $n)
40+
{
41+
$geocache = OkapiServiceRunner::call(
42+
'services/caches/geocache',
43+
new OkapiInternalRequest($request->consumer, $request->token, array(
44+
'cache_code' => $n['code'],
45+
'fields' => 'internal_id'
46+
))
47+
);
48+
$user_id = $request->token->user_id;
49+
$geocache_id = $geocache['internal_id'];
50+
$type = Okapi::logtypename2id($n['type']);
51+
$date = date("Y-m-d H:i:s", strtotime($n['date']));
52+
$text = $n['log'];
53+
54+
Db::query("
55+
insert into field_note (
56+
user_id, geocache_id, type, date, text
57+
) values (
58+
'".Db::escape_string($user_id)."',
59+
'".Db::escape_string($geocache_id)."',
60+
'".Db::escape_string($type)."',
61+
'".Db::escape_string($date)."',
62+
'".Db::escape_string($text)."'
63+
)
64+
");
65+
66+
}
67+
$result = array(
68+
'success' => true,
69+
'totalRecords' => $notes['totalRecords'],
70+
'processedRecords' => $notes['processedRecords']
71+
);
72+
//$result = json_encode($notes, JSON_PRETTY_PRINT); // debug
73+
}
74+
return Okapi::formatted_response($request, $result);
75+
}
76+
77+
// ------------------------------------------------------------------
78+
79+
private static function parse_notes($field_notes)
80+
{
81+
$decoded_field_notes = base64_decode($field_notes, true);
82+
if ($decoded_field_notes === false) return false;
83+
84+
$multiline = self::fieldNotesTxtArea2Array($decoded_field_notes);
85+
$submittable_logtype_names = Okapi::get_submittable_logtype_names();
86+
$records = [];
87+
$totalRecords = 0;
88+
$processedRecords = 0;
89+
90+
foreach ($multiline as $line) {
91+
$totalRecords++;
92+
$line = trim($line);
93+
$fields = self::CSVtoArray($line);
94+
95+
$code = $fields[0];
96+
$date = $fields[1];
97+
$type = $fields[2];
98+
99+
if (!in_array($type, $submittable_logtype_names)) continue;
100+
101+
$log = nl2br($fields[3]);
102+
103+
$records[] = [
104+
'code' => $code,
105+
'date' => $date,
106+
'type' => $type,
107+
'log' => $log,
108+
];
109+
$processedRecords++;
110+
}
111+
return ['success' => true, 'records' => $records, 'totalRecords' => $totalRecords, 'processedRecords' => $processedRecords];
112+
}
113+
114+
115+
// ------------------------------------------------------------------
116+
117+
private static function fieldNotesTxtArea2Array($fieldnotes)
118+
{
119+
$output = [];
120+
$buffer = '';
121+
$start = true;
122+
123+
$lines = explode("\n", $fieldnotes);
124+
$lines = array_filter($lines); // Drop empty lines
125+
126+
foreach ($lines as $line) {
127+
if ($start) {
128+
$buffer = $line;
129+
$start = false;
130+
} else {
131+
if (strpos($line, 'OC') !== 0) {
132+
$buffer .= "\n" . $line;
133+
} else {
134+
$output[] = trim($buffer);
135+
$buffer = $line;
136+
}
137+
}
138+
}
139+
140+
if (!$start) {
141+
$output[] = trim($buffer);
142+
}
143+
144+
return $output;
145+
}
146+
147+
// ------------------------------------------------------------------
148+
149+
private static function CSVtoArray($text)
150+
{
151+
$ret = [''];
152+
$i = 0;
153+
$p = '';
154+
$s = true;
155+
156+
foreach (str_split($text) as $l) {
157+
if ('"' === $l) {
158+
$s = !$s;
159+
if ('"' === $p) {
160+
$ret[$i] .= '"';
161+
$l = '-';
162+
} elseif ('' === $p) {
163+
$l = '-';
164+
}
165+
} elseif ($s && ',' === $l) {
166+
$l = $ret[++$i] = '';
167+
} else {
168+
$ret[$i] .= $l;
169+
}
170+
$p = $l;
171+
}
172+
173+
return $ret;
174+
}
175+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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'>
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+
<ul>
15+
<li>Geocache Code</li>
16+
<li>Date</li>
17+
<li>Log Type</li>
18+
<li>Log Text</li>
19+
</ul>
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>Since the log type is passed as a string, its value must match the
28+
values supported by the platform (case sensitive!):</p>
29+
<pre>
30+
[
31+
"Found it",
32+
"Didn't find it",
33+
"Comment",
34+
"Attended",
35+
"Will attend",
36+
"Archived",
37+
"Ready to search",
38+
"Temporarily unavailable"
39+
]
40+
</pre>
41+
<p>Note: This service method is not supported on all installations</p>
42+
</req>
43+
<common-format-params/>
44+
<returns>
45+
<p>A dictionary of the following structure:</p>
46+
<ul>
47+
<li>success - true</li>
48+
<li>totalRecords - number of records in <i>field_notes</i></li>
49+
<li>processedRecords - number of records inserted into the database</li>
50+
</ul>
51+
<p>processedRecords may be less than totalRecords (it may even be zero) and that
52+
is the case for the following reason: Fieldnotes files are created from
53+
Geocaching client applications on Smartphones. These applications support multiple
54+
geocaching platforms from which opencaching is only one of them. Conseqently the
55+
Fieldnotes file is a "hybrid file" which contains records for multiple different
56+
platforms. For instance for geocaching.com logs the records start with <b>GC....</b>
57+
while of opencaching the log records start with <b>OC....</b>. The client application
58+
uploads one and the same Fielnotes file to all platforms and it is the platform's
59+
task to filter out what matches their objects. Geocaching.com will discard "OC logs"
60+
and opencaching must discard "GC logs" in that file.</p>
61+
<p>In addition, in that hybrid file there will be <i>Log Type</i>, a string that
62+
inevitably has a different definition for the various platforms. For instance, what
63+
is called a "Write note" log on geocaching.com is recognized s "Comments" on
64+
opencaching platforms. Consequently fieldnotes records which have a log type which
65+
is not understood by the opencaching platform will be discarded without notice.</p>
66+
<p>It is the responsibility of the client application to assign the correct log type
67+
string when the offline log is created</p>
68+
</returns>
69+
</xml>

0 commit comments

Comments
 (0)