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); }
/** * 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); }
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() { # 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 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 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 "); } }
private static function ver93() { Db::execute("alter table okapi_tile_caches add column name_crc int(10) unsigned not null default 0;"); }
private static function ver94() { Db::execute("\n CREATE TABLE okapi_stats_monthly (\n consumer_key varchar(32) NOT NULL,\n user_id int(10) NOT NULL,\n period_start datetime NOT NULL,\n service_name varchar(80) NOT NULL,\n total_calls int(10) NOT NULL,\n http_calls int(10) NOT NULL,\n total_runtime float NOT NULL DEFAULT '0',\n http_runtime float NOT NULL DEFAULT '0',\n PRIMARY KEY (consumer_key,user_id,period_start,service_name)\n ) ENGINE=MyISAM DEFAULT CHARSET=utf8;\n "); }
public function execute() { require_once $GLOBALS['rootpath'] . "okapi/services/replicate/replicate_common.inc.php"; $max_revision = ReplicateCommon::get_revision(); $cache_key = 'clog_revisions_daily'; $data = Cache::get($cache_key); if ($data == null) { $data = array(); } $data[time()] = $max_revision; $new_min_revision = 1; $new_data = array(); foreach ($data as $time => $r) { if ($time < time() - 10 * 86400) { $new_min_revision = max($new_min_revision, $r); } else { $new_data[$time] = $r; } } Db::execute("\n delete from okapi_clog\n where id < '" . mysql_real_escape_string($new_min_revision) . "'\n "); Cache::set($cache_key, $new_data, 10 * 86400); Db::query("optimize table okapi_clog"); }
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; }
/** * Edit an log entry image and return its (new) position. * Throws CannotPublishException or BadRequest on errors. */ private static function _call(OkapiRequest $request) { # Developers! Please notice the fundamental difference between throwing # CannotPublishException and the "standard" BadRequest/InvalidParam # exceptions. CannotPublishException will be caught by the service's # call() function and returns a message to be displayed to the user. require_once 'log_images_common.inc.php'; # validate the 'image_uuid' parameter list($image_uuid, $log_internal_id) = LogImagesCommon::validate_image_uuid($request); # validate the 'caption', 'is_spoiler' and 'position' parameters $caption = $request->get_parameter('caption'); if ($caption !== null && $caption == '') { throw new CannotPublishException(sprintf(_("Please enter an image caption."), Okapi::get_normalized_site_name())); } $is_spoiler = $request->get_parameter('is_spoiler'); if ($is_spoiler !== null) { if (!in_array($is_spoiler, array('true', 'false'))) { throw new InvalidParam('is_spoiler'); } } $position = LogImagesCommon::validate_position($request); if ($caption === null && $is_spoiler === null && $position === null) { # If no-params were allowed, what would be the success message? # It's more reasonable to assume that this was a developer's error. throw new BadRequest("At least one of the parameters 'caption', 'is_spoiler' and 'position' must be supplied"); } $image_uuid_escaped = Db::escape_string($image_uuid); $log_entry_modified = false; # update caption if ($caption !== null) { Db::execute("\n update pictures\n set title = '" . Db::escape_string($caption) . "'\n where uuid = '" . $image_uuid_escaped . "'\n "); $log_entry_modified = true; } # update spoiler flag if ($is_spoiler !== null) { Db::execute("\n update pictures\n set spoiler = " . ($is_spoiler == 'true' ? 1 : 0) . "\n where uuid = '" . $image_uuid_escaped . "'\n "); $log_entry_modified = true; } # update position if ($position !== null) { if (Settings::get('OC_BRANCH') == 'oc.pl') { # OCPL as no arbitrary log picture ordering => ignore position parameter # and return the picture's current position. $image_uuids = Db::select_column("\n select uuid from pictures\n where object_type = 1 and object_id = '" . Db::escape_string($log_internal_id) . "'\n order by date_created\n "); $position = array_search($image_uuid, $image_uuids); } else { list($position, $seq) = LogImagesCommon::prepare_position($log_internal_id, $position, 0); # For OCDE the pictures table is write locked now. $old_seq = DB::select_value("\n select seq from pictures where uuid = '" . $image_uuid_escaped . "'\n "); if ($seq != $old_seq) { # First move the edited picture to the end, to make space for rotating. # Remember that we have no transactions at OC.de. If something goes wrong, # the image will stay at the end of the list. $max_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 "); Db::query("\n update pictures\n set seq = '" . Db::escape_string($max_seq + 1) . "'\n where uuid = '" . $image_uuid_escaped . "'\n "); # now move the pictures inbetween if ($seq < $old_seq) { Db::execute("\n update pictures\n set seq = seq + 1\n where\n object_type = 1\n and object_id = '" . Db::escape_string($log_internal_id) . "'\n and seq >= '" . Db::escape_string($seq) . "'\n and seq < '" . Db::escape_string($old_seq) . "'\n order by seq desc\n "); } else { Db::execute("\n update pictures\n set seq = seq - 1\n where\n object_type = 1\n and object_id = '" . Db::escape_string($log_internal_id) . "'\n and seq <= '" . Db::escape_string($seq) . "'\n and seq > '" . Db::escape_string($old_seq) . "'\n order by seq asc\n "); } # and finally move the edited picture into place Db::query("\n update pictures\n set seq = '" . Db::escape_string($seq) . "'\n where uuid = '" . $image_uuid_escaped . "'\n "); } Db::execute('unlock tables'); $log_entry_modified = true; } } if (Settings::get('OC_BRANCH') == 'oc.pl' && $log_entry_modified) { # OCDE touches the log entry via trigger, OCPL needs an explicit update. # This will also update okapi_syncbase. Db::query("\n update cache_logs\n set last_modified = NOW()\n where id = '" . Db::escape_string($log_internal_id) . "'\n "); # OCPL code currently does not update pictures.last_modified when # editing, but that is a bug, see # https://github.com/opencaching/opencaching-pl/issues/341. } return $position; }
private static function ver90() { Db::execute("alter table okapi_consumers change column admin bflags tinyint not null default 0;"); }
private static function ver88() { Db::execute("alter table okapi_consumers add column admin tinyint not null default 0;"); }
private static function ver95() { # See comments on ver7. Db::execute("\n CREATE TABLE okapi_submitted_objects (\n object_type tinyint(2) NOT NULL,\n object_id int(11) NOT NULL,\n consumer_key varchar(20) charset ascii collate ascii_bin NOT NULL,\n PRIMARY KEY (object_type, object_id),\n KEY by_consumer (consumer_key, object_type)\n ) ENGINE=MyISAM DEFAULT CHARSET=utf8\n (\n SELECT\n " . Okapi::OBJECT_TYPE_CACHE_LOG . " object_type,\n log_id object_id,\n consumer_key\n FROM okapi_cache_logs\n )\n "); Db::execute("\n DROP TABLE okapi_cache_logs\n "); }
/** * Remove all OAuth Access Tokens bound to a certain user. This method * should be called (once) e.g. after a user is banned. It will disable his * ability to submit cache logs, etc. * * Note, that OKAPI will *automatically* remove Access Tokens of banned * users, but it will perform this action with a slight delay. This method * can be used to do this immediatelly. See #432 for details. */ public static function remove_user_tokens($user_id) { Db::execute("\n delete from okapi_tokens\n where user_id = '" . Db::escape_string($user_id) . "'\n "); }
private static function insert_log_row($consumer_key, $cache_internal_id, $user_internal_id, $logtype, $when, $formatted_comment, $text_html, $needs_maintenance2) { if (Settings::get('OC_BRANCH') == 'oc.de') { $needs_maintenance_field_SQL = ', needs_maintenance'; if ($needs_maintenance2 == 'true') { $needs_maintenance_SQL = ',2'; } else { if ($needs_maintenance2 == 'false') { $needs_maintenance_SQL = ',1'; } else { // 'null' $needs_maintenance_SQL = ',0'; } } } else { $needs_maintenance_field_SQL = ''; $needs_maintenance_SQL = ''; } $log_uuid = Okapi::create_uuid(); Db::execute("\n insert into cache_logs (\n uuid, cache_id, user_id, type, date, text, text_html,\n last_modified, date_created, node" . $needs_maintenance_field_SQL . "\n ) values (\n '" . Db::escape_string($log_uuid) . "',\n '" . Db::escape_string($cache_internal_id) . "',\n '" . Db::escape_string($user_internal_id) . "',\n '" . Db::escape_string(Okapi::logtypename2id($logtype)) . "',\n from_unixtime('" . Db::escape_string($when) . "'),\n '" . Db::escape_string($formatted_comment) . "',\n '" . Db::escape_string($text_html) . "',\n now(),\n now(),\n '" . Db::escape_string(Settings::get('OC_NODE_ID')) . "'\n " . $needs_maintenance_SQL . "\n );\n "); $log_internal_id = Db::last_insert_id(); # Store additional information on consumer_key which has created this log entry. # (Maybe we'll want to display this somewhen later.) Db::execute("\n insert into okapi_submitted_objects (object_type, object_id, consumer_key)\n values (\n " . Okapi::OBJECT_TYPE_CACHE_LOG . ",\n '" . Db::escape_string($log_internal_id) . "',\n '" . Db::escape_string($consumer_key) . "'\n );\n "); return $log_uuid; }
private static function db_insert_image($consumer_key, $user_id, $log_internal_id, $image_uuid, $position, $caption, $is_spoiler, $file_ext) { require_once 'log_images_common.inc.php'; list($position, $seq, $log_images_count) = LogImagesCommon::prepare_position($log_internal_id, $position, +1); # For OCDE the pictures table is write locked now. # Transactions do not work on OCDE MyISAM tables. However, the worst # thing that can happen on OCDE is creating a sequence number gap, # which is allowed. # # For OCPL InnoDB tables, the transactions DO and MUST work, because # we write to two dependent tables. Db::execute('start transaction'); # shift positions of existing images to make space for the new one if ($position < $log_images_count && Settings::get('OC_BRANCH') == 'oc.de') { Db::execute("\n update pictures\n set seq = seq + 1\n where\n object_type = 1\n and object_id = '" . Db::escape_string($log_internal_id) . "'\n and seq >= '" . Db::escape_string($seq) . "'\n order by seq desc\n "); } if (Settings::get('OC_BRANCH') == 'oc.de') { $local_fields_SQL = "seq"; $local_values_escaped_SQL = "'" . Db::escape_string($seq) . "'"; # All other fields are set by trigger or defaults for OCDE. } else { # These are the additional fields that OCPL newpic.php supplies # (seq is set from default): $local_fields_SQL = "date_created, last_modified, description, desc_html, last_url_check, user_id"; $local_values_escaped_SQL = "NOW(), NOW(), '', 0, NOW(), '" . Db::escape_string($user_id) . "'"; } Db::execute("\n insert into pictures (\n uuid, node, local, title, spoiler, url, object_type, object_id,\n unknown_format, display,\n " . $local_fields_SQL . "\n )\n values (\n '" . Db::escape_string($image_uuid) . "',\n '" . Db::escape_string(Settings::get('OC_NODE_ID')) . "',\n 1,\n '" . Db::escape_string($caption) . "',\n '" . ($is_spoiler == 'true' ? 1 : 0) . "',\n '" . Db::escape_string(Settings::get('IMAGES_URL') . $image_uuid . $file_ext) . "',\n 1,\n '" . Db::escape_string($log_internal_id) . "',\n 0,\n 1,\n " . $local_values_escaped_SQL . "\n )\n "); $image_internal_id = Db::last_insert_id(); # update OCPL log entry properties; OCDE does everything necessary by triggers if (Settings::get('OC_BRANCH') == 'oc.pl') { # 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 triggers. Db::execute("\n update cache_logs\n set\n picturescount = picturescount + 1,\n last_modified = NOW()\n where id = '" . Db::escape_string($log_internal_id) . "'\n "); } # Store information on the consumer_key which uploaded this image. # (Maybe we'll want to display this somewhen later.) Db::execute("\n insert into okapi_submitted_objects (object_type, object_id, consumer_key)\n values (\n '" . Okapi::OBJECT_TYPE_CACHE_LOG_IMAGE . "',\n '" . Db::escape_string($image_internal_id) . "',\n '" . Db::escape_string($consumer_key) . "'\n );\n "); Db::execute('commit'); Db::execute('unlock tables'); return $position; }
/** * 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() { $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); } }
private static function insert_log_row($consumer_key, $cache_internal_id, $user_internal_id, $logtype, $when, $formatted_comment, $text_html) { $log_uuid = self::create_uuid(); Db::execute("\n insert into cache_logs (uuid, cache_id, user_id, type, date, text, text_html, last_modified, date_created, node)\n values (\n '" . mysql_real_escape_string($log_uuid) . "',\n '" . mysql_real_escape_string($cache_internal_id) . "',\n '" . mysql_real_escape_string($user_internal_id) . "',\n '" . mysql_real_escape_string(Okapi::logtypename2id($logtype)) . "',\n from_unixtime('" . mysql_real_escape_string($when) . "'),\n '" . mysql_real_escape_string($formatted_comment) . "',\n '" . mysql_real_escape_string($text_html) . "',\n now(),\n now(),\n '" . mysql_real_escape_string(Settings::get('OC_NODE_ID')) . "'\n );\n "); $log_internal_id = Db::last_insert_id(); # Store additional information on consumer_key which have created this log entry. # (Maybe we'll want to display this somewhere later.) Db::execute("\n insert into okapi_cache_logs (log_id, consumer_key)\n values (\n '" . mysql_real_escape_string($log_internal_id) . "',\n '" . mysql_real_escape_string($consumer_key) . "'\n );\n "); return $log_uuid; }
/** Do 'delete' on many keys at once. */ public static function delete_many($keys) { if (count($keys) == 0) { return; } Db::execute("\n delete from okapi_cache\n where `key` in ('" . implode("','", array_map('mysql_real_escape_string', $keys)) . "')\n "); }
/** * 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)); }
private static function update_geocache_attributes_in_cached_tiles(&$row) { # Update all attributes (for all levels). Note, that we don't need to # update location ($row[1] and $row[2]) - this method is called ONLY # when location stayed untouched! Db::execute("\n update okapi_tile_caches\n set\n status = '" . mysql_real_escape_string($row[3]) . "',\n type = '" . mysql_real_escape_string($row[4]) . "',\n rating = " . ($row[5] === null ? "null" : "'" . mysql_real_escape_string($row[5]) . "'") . ",\n flags = '" . mysql_real_escape_string($row[6]) . "',\n name_crc = '" . mysql_real_escape_string($row[7]) . "'\n where\n cache_id = '" . mysql_real_escape_string($row[0]) . "'\n "); }
if ($searchdata) { # Mode 2 - with "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); } $params['set_and'] = $set_id; $params['status'] = "Available|Temporarily unavailable|Archived"; \okapi\OkapiErrorHandler::disable(); } else { # Mode 1 - without "searchdata". # h_ignored - convert to OKAPI's "exclude_ignored". if ($_GET['h_ignored'] == "true") { $params['exclude_ignored'] = "true";
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 "); }
/** * Find all log entries of the specified user for the specified cache and * mark them as *possibly* modified. See issue #265. * * $cache_id - internal ID of the geocache, * $user_id - internal ID of the user. */ public static function schedule_user_entries_check($cache_id, $user_id) { Db::execute("\n update cache_logs\n set okapi_syncbase = now()\n where\n cache_id = '" . mysql_real_escape_string($cache_id) . "'\n and user_id = '" . mysql_real_escape_string($user_id) . "'\n "); }
/** * 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() { # Remove tokens of banned users (there's no need to remove authorizations). # See https://github.com/opencaching/okapi/issues/432 Db::execute("\n delete t from\n okapi_tokens t,\n user u\n where\n t.user_id = u.user_id\n and u.is_active_flag != 1\n "); }