예제 #1
0
 public static function call(OkapiRequest $request)
 {
     $issue_id = $request->get_parameter('issue_id');
     if (!$issue_id) {
         throw new ParamMissing('issue_id');
     }
     if (!preg_match("/^[0-9]+\$/", $issue_id) || strlen($issue_id) > 6) {
         throw new InvalidParam('issue_id');
     }
     $cache_key = "apiref/issue#" . $issue_id;
     $result = Cache::get($cache_key);
     if ($result == null) {
         # Download the number of comments from GitHub Issue Tracker.
         try {
             $extra_headers = array();
             $extra_headers[] = "Accept: application/vnd.github.v3.html+json";
             $extra_headers[] = "User-Agent: https://github.com/opencaching/okapi/";
             if (Settings::get('GITHUB_ACCESS_TOKEN')) {
                 $extra_headers[] = "Authorization: token " . Settings::get('GITHUB_ACCESS_TOKEN');
             }
             $opts = array('http' => array('method' => "GET", 'timeout' => 2.0, 'header' => implode("\r\n", $extra_headers)));
             $context = stream_context_create($opts);
             $json = file_get_contents("https://api.github.com/repos/opencaching/okapi/issues/{$issue_id}", false, $context);
         } catch (ErrorException $e) {
             throw new BadRequest("Sorry, we could not retrieve issue stats from the GitHub site. " . "This is probably due to a temporary connection problem. Try again later or contact " . "us if this seems permanent.");
         }
         $doc = json_decode($json, true);
         $result = array('id' => $issue_id + 0, 'last_updated' => $doc['updated_at'], 'title' => $doc['title'], 'url' => $doc['html_url'], 'comment_count' => $doc['comments']);
         # On one hand, we want newly added comments to show up quickly.
         # On the other, we don't want OKAPI to spam GitHub with queries.
         # So it's difficult to choose the best timeout for this.
         Cache::set($cache_key, $result, 3600);
     }
     return Okapi::formatted_response($request, $result);
 }
예제 #2
0
 public static function call(OkapiRequest $request)
 {
     require_once 'replicate_common.inc.php';
     $data = Cache::get("last_fulldump");
     if ($data == null) {
         throw new BadRequest("No fulldump found. Try again later. If this doesn't help " . "contact site administrator and/or OKAPI developers.");
     }
     # Check consumer's quota
     $please = $request->get_parameter('pleeaase');
     if ($please != 'true') {
         $not_good = 3 < self::count_calls($request->consumer->key, 30);
         if ($not_good) {
             throw new BadRequest("Consumer's monthly quota exceeded. Try later or call with '&pleeaase=true'.");
         }
     } else {
         $not_good = 5 < self::count_calls($request->consumer->key, 1);
         if ($not_good) {
             throw new BadRequest("No more please. Seriously, dude...");
         }
     }
     $response = new OkapiHttpResponse();
     $response->content_type = $data['meta']['content_type'];
     $response->content_disposition = 'attachment; filename="' . $data['meta']['public_filename'] . '"';
     $response->stream_length = $data['meta']['compressed_size'];
     $response->body = fopen($data['meta']['filepath'], "rb");
     $response->allow_gzip = false;
     return $response;
 }
예제 #3
0
 public static function call(OkapiRequest $request)
 {
     # User is already verified (via OAuth), but we need to verify the
     # cache code (check if it exists). We will simply call a geocache method
     # on it - this will also throw a proper exception if it doesn't exist.
     $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')));
     # watched
     if ($tmp = $request->get_parameter('watched')) {
         if (!in_array($tmp, array('true', 'false', 'unchanged'))) {
             throw new InvalidParam('watched', $tmp);
         }
         if ($tmp == 'true') {
             Db::execute("\n                    insert ignore into cache_watches (cache_id, user_id)\n                    values (\n                        '" . Db::escape_string($geocache['internal_id']) . "',\n                        '" . Db::escape_string($request->token->user_id) . "'\n                    );\n                ");
         } elseif ($tmp == 'false') {
             Db::execute("\n                    delete from cache_watches\n                    where\n                        cache_id = '" . Db::escape_string($geocache['internal_id']) . "'\n                        and user_id = '" . Db::escape_string($request->token->user_id) . "';\n                ");
         }
     }
     # ignored
     if ($tmp = $request->get_parameter('ignored')) {
         if (!in_array($tmp, array('true', 'false', 'unchanged'))) {
             throw new InvalidParam('ignored', $tmp);
         }
         if ($tmp == 'true') {
             Db::execute("\n                    insert ignore into cache_ignore (cache_id, user_id)\n                    values (\n                        '" . Db::escape_string($geocache['internal_id']) . "',\n                        '" . Db::escape_string($request->token->user_id) . "'\n                    );\n                ");
         } elseif ($tmp == 'false') {
             Db::execute("\n                    delete from cache_ignore\n                    where\n                        cache_id = '" . Db::escape_string($geocache['internal_id']) . "'\n                        and user_id = '" . Db::escape_string($request->token->user_id) . "'\n                ");
         }
     }
     $result = array('success' => true);
     return Okapi::formatted_response($request, $result);
 }
예제 #4
0
 public static function call(OkapiRequest $request)
 {
     $cache_code = $request->get_parameter('cache_code');
     if (!$cache_code) {
         throw new ParamMissing('cache_code');
     }
     if (strpos($cache_code, "|") !== false) {
         throw new InvalidParam('cache_code');
     }
     $langpref = $request->get_parameter('langpref');
     if (!$langpref) {
         $langpref = "en";
     }
     $langpref .= "|" . Settings::get('SITELANG');
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         $fields = "code|name|location|type|status";
     }
     $log_fields = $request->get_parameter('log_fields');
     if (!$log_fields) {
         $log_fields = "uuid|date|user|type|comment";
     }
     $lpc = $request->get_parameter('lpc');
     if (!$lpc) {
         $lpc = 10;
     }
     $attribution_append = $request->get_parameter('attribution_append');
     if (!$attribution_append) {
         $attribution_append = 'full';
     }
     $params = array('cache_codes' => $cache_code, 'langpref' => $langpref, 'fields' => $fields, 'attribution_append' => $attribution_append, 'lpc' => $lpc, 'log_fields' => $log_fields);
     $my_location = $request->get_parameter('my_location');
     if ($my_location) {
         $params['my_location'] = $my_location;
     }
     $user_uuid = $request->get_parameter('user_uuid');
     if ($user_uuid) {
         $params['user_uuid'] = $user_uuid;
     }
     # There's no need to validate the fields/lpc parameters as the 'geocaches'
     # method does this (it will raise a proper exception on invalid values).
     $results = OkapiServiceRunner::call('services/caches/geocaches', new OkapiInternalRequest($request->consumer, $request->token, $params));
     $result = $results[$cache_code];
     if ($result === null) {
         # Two errors messages (for OCDE). Makeshift solution for issue #350.
         $exists = Db::select_value("\n                select 1\n                from caches\n                where wp_oc='" . Db::escape_string($cache_code) . "'\n            ");
         if ($exists) {
             throw new InvalidParam('cache_code', "This cache is not accessible via OKAPI.");
         } else {
             throw new InvalidParam('cache_code', "This cache does not exist.");
         }
     }
     return Okapi::formatted_response($request, $result);
 }
예제 #5
0
 public static function call(OkapiRequest $request)
 {
     $callback = $request->get_parameter('oauth_callback');
     if (!$callback) {
         # We require the 1.0a flow (throw an error when there is no oauth_callback).
         throw new ParamMissing("oauth_callback");
     }
     $new_token = Okapi::$data_store->new_request_token($request->consumer, $callback);
     $response = new OkapiHttpResponse();
     $response->content_type = "text/plain; charset=utf-8";
     $response->body = $new_token . "&oauth_callback_confirmed=true";
     return $response;
 }
예제 #6
0
 public static function call(OkapiRequest $request)
 {
     $verifier = $request->get_parameter('oauth_verifier');
     if (!$verifier) {
         # We require the 1.0a flow (throw an error when there is no oauth_verifier).
         throw new ParamMissing("oauth_verifier");
     }
     $new_token = Okapi::$data_store->new_access_token($request->token, $request->consumer, $verifier);
     $response = new OkapiHttpResponse();
     $response->content_type = "text/plain; charset=utf-8";
     $response->body = $new_token;
     return $response;
 }
예제 #7
0
파일: issue.php 프로젝트: 4Vs/oc-server3
 public static function call(OkapiRequest $request)
 {
     $issue_id = $request->get_parameter('issue_id');
     if (!$issue_id) {
         throw new ParamMissing('issue_id');
     }
     if (!preg_match("/^[0-9]+\$/", $issue_id) || strlen($issue_id) > 6) {
         throw new InvalidParam('issue_id');
     }
     # In October 2013, Google Code feed at:
     # http://code.google.com/feeds/issues/p/opencaching-api/issues/$issue_id/comments/full
     # stopped working. We are forced to respond with a simple placeholder.
     $result = array('id' => $issue_id + 0, 'last_updated' => null, 'title' => null, 'url' => "https://code.google.com/p/opencaching-api/issues/detail?id=" . $issue_id, 'comment_count' => null);
     return Okapi::formatted_response($request, $result);
 }
예제 #8
0
 public static function call(OkapiRequest $request)
 {
     $username = $request->get_parameter('username');
     if (!$username) {
         throw new ParamMissing('username');
     }
     $fields = $request->get_parameter('fields');
     # There's no need to validate the fields parameter.
     $results = OkapiServiceRunner::call('services/users/by_usernames', new OkapiInternalRequest($request->consumer, $request->token, array('usernames' => $username, 'fields' => $fields)));
     $result = $results[$username];
     if ($result == null) {
         throw new InvalidParam('username', "There is no user by this username.");
     }
     return Okapi::formatted_response($request, $result);
 }
예제 #9
0
 public static function call(OkapiRequest $request)
 {
     $cache_code = $request->get_parameter('cache_code');
     if (!$cache_code) {
         throw new ParamMissing('cache_code');
     }
     if (strpos($cache_code, "|") !== false) {
         throw new InvalidParam('cache_code');
     }
     $langpref = $request->get_parameter('langpref');
     if (!$langpref) {
         $langpref = "en|" . Settings::get('SITELANG');
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         $fields = "code|name|location|type|status";
     }
     $log_fields = $request->get_parameter('log_fields');
     if (!$log_fields) {
         $log_fields = "uuid|date|user|type|comment";
     }
     $lpc = $request->get_parameter('lpc');
     if (!$lpc) {
         $lpc = 10;
     }
     $attribution_append = $request->get_parameter('attribution_append');
     if (!$attribution_append) {
         $attribution_append = 'full';
     }
     $params = array('cache_codes' => $cache_code, 'langpref' => $langpref, 'fields' => $fields, 'attribution_append' => $attribution_append, 'lpc' => $lpc, 'log_fields' => $log_fields);
     $my_location = $request->get_parameter('my_location');
     if ($my_location) {
         $params['my_location'] = $my_location;
     }
     $user_uuid = $request->get_parameter('user_uuid');
     if ($user_uuid) {
         $params['user_uuid'] = $user_uuid;
     }
     # There's no need to validate the fields/lpc parameters as the 'geocaches'
     # method does this (it will raise a proper exception on invalid values).
     $results = OkapiServiceRunner::call('services/caches/geocaches', new OkapiInternalRequest($request->consumer, $request->token, $params));
     $result = $results[$cache_code];
     if ($result === null) {
         throw new InvalidParam('cache_code', "This cache does not exist.");
     }
     return Okapi::formatted_response($request, $result);
 }
예제 #10
0
 public static function call(OkapiRequest $request)
 {
     # Get current notes, and 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' => 'my_notes|internal_id')));
     $current_value = $geocache['my_notes'];
     if ($current_value == null) {
         $current_value = "";
     }
     $cache_id = $geocache['internal_id'];
     # old_value
     $old_value = $request->get_parameter('old_value');
     if ($old_value === null) {
         $old_value = '';
     }
     # new_value (force "no HTML" policy).
     $new_value = $request->get_parameter('new_value');
     if ($new_value === null) {
         throw new ParamMissing('new_value');
     }
     # Force "no HTML" policy.
     $new_value = strip_tags($new_value);
     # Placeholders for returned values.
     $ret_saved_value = null;
     $ret_replaced = false;
     if (trim($current_value) == "" || self::str_equals($old_value, $current_value)) {
         /* REPLACE mode */
         $ret_replaced = true;
         if (trim($new_value) == "") {
             /* empty new value means delete */
             self::remove_notes($cache_id, $request->token->user_id);
             $ret_saved_value = null;
         } else {
             self::update_notes($cache_id, $request->token->user_id, $new_value);
             $ret_saved_value = $new_value;
         }
     } else {
         /* APPEND mode */
         $ret_saved_value = trim($current_value) . "\n\n" . trim($new_value);
         self::update_notes($cache_id, $request->token->user_id, $ret_saved_value);
     }
     $result = array('saved_value' => $ret_saved_value, 'replaced' => $ret_replaced);
     return Okapi::formatted_response($request, $result);
 }
예제 #11
0
 public static function call(OkapiRequest $request)
 {
     $log_uuid = $request->get_parameter('log_uuid');
     if (!$log_uuid) {
         throw new ParamMissing('log_uuid');
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         $fields = "date|user|type|comment";
     }
     $results = OkapiServiceRunner::call('services/logs/entries', new OkapiInternalRequest($request->consumer, $request->token, array('log_uuids' => $log_uuid, 'fields' => $fields)));
     $result = $results[$log_uuid];
     if ($result == null) {
         throw new InvalidParam('log_uuid', "This log entry does not exist.");
     }
     return Okapi::formatted_response($request, $result);
 }
예제 #12
0
 public static function call(OkapiRequest $request)
 {
     $token_key = $request->get_parameter('oauth_token');
     if (!$token_key) {
         throw new ParamMissing("oauth_token");
     }
     $langpref = $request->get_parameter('langpref');
     $interactivity = $request->get_parameter('interactivity');
     if (!$interactivity) {
         $interactivity = 'minimal';
     }
     if (!in_array($interactivity, array('minimal', 'confirm_user'))) {
         throw new InvalidParam('interactivity', $interactivity);
     }
     # Redirect to the "apps" folder. This is done there (not here)
     # because: 1) we don't want any cookie or session-handling
     # done in the "services" folder. 2) "services" don't display
     # any interactive webpages, they just return the result.
     return new OkapiRedirectResponse(Settings::get('SITE_URL') . "okapi/apps/authorize" . "?oauth_token=" . $token_key . ($langpref != null ? "&langpref=" . $langpref : "") . "&interactivity=" . $interactivity);
 }
예제 #13
0
 public static function call(OkapiRequest $request)
 {
     require_once 'replicate_common.inc.php';
     $since = $request->get_parameter('since');
     if ($since === null) {
         throw new ParamMissing('since');
     }
     if ((int) $since != $since) {
         throw new InvalidParam('since');
     }
     # Let's check the $since parameter.
     if (!ReplicateCommon::check_since_param($since)) {
         throw new BadRequest("The 'since' parameter is too old. You must update your database more frequently.");
     }
     # Select a best chunk for the given $since, get the chunk from the database (or cache).
     list($from, $to) = ReplicateCommon::select_best_chunk($since);
     $clog_entries = ReplicateCommon::get_chunk($from, $to);
     $result = array('changelog' => &$clog_entries, 'revision' => $to + 0, 'more' => $to < ReplicateCommon::get_revision());
     return Okapi::formatted_response($request, $result);
 }
