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); }
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); }
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); }
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); }
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; }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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; }
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) { $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); }
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; }
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) { $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); }
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); }
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); }
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); }
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); }
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); }
/** * 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; }
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); }
/** * 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(); } }
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); } }
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); }
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; }
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); }