public static function call() { $langpref = isset($_GET['langpref']) ? $_GET['langpref'] : Settings::get('SITELANG'); $langprefs = explode("|", $langpref); # Determine which user is logged in to OC. require_once $GLOBALS['rootpath'] . "okapi/lib/oc_session.php"; $OC_user_id = OCSession::get_user_id(); if ($OC_user_id == null) { $after_login = "******" . ($langpref != Settings::get('SITELANG') ? "?langpref=" . $langpref : ""); $login_url = Settings::get('SITE_URL') . "login.php?target=" . urlencode($after_login); return new OkapiRedirectResponse($login_url); } # Get the list of authorized apps. $rs = Db::query("\n select c.`key`, c.name, c.url\n from\n okapi_consumers c,\n okapi_authorizations a\n where\n a.user_id = '" . mysql_real_escape_string($OC_user_id) . "'\n and c.`key` = a.consumer_key\n order by c.name\n "); $vars = array(); $vars['okapi_base_url'] = Settings::get('SITE_URL') . "okapi/"; $vars['site_url'] = Settings::get('SITE_URL'); $vars['site_name'] = Okapi::get_normalized_site_name(); $vars['site_logo'] = Settings::get('SITE_LOGO'); $vars['apps'] = array(); while ($row = mysql_fetch_assoc($rs)) { $vars['apps'][] = $row; } mysql_free_result($rs); $response = new OkapiHttpResponse(); $response->content_type = "text/html; charset=utf-8"; ob_start(); Okapi::gettext_domain_init($langprefs); include 'index.tpl.php'; $response->body = ob_get_clean(); Okapi::gettext_domain_restore(); return $response; }
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); }
static function call(OkapiRequest $request) { require_once 'log_images_common.inc.php'; list($image_uuid, $log_internal_id) = LogImagesCommon::validate_image_uuid($request); $image_uuid_escaped = Db::escape_string($image_uuid); Db::execute('start transaction'); $image_row = Db::select_row("\n select id, node, url, local\n from pictures\n where uuid = '" . $image_uuid_escaped . "'\n "); Db::execute("\n delete from pictures where uuid = '" . $image_uuid_escaped . "'\n "); # Remember that OCPL picture sequence numbers are always 1, and # OCDE sequence numbers may have gaps. So we do not need to adjust # any numbers after deleting from table 'pictures'. if (Settings::get('OC_BRANCH') == 'oc.de') { # OCDE does all the housekeeping by triggers } else { Db::execute("\n INSERT INTO removed_objects (\n localID, uuid, type, removed_date, node\n )\n VALUES (\n " . $image_row['id'] . "\n '" . $image_uuid_escaped . "',\n 6,\n NOW(),\n " . $image_row['node'] . "\n )\n "); # This will also update cache_logs.okapi_syncbase, so that replication # can output the updated log entry with one image less. For OCDE # that's done by DB trigges. Db::execute("\n update cache_logs\n set\n picturescount = greatest(0, picturescount - 1),\n last_modified = NOW()\n where id = '" . Db::escape_string($log_internal_id) . "'\n "); } Db::execute('commit'); if ($image_row['local']) { $filename = basename($image_row['url']); unlink(Settings::get('IMAGES_DIR') . '/' . $filename); } $result = array('success' => true); 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) { $cachekey = "apisrv/stats"; $result = Cache::get($cachekey); if (!$result) { $result = array('cache_count' => 0 + Db::select_value("\n select count(*) from caches where status in (1,2,3)\n "), 'user_count' => 0 + Db::select_value("\n select count(*) from (\n select distinct user_id\n from cache_logs\n where\n type in (1,2,7)\n and " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "deleted = 0" : "true") . "\n UNION DISTINCT\n select distinct user_id\n from caches\n ) as t;\n "), 'apps_count' => 0 + Db::select_value("select count(*) from okapi_consumers;"), 'apps_active' => 0 + 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 -30 day)\n ")); Cache::set($cachekey, $result, 86400); # cache it for one day } return Okapi::formatted_response($request, $result); }
public static function call() { if (!isset($_GET['id'])) { throw new ParamMissing("id"); } $tmp = Db::select_value("\n select data\n from okapi_clog\n where id='" . Db::escape_string($_GET['id']) . "'\n "); $data = unserialize(gzinflate($tmp)); $response = new OkapiHttpResponse(); $response->content_type = "application/json; charset=utf-8"; $response->body = json_encode($data); 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) { $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() { # Determine which user is logged in to OC. require_once $GLOBALS['rootpath'] . "okapi/lib/oc_session.php"; $OC_user_id = OCSession::get_user_id(); # Ensure a user is logged in. if ($OC_user_id == null) { $after_login = "******"; # it is correct, if you're wondering $login_url = Settings::get('SITE_URL') . "login.php?target=" . urlencode($after_login); return new OkapiRedirectResponse($login_url); } $consumer_key = isset($_REQUEST['consumer_key']) ? $_REQUEST['consumer_key'] : ''; # Just remove app (if it doesn't exist - nothing wrong will happen anyway). Db::execute("\n delete from okapi_tokens\n where\n user_id = '" . Db::escape_string($OC_user_id) . "'\n and consumer_key = '" . Db::escape_string($consumer_key) . "'\n "); Db::execute("\n delete from okapi_authorizations\n where\n user_id = '" . Db::escape_string($OC_user_id) . "'\n and consumer_key = '" . Db::escape_string($consumer_key) . "'\n "); # Redirect back to the apps page. return new OkapiRedirectResponse(Settings::get('SITE_URL') . "okapi/apps/"); }
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); }
public static function call() { $token_key = isset($_GET['oauth_token']) ? $_GET['oauth_token'] : ''; $verifier = isset($_GET['oauth_verifier']) ? $_GET['oauth_verifier'] : ''; $langpref = isset($_GET['langpref']) ? $_GET['langpref'] : Settings::get('SITELANG'); $langprefs = explode("|", $langpref); $token = Db::select_row("\n select\n c.`key` as consumer_key,\n c.name as consumer_name,\n c.url as consumer_url,\n t.verifier\n from\n okapi_consumers c,\n okapi_tokens t\n where\n t.`key` = '" . mysql_real_escape_string($token_key) . "'\n and t.consumer_key = c.`key`\n "); if (!$token) { # Probably Request Token has expired or it was already used. We'll # just redirect to the Opencaching main page. return new OkapiRedirectResponse(Settings::get('SITE_URL')); } $vars = array('okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'token' => $token, 'verifier' => $verifier, 'site_name' => Okapi::get_normalized_site_name(), 'site_url' => Settings::get('SITE_URL'), 'site_logo' => Settings::get('SITE_LOGO')); $response = new OkapiHttpResponse(); $response->content_type = "text/html; charset=utf-8"; ob_start(); Okapi::gettext_domain_init($langprefs); include 'authorized.tpl.php'; $response->body = ob_get_clean(); Okapi::gettext_domain_restore(); return $response; }
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 function __construct($options) { Okapi::init_internals(); $this->init_request(); # # Parsing options. # $DEBUG_AS_USERNAME = null; foreach ($options as $key => $value) { switch ($key) { case 'min_auth_level': if (!in_array($value, array(0, 1, 2, 3))) { throw new Exception("'min_auth_level' option has invalid value: {$value}"); } $this->opt_min_auth_level = $value; break; case 'token_type': if (!in_array($value, array("request", "access"))) { throw new Exception("'token_type' option has invalid value: {$value}"); } $this->opt_token_type = $value; break; case 'DEBUG_AS_USERNAME': $DEBUG_AS_USERNAME = $value; break; default: throw new Exception("Unknown option: {$key}"); break; } } if ($this->opt_min_auth_level === null) { throw new Exception("Required 'min_auth_level' option is missing."); } if ($DEBUG_AS_USERNAME != null) { # Enables debugging Level 2 and Level 3 methods. Should not be committed # at any time! If run on production server, make it an error. if (!Settings::get('DEBUG')) { throw new Exception("Attempted to use DEBUG_AS_USERNAME in " . "non-debug environment. Accidental commit?"); } # Lower required authentication to Level 0, to pass the checks. $this->opt_min_auth_level = 0; } # # Let's see if the request is signed. If it is, verify the signature. # It it's not, check if it isn't against the rules defined in the $options. # if ($this->get_parameter('oauth_signature')) { # User is using OAuth. # Check for duplicate keys in the parameters. (Datastore doesn't # do that on its own, it caused vague server errors - issue #307.) $this->get_parameter('oauth_consumer'); $this->get_parameter('oauth_version'); $this->get_parameter('oauth_token'); $this->get_parameter('oauth_nonce'); # Verify OAuth request. list($this->consumer, $this->token) = Okapi::$server->verify_request2($this->request, $this->opt_token_type, $this->opt_min_auth_level == 3); if ($this->get_parameter('consumer_key') && $this->get_parameter('consumer_key') != $this->get_parameter('oauth_consumer_key')) { throw new BadRequest("Inproper mixing of authentication types. You used both 'consumer_key' " . "and 'oauth_consumer_key' parameters (Level 1 and Level 2), but they do not match with " . "each other. Were you trying to hack me? ;)"); } if ($this->opt_min_auth_level == 3 && !$this->token) { throw new BadRequest("This method requires a valid Token to be included (Level 3 " . "Authentication). You didn't provide one."); } } else { if ($this->opt_min_auth_level >= 2) { throw new BadRequest("This method requires OAuth signature (Level " . $this->opt_min_auth_level . " Authentication). You didn't sign your request."); } else { $consumer_key = $this->get_parameter('consumer_key'); if ($consumer_key) { $this->consumer = Okapi::$data_store->lookup_consumer($consumer_key); if (!$this->consumer) { throw new InvalidParam('consumer_key', "Consumer does not exist."); } } if ($this->opt_min_auth_level == 1 && !$this->consumer) { throw new BadRequest("This method requires the 'consumer_key' argument (Level 1 " . "Authentication). You didn't provide one."); } } } if (is_object($this->consumer) && $this->consumer->hasFlag(OkapiConsumer::FLAG_SKIP_LIMITS)) { $this->skip_limits = true; } # # Prevent developers from accessing request parameters with PHP globals. # Remember, that OKAPI requests can be nested within other OKAPI requests! # Search the code for "new OkapiInternalRequest" to see examples. # $_GET = $_POST = $_REQUEST = null; # When debugging, simulate as if been run using a proper Level 3 Authentication. if ($DEBUG_AS_USERNAME != null) { # Note, that this will override any other valid authentication the # developer might have issued. $debug_user_id = Db::select_value("select user_id from user where username='******'DEBUG_AS_USERNAME']) . "'"); if ($debug_user_id == null) { throw new Exception("Invalid user name in DEBUG_AS_USERNAME: '******'DEBUG_AS_USERNAME'] . "'"); } $this->consumer = new OkapiDebugConsumer(); $this->token = new OkapiDebugAccessToken($debug_user_id); } # Read the ETag. if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { $this->etag = $_SERVER['HTTP_IF_NONE_MATCH']; } }
/** * Get an array of all site-specific log-types (id => name in English). */ private static function get_all_logtypes() { if (Settings::get('OC_BRANCH') == 'oc.pl') { # OCPL branch does not store cache types in many languages (just two). $rs = Db::query("select id, en from log_types order by id"); } else { # OCDE branch uses translation tables. $rs = Db::query("\n select\n lt.id,\n stt.text as en\n from\n log_types lt\n left join sys_trans_text stt\n on lt.trans_id = stt.trans_id\n and stt.lang = 'EN'\n order by lt.id\n "); } $dict = array(); while ($row = mysql_fetch_assoc($rs)) { $dict[$row['id']] = $row['en']; } return $dict; }
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); }
/** * Generate a new fulldump file and put it into the OKAPI cache table. * Return the cache key. */ public static function generate_fulldump() { # First we will create temporary files, then compress them in the end. $revision = self::get_revision(); $generated_at = date('c', time()); $dir = Okapi::get_var_dir() . "/okapi-db-dump"; $i = 1; $json_files = array(); # Cleanup (from a previous, possibly unsuccessful, execution) shell_exec("rm -f {$dir}/*"); shell_exec("rmdir {$dir}"); shell_exec("mkdir {$dir}"); shell_exec("chmod 777 {$dir}"); # Geocaches $cache_codes = Db::select_column("select wp_oc from caches"); $cache_code_groups = Okapi::make_groups($cache_codes, self::$chunk_size); unset($cache_codes); foreach ($cache_code_groups as $cache_codes) { $basename = "part" . str_pad($i, 5, "0", STR_PAD_LEFT); $json_files[] = $basename . ".json"; $entries = self::generate_changelog_entries('services/caches/geocaches', 'geocache', 'cache_codes', 'code', $cache_codes, self::$logged_cache_fields, true, false); $filtered = array(); foreach ($entries as $entry) { if ($entry['change_type'] == 'replace') { $filtered[] = $entry; } } unset($entries); file_put_contents("{$dir}/{$basename}.json", json_encode($filtered)); unset($filtered); $i++; } unset($cache_code_groups); # Log entries. We cannot load all the uuids at one time, this would take # too much memory. Hence the offset/limit loop. $offset = 0; while (true) { $log_uuids = Db::select_column("\n select uuid\n from cache_logs\n where " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "deleted = 0" : "true") . "\n order by uuid\n limit {$offset}, 10000\n "); if (count($log_uuids) == 0) { break; } $offset += 10000; $log_uuid_groups = Okapi::make_groups($log_uuids, 500); unset($log_uuids); foreach ($log_uuid_groups as $log_uuids) { $basename = "part" . str_pad($i, 5, "0", STR_PAD_LEFT); $json_files[] = $basename . ".json"; $entries = self::generate_changelog_entries('services/logs/entries', 'log', 'log_uuids', 'uuid', $log_uuids, self::$logged_log_entry_fields, true, false); $filtered = array(); foreach ($entries as $entry) { if ($entry['change_type'] == 'replace') { $filtered[] = $entry; } } unset($entries); file_put_contents("{$dir}/{$basename}.json", json_encode($filtered)); unset($filtered); $i++; } } # Package data. $metadata = array('revision' => $revision, 'data_files' => $json_files, 'meta' => array('site_name' => Okapi::get_normalized_site_name(), 'okapi_version_number' => Okapi::$version_number, 'okapi_revision' => Okapi::$version_number, 'okapi_git_revision' => Okapi::$git_revision, 'generated_at' => $generated_at)); file_put_contents("{$dir}/index.json", json_encode($metadata)); # Compute uncompressed size. $size = filesize("{$dir}/index.json"); foreach ($json_files as $filename) { $size += filesize("{$dir}/{$filename}"); } # Create JSON archive. We use tar options: -j for bzip2, -z for gzip # (bzip2 is MUCH slower). $use_bzip2 = true; $dumpfilename = "okapi-dump.tar." . ($use_bzip2 ? "bz2" : "gz"); shell_exec("tar --directory {$dir} -c" . ($use_bzip2 ? "j" : "z") . "f {$dir}/{$dumpfilename} index.json " . implode(" ", $json_files) . " 2>&1"); # Delete temporary files. shell_exec("rm -f {$dir}/*.json"); # Move the archive one directory upwards, replacing the previous one. # Remove the temporary directory. shell_exec("mv -f {$dir}/{$dumpfilename} " . Okapi::get_var_dir()); shell_exec("rmdir {$dir}"); # Update the database info. $metadata['meta']['filepath'] = Okapi::get_var_dir() . '/' . $dumpfilename; $metadata['meta']['content_type'] = $use_bzip2 ? "application/octet-stream" : "application/x-gzip"; $metadata['meta']['public_filename'] = 'okapi-dump-r' . $metadata['revision'] . '.tar.' . ($use_bzip2 ? "bz2" : "gz"); $metadata['meta']['uncompressed_size'] = $size; $metadata['meta']['compressed_size'] = filesize($metadata['meta']['filepath']); Cache::set("last_fulldump", $metadata, 10 * 86400); }
public function execute() { Db::query("optimize table okapi_tile_caches"); Db::query("optimize table okapi_tile_status"); }
public function cleanup() { Db::execute("\n delete from okapi_nonces\n where\n timestamp < unix_timestamp(date_add(now(), interval -6 minute))\n or timestamp > unix_timestamp(date_add(now(), interval 6 minute))\n "); Db::execute("\n delete from okapi_tokens\n where\n token_type = 'request'\n and timestamp < unix_timestamp(date_add(now(), interval -2 hour))\n "); }
private function loadSearchData($searchData) { \okapi\OkapiErrorHandler::reenable(); // We need to transform OC's "searchdata" into OKAPI's "search set". // First, we need to determine if we ALREADY did that. // Note, that this is not exactly thread-efficient. Multiple threads may // do this transformation in the same time. However, this is done only once // for each searchdata, so we will ignore it. $cache_key = "OC_searchdata_" . $searchData; $set_id = \okapi\Cache::get($cache_key); if ($set_id === null) { // Read the searchdata file into a temporary table. $filepath = \okapi\Settings::get('VAR_DIR') . "/searchdata/" . $searchData; \okapi\Db::execute("\n create temporary table temp_" . $searchData . " (\n cache_id integer primary key\n ) engine=memory\n "); if (file_exists($filepath)) { \okapi\Db::execute("\n load data local infile '{$filepath}'\n into table temp_" . $searchData . "\n fields terminated by ' '\n lines terminated by '\\n'\n (cache_id)\n "); } // Tell OKAPI to import the table into its own internal structures. // Cache it for two hours. $set_info = \okapi\Facade::import_search_set("temp_" . $searchData, 7200, 7200); $set_id = $set_info['set_id']; \okapi\Cache::set($cache_key, $set_id, 7200); } $this->search_params['set_and'] = $set_id; $this->search_params['status'] = "Available|Temporarily unavailable|Archived"; \okapi\OkapiErrorHandler::disable(); return true; }
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); }
public static function call() { $token_key = isset($_GET['oauth_token']) ? $_GET['oauth_token'] : ''; $langpref = isset($_GET['langpref']) ? $_GET['langpref'] : Settings::get('SITELANG'); $langprefs = explode("|", $langpref); $locales = array(); foreach (Locales::$languages as $lang => $attrs) { $locales[$attrs['locale']] = $attrs; } # Current implementation of the "interactivity" parameter is: If developer # wants to "confirm_user", then just log out the current user before we # continue. $force_relogin = isset($_GET['interactivity']) && $_GET['interactivity'] == 'confirm_user'; $token = Db::select_row("\n select\n t.`key` as `key`,\n c.`key` as consumer_key,\n c.name as consumer_name,\n c.url as consumer_url,\n t.callback,\n t.verifier\n from\n okapi_consumers c,\n okapi_tokens t\n where\n t.`key` = '" . Db::escape_string($token_key) . "'\n and t.consumer_key = c.`key`\n and t.user_id is null\n "); $callback_concat_char = strpos($token['callback'], '?') === false ? "?" : "&"; if (!$token) { # Probably Request Token has expired. This will be usually viewed # by the user, who knows nothing on tokens and OAuth. Let's be nice then! $vars = array('okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'token' => $token, 'token_expired' => true, 'site_name' => Okapi::get_normalized_site_name(), 'site_url' => Settings::get('SITE_URL'), 'site_logo' => Settings::get('SITE_LOGO'), 'locales' => $locales); $response = new OkapiHttpResponse(); $response->content_type = "text/html; charset=utf-8"; ob_start(); $vars['locale_displayed'] = Okapi::gettext_domain_init($langprefs); include 'authorize.tpl.php'; $response->body = ob_get_clean(); Okapi::gettext_domain_restore(); return $response; } # Determine which user is logged in to OC. require_once $GLOBALS['rootpath'] . "okapi/lib/oc_session.php"; $OC_user_id = OCSession::get_user_id(); # Ensure a user is logged in (or force re-login). if ($force_relogin || $OC_user_id == null) { # TODO: confirm_user should first ask the user if he's "the proper one", # and then offer to sign in as a different user. $login_page = 'login.php?'; if ($OC_user_id !== null) { if (Settings::get('OC_BRANCH') == 'oc.de') { # OCDE login.php?action=logout&target=... will NOT logout and # then redirect to the target, but it will log out, prompt for # login and then redirect to the target after logging in - # that's exactly the relogin that we want. $login_page .= 'action=logout&'; } else { # OCPL uses REAL MAGIC for session handling. I don't get ANY of it. # The logout.php DOES NOT support the "target" parameter, so we # can't just call it. The only thing that comes to mind is... # Try to destroy EVERYTHING. (This still won't necessarilly work, # because OC may store cookies in separate paths, but hopefully # they won't). if (isset($_SERVER['HTTP_COOKIE'])) { $cookies = explode(';', $_SERVER['HTTP_COOKIE']); foreach ($cookies as $cookie) { $parts = explode('=', $cookie); $name = trim($parts[0]); setcookie($name, '', time() - 1000); setcookie($name, '', time() - 1000, '/'); foreach (self::getPossibleCookieDomains() as $domain) { setcookie($name, '', time() - 1000, '/', $domain); } } } # We should be logged out now. Let's login again. } } $after_login = "******" . ($langpref != Settings::get('SITELANG') ? "&langpref=" . $langpref : ""); $login_url = Settings::get('SITE_URL') . $login_page . "target=" . urlencode($after_login) . "&langpref=" . $langpref; return new OkapiRedirectResponse($login_url); } # Check if this user has already authorized this Consumer. If he did, # then we will automatically authorize all subsequent Request Tokens # from this Consumer. $authorized = Db::select_value("\n select 1\n from okapi_authorizations\n where\n user_id = '" . Db::escape_string($OC_user_id) . "'\n and consumer_key = '" . Db::escape_string($token['consumer_key']) . "'\n ", 0); if (!$authorized) { if (isset($_POST['authorization_result'])) { # Not yet authorized, but user have just submitted the authorization form. # WRTODO: CSRF protection if ($_POST['authorization_result'] == 'granted') { Db::execute("\n insert ignore into okapi_authorizations (consumer_key, user_id)\n values (\n '" . Db::escape_string($token['consumer_key']) . "',\n '" . Db::escape_string($OC_user_id) . "'\n );\n "); $authorized = true; } else { # User denied access. Nothing sensible to do now. Will try to report # back to the Consumer application with an error. if ($token['callback']) { return new OkapiRedirectResponse($token['callback'] . $callback_concat_char . "error=access_denied" . "&oauth_token=" . $token['key']); } else { # Consumer did not provide a callback URL (oauth_callback=oob). # We'll have to redirect to the Opencaching main page then... return new OkapiRedirectResponse(Settings::get('SITE_URL') . "index.php"); } } } else { # Not yet authorized. Display an authorization request. $vars = array('okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'token' => $token, 'site_name' => Okapi::get_normalized_site_name(), 'site_url' => Settings::get('SITE_URL'), 'site_logo' => Settings::get('SITE_LOGO'), 'locales' => $locales); $response = new OkapiHttpResponse(); $response->content_type = "text/html; charset=utf-8"; ob_start(); $vars['locale_displayed'] = Okapi::gettext_domain_init($langprefs); include 'authorize.tpl.php'; $response->body = ob_get_clean(); Okapi::gettext_domain_restore(); return $response; } } # User granted access. Now we can authorize the Request Token. Db::execute("\n update okapi_tokens\n set user_id = '" . Db::escape_string($OC_user_id) . "'\n where `key` = '" . Db::escape_string($token_key) . "';\n "); # Redirect to the callback_url. if ($token['callback']) { return new OkapiRedirectResponse($token['callback'] . $callback_concat_char . "oauth_token=" . $token_key . "&oauth_verifier=" . $token['verifier']); } else { # Consumer did not provide a callback URL (probably the user is using a desktop # or mobile application). We'll just have to display the verifier to the user. return new OkapiRedirectResponse(Settings::get('SITE_URL') . "okapi/apps/authorized?oauth_token=" . $token_key . "&oauth_verifier=" . $token['verifier'] . "&langpref=" . $langpref); } }
/** * Important: YOU HAVE TO make sure $tables and $where_conds don't contain * unescaped user-supplied data! */ public static function get_set($tables, $joins, $where_conds, $min_store, $ref_max_age) { # Compute the "params hash". $params_hash = md5(serialize(array($tables, $joins, $where_conds))); # Check if there exists an entry for this hash, which also meets the # given freshness criteria. list($set_id, $date_created, $expires) = self::find_param_set($params_hash, $ref_max_age); if ($set_id === null) { # To avoid generating the same results by multiple threads at once # (the "tile" method uses the "save" method, so the problem is # quite real!), we will acquire a write-lock here. $lock = OkapiLock::get("search-results-writer"); $lock->acquire(); try { # Make sure we were the first to acquire the lock. list($set_id, $date_created, $expires) = self::find_param_set($params_hash, $ref_max_age); if ($set_id === null) { # We are in the first thread which have acquired the lock. # We will proceed with result-set creation. Other threads # will be waiting until we finish. Db::execute("\n insert into okapi_search_sets (params_hash, date_created, expires)\n values (\n 'processing in progress',\n now(),\n date_add(now(), interval '" . mysql_real_escape_string($min_store + 60) . "' second)\n )\n "); $set_id = Db::last_insert_id(); $date_created = time(); $expires = $date_created + $min_store + 60; Db::execute("\n insert into okapi_search_results (set_id, cache_id)\n select distinct\n '" . mysql_real_escape_string($set_id) . "',\n caches.cache_id\n from\n " . implode(", ", $tables) . "\n " . implode(" ", $joins) . "\n where (" . implode(") and (", $where_conds) . ")\n "); # Lock barrier, to make sure the data is visible by other # sessions. See http://bugs.mysql.com/bug.php?id=36618 Db::execute("lock table okapi_search_results write"); Db::execute("unlock tables"); Db::execute("\n update okapi_search_sets\n set params_hash = '" . mysql_real_escape_string($params_hash) . "'\n where id = '" . mysql_real_escape_string($set_id) . "'\n "); } else { # Some other thread acquired the lock before us and it has # generated the result set. We don't need to do anything. } $lock->release(); } catch (Exception $e) { # SQL error? Make sure the lock is released and rethrow. $lock->release(); throw $e; } } # If we got an old set, we may need to expand its lifetime in order to # meet user's "min_store" criterium. if (time() + $min_store > $expires) { Db::execute("\n update okapi_search_sets\n set expires = date_add(now(), interval '" . mysql_real_escape_string($min_store + 60) . "' second)\n where id = '" . mysql_real_escape_string($set_id) . "'\n "); } return array('set_id' => "{$set_id}", 'generated_at' => date('c', $date_created), 'expires' => date('c', $expires)); }
public static function call() { # Flush the stats, so the page is fresh upon every request. require_once $GLOBALS['rootpath'] . "okapi/cronjobs.php"; CronJobController::force_run("StatsWriterCronJob"); # When services/caches/map/tile method is called, it writes some extra # stats in the okapi_stats_hourly table. This page retrieves and # formats these stats in a readable manner (for debugging). $response = new OkapiHttpResponse(); $response->content_type = "text/plain; charset=utf-8"; ob_start(); $start = isset($_GET['start']) ? $_GET['start'] : date("Y-m-d 00:00:00", time() - 7 * 86400); $end = isset($_GET['end']) ? $_GET['end'] : date("Y-m-d 23:59:59"); print "From: {$start}\n"; print " To: {$end}\n\n"; $rs = Db::query("\n select\n service_name,\n sum(total_calls),\n sum(total_runtime)\n from okapi_stats_hourly\n where\n period_start >= '" . mysql_real_escape_string($start) . "'\n and period_start < '" . mysql_real_escape_string($end) . "'\n and service_name like '%caches/map/tile%'\n group by service_name\n "); $total_calls = 0; $total_runtime = 0.0; $calls = array('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); $runtime = array('A' => 0.0, 'B' => 0.0, 'C' => 0.0, 'D' => 0.0); while (list($name, $c, $r) = mysql_fetch_array($rs)) { if ($name == 'services/caches/map/tile') { $total_calls = $c; $total_runtime = $r; } elseif (strpos($name, 'extra/caches/map/tile/checkpoint') === 0) { $calls[$name[32]] = $c; $runtime[$name[32]] = $r; } } if ($total_calls != $calls['A']) { print "Partial results. Only " . $calls['A'] . " out of {$total_calls} are covered.\n"; print "All other will count as \"unaccounted for\".\n\n"; $total_calls = $calls['A']; } $calls_left = $total_calls; $runtime_left = $total_runtime; $perc = function ($a, $b) { return $b > 0 ? sprintf("%.1f", 100 * $a / $b) . "%" : "(?)"; }; $avg = function ($a, $b) { return $b > 0 ? sprintf("%.4f", $a / $b) . "s" : "(?)"; }; $get_stats = function () use(&$calls_left, &$runtime_left, &$total_calls, &$total_runtime, &$perc) { return str_pad($perc($calls_left, $total_calls), 6, " ", STR_PAD_LEFT) . str_pad($perc($runtime_left, $total_runtime), 7, " ", STR_PAD_LEFT); }; print "%CALLS %TIME Description\n"; print "====== ====== ======================================================================\n"; print $get_stats() . " {$total_calls} responses served. Total runtime: " . sprintf("%.2f", $total_runtime) . "s\n"; print "\n"; print " All of these requests needed a TileTree build/lookup. The average runtime of\n"; print " these lookups was " . $avg($runtime['A'], $total_calls) . ". " . $perc($runtime['A'], $total_runtime) . " of total runtime was spent here.\n"; print "\n"; $runtime_left -= $runtime['A']; print $get_stats() . " All calls passed here after ~" . $avg($runtime['A'], $total_calls) . "\n"; print "\n"; print " Lookup result was then processed and \"image description\" was created. It was\n"; print " passed on to the TileRenderer to compute the ETag hash string. The average runtime\n"; print " of this part was " . $avg($runtime['B'], $total_calls) . ". " . $perc($runtime['B'], $total_runtime) . " of total runtime was spent here.\n"; print "\n"; $runtime_left -= $runtime['B']; print $get_stats() . " All calls passed here after ~" . $avg($runtime['A'] + $runtime['B'], $total_calls) . "\n"; $etag_hits = $calls['B'] - $calls['C']; print "\n"; print " {$etag_hits} of the requests matched the ETag and were served an HTTP 304 response.\n"; print "\n"; $calls_left = $calls['C']; print $get_stats() . " {$calls_left} calls passed here after ~" . $avg($runtime['A'] + $runtime['B'], $total_calls) . "\n"; $imagecache_hits = $calls['C'] - $calls['D']; print "\n"; print " {$imagecache_hits} of these calls hit the server image cache.\n"; print " " . $perc($runtime['C'], $total_runtime) . " of total runtime was spent to find these.\n"; print "\n"; $calls_left = $calls['D']; $runtime_left -= $runtime['C']; print $get_stats() . " {$calls_left} calls passed here after ~" . $avg($runtime['A'] + $runtime['B'] + $runtime['C'], $total_calls) . "\n"; print "\n"; print " These calls required the tile to be rendered. On average, it took\n"; print " " . $avg($runtime['D'], $calls['D']) . " to *render* a tile.\n"; print " " . $perc($runtime['D'], $total_runtime) . " of total runtime was spent here.\n"; print "\n"; $runtime_left -= $runtime['D']; print $perc($runtime_left, $total_runtime) . " of runtime was unaccounted for (other processing).\n"; print "Average response time was " . $avg($total_runtime, $total_calls) . ".\n\n"; print "Current okapi_cache score distribution:\n"; $rs = Db::query("\n select floor(log2(score)), count(*), sum(length(value))\n from okapi_cache\n where score is not null\n group by floor(log2(score))\n "); while (list($log2, $count, $size) = mysql_fetch_array($rs)) { print $count . " elements ({$size} bytes) with score between " . pow(2, $log2) . " and " . pow(2, $log2 + 1) . ".\n"; } $response->body = ob_get_clean(); return $response; }
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); } }
/** * Get the list of cache IDs which were found by given user. * Parameter needs to be *internal* user id, not uuid. */ private static function get_found_cache_ids($internal_user_id) { return Db::select_column("\n select cache_id\n from cache_logs\n where\n user_id = '" . mysql_real_escape_string($internal_user_id) . "'\n and type in (\n '" . mysql_real_escape_string(Okapi::logtypename2id("Found it")) . "',\n '" . mysql_real_escape_string(Okapi::logtypename2id("Attended")) . "'\n )\n and " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "deleted = 0" : "true") . "\n "); }
private static function save_stats($service_name, $request, $runtime) { # Getting rid of nulls. MySQL PRIMARY keys cannot contain nullable columns. # Temp table doesn't have primary key, but other stats tables (which are # dependant on stats table) - do. if ($request !== null) { $consumer_key = $request->consumer != null ? $request->consumer->key : 'anonymous'; $user_id = $request->token != null && $request->token instanceof OkapiAccessToken ? $request->token->user_id : -1; if ($request->is_http_request() && $service_name[0] == 's') { # 's' for "services/", we don't want "extra/" included $calltype = 'http'; } else { $calltype = 'internal'; } } else { $consumer_key = 'internal'; $user_id = -1; $calltype = 'internal'; } Db::execute("\n insert into okapi_stats_temp (`datetime`, consumer_key, user_id, service_name, calltype, runtime)\n values (\n now(),\n '" . mysql_real_escape_string($consumer_key) . "',\n '" . mysql_real_escape_string($user_id) . "',\n '" . mysql_real_escape_string($service_name) . "',\n '" . mysql_real_escape_string($calltype) . "',\n '" . mysql_real_escape_string($runtime) . "'\n );\n "); }
private static function remove_notes($cache_id, $user_id) { if (Settings::get('OC_BRANCH') == 'oc.de') { # we can delete row if and only if there are no coords in it $affected_row_count = Db::execute("\n delete from coordinates\n where\n type = 2 -- personal note\n and cache_id = '" . Db::escape_string($cache_id) . "'\n and user_id = '" . Db::escape_string($user_id) . "'\n and longitude = 0\n and latitude = 0\n "); if ($affected_row_count <= 0) { # no rows deleted - record either doesn't exist, or has coords # remove only description Db::execute("\n update coordinates\n set description = null\n where\n type = 2\n and cache_id = '" . Db::escape_string($cache_id) . "'\n and user_id = '" . Db::escape_string($user_id) . "'\n "); } } else { # oc.pl branch Db::execute("\n delete from cache_notes\n where\n cache_id = '" . Db::escape_string($cache_id) . "'\n and user_id = '" . Db::escape_string($user_id) . "'\n "); } }
/** * OCDE supports arbitrary ordering of log images. The pictures table * contains sequence numbers, which are always > 0 and need not to be * consecutive (may have gaps). There is a unique index which prevents * inserting duplicate seq numbers for the same log. * * OCPL sequence numbers currently are always = 1. * * The purpose of this function is to bring the supplied 'position' * parameter into bounds, and to calculate an appropriate sequence number * from it. * * This function is always called when adding images. When editing images, * it is called only for OCDE and if the position parameter was supplied. */ static function prepare_position($log_internal_id, $position, $end_offset) { if (Settings::get('OC_BRANCH') == 'oc.de' && $position !== null) { # Prevent race conditions when creating sequence numbers if a # user tries to upload multiple images simultaneously. With a # few picture uploads per hour - most of them probably witout # a 'position' parameter - the lock is neglectable. Db::execute('lock tables pictures write'); } $log_images_count = Db::select_value("\n select count(*)\n from pictures\n where object_type = 1 and object_id = '" . Db::escape_string($log_internal_id) . "'\n "); if (Settings::get('OC_BRANCH') == 'oc.pl') { # Ignore the position parameter, always insert at end. # Remember that this function is NOT called when editing OCPL images. $position = $log_images_count; $seq = 1; } else { if ($position === null || $position >= $log_images_count) { $position = $log_images_count - 1 + $end_offset; $seq = Db::select_value("\n select max(seq)\n from pictures\n where object_type = 1 and object_id = '" . Db::escape_string($log_internal_id) . "'\n ") + $end_offset; } else { if ($position <= 0) { $position = 0; $seq = 1; } else { $seq = Db::select_value("\n select seq\n from pictures\n where object_type = 1 and object_id = '" . Db::escape_string($log_internal_id) . "'\n order by seq\n limit " . ($position + 0) . ", 1\n "); } } } # $position may have become a string, as returned by database queries. return array($position + 0, $seq, $log_images_count); }
/** * 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; }