예제 #14
0
 public static function call(OkapiRequest $request)
 {
     $user_uuid = $request->get_parameter('user_uuid');
     if (!$user_uuid) {
         if ($request->token) {
             $tmp = OkapiServiceRunner::call('services/users/by_internal_id', new OkapiInternalRequest($request->consumer, null, array('internal_id' => $request->token->user_id, 'fields' => 'uuid')));
             $user_uuid = $tmp['uuid'];
         } else {
             throw new BadRequest("You must either: 1. supply the user_uuid argument, or " . "2. sign your request with an Access Token.");
         }
     }
     $fields = $request->get_parameter('fields');
     # There's no need to validate the fields parameter as the 'users'
     # method does this (it will raise a proper exception on invalid values).
     $results = OkapiServiceRunner::call('services/users/users', new OkapiInternalRequest($request->consumer, $request->token, array('user_uuids' => $user_uuid, 'fields' => $fields)));
     $result = $results[$user_uuid];
     if ($result == null) {
         throw new InvalidParam('user_uuid', "There is no user by this ID.");
     }
     return Okapi::formatted_response($request, $result);
 }
예제 #15
0
 public static function call(OkapiRequest $request)
 {
     $usernames = $request->get_parameter('usernames');
     if (!$usernames) {
         throw new ParamMissing('usernames');
     }
     $usernames = explode("|", $usernames);
     if (count($usernames) > 500) {
         throw new InvalidParam('usernames', "Maximum allowed number of referenced users " . "is 500. You provided " . count($usernames) . " usernames.");
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         throw new ParamMissing('fields');
     }
     # There's no need to validate the fields parameter as the 'users'
     # method does this (it will raise a proper exception on invalid values).
     $rs = Db::query("\n            select username, uuid\n            from user\n            where username collate " . Settings::get('DB_CHARSET') . "_general_ci in ('" . implode("','", array_map('\\okapi\\Db::escape_string', $usernames)) . "')\n        ");
     $lower_username2useruuid = array();
     while ($row = Db::fetch_assoc($rs)) {
         $lower_username2useruuid[mb_strtolower($row['username'], 'utf-8')] = $row['uuid'];
     }
     Db::free_result($rs);
     # Retrieve data for the found user_uuids.
     if (count($lower_username2useruuid) > 0) {
         $id_results = OkapiServiceRunner::call('services/users/users', new OkapiInternalRequest($request->consumer, $request->token, array('user_uuids' => implode("|", array_values($lower_username2useruuid)), 'fields' => $fields)));
     } else {
         $id_results = array();
     }
     # Map user_uuids back to usernames. Also check which usernames were not found
     # and mark them with null.
     $results = array();
     foreach ($usernames as $username) {
         if (!isset($lower_username2useruuid[mb_strtolower($username, 'utf-8')])) {
             $results[$username] = null;
         } else {
             $results[$username] = $id_results[$lower_username2useruuid[mb_strtolower($username, 'utf-8')]];
         }
     }
     return Okapi::formatted_response($request, $results);
 }
예제 #16
0
 public static function call(OkapiRequest $request)
 {
     # Read the parameters.
     $acode = $request->get_parameter('acode');
     if ($acode === null) {
         throw new ParamMissing('acode');
     }
     if (strstr($acode, '|')) {
         throw new InvalidParam('acode', "Only a single A-code must be supplied.");
     }
     $langpref = $request->get_parameter('langpref');
     if (!$langpref) {
         $langpref = "en";
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         $fields = "name";
     }
     $forward_compatible = $request->get_parameter('forward_compatible');
     if (!$forward_compatible) {
         $forward_compatible = "true";
     }
     # Pass them all to the attributes method.
     $params = array('acodes' => $acode, 'langpref' => $langpref, 'fields' => $fields, 'forward_compatible' => $forward_compatible);
     $results = OkapiServiceRunner::call('services/attrs/attributes', new OkapiInternalRequest($request->consumer, $request->token, $params));
     $result = $results[$acode];
     if ($result === null) {
         /* Note, this can happen only when $forward_compatible is false. */
         throw new InvalidParam('acode', "Unknown A-code.");
     }
     return Okapi::formatted_response($request, $result);
 }
예제 #17
0
 public static function call(OkapiRequest $request)
 {
     $user_uuid = $request->get_parameter('user_uuid');
     if (!$user_uuid) {
         throw new ParamMissing('user_uuid');
     }
     $limit = $request->get_parameter('limit');
     if (!$limit) {
         $limit = "20";
     }
     if (!is_numeric($limit)) {
         throw new InvalidParam('limit', "'{$limit}'");
     }
     $limit = intval($limit);
     if ($limit < 1 || $limit > 1000) {
         throw new InvalidParam('limit', "Has to be in range 1..1000.");
     }
     $offset = $request->get_parameter('offset');
     if (!$offset) {
         $offset = "0";
     }
     if (!is_numeric($offset)) {
         throw new InvalidParam('offset', "'{$offset}'");
     }
     $offset = intval($offset);
     if ($offset < 0) {
         throw new InvalidParam('offset', "'{$offset}'");
     }
     # Check if user exists and retrieve user's ID (this will throw
     # a proper exception on invalid UUID).
     $user = OkapiServiceRunner::call('services/users/user', new OkapiInternalRequest($request->consumer, null, array('user_uuid' => $user_uuid, 'fields' => 'internal_id')));
     # User exists. Retrieving logs.
     $rs = Db::query("\n            select cl.id, cl.uuid, cl.type, unix_timestamp(cl.date) as date, cl.text,\n                c.wp_oc as cache_code\n            from cache_logs cl, caches c\n            where\n                cl.user_id = '" . Db::escape_string($user['internal_id']) . "'\n                and " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "cl.deleted = 0" : "true") . "\n                and c.status in (1,2,3)\n                and cl.cache_id = c.cache_id\n            order by cl.date desc\n            limit {$offset}, {$limit}\n        ");
     $results = array();
     while ($row = Db::fetch_assoc($rs)) {
         $results[] = array('uuid' => $row['uuid'], 'date' => date('c', $row['date']), 'cache_code' => $row['cache_code'], 'type' => Okapi::logtypeid2name($row['type']), 'comment' => $row['text']);
     }
     return Okapi::formatted_response($request, $results);
 }
예제 #18
0
 public static function call(OkapiRequest $request)
 {
     # "Cache control" parameters.
     $tmp = $request->get_parameter('min_store');
     if ($tmp === null) {
         $tmp = "300";
     }
     $min_store = intval($tmp);
     if ("{$min_store}" !== $tmp || $min_store < 0 || $min_store > 64800) {
         throw new InvalidParam('min_store', "Has to be in the 0..64800 range.");
     }
     $tmp = $request->get_parameter('ref_max_age');
     if ($tmp === null) {
         $tmp = "300";
     }
     if ($tmp == "nolimit") {
         $tmp = "9999999";
     }
     $ref_max_age = intval($tmp);
     if ("{$ref_max_age}" !== $tmp || $ref_max_age < 300) {
         throw new InvalidParam('ref_max_age', "Has to be >=300.");
     }
     # Search params.
     $search_assistant = new SearchAssistant($request);
     $search_assistant->prepare_common_search_params();
     $search_params = $search_assistant->get_search_params();
     $tables = array_merge(array('caches'), $search_params['extra_tables']);
     $where_conds = array_merge(array('caches.wp_oc is not null'), $search_params['where_conds']);
     if (isset($search_params['extra_joins']) && is_array($search_params['extra_joins'])) {
         $joins = $search_params['extra_joins'];
     } else {
         $joins = array();
     }
     unset($search_params);
     # Generate, or retrieve an existing set, and return the result.
     # All user-supplied data in $tables and $where_conds MUST be escaped!
     $result = self::get_set($tables, $joins, $where_conds, $min_store, $ref_max_age);
     return Okapi::formatted_response($request, $result);
 }
 public static function call(OkapiRequest $request)
 {
     # Read the parameters.
     $langpref = $request->get_parameter('langpref');
     if (!$langpref) {
         $langpref = "en";
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         $fields = "name";
     }
     $only_locally_used = $request->get_parameter('only_locally_used');
     if (!$only_locally_used) {
         $only_locally_used = "false";
     }
     $only_locally_used = $only_locally_used == "true";
     # Get the list of attributes and filter the A-codes based on the
     # parameters.
     require_once 'attr_helper.inc.php';
     $attrdict = AttrHelper::get_attrdict();
     $acodes = array();
     foreach ($attrdict as $acode => &$attr_ref) {
         if ($only_locally_used && $attr_ref['internal_id'] === null) {
             /* Skip. */
             continue;
         }
         $acodes[] = $acode;
     }
     # Retrieve the attribute objects and return the results.
     if (count($acodes) > 0) {
         $params = array('acodes' => implode("|", $acodes), 'langpref' => $langpref, 'fields' => $fields);
         $results = OkapiServiceRunner::call('services/attrs/attributes', new OkapiInternalRequest($request->consumer, $request->token, $params));
     } else {
         $results = new ArrayObject();
     }
     return Okapi::formatted_response($request, $results);
 }
 public static function call(OkapiRequest $request)
 {
     $internal_ids = $request->get_parameter('internal_ids');
     if (!$internal_ids) {
         throw new ParamMissing('internal_ids');
     }
     $internal_ids = explode("|", $internal_ids);
     if (count($internal_ids) > 500) {
         throw new InvalidParam('internal_ids', "Maximum allowed number of referenced users " . "is 500. You provided " . count($internal_ids) . " references.");
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         throw new ParamMissing('fields');
     }
     # There's no need to validate the fields parameter as the 'users'
     # method does this (it will raise a proper exception on invalid values).
     $rs = Db::query("\n            select user_id, uuid\n            from user\n            where user_id in ('" . implode("','", array_map('mysql_real_escape_string', $internal_ids)) . "')\n        ");
     $internalid2useruuid = array();
     while ($row = mysql_fetch_assoc($rs)) {
         $internalid2useruuid[$row['user_id']] = $row['uuid'];
     }
     mysql_free_result($rs);
     # Retrieve data on given user_uuids.
     $id_results = OkapiServiceRunner::call('services/users/users', new OkapiInternalRequest($request->consumer, $request->token, array('user_uuids' => implode("|", array_values($internalid2useruuid)), 'fields' => $fields)));
     # Map user_uuids to internal_ids. Also check which internal_ids were not found
     # and mark them with null.
     $results = array();
     foreach ($internal_ids as $internal_id) {
         if (!isset($internalid2useruuid[$internal_id])) {
             $results[$internal_id] = null;
         } else {
             $results[$internal_id] = $id_results[$internalid2useruuid[$internal_id]];
         }
     }
     return Okapi::formatted_response($request, $results);
 }
예제 #21
0
 public static function call(OkapiRequest $request)
 {
     $cache_code = $request->get_parameter('cache_code');
     if (!$cache_code) {
         throw new ParamMissing('cache_code');
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         $fields = "uuid|date|user|type|comment";
     }
     $offset = $request->get_parameter('offset');
     if (!$offset) {
         $offset = "0";
     }
     if ((int) $offset != $offset || (int) $offset < 0) {
         throw new InvalidParam('offset', "Expecting non-negative integer.");
     }
     $limit = $request->get_parameter('limit');
     if (!$limit) {
         $limit = "none";
     }
     if ($limit == "none") {
         $limit = "999999999";
     }
     if ((int) $limit != $limit || (int) $limit < 0) {
         throw new InvalidParam('limit', "Expecting non-negative integer or 'none'.");
     }
     # Check if code exists and retrieve cache ID (this will throw
     # a proper exception on invalid code).
     $cache = OkapiServiceRunner::call('services/caches/geocache', new OkapiInternalRequest($request->consumer, null, array('cache_code' => $cache_code, 'fields' => 'internal_id')));
     # Cache exists. Getting the uuids of its logs.
     $log_uuids = Db::select_column("\n            select uuid\n            from cache_logs\n            where\n                cache_id = '" . Db::escape_string($cache['internal_id']) . "'\n                and " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "deleted = 0" : "true") . "\n            order by date desc\n            limit {$offset}, {$limit}\n        ");
     # Getting the logs themselves. Formatting as an ordered list.
     $internal_request = new OkapiInternalRequest($request->consumer, $request->token, array('log_uuids' => implode("|", $log_uuids), 'fields' => $fields));
     $internal_request->skip_limits = true;
     $logs = OkapiServiceRunner::call('services/logs/entries', $internal_request);
     $results = array();
     foreach ($log_uuids as $log_uuid) {
         $results[] = $logs[$log_uuid];
     }
     /* Handle OCPL's "access logs" feature. */
     if (Settings::get('OC_BRANCH') == 'oc.pl' && Settings::get('OCPL_ENABLE_GEOCACHE_ACCESS_LOGS') && count($log_uuids) > 0) {
         require_once $GLOBALS['rootpath'] . 'okapi/lib/ocpl_access_logs.php';
         \okapi\OCPLAccessLogs::log_geocache_access($request, $cache['internal_id']);
     }
     return Okapi::formatted_response($request, $results);
 }
예제 #22
0
 public static function call(OkapiRequest $request)
 {
     # You may wonder, why there are no parameters like "bbox" or "center" in the
     # "search/all" method. This is *intentional* and should be kept this way.
     # Such parameters would fall in conflict with each other and - in result -
     # make the documentation very fuzzy. That's why they were intentionally
     # left out of the "search/all" method, and put in separate (individual) ones.
     # It's much easier to grasp their meaning this way.
     $tmp = $request->get_parameter('bbox');
     if (!$tmp) {
         throw new ParamMissing('bbox');
     }
     $parts = explode('|', $tmp);
     if (count($parts) != 4) {
         throw new InvalidParam('bbox', "Expecting 4 pipe-separated parts, got " . count($parts) . ".");
     }
     foreach ($parts as &$part_ref) {
         if (!preg_match("/^-?[0-9]+(\\.?[0-9]*)\$/", $part_ref)) {
             throw new InvalidParam('bbox', "'{$part_ref}' is not a valid float number.");
         }
         $part_ref = floatval($part_ref);
     }
     list($bbsouth, $bbwest, $bbnorth, $bbeast) = $parts;
     if ($bbnorth <= $bbsouth) {
         throw new InvalidParam('bbox', "Northern edge must be situated to the north of the southern edge.");
     }
     if ($bbeast == $bbwest) {
         throw new InvalidParam('bbox', "Eastern edge longitude is the same as the western one.");
     }
     if ($bbnorth > 90 || $bbnorth < -90 || $bbsouth > 90 || $bbsouth < -90) {
         throw new InvalidParam('bbox', "Latitudes have to be within -90..90 range.");
     }
     if ($bbeast > 180 || $bbeast < -180 || $bbwest > 180 || $bbwest < -180) {
         throw new InvalidParam('bbox', "Longitudes have to be within -180..180 range.");
     }
     # Construct SQL conditions for the specified bounding box.
     $search_assistant = new SearchAssistant($request);
     $search_assistant->prepare_common_search_params();
     $search_assistant->prepare_location_search_params();
     $where_conds = array();
     $lat = $search_assistant->get_latitude_expr();
     $lon = $search_assistant->get_longitude_expr();
     $where_conds[] = "(\n            {$lat} >= '" . Db::escape_string($bbsouth) . "'\n            and {$lat} < '" . Db::escape_string($bbnorth) . "'\n        )";
     if ($bbeast > $bbwest) {
         # Easy one.
         $where_conds[] = "(\n                {$lon} >= '" . Db::escape_string($bbwest) . "'\n                and {$lon} < '" . Db::escape_string($bbeast) . "'\n            )";
     } else {
         # We'll have to assume that this bbox goes through the 180-degree meridian.
         # For example, $bbwest = 179 and $bbeast = -179.
         $where_conds[] = "(\n                {$lon} >= '" . Db::escape_string($bbwest) . "'\n                or {$lon} < '" . Db::escape_string($bbeast) . "'\n            )";
     }
     #
     # In the method description, we promised to return caches ordered by the *rough*
     # distance from the center of the bounding box. We'll use ORDER BY with a simplified
     # distance formula and combine it with the LIMIT clause to get the best results.
     #
     $center_lat = ($bbsouth + $bbnorth) / 2.0;
     $center_lon = ($bbwest + $bbeast) / 2.0;
     $search_params = $search_assistant->get_search_params();
     $search_params['where_conds'] = array_merge($where_conds, $search_params['where_conds']);
     $search_params['order_by'][] = Okapi::get_distance_sql($center_lat, $center_lon, $search_assistant->get_latitude_expr(), $search_assistant->get_longitude_expr());
     # not replaced; added to the end!
     $search_assistant->set_search_params($search_params);
     $result = $search_assistant->get_common_search_result();
     return Okapi::formatted_response($request, $result);
 }
