diff --git a/okapi/core/OkapiServiceRunner.php b/okapi/core/OkapiServiceRunner.php
index a1d0abba..d87eeedf 100644
--- a/okapi/core/OkapiServiceRunner.php
+++ b/okapi/core/OkapiServiceRunner.php
@@ -39,10 +39,18 @@ 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',
'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/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:
+
+
+
diff --git a/okapi/services/lists/add_caches/WebService.php b/okapi/services/lists/add_caches/WebService.php
new file mode 100644
index 00000000..9e129bd4
--- /dev/null
+++ b/okapi/services/lists/add_caches/WebService.php
@@ -0,0 +1,77 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ 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;
+
+ $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($cache_codes)) {
+ throw new InvalidParam('cache_codes', 'cache_codes 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.');
+ }
+
+ $cache_codes_array = array_unique(explode('|', $cache_codes));
+
+ // Check the length
+ 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
+ $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 '".Db::escape_string($list_id)."', cache_id
+ FROM caches
+ WHERE wp_oc IN ('$escaped_cache_codes')
+ ");
+
+ $inserted_count = $rs->rowCount();
+
+ $result = array(
+ 'success' => true,
+ '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
new file mode 100644
index 00000000..8c01e123
--- /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 ::services/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..ac7a3cf9
--- /dev/null
+++ b/okapi/services/lists/create/WebService.php
@@ -0,0 +1,94 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false
+ );
+
+ if (Settings::get('OC_BRANCH') == 'oc.de')
+ {
+ $user_id = $request->token->user_id;
+
+ $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($list_name)) {
+ throw new InvalidParam('list_name', 'list_name is mandatory and must not be empty.');
+ }
+
+ $insert_fields = array(
+ 'name' => Db::escape_string($list_name),
+ 'user_id' => Db::escape_string($user_id)
+ );
+
+ if (!empty($list_description)) {
+ $insert_fields['description'] = Db::escape_string($list_description);
+ }
+
+ 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).');
+ }
+ $insert_fields['is_public'] = $list_status;
+
+ // Handle list_password only if list_status is 0 (private)
+ 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($insert_fields));
+ $values = "'" . implode("', '", $insert_fields) . "'";
+
+ $insert_query = "INSERT INTO cache_lists ($columns) VALUES ($values)";
+ Db::query($insert_query);
+
+ $list_id = Db::last_insert_id();
+
+ // Handle is_watched
+ 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).');
+ }
+
+ 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' => $list_id
+ );
+ }
+ 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..64a7bdf1
--- /dev/null
+++ b/okapi/services/lists/delete/WebService.php
@@ -0,0 +1,60 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false
+ );
+
+ if (Settings::get('OC_BRANCH') == 'oc.de')
+ {
+ $user_id = $request->token->user_id;
+
+ $list_id = $request->get_parameter('list_id');
+
+ 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 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.');
+ }
+
+ // 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,
+ '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..858075ed
--- /dev/null
+++ b/okapi/services/lists/get_caches/WebService.php
@@ -0,0 +1,71 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ 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;
+ $list_id = $request->get_parameter('list_id');
+
+ 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
+ $cache_ids_array = Db::select_column("
+ SELECT cache_id
+ FROM cache_list_items
+ WHERE cache_list_id = '".Db::escape_string($list_id)."'
+ ");
+
+ $cache_count = count($cache_ids_array);
+
+ // Fetch cache_codes based on cache_ids
+ $cache_codes_array = array();
+
+ 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('|', $cache_codes_array),
+ 'cache_count' => $cache_count
+ );
+
+ 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..312200fb
--- /dev/null
+++ b/okapi/services/lists/query/WebService.php
@@ -0,0 +1,64 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false
+ );
+
+ 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 = array(
+ 'success' => true,
+ 'lists' => $lists
+ );
+ }
+ 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..0f535da6
--- /dev/null
+++ b/okapi/services/lists/remove_caches/WebService.php
@@ -0,0 +1,80 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ 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;
+
+ $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($cache_codes)) {
+ throw new InvalidParam('cache_codes', 'cache_codes 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.');
+ }
+
+ $cache_codes_array = array_unique(explode('|', $cache_codes));
+
+ // Check the length
+ 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
+ $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 = '".Db::escape_string($list_id)."'
+ AND cache_id IN (
+ SELECT cache_id
+ FROM caches
+ WHERE wp_oc IN ('$escaped_cache_codes')
+ )
+ ");
+
+ $removed_count = $rs->rowCount();
+
+ $result = array(
+ 'success' => true,
+ '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
new file mode 100644
index 00000000..5e57e94d
--- /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 ::services/lists/query
+
+
+ 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
+
+
+
+ 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..a26a7985
--- /dev/null
+++ b/okapi/services/lists/update/WebService.php
@@ -0,0 +1,121 @@
+ 3
+ );
+ }
+
+ public static function call(OkapiRequest $request)
+ {
+ $result = array(
+ 'success' => false
+ );
+
+ if (Settings::get('OC_BRANCH') == 'oc.de')
+ {
+ $user_id = $request->token->user_id;
+
+ $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($list_id) || !is_numeric($list_id)) {
+ throw new InvalidParam('list_id', 'list_id is mandatory and must be numeric.');
+ }
+
+ // 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.');
+ }
+
+ $update_parts = array();
+
+ if (!empty($list_name)) {
+ $update_parts[] = "name = '".Db::escape_string($list_name)."'";
+ }
+
+ if (!empty($list_description)) {
+ $update_parts[] = "description = '".Db::escape_string($list_description)."'";
+ }
+
+ 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).');
+ }
+ $update_parts[] = "is_public = '".Db::escape_string($list_status)."'";
+
+ // Handle list_password only if list_status is 0 (private)
+ if ($list_status == 0) {
+ if (isset($list_password) && $list_password !== '') {
+ $update_parts[] = "password = '".Db::escape_string(substr($list_password, 0, 16))."'";
+ } else {
+ $update_parts[] = "password = NULL";
+ }
+ }
+ }
+
+ 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($list_id)."'
+ AND user_id = '".Db::escape_string($user_id)."'
+ ");
+
+ if ($list_watch == 1 && $current_watch_state == 0) {
+ Db::query("
+ INSERT INTO cache_list_watches (cache_list_id, user_id)
+ VALUES ('".Db::escape_string($list_id)."', '".Db::escape_string($user_id)."')
+ ");
+ } elseif ($list_watch == 0 && $current_watch_state > 0) {
+ Db::query("
+ DELETE FROM cache_list_watches
+ WHERE cache_list_id = '".Db::escape_string($list_id)."'
+ AND user_id = '".Db::escape_string($user_id)."'
+ ");
+ }
+ }
+
+ 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(
+ '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..d7186cf6
--- /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 successfully
+
+
+