From 2ce5c31971720746891a31ebdad36b8be5de7538 Mon Sep 17 00:00:00 2001
From: Hugo Dimpfelmoser
Date: Thu, 23 Nov 2023 09:37:57 +0100
Subject: [PATCH 1/5] issue #628 save_user_coords
---
okapi/core/OkapiServiceRunner.php | 1 +
.../caches/save_user_coords/WebService.php | 108 ++++++++++++++++++
.../services/caches/save_user_coords/docs.xml | 29 +++++
3 files changed, 138 insertions(+)
create mode 100644 okapi/services/caches/save_user_coords/WebService.php
create mode 100644 okapi/services/caches/save_user_coords/docs.xml
diff --git a/okapi/core/OkapiServiceRunner.php b/okapi/core/OkapiServiceRunner.php
index a1d0abba..5433e5d9 100644
--- a/okapi/core/OkapiServiceRunner.php
+++ b/okapi/core/OkapiServiceRunner.php
@@ -39,6 +39,7 @@ class OkapiServiceRunner
'services/caches/geocaches',
'services/caches/mark',
'services/caches/save_personal_notes',
+ 'services/caches/save_user_coords',
'services/caches/formatters/gpx',
'services/caches/formatters/garmin',
'services/caches/formatters/ggz',
diff --git a/okapi/services/caches/save_user_coords/WebService.php b/okapi/services/caches/save_user_coords/WebService.php
new file mode 100644
index 00000000..bdac1be3
--- /dev/null
+++ b/okapi/services/caches/save_user_coords/WebService.php
@@ -0,0 +1,108 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+
+ $user_coords = $request->get_parameter('user_coords');
+ if ($user_coords == null)
+ throw new ParamMissing('user_coords');
+ $parts = explode('|', $user_coords);
+ if (count($parts) != 2)
+ throw new InvalidParam('user_coords', "Expecting 2 pipe-separated parts, got ".count($parts).".");
+ foreach ($parts as &$part_ref)
+ {
+ if (!preg_match("/^-?[0-9]+(\.?[0-9]*)$/", $part_ref))
+ throw new InvalidParam('user_coords', "'$part_ref' is not a valid float number.");
+ $part_ref = floatval($part_ref);
+ }
+ list($latitude, $longitude) = $parts;
+
+ # Verify cache_code
+
+ $cache_code = $request->get_parameter('cache_code');
+ if ($cache_code == null)
+ throw new ParamMissing('cache_code');
+ $geocache = OkapiServiceRunner::call(
+ 'services/caches/geocache',
+ new OkapiInternalRequest($request->consumer, $request->token, array(
+ 'cache_code' => $cache_code,
+ 'fields' => 'internal_id'
+ ))
+ );
+ $cache_id = $geocache['internal_id'];
+
+ self::update_notes($cache_id, $request->token->user_id, $latitude, $longitude);
+
+ $ret_value = 'ok';
+
+ $result = array(
+ 'status' => $ret_value
+ );
+ return Okapi::formatted_response($request, $result);
+ }
+
+ private static function update_notes($cache_id, $user_id, $latitude, $longitude)
+ {
+ /* See:
+ *
+ * - https://github.com/OpencachingDeutschland/oc-server3/tree/development/htdocs/src/Oc/Libse/CacheNote
+ * - https://www.opencaching.de/okapi/devel/dbstruct
+ */
+
+ $rs = Db::query("
+ select max(id) as id
+ from coordinates
+ where
+ type = 2 -- personal note
+ and cache_id = '".Db::escape_string($cache_id)."'
+ and user_id = '".Db::escape_string($user_id)."'
+ ");
+ $id = null;
+ if($row = Db::fetch_assoc($rs)) {
+ $id = $row['id'];
+ }
+ if ($id == null) {
+ Db::query("
+ insert into coordinates (
+ type, latitude, longitude, cache_id, user_id
+ ) values (
+ 2,
+ '".Db::escape_string($latitude)."',
+ '".Db::escape_string($longitude)."',
+ '".Db::escape_string($cache_id)."',
+ '".Db::escape_string($user_id)."'
+ )
+ ");
+ } else {
+ Db::query("
+ update coordinates
+ set latitude = '".Db::escape_string($latitude)."',
+ longitude = '".Db::escape_string($longitude)."',
+ where
+ id = '".Db::escape_string($id)."'
+ and type = 2
+ ");
+ }
+ }
+
+}
diff --git a/okapi/services/caches/save_user_coords/docs.xml b/okapi/services/caches/save_user_coords/docs.xml
new file mode 100644
index 00000000..9689200d
--- /dev/null
+++ b/okapi/services/caches/save_user_coords/docs.xml
@@ -0,0 +1,29 @@
+
+ Update personal coordinates of a geocache
+ 629
+
+ This method allows your users to update the coordinates of their
+ personal geocache coordinates.
+
+ Current personal coordinates for the geocache can be retrieved
+ using the alt_wpts field in the
+ services/caches/geocache
+ method.
+
+
+ Code of the geocache
+
+
+ The coordinates are defined by a string in the format "lat|lon"
+ Use positive numbers for latitudes in the northern hemisphere and longitudes
+ in the eastern hemisphere (and negative for southern and western hemispheres
+ accordingly). These are full degrees with a dot as a decimal point (ex. "48.7|15.89").
+
+
+
+ A dictionary of the following structure:
+
+
+
From 5194518d74f75a5d3b5b25a0097cd411ced1777f Mon Sep 17 00:00:00 2001
From: Hugo Dimpfelmoser
Date: Thu, 23 Nov 2023 09:37:57 +0100
Subject: [PATCH 2/5] issue #628 save_user_coords
---
.../caches/save_user_coords/WebService.php | 118 ++++++++++++------
.../services/caches/save_user_coords/docs.xml | 2 +-
2 files changed, 78 insertions(+), 42 deletions(-)
diff --git a/okapi/services/caches/save_user_coords/WebService.php b/okapi/services/caches/save_user_coords/WebService.php
index bdac1be3..b7580a7b 100644
--- a/okapi/services/caches/save_user_coords/WebService.php
+++ b/okapi/services/caches/save_user_coords/WebService.php
@@ -51,58 +51,94 @@ public static function call(OkapiRequest $request)
);
$cache_id = $geocache['internal_id'];
- self::update_notes($cache_id, $request->token->user_id, $latitude, $longitude);
-
- $ret_value = 'ok';
+ self::update_coordinates($cache_id, $request->token->user_id, $latitude, $longitude);
$result = array(
- 'status' => $ret_value
+ 'success' => true
);
return Okapi::formatted_response($request, $result);
}
- private static function update_notes($cache_id, $user_id, $latitude, $longitude)
+ private static function update_coordinates($cache_id, $user_id, $latitude, $longitude)
{
- /* See:
- *
- * - https://github.com/OpencachingDeutschland/oc-server3/tree/development/htdocs/src/Oc/Libse/CacheNote
- * - https://www.opencaching.de/okapi/devel/dbstruct
- */
+ if (Settings::get('OC_BRANCH') == 'oc.de')
+ {
- $rs = Db::query("
- select max(id) as id
- from coordinates
- where
- type = 2 -- personal note
- and cache_id = '".Db::escape_string($cache_id)."'
- and user_id = '".Db::escape_string($user_id)."'
- ");
- $id = null;
- if($row = Db::fetch_assoc($rs)) {
- $id = $row['id'];
- }
- if ($id == null) {
- Db::query("
- insert into coordinates (
- type, latitude, longitude, cache_id, user_id
- ) values (
- 2,
- '".Db::escape_string($latitude)."',
- '".Db::escape_string($longitude)."',
- '".Db::escape_string($cache_id)."',
- '".Db::escape_string($user_id)."'
- )
+ /* See:
+ *
+ * - https://github.com/OpencachingDeutschland/oc-server3/tree/development/htdocs/src/Oc/Libse/CacheNote
+ * - https://www.opencaching.de/okapi/devel/dbstruct
+ */
+
+ $rs = Db::query("
+ select max(id) as id
+ from coordinates
+ where
+ type = 2 -- personal note
+ and cache_id = '".Db::escape_string($cache_id)."'
+ and user_id = '".Db::escape_string($user_id)."'
");
- } else {
- Db::query("
- update coordinates
- set latitude = '".Db::escape_string($latitude)."',
- longitude = '".Db::escape_string($longitude)."',
+ $id = null;
+ if($row = Db::fetch_assoc($rs)) {
+ $id = $row['id'];
+ }
+ if ($id == null) {
+ Db::query("
+ insert into coordinates (
+ type, latitude, longitude, cache_id, user_id, description
+ ) values (
+ 2,
+ '".Db::escape_string($latitude)."',
+ '".Db::escape_string($longitude)."',
+ '".Db::escape_string($cache_id)."',
+ '".Db::escape_string($user_id)."',
+ '".Db::escape_string("")."'
+ )
+ ");
+ } else {
+ Db::query("
+ update coordinates
+ set latitude = '".Db::escape_string($latitude)."',
+ longitude = '".Db::escape_string($longitude)."'
+ where
+ id = '".Db::escape_string($id)."'
+ and type = 2
+ ");
+ }
+ }
+ else # oc.pl branch
+ {
+ $rs = Db::query("
+ select max(id) as id
+ from cache_mod_cords
where
- id = '".Db::escape_string($id)."'
- and type = 2
+ cache_id = '".Db::escape_string($cache_id)."'
+ and user_id = '".Db::escape_string($user_id)."'
");
+ $id = null;
+ if($row = Db::fetch_assoc($rs)) {
+ $id = $row['id'];
+ }
+ if ($id == null) {
+ Db::query("
+ insert into cache_mod_cords (
+ cache_id, user_id, latitude, longitude
+ ) values (
+ '".Db::escape_string($cache_id)."',
+ '".Db::escape_string($user_id)."',
+ '".Db::escape_string($latitude)."',
+ '".Db::escape_string($longitude)."'
+ )
+ ");
+ } else {
+ Db::query("
+ update cache_mod_cords
+ set latitude = '".Db::escape_string($latitude)."',
+ longitude = '".Db::escape_string($longitude)."'
+ where
+ id = '".Db::escape_string($id)."'
+ ");
+ }
}
}
-
}
diff --git a/okapi/services/caches/save_user_coords/docs.xml b/okapi/services/caches/save_user_coords/docs.xml
index 9689200d..29422624 100644
--- a/okapi/services/caches/save_user_coords/docs.xml
+++ b/okapi/services/caches/save_user_coords/docs.xml
@@ -23,7 +23,7 @@
A dictionary of the following structure:
- - status - ok
+ - success - true
From 60a163a0bcf5d23cec5c7df8b3185cdac5ee0589 Mon Sep 17 00:00:00 2001
From: hxdimpf
Date: Thu, 12 Mar 2026 19:04:58 +0100
Subject: [PATCH 3/5] Remove unnecessary Db::escape_string on empty string
literal
Use a simple '' instead of Db::escape_string("") for the empty
description value in the INSERT statement.
---
okapi/services/caches/save_user_coords/WebService.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/okapi/services/caches/save_user_coords/WebService.php b/okapi/services/caches/save_user_coords/WebService.php
index b7580a7b..289afdd6 100644
--- a/okapi/services/caches/save_user_coords/WebService.php
+++ b/okapi/services/caches/save_user_coords/WebService.php
@@ -92,7 +92,7 @@ private static function update_coordinates($cache_id, $user_id, $latitude, $long
'".Db::escape_string($longitude)."',
'".Db::escape_string($cache_id)."',
'".Db::escape_string($user_id)."',
- '".Db::escape_string("")."'
+ ''
)
");
} else {
From 82d6947a423fd604746dc3d2bedf9473bc4d877b Mon Sep 17 00:00:00 2001
From: hxdimpf
Date: Wed, 13 May 2026 01:57:15 +0200
Subject: [PATCH 4/5] fix save_user_coords: use INSERT ON DUPLICATE KEY UPDATE
for oc.pl
cache_mod_cords has a composite PK (cache_id, user_id) with no id
column, so the previous select max(id) would fail at runtime.
---
.../caches/save_user_coords/WebService.php | 49 +++++++------------
1 file changed, 19 insertions(+), 30 deletions(-)
diff --git a/okapi/services/caches/save_user_coords/WebService.php b/okapi/services/caches/save_user_coords/WebService.php
index 289afdd6..4c8cc8be 100644
--- a/okapi/services/caches/save_user_coords/WebService.php
+++ b/okapi/services/caches/save_user_coords/WebService.php
@@ -108,37 +108,26 @@ private static function update_coordinates($cache_id, $user_id, $latitude, $long
}
else # oc.pl branch
{
- $rs = Db::query("
- select max(id) as id
- from cache_mod_cords
- where
- cache_id = '".Db::escape_string($cache_id)."'
- and user_id = '".Db::escape_string($user_id)."'
+ # cache_mod_cords uses a composite PK (cache_id, user_id) — no id column.
+ Db::query("
+ INSERT INTO cache_mod_cords (
+ cache_id,
+ user_id,
+ latitude,
+ longitude,
+ date
+ ) VALUES (
+ '".Db::escape_string($cache_id)."',
+ '".Db::escape_string($user_id)."',
+ '".Db::escape_string($latitude)."',
+ '".Db::escape_string($longitude)."',
+ NOW()
+ )
+ ON DUPLICATE KEY UPDATE
+ latitude = VALUES(latitude),
+ longitude = VALUES(longitude),
+ date = NOW()
");
- $id = null;
- if($row = Db::fetch_assoc($rs)) {
- $id = $row['id'];
- }
- if ($id == null) {
- Db::query("
- insert into cache_mod_cords (
- cache_id, user_id, latitude, longitude
- ) values (
- '".Db::escape_string($cache_id)."',
- '".Db::escape_string($user_id)."',
- '".Db::escape_string($latitude)."',
- '".Db::escape_string($longitude)."'
- )
- ");
- } else {
- Db::query("
- update cache_mod_cords
- set latitude = '".Db::escape_string($latitude)."',
- longitude = '".Db::escape_string($longitude)."'
- where
- id = '".Db::escape_string($id)."'
- ");
- }
}
}
}
From d27f91d65d336f4ecab942a31d7a279bfb63f49f Mon Sep 17 00:00:00 2001
From: hxdimpf
Date: Tue, 26 May 2026 20:38:30 +0200
Subject: [PATCH 5/5] save_user_coords: validate cache type on oc.pl, update
docs
OCPL only supports user coordinates for Multi, Quiz, and Other cache
types (per ViewCacheController). Fetch the type field alongside
internal_id and reject unsupported types with HTTP 400.
Also document the OCPL restriction in docs.xml.
---
.../caches/save_user_coords/WebService.php | 20 ++++++++++++++++++-
.../services/caches/save_user_coords/docs.xml | 5 ++++-
2 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/okapi/services/caches/save_user_coords/WebService.php b/okapi/services/caches/save_user_coords/WebService.php
index 4c8cc8be..96808d3f 100644
--- a/okapi/services/caches/save_user_coords/WebService.php
+++ b/okapi/services/caches/save_user_coords/WebService.php
@@ -46,11 +46,13 @@ public static function call(OkapiRequest $request)
'services/caches/geocache',
new OkapiInternalRequest($request->consumer, $request->token, array(
'cache_code' => $cache_code,
- 'fields' => 'internal_id'
+ 'fields' => 'internal_id|type'
))
);
$cache_id = $geocache['internal_id'];
+ self::validate_cache_type($geocache['type']);
+
self::update_coordinates($cache_id, $request->token->user_id, $latitude, $longitude);
$result = array(
@@ -59,6 +61,22 @@ public static function call(OkapiRequest $request)
return Okapi::formatted_response($request, $result);
}
+ private static function validate_cache_type($cache_type)
+ {
+ if (Settings::get('OC_BRANCH') != 'oc.pl') {
+ return;
+ }
+
+ $allowed_types = array('Other', 'Quiz', 'Multi');
+
+ if (!in_array($cache_type, $allowed_types, true)) {
+ throw new InvalidParam(
+ 'cache_code',
+ "User coordinates are not supported for cache type '$cache_type'."
+ );
+ }
+ }
+
private static function update_coordinates($cache_id, $user_id, $latitude, $longitude)
{
if (Settings::get('OC_BRANCH') == 'oc.de')
diff --git a/okapi/services/caches/save_user_coords/docs.xml b/okapi/services/caches/save_user_coords/docs.xml
index 29422624..424a7b6a 100644
--- a/okapi/services/caches/save_user_coords/docs.xml
+++ b/okapi/services/caches/save_user_coords/docs.xml
@@ -11,7 +11,10 @@
method.
- Code of the geocache
+ Code of the geocache.
+ Note: On OCPL-based sites, user coordinates are only supported for
+ caches of type Multi, Quiz, and Other. Supplying a
+ cache of any other type will result in an HTTP 400 error.
The coordinates are defined by a string in the format "lat|lon"