예제 #23
0
 public static function call(OkapiRequest $request)
 {
     $user_uuids = $request->get_parameter('user_uuids');
     if (!$user_uuids) {
         throw new ParamMissing('user_uuids');
     }
     $user_uuids = explode("|", $user_uuids);
     if (count($user_uuids) > 500) {
         throw new InvalidParam('user_uuids', "Maximum allowed number of referenced users " . "is 500. You provided " . count($user_uuids) . " user IDs.");
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         throw new ParamMissing('fields');
     }
     $fields = explode("|", $fields);
     foreach ($fields as $field) {
         if (!in_array($field, self::$valid_field_names)) {
             throw new InvalidParam('fields', "'{$field}' is not a valid field code.");
         }
     }
     $rs = Db::query("\n            select user_id, uuid, username, admin, latitude, longitude, date_created\n            from user\n            where uuid in ('" . implode("','", array_map('\\okapi\\Db::escape_string', $user_uuids)) . "')\n        ");
     $results = array();
     $id2uuid = array();
     $uuid2id = array();
     while ($row = Db::fetch_assoc($rs)) {
         $id2uuid[$row['user_id']] = $row['uuid'];
         $uuid2id[$row['uuid']] = $row['user_id'];
         $entry = array();
         foreach ($fields as $field) {
             switch ($field) {
                 case 'uuid':
                     $entry['uuid'] = $row['uuid'];
                     break;
                 case 'username':
                     $entry['username'] = $row['username'];
                     break;
                 case 'profile_url':
                     $entry['profile_url'] = Settings::get('SITE_URL') . "viewprofile.php?userid=" . $row['user_id'];
                     break;
                 case 'is_admin':
                     if (!$request->token) {
                         $entry['is_admin'] = null;
                     } elseif ($request->token->user_id != $row['user_id']) {
                         $entry['is_admin'] = null;
                     } else {
                         $entry['is_admin'] = $row['admin'] ? true : false;
                     }
                     break;
                 case 'internal_id':
                     $entry['internal_id'] = $row['user_id'];
                     break;
                 case 'date_registered':
                     $entry['date_registered'] = date("Y-m-d", strtotime($row['date_created']));
                 case 'caches_found':
                     /* handled separately */
                     break;
                 case 'caches_notfound':
                     /* handled separately */
                     break;
                 case 'caches_hidden':
                     /* handled separately */
                     break;
                 case 'rcmds_given':
                     /* handled separately */
                     break;
                 case 'home_location':
                     if (!$request->token) {
                         $entry['home_location'] = null;
                     } elseif ($request->token->user_id != $row['user_id']) {
                         $entry['home_location'] = null;
                     } elseif (!$row['latitude'] && !$row['longitude']) {
                         # OCPL sets NULL/NULL for unknown location, OCDE sets 0/0.
                         # It is safe to return null also for OCPL 0/0, as this value
                         # does not make sense.
                         $entry['home_location'] = null;
                     } else {
                         $entry['home_location'] = round($row['latitude'], 6) . "|" . round($row['longitude'], 6);
                     }
                     break;
                 default:
                     throw new Exception("Missing field case: " . $field);
             }
         }
         $results[$row['uuid']] = $entry;
     }
     Db::free_result($rs);
     # caches_found, caches_notfound, caches_hidden
     if (in_array('caches_found', $fields) || in_array('caches_notfound', $fields) || in_array('caches_hidden', $fields) || in_array('rcmds_given', $fields)) {
         # We will load all these stats together. Then we may remove these which
         # the user doesn't need.
         $extras = array();
         if (Settings::get('OC_BRANCH') == 'oc.pl') {
             # OCPL stores user stats in 'user' table.
             $rs = Db::query("\n                    select user_id, founds_count, notfounds_count, hidden_count\n                    from user\n                    where user_id in ('" . implode("','", array_map('\\okapi\\Db::escape_string', array_keys($id2uuid))) . "')\n                ");
         } else {
             # OCDE stores user stats in 'stat_user' table.
             $rs = Db::query("\n                    select\n                        u.user_id,\n                        ifnull(su.found, 0) as founds_count,\n                        ifnull(su.notfound, 0) as notfounds_count,\n                        ifnull(su.hidden, 0) as hidden_count\n                    from\n                        user u\n                        left join stat_user su\n                            on su.user_id = u.user_id\n                    where u.user_id in ('" . implode("','", array_map('\\okapi\\Db::escape_string', array_keys($id2uuid))) . "')\n                ");
         }
         while ($row = Db::fetch_assoc($rs)) {
             $extras[$row['user_id']] = array();
             $extra_ref =& $extras[$row['user_id']];
             $extra_ref['caches_found'] = 0 + $row['founds_count'];
             $extra_ref['caches_notfound'] = 0 + $row['notfounds_count'];
             $extra_ref['caches_hidden'] = 0 + $row['hidden_count'];
         }
         Db::free_result($rs);
         if (in_array('rcmds_given', $fields)) {
             $rs = Db::query("\n                    select user_id, count(*) as rcmds_given\n                    from cache_rating\n                    where user_id in ('" . implode("','", array_map('\\okapi\\Db::escape_string', array_keys($id2uuid))) . "')\n                    group by user_id\n                ");
             $rcmds_counts = array();
             while ($row = Db::fetch_assoc($rs)) {
                 $rcmds_counts[$row['user_id']] = $row['rcmds_given'];
             }
             foreach ($extras as $user_id => &$extra_ref) {
                 $extra_ref['rcmds_given'] = isset($rcmds_counts[$user_id]) ? 0 + $rcmds_counts[$user_id] : 0;
             }
         }
         # "Apply" only those fields which the consumer wanted.
         foreach (array('caches_found', 'caches_notfound', 'caches_hidden', 'rcmds_given') as $field) {
             if (!in_array($field, $fields)) {
                 continue;
             }
             foreach ($results as $uuid => &$result_ref) {
                 $result_ref[$field] = $extras[$uuid2id[$uuid]][$field];
             }
         }
     }
     # Check which user IDs were not found and mark them with null.
     foreach ($user_uuids as $user_uuid) {
         if (!isset($results[$user_uuid])) {
             $results[$user_uuid] = null;
         }
     }
     return Okapi::formatted_response($request, $results);
 }
