diff --git a/okapi/core/OkapiServiceRunner.php b/okapi/core/OkapiServiceRunner.php index a1d0abba..250c0eac 100644 --- a/okapi/core/OkapiServiceRunner.php +++ b/okapi/core/OkapiServiceRunner.php @@ -43,6 +43,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: +

+

+
+ +

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: +

+

+
+ +

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
  • +
+
+