public static function call(OkapiRequest $request) { $issue_id = $request->get_parameter('issue_id'); if (!$issue_id) { throw new ParamMissing('issue_id'); } if (!preg_match("/^[0-9]+\$/", $issue_id) || strlen($issue_id) > 6) { throw new InvalidParam('issue_id'); } $cache_key = "apiref/issue#" . $issue_id; $result = Cache::get($cache_key); if ($result == null) { # Download the number of comments from GitHub Issue Tracker. try { $extra_headers = array(); $extra_headers[] = "Accept: application/vnd.github.v3.html+json"; $extra_headers[] = "User-Agent: https://github.com/opencaching/okapi/"; if (Settings::get('GITHUB_ACCESS_TOKEN')) { $extra_headers[] = "Authorization: token " . Settings::get('GITHUB_ACCESS_TOKEN'); } $opts = array('http' => array('method' => "GET", 'timeout' => 2.0, 'header' => implode("\r\n", $extra_headers))); $context = stream_context_create($opts); $json = file_get_contents("https://api.github.com/repos/opencaching/okapi/issues/{$issue_id}", false, $context); } catch (ErrorException $e) { throw new BadRequest("Sorry, we could not retrieve issue stats from the GitHub site. " . "This is probably due to a temporary connection problem. Try again later or contact " . "us if this seems permanent."); } $doc = json_decode($json, true); $result = array('id' => $issue_id + 0, 'last_updated' => $doc['updated_at'], 'title' => $doc['title'], 'url' => $doc['html_url'], 'comment_count' => $doc['comments']); # On one hand, we want newly added comments to show up quickly. # On the other, we don't want OKAPI to spam GitHub with queries. # So it's difficult to choose the best timeout for this. Cache::set($cache_key, $result, 3600); } return Okapi::formatted_response($request, $result); }
public static function call(OkapiRequest $request) { require_once 'replicate_common.inc.php'; $data = Cache::get("last_fulldump"); if ($data == null) { throw new BadRequest("No fulldump found. Try again later. If this doesn't help " . "contact site administrator and/or OKAPI developers."); } # Check consumer's quota $please = $request->get_parameter('pleeaase'); if ($please != 'true') { $not_good = 3 < self::count_calls($request->consumer->key, 30); if ($not_good) { throw new BadRequest("Consumer's monthly quota exceeded. Try later or call with '&pleeaase=true'."); } } else { $not_good = 5 < self::count_calls($request->consumer->key, 1); if ($not_good) { throw new BadRequest("No more please. Seriously, dude..."); } } $response = new OkapiHttpResponse(); $response->content_type = $data['meta']['content_type']; $response->content_disposition = 'attachment; filename="' . $data['meta']['public_filename'] . '"'; $response->stream_length = $data['meta']['compressed_size']; $response->body = fopen($data['meta']['filepath'], "rb"); $response->allow_gzip = false; return $response; }
public function __construct() { $cache_key = 'changelog'; $cache_backup_key = 'changelog-backup'; $changes_xml = Cache::get($cache_key); $changelog = null; if (!$changes_xml) { # Download the current changelog. try { $opts = array('http' => array('method' => "GET", 'timeout' => 5.0)); $context = stream_context_create($opts); $changes_xml = file_get_contents('https://raw.githubusercontent.com/opencaching/okapi/master/etc/changes.xml', false, $context); $changelog = simplexml_load_string($changes_xml); if (!$changelog) { throw new ErrorException(); } Cache::set($cache_key, $changes_xml, 3600); Cache::set($cache_backup_key, $changes_xml, 3600 * 24 * 30); } catch (Exception $e) { # GitHub failed on us. User backup list, if available. $changes_xml = Cache::get($cache_backup_key); if ($changes_xml) { Cache::set($cache_key, $changes_xml, 3600 * 12); } } } if (!$changelog && $changes_xml) { $changelog = simplexml_load_string($changes_xml); } # TODO: verify XML scheme $this->unavailable_changes = array(); $this->available_changes = array(); if (!$changelog) { # We could not retreive the changelog from Github, and there was # no backup key or it expired. Probably we are on a developer # machine. The template will output some error message. } else { $commits = array(); $versions = array(); foreach ($changelog->changes->change as $change) { $change = array('commit' => (string) $change['commit'], 'version' => (string) $change['version'], 'time' => (string) $change['time'], 'type' => (string) $change['type'], 'comment' => trim(self::get_inner_xml($change))); if (strlen($change['commit']) != 8 || $change['version'] == 0 || $change['time'] == '' || isset($commits[$change['commit']]) || isset($versions[$change['version']])) { # All of these problems would have been detected or prevented # by update_changes. throw new Exception("Someone forgot to run update_changes.php (or ignored error messages)."); } else { if ($change['version'] > Okapi::$version_number) { $this->unavailable_changes[] = $change; } else { $this->available_changes[] = $change; } $commits[$change['commit']] = true; $versions[$change['version']] = true; } } } }
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(OkapiRequest $request) { require_once 'replicate_common.inc.php'; $result = array(); $result['changelog'] = array('min_since' => ReplicateCommon::get_min_since(), 'revision' => ReplicateCommon::get_revision()); $dump = Cache::get("last_fulldump"); if ($dump) { $result['latest_fulldump'] = array('revision' => $dump['revision'], 'generated_at' => $dump['meta']['generated_at'], 'size' => $dump['meta']['compressed_size'], 'size_uncompressed' => $dump['meta']['uncompressed_size']); } else { $result['latest_fulldump'] = null; } return Okapi::formatted_response($request, $result); }
public static function call(OkapiRequest $request) { $methodnames = OkapiServiceRunner::$all_names; sort($methodnames); $cache_key = "api_ref/method_index#" . md5(implode("#", $methodnames)); $results = Cache::get($cache_key); if ($results == null) { $results = array(); foreach ($methodnames as $methodname) { $info = OkapiServiceRunner::call('services/apiref/method', new OkapiInternalRequest(new OkapiInternalConsumer(), null, array('name' => $methodname))); $results[] = array('name' => $info['name'], 'brief_description' => $info['brief_description']); } Cache::set($cache_key, $results, 3600); } return Okapi::formatted_response($request, $results); }
/** * Get the mapping: A-codes => attribute name. The language for the name * is selected based on the $langpref parameter. The result is cached! */ public static function get_acode_to_name_mapping($langpref) { static $mapping = null; if ($mapping !== null) { return $mapping; } $cache_key = md5(serialize(array("attrhelper/acode2name", $langpref, Okapi::$revision, self::cache_key_suffix()))); $mapping = Cache::get($cache_key); if (!$mapping) { self::init_from_cache(); $mapping = array(); foreach (self::$attr_dict as $acode => &$attr_ref) { $mapping[$acode] = Okapi::pick_best_language($attr_ref['names'], $langpref); } Cache::set($cache_key, $mapping, self::ttl()); } return $mapping; }
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; }
/** * 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 static function call(OkapiRequest $request) { $checkpointA_started = microtime(true); # Make sure the request is internal. if (in_array($request->consumer->key, array('internal', 'facade'))) { /* Okay, these two consumers can always access it. */ } elseif ($request->consumer->hasFlag(OkapiConsumer::FLAG_MAPTILE_ACCESS)) { /* If the Consumer is aware that it is not backward-compatible, then * he may be granted permission to access it. */ } else { throw new BadRequest("Your Consumer Key has not been allowed to access this method."); } # zoom, x, y - required tile-specific parameters. $zoom = self::require_uint($request, 'z'); if ($zoom > 21) { throw new InvalidParam('z', "Maximum value for this parameter is 21."); } $x = self::require_uint($request, 'x'); $y = self::require_uint($request, 'y'); if ($x >= 1 << $zoom) { throw new InvalidParam('x', "Should be in 0.." . ((1 << $zoom) - 1) . "."); } if ($y >= 1 << $zoom) { throw new InvalidParam('y', "Should be in 0.." . ((1 << $zoom) - 1) . "."); } # Now, we will create a search set (or use one previously created). # Instead of creating a new OkapiInternalRequest object, we will pass # the current request directly. We can do that, because we inherit all # of the "save" method's parameters. $search_set = OkapiServiceRunner::call('services/caches/search/save', new OkapiInternalRequest($request->consumer, $request->token, $request->get_all_parameters_including_unknown())); $set_id = $search_set['set_id']; # Get caches which are present in the result set AND within the tile # (+ those around the borders). $rs = TileTree::query_fast($zoom, $x, $y, $set_id); $rows = array(); if ($rs !== null) { while ($row = Db::fetch_row($rs)) { $rows[] = $row; } unset($row); } OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointA", null, microtime(true) - $checkpointA_started); $checkpointB_started = microtime(true); # Add dynamic, user-related flags. if (count($rows) > 0) { # Load user-related cache ids. $cache_key = "tileuser/" . $request->token->user_id; $user = self::$USE_OTHER_CACHE ? Cache::get($cache_key) : null; if ($user === null) { $user = array(); # Ignored caches. $rs = Db::query("\n select cache_id\n from cache_ignore\n where user_id = '" . Db::escape_string($request->token->user_id) . "'\n "); $user['ignored'] = array(); while (list($cache_id) = Db::fetch_row($rs)) { $user['ignored'][$cache_id] = true; } # Found caches. $rs = Db::query("\n select distinct cache_id\n from cache_logs\n where\n user_id = '" . Db::escape_string($request->token->user_id) . "'\n and type = 1\n and " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "deleted = 0" : "true") . "\n "); $user['found'] = array(); while (list($cache_id) = Db::fetch_row($rs)) { $user['found'][$cache_id] = true; } # Own caches. $rs = Db::query("\n select distinct cache_id\n from caches\n where user_id = '" . Db::escape_string($request->token->user_id) . "'\n "); $user['own'] = array(); while (list($cache_id) = Db::fetch_row($rs)) { $user['own'][$cache_id] = true; } Cache::set($cache_key, $user, 30); } # Add extra flags to geocaches. foreach ($rows as &$row_ref) { # Add the "found" flag (to indicate that this cache needs # to be drawn as found) and the "own" flag (to indicate that # the current user is the owner). if (isset($user['found'][$row_ref[0]])) { $row_ref[6] |= TileTree::$FLAG_FOUND; } # $row[6] is "flags" if (isset($user['own'][$row_ref[0]])) { $row_ref[6] |= TileTree::$FLAG_OWN; } # $row[6] is "flags" } } # Compute the image hash/fingerprint. This will be used both for ETags # and internal cache ($cache_key). $tile = new TileRenderer($zoom, $rows); $image_fingerprint = $tile->get_unique_hash(); # Start creating response. $response = new OkapiHttpResponse(); $response->content_type = $tile->get_content_type(); $response->cache_control = "Cache-Control: private, max-age=600"; $response->etag = 'W/"' . $image_fingerprint . '"'; $response->allow_gzip = false; // images are usually compressed, prevent compression at Apache level # Check if the request didn't include the same ETag. OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointB", null, microtime(true) - $checkpointB_started); $checkpointC_started = microtime(true); if (self::$USE_ETAGS_CACHE && $request->etag == $response->etag) { # Hit. Report the content was unmodified. $response->etag = null; $response->status = "304 Not Modified"; return $response; } # Check if the image was recently rendered and is kept in image cache. $cache_key = "tile/" . $image_fingerprint; $response->body = self::$USE_IMAGE_CACHE ? Cache::get($cache_key) : null; OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointC", null, microtime(true) - $checkpointC_started); $checkpointD_started = microtime(true); if ($response->body !== null) { # Hit. We will use the cached version of the image. return $response; } # Miss. Render the image. Cache the result. $response->body = $tile->render(); Cache::set_scored($cache_key, $response->body); OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointD", null, microtime(true) - $checkpointD_started); return $response; }
/** Same as `cache_delete`, but works on many keys at once. */ public static function cache_delete_many($keys) { $prefixed_keys = array(); foreach ($keys as $key) { $prefixed_keys[] = "facade#" . $key; } Cache::delete_many($prefixed_keys); }
# 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"; } # h_avail, h_temp_unavail, h_arch ("hide available" etc.) - convert to # OKAPI's "status" filter. $tmp = array(); if ($_GET['h_avail'] != "true") { $tmp[] = "Available";
/** Send an email message to local OKAPI administrators. */ public static function mail_admins($subject, $message) { # Make sure we're not sending HUGE emails. if (strlen($message) > 10000) { $message = substr($message, 0, 10000) . "\n\n...(message clipped at 10k chars)\n"; } # Make sure we're not spamming. $cache_key = 'mail_admins_counter/' . floor(time() / 3600) * 3600 . '/' . md5($subject); try { $counter = Cache::get($cache_key); } catch (DbException $e) { # This exception can occur during OKAPI update (#156), or when # the cache table is broken (#340). I am not sure which option is # better: 1. notify the admins about the error and risk spamming # them, 2. don't notify and don't risk spamming them. Currently, # I choose option 2. return; } if ($counter === null) { $counter = 0; } $counter++; try { Cache::set($cache_key, $counter, 3600); } catch (DbException $e) { # If `get` suceeded and `set` did not, then probably we're having # issue #156 scenario. We can ignore it here. } if ($counter <= 5) { # We're not spamming yet. self::mail_from_okapi(get_admin_emails(), $subject, $message); } else { # We are spamming. Prevent sending more emails. $content_cache_key_prefix = 'mail_admins_spam/' . floor(time() / 3600) * 3600 . '/'; $timeout = 86400; if ($counter == 6) { self::mail_from_okapi(get_admin_emails(), "Anti-spam mode activated for '{$subject}'", "OKAPI has activated an \"anti-spam\" mode for the following subject:\n\n" . "\"{$subject}\"\n\n" . "Anti-spam mode is activiated when more than 5 messages with\n" . "the same subject are sent within one hour.\n\n" . "Additional debug information:\n" . "- counter cache key: {$cache_key}\n" . "- content prefix: {$content_cache_key_prefix}<n>\n" . "- content timeout: {$timeout}\n"); } $content_cache_key = $content_cache_key_prefix . $counter; Cache::set($content_cache_key, $message, $timeout); } }
private static function ver41() { # Force changelog reset (will be produced one day back) Db::execute("delete from okapi_vars where var='last_clog_update'"); # Force all cronjobs rerun Okapi::set_var("cron_nearest_event", 0); Cache::delete('cron_schedule'); }
/** Send an email message to local OKAPI administrators. */ public static function mail_admins($subject, $message) { # Make sure we're not sending HUGE emails. if (strlen($message) > 10000) { $message = substr($message, 0, 10000) . "\n\n...(message clipped at 10k chars)\n"; } # Make sure we're not spamming. $cache_key = 'mail_admins_counter/' . floor(time() / 3600) * 3600 . '/' . md5($subject); try { $counter = Cache::get($cache_key); } catch (DbException $e) { # Why catching exceptions here? See bug#156. $counter = null; } if ($counter === null) { $counter = 0; } $counter++; try { Cache::set($cache_key, $counter, 3600); } catch (DbException $e) { # Why catching exceptions here? See bug#156. } if ($counter <= 5) { # We're not spamming yet. self::mail_from_okapi(get_admin_emails(), $subject, $message); } else { # We are spamming. Prevent sending more emails. $content_cache_key_prefix = 'mail_admins_spam/' . floor(time() / 3600) * 3600 . '/'; $timeout = 86400; if ($counter == 6) { self::mail_from_okapi(get_admin_emails(), "Anti-spam mode activated for '{$subject}'", "OKAPI has activated an \"anti-spam\" mode for the following subject:\n\n" . "\"{$subject}\"\n\n" . "Anti-spam mode is activiated when more than 5 messages with\n" . "the same subject are sent within one hour.\n\n" . "Additional debug information:\n" . "- counter cache key: {$cache_key}\n" . "- content prefix: {$content_cache_key_prefix}<n>\n" . "- content timeout: {$timeout}\n"); } $content_cache_key = $content_cache_key_prefix . $counter; Cache::set($content_cache_key, $message, $timeout); } }
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"); }
public static function call() { # This is a hidden page for OKAPI developers. It will output a cronjobs # report. This is useful for debugging. $response = new OkapiHttpResponse(); $response->content_type = "text/plain; charset=utf-8"; ob_start(); require_once $GLOBALS['rootpath'] . "okapi/cronjobs.php"; $schedule = Cache::get("cron_schedule"); if ($schedule == null) { $schedule = array(); } print "Nearest event: "; if (Okapi::get_var('cron_nearest_event')) { print "in " . (Okapi::get_var('cron_nearest_event') - time()) . " seconds.\n\n"; } else { print "NOT SET\n\n"; } $cronjobs = CronJobController::get_enabled_cronjobs(); usort($cronjobs, function ($a, $b) { $cmp = function ($a, $b) { return $a < $b ? -1 : ($a > $b ? 1 : 0); }; $by_type = $cmp($a->get_type(), $b->get_type()); if ($by_type != 0) { return $by_type; } return $cmp($a->get_name(), $b->get_name()); }); print str_pad("TYPE", 11) . " " . str_pad("NAME", 40) . " SCHEDULE\n"; print str_pad("----", 11) . " " . str_pad("----", 40) . " --------\n"; foreach ($cronjobs as $cronjob) { $type = $cronjob->get_type(); $name = $cronjob->get_name(); print str_pad($type, 11) . " " . str_pad($name, 40) . " "; if (!isset($schedule[$name])) { print "NOT YET SCHEDULED\n"; } elseif ($schedule[$name] <= time()) { print "DELAYED: should be run " . (time() - $schedule[$name]) . " seconds ago\n"; } else { print "scheduled to run in " . str_pad($schedule[$name] - time(), 6, " ", STR_PAD_LEFT) . " seconds\n"; } } print "\n"; print "Crontab last ping: "; if (Cache::get('crontab_last_ping')) { print time() - Cache::get('crontab_last_ping') . " seconds ago"; } else { print "NEVER"; } print " (crontab_check_counter: " . Cache::get('crontab_check_counter') . ").\n"; print "clog_revisions_daily: "; if (Cache::get('clog_revisions_daily')) { foreach (Cache::get('clog_revisions_daily') as $time => $rev) { print "{$rev} "; } print "\n"; } else { print "NULL\n"; } $response->body = ob_get_clean(); return $response; }
/** * Return 64x26 bitmap with the caption (name) for the given geocache. */ private function get_caption($cache_id) { # Check cache. $cache_key = "tilecaption/" . self::$VERSION . "/" . $cache_id; $gd2 = self::$USE_CAPTIONS_CACHE ? Cache::get($cache_key) : null; if ($gd2 === null) { # We'll work with 16x bigger image to get smoother interpolation. $im = imagecreatetruecolor(64 * 4, 26 * 4); imagealphablending($im, false); $transparent = imagecolorallocatealpha($im, 255, 255, 255, 127); imagefilledrectangle($im, 0, 0, 64 * 4, 26 * 4, $transparent); imagealphablending($im, true); # Get the name of the cache. $name = Db::select_value("\n select name\n from caches\n where cache_id = '" . mysql_real_escape_string($cache_id) . "'\n "); # Split the name into a couple of lines. //$font = $GLOBALS['rootpath'].'util.sec/bt.ttf'; $font = $GLOBALS['rootpath'] . 'okapi/static/tilemap/tahoma.ttf'; $size = 25; $lines = explode("\n", self::wordwrap($font, $size, 64 * 4 - 6 * 2, $name)); # For each line, compute its (x, y) so that the text is centered. $y = 0; $positions = array(); foreach ($lines as $line) { $bbox = imagettfbbox($size, 0, $font, $line); $width = $bbox[2] - $bbox[0]; $x = 128 - ($width >> 1); $positions[] = array($x, $y); $y += 36; } $drawer = function ($x, $y, $color) use(&$lines, &$positions, &$im, &$size, &$font) { $len = count($lines); for ($i = 0; $i < $len; $i++) { $line = $lines[$i]; list($offset_x, $offset_y) = $positions[$i]; imagettftext($im, $size, 0, $offset_x + $x, $offset_y + $y, $color, $font, $line); } }; # Draw an outline. $outline_color = imagecolorallocatealpha($im, 255, 255, 255, 80); for ($x = 0; $x <= 12; $x += 3) { for ($y = $size - 3; $y <= $size + 9; $y += 3) { $drawer($x, $y, $outline_color); } } # Add a slight shadow effect (on top of the outline). $drawer(9, $size + 3, imagecolorallocatealpha($im, 0, 0, 0, 110)); # Draw the caption. $drawer(6, $size + 3, imagecolorallocatealpha($im, 150, 0, 0, 40)); # Resample. imagealphablending($im, false); $small = imagecreatetruecolor(64, 26); imagealphablending($small, false); imagecopyresampled($small, $im, 0, 0, 0, 0, 64, 26, 64 * 4, 26 * 4); # Cache it! ob_start(); imagegd2($small); $gd2 = ob_get_clean(); Cache::set_scored($cache_key, $gd2); } return imagecreatefromstring($gd2); }
public static function call(OkapiRequest $request) { $cache_codes = $request->get_parameter('cache_codes'); if ($cache_codes === null) { throw new ParamMissing('cache_codes'); } # Issue 106 requires us to allow empty list of cache codes to be passed into this method. # All of the queries below have to be ready for $cache_codes to be empty! $langpref = $request->get_parameter('langpref'); if (!$langpref) { $langpref = "en|" . Settings::get('SITELANG'); } $images = $request->get_parameter('images'); if (!$images) { $images = "all"; } if (!in_array($images, array("none", "all", "spoilers", "nonspoilers"))) { throw new InvalidParam('images'); } $format = $request->get_parameter('caches_format'); if (!$format) { $format = "gpx"; } if (!in_array($format, array("gpx", "ggz"))) { throw new InvalidParam('caches_format'); } $location_source = $request->get_parameter('location_source'); $location_change_prefix = $request->get_parameter('location_change_prefix'); # Start creating ZIP archive. $response = new OkapiZIPHttpResponse(); # Include a GPX/GGZ file compatible with Garmin devices. It should include all # Geocaching.com (groundspeak:) and Opencaching.com (ox:) extensions. It will # also include image references (actual images will be added as separate files later) # and personal data (if the method was invoked using Level 3 Authentication). switch ($format) { case 'gpx': $data_filename = "Garmin/GPX/opencaching" . time() . rand(100000, 999999) . ".gpx"; $data_method = 'services/caches/formatters/gpx'; $data_use_compression = true; break; case 'ggz': $data_filename = "Garmin/GGZ/opencaching" . time() . rand(100000, 999999) . ".ggz"; $data_method = 'services/caches/formatters/ggz'; $data_use_compression = false; break; } $response->zip->FileAdd($data_filename, OkapiServiceRunner::call($data_method, new OkapiInternalRequest($request->consumer, $request->token, array('cache_codes' => $cache_codes, 'langpref' => $langpref, 'ns_ground' => 'true', 'ns_ox' => 'true', 'images' => 'ox:all', 'attrs' => 'ox:tags', 'trackables' => 'desc:count', 'alt_wpts' => 'true', 'recommendations' => 'desc:count', 'latest_logs' => 'true', 'lpc' => 'all', 'my_notes' => $request->token != null ? "desc:text" : "none", 'location_source' => $location_source, 'location_change_prefix' => $location_change_prefix)))->get_body(), clsTbsZip::TBSZIP_STRING, $data_use_compression); # Then, include all the images. $caches = OkapiServiceRunner::call('services/caches/geocaches', new OkapiInternalRequest($request->consumer, $request->token, array('cache_codes' => $cache_codes, 'langpref' => $langpref, 'fields' => "images"))); if (count($caches) > 50) { throw new InvalidParam('cache_codes', "The maximum number of caches allowed to be downloaded with this method is 50."); } if ($images != 'none') { $supported_extensions = array('jpg', 'jpeg', 'gif', 'png', 'bmp'); foreach ($caches as $cache_code => $dict) { $imgs = $dict['images']; if (count($imgs) == 0) { continue; } $dir = "Garmin/GeocachePhotos/" . $cache_code[strlen($cache_code) - 1]; $dir .= "/" . $cache_code[strlen($cache_code) - 2]; $dir .= "/" . $cache_code; foreach ($imgs as $no => $img) { if ($images == 'spoilers' && !$img['is_spoiler']) { continue; } if ($images == 'nonspoilers' && $img['is_spoiler']) { continue; } $tmp = false; foreach ($supported_extensions as $ext) { if (strtolower(substr($img['url'], strlen($img['url']) - strlen($ext) - 1)) != "." . $ext) { $tmp = true; continue; } } if (!$tmp) { continue; } # unsupported file extension if ($img['is_spoiler']) { $zippath = $dir . "/Spoilers/" . $img['unique_caption'] . ".jpg"; } else { $zippath = $dir . "/" . $img['unique_caption'] . ".jpg"; } # The safest way would be to use the URL, but that would be painfully slow! # That's why we're trying to access files directly (and fail silently on error). # This was tested on OCPL server only. # Note: Oliver Dietz (oc.de) replied that images with 'local' set to 0 could not # be accessed locally. But all the files have 'local' set to 1 anyway. $syspath = Settings::get('IMAGES_DIR') . "/" . $img['uuid'] . ".jpg"; if (file_exists($syspath)) { $response->zip->FileAdd($zippath, $syspath, clsTbsZip::TBSZIP_FILE, false); } else { # If file exists, but does not end with ".jpg", we will create # JPEG version of it and store it in the cache. $cache_key = "jpg#" . $img['uuid']; $jpeg_contents = Cache::get($cache_key); if ($jpeg_contents === null) { foreach ($supported_extensions as $ext) { $syspath_other = Settings::get('IMAGES_DIR') . "/" . $img['uuid'] . "." . $ext; if (file_exists($syspath_other)) { try { $image = imagecreatefromstring(file_get_contents($syspath_other)); ob_start(); imagejpeg($image); $jpeg_contents = ob_get_clean(); imagedestroy($image); } catch (Exception $e) { # GD couldn't parse the file. We will skip it, and cache # the "false" value as the contents. This way, we won't # attempt to parse it during the next 24 hours. $jpeg_contents = false; } Cache::set($cache_key, $jpeg_contents, 86400); break; } } } if ($jpeg_contents) { # This can be "null" *or* "false"! $response->zip->FileAdd($zippath, $jpeg_contents, clsTbsZip::TBSZIP_STRING, false); } } } } } # The result could be big, but it's created and streamed right # to the browser, so it shouldn't hit our memory limit. We also # should set a higher time limit, because downloading this response # may take some time over slow network connections (and I'm not sure # what is the PHP's default way of handling such scenario). set_time_limit(600); $response->content_type = "application/zip"; $response->content_disposition = 'attachment; filename="results.zip"'; return $response; }
public static function call(OkapiRequest $request) { # The list of installations is periodically refreshed by contacting OKAPI # repository. This method usually displays the cached version of it. $cachekey = 'apisrv/installations'; $backupkey = 'apisrv/installations-backup'; $results = Cache::get($cachekey); if (!$results) { # Download the current list of OKAPI servers. try { $opts = array('http' => array('method' => "GET", 'timeout' => 5.0)); $context = stream_context_create($opts); $xml = file_get_contents("http://opencaching-api.googlecode.com/svn/trunk/etc/installations.xml", false, $context); $doc = simplexml_load_string($xml); if (!$doc) { throw new ErrorException(); # just to get to the catch block } } catch (ErrorException $e) { # Google failed on us. Try to respond with a backup list. $results = Cache::get($backupkey); if ($results) { Cache::set($cachekey, $results, 12 * 3600); # so to retry no earlier than after 12 hours return Okapi::formatted_response($request, $results); } # Backup has expired (or have never been cached). If we're on a development # server then probably it's okay. In production this SHOULD NOT happen. $results = array(array('site_url' => Settings::get('SITE_URL'), 'site_name' => "Unable to retrieve!", 'okapi_base_url' => Settings::get('SITE_URL') . "okapi/")); Cache::set($cachekey, $results, 12 * 3600); # so to retry no earlier than after 12 hours return Okapi::formatted_response($request, $results); } $results = array(); $i_was_included = false; foreach ($doc->installation as $inst) { $site_url = (string) $inst[0]['site_url']; if ($inst[0]['okapi_base_url']) { $okapi_base_url = (string) $inst[0]['okapi_base_url']; } else { $okapi_base_url = $site_url . "okapi/"; } if ($inst[0]['site_name']) { $site_name = (string) $inst[0]['site_name']; } else { $site_name = Okapi::get_normalized_site_name($site_url); } $results[] = array('site_url' => $site_url, 'site_name' => $site_name, 'okapi_base_url' => $okapi_base_url); if ($site_url == Settings::get('SITE_URL')) { $i_was_included = true; } } # If running on a local development installation, then include the local # installation URL. if (!$i_was_included) { $results[] = array('site_url' => Settings::get('SITE_URL'), 'site_name' => "DEVELSITE", 'okapi_base_url' => Settings::get('SITE_URL') . "okapi/"); # Contact OKAPI developers in order to get added to the official sites list! } # Cache it for one day. Also, save a backup (valid for 30 days). Cache::set($cachekey, $results, 86400); Cache::set($backupkey, $results, 86400 * 30); } return Okapi::formatted_response($request, $results); }