예제 #24
0
파일: gpx.php 프로젝트: Slini11/okapi
 /**
  * Generate a GPX file.
  *
  * @param OkapiRequest $request
  * @param integer $flags
  * @throws BadRequest
  * @return An array with GPX file content under 'gpx' key
  */
 public static function create_gpx(OkapiRequest $request, $flags = null)
 {
     $vars = array();
     # Validating arguments. We will also assign some of them to the
     # $vars variable which we will use later in the GPS template.
     $cache_codes = $request->get_parameter('cache_codes');
     if ($cache_codes === null) {
         throw new ParamMissing('cache_codes');
     }
     # Issue 106 requires us to allow empty list of cache codes to be passed into this method.
     # All of the queries below have to be ready for $cache_codes to be empty!
     $langpref = $request->get_parameter('langpref');
     if (!$langpref) {
         $langpref = "en";
     }
     $langpref .= "|" . Settings::get('SITELANG');
     foreach (array('ns_ground', 'ns_gsak', 'ns_ox', 'latest_logs', 'alt_wpts', 'mark_found') as $param) {
         $val = $request->get_parameter($param);
         if (!$val) {
             $val = "false";
         } elseif (!in_array($val, array("true", "false"))) {
             throw new InvalidParam($param);
         }
         $vars[$param] = $val == "true";
     }
     if ($vars['latest_logs'] && !$vars['ns_ground']) {
         throw new BadRequest("In order for 'latest_logs' to work you have to also include 'ns_ground' extensions.");
     }
     $tmp = $request->get_parameter('my_notes');
     $vars['my_notes'] = array();
     if ($tmp && $tmp != 'none') {
         $tmp = explode('|', $tmp);
         foreach ($tmp as $elem) {
             if ($elem == 'none') {
                 /* pass */
             } elseif (in_array($elem, array('desc:text', 'gc:personal_note'))) {
                 if (in_array('none', $tmp)) {
                     throw new InvalidParam('my_notes', "You cannot mix 'none' and '{$elem}'");
                 }
                 if ($request->token == null) {
                     throw new BadRequest("Level 3 Authentication is required to access my_notes data.");
                 }
                 $vars['my_notes'][] = $elem;
             } else {
                 throw new InvalidParam('my_notes', "Invalid list entry: '{$elem}'");
             }
         }
     }
     $images = $request->get_parameter('images');
     if (!$images) {
         $images = 'descrefs:nonspoilers';
     }
     if (!in_array($images, array('none', 'descrefs:thumblinks', 'descrefs:nonspoilers', 'descrefs:all', 'ox:all'))) {
         throw new InvalidParam('images', "'{$images}'");
     }
     $vars['images'] = $images;
     $tmp = $request->get_parameter('attrs');
     if (!$tmp) {
         $tmp = 'desc:text';
     }
     $tmp = explode("|", $tmp);
     $vars['attrs'] = array();
     foreach ($tmp as $elem) {
         if ($elem == 'none') {
             /* pass */
         } elseif (in_array($elem, array('desc:text', 'ox:tags', 'gc:attrs', 'gc_ocde:attrs'))) {
             if ($elem == 'gc_ocde:attrs' && Settings::get('OC_BRANCH') != 'oc.de') {
                 $vars['attrs'][] = 'gc:attrs';
             } else {
                 $vars['attrs'][] = $elem;
             }
         } else {
             throw new InvalidParam('attrs', "Invalid list entry: '{$elem}'");
         }
     }
     $protection_areas = $request->get_parameter('protection_areas');
     if (!$protection_areas || $protection_areas == 'desc:auto') {
         if (Settings::get('OC_BRANCH') == 'oc.de') {
             $protection_areas = 'desc:text';
         } else {
             $protection_areas = 'none';
         }
     }
     if (!in_array($protection_areas, array('none', 'desc:text'))) {
         throw new InvalidParam('protection_areas', "'{$protection_areas}'");
     }
     $vars['protection_areas'] = $protection_areas;
     $tmp = $request->get_parameter('trackables');
     if (!$tmp) {
         $tmp = 'none';
     }
     if (!in_array($tmp, array('none', 'desc:list', 'desc:count'))) {
         throw new InvalidParam('trackables', "'{$tmp}'");
     }
     $vars['trackables'] = $tmp;
     $tmp = $request->get_parameter('recommendations');
     if (!$tmp) {
         $tmp = 'none';
     }
     if (!in_array($tmp, array('none', 'desc:count'))) {
         throw new InvalidParam('recommendations', "'{$tmp}'");
     }
     $vars['recommendations'] = $tmp;
     $lpc = $request->get_parameter('lpc');
     if ($lpc === null) {
         $lpc = 10;
     }
     # will be checked in services/caches/geocaches call
     $user_uuid = $request->get_parameter('user_uuid');
     # location_source (part 1 of 2)
     $location_source = $request->get_parameter('location_source');
     if (!$location_source) {
         $location_source = 'default-coords';
     }
     # Make sure location_source has prefix alt_wpt:
     if ($location_source != 'default-coords' && strncmp($location_source, 'alt_wpt:', 8) != 0) {
         throw new InvalidParam('location_source', '\'' . $location_source . '\'');
     }
     # Make sure we have sufficient authorization
     if ($location_source == 'alt_wpt:user-coords' && $request->token == null) {
         throw new BadRequest("Level 3 Authentication is required to access 'alt_wpt:user-coords'.");
     }
     # Which fields of the services/caches/geocaches method do we need?
     $fields = 'code|name|location|date_created|url|type|status|size|size2|oxsize' . '|difficulty|terrain|description|hint2|rating|owner|url|internal_id' . '|protection_areas|short_description';
     if ($vars['images'] != 'none') {
         $fields .= "|images";
     }
     if (count($vars['attrs']) > 0) {
         $fields .= "|attrnames|attr_acodes";
     }
     if ($vars['trackables'] == 'desc:list') {
         $fields .= "|trackables";
     } elseif ($vars['trackables'] == 'desc:count') {
         $fields .= "|trackables_count";
     }
     if ($vars['alt_wpts'] == 'true' || $location_source != 'default-coords') {
         $fields .= "|alt_wpts";
     }
     if ($vars['recommendations'] != 'none') {
         $fields .= "|recommendations|founds";
     }
     if (count($vars['my_notes']) > 0) {
         $fields .= "|my_notes";
     }
     if ($vars['latest_logs']) {
         $fields .= "|latest_logs";
     }
     if ($vars['mark_found']) {
         $fields .= "|is_found";
     }
     $vars['caches'] = OkapiServiceRunner::call('services/caches/geocaches', new OkapiInternalRequest($request->consumer, $request->token, array('cache_codes' => $cache_codes, 'langpref' => $langpref, 'fields' => $fields, 'lpc' => $lpc, 'user_uuid' => $user_uuid, 'log_fields' => 'uuid|date|user|type|comment|internal_id|was_recommended')));
     # Get rid of invalid cache references.
     $valid = array();
     foreach ($vars['caches'] as $key => &$ref) {
         if ($ref !== null) {
             $valid[$key] =& $ref;
         }
     }
     $vars['caches'] =& $valid;
     unset($valid);
     # Get all the other data need.
     $vars['installation'] = OkapiServiceRunner::call('services/apisrv/installation', new OkapiInternalRequest(new OkapiInternalConsumer(), null, array()));
     $vars['cache_GPX_types'] = self::$cache_GPX_types;
     $vars['cache_GPX_sizes'] = self::$cache_GPX_sizes;
     if (count($vars['attrs']) > 0) {
         /* The user asked for some kind of attribute output. We'll fetch all
          * the data we MAY need. This is often far too much, but thanks to
          * caching, it will work fast. */
         $vars['attr_index'] = OkapiServiceRunner::call('services/attrs/attribute_index', new OkapiInternalRequest($request->consumer, $request->token, array('only_locally_used' => 'true', 'langpref' => $langpref, 'fields' => 'name|gc_equivs')));
         # prepare GS attribute data
         $vars['gc_attrs'] = in_array('gc:attrs', $vars['attrs']);
         $vars['gc_ocde_attrs'] = in_array('gc_ocde:attrs', $vars['attrs']);
         if ($vars['gc_attrs'] || $vars['gc_ocde_attrs']) {
             if ($vars['gc_ocde_attrs']) {
                 # As this is an OCDE compatibility feature, we use the same Pseudo-GS
                 # attribute names here as OCDE. Note that this code is specific to OCDE
                 # database; OCPL stores attribute names in a different way and may use
                 # different names for equivalent attributes.
                 $ocde_attrnames = Db::select_group_by('id', "\n                        select id, name\n                        from cache_attrib\n                    ");
                 $attr_dict = AttrHelper::get_attrdict();
             }
             foreach ($vars['caches'] as &$cache_ref) {
                 $cache_ref['gc_attrs'] = array();
                 foreach ($cache_ref['attr_acodes'] as $acode) {
                     $has_gc_equivs = false;
                     foreach ($vars['attr_index'][$acode]['gc_equivs'] as $gc) {
                         # The assignment via GC-ID as array key will prohibit duplicate
                         # GC attributes, which can result from
                         # - assigning the same GC ID to multiple A-Codes,
                         # - contradicting attributes in one OC listing, e.g. 24/4 + not 24/7.
                         $cache_ref['gc_attrs'][$gc['id']] = $gc;
                         $has_gc_equivs = true;
                     }
                     if (!$has_gc_equivs && $vars['gc_ocde_attrs']) {
                         # Generate an OCDE pseudo-GS attribute;
                         # see https://github.com/opencaching/okapi/issues/190 and
                         # https://github.com/opencaching/okapi/issues/271.
                         #
                         # Groundspeak uses ID 1..65 (as of June, 2013), and OCDE makeshift
                         # IDs start at 106, so there is space for 40 new GS attributes.
                         $internal_id = $attr_dict[$acode]['internal_id'];
                         $cache_ref['gc_attrs'][100 + $internal_id] = array('inc' => 1, 'name' => $ocde_attrnames[$internal_id][0]['name']);
                     }
                 }
             }
         }
     }
     /* OC sites always used internal user_ids in their generated GPX files.
      * This might be considered an error in itself (Groundspeak's XML namespace
      * doesn't allow that), but it very common (Garmin's OpenCaching.COM
      * also does that). Therefore, for backward-compatibility reasons, OKAPI
      * will do it the same way. See issue 174.
      *
      * Currently, the caches method does not expose "owner.internal_id" and
      * "latest_logs.user.internal_id" fields, we will read them manually
      * from the database here. */
     $dict = array();
     foreach ($vars['caches'] as &$cache_ref) {
         $dict[$cache_ref['owner']['uuid']] = true;
         if (isset($cache_ref['latest_logs'])) {
             foreach ($cache_ref['latest_logs'] as &$log_ref) {
                 $dict[$log_ref['user']['uuid']] = true;
             }
         }
     }
     $rs = Db::query("\n            select uuid, user_id\n            from user\n            where uuid in ('" . implode("','", array_map('mysql_real_escape_string', array_keys($dict))) . "')\n        ");
     while ($row = mysql_fetch_assoc($rs)) {
         $dict[$row['uuid']] = $row['user_id'];
     }
     $vars['user_uuid_to_internal_id'] =& $dict;
     unset($dict);
     # location_source (part 2 of 2)
     if ($location_source != 'default-coords') {
         $location_change_prefix = $request->get_parameter('location_change_prefix');
         if (!$location_change_prefix) {
             $location_change_prefix = '# ';
         }
         # lets find requested coords
         foreach ($vars['caches'] as &$cache_ref) {
             foreach ($cache_ref['alt_wpts'] as $alt_wpt_key => $alt_wpt) {
                 if ('alt_wpt:' . $alt_wpt['type'] == $location_source) {
                     # Switch locations between primary wpt and alternate wpt.
                     # Also alter the cache name and make sure to append a proper
                     # notice.
                     $original_location = $cache_ref['location'];
                     $cache_ref['location'] = $alt_wpt['location'];
                     $cache_ref['name_2'] = $location_change_prefix . $cache_ref['name'];
                     if ($location_source == "alt_wpt:user-coords") {
                         # In case of "user-coords", replace the default warning with a custom-tailored one.
                         $cache_ref['warning_prefix'] = _("<b>Geocache coordinates have been changed.</b> They have been replaced with " . "your own custom coordinates which you have provided for this geocache.");
                     } else {
                         # Default warning
                         $cache_ref['warning_prefix'] = _("<b>Geocache coordinates have been changed.</b> Currently they " . "point to one of the alternate waypoints originally described as:") . " " . $alt_wpt['description'];
                     }
                     # remove current alt waypoint
                     unset($cache_ref['alt_wpts'][$alt_wpt_key]);
                     # add original location as alternate
                     if ($vars['alt_wpts']) {
                         $cache_ref['alt_wpts'][] = array('name' => $cache_ref['code'] . '-DEFAULT-COORDS', 'location' => $original_location, 'type' => 'default-coords', 'type_name' => _("Original geocache location"), 'sym' => 'Block, Blue', 'description' => sprintf(_("Original (owner-supplied) location of the %s geocache"), $cache_ref['code']));
                     }
                     break;
                 }
             }
         }
     }
     # Do we need a GGZ index?
     if ($flags & self::FLAG_CREATE_GGZ_IDX) {
         # GGZ index consist of entries - one per each waypoint in the GPX file.
         # We will keep a list of all such entries here.
         $ggz_entries = array();
         foreach ($vars['caches'] as &$cache_ref) {
             # Every $cache_ref will also be holding a reference to its entry.
             # Note, that more attributes are added while processing gpsfile.tpl.php!
             if (!isset($cache_ref['ggz_entry'])) {
                 $cache_ref['ggz_entry'] = array();
             }
             $ggz_entry =& $cache_ref['ggz_entry'];
             $ggz_entries[] =& $ggz_entry;
             $ggz_entry['code'] = $cache_ref['code'];
             $ggz_entry['name'] = isset($cache_ref['name_2']) ? $cache_ref['name_2'] : $cache_ref['name'];
             $ggz_entry['type'] = $vars['cache_GPX_types'][$cache_ref['type']];
             list($lat, $lon) = explode("|", $cache_ref['location']);
             $ggz_entry['lat'] = $lat;
             $ggz_entry['lon'] = $lon;
             $ggz_entry['ratings'] = array();
             $ratings_ref =& $ggz_entry['ratings'];
             if (isset($cache_ref['rating'])) {
                 $ratings_ref['awesomeness'] = $cache_ref['rating'];
             }
             $ratings_ref['difficulty'] = $cache_ref['difficulty'];
             if (!isset($cache_ref['size'])) {
                 $ratings_ref['size'] = 0;
                 // Virtual, Event
             } else {
                 if ($cache_ref['oxsize'] !== null) {
                     // is this ox size one-to-one?
                     $ratings_ref['size'] = $cache_ref['oxsize'];
                 }
             }
             $ratings_ref['terrain'] = $cache_ref['terrain'];
             if ($vars['mark_found'] && $cache_ref['is_found']) {
                 $ggz_entry['found'] = true;
             }
             # Additional waypoints. Currently, we're not 100% sure if their entries should
             # be included in the GGZ file (the format is undocumented).
             if (isset($cache_ref['alt_wpts'])) {
                 $idx = 1;
                 foreach ($cache_ref['alt_wpts'] as &$alt_wpt_ref) {
                     if (!isset($alt_wpt_ref['ggz_entry'])) {
                         $alt_wpt_ref['ggz_entry'] = array();
                     }
                     $ggz_entry =& $alt_wpt_ref['ggz_entry'];
                     $ggz_entries[] =& $ggz_entry;
                     $ggz_entry['code'] = $cache_ref['code'] . '-' . $idx;
                     $ggz_entry['name'] = $alt_wpt_ref['type_name'];
                     $ggz_entry['type'] = $alt_wpt_ref['sym'];
                     list($lat, $lon) = explode("|", $alt_wpt_ref['location']);
                     $ggz_entry['lat'] = $lat;
                     $ggz_entry['lon'] = $lon;
                     $idx++;
                 }
             }
         }
     }
     ob_start();
     Okapi::gettext_domain_init(explode("|", $langpref));
     # Consumer gets properly localized GPX file.
     include 'gpxfile.tpl.php';
     Okapi::gettext_domain_restore();
     $result = array('gpx' => ob_get_clean());
     if ($flags & self::FLAG_CREATE_GGZ_IDX) {
         $result['ggz_entries'] = $ggz_entries;
     }
     return $result;
 }
예제 #25
0
 public static function call(OkapiRequest $request)
 {
     $methodname = $request->get_parameter('name');
     if (!$methodname) {
         throw new ParamMissing('name');
     }
     if (!preg_match("#^services/[0-9a-z_/]*\$#", $methodname)) {
         throw new InvalidParam('name');
     }
     if (!OkapiServiceRunner::exists($methodname)) {
         throw new InvalidParam('name', "Method does not exist: '{$methodname}'.");
     }
     $options = OkapiServiceRunner::options($methodname);
     if (!isset($options['min_auth_level'])) {
         throw new Exception("Method {$methodname} is missing a required 'min_auth_level' option!");
     }
     $docs = simplexml_load_string(OkapiServiceRunner::docs($methodname));
     $exploded = explode("/", $methodname);
     $result = array('name' => $methodname, 'short_name' => end($exploded), 'ref_url' => Settings::get('SITE_URL') . "okapi/{$methodname}.html", 'auth_options' => array('min_auth_level' => $options['min_auth_level'], 'oauth_consumer' => $options['min_auth_level'] >= 2, 'oauth_token' => $options['min_auth_level'] >= 3));
     if (!$docs->brief) {
         throw new Exception("Missing <brief> element in the {$methodname}.xml file.");
     }
     if ($docs->brief != self::get_inner_xml($docs->brief)) {
         throw new Exception("The <brief> element may not contain HTML markup ({$methodname}.xml).");
     }
     if (strlen($docs->brief) > 80) {
         throw new Exception("The <brief> description may not be longer than 80 characters ({$methodname}.xml).");
     }
     if (strpos($docs->brief, "\n") !== false) {
         throw new Exception("The <brief> element may not contain new-lines ({$methodname}.xml).");
     }
     if (substr(trim($docs->brief), -1) == '.') {
         throw new Exception("The <brief> element should not end with a dot ({$methodname}.xml).");
     }
     $result['brief_description'] = self::get_inner_xml($docs->brief);
     if ($docs->{'issue-id'}) {
         $result['issue_id'] = (string) $docs->{'issue-id'};
     } else {
         $result['issue_id'] = null;
     }
     if (!$docs->desc) {
         throw new Exception("Missing <desc> element in the {$methodname}.xml file.");
     }
     $result['description'] = self::get_inner_xml($docs->desc);
     $result['arguments'] = array();
     foreach ($docs->req as $arg) {
         $result['arguments'][] = self::arg_desc($arg);
     }
     foreach ($docs->opt as $arg) {
         $result['arguments'][] = self::arg_desc($arg);
     }
     foreach ($docs->{'import-params'} as $import_desc) {
         $attrs = $import_desc->attributes();
         $referenced_methodname = $attrs['method'];
         $referenced_method_info = OkapiServiceRunner::call('services/apiref/method', new OkapiInternalRequest(new OkapiInternalConsumer(), null, array('name' => $referenced_methodname)));
         $include_list = isset($attrs['params']) ? explode("|", $attrs['params']) : null;
         $exclude_list = isset($attrs['except']) ? explode("|", $attrs['except']) : array();
         foreach ($referenced_method_info['arguments'] as $arg) {
             if ($arg['class'] == 'common-formatting') {
                 continue;
             }
             if ($include_list === null && count($exclude_list) == 0) {
                 $arg['description'] = "<i>Inherited from <a href='" . $referenced_method_info['ref_url'] . "#arg_" . $arg['name'] . "'>" . $referenced_method_info['name'] . "</a> method.</i>";
             } elseif (($include_list === null || in_array($arg['name'], $include_list)) && !in_array($arg['name'], $exclude_list)) {
                 $arg['description'] = "<i>Same as in the <a href='" . $referenced_method_info['ref_url'] . "#arg_" . $arg['name'] . "'>" . $referenced_method_info['name'] . "</a> method.</i>";
             } else {
                 continue;
             }
             $arg['class'] = 'inherited';
             $result['arguments'][] = $arg;
         }
     }
     if ($docs->{'common-format-params'}) {
         $result['arguments'][] = array('name' => 'format', 'is_required' => false, 'is_deprecated' => false, 'class' => 'common-formatting', 'description' => "<i>Standard <a href='" . Settings::get('SITE_URL') . "okapi/introduction.html#common-formatting'>common formatting</a> argument.</i>");
         $result['arguments'][] = array('name' => 'callback', 'is_required' => false, 'is_deprecated' => false, 'class' => 'common-formatting', 'description' => "<i>Standard <a href='" . Settings::get('SITE_URL') . "okapi/introduction.html#common-formatting'>common formatting</a> argument.</i>");
     }
     foreach ($result['arguments'] as &$arg_ref) {
         if ($arg_ref['is_deprecated']) {
             $arg_ref['class'] .= " deprecated";
         }
     }
     if (!$docs->returns) {
         throw new Exception("Missing <returns> element in the {$methodname}.xml file. " . "If your method does not return anything, you should document in nonetheless.");
     }
     $result['returns'] = self::get_inner_xml($docs->returns);
     return Okapi::formatted_response($request, $result);
 }
예제 #26
0
파일: core.php 프로젝트: Slini11/okapi
 /**
  * Return object as a standard OKAPI response. The $object will be formatted
  * using one of the default formatters (JSON, JSONP, XML, etc.). Formatter is
  * auto-detected by peeking on the $request's 'format' parameter. In some
  * specific cases, this method can also return the $object itself, instead
  * of OkapiResponse - this allows nesting methods within other methods.
  */
 public static function formatted_response(OkapiRequest $request, &$object)
 {
     if ($request instanceof OkapiInternalRequest && !$request->i_want_OkapiResponse) {
         # If you call a method internally, then you probably expect to get
         # the actual object instead of it's formatted representation.
         return $object;
     }
     $format = $request->get_parameter('format');
     if ($format == null) {
         $format = 'json';
     }
     if (!in_array($format, array('json', 'jsonp', 'xmlmap', 'xmlmap2'))) {
         throw new InvalidParam('format', "'{$format}'");
     }
     $callback = $request->get_parameter('callback');
     if ($callback && $format != 'jsonp') {
         throw new BadRequest("The 'callback' parameter is reserved to be used with the JSONP output format.");
     }
     if ($format == 'json') {
         $response = new OkapiHttpResponse();
         $response->content_type = "application/json; charset=utf-8";
         $response->body = json_encode($object);
         return $response;
     } elseif ($format == 'jsonp') {
         if (!$callback) {
             throw new BadRequest("'callback' parameter is required for JSONP calls");
         }
         if (!preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*\$/", $callback)) {
             throw new InvalidParam('callback', "'{$callback}' doesn't seem to be a valid JavaScript function name (should match /^[a-zA-Z_][a-zA-Z0-9_]*\$/).");
         }
         $response = new OkapiHttpResponse();
         $response->content_type = "application/javascript; charset=utf-8";
         $response->body = $callback . "(" . json_encode($object) . ");";
         return $response;
     } elseif ($format == 'xmlmap') {
         # Deprecated (see issue 128). Keeping this for backward-compatibility.
         $response = new OkapiHttpResponse();
         $response->content_type = "text/xml; charset=utf-8";
         $response->body = self::xmlmap_dumps($object);
         return $response;
     } elseif ($format == 'xmlmap2') {
         $response = new OkapiHttpResponse();
         $response->content_type = "text/xml; charset=utf-8";
         $response->body = self::xmlmap2_dumps($object);
         return $response;
     } else {
         # Should not happen (as we do a proper check above).
         throw new Exception();
     }
 }
