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) { # 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 get_installations() { $installations = OkapiServiceRunner::call("services/apisrv/installations", new OkapiInternalRequest(new OkapiInternalConsumer(), null, array())); foreach ($installations as &$inst_ref) { $inst_ref['selected'] = $inst_ref['site_url'] == Settings::get('SITE_URL'); } return $installations; }
/** * This works like service_call with two exceptions: 1. It passes all your * current HTTP request headers to OKAPI (which can make use of them in * terms of caching), 2. It outputs the service response directly, instead * of returning it. */ public static function service_display($service_name, $user_id_or_null, $parameters) { $request = new OkapiInternalRequest(new OkapiFacadeConsumer(), $user_id_or_null !== null ? new OkapiFacadeAccessToken($user_id_or_null) : null, $parameters); $request->perceive_as_http_request = true; if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { $request->etag = $_SERVER['HTTP_IF_NONE_MATCH']; } $response = OkapiServiceRunner::call($service_name, $request); $response->display(); }
public static function call($methodname) { require_once $GLOBALS['rootpath'] . 'okapi/service_runner.php'; if (!OkapiServiceRunner::exists($methodname)) { throw new BadRequest("Method '{$methodname}' does not exist. " . "See OKAPI docs at " . Settings::get('SITE_URL') . "okapi/"); } $options = OkapiServiceRunner::options($methodname); $request = new OkapiHttpRequest($options); return OkapiServiceRunner::call($methodname, $request); }
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() { require_once $GLOBALS['rootpath'] . 'okapi/service_runner.php'; require_once $GLOBALS['rootpath'] . 'okapi/views/menu.inc.php'; $vars = array('menu' => OkapiMenu::get_menu_html("introduction.html"), 'okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'site_url' => Settings::get('SITE_URL'), 'method_index' => OkapiServiceRunner::call('services/apiref/method_index', new OkapiInternalRequest(new OkapiInternalConsumer(), null, array())), 'installations' => OkapiMenu::get_installations(), 'okapi_rev' => Okapi::$version_number); $response = new OkapiHttpResponse(); $response->content_type = "text/html; charset=utf-8"; ob_start(); include 'introduction.tpl.php'; $response->body = ob_get_clean(); return $response; }
public static function call() { # By default, this view is turned off in the urls.php file. # This view is for debugging TileMap performace only! set_time_limit(0); header("Content-Type: text/plain; charset=utf-8"); $user_id = $_GET['u']; self::out("Yo. I'm {$user_id}.\n\n"); while (true) { srand(floor(time() / 10)); $mode2 = rand(0, 9) <= 7; if ($mode2) { $row = Db::select_row("\n select z, x, y\n from okapi_tile_status\n where status = 2 and z < 20\n order by rand()\n limit 1;\n "); $z = $row['z'] + 1; $x = $row['x'] << 1; $y = $row['y'] << 1; $x += rand(0, 1); $y += rand(0, 1); } else { $z = rand(5, 21); $x = rand(0, (1 << $z) - 1); $y = rand(0, (1 << $z) - 1); } $tiles = array(); for ($xx = $x; $xx < $x + 4; $xx++) { for ($yy = $y; $yy < $y + 4; $yy++) { $tiles[] = array($xx, $yy); } } srand(); shuffle($tiles); foreach ($tiles as $tile) { list($x, $y) = $tile; self::out("Loading " . str_pad("({$z}, {$x}, {$y})... ", 30)); $time_started = microtime(true); try { $response = OkapiServiceRunner::call('services/caches/map/tile', new OkapiInternalRequest(new OkapiInternalConsumer(), new OkapiInternalAccessToken($user_id), array('z' => "{$z}", 'x' => "{$x}", 'y' => "{$y}"))); $runtime = microtime(true) - $time_started; $ds = floor($runtime * 100); self::out(str_repeat("#", $ds) . " "); $b = floor(strlen($response->get_body()) / 256); self::out(str_repeat("@", $b) . "\n"); } catch (Exception $e) { self::out("\n\n" . OkapiExceptionHandler::get_exception_info($e)); die; } } } }
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) { $methodnames = OkapiServiceRunner::$all_names; sort($methodnames); $cache_key = "api_ref/method_index#" . md5(implode("#", $methodnames)); $results = Cache::get($cache_key); if ($results == null) { $results = array(); foreach ($methodnames as $methodname) { $info = OkapiServiceRunner::call('services/apiref/method', new OkapiInternalRequest(new OkapiInternalConsumer(), null, array('name' => $methodname))); $results[] = array('name' => $info['name'], 'brief_description' => $info['brief_description']); } Cache::set($cache_key, $results, 3600); } 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) { # 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) { $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($methodname) { require_once $GLOBALS['rootpath'] . 'okapi/service_runner.php'; require_once $GLOBALS['rootpath'] . 'okapi/views/menu.inc.php'; try { $method = OkapiServiceRunner::call('services/apiref/method', new OkapiInternalRequest(null, null, array('name' => $methodname))); } catch (BadRequest $e) { throw new Http404(); } $vars = array('method' => $method, 'menu' => OkapiMenu::get_menu_html($methodname . ".html"), 'okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'installations' => OkapiMenu::get_installations(), 'okapi_rev' => Okapi::$revision); $response = new OkapiHttpResponse(); $response->content_type = "text/html; charset=utf-8"; ob_start(); include 'method_doc.tpl.php'; $response->body = ob_get_clean(); return $response; }
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 get_installations() { $installations = OkapiServiceRunner::call("services/apisrv/installations", new OkapiInternalRequest(new OkapiInternalConsumer(), null, array())); $site_url = Settings::get('SITE_URL'); foreach ($installations as &$inst_ref) { # $inst_ref['site_url'] and $site_url can have different protocols # (http / https). We compare only the domain parts and use # $site_url (which has the current request's protocol) for the menu # so that the menu works properly. if (self::domains_are_equal($inst_ref['site_url'], $site_url)) { $inst_ref['site_url'] = $site_url; $inst_ref['okapi_base_url'] = $site_url . 'okapi/'; $inst_ref['selected'] = true; } else { $inst_ref['selected'] = false; } } return $installations; }
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) { $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); }
private static function handle_geocache_replace($c) { # Check if any relevant geocache attributes have changed. # We will pick up "our" copy of the cache from zero-zoom level. try { $cache = OkapiServiceRunner::call("services/caches/geocache", new OkapiInternalRequest(new OkapiInternalConsumer(), null, array('cache_code' => $c['object_key']['code'], 'fields' => 'internal_id|code|name|location|type|status|rating|recommendations|founds|trackables_count'))); } catch (InvalidParam $e) { # Unprobable, but possible. Ignore changelog entry. return; } # Fetch our copy of the cache. $ours = mysql_fetch_row(Db::query("\n select cache_id, z21x, z21y, status, type, rating, flags, name_crc\n from okapi_tile_caches\n where\n z=0\n and cache_id = '" . mysql_real_escape_string($cache['internal_id']) . "'\n ")); # Caches near the poles caused our computations to break here. We will # ignore such caches! list($lat, $lon) = explode("|", $cache['location']); if (floatval($lat) >= 89.98999999999999 || floatval($lat) <= -89.98999999999999) { if ($ours) { self::remove_geocache_from_cached_tiles($ours[0]); } return; } # Compute the new row for okapi_tile_caches. Compare with the old one. $theirs = TileTree::generate_short_row($cache); if (!$ours) { # Aaah, a new geocache! How nice... ;) self::add_geocache_to_cached_tiles($theirs); } elseif ($ours[1] != $theirs[1] || $ours[2] != $theirs[2]) { # Location changed. self::remove_geocache_from_cached_tiles($ours[0]); self::add_geocache_to_cached_tiles($theirs); } elseif ($ours != $theirs) { self::update_geocache_attributes_in_cached_tiles($theirs); } else { # No need to update anything. This is very common (i.e. when the # cache was simply found, not actually changed). Replicate module generates # many updates which do not influence our cache. } }
public static function call(OkapiRequest $request) { # Read the parameters. $langpref = $request->get_parameter('langpref'); if (!$langpref) { $langpref = "en"; } $fields = $request->get_parameter('fields'); if (!$fields) { $fields = "name"; } $only_locally_used = $request->get_parameter('only_locally_used'); if (!$only_locally_used) { $only_locally_used = "false"; } $only_locally_used = $only_locally_used == "true"; # Get the list of attributes and filter the A-codes based on the # parameters. require_once 'attr_helper.inc.php'; $attrdict = AttrHelper::get_attrdict(); $acodes = array(); foreach ($attrdict as $acode => &$attr_ref) { if ($only_locally_used && $attr_ref['internal_id'] === null) { /* Skip. */ continue; } $acodes[] = $acode; } # Retrieve the attribute objects and return the results. if (count($acodes) > 0) { $params = array('acodes' => implode("|", $acodes), 'langpref' => $langpref, 'fields' => $fields); $results = OkapiServiceRunner::call('services/attrs/attributes', new OkapiInternalRequest($request->consumer, $request->token, $params)); } else { $results = new ArrayObject(); } return Okapi::formatted_response($request, $results); }
public static function call(OkapiRequest $request) { $internal_ids = $request->get_parameter('internal_ids'); if (!$internal_ids) { throw new ParamMissing('internal_ids'); } $internal_ids = explode("|", $internal_ids); if (count($internal_ids) > 500) { throw new InvalidParam('internal_ids', "Maximum allowed number of referenced users " . "is 500. You provided " . count($internal_ids) . " references."); } $fields = $request->get_parameter('fields'); if (!$fields) { throw new ParamMissing('fields'); } # There's no need to validate the fields parameter as the 'users' # method does this (it will raise a proper exception on invalid values). $rs = Db::query("\n select user_id, uuid\n from user\n where user_id in ('" . implode("','", array_map('mysql_real_escape_string', $internal_ids)) . "')\n "); $internalid2useruuid = array(); while ($row = mysql_fetch_assoc($rs)) { $internalid2useruuid[$row['user_id']] = $row['uuid']; } mysql_free_result($rs); # Retrieve data on given user_uuids. $id_results = OkapiServiceRunner::call('services/users/users', new OkapiInternalRequest($request->consumer, $request->token, array('user_uuids' => implode("|", array_values($internalid2useruuid)), 'fields' => $fields))); # Map user_uuids to internal_ids. Also check which internal_ids were not found # and mark them with null. $results = array(); foreach ($internal_ids as $internal_id) { if (!isset($internalid2useruuid[$internal_id])) { $results[$internal_id] = null; } else { $results[$internal_id] = $id_results[$internalid2useruuid[$internal_id]]; } } return Okapi::formatted_response($request, $results); }
/** * Register new OKAPI Consumer, send him an email with his key-pair, etc. * This method does not verify parameter values, check if they are in * a correct format prior the execution. */ public static function register_new_consumer($appname, $appurl, $email) { require_once $GLOBALS['rootpath'] . "okapi/service_runner.php"; $consumer = new OkapiConsumer(Okapi::generate_key(20), Okapi::generate_key(40), $appname, $appurl, $email); $sample_cache = OkapiServiceRunner::call("services/caches/search/all", new OkapiInternalRequest($consumer, null, array('limit', 1))); if (count($sample_cache['results']) > 0) { $sample_cache_code = $sample_cache['results'][0]; } else { $sample_cache_code = "CACHECODE"; } # Message for the Consumer. ob_start(); print "This is the key-pair we have created for your application:\n\n"; print "Consumer Key: {$consumer->key}\n"; print "Consumer Secret: {$consumer->secret}\n\n"; print "Note: Consumer Secret is needed only when you intend to use OAuth.\n"; print "You don't need Consumer Secret for Level 1 Authentication.\n\n"; print "Now you can easily access Level 1 OKAPI methods. E.g.:\n"; print Settings::get('SITE_URL') . "okapi/services/caches/geocache?cache_code={$sample_cache_code}&consumer_key={$consumer->key}\n\n"; print "If you plan on using OKAPI for a longer time, then you may want to\n"; print "subscribe to the OKAPI News blog to stay up-to-date:\n"; print "http://opencaching-api.blogspot.com/\n\n"; print "Have fun!\n\n"; print "-- \n"; print "OKAPI Team\n"; Okapi::mail_from_okapi($email, "Your OKAPI Consumer Key", ob_get_clean()); # Message for the Admins. ob_start(); print "Name: {$consumer->name}\n"; print "Developer: {$consumer->email}\n"; print $consumer->url ? "URL: {$consumer->url}\n" : ""; print "Consumer Key: {$consumer->key}\n"; Okapi::mail_admins("New OKAPI app registered!", ob_get_clean()); Db::execute("\n insert into okapi_consumers (`key`, name, secret, url, email, date_created)\n values (\n '" . mysql_real_escape_string($consumer->key) . "',\n '" . mysql_real_escape_string($consumer->name) . "',\n '" . mysql_real_escape_string($consumer->secret) . "',\n '" . mysql_real_escape_string($consumer->url) . "',\n '" . mysql_real_escape_string($consumer->email) . "',\n now()\n );\n "); }
/** * Check if the URL can be safely retrieved. See issue #252. */ private static function requireSafe($url) { require_once $GLOBALS['rootpath'] . 'okapi/service_runner.php'; $installations = OkapiServiceRunner::call("services/apisrv/installations", new OkapiInternalRequest(new OkapiInternalConsumer(), null, array())); $allowed = array(); foreach ($installations as $i) { $allowed_url = $i['okapi_base_url'] . "devel/dbstruct"; $allowed[] = $allowed_url; if ($url == $allowed_url) { return; } } throw new BadRequest("The following URL is not on our whitelist: \"" . $url . "\".\n\n" . "Please use one of the following:\n" . "\"" . implode("\",\n\"", $allowed) . "\"."); }
/** * Generate OKAPI changelog entries. This method will call $feeder_method OKAPI * service with the following parameters: array($feeder_keys_param => implode('|', $key_values), * 'fields' => $fields). Then it will generate the changelog, based on the result. * This looks pretty much the same for various object types, that's why it's here. * * If $use_cache is true, then all the dictionaries from $feeder_method will be also * kept in OKAPI cache, for future comparison. * * In normal mode, update the changelog and don't return anything. * In fulldump mode, return the generated changelog entries *instead* of * updating it. */ private static function generate_changelog_entries($feeder_method, $object_type, $feeder_keys_param, $key_name, $key_values, $fields, $fulldump_mode, $use_cache, $cache_timeout = 86400) { # Retrieve the previous versions of all objects from OKAPI cache. if ($use_cache) { $cache_keys1 = array(); $cache_keys2 = array(); foreach ($key_values as $key) { $cache_keys1[] = 'clog#' . $object_type . '#' . $key; } foreach ($key_values as $key) { $cache_keys2[] = 'clogmd5#' . $object_type . '#' . $key; } $cached_values1 = Cache::get_many($cache_keys1); $cached_values2 = Cache::get_many($cache_keys2); if (!$fulldump_mode) { Cache::delete_many($cache_keys1); Cache::delete_many($cache_keys2); } unset($cache_keys1); unset($cache_keys2); } # Get the current values for objects. Compare them with their previous versions # and generate changelog entries. require_once $GLOBALS['rootpath'] . 'okapi/service_runner.php'; $current_values = OkapiServiceRunner::call($feeder_method, new OkapiInternalRequest(new OkapiInternalConsumer(), null, array($feeder_keys_param => implode("|", $key_values), 'fields' => $fields, 'attribution_append' => 'static'))); $entries = array(); foreach ($current_values as $key => $object) { if ($object !== null) { # Currently, the object exists. if ($use_cache) { # First, compare the cached hash. The hash has much longer lifetime # than the actual cached object. $cached_md5 = $cached_values2['clogmd5#' . $object_type . '#' . $key]; $current_md5 = md5(serialize($object)); if ($cached_md5 == $current_md5) { # The object was not changed since it was last replaced. continue; } $diff = self::get_diff($cached_values1['clog#' . $object_type . '#' . $key], $object); if (count($diff) == 0) { # Md5 differs, but diff does not. Weird, but it can happen # (e.g. just after the md5 extension was introduced, or if # md5 somehow expired before the actual object did). continue; } } $entries[] = array('object_type' => $object_type, 'object_key' => array($key_name => $key), 'change_type' => 'replace', 'data' => $use_cache ? $diff : $object); if ($use_cache) { # Save the last-published state of the object, for future comparison. $cached_values2['clogmd5#' . $object_type . '#' . $key] = $current_md5; $cached_values1['clog#' . $object_type . '#' . $key] = $object; } } else { # Currently, the object does not exist. if ($use_cache && $cached_values1['clog#' . $object_type . '#' . $key] === false) { # No need to delete, we have already published its deletion. continue; } $entries[] = array('object_type' => $object_type, 'object_key' => array($key_name => $key), 'change_type' => 'delete'); if ($use_cache) { # Cache the fact, that the object was deleted. $cached_values2['clogmd5#' . $object_type . '#' . $key] = false; $cached_values1['clog#' . $object_type . '#' . $key] = false; } } } if ($fulldump_mode) { return $entries; } else { # Save the entries to the clog table. if (count($entries) > 0) { $data_values = array(); foreach ($entries as $entry) { $data_values[] = gzdeflate(serialize($entry)); } Db::execute("\n insert into okapi_clog (data)\n values ('" . implode("'),('", array_map('mysql_real_escape_string', $data_values)) . "');\n "); } # Update the values kept in OKAPI cache. if ($use_cache) { Cache::set_many($cached_values1, $cache_timeout); Cache::set_many($cached_values2, null); # make it persistent } } }
public function execute() { ob_start(); $apisrv_stats = OkapiServiceRunner::call('services/apisrv/stats', new OkapiInternalRequest(new OkapiInternalConsumer(), null, array())); $active_apps_count = Db::select_value("\n select count(distinct s.consumer_key)\n from\n okapi_stats_hourly s,\n okapi_consumers c\n where\n s.consumer_key = c.`key`\n and s.period_start > date_add(now(), interval -7 day)\n "); $weekly_stats = Db::select_row("\n select\n sum(s.http_calls) as total_http_calls,\n sum(s.http_runtime) as total_http_runtime\n from okapi_stats_hourly s\n where\n s.consumer_key != 'internal' -- we don't want to exclude 'anonymous' nor 'facade'\n and s.period_start > date_add(now(), interval -7 day)\n "); print "Hello! This is your weekly summary of OKAPI usage.\n\n"; print "Apps active this week: " . $active_apps_count . " out of " . $apisrv_stats['apps_count'] . ".\n"; print "Total of " . $weekly_stats['total_http_calls'] . " requests were made (" . sprintf("%01.1f", $weekly_stats['total_http_runtime']) . " seconds).\n\n"; $consumers = Db::select_all("\n select\n s.consumer_key,\n c.name,\n sum(s.http_calls) as http_calls,\n sum(s.http_runtime) as http_runtime\n from\n okapi_stats_hourly s\n left join okapi_consumers c\n on s.consumer_key = c.`key`\n where s.period_start > date_add(now(), interval -7 day)\n group by s.consumer_key\n having sum(s.http_calls) > 0\n order by sum(s.http_calls) desc\n "); print "== Consumers ==\n\n"; print "Consumer name Calls Runtime\n"; print "----------------------------------- ------- -----------\n"; foreach ($consumers as $row) { $name = $row['name']; if ($row['consumer_key'] == 'anonymous') { $name = "Anonymous (Level 0 Authentication)"; } elseif ($row['consumer_key'] == 'facade') { $name = "Internal usage via Facade"; } if (mb_strlen($name) > 35) { $name = mb_substr($name, 0, 32) . "..."; } print self::mb_str_pad($name, 35, " ", STR_PAD_RIGHT); print str_pad($row['http_calls'], 8, " ", STR_PAD_LEFT); print str_pad(sprintf("%01.2f", $row['http_runtime']), 11, " ", STR_PAD_LEFT) . "s\n"; } print "\n"; $methods = Db::select_all("\n select\n s.service_name,\n sum(s.http_calls) as http_calls,\n sum(s.http_runtime) as http_runtime\n from okapi_stats_hourly s\n where s.period_start > date_add(now(), interval -7 day)\n group by s.service_name\n having sum(s.http_calls) > 0\n order by sum(s.http_calls) desc\n "); print "== Methods ==\n\n"; print "Service name Calls Runtime Avg\n"; print "----------------------------------- ------- ----------- --------\n"; foreach ($methods as $row) { $name = $row['service_name']; if (mb_strlen($name) > 35) { $name = mb_substr($name, 0, 32) . "..."; } print self::mb_str_pad($name, 35, " ", STR_PAD_RIGHT); print str_pad($row['http_calls'], 8, " ", STR_PAD_LEFT); print str_pad(sprintf("%01.2f", $row['http_runtime']), 11, " ", STR_PAD_LEFT) . "s"; print str_pad(sprintf("%01.4f", $row['http_calls'] > 0 ? $row['http_runtime'] / $row['http_calls'] : 0), 8, " ", STR_PAD_LEFT) . "s\n"; } print "\n"; $oauth_users = Db::select_all("\n select\n c.name,\n count(*) as users\n from\n okapi_authorizations a,\n okapi_consumers c\n where a.consumer_key = c.`key`\n group by a.consumer_key\n having count(*) >= 5\n order by count(*) desc;\n "); print "== Current OAuth usage by Consumers with at least 5 users ==\n\n"; print "Consumer name Users\n"; print "----------------------------------- -------\n"; foreach ($oauth_users as $row) { $name = $row['name']; if (mb_strlen($name) > 35) { $name = mb_substr($name, 0, 32) . "..."; } print self::mb_str_pad($name, 35, " ", STR_PAD_RIGHT); print str_pad($row['users'], 8, " ", STR_PAD_LEFT) . "\n"; } print "\n"; print "This report includes requests from external consumers and those made via\n"; print "Facade class (used by OC code). It does not include methods used by OKAPI\n"; print "internally (i.e. while running cronjobs). Runtimes do not include HTTP\n"; print "request handling overhead.\n"; $message = ob_get_clean(); Okapi::mail_admins("Weekly OKAPI usage report", $message); }
/** * Load, parse and check common geocache search parameters (the ones * described in services/caches/search/all method) from $this->request. * Most cache search methods share a common set * of filtering parameters recognized by this method. It initalizes * search params, which can be further altered by calls to other methods * of this class, or outside of this class by a call to get_search_params(); * * This method doesn't return anything. See get_search_params method. */ public function prepare_common_search_params() { $where_conds = array('true'); $extra_tables = array(); $extra_joins = array(); # At the beginning we have to set up some "magic e$Xpressions". # We will use them to make our query run on both OCPL and OCDE databases. if (Settings::get('OC_BRANCH') == 'oc.pl') { # OCPL's 'caches' table contains some fields which OCDE's does not # (topratings, founds, notfounds, last_found, votes, score). If # we're being run on OCPL installation, we will simply use them. $X_TOPRATINGS = 'caches.topratings'; $X_FOUNDS = 'caches.founds'; $X_NOTFOUNDS = 'caches.notfounds'; $X_LAST_FOUND = 'caches.last_found'; $X_VOTES = 'caches.votes'; $X_SCORE = 'caches.score'; } else { # OCDE holds this data in a separate table. Additionally, OCDE # does not provide a rating system (votes and score fields). # If we're being run on OCDE database, we will include this # additional table in our query and we will map the field # expressions to approriate places. # stat_caches entries are optional, therefore we must do a left join: $extra_joins[] = 'left join stat_caches on stat_caches.cache_id = caches.cache_id'; $X_TOPRATINGS = 'ifnull(stat_caches.toprating,0)'; $X_FOUNDS = 'ifnull(stat_caches.found,0)'; $X_NOTFOUNDS = 'ifnull(stat_caches.notfound,0)'; $X_LAST_FOUND = 'ifnull(stat_caches.last_found,0)'; $X_VOTES = '0'; // no support for ratings $X_SCORE = '0'; // no support for ratings } # # type # if ($tmp = $this->request->get_parameter('type')) { $operator = "in"; if ($tmp[0] == '-') { $tmp = substr($tmp, 1); $operator = "not in"; } $types = array(); foreach (explode("|", $tmp) as $name) { try { $id = Okapi::cache_type_name2id($name); $types[] = $id; } catch (Exception $e) { throw new InvalidParam('type', "'{$name}' is not a valid cache type."); } } if (count($types) > 0) { $where_conds[] = "caches.type {$operator} ('" . implode("','", array_map('mysql_real_escape_string', $types)) . "')"; } else { if ($operator == "in") { $where_conds[] = "false"; } } } # # size2 # if ($tmp = $this->request->get_parameter('size2')) { $operator = "in"; if ($tmp[0] == '-') { $tmp = substr($tmp, 1); $operator = "not in"; } $types = array(); foreach (explode("|", $tmp) as $name) { try { $id = Okapi::cache_size2_to_sizeid($name); $types[] = $id; } catch (Exception $e) { throw new InvalidParam('size2', "'{$name}' is not a valid cache size."); } } $where_conds[] = "caches.size {$operator} ('" . implode("','", array_map('mysql_real_escape_string', $types)) . "')"; } # # status - filter by status codes # $tmp = $this->request->get_parameter('status'); if ($tmp == null) { $tmp = "Available"; } $codes = array(); foreach (explode("|", $tmp) as $name) { try { $codes[] = Okapi::cache_status_name2id($name); } catch (Exception $e) { throw new InvalidParam('status', "'{$name}' is not a valid cache status."); } } $where_conds[] = "caches.status in ('" . implode("','", array_map('mysql_real_escape_string', $codes)) . "')"; # # owner_uuid # if ($tmp = $this->request->get_parameter('owner_uuid')) { $operator = "in"; if ($tmp[0] == '-') { $tmp = substr($tmp, 1); $operator = "not in"; } try { $users = OkapiServiceRunner::call("services/users/users", new OkapiInternalRequest($this->request->consumer, null, array('user_uuids' => $tmp, 'fields' => 'internal_id'))); } catch (InvalidParam $e) { throw new InvalidParam('owner_uuid', $e->whats_wrong_about_it); } $user_ids = array(); foreach ($users as $user) { $user_ids[] = $user['internal_id']; } $where_conds[] = "caches.user_id {$operator} ('" . implode("','", array_map('mysql_real_escape_string', $user_ids)) . "')"; } # # terrain, difficulty, size, rating - these are similar, we'll do them in a loop # foreach (array('terrain', 'difficulty', 'size', 'rating') as $param_name) { if ($tmp = $this->request->get_parameter($param_name)) { if (!preg_match("/^[1-5]-[1-5](\\|X)?\$/", $tmp)) { throw new InvalidParam($param_name, "'{$tmp}'"); } list($min, $max) = explode("-", $tmp); if (strpos($max, "|X") !== false) { $max = $max[0]; $allow_null = true; } else { $allow_null = false; } if ($min > $max) { throw new InvalidParam($param_name, "'{$tmp}'"); } switch ($param_name) { case 'terrain': if ($allow_null) { throw new InvalidParam($param_name, "The '|X' suffix is not allowed here."); } if ($min == 1 && $max == 5) { /* no extra condition necessary */ } else { $where_conds[] = "caches.terrain between 2*{$min} and 2*{$max}"; } break; case 'difficulty': if ($allow_null) { throw new InvalidParam($param_name, "The '|X' suffix is not allowed here."); } if ($min == 1 && $max == 5) { /* no extra condition necessary */ } else { $where_conds[] = "caches.difficulty between 2*{$min} and 2*{$max}"; } break; case 'size': # Deprecated. Leave it for backward-compatibility. See issue 155. if ($min == 1 && $max == 5 && $allow_null) { # No extra condition necessary ('other' caches will be # included). } else { # 'other' size caches will NOT be included (user must use the # 'size2' parameter to search these). 'nano' caches will be # included whenever 'micro' caches are included ($min=1). $where_conds[] = "(caches.size between {$min}+1 and {$max}+1)" . ($allow_null ? " or caches.size=7" : "") . ($min == 1 ? " or caches.size=8" : ""); } break; case 'rating': if (Settings::get('OC_BRANCH') == 'oc.pl') { if ($min == 1 && $max == 5 && $allow_null) { /* no extra condition necessary */ } else { $divisors = array(-999, -1.0, 0.1, 1.4, 2.2, 999); $min = $divisors[$min - 1]; $max = $divisors[$max]; $where_conds[] = "({$X_SCORE} >= {$min} and {$X_SCORE} < {$max} and {$X_VOTES} >= 3)" . ($allow_null ? " or ({$X_VOTES} < 3)" : ""); } } else { # OCDE does not support rating. We will ignore this parameter. } break; } } } # # min_rcmds # if ($tmp = $this->request->get_parameter('min_rcmds')) { if ($tmp[strlen($tmp) - 1] == '%') { $tmp = substr($tmp, 0, strlen($tmp) - 1); if (!is_numeric($tmp)) { throw new InvalidParam('min_rcmds', "'{$tmp}'"); } $tmp = intval($tmp); if ($tmp > 100 || $tmp < 0) { throw new InvalidParam('min_rcmds', "'{$tmp}'"); } $tmp = floatval($tmp) / 100.0; $where_conds[] = "{$X_TOPRATINGS} >= {$X_FOUNDS} * '" . mysql_real_escape_string($tmp) . "'"; $where_conds[] = "{$X_FOUNDS} > 0"; } if (!is_numeric($tmp)) { throw new InvalidParam('min_rcmds', "'{$tmp}'"); } $where_conds[] = "{$X_TOPRATINGS} >= '" . mysql_real_escape_string($tmp) . "'"; } # # min_founds # if ($tmp = $this->request->get_parameter('min_founds')) { if (!is_numeric($tmp)) { throw new InvalidParam('min_founds', "'{$tmp}'"); } $where_conds[] = "{$X_FOUNDS} >= '" . mysql_real_escape_string($tmp) . "'"; } # # max_founds # may be '0' for FTF hunts # if (!is_null($tmp = $this->request->get_parameter('max_founds'))) { if (!is_numeric($tmp)) { throw new InvalidParam('max_founds', "'{$tmp}'"); } $where_conds[] = "{$X_FOUNDS} <= '" . mysql_real_escape_string($tmp) . "'"; } # # modified_since # if ($tmp = $this->request->get_parameter('modified_since')) { $timestamp = strtotime($tmp); if ($timestamp) { $where_conds[] = "unix_timestamp(caches.last_modified) > '" . mysql_real_escape_string($timestamp) . "'"; } else { throw new InvalidParam('modified_since', "'{$tmp}' is not in a valid format or is not a valid date."); } } # # found_status # if ($tmp = $this->request->get_parameter('found_status')) { if ($this->request->token == null) { throw new InvalidParam('found_status', "Might be used only for requests signed with an Access Token."); } if (!in_array($tmp, array('found_only', 'notfound_only', 'either'))) { throw new InvalidParam('found_status', "'{$tmp}'"); } if ($tmp != 'either') { $found_cache_ids = self::get_found_cache_ids($this->request->token->user_id); $operator = $tmp == 'found_only' ? "in" : "not in"; $where_conds[] = "caches.cache_id {$operator} ('" . implode("','", array_map('mysql_real_escape_string', $found_cache_ids)) . "')"; } } # # found_by # if ($tmp = $this->request->get_parameter('found_by')) { try { $user = OkapiServiceRunner::call("services/users/user", new OkapiInternalRequest($this->request->consumer, null, array('user_uuid' => $tmp, 'fields' => 'internal_id'))); } catch (InvalidParam $e) { # invalid uuid throw new InvalidParam('found_by', $e->whats_wrong_about_it); } $found_cache_ids = self::get_found_cache_ids($user['internal_id']); $where_conds[] = "caches.cache_id in ('" . implode("','", array_map('mysql_real_escape_string', $found_cache_ids)) . "')"; } # # not_found_by # if ($tmp = $this->request->get_parameter('not_found_by')) { try { $user = OkapiServiceRunner::call("services/users/user", new OkapiInternalRequest($this->request->consumer, null, array('user_uuid' => $tmp, 'fields' => 'internal_id'))); } catch (InvalidParam $e) { # invalid uuid throw new InvalidParam('not_found_by', $e->whats_wrong_about_it); } $found_cache_ids = self::get_found_cache_ids($user['internal_id']); $where_conds[] = "caches.cache_id not in ('" . implode("','", array_map('mysql_real_escape_string', $found_cache_ids)) . "')"; } # # watched_only # if ($tmp = $this->request->get_parameter('watched_only')) { if ($this->request->token == null) { throw new InvalidParam('watched_only', "Might be used only for requests signed with an Access Token."); } if (!in_array($tmp, array('true', 'false'))) { throw new InvalidParam('watched_only', "'{$tmp}'"); } if ($tmp == 'true') { $watched_cache_ids = Db::select_column("\n select cache_id\n from cache_watches\n where user_id = '" . mysql_real_escape_string($this->request->token->user_id) . "'\n "); if (Settings::get('OC_BRANCH') == 'oc.de') { $watched_cache_ids = array_merge($watched_cache_ids, Db::select_column("\n select cache_id\n from cache_list_items cli, cache_list_watches clw\n where cli.cache_list_id = clw.cache_list_id\n and clw.user_id = '" . mysql_real_escape_string($this->request->token->user_id) . "'\n ")); } $where_conds[] = "caches.cache_id in ('" . implode("','", array_map('mysql_real_escape_string', $watched_cache_ids)) . "')"; } } # # exclude_ignored # if ($tmp = $this->request->get_parameter('exclude_ignored')) { if ($this->request->token == null) { throw new InvalidParam('exclude_ignored', "Might be used only for requests signed with an Access Token."); } if (!in_array($tmp, array('true', 'false'))) { throw new InvalidParam('exclude_ignored', "'{$tmp}'"); } if ($tmp == 'true') { $ignored_cache_ids = Db::select_column("\n select cache_id\n from cache_ignore\n where user_id = '" . mysql_real_escape_string($this->request->token->user_id) . "'\n "); $where_conds[] = "caches.cache_id not in ('" . implode("','", array_map('mysql_real_escape_string', $ignored_cache_ids)) . "')"; } } # # exclude_my_own # if ($tmp = $this->request->get_parameter('exclude_my_own')) { if ($this->request->token == null) { throw new InvalidParam('exclude_my_own', "Might be used only for requests signed with an Access Token."); } if (!in_array($tmp, array('true', 'false'))) { throw new InvalidParam('exclude_my_own', "'{$tmp}'"); } if ($tmp == 'true') { $where_conds[] = "caches.user_id != '" . mysql_real_escape_string($this->request->token->user_id) . "'"; } } # # name # if ($tmp = $this->request->get_parameter('name')) { # WRTODO: Make this more user-friendly. See: # https://github.com/opencaching/okapi/issues/121 if (strlen($tmp) > 100) { throw new InvalidParam('name', "Maximum length of 'name' parameter is 100 characters"); } $tmp = str_replace("*", "%", str_replace("%", "%%", $tmp)); $where_conds[] = "caches.name LIKE '" . mysql_real_escape_string($tmp) . "'"; } # # with_trackables_only # if ($tmp = $this->request->get_parameter('with_trackables_only')) { if (!in_array($tmp, array('true', 'false'), 1)) { throw new InvalidParam('with_trackables_only', "'{$tmp}'"); } if ($tmp == 'true') { $where_conds[] = "\n caches.wp_oc in (\n select distinct wp\n from gk_item_waypoint\n )\n "; } } # # ftf_hunter # if ($tmp = $this->request->get_parameter('ftf_hunter')) { if (!in_array($tmp, array('true', 'false'), 1)) { throw new InvalidParam('ftf_hunter', "'{$tmp}'"); } if ($tmp == 'true') { $where_conds[] = "{$X_FOUNDS} = 0"; } } # # powertrail_only, powertrail_ids # $join_powertrails = false; if ($tmp = $this->request->get_parameter('powertrail_only')) { if ($tmp === 'true') { $join_powertrails = true; } elseif ($tmp === 'false') { $join_powertrails = false; } else { throw new InvalidParam('powertrail_only', "Boolean expected, '{$tmp}' found."); } } $powertrail_ids = $this->request->get_parameter('powertrail_ids'); if ($powertrail_ids) { $join_powertrails = true; } if ($join_powertrails) { if (Settings::get('OC_BRANCH') == 'oc.pl') { $extra_tables[] = "powerTrail_caches"; $extra_tables[] = "PowerTrail"; $where_conds[] = "powerTrail_caches.cacheId = caches.cache_id"; $where_conds[] = "PowerTrail.id = powerTrail_caches.powerTrailId"; $where_conds[] = 'PowerTrail.status = 1'; if ($powertrail_ids) { $where_conds[] = "PowerTrail.id in ('" . implode("','", array_map('mysql_real_escape_string', explode("|", $powertrail_ids))) . "')"; } } else { $where_conds[] = "0=1"; } } unset($powertrail_ids, $join_powertrails); # # set_and # if ($tmp = $this->request->get_parameter('set_and')) { # Check if the set exists. $exists = Db::select_value("\n select 1\n from okapi_search_sets\n where id = '" . mysql_real_escape_string($tmp) . "'\n "); if (!$exists) { throw new InvalidParam('set_and', "Couldn't find a set by given ID."); } $extra_tables[] = "okapi_search_results osr_and"; $where_conds[] = "osr_and.cache_id = caches.cache_id"; $where_conds[] = "osr_and.set_id = '" . mysql_real_escape_string($tmp) . "'"; } # # limit # $limit = $this->request->get_parameter('limit'); if ($limit == null) { $limit = "100"; } if (!is_numeric($limit)) { throw new InvalidParam('limit', "'{$limit}'"); } if ($limit < 1 || $limit > 500 && !$this->request->skip_limits) { throw new InvalidParam('limit', $this->request->skip_limits ? "Cannot be lower than 1." : "Has to be between 1 and 500."); } # # offset # $offset = $this->request->get_parameter('offset'); if ($offset == null) { $offset = "0"; } if (!is_numeric($offset)) { throw new InvalidParam('offset', "'{$offset}'"); } if ($offset + $limit > 500 && !$this->request->skip_limits) { throw new BadRequest("The sum of offset and limit may not exceed 500."); } if ($offset < 0 || $offset > 499 && !$this->request->skip_limits) { throw new InvalidParam('offset', $this->request->skip_limits ? "Cannot be lower than 0." : "Has to be between 0 and 499."); } # # order_by # $order_clauses = array(); $order_by = $this->request->get_parameter('order_by'); if ($order_by != null) { $order_by = explode('|', $order_by); foreach ($order_by as $field) { $dir = 'asc'; if ($field[0] == '-') { $dir = 'desc'; $field = substr($field, 1); } elseif ($field[0] == '+') { $field = substr($field, 1); } # ignore leading "+" switch ($field) { case 'code': $cl = "caches.wp_oc"; break; case 'name': $cl = "caches.name"; break; case 'founds': $cl = "{$X_FOUNDS}"; break; case 'rcmds': $cl = "{$X_TOPRATINGS}"; break; case 'rcmds%': $cl = "{$X_TOPRATINGS} / if({$X_FOUNDS} = 0, 1, {$X_FOUNDS})"; break; default: throw new InvalidParam('order_by', "Unsupported field '{$field}'"); } $order_clauses[] = "({$cl}) {$dir}"; } } # To avoid join errors, put each of the $where_conds in extra paranthesis. $tmp = array(); foreach ($where_conds as $cond) { $tmp[] = "(" . $cond . ")"; } $where_conds = $tmp; unset($tmp); $ret_array = array('where_conds' => $where_conds, 'offset' => (int) $offset, 'limit' => (int) $limit, 'order_by' => $order_clauses, 'extra_tables' => $extra_tables, 'extra_joins' => $extra_joins); if ($this->search_params === NULL) { $this->search_params = $ret_array; } else { $this->search_params = array_merge_recursive($this->search_params, $ret_array); } }
/** * Precache the ($zoom, $x, $y) slot in the okapi_tile_caches table. */ public static function compute_tile($zoom, $x, $y) { $time_started = microtime(true); # Note, that multiple threads may try to compute tiles simulatanously. # For low-level tiles, this can be expensive. $status = self::get_tile_status($zoom, $x, $y); if ($status !== null) { return $status; } if ($zoom === 0) { # When computing zoom zero, we don't have a parent to speed up # the computation. We need to use the caches table. Note, that # zoom level 0 contains *entire world*, so we don't have to use # any WHERE condition in the following query. # This can be done a little faster (without the use of internal requests), # but there is *no need* to - this query is run seldom and is cached. $params = array(); $params['status'] = "Available|Temporarily unavailable|Archived"; # we want them all $params['limit'] = "10000000"; # no limit $internal_request = new OkapiInternalRequest(new OkapiInternalConsumer(), null, $params); $internal_request->skip_limits = true; $response = OkapiServiceRunner::call("services/caches/search/all", $internal_request); $cache_codes = $response['results']; $internal_request = new OkapiInternalRequest(new OkapiInternalConsumer(), null, array('cache_codes' => implode('|', $cache_codes), 'fields' => 'internal_id|code|name|location|type|status|rating|recommendations|founds|trackables_count')); $internal_request->skip_limits = true; $caches = OkapiServiceRunner::call("services/caches/geocaches", $internal_request); foreach ($caches as $cache) { $row = self::generate_short_row($cache); if (!$row) { /* Some caches cannot be included, e.g. the ones near the poles. */ continue; } Db::execute("\n replace into okapi_tile_caches (\n z, x, y, cache_id, z21x, z21y, status, type, rating, flags, name_crc\n ) values (\n 0, 0, 0,\n '" . Db::escape_string($row[0]) . "',\n '" . Db::escape_string($row[1]) . "',\n '" . Db::escape_string($row[2]) . "',\n '" . Db::escape_string($row[3]) . "',\n '" . Db::escape_string($row[4]) . "',\n " . ($row[5] === null ? "null" : "'" . Db::escape_string($row[5]) . "'") . ",\n '" . Db::escape_string($row[6]) . "',\n '" . Db::escape_string($row[7]) . "'\n );\n "); } $status = 2; } else { # We will use the parent tile to compute the contents of this tile. $parent_zoom = $zoom - 1; $parent_x = $x >> 1; $parent_y = $y >> 1; $status = self::get_tile_status($parent_zoom, $parent_x, $parent_y); if ($status === null) { $time_started = microtime(true); $status = self::compute_tile($parent_zoom, $parent_x, $parent_y); } if ($status === 1) { # No need to check. } else { $scale = 8 + 21 - $zoom; $parentcenter_z21x = ($parent_x << 1 | 1) << $scale; $parentcenter_z21y = ($parent_y << 1 | 1) << $scale; $margin = 1 << $scale - 2; $left_z21x = ($parent_x << 1 << $scale) - $margin; $right_z21x = ($parent_x + 1 << 1 << $scale) + $margin; $top_z21y = ($parent_y << 1 << $scale) - $margin; $bottom_z21y = ($parent_y + 1 << 1 << $scale) + $margin; # Choose the right quarter. # |1 2| # |3 4| if ($x & 1) { # 2 or 4 $left_z21x = $parentcenter_z21x - $margin; } else { # 1 or 3 $right_z21x = $parentcenter_z21x + $margin; } if ($y & 1) { # 3 or 4 $top_z21y = $parentcenter_z21y - $margin; } else { # 1 or 2 $bottom_z21y = $parentcenter_z21y + $margin; } # Cache the result. # Avoid deadlocks, see https://github.com/opencaching/okapi/issues/388 Db::execute("lock tables okapi_tile_caches write, okapi_tile_caches tc2 read"); Db::execute("\n replace into okapi_tile_caches (\n z, x, y, cache_id, z21x, z21y, status, type, rating,\n flags, name_crc\n )\n select\n '" . Db::escape_string($zoom) . "',\n '" . Db::escape_string($x) . "',\n '" . Db::escape_string($y) . "',\n cache_id, z21x, z21y, status, type, rating,\n flags, name_crc\n from okapi_tile_caches tc2\n where\n z = '" . Db::escape_string($parent_zoom) . "'\n and x = '" . Db::escape_string($parent_x) . "'\n and y = '" . Db::escape_string($parent_y) . "'\n and z21x between {$left_z21x} and {$right_z21x}\n and z21y between {$top_z21y} and {$bottom_z21y}\n "); Db::execute("unlock tables"); $test = Db::select_value("\n select 1\n from okapi_tile_caches\n where\n z = '" . Db::escape_string($zoom) . "'\n and x = '" . Db::escape_string($x) . "'\n and y = '" . Db::escape_string($y) . "'\n limit 1;\n "); if ($test) { $status = 2; } else { $status = 1; } } } # Mark tile as computed. Db::execute("\n replace into okapi_tile_status (z, x, y, status)\n values (\n '" . Db::escape_string($zoom) . "',\n '" . Db::escape_string($x) . "',\n '" . Db::escape_string($y) . "',\n '" . Db::escape_string($status) . "'\n );\n "); return $status; }
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; }
/** * 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; }