From 2ce5c31971720746891a31ebdad36b8be5de7538 Mon Sep 17 00:00:00 2001
From: Hugo Dimpfelmoser
Date: Thu, 23 Nov 2023 09:37:57 +0100
Subject: [PATCH 1/3] 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 1c97e1b04db9f886b70940f3d9c8090f84682f1f Mon Sep 17 00:00:00 2001
From: Hugo Dimpfelmoser
Date: Tue, 28 Nov 2023 13:54:14 +0100
Subject: [PATCH 2/3] issue#627 listservices
---
okapi/core/OkapiServiceRunner.php | 7 ++
.../services/lists/add_caches/WebService.php | 67 ++++++++++
okapi/services/lists/add_caches/docs.xml | 23 ++++
okapi/services/lists/create/WebService.php | 93 ++++++++++++++
okapi/services/lists/create/docs.xml | 46 +++++++
okapi/services/lists/delete/WebService.php | 57 +++++++++
okapi/services/lists/delete/docs.xml | 20 +++
.../services/lists/get_caches/WebService.php | 61 +++++++++
okapi/services/lists/get_caches/docs.xml | 19 +++
okapi/services/lists/query/WebService.php | 70 +++++++++++
okapi/services/lists/query/docs.xml | 40 ++++++
.../lists/remove_caches/WebService.php | 74 +++++++++++
okapi/services/lists/remove_caches/docs.xml | 23 ++++
okapi/services/lists/update/WebService.php | 117 ++++++++++++++++++
okapi/services/lists/update/docs.xml | 50 ++++++++
15 files changed, 767 insertions(+)
create mode 100644 okapi/services/lists/add_caches/WebService.php
create mode 100644 okapi/services/lists/add_caches/docs.xml
create mode 100644 okapi/services/lists/create/WebService.php
create mode 100644 okapi/services/lists/create/docs.xml
create mode 100644 okapi/services/lists/delete/WebService.php
create mode 100644 okapi/services/lists/delete/docs.xml
create mode 100644 okapi/services/lists/get_caches/WebService.php
create mode 100644 okapi/services/lists/get_caches/docs.xml
create mode 100644 okapi/services/lists/query/WebService.php
create mode 100644 okapi/services/lists/query/docs.xml
create mode 100644 okapi/services/lists/remove_caches/WebService.php
create mode 100644 okapi/services/lists/remove_caches/docs.xml
create mode 100644 okapi/services/lists/update/WebService.php
create mode 100644 okapi/services/lists/update/docs.xml
diff --git a/okapi/core/OkapiServiceRunner.php b/okapi/core/OkapiServiceRunner.php
index 5433e5d9..d87eeedf 100644
--- a/okapi/core/OkapiServiceRunner.php
+++ b/okapi/core/OkapiServiceRunner.php
@@ -44,6 +44,13 @@ class OkapiServiceRunner
'services/caches/formatters/garmin',
'services/caches/formatters/ggz',
'services/caches/map/tile',
+ 'services/lists/add_caches',
+ 'services/lists/create',
+ 'services/lists/delete',
+ 'services/lists/get_caches',
+ 'services/lists/remove_caches',
+ 'services/lists/query',
+ 'services/lists/update',
'services/logs/capabilities',
'services/logs/delete',
'services/logs/edit',
diff --git a/okapi/services/lists/add_caches/WebService.php b/okapi/services/lists/add_caches/WebService.php
new file mode 100644
index 00000000..5c3894bf
--- /dev/null
+++ b/okapi/services/lists/add_caches/WebService.php
@@ -0,0 +1,67 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false
+ );
+
+ $user_id = $request->token->user_id;
+
+ $listId = $request->get_parameter('list_id');
+ $cacheCodes = $request->get_parameter('cache_codes');
+
+ if (empty($listId)) {
+ throw new InvalidParam('list_id', 'list_id is mandatory and must not be empty.');
+ }
+
+ if (empty($cacheCodes)) {
+ throw new InvalidParam('cache_codes', 'cache_codes is mandatory and must not be empty.');
+ }
+
+ $cacheCodesArray = array_unique(explode('|', $cacheCodes));
+
+ // Check the length
+ if (count($cacheCodesArray) > 500) {
+ throw new InvalidParam('cache_codes', 'The number of cache codes exceeds the limit of 500.');
+ }
+
+ // Escape cache codes and build the SQL query
+ $escapedCacheCodes = implode("','", array_map('\okapi\core\Db::escape_string', $cacheCodesArray));
+
+ // Fetch cache_ids from the caches table using INSERT IGNORE
+ $rs = Db::query("
+ INSERT IGNORE INTO cache_list_items (cache_list_id, cache_id)
+ SELECT '$listId', cache_id
+ FROM caches
+ WHERE wp_oc IN ('$escapedCacheCodes')
+ ");
+
+ $insertedCount = $rs->rowCount(); // Get the number of affected rows
+
+ $result = array(
+ 'success' => true,
+ 'added_count' => $insertedCount
+ );
+
+ return Okapi::formatted_response($request, $result);
+ }
+}
+
diff --git a/okapi/services/lists/add_caches/docs.xml b/okapi/services/lists/add_caches/docs.xml
new file mode 100644
index 00000000..5146b97c
--- /dev/null
+++ b/okapi/services/lists/add_caches/docs.xml
@@ -0,0 +1,23 @@
+
+ Add Caches To List
+ 627
+
+ This method is used to add geocaches to an existing list.
+
+
+ The id of a list. List IDs can be obtained by ::service/lists/query
+
+
+ A pipe separated list of cache_codes to be added to the list.
+ Up to 500 geoaches can be added to the list by one request to
+ this method
+
+
+
+ A dictionary of the following structure:
+
+ - success - true
+ - added_count - number of geocaches added to the list
+
+
+
diff --git a/okapi/services/lists/create/WebService.php b/okapi/services/lists/create/WebService.php
new file mode 100644
index 00000000..9e949d57
--- /dev/null
+++ b/okapi/services/lists/create/WebService.php
@@ -0,0 +1,93 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false
+ );
+
+ if (Settings::get('OC_BRANCH') == 'oc.de')
+ {
+ $user_id = $request->token->user_id;
+
+ $listName = $request->get_parameter('list_name');
+ $listDescription = $request->get_parameter('list_description');
+ $listStatus = $request->get_parameter('list_status');
+ $isWatched = $request->get_parameter('is_watched');
+ $listPassword = $request->get_parameter('list_password');
+
+ if (empty($listName)) {
+ throw new InvalidParam('list_name', 'list_name is mandatory and must not be empty.');
+ }
+
+ $insertFields = array(
+ 'name' => Db::escape_string($listName),
+ 'user_id' => Db::escape_string($user_id)
+ );
+
+ if (!empty($listDescription)) {
+ $insertFields['description'] = Db::escape_string($listDescription);
+ }
+
+ if ($listStatus !== null && $listStatus !== '') {
+ $listStatus = (int)$listStatus;
+ if (!in_array($listStatus, [0, 2, 3])) {
+ throw new InvalidParam('list_status', 'list_status must be a valid value (0, 2, 3).');
+ }
+ $insertFields['is_public'] = $listStatus;
+
+ // Handle list_password only if list_status is 0 (private)
+ if ($listStatus == 0) {
+ if (isset($listPassword) && $listPassword !== '') {
+ $insertFields['password'] = substr(Db::escape_string($listPassword), 0, 16);
+ }
+ }
+ }
+
+ $columns = implode(', ', array_keys($insertFields));
+ $values = "'" . implode("', '", $insertFields) . "'";
+
+ $insertQuery = "INSERT INTO cache_lists ($columns) VALUES ($values)";
+ Db::query($insertQuery);
+
+ $listId = Db::last_insert_id();
+
+ // Handle is_watched
+ if ($isWatched !== null && $isWatched !== '') {
+ $isWatched = (int)$isWatched;
+ if (!in_array($isWatched, [0, 1])) {
+ throw new InvalidParam('is_watched', 'is_watched must be a valid value (0, 1).');
+ }
+
+ // Insert a new record
+ Db::query("INSERT INTO cache_list_watches (cache_list_id, user_id, is_watched) VALUES (LAST_INSERT_ID(), '$user_id', $isWatched)");
+ }
+
+ $result = array(
+ 'success' => true,
+ 'message' => 'Cache list created successfully.',
+ 'list_id' => $listId
+ );
+ }
+ return Okapi::formatted_response($request, $result);
+ }
+}
+
diff --git a/okapi/services/lists/create/docs.xml b/okapi/services/lists/create/docs.xml
new file mode 100644
index 00000000..f8d9fd05
--- /dev/null
+++ b/okapi/services/lists/create/docs.xml
@@ -0,0 +1,46 @@
+
+ Create Cache List
+ 627
+
+ This method creates a list for adding geocaches to. Only the list is created,
+ no geocaches are added to it during the create. For adding and removing geocaches
+ to/from the list use specific methods within the ::services/lists namespace
+
+
+
+ A string, defining the human readable name of the list
+
+
+ A string via which a description of the list's purpose and
+ potentially content can be defined.
+
+
+ This parameter can take the following values:
+
+ - 0 - The list is private
+ - 2 - The list is public
+ - 3 - The list is public and is visible in cache listings
+
+
+
+
+ This parameter allows to add a password to a private list. The password
+ has no meaning if the list is public. The first 16 alphanumeric characters
+ are used as a password. No encryption is performed in the password, instead
+ it is stored in the database in plain text.
+
+
+ A boolean, either 0, 1, false, true. If set to 1 or true
+ a user wants to get a notification if the content of the list
+ changes.
+
+
+
+ A dictionary of the following structure:
+
+ - success - true
+ - message - Cache list created successfully
+ - list_id - The id of the list by which it can be referenced
+
+
+
diff --git a/okapi/services/lists/delete/WebService.php b/okapi/services/lists/delete/WebService.php
new file mode 100644
index 00000000..89dd1ea8
--- /dev/null
+++ b/okapi/services/lists/delete/WebService.php
@@ -0,0 +1,57 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false
+ );
+
+ if (Settings::get('OC_BRANCH') == 'oc.de')
+ {
+ $user_id = $request->token->user_id;
+
+ $listId = $request->get_parameter('list_id');
+
+ if (empty($listId) || !is_numeric($listId)) {
+ throw new InvalidParam('list_id', 'list_id is mandatory and must be numeric.');
+ }
+
+ // Check if the list exists
+ $countQuery = Db::query("SELECT COUNT(*) AS count FROM cache_lists WHERE id = '$listId' AND user_id = '$user_id'");
+ $listExists = Db::fetch_assoc($countQuery)['count'];
+ if ($listExists == 0) {
+ throw new InvalidParam('list_id', 'The specified list does not exist.');
+ }
+
+ // Proceed with the deletion process
+ Db::query("DELETE FROM cache_lists WHERE id = '$listId'");
+ Db::query("DELETE FROM cache_list_watches WHERE cache_list_id = '$listId'");
+ Db::query("DELETE FROM cache_list_items WHERE cache_list_id = '$listId'");
+
+ $result = array(
+ 'success' => true,
+ 'message' => 'Cache list deleted successfully.'
+ );
+ }
+ return Okapi::formatted_response($request, $result);
+ }
+}
+
diff --git a/okapi/services/lists/delete/docs.xml b/okapi/services/lists/delete/docs.xml
new file mode 100644
index 00000000..f6d218cc
--- /dev/null
+++ b/okapi/services/lists/delete/docs.xml
@@ -0,0 +1,20 @@
+
+ Delete Cache List
+ 627
+
+ This method is used to delete a geocache list. The geocache objects
+ that were on the list will not be touched in any way.
+
+
+ The id of the list to be removed. IDs can be obtained by
+ the service ::services/lists/query
+
+
+
+ A dictionary of the following structure:
+
+ - success - true
+ - message - cache list deleted successfully
+
+
+
diff --git a/okapi/services/lists/get_caches/WebService.php b/okapi/services/lists/get_caches/WebService.php
new file mode 100644
index 00000000..b5e6e751
--- /dev/null
+++ b/okapi/services/lists/get_caches/WebService.php
@@ -0,0 +1,61 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false
+ );
+
+ $user_id = $request->token->user_id;
+ $listId = $request->get_parameter('list_id');
+
+ if (empty($listId)) {
+ throw new InvalidParam('list_id', 'list_id is mandatory and must not be empty.');
+ }
+
+ // Fetch cache_ids associated with the specified list
+ $cacheIdsArray = Db::select_column("
+ SELECT cache_id
+ FROM cache_list_items
+ WHERE cache_list_id = '$listId'
+ ");
+
+ $cacheCount = count($cacheIdsArray);
+
+ // Fetch cache_codes based on cache_ids
+ $cacheCodesArray = array();
+
+ if (!empty($cacheIdsArray)) {
+ $cacheIds = implode(',', $cacheIdsArray);
+ $cacheCodesArray = Db::select_column(
+ "SELECT wp_oc FROM caches WHERE cache_id IN ($cacheIds)"
+ );
+ }
+
+ $result = array(
+ 'success' => true,
+ 'cache_codes' => implode('|', $cacheCodesArray),
+ 'cache_count' => $cacheCount
+ );
+
+ return Okapi::formatted_response($request, $result);
+ }
+}
+
diff --git a/okapi/services/lists/get_caches/docs.xml b/okapi/services/lists/get_caches/docs.xml
new file mode 100644
index 00000000..c6885d8a
--- /dev/null
+++ b/okapi/services/lists/get_caches/docs.xml
@@ -0,0 +1,19 @@
+
+ Get Caches On A List
+ 627
+
+ This method is used to get the geocache codes for the caches that are on the list.
+
+
+ The id of a list. List IDs can be obtained by ::service/lists/query
+
+
+
+ A dictionary of the following structure:
+
+ - success - true
+ - cache_codes - A pipe separated string of cache_codes that are on the list
+ - cache_count - the number of geocaches on the list
+
+
+
diff --git a/okapi/services/lists/query/WebService.php b/okapi/services/lists/query/WebService.php
new file mode 100644
index 00000000..f0f3e490
--- /dev/null
+++ b/okapi/services/lists/query/WebService.php
@@ -0,0 +1,70 @@
+ 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')
+ {
+ $user_id = $request->token->user_id;
+ $rs = Db::query("
+ SELECT
+ id,
+ name,
+ date_created,
+ last_modified,
+ last_added,
+ description,
+ is_public,
+ (
+ SELECT COUNT(*)
+ FROM cache_list_items
+ WHERE cache_list_id = cache_lists.id
+ ) AS caches_count,
+ (
+ SELECT COUNT(*)
+ FROM cache_list_watches
+ WHERE cache_list_id = cache_lists.id
+ ) AS watches_count
+ FROM cache_lists
+ WHERE user_id = '".Db::escape_string($user_id)."'
+ ");
+
+ $lists = [];
+ while ($list = Db::fetch_assoc($rs))
+ {
+ $lists[] = $list;
+ }
+
+ $result = json_encode($lists, JSON_PRETTY_PRINT);
+ }
+ return Okapi::formatted_response($request, $result);
+ }
+
+
+ // ------------------------------------------------------------------
+
+}
diff --git a/okapi/services/lists/query/docs.xml b/okapi/services/lists/query/docs.xml
new file mode 100644
index 00000000..8c3d0c71
--- /dev/null
+++ b/okapi/services/lists/query/docs.xml
@@ -0,0 +1,40 @@
+
+ Query Cache Lists
+ 627
+
+ This method is used to query the metadata of all user owned cache lists.
+ Such a query ist typically performed by a client application that wants to render
+ a list of geocache lists at the UI. The metadata of the list is necessary and sufficient
+ for this purpose. Please note, an id is also part of the returned meta data.
+ Using this id as a reference code, specific operations can be performed on selected lists
+ for instance adding geocaches to the list, removing them, updating the metadata of the
+ referenced list or deleting the referenced list entirely. Typically such entities as
+ geocaches, logs, or lists, are referenced by a referenceCode, for instance
+ for geocaches, there would be a cache_code in the namespace OCxxxxx where
+ cache_code would be interpreted as a referenceCode for geocaches. Unfortunately lists
+ do not yet have such a referenceCode. They can be referenced only by their internal id,
+ which is named id in the returned objects for this method. This id is used as the
+ list_id parameter in all subsequent methods dealing with lists.
+
+
+
+
+ An array of the following structure:
+
+[
+ {
+ "id": 13,
+ "name": "This is my list",
+ "date_created": "2023-11-28 09:29:34",
+ "last_modified": "2023-11-28 09:49:16",
+ "last_added": "2023-11-28 09:40:18",
+ "description": "Just a list for testing",
+ "is_public": 0,
+ "caches_count": 4,
+ "watches_count": 1
+ },
+ { .... }
+]
+
+
+
diff --git a/okapi/services/lists/remove_caches/WebService.php b/okapi/services/lists/remove_caches/WebService.php
new file mode 100644
index 00000000..1ff393c4
--- /dev/null
+++ b/okapi/services/lists/remove_caches/WebService.php
@@ -0,0 +1,74 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false
+ );
+
+ $user_id = $request->token->user_id;
+
+ $listId = $request->get_parameter('list_id');
+ $cacheCodes = $request->get_parameter('cache_codes');
+
+ if (empty($listId)) {
+ throw new InvalidParam('list_id', 'list_id is mandatory and must not be empty.');
+ }
+
+ if (empty($cacheCodes)) {
+ throw new InvalidParam('cache_codes', 'cache_codes is mandatory and must not be empty.');
+ }
+
+ $cacheCodesArray = array_unique(explode('|', $cacheCodes));
+
+ // Check the length
+ if (count($cacheCodesArray) > 500) {
+ throw new InvalidParam('cache_codes', 'The number of cache codes exceeds the limit of 500.');
+ }
+
+ // Escape cache codes and build the SQL query
+ $escapedCacheCodes = implode("','", array_map('\okapi\core\Db::escape_string', $cacheCodesArray));
+
+ // Delete cache_ids from the cache_list_items table
+ $rs = Db::query("
+ DELETE FROM cache_list_items
+ WHERE cache_list_id = '$listId'
+ AND cache_id IN (
+ SELECT cache_id
+ FROM caches
+ WHERE wp_oc IN ('$escapedCacheCodes')
+ )
+ ");
+
+ $removedCount = $rs->rowCount(); // Get the number of affected rows
+
+ $result = array(
+ 'success' => true,
+ 'removed_count' => $removedCount
+ );
+
+ return Okapi::formatted_response($request, $result);
+ }
+}
+
diff --git a/okapi/services/lists/remove_caches/docs.xml b/okapi/services/lists/remove_caches/docs.xml
new file mode 100644
index 00000000..bbb1de4c
--- /dev/null
+++ b/okapi/services/lists/remove_caches/docs.xml
@@ -0,0 +1,23 @@
+
+ Remove Caches From List
+ 627
+
+ This method removes geocaches from a list.
+
+
+ The id of a list. List IDs can be obtained by ::service/lists/query
+
+
+ A pipe separated list of cache_codes to be removed to the list.
+ Up to 500 geoaches can be removed from the list by one request to
+ this method
+
+
+
+ A dictionary of the following structure:
+
+ - success - true
+ - removed_count - number of geocaches removed from the list
+
+
+
diff --git a/okapi/services/lists/update/WebService.php b/okapi/services/lists/update/WebService.php
new file mode 100644
index 00000000..0330abcb
--- /dev/null
+++ b/okapi/services/lists/update/WebService.php
@@ -0,0 +1,117 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false
+ );
+
+ if (Settings::get('OC_BRANCH') == 'oc.de')
+ {
+ $user_id = $request->token->user_id;
+
+ $listId = $request->get_parameter('list_id');
+ $listName = $request->get_parameter('list_name');
+ $listDescription = $request->get_parameter('list_description');
+ $listStatus = $request->get_parameter('list_status');
+ $listWatch = $request->get_parameter('list_watch');
+ $listPassword = $request->get_parameter('list_password');
+
+ if (empty($listId) || !is_numeric($listId)) {
+ throw new InvalidParam('list_id', 'list_id is mandatory and must be numeric.');
+ }
+
+ if (empty($listName) && empty($listDescription) && ($listStatus === null || $listStatus === '') && ($listWatch === null || $listWatch === '') && ($listPassword === null || $listPassword === '')) {
+ throw new InvalidParam('list_name, list_description, list_status, list_watch, list_password', 'At least one optional parameter is required.');
+ }
+
+ $updateFields = array();
+
+ if (!empty($listName)) {
+ $updateFields['name'] = Db::escape_string($listName);
+ }
+
+ if (!empty($listDescription)) {
+ $updateFields['description'] = Db::escape_string($listDescription);
+ }
+
+ if ($listStatus !== null && $listStatus !== '') {
+ $listStatus = (int)$listStatus;
+ if (!in_array($listStatus, [0, 2, 3])) {
+ throw new InvalidParam('list_status', 'list_status must be a valid value (0, 2, 3).');
+ }
+ $updateFields['is_public'] = $listStatus;
+
+ // Handle list_password only if list_status is 0 (private)
+ if ($listStatus == 0) {
+ if (isset($listPassword) && $listPassword !== '') {
+ $updateFields['password'] = substr(Db::escape_string($listPassword), 0, 16);
+ } else {
+ $updateFields['password'] = null; // Remove the password
+ }
+ }
+ }
+
+ if ($listWatch !== null && $listWatch !== '') {
+ $listWatch = (int)$listWatch;
+ $currentWatchState = (int) Db::query("
+ SELECT COUNT(*)
+ FROM cache_list_watches
+ WHERE cache_list_id = '" . Db::escape_string($listId) . "'
+ AND user_id = '" . Db::escape_string($user_id) . "'
+ ")->fetchColumn();
+
+ if ($listWatch == 1 && $currentWatchState == 0) {
+ // Watched and not in cache_list_watches, insert
+ Db::query("
+ INSERT INTO cache_list_watches (cache_list_id, user_id)
+ VALUES ('" . Db::escape_string($listId) . "', '" . Db::escape_string($user_id) . "')
+ ");
+ } elseif ($listWatch == 0 && $currentWatchState > 0) {
+ // Unwatched and in cache_list_watches, delete
+ Db::query("
+ DELETE FROM cache_list_watches
+ WHERE cache_list_id = '" . Db::escape_string($listId) . "'
+ AND user_id = '" . Db::escape_string($user_id) . "'
+ ");
+ }
+ }
+
+ if (!empty($updateFields)) {
+ $updateQuery = "UPDATE cache_lists SET ";
+ $updateQuery .= implode(', ', array_map(function ($field, $value) {
+ return "$field = '$value'";
+ }, array_keys($updateFields), $updateFields));
+ $updateQuery .= " WHERE id = '" . Db::escape_string($listId) . "'";
+
+ Db::query($updateQuery);
+ }
+
+ $result = array(
+ 'success' => true,
+ 'message' => 'Cache list updated successfully.'
+ );
+ }
+ return Okapi::formatted_response($request, $result);
+ }
+}
+
diff --git a/okapi/services/lists/update/docs.xml b/okapi/services/lists/update/docs.xml
new file mode 100644
index 00000000..34c1e10e
--- /dev/null
+++ b/okapi/services/lists/update/docs.xml
@@ -0,0 +1,50 @@
+
+ Update Cache List
+ 627
+
+ This method is is used to update the meta data of an existing cache list.
+
+
+ The list_id uniquely references the
+ list for which the metadata should be updated. The list_id can be obtained
+ by ::services/lists/query
+
+
+ The new name of the list. The current name can be obtained by
+ ::services/lists/query
+
+
+ The new description of the list. The current description can be obtained by
+ ::services/lists/query
+
+
+ The new status of the list. The current status can be obtained by
+ ::services/lists/query
+ Status is defined as follows:
+
+ - 0 - indicates a private list
+ - 2 - indicates a public list
+ - 3 - indicates a public list, visible for all users in cache listings
+
+
+
+
+ The password for the list. Passwords only have a meaning if the list status is set to
+ private. The parameter is silently ignored otherwise. If the list is private, the first
+ 16 alphanumeric charactes are taken as a password. No encryption is performed on the
+ password, instead it is stored as plain text in the database.
+
+
+ A boolean, either 0, 1, false, true. If set to 1 or true
+ a user wants to get a notification if the content of the list
+ changes.
+
+
+
+ A dictionary of the following structure:
+
+ - success - true
+ - message - Cache list updated sucessfully
+
+
+
From b3ec196ac536e10b46d9075de666f1e8fdcbebd9 Mon Sep 17 00:00:00 2001
From: hxdimpf
Date: Thu, 12 Mar 2026 19:05:08 +0100
Subject: [PATCH 3/3] Fix list services: SQL injection, auth, bugs, style, docs
Security:
- Add Db::escape_string() to all SQL-interpolated list_id values
- Add ownership verification (user_id check) to add_caches,
get_caches, remove_caches, and update endpoints
- Add oc.de gate to add_caches, get_caches, remove_caches
Bug fixes:
- Fix double JSON encoding in query (pass array, not json_encode)
- Fix password truncation in create/update: truncate before escaping
to avoid corrupting escape sequences
- Fix deletion order in delete: remove child records (items, watches)
before parent (cache_lists) to avoid FK constraint issues
- Fix NULL password handling in update: generate 'password = NULL'
instead of 'password = ''' when clearing password
Style:
- Rename all camelCase variables to snake_case
- Remove unused imports (LogsCommon, OkapiServiceRunner, etc.)
- Restore save_user_coords in OkapiServiceRunner (kept per PR #628)
Documentation:
- Fix typo ::service/lists/query -> ::services/lists/query
- Fix "removed to the list" -> "removed from the list"
- Fix "sucessfully" -> "successfully"
---
.../services/lists/add_caches/WebService.php | 42 +++++----
okapi/services/lists/add_caches/docs.xml | 2 +-
okapi/services/lists/create/WebService.php | 61 ++++++------
okapi/services/lists/delete/WebService.php | 27 +++---
.../services/lists/get_caches/WebService.php | 44 +++++----
okapi/services/lists/query/WebService.php | 16 +---
.../lists/remove_caches/WebService.php | 68 ++++++++------
okapi/services/lists/remove_caches/docs.xml | 4 +-
okapi/services/lists/update/WebService.php | 94 ++++++++++---------
okapi/services/lists/update/docs.xml | 2 +-
10 files changed, 194 insertions(+), 166 deletions(-)
diff --git a/okapi/services/lists/add_caches/WebService.php b/okapi/services/lists/add_caches/WebService.php
index 5c3894bf..9e129bd4 100644
--- a/okapi/services/lists/add_caches/WebService.php
+++ b/okapi/services/lists/add_caches/WebService.php
@@ -3,10 +3,11 @@
namespace okapi\services\lists\add_caches;
use okapi\core\Db;
+use okapi\core\Exception\BadRequest;
+use okapi\core\Exception\InvalidParam;
use okapi\core\Okapi;
use okapi\core\Request\OkapiRequest;
use okapi\Settings;
-use okapi\core\Exception\InvalidParam;
class WebService
{
@@ -19,49 +20,58 @@ public static function options()
public static function call(OkapiRequest $request)
{
- $result = array(
- 'success' => false
- );
+ if (Settings::get('OC_BRANCH') != 'oc.de')
+ throw new BadRequest('This method is not supported in this OKAPI installation.');
$user_id = $request->token->user_id;
- $listId = $request->get_parameter('list_id');
- $cacheCodes = $request->get_parameter('cache_codes');
+ $list_id = $request->get_parameter('list_id');
+ $cache_codes = $request->get_parameter('cache_codes');
- if (empty($listId)) {
+ if (empty($list_id)) {
throw new InvalidParam('list_id', 'list_id is mandatory and must not be empty.');
}
- if (empty($cacheCodes)) {
+ if (empty($cache_codes)) {
throw new InvalidParam('cache_codes', 'cache_codes is mandatory and must not be empty.');
}
- $cacheCodesArray = array_unique(explode('|', $cacheCodes));
+ // Verify list ownership
+ $count = Db::select_value("
+ SELECT COUNT(*)
+ FROM cache_lists
+ WHERE id = '".Db::escape_string($list_id)."'
+ AND user_id = '".Db::escape_string($user_id)."'
+ ");
+ if ($count == 0) {
+ throw new InvalidParam('list_id', 'The specified list does not exist or does not belong to you.');
+ }
+
+ $cache_codes_array = array_unique(explode('|', $cache_codes));
// Check the length
- if (count($cacheCodesArray) > 500) {
+ if (count($cache_codes_array) > 500) {
throw new InvalidParam('cache_codes', 'The number of cache codes exceeds the limit of 500.');
}
// Escape cache codes and build the SQL query
- $escapedCacheCodes = implode("','", array_map('\okapi\core\Db::escape_string', $cacheCodesArray));
+ $escaped_cache_codes = implode("','", array_map('\okapi\core\Db::escape_string', $cache_codes_array));
// Fetch cache_ids from the caches table using INSERT IGNORE
$rs = Db::query("
INSERT IGNORE INTO cache_list_items (cache_list_id, cache_id)
- SELECT '$listId', cache_id
+ SELECT '".Db::escape_string($list_id)."', cache_id
FROM caches
- WHERE wp_oc IN ('$escapedCacheCodes')
+ WHERE wp_oc IN ('$escaped_cache_codes')
");
- $insertedCount = $rs->rowCount(); // Get the number of affected rows
+ $inserted_count = $rs->rowCount();
$result = array(
'success' => true,
- 'added_count' => $insertedCount
+ 'added_count' => $inserted_count
);
return Okapi::formatted_response($request, $result);
}
}
-
diff --git a/okapi/services/lists/add_caches/docs.xml b/okapi/services/lists/add_caches/docs.xml
index 5146b97c..8c01e123 100644
--- a/okapi/services/lists/add_caches/docs.xml
+++ b/okapi/services/lists/add_caches/docs.xml
@@ -5,7 +5,7 @@
This method is used to add geocaches to an existing list.
- The id of a list. List IDs can be obtained by ::service/lists/query
+ The id of a list. List IDs can be obtained by ::services/lists/query
A pipe separated list of cache_codes to be added to the list.
diff --git a/okapi/services/lists/create/WebService.php b/okapi/services/lists/create/WebService.php
index 9e949d57..ac7a3cf9 100644
--- a/okapi/services/lists/create/WebService.php
+++ b/okapi/services/lists/create/WebService.php
@@ -3,10 +3,10 @@
namespace okapi\services\lists\create;
use okapi\core\Db;
+use okapi\core\Exception\InvalidParam;
use okapi\core\Okapi;
use okapi\core\Request\OkapiRequest;
use okapi\Settings;
-use okapi\core\Exception\InvalidParam;
class WebService
{
@@ -28,66 +28,67 @@ public static function call(OkapiRequest $request)
{
$user_id = $request->token->user_id;
- $listName = $request->get_parameter('list_name');
- $listDescription = $request->get_parameter('list_description');
- $listStatus = $request->get_parameter('list_status');
- $isWatched = $request->get_parameter('is_watched');
- $listPassword = $request->get_parameter('list_password');
+ $list_name = $request->get_parameter('list_name');
+ $list_description = $request->get_parameter('list_description');
+ $list_status = $request->get_parameter('list_status');
+ $is_watched = $request->get_parameter('is_watched');
+ $list_password = $request->get_parameter('list_password');
- if (empty($listName)) {
+ if (empty($list_name)) {
throw new InvalidParam('list_name', 'list_name is mandatory and must not be empty.');
}
- $insertFields = array(
- 'name' => Db::escape_string($listName),
+ $insert_fields = array(
+ 'name' => Db::escape_string($list_name),
'user_id' => Db::escape_string($user_id)
);
- if (!empty($listDescription)) {
- $insertFields['description'] = Db::escape_string($listDescription);
+ if (!empty($list_description)) {
+ $insert_fields['description'] = Db::escape_string($list_description);
}
- if ($listStatus !== null && $listStatus !== '') {
- $listStatus = (int)$listStatus;
- if (!in_array($listStatus, [0, 2, 3])) {
+ if ($list_status !== null && $list_status !== '') {
+ $list_status = (int)$list_status;
+ if (!in_array($list_status, [0, 2, 3])) {
throw new InvalidParam('list_status', 'list_status must be a valid value (0, 2, 3).');
}
- $insertFields['is_public'] = $listStatus;
+ $insert_fields['is_public'] = $list_status;
// Handle list_password only if list_status is 0 (private)
- if ($listStatus == 0) {
- if (isset($listPassword) && $listPassword !== '') {
- $insertFields['password'] = substr(Db::escape_string($listPassword), 0, 16);
+ if ($list_status == 0) {
+ if (isset($list_password) && $list_password !== '') {
+ $insert_fields['password'] = Db::escape_string(substr($list_password, 0, 16));
}
}
}
- $columns = implode(', ', array_keys($insertFields));
- $values = "'" . implode("', '", $insertFields) . "'";
+ $columns = implode(', ', array_keys($insert_fields));
+ $values = "'" . implode("', '", $insert_fields) . "'";
- $insertQuery = "INSERT INTO cache_lists ($columns) VALUES ($values)";
- Db::query($insertQuery);
+ $insert_query = "INSERT INTO cache_lists ($columns) VALUES ($values)";
+ Db::query($insert_query);
- $listId = Db::last_insert_id();
+ $list_id = Db::last_insert_id();
// Handle is_watched
- if ($isWatched !== null && $isWatched !== '') {
- $isWatched = (int)$isWatched;
- if (!in_array($isWatched, [0, 1])) {
+ if ($is_watched !== null && $is_watched !== '') {
+ $is_watched = (int)$is_watched;
+ if (!in_array($is_watched, [0, 1])) {
throw new InvalidParam('is_watched', 'is_watched must be a valid value (0, 1).');
}
- // Insert a new record
- Db::query("INSERT INTO cache_list_watches (cache_list_id, user_id, is_watched) VALUES (LAST_INSERT_ID(), '$user_id', $isWatched)");
+ Db::query("
+ INSERT INTO cache_list_watches (cache_list_id, user_id, is_watched)
+ VALUES ('".Db::escape_string($list_id)."', '".Db::escape_string($user_id)."', '".Db::escape_string($is_watched)."')
+ ");
}
$result = array(
'success' => true,
'message' => 'Cache list created successfully.',
- 'list_id' => $listId
+ 'list_id' => $list_id
);
}
return Okapi::formatted_response($request, $result);
}
}
-
diff --git a/okapi/services/lists/delete/WebService.php b/okapi/services/lists/delete/WebService.php
index 89dd1ea8..64a7bdf1 100644
--- a/okapi/services/lists/delete/WebService.php
+++ b/okapi/services/lists/delete/WebService.php
@@ -3,10 +3,10 @@
namespace okapi\services\lists\delete;
use okapi\core\Db;
+use okapi\core\Exception\InvalidParam;
use okapi\core\Okapi;
use okapi\core\Request\OkapiRequest;
use okapi\Settings;
-use okapi\core\Exception\InvalidParam;
class WebService
{
@@ -28,23 +28,27 @@ public static function call(OkapiRequest $request)
{
$user_id = $request->token->user_id;
- $listId = $request->get_parameter('list_id');
+ $list_id = $request->get_parameter('list_id');
- if (empty($listId) || !is_numeric($listId)) {
+ if (empty($list_id) || !is_numeric($list_id)) {
throw new InvalidParam('list_id', 'list_id is mandatory and must be numeric.');
}
- // Check if the list exists
- $countQuery = Db::query("SELECT COUNT(*) AS count FROM cache_lists WHERE id = '$listId' AND user_id = '$user_id'");
- $listExists = Db::fetch_assoc($countQuery)['count'];
- if ($listExists == 0) {
+ // Check if the list exists and belongs to the user
+ $count = Db::select_value("
+ SELECT COUNT(*)
+ FROM cache_lists
+ WHERE id = '".Db::escape_string($list_id)."'
+ AND user_id = '".Db::escape_string($user_id)."'
+ ");
+ if ($count == 0) {
throw new InvalidParam('list_id', 'The specified list does not exist.');
}
- // Proceed with the deletion process
- Db::query("DELETE FROM cache_lists WHERE id = '$listId'");
- Db::query("DELETE FROM cache_list_watches WHERE cache_list_id = '$listId'");
- Db::query("DELETE FROM cache_list_items WHERE cache_list_id = '$listId'");
+ // Delete child records before parent to avoid FK constraint issues
+ Db::query("DELETE FROM cache_list_items WHERE cache_list_id = '".Db::escape_string($list_id)."'");
+ Db::query("DELETE FROM cache_list_watches WHERE cache_list_id = '".Db::escape_string($list_id)."'");
+ Db::query("DELETE FROM cache_lists WHERE id = '".Db::escape_string($list_id)."'");
$result = array(
'success' => true,
@@ -54,4 +58,3 @@ public static function call(OkapiRequest $request)
return Okapi::formatted_response($request, $result);
}
}
-
diff --git a/okapi/services/lists/get_caches/WebService.php b/okapi/services/lists/get_caches/WebService.php
index b5e6e751..858075ed 100644
--- a/okapi/services/lists/get_caches/WebService.php
+++ b/okapi/services/lists/get_caches/WebService.php
@@ -3,10 +3,11 @@
namespace okapi\services\lists\get_caches;
use okapi\core\Db;
+use okapi\core\Exception\BadRequest;
+use okapi\core\Exception\InvalidParam;
use okapi\core\Okapi;
use okapi\core\Request\OkapiRequest;
use okapi\Settings;
-use okapi\core\Exception\InvalidParam;
class WebService
{
@@ -19,43 +20,52 @@ public static function options()
public static function call(OkapiRequest $request)
{
- $result = array(
- 'success' => false
- );
+ if (Settings::get('OC_BRANCH') != 'oc.de')
+ throw new BadRequest('This method is not supported in this OKAPI installation.');
$user_id = $request->token->user_id;
- $listId = $request->get_parameter('list_id');
+ $list_id = $request->get_parameter('list_id');
- if (empty($listId)) {
+ if (empty($list_id)) {
throw new InvalidParam('list_id', 'list_id is mandatory and must not be empty.');
}
+ // Verify list ownership
+ $count = Db::select_value("
+ SELECT COUNT(*)
+ FROM cache_lists
+ WHERE id = '".Db::escape_string($list_id)."'
+ AND user_id = '".Db::escape_string($user_id)."'
+ ");
+ if ($count == 0) {
+ throw new InvalidParam('list_id', 'The specified list does not exist or does not belong to you.');
+ }
+
// Fetch cache_ids associated with the specified list
- $cacheIdsArray = Db::select_column("
+ $cache_ids_array = Db::select_column("
SELECT cache_id
FROM cache_list_items
- WHERE cache_list_id = '$listId'
+ WHERE cache_list_id = '".Db::escape_string($list_id)."'
");
- $cacheCount = count($cacheIdsArray);
+ $cache_count = count($cache_ids_array);
// Fetch cache_codes based on cache_ids
- $cacheCodesArray = array();
+ $cache_codes_array = array();
- if (!empty($cacheIdsArray)) {
- $cacheIds = implode(',', $cacheIdsArray);
- $cacheCodesArray = Db::select_column(
- "SELECT wp_oc FROM caches WHERE cache_id IN ($cacheIds)"
+ if (!empty($cache_ids_array)) {
+ $escaped_ids = implode(',', array_map('intval', $cache_ids_array));
+ $cache_codes_array = Db::select_column(
+ "SELECT wp_oc FROM caches WHERE cache_id IN ($escaped_ids)"
);
}
$result = array(
'success' => true,
- 'cache_codes' => implode('|', $cacheCodesArray),
- 'cache_count' => $cacheCount
+ 'cache_codes' => implode('|', $cache_codes_array),
+ 'cache_count' => $cache_count
);
return Okapi::formatted_response($request, $result);
}
}
-
diff --git a/okapi/services/lists/query/WebService.php b/okapi/services/lists/query/WebService.php
index f0f3e490..312200fb 100644
--- a/okapi/services/lists/query/WebService.php
+++ b/okapi/services/lists/query/WebService.php
@@ -2,14 +2,9 @@
namespace okapi\services\lists\query;
-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
@@ -24,7 +19,7 @@ public static function options()
public static function call(OkapiRequest $request)
{
$result = array(
- 'success' => false // if the installation doesn't support it
+ 'success' => false
);
if (Settings::get('OC_BRANCH') == 'oc.de')
@@ -59,12 +54,11 @@ public static function call(OkapiRequest $request)
$lists[] = $list;
}
- $result = json_encode($lists, JSON_PRETTY_PRINT);
+ $result = array(
+ 'success' => true,
+ 'lists' => $lists
+ );
}
return Okapi::formatted_response($request, $result);
}
-
-
- // ------------------------------------------------------------------
-
}
diff --git a/okapi/services/lists/remove_caches/WebService.php b/okapi/services/lists/remove_caches/WebService.php
index 1ff393c4..0f535da6 100644
--- a/okapi/services/lists/remove_caches/WebService.php
+++ b/okapi/services/lists/remove_caches/WebService.php
@@ -2,18 +2,15 @@
namespace okapi\services\lists\remove_caches;
-use okapi\core\Exception\InvalidParam;
-use okapi\core\Exception\ParamMissing;
use okapi\core\Db;
+use okapi\core\Exception\BadRequest;
+use okapi\core\Exception\InvalidParam;
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(
@@ -22,53 +19,62 @@ public static function options()
}
public static function call(OkapiRequest $request)
- {
- $result = array(
- 'success' => false
- );
-
+ {
+ if (Settings::get('OC_BRANCH') != 'oc.de')
+ throw new BadRequest('This method is not supported in this OKAPI installation.');
+
$user_id = $request->token->user_id;
- $listId = $request->get_parameter('list_id');
- $cacheCodes = $request->get_parameter('cache_codes');
-
- if (empty($listId)) {
+ $list_id = $request->get_parameter('list_id');
+ $cache_codes = $request->get_parameter('cache_codes');
+
+ if (empty($list_id)) {
throw new InvalidParam('list_id', 'list_id is mandatory and must not be empty.');
}
-
- if (empty($cacheCodes)) {
+
+ if (empty($cache_codes)) {
throw new InvalidParam('cache_codes', 'cache_codes is mandatory and must not be empty.');
}
-
- $cacheCodesArray = array_unique(explode('|', $cacheCodes));
-
+
+ // Verify list ownership
+ $count = Db::select_value("
+ SELECT COUNT(*)
+ FROM cache_lists
+ WHERE id = '".Db::escape_string($list_id)."'
+ AND user_id = '".Db::escape_string($user_id)."'
+ ");
+ if ($count == 0) {
+ throw new InvalidParam('list_id', 'The specified list does not exist or does not belong to you.');
+ }
+
+ $cache_codes_array = array_unique(explode('|', $cache_codes));
+
// Check the length
- if (count($cacheCodesArray) > 500) {
+ if (count($cache_codes_array) > 500) {
throw new InvalidParam('cache_codes', 'The number of cache codes exceeds the limit of 500.');
}
// Escape cache codes and build the SQL query
- $escapedCacheCodes = implode("','", array_map('\okapi\core\Db::escape_string', $cacheCodesArray));
-
+ $escaped_cache_codes = implode("','", array_map('\okapi\core\Db::escape_string', $cache_codes_array));
+
// Delete cache_ids from the cache_list_items table
$rs = Db::query("
DELETE FROM cache_list_items
- WHERE cache_list_id = '$listId'
+ WHERE cache_list_id = '".Db::escape_string($list_id)."'
AND cache_id IN (
SELECT cache_id
FROM caches
- WHERE wp_oc IN ('$escapedCacheCodes')
+ WHERE wp_oc IN ('$escaped_cache_codes')
)
");
-
- $removedCount = $rs->rowCount(); // Get the number of affected rows
-
- $result = array(
+
+ $removed_count = $rs->rowCount();
+
+ $result = array(
'success' => true,
- 'removed_count' => $removedCount
+ 'removed_count' => $removed_count
);
return Okapi::formatted_response($request, $result);
}
}
-
diff --git a/okapi/services/lists/remove_caches/docs.xml b/okapi/services/lists/remove_caches/docs.xml
index bbb1de4c..5e57e94d 100644
--- a/okapi/services/lists/remove_caches/docs.xml
+++ b/okapi/services/lists/remove_caches/docs.xml
@@ -5,10 +5,10 @@
This method removes geocaches from a list.
- The id of a list. List IDs can be obtained by ::service/lists/query
+ The id of a list. List IDs can be obtained by ::services/lists/query
- A pipe separated list of cache_codes to be removed to the list.
+ A pipe separated list of cache_codes to be removed from the list.
Up to 500 geoaches can be removed from the list by one request to
this method
diff --git a/okapi/services/lists/update/WebService.php b/okapi/services/lists/update/WebService.php
index 0330abcb..a26a7985 100644
--- a/okapi/services/lists/update/WebService.php
+++ b/okapi/services/lists/update/WebService.php
@@ -2,10 +2,9 @@
namespace okapi\services\lists\update;
-use okapi\core\Exception\InvalidParam;
use okapi\core\Db;
+use okapi\core\Exception\InvalidParam;
use okapi\core\Okapi;
-use okapi\core\OkapiServiceRunner;
use okapi\core\Request\OkapiRequest;
use okapi\Settings;
@@ -29,81 +28,87 @@ public static function call(OkapiRequest $request)
{
$user_id = $request->token->user_id;
- $listId = $request->get_parameter('list_id');
- $listName = $request->get_parameter('list_name');
- $listDescription = $request->get_parameter('list_description');
- $listStatus = $request->get_parameter('list_status');
- $listWatch = $request->get_parameter('list_watch');
- $listPassword = $request->get_parameter('list_password');
+ $list_id = $request->get_parameter('list_id');
+ $list_name = $request->get_parameter('list_name');
+ $list_description = $request->get_parameter('list_description');
+ $list_status = $request->get_parameter('list_status');
+ $list_watch = $request->get_parameter('list_watch');
+ $list_password = $request->get_parameter('list_password');
- if (empty($listId) || !is_numeric($listId)) {
+ if (empty($list_id) || !is_numeric($list_id)) {
throw new InvalidParam('list_id', 'list_id is mandatory and must be numeric.');
}
- if (empty($listName) && empty($listDescription) && ($listStatus === null || $listStatus === '') && ($listWatch === null || $listWatch === '') && ($listPassword === null || $listPassword === '')) {
+ // Verify list ownership
+ $count = Db::select_value("
+ SELECT COUNT(*)
+ FROM cache_lists
+ WHERE id = '".Db::escape_string($list_id)."'
+ AND user_id = '".Db::escape_string($user_id)."'
+ ");
+ if ($count == 0) {
+ throw new InvalidParam('list_id', 'The specified list does not exist or does not belong to you.');
+ }
+
+ if (empty($list_name) && empty($list_description) && ($list_status === null || $list_status === '') && ($list_watch === null || $list_watch === '') && ($list_password === null || $list_password === '')) {
throw new InvalidParam('list_name, list_description, list_status, list_watch, list_password', 'At least one optional parameter is required.');
}
- $updateFields = array();
+ $update_parts = array();
- if (!empty($listName)) {
- $updateFields['name'] = Db::escape_string($listName);
+ if (!empty($list_name)) {
+ $update_parts[] = "name = '".Db::escape_string($list_name)."'";
}
- if (!empty($listDescription)) {
- $updateFields['description'] = Db::escape_string($listDescription);
+ if (!empty($list_description)) {
+ $update_parts[] = "description = '".Db::escape_string($list_description)."'";
}
- if ($listStatus !== null && $listStatus !== '') {
- $listStatus = (int)$listStatus;
- if (!in_array($listStatus, [0, 2, 3])) {
+ if ($list_status !== null && $list_status !== '') {
+ $list_status = (int)$list_status;
+ if (!in_array($list_status, [0, 2, 3])) {
throw new InvalidParam('list_status', 'list_status must be a valid value (0, 2, 3).');
}
- $updateFields['is_public'] = $listStatus;
+ $update_parts[] = "is_public = '".Db::escape_string($list_status)."'";
// Handle list_password only if list_status is 0 (private)
- if ($listStatus == 0) {
- if (isset($listPassword) && $listPassword !== '') {
- $updateFields['password'] = substr(Db::escape_string($listPassword), 0, 16);
+ if ($list_status == 0) {
+ if (isset($list_password) && $list_password !== '') {
+ $update_parts[] = "password = '".Db::escape_string(substr($list_password, 0, 16))."'";
} else {
- $updateFields['password'] = null; // Remove the password
+ $update_parts[] = "password = NULL";
}
}
}
- if ($listWatch !== null && $listWatch !== '') {
- $listWatch = (int)$listWatch;
- $currentWatchState = (int) Db::query("
+ if ($list_watch !== null && $list_watch !== '') {
+ $list_watch = (int)$list_watch;
+ $current_watch_state = (int) Db::select_value("
SELECT COUNT(*)
FROM cache_list_watches
- WHERE cache_list_id = '" . Db::escape_string($listId) . "'
- AND user_id = '" . Db::escape_string($user_id) . "'
- ")->fetchColumn();
+ WHERE cache_list_id = '".Db::escape_string($list_id)."'
+ AND user_id = '".Db::escape_string($user_id)."'
+ ");
- if ($listWatch == 1 && $currentWatchState == 0) {
- // Watched and not in cache_list_watches, insert
+ if ($list_watch == 1 && $current_watch_state == 0) {
Db::query("
INSERT INTO cache_list_watches (cache_list_id, user_id)
- VALUES ('" . Db::escape_string($listId) . "', '" . Db::escape_string($user_id) . "')
+ VALUES ('".Db::escape_string($list_id)."', '".Db::escape_string($user_id)."')
");
- } elseif ($listWatch == 0 && $currentWatchState > 0) {
- // Unwatched and in cache_list_watches, delete
+ } elseif ($list_watch == 0 && $current_watch_state > 0) {
Db::query("
DELETE FROM cache_list_watches
- WHERE cache_list_id = '" . Db::escape_string($listId) . "'
- AND user_id = '" . Db::escape_string($user_id) . "'
+ WHERE cache_list_id = '".Db::escape_string($list_id)."'
+ AND user_id = '".Db::escape_string($user_id)."'
");
}
}
- if (!empty($updateFields)) {
- $updateQuery = "UPDATE cache_lists SET ";
- $updateQuery .= implode(', ', array_map(function ($field, $value) {
- return "$field = '$value'";
- }, array_keys($updateFields), $updateFields));
- $updateQuery .= " WHERE id = '" . Db::escape_string($listId) . "'";
-
- Db::query($updateQuery);
+ if (!empty($update_parts)) {
+ $update_query = "UPDATE cache_lists SET "
+ . implode(', ', $update_parts)
+ . " WHERE id = '".Db::escape_string($list_id)."'";
+ Db::query($update_query);
}
$result = array(
@@ -114,4 +119,3 @@ public static function call(OkapiRequest $request)
return Okapi::formatted_response($request, $result);
}
}
-
diff --git a/okapi/services/lists/update/docs.xml b/okapi/services/lists/update/docs.xml
index 34c1e10e..d7186cf6 100644
--- a/okapi/services/lists/update/docs.xml
+++ b/okapi/services/lists/update/docs.xml
@@ -44,7 +44,7 @@
A dictionary of the following structure:
- success - true
- - message - Cache list updated sucessfully
+ - message - Cache list updated successfully