예제 #27
0
 public static function call(OkapiRequest $request)
 {
     # Retrieve the list of URLs to check.
     $tmp = $request->get_parameter('urls');
     if (!$tmp) {
         throw new ParamMissing('urls');
     }
     $urls = explode('|', $tmp);
     $as_dict = $request->get_parameter('as_dict');
     if (!$as_dict) {
         $as_dict = 'false';
     }
     if (!in_array($as_dict, array('true', 'false'))) {
         throw new InvalidParam('as_dict');
     }
     $as_dict = $as_dict == 'true';
     # Generate the lists of keys.
     $results = array();
     $urls_with = array('cache_code' => array(), 'internal_id' => array(), 'uuid' => array());
     foreach ($urls as &$url_ref) {
         $key = self::get_cache_key($url_ref);
         if ($key != null) {
             $urls_with[$key[0]][$url_ref] = $key[1];
         } else {
             $results[$url_ref] = null;
         }
     }
     # Include 'cache_code' references.
     foreach ($urls_with['cache_code'] as $url => $cache_code) {
         $results[$url] = $cache_code;
     }
     # Include 'internal_id' references.
     $internal_ids = array_values($urls_with['internal_id']);
     if (count($internal_ids) > 0) {
         $rs = Db::query("\n                select cache_id, wp_oc\n                from caches\n                where\n                    cache_id in ('" . implode("','", array_map('mysql_real_escape_string', $internal_ids)) . "')\n                    and status in (1,2,3)\n            ");
         $dict = array();
         while ($row = mysql_fetch_assoc($rs)) {
             $dict[$row['cache_id']] = $row['wp_oc'];
         }
         foreach ($urls_with['internal_id'] as $url => $internal_id) {
             if (isset($dict[$internal_id])) {
                 $results[$url] = $dict[$internal_id];
             } else {
                 $results[$url] = null;
             }
         }
     }
     # Include 'uuid' references.
     $uuids = array_values($urls_with['uuid']);
     if (count($uuids) > 0) {
         $rs = Db::query("\n                select uuid, wp_oc\n                from caches\n                where\n                    uuid in ('" . implode("','", array_map('mysql_real_escape_string', $uuids)) . "')\n                    and status in (1,2,3)\n            ");
         $dict = array();
         while ($row = mysql_fetch_assoc($rs)) {
             $dict[$row['uuid']] = $row['wp_oc'];
         }
         foreach ($urls_with['uuid'] as $url => $uuid) {
             if (isset($dict[$uuid])) {
                 $results[$url] = $dict[$uuid];
             } else {
                 $results[$url] = null;
             }
         }
     }
     # Format the results according to the 'as_dict' parameter.
     if ($as_dict) {
         return Okapi::formatted_response($request, $results);
     } else {
         $cache_codes = array();
         foreach ($results as $url => $cache_code) {
             if ($cache_code != null) {
                 $cache_codes[$cache_code] = true;
             }
         }
         $flattened = array('results' => array_keys($cache_codes));
         return Okapi::formatted_response($request, $flattened);
     }
 }
예제 #28
0
파일: geocaches.php 프로젝트: Slini11/okapi
 public static function call(OkapiRequest $request)
 {
     $cache_codes = $request->get_parameter('cache_codes');
     if ($cache_codes === null) {
         throw new ParamMissing('cache_codes');
     }
     if ($cache_codes === "") {
         # Issue 106 requires us to allow empty list of cache codes to be passed into this method.
         # All of the queries below have to be ready for $cache_codes to be empty!
         $cache_codes = array();
     } else {
         $cache_codes = explode("|", $cache_codes);
     }
     if (count($cache_codes) > 500 && !$request->skip_limits) {
         throw new InvalidParam('cache_codes', "Maximum allowed number of referenced " . "caches is 500. You provided " . count($cache_codes) . " cache codes.");
     }
     if (count($cache_codes) != count(array_unique($cache_codes))) {
         throw new InvalidParam('cache_codes', "Duplicate codes detected (make sure each cache is referenced only once).");
     }
     $langpref = $request->get_parameter('langpref');
     if (!$langpref) {
         $langpref = "en";
     }
     $langpref .= "|" . Settings::get('SITELANG');
     $langpref = explode("|", $langpref);
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         $fields = "code|name|location|type|status";
     }
     $fields = explode("|", $fields);
     foreach ($fields as $field) {
         if (!in_array($field, self::$valid_field_names)) {
             throw new InvalidParam('fields', "'{$field}' is not a valid field code.");
         }
     }
     # Some fields need to be temporarily included whenever the "description"
     # or "attribution_note" field are included. That's a little ugly, but
     # helps performance and conforms to the DRY rule.
     $fields_to_remove_later = array();
     if (in_array('description', $fields) || in_array('descriptions', $fields) || in_array('short_description', $fields) || in_array('short_descriptions', $fields) || in_array('hint', $fields) || in_array('hints', $fields) || in_array('hint2', $fields) || in_array('hints2', $fields) || in_array('attribution_note', $fields)) {
         if (!in_array('owner', $fields)) {
             $fields[] = "owner";
             $fields_to_remove_later[] = "owner";
         }
         if (!in_array('internal_id', $fields)) {
             $fields[] = "internal_id";
             $fields_to_remove_later[] = "internal_id";
         }
     }
     $attribution_append = $request->get_parameter('attribution_append');
     if (!$attribution_append) {
         $attribution_append = 'full';
     }
     if (!in_array($attribution_append, array('none', 'static', 'full'))) {
         throw new InvalidParam('attribution_append');
     }
     $log_fields = $request->get_parameter('log_fields');
     if (!$log_fields) {
         $log_fields = "uuid|date|user|type|comment";
     }
     // validation is done on call
     $user_uuid = $request->get_parameter('user_uuid');
     if ($user_uuid != null) {
         $user_id = Db::select_value("select user_id from user where uuid='" . mysql_real_escape_string($user_uuid) . "'");
         if ($user_id == null) {
             throw new InvalidParam('user_uuid', "User not found.");
         }
         if ($request->token != null && $request->token->user_id != $user_id) {
             throw new InvalidParam('user_uuid', "User does not match the Access Token used.");
         }
     } elseif ($user_uuid == null && $request->token != null) {
         $user_id = $request->token->user_id;
     } else {
         $user_id = null;
     }
     $lpc = $request->get_parameter('lpc');
     if ($lpc === null) {
         $lpc = 10;
     }
     if ($lpc == 'all') {
         $lpc = null;
     } else {
         if (!is_numeric($lpc)) {
             throw new InvalidParam('lpc', "Invalid number: '{$lpc}'");
         }
         $lpc = intval($lpc);
         if ($lpc < 0) {
             throw new InvalidParam('lpc', "Must be a positive value.");
         }
     }
     if (in_array('distance', $fields) || in_array('bearing', $fields) || in_array('bearing2', $fields) || in_array('bearing3', $fields)) {
         $tmp = $request->get_parameter('my_location');
         if (!$tmp) {
             throw new BadRequest("When using 'distance' or 'bearing' fields, you have to supply 'my_location' parameter.");
         }
         $parts = explode('|', $tmp);
         if (count($parts) != 2) {
             throw new InvalidParam('my_location', "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('my_location', "'{$part_ref}' is not a valid float number.");
             }
             $part_ref = floatval($part_ref);
         }
         list($center_lat, $center_lon) = $parts;
         if ($center_lat > 90 || $center_lat < -90) {
             throw new InvalidParam('current_position', "Latitudes have to be within -90..90 range.");
         }
         if ($center_lon > 180 || $center_lon < -180) {
             throw new InvalidParam('current_position', "Longitudes have to be within -180..180 range.");
         }
     }
     if (Settings::get('OC_BRANCH') == 'oc.de') {
         # DE branch:
         # - Caches do not have ratings.
         # - Total numbers of founds and notfounds are kept in the "stat_caches" table.
         # - search_time and way_length are both round trip values and cannot be null;
         #     0 = not specified
         # - will-attend-count is stored in separate field
         $rs = Db::query("\n                select\n                    c.cache_id, c.name, c.longitude, c.latitude, c.listing_last_modified as last_modified,\n                    c.date_created, c.type, c.status, c.date_hidden, c.size, c.difficulty,\n                    c.terrain, c.wp_oc, c.wp_gc, c.logpw, c.user_id,\n                    if(c.search_time=0, null, c.search_time) as trip_time,\n                    if(c.way_length=0, null, c.way_length) as trip_distance,\n\n                    ifnull(sc.toprating, 0) as topratings,\n                    ifnull(sc.found, 0) as founds,\n                    ifnull(sc.notfound, 0) as notfounds,\n                    ifnull(sc.will_attend, 0) as willattends,\n                    sc.last_found,\n                    0 as votes, 0 as score\n                    -- SEE ALSO OC.PL BRANCH BELOW\n                from\n                    caches c\n                    left join stat_caches as sc on c.cache_id = sc.cache_id\n                where\n                    wp_oc in ('" . implode("','", array_map('mysql_real_escape_string', $cache_codes)) . "')\n                    and status in (1,2,3)\n            ");
     } elseif (Settings::get('OC_BRANCH') == 'oc.pl') {
         # PL branch:
         # - Caches have ratings.
         # - Total numbers of found and notfounds are kept in the "caches" table.
         # - search_time is round trip and way_length one way or both ways (this is different on OCDE!);
         #   both can be null; 0 or null = not specified
         # - will-attend-count is stored in caches.notfounds
         $rs = Db::query("\n                select\n                    c.cache_id, c.name, c.longitude, c.latitude, c.last_modified,\n                    c.date_created, c.type, c.status, c.date_hidden, c.size, c.difficulty,\n                    c.terrain, c.wp_oc, c.wp_gc, c.logpw, c.user_id,\n                    if(c.search_time=0, null, c.search_time) as trip_time,\n                    if(c.way_length=0, null, c.way_length) as trip_distance,\n\n                    c.topratings,\n                    c.founds,\n                    c.notfounds,\n                    c.notfounds as willattends,\n                    c.last_found,\n                    c.votes, c.score\n                    -- SEE ALSO OC.DE BRANCH ABOVE\n                from\n                    caches c\n                where\n                    wp_oc in ('" . implode("','", array_map('mysql_real_escape_string', $cache_codes)) . "')\n                    and c.status in (1,2,3)\n            ");
     }
     $results = new ArrayObject();
     $cacheid2wptcode = array();
     $owner_ids = array();
     while ($row = mysql_fetch_assoc($rs)) {
         $entry = array();
         $cacheid2wptcode[$row['cache_id']] = $row['wp_oc'];
         foreach ($fields as $field) {
             switch ($field) {
                 case 'code':
                     $entry['code'] = $row['wp_oc'];
                     break;
                 case 'gc_code':
                     // OC software allows entering anything here, and that's what users do.
                     // We do a formal verification so that only a valid GC code is returned:
                     if (preg_match('/^\\s*[Gg][Cc][A-Za-z0-9]+\\s*$/', $row['wp_gc'])) {
                         $entry['gc_code'] = strtoupper(trim($row['wp_gc']));
                     } else {
                         $entry['gc_code'] = null;
                     }
                     break;
                 case 'name':
                     $entry['name'] = $row['name'];
                     break;
                 case 'names':
                     $entry['names'] = array(Settings::get('SITELANG') => $row['name']);
                     break;
                     // for the future
                 // for the future
                 case 'location':
                     $entry['location'] = round($row['latitude'], 6) . "|" . round($row['longitude'], 6);
                     break;
                 case 'type':
                     $entry['type'] = Okapi::cache_type_id2name($row['type']);
                     break;
                 case 'status':
                     $entry['status'] = Okapi::cache_status_id2name($row['status']);
                     break;
                 case 'url':
                     $entry['url'] = Settings::get('SITE_URL') . "viewcache.php?wp=" . $row['wp_oc'];
                     break;
                 case 'owner':
                     $owner_ids[$row['wp_oc']] = $row['user_id'];
                     /* continued later */
                     break;
                 case 'distance':
                     $entry['distance'] = (int) Okapi::get_distance($center_lat, $center_lon, $row['latitude'], $row['longitude']);
                     break;
                 case 'bearing':
                     $tmp = Okapi::get_bearing($center_lat, $center_lon, $row['latitude'], $row['longitude']);
                     $entry['bearing'] = $tmp !== null ? (int) (10 * $tmp) / 10.0 : null;
                     break;
                 case 'bearing2':
                     $tmp = Okapi::get_bearing($center_lat, $center_lon, $row['latitude'], $row['longitude']);
                     $entry['bearing2'] = Okapi::bearing_as_two_letters($tmp);
                     break;
                 case 'bearing3':
                     $tmp = Okapi::get_bearing($center_lat, $center_lon, $row['latitude'], $row['longitude']);
                     $entry['bearing3'] = Okapi::bearing_as_three_letters($tmp);
                     break;
                 case 'is_found':
                     /* handled separately */
                     break;
                 case 'is_not_found':
                     /* handled separately */
                     break;
                 case 'is_watched':
                     /* handled separately */
                     break;
                 case 'is_ignored':
                     /* handled separately */
                     break;
                 case 'founds':
                     $entry['founds'] = $row['founds'] + 0;
                     break;
                 case 'notfounds':
                     if ($row['type'] != 6) {
                         # non-event
                         $entry['notfounds'] = $row['notfounds'] + 0;
                     } else {
                         # event
                         $entry['notfounds'] = 0;
                     }
                     break;
                 case 'willattends':
                     if ($row['type'] == 6) {
                         # event
                         $entry['willattends'] = $row['willattends'] + 0;
                     } else {
                         # non-event
                         $entry['willattends'] = 0;
                     }
                     break;
                 case 'size':
                     # Deprecated. Leave it for backward-compatibility. See issue 155.
                     switch (Okapi::cache_sizeid_to_size2($row['size'])) {
                         case 'none':
                             $entry['size'] = null;
                             break;
                         case 'nano':
                             $entry['size'] = 1.0;
                             break;
                             # same as micro
                         # same as micro
                         case 'micro':
                             $entry['size'] = 1.0;
                             break;
                         case 'small':
                             $entry['size'] = 2.0;
                             break;
                         case 'regular':
                             $entry['size'] = 3.0;
                             break;
                         case 'large':
                             $entry['size'] = 4.0;
                             break;
                         case 'xlarge':
                             $entry['size'] = 5.0;
                             break;
                         case 'other':
                             $entry['size'] = null;
                             break;
                             # same as none
                         # same as none
                         default:
                             throw new Exception();
                     }
                     break;
                 case 'size2':
                     $entry['size2'] = Okapi::cache_sizeid_to_size2($row['size']);
                     break;
                 case 'oxsize':
                     $entry['oxsize'] = Okapi::cache_size2_to_oxsize(Okapi::cache_sizeid_to_size2($row['size']));
                     break;
                 case 'difficulty':
                     $entry['difficulty'] = round($row['difficulty'] / 2.0, 1);
                     break;
                 case 'terrain':
                     $entry['terrain'] = round($row['terrain'] / 2.0, 1);
                     break;
                 case 'trip_time':
                     # search time is entered in hours:minutes and converted to decimal hours,
                     # which can produce lots of unneeded decimal places; 2 of them are sufficient here
                     $entry['trip_time'] = $row['trip_time'] === null ? null : round($row['trip_time'], 2);
                     break;
                     break;
                 case 'trip_distance':
                     # way length is entered in km as decimal fraction, but number conversions can
                     # create fake digits which should be stripped; meter precision is sufficient here
                     $entry['trip_distance'] = $row['trip_distance'] === null ? null : round($row['trip_distance'], 3);
                     break;
                     break;
                 case 'rating':
                     if ($row['votes'] < 3) {
                         $entry['rating'] = null;
                     } elseif ($row['score'] >= 2.2) {
                         $entry['rating'] = 5.0;
                     } elseif ($row['score'] >= 1.4) {
                         $entry['rating'] = 4.0;
                     } elseif ($row['score'] >= 0.1) {
                         $entry['rating'] = 3.0;
                     } elseif ($row['score'] >= -1.0) {
                         $entry['rating'] = 2.0;
                     } else {
                         $entry['rating'] = 1.0;
                     }
                     break;
                 case 'rating_votes':
                     $entry['rating_votes'] = $row['votes'] + 0;
                     break;
                 case 'recommendations':
                     $entry['recommendations'] = $row['topratings'] + 0;
                     break;
                 case 'req_passwd':
                     $entry['req_passwd'] = $row['logpw'] ? true : false;
                     break;
                 case 'short_description':
                     /* handled separately */
                     break;
                 case 'short_descriptions':
                     /* handled separately */
                     break;
                 case 'description':
                     /* handled separately */
                     break;
                 case 'descriptions':
                     /* handled separately */
                     break;
                 case 'hint':
                     /* handled separately */
                     break;
                 case 'hints':
                     /* handled separately */
                     break;
                 case 'hint2':
                     /* handled separately */
                     break;
                 case 'hints2':
                     /* handled separately */
                     break;
                 case 'images':
                     /* handled separately */
                     break;
                 case 'preview_image':
                     /* handled separately */
                     break;
                 case 'attr_acodes':
                     /* handled separately */
                     break;
                 case 'attrnames':
                     /* handled separately */
                     break;
                 case 'latest_logs':
                     /* handled separately */
                     break;
                 case 'my_notes':
                     /* handles separately */
                     break;
                 case 'trackables_count':
                     /* handled separately */
                     break;
                 case 'trackables':
                     /* handled separately */
                     break;
                 case 'alt_wpts':
                     /* handled separately */
                     break;
                 case 'country':
                     /* handled separately */
                     break;
                 case 'state':
                     /* handled separately */
                     break;
                 case 'last_found':
                     $entry['last_found'] = $row['last_found'] > '1980' ? date('c', strtotime($row['last_found'])) : null;
                     break;
                 case 'last_modified':
                     $entry['last_modified'] = date('c', strtotime($row['last_modified']));
                     break;
                 case 'date_created':
                     $entry['date_created'] = date('c', strtotime($row['date_created']));
                     break;
                 case 'date_hidden':
                     $entry['date_hidden'] = date('c', strtotime($row['date_hidden']));
                     break;
                 case 'internal_id':
                     $entry['internal_id'] = $row['cache_id'];
                     break;
                 case 'attribution_note':
                     /* handled separately */
                     break;
                 case 'protection_areas':
                     /* handled separately */
                     break;
                 default:
                     throw new Exception("Missing field case: " . $field);
             }
         }
         $results[$row['wp_oc']] = $entry;
     }
     mysql_free_result($rs);
     # owner
     if (in_array('owner', $fields) && count($results) > 0) {
         $rs = Db::query("\n                select user_id, uuid, username\n                from user\n                where user_id in ('" . implode("','", array_map('mysql_real_escape_string', array_values($owner_ids))) . "')\n            ");
         $tmp = array();
         while ($row = mysql_fetch_assoc($rs)) {
             $tmp[$row['user_id']] = $row;
         }
         foreach ($results as $cache_code => &$result_ref) {
             $row = $tmp[$owner_ids[$cache_code]];
             $result_ref['owner'] = array('uuid' => $row['uuid'], 'username' => $row['username'], 'profile_url' => Settings::get('SITE_URL') . "viewprofile.php?userid=" . $row['user_id']);
         }
     }
     # is_found
     if (in_array('is_found', $fields)) {
         if ($user_id == null) {
             throw new BadRequest("Either 'user_uuid' parameter OR Level 3 Authentication is required to access 'is_found' field.");
         }
         $tmp = Db::select_column("\n                select c.wp_oc\n                from\n                    caches c,\n                    cache_logs cl\n                where\n                    c.cache_id = cl.cache_id\n                    and cl.type in (\n                        '" . mysql_real_escape_string(Okapi::logtypename2id("Found it")) . "',\n                        '" . mysql_real_escape_string(Okapi::logtypename2id("Attended")) . "'\n                    )\n                    and cl.user_id = '" . mysql_real_escape_string($user_id) . "'\n                    " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "and cl.deleted = 0" : "") . "\n            ");
         $tmp2 = array();
         foreach ($tmp as $cache_code) {
             $tmp2[$cache_code] = true;
         }
         foreach ($results as $cache_code => &$result_ref) {
             $result_ref['is_found'] = isset($tmp2[$cache_code]);
         }
     }
     # is_not_found
     if (in_array('is_not_found', $fields)) {
         if ($user_id == null) {
             throw new BadRequest("Either 'user_uuid' parameter OR Level 3 Authentication is required to access 'is_not_found' field.");
         }
         $tmp = Db::select_column("\n                select c.wp_oc\n                from\n                    caches c,\n                    cache_logs cl\n                where\n                    c.cache_id = cl.cache_id\n                    and cl.type = '" . mysql_real_escape_string(Okapi::logtypename2id("Didn't find it")) . "'\n                    and cl.user_id = '" . mysql_real_escape_string($user_id) . "'\n                    " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "and cl.deleted = 0" : "") . "\n            ");
         $tmp2 = array();
         foreach ($tmp as $cache_code) {
             $tmp2[$cache_code] = true;
         }
         foreach ($results as $cache_code => &$result_ref) {
             $result_ref['is_not_found'] = isset($tmp2[$cache_code]);
         }
     }
     # is_watched
     if (in_array('is_watched', $fields)) {
         if ($request->token == null) {
             throw new BadRequest("Level 3 Authentication is required to access 'is_watched' field.");
         }
         $tmp = Db::select_column("\n                select c.wp_oc\n                from\n                    caches c,\n                    cache_watches cw\n                where\n                    c.cache_id = cw.cache_id\n                    and cw.user_id = '" . mysql_real_escape_string($request->token->user_id) . "'\n            ");
         $tmp2 = array();
         foreach ($tmp as $cache_code) {
             $tmp2[$cache_code] = true;
         }
         # OCDE caches can also be indirectly watched by watching cache lists:
         if (Settings::get('OC_BRANCH') == 'oc.de') {
             $tmp = Db::select_column("\n                  select c.wp_oc\n                  from\n                      caches c,\n                      cache_list_items cli,\n                      cache_list_watches clw\n                  where\n                      cli.cache_id = c.cache_id\n                      and clw.cache_list_id = cli.cache_list_id\n                      and clw.user_id = '" . mysql_real_escape_string($request->token->user_id) . "'\n              ");
             foreach ($tmp as $cache_code) {
                 $tmp2[$cache_code] = true;
             }
         }
         foreach ($results as $cache_code => &$result_ref) {
             $result_ref['is_watched'] = isset($tmp2[$cache_code]);
         }
     }
     # is_ignored
     if (in_array('is_ignored', $fields)) {
         if ($request->token == null) {
             throw new BadRequest("Level 3 Authentication is required to access 'is_ignored' field.");
         }
         $tmp = Db::select_column("\n                select c.wp_oc\n                from\n                    caches c,\n                    cache_ignore ci\n                where\n                    c.cache_id = ci.cache_id\n                    and ci.user_id = '" . mysql_real_escape_string($request->token->user_id) . "'\n            ");
         $tmp2 = array();
         foreach ($tmp as $cache_code) {
             $tmp2[$cache_code] = true;
         }
         foreach ($results as $cache_code => &$result_ref) {
             $result_ref['is_ignored'] = isset($tmp2[$cache_code]);
         }
     }
     # Descriptions and hints.
     if (in_array('description', $fields) || in_array('descriptions', $fields) || in_array('short_description', $fields) || in_array('short_descriptions', $fields) || in_array('hint', $fields) || in_array('hints', $fields) || in_array('hint2', $fields) || in_array('hints2', $fields)) {
         # At first, we will fill all those fields, even if user requested just one
         # of them. We will chop off the unwanted ones at the end.
         foreach ($results as &$result_ref) {
             $result_ref['short_descriptions'] = new ArrayObject();
             $result_ref['descriptions'] = new ArrayObject();
             $result_ref['hints'] = new ArrayObject();
             $result_ref['hints2'] = new ArrayObject();
         }
         # Get cache descriptions and hints.
         $rs = Db::query("\n                select cache_id, language, `desc`, short_desc, hint\n                from cache_desc\n                where cache_id in ('" . implode("','", array_map('mysql_real_escape_string', array_keys($cacheid2wptcode))) . "')\n            ");
         while ($row = mysql_fetch_assoc($rs)) {
             $cache_code = $cacheid2wptcode[$row['cache_id']];
             // strtolower - ISO 639-1 codes are lowercase
             if ($row['desc']) {
                 /* Note, that the "owner" and "internal_id" fields are automatically included,
                  * whenever the cache description is included. */
                 $tmp = Okapi::fix_oc_html($row['desc']);
                 if ($attribution_append != 'none') {
                     $tmp .= "\n<p><em>" . self::get_cache_attribution_note($row['cache_id'], strtolower($row['language']), $langpref, $results[$cache_code]['owner'], $attribution_append) . "</em></p>";
                 }
                 $results[$cache_code]['descriptions'][strtolower($row['language'])] = $tmp;
             }
             if ($row['short_desc']) {
                 $results[$cache_code]['short_descriptions'][strtolower($row['language'])] = $row['short_desc'];
             }
             if ($row['hint']) {
                 $results[$cache_code]['hints'][strtolower($row['language'])] = $row['hint'];
                 $results[$cache_code]['hints2'][strtolower($row['language'])] = htmlspecialchars_decode(mb_ereg_replace("<br />", "", $row['hint']), ENT_COMPAT);
             }
         }
         foreach ($results as &$result_ref) {
             $result_ref['short_description'] = Okapi::pick_best_language($result_ref['short_descriptions'], $langpref);
             $result_ref['description'] = Okapi::pick_best_language($result_ref['descriptions'], $langpref);
             $result_ref['hint'] = Okapi::pick_best_language($result_ref['hints'], $langpref);
             $result_ref['hint2'] = Okapi::pick_best_language($result_ref['hints2'], $langpref);
         }
         # Remove unwanted fields.
         foreach (array('short_description', 'short_descriptions', 'description', 'descriptions', 'hint', 'hints', 'hint2', 'hints2') as $field) {
             if (!in_array($field, $fields)) {
                 foreach ($results as &$result_ref) {
                     unset($result_ref[$field]);
                 }
             }
         }
     }
     # Images.
     if (in_array('images', $fields) || in_array('preview_image', $fields)) {
         if (in_array('images', $fields)) {
             foreach ($results as &$result_ref) {
                 $result_ref['images'] = array();
             }
         }
         if (in_array('preview_image', $fields)) {
             foreach ($results as &$result_ref) {
                 $result_ref['preview_image'] = null;
             }
         }
         if (Db::field_exists('pictures', 'mappreview')) {
             $preview_field = "mappreview";
         } else {
             $preview_field = "0";
         }
         $sql = "\n                select object_id, uuid, url, title, spoiler, " . $preview_field . " as preview\n                from pictures\n                where\n                    object_id in ('" . implode("','", array_map('mysql_real_escape_string', array_keys($cacheid2wptcode))) . "')\n                    and display = 1\n                    and object_type = 2\n                    and unknown_format = 0\n            ";
         if (Settings::get('OC_BRANCH') == 'oc.pl') {
             // oc.pl installation allows arbitrary order of the geocache's images
             $sql .= "order by object_id, seq, date_created";
         } else {
             $sql .= "order by object_id, date_created";
         }
         $rs = Db::query($sql);
         unset($sql);
         $prev_cache_code = null;
         while ($row = mysql_fetch_assoc($rs)) {
             $cache_code = $cacheid2wptcode[$row['object_id']];
             if ($cache_code != $prev_cache_code) {
                 # Group images together. Images must have unique captions within one cache.
                 self::reset_unique_captions();
                 $prev_cache_code = $cache_code;
             }
             if (Settings::get('OC_BRANCH') == 'oc.de') {
                 $object_type_param = 'type=2&';
             } else {
                 $object_type_param = '';
             }
             $image = array('uuid' => $row['uuid'], 'url' => $row['url'], 'thumb_url' => Settings::get('SITE_URL') . 'thumbs.php?' . $object_type_param . 'uuid=' . $row['uuid'], 'caption' => $row['title'], 'unique_caption' => self::get_unique_caption($row['title']), 'is_spoiler' => $row['spoiler'] ? true : false);
             if (in_array('images', $fields)) {
                 $results[$cache_code]['images'][] = $image;
             }
             if ($row['preview'] != 0 && in_array('preview_image', $fields)) {
                 $results[$cache_code]['preview_image'] = $image;
             }
         }
     }
     # A-codes and attrnames
     if (in_array('attr_acodes', $fields) || in_array('attrnames', $fields)) {
         # Either case, we'll need acodes. If the user didn't want them,
         # remember to remove them later.
         if (!in_array('attr_acodes', $fields)) {
             $fields_to_remove_later[] = 'attr_acodes';
         }
         foreach ($results as &$result_ref) {
             $result_ref['attr_acodes'] = array();
         }
         # Load internal_attr_id => acode mapping.
         require_once $GLOBALS['rootpath'] . 'okapi/services/attrs/attr_helper.inc.php';
         $internal2acode = AttrHelper::get_internal_id_to_acode_mapping();
         $rs = Db::query("\n                select cache_id, attrib_id\n                from caches_attributes\n                where cache_id in ('" . implode("','", array_map('mysql_real_escape_string', array_keys($cacheid2wptcode))) . "')\n            ");
         while ($row = mysql_fetch_assoc($rs)) {
             $cache_code = $cacheid2wptcode[$row['cache_id']];
             $attr_internal_id = $row['attrib_id'];
             if (!isset($internal2acode[$attr_internal_id])) {
                 # Unknown attribute. Ignore.
                 continue;
             }
             $results[$cache_code]['attr_acodes'][] = $internal2acode[$attr_internal_id];
         }
         # Now, each cache object has a list of its acodes. We can get
         # the attrnames now.
         if (in_array('attrnames', $fields)) {
             $acode2bestname = AttrHelper::get_acode_to_name_mapping($langpref);
             foreach ($results as &$result_ref) {
                 $result_ref['attrnames'] = array();
                 foreach ($result_ref['attr_acodes'] as $acode) {
                     $result_ref['attrnames'][] = $acode2bestname[$acode];
                 }
             }
         }
     }
     # Latest log entries.
     if (in_array('latest_logs', $fields)) {
         foreach ($results as &$result_ref) {
             $result_ref['latest_logs'] = array();
         }
         # Get all log IDs with dates. Sort in groups. Filter out latest ones. This is the fastest
         # technique I could think of...
         $rs = Db::query("\n                select cache_id, uuid, date\n                from cache_logs\n                where\n                    cache_id in ('" . implode("','", array_map('mysql_real_escape_string', array_keys($cacheid2wptcode))) . "')\n                    and " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "deleted = 0" : "true") . "\n                order by cache_id, date desc, date_created desc\n            ");
         $loguuids = array();
         $log2cache_map = array();
         if ($lpc !== null) {
             # User wants some of the latest logs.
             $tmp = array();
             while ($row = mysql_fetch_assoc($rs)) {
                 $tmp[$row['cache_id']][] = $row;
             }
             foreach ($tmp as $cache_key => &$rowslist_ref) {
                 usort($rowslist_ref, function ($rowa, $rowb) {
                     # (reverse order by date)
                     return $rowa['date'] < $rowb['date'] ? 1 : ($rowa['date'] == $rowb['date'] ? 0 : -1);
                 });
                 for ($i = 0; $i < min(count($rowslist_ref), $lpc); $i++) {
                     $loguuids[] = $rowslist_ref[$i]['uuid'];
                     $log2cache_map[$rowslist_ref[$i]['uuid']] = $cacheid2wptcode[$rowslist_ref[$i]['cache_id']];
                 }
             }
         } else {
             # User wants ALL logs.
             while ($row = mysql_fetch_assoc($rs)) {
                 $loguuids[] = $row['uuid'];
                 $log2cache_map[$row['uuid']] = $cacheid2wptcode[$row['cache_id']];
             }
         }
         # We need to retrieve logs/entry for each of the $logids. We do this in groups
         # (there is a limit for log uuids passed to logs/entries method).
         try {
             foreach (Okapi::make_groups($loguuids, 500) as $subset) {
                 $entries = OkapiServiceRunner::call("services/logs/entries", new OkapiInternalRequest($request->consumer, $request->token, array('log_uuids' => implode("|", $subset), 'fields' => $log_fields)));
                 foreach ($subset as $log_uuid) {
                     if ($entries[$log_uuid]) {
                         $results[$log2cache_map[$log_uuid]]['latest_logs'][] = $entries[$log_uuid];
                     }
                 }
             }
         } catch (Exception $e) {
             if ($e instanceof InvalidParam && $e->paramName == 'fields') {
                 throw new InvalidParam('log_fields', $e->whats_wrong_about_it);
             } else {
                 /* Something is wrong with OUR code. */
                 throw new Exception($e);
             }
         }
     }
     # My notes
     if (in_array('my_notes', $fields)) {
         if ($request->token == null) {
             throw new BadRequest("Level 3 Authentication is required to access 'my_notes' field.");
         }
         foreach ($results as &$result_ref) {
             $result_ref['my_notes'] = null;
         }
         if (Settings::get('OC_BRANCH') == 'oc.pl') {
             # OCPL uses cache_notes table to store notes.
             $rs = Db::query("\n                    select cache_id, max(date) as date, group_concat(`desc`) as `desc`\n                    from cache_notes\n                    where\n                        cache_id in ('" . implode("','", array_map('mysql_real_escape_string', array_keys($cacheid2wptcode))) . "')\n                        and user_id = '" . mysql_real_escape_string($request->token->user_id) . "'\n                    group by cache_id\n                ");
         } else {
             # OCDE uses coordinates table (with type == 2) to store notes (this is somewhat weird).
             $rs = Db::query("\n                    select cache_id, null as date, group_concat(description) as `desc`\n                    from coordinates\n                    where\n                        type = 2  -- personal note\n                        and cache_id in ('" . implode("','", array_map('mysql_real_escape_string', array_keys($cacheid2wptcode))) . "')\n                        and user_id = '" . mysql_real_escape_string($request->token->user_id) . "'\n                    group by cache_id\n                ");
         }
         while ($row = mysql_fetch_assoc($rs)) {
             # This one is plain-text. We may add my_notes_html for those who want it in HTML.
             $results[$cacheid2wptcode[$row['cache_id']]]['my_notes'] = strip_tags($row['desc']);
         }
     }
     if (in_array('trackables', $fields)) {
         # Currently we support Geokrety only. But this interface should remain
         # compatible. In future, other trackables might be returned the same way.
         $rs = Db::query("\n                select\n                    gkiw.wp as cache_code,\n                    gki.id as gk_id,\n                    gki.name\n                from\n                    gk_item_waypoint gkiw,\n                    gk_item gki\n                where\n                    gkiw.id = gki.id\n                    and gkiw.wp in ('" . implode("','", array_map('mysql_real_escape_string', $cache_codes)) . "')\n            ");
         $trs = array();
         while ($row = mysql_fetch_assoc($rs)) {
             $trs[$row['cache_code']][] = $row;
         }
         foreach ($results as $cache_code => &$result_ref) {
             $result_ref['trackables'] = array();
             if (!isset($trs[$cache_code])) {
                 continue;
             }
             foreach ($trs[$cache_code] as $t) {
                 $result_ref['trackables'][] = array('code' => 'GK' . str_pad(strtoupper(dechex($t['gk_id'])), 4, "0", STR_PAD_LEFT), 'name' => $t['name'], 'url' => 'http://geokrety.org/konkret.php?id=' . $t['gk_id']);
             }
         }
         unset($trs);
     }
     if (in_array('trackables_count', $fields)) {
         if (in_array('trackables', $fields)) {
             # We already got all trackables data, no need to query database again.
             foreach ($results as $cache_code => &$result_ref) {
                 $result_ref['trackables_count'] = count($result_ref['trackables']);
             }
         } else {
             $rs = Db::query("\n                    select wp as cache_code, count(*) as count\n                    from gk_item_waypoint\n                    where wp in ('" . implode("','", array_map('mysql_real_escape_string', $cache_codes)) . "')\n                    group by wp\n                ");
             $tr_counts = new ArrayObject();
             while ($row = mysql_fetch_assoc($rs)) {
                 $tr_counts[$row['cache_code']] = $row['count'];
             }
             foreach ($results as $cache_code => &$result_ref) {
                 if (isset($tr_counts[$cache_code])) {
                     $result_ref['trackables_count'] = $tr_counts[$cache_code] + 0;
                 } else {
                     $result_ref['trackables_count'] = 0;
                 }
             }
             unset($tr_counts);
         }
     }
     # Alternate/Additional waypoints.
     if (in_array('alt_wpts', $fields)) {
         $internal_wpt_type_id2names = array();
         if (Settings::get('OC_BRANCH') == 'oc.de') {
             $rs = Db::query("\n                    select\n                        ct.id,\n                        LOWER(stt.lang) as language,\n                        stt.`text`\n                    from\n                        coordinates_type ct\n                        left join sys_trans_text stt on stt.trans_id = ct.trans_id\n                ");
             while ($row = mysql_fetch_assoc($rs)) {
                 $internal_wpt_type_id2names[$row['id']][$row['language']] = $row['text'];
             }
             mysql_free_result($rs);
         } else {
             $rs = Db::query("\n                    select id, pl, en\n                    from waypoint_type\n                    where id > 0\n                ");
             while ($row = mysql_fetch_assoc($rs)) {
                 $internal_wpt_type_id2names[$row['id']]['pl'] = $row['pl'];
                 $internal_wpt_type_id2names[$row['id']]['en'] = $row['en'];
             }
         }
         foreach ($results as &$result_ref) {
             $result_ref['alt_wpts'] = array();
         }
         $cache_codes_escaped_and_imploded = "'" . implode("','", array_map('mysql_real_escape_string', array_keys($cacheid2wptcode))) . "'";
         if (Settings::get('OC_BRANCH') == 'oc.pl') {
             # OCPL uses 'waypoints' table to store additional waypoints and defines
             # waypoint types in 'waypoint_type' table.
             # OCPL also have a special 'status' field to denote a hidden waypoint
             # (i.e. final location of a multicache). Such hidden waypoints are not
             # exposed by OKAPI.
             $cacheid2waypoints = Db::select_group_by("cache_id", "\n                    select\n                        cache_id, stage, latitude, longitude, `desc`,\n                        type as internal_type_id,\n                        case type\n                            when 3 then 'Flag, Red'\n                            when 4 then 'Circle with X'\n                            when 5 then 'Parking Area'\n                            else 'Flag, Green'\n                        end as sym,\n                        case type\n                            when 1 then 'physical-stage'\n                            when 2 then 'virtual-stage'\n                            when 3 then 'final'\n                            when 4 then 'poi'\n                            when 5 then 'parking'\n                            else 'other'\n                        end as okapi_type\n                    from waypoints\n                    where\n                        cache_id in (" . $cache_codes_escaped_and_imploded . ")\n                        and status = 1\n                    order by cache_id, stage, `desc`\n                ");
         } else {
             # OCDE uses 'coordinates' table (with type=1) to store additional waypoints
             # and defines waypoint types in 'coordinates_type' table.
             # All additional waypoints are public.
             $cacheid2waypoints = Db::select_group_by("cache_id", "\n                    select\n                        cache_id,\n                        false as stage,\n                        latitude, longitude,\n                        description as `desc`,\n                        subtype as internal_type_id,\n                        case subtype\n                            when 1 then 'Parking Area'\n                            when 3 then 'Flag, Blue'\n                            when 4 then 'Circle with X'\n                            when 5 then 'Diamond, Green'\n                            else 'Flag, Green'\n                        end as sym,\n                        case subtype\n                            when 1 then 'parking'\n                            when 2 then 'stage'\n                            when 3 then 'path'\n                            when 4 then 'final'\n                            when 5 then 'poi'\n                            else 'other'\n                        end as okapi_type\n                    from coordinates\n                    where\n                        type = 1\n                        and cache_id in (" . $cache_codes_escaped_and_imploded . ")\n                    order by cache_id, id\n                ");
         }
         foreach ($cacheid2waypoints as $cache_id => $waypoints) {
             $cache_code = $cacheid2wptcode[$cache_id];
             $wpt_format = $cache_code . "-%0" . strlen(count($waypoints)) . "d";
             $index = 0;
             foreach ($waypoints as $row) {
                 if (!isset($internal_wpt_type_id2names[$row['internal_type_id']])) {
                     # Sanity check. Waypoints of undefined type won't be accessible via OKAPI.
                     # See issue 219.
                     continue;
                 }
                 $index++;
                 $results[$cache_code]['alt_wpts'][] = array('name' => sprintf($wpt_format, $index), 'location' => round($row['latitude'], 6) . "|" . round($row['longitude'], 6), 'type' => $row['okapi_type'], 'type_name' => Okapi::pick_best_language($internal_wpt_type_id2names[$row['internal_type_id']], $langpref), 'sym' => $row['sym'], 'description' => ($row['stage'] ? _("Stage") . " " . $row['stage'] . ": " : "") . $row['desc']);
             }
         }
         # Issue #298 - User coordinates implemented in oc.pl
         # Issue #305 - User coordinates implemented in oc.de
         if ($request->token != null) {
             # Query DB for user provided coordinates
             if (Settings::get('OC_BRANCH') == 'oc.pl') {
                 $cacheid2user_coords = Db::select_group_by('cache_id', "\n                        select\n                            cache_id, longitude, latitude\n                        from cache_mod_cords\n                        where\n                            cache_id in ({$cache_codes_escaped_and_imploded})\n                            and user_id = '" . mysql_real_escape_string($request->token->user_id) . "'\n                    ");
             } else {
                 # oc.de
                 $cacheid2user_coords = Db::select_group_by('cache_id', "\n                        select\n                            cache_id, longitude, latitude\n                        from coordinates\n                        where\n                            cache_id in ({$cache_codes_escaped_and_imploded})\n                            and user_id = '" . mysql_real_escape_string($request->token->user_id) . "'\n                            and type = 2\n                            and longitude != 0\n                            and latitude != 0\n                    ");
             }
             foreach ($cacheid2user_coords as $cache_id => $waypoints) {
                 $cache_code = $cacheid2wptcode[$cache_id];
                 foreach ($waypoints as $row) {
                     # there should be only one user waypoint per cache...
                     $results[$cache_code]['alt_wpts'][] = array('name' => $cache_code . '-USER-COORDS', 'location' => round($row['latitude'], 6) . "|" . round($row['longitude'], 6), 'type' => 'user-coords', 'type_name' => _("User location"), 'sym' => 'Block, Green', 'description' => sprintf(_("Your own custom coordinates for the %s geocache"), $cache_code));
                 }
             }
         }
     }
     # Country and/or state.
     if (in_array('country', $fields) || in_array('state', $fields)) {
         $countries = array();
         $states = array();
         if (Settings::get('OC_BRANCH') == 'oc.de') {
             # OCDE:
             #  - cache_location entries are created by a cronjob *after* listing the
             #      caches and may not yet exist.
             #  - The state is in adm2 field.
             #  - caches.country overrides cache_location.code1/adm1. If both differ,
             #      cache_location.adm2 to adm4 is invalid and the state unknown.
             #  - OCDE databases may contain caches with invalid country code.
             #      Such errors must be handled gracefully.
             #  - adm1 should always be ignored. Instead, code1 should be translated
             #      into a country name, depending on langpref.
             # build country code translation table
             $rs = Db::query("\n                    select distinct\n                        c.country,\n                        lower(stt.lang) as language,\n                        stt.`text`\n                    from\n                        caches c\n                        inner join countries on countries.short=c.country\n                        inner join sys_trans_text stt on stt.trans_id = countries.trans_id\n                    where\n                        c.wp_oc in ('" . implode("','", array_map('mysql_real_escape_string', $cache_codes)) . "')\n                ");
             $country_codes2names = array();
             while ($row = mysql_fetch_assoc($rs)) {
                 $country_codes2names[$row['country']][$row['language']] = $row['text'];
             }
             mysql_free_result($rs);
             # get geocache countries and states
             $rs = Db::query("\n                    select\n                        c.wp_oc as cache_code,\n                        c.country as country_code,\n                        ifnull(if(c.country<>cl.code1,'',cl.adm2),'') as state\n                    from\n                        caches c\n                        left join cache_location cl on c.cache_id = cl.cache_id\n                    where\n                        c.wp_oc in ('" . implode("','", array_map('mysql_real_escape_string', $cache_codes)) . "')\n                ");
             while ($row = mysql_fetch_assoc($rs)) {
                 if (!isset($country_codes2names[$row['country_code']])) {
                     $countries[$row['cache_code']] = '';
                 } else {
                     $countries[$row['cache_code']] = Okapi::pick_best_language($country_codes2names[$row['country_code']], $langpref);
                 }
                 $states[$row['cache_code']] = $row['state'];
             }
             mysql_free_result($rs);
         } else {
             # OCPL:
             #  - cache_location data is entered by the user.
             #  - The state is in adm3 field.
             # get geocache countries and states
             $rs = Db::query("\n                    select\n                        c.wp_oc as cache_code,\n                        cl.adm1 as country,\n                        cl.adm3 as state\n                    from\n                        caches c,\n                        cache_location cl\n                    where\n                        c.wp_oc in ('" . implode("','", array_map('mysql_real_escape_string', $cache_codes)) . "')\n                        and c.cache_id = cl.cache_id\n                ");
             while ($row = mysql_fetch_assoc($rs)) {
                 $countries[$row['cache_code']] = $row['country'];
                 $states[$row['cache_code']] = $row['state'];
             }
             mysql_free_result($rs);
         }
         if (in_array('country', $fields)) {
             foreach ($results as $cache_code => &$row_ref) {
                 $row_ref['country'] = isset($countries[$cache_code]) ? $countries[$cache_code] : null;
             }
         }
         if (in_array('state', $fields)) {
             foreach ($results as $cache_code => &$row_ref) {
                 $row_ref['state'] = isset($states[$cache_code]) ? $states[$cache_code] : null;
             }
         }
         unset($countries);
         unset($states);
     }
     # Attribution note
     if (in_array('attribution_note', $fields)) {
         /* Note, that the "owner" and "internal_id" fields are automatically included,
          * whenever the attribution_note is included. */
         foreach ($results as $cache_code => &$result_ref) {
             $result_ref['attribution_note'] = self::get_cache_attribution_note($result_ref['internal_id'], $langpref[0], $langpref, $results[$cache_code]['owner'], 'full');
         }
     }
     # Protection areas
     if (in_array('protection_areas', $fields)) {
         $cache_ids_escaped_and_imploded = "'" . implode("','", array_map('mysql_real_escape_string', array_keys($cacheid2wptcode))) . "'";
         if (Settings::get('OC_BRANCH') == 'oc.de') {
             $rs = Db::query("\n                    select\n                        c.wp_oc as cache_code,\n                        npa_types.name as type,\n                        npa_areas.name as name\n                    from\n                        caches c\n                        inner join cache_npa_areas on cache_npa_areas.cache_id=c.cache_id\n                        inner join npa_areas on cache_npa_areas.npa_id = npa_areas.id\n                        inner join npa_types on npa_areas.type_id = npa_types.id\n                    where\n                        c.cache_id in (" . $cache_ids_escaped_and_imploded . ")\n                    group by npa_areas.type_id, npa_areas.name\n                    order by npa_types.ordinal\n                ");
         } else {
             if (Settings::get('ORIGIN_URL') == 'http://opencaching.pl/' || Settings::get('ORIGIN_URL') == 'http://www.opencaching.nl/') {
                 # Current OCPL table definitions use collation 'latin1' for parkipl
                 # and 'utf8' for np_areas. Union needs identical collations.
                 # To be sure, we convert both to utf8.
                 #
                 # TODO: use DB_CHARSET setting instead of literal 'utf8'
                 $rs = Db::query("\n                    select\n                        c.wp_oc as cache_code,\n                        '" . _('National Park / Landscape') . "' as type,\n                        CONVERT(parkipl.name USING utf8) as name\n                    from\n                        caches c\n                        inner join cache_npa_areas on cache_npa_areas.cache_id=c.cache_id\n                        inner join parkipl on cache_npa_areas.parki_id=parkipl.id\n                    where\n                        c.cache_id in (" . $cache_ids_escaped_and_imploded . ")\n                        and cache_npa_areas.parki_id != 0\n                    union\n                    select\n                        c.wp_oc as cache_code,\n                        'Natura 2000' as type,\n                        CONVERT(npa_areas.sitename USING utf8) as name\n                    from\n                        caches c\n                        inner join cache_npa_areas on cache_npa_areas.cache_id=c.cache_id\n                        inner join npa_areas on cache_npa_areas.npa_id=npa_areas.id\n                    where\n                        c.cache_id in (" . $cache_ids_escaped_and_imploded . ")\n                        and cache_npa_areas.npa_id != 0\n                    ");
             } else {
                 # OC.US and .UK do not have a 'parkipl' table.
                 # OC.US has a 'us_parks' table instead.
                 # Natura 2000 is Europe-only.
                 $rs = null;
             }
         }
         foreach ($results as &$result_ref) {
             $result_ref['protection_areas'] = array();
         }
         if ($rs) {
             while ($row = mysql_fetch_assoc($rs)) {
                 $results[$row['cache_code']]['protection_areas'][] = array('type' => $row['type'], 'name' => $row['name']);
             }
             mysql_free_result($rs);
         }
     }
     # Check which cache codes were not found and mark them with null.
     foreach ($cache_codes as $cache_code) {
         if (!isset($results[$cache_code])) {
             $results[$cache_code] = null;
         }
     }
     if (count($fields_to_remove_later) > 0) {
         # Some of the fields in $results were added only temporarily
         # (the Consumer did not ask for them). We will remove these fields now.
         foreach ($results as &$result_ref) {
             foreach ($fields_to_remove_later as $field) {
                 unset($result_ref[$field]);
             }
         }
     }
     # Order the results in the same order as the input codes were given.
     # This might come in handy for languages which support ordered dictionaries
     # (especially with conjunction with the search_and_retrieve method).
     # See issue#97. PHP dictionaries (assoc arrays) are ordered structures,
     # so we just have to rewrite it (sequentially).
     $ordered_results = new ArrayObject();
     foreach ($cache_codes as $cache_code) {
         $ordered_results[$cache_code] = $results[$cache_code];
     }
     /* Handle OCPL's "access logs" feature. */
     if (Settings::get('OC_BRANCH') == 'oc.pl' && Settings::get('OCPL_ENABLE_GEOCACHE_ACCESS_LOGS')) {
         $cache_ids = array_keys($cacheid2wptcode);
         /* Log this event only if some specific fields were accessed. */
         if (in_array('location', $fields) && count(array_intersect(array('hint', 'hints', 'hint2', 'hints2', 'description', 'descriptions'), $fields)) > 0) {
             require_once $GLOBALS['rootpath'] . 'okapi/lib/ocpl_access_logs.php';
             \okapi\OCPLAccessLogs::log_geocache_access($request, $cache_ids);
         }
     }
     return Okapi::formatted_response($request, $ordered_results);
 }
예제 #29
0
 public static function call(OkapiRequest $request)
 {
     $cache_codes = $request->get_parameter('cache_codes');
     if ($cache_codes === null) {
         throw new ParamMissing('cache_codes');
     }
     # Issue 106 requires us to allow empty list of cache codes to be passed into this method.
     # All of the queries below have to be ready for $cache_codes to be empty!
     $langpref = $request->get_parameter('langpref');
     if (!$langpref) {
         $langpref = "en|" . Settings::get('SITELANG');
     }
     $images = $request->get_parameter('images');
     if (!$images) {
         $images = "all";
     }
     if (!in_array($images, array("none", "all", "spoilers", "nonspoilers"))) {
         throw new InvalidParam('images');
     }
     $format = $request->get_parameter('caches_format');
     if (!$format) {
         $format = "gpx";
     }
     if (!in_array($format, array("gpx", "ggz"))) {
         throw new InvalidParam('caches_format');
     }
     $location_source = $request->get_parameter('location_source');
     $location_change_prefix = $request->get_parameter('location_change_prefix');
     # Start creating ZIP archive.
     $response = new OkapiZIPHttpResponse();
     # Include a GPX/GGZ file compatible with Garmin devices. It should include all
     # Geocaching.com (groundspeak:) and Opencaching.com (ox:) extensions. It will
     # also include image references (actual images will be added as separate files later)
     # and personal data (if the method was invoked using Level 3 Authentication).
     switch ($format) {
         case 'gpx':
             $data_filename = "Garmin/GPX/opencaching" . time() . rand(100000, 999999) . ".gpx";
             $data_method = 'services/caches/formatters/gpx';
             $data_use_compression = true;
             break;
         case 'ggz':
             $data_filename = "Garmin/GGZ/opencaching" . time() . rand(100000, 999999) . ".ggz";
             $data_method = 'services/caches/formatters/ggz';
             $data_use_compression = false;
             break;
     }
     $response->zip->FileAdd($data_filename, OkapiServiceRunner::call($data_method, new OkapiInternalRequest($request->consumer, $request->token, array('cache_codes' => $cache_codes, 'langpref' => $langpref, 'ns_ground' => 'true', 'ns_ox' => 'true', 'images' => 'ox:all', 'attrs' => 'ox:tags', 'trackables' => 'desc:count', 'alt_wpts' => 'true', 'recommendations' => 'desc:count', 'latest_logs' => 'true', 'lpc' => 'all', 'my_notes' => $request->token != null ? "desc:text" : "none", 'location_source' => $location_source, 'location_change_prefix' => $location_change_prefix)))->get_body(), clsTbsZip::TBSZIP_STRING, $data_use_compression);
     # Then, include all the images.
     $caches = OkapiServiceRunner::call('services/caches/geocaches', new OkapiInternalRequest($request->consumer, $request->token, array('cache_codes' => $cache_codes, 'langpref' => $langpref, 'fields' => "images")));
     if (count($caches) > 50) {
         throw new InvalidParam('cache_codes', "The maximum number of caches allowed to be downloaded with this method is 50.");
     }
     if ($images != 'none') {
         $supported_extensions = array('jpg', 'jpeg', 'gif', 'png', 'bmp');
         foreach ($caches as $cache_code => $dict) {
             $imgs = $dict['images'];
             if (count($imgs) == 0) {
                 continue;
             }
             $dir = "Garmin/GeocachePhotos/" . $cache_code[strlen($cache_code) - 1];
             $dir .= "/" . $cache_code[strlen($cache_code) - 2];
             $dir .= "/" . $cache_code;
             foreach ($imgs as $no => $img) {
                 if ($images == 'spoilers' && !$img['is_spoiler']) {
                     continue;
                 }
                 if ($images == 'nonspoilers' && $img['is_spoiler']) {
                     continue;
                 }
                 $tmp = false;
                 foreach ($supported_extensions as $ext) {
                     if (strtolower(substr($img['url'], strlen($img['url']) - strlen($ext) - 1)) != "." . $ext) {
                         $tmp = true;
                         continue;
                     }
                 }
                 if (!$tmp) {
                     continue;
                 }
                 # unsupported file extension
                 if ($img['is_spoiler']) {
                     $zippath = $dir . "/Spoilers/" . $img['unique_caption'] . ".jpg";
                 } else {
                     $zippath = $dir . "/" . $img['unique_caption'] . ".jpg";
                 }
                 # The safest way would be to use the URL, but that would be painfully slow!
                 # That's why we're trying to access files directly (and fail silently on error).
                 # This was tested on OCPL server only.
                 # Note: Oliver Dietz (oc.de) replied that images with 'local' set to 0 could not
                 # be accessed locally. But all the files have 'local' set to 1 anyway.
                 $syspath = Settings::get('IMAGES_DIR') . "/" . $img['uuid'] . ".jpg";
                 if (file_exists($syspath)) {
                     $response->zip->FileAdd($zippath, $syspath, clsTbsZip::TBSZIP_FILE, false);
                 } else {
                     # If file exists, but does not end with ".jpg", we will create
                     # JPEG version of it and store it in the cache.
                     $cache_key = "jpg#" . $img['uuid'];
                     $jpeg_contents = Cache::get($cache_key);
                     if ($jpeg_contents === null) {
                         foreach ($supported_extensions as $ext) {
                             $syspath_other = Settings::get('IMAGES_DIR') . "/" . $img['uuid'] . "." . $ext;
                             if (file_exists($syspath_other)) {
                                 try {
                                     $image = imagecreatefromstring(file_get_contents($syspath_other));
                                     ob_start();
                                     imagejpeg($image);
                                     $jpeg_contents = ob_get_clean();
                                     imagedestroy($image);
                                 } catch (Exception $e) {
                                     # GD couldn't parse the file. We will skip it, and cache
                                     # the "false" value as the contents. This way, we won't
                                     # attempt to parse it during the next 24 hours.
                                     $jpeg_contents = false;
                                 }
                                 Cache::set($cache_key, $jpeg_contents, 86400);
                                 break;
                             }
                         }
                     }
                     if ($jpeg_contents) {
                         # This can be "null" *or* "false"!
                         $response->zip->FileAdd($zippath, $jpeg_contents, clsTbsZip::TBSZIP_STRING, false);
                     }
                 }
             }
         }
     }
     # The result could be big, but it's created and streamed right
     # to the browser, so it shouldn't hit our memory limit. We also
     # should set a higher time limit, because downloading this response
     # may take some time over slow network connections (and I'm not sure
     # what is the PHP's default way of handling such scenario).
     set_time_limit(600);
     $response->content_type = "application/zip";
     $response->content_disposition = 'attachment; filename="results.zip"';
     return $response;
 }
예제 #30
0
 public static function call(OkapiRequest $request)
 {
     # You may wonder, why there are no parameters like "bbox" or "center" in the
     # "search/all" method. This is *intentional* and should be kept this way.
     # Such parameters would fall in conflict with each other and - in result -
     # make the documentation very fuzzy. That's why they were intentionally
     # left out of the "search/all" method, and put in separate (individual) ones.
     # It's much easier to grasp their meaning this way.
     $tmp = $request->get_parameter('center');
     if (!$tmp) {
         throw new ParamMissing('center');
     }
     $parts = explode('|', $tmp);
     if (count($parts) != 2) {
         throw new InvalidParam('center', "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('center', "'{$part_ref}' is not a valid float number.");
         }
         $part_ref = floatval($part_ref);
     }
     list($center_lat, $center_lon) = $parts;
     if ($center_lat > 90 || $center_lat < -90) {
         throw new InvalidParam('center', "Latitudes have to be within -90..90 range.");
     }
     if ($center_lon > 180 || $center_lon < -180) {
         throw new InvalidParam('center', "Longitudes have to be within -180..180 range.");
     }
     #
     # In the method description, we promised to return caches ordered by the *rough*
     # distance from the center point. We'll use ORDER BY with a simplified distance
     # formula and combine it with the LIMIT clause to get the best results.
     #
     $search_assistant = new SearchAssistant($request);
     $search_assistant->prepare_common_search_params();
     $search_assistant->prepare_location_search_params();
     $distance_formula = Okapi::get_distance_sql($center_lat, $center_lon, $search_assistant->get_latitude_expr(), $search_assistant->get_longitude_expr());
     # 'radius' parameter is optional. If not given, we'll have to calculate the
     # distance for every cache in the database.
     $where_conds = array();
     $radius = null;
     if ($tmp = $request->get_parameter('radius')) {
         if (!preg_match("/^-?[0-9]+(\\.?[0-9]*)\$/", $tmp)) {
             throw new InvalidParam('radius', "'{$tmp}' is not a valid float number.");
         }
         $radius = floatval($tmp);
         # is given in kilometers
         if ($radius <= 0) {
             throw new InvalidParam('radius', "Has to be a positive number.");
         }
         # Apply a latitude-range prefilter if it looks promising.
         # See https://github.com/opencaching/okapi/issues/363 for more info.
         $optimization_radius = 100;
         # in kilometers, optimized for Opencaching.de
         $km2degrees_upper_estimate_factor = 0.01;
         if ($radius <= $optimization_radius) {
             $radius_degrees = $radius * $km2degrees_upper_estimate_factor;
             $where_conds[] = "\n                    caches.latitude >= '" . Db::escape_string($center_lat - $radius_degrees) . "'\n                    and caches.latitude <= '" . Db::escape_string($center_lat + $radius_degrees) . "'\n                    ";
         }
         $radius *= 1000;
         # convert from kilometers to meters
         $where_conds[] = "{$distance_formula} <= '" . Db::escape_string($radius) . "'";
     }
     $search_params = $search_assistant->get_search_params();
     $search_params['where_conds'] = array_merge($where_conds, $search_params['where_conds']);
     $search_params['caches_indexhint'] = "use index (latitude)";
     $search_params['order_by'][] = $distance_formula;
     # not replaced; added to the end!
     $search_assistant->set_search_params($search_params);
     $result = $search_assistant->get_common_search_result();
     if ($radius == null) {
         # 'more' is meaningless in this case, we'll remove it.
         unset($result['more']);
     }
     return Okapi::formatted_response($request, $result);
 }