Merge pull request #148 from wrygiel/okapi

OKAPI Project update (r1031)
This commit is contained in:
bohrsty 2014-11-21 09:32:42 +01:00 committed by Nils Bohrs
commit c6e3022fee
135 changed files with 19432 additions and 18479 deletions

View File

@ -23,75 +23,75 @@ require_once($GLOBALS['rootpath'].'okapi/urls.php');
if (ob_list_handlers() == array('default output handler'))
{
# We will assume that this one comes from "output_buffering" being turned on
# in PHP config. This is very common and probably is good for most other OC
# pages. But we don't need it in OKAPI. We will just turn this off.
# We will assume that this one comes from "output_buffering" being turned on
# in PHP config. This is very common and probably is good for most other OC
# pages. But we don't need it in OKAPI. We will just turn this off.
ob_end_clean();
ob_end_clean();
}
class OkapiScriptEntryPointController
{
public static function dispatch_request($uri)
{
# Chop off the ?args=... part.
public static function dispatch_request($uri)
{
# Chop off the ?args=... part.
if (strpos($uri, '?') !== false)
$uri = substr($uri, 0, strpos($uri, '?'));
if (strpos($uri, '?') !== false)
$uri = substr($uri, 0, strpos($uri, '?'));
# Chop off everything before "/okapi/". This should work for okay for most "weird"
# server configurations. It will also address a more subtle issue described here:
# http://stackoverflow.com/questions/8040461/request-uri-unexpectedly-contains-fqdn
# Chop off everything before "/okapi/". This should work for okay for most "weird"
# server configurations. It will also address a more subtle issue described here:
# http://stackoverflow.com/questions/8040461/request-uri-unexpectedly-contains-fqdn
if (strpos($uri, "/okapi/") !== false)
$uri = substr($uri, strpos($uri, "/okapi/"));
if (strpos($uri, "/okapi/") !== false)
$uri = substr($uri, strpos($uri, "/okapi/"));
# Make sure we're in the right directory (.htaccess should make sure of that).
# Make sure we're in the right directory (.htaccess should make sure of that).
if (strpos($uri, "/okapi/") !== 0)
throw new Exception("'$uri' is outside of the /okapi/ path.");
$uri = substr($uri, 7);
if (strpos($uri, "/okapi/") !== 0)
throw new Exception("'$uri' is outside of the /okapi/ path.");
$uri = substr($uri, 7);
# Initializing internals and running pre-request cronjobs (we don't want
# cronjobs to be run before "okapi/update", for example before database
# was installed).
# Initializing internals and running pre-request cronjobs (we don't want
# cronjobs to be run before "okapi/update", for example before database
# was installed).
$allow_cronjobs = ($uri != "update");
Okapi::init_internals($allow_cronjobs);
$allow_cronjobs = ($uri != "update");
Okapi::init_internals($allow_cronjobs);
# Checking for allowed patterns...
# Checking for allowed patterns...
try
{
foreach (OkapiUrls::$mapping as $pattern => $namespace)
{
$matches = null;
if (preg_match("#$pattern#", $uri, $matches))
{
# Pattern matched! Moving on to the proper View...
try
{
foreach (OkapiUrls::$mapping as $pattern => $namespace)
{
$matches = null;
if (preg_match("#$pattern#", $uri, $matches))
{
# Pattern matched! Moving on to the proper View...
array_shift($matches);
require_once($GLOBALS['rootpath']."okapi/views/$namespace.php");
$response = call_user_func_array(array('\\okapi\\views\\'.
str_replace('/', '\\', $namespace).'\\View', 'call'), $matches);
if ($response)
$response->display();
return;
}
}
}
catch (Http404 $e)
{
/* pass */
}
array_shift($matches);
require_once($GLOBALS['rootpath']."okapi/views/$namespace.php");
$response = call_user_func_array(array('\\okapi\\views\\'.
str_replace('/', '\\', $namespace).'\\View', 'call'), $matches);
if ($response)
$response->display();
return;
}
}
}
catch (Http404 $e)
{
/* pass */
}
# None of the patterns matched OR method threw the Http404 exception.
# None of the patterns matched OR method threw the Http404 exception.
require_once($GLOBALS['rootpath']."okapi/views/http404.php");
$response = \okapi\views\http404\View::call();
$response->display();
}
require_once($GLOBALS['rootpath']."okapi/views/http404.php");
$response = \okapi\views\http404\View::call();
$response->display();
}
}
Okapi::gettext_domain_init();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,180 +6,180 @@ use OAuthDataStore;
class OkapiDataStore extends OAuthDataStore
{
public function lookup_consumer($consumer_key)
{
$row = Db::select_row("
select `key`, secret, name, url, email, admin
from okapi_consumers
where `key` = '".mysql_real_escape_string($consumer_key)."'
");
if (!$row)
return null;
return new OkapiConsumer($row['key'], $row['secret'], $row['name'],
$row['url'], $row['email'], $row['admin'] ? true : false);
}
public function lookup_consumer($consumer_key)
{
$row = Db::select_row("
select `key`, secret, name, url, email, admin
from okapi_consumers
where `key` = '".mysql_real_escape_string($consumer_key)."'
");
if (!$row)
return null;
return new OkapiConsumer($row['key'], $row['secret'], $row['name'],
$row['url'], $row['email'], $row['admin'] ? true : false);
}
public function lookup_token($consumer, $token_type, $token)
{
$row = Db::select_row("
select `key`, consumer_key, secret, token_type, user_id, verifier, callback
from okapi_tokens
where
consumer_key = '".mysql_real_escape_string($consumer->key)."'
and token_type = '".mysql_real_escape_string($token_type)."'
and `key` = '".mysql_real_escape_string($token)."'
");
if (!$row)
return null;
switch ($row['token_type'])
{
case 'request':
return new OkapiRequestToken($row['key'], $row['secret'],
$row['consumer_key'], $row['callback'], $row['user_id'],
$row['verifier']);
case 'access':
return new OkapiAccessToken($row['key'], $row['secret'],
$row['consumer_key'], $row['user_id']);
default:
throw new Exception();
}
}
public function lookup_token($consumer, $token_type, $token)
{
$row = Db::select_row("
select `key`, consumer_key, secret, token_type, user_id, verifier, callback
from okapi_tokens
where
consumer_key = '".mysql_real_escape_string($consumer->key)."'
and token_type = '".mysql_real_escape_string($token_type)."'
and `key` = '".mysql_real_escape_string($token)."'
");
if (!$row)
return null;
switch ($row['token_type'])
{
case 'request':
return new OkapiRequestToken($row['key'], $row['secret'],
$row['consumer_key'], $row['callback'], $row['user_id'],
$row['verifier']);
case 'access':
return new OkapiAccessToken($row['key'], $row['secret'],
$row['consumer_key'], $row['user_id']);
default:
throw new Exception();
}
}
public function lookup_nonce($consumer, $token, $nonce, $timestamp)
{
# Since it's not important for us to save the actual token and nonce
# value, we will save a hash only. We could also include the consumer
# key in this hash and drop the column, but we will leave it be for
# now (for a couple of less important reasons).
public function lookup_nonce($consumer, $token, $nonce, $timestamp)
{
# Since it's not important for us to save the actual token and nonce
# value, we will save a hash only. We could also include the consumer
# key in this hash and drop the column, but we will leave it be for
# now (for a couple of less important reasons).
$nonce_hash = md5(serialize(array(
$token ? $token->key : null,
$timestamp,
$nonce
)));
try
{
# Time timestamp is saved separately, because we are periodically
# removing older nonces from the database (see cronjobs).
$nonce_hash = md5(serialize(array(
$token ? $token->key : null,
$timestamp,
$nonce
)));
try
{
# Time timestamp is saved separately, because we are periodically
# removing older nonces from the database (see cronjobs).
Db::execute("
insert into okapi_nonces (consumer_key, nonce_hash, timestamp)
values (
'".mysql_real_escape_string($consumer->key)."',
'".mysql_real_escape_string($nonce_hash)."',
'".mysql_real_escape_string($timestamp)."'
);
");
return null;
}
catch (\Exception $e)
{
# INSERT failed. This nonce was already used.
Db::execute("
insert into okapi_nonces (consumer_key, nonce_hash, timestamp)
values (
'".mysql_real_escape_string($consumer->key)."',
'".mysql_real_escape_string($nonce_hash)."',
'".mysql_real_escape_string($timestamp)."'
);
");
return null;
}
catch (\Exception $e)
{
# INSERT failed. This nonce was already used.
return $nonce;
}
}
return $nonce;
}
}
public function new_request_token($consumer, $callback = null)
{
if ((preg_match("#^[a-z][a-z0-9_.-]*://#", $callback) > 0) ||
$callback == "oob")
{ /* ok */ }
else { throw new BadRequest("oauth_callback should begin with <scheme>://, or should equal 'oob'."); }
$token = new OkapiRequestToken(Okapi::generate_key(20), Okapi::generate_key(40),
$consumer->key, $callback, null, Okapi::generate_key(8, true));
Db::execute("
insert into okapi_tokens
(`key`, secret, token_type, timestamp,
user_id, consumer_key, verifier, callback)
values (
'".mysql_real_escape_string($token->key)."',
'".mysql_real_escape_string($token->secret)."',
'request',
unix_timestamp(),
null,
'".mysql_real_escape_string($consumer->key)."',
'".mysql_real_escape_string($token->verifier)."',
".(($token->callback_url == 'oob')
? "null"
: "'".mysql_real_escape_string($token->callback_url)."'"
)."
);
");
return $token;
}
public function new_request_token($consumer, $callback = null)
{
if ((preg_match("#^[a-z][a-z0-9_.-]*://#", $callback) > 0) ||
$callback == "oob")
{ /* ok */ }
else { throw new BadRequest("oauth_callback should begin with lower case <scheme>://, or should equal 'oob'."); }
$token = new OkapiRequestToken(Okapi::generate_key(20), Okapi::generate_key(40),
$consumer->key, $callback, null, Okapi::generate_key(8, true));
Db::execute("
insert into okapi_tokens
(`key`, secret, token_type, timestamp,
user_id, consumer_key, verifier, callback)
values (
'".mysql_real_escape_string($token->key)."',
'".mysql_real_escape_string($token->secret)."',
'request',
unix_timestamp(),
null,
'".mysql_real_escape_string($consumer->key)."',
'".mysql_real_escape_string($token->verifier)."',
".(($token->callback_url == 'oob')
? "null"
: "'".mysql_real_escape_string($token->callback_url)."'"
)."
);
");
return $token;
}
public function new_access_token($token, $consumer, $verifier = null)
{
if ($token->consumer_key != $consumer->key)
throw new BadRequest("Request Token given is not associated with the Consumer who signed the request.");
if (!$token->authorized_by_user_id)
throw new BadRequest("Request Token given has not been authorized.");
if ($token->verifier != $verifier)
throw new BadRequest("Invalid verifier.");
public function new_access_token($token, $consumer, $verifier = null)
{
if ($token->consumer_key != $consumer->key)
throw new BadRequest("Request Token given is not associated with the Consumer who signed the request.");
if (!$token->authorized_by_user_id)
throw new BadRequest("Request Token given has not been authorized.");
if ($token->verifier != $verifier)
throw new BadRequest("Invalid verifier.");
# Invalidate the Request Token.
# Invalidate the Request Token.
Db::execute("
delete from okapi_tokens
where `key` = '".mysql_real_escape_string($token->key)."'
");
Db::execute("
delete from okapi_tokens
where `key` = '".mysql_real_escape_string($token->key)."'
");
# In OKAPI, all Access Tokens are long lived. Therefore, we don't want
# to generate a new one every time a Consumer wants it. We will check
# if there is already an Access Token generated for this (Consumer, User)
# pair and return it if there is.
# In OKAPI, all Access Tokens are long lived. Therefore, we don't want
# to generate a new one every time a Consumer wants it. We will check
# if there is already an Access Token generated for this (Consumer, User)
# pair and return it if there is.
$row = Db::select_row("
select `key`, secret
from okapi_tokens
where
token_type = 'access'
and user_id = '".mysql_real_escape_string($token->authorized_by_user_id)."'
and consumer_key = '".mysql_real_escape_string($consumer->key)."'
");
if ($row)
{
# Use existing Access Token
$row = Db::select_row("
select `key`, secret
from okapi_tokens
where
token_type = 'access'
and user_id = '".mysql_real_escape_string($token->authorized_by_user_id)."'
and consumer_key = '".mysql_real_escape_string($consumer->key)."'
");
if ($row)
{
# Use existing Access Token
$access_token = new OkapiAccessToken($row['key'], $row['secret'],
$consumer->key, $token->authorized_by_user_id);
}
else
{
# Generate a new Access Token.
$access_token = new OkapiAccessToken($row['key'], $row['secret'],
$consumer->key, $token->authorized_by_user_id);
}
else
{
# Generate a new Access Token.
$access_token = new OkapiAccessToken(Okapi::generate_key(20), Okapi::generate_key(40),
$consumer->key, $token->authorized_by_user_id);
Db::execute("
insert into okapi_tokens
(`key`, secret, token_type, timestamp, user_id, consumer_key)
values (
'".mysql_real_escape_string($access_token->key)."',
'".mysql_real_escape_string($access_token->secret)."',
'access',
unix_timestamp(),
'".mysql_real_escape_string($access_token->user_id)."',
'".mysql_real_escape_string($consumer->key)."'
);
");
}
return $access_token;
}
$access_token = new OkapiAccessToken(Okapi::generate_key(20), Okapi::generate_key(40),
$consumer->key, $token->authorized_by_user_id);
Db::execute("
insert into okapi_tokens
(`key`, secret, token_type, timestamp, user_id, consumer_key)
values (
'".mysql_real_escape_string($access_token->key)."',
'".mysql_real_escape_string($access_token->secret)."',
'access',
unix_timestamp(),
'".mysql_real_escape_string($access_token->user_id)."',
'".mysql_real_escape_string($consumer->key)."'
);
");
}
return $access_token;
}
public function cleanup()
{
Db::execute("
delete from okapi_nonces
where
timestamp < unix_timestamp(date_add(now(), interval -6 minute))
or timestamp > unix_timestamp(date_add(now(), interval 6 minute))
");
Db::execute("
delete from okapi_tokens
where
token_type = 'request'
and timestamp < unix_timestamp(date_add(now(), interval -2 hour))
");
}
public function cleanup()
{
Db::execute("
delete from okapi_nonces
where
timestamp < unix_timestamp(date_add(now(), interval -6 minute))
or timestamp > unix_timestamp(date_add(now(), interval 6 minute))
");
Db::execute("
delete from okapi_tokens
where
token_type = 'request'
and timestamp < unix_timestamp(date_add(now(), interval -2 hour))
");
}
}

View File

@ -34,6 +34,7 @@ use okapi\OkapiServiceRunner;
use okapi\OkapiInternalRequest;
use okapi\OkapiFacadeConsumer;
use okapi\OkapiFacadeAccessToken;
use okapi\Cache;
require_once($GLOBALS['rootpath']."okapi/core.php");
OkapiErrorHandler::$treat_notices_as_errors = true;
@ -45,131 +46,214 @@ Okapi::init_internals();
*/
class Facade
{
/**
* Perform OKAPI service call, signed by internal 'facade' consumer key, and return the result
* (this will be PHP object or OkapiHttpResponse, depending on the method). Use this method
* whenever you need to access OKAPI services from within OC code. If you want to simulate
* Level 3 Authentication, you should supply user's internal ID (the second parameter).
*/
public static function service_call($service_name, $user_id_or_null, $parameters)
{
$request = new OkapiInternalRequest(
new OkapiFacadeConsumer(),
($user_id_or_null !== null) ? new OkapiFacadeAccessToken($user_id_or_null) : null,
$parameters
);
$request->perceive_as_http_request = true;
return OkapiServiceRunner::call($service_name, $request);
}
/**
* Perform OKAPI service call, signed by internal 'facade' consumer key, and return the result
* (this will be PHP object or OkapiHttpResponse, depending on the method). Use this method
* whenever you need to access OKAPI services from within OC code. If you want to simulate
* Level 3 Authentication, you should supply user's internal ID (the second parameter).
*/
public static function service_call($service_name, $user_id_or_null, $parameters)
{
$request = new OkapiInternalRequest(
new OkapiFacadeConsumer(),
($user_id_or_null !== null) ? new OkapiFacadeAccessToken($user_id_or_null) : null,
$parameters
);
$request->perceive_as_http_request = true;
return OkapiServiceRunner::call($service_name, $request);
}
/**
* This works like service_call with two exceptions: 1. It passes all your
* current HTTP request headers to OKAPI (which can make use of them in
* terms of caching), 2. It outputs the service response directly, instead
* of returning it.
*/
public static function service_display($service_name, $user_id_or_null, $parameters)
{
$request = new OkapiInternalRequest(
new OkapiFacadeConsumer(),
($user_id_or_null !== null) ? new OkapiFacadeAccessToken($user_id_or_null) : null,
$parameters
);
$request->perceive_as_http_request = true;
if (isset($_SERVER['HTTP_IF_NONE_MATCH']))
$request->etag = $_SERVER['HTTP_IF_NONE_MATCH'];
$response = OkapiServiceRunner::call($service_name, $request);
$response->display();
}
/**
* This works like service_call with two exceptions: 1. It passes all your
* current HTTP request headers to OKAPI (which can make use of them in
* terms of caching), 2. It outputs the service response directly, instead
* of returning it.
*/
public static function service_display($service_name, $user_id_or_null, $parameters)
{
$request = new OkapiInternalRequest(
new OkapiFacadeConsumer(),
($user_id_or_null !== null) ? new OkapiFacadeAccessToken($user_id_or_null) : null,
$parameters
);
$request->perceive_as_http_request = true;
if (isset($_SERVER['HTTP_IF_NONE_MATCH']))
$request->etag = $_SERVER['HTTP_IF_NONE_MATCH'];
$response = OkapiServiceRunner::call($service_name, $request);
$response->display();
}
/**
* Create a search set from a temporary table. This is very similar to
* the "services/caches/search/save" method, but allows OC server to
* include its own result instead of using OKAPI's search options. The
* $temp_table should be a valid name of a temporary table with the
* following (or similar) structure:
*
* create temporary table temp_12345 (
* cache_id integer primary key
* ) engine=memory;
*/
public static function import_search_set($temp_table, $min_store, $max_ref_age)
{
require_once 'services/caches/search/save.php';
$tables = array('caches', $temp_table);
$where_conds = array(
$temp_table.".cache_id = caches.cache_id",
'caches.status in (1,2,3)',
);
return \okapi\services\caches\search\save\WebService::get_set(
$tables, $where_conds, $min_store, $max_ref_age
);
}
/**
* Create a search set from a temporary table. This is very similar to
* the "services/caches/search/save" method, but allows OC server to
* include its own result instead of using OKAPI's search options. The
* $temp_table should be a valid name of a temporary table with the
* following (or similar) structure:
*
* create temporary table temp_12345 (
* cache_id integer primary key
* ) engine=memory;
*/
public static function import_search_set($temp_table, $min_store, $max_ref_age)
{
require_once($GLOBALS['rootpath'].'okapi/services/caches/search/save.php');
$tables = array('caches', $temp_table);
$where_conds = array(
$temp_table.".cache_id = caches.cache_id",
'caches.status in (1,2,3)',
);
return \okapi\services\caches\search\save\WebService::get_set(
$tables, $where_conds, $min_store, $max_ref_age
);
}
/**
* Mark the specified caches as *possibly* modified. The replicate module
* will scan for changes within these caches on the next changelog update.
* This is useful in some cases, when OKAPI cannot detect the modification
* for itself (grep OCPL code for examples). See issue #179.
*
* $cache_codes - a single cache code OR an array of cache codes.
*/
public static function schedule_geocache_check($cache_codes)
{
if (!is_array($cache_codes))
$cache_codes = array($cache_codes);
Db::execute("
update caches
set okapi_syncbase = now()
where wp_oc in ('".implode("','", array_map('mysql_real_escape_string', $cache_codes))."')
");
}
/**
* Mark the specified caches as *possibly* modified. The replicate module
* will scan for changes within these caches on the next changelog update.
* This is useful in some cases, when OKAPI cannot detect the modification
* for itself (grep OCPL code for examples). See issue #179.
*
* $cache_codes - a single cache code OR an array of cache codes.
*/
public static function schedule_geocache_check($cache_codes)
{
if (!is_array($cache_codes))
$cache_codes = array($cache_codes);
Db::execute("
update caches
set okapi_syncbase = now()
where wp_oc in ('".implode("','", array_map('mysql_real_escape_string', $cache_codes))."')
");
}
/**
* 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("
update cache_logs
set okapi_syncbase = now()
where
cache_id = '".mysql_real_escape_string($cache_id)."'
and user_id = '".mysql_real_escape_string($user_id)."'
");
}
/**
* 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("
update cache_logs
set okapi_syncbase = now()
where
cache_id = '".mysql_real_escape_string($cache_id)."'
and user_id = '".mysql_real_escape_string($user_id)."'
");
}
/**
* Run OKAPI database update.
* Will output messages to stdout.
*/
public static function database_update()
{
require_once($GLOBALS['rootpath']."okapi/views/update.php");
$update = new views\update\View;
$update->call();
}
/**
* Run OKAPI database update.
* Will output messages to stdout.
*/
public static function database_update()
{
require_once($GLOBALS['rootpath']."okapi/views/update.php");
$update = new views\update\View;
$update->call();
}
/**
* You will probably want to call that with FALSE when using Facade
* in buggy, legacy OC code. This will disable OKAPI's default behavior
* of treating NOTICEs as errors.
*/
public static function disable_error_handling()
{
OkapiErrorHandler::disable();
}
/**
* You will probably want to call that with FALSE when using Facade
* in buggy, legacy OC code. This will disable OKAPI's default behavior
* of treating NOTICEs as errors.
*/
public static function disable_error_handling()
{
OkapiErrorHandler::disable();
}
/**
* If you disabled OKAPI's error handling with disable_error_handling,
* you may reenable it with this method.
*/
public static function reenable_error_handling()
{
OkapiErrorHandler::reenable();
}
/**
* If you disabled OKAPI's error handling with disable_error_handling,
* you may reenable it with this method.
*/
public static function reenable_error_handling()
{
OkapiErrorHandler::reenable();
}
/**
* Store the object $value in OKAPI's cache, under the name of $key.
*
* Parameters:
*
* $key -- must be a string of max 57 characters in length (you can use
* md5(...) to shorten your keys). Use the same $key to retrieve your
* value later.
*
* $value -- can be any serializable PHP object. Currently there's no
* strict size limit, but try to keep it below 1 MB (for future
* compatibility with memcached).
*
* $timeout -- *maximum* time allowed to store the value, given in seconds
* (however, the value *can* be removed sooner than that, see the note
* below). Timeout can be also set to null, but you should avoid this,
* because such objects may clutter the cache unnecessarilly. (You must
* remember to remove them yourself!)
*
* Please note, that this cache is not guaranteed to be persistent.
* Usually it is, but it can be emptied in case of emergency (low disk
* space), or if we decide to switch the underlying cache engine in the
* future (e.g. to memcached). Most of your values should be safe though.
*/
public static function cache_set($key, $value, $timeout)
{
Cache::set("facade#".$key, $value, $timeout);
}
/** Same as `cache_set`, but works on many key->value pair at once. */
public static function cache_set_many($dict, $timeout)
{
$prefixed_dict = array();
foreach ($dict as $key => &$value_ref) {
$prefixed_dict["facade#".$key] = &$value_ref;
}
Cache::set_many($prefixed_dict, $timeout);
}
/**
* Retrieve object stored in cache under the name of $key. If object does
* not exist or its timeout has expired, return null.
*/
public static function cache_get($key)
{
return Cache::get("facade#".$key);
}
/** Same as `cache_get`, but it works on multiple keys at once. */
public static function get_many($keys)
{
$prefixed_keys = array();
foreach ($keys as $key) {
$prefixed_keys[] = "facade#".$key;
}
$prefixed_result = Cache::get_many($prefixed_keys);
$result = array();
foreach ($prefixed_result as $prefixed_key => &$value_ref) {
$result[substr($prefixed_key, 7)] = &$value_ref;
}
return $result;
}
/**
* Delete the entry named $key from the cache.
*/
public static function cache_delete($key)
{
Cache::delete("facade#".$key);
}
/** 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);
}
}
# (This comment is added here simply to debug OKAPI deployment.....)

View File

@ -8,23 +8,23 @@ namespace okapi;
*/
class OCSession
{
/** Return ID of currently logged in user or NULL if no user is logged in. */
public static function get_user_id()
{
static $cached_result = false;
if ($cached_result !== false)
return $cached_result;
/** Return ID of currently logged in user or NULL if no user is logged in. */
public static function get_user_id()
{
static $cached_result = false;
if ($cached_result !== false)
return $cached_result;
$cookie_name = Settings::get('OC_COOKIE_NAME');
if (!isset($_COOKIE[$cookie_name]))
return null;
$OC_data = unserialize(base64_decode($_COOKIE[$cookie_name]));
if (!isset($OC_data['sessionid']))
return null;
$OC_sessionid = $OC_data['sessionid'];
if (!$OC_sessionid)
return null;
$cookie_name = Settings::get('OC_COOKIE_NAME');
if (!isset($_COOKIE[$cookie_name]))
return null;
$OC_data = unserialize(base64_decode($_COOKIE[$cookie_name]));
if (!isset($OC_data['sessionid']))
return null;
$OC_sessionid = $OC_data['sessionid'];
if (!$OC_sessionid)
return null;
return Db::select_value("select user_id from sys_sessions where uuid='".mysql_real_escape_string($OC_sessionid)."'");
}
return Db::select_value("select user_id from sys_sessions where uuid='".mysql_real_escape_string($OC_sessionid)."'");
}
}

View File

@ -2,11 +2,11 @@ msgid ""
msgstr ""
"Project-Id-Version: OKAPI\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-07-10 12:30+0100\n"
"PO-Revision-Date: 2013-07-10 12:31+0100\n"
"Last-Translator: following <following@online.de>\n"
"POT-Creation-Date: 2014-01-23 15:51+0100\n"
"PO-Revision-Date: 2014-01-23 15:52+0100\n"
"Last-Translator: Wojciech Rygielski <rygielski@mimuw.edu.pl>\n"
"Language-Team: following <following@online.de>\n"
"Language: German\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@ -14,20 +14,29 @@ msgstr ""
"X-Poedit-Basepath: .\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Generator: Poedit 1.5.5\n"
"X-Generator: Poedit 1.6.3\n"
"X-Poedit-SearchPath-0: c:\\source\\oc\\server-3.0\\htdocs\\okapi\n"
"X-Poedit-SearchPath-1: D:\\PRIV\\Projekty\\EclipseWorkspace\\opencaching-api"
"\\okapi\n"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/geocaches.php:956
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:957
msgid "Stage"
msgstr "Station"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/geocaches.php:1108
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:986
msgid "User location"
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:989
#, php-format
msgid "Your own custom coordinates for the %s geocache"
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:1148
msgid "National Park / Landscape"
msgstr "Nationalpark / Landschaft"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/geocaches.php:1260
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:1300
#, php-format
msgid ""
"This <a href='%s'>geocache</a> description comes from the <a href='%s'>%s</"
@ -35,7 +44,7 @@ msgid ""
msgstr ""
"Diese <a href='%s'>Cache</a>-Beschreibung stammt von <a href='%s'>%s</a>."
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/geocaches.php:1272
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:1312
#, php-format
msgid ""
"&copy; <a href='%s'>%s</a>, <a href='%s'>%s</a>, <a href='http://"
@ -46,7 +55,7 @@ msgstr ""
"creativecommons.org/licenses/by-nc-nd/3.0/de/'>CC-BY-NC-ND</a>, Stand: %s; "
"alle Logeinträge &copy; jeweiliger Autor"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/geocaches.php:1283
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:1323
#, php-format
msgid ""
"&copy; <a href='%s'>%s</a>, <a href='%s'>%s</a>, <a href='http://"
@ -57,63 +66,84 @@ msgstr ""
"creativecommons.org/licenses/by-nc-nd/3.0/de/'>CC-BY-NC-ND</a>; alle "
"Logeinträge &copy; jeweiliger Autor"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:31
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:60
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpx.php:360
msgid ""
"<b>Geocache coordinates have been changed.</b> They have been replaced with "
"your own custom coordinates which you have provided for this geocache."
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpx.php:366
msgid ""
"<b>Geocache coordinates have been changed.</b> Currently they point to one "
"of the alternate waypoints originally described as:"
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpx.php:379
msgid "Original geocache location"
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpx.php:381
#, php-format
msgid "Original (owner-supplied) location of the %s geocache"
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:30
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:62
msgid "hidden by"
msgstr "versteckt von"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:62
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:64
#, php-format
msgid "%d recommendation"
msgid_plural "%d recommendations"
msgstr[0] "%d Empfehlung"
msgstr[1] "%d Empfehlungen"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:63
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:65
#, php-format
msgid "found %d time"
msgid_plural "found %d times"
msgstr[0] "%d mal gefunden"
msgstr[1] "%d mal gefunden"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:66
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:68
#, php-format
msgid "%d trackable"
msgid_plural "%d trackables"
msgstr[0] "%d Geokret"
msgstr[1] "%d Geokrets"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:70
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:72
msgid "Personal notes"
msgstr "Persönliche Notizen"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:74
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:76
msgid "Attributes"
msgstr "Attribute"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:78
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:80
msgid "Trackables"
msgstr "Geokrets"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:88
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:104
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:90
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:106
msgid "Images"
msgstr "Bilder"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:111
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:113
msgid "Spoilers"
msgstr "Spoiler"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:120
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:122
msgid "Image descriptions"
msgstr "Bildbeschreibungen"
#: c:\source\oc\server-3.0\htdocs\okapi/services/caches/formatters/gpxfile.tpl.php:128
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:130
msgid "The cache probably is located in the following protection areas:"
msgstr ""
"Der Geocache befindet sich wahrscheinlich in den folgenden Schutzgebieten:"
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:70
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:70
msgid ""
"You are trying to publish a log entry with a date in future. Cache log "
"entries are allowed to be published in the past, but NOT in the future."
@ -121,7 +151,7 @@ msgstr ""
"Das Datum deines Logeintrags liegt in der Zukunft. Cache-Logs können nur für "
"die Vergangenheit oder für heute eingetragen werden."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:92
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:92
#, php-format
msgid ""
"However, your cache rating was ignored, because %s does not have a rating "
@ -129,7 +159,7 @@ msgid ""
msgstr ""
"Deine Cachewertung wurde jedoch ignoriert, weil %s kein Bewertungssystem hat."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:111
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:111
#, php-format
msgid ""
"However, your cache recommendation was ignored, because %s does not allow "
@ -138,7 +168,7 @@ msgstr ""
"Deine Empfehlung wurde jedoch ignoriert, weil auf %s keine Event-Caches "
"empfohlen werden können."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:125
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:125
#, php-format
msgid ""
"However, your \"needs maintenance\" flag was ignored, because %s does not "
@ -147,7 +177,7 @@ msgstr ""
"Deine Angabe \"benötigt Wartung\" wurde jedoch ignoriert, weil es diese "
"Funktion bei %s nicht gibt."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:145
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:145
msgid ""
"This cache is an Event cache. You cannot \"Find\" it (but you can attend it, "
"or comment on it)!"
@ -155,7 +185,7 @@ msgstr ""
"Dies ist ein Event-Cache. Du kannst ihn nicht \"finden\" (aber du kannst am "
"Event teilnehmen oder einen Hinweis loggen)."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:150
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:150
msgid ""
"This cache is NOT an Event cache. You cannot \"Attend\" it (but you can find "
"it, or comment on it)!"
@ -163,26 +193,26 @@ msgstr ""
"Dies ist KEIN Event-Cache. Du kannst an ihm nicht \"teilnehmen\" (aber du "
"kannst ihn finden oder kommentieren)."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:155
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:155
msgid "Your have to supply some text for your comment."
msgstr "Du musst einen Text für dein Hinweislog eingeben!"
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:168
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:168
msgid "This cache requires a password. You didn't provide one!"
msgstr ""
"Dieser Cache kann nur mit Kennwort geloggt werden, aber du hast keines "
"angegeben."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:170
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:170
msgid "Invalid password!"
msgstr "Ungültiges Kennwort!"
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:282
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:285
msgid "You have already submitted a log entry with exactly the same contents."
msgstr ""
"Du hast bereits einen Logeintrag mit genau dem gleichen Inhalt gemacht."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:305
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:308
msgid ""
"You have already submitted a \"Found it\" log entry once. Now you may submit "
"\"Comments\" only!"
@ -190,53 +220,53 @@ msgstr ""
"Du hast diesen Cache bereits als gefunden geloggt. Ein zweites Fundlog ist "
"nicht möglich, aber du kannst stattdessen einen Hinweis loggen."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:307
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:310
msgid "You are the owner of this cache. You may submit \"Comments\" only!"
msgstr ""
"Als Besitzer des Caches kannst du nur Hinweise loggen, keine Funde oder "
"Nichtfunde."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:325
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:328
msgid "You have already rated this cache once. Your rating cannot be changed."
msgstr ""
"Du hast diesen Cache bereits bewertet. Deine Bewertung ist nicht änderbar."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:342
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:345
msgid "You have already recommended this cache once."
msgstr "Du hast diesen Cache bereits empfohlen."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:352
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:355
msgid "You don't have any recommendations to give. Find more caches first!"
msgstr ""
"Du musst mehr Caches finden, um eine weitere Bewertung abgeben zu können!"
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:395
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:398
msgid "Event caches cannot \"need maintenance\"."
msgstr "Event-Caches können keine \"Wartung benötigen\"."
#: c:\source\oc\server-3.0\htdocs\okapi/services/logs/submit.php:525
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:528
msgid "Your cache log entry was posted successfully."
msgstr "Dein Log wurde veröffentlicht."
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorize.tpl.php:5
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:5
msgid "Authorization Form"
msgstr "Authorisierungs-Formular"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorize.tpl.php:46
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:46
msgid "Expired request"
msgstr "Anfrage abgelaufen"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorize.tpl.php:47
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:47
msgid "Unfortunately, the request has expired. Please try again."
msgstr ""
"Die Anfrage ist wegen Zeitüberschreitung abgelaufen. Bitte versuche es noch "
"einmal."
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorize.tpl.php:49
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:49
msgid "External application is requesting access..."
msgstr "Eine externe Anwendung wünscht Zugriff ..."
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorize.tpl.php:50
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:50
#, php-format
msgid ""
"<b>%s</b> wants to access your <b>%s</b> account. Do you agree to grant "
@ -245,28 +275,29 @@ msgstr ""
"<b>%s</b> möchte auf dein <b>%s</b>-Benutzerkonto zugreifen. Möchtest du "
"dieser Anwendung Zugriff gewähren?"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorize.tpl.php:53
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:53
msgid "I agree"
msgstr "Ja"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorize.tpl.php:54
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:54
msgid "Decline"
msgstr "Nein"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorize.tpl.php:56
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:56
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>Once permission is granted it is valid until its withdrawal on\n"
"\t\t\t\t\tthe <a href='%s'>applications management</a> page.</p>\n"
"\t\t\t\t\t<p>The application will access your acount via <a href='%s'>the "
"OKAPI Framework</a>.\n"
"\t\t\t\t\tIf you allow this request application will be able to access all "
"methods delivered\n"
"\t\t\t\t\tby the OKAPI Framework, i.e. post log entries on geocaches in your "
"name.\n"
"\t\t\t\t\tYou can revoke this permission at any moment.</p>\n"
"\t\t\t\t"
" <p>Once permission is granted it is valid until its "
"withdrawal on\n"
" the <a href='%s'>applications management</a> page.</p>\n"
" <p>The application will access your acount via <a "
"href='%s'>the OKAPI Framework</a>.\n"
" If you allow this request application will be able to "
"access all methods delivered\n"
" by the OKAPI Framework, i.e. post log entries on "
"geocaches in your name.\n"
" You can revoke this permission at any moment.</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t\t<p>Wenn die Erlaubnis erteilt wurde, ist sie so lange gültig, bis "
@ -281,23 +312,23 @@ msgstr ""
"\t\t\t\t\tDu kannst diese Erlaubnis jederzeit widerrufen.</p>\n"
"\t\t\t\t"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorized.tpl.php:5
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorized.tpl.php:5
msgid "Authorization Succeeded"
msgstr "Authorisierung erfolgreich"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorized.tpl.php:28
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorized.tpl.php:28
msgid "Access successfully granted"
msgstr "Zugang wurde gewährt"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/authorized.tpl.php:29
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorized.tpl.php:29
#, php-format
msgid ""
"\n"
"\t\t\t\t<p><b>You've just granted %s application access to your %s account.</"
"b>\n"
"\t\t\t\tTo complete the operation, go back to %s and enter the following PIN "
"code:</p>\n"
"\t\t\t"
" <p><b>You've just granted %s application access to your %s "
"account.</b>\n"
" To complete the operation, go back to %s and enter the "
"following PIN code:</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t<p><b>Du hast der Anwendung \"%s\" Zugriff auf dein %s-Benutzerkonto "
@ -306,26 +337,26 @@ msgstr ""
"PIN-Code ein:</p>\n"
"\t\t\t"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/index.tpl.php:5
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/index.tpl.php:5
msgid "My Apps"
msgstr "Meine Apps"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/index.tpl.php:29
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/index.tpl.php:29
msgid "Your external applications"
msgstr "Deine externen Anwendungen"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/index.tpl.php:31
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/index.tpl.php:31
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>This is the list of applications which you granted access to "
"your <b>%s</b> account.\n"
"\t\t\t\t\tThis page gives you the abbility to revoke all previously granted "
"privileges.\n"
"\t\t\t\t\tOnce you click \"remove\" the application will no longer be able "
"to perform any\n"
"\t\t\t\t\tactions on your behalf.</p>\n"
"\t\t\t\t"
" <p>This is the list of applications which you granted "
"access to your <b>%s</b> account.\n"
" This page gives you the abbility to revoke all "
"previously granted privileges.\n"
" Once you click \"remove\" the application will no longer "
"be able to perform any\n"
" actions on your behalf.</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t\t<p>Dies ist eine Liste der Anwendungen, denen du Zugriff auf dein "
@ -336,21 +367,21 @@ msgstr ""
"Aktionen mehr unter deinem \t\t\t\t\tBenutzername ausführen können.</p>\n"
"\t\t\t\t"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/index.tpl.php:45
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/index.tpl.php:45
msgid "remove"
msgstr "entfernen"
#: c:\source\oc\server-3.0\htdocs\okapi/views/apps/index.tpl.php:50
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/index.tpl.php:50
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>Thanks to the <a href='%s'>OKAPI Framework</a> you can grant "
"external applications\n"
"\t\t\t\t\taccess to your <b>%s</b> account. Currently no applications are "
"authorized to act\n"
"\t\t\t\t\ton your behalf. Once you start using external Opencaching "
"applications, they will appear here.</p>\n"
"\t\t\t\t"
" <p>Thanks to the <a href='%s'>OKAPI Framework</a> you "
"can grant external applications\n"
" access to your <b>%s</b> account. Currently no "
"applications are authorized to act\n"
" on your behalf. Once you start using external "
"Opencaching applications, they will appear here.</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t\t<p>Die <a href='%s'>OKAPI-Schnittstelle</a> ermöglichst es dir, "

View File

@ -2,11 +2,11 @@ msgid ""
msgstr ""
"Project-Id-Version: OKAPI\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-07-19 08:41+0100\n"
"PO-Revision-Date: 2013-07-19 08:46+0100\n"
"Last-Translator: faina09 <stefanocotterli@gmail.com>\n"
"POT-Creation-Date: 2014-01-23 15:53+0100\n"
"PO-Revision-Date: 2014-01-23 16:04+0100\n"
"Last-Translator: Wojciech Rygielski <rygielski@mimuw.edu.pl>\n"
"Language-Team: following <following@online.de>\n"
"Language: Italian\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@ -14,22 +14,31 @@ msgstr ""
"X-Poedit-Basepath: .\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Generator: Poedit 1.5.7\n"
"X-Generator: Poedit 1.6.3\n"
"X-Poedit-SearchPath-0: c:\\source\\okapi\\following2\n"
"X-Poedit-SearchPath-1: D:\\PRIV\\Projekty\\EclipseWorkspace\\opencaching-api"
"\\okapi\n"
"X-Poedit-SearchPath-2: C:\\Users\\stefano.cotterli\\Desktop\\opencaching-"
"api\n"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/geocaches.php:956
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:957
msgid "Stage"
msgstr "Passo"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/geocaches.php:1111
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:986
msgid "User location"
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:989
#, php-format
msgid "Your own custom coordinates for the %s geocache"
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:1148
msgid "National Park / Landscape"
msgstr "Parco Nazionale / Paesaggio"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/geocaches.php:1263
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:1300
#, php-format
msgid ""
"This <a href='%s'>geocache</a> description comes from the <a href='%s'>%s</"
@ -38,7 +47,7 @@ msgstr ""
"La deescrizione di questa <a href='%s'>geocache</a>proviene dal sito <a "
"href='%s'>%s</a>."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/geocaches.php:1275
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:1312
#, php-format
msgid ""
"&copy; <a href='%s'>%s</a>, <a href='%s'>%s</a>, <a href='http://"
@ -46,7 +55,7 @@ msgid ""
"%s; all log entries &copy; their authors"
msgstr ""
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/geocaches.php:1286
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/geocaches.php:1323
#, php-format
msgid ""
"&copy; <a href='%s'>%s</a>, <a href='%s'>%s</a>, <a href='http://"
@ -54,62 +63,83 @@ msgid ""
"log entries &copy; their authors"
msgstr ""
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:31
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:60
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpx.php:360
msgid ""
"<b>Geocache coordinates have been changed.</b> They have been replaced with "
"your own custom coordinates which you have provided for this geocache."
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpx.php:366
msgid ""
"<b>Geocache coordinates have been changed.</b> Currently they point to one "
"of the alternate waypoints originally described as:"
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpx.php:379
msgid "Original geocache location"
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpx.php:381
#, php-format
msgid "Original (owner-supplied) location of the %s geocache"
msgstr ""
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:30
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:62
msgid "hidden by"
msgstr "nascosta da"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:62
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:64
#, php-format
msgid "%d recommendation"
msgid_plural "%d recommendations"
msgstr[0] "%d raccomandazione"
msgstr[1] "%d raccomandazioni"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:63
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:65
#, php-format
msgid "found %d time"
msgid_plural "found %d times"
msgstr[0] "trovata %d volta"
msgstr[1] "trovata %d volte"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:66
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:68
#, php-format
msgid "%d trackable"
msgid_plural "%d trackables"
msgstr[0] "%d travel bug"
msgstr[1] "%d travel bugs"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:70
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:72
msgid "Personal notes"
msgstr "Note personali"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:74
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:76
msgid "Attributes"
msgstr "Attributi"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:78
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:80
msgid "Trackables"
msgstr "Travel bugs"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:88
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:104
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:90
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:106
msgid "Images"
msgstr "Immagini"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:111
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:113
msgid "Spoilers"
msgstr "Spoiler"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:120
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:122
msgid "Image descriptions"
msgstr "Descrizione immagine"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/caches/formatters/gpxfile.tpl.php:128
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/caches/formatters/gpxfile.tpl.php:130
msgid "The cache probably is located in the following protection areas:"
msgstr "La cache probabilmente &egrave; situata nelle seguenti aree protette:"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:70
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:70
msgid ""
"You are trying to publish a log entry with a date in future. Cache log "
"entries are allowed to be published in the past, but NOT in the future."
@ -117,7 +147,7 @@ msgstr ""
"Hai cercato di pubblicare un log con una data nel futuro. E' permsso loggare "
"una cache con una data passata, ma NON futura."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:92
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:92
#, php-format
msgid ""
"However, your cache rating was ignored, because %s does not have a rating "
@ -126,7 +156,7 @@ msgstr ""
"Tuttavia, la tua valutazione della cache sarà ignorata, perché %s non ha un "
"sistema di valutazione."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:111
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:111
#, php-format
msgid ""
"However, your cache recommendation was ignored, because %s does not allow "
@ -135,7 +165,7 @@ msgstr ""
"Tuttavia, la tua valutazione della cache sarà ignorata, perché %s non ha "
"permette la valutazione di cache evento."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:125
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:125
#, php-format
msgid ""
"However, your \"needs maintenance\" flag was ignored, because %s does not "
@ -144,7 +174,7 @@ msgstr ""
"Tuttavia, la tua segnalazione di \"manutenzione necessaria\" sarà ignorata, "
"perché %s non supporta questa opzione."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:145
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:145
msgid ""
"This cache is an Event cache. You cannot \"Find\" it (but you can attend it, "
"or comment on it)!"
@ -152,7 +182,7 @@ msgstr ""
"Questa cache è una cache Evento. Non puoi \"Trovarla\" (ma puoi partecipare, "
"o commentare l'evento)!"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:150
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:150
msgid ""
"This cache is NOT an Event cache. You cannot \"Attend\" it (but you can find "
"it, or comment on it)!"
@ -160,23 +190,23 @@ msgstr ""
"Questa cache NON è una cache Evento. Non puoi \"Partecipare\" (ma puoi "
"trovarla, o inserirci un commento)!"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:155
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:155
msgid "Your have to supply some text for your comment."
msgstr "Devi inserire del testo per il tuo commento."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:168
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:168
msgid "This cache requires a password. You didn't provide one!"
msgstr "Questa cache richiede una password, ma non ne hai fornita nessuuna."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:170
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:170
msgid "Invalid password!"
msgstr "Password non valida!"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:282
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:285
msgid "You have already submitted a log entry with exactly the same contents."
msgstr "Hai già inserito un log esattamente con lo stesso contenuto."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:305
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:308
msgid ""
"You have already submitted a \"Found it\" log entry once. Now you may submit "
"\"Comments\" only!"
@ -184,49 +214,49 @@ msgstr ""
"Hai già inserito un log \"Trovata\". Adesso puoi inserire solamente "
"\"Commenti\"!"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:307
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:310
msgid "You are the owner of this cache. You may submit \"Comments\" only!"
msgstr "Sei il proprietario della cache. Puoi inserire solamente \"Commenti\"!"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:325
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:328
msgid "You have already rated this cache once. Your rating cannot be changed."
msgstr ""
"Hai giàvalutato questa cache. La tua valutazione non può essere cambiata."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:342
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:345
msgid "You have already recommended this cache once."
msgstr "Hai già raccmandato questa cache."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:352
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:355
msgid "You don't have any recommendations to give. Find more caches first!"
msgstr ""
"Non hai possibilità di dare raccomandazioni. Prima devio trovare altre cache!"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:395
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:398
msgid "Event caches cannot \"need maintenance\"."
msgstr "Le cache evento non necessitano di manutenzione!"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/services/logs/submit.php:525
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/services/logs/submit.php:528
msgid "Your cache log entry was posted successfully."
msgstr "Il tuo log sulla cache è stato correttamente inserito."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorize.tpl.php:5
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:5
msgid "Authorization Form"
msgstr "Modulo di autorizzazione"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorize.tpl.php:46
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:46
msgid "Expired request"
msgstr "Richiesta scaduta"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorize.tpl.php:47
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:47
msgid "Unfortunately, the request has expired. Please try again."
msgstr "Sfortunatamente, la richiesta è scaduta. Per favore riprova."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorize.tpl.php:49
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:49
msgid "External application is requesting access..."
msgstr "Una applicazione esterna sta richiedendo l'accesso."
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorize.tpl.php:50
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:50
#, php-format
msgid ""
"<b>%s</b> wants to access your <b>%s</b> account. Do you agree to grant "
@ -235,122 +265,118 @@ msgstr ""
"<b>%s</b> vuole accedere al tuo account <b>%s</b>. Sei d'accordo nel "
"concedere l'accesso a questa applicazione?"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorize.tpl.php:53
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:53
msgid "I agree"
msgstr "Sono d'accordo"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorize.tpl.php:54
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:54
msgid "Decline"
msgstr "No"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorize.tpl.php:56
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorize.tpl.php:56
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>Once permission is granted it is valid until its withdrawal on\n"
"\t\t\t\t\tthe <a href='%s'>applications management</a> page.</p>\n"
"\t\t\t\t\t<p>The application will access your acount via <a href='%s'>the "
"OKAPI Framework</a>.\n"
"\t\t\t\t\tIf you allow this request application will be able to access all "
"methods delivered\n"
"\t\t\t\t\tby the OKAPI Framework, i.e. post log entries on geocaches in your "
"name.\n"
"\t\t\t\t\tYou can revoke this permission at any moment.</p>\n"
"\t\t\t\t"
" <p>Once permission is granted it is valid until its "
"withdrawal on\n"
" the <a href='%s'>applications management</a> page.</p>\n"
" <p>The application will access your acount via <a "
"href='%s'>the OKAPI Framework</a>.\n"
" If you allow this request application will be able to "
"access all methods delivered\n"
" by the OKAPI Framework, i.e. post log entries on "
"geocaches in your name.\n"
" You can revoke this permission at any moment.</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t\t<p>Una volta concessa, l'autorizzazoine è valida finché non venga\n"
"\t\t\t\t\trevocata nella pagina di <a href='%s'>gestione applicazioni</a>.</"
"p>\n"
"\t\t\t\t\t<p>L'applicazione accederà al tuo account tramite il <a "
"href='%s'>frameword OKAPI</a>.\n"
"\t\t\t\t\tSe permetti questa richiesta, l'applicazione potrà accedere a "
"tutti i metodi forniti\n"
"\t\t\t\t\tdal framework OKAPI, per es. postare i log delle geocache a tuo "
"nome..\n"
"\t\t\t\t\tPuoi revocare questo permesso in qualsiasi momento.</p>\n"
"\t\t\t\t"
"<p>Una volta concessa, l'autorizzazoine è valida finché non venga\n"
"revocata nella pagina di <a href='%s'>gestione applicazioni</a>.</p>\n"
"<p>L'applicazione accederà al tuo account tramite il <a href='%s'>frameword "
"OKAPI</a>.\n"
"Se permetti questa richiesta, l'applicazione potrà accedere a tutti i metodi "
"forniti\n"
"dal framework OKAPI, per es. postare i log delle geocache a tuo nome..\n"
"Puoi revocare questo permesso in qualsiasi momento.</p>"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorized.tpl.php:5
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorized.tpl.php:5
msgid "Authorization Succeeded"
msgstr "Autorizzazione concessa"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorized.tpl.php:28
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorized.tpl.php:28
msgid "Access successfully granted"
msgstr "Accesso correttamente consentito"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/authorized.tpl.php:29
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/authorized.tpl.php:29
#, php-format
msgid ""
"\n"
"\t\t\t\t<p><b>You've just granted %s application access to your %s account.</"
"b>\n"
"\t\t\t\tTo complete the operation, go back to %s and enter the following PIN "
"code:</p>\n"
"\t\t\t"
" <p><b>You've just granted %s application access to your %s "
"account.</b>\n"
" To complete the operation, go back to %s and enter the "
"following PIN code:</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t<p><b>Hai appena concesso all'applicazione \"%s\" l'accesso al tuo "
"account %s.</b>\n"
"\t\t\t\tPer completare l'operazione, riorna a %s e inserisci il seguente "
"codice PIN:</p>\n"
"\t\t\t"
"<p><b>Hai appena concesso all'applicazione \"%s\" l'accesso al tuo account "
"%s.</b>\n"
"Per completare l'operazione, riorna a %s e inserisci il seguente codice PIN:"
"</p>"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/index.tpl.php:5
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/index.tpl.php:5
msgid "My Apps"
msgstr "Mie App"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/index.tpl.php:29
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/index.tpl.php:29
msgid "Your external applications"
msgstr "Le tue applicazioni esterne"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/index.tpl.php:31
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/index.tpl.php:31
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>This is the list of applications which you granted access to "
"your <b>%s</b> account.\n"
"\t\t\t\t\tThis page gives you the abbility to revoke all previously granted "
"privileges.\n"
"\t\t\t\t\tOnce you click \"remove\" the application will no longer be able "
"to perform any\n"
"\t\t\t\t\tactions on your behalf.</p>\n"
"\t\t\t\t"
" <p>This is the list of applications which you granted "
"access to your <b>%s</b> account.\n"
" This page gives you the abbility to revoke all "
"previously granted privileges.\n"
" Once you click \"remove\" the application will no longer "
"be able to perform any\n"
" actions on your behalf.</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t\t<p>Questa è la lista delle applicazioni a cui hai concesso "
"l'accesso al tuo account <b>%s</b.\n"
"\t\t\t\t\tQuesta pagina ti da la possibilità di revocare tutti privilegi "
"<p>Questa è la lista delle applicazioni a cui hai concesso l'accesso al tuo "
"account <b>%s</b.\n"
"Questa pagina ti da la possibilità di revocare tutti privilegi "
"precedentemente concessi.\n"
"\t\t\t\t\tQundo clicchi su \"rimuovi\" all'applicazione non sarà più "
"concesso eseguire nessuna\n"
" \t\t\t\t\tazione per tuo conto.</p>\n"
"\t\t\t\t"
"Qundo clicchi su \"rimuovi\" all'applicazione non sarà più concesso eseguire "
"nessuna\n"
"azione per tuo conto.</p>"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/index.tpl.php:45
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/index.tpl.php:45
msgid "remove"
msgstr "entfernen"
#: C:\Users\stefano.cotterli\Desktop\opencaching-api/okapi/views/apps/index.tpl.php:50
#: D:\PRIV\Projekty\EclipseWorkspace\opencaching-api\okapi/views/apps/index.tpl.php:50
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>Thanks to the <a href='%s'>OKAPI Framework</a> you can grant "
"external applications\n"
"\t\t\t\t\taccess to your <b>%s</b> account. Currently no applications are "
"authorized to act\n"
"\t\t\t\t\ton your behalf. Once you start using external Opencaching "
"applications, they will appear here.</p>\n"
"\t\t\t\t"
" <p>Thanks to the <a href='%s'>OKAPI Framework</a> you "
"can grant external applications\n"
" access to your <b>%s</b> account. Currently no "
"applications are authorized to act\n"
" on your behalf. Once you start using external "
"Opencaching applications, they will appear here.</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t\t<p>Grazie al <a href='%s'>framework OKAPI</a>puoi concedere ad "
"applicazioni esterne\n"
"\t\t\t\t\t l'accesso al tuo account <b>%s</b>. Attualmente non ci son "
"applicazioni autorizzate\n"
"\t\t\t\t\tad agire per tuo conto. Quando userai applicazioni esterne a "
"Opencaching, queste appariranno qui</p>\n"
"\t\t\t\t"
"<p>Grazie al <a href='%s'>framework OKAPI</a>puoi concedere ad applicazioni "
"esterne\n"
"l'accesso al tuo account <b>%s</b>. Attualmente non ci son applicazioni "
"autorizzate\n"
"ad agire per tuo conto. Quando userai applicazioni esterne a Opencaching, "
"queste appariranno qui</p>\n"
" "
#~ msgid "Recommending is allowed only for 'Found it' logtypes."
#~ msgstr "Le raccomandazioni sono ammesse solo per i log di tipo 'Trovata'"

View File

@ -4,53 +4,53 @@ namespace okapi;
class Locales
{
public static $languages = array(
'pl' => array('lang' => 'pl', 'locale' => 'pl_PL.utf8', 'name' => 'Polish'),
'en' => array('lang' => 'en', 'locale' => 'en_US.utf8', 'name' => 'English'),
'nl' => array('lang' => 'nl', 'locale' => 'nl_NL.utf8', 'name' => 'Dutch'),
'de' => array('lang' => 'de', 'locale' => 'de_DE.utf8', 'name' => 'German'),
'it' => array('lang' => 'it', 'locale' => 'it_IT.utf8', 'name' => 'Italian'),
);
public static $languages = array(
'pl' => array('lang' => 'pl', 'locale' => 'pl_PL.utf8', 'name' => 'Polish'),
'en' => array('lang' => 'en', 'locale' => 'en_US.utf8', 'name' => 'English'),
'nl' => array('lang' => 'nl', 'locale' => 'nl_NL.utf8', 'name' => 'Dutch'),
'de' => array('lang' => 'de', 'locale' => 'de_DE.utf8', 'name' => 'German'),
'it' => array('lang' => 'it', 'locale' => 'it_IT.utf8', 'name' => 'Italian'),
);
/**
* Get the list of locales that should be installed on the system in order
* for all translations to work properly.
*/
public static function get_required_locales()
{
$arr = array('POSIX');
foreach (self::$languages as $key => $value)
$arr[] = $value['locale'];
return $arr;
}
/**
* Get the list of locales that should be installed on the system in order
* for all translations to work properly.
*/
public static function get_required_locales()
{
$arr = array('POSIX');
foreach (self::$languages as $key => $value)
$arr[] = $value['locale'];
return $arr;
}
/**
* Get the list of locales installed on the current system.
*/
public static function get_installed_locales()
{
$arr = array();
foreach (explode("\n", shell_exec("locale -a")) as $item)
if ($item)
$arr[] = $item;
return $arr;
}
/**
* Get the list of locales installed on the current system.
*/
public static function get_installed_locales()
{
$arr = array();
foreach (explode("\n", shell_exec("locale -a")) as $item)
if ($item)
$arr[] = $item;
return $arr;
}
private static function get_locale_for_language($lang)
{
if (isset(self::$languages[$lang]))
return self::$languages[$lang]['locale'];
return null;
}
private static function get_locale_for_language($lang)
{
if (isset(self::$languages[$lang]))
return self::$languages[$lang]['locale'];
return null;
}
public static function get_best_locale($langprefs)
{
foreach ($langprefs as $lang)
{
$locale = self::get_locale_for_language($lang);
if ($locale != null)
return $locale;
}
return self::$languages['en']['locale'];
}
public static function get_best_locale($langprefs)
{
foreach ($langprefs as $lang)
{
$locale = self::get_locale_for_language($lang);
if ($locale != null)
return $locale;
}
return self::$languages['en']['locale'];
}
}

View File

@ -2,9 +2,9 @@ msgid ""
msgstr ""
"Project-Id-Version: OKAPI\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-04-10 01:08+0100\n"
"PO-Revision-Date: 2013-04-10 01:12+0100\n"
"Last-Translator: Wojciech Rygielski <rygielski@mimuw.edu.pl>\n"
"POT-Creation-Date: 2014-01-23 16:05+0100\n"
"PO-Revision-Date: 2014-08-09 03:47+0100\n"
"Last-Translator: Harrie Klomp <harrie@harrieklomp.be>\n"
"Language-Team: \n"
"Language: nl_NL\n"
"MIME-Version: 1.0\n"
@ -13,50 +13,96 @@ msgstr ""
"X-Poedit-KeywordsList: _;gettext;gettext_noop\n"
"X-Poedit-Basepath: D:\\PRIV\\Projekty\\EclipseWorkspace\\opencaching-api\\\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.5.5\n"
"X-Generator: Poedit 1.5.4\n"
"X-Poedit-SearchPath-0: .\n"
# For additional waypoints. As in "Stage 1: Parking".
#: okapi/services/caches/geocaches.php:846
#: okapi/services/caches/geocaches.php:957
msgid "Stage"
msgstr "Etappe"
#: okapi/services/caches/geocaches.php:1009
#: okapi/services/caches/geocaches.php:986
msgid "User location"
msgstr "Gebruikers locatie"
#: okapi/services/caches/geocaches.php:989
#, php-format
msgid "Your own custom coordinates for the %s geocache"
msgstr "Eigen gecorrigeerde coördinaten voor %s geocache"
#: okapi/services/caches/geocaches.php:1148
msgid "National Park / Landscape"
msgstr "Nationaal park / Landschap"
#: okapi/services/caches/geocaches.php:1300
#, php-format
msgid ""
"This <a href='%s'>geocache</a> description comes from the <a href='%s'>%s</"
"a> site."
msgstr ""
"Deze <a href='%s'>geocache</a> beschrijving komt van de <a href='%s'>%s</a> "
"\"\"site."
#: okapi/services/caches/geocaches.php:1021
#, php-format
#: okapi/services/caches/geocaches.php:1312
#, fuzzy, php-format
msgid ""
"&copy; <a href='%s'>%s</a>, <a href='%s'>%s</a>, <a href='http://"
"creativecommons.org/licenses/by-nc-nd/3.0/de/deed.en'>CC-BY-NC-ND</a>, as of "
"%s; all log entries &copy; their authors"
msgstr ""
"&copy; <a href='%s'>%s</a>, <a href='%s'>%s</a>, <a href='http://"
"creativecommons.org/licenses/by-nc-nd/3.0/nl/deed.en'>CC-BY-NC-ND</a>, as of "
"%s; all log entries &copy; their authors"
#: okapi/services/caches/geocaches.php:1032
#, php-format
#: okapi/services/caches/geocaches.php:1323
#, fuzzy, php-format
msgid ""
"&copy; <a href='%s'>%s</a>, <a href='%s'>%s</a>, <a href='http://"
"creativecommons.org/licenses/by-nc-nd/3.0/de/deed.en'>CC-BY-NC-ND</a>; all "
"log entries &copy; their authors"
msgstr ""
"&copy; <a href='%s'>%s</a>, <a href='%s'>%s</a>, <a href='http://"
"creativecommons.org/licenses/by-nc-nd/3.0/nl/deed.en'>CC-BY-NC-ND</a>; all "
"log entries &copy; their authors"
#: okapi/services/caches/formatters/gpxfile.tpl.php:31
#: okapi/services/caches/formatters/gpxfile.tpl.php:48
#: okapi/services/caches/formatters/gpx.php:360
msgid ""
"<b>Geocache coordinates have been changed.</b> They have been replaced with "
"your own custom coordinates which you have provided for this geocache."
msgstr ""
"Geocache coördinaten zijn veranderd.</b> Deze zijn aangepast door "
"coördinaten die je zelf hebt gewijzigd voor deze geocache."
#: okapi/services/caches/formatters/gpx.php:366
msgid ""
"<b>Geocache coordinates have been changed.</b> Currently they point to one "
"of the alternate waypoints originally described as:"
msgstr ""
"Geocache coördinaten zijn veranderd.</b> Momenteel wijzen zij een van de "
"alternatieve waypoints oorspronkelijk beschreven als:"
#: okapi/services/caches/formatters/gpx.php:379
msgid "Original geocache location"
msgstr "Orginele geocache locatie"
#: okapi/services/caches/formatters/gpx.php:381
#, php-format
msgid "Original (owner-supplied) location of the %s geocache"
msgstr "Originele (opgegeven door plaatser) locatie van de %s geocache"
#: okapi/services/caches/formatters/gpxfile.tpl.php:30
#: okapi/services/caches/formatters/gpxfile.tpl.php:62
msgid "hidden by"
msgstr "geplaatst door"
#: okapi/services/caches/formatters/gpxfile.tpl.php:50
#: okapi/services/caches/formatters/gpxfile.tpl.php:64
#, php-format
msgid "%d recommendation"
msgid_plural "%d recommendations"
msgstr[0] "%d aanbeveling"
msgstr[1] "%d aanbevelingen"
#: okapi/services/caches/formatters/gpxfile.tpl.php:51
#: okapi/services/caches/formatters/gpxfile.tpl.php:65
#, php-format
msgid "found %d time"
msgid_plural "found %d times"
@ -64,37 +110,42 @@ msgstr[0] "%d keer gevonden"
msgstr[1] "%d keren gevonden"
# Generic term for trackable items (like geocoins and travelbugs).
#: okapi/services/caches/formatters/gpxfile.tpl.php:54
#: okapi/services/caches/formatters/gpxfile.tpl.php:68
#, php-format
msgid "%d trackable"
msgid_plural "%d trackables"
msgstr[0] "%d trackable"
msgstr[1] "%d trackables"
#: okapi/services/caches/formatters/gpxfile.tpl.php:58
#: okapi/services/caches/formatters/gpxfile.tpl.php:72
msgid "Personal notes"
msgstr "Persoonlijke notities"
#: okapi/services/caches/formatters/gpxfile.tpl.php:62
#: okapi/services/caches/formatters/gpxfile.tpl.php:76
msgid "Attributes"
msgstr "Attributen"
#: okapi/services/caches/formatters/gpxfile.tpl.php:66
#: okapi/services/caches/formatters/gpxfile.tpl.php:80
msgid "Trackables"
msgstr "Trackables"
#: okapi/services/caches/formatters/gpxfile.tpl.php:84
#: okapi/services/caches/formatters/gpxfile.tpl.php:90
#: okapi/services/caches/formatters/gpxfile.tpl.php:106
msgid "Images"
msgstr "Afbeeldingen"
#: okapi/services/caches/formatters/gpxfile.tpl.php:91
#: okapi/services/caches/formatters/gpxfile.tpl.php:113
msgid "Spoilers"
msgstr "Spoilers"
#: okapi/services/caches/formatters/gpxfile.tpl.php:99
#: okapi/services/caches/formatters/gpxfile.tpl.php:122
msgid "Image descriptions"
msgstr "Afbeelding omschrijving"
#: okapi/services/caches/formatters/gpxfile.tpl.php:130
msgid "The cache probably is located in the following protection areas:"
msgstr "De cache is waarschijnlijk in een volgend beschermd gebied geplaatst:"
#: okapi/services/logs/submit.php:70
msgid ""
"You are trying to publish a log entry with a date in future. Cache log "
@ -112,40 +163,59 @@ msgstr ""
"De cachewaardering is genegeerd, omdat %s geen waarderingen in het systeem "
"heeft."
#: okapi/services/logs/submit.php:113
#: okapi/services/logs/submit.php:111
#, php-format
msgid ""
"However, your cache recommendation was ignored, because %s does not allow "
"recommending event caches."
msgstr ""
"De cachewaardering is genegeerd, omdat %s geen waarderingen op evenementen "
"ondersteund."
#: okapi/services/logs/submit.php:125
#, php-format
msgid ""
"However, your \"needs maintenance\" flag was ignored, because %s does not "
"support this feature."
msgstr ""
"Maar jouw \"Heeft onderhoud nodig\" log is genegeerd omdat %s dit niet "
"ondersteunt."
# The user will see this error when he is trying to submit a "Fount it" entry on an event cache. "Attended" or "Will attend" log entries are not the same as "Found it". Currently OKAPI does not allow to submit "Attended" nor "Will attend" log entries, that's why user can only "Comment" on it.
#: okapi/services/logs/submit.php:131
# Missing "you can attend it"
#: okapi/services/logs/submit.php:145
msgid ""
"This cache is an Event cache. You cannot \"Find it\"! (But - you may "
"\"Comment\" on it.)"
"This cache is an Event cache. You cannot \"Find\" it (but you can attend it, "
"or comment on it)!"
msgstr ""
"Dit is een eventcache. Deze kan niet als \"Gevonden\" gelogd worden. Maar "
"wel als \"Notitie\"."
"Dit is een evenement. Deze kan niet als \"Gevonden\" gelogd worden. (maar "
"wel deelgenomen of een notitie plaatsen)."
#: okapi/services/logs/submit.php:133
#: okapi/services/logs/submit.php:150
msgid ""
"This cache is NOT an Event cache. You cannot \"Attend\" it (but you can find "
"it, or comment on it)!"
msgstr ""
"Deze cache is GEEN evenement. Je kunt niet \"Deelnemen\" (maar wel als "
"gevonden loggen of een notitie plaatsen)"
#: okapi/services/logs/submit.php:155
msgid "Your have to supply some text for your comment."
msgstr "Er dient enige tekst ingevuld te worden."
#: okapi/services/logs/submit.php:146
#: okapi/services/logs/submit.php:168
msgid "This cache requires a password. You didn't provide one!"
msgstr "Deze cache vraagt om een wachtwoord. Deze is niet opgegeven!"
#: okapi/services/logs/submit.php:148
#: okapi/services/logs/submit.php:170
msgid "Invalid password!"
msgstr "Verkeerd wachtwoord!"
# This error will be shown to the user when he tries to submit a log entry which is EXACTLY the same as one he had submitted before.
#: okapi/services/logs/submit.php:260
#: okapi/services/logs/submit.php:285
msgid "You have already submitted a log entry with exactly the same contents."
msgstr "Deze log is reeds met dezelfde tekst verzonden."
#: okapi/services/logs/submit.php:279
#: okapi/services/logs/submit.php:308
msgid ""
"You have already submitted a \"Found it\" log entry once. Now you may submit "
"\"Comments\" only!"
@ -153,25 +223,29 @@ msgstr ""
"Je hebt deze cache al als \"Gevonden\" gelogd. Je kunt nu wel een \"Notitie"
"\" plaatsen."
# The English text was changed from "you cannot rate it" to "you cannot find it". The translation remained.
#: okapi/services/logs/submit.php:281
#: okapi/services/logs/submit.php:310
msgid "You are the owner of this cache. You may submit \"Comments\" only!"
msgstr ""
"Je bent eigenaar van deze cache. Je kunt alleen een \"Notitie\" plaatsen."
#: okapi/services/logs/submit.php:299
#: okapi/services/logs/submit.php:328
msgid "You have already rated this cache once. Your rating cannot be changed."
msgstr ""
"Je hebt deze cache al gewaardeerd. De waardering kan niet veranderd worden."
#: okapi/services/logs/submit.php:316
#: okapi/services/logs/submit.php:345
msgid "You have already recommended this cache once."
msgstr ""
msgstr "Je hebt al een aanbeveling op deze cache gegeven."
#: okapi/services/logs/submit.php:323
#: okapi/services/logs/submit.php:355
msgid "You don't have any recommendations to give. Find more caches first!"
msgstr ""
msgstr "Je kunt geen aanbeveling meer geven. Vind eerst meer caches."
#: okapi/services/logs/submit.php:491
#: okapi/services/logs/submit.php:398
msgid "Event caches cannot \"need maintenance\"."
msgstr "Evenementen hebben geen \"onderhoud nodig"
#: okapi/services/logs/submit.php:528
msgid "Your cache log entry was posted successfully."
msgstr "De log is succesvol verzonden."
@ -208,35 +282,33 @@ msgstr "Toestemmen"
msgid "Decline"
msgstr "Afwijzen"
# This should begin with "\n", but you may ignore the rest of \n and \r characters.
# This should begin with "\n", but you may ignore the rest of \n characters.
# This message is shown to the user when external application is trying to get user's permission to access his account.
# Sample: http://i.imgur.com/ZCJNT.png
#: okapi/views/apps/authorize.tpl.php:56
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>Once permission is granted it is valid until its withdrawal on\n"
"\t\t\t\t\tthe <a href='%s'>applications management</a> page.</p>\n"
"\t\t\t\t\t<p>The application will access your acount via <a href='%s'>the "
"OKAPI Framework</a>.\n"
"\t\t\t\t\tIf you allow this request application will be able to access all "
"methods delivered\n"
"\t\t\t\t\tby the OKAPI Framework, i.e. post log entries on geocaches in your "
"name.\n"
"\t\t\t\t\tYou can revoke this permission at any moment.</p>\n"
"\t\t\t\t"
" <p>Once permission is granted it is valid until its "
"withdrawal on\n"
" the <a href='%s'>applications management</a> page.</p>\n"
" <p>The application will access your acount via <a "
"href='%s'>the OKAPI Framework</a>.\n"
" If you allow this request application will be able to "
"access all methods delivered\n"
" by the OKAPI Framework, i.e. post log entries on "
"geocaches in your name.\n"
" You can revoke this permission at any moment.</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t\t<p>Wanneer toegestemd is blijft deze geldig tot intrekking op\n"
"\t\t\t\t\tde <a href='%s'>toepassingsbeheer</a> pagina.</p>\n"
"\t\t\t\t\t<p>De toepassing zal toegang krijgen via jouw account op <a "
"href='%s'>the OKAPI Framework</a>.\n"
"\t\t\t\t\tWanneer je toestemming geeft voor deze toepassing zullen de "
"mogelijkheden\n"
"\t\t\t\t\tvan OKAPI Framework toegepast worden, b.v. het loggen van een "
"cache.\n"
"\t\t\t\t\tDe toestemming kan elk moment ingetrokken worden.</p>\n"
"\t\t\t\t"
"<p>Wanneer toegestemd is blijft deze geldig tot intrekking op\n"
"de <a href='%s'>toepassingsbeheer</a> pagina.</p>\n"
"<p>De toepassing zal toegang krijgen via jouw account op <a href='%s'>the "
"OKAPI Framework</a>.\n"
"Wanneer je toestemming geeft voor deze toepassing zullen de mogelijkheden\n"
"van OKAPI Framework toegepast worden, b.v. het loggen van een cache.\n"
"De toestemming kan elk moment ingetrokken worden.</p>"
#: okapi/views/apps/authorized.tpl.php:5
msgid "Authorization Succeeded"
@ -251,18 +323,16 @@ msgstr "Met succes toegang verleend"
#, php-format
msgid ""
"\n"
"\t\t\t\t<p><b>You've just granted %s application access to your %s account.</"
"b>\n"
"\t\t\t\tTo complete the operation, go back to %s and enter the following PIN "
"code:</p>\n"
"\t\t\t"
" <p><b>You've just granted %s application access to your %s "
"account.</b>\n"
" To complete the operation, go back to %s and enter the "
"following PIN code:</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t<p><b>Je hebt toegang verleent voor %s toepassing op jouw %s account."
"</b>\n"
"\t\t\t\tOm de aktie te voltooien, ga terug naar %s en gebruik de volgende "
"PIN code:</p>\n"
"\t\t\t"
"<p><b>Je hebt toegang verleent voor %s toepassing op jouw %s account.</b>\n"
"Om de actie te voltooien, ga terug naar %s en gebruik de volgende PIN code:</"
"p>"
#: okapi/views/apps/index.tpl.php:5
msgid "My Apps"
@ -273,29 +343,25 @@ msgid "Your external applications"
msgstr "Jouw externe toepassingen"
# This will be shown when user visits /okapi/apps page.
# Sample: http://i.imgur.com/ZCJNT.png
#: okapi/views/apps/index.tpl.php:31
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>This is the list of applications which you granted access to "
"your <b>%s</b> account.\n"
"\t\t\t\t\tThis page gives you the abbility to revoke all previously granted "
"privileges.\n"
"\t\t\t\t\tOnce you click \"remove\" the application will no longer be able "
"to perform any\n"
"\t\t\t\t\tactions on your behalf.</p>\n"
"\t\t\t\t"
" <p>This is the list of applications which you granted "
"access to your <b>%s</b> account.\n"
" This page gives you the abbility to revoke all "
"previously granted privileges.\n"
" Once you click \"remove\" the application will no longer "
"be able to perform any\n"
" actions on your behalf.</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t\t<p>Dit is een lijst met toegestane toepassingen op jouw <b>%s</b> "
"account.\n"
"\t\t\t\t\tOp deze pagina kun je alle toestemmingen intrekken die gegeven "
"zijn.\n"
"\t\t\t\t\tMet een klik op \"verwijderen\" zal de toepassing verwijderen en "
"is dan ook niet meer beschikbaar\n"
"\t\t\t\t\voor anderen.</p>\n"
"\t\t\t\t"
"<p>Dit is een lijst met toegestane toepassingen op jouw <b>%s</b> account.\n"
"Op deze pagina kun je alle toestemmingen intrekken die gegeven zijn.\n"
"Met een klik op \"verwijderen\" zal de toepassing verwijderen en is dan ook "
"niet meer beschikbaar\n"
"oor anderen.</p>"
#: okapi/views/apps/index.tpl.php:45
msgid "remove"
@ -305,22 +371,21 @@ msgstr "verwijderen"
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>Thanks to the <a href='%s'>OKAPI Framework</a> you can grant "
"external applications\n"
"\t\t\t\t\taccess to your <b>%s</b> account. Currently no applications are "
"authorized to act\n"
"\t\t\t\t\ton your behalf. Once you start using external Opencaching "
"applications, they will appear here.</p>\n"
"\t\t\t\t"
" <p>Thanks to the <a href='%s'>OKAPI Framework</a> you "
"can grant external applications\n"
" access to your <b>%s</b> account. Currently no "
"applications are authorized to act\n"
" on your behalf. Once you start using external "
"Opencaching applications, they will appear here.</p>\n"
" "
msgstr ""
"\n"
"\t\t\t\t\t<p>Dankzij het <a href='%s'>OKAPI Framework</a> kun je toegang "
"verlenen via externe\n"
"\t\t\t\t\ttoepassingen op een <b>%s</b> account. Momenteel zijn op dit "
"account nog geen externe\n"
"\t\t\t\t\ttoepassingen actief. Geactiveerde Opencaching toepassingen "
"zullen hier getoond worden.</p>\n"
"\t\t\t\t"
"<p>Dankzij het <a href='%s'>OKAPI Framework</a> kun je toegang verlenen via "
"externe\n"
"toepassingen op een <b>%s</b> account. Momenteel zijn op dit account nog "
"geen externe\n"
"toepassingen actief. Geactiveerde Opencaching toepassingen zullen hier "
"getoond worden.</p>"
#~ msgid ""
#~ "This cache is archived. Only admins and the owner are allowed to add a "

View File

@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: OKAPI\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-07-11 08:28+0100\n"
"PO-Revision-Date: 2013-07-11 08:35+0100\n"
"POT-Creation-Date: 2014-01-23 15:48+0100\n"
"PO-Revision-Date: 2014-01-23 15:49+0100\n"
"Last-Translator: Wojciech Rygielski <rygielski@mimuw.edu.pl>\n"
"Language-Team: \n"
"Language: pl_PL\n"
@ -15,26 +15,35 @@ msgstr ""
"\\okapi\n"
"Plural-Forms: nplurals=3; plural= n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2;\n"
"X-Poedit-SourceCharset: utf-8\n"
"X-Generator: Poedit 1.5.5\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Generator: Poedit 1.6.3\n"
"X-Poedit-SearchPath-0: .\n"
#: services/caches/geocaches.php:956
#: services/caches/geocaches.php:957
msgid "Stage"
msgstr "Etap"
#: services/caches/geocaches.php:1111
#: services/caches/geocaches.php:986
msgid "User location"
msgstr "Współrzędne użytkownika"
#: services/caches/geocaches.php:989
#, php-format
msgid "Your own custom coordinates for the %s geocache"
msgstr "Twoje osobiste współrzędne skrzynki %s"
#: services/caches/geocaches.php:1148
msgid "National Park / Landscape"
msgstr "Park narodowy lub krajobrazowy"
#: services/caches/geocaches.php:1263
#: services/caches/geocaches.php:1300
#, php-format
msgid ""
"This <a href='%s'>geocache</a> description comes from the <a href='%s'>%s</"
"a> site."
msgstr "Opis <a href='%s'>skrzynki</a> pochodzi z serwisu <a href='%s'>%s</a>."
#: services/caches/geocaches.php:1275
#: services/caches/geocaches.php:1312
#, php-format
msgid ""
"&copy; <a href='%s'>%s</a>, <a href='%s'>%s</a>, <a href='http://"
@ -45,7 +54,7 @@ msgstr ""
"creativecommons.org/licenses/by-nc-nd/3.0/de/deed.pl'>CC-BY-NC-ND</a>, w "
"dniu %s. Prawa autorskie wpisów do logów należą do ich autorów."
#: services/caches/geocaches.php:1286
#: services/caches/geocaches.php:1323
#, php-format
msgid ""
"&copy; <a href='%s'>%s</a>, <a href='%s'>%s</a>, <a href='http://"
@ -56,12 +65,37 @@ msgstr ""
"creativecommons.org/licenses/by-nc-nd/3.0/de/deed.pl'>CC-BY-NC-ND</a>. Prawa "
"autorskie wpisów do logów należą do ich autorów."
#: services/caches/formatters/gpxfile.tpl.php:31
#: services/caches/formatters/gpxfile.tpl.php:60
#: services/caches/formatters/gpx.php:360
msgid ""
"<b>Geocache coordinates have been changed.</b> They have been replaced with "
"your own custom coordinates which you have provided for this geocache."
msgstr ""
"<b>Współrzędne skrzynki zostały zmienione.</b> Zostały zastąpione Twoimi "
"osobistymi współrzędnymi, które wprowadziłeś na stronie tej skrzynki."
#: services/caches/formatters/gpx.php:366
msgid ""
"<b>Geocache coordinates have been changed.</b> Currently they point to one "
"of the alternate waypoints originally described as:"
msgstr ""
"<b>Współrzędne skrzynki zostały zmienione.</b> Aktualnie wskazują one na "
"jeden z dodatkowych waypointów, oryginalnie opisanego jako:"
#: services/caches/formatters/gpx.php:379
msgid "Original geocache location"
msgstr "Oryginalne współrzędne skrzynki"
#: services/caches/formatters/gpx.php:381
#, php-format
msgid "Original (owner-supplied) location of the %s geocache"
msgstr "Oryginalne współrzędne skrzynki %s (podane przez autora)"
#: services/caches/formatters/gpxfile.tpl.php:30
#: services/caches/formatters/gpxfile.tpl.php:62
msgid "hidden by"
msgstr "ukryta przez"
#: services/caches/formatters/gpxfile.tpl.php:62
#: services/caches/formatters/gpxfile.tpl.php:64
#, php-format
msgid "%d recommendation"
msgid_plural "%d recommendations"
@ -69,7 +103,7 @@ msgstr[0] "%d rekomendacja"
msgstr[1] "%d rekomendacje"
msgstr[2] "%d rekomendacji"
#: services/caches/formatters/gpxfile.tpl.php:63
#: services/caches/formatters/gpxfile.tpl.php:65
#, php-format
msgid "found %d time"
msgid_plural "found %d times"
@ -77,7 +111,7 @@ msgstr[0] "znaleziona %d raz"
msgstr[1] "znaleziona %d razy"
msgstr[2] "znaleziona %d razy"
#: services/caches/formatters/gpxfile.tpl.php:66
#: services/caches/formatters/gpxfile.tpl.php:68
#, php-format
msgid "%d trackable"
msgid_plural "%d trackables"
@ -85,32 +119,32 @@ msgstr[0] "%d GeoKret (lub TravelBug)"
msgstr[1] "%d GeoKrety (lub TravelBugi)"
msgstr[2] "%d GeoKretów (lub TravelBugów)"
#: services/caches/formatters/gpxfile.tpl.php:70
#: services/caches/formatters/gpxfile.tpl.php:72
msgid "Personal notes"
msgstr "Osobiste notatki"
#: services/caches/formatters/gpxfile.tpl.php:74
#: services/caches/formatters/gpxfile.tpl.php:76
msgid "Attributes"
msgstr "Atrybuty"
#: services/caches/formatters/gpxfile.tpl.php:78
#: services/caches/formatters/gpxfile.tpl.php:80
msgid "Trackables"
msgstr "Geokrety, Travelbugi itp."
#: services/caches/formatters/gpxfile.tpl.php:88
#: services/caches/formatters/gpxfile.tpl.php:104
#: services/caches/formatters/gpxfile.tpl.php:90
#: services/caches/formatters/gpxfile.tpl.php:106
msgid "Images"
msgstr "Obrazki"
#: services/caches/formatters/gpxfile.tpl.php:111
#: services/caches/formatters/gpxfile.tpl.php:113
msgid "Spoilers"
msgstr "Spoilery"
#: services/caches/formatters/gpxfile.tpl.php:120
#: services/caches/formatters/gpxfile.tpl.php:122
msgid "Image descriptions"
msgstr "Opisy obrazków"
#: services/caches/formatters/gpxfile.tpl.php:128
#: services/caches/formatters/gpxfile.tpl.php:130
msgid "The cache probably is located in the following protection areas:"
msgstr "Prawdopodobnie skrzynka znajduje się na terenie obszarów chronionych:"
@ -177,11 +211,11 @@ msgstr "Ta skrzynka wymaga podania hasła. Nie wpisałeś go."
msgid "Invalid password!"
msgstr "Niepoprawne hasło!"
#: services/logs/submit.php:282
#: services/logs/submit.php:285
msgid "You have already submitted a log entry with exactly the same contents."
msgstr "Już opublikowałeś wcześniej wpis z dokładnie taką samą treścią."
#: services/logs/submit.php:305
#: services/logs/submit.php:308
msgid ""
"You have already submitted a \"Found it\" log entry once. Now you may submit "
"\"Comments\" only!"
@ -189,30 +223,30 @@ msgstr ""
"Już opublikowałeś jeden wpis typu \"Znaleziona\" dla tej skrzynki. Teraz "
"możesz dodawać jedynie \"Komentarze\"!"
#: services/logs/submit.php:307
#: services/logs/submit.php:310
msgid "You are the owner of this cache. You may submit \"Comments\" only!"
msgstr ""
"Jesteś właścicielem tej skrzynki. Możesz przesyłać jedynie \"Komentarze\"."
#: services/logs/submit.php:325
#: services/logs/submit.php:328
msgid "You have already rated this cache once. Your rating cannot be changed."
msgstr "Już oceniłeś tę skrzynkę. Ocena nie może być zmieniona."
#: services/logs/submit.php:342
#: services/logs/submit.php:345
msgid "You have already recommended this cache once."
msgstr "Już raz zarekomendowałeś tę skrzynkę."
#: services/logs/submit.php:352
#: services/logs/submit.php:355
msgid "You don't have any recommendations to give. Find more caches first!"
msgstr ""
"Aktualnie nie możesz wystawić kolejnej rekomendacji. Znajdź najpierw więcej "
"skrzynek!"
#: services/logs/submit.php:395
#: services/logs/submit.php:398
msgid "Event caches cannot \"need maintenance\"."
msgstr "Skrzynki typu Wydarzenie nie mogą \"potrzebować serwisu\"."
#: services/logs/submit.php:525
#: services/logs/submit.php:528
msgid "Your cache log entry was posted successfully."
msgstr "Twój wpis do logbooka został opublikowany pomyślnie."
@ -253,16 +287,17 @@ msgstr "Odmawiam"
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>Once permission is granted it is valid until its withdrawal on\n"
"\t\t\t\t\tthe <a href='%s'>applications management</a> page.</p>\n"
"\t\t\t\t\t<p>The application will access your acount via <a href='%s'>the "
"OKAPI Framework</a>.\n"
"\t\t\t\t\tIf you allow this request application will be able to access all "
"methods delivered\n"
"\t\t\t\t\tby the OKAPI Framework, i.e. post log entries on geocaches in your "
"name.\n"
"\t\t\t\t\tYou can revoke this permission at any moment.</p>\n"
"\t\t\t\t"
" <p>Once permission is granted it is valid until its "
"withdrawal on\n"
" the <a href='%s'>applications management</a> page.</p>\n"
" <p>The application will access your acount via <a "
"href='%s'>the OKAPI Framework</a>.\n"
" If you allow this request application will be able to "
"access all methods delivered\n"
" by the OKAPI Framework, i.e. post log entries on "
"geocaches in your name.\n"
" You can revoke this permission at any moment.</p>\n"
" "
msgstr ""
"\n"
"<p>Raz udzielona zgoda jest ważna aż do momentu jej wycofania na stronie <a "
@ -285,11 +320,11 @@ msgstr "Pomyślnie dałeś dostęp"
#, php-format
msgid ""
"\n"
"\t\t\t\t<p><b>You've just granted %s application access to your %s account.</"
"b>\n"
"\t\t\t\tTo complete the operation, go back to %s and enter the following PIN "
"code:</p>\n"
"\t\t\t"
" <p><b>You've just granted %s application access to your %s "
"account.</b>\n"
" To complete the operation, go back to %s and enter the "
"following PIN code:</p>\n"
" "
msgstr ""
"\n"
"<p><b>Właśnie dałeś dostęp aplikacji %s do Twojego konta %s.</b>\n"
@ -308,14 +343,14 @@ msgstr "Twoje zewnętrzne aplikacje"
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>This is the list of applications which you granted access to "
"your <b>%s</b> account.\n"
"\t\t\t\t\tThis page gives you the abbility to revoke all previously granted "
"privileges.\n"
"\t\t\t\t\tOnce you click \"remove\" the application will no longer be able "
"to perform any\n"
"\t\t\t\t\tactions on your behalf.</p>\n"
"\t\t\t\t"
" <p>This is the list of applications which you granted "
"access to your <b>%s</b> account.\n"
" This page gives you the abbility to revoke all "
"previously granted privileges.\n"
" Once you click \"remove\" the application will no longer "
"be able to perform any\n"
" actions on your behalf.</p>\n"
" "
msgstr ""
"\n"
"<p>Następującym aplikacjom zezwoliłeś na dostęp do swojego konta <b>%s</b>.\n"
@ -332,13 +367,13 @@ msgstr "usuń"
#, php-format
msgid ""
"\n"
"\t\t\t\t\t<p>Thanks to the <a href='%s'>OKAPI Framework</a> you can grant "
"external applications\n"
"\t\t\t\t\taccess to your <b>%s</b> account. Currently no applications are "
"authorized to act\n"
"\t\t\t\t\ton your behalf. Once you start using external Opencaching "
"applications, they will appear here.</p>\n"
"\t\t\t\t"
" <p>Thanks to the <a href='%s'>OKAPI Framework</a> you "
"can grant external applications\n"
" access to your <b>%s</b> account. Currently no "
"applications are authorized to act\n"
" on your behalf. Once you start using external "
"Opencaching applications, they will appear here.</p>\n"
" "
msgstr ""
"\n"
"<p>Dzięki platformie <a href='%s'>OKAPI</a> możesz dawać zewnętrznym "
@ -348,6 +383,30 @@ msgstr ""
"w Twoim imieniu. Gdy zaczniesz korzystać z zewnętrznych aplikacji, ich lista "
"pojawi się tutaj.</p>"
#~ msgid ""
#~ "\n"
#~ "\t\t\t\t\t\t\t\t<b>Warning: Changed coordinates.</b> These are not the "
#~ "original\n"
#~ "\t\t\t\t\t\t\t\tcoordinates of this geocache (as supplied by the owner). "
#~ "They\n"
#~ "\t\t\t\t\t\t\t\thave been replaced with other coordinates:\n"
#~ "\t\t\t\t\t\t\t"
#~ msgstr ""
#~ "\n"
#~ "<b>Uwaga: Zmienione współrzędne.</b> To nie są oryginalne współrzędne tej "
#~ "skrzynki (takie, jakie podał jej autor). Współrzędne zostały nadpisane "
#~ "innymi współrzędnymi:"
#~ msgid ""
#~ "<b>Geocache's coordinates has been changed</b> to point to the user "
#~ "supplied value."
#~ msgstr ""
#~ "<b>Współrzędne skrzynki zostały zmienione</b> na własną wartość "
#~ "wprowadzoną przez użytkownika."
#~ msgid "User-supplied location of the %s geocache"
#~ msgstr "Współrzędne skrzynki %s wprowadzone przez użytkownika"
#~ msgid "Recommending is allowed only for 'Found it' logtypes."
#~ msgstr "Rekomendacje są dozwolone jedynie z wpisem \"Znaleziona\"."

View File

@ -839,6 +839,9 @@ class OAuthServer {
if( ! $timestamp )
throw new OAuthMissingParameterException('oauth_timestamp');
// Cast to integer. See issue #314.
$timestamp = $timestamp + 0;
// verify that timestamp is recentish
$now = time();
if (abs($now - $timestamp) > $this->timestamp_threshold) {

View File

@ -6,186 +6,187 @@ use Exception;
class OkapiServiceRunner
{
#
# This the list of all available OKAPI methods. All methods on this list become
# immediately public and all of them have to be documented. It is not possible
# to create an invisible or undocumented OKAPI method. If you want to test your
# methods, you should do it in your local development server. If you want to
# create a private, "internal" method, you still have to document it properly
# (you may describe it as "internal" and accessible to selected consumer keys only).
#
public static $all_names = array(
# Valid format: ^services/[0-9a-z_/]*$ (it means you may use only alphanumeric
# characters and the "_" sign in your method names).
'services/apisrv/installation',
'services/apisrv/installations',
'services/apisrv/stats',
'services/apiref/method',
'services/apiref/method_index',
'services/apiref/issue',
'services/attrs/attribute_index',
'services/attrs/attribute',
'services/attrs/attributes',
'services/oauth/request_token',
'services/oauth/authorize',
'services/oauth/access_token',
'services/caches/search/all',
'services/caches/search/bbox',
'services/caches/search/nearest',
'services/caches/search/by_urls',
'services/caches/search/save',
'services/caches/shortcuts/search_and_retrieve',
'services/caches/geocache',
'services/caches/geocaches',
'services/caches/mark',
'services/caches/formatters/gpx',
'services/caches/formatters/garmin',
'services/caches/map/tile',
'services/logs/entries',
'services/logs/entry',
'services/logs/logs',
'services/logs/userlogs',
'services/logs/submit',
'services/users/user',
'services/users/users',
'services/users/by_usernames',
'services/users/by_username',
'services/users/by_internal_id',
'services/users/by_internal_ids',
'services/replicate/changelog',
'services/replicate/fulldump',
'services/replicate/info',
);
#
# This the list of all available OKAPI methods. All methods on this list become
# immediately public and all of them have to be documented. It is not possible
# to create an invisible or undocumented OKAPI method. If you want to test your
# methods, you should do it in your local development server. If you want to
# create a private, "internal" method, you still have to document it properly
# (you may describe it as "internal" and accessible to selected consumer keys only).
#
public static $all_names = array(
# Valid format: ^services/[0-9a-z_/]*$ (it means you may use only alphanumeric
# characters and the "_" sign in your method names).
'services/apisrv/installation',
'services/apisrv/installations',
'services/apisrv/stats',
'services/apiref/method',
'services/apiref/method_index',
'services/apiref/issue',
'services/attrs/attribute_index',
'services/attrs/attribute',
'services/attrs/attributes',
'services/oauth/request_token',
'services/oauth/authorize',
'services/oauth/access_token',
'services/caches/search/all',
'services/caches/search/bbox',
'services/caches/search/nearest',
'services/caches/search/by_urls',
'services/caches/search/save',
'services/caches/shortcuts/search_and_retrieve',
'services/caches/geocache',
'services/caches/geocaches',
'services/caches/mark',
'services/caches/save_personal_notes',
'services/caches/formatters/gpx',
'services/caches/formatters/garmin',
'services/caches/map/tile',
'services/logs/entries',
'services/logs/entry',
'services/logs/logs',
'services/logs/userlogs',
'services/logs/submit',
'services/users/user',
'services/users/users',
'services/users/by_usernames',
'services/users/by_username',
'services/users/by_internal_id',
'services/users/by_internal_ids',
'services/replicate/changelog',
'services/replicate/fulldump',
'services/replicate/info',
);
/** Check if method exists. */
public static function exists($service_name)
{
return in_array($service_name, self::$all_names);
}
/** Check if method exists. */
public static function exists($service_name)
{
return in_array($service_name, self::$all_names);
}
/** Get method options (is consumer required etc.). */
public static function options($service_name)
{
if (!self::exists($service_name))
throw new Exception();
require_once($GLOBALS['rootpath']."okapi/$service_name.php");
try
{
return call_user_func(array('\\okapi\\'.
str_replace('/', '\\', $service_name).'\\WebService', 'options'));
} catch (Exception $e)
{
throw new Exception("Make sure you've declared your WebService class ".
"in an valid namespace (".'okapi\\'.str_replace('/', '\\', $service_name)."); ".
$e->getMessage());
}
}
/** Get method options (is consumer required etc.). */
public static function options($service_name)
{
if (!self::exists($service_name))
throw new Exception();
require_once($GLOBALS['rootpath']."okapi/$service_name.php");
try
{
return call_user_func(array('\\okapi\\'.
str_replace('/', '\\', $service_name).'\\WebService', 'options'));
} catch (Exception $e)
{
throw new Exception("Make sure you've declared your WebService class ".
"in an valid namespace (".'okapi\\'.str_replace('/', '\\', $service_name)."); ".
$e->getMessage());
}
}
/**
* Get method documentation file contents (stuff within the XML file).
* If you're looking for a parsed representation, use services/apiref/method.
*/
public static function docs($service_name)
{
if (!self::exists($service_name))
throw new Exception();
try {
return file_get_contents("$service_name.xml", true);
} catch (Exception $e) {
throw new Exception("Missing documentation file: $service_name.xml");
}
}
/**
* Get method documentation file contents (stuff within the XML file).
* If you're looking for a parsed representation, use services/apiref/method.
*/
public static function docs($service_name)
{
if (!self::exists($service_name))
throw new Exception();
try {
return file_get_contents("$service_name.xml", true);
} catch (Exception $e) {
throw new Exception("Missing documentation file: $service_name.xml");
}
}
/**
* Execute the method and return the result.
*
* OKAPI methods return OkapiHttpResponses, but some MAY also return
* PHP objects (see OkapiRequest::construct_inside_request for details).
*
* If $request must be consistent with given method's options (must
* include Consumer and Token, if they are required).
*/
public static function call($service_name, OkapiRequest $request)
{
Okapi::init_internals();
/**
* Execute the method and return the result.
*
* OKAPI methods return OkapiHttpResponses, but some MAY also return
* PHP objects (see OkapiRequest::construct_inside_request for details).
*
* If $request must be consistent with given method's options (must
* include Consumer and Token, if they are required).
*/
public static function call($service_name, OkapiRequest $request)
{
Okapi::init_internals();
if (!self::exists($service_name))
throw new Exception("Method does not exist: '$service_name'");
if (!self::exists($service_name))
throw new Exception("Method does not exist: '$service_name'");
$options = self::options($service_name);
if ($options['min_auth_level'] >= 2 && $request->consumer == null)
{
throw new Exception("Method '$service_name' called with mismatched OkapiRequest: ".
"\$request->consumer MAY NOT be empty for Level 2 and Level 3 methods. Provide ".
"a dummy Consumer if you have to.");
}
if ($options['min_auth_level'] >= 3 && $request->token == null)
{
throw new Exception("Method '$service_name' called with mismatched OkapiRequest: ".
"\$request->token MAY NOT be empty for Level 3 methods.");
}
$options = self::options($service_name);
if ($options['min_auth_level'] >= 2 && $request->consumer == null)
{
throw new Exception("Method '$service_name' called with mismatched OkapiRequest: ".
"\$request->consumer MAY NOT be empty for Level 2 and Level 3 methods. Provide ".
"a dummy Consumer if you have to.");
}
if ($options['min_auth_level'] >= 3 && $request->token == null)
{
throw new Exception("Method '$service_name' called with mismatched OkapiRequest: ".
"\$request->token MAY NOT be empty for Level 3 methods.");
}
$time_started = microtime(true);
Okapi::gettext_domain_init();
try
{
require_once($GLOBALS['rootpath']."okapi/$service_name.php");
$response = call_user_func(array('\\okapi\\'.
str_replace('/', '\\', $service_name).'\\WebService', 'call'), $request);
Okapi::gettext_domain_restore();
} catch (Exception $e) {
Okapi::gettext_domain_restore();
throw $e;
}
$runtime = microtime(true) - $time_started;
$time_started = microtime(true);
Okapi::gettext_domain_init();
try
{
require_once($GLOBALS['rootpath']."okapi/$service_name.php");
$response = call_user_func(array('\\okapi\\'.
str_replace('/', '\\', $service_name).'\\WebService', 'call'), $request);
Okapi::gettext_domain_restore();
} catch (Exception $e) {
Okapi::gettext_domain_restore();
throw $e;
}
$runtime = microtime(true) - $time_started;
# Log the request to the stats table. Only valid requests (these which didn't end up
# with an exception) are logged.
self::save_stats($service_name, $request, $runtime);
# Log the request to the stats table. Only valid requests (these which didn't end up
# with an exception) are logged.
self::save_stats($service_name, $request, $runtime);
return $response;
}
return $response;
}
/**
* For internal use only. The stats table can be used to store any kind of
* runtime-stats data, i.e. not only regarding services. This is a special
* version of save_stats which saves runtime stats under the name of $extra_name.
* Note, that $request can be null.
*/
public static function save_stats_extra($extra_name, $request, $runtime)
{
self::save_stats("extra/".$extra_name, $request, $runtime);
}
/**
* For internal use only. The stats table can be used to store any kind of
* runtime-stats data, i.e. not only regarding services. This is a special
* version of save_stats which saves runtime stats under the name of $extra_name.
* Note, that $request can be null.
*/
public static function save_stats_extra($extra_name, $request, $runtime)
{
self::save_stats("extra/".$extra_name, $request, $runtime);
}
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.
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';
}
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("
insert into okapi_stats_temp (`datetime`, consumer_key, user_id, service_name, calltype, runtime)
values (
now(),
'".mysql_real_escape_string($consumer_key)."',
'".mysql_real_escape_string($user_id)."',
'".mysql_real_escape_string($service_name)."',
'".mysql_real_escape_string($calltype)."',
'".mysql_real_escape_string($runtime)."'
);
");
}
Db::execute("
insert into okapi_stats_temp (`datetime`, consumer_key, user_id, service_name, calltype, runtime)
values (
now(),
'".mysql_real_escape_string($consumer_key)."',
'".mysql_real_escape_string($user_id)."',
'".mysql_real_escape_string($service_name)."',
'".mysql_real_escape_string($calltype)."',
'".mysql_real_escape_string($runtime)."'
);
");
}
}

View File

@ -15,32 +15,32 @@ use okapi\Cache;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function options()
{
return array(
'min_auth_level' => 0
);
}
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');
public static function call(OkapiRequest $request)
{
$issue_id = $request->get_parameter('issue_id');
if (!$issue_id)
throw new ParamMissing('issue_id');
if ((!preg_match("/^[0-9]+$/", $issue_id)) || (strlen($issue_id) > 6))
throw new InvalidParam('issue_id');
# In October 2013, Google Code feed at:
# http://code.google.com/feeds/issues/p/opencaching-api/issues/$issue_id/comments/full
# stopped working. We are forced to respond with a simple placeholder.
# In October 2013, Google Code feed at:
# http://code.google.com/feeds/issues/p/opencaching-api/issues/$issue_id/comments/full
# stopped working. We are forced to respond with a simple placeholder.
$result = array(
'id' => $issue_id + 0,
'last_updated' => null,
'title' => null,
'url' => "https://code.google.com/p/opencaching-api/issues/detail?id=".$issue_id,
'comment_count' => null
);
return Okapi::formatted_response($request, $result);
}
$result = array(
'id' => $issue_id + 0,
'last_updated' => null,
'title' => null,
'url' => "https://code.google.com/p/opencaching-api/issues/detail?id=".$issue_id,
'comment_count' => null
);
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,33 +1,33 @@
<xml>
<brief>Retrieve information on given issue</brief>
<issue-id>11</issue-id>
<desc>
<p><b>Important:</b> This method stopped working properly in October 2013.
Now, it returns a simple placeholder.
<a href='https://code.google.com/p/opencaching-api/issues/detail?id=288'>Read more</a>.</p>
<brief>Retrieve information on given issue</brief>
<issue-id>11</issue-id>
<desc>
<p><b>Important:</b> This method stopped working properly in October 2013.
Now, it returns a simple placeholder.
<a href='https://code.google.com/p/opencaching-api/issues/detail?id=288'>Read more</a>.</p>
<p>OKAPI is trying to be as <b>integrated</b> with its
<a href='http://code.google.com/p/opencaching-api/'>Main Project Page</a> as it can.
This method retrieves basic information on a given issue from our project
<a href='http://code.google.com/p/opencaching-api/issues/list'>Issue Tracker</a>.
In future, it <b>might</b> also return some of the latest comments (we're not yet
sure if we want them displayed on our documentation pages).</p>
</desc>
<req name='issue_id'>
ID of an Issue.
</req>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>id</b> - number of the issue created for this method,</li>
<li><b>last_updated</b> - date and time (ISO 8601) when the issue was last updated
<b>or null</b> if unknown,</li>
<li><b>title</b> - issue title <b>or null</b> if unknown,</li>
<li><b>url</b> - URL of the issue page,</li>
<li><b>comment_count</b> - total number of submitted comments <b>or null</b> if unknown.</li>
</ul>
<p>Note, that this will respond with HTTP 400 if we fail to retrieve data from
the Google Code site.</p>
</returns>
<p>OKAPI is trying to be as <b>integrated</b> with its
<a href='http://code.google.com/p/opencaching-api/'>Main Project Page</a> as it can.
This method retrieves basic information on a given issue from our project
<a href='http://code.google.com/p/opencaching-api/issues/list'>Issue Tracker</a>.
In future, it <b>might</b> also return some of the latest comments (we're not yet
sure if we want them displayed on our documentation pages).</p>
</desc>
<req name='issue_id'>
ID of an Issue.
</req>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>id</b> - number of the issue created for this method,</li>
<li><b>last_updated</b> - date and time (ISO 8601) when the issue was last updated
<b>or null</b> if unknown,</li>
<li><b>title</b> - issue title <b>or null</b> if unknown,</li>
<li><b>url</b> - URL of the issue page,</li>
<li><b>comment_count</b> - total number of submitted comments <b>or null</b> if unknown.</li>
</ul>
<p>Note, that this will respond with HTTP 400 if we fail to retrieve data from
the Google Code site.</p>
</returns>
</xml>

View File

@ -14,158 +14,216 @@ use okapi\OkapiInternalConsumer;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function options()
{
return array(
'min_auth_level' => 0
);
}
private static function arg_desc($arg_node)
{
$attrs = $arg_node->attributes();
return array(
'name' => (string)$attrs['name'],
'is_required' => $arg_node->getName() == 'req',
'is_deprecated' => (isset($attrs['class']) && (strpos($attrs['class'], 'deprecated') !== false)),
'class' => 'public',
'description' =>
(isset($attrs['default']) ? ("<p>Default value: <b>".$attrs['default']."</b></p>") : "").
self::get_inner_xml($arg_node),
private static function arg_desc($arg_node)
{
$attrs = $arg_node->attributes();
return array(
'name' => (string)$attrs['name'],
'is_required' => $arg_node->getName() == 'req',
'is_deprecated' => (isset($attrs['class']) && (strpos($attrs['class'], 'deprecated') !== false)),
'class' => 'public',
'description' =>
(isset($attrs['default']) ? ("<p>Default value: <b>".$attrs['default']."</b></p>") : "").
self::get_inner_xml($arg_node),
);
}
);
}
private static function get_inner_xml($node)
{
/* Fetch as <some-node>content</some-node>, extract content. */
private static function get_inner_xml($node)
{
/* Fetch as <some-node>content</some-node>, extract content. */
$s = $node->asXML();
$start = strpos($s, ">") + 1;
$length = strlen($s) - $start - (3 + strlen($node->getName()));
$s = substr($s, $start, $length);
$s = $node->asXML();
$start = strpos($s, ">") + 1;
$length = strlen($s) - $start - (3 + strlen($node->getName()));
$s = substr($s, $start, $length);
/* Find and replace %okapi:plugins%. */
/* Find and replace %okapi:plugins%. */
$s = preg_replace_callback("~%OKAPI:([a-z:]+)%~", array("self", "plugin_callback"), $s);
$s = preg_replace_callback('~%OKAPI:([a-z:/_#]+)%~', array("self", "plugin_callback"), $s);
return $s;
}
return $s;
}
public static function plugin_callback($matches)
{
$input = $matches[1];
$arr = explode(":", $input);
$plugin_name = $arr[0];
/**
* You can use the following syntax:
*
* <a href="%OKAPI:docurl:fragment%">any text</a> - to reference fragment of introducing
* documentation
*
* <a href="%OKAPI:methodref:methodname%">any text</a> - to reference any other method
*
* <a href="%OKAPI:methodref:methodname#html_anchor%">any text</a> - to reference
* any HTML anchor in other method
*
* <a href="%OKAPI:methodref:#html_anchor%">any text</a> - to reference any HTML
* anchor within current document
*
* <a href="%OKAPI:methodargref:methodname#argument_name%">any text</a> - to
* reference argument of another method
*
* <a href="%OKAPI:methodargref:#argument_name%">any text</a> - to reference
* argument within current method
*
* <a href="%OKAPI:methodretref:methodname#returned_key%">any text</a> - to
* reference returned value of another method
*
* <a href="%OKAPI:methodretref:#returned_key%">any text</a> - to reference
* returned value within current method
*
* NOTE!
*
* Since returned JSON dictionaries are not standardized (they are simply plain
* HTML in the docs), to reference returned values you must manually create an
* anchor prefixed with ret_, i.e. (HTML snippet): <li
* id="ret_alt_wpts"><p><b>alt_wpts</b> - list of alternate/additional
* waypoints</...> and access it with (HTML snippet): <a
* href="%OKAPI:methodretref:#alt_wpts%">any text</a>.
*/
public static function plugin_callback($matches)
{
$input = $matches[1];
$arr = explode(":", $input);
$plugin_name = $arr[0];
switch ($plugin_name) {
case 'docurl':
$fragment = $arr[1];
return Settings::get('SITE_URL')."okapi/introduction.html#".$fragment;
default:
throw new Exception("Unknown plugin: ".$input);
}
}
switch ($plugin_name) {
case 'docurl':
$fragment = $arr[1];
return Settings::get('SITE_URL')."okapi/introduction.html#".$fragment;
case 'methodref':
case 'methodargref':
case 'methodretref':
$elements = explode('#', $arr[1]);
$result = '';
if ($elements[0] != '')
{
$result .= Settings::get('SITE_URL')."okapi/".$elements[0].'.html';
}
if (count($elements) > 1)
{
$result .= '#';
switch ($plugin_name) {
case 'methodargref':
$result .= 'arg_';
break;
case 'methodretref':
$result .= 'ret_';
break;
}
$result .= $elements[1];
}
return $result;
default:
throw new Exception("Unknown plugin: ".$input);
}
}
public static function call(OkapiRequest $request)
{
$methodname = $request->get_parameter('name');
if (!$methodname)
throw new ParamMissing('name');
if (!preg_match("#^services/[0-9a-z_/]*$#", $methodname))
throw new InvalidParam('name');
if (!OkapiServiceRunner::exists($methodname))
throw new InvalidParam('name', "Method does not exist: '$methodname'.");
$options = OkapiServiceRunner::options($methodname);
if (!isset($options['min_auth_level']))
throw new Exception("Method $methodname is missing a required 'min_auth_level' option!");
$docs = simplexml_load_string(OkapiServiceRunner::docs($methodname));
$exploded = explode("/", $methodname);
$result = array(
'name' => $methodname,
'short_name' => end($exploded),
'ref_url' => Settings::get('SITE_URL')."okapi/$methodname.html",
'auth_options' => array(
'min_auth_level' => $options['min_auth_level'],
'oauth_consumer' => $options['min_auth_level'] >= 2,
'oauth_token' => $options['min_auth_level'] >= 3,
)
);
if (!$docs->brief)
throw new Exception("Missing <brief> element in the $methodname.xml file.");
if ($docs->brief != self::get_inner_xml($docs->brief))
throw new Exception("The <brief> element may not contain HTML markup ($methodname.xml).");
if (strlen($docs->brief) > 80)
throw new Exception("The <brief> description may not be longer than 80 characters ($methodname.xml).");
if (strpos($docs->brief, "\n") !== false)
throw new Exception("The <brief> element may not contain new-lines ($methodname.xml).");
if (substr(trim($docs->brief), -1) == '.')
throw new Exception("The <brief> element should not end with a dot ($methodname.xml).");
$result['brief_description'] = self::get_inner_xml($docs->brief);
if ($docs->{'issue-id'})
$result['issue_id'] = (string)$docs->{'issue-id'};
else
$result['issue_id'] = null;
if (!$docs->desc)
throw new Exception("Missing <desc> element in the $methodname.xml file.");
$result['description'] = self::get_inner_xml($docs->desc);
$result['arguments'] = array();
foreach ($docs->req as $arg) { $result['arguments'][] = self::arg_desc($arg); }
foreach ($docs->opt as $arg) { $result['arguments'][] = self::arg_desc($arg); }
foreach ($docs->{'import-params'} as $import_desc)
{
$attrs = $import_desc->attributes();
$referenced_methodname = $attrs['method'];
$referenced_method_info = OkapiServiceRunner::call('services/apiref/method',
new OkapiInternalRequest(new OkapiInternalConsumer(), null, array('name' => $referenced_methodname)));
$include_list = isset($attrs['params']) ? explode("|", $attrs['params']) : null;
$exclude_list = isset($attrs['except']) ? explode("|", $attrs['except']) : array();
foreach ($referenced_method_info['arguments'] as $arg)
{
if ($arg['class'] == 'common-formatting')
continue;
if (($include_list === null) && (count($exclude_list) == 0))
{
$arg['description'] = "<i>Inherited from <a href='".$referenced_method_info['ref_url'].
"'>".$referenced_method_info['name']."</a> method.</i>";
}
elseif (
(($include_list === null) || in_array($arg['name'], $include_list))
&& (!in_array($arg['name'], $exclude_list))
) {
$arg['description'] = "<i>Same as in the <a href='".$referenced_method_info['ref_url'].
"'>".$referenced_method_info['name']."</a> method.</i>";
} else {
continue;
}
$arg['class'] = 'inherited';
$result['arguments'][] = $arg;
}
}
if ($docs->{'common-format-params'})
{
$result['arguments'][] = array(
'name' => 'format',
'is_required' => false,
'is_deprecated' => false,
'class' => 'common-formatting',
'description' => "<i>Standard <a href='".Settings::get('SITE_URL')."okapi/introduction.html#common-formatting'>common formatting</a> argument.</i>"
);
$result['arguments'][] = array(
'name' => 'callback',
'is_required' => false,
'is_deprecated' => false,
'class' => 'common-formatting',
'description' => "<i>Standard <a href='".Settings::get('SITE_URL')."okapi/introduction.html#common-formatting'>common formatting</a> argument.</i>"
);
}
foreach ($result['arguments'] as &$arg_ref)
if ($arg_ref['is_deprecated'])
$arg_ref['class'] .= " deprecated";
if (!$docs->returns)
throw new Exception("Missing <returns> element in the $methodname.xml file. ".
"If your method does not return anything, you should document in nonetheless.");
$result['returns'] = self::get_inner_xml($docs->returns);
return Okapi::formatted_response($request, $result);
}
public static function call(OkapiRequest $request)
{
$methodname = $request->get_parameter('name');
if (!$methodname)
throw new ParamMissing('name');
if (!preg_match("#^services/[0-9a-z_/]*$#", $methodname))
throw new InvalidParam('name');
if (!OkapiServiceRunner::exists($methodname))
throw new InvalidParam('name', "Method does not exist: '$methodname'.");
$options = OkapiServiceRunner::options($methodname);
if (!isset($options['min_auth_level']))
throw new Exception("Method $methodname is missing a required 'min_auth_level' option!");
$docs = simplexml_load_string(OkapiServiceRunner::docs($methodname));
$exploded = explode("/", $methodname);
$result = array(
'name' => $methodname,
'short_name' => end($exploded),
'ref_url' => Settings::get('SITE_URL')."okapi/$methodname.html",
'auth_options' => array(
'min_auth_level' => $options['min_auth_level'],
'oauth_consumer' => $options['min_auth_level'] >= 2,
'oauth_token' => $options['min_auth_level'] >= 3,
)
);
if (!$docs->brief)
throw new Exception("Missing <brief> element in the $methodname.xml file.");
if ($docs->brief != self::get_inner_xml($docs->brief))
throw new Exception("The <brief> element may not contain HTML markup ($methodname.xml).");
if (strlen($docs->brief) > 80)
throw new Exception("The <brief> description may not be longer than 80 characters ($methodname.xml).");
if (strpos($docs->brief, "\n") !== false)
throw new Exception("The <brief> element may not contain new-lines ($methodname.xml).");
if (substr(trim($docs->brief), -1) == '.')
throw new Exception("The <brief> element should not end with a dot ($methodname.xml).");
$result['brief_description'] = self::get_inner_xml($docs->brief);
if ($docs->{'issue-id'})
$result['issue_id'] = (string)$docs->{'issue-id'};
else
$result['issue_id'] = null;
if (!$docs->desc)
throw new Exception("Missing <desc> element in the $methodname.xml file.");
$result['description'] = self::get_inner_xml($docs->desc);
$result['arguments'] = array();
foreach ($docs->req as $arg) { $result['arguments'][] = self::arg_desc($arg); }
foreach ($docs->opt as $arg) { $result['arguments'][] = self::arg_desc($arg); }
foreach ($docs->{'import-params'} as $import_desc)
{
$attrs = $import_desc->attributes();
$referenced_methodname = $attrs['method'];
$referenced_method_info = OkapiServiceRunner::call('services/apiref/method',
new OkapiInternalRequest(new OkapiInternalConsumer(), null, array('name' => $referenced_methodname)));
$include_list = isset($attrs['params']) ? explode("|", $attrs['params']) : null;
$exclude_list = isset($attrs['except']) ? explode("|", $attrs['except']) : array();
foreach ($referenced_method_info['arguments'] as $arg)
{
if ($arg['class'] == 'common-formatting')
continue;
if (($include_list === null) && (count($exclude_list) == 0))
{
$arg['description'] = "<i>Inherited from <a href='".$referenced_method_info['ref_url'].
"'>".$referenced_method_info['name']."</a> method.</i>";
}
elseif (
(($include_list === null) || in_array($arg['name'], $include_list))
&& (!in_array($arg['name'], $exclude_list))
) {
$arg['description'] = "<i>Same as in the <a href='".$referenced_method_info['ref_url'].
"'>".$referenced_method_info['name']."</a> method.</i>";
} else {
continue;
}
$arg['class'] = 'inherited';
$result['arguments'][] = $arg;
}
}
if ($docs->{'common-format-params'})
{
$result['arguments'][] = array(
'name' => 'format',
'is_required' => false,
'is_deprecated' => false,
'class' => 'common-formatting',
'description' => "<i>Standard <a href='".Settings::get('SITE_URL')."okapi/introduction.html#common-formatting'>common formatting</a> argument.</i>"
);
$result['arguments'][] = array(
'name' => 'callback',
'is_required' => false,
'is_deprecated' => false,
'class' => 'common-formatting',
'description' => "<i>Standard <a href='".Settings::get('SITE_URL')."okapi/introduction.html#common-formatting'>common formatting</a> argument.</i>"
);
}
foreach ($result['arguments'] as &$arg_ref)
if ($arg_ref['is_deprecated'])
$arg_ref['class'] .= " deprecated";
if (!$docs->returns)
throw new Exception("Missing <returns> element in the $methodname.xml file. ".
"If your method does not return anything, you should document in nonetheless.");
$result['returns'] = self::get_inner_xml($docs->returns);
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,55 +1,55 @@
<xml>
<brief>Get information on a given OKAPI service method</brief>
<issue-id>13</issue-id>
<desc>
<p>This method allows you to <b>access API documentation</b> (the same which you
are reading just now). Given a method name, it returns a complete method description.</p>
</desc>
<req name='name'>
Name of a method (begins with "services/").
</req>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>name</b> - name of the method,</li>
<li><b>short_name</b> - name without a path,</li>
<li><b>issue_id</b> - ID of the "general discussion" issue (in our project's
homepage Issue Tracker) associated with this method <b>or null</b> if this
method has associated issue,</li>
<li><b>description</b> - HTML-formatted description of what the method does,</li>
<li><b>brief_description</b> - brief (max 80 characters), single-line,
plain-text description of what the method does,</li>
<li><b>ref_url</b> - URL of the documentation page with method description,</li>
<li>
<b>auth_options</b> - a dictionary which describes authentication
requirements for this method, it has a following structure:
<ul>
<li><b>min_auth_level</b> - integer, in range from 0 to 3,
see Introduction page.</li>
<li><b>oauth_consumer</b> - true, if requests are required to be signed
with OAuth Consumer Key (min_auth_level >= 2),</li>
<li><b>oauth_token</b> - true, if requests are required to include an
OAuth Token (min_auth_level == 3).</li>
</ul>
</li>
<li><b>arguments</b> - list of dictionaries, describes method
arguments. Each dictionary has a following structure:
<ul>
<li><b>name</b> - name of an argument,</li>
<li><b>is_required</b> - boolean, true if the argument is required,</li>
<li><b>is_deprecated</b> - boolean, true if the argument is deprecated,</li>
<li><b>description</b> - HTML-formatted description of an argument.</li>
<li>
<p><b>class</b> - space separated list of the following values: <i>public</i>,
<i>inherited</i>, <i>common-formatting</i> and <i>deprecated</i>
(other values might be introduced in future).</p>
<p>Currently these values do not mean anything specific. They are
used for different coloring/styling in the documentation pages.</p>
</li>
</ul>
</li>
<li><b>returns</b> - HTML-formatted description method's return value.</li>
</ul>
</returns>
<brief>Get information on a given OKAPI service method</brief>
<issue-id>13</issue-id>
<desc>
<p>This method allows you to <b>access API documentation</b> (the same which you
are reading just now). Given a method name, it returns a complete method description.</p>
</desc>
<req name='name'>
Name of a method (begins with "services/").
</req>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>name</b> - name of the method,</li>
<li><b>short_name</b> - name without a path,</li>
<li><b>issue_id</b> - ID of the "general discussion" issue (in our project's
homepage Issue Tracker) associated with this method <b>or null</b> if this
method has associated issue,</li>
<li><b>description</b> - HTML-formatted description of what the method does,</li>
<li><b>brief_description</b> - brief (max 80 characters), single-line,
plain-text description of what the method does,</li>
<li><b>ref_url</b> - URL of the documentation page with method description,</li>
<li>
<b>auth_options</b> - a dictionary which describes authentication
requirements for this method, it has a following structure:
<ul>
<li><b>min_auth_level</b> - integer, in range from 0 to 3,
see Introduction page.</li>
<li><b>oauth_consumer</b> - true, if requests are required to be signed
with OAuth Consumer Key (min_auth_level >= 2),</li>
<li><b>oauth_token</b> - true, if requests are required to include an
OAuth Token (min_auth_level == 3).</li>
</ul>
</li>
<li><b>arguments</b> - list of dictionaries, describes method
arguments. Each dictionary has a following structure:
<ul>
<li><b>name</b> - name of an argument,</li>
<li><b>is_required</b> - boolean, true if the argument is required,</li>
<li><b>is_deprecated</b> - boolean, true if the argument is deprecated,</li>
<li><b>description</b> - HTML-formatted description of an argument.</li>
<li>
<p><b>class</b> - space separated list of the following values: <i>public</i>,
<i>inherited</i>, <i>common-formatting</i> and <i>deprecated</i>
(other values might be introduced in future).</p>
<p>Currently these values do not mean anything specific. They are
used for different coloring/styling in the documentation pages.</p>
</li>
</ul>
</li>
<li><b>returns</b> - HTML-formatted description method's return value.</li>
</ul>
</returns>
</xml>

View File

@ -15,33 +15,33 @@ use okapi\OkapiInternalRequest;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function call(OkapiRequest $request)
{
$methodnames = OkapiServiceRunner::$all_names;
sort($methodnames);
$cache_key = "api_ref/method_index#".md5(implode("#", $methodnames));
$results = Cache::get($cache_key);
if ($results == null)
{
$results = array();
foreach ($methodnames as $methodname)
{
$info = OkapiServiceRunner::call('services/apiref/method', new OkapiInternalRequest(
new OkapiInternalConsumer(), null, array('name' => $methodname)));
$results[] = array(
'name' => $info['name'],
'brief_description' => $info['brief_description'],
);
}
Cache::set($cache_key, $results, 3600);
}
return Okapi::formatted_response($request, $results);
}
public static function call(OkapiRequest $request)
{
$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);
}
}

View File

@ -1,17 +1,17 @@
<xml>
<brief>Get a list of OKAPI methods with brief descriptions</brief>
<issue-id>12</issue-id>
<desc>
<p>Get a list of OKAPI methods with brief descriptions.</p>
</desc>
<common-format-params/>
<returns>
A list of dictionaries, each of which contains one API
method description in the following format:
<ul>
<li><b>name</b> - name of a method,</li>
<li><b>brief_description</b> - brief (max 80 characters), single-line,
plain-text description of what the method does.</li>
</ul>
</returns>
<brief>Get a list of OKAPI methods with brief descriptions</brief>
<issue-id>12</issue-id>
<desc>
<p>Get a list of OKAPI methods with brief descriptions.</p>
</desc>
<common-format-params/>
<returns>
A list of dictionaries, each of which contains one API
method description in the following format:
<ul>
<li><b>name</b> - name of a method,</li>
<li><b>brief_description</b> - brief (max 80 characters), single-line,
plain-text description of what the method does.</li>
</ul>
</returns>
</xml>

View File

@ -13,20 +13,20 @@ use okapi\OkapiInternalRequest;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function call(OkapiRequest $request)
{
$result = array();
$result['site_url'] = Settings::get('SITE_URL');
$result['okapi_base_url'] = $result['site_url']."okapi/";
$result['site_name'] = Okapi::get_normalized_site_name();
$result['okapi_revision'] = Okapi::$revision;
return Okapi::formatted_response($request, $result);
}
public static function call(OkapiRequest $request)
{
$result = array();
$result['site_url'] = Settings::get('SITE_URL');
$result['okapi_base_url'] = $result['site_url']."okapi/";
$result['site_name'] = Okapi::get_normalized_site_name();
$result['okapi_revision'] = Okapi::$revision;
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,31 +1,31 @@
<xml>
<brief>Get information on this OKAPI installation</brief>
<issue-id>14</issue-id>
<desc>
Retrieve some basic information about this OKAPI installation.
</desc>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li>
<b>site_url</b> - URL of the Opencaching site which is running
the OKAPI installation (usually this looks like
"http://www.opencaching.<i>xx</i>/", where <b>xx</b> is a top
level domain of a country).
</li>
<li>
<b>okapi_base_url</b> - URL of the OKAPI installation (usually this is
<b>site_url</b> with "okapi/" appended, but you should not assume
that); this value is to be used as a prefix when constructing service
method URLs,
</li>
<li>
<b>site_name</b> - international name of the Opencaching site,
</li>
<li><b>okapi_revision</b> - integer, an SVN revision of the OKAPI project
installed on this Opencaching site, <b>or null</b>, when could not
determine revision number.</li>
</ul>
</returns>
<brief>Get information on this OKAPI installation</brief>
<issue-id>14</issue-id>
<desc>
Retrieve some basic information about this OKAPI installation.
</desc>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li>
<b>site_url</b> - URL of the Opencaching site which is running
the OKAPI installation (usually this looks like
"http://www.opencaching.<i>xx</i>/", where <b>xx</b> is a top
level domain of a country).
</li>
<li>
<b>okapi_base_url</b> - URL of the OKAPI installation (usually this is
<b>site_url</b> with "okapi/" appended, but you should not assume
that); this value is to be used as a prefix when constructing service
method URLs,
</li>
<li>
<b>site_name</b> - international name of the Opencaching site,
</li>
<li><b>okapi_revision</b> - integer, an SVN revision of the OKAPI project
installed on this Opencaching site, <b>or null</b>, when could not
determine revision number.</li>
</ul>
</returns>
</xml>

View File

@ -15,104 +15,104 @@ use okapi\OkapiInternalRequest;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function options()
{
return array(
'min_auth_level' => 0
);
}
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.
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.
$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);
}
catch (ErrorException $e)
{
# Google failed on us. Try to respond with a backup list.
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);
}
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);
}
$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.
# 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(
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);
}
$doc = simplexml_load_string($xml);
$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;
}
$doc = simplexml_load_string($xml);
$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 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!
}
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 it for one day. Also, save a backup (valid for 30 days).
Cache::set($cachekey, $results, 86400);
Cache::set($backupkey, $results, 86400*30);
}
Cache::set($cachekey, $results, 86400);
Cache::set($backupkey, $results, 86400*30);
}
return Okapi::formatted_response($request, $results);
}
return Okapi::formatted_response($request, $results);
}
}

View File

@ -1,33 +1,33 @@
<xml>
<brief>Get the list of all public OKAPI installations</brief>
<issue-id>39</issue-id>
<desc>
Get the list of all public OKAPI installations. Keep in mind that
OKAPI installations might differ slightly. If you plan on using
multiple OKAPI installations in your application (which is a very
good thing!) you should test it against the one with the <b>lowest</b>
OKAPI revision number.
</desc>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li>
<b>site_url</b> - URL of the Opencaching site which is running
the OKAPI installation (usually this looks like
"http://www.opencaching.<i>xx</i>/", where <b>xx</b> is a top
level domain of a country).
</li>
<li>
<b>site_name</b> - universal name for this site (should be fine
for all languages),
</li>
<li>
<b>okapi_base_url</b> - URL of the OKAPI installation (usually this is
<b>site_url</b> with "okapi/" appended, but you should not assume
that); this value is to be used as a prefix when constructing service
method URLs.
</li>
</ul>
</returns>
<brief>Get the list of all public OKAPI installations</brief>
<issue-id>39</issue-id>
<desc>
Get the list of all public OKAPI installations. Keep in mind that
OKAPI installations might differ slightly. If you plan on using
multiple OKAPI installations in your application (which is a very
good thing!) you should test it against the one with the <b>lowest</b>
OKAPI revision number.
</desc>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li>
<b>site_url</b> - URL of the Opencaching site which is running
the OKAPI installation (usually this looks like
"http://www.opencaching.<i>xx</i>/", where <b>xx</b> is a top
level domain of a country).
</li>
<li>
<b>site_name</b> - universal name for this site (should be fine
for all languages),
</li>
<li>
<b>okapi_base_url</b> - URL of the OKAPI installation (usually this is
<b>site_url</b> with "okapi/" appended, but you should not assume
that); this value is to be used as a prefix when constructing service
method URLs.
</li>
</ul>
</returns>
</xml>

View File

@ -15,48 +15,48 @@ use okapi\Settings;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function call(OkapiRequest $request)
{
$cachekey = "apisrv/stats";
$result = Cache::get($cachekey);
if (!$result)
{
$result = array(
'cache_count' => 0 + Db::select_value("
select count(*) from caches where status in (1,2,3)
"),
'user_count' => 0 + Db::select_value("
select count(*) from (
select distinct user_id
from cache_logs
where
type in (1,2)
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "deleted = 0" : "true")."
UNION DISTINCT
select distinct user_id
from caches
) as t;
"),
'apps_count' => 0 + Db::select_value("select count(*) from okapi_consumers;"),
'apps_active' => 0 + Db::select_value("
select count(distinct s.consumer_key)
from
okapi_stats_hourly s,
okapi_consumers c
where
s.consumer_key = c.`key`
and s.period_start > date_add(now(), interval -30 day)
"),
);
Cache::set($cachekey, $result, 86400); # cache it for one day
}
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("
select count(*) from caches where status in (1,2,3)
"),
'user_count' => 0 + Db::select_value("
select count(*) from (
select distinct user_id
from cache_logs
where
type in (1,2)
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "deleted = 0" : "true")."
UNION DISTINCT
select distinct user_id
from caches
) as t;
"),
'apps_count' => 0 + Db::select_value("select count(*) from okapi_consumers;"),
'apps_active' => 0 + Db::select_value("
select count(distinct s.consumer_key)
from
okapi_stats_hourly s,
okapi_consumers c
where
s.consumer_key = c.`key`
and s.period_start > date_add(now(), interval -30 day)
"),
);
Cache::set($cachekey, $result, 86400); # cache it for one day
}
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,20 +1,20 @@
<xml>
<brief>Get some basic stats about the site</brief>
<issue-id>43</issue-id>
<desc>
Retrieve some basic statistics about this OKAPI installation.
If you want some more stats, post a comment!
</desc>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>cache_count</b> - approximate total number of geocaches stored at this site,</li>
<li><b>user_count</b> - approximate total number of active users of this site,</li>
<li><b>apps_count</b> - approximate total number of all OKAPI applications (number of
registered API Keys).</li>
<li><b>apps_active</b> - approximate number of active OKAPI applications (the ones which issued
at least one, non-anonymous OKAPI request during the last month).</li>
</ul>
</returns>
<brief>Get some basic stats about the site</brief>
<issue-id>43</issue-id>
<desc>
Retrieve some basic statistics about this OKAPI installation.
If you want some more stats, post a comment!
</desc>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>cache_count</b> - approximate total number of geocaches stored at this site,</li>
<li><b>user_count</b> - approximate total number of active users of this site,</li>
<li><b>apps_count</b> - approximate total number of all OKAPI applications (number of
registered API Keys).</li>
<li><b>apps_active</b> - approximate number of active OKAPI applications (the ones which issued
at least one, non-anonymous OKAPI request during the last month).</li>
</ul>
</returns>
</xml>

View File

@ -18,272 +18,272 @@ use SimpleXMLElement;
class AttrHelper
{
/**
* By default, when DEBUG mode is enabled, the attributes.xml file is
* reloaded practically on every request. If you don't want that, you can
* temporarilly disable this behavior by settings this to false.
*/
private static $RELOAD_ON_DEBUG = true;
/**
* By default, when DEBUG mode is enabled, the attributes.xml file is
* reloaded practically on every request. If you don't want that, you can
* temporarilly disable this behavior by settings this to false.
*/
private static $RELOAD_ON_DEBUG = true;
private static $attr_dict = null;
private static $attr_dict = null;
/**
* Return the cache key suffix to be used for caching. This should be used
* In order for the $RELOAD_ON_DEBUG to work properly when switching to/from
* DEBUG mode.
*/
private static function cache_key_suffix()
{
return (self::$RELOAD_ON_DEBUG) ? "#DBG" : "";
}
/**
* Return the cache key suffix to be used for caching. This should be used
* In order for the $RELOAD_ON_DEBUG to work properly when switching to/from
* DEBUG mode.
*/
private static function cache_key_suffix()
{
return (self::$RELOAD_ON_DEBUG) ? "#DBG" : "";
}
/** Return the timeout to be used for attribute caching. */
private static function ttl()
{
return (Settings::get('DEBUG') && self::$RELOAD_ON_DEBUG) ? 2 : 86400;
}
/** Return the timeout to be used for attribute caching. */
private static function ttl()
{
return (Settings::get('DEBUG') && self::$RELOAD_ON_DEBUG) ? 2 : 86400;
}
/**
* Forces an immediate refresh of the current attributes from the
* attribute-definitions.xml file.
*/
public static function refresh_now()
{
try
{
$path = $GLOBALS['rootpath']."okapi/services/attrs/attribute-definitions.xml";
$xml = file_get_contents($path);
self::refresh_from_string($xml);
}
catch (Exception $e)
{
# Failed to read or parse the file (i.e. after a syntax error was
# commited). Let's check when the last successful parse occured.
/**
* Forces an immediate refresh of the current attributes from the
* attribute-definitions.xml file.
*/
public static function refresh_now()
{
try
{
$path = $GLOBALS['rootpath']."okapi/services/attrs/attribute-definitions.xml";
$xml = file_get_contents($path);
self::refresh_from_string($xml);
}
catch (Exception $e)
{
# Failed to read or parse the file (i.e. after a syntax error was
# commited). Let's check when the last successful parse occured.
self::init_from_cache(false);
self::init_from_cache(false);
if (self::$attr_dict === null)
{
# That's bad! We don't have ANY copy of the data AND we failed
# to parse it. We will use a fake, empty data.
if (self::$attr_dict === null)
{
# That's bad! We don't have ANY copy of the data AND we failed
# to parse it. We will use a fake, empty data.
$cache_key = "attrhelper/dict#".Okapi::$revision.self::cache_key_suffix();
$cachedvalue = array(
'attr_dict' => array(),
);
Cache::set($cache_key, $cachedvalue, self::ttl());
}
$cache_key = "attrhelper/dict#".Okapi::$revision.self::cache_key_suffix();
$cachedvalue = array(
'attr_dict' => array(),
);
Cache::set($cache_key, $cachedvalue, self::ttl());
}
return;
}
}
return;
}
}
/**
* Refresh all attributes from the given XML. Usually, this file is
* downloaded from Google Code (using refresh_now).
*/
public static function refresh_from_string($xml)
{
/* The attribute-definitions.xml file defines relationships between
* attributes originating from various OC installations. Each
* installation uses internal IDs of its own. Which "attribute schema"
* is being used in THIS installation? */
/**
* Refresh all attributes from the given XML. Usually, this file is
* downloaded from Google Code (using refresh_now).
*/
public static function refresh_from_string($xml)
{
/* The attribute-definitions.xml file defines relationships between
* attributes originating from various OC installations. Each
* installation uses internal IDs of its own. Which "attribute schema"
* is being used in THIS installation? */
$my_schema = Settings::get('ORIGIN_URL');
$my_schema = Settings::get('ORIGIN_URL');
$doc = simplexml_load_string($xml);
$cachedvalue = array(
'attr_dict' => array(),
);
$doc = simplexml_load_string($xml);
$cachedvalue = array(
'attr_dict' => array(),
);
# Build cache attributes dictionary
# Build cache attributes dictionary
$all_internal_ids = array();
foreach ($doc->attr as $attrnode)
{
$attr = array(
'acode' => (string)$attrnode['acode'],
'gc_equivs' => array(),
'internal_id' => null,
'names' => array(),
'descriptions' => array(),
'is_discontinued' => true
);
foreach ($attrnode->groundspeak as $gsnode)
{
$attr['gc_equivs'][] = array(
'id' => (int)$gsnode['id'],
'inc' => in_array((string)$gsnode['inc'], array("true", "1")) ? 1 : 0,
'name' => (string)$gsnode['name']
);
}
foreach ($attrnode->opencaching as $ocnode)
{
/* If it is used by at least one OC node, then it's NOT discontinued. */
$attr['is_discontinued'] = false;
$all_internal_ids = array();
foreach ($doc->attr as $attrnode)
{
$attr = array(
'acode' => (string)$attrnode['acode'],
'gc_equivs' => array(),
'internal_id' => null,
'names' => array(),
'descriptions' => array(),
'is_discontinued' => true
);
foreach ($attrnode->groundspeak as $gsnode)
{
$attr['gc_equivs'][] = array(
'id' => (int)$gsnode['id'],
'inc' => in_array((string)$gsnode['inc'], array("true", "1")) ? 1 : 0,
'name' => (string)$gsnode['name']
);
}
foreach ($attrnode->opencaching as $ocnode)
{
/* If it is used by at least one OC node, then it's NOT discontinued. */
$attr['is_discontinued'] = false;
if ((string)$ocnode['schema'] == $my_schema)
{
/* It is used by THIS OC node. */
if ((string)$ocnode['schema'] == $my_schema)
{
/* It is used by THIS OC node. */
$internal_id = (int)$ocnode['id'];
if (isset($all_internal_ids[$internal_id]))
throw new Exception("The internal attribute ".$internal_id.
" has multiple assigments to OKAPI attributes.");
$all_internal_ids[$internal_id] = true;
if (!is_null($attr['internal_id']))
throw new Exception("There are multiple internal IDs for the ".
$attr['acode']." attribute.");
$attr['internal_id'] = $internal_id;
}
}
foreach ($attrnode->lang as $langnode)
{
$lang = (string)$langnode['id'];
foreach ($langnode->name as $namenode)
{
if (isset($attr['names'][$lang]))
throw new Exception("Duplicate ".$lang." name of attribute ".$attr['acode']);
$attr['names'][$lang] = (string)$namenode;
}
foreach ($langnode->desc as $descnode)
{
if (isset($attr['descriptions'][$lang]))
throw new Exception("Duplicate ".$lang." description of attribute ".$attr['acode']);
$xml = $descnode->asxml(); /* contains "<desc>" and "</desc>" */
$innerxml = preg_replace("/(^[^>]+>)|(<[^<]+$)/us", "", $xml);
$attr['descriptions'][$lang] = self::cleanup_string($innerxml);
}
}
$cachedvalue['attr_dict'][$attr['acode']] = $attr;
}
$internal_id = (int)$ocnode['id'];
if (isset($all_internal_ids[$internal_id]))
throw new Exception("The internal attribute ".$internal_id.
" has multiple assigments to OKAPI attributes.");
$all_internal_ids[$internal_id] = true;
if (!is_null($attr['internal_id']))
throw new Exception("There are multiple internal IDs for the ".
$attr['acode']." attribute.");
$attr['internal_id'] = $internal_id;
}
}
foreach ($attrnode->lang as $langnode)
{
$lang = (string)$langnode['id'];
foreach ($langnode->name as $namenode)
{
if (isset($attr['names'][$lang]))
throw new Exception("Duplicate ".$lang." name of attribute ".$attr['acode']);
$attr['names'][$lang] = (string)$namenode;
}
foreach ($langnode->desc as $descnode)
{
if (isset($attr['descriptions'][$lang]))
throw new Exception("Duplicate ".$lang." description of attribute ".$attr['acode']);
$xml = $descnode->asxml(); /* contains "<desc>" and "</desc>" */
$innerxml = preg_replace("/(^[^>]+>)|(<[^<]+$)/us", "", $xml);
$attr['descriptions'][$lang] = self::cleanup_string($innerxml);
}
}
$cachedvalue['attr_dict'][$attr['acode']] = $attr;
}
$cache_key = "attrhelper/dict#".Okapi::$revision.self::cache_key_suffix();
Cache::set($cache_key, $cachedvalue, self::ttl());
self::$attr_dict = $cachedvalue['attr_dict'];
}
$cache_key = "attrhelper/dict#".Okapi::$revision.self::cache_key_suffix();
Cache::set($cache_key, $cachedvalue, self::ttl());
self::$attr_dict = $cachedvalue['attr_dict'];
}
/**
* Object to be used for forward-compatibility (see the attributes method).
*/
public static function get_unknown_placeholder($acode)
{
return array(
'acode' => $acode,
'gc_equivs' => array(),
'internal_id' => null,
'names' => array(
'en' => "Unknown attribute"
),
'descriptions' => array(
'en' => (
"This attribute ($acode) is unknown at ".Okapi::get_normalized_site_name().
". It might not exist, or it may be a new attribute, recognized ".
"only in newer OKAPI installations. Perhaps ".Okapi::get_normalized_site_name().
" needs to have its OKAPI updated?"
)
),
'is_discontinued' => true
);
}
/**
* Object to be used for forward-compatibility (see the attributes method).
*/
public static function get_unknown_placeholder($acode)
{
return array(
'acode' => $acode,
'gc_equivs' => array(),
'internal_id' => null,
'names' => array(
'en' => "Unknown attribute"
),
'descriptions' => array(
'en' => (
"This attribute ($acode) is unknown at ".Okapi::get_normalized_site_name().
". It might not exist, or it may be a new attribute, recognized ".
"only in newer OKAPI installations. Perhaps ".Okapi::get_normalized_site_name().
" needs to have its OKAPI updated?"
)
),
'is_discontinued' => true
);
}
/**
* Initialize all the internal attributes (if not yet initialized). This
* loads attribute values from the cache. If they are not present in the
* cache, it will read and parse them from attribute-definitions.xml file.
*/
private static function init_from_cache($allow_refreshing=true)
{
if (self::$attr_dict !== null)
{
/* Already initialized. */
return;
}
$cache_key = "attrhelper/dict#".Okapi::$revision.self::cache_key_suffix();
$cachedvalue = Cache::get($cache_key);
if ($cachedvalue === null)
{
# I.e. after Okapi::$revision is changed, or cache got invalidated.
/**
* Initialize all the internal attributes (if not yet initialized). This
* loads attribute values from the cache. If they are not present in the
* cache, it will read and parse them from attribute-definitions.xml file.
*/
private static function init_from_cache($allow_refreshing=true)
{
if (self::$attr_dict !== null)
{
/* Already initialized. */
return;
}
$cache_key = "attrhelper/dict#".Okapi::$revision.self::cache_key_suffix();
$cachedvalue = Cache::get($cache_key);
if ($cachedvalue === null)
{
# I.e. after Okapi::$revision is changed, or cache got invalidated.
if ($allow_refreshing)
{
self::refresh_now();
self::init_from_cache(false);
return;
}
else
{
$cachedvalue = array(
'attr_dict' => array(),
);
}
}
self::$attr_dict = $cachedvalue['attr_dict'];
}
if ($allow_refreshing)
{
self::refresh_now();
self::init_from_cache(false);
return;
}
else
{
$cachedvalue = array(
'attr_dict' => array(),
);
}
}
self::$attr_dict = $cachedvalue['attr_dict'];
}
/**
* Return a dictionary of all attributes. The format is INTERNAL and PRIVATE,
* it is NOT the same as in the "attributes" method (but it is quite similar).
*/
public static function get_attrdict()
{
self::init_from_cache();
return self::$attr_dict;
}
/**
* Return a dictionary of all attributes. The format is INTERNAL and PRIVATE,
* it is NOT the same as in the "attributes" method (but it is quite similar).
*/
public static function get_attrdict()
{
self::init_from_cache();
return self::$attr_dict;
}
/** "\n\t\tBla blabla\n\t\t<b>bla</b>bla.\n\t" => "Bla blabla <b>bla</b>bla." */
private static function cleanup_string($s)
{
return preg_replace('/(^\s+)|(\s+$)/us', "", preg_replace('/\s+/us', " ", $s));
}
/** "\n\t\tBla blabla\n\t\t<b>bla</b>bla.\n\t" => "Bla blabla <b>bla</b>bla." */
private static function cleanup_string($s)
{
return preg_replace('/(^\s+)|(\s+$)/us', "", preg_replace('/\s+/us', " ", $s));
}
/**
* Get the mapping table between internal attribute id => OKAPI A-code.
* The result is cached!
*/
public static function get_internal_id_to_acode_mapping()
{
static $mapping = null;
if ($mapping !== null)
return $mapping;
/**
* Get the mapping table between internal attribute id => OKAPI A-code.
* The result is cached!
*/
public static function get_internal_id_to_acode_mapping()
{
static $mapping = null;
if ($mapping !== null)
return $mapping;
$cache_key = "attrhelper/id2acode/".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[$attr_ref['internal_id']] = $acode;
Cache::set($cache_key, $mapping, self::ttl());
}
return $mapping;
}
$cache_key = "attrhelper/id2acode/".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[$attr_ref['internal_id']] = $acode;
Cache::set($cache_key, $mapping, self::ttl());
}
return $mapping;
}
/**
* 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;
/**
* 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;
}
$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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -17,45 +17,45 @@ use okapi\services\attrs\AttrHelper;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
# Read the parameters.
public static function call(OkapiRequest $request)
{
# Read the parameters.
$acode = $request->get_parameter('acode');
if ($acode === null) throw new ParamMissing('acode');
$acode = $request->get_parameter('acode');
if ($acode === null) throw new ParamMissing('acode');
$langpref = $request->get_parameter('langpref');
if (!$langpref) $langpref = "en";
$langpref = $request->get_parameter('langpref');
if (!$langpref) $langpref = "en";
$fields = $request->get_parameter('fields');
if (!$fields) $fields = "name";
$fields = $request->get_parameter('fields');
if (!$fields) $fields = "name";
$forward_compatible = $request->get_parameter('forward_compatible');
if (!$forward_compatible) $forward_compatible = "true";
$forward_compatible = $request->get_parameter('forward_compatible');
if (!$forward_compatible) $forward_compatible = "true";
# Pass them all to the attributes method.
# Pass them all to the attributes method.
$params = array(
'acodes' => $acode,
'langpref' => $langpref,
'fields' => $fields,
'forward_compatible' => $forward_compatible
);
$results = OkapiServiceRunner::call('services/attrs/attributes',
new OkapiInternalRequest($request->consumer, $request->token, $params));
$result = $results[$acode];
if ($result === null)
{
/* Note, this can happen only when $forward_compatible is false. */
throw new InvalidParam('acode', "Unknown A-code.");
}
return Okapi::formatted_response($request, $result);
}
$params = array(
'acodes' => $acode,
'langpref' => $langpref,
'fields' => $fields,
'forward_compatible' => $forward_compatible
);
$results = OkapiServiceRunner::call('services/attrs/attributes',
new OkapiInternalRequest($request->consumer, $request->token, $params));
$result = $results[$acode];
if ($result === null)
{
/* Note, this can happen only when $forward_compatible is false. */
throw new InvalidParam('acode', "Unknown A-code.");
}
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,134 +1,134 @@
<xml>
<brief>Retrieve data on a single attribute</brief>
<issue-id>268</issue-id>
<desc>
<p>Retrieve data on a single OKAPI geocache-attribute.</p>
<brief>Retrieve data on a single attribute</brief>
<issue-id>268</issue-id>
<desc>
<p>Retrieve data on a single OKAPI geocache-attribute.</p>
<p>OKAPI attributes are identified by an unique ID called an <b>A-code</b>.
All OKAPI attributes are shared among all OKAPI servers. Once an attribute is
published (e.g. via the <b>attribute_index</b> method), it won't
disappear in any of the future OKAPI revisions, nor will its meaning change.
Some attributes may get discontinued in the future, but they will remain accessible
by their original A-code.</p>
</desc>
<req name='acode'>
The A-code of the attribute you're interested in.
</req>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for fields like
<b>name</b> and <b>description</b>.</p>
</opt>
<opt name='fields' default='name'>
<p>Pipe-separated list of field names which you are interested with.
See below for the list of available fields.</p>
</opt>
<opt name='forward_compatible' default='true'>
By default, OKAPI will return an empty placeholder if you ask for an
unknown attribute. If you'd like to catch such errors and handle them
differently, then you may change this behavior by setting this parameter
to <b>false</b>. Then, OKAPI will return HTTP 400 error response,
instead of the placeholder (note that it behaves differently in the
<b>attributes</b> method).
</opt>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected in the <b>fields</b>
parameter. Available fields:</p>
<p>OKAPI attributes are identified by an unique ID called an <b>A-code</b>.
All OKAPI attributes are shared among all OKAPI servers. Once an attribute is
published (e.g. via the <b>attribute_index</b> method), it won't
disappear in any of the future OKAPI revisions, nor will its meaning change.
Some attributes may get discontinued in the future, but they will remain accessible
by their original A-code.</p>
</desc>
<req name='acode'>
The A-code of the attribute you're interested in.
</req>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for fields like
<b>name</b> and <b>description</b>.</p>
</opt>
<opt name='fields' default='name'>
<p>Pipe-separated list of field names which you are interested with.
See below for the list of available fields.</p>
</opt>
<opt name='forward_compatible' default='true'>
By default, OKAPI will return an empty placeholder if you ask for an
unknown attribute. If you'd like to catch such errors and handle them
differently, then you may change this behavior by setting this parameter
to <b>false</b>. Then, OKAPI will return HTTP 400 error response,
instead of the placeholder (note that it behaves differently in the
<b>attributes</b> method).
</opt>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected in the <b>fields</b>
parameter. Available fields:</p>
<ul>
<li>
<p><b>acode</b> - string, the A-code. Unique identifier of the
attribute.</p>
</li>
<li>
<p><b>name</b> - plaintext string, name of the attribute (language is
selected based on your <b>langpref</b> parameter),</p>
<ul>
<li>
<p><b>acode</b> - string, the A-code. Unique identifier of the
attribute.</p>
</li>
<li>
<p><b>name</b> - plaintext string, name of the attribute (language is
selected based on your <b>langpref</b> parameter),</p>
<p>If you think your language is missing, then feel free to add missing
translations directly to OKAPI repository. See
<a href='https://code.google.com/p/opencaching-api/source/browse/trunk/okapi/services/attrs/attribute-definitions.xml'>here</a>.</p>
</li>
<li>
<p><b>names</b> - a dictionary of all known names of the attribute, in
various languages (ISO 639-1 language code is used as dictionary
key).</p>
<p>If you think your language is missing, then feel free to add missing
translations directly to OKAPI repository. See
<a href='https://code.google.com/p/opencaching-api/source/browse/trunk/okapi/services/attrs/attribute-definitions.xml'>here</a>.</p>
</li>
<li>
<p><b>names</b> - a dictionary of all known names of the attribute, in
various languages (ISO 639-1 language code is used as dictionary
key).</p>
<p>If you think your language is missing, then feel free to add missing
translations directly to OKAPI repository. See
<a href='https://code.google.com/p/opencaching-api/source/browse/trunk/okapi/services/attrs/attribute-definitions.xml'>here</a>.</p>
</li>
<li>
<b>description</b> - HTML string, description of the attribute (language is
selected based on your <b>langpref</b> parameter),
<p>If you think your language is missing, then feel free to add missing
translations directly to OKAPI repository. See
<a href='https://code.google.com/p/opencaching-api/source/browse/trunk/okapi/services/attrs/attribute-definitions.xml'>here</a>.</p>
</li>
<li>
<b>description</b> - HTML string, description of the attribute (language is
selected based on your <b>langpref</b> parameter),
<p>If you think your language is missing, then feel free to add missing
translations directly to OKAPI repository. See
<a href='https://code.google.com/p/opencaching-api/source/browse/trunk/okapi/services/attrs/attribute-definitions.xml'>here</a>.</p>
</li>
<li>
<p><b>descriptions</b> - a dictionary of all known descriptions of the
attribute, in various languages (ISO 639-1 language code is used as
dictionary key).</p>
<p>If you think your language is missing, then feel free to add missing
translations directly to OKAPI repository. See
<a href='https://code.google.com/p/opencaching-api/source/browse/trunk/okapi/services/attrs/attribute-definitions.xml'>here</a>.</p>
</li>
<li>
<p><b>descriptions</b> - a dictionary of all known descriptions of the
attribute, in various languages (ISO 639-1 language code is used as
dictionary key).</p>
<p>If you think your language is missing, then feel free to add missing
translations directly to OKAPI repository. See
<a href='https://code.google.com/p/opencaching-api/source/browse/trunk/okapi/services/attrs/attribute-definitions.xml'>here</a>.</p>
</li>
<li>
<p><b>gc_equivs</b> - a list of Geocaching.com (Groundspeak)
attributes, which have exactly the same (or a very similar) meaning. Each
attribute is described as a dictionary of the following structure:</p>
<p>If you think your language is missing, then feel free to add missing
translations directly to OKAPI repository. See
<a href='https://code.google.com/p/opencaching-api/source/browse/trunk/okapi/services/attrs/attribute-definitions.xml'>here</a>.</p>
</li>
<li>
<p><b>gc_equivs</b> - a list of Geocaching.com (Groundspeak)
attributes, which have exactly the same (or a very similar) meaning. Each
attribute is described as a dictionary of the following structure:</p>
<ul>
<li><b>id</b> - ID of the Geocaching.com attribute,</li>
<li>
<b>inc</b> - integer, either 1 or 0. See Geocaching.com's
XSD for details on its meaning,
</li>
<li>
<b>name</b> - the name of the attribute (as it is included
in Geocaching.com GPX files).
</li>
</ul>
<ul>
<li><b>id</b> - ID of the Geocaching.com attribute,</li>
<li>
<b>inc</b> - integer, either 1 or 0. See Geocaching.com's
XSD for details on its meaning,
</li>
<li>
<b>name</b> - the name of the attribute (as it is included
in Geocaching.com GPX files).
</li>
</ul>
<p>Note that one gc_equivs list may have multiple items on it, and
that one Geocaching.com ID may be present in many gc_equivs.</p>
</li>
<li>
<p><b>is_locally_used</b> - boolean, indicates if the attribute is <i>currently</i>
used by <i>this</i> Opencaching server. Or, to be more specific, <b>true</b>
means that the attribute can currently be included in the <b>attr_acodes</b> field
of the <b>geocache</b> method in this OKAPI installation.</p>
<p>Note that one gc_equivs list may have multiple items on it, and
that one Geocaching.com ID may be present in many gc_equivs.</p>
</li>
<li>
<p><b>is_locally_used</b> - boolean, indicates if the attribute is <i>currently</i>
used by <i>this</i> Opencaching server. Or, to be more specific, <b>true</b>
means that the attribute can currently be included in the <b>attr_acodes</b> field
of the <b>geocache</b> method in this OKAPI installation.</p>
<p>Note that this flag can change in time. Some attributes may get
introduced into other installations, whereas other attributes may
(temporarily or permanently) stop being used. In general, we are aiming
towards global unification of all attributes between all OC nodes,
but this process will take time (and probably it will never
be 100% complete).</p>
</li>
<li>
<p><b>local_icon_url</b> - an URL pointing to an image associated with
this particular attribute in the local OC server, <b>or null</b> if the
current server does not have any image for this attribute.</p>
<p>Note that this flag can change in time. Some attributes may get
introduced into other installations, whereas other attributes may
(temporarily or permanently) stop being used. In general, we are aiming
towards global unification of all attributes between all OC nodes,
but this process will take time (and probably it will never
be 100% complete).</p>
</li>
<li>
<p><b>local_icon_url</b> - an URL pointing to an image associated with
this particular attribute in the local OC server, <b>or null</b> if the
current server does not have any image for this attribute.</p>
<p>Please note, that each OC server uses a different image set for their
attributes. All these images come in various sizes and can change over time.
In other words, if you want to use this attribute, then you must always be
prepared to receive <b>null</b>, or an image of unexpected dimensions.</p>
</li>
<li>
<p><b>is_discontinued</b> - boolean, indicates if the attribute is discontinued.
This means that it is no longer in use at OC servers which run current OKAPI
versions, i.e. geocaches are no longer tagged with this attribute. However,
it may still be in use at servers which have not been updated yet to the
current OKAPI version.</p>
<p>Please note, that each OC server uses a different image set for their
attributes. All these images come in various sizes and can change over time.
In other words, if you want to use this attribute, then you must always be
prepared to receive <b>null</b>, or an image of unexpected dimensions.</p>
</li>
<li>
<p><b>is_discontinued</b> - boolean, indicates if the attribute is discontinued.
This means that it is no longer in use at OC servers which run current OKAPI
versions, i.e. geocaches are no longer tagged with this attribute. However,
it may still be in use at servers which have not been updated yet to the
current OKAPI version.</p>
<p><b>Important:</b> This flag can change in time. Discontinued attributes can
"come back to life" later. You can never be 100% sure you will not
encounter them in OKAPI responses, so this field is purely informative.</p>
</li>
</ul>
</returns>
<p><b>Important:</b> This flag can change in time. Discontinued attributes can
"come back to life" later. You can never be 100% sure you will not
encounter them in OKAPI responses, so this field is purely informative.</p>
</li>
</ul>
</returns>
</xml>

View File

@ -18,56 +18,56 @@ use okapi\services\attrs\AttrHelper;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
# Read the parameters.
public static function call(OkapiRequest $request)
{
# Read the parameters.
$langpref = $request->get_parameter('langpref');
if (!$langpref) $langpref = "en";
$langpref = $request->get_parameter('langpref');
if (!$langpref) $langpref = "en";
$fields = $request->get_parameter('fields');
if (!$fields) $fields = "name";
$fields = $request->get_parameter('fields');
if (!$fields) $fields = "name";
$only_locally_used = $request->get_parameter('only_locally_used');
if (!$only_locally_used) $only_locally_used = "false";
$only_locally_used = ($only_locally_used == "true");
$only_locally_used = $request->get_parameter('only_locally_used');
if (!$only_locally_used) $only_locally_used = "false";
$only_locally_used = ($only_locally_used == "true");
# Get the list of attributes and filter the A-codes based on the
# parameters.
# Get the list of attributes and filter the A-codes based on the
# parameters.
require_once 'attr_helper.inc.php';
$attrdict = AttrHelper::get_attrdict();
$acodes = array();
foreach ($attrdict as $acode => &$attr_ref)
{
if ($only_locally_used && ($attr_ref['internal_id'] === null)) {
/* Skip. */
continue;
}
require_once 'attr_helper.inc.php';
$attrdict = AttrHelper::get_attrdict();
$acodes = array();
foreach ($attrdict as $acode => &$attr_ref)
{
if ($only_locally_used && ($attr_ref['internal_id'] === null)) {
/* Skip. */
continue;
}
$acodes[] = $acode;
}
$acodes[] = $acode;
}
# Retrieve the attribute objects and return the results.
# Retrieve the attribute objects and return the results.
if (count($acodes) > 0) {
$params = array(
'acodes' => implode("|", $acodes),
'langpref' => $langpref,
'fields' => $fields,
);
$results = OkapiServiceRunner::call('services/attrs/attributes',
new OkapiInternalRequest($request->consumer, $request->token, $params));
} else {
$results = new ArrayObject();
}
return Okapi::formatted_response($request, $results);
}
if (count($acodes) > 0) {
$params = array(
'acodes' => implode("|", $acodes),
'langpref' => $langpref,
'fields' => $fields,
);
$results = OkapiServiceRunner::call('services/attrs/attributes',
new OkapiInternalRequest($request->consumer, $request->token, $params));
} else {
$results = new ArrayObject();
}
return Okapi::formatted_response($request, $results);
}
}

View File

@ -1,35 +1,35 @@
<xml>
<brief>Get the list of all OKAPI attributes (A-codes)</brief>
<issue-id>270</issue-id>
<desc>
<p>This method returns <b>all</b> currently defined OKAPI geocache-attributes.
It is useful when you want to cache the data on the client-side.</p>
<brief>Get the list of all OKAPI attributes (A-codes)</brief>
<issue-id>270</issue-id>
<desc>
<p>This method returns <b>all</b> currently defined OKAPI geocache-attributes.
It is useful when you want to cache the data on the client-side.</p>
<p>Keep in mind that the number of attributes will grow. Hence, <b>if your application
uses multiple OKAPI servers, then it's best to use one of the frequently
updated servers for pre-caching attribute data</b> (currently, Opencaching.PL
is the most frequently updated installation). Also, you should never assume
that you have the complete list cached: A new attribute may be created at any time!</p>
<p>Keep in mind that the number of attributes will grow. Hence, <b>if your application
uses multiple OKAPI servers, then it's best to use one of the frequently
updated servers for pre-caching attribute data</b> (currently, Opencaching.PL
is the most frequently updated installation). Also, you should never assume
that you have the complete list cached: A new attribute may be created at any time!</p>
<p>Once an attribute is published via this method, it won't disappear, nor
will its meaning change (names and descriptions can be slightly altered though).</p>
</desc>
<opt name='langpref' default='en'>
Works the same as in the <b>attribute</b> method.
</opt>
<opt name='fields' default='name'>
Works the same as in the <b>attribute</b> method.
</opt>
<opt name='only_locally_used' default='false'>
<p>By default, all known attributes are returned - including those which
were used a couple of years back, or those which are used by other OC nodes
(and may perhaps be used by the local OC node in the future). If you're
interested only in the attributes *currently used* within *this* Opencaching
installation, then you may set this parameter to <b>true</b>.</p>
</opt>
<common-format-params/>
<returns>
A dictionary. All A-codes will be mapped to dictionary keys, and
each value will be an object, as described in the <b>attribute</b> method.
</returns>
<p>Once an attribute is published via this method, it won't disappear, nor
will its meaning change (names and descriptions can be slightly altered though).</p>
</desc>
<opt name='langpref' default='en'>
Works the same as in the <b>attribute</b> method.
</opt>
<opt name='fields' default='name'>
Works the same as in the <b>attribute</b> method.
</opt>
<opt name='only_locally_used' default='false'>
<p>By default, all known attributes are returned - including those which
were used a couple of years back, or those which are used by other OC nodes
(and may perhaps be used by the local OC node in the future). If you're
interested only in the attributes *currently used* within *this* Opencaching
installation, then you may set this parameter to <b>true</b>.</p>
</opt>
<common-format-params/>
<returns>
A dictionary. All A-codes will be mapped to dictionary keys, and
each value will be an object, as described in the <b>attribute</b> method.
</returns>
</xml>

View File

@ -18,120 +18,120 @@ use okapi\Db;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
private static $valid_field_names = array(
'acode', 'name', 'names', 'description', 'descriptions', 'gc_equivs',
'is_locally_used', 'is_deprecated', 'local_icon_url', 'is_discontinued'
);
private static $valid_field_names = array(
'acode', 'name', 'names', 'description', 'descriptions', 'gc_equivs',
'is_locally_used', 'is_deprecated', 'local_icon_url', 'is_discontinued'
);
public static function call(OkapiRequest $request)
{
# Read the parameters.
public static function call(OkapiRequest $request)
{
# Read the parameters.
$acodes = $request->get_parameter('acodes');
if (!$acodes) throw new ParamMissing('acodes');
$acodes = explode("|", $acodes);
$acodes = $request->get_parameter('acodes');
if (!$acodes) throw new ParamMissing('acodes');
$acodes = explode("|", $acodes);
$langpref = $request->get_parameter('langpref');
if (!$langpref) $langpref = "en";
$langpref = explode("|", $langpref);
$langpref = $request->get_parameter('langpref');
if (!$langpref) $langpref = "en";
$langpref = explode("|", $langpref);
$fields = $request->get_parameter('fields');
if (!$fields) $fields = "name";
$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.");
}
$fields = $request->get_parameter('fields');
if (!$fields) $fields = "name";
$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.");
}
$forward_compatible = $request->get_parameter('forward_compatible');
if (!$forward_compatible) $forward_compatible = "true";
if (!in_array($forward_compatible, array("true", "false")))
throw new InvalidParam('forward_compatible');
$forward_compatible = ($forward_compatible == "true");
$forward_compatible = $request->get_parameter('forward_compatible');
if (!$forward_compatible) $forward_compatible = "true";
if (!in_array($forward_compatible, array("true", "false")))
throw new InvalidParam('forward_compatible');
$forward_compatible = ($forward_compatible == "true");
# Load the attributes (all of them).
# Load the attributes (all of them).
require_once 'attr_helper.inc.php';
$attrdict = AttrHelper::get_attrdict();
require_once 'attr_helper.inc.php';
$attrdict = AttrHelper::get_attrdict();
# For each A-code, check if it exists, filter its fields and add it
# to the results.
# For each A-code, check if it exists, filter its fields and add it
# to the results.
$results = array();
foreach ($acodes as $acode)
{
/* Please note, that the $attr variable from the $attrdict dictionary
* below is NOT fully compatible with the interface of the "attribute"
* method. Some of $attr's fields are private and should not be exposed,
* other fields don't exist and have to be added dynamically! */
$results = array();
foreach ($acodes as $acode)
{
/* Please note, that the $attr variable from the $attrdict dictionary
* below is NOT fully compatible with the interface of the "attribute"
* method. Some of $attr's fields are private and should not be exposed,
* other fields don't exist and have to be added dynamically! */
if (isset($attrdict[$acode])) {
$attr = $attrdict[$acode];
} elseif ($forward_compatible) {
$attr = AttrHelper::get_unknown_placeholder($acode);
} else {
$results[$acode] = null;
continue;
}
if (isset($attrdict[$acode])) {
$attr = $attrdict[$acode];
} elseif ($forward_compatible) {
$attr = AttrHelper::get_unknown_placeholder($acode);
} else {
$results[$acode] = null;
continue;
}
# Fill langpref-specific fields.
# Fill langpref-specific fields.
$attr['name'] = Okapi::pick_best_language($attr['names'], $langpref);
$attr['description'] = Okapi::pick_best_language($attr['descriptions'], $langpref);
$attr['name'] = Okapi::pick_best_language($attr['names'], $langpref);
$attr['description'] = Okapi::pick_best_language($attr['descriptions'], $langpref);
# Fill some other fields (not kept in the cached attrdict).
# Fill some other fields (not kept in the cached attrdict).
$attr['is_locally_used'] = ($attr['internal_id'] !== null);
$attr['is_deprecated'] = $attr['is_discontinued']; // deprecated and undocumetned field, see issue 70
$attr['is_locally_used'] = ($attr['internal_id'] !== null);
$attr['is_deprecated'] = $attr['is_discontinued']; // deprecated and undocumetned field, see issue 70
# Add to results.
# Add to results.
$results[$acode] = $attr;
}
$results[$acode] = $attr;
}
# If the user wanted local_icon_urls, fetch them now. (We cannot cache them
# in the $attrdict because currently we have no way of knowing then they
# change.)
# If the user wanted local_icon_urls, fetch them now. (We cannot cache them
# in the $attrdict because currently we have no way of knowing then they
# change.)
if (in_array('local_icon_url', $fields))
{
$tmp = Db::select_all("
select id, icon_large
from cache_attrib
");
$map = array();
foreach ($tmp as &$row_ref) {
$map[$row_ref['id']] = &$row_ref;
}
$prefix = Settings::get('SITE_URL');
foreach ($results as &$attr_ref) {
$internal_id = $attr_ref['internal_id'];
if (isset($map[$internal_id])) {
$row = $map[$internal_id];
$attr_ref['local_icon_url'] = $prefix.$row['icon_large'];
} else {
$attr_ref['local_icon_url'] = null;
}
}
}
if (in_array('local_icon_url', $fields))
{
$tmp = Db::select_all("
select id, icon_large
from cache_attrib
");
$map = array();
foreach ($tmp as &$row_ref) {
$map[$row_ref['id']] = &$row_ref;
}
$prefix = Settings::get('SITE_URL');
foreach ($results as &$attr_ref) {
$internal_id = $attr_ref['internal_id'];
if (isset($map[$internal_id])) {
$row = $map[$internal_id];
$attr_ref['local_icon_url'] = $prefix.$row['icon_large'];
} else {
$attr_ref['local_icon_url'] = null;
}
}
}
# Filter the fields.
# Filter the fields.
foreach ($results as &$attr_ref) {
$clean_row = array();
foreach ($fields as $field)
$clean_row[$field] = $attr_ref[$field];
$attr_ref = $clean_row;
}
foreach ($results as &$attr_ref) {
$clean_row = array();
foreach ($fields as $field)
$clean_row[$field] = $attr_ref[$field];
$attr_ref = $clean_row;
}
return Okapi::formatted_response($request, $results);
}
return Okapi::formatted_response($request, $results);
}
}

View File

@ -1,30 +1,30 @@
<xml>
<brief>Retrieve data on multiple attributes at once</brief>
<issue-id>269</issue-id>
<desc>
<p>This method works like the <b>attribute</b> method, but
with multiple A-codes instead of only one. Read the docs for
the <b>attribute</b> method first!</p>
</desc>
<req name='acodes'>
Pipe-separated list of A-codes you're interested in.
</req>
<opt name='langpref' default='en'>
Works the same as in the <b>attribute</b> method.
</opt>
<opt name='fields' default='name'>
Works the same as in the <b>attribute</b> method.
</opt>
<opt name='forward_compatible' default='true'>
This has a similar meaning as in the <b>attribute</b> method, but works
differently. If set to <b>false</b>, OKAPI will return <b>null</b>s
for unknown keys (A-codes). You will still receive an HTTP 200 response
though!
</opt>
<common-format-params/>
<returns>
<p>A dictionary. A-codes you provide will be mapped to dictionary keys,
and each value will be an object, as described in the <b>attribute</b>
method.</p>
</returns>
<brief>Retrieve data on multiple attributes at once</brief>
<issue-id>269</issue-id>
<desc>
<p>This method works like the <b>attribute</b> method, but
with multiple A-codes instead of only one. Read the docs for
the <b>attribute</b> method first!</p>
</desc>
<req name='acodes'>
Pipe-separated list of A-codes you're interested in.
</req>
<opt name='langpref' default='en'>
Works the same as in the <b>attribute</b> method.
</opt>
<opt name='fields' default='name'>
Works the same as in the <b>attribute</b> method.
</opt>
<opt name='forward_compatible' default='true'>
This has a similar meaning as in the <b>attribute</b> method, but works
differently. If set to <b>false</b>, OKAPI will return <b>null</b>s
for unknown keys (A-codes). You will still receive an HTTP 200 response
though!
</opt>
<common-format-params/>
<returns>
<p>A dictionary. A-codes you provide will be mapped to dictionary keys,
and each value will be an object, as described in the <b>attribute</b>
method.</p>
</returns>
</xml>

View File

@ -20,198 +20,202 @@ use \Exception;
class WebService
{
private static $shutdown_function_registered = false;
private static $files_to_unlink = array();
private static $shutdown_function_registered = false;
private static $files_to_unlink = array();
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
$cache_codes = $request->get_parameter('cache_codes');
if ($cache_codes === null) throw new ParamMissing('cache_codes');
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!
# 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";
$images = $request->get_parameter('images');
if (!$images) $images = "all";
if (!in_array($images, array("none", "all", "spoilers", "nonspoilers")))
throw new InvalidParam('images');
$langpref = $request->get_parameter('langpref');
if (!$langpref) $langpref = "en";
$images = $request->get_parameter('images');
if (!$images) $images = "all";
if (!in_array($images, array("none", "all", "spoilers", "nonspoilers")))
throw new InvalidParam('images');
$location_source = $request->get_parameter('location_source');
$location_change_prefix = $request->get_parameter('location_change_prefix');
# Start creating ZIP archive.
# Start creating ZIP archive.
$tempfilename = Okapi::get_var_dir()."/garmin".time().rand(100000,999999).".zip";
$zip = new ZipArchive();
if ($zip->open($tempfilename, ZIPARCHIVE::CREATE) !== true)
throw new Exception("ZipArchive class could not create temp file $tempfilename. Check permissions!");
$tempfilename = Okapi::get_var_dir()."/garmin".time().rand(100000,999999).".zip";
$zip = new ZipArchive();
if ($zip->open($tempfilename, ZIPARCHIVE::CREATE) !== true)
throw new Exception("ZipArchive class could not create temp file $tempfilename. Check permissions!");
# Create basic structure
# Create basic structure
$zip->addEmptyDir("Garmin");
$zip->addEmptyDir("Garmin/GPX");
$zip->addEmptyDir("Garmin/GeocachePhotos");
$zip->addEmptyDir("Garmin");
$zip->addEmptyDir("Garmin/GPX");
$zip->addEmptyDir("Garmin/GeocachePhotos");
# Include a GPX 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).
# Include a GPX 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).
$zip->addFromString("Garmin/GPX/opencaching".time().rand(100000,999999).".gpx",
OkapiServiceRunner::call('services/caches/formatters/gpx', 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"
)))->get_body());
$zip->addFromString("Garmin/GPX/opencaching".time().rand(100000,999999).".gpx",
OkapiServiceRunner::call('services/caches/formatters/gpx', 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());
# Then, include all the images.
# 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];
$zip->addEmptyDir($dir); # fails silently if it already exists
$dir .= "/".$cache_code[strlen($cache_code) - 2];
$zip->addEmptyDir($dir);
$dir .= "/".$cache_code;
$zip->addEmptyDir($dir);
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
$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];
$zip->addEmptyDir($dir); # fails silently if it already exists
$dir .= "/".$cache_code[strlen($cache_code) - 2];
$zip->addEmptyDir($dir);
$dir .= "/".$cache_code;
$zip->addEmptyDir($dir);
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']) {
$zip->addEmptyDir($dir."/Spoilers");
$zippath = $dir."/Spoilers/".$img['unique_caption'].".jpg";
} else {
$zippath = $dir."/".$img['unique_caption'].".jpg";
}
if ($img['is_spoiler']) {
$zip->addEmptyDir($dir."/Spoilers");
$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.
# 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.
# 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))
{
$file = file_get_contents($syspath);
if ($file)
$zip->addFromString($zippath, $file);
}
else
{
# If file exists, but does not end with ".jpg", we will create
# JPEG version of it and store it in the cache.
$syspath = Settings::get('IMAGES_DIR')."/".$img['uuid'].".jpg";
if (file_exists($syspath))
{
$file = file_get_contents($syspath);
if ($file)
$zip->addFromString($zippath, $file);
}
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.
$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"!
$zip->addFromString($zippath, $jpeg_contents);
}
}
}
}
$jpeg_contents = false;
}
Cache::set($cache_key, $jpeg_contents, 86400);
break;
}
}
}
if ($jpeg_contents) # This can be "null" *or* "false"!
$zip->addFromString($zippath, $jpeg_contents);
}
}
}
}
$zip->close();
$zip->close();
# The result could be big. Bigger than our memory limit. We will
# return an open file stream instead of a string. 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).
# The result could be big. Bigger than our memory limit. We will
# return an open file stream instead of a string. 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 = new OkapiHttpResponse();
$response->content_type = "application/zip";
$response->content_disposition = 'attachment; filename="results.zip"';
$response->stream_length = filesize($tempfilename);
$response->body = fopen($tempfilename, "rb");
$response->allow_gzip = false;
self::add_file_to_unlink($tempfilename);
return $response;
}
set_time_limit(600);
$response = new OkapiHttpResponse();
$response->content_type = "application/zip";
$response->content_disposition = 'attachment; filename="results.zip"';
$response->stream_length = filesize($tempfilename);
$response->body = fopen($tempfilename, "rb");
$response->allow_gzip = false;
self::add_file_to_unlink($tempfilename);
return $response;
}
private static function add_file_to_unlink($filename)
{
if (!self::$shutdown_function_registered)
register_shutdown_function(array("okapi\\services\\caches\\formatters\\garmin\\WebService", "unlink_temporary_files"));
self::$files_to_unlink[] = $filename;
}
private static function add_file_to_unlink($filename)
{
if (!self::$shutdown_function_registered)
register_shutdown_function(array("okapi\\services\\caches\\formatters\\garmin\\WebService", "unlink_temporary_files"));
self::$files_to_unlink[] = $filename;
}
public static function unlink_temporary_files()
{
foreach (self::$files_to_unlink as $filename)
@unlink($filename);
self::$files_to_unlink = array();
}
public static function unlink_temporary_files()
{
foreach (self::$files_to_unlink as $filename)
@unlink($filename);
self::$files_to_unlink = array();
}
}

View File

@ -1,47 +1,55 @@
<xml>
<brief>Retrieve ZIP file for Garmin devices</brief>
<issue-id>99</issue-id>
<desc>
<p>Produce a ZIP file with content compatible with Geocaching-enabled Garmin
GPS devices. The general idea is, <b>you should <u>extract</u> the contents</b> of the ZIP
file directly <b>into the root directory</b> of your Garmin's internal memory storage
(currently it doesn't work if you extract the data to the external MicroSD card). The device
will then be filled with all the data we are currently able to fill it with.</p>
<p>Currently, the ZIP file will contain GPX file and all JPEG images associated
with the chosen geocaches. Options for the GPX file are fixed, use the
services/caches/formatters/gpx method if
you want a custom-tailored GPX file.</p>
<p><b>Important note:</b> The contents of the returned ZIP archive may change in the
future. You should not parse the contents, nor assume that they have any specific
structure.</p>
<p><b>Important note:</b> We might be increasing required authentication level for
this method to level 2 or 3. You should be prepared for that. Use the highest authentication
level you've got. Also, if you want the GPX file to include <b>personal data (like user's notes)</b>,
you have to use Level 3 Authentication anyway.</p>
<p><b>Note:</b> All non-JPEG images will be skipped. Currently OKAPI does not convert
other types of images to JPEG.</p>
</desc>
<req name='cache_codes'>
<p>Pipe-separated list of cache codes which you are interested in.
No more than 50 codes are allowed. (This limit is smaller than usual, because
we're afraid about the sizes of ZIP files produced.)
This CAN be an empty string (it will still result in a valid ZIP file).</p>
</req>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for GPX entities.</p>
</opt>
<opt name='images' default='all'>
<p>One of the following values:</p>
<ul>
<li><b>none</b> - no images will be included in the result,</li>
<li><b>all</b> - all images will be included in the result,</li>
<li><b>spoilers</b> - only spoiler images will be included in the result,</li>
<li><b>nonspoilers</b> - only non-spoiler images will be included in the result.</li>
</ul>
</opt>
<returns>
<p>ZIP file. You should extract it's contents directly into the root
directory of your Garmin's internal memory storage.</p>
</returns>
<brief>Retrieve ZIP file for Garmin devices</brief>
<issue-id>99</issue-id>
<desc>
<p>Produce a ZIP file with content compatible with Geocaching-enabled Garmin
GPS devices. The general idea is, <b>you should <u>extract</u> the contents</b> of the ZIP
file directly <b>into the root directory</b> of your Garmin's internal memory storage
(currently it doesn't work if you extract the data to the external MicroSD card). The device
will then be filled with all the data we are currently able to fill it with.</p>
<p>Currently, the ZIP file will contain GPX file and all JPEG images associated
with the chosen geocaches. Options for the GPX file are fixed, use the
services/caches/formatters/gpx method if
you want a custom-tailored GPX file.</p>
<p><b>Important note:</b> The contents of the returned ZIP archive may change in the
future. You should not parse the contents, nor assume that they have any specific
structure.</p>
<p><b>Important note:</b> We might be increasing required authentication level for
this method to level 2 or 3. You should be prepared for that. Use the highest authentication
level you've got. Also, if you want the GPX file to include <b>personal data (like user's notes)</b>,
you have to use Level 3 Authentication anyway.</p>
<p><b>Note:</b> All non-JPEG images will be skipped. Currently OKAPI does not convert
other types of images to JPEG.</p>
</desc>
<req name='cache_codes'>
<p>Pipe-separated list of cache codes which you are interested in.
No more than 50 codes are allowed. (This limit is smaller than usual, because
we're afraid about the sizes of ZIP files produced.)
This CAN be an empty string (it will still result in a valid ZIP file).</p>
</req>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for GPX entities.</p>
</opt>
<opt name='images' default='all'>
<p>One of the following values:</p>
<ul>
<li><b>none</b> - no images will be included in the result,</li>
<li><b>all</b> - all images will be included in the result,</li>
<li><b>spoilers</b> - only spoiler images will be included in the result,</li>
<li><b>nonspoilers</b> - only non-spoiler images will be included in the result.</li>
</ul>
</opt>
<opt name="location_source" default='default-coords'>
Same as in the <a href="%OKAPI:methodargref:services/caches/formatters/gpx#location_source%">
services/caches/formatters/gpx</a> method.
</opt>
<opt name="location_change_prefix" default="#">
Same as in the <a href="%OKAPI:methodargref:services/caches/formatters/gpx#location_change_prefix%">
services/caches/formatters/gpx</a> method.
</opt>
<returns>
<p>ZIP file. You should extract it's contents directly into the root
directory of your Garmin's internal memory storage.</p>
</returns>
</xml>

View File

@ -19,297 +19,383 @@ use okapi\services\attrs\AttrHelper;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
/** Maps OKAPI cache type codes to Geocaching.com GPX cache types. */
public static $cache_GPX_types = array(
'Traditional' => 'Traditional Cache',
'Multi' => 'Multi-Cache',
'Quiz' => 'Unknown Cache',
'Event' => 'Event Cache',
'Virtual' => 'Virtual Cache',
'Webcam' => 'Webcam Cache',
'Moving' => 'Unknown Cache',
'Math/Physics' => 'Unknown Cache',
'Drive-In' => 'Traditional Cache',
'Own' => 'Unknown Cache',
'Other' => 'Unknown Cache'
);
/** Maps OKAPI cache type codes to Geocaching.com GPX cache types. */
public static $cache_GPX_types = array(
'Traditional' => 'Traditional Cache',
'Multi' => 'Multi-Cache',
'Quiz' => 'Unknown Cache',
'Event' => 'Event Cache',
'Virtual' => 'Virtual Cache',
'Webcam' => 'Webcam Cache',
'Moving' => 'Unknown Cache',
'Math/Physics' => 'Unknown Cache',
'Drive-In' => 'Traditional Cache',
'Podcast' => 'Unknown Cache',
'Own' => 'Unknown Cache',
'Other' => 'Unknown Cache'
);
/** Maps OKAPI's 'size2' values to geocaching.com size codes. */
public static $cache_GPX_sizes = array(
'none' => 'Virtual',
'nano' => 'Micro',
'micro' => 'Micro',
'small' => 'Small',
'regular' => 'Regular',
'large' => 'Large',
'xlarge' => 'Large',
'other' => 'Other',
);
/** Maps OKAPI's 'size2' values to geocaching.com size codes. */
public static $cache_GPX_sizes = array(
'none' => 'Virtual',
'nano' => 'Micro',
'micro' => 'Micro',
'small' => 'Small',
'regular' => 'Regular',
'large' => 'Large',
'xlarge' => 'Large',
'other' => 'Other',
);
public static function call(OkapiRequest $request)
{
$vars = array();
public static function call(OkapiRequest $request)
{
$vars = array();
# Validating arguments. We will also assign some of them to the
# $vars variable which we will use later in the GPS template.
# Validating arguments. We will also assign some of them to the
# $vars variable which we will use later in the GPS template.
$cache_codes = $request->get_parameter('cache_codes');
if ($cache_codes === null) throw new ParamMissing('cache_codes');
$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!
# 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";
foreach (array('ns_ground', 'ns_gsak', 'ns_ox', 'latest_logs', 'alt_wpts', 'mark_found') as $param)
{
$val = $request->get_parameter($param);
if (!$val) $val = "false";
elseif (!in_array($val, array("true", "false")))
throw new InvalidParam($param);
$vars[$param] = ($val == "true");
}
if ($vars['latest_logs'] && (!$vars['ns_ground']))
throw new BadRequest("In order for 'latest_logs' to work you have to also include 'ns_ground' extensions.");
$langpref = $request->get_parameter('langpref');
if (!$langpref) $langpref = "en";
foreach (array('ns_ground', 'ns_gsak', 'ns_ox', 'latest_logs', 'alt_wpts', 'mark_found') as $param)
{
$val = $request->get_parameter($param);
if (!$val) $val = "false";
elseif (!in_array($val, array("true", "false")))
throw new InvalidParam($param);
$vars[$param] = ($val == "true");
}
if ($vars['latest_logs'] && (!$vars['ns_ground']))
throw new BadRequest("In order for 'latest_logs' to work you have to also include 'ns_ground' extensions.");
$tmp = $request->get_parameter('my_notes');
$vars['my_notes'] = array();
if ($tmp && $tmp != 'none') {
$tmp = explode('|', $tmp);
foreach ($tmp as $elem) {
if ($elem == 'none') {
/* pass */
} elseif (in_array($elem, array('desc:text', 'gc:personal_note'))) {
if (in_array('none', $tmp)) {
throw new InvalidParam(
'my_notes', "You cannot mix 'none' and '$elem'"
);
}
if ($request->token == null) {
throw new BadRequest(
"Level 3 Authentication is required to access my_notes data."
);
}
$vars['my_notes'][] = $elem;
} else {
throw new InvalidParam('my_notes', "Invalid list entry: '$elem'");
}
}
}
$tmp = $request->get_parameter('my_notes');
$vars['my_notes'] = array();
if ($tmp && $tmp != 'none') {
$tmp = explode('|', $tmp);
foreach ($tmp as $elem) {
if ($elem == 'none') {
/* pass */
} elseif (in_array($elem, array('desc:text', 'gc:personal_note'))) {
if (in_array('none', $tmp)) {
throw new InvalidParam(
'my_notes', "You cannot mix 'none' and '$elem'"
);
}
if ($request->token == null) {
throw new BadRequest(
"Level 3 Authentication is required to access my_notes data."
);
}
$vars['my_notes'][] = $elem;
} else {
throw new InvalidParam('my_notes', "Invalid list entry: '$elem'");
}
}
}
$images = $request->get_parameter('images');
if (!$images) $images = 'descrefs:nonspoilers';
if (!in_array($images, array('none', 'descrefs:thumblinks', 'descrefs:nonspoilers', 'descrefs:all', 'ox:all')))
throw new InvalidParam('images', "'$images'");
$vars['images'] = $images;
$images = $request->get_parameter('images');
if (!$images) $images = 'descrefs:nonspoilers';
if (!in_array($images, array('none', 'descrefs:thumblinks', 'descrefs:nonspoilers', 'descrefs:all', 'ox:all')))
throw new InvalidParam('images', "'$images'");
$vars['images'] = $images;
$tmp = $request->get_parameter('attrs');
if (!$tmp) $tmp = 'desc:text';
$tmp = explode("|", $tmp);
$vars['attrs'] = array();
foreach ($tmp as $elem)
{
if ($elem == 'none') {
/* pass */
} elseif (in_array($elem, array('desc:text', 'ox:tags', 'gc:attrs', 'gc_ocde:attrs'))) {
if ($elem == 'gc_ocde:attrs' && Settings::get('OC_BRANCH') != 'oc.de')
$vars['attrs'][] = 'gc:attrs';
else
$vars['attrs'][] = $elem;
} else {
throw new InvalidParam('attrs', "Invalid list entry: '$elem'");
}
}
$tmp = $request->get_parameter('attrs');
if (!$tmp) $tmp = 'desc:text';
$tmp = explode("|", $tmp);
$vars['attrs'] = array();
foreach ($tmp as $elem)
{
if ($elem == 'none') {
/* pass */
} elseif (in_array($elem, array('desc:text', 'ox:tags', 'gc:attrs', 'gc_ocde:attrs'))) {
if ($elem == 'gc_ocde:attrs' && Settings::get('OC_BRANCH') != 'oc.de')
$vars['attrs'][] = 'gc:attrs';
else
$vars['attrs'][] = $elem;
} else {
throw new InvalidParam('attrs', "Invalid list entry: '$elem'");
}
}
$protection_areas = $request->get_parameter('protection_areas');
if (!$protection_areas || $protection_areas == 'desc:auto')
{
if (Settings::get('OC_BRANCH') == 'oc.de') $protection_areas = 'desc:text';
else $protection_areas = 'none';
}
if (!in_array($protection_areas, array('none', 'desc:text')))
throw new InvalidParam('protection_areas',"'$protection_areas'");
$vars['protection_areas'] = $protection_areas;
$protection_areas = $request->get_parameter('protection_areas');
if (!$protection_areas || $protection_areas == 'desc:auto')
{
if (Settings::get('OC_BRANCH') == 'oc.de') $protection_areas = 'desc:text';
else $protection_areas = 'none';
}
if (!in_array($protection_areas, array('none', 'desc:text')))
throw new InvalidParam('protection_areas',"'$protection_areas'");
$vars['protection_areas'] = $protection_areas;
$tmp = $request->get_parameter('trackables');
if (!$tmp) $tmp = 'none';
if (!in_array($tmp, array('none', 'desc:list', 'desc:count')))
throw new InvalidParam('trackables', "'$tmp'");
$vars['trackables'] = $tmp;
$tmp = $request->get_parameter('trackables');
if (!$tmp) $tmp = 'none';
if (!in_array($tmp, array('none', 'desc:list', 'desc:count')))
throw new InvalidParam('trackables', "'$tmp'");
$vars['trackables'] = $tmp;
$tmp = $request->get_parameter('recommendations');
if (!$tmp) $tmp = 'none';
if (!in_array($tmp, array('none', 'desc:count')))
throw new InvalidParam('recommendations', "'$tmp'");
$vars['recommendations'] = $tmp;
$tmp = $request->get_parameter('recommendations');
if (!$tmp) $tmp = 'none';
if (!in_array($tmp, array('none', 'desc:count')))
throw new InvalidParam('recommendations', "'$tmp'");
$vars['recommendations'] = $tmp;
$lpc = $request->get_parameter('lpc');
if ($lpc === null) $lpc = 10; # will be checked in services/caches/geocaches call
$lpc = $request->get_parameter('lpc');
if ($lpc === null) $lpc = 10; # will be checked in services/caches/geocaches call
$user_uuid = $request->get_parameter('user_uuid');
$user_uuid = $request->get_parameter('user_uuid');
# Which fields of the services/caches/geocaches method do we need?
# location_source (part 1 of 2)
$fields = 'code|name|location|date_created|url|type|status|size|size2|oxsize'.
'|difficulty|terrain|description|hint2|rating|owner|url|internal_id'.
'|protection_areas';
if ($vars['images'] != 'none')
$fields .= "|images";
if (count($vars['attrs']) > 0)
$fields .= "|attrnames|attr_acodes";
if ($vars['trackables'] == 'desc:list')
$fields .= "|trackables";
elseif ($vars['trackables'] == 'desc:count')
$fields .= "|trackables_count";
if ($vars['alt_wpts'] == 'true')
$fields .= "|alt_wpts";
if ($vars['recommendations'] != 'none')
$fields .= "|recommendations|founds";
if (count($vars['my_notes']) > 0)
$fields .= "|my_notes";
if ($vars['latest_logs'])
$fields .= "|latest_logs";
if ($vars['mark_found'])
$fields .= "|is_found";
$location_source = $request->get_parameter('location_source');
if (!$location_source)
{
$location_source = 'default-coords';
}
# Make sure location_source has prefix alt_wpt:
if ($location_source != 'default-coords' && strncmp($location_source, 'alt_wpt:', 8) != 0)
{
throw new InvalidParam('location_source', '\''.$location_source.'\'');
}
$vars['caches'] = OkapiServiceRunner::call(
'services/caches/geocaches', new OkapiInternalRequest(
$request->consumer, $request->token, array(
'cache_codes' => $cache_codes,
'langpref' => $langpref,
'fields' => $fields,
'lpc' => $lpc,
'user_uuid' => $user_uuid,
'log_fields' => 'uuid|date|user|type|comment|internal_id|was_recommended'
)
)
);
# Make sure we have sufficient authorization
if ($location_source == 'alt_wpt:user-coords' && $request->token == null)
{
throw new BadRequest("Level 3 Authentication is required to access 'alt_wpt:user-coords'.");
}
# Get all the other data need.
# Which fields of the services/caches/geocaches method do we need?
$vars['installation'] = OkapiServiceRunner::call(
'services/apisrv/installation', new OkapiInternalRequest(
new OkapiInternalConsumer(), null, array()
)
);
$vars['cache_GPX_types'] = self::$cache_GPX_types;
$vars['cache_GPX_sizes'] = self::$cache_GPX_sizes;
$fields = 'code|name|location|date_created|url|type|status|size|size2|oxsize'.
'|difficulty|terrain|description|hint2|rating|owner|url|internal_id'.
'|protection_areas|short_description';
if ($vars['images'] != 'none')
$fields .= "|images";
if (count($vars['attrs']) > 0)
$fields .= "|attrnames|attr_acodes";
if ($vars['trackables'] == 'desc:list')
$fields .= "|trackables";
elseif ($vars['trackables'] == 'desc:count')
$fields .= "|trackables_count";
if ($vars['alt_wpts'] == 'true' || $location_source != 'default-coords')
$fields .= "|alt_wpts";
if ($vars['recommendations'] != 'none')
$fields .= "|recommendations|founds";
if (count($vars['my_notes']) > 0)
$fields .= "|my_notes";
if ($vars['latest_logs'])
$fields .= "|latest_logs";
if ($vars['mark_found'])
$fields .= "|is_found";
if (count($vars['attrs']) > 0)
{
/* The user asked for some kind of attribute output. We'll fetch all
* the data we MAY need. This is often far too much, but thanks to
* caching, it will work fast. */
$vars['caches'] = OkapiServiceRunner::call(
'services/caches/geocaches', new OkapiInternalRequest(
$request->consumer, $request->token, array(
'cache_codes' => $cache_codes,
'langpref' => $langpref,
'fields' => $fields,
'lpc' => $lpc,
'user_uuid' => $user_uuid,
'log_fields' => 'uuid|date|user|type|comment|internal_id|was_recommended'
)
)
);
$vars['attr_index'] = OkapiServiceRunner::call(
'services/attrs/attribute_index', new OkapiInternalRequest(
$request->consumer, $request->token, array(
'only_locally_used' => 'true',
'langpref' => $langpref,
'fields' => 'name|gc_equivs'
)
)
);
# Get rid of invalid cache references.
# prepare GS attribute data
$valid = array();
foreach ($vars['caches'] as $key => &$ref) {
if ($ref !== null) {
$valid[$key] = &$ref;
}
}
$vars['caches'] = &$valid;
unset($valid);
$vars['gc_attrs'] = in_array('gc:attrs', $vars['attrs']);
$vars['gc_ocde_attrs'] = in_array('gc_ocde:attrs', $vars['attrs']);
if ($vars['gc_attrs'] || $vars['gc_ocde_attrs'])
{
if ($vars['gc_ocde_attrs'])
{
# As this is an OCDE compatibility feature, we use the same Pseudo-GS
# attribute names here as OCDE. Note that this code is specific to OCDE
# database; OCPL stores attribute names in a different way and may use
# different names for equivalent attributes.
# Get all the other data need.
$ocde_attrnames = Db::select_group_by('id',"
select id, name
from cache_attrib
");
$attr_dict = AttrHelper::get_attrdict();
}
$vars['installation'] = OkapiServiceRunner::call(
'services/apisrv/installation', new OkapiInternalRequest(
new OkapiInternalConsumer(), null, array()
)
);
$vars['cache_GPX_types'] = self::$cache_GPX_types;
$vars['cache_GPX_sizes'] = self::$cache_GPX_sizes;
foreach ($vars['caches'] as &$cache)
{
$cache['gc_attrs'] = array();
foreach ($cache['attr_acodes'] as $acode)
{
$has_gc_equivs = false;
foreach ($vars['attr_index'][$acode]['gc_equivs'] as $gc)
{
# The assignment via GC-ID as array key will prohibit duplicate
# GC attributes, which can result from
# - assigning the same GC ID to multiple A-Codes,
# - contradicting attributes in one OC listing, e.g. 24/4 + not 24/7.
if (count($vars['attrs']) > 0)
{
/* The user asked for some kind of attribute output. We'll fetch all
* the data we MAY need. This is often far too much, but thanks to
* caching, it will work fast. */
$cache['gc_attrs'][$gc['id']] = $gc;
$has_gc_equivs = true;
}
if (!$has_gc_equivs && $vars['gc_ocde_attrs'])
{
# Generate an OCDE pseudo-GS attribute;
# see http://code.google.com/p/opencaching-api/issues/detail?id=190 and
# http://code.google.com/p/opencaching-api/issues/detail?id=271.
#
# Groundspeak uses ID 1..65 (as of June, 2013), and OCDE makeshift
# IDs start at 106, so there is space for 40 new GS attributes.
$vars['attr_index'] = OkapiServiceRunner::call(
'services/attrs/attribute_index', new OkapiInternalRequest(
$request->consumer, $request->token, array(
'only_locally_used' => 'true',
'langpref' => $langpref,
'fields' => 'name|gc_equivs'
)
)
);
$internal_id = $attr_dict[$acode]['internal_id'];
$cache['gc_attrs'][100 + $internal_id] = array(
'inc' => 1,
'name' => $ocde_attrnames[$internal_id][0]['name'],
);
}
}
}
}
}
# prepare GS attribute data
/* OC sites always used internal user_ids in their generated GPX files.
* This might be considered an error in itself (Groundspeak's XML namespace
* doesn't allow that), but it very common (Garmin's OpenCaching.COM
* also does that). Therefore, for backward-compatibility reasons, OKAPI
* will do it the same way. See issue 174.
*
* Currently, the caches method does not expose "owner.internal_id" and
* "latest_logs.user.internal_id" fields, we will read them manually
* from the database here. */
$vars['gc_attrs'] = in_array('gc:attrs', $vars['attrs']);
$vars['gc_ocde_attrs'] = in_array('gc_ocde:attrs', $vars['attrs']);
if ($vars['gc_attrs'] || $vars['gc_ocde_attrs'])
{
if ($vars['gc_ocde_attrs'])
{
# As this is an OCDE compatibility feature, we use the same Pseudo-GS
# attribute names here as OCDE. Note that this code is specific to OCDE
# database; OCPL stores attribute names in a different way and may use
# different names for equivalent attributes.
$dict = array();
foreach ($vars['caches'] as &$cache_ref)
{
$dict[$cache_ref['owner']['uuid']] = true;
if (isset($cache_ref['latest_logs']))
foreach ($cache_ref['latest_logs'] as &$log_ref)
$dict[$log_ref['user']['uuid']] = true;
}
$rs = Db::query("
select uuid, user_id
from user
where uuid in ('".implode("','", array_map('mysql_real_escape_string', array_keys($dict)))."')
");
while ($row = mysql_fetch_assoc($rs))
$dict[$row['uuid']] = $row['user_id'];
$vars['user_uuid_to_internal_id'] = &$dict;
unset($dict);
$ocde_attrnames = Db::select_group_by('id',"
select id, name
from cache_attrib
");
$attr_dict = AttrHelper::get_attrdict();
}
$response = new OkapiHttpResponse();
$response->content_type = "application/gpx; charset=utf-8";
$response->content_disposition = 'attachment; filename="results.gpx"';
ob_start();
Okapi::gettext_domain_init(explode("|", $langpref)); # Consumer gets properly localized GPX file.
include 'gpxfile.tpl.php';
Okapi::gettext_domain_restore();
$response->body = ob_get_clean();
return $response;
}
foreach ($vars['caches'] as &$cache_ref)
{
$cache_ref['gc_attrs'] = array();
foreach ($cache_ref['attr_acodes'] as $acode)
{
$has_gc_equivs = false;
foreach ($vars['attr_index'][$acode]['gc_equivs'] as $gc)
{
# The assignment via GC-ID as array key will prohibit duplicate
# GC attributes, which can result from
# - assigning the same GC ID to multiple A-Codes,
# - contradicting attributes in one OC listing, e.g. 24/4 + not 24/7.
$cache_ref['gc_attrs'][$gc['id']] = $gc;
$has_gc_equivs = true;
}
if (!$has_gc_equivs && $vars['gc_ocde_attrs'])
{
# Generate an OCDE pseudo-GS attribute;
# see http://code.google.com/p/opencaching-api/issues/detail?id=190 and
# http://code.google.com/p/opencaching-api/issues/detail?id=271.
#
# Groundspeak uses ID 1..65 (as of June, 2013), and OCDE makeshift
# IDs start at 106, so there is space for 40 new GS attributes.
$internal_id = $attr_dict[$acode]['internal_id'];
$cache_ref['gc_attrs'][100 + $internal_id] = array(
'inc' => 1,
'name' => $ocde_attrnames[$internal_id][0]['name'],
);
}
}
}
}
}
/* OC sites always used internal user_ids in their generated GPX files.
* This might be considered an error in itself (Groundspeak's XML namespace
* doesn't allow that), but it very common (Garmin's OpenCaching.COM
* also does that). Therefore, for backward-compatibility reasons, OKAPI
* will do it the same way. See issue 174.
*
* Currently, the caches method does not expose "owner.internal_id" and
* "latest_logs.user.internal_id" fields, we will read them manually
* from the database here. */
$dict = array();
foreach ($vars['caches'] as &$cache_ref)
{
$dict[$cache_ref['owner']['uuid']] = true;
if (isset($cache_ref['latest_logs']))
foreach ($cache_ref['latest_logs'] as &$log_ref)
$dict[$log_ref['user']['uuid']] = true;
}
$rs = Db::query("
select uuid, user_id
from user
where uuid in ('".implode("','", array_map('mysql_real_escape_string', array_keys($dict)))."')
");
while ($row = mysql_fetch_assoc($rs))
$dict[$row['uuid']] = $row['user_id'];
$vars['user_uuid_to_internal_id'] = &$dict;
unset($dict);
# location_source (part 2 of 2)
if ($location_source != 'default-coords')
{
$location_change_prefix = $request->get_parameter('location_change_prefix');
if (!$location_change_prefix) {
$location_change_prefix = '# ';
}
# lets find requested coords
foreach ($vars['caches'] as &$cache_ref)
{
foreach ($cache_ref['alt_wpts'] as $alt_wpt_key => $alt_wpt)
{
if ('alt_wpt:'.$alt_wpt['type'] == $location_source)
{
# Switch locations between primary wpt and alternate wpt.
# Also alter the cache name and make sure to append a proper
# notice.
$original_location = $cache_ref['location'];
$cache_ref['location'] = $alt_wpt['location'];
$cache_ref['name_2'] = $location_change_prefix.$cache_ref['name'];
if ($location_source == "alt_wpt:user-coords") {
# In case of "user-coords", replace the default warning with a custom-tailored one.
$cache_ref['warning_prefix'] = _(
"<b>Geocache coordinates have been changed.</b> They have been replaced with ".
"your own custom coordinates which you have provided for this geocache."
);
} else {
# Default warning
$cache_ref['warning_prefix'] = _(
"<b>Geocache coordinates have been changed.</b> Currently they ".
"point to one of the alternate waypoints originally described as:"
) . " " . $alt_wpt['description'];
}
# remove current alt waypoint
unset($cache_ref['alt_wpts'][$alt_wpt_key]);
# add original location as alternate
if ($vars['alt_wpts'])
{
$cache_ref['alt_wpts'][] = array(
'name' => $cache_ref['code'].'-DEFAULT-COORDS',
'location' => $original_location,
'type' => 'default-coords',
'type_name' => _("Original geocache location"),
'sym' => 'Block, Blue',
'description' => sprintf(_("Original (owner-supplied) location of the %s geocache"), $cache_ref['code']),
);
}
break;
}
}
}
}
$response = new OkapiHttpResponse();
$response->content_type = "application/gpx; charset=utf-8";
$response->content_disposition = 'attachment; filename="results.gpx"';
ob_start();
Okapi::gettext_domain_init(explode("|", $langpref)); # Consumer gets properly localized GPX file.
include 'gpxfile.tpl.php';
Okapi::gettext_domain_restore();
$response->body = ob_get_clean();
return $response;
}
}

View File

@ -1,198 +1,257 @@
<xml>
<brief>Retrieve geocaches in GPX format</brief>
<issue-id>40</issue-id>
<desc>
<p>Produces a standards-compliant Geocaching GPX file.</p>
<p>Unlike the services/caches/geocaches method responses, GPX files cannot
contain names and descriptions in separate multiple languages. This method
will attempt to choose the best language based on your preference
(see <b>langpref</b> argument).</p>
<p>Remember that a full-blown GPX file is much larger than a basic one.
This method can produce many various types of GPX files. The simplest list of
waypoints takes ~50 times less space than the same list with cache descriptions
and log entries included.</p>
<p>Note, that GPX files may contain <a href='%OKAPI:docurl:html%'>unvalidated
HTML</a>.</p>
</desc>
<req name='cache_codes'>
<p>Pipe-separated list of cache codes which you are interested in.
No more than 500 codes are allowed. This CAN be an empty string (it will
result in an empty, but valid, GPX file). All invalid cache codes will
be skipped without any notice!</p>
</req>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for GPX entities.</p>
</opt>
<opt name='ns_ground' default='false'>
Boolean. If <b>true</b> then response will include
<a href='http://www.groundspeak.com/cache/1/0/1/cache.xsd'>Groundspeak's
GPX extension</a>. This namespace declares an extra &lt;cache&gt; element
used by <a href='http://www.geocaching.com/'>geocaching.com</a> and <b>many others</b>.
This namespace was initially associatied with geocaching.com, but now seems to
be used in every geocaching GPX file. You probably want to have it.
</opt>
<opt name='ns_gsak' default='false'>
<p>Boolean. If <b>true</b> then response will include
<a href='http://www.gsak.net/xmlv1/5/gsak.xsd'>GSAK GPX extension</a>.
This namespace declares an extra &lt;wptExtension&gt; element,
which allows including "waypoint inheritance" information (parent-child relations).
This in turn allows us to include "alternate waypoints" in the response.</p>
<p>We know of <b>at least</b> <a href='http://www.gsak.net/'>one tool</a> that
makes use of this.</p>
</opt>
<opt name='ns_ox' default='false'>
Boolean. If <b>true</b> then response will include Garmin's
<a href='http://www.opencaching.com/xmlschemas/opencaching/1/0/opencaching.xsd'>OpenCaching.com
GPX extension</a>. This namespace declares an extra &lt;opencaching&gt; element
used by <a href='http://www.opencaching.com/'>Garmin's Opencaching.com</a>.
The element includes information on cache difficulty, ratings, tags and images.
It is used within Garmin's Geocaching-enabled handheld GPS devices.
</opt>
<opt name='images' default='descrefs:nonspoilers'>
<p>Which images to include (and how to include them). One of the following values:</p>
<ul>
<li><b>none</b> - no images will be included,</li>
<li><b>descrefs:thumblinks</b> - all images will be included as "thumbnail"
&lt;img&gt; references at the end of each cache description, with a
replacement image used for spoilers; the thumbnails are linked to the
large images,</li>
<li><b>descrefs:nonspoilers</b> - all non-spoiler images will be included
as &lt;img&gt; references at the end of each cache description,</li>
<li><b>descrefs:all</b> - all images will be included (including spoilers)
as &lt;img&gt; references at the end of each cache description,</li>
<li><b>ox:all</b> - all images will be included (including spoilers)
as Garmin's &lt;ox:image&gt; references.</li>
</ul>
<p>Note: When using "descrefs:" mode, remember to set <b>ns_ground</b> to <b>true</b>.
The default value is "descrefs:nonspoilers" for compatibilty reasons, but for
most applications "descrefs:thumblinks" will be the better choice.</p>
<p>Note: When using "ox:" mode, remember to set <b>ns_ox</b> to <b>true</b>. You must also
include JPEG files along the GPX for this to work properly - see
services/caches/formatters/garmin for more information.</p>
<p>In the future, more generic ways of including images in GPX files may emerge.</p>
</opt>
<opt name='attrs' default='desc:text'>
<p>This argument controls whether cache attributes are included and how they are included.
Pipe-separated list consisting of any set of the following values:</p>
<brief>Retrieve geocaches in GPX format</brief>
<issue-id>40</issue-id>
<desc>
<p>Produces a standards-compliant Geocaching GPX file.</p>
<p>Unlike the services/caches/geocaches method responses, GPX files cannot
contain names and descriptions in separate multiple languages. This method
will attempt to choose the best language based on your preference
(see <b>langpref</b> argument).</p>
<p>Remember that a full-blown GPX file is much larger than a basic one.
This method can produce many various types of GPX files. The simplest list of
waypoints takes ~50 times less space than the same list with cache descriptions
and log entries included.</p>
<p>Note, that GPX files may contain <a href='%OKAPI:docurl:html%'>unvalidated
HTML</a>.</p>
</desc>
<req name='cache_codes'>
<p>Pipe-separated list of cache codes which you are interested in.
No more than 500 codes are allowed. This CAN be an empty string (it will
result in an empty, but valid, GPX file). All invalid cache codes will
be skipped without any notice!</p>
</req>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for GPX entities.</p>
</opt>
<opt name='ns_ground' default='false'>
Boolean. If <b>true</b> then response will include
<a href='http://www.groundspeak.com/cache/1/0/1/cache.xsd'>Groundspeak's
GPX extension</a>. This namespace declares an extra &lt;cache&gt; element
used by <a href='http://www.geocaching.com/'>geocaching.com</a> and <b>many others</b>.
This namespace was initially associatied with geocaching.com, but now seems to
be used in every geocaching GPX file. You probably want to have it.
</opt>
<opt name='ns_gsak' default='false'>
<p>Boolean. If <b>true</b> then response will include
<a href='http://www.gsak.net/xmlv1/5/gsak.xsd'>GSAK GPX extension</a>.
This namespace declares an extra &lt;wptExtension&gt; element,
which allows including "waypoint inheritance" information (parent-child relations).
This in turn allows us to include "alternate waypoints" in the response.</p>
<p>We know of <b>at least</b> <a href='http://www.gsak.net/'>one tool</a> that
makes use of this.</p>
</opt>
<opt name='ns_ox' default='false'>
Boolean. If <b>true</b> then response will include Garmin's
<a href='http://www.opencaching.com/xmlschemas/opencaching/1/0/opencaching.xsd'>OpenCaching.com
GPX extension</a>. This namespace declares an extra &lt;opencaching&gt; element
used by <a href='http://www.opencaching.com/'>Garmin's Opencaching.com</a>.
The element includes information on cache difficulty, ratings, tags and images.
It is used within Garmin's Geocaching-enabled handheld GPS devices.
</opt>
<opt name='images' default='descrefs:nonspoilers'>
<p>Which images to include (and how to include them). One of the following values:</p>
<ul>
<li><b>none</b> - no images will be included,</li>
<li><b>descrefs:thumblinks</b> - all images will be included as "thumbnail"
&lt;img&gt; references at the end of each cache description, with a
replacement image used for spoilers; the thumbnails are linked to the
large images,</li>
<li><b>descrefs:nonspoilers</b> - all non-spoiler images will be included
as &lt;img&gt; references at the end of each cache description,</li>
<li><b>descrefs:all</b> - all images will be included (including spoilers)
as &lt;img&gt; references at the end of each cache description,</li>
<li><b>ox:all</b> - all images will be included (including spoilers)
as Garmin's &lt;ox:image&gt; references.</li>
</ul>
<p>Note: When using "descrefs:" mode, remember to set <b>ns_ground</b> to <b>true</b>.
The default value is "descrefs:nonspoilers" for compatibilty reasons, but for
most applications "descrefs:thumblinks" will be the better choice.</p>
<p>Note: When using "ox:" mode, remember to set <b>ns_ox</b> to <b>true</b>. You must also
include JPEG files along the GPX for this to work properly - see
services/caches/formatters/garmin for more information.</p>
<p>In the future, more generic ways of including images in GPX files may emerge.</p>
</opt>
<opt name='attrs' default='desc:text'>
<p>This argument controls whether cache attributes are included and how they are included.
Pipe-separated list consisting of any set of the following values:</p>
<ul>
<li><b>desc:text</b> - attributes will be listed (in plain-text) within
the cache description (<b>ns_ground</b> has to be <b>true</b>),</li>
<li><b>ox:tags</b> - attributes will be listed as Garmin's <i>ox:tag</i>
elements (<b>ns_ox</b> has to be <b>true</b>),</li>
<li>
<p><b>gc:attrs</b> - attributes will be listed as Groundspeak's
<i>groundspeak:attribute</i> elements (<b>ns_ground</b> has to be <b>true</b>),
compatible with Geocaching.com (see the list
<a href='http://www.geocaching.com/about/icons.aspx'>here</a>).</p>
<p>Note that most Opencaching attributes don't have Geocaching.com counterparts.
Such attributes cannot be included as gc:attrs and will be ignored.</p>
</li>
<li>
<p><b>gc_ocde:attrs</b> - attributes will be listed as Groundspeak's
<i>groundspeak:attribute</i> elements (<b>ns_ground</b> has to be <b>true</b>).
Opencaching attributes which have no Geocaching.com counterparts will be
included according to an Opencaching.DE convention, using "makeshift IDs"
which may change in the future.</p>
<p>Note that this option is supported only by some OC sites. Other sites will
handle <b>gc_ocde:attrs</b> in the same way as <b>gc:attrs</b>.</p>
</li>
</ul>
<ul>
<li><b>desc:text</b> - attributes will be listed (in plain-text) within
the cache description (<b>ns_ground</b> has to be <b>true</b>),</li>
<li><b>ox:tags</b> - attributes will be listed as Garmin's <i>ox:tag</i>
elements (<b>ns_ox</b> has to be <b>true</b>),</li>
<li>
<p><b>gc:attrs</b> - attributes will be listed as Groundspeak's
<i>groundspeak:attribute</i> elements (<b>ns_ground</b> has to be <b>true</b>),
compatible with Geocaching.com (see the list
<a href='http://www.geocaching.com/about/icons.aspx'>here</a>).</p>
<p>Note that most Opencaching attributes don't have Geocaching.com counterparts.
Such attributes cannot be included as gc:attrs and will be ignored.</p>
</li>
<li>
<p><b>gc_ocde:attrs</b> - attributes will be listed as Groundspeak's
<i>groundspeak:attribute</i> elements (<b>ns_ground</b> has to be <b>true</b>).
Opencaching attributes which have no Geocaching.com counterparts will be
included according to an Opencaching.DE convention, using "makeshift IDs"
which may change in the future.</p>
<p>Note that this option is supported only by some OC sites. Other sites will
handle <b>gc_ocde:attrs</b> in the same way as <b>gc:attrs</b>.</p>
</li>
</ul>
<p>If you don't want any attributes to be included, you must set the <b>attrs</b>
parameter to <b>none</b>. Using an empty string won't work this way - it will
trigger the default (<b>desc:text</b>) to be selected, for backward-compatibility.</p>
</opt>
<opt name='protection_areas' default='desc:auto'>
<p>This argument controls whether protection area information is included and how
it is included.</p>
<ul>
<li><b>desc:text</b> - if the cache (probably) is located within one or more
protection areas, e.g. a nature reserve, a list of the protection areas will
be included in the cache description.</li>
<li><b>desc:auto</b> - protection area information may be included in the
cache description, depending on the installation.</li>
<li><b>none</b> - no protection area information will be included in the
GPX data.</li>
</ul>
<p>Note that information on protection areas may be incomplete or outdated
or completely missing on some installations.</p>
</opt>
<opt name='trackables' default='none'>
<p>This argument controls whether information on trackables is included and how it is included.
One of the following values:</p>
<ul>
<li><b>none</b> - no trackables-data will be included,</li>
<li><b>desc:list</b> - trackables will be listed (in plain-text) within the cache description,</li>
<li><b>desc:count</b> - total number of trackables will be included (in plain-text) within the cache description.</li>
</ul>
<p>Note: When using "desc:" mode, remember to set <b>ns_ground</b> to <b>true</b>.</p>
</opt>
<opt name='recommendations' default='none'>
<p>This argument controls whether information on recommendations is included and how it is included.
One of the following values:</p>
<ul>
<li><b>none</b> - no recommendations-info will be included,</li>
<li><b>desc:count</b> - number of recommendations (and founds) will be included
(in plain-text) within the cache description.</li>
</ul>
<p>Note: When using "desc:" mode, remember to set <b>ns_ground</b> to <b>true</b>.</p>
</opt>
<opt name='my_notes' default='none'>
<p>Allows you to include personal user's notes on each cache. This parameter
takes either <b>none</b> (default value), or pipe separeted list of the
following values:</p>
<p>If you don't want any attributes to be included, you must set the <b>attrs</b>
parameter to <b>none</b>. Using an empty string won't work this way - it will
trigger the default (<b>desc:text</b>) to be selected, for backward-compatibility.</p>
</opt>
<opt name='protection_areas' default='desc:auto'>
<p>This argument controls whether protection area information is included and how
it is included.</p>
<ul>
<li><b>desc:text</b> - if the cache (probably) is located within one or more
protection areas, e.g. a nature reserve, a list of the protection areas will
be included in the cache description.</li>
<li><b>desc:auto</b> - protection area information may be included in the
cache description, depending on the installation.</li>
<li><b>none</b> - no protection area information will be included in the
GPX data.</li>
</ul>
<p>Note that information on protection areas may be incomplete or outdated
or completely missing on some installations.</p>
</opt>
<opt name='trackables' default='none'>
<p>This argument controls whether information on trackables is included and how it is included.
One of the following values:</p>
<ul>
<li><b>none</b> - no trackables-data will be included,</li>
<li><b>desc:list</b> - trackables will be listed (in plain-text) within the cache description,</li>
<li><b>desc:count</b> - total number of trackables will be included (in plain-text) within the cache description.</li>
</ul>
<p>Note: When using "desc:" mode, remember to set <b>ns_ground</b> to <b>true</b>.</p>
</opt>
<opt name='recommendations' default='none'>
<p>This argument controls whether information on recommendations is included and how it is included.
One of the following values:</p>
<ul>
<li><b>none</b> - no recommendations-info will be included,</li>
<li><b>desc:count</b> - number of recommendations (and founds) will be included
(in plain-text) within the cache description.</li>
</ul>
<p>Note: When using "desc:" mode, remember to set <b>ns_ground</b> to <b>true</b>.</p>
</opt>
<opt name='my_notes' default='none'>
<p>Allows you to include personal user's notes on each cache. This parameter
takes either <b>none</b> (default value), or pipe separeted list of the
following values:</p>
<ul>
<li><b>desc:text</b> - include personal notes as part of the cache
description.</li>
<ul>
<li><b>desc:text</b> - include personal notes as part of the cache
description.</li>
<li>
<p><b>gc:personal_note</b> - include personal notes as
&lt;groundspeak:personal_note&gt; element (under
&lt;groundspeak:cache&gt; element).</p>
<li>
<p><b>gc:personal_note</b> - include personal notes as
&lt;groundspeak:personal_note&gt; element (under
&lt;groundspeak:cache&gt; element).</p>
<p><b>Warning:</b> This element is not a part of
<i>http://www.groundspeak.com/cache/1/0/1</i> groundspeak schema, but it
is a <i>http://www.groundspeak.com/cache/1/0/2</i> schema element. Using
this option will generate formally invalid XML file.</p>
</li>
</ul>
<p>Note: You need to use Level 3 Authentication in order to set it to anything else than "none".</p>
</opt>
<opt name='latest_logs' default='false'>
<p>Boolean. If <b>true</b> then response will include a couple of
latest log entries for this cache (see also the <b>lpc</b> argument).</p>
<p>You <b>must</b> set <b>ns_ground</b> argument to <b>true</b> if you want to use this.</p>
</opt>
<opt name='lpc' default='10'>
Log-entries per cache - the number of log entries included in each returned cache.
This should be an integer or a special "all" value. Please note, that the <b>ns_ground</b>
and <b>latest_logs</b> arguments must be set to <b>true</b> in order for the logs to be included.
</opt>
<opt name='alt_wpts' default='false'>
<p>Boolean. If <b>true</b> then we will include additional (alternate) waypoints in the response.
These are all places associated with the cache.</p>
<p>Combine this with <b>ns_gsak</b> if you want the result to include additional references between
waypoints and their geocaches. Please note that not many applications are currently able to
properly understand GSAK metadata.</p>
</opt>
<opt name='mark_found' default='false'>
<p>Boolean. If <b>true</b> then all caches which are already found, will be marked appropriately
(i.e. with an "found cache" symbol).</p>
<p>This field requires you to use the <b>user_uuid</b> parameter (or Level 3 Authentication).</p>
</opt>
<opt name="user_uuid">
<p>User'd ID. Required to mark found caches using <b>Level 1</b> Authentication.</p>
<p>If you use <b>Level 3</b> Authentication, you shouldn't use this parameter. Or, to be exact:</p>
<ul>
<li><b>user_uuid</b> will be extracted from the Access Token you use. You don't have to provide it.</li>
<li>If you provide <b>user_uuid</b>, then it HAS TO match your Access Token. If it doesn't, OKAPI
will respond with HTTP 400 error.</li>
</ul>
</opt>
<returns>
<p>GPX file. All invalid cache codes will be skipped without any notice!</p>
</returns>
<p><b>Warning:</b> This element is not a part of
<i>http://www.groundspeak.com/cache/1/0/1</i> groundspeak schema, but it
is a <i>http://www.groundspeak.com/cache/1/0/2</i> schema element. Using
this option will generate formally invalid XML file.</p>
</li>
</ul>
<p>Note: You need to use Level 3 Authentication in order to set it to anything else than "none".</p>
</opt>
<opt name='latest_logs' default='false'>
<p>Boolean. If <b>true</b> then response will include a couple of
latest log entries for this cache (see also the <b>lpc</b> argument).</p>
<p>You <b>must</b> set <b>ns_ground</b> argument to <b>true</b> if you want to use this.</p>
</opt>
<opt name='lpc' default='10'>
Log-entries per cache - the number of log entries included in each returned cache.
This should be an integer or a special "all" value. Please note, that the <b>ns_ground</b>
and <b>latest_logs</b> arguments must be set to <b>true</b> in order for the logs to be included.
</opt>
<opt name='alt_wpts' default='false'>
<p>Boolean. If <b>true</b> then we will include additional (alternate) waypoints in the response.
These are all places associated with the cache.</p>
<p>Combine this with <b>ns_gsak</b> if you want the result to include additional references between
waypoints and their geocaches. Please note that not many applications are currently able to
properly understand GSAK metadata.</p>
</opt>
<opt name='mark_found' default='false'>
<p>Boolean. If <b>true</b> then all caches which are already found, will be marked appropriately
(i.e. with an "found cache" symbol).</p>
<p>This field requires you to use the <b>user_uuid</b> parameter (or Level 3 Authentication).</p>
</opt>
<opt name="user_uuid">
<p>User'd ID. Required to mark found caches using <b>Level 1</b> Authentication.</p>
<p>If you use <b>Level 3</b> Authentication, you shouldn't use this parameter. Or, to be exact:</p>
<ul>
<li><b>user_uuid</b> will be extracted from the Access Token you use. You don't have to provide it.</li>
<li>If you provide <b>user_uuid</b>, then it HAS TO match your Access Token. If it doesn't, OKAPI
will respond with HTTP 400 error.</li>
</ul>
</opt>
<opt name="location_source" default='default-coords'>
<p>If you supply a value, then it must be prefixed with <i>alt_wpt:</i>,
and should match one of alternate waypoint types documented in the
<a href="%OKAPI:methodretref:services/caches/geocache#alt_wpts%">alt_wpts</a>
field returned by <a href="%OKAPI:methodref:services/caches/geocache%">services/caches/geocache</a>
method. If the type doesn't match, it will be ignored.</p>
<p>By default, the <b>lat</b> and <b>lon</b> attributes of the <b>&lt;wpt&gt;</b> element
will return the default coordinates of the cache, as supplied by the owner of the cache.
This option allows you to replace these coordinates with other set of coordinates, loaded
from the <b>alt_wpts</b> field mentioned above.</p>
<p>This may have some advantages in some scenarios. For example, if your user
is driving a car, he might be more interested in the coordinates of the
nearest suitable parking spot than in the coordinates of the geocache itself.
If you set <b>location_source</b> to <b>alt_wpt:parking</b> then you'll be
pre-fetching parking locations (if provided) without the need of loading all
other <b>alternate waypoints</b> in your requests.</p>
<p>Other use case: setting this to <b>alt_wpt:user-coords</b> allows
personalized results in some OC installations.</p>
<ul>
<li>If the type provided doesn't match any alternate waypoints, the default
coordinates of the geocache will be used.</li>
<li>
<p>If the type matches exactly one alternate waypoint, then:</p>
<ul>
<li>The <b>lat</b> and <b>lon</b> attributes of the "primary"
<b>&lt;wpt&gt;</b> element will be replaced with the location of
the matched alternate waypoint,</li>
<li>The name of the geocache will be prefixed with the value suppiled in
<b><a href="%OKAPI:methodargref:#location_change_prefix%">location_change_prefix</a></b>
parameter,</li>
<li>The desription of the waypoint will be included in the
header of the cache description,</li>
<li>The matched alternate waypoint will be removed from the list
of alternate waypoins (in order to avoid duplicate locations),</li>
<li>Extra alternate waypoint will be created, with the original
location of the geocache.</li>
</ul>
</li>
<li>If the type provided matches multiple alternate waypoints, then
the first one will be chosen.</li>
</ul>
</opt>
<opt name="location_change_prefix" default="#">
<p>Prefix to be added to the geocache name, in case its location has been changed due to
the <b><a href="%OKAPI:methodargref:#location_source%">location_source</a></b> parameter
matching any of the alternate waypoint.</p>
</opt>
<returns>
<p>GPX file. All invalid cache codes will be skipped without any notice!</p>
</returns>
</xml>

View File

@ -16,187 +16,192 @@ http://www.groundspeak.com/cache/1/0/1 http://www.groundspeak.com/cache/1/0/1/ca
http://geocaching.com.au/geocache/1 http://geocaching.com.au/geocache/1/geocache.xsd
http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
">
<name><?= $vars['installation']['site_name'] ?> Geocache Search Results</name>
<desc><?= $vars['installation']['site_name'] ?> Geocache Search Results, downloaded via OKAPI - <?= $vars['installation']['okapi_base_url'] . ($vars['alt_wpts'] && $vars['ns_gsak'] ? ' (HasChildren)' : '') ?></desc>
<author><?= $vars['installation']['site_name'] ?></author>
<url><?= $vars['installation']['site_url'] ?></url>
<urlname><?= $vars['installation']['site_name'] ?></urlname>
<time><?= date('c') ?></time>
<? foreach ($vars['caches'] as $c) { ?>
<? if ($c == null) continue; /* This happens when there is an invalid code in cache_codes */ ?>
<? list($lat, $lon) = explode("|", $c['location']); ?>
<wpt lat="<?= $lat ?>" lon="<?= $lon ?>">
<time><?= $c['date_created'] ?></time>
<name><?= $c['code'] ?></name>
<desc><?= Okapi::xmlescape($c['name']) ?> <?= _("hidden by") ?> <?= Okapi::xmlescape($c['owner']['username']) ?> :: <?= ucfirst($c['type']) ?> Cache (<?= $c['difficulty'] ?>/<?= $c['terrain'] ?><? if ($c['size'] !== null) { echo "/".$c['size']; } else { echo "/X"; } ?>/<?= $c['rating'] ?>)</desc>
<url><?= $c['url'] ?></url>
<urlname><?= Okapi::xmlescape($c['name']) ?></urlname>
<sym><?= ($vars['mark_found'] && $c['is_found']) ? "Geocache Found" : "Geocache" ?></sym>
<type>Geocache|<?= $vars['cache_GPX_types'][$c['type']] ?></type>
<? if ($vars['ns_ground']) { /* Does user want us to include Groundspeak's <cache> element? */ ?>
<groundspeak:cache archived="<?= ($c['status'] == 'Archived') ? "True" : "False" ?>" available="<?= ($c['status'] == 'Available') ? "True" : "False" ?>" id="<?= $c['internal_id'] ?>" xmlns:groundspeak="http://www.groundspeak.com/cache/1/0/1">
<groundspeak:name><?= Okapi::xmlescape($c['name']) ?></groundspeak:name>
<groundspeak:placed_by><?= Okapi::xmlescape($c['owner']['username']) ?></groundspeak:placed_by>
<groundspeak:owner id="<?= $vars['user_uuid_to_internal_id'][$c['owner']['uuid']] ?>"><?= Okapi::xmlescape($c['owner']['username']) ?></groundspeak:owner>
<groundspeak:type><?= $vars['cache_GPX_types'][$c['type']] ?></groundspeak:type>
<groundspeak:container><?= $vars['cache_GPX_sizes'][$c['size2']] ?></groundspeak:container>
<? if ($vars['gc_attrs'] || $vars['gc_ocde_attrs']) { /* Does user want us to include groundspeak:attributes? */ ?>
<groundspeak:attributes>
<?
foreach ($c['gc_attrs'] as $gc_id => $gc_attr) {
print "<groundspeak:attribute id=\"".$gc_id."\" ";
print "inc=\"".$gc_attr['inc']."\">";
print Okapi::xmlescape($gc_attr['name']);
print "</groundspeak:attribute>";
}
?>
</groundspeak:attributes>
<? } ?>
<groundspeak:difficulty><?= $c['difficulty'] ?></groundspeak:difficulty>
<groundspeak:terrain><?= $c['terrain'] ?></groundspeak:terrain>
<groundspeak:long_description html="True">
&lt;p&gt;
&lt;a href="<?= $c['url'] ?>"&gt;<?= Okapi::xmlescape($c['name']) ?>&lt;/a&gt;
<?= _("hidden by") ?> &lt;a href='<?= $c['owner']['profile_url'] ?>'&gt;<?= Okapi::xmlescape($c['owner']['username']) ?>&lt;/a&gt;&lt;br/&gt;
<? if ($vars['recommendations'] == 'desc:count') { /* Does user want us to include recommendations count? */ ?>
<?= sprintf(ngettext("%d recommendation", "%d recommendations", $c['recommendations']), $c['recommendations']) ?>
(<?= sprintf(ngettext("found %d time", "found %d times", $c['founds']), $c['founds']) ?>).
<? } ?>
<? if ($vars['trackables'] == 'desc:count') { /* Does user want us to include trackables count? */ ?>
<?= sprintf(ngettext("%d trackable", "%d trackables", $c['trackables_count']), $c['trackables_count']) ?>.
<? } ?>
&lt;/p&gt;
<? if ((in_array('desc:text', $vars['my_notes'])) && ($c['my_notes'] != null)) { /* Does user want us to include personal notes? */ ?>
&lt;p&gt;&lt;b&gt;<?= _("Personal notes") ?>:&lt;/b&gt;&lt;br&gt;<?= Okapi::xmlescape(nl2br($c['my_notes'])) ?>&lt;/p&gt;
<? } ?>
<name><?= $vars['installation']['site_name'] ?> Geocache Search Results</name>
<desc><?= $vars['installation']['site_name'] ?> Geocache Search Results, downloaded via OKAPI - <?= $vars['installation']['okapi_base_url'] . ($vars['alt_wpts'] && $vars['ns_gsak'] ? ' (HasChildren)' : '') ?></desc>
<author><?= $vars['installation']['site_name'] ?></author>
<url><?= $vars['installation']['site_url'] ?></url>
<urlname><?= $vars['installation']['site_name'] ?></urlname>
<time><?= date('c') ?></time>
<? foreach ($vars['caches'] as $c) { ?>
<? list($lat, $lon) = explode("|", $c['location']); ?>
<wpt lat="<?= $lat ?>" lon="<?= $lon ?>">
<time><?= $c['date_created'] ?></time>
<name><?= $c['code'] ?></name>
<desc><?= Okapi::xmlescape(isset($c['name_2']) ? $c['name_2'] : $c['name']) ?> <?= _("hidden by") ?> <?= Okapi::xmlescape($c['owner']['username']) ?> :: <?= ucfirst($c['type']) ?> Cache (<?= $c['difficulty'] ?>/<?= $c['terrain'] ?><? if ($c['size'] !== null) { echo "/".$c['size']; } else { echo "/X"; } ?>/<?= $c['rating'] ?>)</desc>
<url><?= $c['url'] ?></url>
<urlname><?= Okapi::xmlescape($c['name']) ?></urlname>
<sym><?= ($vars['mark_found'] && $c['is_found']) ? "Geocache Found" : "Geocache" ?></sym>
<type>Geocache|<?= $vars['cache_GPX_types'][$c['type']] ?></type>
<? if ($vars['ns_ground']) { /* Does user want us to include Groundspeak's <cache> element? */ ?>
<groundspeak:cache archived="<?= ($c['status'] == 'Archived') ? "True" : "False" ?>" available="<?= ($c['status'] == 'Available') ? "True" : "False" ?>" id="<?= $c['internal_id'] ?>" xmlns:groundspeak="http://www.groundspeak.com/cache/1/0/1">
<groundspeak:name><?= Okapi::xmlescape(isset($c['name_2']) ? $c['name_2'] : $c['name']) ?></groundspeak:name>
<groundspeak:placed_by><?= Okapi::xmlescape($c['owner']['username']) ?></groundspeak:placed_by>
<groundspeak:owner id="<?= $vars['user_uuid_to_internal_id'][$c['owner']['uuid']] ?>"><?= Okapi::xmlescape($c['owner']['username']) ?></groundspeak:owner>
<groundspeak:type><?= $vars['cache_GPX_types'][$c['type']] ?></groundspeak:type>
<groundspeak:container><?= $vars['cache_GPX_sizes'][$c['size2']] ?></groundspeak:container>
<? if ($vars['gc_attrs'] || $vars['gc_ocde_attrs']) { /* Does user want us to include groundspeak:attributes? */ ?>
<groundspeak:attributes>
<?
foreach ($c['gc_attrs'] as $gc_id => $gc_attr) {
print "<groundspeak:attribute id=\"".$gc_id."\" ";
print "inc=\"".$gc_attr['inc']."\">";
print Okapi::xmlescape($gc_attr['name']);
print "</groundspeak:attribute>";
}
?>
</groundspeak:attributes>
<? } ?>
<groundspeak:difficulty><?= $c['difficulty'] ?></groundspeak:difficulty>
<groundspeak:terrain><?= $c['terrain'] ?></groundspeak:terrain>
<? if ($c['short_description']) { ?>
<groundspeak:short_description html="False"><?= Okapi::xmlescape($c['short_description']) ?></groundspeak:short_description>
<? } ?>
<groundspeak:long_description html="True">
<? if (isset($c['warning_prefix'])) { ?>
&lt;p style='font-size: 120%'&gt;<?= Okapi::xmlescape($c['warning_prefix']) ?>&lt;/p&gt;
<? } ?>
&lt;p&gt;
&lt;a href="<?= $c['url'] ?>"&gt;<?= Okapi::xmlescape($c['name']) ?>&lt;/a&gt;
<?= _("hidden by") ?> &lt;a href='<?= $c['owner']['profile_url'] ?>'&gt;<?= Okapi::xmlescape($c['owner']['username']) ?>&lt;/a&gt;&lt;br/&gt;
<? if ($vars['recommendations'] == 'desc:count') { /* Does user want us to include recommendations count? */ ?>
<?= sprintf(ngettext("%d recommendation", "%d recommendations", $c['recommendations']), $c['recommendations']) ?>
(<?= sprintf(ngettext("found %d time", "found %d times", $c['founds']), $c['founds']) ?>).
<? } ?>
<? if ($vars['trackables'] == 'desc:count') { /* Does user want us to include trackables count? */ ?>
<?= sprintf(ngettext("%d trackable", "%d trackables", $c['trackables_count']), $c['trackables_count']) ?>.
<? } ?>
&lt;/p&gt;
<? if ((in_array('desc:text', $vars['my_notes'])) && ($c['my_notes'] != null)) { /* Does user want us to include personal notes? */ ?>
&lt;p&gt;&lt;b&gt;<?= _("Personal notes") ?>:&lt;/b&gt;&lt;br&gt;<?= Okapi::xmlescape(nl2br($c['my_notes'])) ?>&lt;/p&gt;
<? } ?>
<? if (in_array('desc:text', $vars['attrs']) && count($c['attrnames']) > 0) { /* Does user want us to include attributes? */ ?>
&lt;p&gt;<?= _("Attributes") ?>:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;<?= implode("&lt;/li&gt;&lt;li&gt;", $c['attrnames']) ?>&lt;/li&gt;&lt;/ul&gt;
<? } ?>
<? if ($vars['trackables'] == 'desc:list' && count($c['trackables']) > 0) { /* Does user want us to include trackables list? */ ?>
&lt;p&gt;<?= _("Trackables") ?>:&lt;/p&gt;
&lt;ul&gt;
<? foreach ($c['trackables'] as $t) { ?>
&lt;li&gt;&lt;a href='<?= Okapi::xmlescape($t['url']) ?>'&gt;<?= Okapi::xmlescape($t['name']) ?>&lt;/a&gt; (<?= $t['code'] ?>)&lt;/li&gt;
<? } ?>
&lt;/ul&gt;
<? } ?>
<?= Okapi::xmlescape($c['description']) ?>
<? if ((strpos($vars['images'], "descrefs:") === 0) && count($c['images']) > 0) { /* Does user want us to include <img> references in cache descriptions? */
if ($vars['images'] == "descrefs:thumblinks") { ?>
&lt;h2&gt;<?= _("Images") ?> (<?= count($c['images']) ?>)&lt;/h2&gt;
&lt;div&gt;
<? foreach ($c['images'] as $img) { ?>
&lt;div style='float:left; padding:6px'&gt;&lt;a href='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;img src='<?= Okapi::xmlescape($img['thumb_url']) ?>'&gt;&lt;/a&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/div&gt;
<? } ?>
&lt;/div&gt;
<? } else {
# We will split images into two subcategories: spoilers and nonspoilers.
$spoilers = array();
$nonspoilers = array();
foreach ($c['images'] as $img)
if ($img['is_spoiler']) $spoilers[] = $img;
else $nonspoilers[] = $img;
?>
<? if (count($nonspoilers) > 0) { ?>
&lt;h2&gt;<?= _("Images") ?> (<?= count($nonspoilers) ?>)&lt;/h2&gt;
<? foreach ($nonspoilers as $img) { ?>
&lt;p&gt;&lt;img src='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/p&gt;
<? } ?>
<? } ?>
<? if (count($spoilers) > 0 && $vars['images'] == 'descrefs:all') { ?>
&lt;h2&gt;<?= _("Spoilers") ?> (<?= count($spoilers) ?>)&lt;/h2&gt;
<? foreach ($spoilers as $img) { ?>
&lt;p&gt;&lt;img src='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/p&gt;
<? } ?>
<? } ?>
<? } ?>
<? } ?>
<? if ((strpos($vars['images'], "ox:") === 0) && count($c['images']) > 0) { /* Include image descriptions (for ox:image numbers)? */ ?>
&lt;p&gt;<?= _("Image descriptions") ?>:&lt;/p&gt;
&lt;ul&gt;
<? foreach ($c['images'] as $no => $img) { ?>
&lt;li&gt;<?= $img['unique_caption'] ?>. <?= Okapi::xmlescape($img['caption']) ?>&lt;/li&gt;
<? } ?>
&lt;/ul&gt;
<? } ?>
<? if ($vars['protection_areas'] == 'desc:text' && count($c['protection_areas'])) { ?>
&lt;p&gt;<?= _("The cache probably is located in the following protection areas:") ?>&lt;/p&gt;
&lt;ul&gt;
<? foreach($c['protection_areas'] as $protection_area) { ?>
&lt;li&gt;<?= Okapi::xmlescape($protection_area['type'])." - ".Okapi::xmlescape($protection_area['name']) ?>&lt;/li&gt;
<? } ?>
&lt;/ul;&gt;
<? } ?>
</groundspeak:long_description>
<groundspeak:encoded_hints><?= Okapi::xmlescape($c['hint2']) ?></groundspeak:encoded_hints>
<? if ((in_array('gc:personal_note', $vars['my_notes'])) && ($c['my_notes'] != null)) { /* Does user want us to include personal notes? -> Issue 294 */ ?>
<groundspeak:personal_note><?= Okapi::xmlescape($c['my_notes']) ?></groundspeak:personal_note>
<? } ?>
<? if ($vars['latest_logs']) { /* Does user want us to include latest log entries? */ ?>
<groundspeak:logs>
<? foreach ($c['latest_logs'] as $log) { ?>
<groundspeak:log id="<?= $log['internal_id'] ?>">
<groundspeak:date><?= $log['date'] ?></groundspeak:date>
<groundspeak:type><?= $log['type'] ?></groundspeak:type>
<groundspeak:finder id="<?= $vars['user_uuid_to_internal_id'][$log['user']['uuid']] ?>"><?= Okapi::xmlescape($log['user']['username']) ?></groundspeak:finder>
<groundspeak:text encoded="False"><?= $log['was_recommended'] ? "(*) ": "" ?><?= Okapi::xmlescape($log['comment']) ?></groundspeak:text>
</groundspeak:log>
<? } ?>
</groundspeak:logs>
<? } ?>
<? /* groundspeak:travelbugs - does it actually DO anything? WRTODO */ ?>
</groundspeak:cache>
<? } ?>
<? if ($vars['ns_ox']) { /* Does user want us to include Garmin's <opencaching> element? */ ?>
<ox:opencaching xmlns:ox="http://www.opencaching.com/xmlschemas/opencaching/1/0">
<ox:ratings>
<? if ($c['rating'] !== null) { ?><ox:awesomeness><?= $c['rating'] ?></ox:awesomeness><? } ?>
<ox:difficulty><?= $c['difficulty'] ?></ox:difficulty>
<? if ($c['oxsize'] !== null) { ?><ox:size><?= $c['oxsize'] ?></ox:size><? } ?>
<ox:terrain><?= $c['terrain'] ?></ox:terrain>
</ox:ratings>
<? if (in_array('ox:tags', $vars['attrs']) && count($c['attrnames']) > 0) { /* Does user want us to include ox:tags? */ ?>
<ox:tags><ox:tag><?= implode("</ox:tag><ox:tag>", $c['attrnames']) ?></ox:tag></ox:tags>
<? } ?>
<? if ((strpos($vars['images'], "ox:") === 0) && count($c['images']) > 0) { /* Does user want us to include ox:image references? */ ?>
<ox:images>
<? foreach ($c['images'] as $no => $img) { ?>
<ox:image>
<ox:name><?= $img['unique_caption'] ?>.jpg</ox:name>
<ox:size>0</ox:size>
<ox:required>false</ox:required>
<ox:spoiler><?= ($img['is_spoiler'] ? "true" : "false") ?></ox:spoiler>
</ox:image>
<? } ?>
</ox:images>
<? } ?>
</ox:opencaching>
<? } ?>
</wpt>
<? if ($vars['alt_wpts']) { ?>
<? foreach ($c['alt_wpts'] as $wpt) { ?>
<? list($lat, $lon) = explode("|", $wpt['location']); ?>
<wpt lat="<?= $lat ?>" lon="<?= $lon ?>">
<time><?= $c['date_created'] ?></time>
<name><?= Okapi::xmlescape($wpt['name']) ?></name>
<cmt><?= Okapi::xmlescape($wpt['description']) ?></cmt>
<desc><?= Okapi::xmlescape($wpt['type_name']) ?></desc>
<url><?= $c['url'] ?></url>
<urlname><?= Okapi::xmlescape($c['name']) ?></urlname>
<sym><?= $wpt['sym'] ?></sym>
<type>Waypoint|<?= $wpt['sym'] ?></type>
<? if ($vars['ns_gsak']) { ?>
<gsak:wptExtension xmlns:gsak="http://www.gsak.net/xmlv1/5">
<gsak:Parent><?= $c['code'] ?></gsak:Parent>
</gsak:wptExtension>
<? } ?>
</wpt>
<? } ?>
<? } ?>
<? } ?>
<? if (in_array('desc:text', $vars['attrs']) && count($c['attrnames']) > 0) { /* Does user want us to include attributes? */ ?>
&lt;p&gt;<?= _("Attributes") ?>:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;<?= implode("&lt;/li&gt;&lt;li&gt;", $c['attrnames']) ?>&lt;/li&gt;&lt;/ul&gt;
<? } ?>
<? if ($vars['trackables'] == 'desc:list' && count($c['trackables']) > 0) { /* Does user want us to include trackables list? */ ?>
&lt;p&gt;<?= _("Trackables") ?>:&lt;/p&gt;
&lt;ul&gt;
<? foreach ($c['trackables'] as $t) { ?>
&lt;li&gt;&lt;a href='<?= Okapi::xmlescape($t['url']) ?>'&gt;<?= Okapi::xmlescape($t['name']) ?>&lt;/a&gt; (<?= $t['code'] ?>)&lt;/li&gt;
<? } ?>
&lt;/ul&gt;
<? } ?>
<?= Okapi::xmlescape($c['description']) ?>
<? if ((strpos($vars['images'], "descrefs:") === 0) && count($c['images']) > 0) { /* Does user want us to include <img> references in cache descriptions? */
if ($vars['images'] == "descrefs:thumblinks") { ?>
&lt;h2&gt;<?= _("Images") ?> (<?= count($c['images']) ?>)&lt;/h2&gt;
&lt;div&gt;
<? foreach ($c['images'] as $img) { ?>
&lt;div style='float:left; padding:6px'&gt;&lt;a href='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;img src='<?= Okapi::xmlescape($img['thumb_url']) ?>'&gt;&lt;/a&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/div&gt;
<? } ?>
&lt;/div&gt;
<? } else {
# We will split images into two subcategories: spoilers and nonspoilers.
$spoilers = array();
$nonspoilers = array();
foreach ($c['images'] as $img)
if ($img['is_spoiler']) $spoilers[] = $img;
else $nonspoilers[] = $img;
?>
<? if (count($nonspoilers) > 0) { ?>
&lt;h2&gt;<?= _("Images") ?> (<?= count($nonspoilers) ?>)&lt;/h2&gt;
<? foreach ($nonspoilers as $img) { ?>
&lt;p&gt;&lt;img src='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/p&gt;
<? } ?>
<? } ?>
<? if (count($spoilers) > 0 && $vars['images'] == 'descrefs:all') { ?>
&lt;h2&gt;<?= _("Spoilers") ?> (<?= count($spoilers) ?>)&lt;/h2&gt;
<? foreach ($spoilers as $img) { ?>
&lt;p&gt;&lt;img src='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/p&gt;
<? } ?>
<? } ?>
<? } ?>
<? } ?>
<? if ((strpos($vars['images'], "ox:") === 0) && count($c['images']) > 0) { /* Include image descriptions (for ox:image numbers)? */ ?>
&lt;p&gt;<?= _("Image descriptions") ?>:&lt;/p&gt;
&lt;ul&gt;
<? foreach ($c['images'] as $no => $img) { ?>
&lt;li&gt;<?= $img['unique_caption'] ?>. <?= Okapi::xmlescape($img['caption']) ?>&lt;/li&gt;
<? } ?>
&lt;/ul&gt;
<? } ?>
<? if ($vars['protection_areas'] == 'desc:text' && count($c['protection_areas'])) { ?>
&lt;p&gt;<?= _("The cache probably is located in the following protection areas:") ?>&lt;/p&gt;
&lt;ul&gt;
<? foreach($c['protection_areas'] as $protection_area) { ?>
&lt;li&gt;<?= Okapi::xmlescape($protection_area['type'])." - ".Okapi::xmlescape($protection_area['name']) ?>&lt;/li&gt;
<? } ?>
&lt;/ul;&gt;
<? } ?>
</groundspeak:long_description>
<groundspeak:encoded_hints><?= Okapi::xmlescape($c['hint2']) ?></groundspeak:encoded_hints>
<? if ((in_array('gc:personal_note', $vars['my_notes'])) && ($c['my_notes'] != null)) { /* Does user want us to include personal notes? -> Issue 294 */ ?>
<groundspeak:personal_note><?= Okapi::xmlescape($c['my_notes']) ?></groundspeak:personal_note>
<? } ?>
<? if ($vars['latest_logs']) { /* Does user want us to include latest log entries? */ ?>
<groundspeak:logs>
<? foreach ($c['latest_logs'] as $log) { ?>
<groundspeak:log id="<?= $log['internal_id'] ?>">
<groundspeak:date><?= $log['date'] ?></groundspeak:date>
<groundspeak:type><?= $log['type'] ?></groundspeak:type>
<groundspeak:finder id="<?= $vars['user_uuid_to_internal_id'][$log['user']['uuid']] ?>"><?= Okapi::xmlescape($log['user']['username']) ?></groundspeak:finder>
<groundspeak:text encoded="False"><?= $log['was_recommended'] ? "(*) ": "" ?><?= Okapi::xmlescape($log['comment']) ?></groundspeak:text>
</groundspeak:log>
<? } ?>
</groundspeak:logs>
<? } ?>
<? /* groundspeak:travelbugs - does it actually DO anything? WRTODO */ ?>
</groundspeak:cache>
<? } ?>
<? if ($vars['ns_ox']) { /* Does user want us to include Garmin's <opencaching> element? */ ?>
<ox:opencaching xmlns:ox="http://www.opencaching.com/xmlschemas/opencaching/1/0">
<ox:ratings>
<? if ($c['rating'] !== null) { ?><ox:awesomeness><?= $c['rating'] ?></ox:awesomeness><? } ?>
<ox:difficulty><?= $c['difficulty'] ?></ox:difficulty>
<? if ($c['oxsize'] !== null) { ?><ox:size><?= $c['oxsize'] ?></ox:size><? } ?>
<ox:terrain><?= $c['terrain'] ?></ox:terrain>
</ox:ratings>
<? if (in_array('ox:tags', $vars['attrs']) && count($c['attrnames']) > 0) { /* Does user want us to include ox:tags? */ ?>
<ox:tags><ox:tag><?= implode("</ox:tag><ox:tag>", $c['attrnames']) ?></ox:tag></ox:tags>
<? } ?>
<? if ((strpos($vars['images'], "ox:") === 0) && count($c['images']) > 0) { /* Does user want us to include ox:image references? */ ?>
<ox:images>
<? foreach ($c['images'] as $no => $img) { ?>
<ox:image>
<ox:name><?= $img['unique_caption'] ?>.jpg</ox:name>
<ox:size>0</ox:size>
<ox:required>false</ox:required>
<ox:spoiler><?= ($img['is_spoiler'] ? "true" : "false") ?></ox:spoiler>
</ox:image>
<? } ?>
</ox:images>
<? } ?>
</ox:opencaching>
<? } ?>
</wpt>
<? if ($vars['alt_wpts']) { ?>
<? foreach ($c['alt_wpts'] as $wpt) { ?>
<? list($lat, $lon) = explode("|", $wpt['location']); ?>
<wpt lat="<?= $lat ?>" lon="<?= $lon ?>">
<time><?= $c['date_created'] ?></time>
<name><?= Okapi::xmlescape($wpt['name']) ?></name>
<cmt><?= Okapi::xmlescape($wpt['description']) ?></cmt>
<desc><?= Okapi::xmlescape($wpt['type_name']) ?></desc>
<url><?= $c['url'] ?></url>
<urlname><?= Okapi::xmlescape($c['name']) ?></urlname>
<sym><?= $wpt['sym'] ?></sym>
<type>Waypoint|<?= $wpt['sym'] ?></type>
<? if ($vars['ns_gsak']) { ?>
<gsak:wptExtension xmlns:gsak="http://www.gsak.net/xmlv1/5">
<gsak:Parent><?= $c['code'] ?></gsak:Parent>
</gsak:wptExtension>
<? } ?>
</wpt>
<? } ?>
<? } ?>
<? } ?>
</gpx>

View File

@ -12,51 +12,51 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
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";
$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;
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";
$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).
# There's no need to validate the fields/lpc parameters as the 'geocaches'
# method does this (it will raise a proper exception on invalid values).
$results = OkapiServiceRunner::call('services/caches/geocaches', new OkapiInternalRequest(
$request->consumer, $request->token, $params));
$result = $results[$cache_code];
if ($result === null)
throw new InvalidParam('cache_code', "This cache does not exist.");
return Okapi::formatted_response($request, $result);
}
$results = OkapiServiceRunner::call('services/caches/geocaches', new OkapiInternalRequest(
$request->consumer, $request->token, $params));
$result = $results[$cache_code];
if ($result === null)
throw new InvalidParam('cache_code', "This cache does not exist.");
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,413 +1,440 @@
<xml>
<brief>Retrieve information on a single geocache</brief>
<issue-id>19</issue-id>
<desc>
<p>Retrieve information on a single geocache.</p>
</desc>
<req name='cache_code'>Unique code of the geocache</req>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for fields like
<b>name</b> and <b>description</b>.</p>
<p>Please note, that you may also access caches' descriptions in all
available languages. If you want to do this, you should use fields
<b>names</b>, <b>descriptions</b> (etc.) instead of <b>name</b> and
<b>description</b> (etc.).</p>
</opt>
<opt name='fields' default='code|name|location|type|status'>
<p>Pipe-separated list of field names which you are interested with.
Selected fields will be included in the response. See below for the
list of available fields.</p>
</opt>
<opt name='attribution_append' default='full'>
<p>By default, OKAPI appends the value of <b>attribution_note</b> field to all the
cache descriptions. If you would like to display the attribution note separately,
you may use this parameter. Use one of the following values:</p>
<ul>
<li>
<b>full</b> - OKAPI will append the attribution note to the descriptions of the
geocache. The language of the note will match the language of the description
(i.e. each value in <b>descriptions</b> may contain attribution notes in
a different language).
</li>
<li>
<p><b>none</b> - OKAPI will not append the attribution note to geocache
descriptions. You will use the <b>attribution_note</b> instead (which depends
on the <b>langpref</b> parameter).</p>
<brief>Retrieve information on a single geocache</brief>
<issue-id>19</issue-id>
<desc>
<p>Retrieve information on a single geocache.</p>
</desc>
<req name='cache_code'>Unique code of the geocache</req>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for fields like
<b>name</b> and <b>description</b>.</p>
<p>Please note, that you may also access caches' descriptions in all
available languages. If you want to do this, you should use fields
<b>names</b>, <b>descriptions</b> (etc.) instead of <b>name</b> and
<b>description</b> (etc.).</p>
</opt>
<opt name='fields' default='code|name|location|type|status'>
<p>Pipe-separated list of field names which you are interested with.
Selected fields will be included in the response. See below for the
list of available fields.</p>
</opt>
<opt name='attribution_append' default='full'>
<p>By default, OKAPI appends the value of <b>attribution_note</b> field to all the
cache descriptions. If you would like to display the attribution note separately,
you may use this parameter. Use one of the following values:</p>
<ul>
<li>
<b>full</b> - OKAPI will append the attribution note to the descriptions of the
geocache. The language of the note will match the language of the description
(i.e. each value in <b>descriptions</b> may contain attribution notes in
a different language).
</li>
<li>
<p><b>none</b> - OKAPI will not append the attribution note to geocache
descriptions. You will use the <b>attribution_note</b> instead (which depends
on the <b>langpref</b> parameter).</p>
<p><b>Important note:</b> You are still <b>required</b> to display the
<b>attribution_note</b> field in some other way.</p>
</li>
<li>
<b>static</b> - OKAPI will append a "static" attribution note to the descriptions
of the geocache. This might equal the "full" attribution note, but not necessarilly,
since the "full" note may contain a current date
(<a href='https://code.google.com/p/opencaching-api/issues/detail?id=178'>why?</a>),
and the "static" note will not. This is implemented primarily for internal use
(i.e. the replicate module). Usually you should not use it, because on some
installations the static note will not be sufficient to meet the data license
requirements.
</li>
</ul>
</opt>
<opt name='lpc' default='10'>
Log-entries per cache - the number of logs returned in the <b>latest_logs</b> field.
This should be an integer or a special "all" value. Please note, that you must include
the <b>latest_logs</b> field in <b>fields</b> in order to see the log entries.
</opt>
<opt name='log_fields' default='uuid|date|user|type|comment'>
Pipe-separated list of log fields to include in the <b>latest_logs</b> field.
For valid field names, see <b>logs/entry</b> method.
</opt>
<opt name='my_location'>
<p>The reference point for cache distance and bearing calculation (typically - the user's location),
in the "lat|lon" format. This parameter is required when accessing <b>distance</b> and/or <b>bearing</b>
fields.</p>
<p>Use positive numbers for latitudes in the northern hemisphere and
longitudes in the eastern hemisphere (and negative for southern and
western hemispheres accordingly). These are full degrees with a dot
as a decimal point (ex. "54.3|22.3").</p>
</opt>
<opt name="user_uuid">
<p>User'd ID. Required to access fields like <b>is_found</b> using <b>Level 1</b> Authentication.</p>
<p>Please note that if you want to access private fields (like <b>my_notes</b>), then
this parameter won't help you. You have to use <b>Level 3</b> Authentication instead.</p>
<p>If you use <b>Level 3</b> Authentication, you shouldn't use this parameter. Or, to be exact:</p>
<ul>
<li><b>user_uuid</b> will be extracted from the Access Token you use. You don't have to provide it.</li>
<li>If you provide <b>user_uuid</b>, then it HAS TO match your Access Token. If it doesn't, OKAPI
will respond with HTTP 400 error.</li>
</ul>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected. Currently available fields:</p>
<p><b>Important note:</b> You are still <b>required</b> to display the
<b>attribution_note</b> field in some other way.</p>
</li>
<li>
<b>static</b> - OKAPI will append a "static" attribution note to the descriptions
of the geocache. This might equal the "full" attribution note, but not necessarilly,
since the "full" note may contain a current date
(<a href='https://code.google.com/p/opencaching-api/issues/detail?id=178'>why?</a>),
and the "static" note will not. This is implemented primarily for internal use
(i.e. the replicate module). Usually you should not use it, because on some
installations the static note will not be sufficient to meet the data license
requirements.
</li>
</ul>
</opt>
<opt name='lpc' default='10'>
Log-entries per cache - the number of logs returned in the <b>latest_logs</b> field.
This should be an integer or a special "all" value. Please note, that you must include
the <b>latest_logs</b> field in <b>fields</b> in order to see the log entries.
</opt>
<opt name='log_fields' default='uuid|date|user|type|comment'>
Pipe-separated list of log fields to include in the <b>latest_logs</b> field.
For valid field names, see <b>logs/entry</b> method.
</opt>
<opt name='my_location'>
<p>The reference point for cache distance and bearing calculation (typically - the user's location),
in the "lat|lon" format. This parameter is required when accessing <b>distance</b> and/or <b>bearing</b>
fields.</p>
<p>Use positive numbers for latitudes in the northern hemisphere and
longitudes in the eastern hemisphere (and negative for southern and
western hemispheres accordingly). These are full degrees with a dot
as a decimal point (ex. "54.3|22.3").</p>
</opt>
<opt name="user_uuid">
<p>User'd ID. Required to access fields like <b>is_found</b> using <b>Level 1</b> Authentication.</p>
<p>Please note that if you want to access private fields (like <b>my_notes</b>), then
this parameter won't help you. You have to use <b>Level 3</b> Authentication instead.</p>
<p>If you use <b>Level 3</b> Authentication, you shouldn't use this parameter. Or, to be exact:</p>
<ul>
<li><b>user_uuid</b> will be extracted from the Access Token you use. You don't have to provide it.</li>
<li>If you provide <b>user_uuid</b>, then it HAS TO match your Access Token. If it doesn't, OKAPI
will respond with HTTP 400 error.</li>
</ul>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected. Currently available fields:</p>
<ul>
<li><b>code</b> - unique Opencaching code of the geocache,</li>
<li><b>name</b> - name of the geocache,</li>
<li><b>names</b> - a dictionary (language code => plain-text string) of
names of the geocache (at this time, there will be only one language,
but this may change in future),</li>
<li><b>location</b> - location of the cache in the "lat|lon" format
(<i>lat</i> and <i>lon</i> are in full degrees with a dot as a decimal point),</li>
<li>
<p><b>type</b> - cache type. This might be <b>pretty much everything</b>,
but there are some predefined types that you might want to treat
in a special way (separate icons etc.). Primary types include: </p>
<ul>
<li><b>Traditional</b>,</li>
<li><b>Multi</b>,</li>
<li><b>Quiz</b>,</li>
<li><b>Virtual</b>,</li>
<li><b>Event</b>,</li>
<li><i>(any other value is valid too)</i></li>
</ul>
<p><b>Event</b> is a peculiar type of geocache which is NOT a geocache
at all, but it <b>is</b> stored as a geocache in OC database (this design
decision is old as the OC network itself!). Just keep in mind, that
in case of Event Caches, some fields may have a little different meaning
than you would tell by their name.</p>
</li>
<li>
<p><b>status</b> - cache status. Valid cache status codes currently include:</p>
<ul>
<li><b>Available</b> - Cache is available and ready for search,</li>
<li><b>Temporarily unavailable</b> - Cache is probably unavailable (i.e. in need of maintenance)</li>
<li><b>Archived</b> - Cache is permanently unavailable (moved to the archives).</li>
</ul>
</li>
<li><b>url</b> - URL of the cache's web page,</li>
<li>
<p><b>owner</b> - dictionary of:</p>
<ul>
<li><b>uuid</b> - geocache owner's user ID,</li>
<li><b>username</b> - name of the user,</li>
<li><b>profile_url</b> - URL of the user profile page,</li>
</ul>
</li>
<li>
<p><b>gc_code</b> - Geocaching.com code (GC code) of the geocache
<b>or null</b> if the cache is not listed on GC or the GC code is
unknown.</p>
<ul>
<li><b>code</b> - unique Opencaching code of the geocache,</li>
<li><b>name</b> - name of the geocache,</li>
<li><b>names</b> - a dictionary (language code => plain-text string) of
names of the geocache (at this time, there will be only one language,
but this may change in future),</li>
<li>
<b>location</b> - location of the cache in the "lat|lon" format
(<i>lat</i> and <i>lon</i> are in full degrees with a dot as a decimal point),
</li>
<li>
<p><b>type</b> - cache type. This might be <b>pretty much everything</b>,
but there are some predefined types that you might want to treat
in a special way (separate icons etc.). Primary types include: </p>
<ul>
<li><b>Traditional</b>,</li>
<li><b>Multi</b>,</li>
<li><b>Quiz</b>,</li>
<li><b>Virtual</b>,</li>
<li><b>Event</b>,</li>
<li><i>(any other value is valid too)</i></li>
</ul>
<p><b>Event</b> is a peculiar type of geocache which is NOT a geocache
at all, but it <b>is</b> stored as a geocache in OC database (this design
decision is old as the OC network itself!). Just keep in mind, that
in case of Event Caches, some fields may have a little different meaning
than you would tell by their name.</p>
</li>
<li>
<p><b>status</b> - cache status. Valid cache status codes currently include:</p>
<ul>
<li><b>Available</b> - Cache is available and ready for search,</li>
<li><b>Temporarily unavailable</b> - Cache is probably unavailable (i.e. in need of maintenance)</li>
<li><b>Archived</b> - Cache is permanently unavailable (moved to the archives).</li>
</ul>
</li>
<li><b>url</b> - URL of the cache's web page,</li>
<li>
<p><b>owner</b> - dictionary of:</p>
<ul>
<li><b>uuid</b> - geocache owner's user ID,</li>
<li><b>username</b> - name of the user,</li>
<li><b>profile_url</b> - URL of the user profile page,</li>
</ul>
</li>
<li>
<p><b>gc_code</b> - Geocaching.com code (GC code) of the geocache
<b>or null</b> if the cache is not listed on GC or the GC code is
unknown.</p>
<p>Please note that this information is supplied by cache owners
and it is often missing, obsolete or otherwise incorrect.</p>
</li>
<li>
<p><b>distance</b> - float, the distance to a cache, in meters.
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>bearing</b> - float, the absolute bearing to the cache, measured in degrees from north,
<b>or null</b> if it cannot be calculated (i.e. when you are exactly at the target's location).
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>bearing2</b> - string, the absolute bearing to the cache, represented as a typical direction
string of length of 1 or 2 characters (ex. "N", "NE", "E", "SE", etc.), or "n/a" if it cannot be calculated.
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>bearing3</b> - string, the absolute bearing to the cache, represented as a typical direction
string of length of 1 or 2 or 3 characters (ex. "N", "NNE", "NE", "ENE", etc.), or "n/a" if it cannot be calculated.
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>is_found</b> - boolean, true if the user already found this cache.
See also <b>found_status</b> parameter of the services/caches/search/all
method.</p>
<p>This field requires you to use the <b>user_uuid</b> parameter
(or Level 3 Authentication). Please note, that this will also return <b>true</b>
for attended Event Caches.</p>
</li>
<li>
<p><b>is_not_found</b> - boolean, true if the user has submitted "Didn't find it" log entry for this cache.</p>
<p>This field requires you to use the <b>user_uuid</b> parameter (or Level 3 Authentication).</p>
</li>
<li>
<p><b>is_watched</b> - boolean, true if the user is watching this cache. You may consider highlighting
such geocaches in some fashion, as the users may use this functionality to temporarily mark geocaches
of particular interest (i.e. geocaches they plan to find today).</p>
<p>This is private data. You will need Level 3 Authentication to access this field.</p>
</li>
<li>
<p><b>is_ignored</b> - boolean, true if the user is ignoring this cache. (See also <b>exclude_ignored</b>
parameter of all search methods.)</p>
<p>This is private data. You will need Level 3 Authentication to access this field.</p>
</li>
<li>
<p><b>founds</b> - number of times the geocache was successfully found
(or attended, in case of Event Caches),</p>
</li>
<li>
<p><b>notfounds</b> - number of times the geocache was not found
(in case of Event Caches this will always be zero),</p>
</li>
<li>
<p><b>willattends</b> - in case of Event Caches, this is the number of
"Will attend" log entries. In case of any other cache type, it will
be <b>zero</b> (not null, for <a href='https://code.google.com/p/opencaching-api/issues/detail?id=233'>backward
compatibility</a>),</p>
</li>
<li class='deprecated'>
<b>size</b> - deprecated
(<a href='http://code.google.com/p/opencaching-api/issues/detail?id=155'>why?</a>),
use <b>size2</b> instead. Float (between 1 and 5), size rating of the container, or
<b>null</b> if geocache does not have a container,
</li>
<li>
<p><b>size2</b> - string indicating the size of the container, so called
"size2 code". One of the following values:
'none', 'nano', 'micro', 'small', 'regular', 'large', 'xlarge', 'other'.</p>
</li>
<li>
<p><b>oxsize</b> - float (between 1 and 5) or null, this is a size rating
variant, compatible with the one used by opencaching.com (and lately,
Garmin GPS devices).</p>
<p><b>Note:</b> The mapping is undocumented and may change without notice.</p>
<p><b>Note:</b> Some of OC's size values cannot be properly mapped to <b>oxsize</b>,
i.e. the 'other' size.</p>
</li>
<li><b>difficulty</b> - float (between 1 and 5), difficulty rating of the cache,</li>
<li><b>terrain</b> - float (between 1 and 5), terrain rating of the cache,</li>
<li>
<b>trip_time</b> - float, approximate total amount of time needed to
find the cache, in hours; this will usually include the time to reach the
cache location <em>and</em> go back (from/to a parking spot, etc.);
<b>null</b> if unknown,
</li>
<li><b>trip_distance</b> - float, approximate total distance needed to
find the cache, in kilometers; this will usually include the time to reach the
cache location <em>and</em> go back (from/to a parking spot, etc.);
<b>null</b> if unknown,
</li>
<li>
<p><b>rating</b> - float (between 1 and 5), an overall rating of the cache,
or <b>null</b> when the geocache does not have a sufficient amount of votes
to have a rating.</p>
<p>Please note: some OC installations do not use the rating/voting system. The <b>rating</b> will
always be <b>null</b> on such installations.</p>
</li>
<li>
<p><b>rating_votes</b> - number of votes submitted for the rating of this cache.</p>
<p>Please note: some OC installations do not use the rating/voting system. The <b>rating_votes</b> will
always be <b>0</b> on such installations.</p>
</li>
<li><b>recommendations</b> - number of recommendations for this cache,</li>
<li><b>req_passwd</b> - boolean; states if this cache requires a password
in order to submit a "Found it" log entry,</li>
<li><b>description</b> - <a href='%OKAPI:docurl:html%'>HTML string</a>,
description of the cache,</li>
<li><b>descriptions</b> - a dictionary (language code =&gt;
<a href='%OKAPI:docurl:html%'>HTML string</a>) of cache descriptions,</li>
<li class="deprecated"><b>hint</b> - <a href='%OKAPI:docurl:html%'>HTML-encoded</a>
string, cache hints/spoilers; deprecated
(<a href="http://code.google.com/p/opencaching-api/issues/detail?id=261">why?</a>),
use <b>hint2</b> instead,</li>
<li class="deprecated"><b>hints</b> - a dictionary (language code =&gt;
<a href='%OKAPI:docurl:html%'>HTML-encoded</a> string) of cache hints/spoilers;
deprecated, use hints2 instead,</li>
<li><b>hint2</b> - plain-text string, cache hints/spoilers; in general, hints
should not be displayed to the user unless user specifically asks for them,</li>
<li><b>hints2</b> - a dictionary (language code =&gt;
plain-text string) of cache hints/spoilers,</li>
<li>
<p><b>images</b> - list of dictionaries, each dictionary represents one
image saved along the cache description; each dictionary has the
following structure:</p>
<ul>
<li><b>uuid</b> - UUID of the image,</li>
<li><b>url</b> - URL of the image,</li>
<li><b>thumb_url</b> - URL of a small (thumb) version of the image,</li>
<li><b>caption</b> - plain-text string, caption of the image,</li>
<li><b>unique_caption</b> - plain-text string, to be used as a filename
for Garmin's crappy images implementation (currently, they get image
caption from the image's filename itself),</li>
<li><b>is_spoiler</b> - boolean, if <b>true</b> then the image is
a spoiler image and should not be displayed to the user unless
the user explicitly asks for it,</li>
</ul>
</li>
<li>
<p><b>preview_image</b> - On some installations, owners may select one of the <b>images</b>
(see above) as a preview image. You are encouraged to display it as a 'teaser'
for this cache. On other installations this functionality is disabled and you
will always get the <b>null</b> value here.</p>
<p>Please note that this information is supplied by cache owners
and it is often missing, obsolete or otherwise incorrect.</p>
</li>
<li>
<p><b>distance</b> - float, the distance to a cache, in meters.
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>bearing</b> - float, the absolute bearing to the cache, measured in degrees from north,
<b>or null</b> if it cannot be calculated (i.e. when you are exactly at the target's location).
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>bearing2</b> - string, the absolute bearing to the cache, represented as a typical direction
string of length of 1 or 2 characters (ex. "N", "NE", "E", "SE", etc.), or "n/a" if it cannot be calculated.
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>bearing3</b> - string, the absolute bearing to the cache, represented as a typical direction
string of length of 1 or 2 or 3 characters (ex. "N", "NNE", "NE", "ENE", etc.), or "n/a" if it cannot be calculated.
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>is_found</b> - boolean, true if the user already found this cache.
See also <b>found_status</b> parameter of the services/caches/search/all
method.</p>
<p>This field requires you to use the <b>user_uuid</b> parameter
(or Level 3 Authentication). Please note, that this will also return <b>true</b>
for attended Event Caches.</p>
</li>
<li>
<p><b>is_not_found</b> - boolean, true if the user has submitted "Didn't find it" log entry for this cache.</p>
<p>This field requires you to use the <b>user_uuid</b> parameter (or Level 3 Authentication).</p>
</li>
<li>
<p><b>is_watched</b> - boolean, true if the user is watching this cache. You may consider highlighting
such geocaches in some fashion, as the users may use this functionality to temporarily mark geocaches
of particular interest (i.e. geocaches they plan to find today).</p>
<p>This is private data. You will need Level 3 Authentication to access this field.</p>
</li>
<li>
<p><b>is_ignored</b> - boolean, true if the user is ignoring this cache. (See also <b>exclude_ignored</b>
parameter of all search methods.)</p>
<p>This is private data. You will need Level 3 Authentication to access this field.</p>
</li>
<li>
<p><b>founds</b> - number of times the geocache was successfully found
(or attended, in case of Event Caches),</p>
</li>
<li>
<p><b>notfounds</b> - number of times the geocache was not found
(in case of Event Caches this will always be zero),</p>
</li>
<li>
<p><b>willattends</b> - in case of Event Caches, this is the number of
"Will attend" log entries. In case of any other cache type, it will
be <b>zero</b> (not null, for <a href='https://code.google.com/p/opencaching-api/issues/detail?id=233'>backward
compatibility</a>),</p>
</li>
<li class='deprecated'>
<b>size</b> - deprecated
(<a href='http://code.google.com/p/opencaching-api/issues/detail?id=155'>why?</a>),
use <b>size2</b> instead. Float (between 1 and 5), size rating of the container, or
<b>null</b> if geocache does not have a container,
</li>
<li>
<p><b>size2</b> - string indicating the size of the container, so called
"size2 code". One of the following values:
'none', 'nano', 'micro', 'small', 'regular', 'large', 'xlarge', 'other'.</p>
</li>
<li>
<p><b>oxsize</b> - float (between 1 and 5) or null, this is a size rating
variant, compatible with the one used by opencaching.com (and lately,
Garmin GPS devices).</p>
<p><b>Note:</b> The mapping is undocumented and may change without notice.</p>
<p><b>Note:</b> Some of OC's size values cannot be properly mapped to <b>oxsize</b>,
i.e. the 'other' size.</p>
</li>
<li><b>difficulty</b> - float (between 1 and 5), difficulty rating of the cache,</li>
<li><b>terrain</b> - float (between 1 and 5), terrain rating of the cache,</li>
<li>
<b>trip_time</b> - float, approximate total amount of time needed to
find the cache, in hours; this will usually include the time to reach the
cache location <em>and</em> go back (from/to a parking spot, etc.);
<b>null</b> if unknown,
</li>
<li><b>trip_distance</b> - float, approximate total distance needed to
find the cache, in kilometers; this will usually include the time to reach the
cache location <em>and</em> go back (from/to a parking spot, etc.);
<b>null</b> if unknown,
</li>
<li>
<p><b>rating</b> - float (between 1 and 5), an overall rating of the cache,
or <b>null</b> when the geocache does not have a sufficient amount of votes
to have a rating.</p>
<p>Please note: some OC installations do not use the rating/voting system. The <b>rating</b> will
always be <b>null</b> on such installations.</p>
</li>
<li>
<p><b>rating_votes</b> - number of votes submitted for the rating of this cache.</p>
<p>Please note: some OC installations do not use the rating/voting system. The <b>rating_votes</b> will
always be <b>0</b> on such installations.</p>
</li>
<li><b>recommendations</b> - number of recommendations for this cache,</li>
<li><b>req_passwd</b> - boolean; states if this cache requires a password
in order to submit a "Found it" log entry,</li>
<li><b>short_description</b> - a plaintext string with a single line (very short)
description of the cache (kind of a "tagline text"),</li>
<li><b>short_descriptions</b> - a dictionary (language code =&gt;
plaintext string) of short cache descriptions,</li>
<li><b>description</b> - <a href='%OKAPI:docurl:html%'>HTML string</a>,
description of the cache,</li>
<li><b>descriptions</b> - a dictionary (language code =&gt;
<a href='%OKAPI:docurl:html%'>HTML string</a>) of cache descriptions,</li>
<li class="deprecated"><b>hint</b> - <a href='%OKAPI:docurl:html%'>HTML-encoded</a>
string, cache hints/spoilers; deprecated
(<a href="http://code.google.com/p/opencaching-api/issues/detail?id=261">why?</a>),
use <b>hint2</b> instead,</li>
<li class="deprecated"><b>hints</b> - a dictionary (language code =&gt;
<a href='%OKAPI:docurl:html%'>HTML-encoded</a> string) of cache hints/spoilers;
deprecated, use hints2 instead,</li>
<li><b>hint2</b> - plain-text string, cache hints/spoilers; in general, hints
should not be displayed to the user unless user specifically asks for them,</li>
<li><b>hints2</b> - a dictionary (language code =&gt;
plain-text string) of cache hints/spoilers,</li>
<li>
<p><b>images</b> - list of dictionaries, each dictionary represents one
image saved along the cache description; each dictionary has the
following structure:</p>
<ul>
<li><b>uuid</b> - UUID of the image,</li>
<li><b>url</b> - URL of the image,</li>
<li><b>thumb_url</b> - URL of a small (thumb) version of the image,</li>
<li><b>caption</b> - plain-text string, caption of the image,</li>
<li><b>unique_caption</b> - plain-text string, to be used as a filename
for Garmin's crappy images implementation (currently, they get image
caption from the image's filename itself),</li>
<li><b>is_spoiler</b> - boolean, if <b>true</b> then the image is
a spoiler image and should not be displayed to the user unless
the user explicitly asks for it,</li>
</ul>
</li>
<li>
<p><b>preview_image</b> - On some installations, owners may select one of the <b>images</b>
(see above) as a preview image. You are encouraged to display it as a 'teaser'
for this cache. On other installations this functionality is disabled and you
will always get the <b>null</b> value here.</p>
<p>The value of <b>preview_image</b> is either <b>null</b> or a dictionary describing
an image. The structure of this dictionary is the same as of a single entry on
the <b>images</b> list described above.</p>
</li>
<li>
<p><b>attr_acodes</b> - unordered list of OKAPI geocache-attribute IDs (A-codes) with
which the cache was tagged. Use the <b>attrs</b> module to retrieve information on
these attributes.</p>
</li>
<li>
<b>attrnames</b> - if you don't want to dig into <b>attr_acodes</b> just now, then
you can use these as a simple alternative. <b>attrnames</b> is an unordered list of
names of the attributes with which the cache was tagged; the language will be
selected based on the <b>langpref</b> parameter.
</li>
<li>
<p><b>attribution_note</b> - the proper attribution note for the cache listing according
to the local OC site's Data License. By default, this note is also appended to geocache
descriptions (see the <b>attribution_append</b> parameter).</p>
</li>
<li>
<p><b>latest_logs</b> - a couple of latest log entries in the cache.
The format is the same as the one returned by the services/logs/logs method.</p>
<p>Notice: The number of logs returned can be set with the <b>lpc</b> option.</p>
</li>
<li>
<p><b>my_notes</b> - user's notes on the cache, in plain-text, or <b>null</b>
if user hadn't defined any notes.</p>
<p>This field requires you to use the <b>Level 3</b> Authentication.</p>
</li>
<li><b>trackables_count</b> - a total number of trackables hidden within the cache.</li>
<li>
<p><b>trackables</b> - list of dictionaries, each dictionary represents one
trackable hidden within the cache container; each dictionary has the
following structure:</p>
<ul>
<li><b>code</b> - code of the trackable,</li>
<li><b>name</b> - plain text name of the trackable,</li>
<li><b>url</b> - trackable's own webpage address <b>or null</b>, if OKAPI
cannot automatically determine this address.</li>
</ul>
</li>
<li>
<p><b>alt_wpts</b> - list of alternate/additional waypoints associated
with this geocache. Each item is a dictionary of the following structure:</p>
<ul>
<li><b>name</b> - plain-text, short, unique "codename" for the waypoint,</li>
<li><b>location</b> - location of the waypoint in the "lat|lon" format
(<i>lat</i> and <i>lon</i> are in full degrees with a dot as a decimal point),</li>
<li><b>type</b> - string, unique identifier for the type of waypoint; one
of the following: <b>parking</b>, <b>path</b>, <b>stage</b>,
<b>physical-stage</b>, <b>virtual-stage</b>, <b>final</b>, <b>poi</b>, <b>other</b>;
more types may be added; unknown types should be treated as <b>other</b>,
</li>
<li><b>type_name</b> - string, the human-readable name of the waypoint type,
e.g. "Parking area" or "Final location"; the language will be selected
based on the langpref argument,
</li>
<li>
<b>sym</b> - string, one of commonly recognized waypoint symbol
names, originally introduced by Garmin in their devices and GPX
files (e.g. "Flag, Green" or "Parking Area"),
</li>
<li><b>description</b> - plain-text longer description of the waypoint.</li>
</ul>
</li>
<li>
<p><b>country</b> - name of the country the cache is placed in;
may be empty ("") if the country is unknown.</p>
<p><b>Note:</b> This data is user-supplied and is not validated in
any way. Consider using external geocoding services instead. Also,
currently you have no way of knowing in which language it will appear
in (but it *may* start to vary on the value of your <b>langpref</b>
parameter in the future).</p>
</li>
<li>
<p><b>state</b> - name of the state the cache is placed in;
may be empty ("") if the state is unknown.</p>
<p><b>Note:</b> On some installations this data is user-supplied and
is not validated in any way. Other installations calculate it from
cache coordinates but may have problems in border regions.
Consider using external geocoding services instead. Also,
currently you have no way of knowing in which language it will appear
in (but it *may* start to vary on the value of your <b>langpref</b>
parameter in the future).</p>
</li>
<li>
<p><b>protection_areas</b> - list of dictionaries, each representing a
protection area in which the cache probably is located; each dictionary
has the following structure:</p>
<ul>
<li><b>type</b> - the type of the protection area, e.g.
"National Nature Reserve",</li>
<li><b>name</b> - the name of the protection area, e.g.
"East Dartmoor Woods and Heaths".</li>
</ul>
<p>The types and names currently are in a local language of the OC site but
may be translated depending on the <b>langpref</b> parameter in the future.</p>
<p>Note that this information is not authoritative. It may be outdated
or incomplete, and it is completely missing on some OC installations.</p>
</li>
<li>
<p><b>last_found</b> - date and time (ISO 8601) when the
geocache was last found <b>or null</b> when it hasn't been yet found.</p>
<p>Note, that some Opencaching servers don't store the exact times along
with the log entries.</p>
</li>
<li><b>last_modified</b> - date and time (ISO 8601) when the
geocache was last modified (changed status, attributes, etc.),</li>
<li><b>date_created</b> - date and time (ISO 8601) when the
geocache was listed at the Opencaching site,</li>
<li><b>date_hidden</b> - date and time (ISO 8601) when
<ul>
<li>the geocache was first hidden (for physical caches), or </li>
<li>the geocache was first published (for virtual caches), or</li>
<li>the event takes place (for event caches),</li>
</ul>
</li>
<!-- Note: I think cache uuids should not be ever revealed to the public.
We have already one universally unique key - the cache code. It is uncommon
to have multiple universally unique keys. -->
<li><b>internal_id</b> - internal ID of the cache (<b>do not</b> use
this, unless you know what you're doing; use the "code" as an identifier).</li>
</ul>
<p>The value of <b>preview_image</b> is either <b>null</b> or a dictionary describing
an image. The structure of this dictionary is the same as of a single entry on
the <b>images</b> list described above.</p>
</li>
<li>
<p><b>attr_acodes</b> - unordered list of OKAPI geocache-attribute IDs (A-codes) with
which the cache was tagged. Use the <b>attrs</b> module to retrieve information on
these attributes.</p>
</li>
<li>
<b>attrnames</b> - if you don't want to dig into <b>attr_acodes</b> just now, then
you can use these as a simple alternative. <b>attrnames</b> is an unordered list of
names of the attributes with which the cache was tagged; the language will be
selected based on the <b>langpref</b> parameter.
</li>
<li>
<p><b>attribution_note</b> - the proper attribution note for the cache listing according
to the local OC site's Data License. By default, this note is also appended to geocache
descriptions (see the <b>attribution_append</b> parameter).</p>
</li>
<li>
<p><b>latest_logs</b> - a couple of latest log entries in the cache.
The format is the same as the one returned by the services/logs/logs method.</p>
<p>Notice: The number of logs returned can be set with the <b>lpc</b> option.</p>
</li>
<li>
<p><b>my_notes</b> - user's notes on the cache, in plain-text, or <b>null</b>
if user hadn't defined any notes.</p>
<p>This field requires you to use the <b>Level 3</b> Authentication.</p>
</li>
<li><b>trackables_count</b> - a total number of trackables hidden within the cache.</li>
<li>
<p><b>trackables</b> - list of dictionaries, each dictionary represents one
trackable hidden within the cache container; each dictionary has the
following structure:</p>
<ul>
<li><b>code</b> - code of the trackable,</li>
<li><b>name</b> - plain text name of the trackable,</li>
<li><b>url</b> - trackable's own webpage address <b>or null</b>, if OKAPI
cannot automatically determine this address.</li>
</ul>
</li>
<li id="ret_alt_wpts">
<p><b>alt_wpts</b> - list of alternate/additional waypoints associated
with this geocache. Each item is a dictionary of the following structure:</p>
<ul>
<li><b>name</b> - plain-text, short, unique "codename" for the waypoint,</li>
<li><b>location</b> - location of the waypoint in the "lat|lon" format
(<i>lat</i> and <i>lon</i> are in full degrees with a dot as a decimal point),</li>
<li>
<p><b>type</b> - string, unique identifier for the type of waypoint;
one of the following:</p>
<p>For example, for <i>geocache?cache_code=OP3D96&amp;fields=type</i>
query, the result might look something link this:</p>
<pre>{"type": "Traditional"}</pre>
<p>If given cache code does not exist, the method will
respond with an HTTP 400 error.</p>
</returns>
<ul>
<li><b>parking</b>, <b>path</b>, <b>stage</b>, <b>physical-stage</b>,
<b>virtual-stage</b>, <b>final</b>, <b>poi</b>, <b>other</b> - used by
OC itself, for detailed descriptions of these you'll have to refer to
external Opencaching documenation,</li>
<li><b>user-coords</b> - extra coordinates supplied <i>by the user who
had found the cache</i> (NOT the owner of the cache), most probably
pointing to the final location of the cache (e.g. a quiz cache);
this type of waypoint is available only in some installations and only
if you're using Level 3 Authentication,</li>
<li>more types may be added at any moment; unknown types should be
treated as <b>other</b>.</li>
</ul>
</li>
<li>
<b>type_name</b> - string, the human-readable name of the waypoint type,
e.g. "Parking area" or "Final location"; the language will be selected
based on the langpref argument,
</li>
<li>
<p><b>sym</b> - string, one of commonly recognized waypoint symbol
names, originally introduced by Garmin in their devices and GPX
files (e.g. "Flag, Green" or "Parking Area").</p>
<p>These symbol codes are only suggestions. They are understood by
Garmin-related software and devices. If you don't know how to display such
symbols, you are welcome to use any symbol you'd like in your
application.</p>
</li>
<li><b>description</b> - plain-text longer description of the waypoint.</li>
</ul>
</li>
<li>
<p><b>country</b> - name of the country the cache is placed in;
may be empty ("") if the country is unknown.</p>
<p><b>Note:</b> This data is user-supplied and is not validated in
any way. Consider using external geocoding services instead. Also,
currently you have no way of knowing in which language it will appear
in (but it *may* start to vary on the value of your <b>langpref</b>
parameter in the future).</p>
</li>
<li>
<p><b>state</b> - name of the state the cache is placed in;
may be empty ("") if the state is unknown.</p>
<p><b>Note:</b> On some installations this data is user-supplied and
is not validated in any way. Other installations calculate it from
cache coordinates but may have problems in border regions.
Consider using external geocoding services instead. Also,
currently you have no way of knowing in which language it will appear
in (but it *may* start to vary on the value of your <b>langpref</b>
parameter in the future).</p>
</li>
<li>
<p><b>protection_areas</b> - list of dictionaries, each representing a
protection area in which the cache probably is located; each dictionary
has the following structure:</p>
<ul>
<li><b>type</b> - the type of the protection area, e.g.
"National Nature Reserve",</li>
<li><b>name</b> - the name of the protection area, e.g.
"East Dartmoor Woods and Heaths".</li>
</ul>
<p>The types and names currently are in a local language of the OC site but
may be translated depending on the <b>langpref</b> parameter in the future.</p>
<p>Note that this information is not authoritative. It may be outdated
or incomplete, and it is completely missing on some OC installations.</p>
</li>
<li>
<p><b>last_found</b> - date and time (ISO 8601) when the
geocache was last found <b>or null</b> when it hasn't been yet found.</p>
<p>Note, that some Opencaching servers don't store the exact times along
with the log entries.</p>
</li>
<li><b>last_modified</b> - date and time (ISO 8601) when the
geocache was last modified (changed status, attributes, etc.),</li>
<li><b>date_created</b> - date and time (ISO 8601) when the
geocache was listed at the Opencaching site,</li>
<li><b>date_hidden</b> - date and time (ISO 8601) when
<ul>
<li>the geocache was first hidden (for physical caches), or </li>
<li>the geocache was first published (for virtual caches), or</li>
<li>the event takes place (for event caches),</li>
</ul>
</li>
<!-- Note: I think cache uuids should not be ever revealed to the public.
We have already one universally unique key - the cache code. It is uncommon
to have multiple universally unique keys. -->
<li><b>internal_id</b> - internal ID of the cache (<b>do not</b> use
this, unless you know what you're doing; use the "code" as an identifier).</li>
</ul>
<p>For example, for <i>geocache?cache_code=OP3D96&amp;fields=type</i>
query, the result might look something link this:</p>
<pre>{"type": "Traditional"}</pre>
<p>If given cache code does not exist, the method will
respond with an HTTP 400 error.</p>
</returns>
</xml>

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +1,54 @@
<xml>
<brief>Retrieve information on multiple geocaches</brief>
<issue-id>20</issue-id>
<desc>
<p>This method works like the services/caches/geocache method, but works
with multiple geocaches (instead of only one).</p>
</desc>
<req name='cache_codes'>
<p>Pipe-separated list of cache cache codes. These represent the
geocaches you are interested in. No more than 500 codes are allowed.
Unlike in the "geocache" method, this CAN be an empty string (it will
result in an empty, but valid, response).</p>
</req>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for fields like
<b>name</b> and <b>description</b>.</p>
<p>Please note, that you may also access caches' descriptions in all
available languages. If you want to do this, you should use fields
<b>names</b>, <b>descriptions</b> (etc.) instead of <b>name</b> and
<b>description</b> (etc.).</p>
</opt>
<opt name='fields' default='code|name|location|type|status'>
<p>Same as in the services/caches/geocache method. Pipe-separated list
of field names which you are interested with.
See services/caches/geocache method for a list available values.</p>
</opt>
<opt name='attribution_append' default='full'>
<p>Same as in the services/caches/geocache method.</p>
</opt>
<opt name='lpc' default='10'>
Same as in the services/caches/geocache method.
</opt>
<opt name='log_fields' default='uuid|date|user|type|comment'>
Same as in the services/caches/geocache method.
</opt>
<opt name='user_uuid'>
Same as in the services/caches/geocache method.
</opt>
<common-format-params/>
<returns>
<p>A dictionary. Cache codes you provide will be mapped to dictionary keys,
and each value will be a dictionary of fields you have selected.</p>
<p>For example, for <i>geocaches?cache_codes=OP3D96|OC124&amp;fields=type</i>
query, the result might look something link this:</p>
<pre>{"OP3D96": {"type": "Traditional"}, "OC124": null}</pre>
<p>Value of <b>null</b> means that the given cache haven't been found.
(This behavior is different than in the services/caches/geocache method, which
responds with an HTTP 400 error in such case.)</p>
</returns>
<brief>Retrieve information on multiple geocaches</brief>
<issue-id>20</issue-id>
<desc>
<p>This method works like the services/caches/geocache method, but works
with multiple geocaches (instead of only one).</p>
</desc>
<req name='cache_codes'>
<p>Pipe-separated list of cache cache codes. These represent the
geocaches you are interested in. No more than 500 codes are allowed.
Unlike in the "geocache" method, this CAN be an empty string (it will
result in an empty, but valid, response).</p>
</req>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for fields like
<b>name</b> and <b>description</b>.</p>
<p>Please note, that you may also access caches' descriptions in all
available languages. If you want to do this, you should use fields
<b>names</b>, <b>descriptions</b> (etc.) instead of <b>name</b> and
<b>description</b> (etc.).</p>
</opt>
<opt name='fields' default='code|name|location|type|status'>
<p>Same as in the services/caches/geocache method. Pipe-separated list
of field names which you are interested with.
See services/caches/geocache method for a list available values.</p>
</opt>
<opt name='attribution_append' default='full'>
<p>Same as in the services/caches/geocache method.</p>
</opt>
<opt name='lpc' default='10'>
Same as in the services/caches/geocache method.
</opt>
<opt name='log_fields' default='uuid|date|user|type|comment'>
Same as in the services/caches/geocache method.
</opt>
<opt name='my_location'>
Same as in the services/caches/geocache method.
</opt>
<opt name='user_uuid'>
Same as in the services/caches/geocache method.
</opt>
<common-format-params/>
<returns>
<p>A dictionary. Cache codes you provide will be mapped to dictionary keys,
and each value will be a dictionary of fields you have selected.</p>
<p>For example, for <i>geocaches?cache_codes=OP3D96|OC124&amp;fields=type</i>
query, the result might look something link this:</p>
<pre>{"OP3D96": {"type": "Traditional"}, "OC124": null}</pre>
<p>Value of <b>null</b> means that the given cache haven't been found.
(This behavior is different than in the services/caches/geocache method, which
responds with an HTTP 400 error in such case.)</p>
</returns>
</xml>

View File

@ -23,230 +23,246 @@ require_once 'tiletree.inc.php';
class ReplicateListener
{
public static function receive($changelog)
{
# This will be called every time new items arrive from replicate module's
# changelog. The format of $changelog is described in the replicate module
# (NOT the entire response, just the "changelog" key).
public static function receive($changelog)
{
# This will be called every time new items arrive from replicate module's
# changelog. The format of $changelog is described in the replicate module
# (NOT the entire response, just the "changelog" key).
foreach ($changelog as $c)
{
if ($c['object_type'] == 'geocache')
{
if ($c['change_type'] == 'replace')
self::handle_geocache_replace($c);
else
self::handle_geocache_delete($c);
}
}
}
foreach ($changelog as $c)
{
if ($c['object_type'] == 'geocache')
{
if ($c['change_type'] == 'replace')
self::handle_geocache_replace($c);
else
self::handle_geocache_delete($c);
}
}
}
public static function reset($mail_admins = true)
{
# This will be called when there are "too many" entries in the changelog
# and the replicate module thinks it better to just reset the entire TileTree.
# For the first hours after such reset maps may work a little slower.
public static function reset($mail_admins = true)
{
# This will be called when there are "too many" entries in the changelog
# and the replicate module thinks it better to just reset the entire TileTree.
# For the first hours after such reset maps may work a little slower.
Db::execute("delete from okapi_tile_status");
Db::execute("delete from okapi_tile_caches");
}
Db::execute("delete from okapi_tile_status");
Db::execute("delete from okapi_tile_caches");
}
private static function handle_geocache_replace($c)
{
# Check if any relevant geocache attributes have changed.
# We will pick up "our" copy of the cache from zero-zoom level.
private static function handle_geocache_replace($c)
{
# Check if any relevant geocache attributes have changed.
# We will pick up "our" copy of the cache from zero-zoom level.
try {
$cache = OkapiServiceRunner::call("services/caches/geocache", new OkapiInternalRequest(new OkapiInternalConsumer(), null, array(
'cache_code' => $c['object_key']['code'],
'fields' => 'internal_id|code|name|location|type|status|rating|recommendations|founds|trackables_count'
)));
} catch (InvalidParam $e) {
# Unprobable, but possible. Ignore changelog entry.
return;
}
try {
$cache = OkapiServiceRunner::call("services/caches/geocache", new OkapiInternalRequest(new OkapiInternalConsumer(), null, array(
'cache_code' => $c['object_key']['code'],
'fields' => 'internal_id|code|name|location|type|status|rating|recommendations|founds|trackables_count'
)));
} catch (InvalidParam $e) {
# Unprobable, but possible. Ignore changelog entry.
return;
}
$theirs = TileTree::generate_short_row($cache);
$ours = mysql_fetch_row(Db::query("
select cache_id, z21x, z21y, status, type, rating, flags
from okapi_tile_caches
where
z=0
and cache_id = '".mysql_real_escape_string($cache['internal_id'])."'
"));
if (!$ours)
{
# Aaah, a new geocache! How nice... ;)
# Fetch our copy of the cache.
self::add_geocache_to_cached_tiles($theirs);
}
elseif (($ours[1] != $theirs[1]) || ($ours[2] != $theirs[2])) # z21x & z21y fields
{
# Location changed.
$ours = mysql_fetch_row(Db::query("
select cache_id, z21x, z21y, status, type, rating, flags
from okapi_tile_caches
where
z=0
and cache_id = '".mysql_real_escape_string($cache['internal_id'])."'
"));
self::remove_geocache_from_cached_tiles($ours[0]);
self::add_geocache_to_cached_tiles($theirs);
}
elseif ($ours != $theirs)
{
self::update_geocache_attributes_in_cached_tiles($theirs);
}
else
{
# No need to update anything. This is very common (i.e. when the
# cache was simply found, not actually changed). Replicate module generates
# many updates which do not influence our cache.
}
}
# Caches near the poles caused our computations to break here. We will
# ignore such caches!
private static function remove_geocache_from_cached_tiles($cache_id)
{
# Simply remove all traces of this geocache from all tiles.
# This includes all references along tiles' borders, etc.
list($lat, $lon) = explode("|", $cache['location']);
if ((floatval($lat) >= 89.99) || (floatval($lat) <= -89.99)) {
if ($ours) {
self::remove_geocache_from_cached_tiles($ours[0]);
}
return;
}
Db::execute("
delete from okapi_tile_caches
where cache_id = '".mysql_real_escape_string($cache_id)."'
");
# Compute the new row for okapi_tile_caches. Compare with the old one.
# Note, that after this operation, okapi_tile_status may be out-of-date.
# There might exist some rows with status==2, but they should be in status==1.
# Currently, we can ignore this, because status==1 is just a shortcut to
# avoid making unnecessary queries.
}
$theirs = TileTree::generate_short_row($cache);
if (!$ours)
{
# Aaah, a new geocache! How nice... ;)
private static function add_geocache_to_cached_tiles(&$row)
{
# This one is the most complicated. We need to identify all tiles
# where the cache should be present. This include 22 obvious "exact match"
# tiles (one per each zoom level), *and* all "just outside the border"
# tiles (one geocache can be present in up to 4 tiles per zoom level).
# This gives us max. 88 tiles to add the geocache to.
self::add_geocache_to_cached_tiles($theirs);
}
elseif (($ours[1] != $theirs[1]) || ($ours[2] != $theirs[2])) # z21x & z21y fields
{
# Location changed.
$tiles_to_update = array();
self::remove_geocache_from_cached_tiles($ours[0]);
self::add_geocache_to_cached_tiles($theirs);
}
elseif ($ours != $theirs)
{
self::update_geocache_attributes_in_cached_tiles($theirs);
}
else
{
# No need to update anything. This is very common (i.e. when the
# cache was simply found, not actually changed). Replicate module generates
# many updates which do not influence our cache.
}
}
# We will begin at zoom 21 and then go down to zoom 0.
private static function remove_geocache_from_cached_tiles($cache_id)
{
# Simply remove all traces of this geocache from all tiles.
# This includes all references along tiles' borders, etc.
$z21x = $row[1];
$z21y = $row[2];
$ex = $z21x >> 8; # initially, z21x / <tile width>
$ey = $z21y >> 8; # initially, z21y / <tile height>
for ($zoom = 21; $zoom >= 0; $zoom--, $ex >>= 1, $ey >>= 1)
{
# ($ex, $ey) points to the "exact match" tile. We need to determine
# tile-range to check for "just outside the border" tiles. We will
# go with the simple approach and check all 1+8 bordering tiles.
Db::execute("
delete from okapi_tile_caches
where cache_id = '".mysql_real_escape_string($cache_id)."'
");
$tiles_in_this_region = array();
for ($x=$ex-1; $x<=$ex+1; $x++)
for ($y=$ey-1; $y<=$ey+1; $y++)
if (($x >= 0) && ($x < 1<<$zoom) && ($y >= 0) && ($y < 1<<$zoom))
$tiles_in_this_region[] = array($x, $y);
# Note, that after this operation, okapi_tile_status may be out-of-date.
# There might exist some rows with status==2, but they should be in status==1.
# Currently, we can ignore this, because status==1 is just a shortcut to
# avoid making unnecessary queries.
}
foreach ($tiles_in_this_region as $coords)
{
list($x, $y) = $coords;
private static function add_geocache_to_cached_tiles(&$row)
{
# This one is the most complicated. We need to identify all tiles
# where the cache should be present. This include 22 obvious "exact match"
# tiles (one per each zoom level), *and* all "just outside the border"
# tiles (one geocache can be present in up to 4 tiles per zoom level).
# This gives us max. 88 tiles to add the geocache to.
$scale = 8 + 21 - $zoom;
$margin = 1 << ($scale - 3); # 32px of current $zoom level, measured in z21 pixels.
$tiles_to_update = array();
$left_z21x = ($x << $scale) - $margin;
$right_z21x = (($x + 1) << $scale) + $margin;
$top_z21y = ($y << $scale) - $margin;
$bottom_z21y = (($y + 1) << $scale) + $margin;
# We will begin at zoom 21 and then go down to zoom 0.
if ($z21x < $left_z21x)
continue;
if ($z21x > $right_z21x)
continue;
if ($z21y < $top_z21y)
continue;
if ($z21y > $bottom_z21y)
continue;
$z21x = $row[1];
$z21y = $row[2];
$ex = $z21x >> 8; # initially, z21x / <tile width>
$ey = $z21y >> 8; # initially, z21y / <tile height>
for ($zoom = 21; $zoom >= 0; $zoom--, $ex >>= 1, $ey >>= 1)
{
# ($ex, $ey) points to the "exact match" tile. We need to determine
# tile-range to check for "just outside the border" tiles. We will
# go with the simple approach and check all 1+8 bordering tiles.
# We found a match. Store it for later.
$tiles_in_this_region = array();
for ($x=$ex-1; $x<=$ex+1; $x++)
for ($y=$ey-1; $y<=$ey+1; $y++)
if (($x >= 0) && ($x < 1<<$zoom) && ($y >= 0) && ($y < 1<<$zoom))
$tiles_in_this_region[] = array($x, $y);
$tiles_to_update[] = array($zoom, $x, $y);
}
}
foreach ($tiles_in_this_region as $coords)
{
list($x, $y) = $coords;
# We have a list of all possible tiles that need updating.
# Most of these tiles aren't cached at all. We need to update
# only the cached ones.
$scale = 8 + 21 - $zoom;
$margin = 1 << ($scale - 3); # 32px of current $zoom level, measured in z21 pixels.
$alternatives_escaped = array();
foreach ($tiles_to_update as $coords)
{
list($z, $x, $y) = $coords;
$alternatives_escaped[] = "(
z = '".mysql_real_escape_string($z)."'
and x = '".mysql_real_escape_string($x)."'
and y = '".mysql_real_escape_string($y)."'
)";
}
if (count($alternatives_escaped) > 0)
{
Db::execute("
replace into okapi_tile_caches (
z, x, y, cache_id, z21x, z21y, status, type, rating, flags
)
select
z, x, y,
'".mysql_real_escape_string($row[0])."',
'".mysql_real_escape_string($row[1])."',
'".mysql_real_escape_string($row[2])."',
'".mysql_real_escape_string($row[3])."',
'".mysql_real_escape_string($row[4])."',
".(($row[5] === null) ? "null" : "'".mysql_real_escape_string($row[5])."'").",
'".mysql_real_escape_string($row[6])."'
from okapi_tile_status
where
(".implode(" or ", $alternatives_escaped).")
and status in (1,2)
");
$left_z21x = ($x << $scale) - $margin;
$right_z21x = (($x + 1) << $scale) + $margin;
$top_z21y = ($y << $scale) - $margin;
$bottom_z21y = (($y + 1) << $scale) + $margin;
# We might have just filled some empty tiles (status 1) with data.
# We need to update their status to 2.
if ($z21x < $left_z21x)
continue;
if ($z21x > $right_z21x)
continue;
if ($z21y < $top_z21y)
continue;
if ($z21y > $bottom_z21y)
continue;
Db::execute("
update okapi_tile_status
set status=2
where
(".implode(" or ", $alternatives_escaped).")
and status=1
");
}
# We found a match. Store it for later.
# And that's all. That should do the trick.
}
$tiles_to_update[] = array($zoom, $x, $y);
}
}
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!
# We have a list of all possible tiles that need updating.
# Most of these tiles aren't cached at all. We need to update
# only the cached ones.
Db::execute("
update okapi_tile_caches
set
status = '".mysql_real_escape_string($row[3])."',
type = '".mysql_real_escape_string($row[4])."',
rating = ".(($row[5] === null) ? "null" : "'".mysql_real_escape_string($row[5])."'").",
flags = '".mysql_real_escape_string($row[6])."'
where
cache_id = '".mysql_real_escape_string($row[0])."'
");
}
$alternatives_escaped = array();
foreach ($tiles_to_update as $coords)
{
list($z, $x, $y) = $coords;
$alternatives_escaped[] = "(
z = '".mysql_real_escape_string($z)."'
and x = '".mysql_real_escape_string($x)."'
and y = '".mysql_real_escape_string($y)."'
)";
}
if (count($alternatives_escaped) > 0)
{
Db::execute("
replace into okapi_tile_caches (
z, x, y, cache_id, z21x, z21y, status, type, rating, flags
)
select
z, x, y,
'".mysql_real_escape_string($row[0])."',
'".mysql_real_escape_string($row[1])."',
'".mysql_real_escape_string($row[2])."',
'".mysql_real_escape_string($row[3])."',
'".mysql_real_escape_string($row[4])."',
".(($row[5] === null) ? "null" : "'".mysql_real_escape_string($row[5])."'").",
'".mysql_real_escape_string($row[6])."'
from okapi_tile_status
where
(".implode(" or ", $alternatives_escaped).")
and status in (1,2)
");
private static function handle_geocache_delete($c)
{
# Simply delete the cache at all zoom levels.
# We might have just filled some empty tiles (status 1) with data.
# We need to update their status to 2.
$cache_id = Db::select_value("
select cache_id
from caches
where wp_oc='".mysql_real_escape_string($c['object_key']['code'])."'
");
self::remove_geocache_from_cached_tiles($cache_id);
}
Db::execute("
update okapi_tile_status
set status=2
where
(".implode(" or ", $alternatives_escaped).")
and status=1
");
}
# And that's all. That should do the trick.
}
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("
update okapi_tile_caches
set
status = '".mysql_real_escape_string($row[3])."',
type = '".mysql_real_escape_string($row[4])."',
rating = ".(($row[5] === null) ? "null" : "'".mysql_real_escape_string($row[5])."'").",
flags = '".mysql_real_escape_string($row[6])."'
where
cache_id = '".mysql_real_escape_string($row[0])."'
");
}
private static function handle_geocache_delete($c)
{
# Simply delete the cache at all zoom levels.
$cache_id = Db::select_value("
select cache_id
from caches
where wp_oc='".mysql_real_escape_string($c['object_key']['code'])."'
");
self::remove_geocache_from_cached_tiles($cache_id);
}
}

View File

@ -29,200 +29,200 @@ require_once($GLOBALS['rootpath']."okapi/services/caches/search/searching.inc.ph
class WebService
{
/**
* Should be always true. You may temporarily set it to false, when you're
* testing/debugging the tile renderer.
*/
private static $USE_ETAGS_CACHE = true;
/**
* Should be always true. You may temporarily set it to false, when you're
* testing/debugging the tile renderer.
*/
private static $USE_ETAGS_CACHE = true;
/**
* Should be always true. You may temporarily set it to false, when you're
* testing/debugging the tile renderer.
*/
private static $USE_IMAGE_CACHE = true;
/**
* Should be always true. You may temporarily set it to false, when you're
* testing/debugging the tile renderer.
*/
private static $USE_IMAGE_CACHE = true;
/**
* Should be always true. You may temporarily set it to false, when you're
* testing/debugging. Grep the code to check when this flag is used.
*/
private static $USE_OTHER_CACHE = true;
/**
* Should be always true. You may temporarily set it to false, when you're
* testing/debugging. Grep the code to check when this flag is used.
*/
private static $USE_OTHER_CACHE = true;
public static function options()
{
return array(
'min_auth_level' => 3
);
}
public static function options()
{
return array(
'min_auth_level' => 3
);
}
private static function require_uint($request, $name, $min_value = 0)
{
$val = $request->get_parameter($name);
if ($val === null)
throw new ParamMissing($name);
$ret = intval($val);
if ($ret < 0 || ("$ret" !== $val))
throw new InvalidParam($name, "Expecting non-negative integer.");
return $ret;
}
private static function require_uint($request, $name, $min_value = 0)
{
$val = $request->get_parameter($name);
if ($val === null)
throw new ParamMissing($name);
$ret = intval($val);
if ($ret < 0 || ("$ret" !== $val))
throw new InvalidParam($name, "Expecting non-negative integer.");
return $ret;
}
public static function call(OkapiRequest $request)
{
$checkpointA_started = microtime(true);
public static function call(OkapiRequest $request)
{
$checkpointA_started = microtime(true);
# Make sure the request is internal.
# Make sure the request is internal.
if (!in_array($request->consumer->key, array('internal', 'facade')))
throw new BadRequest("Your Consumer Key has not been allowed to access this method.");
if (!in_array($request->consumer->key, array('internal', 'facade')))
throw new BadRequest("Your Consumer Key has not been allowed to access this method.");
# zoom, x, y - required tile-specific parameters.
# 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).".");
$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.
# 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', $request);
$set_id = $search_set['set_id'];
$search_set = OkapiServiceRunner::call('services/caches/search/save', $request);
$set_id = $search_set['set_id'];
# Get caches which are present in the result set AND within the tile
# (+ those around the borders).
# 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 = mysql_fetch_row($rs))
$rows[] = $row;
unset($row);
}
OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointA", null,
microtime(true) - $checkpointA_started);
$checkpointB_started = microtime(true);
$rs = TileTree::query_fast($zoom, $x, $y, $set_id);
$rows = array();
if ($rs !== null)
{
while ($row = mysql_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.
# Add dynamic, user-related flags.
if (count($rows) > 0)
{
# Load user-related cache ids.
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();
$cache_key = "tileuser/".$request->token->user_id;
$user = self::$USE_OTHER_CACHE ? Cache::get($cache_key) : null;
if ($user === null)
{
$user = array();
# Ignored caches.
# Ignored caches.
$rs = Db::query("
select cache_id
from cache_ignore
where user_id = '".mysql_real_escape_string($request->token->user_id)."'
");
$user['ignored'] = array();
while (list($cache_id) = mysql_fetch_row($rs))
$user['ignored'][$cache_id] = true;
$rs = Db::query("
select cache_id
from cache_ignore
where user_id = '".mysql_real_escape_string($request->token->user_id)."'
");
$user['ignored'] = array();
while (list($cache_id) = mysql_fetch_row($rs))
$user['ignored'][$cache_id] = true;
# Found caches.
# Found caches.
$rs = Db::query("
select distinct cache_id
from cache_logs
where
user_id = '".mysql_real_escape_string($request->token->user_id)."'
and type = 1
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "deleted = 0" : "true")."
");
$user['found'] = array();
while (list($cache_id) = mysql_fetch_row($rs))
$user['found'][$cache_id] = true;
$rs = Db::query("
select distinct cache_id
from cache_logs
where
user_id = '".mysql_real_escape_string($request->token->user_id)."'
and type = 1
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "deleted = 0" : "true")."
");
$user['found'] = array();
while (list($cache_id) = mysql_fetch_row($rs))
$user['found'][$cache_id] = true;
# Own caches.
# Own caches.
$rs = Db::query("
select distinct cache_id
from caches
where user_id = '".mysql_real_escape_string($request->token->user_id)."'
");
$user['own'] = array();
while (list($cache_id) = mysql_fetch_row($rs))
$user['own'][$cache_id] = true;
$rs = Db::query("
select distinct cache_id
from caches
where user_id = '".mysql_real_escape_string($request->token->user_id)."'
");
$user['own'] = array();
while (list($cache_id) = mysql_fetch_row($rs))
$user['own'][$cache_id] = true;
Cache::set($cache_key, $user, 30);
}
Cache::set($cache_key, $user, 30);
}
# Add extra flags to geocaches.
# 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).
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"
}
}
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).
# Compute the image hash/fingerprint. This will be used both for ETags
# and internal cache ($cache_key).
$tile = new DefaultTileRenderer($zoom, $rows);
$image_fingerprint = $tile->get_unique_hash();
$tile = new DefaultTileRenderer($zoom, $rows);
$image_fingerprint = $tile->get_unique_hash();
# Start creating response.
# 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 = new OkapiHttpResponse();
$response->content_type = $tile->get_content_type();
$response->cache_control = "Cache-Control: private, max-age=600";
$response->etag = 'W/"'.$image_fingerprint.'"';
# Check if the request didn't include the same ETag.
# 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.
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;
}
$response->etag = null;
$response->status = "304 Not Modified";
return $response;
}
# Check if the image was recently rendered and is kept in image cache.
# 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.
$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;
}
return $response;
}
# Miss. Render the image. Cache the result.
# 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);
$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;
}
return $response;
}
}

View File

@ -1,18 +1,18 @@
<xml>
<brief>Get cache map tile</brief>
<issue-id>150</issue-id>
<desc>
<p><b>CLOSED BETA</b> version. Due to long-term performance tests, currently
this method is not publicly accessible. Let us know if you're interested
in using it.</p>
<p>Use this method to retrieve a tile-map of all caches included in your search
result.</p>
</desc>
<req name='z'>Zoom level (0..21).</req>
<req name='x'>Tile number on the X axis.</req>
<req name='y'>Tile number on the Y axis.</req>
<import-params method="services/caches/search/save"/>
<returns>
The PNG image with the requested map tile.
</returns>
<brief>Get cache map tile</brief>
<issue-id>150</issue-id>
<desc>
<p><b>CLOSED BETA</b> version. Due to long-term performance tests, currently
this method is not publicly accessible. Let us know if you're interested
in using it.</p>
<p>Use this method to retrieve a tile-map of all caches included in your search
result.</p>
</desc>
<req name='z'>Zoom level (0..21).</req>
<req name='x'>Tile number on the X axis.</req>
<req name='y'>Tile number on the Y axis.</req>
<import-params method="services/caches/search/save"/>
<returns>
The PNG image with the requested map tile.
</returns>
</xml>

File diff suppressed because it is too large Load Diff

View File

@ -21,274 +21,283 @@ use okapi\OkapiLock;
class TileTree
{
# Static flags (stored in the database).
public static $FLAG_STAR = 0x01;
public static $FLAG_HAS_TRACKABLES = 0x02;
public static $FLAG_NOT_YET_FOUND = 0x04;
# Static flags (stored in the database).
public static $FLAG_STAR = 0x01;
public static $FLAG_HAS_TRACKABLES = 0x02;
public static $FLAG_NOT_YET_FOUND = 0x04;
# Dynamic flags (added at runtime).
public static $FLAG_FOUND = 0x0100;
public static $FLAG_OWN = 0x0200;
public static $FLAG_NEW = 0x0400;
public static $FLAG_DRAW_CAPTION = 0x0800;
# Dynamic flags (added at runtime).
public static $FLAG_FOUND = 0x0100;
public static $FLAG_OWN = 0x0200;
public static $FLAG_NEW = 0x0400;
public static $FLAG_DRAW_CAPTION = 0x0800;
/**
* Return null if not computed, 1 if computed and empty, 2 if computed and not empty.
*/
public static function get_tile_status($zoom, $x, $y)
{
return Db::select_value("
select status
from okapi_tile_status
where
z = '".mysql_real_escape_string($zoom)."'
and x = '".mysql_real_escape_string($x)."'
and y = '".mysql_real_escape_string($y)."'
");
}
/**
* Return null if not computed, 1 if computed and empty, 2 if computed and not empty.
*/
public static function get_tile_status($zoom, $x, $y)
{
return Db::select_value("
select status
from okapi_tile_status
where
z = '".mysql_real_escape_string($zoom)."'
and x = '".mysql_real_escape_string($x)."'
and y = '".mysql_real_escape_string($y)."'
");
}
/**
* Return MySQL's result set iterator over all caches which are present
* in the given result set AND in the given tile.
*
* Each row is an array of the following format:
* list(cache_id, $pixel_x, $pixel_y, status, type, rating, flags, count).
*
* Note that $pixels can also be negative or >=256 (up to a margin of 32px).
* Count is the number of other caches "eclipsed" by this geocache (such
* eclipsed geocaches are not included in the result).
*/
public static function query_fast($zoom, $x, $y, $set_id)
{
# First, we check if the cache-set for this tile was already computed
# (and if it was, was it empty).
/**
* Return MySQL's result set iterator over all caches which are present
* in the given result set AND in the given tile.
*
* Each row is an array of the following format:
* list(cache_id, $pixel_x, $pixel_y, status, type, rating, flags, count).
*
* Note that $pixels can also be negative or >=256 (up to a margin of 32px).
* Count is the number of other caches "eclipsed" by this geocache (such
* eclipsed geocaches are not included in the result).
*/
public static function query_fast($zoom, $x, $y, $set_id)
{
# First, we check if the cache-set for this tile was already computed
# (and if it was, was it empty).
$status = self::get_tile_status($zoom, $x, $y);
if ($status === null) # Not yet computed.
{
# Note, that computing the tile does not involve taking any
# search parameters.
$status = self::get_tile_status($zoom, $x, $y);
if ($status === null) # Not yet computed.
{
# Note, that computing the tile does not involve taking any
# search parameters.
$status = self::compute_tile($zoom, $x, $y);
}
$status = self::compute_tile($zoom, $x, $y);
}
if ($status === 1) # Computed and empty.
{
# This tile was already computed and it is empty.
return null;
}
if ($status === 1) # Computed and empty.
{
# This tile was already computed and it is empty.
return null;
}
# If we got here, then the tile is computed and not empty (status 2).
# If we got here, then the tile is computed and not empty (status 2).
$tile_upper_x = $x << 8;
$tile_leftmost_y = $y << 8;
$tile_upper_x = $x << 8;
$tile_leftmost_y = $y << 8;
$zoom_escaped = "'".mysql_real_escape_string($zoom)."'";
$tile_upper_x_escaped = "'".mysql_real_escape_string($tile_upper_x)."'";
$tile_leftmost_y_escaped = "'".mysql_real_escape_string($tile_leftmost_y)."'";
return Db::query("
select
otc.cache_id,
cast(otc.z21x >> (21 - $zoom_escaped) as signed) - $tile_upper_x_escaped as px,
cast(otc.z21y >> (21 - $zoom_escaped) as signed) - $tile_leftmost_y_escaped as py,
otc.status, otc.type, otc.rating, otc.flags, count(*)
from
okapi_tile_caches otc,
okapi_search_results osr
where
z = $zoom_escaped
and x = '".mysql_real_escape_string($x)."'
and y = '".mysql_real_escape_string($y)."'
and otc.cache_id = osr.cache_id
and osr.set_id = '".mysql_real_escape_string($set_id)."'
group by
z21x >> (3 + (21 - $zoom_escaped)),
z21y >> (3 + (21 - $zoom_escaped))
order by
z21y >> (3 + (21 - $zoom_escaped)),
z21x >> (3 + (21 - $zoom_escaped))
");
}
$zoom_escaped = "'".mysql_real_escape_string($zoom)."'";
$tile_upper_x_escaped = "'".mysql_real_escape_string($tile_upper_x)."'";
$tile_leftmost_y_escaped = "'".mysql_real_escape_string($tile_leftmost_y)."'";
return Db::query("
select
otc.cache_id,
cast(otc.z21x >> (21 - $zoom_escaped) as signed) - $tile_upper_x_escaped as px,
cast(otc.z21y >> (21 - $zoom_escaped) as signed) - $tile_leftmost_y_escaped as py,
otc.status, otc.type, otc.rating, otc.flags, count(*)
from
okapi_tile_caches otc,
okapi_search_results osr
where
z = $zoom_escaped
and x = '".mysql_real_escape_string($x)."'
and y = '".mysql_real_escape_string($y)."'
and otc.cache_id = osr.cache_id
and osr.set_id = '".mysql_real_escape_string($set_id)."'
group by
z21x >> (3 + (21 - $zoom_escaped)),
z21y >> (3 + (21 - $zoom_escaped))
order by
z21y >> (3 + (21 - $zoom_escaped)),
z21x >> (3 + (21 - $zoom_escaped))
");
}
/**
* Precache the ($zoom, $x, $y) slot in the okapi_tile_caches table.
*/
public static function compute_tile($zoom, $x, $y)
{
$time_started = microtime(true);
/**
* 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. WRTODO: Think of some
# appropriate locks.
# Note, that multiple threads may try to compute tiles simulatanously.
# For low-level tiles, this can be expensive. WRTODO: Think of some
# appropriate locks.
$status = self::get_tile_status($zoom, $x, $y);
if ($status !== null)
return $status;
$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.
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.
# 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
$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, $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);
$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);
Db::execute("
replace into okapi_tile_caches (
z, x, y, cache_id, z21x, z21y, status, type, rating, flags
) values (
0, 0, 0,
'".mysql_real_escape_string($row[0])."',
'".mysql_real_escape_string($row[1])."',
'".mysql_real_escape_string($row[2])."',
'".mysql_real_escape_string($row[3])."',
'".mysql_real_escape_string($row[4])."',
".(($row[5] === null) ? "null" : "'".mysql_real_escape_string($row[5])."'").",
'".mysql_real_escape_string($row[6])."'
);
");
}
$status = 2;
}
else
{
# We will use the parent tile to compute the contents of this tile.
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("
replace into okapi_tile_caches (
z, x, y, cache_id, z21x, z21y, status, type, rating, flags
) values (
0, 0, 0,
'".mysql_real_escape_string($row[0])."',
'".mysql_real_escape_string($row[1])."',
'".mysql_real_escape_string($row[2])."',
'".mysql_real_escape_string($row[3])."',
'".mysql_real_escape_string($row[4])."',
".(($row[5] === null) ? "null" : "'".mysql_real_escape_string($row[5])."'").",
'".mysql_real_escape_string($row[6])."'
);
");
}
$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;
$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) # Not computed.
{
$time_started = microtime(true);
$status = self::compute_tile($parent_zoom, $parent_x, $parent_y);
}
$status = self::get_tile_status($parent_zoom, $parent_x, $parent_y);
if ($status === null) # Not computed.
{
$time_started = microtime(true);
$status = self::compute_tile($parent_zoom, $parent_x, $parent_y);
}
if ($status === 1) # Computed and empty.
{
# No need to check.
}
else # Computed, not empty.
{
$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;
if ($status === 1) # Computed and empty.
{
# No need to check.
}
else # Computed, not empty.
{
$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|
# 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;
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.
# Cache the result.
Db::execute("
replace into okapi_tile_caches (
z, x, y, cache_id, z21x, z21y, status, type, rating, flags
)
select
'".mysql_real_escape_string($zoom)."',
'".mysql_real_escape_string($x)."',
'".mysql_real_escape_string($y)."',
cache_id, z21x, z21y, status, type, rating, flags
from okapi_tile_caches
where
z = '".mysql_real_escape_string($parent_zoom)."'
and x = '".mysql_real_escape_string($parent_x)."'
and y = '".mysql_real_escape_string($parent_y)."'
and z21x between $left_z21x and $right_z21x
and z21y between $top_z21y and $bottom_z21y
");
$test = Db::select_value("
select 1
from okapi_tile_caches
where
z = '".mysql_real_escape_string($zoom)."'
and x = '".mysql_real_escape_string($x)."'
and y = '".mysql_real_escape_string($y)."'
limit 1;
");
if ($test)
$status = 2;
else
$status = 1;
}
}
Db::execute("
replace into okapi_tile_caches (
z, x, y, cache_id, z21x, z21y, status, type, rating, flags
)
select
'".mysql_real_escape_string($zoom)."',
'".mysql_real_escape_string($x)."',
'".mysql_real_escape_string($y)."',
cache_id, z21x, z21y, status, type, rating, flags
from okapi_tile_caches
where
z = '".mysql_real_escape_string($parent_zoom)."'
and x = '".mysql_real_escape_string($parent_x)."'
and y = '".mysql_real_escape_string($parent_y)."'
and z21x between $left_z21x and $right_z21x
and z21y between $top_z21y and $bottom_z21y
");
$test = Db::select_value("
select 1
from okapi_tile_caches
where
z = '".mysql_real_escape_string($zoom)."'
and x = '".mysql_real_escape_string($x)."'
and y = '".mysql_real_escape_string($y)."'
limit 1;
");
if ($test)
$status = 2;
else
$status = 1;
}
}
# Mark tile as computed.
# Mark tile as computed.
Db::execute("
replace into okapi_tile_status (z, x, y, status)
values (
'".mysql_real_escape_string($zoom)."',
'".mysql_real_escape_string($x)."',
'".mysql_real_escape_string($y)."',
'".mysql_real_escape_string($status)."'
);
");
Db::execute("
replace into okapi_tile_status (z, x, y, status)
values (
'".mysql_real_escape_string($zoom)."',
'".mysql_real_escape_string($x)."',
'".mysql_real_escape_string($y)."',
'".mysql_real_escape_string($status)."'
);
");
return $status;
}
return $status;
}
/**
* Convert OKAPI's cache object to a short database row to be inserted
* into okapi_tile_caches table. Returns the list of the following attributes:
* cache_id, z21x, z21y, status, type, rating, flags (rating might be null!).
*/
public static function generate_short_row($cache)
{
list($lat, $lon) = explode("|", $cache['location']);
list($z21x, $z21y) = self::latlon_to_z21xy($lat, $lon);
$flags = 0;
if (($cache['founds'] > 6) && (($cache['recommendations'] / $cache['founds']) > 0.3))
$flags |= self::$FLAG_STAR;
if ($cache['trackables_count'] > 0)
$flags |= self::$FLAG_HAS_TRACKABLES;
if ($cache['founds'] == 0)
$flags |= self::$FLAG_NOT_YET_FOUND;
return array($cache['internal_id'], $z21x, $z21y, Okapi::cache_status_name2id($cache['status']),
Okapi::cache_type_name2id($cache['type']), $cache['rating'], $flags);
}
/**
* Convert OKAPI's cache object to a short database row to be inserted
* into okapi_tile_caches table. Returns the list of the following attributes:
* cache_id, z21x, z21y, status, type, rating, flags (rating might be null!).
*/
public static function generate_short_row($cache)
{
list($lat, $lon) = explode("|", $cache['location']);
try {
list($z21x, $z21y) = self::latlon_to_z21xy($lat, $lon);
} catch (Exception $e) {
/* E.g. division by zero, if the cache is placed at the north pole. */
return false;
}
$flags = 0;
if (($cache['founds'] > 6) && (($cache['recommendations'] / $cache['founds']) > 0.3))
$flags |= self::$FLAG_STAR;
if ($cache['trackables_count'] > 0)
$flags |= self::$FLAG_HAS_TRACKABLES;
if ($cache['founds'] == 0)
$flags |= self::$FLAG_NOT_YET_FOUND;
return array($cache['internal_id'], $z21x, $z21y, Okapi::cache_status_name2id($cache['status']),
Okapi::cache_type_name2id($cache['type']), $cache['rating'], $flags);
}
private static function latlon_to_z21xy($lat, $lon)
{
$offset = 128 << 21;
$x = round($offset + ($offset * $lon / 180));
$y = round($offset - $offset/pi() * log((1 + sin($lat * pi() / 180)) / (1 - sin($lat * pi() / 180))) / 2);
return array($x, $y);
}
private static function latlon_to_z21xy($lat, $lon)
{
$offset = 128 << 21;
$x = round($offset + ($offset * $lon / 180));
$y = round($offset - $offset/pi() * log((1 + sin($lat * pi() / 180)) / (1 - sin($lat * pi() / 180))) / 2);
return array($x, $y);
}
}

View File

@ -17,74 +17,74 @@ use okapi\Settings;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 3
);
}
public static function options()
{
return array(
'min_auth_level' => 3
);
}
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.
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')));
$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
# 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("
insert ignore into cache_watches (cache_id, user_id)
values (
'".mysql_real_escape_string($geocache['internal_id'])."',
'".mysql_real_escape_string($request->token->user_id)."'
);
");
elseif ($tmp == 'false')
Db::execute("
delete from cache_watches
where
cache_id = '".mysql_real_escape_string($geocache['internal_id'])."'
and user_id = '".mysql_real_escape_string($request->token->user_id)."';
");
}
if ($tmp = $request->get_parameter('watched'))
{
if (!in_array($tmp, array('true', 'false', 'unchanged')))
throw new InvalidParam('watched', $tmp);
if ($tmp == 'true')
Db::execute("
insert ignore into cache_watches (cache_id, user_id)
values (
'".mysql_real_escape_string($geocache['internal_id'])."',
'".mysql_real_escape_string($request->token->user_id)."'
);
");
elseif ($tmp == 'false')
Db::execute("
delete from cache_watches
where
cache_id = '".mysql_real_escape_string($geocache['internal_id'])."'
and user_id = '".mysql_real_escape_string($request->token->user_id)."';
");
}
# ignored
# 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("
insert ignore into cache_ignore (cache_id, user_id)
values (
'".mysql_real_escape_string($geocache['internal_id'])."',
'".mysql_real_escape_string($request->token->user_id)."'
);
");
elseif ($tmp == 'false')
Db::execute("
delete from cache_ignore
where
cache_id = '".mysql_real_escape_string($geocache['internal_id'])."'
and user_id = '".mysql_real_escape_string($request->token->user_id)."'
");
}
if ($tmp = $request->get_parameter('ignored'))
{
if (!in_array($tmp, array('true', 'false', 'unchanged')))
throw new InvalidParam('ignored', $tmp);
if ($tmp == 'true')
Db::execute("
insert ignore into cache_ignore (cache_id, user_id)
values (
'".mysql_real_escape_string($geocache['internal_id'])."',
'".mysql_real_escape_string($request->token->user_id)."'
);
");
elseif ($tmp == 'false')
Db::execute("
delete from cache_ignore
where
cache_id = '".mysql_real_escape_string($geocache['internal_id'])."'
and user_id = '".mysql_real_escape_string($request->token->user_id)."'
");
}
$result = array(
'success' => true,
);
return Okapi::formatted_response($request, $result);
}
$result = array(
'success' => true,
);
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,33 +1,33 @@
<xml>
<brief>Mark cache as watched or ignored</brief>
<issue-id>166</issue-id>
<desc>
<p>This method allows your users to mark the geocache as <b>watched</b> or
<b>ignored</b>.
Read the docs on separate parameters for details.</p>
</desc>
<req name='cache_code'>
<p>Code of the geocache.</p>
</req>
<opt name='watched' default='unchanged'>
<p>Mark (or unmark) the cache as watched. This should be <b>true</b>,
<b>false</b> or <b>unchanged</b>. You may access the current state of this
flag with the <b>is_watched</b> field of the geocache method.</p>
</opt>
<opt name='ignored' default='unchanged'>
<p>Mark (or unmark) the cache as ignored. This should be <b>true</b>,
<b>false</b> or <b>unchanged</b>. You may access the current state of this
flag with the <b>is_ignored</b> field of the geocache method.</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>success</b> - true, if all went well.</li>
</ul>
<p>Please note, that currently this will <b>always</b> be true! Nothing can go
wrong as long as you pass your parameters in a right way (and if you don't,
you will get an HTTP 400 response). If you have received an HTTP 200 response,
then you may assume that all went well.</p>
</returns>
<brief>Mark cache as watched or ignored</brief>
<issue-id>166</issue-id>
<desc>
<p>This method allows your users to mark the geocache as <b>watched</b> or
<b>ignored</b>.
Read the docs on separate parameters for details.</p>
</desc>
<req name='cache_code'>
<p>Code of the geocache.</p>
</req>
<opt name='watched' default='unchanged'>
<p>Mark (or unmark) the cache as watched. This should be <b>true</b>,
<b>false</b> or <b>unchanged</b>. You may access the current state of this
flag with the <b>is_watched</b> field of the geocache method.</p>
</opt>
<opt name='ignored' default='unchanged'>
<p>Mark (or unmark) the cache as ignored. This should be <b>true</b>,
<b>false</b> or <b>unchanged</b>. You may access the current state of this
flag with the <b>is_ignored</b> field of the geocache method.</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>success</b> - true, if all went well.</li>
</ul>
<p>Please note, that currently this will <b>always</b> be true! Nothing can go
wrong as long as you pass your parameters in a right way (and if you don't,
you will get an HTTP 400 response). If you have received an HTTP 200 response,
then you may assume that all went well.</p>
</returns>
</xml>

View File

@ -20,17 +20,18 @@ require_once('searching.inc.php');
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
$search_params = SearchAssistant::get_common_search_params($request);
$result = SearchAssistant::get_common_search_result($search_params);
return Okapi::formatted_response($request, $result);
}
public static function call(OkapiRequest $request)
{
$search_assistant = new SearchAssistant($request);
$search_assistant->prepare_common_search_params();
$result = $search_assistant->get_common_search_result();
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,209 +1,209 @@
<xml>
<brief>Search for geocaches</brief>
<issue-id>15</issue-id>
<desc>
<p>Search for geocaches using some simple filters.
All of them are optional, but you surely want to use at least some of them.</p>
<ul>
<li>If you're looking for a way to keep your geocaching database in sync with ours,
have a peek at the <b>replicate</b> module.</li>
<li>Usually you will want to use search_and_retrieve method instead of this one.
This way, you can get much more data in <b>one request</b>.</li>
</ul>
</desc>
<opt name='type'>
<p>Pipe-separated list of cache type codes. If given, the results will be limited
to the given cache types. For a list of cache type codes, see services/caches/geocache
method.</p>
<p><b>Notice:</b> If you want to include all cache types <b>except</b>
given ones, prepend your list with the "-" sign.</p>
</opt>
<opt name='status' default='Available'>
<p>Pipe-separated list of status codes. Only caches matching any of the given
statuses will included in the response. For a list of cache status codes, see
services/caches/geocache method.</p>
</opt>
<opt name='owner_uuid'>
<p>Pipe-separated list of user IDs. If given, the list of returned caches
will be limited to those owned by the given users.</p>
<p><b>Notice:</b> User ID and Username are two different things!</p>
<p><b>Notice:</b> If you want to include all caches <b>except</b> the ones
owned by the given users, prepend your list with the "-" sign.</p>
</opt>
<opt name='name'>
<p>UTF-8 encoded string - the name (or part of the name) of the cache.</p>
<p>Allowed wildcard characters:</p>
<ul>
<li>asterisk ("*") will match any string of characters,</li>
<li>underscore ("_") will match any single character.</li>
</ul>
<p>Name matching is case-insensitive. Maximum length for the <b>name</b> parameter
is 100 characters.</p>
<p><b>Examples:</b></p>
<ul>
<li>"name=the *" returns caches which name starts with "the " (or "The ", etc.),</li>
<li>"name=water tower" returns caches which have <b>exactly</b> that name (case insensitive),</li>
<li>"name=*water tower*" will match caches like "Beautiful Water Tower II",</li>
<li>"name=*tower_" will match "Water Towers" but will <b>not</b> match "Water Tower".</li>
</ul>
<p><b>Notice:</b> Cache code and name are two different things - if you want to
find a cache by its code, use the services/caches/geocache method.</p>
</opt>
<opt name='terrain' default='1-5'>
<p>A string "X-Y", where X and Y are integers between 1 and 5, and X &lt;= Y.
Only caches with terrain rating between these numbers (inclusive) will be returned.</p>
</opt>
<opt name='difficulty' default='1-5'>
<p>A string "X-Y", where X and Y are integers between 1 and 5, and X &lt;= Y.
Only caches with difficulty rating between these numbers (inclusive) will be returned.</p>
</opt>
<opt name='size' class='deprecated'>
<p>Deprecated. Please use <b>size2</b> instead - this will allow you to
differentiate "nano" vs. "micro" and "none" vs. "other" sizes
(<a href='http://code.google.com/p/opencaching-api/issues/detail?id=155'>details</a>).</p>
<p>A string "X-Y", where X and Y are integers between 1 and 5, and X &lt;= Y.
Only caches with size attribute between these numbers (inclusive) will be returned
(1 - micro, 5 - very big).</p>
<p><b>Notice:</b> If you use this parameter, all caches which do not have a container
(like Event or Virtual caches) will be excluded from the results. If you want caches
with no container included, append "|X" suffix to the value of this parameter
(e.g. "3-5|X").</p>
</opt>
<opt name='size2'>
<p>Pipe-separated list of "size2 codes". Only matching caches will be
included. The codes are: 'none', 'nano', 'micro', 'small', 'regular',
'large', 'xlarge', 'other'.</p>
<p><b>Note:</b> Not all OC servers use all of these. I.e. OCPL does not
have 'nano' nor 'other' caches (but it might have them in the future).</p>
</opt>
<opt name='rating'>
<p>A string "X-Y", where X and Y are integers between 1 and 5, and X &lt;= Y.
Only caches with an overall rating between these numbers (inclusive) will be returned
(1 - poor, 5 - excellent).</p>
<p><b>Notice:</b> If you use this parameter, all caches with too few votes will be
excluded from results. If you still want unrated caches included, append "|X" suffix
to the value of this parameter (e.g. "3-5|X").</p>
<p><b>Notice:</b> Some OC installations do not provide ratings for their geocaches.
On such installation, this parameter will be ignored.</p>
</opt>
<opt name='min_rcmds'>
<p>There are two possible value-types for this argument:</p>
<ul>
<li>Integer N. If <i>N</i> given, the result will contain only those caches which have
at least <i>N</i> recommendations.</li>
<li>Integer N with a percent sign appended (e.g. "20%"). The result will contain
only those caches which have at last <i>N%</i> ratio of recommendations/founds.
Please note, that this is useful only with conjunction with <b>min_founds</b>
parameter.</li>
</ul>
</opt>
<opt name='min_founds'>
<p>Integer N. If <i>N</i> given, the result will contain only those caches which have
been found at least <i>N</i> times. Useful if you want to skip "unverified" caches.</p>
</opt>
<opt name='max_founds'>
<p>Integer N. If <i>N</i> given, the result will contain only those caches which have
been found at most <i>N</i> times. Useful for FTF hunters.</p>
</opt>
<opt name='modified_since'>
<p>A date and time string. This should be in ISO 8601 format (currently any
format acceptable by PHP's <a href='http://pl2.php.net/strtotime'>strtotime</a>
function also will do, but most of them don't handle time zones properly,
try to use ISO 8601!).</p>
<p>If given, the list of returned geocaches will be limited to those which were
created or modified since that date. (Note: currently caches do not get
"modified" when user makes a log entry.)</p>
</opt>
<opt name='found_status' default='either'>
<p><b>Notice:</b> This parameter may be used only for requests signed with an Access Token
(you will need to use <b>Level 3</b> Authentication).</p>
<p>Should be one of the following:</p>
<ul>
<li><b>found_only</b> - only caches found by the user will be returned,</li>
<li><b>notfound_only</b> - only caches <b>not</b> found by the user will be returned,</li>
<li><b>either</b> - all caches will be returned.</li>
</ul>
</opt>
<opt name='found_by'>
<p>User UUID. If given, the response will only include geocaches found by
the given user.</p>
</opt>
<opt name='not_found_by'>
<p>User UUID. If given, the response will only include geocaches not found by
the given user.</p>
</opt>
<opt name='watched_only' default='false'>
<p><b>Notice:</b> This parameter may be used only for requests signed with an Access Token
(you will need to use <b>Level 3</b> Authentication).</p>
<p>Boolean. If set to <b>true</b>, only caches which the user has marked as watched
will be included in the result. This might be used to temporarily mark geocaches
of particular interest (e.g. "all the caches I plan to find today").</p>
</opt>
<opt name='exclude_ignored' default='false'>
<p><b>Notice:</b> This parameter may be used only for requests signed
with an Access Token (you will need to use <b>Level 3</b> Authentication).</p>
<p>Boolean. If set to <b>true</b>, caches which the user has marked as ignored
will not be included in the result.</p>
</opt>
<opt name='exclude_my_own' default='false'>
<p><b>Notice:</b> This parameter may be used only for requests signed
with an Access Token (you will need to use <b>Level 3</b> Authentication).
See <b>owner_uuid</b> parameter if you don't want to use OAuth.</p>
<p>Boolean. If set to <b>true</b>, caches which the user is an owner of will
not be included in the result.</p>
</opt>
<opt name='with_trackables_only' default='false'>
Boolean. If set to <b>true</b>, only caches with at least one trackable
will be included in the result.
</opt>
<opt name='ftf_hunter' default='false'>
Boolean. If set to <b>true</b>, only caches which have not yet been
found <b>by anyone</b> will be included.
</opt>
<opt name='set_and'>
<p>ID of a set previously created with the <b>search/save</b> method.
If given, the results are <a href='http://en.wikipedia.org/wiki/Logical_conjunction'>AND</a>ed
together with this set.</p>
<p>If you want to list the set contents only, please note the default
value of the <b>status</b> parameter! You may want to override it
in order to include unavailable and/or archived geocaches within the set.</p>
</opt>
<opt name='limit' default='100'>
<p>Integer in range 1..500. Maximum number of cache codes returned.</p>
</opt>
<opt name='offset' default='0'>
<p>An offset, the amount of items to skip at the beginning of the result. Combined with limit
allows pagination of the result set. Please note, that the sum of offset and limit still cannot
exceed 500. You MAY NOT use this method to list all the caches, it is for searching only.
Have a peek at the <b>replicate</b> module if you need all the caches.</p>
</opt>
<opt name='order_by'>
<p>Pipe separated list of fields to order the results by. Prefix the field name with
a '-' sign to indicate a descending order.</p>
<p>Currently, fields which you can order by include: <b>code</b>, <b>name</b>,
<b>founds</b>, <b>rcmds</b>, <b>rcmds%</b> (tell us if you want more).</p>
<p><b>Examples:</b></p>
<ul>
<li>to order by cache name use "order_by=name" or "order_by=+name",</li>
<li>to have the most recommended caches caches in front, use "order_by=-rcmds%",</li>
<li>multicolumn sorting is also allowed, ex. "order_by=-founds|name"</li>
</ul>
<p><b>Note:</b> Try to avoid executing separate OKAPI request every time you
want to sort the data which you <b>already have</b>. Consider caching and
sorting on client side. This will speed up things for both sides.</p>
<p><b>Note:</b> For most other methods (like <b>bbox</b> and <b>nearest</b>),
the default order is <i>by the distance from the center</i>. If you supply
custom <b>order_by</b> parameter, then we will try to order by your preference
first, and then by the distance later.</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>results</b> - a list of cache codes,</li>
<li><b>more</b> - boolean, <b>true</b> means that there were more
results for your query, but they were not returned because of the
<b>limit</b> parameter.</li>
</ul>
</returns>
<brief>Search for geocaches</brief>
<issue-id>15</issue-id>
<desc>
<p>Search for geocaches using some simple filters.
All of them are optional, but you surely want to use at least some of them.</p>
<ul>
<li>If you're looking for a way to keep your geocaching database in sync with ours,
have a peek at the <b>replicate</b> module.</li>
<li>Usually you will want to use search_and_retrieve method instead of this one.
This way, you can get much more data in <b>one request</b>.</li>
</ul>
</desc>
<opt name='type'>
<p>Pipe-separated list of cache type codes. If given, the results will be limited
to the given cache types. For a list of cache type codes, see services/caches/geocache
method.</p>
<p><b>Notice:</b> If you want to include all cache types <b>except</b>
given ones, prepend your list with the "-" sign.</p>
</opt>
<opt name='status' default='Available'>
<p>Pipe-separated list of status codes. Only caches matching any of the given
statuses will included in the response. For a list of cache status codes, see
services/caches/geocache method.</p>
</opt>
<opt name='owner_uuid'>
<p>Pipe-separated list of user IDs. If given, the list of returned caches
will be limited to those owned by the given users.</p>
<p><b>Notice:</b> User ID and Username are two different things!</p>
<p><b>Notice:</b> If you want to include all caches <b>except</b> the ones
owned by the given users, prepend your list with the "-" sign.</p>
</opt>
<opt name='name'>
<p>UTF-8 encoded string - the name (or part of the name) of the cache.</p>
<p>Allowed wildcard characters:</p>
<ul>
<li>asterisk ("*") will match any string of characters,</li>
<li>underscore ("_") will match any single character.</li>
</ul>
<p>Name matching is case-insensitive. Maximum length for the <b>name</b> parameter
is 100 characters.</p>
<p><b>Examples:</b></p>
<ul>
<li>"name=the *" returns caches which name starts with "the " (or "The ", etc.),</li>
<li>"name=water tower" returns caches which have <b>exactly</b> that name (case insensitive),</li>
<li>"name=*water tower*" will match caches like "Beautiful Water Tower II",</li>
<li>"name=*tower_" will match "Water Towers" but will <b>not</b> match "Water Tower".</li>
</ul>
<p><b>Notice:</b> Cache code and name are two different things - if you want to
find a cache by its code, use the services/caches/geocache method.</p>
</opt>
<opt name='terrain' default='1-5'>
<p>A string "X-Y", where X and Y are integers between 1 and 5, and X &lt;= Y.
Only caches with terrain rating between these numbers (inclusive) will be returned.</p>
</opt>
<opt name='difficulty' default='1-5'>
<p>A string "X-Y", where X and Y are integers between 1 and 5, and X &lt;= Y.
Only caches with difficulty rating between these numbers (inclusive) will be returned.</p>
</opt>
<opt name='size' class='deprecated'>
<p>Deprecated. Please use <b>size2</b> instead - this will allow you to
differentiate "nano" vs. "micro" and "none" vs. "other" sizes
(<a href='http://code.google.com/p/opencaching-api/issues/detail?id=155'>details</a>).</p>
<p>A string "X-Y", where X and Y are integers between 1 and 5, and X &lt;= Y.
Only caches with size attribute between these numbers (inclusive) will be returned
(1 - micro, 5 - very big).</p>
<p><b>Notice:</b> If you use this parameter, all caches which do not have a container
(like Event or Virtual caches) will be excluded from the results. If you want caches
with no container included, append "|X" suffix to the value of this parameter
(e.g. "3-5|X").</p>
</opt>
<opt name='size2'>
<p>Pipe-separated list of "size2 codes". Only matching caches will be
included. The codes are: 'none', 'nano', 'micro', 'small', 'regular',
'large', 'xlarge', 'other'.</p>
<p><b>Note:</b> Not all OC servers use all of these. I.e. OCPL does not
have 'nano' nor 'other' caches (but it might have them in the future).</p>
</opt>
<opt name='rating'>
<p>A string "X-Y", where X and Y are integers between 1 and 5, and X &lt;= Y.
Only caches with an overall rating between these numbers (inclusive) will be returned
(1 - poor, 5 - excellent).</p>
<p><b>Notice:</b> If you use this parameter, all caches with too few votes will be
excluded from results. If you still want unrated caches included, append "|X" suffix
to the value of this parameter (e.g. "3-5|X").</p>
<p><b>Notice:</b> Some OC installations do not provide ratings for their geocaches.
On such installation, this parameter will be ignored.</p>
</opt>
<opt name='min_rcmds'>
<p>There are two possible value-types for this argument:</p>
<ul>
<li>Integer N. If <i>N</i> given, the result will contain only those caches which have
at least <i>N</i> recommendations.</li>
<li>Integer N with a percent sign appended (e.g. "20%"). The result will contain
only those caches which have at last <i>N%</i> ratio of recommendations/founds.
Please note, that this is useful only with conjunction with <b>min_founds</b>
parameter.</li>
</ul>
</opt>
<opt name='min_founds'>
<p>Integer N. If <i>N</i> given, the result will contain only those caches which have
been found at least <i>N</i> times. Useful if you want to skip "unverified" caches.</p>
</opt>
<opt name='max_founds'>
<p>Integer N. If <i>N</i> given, the result will contain only those caches which have
been found at most <i>N</i> times. Useful for FTF hunters.</p>
</opt>
<opt name='modified_since'>
<p>A date and time string. This should be in ISO 8601 format (currently any
format acceptable by PHP's <a href='http://pl2.php.net/strtotime'>strtotime</a>
function also will do, but most of them don't handle time zones properly,
try to use ISO 8601!).</p>
<p>If given, the list of returned geocaches will be limited to those which were
created or modified since that date. (Note: currently caches do not get
"modified" when user makes a log entry.)</p>
</opt>
<opt name='found_status' default='either'>
<p><b>Notice:</b> This parameter may be used only for requests signed with an Access Token
(you will need to use <b>Level 3</b> Authentication).</p>
<p>Should be one of the following:</p>
<ul>
<li><b>found_only</b> - only caches found by the user will be returned,</li>
<li><b>notfound_only</b> - only caches <b>not</b> found by the user will be returned,</li>
<li><b>either</b> - all caches will be returned.</li>
</ul>
</opt>
<opt name='found_by'>
<p>User UUID. If given, the response will only include geocaches found by
the given user.</p>
</opt>
<opt name='not_found_by'>
<p>User UUID. If given, the response will only include geocaches not found by
the given user.</p>
</opt>
<opt name='watched_only' default='false'>
<p><b>Notice:</b> This parameter may be used only for requests signed with an Access Token
(you will need to use <b>Level 3</b> Authentication).</p>
<p>Boolean. If set to <b>true</b>, only caches which the user has marked as watched
will be included in the result. This might be used to temporarily mark geocaches
of particular interest (e.g. "all the caches I plan to find today").</p>
</opt>
<opt name='exclude_ignored' default='false'>
<p><b>Notice:</b> This parameter may be used only for requests signed
with an Access Token (you will need to use <b>Level 3</b> Authentication).</p>
<p>Boolean. If set to <b>true</b>, caches which the user has marked as ignored
will not be included in the result.</p>
</opt>
<opt name='exclude_my_own' default='false'>
<p><b>Notice:</b> This parameter may be used only for requests signed
with an Access Token (you will need to use <b>Level 3</b> Authentication).
See <b>owner_uuid</b> parameter if you don't want to use OAuth.</p>
<p>Boolean. If set to <b>true</b>, caches which the user is an owner of will
not be included in the result.</p>
</opt>
<opt name='with_trackables_only' default='false'>
Boolean. If set to <b>true</b>, only caches with at least one trackable
will be included in the result.
</opt>
<opt name='ftf_hunter' default='false'>
Boolean. If set to <b>true</b>, only caches which have not yet been
found <b>by anyone</b> will be included.
</opt>
<opt name='set_and'>
<p>ID of a set previously created with the <b>search/save</b> method.
If given, the results are <a href='http://en.wikipedia.org/wiki/Logical_conjunction'>AND</a>ed
together with this set.</p>
<p>If you want to list the set contents only, please note the default
value of the <b>status</b> parameter! You may want to override it
in order to include unavailable and/or archived geocaches within the set.</p>
</opt>
<opt name='limit' default='100'>
<p>Integer in range 1..500. Maximum number of cache codes returned.</p>
</opt>
<opt name='offset' default='0'>
<p>An offset, the amount of items to skip at the beginning of the result. Combined with limit
allows pagination of the result set. Please note, that the sum of offset and limit still cannot
exceed 500. You MAY NOT use this method to list all the caches, it is for searching only.
Have a peek at the <b>replicate</b> module if you need all the caches.</p>
</opt>
<opt name='order_by'>
<p>Pipe separated list of fields to order the results by. Prefix the field name with
a '-' sign to indicate a descending order.</p>
<p>Currently, fields which you can order by include: <b>code</b>, <b>name</b>,
<b>founds</b>, <b>rcmds</b>, <b>rcmds%</b> (tell us if you want more).</p>
<p><b>Examples:</b></p>
<ul>
<li>to order by cache name use "order_by=name" or "order_by=+name",</li>
<li>to have the most recommended caches in front, use "order_by=-rcmds%",</li>
<li>multicolumn sorting is also allowed, ex. "order_by=-founds|name"</li>
</ul>
<p><b>Note:</b> Try to avoid executing separate OKAPI request every time you
want to sort the data which you <b>already have</b>. Consider caching and
sorting on client side. This will speed up things for both sides.</p>
<p><b>Note:</b> For most other methods (like <b>bbox</b> and <b>nearest</b>),
the default order is <i>by the distance from the center</i>. If you supply
custom <b>order_by</b> parameter, then we will try to order by your preference
first, and then by the distance later.</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>results</b> - a list of cache codes,</li>
<li><b>more</b> - boolean, <b>true</b> means that there were more
results for your query, but they were not returned because of the
<b>limit</b> parameter.</li>
</ul>
</returns>
</xml>

View File

@ -12,76 +12,83 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
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.
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.");
$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.
# Construct SQL conditions for the specified bounding box.
$where_conds = array();
$where_conds[] = "caches.latitude between '".mysql_real_escape_string($bbsouth)."' and '".mysql_real_escape_string($bbnorth)."'";
if ($bbeast > $bbwest)
{
# Easy one.
$where_conds[] = "caches.longitude between '".mysql_real_escape_string($bbwest)."' and '".mysql_real_escape_string($bbeast)."'";
}
else
{
# We'll have to assume that this box goes through the 180-degree meridian.
# For example, $bbwest = 179 and $bbeast = -179.
$where_conds[] = "(caches.longitude > '".mysql_real_escape_string($bbwest)."' or caches.longitude < '".mysql_real_escape_string($bbeast)."')";
}
$search_assistant = new SearchAssistant($request);
$search_assistant->prepare_common_search_params();
$search_assistant->prepare_location_search_params();
#
# 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.
#
$where_conds = array();
$where_conds[] = $search_assistant->get_latitude_expr()." between '".mysql_real_escape_string($bbsouth)."' and '".mysql_real_escape_string($bbnorth)."'";
if ($bbeast > $bbwest)
{
# Easy one.
$where_conds[] = $search_assistant->get_longitude_expr()." between '".mysql_real_escape_string($bbwest)."' and '".mysql_real_escape_string($bbeast)."'";
}
else
{
# We'll have to assume that this box goes through the 180-degree meridian.
# For example, $bbwest = 179 and $bbeast = -179.
$where_conds[] = "(".$search_assistant->get_longitude_expr()." > '".mysql_real_escape_string($bbwest)
."' or ".$search_assistant->get_longitude_expr()." < '".mysql_real_escape_string($bbeast)."')";
}
$center_lat = ($bbsouth + $bbnorth) / 2.0;
$center_lon = ($bbwest + $bbeast) / 2.0;
#
# 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.
#
$search_params = SearchAssistant::get_common_search_params($request);
$search_params['where_conds'] = array_merge($where_conds, $search_params['where_conds']);
$search_params['order_by'][] = Okapi::get_distance_sql($center_lat, $center_lon,
"caches.latitude", "caches.longitude"); # not replaced; added to the end!
$center_lat = ($bbsouth + $bbnorth) / 2.0;
$center_lon = ($bbwest + $bbeast) / 2.0;
$result = SearchAssistant::get_common_search_result($search_params);
$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);
return Okapi::formatted_response($request, $result);
}
$result = $search_assistant->get_common_search_result();
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,34 +1,38 @@
<xml>
<brief>Search for caches within specified bounding box</brief>
<issue-id>16</issue-id>
<desc>
<p>This method is similar to the search/all method, but the results
are restricted to the caches situated within a given bounding box
(a rectangle on the map).</p>
<p>Unless overriden, results are ordered by the distance from the center of the bounding
box. This means, that if you hit the limit of geocaches
returned, you will receive the ones that are in the middle of your
box, and miss the ones on the edges.</p>
<p>Usually you will want to use search_and_retrieve method instead of this one.
This way, you can get much more data in <b>one request</b>.</p>
</desc>
<req name='bbox'>
<p>The bounding box within to search for caches. The box is defined
by a string in "<b>S|W|N|E</b>" format, where:</p>
<ul>
<li><b>S</b> stands for southern edge latitude of the box,</li>
<li><b>W</b> stands for western edge longitude of the box,</li>
<li><b>N</b> stands for northern edge latitude of the box,</li>
<li><b>E</b> stands for eastern edge longitude of the box.</li>
</ul>
<p>Use positive numbers for latitudes in the northern hemisphere and
longitudes in the eastern hemisphere (and negative for southern and
western hemispheres accordingly). These are full degrees with a dot
as a decimal point (ex. "48.7|15.8|54|24.9").</p>
</req>
<import-params method='services/caches/search/all'/>
<common-format-params/>
<returns>
<p>Same format as in the search/all method.</p>
</returns>
<brief>Search for caches within specified bounding box</brief>
<issue-id>16</issue-id>
<desc>
<p>This method is similar to the search/all method, but the results
are restricted to the caches situated within a given bounding box
(a rectangle on the map).</p>
<p>Unless overriden, results are ordered by the distance from the center of the bounding
box. This means, that if you hit the limit of geocaches
returned, you will receive the ones that are in the middle of your
box, and miss the ones on the edges.</p>
<p>Usually you will want to use search_and_retrieve method instead of this one.
This way, you can get much more data in <b>one request</b>.</p>
</desc>
<req name='bbox'>
<p>The bounding box within to search for caches. The box is defined
by a string in "<b>S|W|N|E</b>" format, where:</p>
<ul>
<li><b>S</b> stands for southern edge latitude of the box,</li>
<li><b>W</b> stands for western edge longitude of the box,</li>
<li><b>N</b> stands for northern edge latitude of the box,</li>
<li><b>E</b> stands for eastern edge longitude of the box.</li>
</ul>
<p>Use positive numbers for latitudes in the northern hemisphere and
longitudes in the eastern hemisphere (and negative for southern and
western hemispheres accordingly). These are full degrees with a dot
as a decimal point (ex. "48.7|15.8|54|24.9").</p>
</req>
<opt name='location_source' default='default-coords'>
Same as in the <a href="%OKAPI:methodargref:services/caches/search/nearest#location_source%">
services/caches/search/nearest</a> method.
</opt>
<import-params method='services/caches/search/all'/>
<common-format-params/>
<returns>
<p>Same format as in the search/all method.</p>
</returns>
</xml>

View File

@ -13,165 +13,165 @@ use okapi\Db;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
/**
* Returns one of: array('cache_code', 'OPXXXX'), array('internal_id', '12345'),
* array('uuid', 'A408C3...') or null.
*/
private static function get_cache_key($url)
{
# Determine our own domain.
/**
* Returns one of: array('cache_code', 'OPXXXX'), array('internal_id', '12345'),
* array('uuid', 'A408C3...') or null.
*/
private static function get_cache_key($url)
{
# Determine our own domain.
static $host = null;
static $length = null;
if ($host == null)
{
$host = parse_url(Settings::get('SITE_URL'), PHP_URL_HOST);
if (strpos($host, "www.") === 0)
$host = substr($host, 4);
$length = strlen($host);
}
static $host = null;
static $length = null;
if ($host == null)
{
$host = parse_url(Settings::get('SITE_URL'), PHP_URL_HOST);
if (strpos($host, "www.") === 0)
$host = substr($host, 4);
$length = strlen($host);
}
# Parse the URL
# Parse the URL
$uri = parse_url($url);
if ($uri == false)
return null;
if ((!isset($uri['scheme'])) || (!in_array($uri['scheme'], array('http', 'https'))))
return null;
if ((!isset($uri['host'])) || (substr($uri['host'], -$length) != $host))
return null;
if (!isset($uri['path']))
return null;
if (preg_match("#^/(O[A-Z][A-Z0-9]{4,5})$#", $uri['path'], $matches))
{
# Some servers allow "http://oc.xx/<cache_code>" shortcut.
return array('cache_code', $matches[1]);
}
$parts = array();
if (isset($uri['query']))
$parts = array_merge($parts, explode('&', $uri['query']));
if (isset($uri['fragment']))
$parts = array_merge($parts, explode('&', $uri['fragment']));
foreach ($parts as $param)
{
$item = explode('=', $param, 2);
if (count($item) != 2)
continue;
$key = $item[0];
$value = $item[1];
if ($key == 'wp')
return array('cache_code', $value);
if ($key == 'cacheid')
return array('internal_id', $value);
if ($key == 'uuid')
return array('uuid', $value);
}
return null;
}
$uri = parse_url($url);
if ($uri == false)
return null;
if ((!isset($uri['scheme'])) || (!in_array($uri['scheme'], array('http', 'https'))))
return null;
if ((!isset($uri['host'])) || (substr($uri['host'], -$length) != $host))
return null;
if (!isset($uri['path']))
return null;
if (preg_match("#^/(O[A-Z][A-Z0-9]{4,5})$#", $uri['path'], $matches))
{
# Some servers allow "http://oc.xx/<cache_code>" shortcut.
return array('cache_code', $matches[1]);
}
$parts = array();
if (isset($uri['query']))
$parts = array_merge($parts, explode('&', $uri['query']));
if (isset($uri['fragment']))
$parts = array_merge($parts, explode('&', $uri['fragment']));
foreach ($parts as $param)
{
$item = explode('=', $param, 2);
if (count($item) != 2)
continue;
$key = $item[0];
$value = $item[1];
if ($key == 'wp')
return array('cache_code', $value);
if ($key == 'cacheid')
return array('internal_id', $value);
if ($key == 'uuid')
return array('uuid', $value);
}
return null;
}
public static function call(OkapiRequest $request)
{
# Retrieve the list of URLs to check.
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');
$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.
# 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;
}
$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.
# Include 'cache_code' references.
foreach ($urls_with['cache_code'] as $url => $cache_code)
$results[$url] = $cache_code;
foreach ($urls_with['cache_code'] as $url => $cache_code)
$results[$url] = $cache_code;
# Include 'internal_id' references.
# Include 'internal_id' references.
$internal_ids = array_values($urls_with['internal_id']);
if (count($internal_ids) > 0)
{
$rs = Db::query("
select cache_id, wp_oc
from caches
where
cache_id in ('".implode("','", array_map('mysql_real_escape_string', $internal_ids))."')
and status in (1,2,3)
");
$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;
}
}
$internal_ids = array_values($urls_with['internal_id']);
if (count($internal_ids) > 0)
{
$rs = Db::query("
select cache_id, wp_oc
from caches
where
cache_id in ('".implode("','", array_map('mysql_real_escape_string', $internal_ids))."')
and status in (1,2,3)
");
$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.
# Include 'uuid' references.
$uuids = array_values($urls_with['uuid']);
if (count($uuids) > 0)
{
$rs = Db::query("
select uuid, wp_oc
from caches
where
uuid in ('".implode("','", array_map('mysql_real_escape_string', $uuids))."')
and status in (1,2,3)
");
$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;
}
}
$uuids = array_values($urls_with['uuid']);
if (count($uuids) > 0)
{
$rs = Db::query("
select uuid, wp_oc
from caches
where
uuid in ('".implode("','", array_map('mysql_real_escape_string', $uuids))."')
and status in (1,2,3)
");
$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.
# 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);
}
}
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);
}
}
}

View File

@ -1,32 +1,32 @@
<xml>
<brief>Resolve cache references from given URLs</brief>
<issue-id>116</issue-id>
<desc>
<p>Given a set of URLs, determine codes of geocaches referenced within them.</p>
<p>This might be useful if you have a link to an Opencaching geocache page, but you're
not able to extract cache code from such link (and you need the cache code to operate
on a cache using OKAPI). Geocache pages may be referenced using various types of URLs,
only some of them contain the cache code.</p>
</desc>
<req name='urls'>
<p>Pipe-separated list of URLs. No more than 500.</p>
</req>
<opt name='as_dict' default='false'>
<p>If <b>false</b>, then the result of this method will be compatible with
search/all method (and therefore can be used with search_and_retrieve).</p>
<p>If <b>true</b>, then the result will be a dictionary. Your URLs will be
mapped to the keys of this dictionary. Each value will be either a string
(cache code) or null (if no cache code was found for the given URL).</p>
</opt>
<common-format-params/>
<returns>
<p>As described in the <b>as_dict</b> parameter.</p>
<p>Examples:</p>
<p>For <i>by_urls?urls=url1|url2|url3</i>
query, the result might look something link this:</p>
<pre>{"results": ["OP28F9"]}</pre>
<p>For <i>by_urls?urls=url1|url2|url3&amp;as_dict=true</i>
query, the result might look something link this:</p>
<pre>{"url1": "OP28F9", "url2": null, "url3": "OP28F9"}</pre>
</returns>
<brief>Resolve cache references from given URLs</brief>
<issue-id>116</issue-id>
<desc>
<p>Given a set of URLs, determine codes of geocaches referenced within them.</p>
<p>This might be useful if you have a link to an Opencaching geocache page, but you're
not able to extract cache code from such link (and you need the cache code to operate
on a cache using OKAPI). Geocache pages may be referenced using various types of URLs,
only some of them contain the cache code.</p>
</desc>
<req name='urls'>
<p>Pipe-separated list of URLs. No more than 500.</p>
</req>
<opt name='as_dict' default='false'>
<p>If <b>false</b>, then the result of this method will be compatible with
search/all method (and therefore can be used with search_and_retrieve).</p>
<p>If <b>true</b>, then the result will be a dictionary. Your URLs will be
mapped to the keys of this dictionary. Each value will be either a string
(cache code) or null (if no cache code was found for the given URL).</p>
</opt>
<common-format-params/>
<returns>
<p>As described in the <b>as_dict</b> parameter.</p>
<p>Examples:</p>
<p>For <i>by_urls?urls=url1|url2|url3</i>
query, the result might look something link this:</p>
<pre>{"results": ["OP28F9"]}</pre>
<p>For <i>by_urls?urls=url1|url2|url3&amp;as_dict=true</i>
query, the result might look something link this:</p>
<pre>{"url1": "OP28F9", "url2": null, "url3": "OP28F9"}</pre>
</returns>
</xml>

View File

@ -12,75 +12,82 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
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.
public static function call(OkapiRequest $request)
{
# You may wonder, why there are no parameters like "bbox" or "center" in the
# "search/all" method. This is *intentional* and should be kept this way.
# Such parameters would fall in conflict with each other and - in result -
# make the documentation very fuzzy. That's why they were intentionally
# left out of the "search/all" method, and put in separate (individual) ones.
# It's much easier to grasp their meaning this way.
$tmp = $request->get_parameter('center');
if (!$tmp)
throw new ParamMissing('center');
$parts = explode('|', $tmp);
if (count($parts) != 2)
throw new InvalidParam('center', "Expecting 2 pipe-separated parts, got ".count($parts).".");
foreach ($parts as &$part_ref)
{
if (!preg_match("/^-?[0-9]+(\.?[0-9]*)$/", $part_ref))
throw new InvalidParam('center', "'$part_ref' is not a valid float number.");
$part_ref = floatval($part_ref);
}
list($center_lat, $center_lon) = $parts;
if ($center_lat > 90 || $center_lat < -90)
throw new InvalidParam('center', "Latitudes have to be within -90..90 range.");
if ($center_lon > 180 || $center_lon < -180)
throw new InvalidParam('center', "Longitudes have to be within -180..180 range.");
$tmp = $request->get_parameter('center');
if (!$tmp)
throw new ParamMissing('center');
$parts = explode('|', $tmp);
if (count($parts) != 2)
throw new InvalidParam('center', "Expecting 2 pipe-separated parts, got ".count($parts).".");
foreach ($parts as &$part_ref)
{
if (!preg_match("/^-?[0-9]+(\.?[0-9]*)$/", $part_ref))
throw new InvalidParam('center', "'$part_ref' is not a valid float number.");
$part_ref = floatval($part_ref);
}
list($center_lat, $center_lon) = $parts;
if ($center_lat > 90 || $center_lat < -90)
throw new InvalidParam('center', "Latitudes have to be within -90..90 range.");
if ($center_lon > 180 || $center_lon < -180)
throw new InvalidParam('center', "Longitudes have to be within -180..180 range.");
#
# In the method description, we promised to return caches ordered by the *rough*
# distance from the center point. We'll use ORDER BY with a simplified distance
# formula and combine it with the LIMIT clause to get the best results.
#
#
# In the method description, we promised to return caches ordered by the *rough*
# distance from the center point. We'll use ORDER BY with a simplified distance
# formula and combine it with the LIMIT clause to get the best results.
#
$distance_formula = Okapi::get_distance_sql($center_lat, $center_lon, "caches.latitude", "caches.longitude");
$search_assistant = new SearchAssistant($request);
$search_assistant->prepare_common_search_params();
$search_assistant->prepare_location_search_params();
$distance_formula = Okapi::get_distance_sql(
$center_lat, $center_lon,
$search_assistant->get_latitude_expr(), $search_assistant->get_longitude_expr()
);
# 'radius' parameter is optional. If not given, we'll have to calculate the
# distance for every cache in the database.
# 'radius' parameter is optional. If not given, we'll have to calculate the
# distance for every cache in the database.
$where_conds = array();
$radius = null;
if ($tmp = $request->get_parameter('radius'))
{
if (!preg_match("/^-?[0-9]+(\.?[0-9]*)$/", $tmp))
throw new InvalidParam('radius', "'$tmp' is not a valid float number.");
$radius = floatval($tmp);
if ($radius <= 0)
throw new InvalidParam('radius', "Has to be a positive number.");
$radius *= 1000; # this one is given in kilemeters, converting to meters!
$where_conds[] = "$distance_formula <= '".mysql_real_escape_string($radius)."'";
}
$where_conds = array();
$radius = null;
if ($tmp = $request->get_parameter('radius'))
{
if (!preg_match("/^-?[0-9]+(\.?[0-9]*)$/", $tmp))
throw new InvalidParam('radius', "'$tmp' is not a valid float number.");
$radius = floatval($tmp);
if ($radius <= 0)
throw new InvalidParam('radius', "Has to be a positive number.");
$radius *= 1000; # this one is given in kilemeters, converting to meters!
$where_conds[] = "$distance_formula <= '".mysql_real_escape_string($radius)."'";
}
$search_params = SearchAssistant::get_common_search_params($request);
$search_params['where_conds'] = array_merge($where_conds, $search_params['where_conds']);
$search_params['order_by'][] = $distance_formula; # not replaced; added to the end!
$search_params = $search_assistant->get_search_params();
$search_params['where_conds'] = array_merge($where_conds, $search_params['where_conds']);
$search_params['order_by'][] = $distance_formula; # not replaced; added to the end!
$search_assistant->set_search_params($search_params);
$result = SearchAssistant::get_common_search_result($search_params);
if ($radius == null)
{
# 'more' is meaningless in this case, we'll remove it.
unset($result['more']);
}
$result = $search_assistant->get_common_search_result();
if ($radius == null)
{
# 'more' is meaningless in this case, we'll remove it.
unset($result['more']);
}
return Okapi::formatted_response($request, $result);
}
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,30 +1,44 @@
<xml>
<brief>Search for nearest geocaches</brief>
<issue-id>17</issue-id>
<desc>
<p>Find the nearest geocaches. Unless overriden, results are ordered by the distance from
the given center point.</p>
<p>Usually you will want to use search_and_retrieve method instead of this one.
This way, you can get much more data in <b>one request</b>.</p>
</desc>
<req name='center'>
<p>The center point (typically - the user's location), in the
"lat|lon" format.</p>
<p>Use positive numbers for latitudes in the northern hemisphere and
longitudes in the eastern hemisphere (and negative for southern and
western hemispheres accordingly). These are full degrees with a dot
as a decimal point (ex. "54.3|22.3").</p>
</req>
<opt name='radius'>
<p>Maximal distance (from the center point) for the cache to
be included in the results. Unlike in most other places, this
distance is given <b>in kilometers</b> instead of meters
(it can contain a floating point though).</p>
</opt>
<import-params method='services/caches/search/all'/>
<common-format-params/>
<returns>
<p>Same format as in the search/all method. The <b>more</b> key will
be skipped when no <b>radius</b> argument is given.</p>
</returns>
<brief>Search for nearest geocaches</brief>
<issue-id>17</issue-id>
<desc>
<p>Find the nearest geocaches. Unless overriden, results are ordered by the distance from
the given center point.</p>
<p>Usually you will want to use search_and_retrieve method instead of this one.
This way, you can get much more data in <b>one request</b>.</p>
</desc>
<req name='center'>
<p>The center point (typically - the user's location), in the
"lat|lon" format.</p>
<p>Use positive numbers for latitudes in the northern hemisphere and
longitudes in the eastern hemisphere (and negative for southern and
western hemispheres accordingly). These are full degrees with a dot
as a decimal point (ex. "54.3|22.3").</p>
</req>
<opt name='radius'>
<p>Maximal distance (from the center point) for the cache to
be included in the results. Unlike in most other places, this
distance is given <b>in kilometers</b> instead of meters
(it can contain a floating point though).</p>
</opt>
<opt name='location_source' default='default-coords'>
<p>In general, this parameter should take the same value as in the
<a href="%OKAPI:methodargref:services/caches/formatters/gpx#location_source%">
services/caches/formatters/gpx</a> method, but currently <u>only two values are
supported</u>: <b>default-coords</b> and <b>alt_wpt:user-coords</b>.</p>
<p>Allows you to search among alternate locations of the geocache,
instead of the default one. Particularily useful with <b>alt_wpt:user-coords</b>
alternate waypoint.</p>
<p>Please note, that if you plan on using this option in conjunction
with <b>search_and_retrieve</b> method, then you'd probably want to use
the same option in your <i>retr_method</i> too (if available).</p>
</opt>
<import-params method='services/caches/search/all'/>
<common-format-params/>
<returns>
<p>Same format as in the search/all method. The <b>more</b> key will
be skipped when no <b>radius</b> argument is given.</p>
</returns>
</xml>

View File

@ -14,169 +14,171 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
/**
* Get [set_id, date_created, expires] for the given params_hash
* (or [null, null, null] if not found).
*/
private static function find_param_set($params_hash, $ref_max_age)
{
$tmp = Db::select_row("
select
id as id,
unix_timestamp(date_created) as date_created,
unix_timestamp(expires) as expires
from okapi_search_sets
where
params_hash = '".mysql_real_escape_string($params_hash)."'
and date_add(date_created, interval '".mysql_real_escape_string($ref_max_age)."' second) > now()
order by id desc
limit 1
");
if ($tmp === null)
return array(null, null, null);
return array($tmp['id'], $tmp['date_created'], $tmp['expires']);
}
/**
* Get [set_id, date_created, expires] for the given params_hash
* (or [null, null, null] if not found).
*/
private static function find_param_set($params_hash, $ref_max_age)
{
$tmp = Db::select_row("
select
id as id,
unix_timestamp(date_created) as date_created,
unix_timestamp(expires) as expires
from okapi_search_sets
where
params_hash = '".mysql_real_escape_string($params_hash)."'
and date_add(date_created, interval '".mysql_real_escape_string($ref_max_age)."' second) > now()
order by id desc
limit 1
");
if ($tmp === null)
return array(null, null, null);
return array($tmp['id'], $tmp['date_created'], $tmp['expires']);
}
public static function call(OkapiRequest $request)
{
# "Cache control" parameters.
public static function call(OkapiRequest $request)
{
# "Cache control" parameters.
$tmp = $request->get_parameter('min_store');
if ($tmp === null) $tmp = "300";
$min_store = intval($tmp);
if (("$min_store" !== $tmp) ||($min_store < 0) || ($min_store > 64800))
throw new InvalidParam('min_store', "Has to be in the 0..64800 range.");
$tmp = $request->get_parameter('min_store');
if ($tmp === null) $tmp = "300";
$min_store = intval($tmp);
if (("$min_store" !== $tmp) ||($min_store < 0) || ($min_store > 64800))
throw new InvalidParam('min_store', "Has to be in the 0..64800 range.");
$tmp = $request->get_parameter('ref_max_age');
if ($tmp === null) $tmp = "300";
if ($tmp == "nolimit") $tmp = "9999999";
$ref_max_age = intval($tmp);
if (("$ref_max_age" !== $tmp) || ($ref_max_age < 300))
throw new InvalidParam('ref_max_age', "Has to be >=300.");
$tmp = $request->get_parameter('ref_max_age');
if ($tmp === null) $tmp = "300";
if ($tmp == "nolimit") $tmp = "9999999";
$ref_max_age = intval($tmp);
if (("$ref_max_age" !== $tmp) || ($ref_max_age < 300))
throw new InvalidParam('ref_max_age', "Has to be >=300.");
# Search params.
# Search params.
$search_params = SearchAssistant::get_common_search_params($request);
$tables = array_merge(
array('caches'),
$search_params['extra_tables']
);
$where_conds = array_merge(
array('caches.wp_oc is not null'),
$search_params['where_conds']
);
unset($search_params);
$search_assistant = new SearchAssistant($request);
$search_assistant->prepare_common_search_params();
$search_params = $search_assistant->get_search_params();
$tables = array_merge(
array('caches'),
$search_params['extra_tables']
);
$where_conds = array_merge(
array('caches.wp_oc is not null'),
$search_params['where_conds']
);
unset($search_params);
# Generate, or retrieve an existing set, and return the result.
# All user-supplied data in $tables and $where_conds MUST be escaped!
# Generate, or retrieve an existing set, and return the result.
# All user-supplied data in $tables and $where_conds MUST be escaped!
$result = self::get_set($tables, $where_conds, $min_store, $ref_max_age);
return Okapi::formatted_response($request, $result);
}
$result = self::get_set($tables, $where_conds, $min_store, $ref_max_age);
return Okapi::formatted_response($request, $result);
}
/**
* Important: YOU HAVE TO make sure $tables and $where_conds don't contain
* unescaped user-supplied data!
*/
public static function get_set($tables, $where_conds, $min_store, $ref_max_age)
{
# Compute the "params hash".
/**
* Important: YOU HAVE TO make sure $tables and $where_conds don't contain
* unescaped user-supplied data!
*/
public static function get_set($tables, $where_conds, $min_store, $ref_max_age)
{
# Compute the "params hash".
$params_hash = md5(serialize(array($tables, $where_conds)));
$params_hash = md5(serialize(array($tables, $where_conds)));
# Check if there exists an entry for this hash, which also meets the
# given freshness criteria.
# 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.
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();
$lock = OkapiLock::get("search-results-writer");
$lock->acquire();
try
{
# Make sure we were the first to acquire the lock.
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.
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("
insert into okapi_search_sets (params_hash, date_created, expires)
values (
'processing in progress',
now(),
date_add(now(), interval '".mysql_real_escape_string($min_store + 60)."' second)
)
");
$set_id = Db::last_insert_id();
$date_created = time();
$expires = $date_created + $min_store + 60;
Db::execute("
insert into okapi_search_results (set_id, cache_id)
select distinct
'".mysql_real_escape_string($set_id)."',
caches.cache_id
from ".implode(", ", $tables)."
where (".implode(") and (", $where_conds).")
");
Db::execute("
insert into okapi_search_sets (params_hash, date_created, expires)
values (
'processing in progress',
now(),
date_add(now(), interval '".mysql_real_escape_string($min_store + 60)."' second)
)
");
$set_id = Db::last_insert_id();
$date_created = time();
$expires = $date_created + $min_store + 60;
Db::execute("
insert into okapi_search_results (set_id, cache_id)
select distinct
'".mysql_real_escape_string($set_id)."',
caches.cache_id
from ".implode(", ", $tables)."
where (".implode(") and (", $where_conds).")
");
# Lock barrier, to make sure the data is visible by other
# sessions. See http://bugs.mysql.com/bug.php?id=36618
# 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("lock table okapi_search_results write");
Db::execute("unlock tables");
Db::execute("
update okapi_search_sets
set params_hash = '".mysql_real_escape_string($params_hash)."'
where id = '".mysql_real_escape_string($set_id)."'
");
} 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.
Db::execute("
update okapi_search_sets
set params_hash = '".mysql_real_escape_string($params_hash)."'
where id = '".mysql_real_escape_string($set_id)."'
");
} 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;
}
}
$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 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("
update okapi_search_sets
set expires = date_add(now(), interval '".mysql_real_escape_string($min_store + 60)."' second)
where id = '".mysql_real_escape_string($set_id)."'
");
}
if (time() + $min_store > $expires)
{
Db::execute("
update okapi_search_sets
set expires = date_add(now(), interval '".mysql_real_escape_string($min_store + 60)."' second)
where id = '".mysql_real_escape_string($set_id)."'
");
}
return array(
'set_id' => "$set_id",
'generated_at' => date('c', $date_created),
'expires' => date('c', $expires),
);
}
return array(
'set_id' => "$set_id",
'generated_at' => date('c', $date_created),
'expires' => date('c', $expires),
);
}
}

View File

@ -1,36 +1,36 @@
<xml>
<brief>Save a search result set</brief>
<issue-id>163</issue-id>
<desc>
<p>This works similar to the <b>search/all</b> method, but the returned
set of geocaches is temporarilly stored, instead of being directly
returned to you.</p>
<p>You may want to use this method when you don't want your search
results modified while the user is browsing through them, page by page.
To view a portion of a saved search, use the <b>search/all</b>
method with proper <b>set_and</b> <u>and <b>status</b></u> (!)
parameters.</p>
</desc>
<opt name='min_store' default="300">
The amount of time (in seconds) after which you allow OKAPI to delete
the set (OKAPI <b>may</b> remove it, but doesn't have to).
The maximum allowed value is 64800 (18 hours).
</opt>
<opt name='ref_max_age' default="300">
<p>If OKAPI finds an existing result set which was created for your
search query, it may return the ID of this existing set (and possibly
extend its lifetime so it fits your <b>min_store</b>). What is the
maximum age of the existing result set which you are willing to accept?</p>
<p>This should be an integer (in seconds) <b>or</b> a special
<b>nolimit</b> value. It must be greater or equal to 300 (5 minutes).</p>
</opt>
<import-params method='services/caches/search/all' except="offset|limit|order_by"/>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>set_id</b> - string, the identifier of your saved set,
for future reference.</li>
</ul>
</returns>
<brief>Save a search result set</brief>
<issue-id>163</issue-id>
<desc>
<p>This works similar to the <b>search/all</b> method, but the returned
set of geocaches is temporarilly stored, instead of being directly
returned to you.</p>
<p>You may want to use this method when you don't want your search
results modified while the user is browsing through them, page by page.
To view a portion of a saved search, use the <b>search/all</b>
method with proper <b>set_and</b> <u>and <b>status</b></u> (!)
parameters.</p>
</desc>
<opt name='min_store' default="300">
The amount of time (in seconds) after which you allow OKAPI to delete
the set (OKAPI <b>may</b> remove it, but doesn't have to).
The maximum allowed value is 64800 (18 hours).
</opt>
<opt name='ref_max_age' default="300">
<p>If OKAPI finds an existing result set which was created for your
search query, it may return the ID of this existing set (and possibly
extend its lifetime so it fits your <b>min_store</b>). What is the
maximum age of the existing result set which you are willing to accept?</p>
<p>This should be an integer (in seconds) <b>or</b> a special
<b>nolimit</b> value. It must be greater or equal to 300 (5 minutes).</p>
</opt>
<import-params method='services/caches/search/all' except="offset|limit|order_by"/>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>set_id</b> - string, the identifier of your saved set,
for future reference.</li>
</ul>
</returns>
</xml>

File diff suppressed because it is too large Load Diff

View File

@ -13,109 +13,109 @@ use okapi\BadRequest;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
# Check search method
$search_method = $request->get_parameter('search_method');
if (!$search_method)
throw new ParamMissing('search_method');
if (strpos($search_method, "services/caches/search/") !== 0)
throw new InvalidParam('search_method', "Should begin with 'services/caches/search/'.");
if (!OkapiServiceRunner::exists($search_method))
throw new InvalidParam('search_method', "Method does not exist: '$search_method'");
$search_params = $request->get_parameter('search_params');
if (!$search_params)
throw new ParamMissing('search_params');
$search_params = json_decode($search_params, true);
if (!is_array($search_params))
throw new InvalidParam('search_params', "Should be a JSON-encoded dictionary");
public static function call(OkapiRequest $request)
{
# Check search method
$search_method = $request->get_parameter('search_method');
if (!$search_method)
throw new ParamMissing('search_method');
if (strpos($search_method, "services/caches/search/") !== 0)
throw new InvalidParam('search_method', "Should begin with 'services/caches/search/'.");
if (!OkapiServiceRunner::exists($search_method))
throw new InvalidParam('search_method', "Method does not exist: '$search_method'");
$search_params = $request->get_parameter('search_params');
if (!$search_params)
throw new ParamMissing('search_params');
$search_params = json_decode($search_params, true);
if (!is_array($search_params))
throw new InvalidParam('search_params', "Should be a JSON-encoded dictionary");
# Check retrieval method
$retr_method = $request->get_parameter('retr_method');
if (!$retr_method)
throw new ParamMissing('retr_method');
if (!OkapiServiceRunner::exists($retr_method))
throw new InvalidParam('retr_method', "Method does not exist: '$retr_method'");
$retr_params = $request->get_parameter('retr_params');
if (!$retr_params)
throw new ParamMissing('retr_params');
$retr_params = json_decode($retr_params, true);
if (!is_array($retr_params))
throw new InvalidParam('retr_params', "Should be a JSON-encoded dictionary");
# Check retrieval method
$retr_method = $request->get_parameter('retr_method');
if (!$retr_method)
throw new ParamMissing('retr_method');
if (!OkapiServiceRunner::exists($retr_method))
throw new InvalidParam('retr_method', "Method does not exist: '$retr_method'");
$retr_params = $request->get_parameter('retr_params');
if (!$retr_params)
throw new ParamMissing('retr_params');
$retr_params = json_decode($retr_params, true);
if (!is_array($retr_params))
throw new InvalidParam('retr_params', "Should be a JSON-encoded dictionary");
self::map_values_to_strings($search_params);
self::map_values_to_strings($retr_params);
self::map_values_to_strings($search_params);
self::map_values_to_strings($retr_params);
# Wrapped?
$wrap = $request->get_parameter('wrap');
if ($wrap == null) throw new ParamMissing('wrap');
if (!in_array($wrap, array('true', 'false')))
throw new InvalidParam('wrap');
$wrap = ($wrap == 'true');
# Wrapped?
$wrap = $request->get_parameter('wrap');
if ($wrap == null) throw new ParamMissing('wrap');
if (!in_array($wrap, array('true', 'false')))
throw new InvalidParam('wrap');
$wrap = ($wrap == 'true');
# Run search method
try
{
$search_result = OkapiServiceRunner::call($search_method, new OkapiInternalRequest(
$request->consumer, $request->token, $search_params));
}
catch (BadRequest $e)
{
throw new InvalidParam('search_params', "Search method responded with the ".
"following error message: ".$e->getMessage());
}
# Run search method
try
{
$search_result = OkapiServiceRunner::call($search_method, new OkapiInternalRequest(
$request->consumer, $request->token, $search_params));
}
catch (BadRequest $e)
{
throw new InvalidParam('search_params', "Search method responded with the ".
"following error message: ".$e->getMessage());
}
# Run retrieval method
try
{
$retr_result = OkapiServiceRunner::call($retr_method, new OkapiInternalRequest(
$request->consumer, $request->token, array_merge($retr_params,
array('cache_codes' => implode("|", $search_result['results'])))));
}
catch (BadRequest $e)
{
throw new InvalidParam('retr_params', "Retrieval method responded with the ".
"following error message: ".$e->getMessage());
}
# Run retrieval method
try
{
$retr_result = OkapiServiceRunner::call($retr_method, new OkapiInternalRequest(
$request->consumer, $request->token, array_merge($retr_params,
array('cache_codes' => implode("|", $search_result['results'])))));
}
catch (BadRequest $e)
{
throw new InvalidParam('retr_params', "Retrieval method responded with the ".
"following error message: ".$e->getMessage());
}
if ($wrap)
{
# $retr_result might be a PHP object, but also might be a binary response
# (e.g. a GPX file).
if ($retr_result instanceof OkapiHttpResponse)
$result = array('results' => $retr_result->get_body());
else
$result = array('results' => $retr_result);
foreach ($search_result as $key => &$value_ref)
if ($key != 'results')
$result[$key] = $value_ref;
return Okapi::formatted_response($request, $result);
}
else
{
if ($retr_result instanceof OkapiHttpResponse)
return $retr_result;
else
return Okapi::formatted_response($request, $retr_result);
}
}
if ($wrap)
{
# $retr_result might be a PHP object, but also might be a binary response
# (e.g. a GPX file).
if ($retr_result instanceof OkapiHttpResponse)
$result = array('results' => $retr_result->get_body());
else
$result = array('results' => $retr_result);
foreach ($search_result as $key => &$value_ref)
if ($key != 'results')
$result[$key] = $value_ref;
return Okapi::formatted_response($request, $result);
}
else
{
if ($retr_result instanceof OkapiHttpResponse)
return $retr_result;
else
return Okapi::formatted_response($request, $retr_result);
}
}
private static function map_values_to_strings(&$dict)
{
foreach (array_keys($dict) as $key)
{
$val = $dict[$key];
if (is_numeric($val) || is_string($val))
$dict[$key] = (string)$val;
else
throw new BadRequest("Invalid value format for key: ".$key);
}
}
private static function map_values_to_strings(&$dict)
{
foreach (array_keys($dict) as $key)
{
$val = $dict[$key];
if (is_numeric($val) || is_string($val))
$dict[$key] = (string)$val;
else
throw new BadRequest("Invalid value format for key: ".$key);
}
}
}

View File

@ -1,65 +1,65 @@
<xml>
<brief>Search for caches and retrieve formatted results</brief>
<issue-id>18</issue-id>
<desc>
<p>We think that searching and retrieving data are two different things
and that's why they were cleanly separated in the documentation (put in
separate methods). This was done primarily to keep the docs clean.
This method allows you to do a quick search+retrieve task in one request.</p>
<p>All services/caches/search/* methods respond with a list of cache codes.
Then you have to use other method (like services/caches/geocaches) to
retrieve the names and locations, based on those cache codes. The
<b>search_and_retrieve</b> method allows you to do these two steps in one
method call.</p>
<p>First, you have to choose both methods and their parameters - one method
which returns the cache codes, and the other one, that responds
with additional data for the given caches.</p>
</desc>
<req name='search_method'>
<p>Name of the search method (begin with "services/").</p>
<p>E.g. <i>services/caches/search/nearest</i>.</p>
</req>
<req name='search_params'>
<p>JSON-formatted dictionary of parameters to be passed on
to the search method.</p>
<p>E.g. <i>{"center": "49|19", "status": "Available"}</i>.</p>
</req>
<req name='retr_method'>
<p>Name of the retrieval method (begin with "services/").</p>
<p>E.g. <i>services/caches/geocaches</i>.</p>
</req>
<req name='retr_params'>
<p>JSON-formatted dictionary of parameters to be passed on
to the retrieval method.</p>
<p>E.g. <i>{"fields": "name|location|type"}</i></p>
<p>The method will be called with one additional parameter - <b>cache_codes</b>.
These will be the cache codes collected from the results of the search method.</p>
</req>
<req name='wrap'>
<p>Boolean.</p>
<ul>
<li>If <b>true</b>, then results will be wrapped in an additional
object, in order to include any additional data received along with the
search_method response (i.e. the <b>more</b> value).</li>
<li>If <b>false</b>, then this method will return exactly what the
<b>retr_method</b> will respond with.</li>
</ul>
</req>
<common-format-params/>
<returns>
<p>If <b>wrap</b> is <b>true</b>, then the method will return a
dictionary of the following structure:</p>
<ul>
<li><b>results</b> - anything the retrival method
responds with (as long as it's not an error).</li>
<li>any extra keys and values received as a response of
the search_method (i.e. the <b>more</b> variable).</li>
</ul>
<p>If <b>wrap</b> is <b>false</b>, then the method will return
anything the retrieval methods responds with (along with its HTTP headers).</p>
<p><b>Example:</b></p>
<pre>search_and_retrieve<br/>?search_method=services/caches/search/bbox<br/>&amp;search_params={"bbox":"49|19|50|20","limit":"1"}<br/>&amp;retr_method=services/caches/geocaches<br/>&amp;retr_params={"fields":"location"}<br/>&amp;wrap=false</pre>
<p>Possible output:<br/><code>{"OP205A": {"location": "49.572417|19.525867"}}</code></p>
<p>The same example with <i>wrap=true</i> would return:<br/><code>{"results": {"OP205A": {"location": "49.572417|19.525867"}}, "more": true}</code></p>
</returns>
<brief>Search for caches and retrieve formatted results</brief>
<issue-id>18</issue-id>
<desc>
<p>We think that searching and retrieving data are two different things
and that's why they were cleanly separated in the documentation (put in
separate methods). This was done primarily to keep the docs clean.
This method allows you to do a quick search+retrieve task in one request.</p>
<p>All services/caches/search/* methods respond with a list of cache codes.
Then you have to use other method (like services/caches/geocaches) to
retrieve the names and locations, based on those cache codes. The
<b>search_and_retrieve</b> method allows you to do these two steps in one
method call.</p>
<p>First, you have to choose both methods and their parameters - one method
which returns the cache codes, and the other one, that responds
with additional data for the given caches.</p>
</desc>
<req name='search_method'>
<p>Name of the search method (begin with "services/").</p>
<p>E.g. <i>services/caches/search/nearest</i>.</p>
</req>
<req name='search_params'>
<p>JSON-formatted dictionary of parameters to be passed on
to the search method.</p>
<p>E.g. <i>{"center": "49|19", "status": "Available"}</i>.</p>
</req>
<req name='retr_method'>
<p>Name of the retrieval method (begin with "services/").</p>
<p>E.g. <i>services/caches/geocaches</i>.</p>
</req>
<req name='retr_params'>
<p>JSON-formatted dictionary of parameters to be passed on
to the retrieval method.</p>
<p>E.g. <i>{"fields": "name|location|type"}</i></p>
<p>The method will be called with one additional parameter - <b>cache_codes</b>.
These will be the cache codes collected from the results of the search method.</p>
</req>
<req name='wrap'>
<p>Boolean.</p>
<ul>
<li>If <b>true</b>, then results will be wrapped in an additional
object, in order to include any additional data received along with the
search_method response (i.e. the <b>more</b> value).</li>
<li>If <b>false</b>, then this method will return exactly what the
<b>retr_method</b> will respond with.</li>
</ul>
</req>
<common-format-params/>
<returns>
<p>If <b>wrap</b> is <b>true</b>, then the method will return a
dictionary of the following structure:</p>
<ul>
<li><b>results</b> - anything the retrival method
responds with (as long as it's not an error).</li>
<li>any extra keys and values received as a response of
the search_method (i.e. the <b>more</b> variable).</li>
</ul>
<p>If <b>wrap</b> is <b>false</b>, then the method will return
anything the retrieval methods responds with (along with its HTTP headers).</p>
<p><b>Example:</b></p>
<pre>search_and_retrieve<br/>?search_method=services/caches/search/bbox<br/>&amp;search_params={"bbox":"49|19|50|20","limit":"1"}<br/>&amp;retr_method=services/caches/geocaches<br/>&amp;retr_params={"fields":"location"}<br/>&amp;wrap=false</pre>
<p>Possible output:<br/><code>{"OP205A": {"location": "49.572417|19.525867"}}</code></p>
<p>The same example with <i>wrap=true</i> would return:<br/><code>{"results": {"OP205A": {"location": "49.572417|19.525867"}}, "more": true}</code></p>
</returns>
</xml>

View File

@ -15,149 +15,149 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
private static $valid_field_names = array(
'uuid', 'cache_code', 'date', 'user', 'type', 'was_recommended', 'comment',
'images', 'internal_id', 'oc_team_entry',
);
private static $valid_field_names = array(
'uuid', 'cache_code', 'date', 'user', 'type', 'was_recommended', 'comment',
'images', 'internal_id', 'oc_team_entry',
);
public static function call(OkapiRequest $request)
{
$log_uuids = $request->get_parameter('log_uuids');
if ($log_uuids === null) throw new ParamMissing('log_uuids');
if ($log_uuids === "")
{
$log_uuids = array();
}
else
$log_uuids = explode("|", $log_uuids);
public static function call(OkapiRequest $request)
{
$log_uuids = $request->get_parameter('log_uuids');
if ($log_uuids === null) throw new ParamMissing('log_uuids');
if ($log_uuids === "")
{
$log_uuids = array();
}
else
$log_uuids = explode("|", $log_uuids);
if ((count($log_uuids) > 500) && (!$request->skip_limits))
throw new InvalidParam('log_uuids', "Maximum allowed number of referenced ".
"log entries is 500. You provided ".count($log_uuids)." UUIDs.");
if (count($log_uuids) != count(array_unique($log_uuids)))
throw new InvalidParam('log_uuids', "Duplicate UUIDs detected (make sure each UUID is referenced only once).");
$fields = $request->get_parameter('fields');
if (!$fields) $fields = "date|user|type|comment";
$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.");
if ((count($log_uuids) > 500) && (!$request->skip_limits))
throw new InvalidParam('log_uuids', "Maximum allowed number of referenced ".
"log entries is 500. You provided ".count($log_uuids)." UUIDs.");
if (count($log_uuids) != count(array_unique($log_uuids)))
throw new InvalidParam('log_uuids', "Duplicate UUIDs detected (make sure each UUID is referenced only once).");
$fields = $request->get_parameter('fields');
if (!$fields) $fields = "date|user|type|comment";
$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.");
if (Settings::get('OC_BRANCH') == 'oc.de')
{
$teamentry_field = 'cl.oc_team_comment';
$ratingdate_condition = 'and cr.rating_date=cl.date';
}
else
{
$teamentry_field = '(cl.type=12)';
$ratingdate_condition = '';
}
$rs = Db::query("
select
cl.id, c.wp_oc as cache_code, cl.uuid, cl.type,
".$teamentry_field." as oc_team_entry,
unix_timestamp(cl.date) as date, cl.text,
u.uuid as user_uuid, u.username, u.user_id,
if(cr.user_id is null, 0, 1) as was_recommended
from
(cache_logs cl,
user u,
caches c)
left join cache_rating cr
on cr.user_id = u.user_id
and cr.cache_id = c.cache_id
".$ratingdate_condition."
and cl.type in (
".Okapi::logtypename2id("Found it").",
".Okapi::logtypename2id("Attended")."
)
where
cl.uuid in ('".implode("','", array_map('mysql_real_escape_string', $log_uuids))."')
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "cl.deleted = 0" : "true")."
and cl.user_id = u.user_id
and c.cache_id = cl.cache_id
and c.status in (1,2,3)
");
$results = array();
$log_id2uuid = array(); /* Maps logs' internal_ids to uuids */
while ($row = mysql_fetch_assoc($rs))
{
$results[$row['uuid']] = array(
'uuid' => $row['uuid'],
'cache_code' => $row['cache_code'],
'date' => date('c', $row['date']),
'user' => array(
'uuid' => $row['user_uuid'],
'username' => $row['username'],
'profile_url' => Settings::get('SITE_URL')."viewprofile.php?userid=".$row['user_id'],
),
'type' => Okapi::logtypeid2name($row['type']),
'was_recommended' => $row['was_recommended'] ? true : false,
'comment' => Okapi::fix_oc_html($row['text']),
'images' => array(),
'internal_id' => $row['id'],
);
if (in_array('oc_team_entry',$fields))
$results[$row['uuid']]['oc_team_entry'] = $row['oc_team_entry'] ? true : false;
$log_id2uuid[$row['id']] = $row['uuid'];
}
mysql_free_result($rs);
if (Settings::get('OC_BRANCH') == 'oc.de')
{
$teamentry_field = 'cl.oc_team_comment';
$ratingdate_condition = 'and cr.rating_date=cl.date';
}
else
{
$teamentry_field = '(cl.type=12)';
$ratingdate_condition = '';
}
$rs = Db::query("
select
cl.id, c.wp_oc as cache_code, cl.uuid, cl.type,
".$teamentry_field." as oc_team_entry,
unix_timestamp(cl.date) as date, cl.text,
u.uuid as user_uuid, u.username, u.user_id,
if(cr.user_id is null, 0, 1) as was_recommended
from
(cache_logs cl,
user u,
caches c)
left join cache_rating cr
on cr.user_id = u.user_id
and cr.cache_id = c.cache_id
".$ratingdate_condition."
and cl.type in (
".Okapi::logtypename2id("Found it").",
".Okapi::logtypename2id("Attended")."
)
where
cl.uuid in ('".implode("','", array_map('mysql_real_escape_string', $log_uuids))."')
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "cl.deleted = 0" : "true")."
and cl.user_id = u.user_id
and c.cache_id = cl.cache_id
and c.status in (1,2,3)
");
$results = array();
$log_id2uuid = array(); /* Maps logs' internal_ids to uuids */
while ($row = mysql_fetch_assoc($rs))
{
$results[$row['uuid']] = array(
'uuid' => $row['uuid'],
'cache_code' => $row['cache_code'],
'date' => date('c', $row['date']),
'user' => array(
'uuid' => $row['user_uuid'],
'username' => $row['username'],
'profile_url' => Settings::get('SITE_URL')."viewprofile.php?userid=".$row['user_id'],
),
'type' => Okapi::logtypeid2name($row['type']),
'was_recommended' => $row['was_recommended'] ? true : false,
'comment' => Okapi::fix_oc_html($row['text']),
'images' => array(),
'internal_id' => $row['id'],
);
if (in_array('oc_team_entry',$fields))
$results[$row['uuid']]['oc_team_entry'] = $row['oc_team_entry'] ? true : false;
$log_id2uuid[$row['id']] = $row['uuid'];
}
mysql_free_result($rs);
# fetch images
# fetch images
if (in_array('images', $fields))
{
$rs = Db::query("
select object_id, uuid, url, title, spoiler
from pictures
where
object_type = 1
and object_id in ('".implode("','", array_map('mysql_real_escape_string', array_keys($log_id2uuid)))."')
and display = 1 /* currently is always 1 for logpix */
and unknown_format = 0
order by date_created
");
while ($row = mysql_fetch_assoc($rs))
{
$results[$log_id2uuid[$row['object_id']]]['images'][] =
array(
'uuid' => $row['uuid'],
'url' => $row['url'],
'thumb_url' => Settings::get('SITE_URL') . 'thumbs.php?uuid=' . $row['uuid'],
'caption' => $row['title'],
'is_spoiler' => ($row['spoiler'] ? true : false),
);
}
mysql_free_result($rs);
}
if (in_array('images', $fields))
{
$rs = Db::query("
select object_id, uuid, url, title, spoiler
from pictures
where
object_type = 1
and object_id in ('".implode("','", array_map('mysql_real_escape_string', array_keys($log_id2uuid)))."')
and display = 1 /* currently is always 1 for logpix */
and unknown_format = 0
order by date_created
");
while ($row = mysql_fetch_assoc($rs))
{
$results[$log_id2uuid[$row['object_id']]]['images'][] =
array(
'uuid' => $row['uuid'],
'url' => $row['url'],
'thumb_url' => Settings::get('SITE_URL') . 'thumbs.php?uuid=' . $row['uuid'],
'caption' => $row['title'],
'is_spoiler' => ($row['spoiler'] ? true : false),
);
}
mysql_free_result($rs);
}
# Check which UUIDs were not found and mark them with null.
# Check which UUIDs were not found and mark them with null.
foreach ($log_uuids as $log_uuid)
if (!isset($results[$log_uuid]))
$results[$log_uuid] = null;
foreach ($log_uuids as $log_uuid)
if (!isset($results[$log_uuid]))
$results[$log_uuid] = null;
# Remove unwanted fields.
# Remove unwanted fields.
foreach (self::$valid_field_names as $field)
if (!in_array($field, $fields))
foreach ($results as &$result_ref)
unset($result_ref[$field]);
foreach (self::$valid_field_names as $field)
if (!in_array($field, $fields))
foreach ($results as &$result_ref)
unset($result_ref[$field]);
# Order the results in the same order as the input codes were given.
# Order the results in the same order as the input codes were given.
$ordered_results = array();
foreach ($log_uuids as $log_uuid)
$ordered_results[$log_uuid] = $results[$log_uuid];
$ordered_results = array();
foreach ($log_uuids as $log_uuid)
$ordered_results[$log_uuid] = $results[$log_uuid];
return Okapi::formatted_response($request, $ordered_results);
}
return Okapi::formatted_response($request, $ordered_results);
}
}

View File

@ -1,27 +1,27 @@
<xml>
<brief>Retrieve information on multiple log entries</brief>
<issue-id>107</issue-id>
<desc>
<p>This method works like the services/logs/entry method, but works
with multiple log entries (instead of only one).</p>
</desc>
<req name='log_uuids'>
<p>Pipe-separated list of UUIDs. These represent the log entries you
are interested in. No more than 500 codes are allowed.
Unlike in the "entry" method, this CAN be an empty string (it will
result in an empty, but valid, response).</p>
</req>
<opt name='fields' default='date|user|type|comment'>
<p>Same as in the services/logs/entry method. Pipe-separated list
of field names which you are interested with.
See services/logs/entry method for a list of available values.</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary. UUIDs you provide will be mapped to dictionary keys,
and each value will be a dictionary of fields you have selected.</p>
<p>Value of <b>null</b> means that the given UUID haven't been found.
(This behavior is different than in the services/logs/entry method, which
responds with an HTTP 400 error in such case.)</p>
</returns>
<brief>Retrieve information on multiple log entries</brief>
<issue-id>107</issue-id>
<desc>
<p>This method works like the services/logs/entry method, but works
with multiple log entries (instead of only one).</p>
</desc>
<req name='log_uuids'>
<p>Pipe-separated list of UUIDs. These represent the log entries you
are interested in. No more than 500 codes are allowed.
Unlike in the "entry" method, this CAN be an empty string (it will
result in an empty, but valid, response).</p>
</req>
<opt name='fields' default='date|user|type|comment'>
<p>Same as in the services/logs/entry method. Pipe-separated list
of field names which you are interested with.
See services/logs/entry method for a list of available values.</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary. UUIDs you provide will be mapped to dictionary keys,
and each value will be a dictionary of fields you have selected.</p>
<p>Value of <b>null</b> means that the given UUID haven't been found.
(This behavior is different than in the services/logs/entry method, which
responds with an HTTP 400 error in such case.)</p>
</returns>
</xml>

View File

@ -11,26 +11,26 @@ use okapi\InvalidParam;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
$log_uuid = $request->get_parameter('log_uuid');
if (!$log_uuid) throw new ParamMissing('log_uuid');
$fields = $request->get_parameter('fields');
if (!$fields) $fields = "date|user|type|comment";
public static function call(OkapiRequest $request)
{
$log_uuid = $request->get_parameter('log_uuid');
if (!$log_uuid) throw new ParamMissing('log_uuid');
$fields = $request->get_parameter('fields');
if (!$fields) $fields = "date|user|type|comment";
$results = OkapiServiceRunner::call('services/logs/entries', new OkapiInternalRequest(
$request->consumer, $request->token, array('log_uuids' => $log_uuid,
'fields' => $fields)));
$result = $results[$log_uuid];
if ($result == null)
throw new InvalidParam('log_uuid', "This log entry does not exist.");
return Okapi::formatted_response($request, $result);
}
$results = OkapiServiceRunner::call('services/logs/entries', new OkapiInternalRequest(
$request->consumer, $request->token, array('log_uuids' => $log_uuid,
'fields' => $fields)));
$result = $results[$log_uuid];
if ($result == null)
throw new InvalidParam('log_uuid', "This log entry does not exist.");
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,117 +1,117 @@
<xml>
<brief>Retrieve information on a single log entry</brief>
<issue-id>108</issue-id>
<desc>
<p>Retrieve information on a single log entry.</p>
</desc>
<req name='log_uuid'>UUID of the log entry</req>
<opt name='fields' default='date|user|type|comment'>
<p>Pipe-separated list of field names which you are interested with.
Selected fields will be included in the response. See below fot the list
of available fields.</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected. Currently available fields:</p>
<brief>Retrieve information on a single log entry</brief>
<issue-id>108</issue-id>
<desc>
<p>Retrieve information on a single log entry.</p>
</desc>
<req name='log_uuid'>UUID of the log entry</req>
<opt name='fields' default='date|user|type|comment'>
<p>Pipe-separated list of field names which you are interested with.
Selected fields will be included in the response. See below fot the list
of available fields.</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected. Currently available fields:</p>
<ul>
<li><b>uuid</b> - unique ID of the log entry,</li>
<li><b>cache_code</b> - code of the cache which the log entry refers to,</li>
<li>
<p><b>date</b> - date and time (ISO 8601) when the log entry was submitted.</p>
<p>Please note that log entries often contain dates only (with the times
truncated to midnight, as in the local timezone). In such cases, you may
want to avoid displaying the time. You may assume that if the <b>date</b>
value contains the "00:00:00" string, then it is date-only.</p>
</li>
<li>
<p><b>user</b> - a dictionary:</p>
<ul>
<li><b>uuid</b> - ID of the user (author of the log entry),</li>
<li><b>username</b> - name of the user (who submitted the log entry),</li>
<li><b>profile_url</b> - URL of the profile page of the user,</li>
</ul>
</li>
<li>
<p><b>type</b> - string; log type. One of the values documented
below.</p>
<ul>
<li><b>uuid</b> - unique ID of the log entry,</li>
<li><b>cache_code</b> - code of the cache which the log entry refers to,</li>
<li>
<p><b>date</b> - date and time (ISO 8601) when the log entry was submitted.</p>
<p>Please note that log entries often contain dates only (with the times
truncated to midnight, as in the local timezone). In such cases, you may
want to avoid displaying the time. You may assume that if the <b>date</b>
value contains the "00:00:00" string, then it is date-only.</p>
</li>
<li>
<p><b>user</b> - a dictionary:</p>
<ul>
<li><b>uuid</b> - ID of the user (author of the log entry),</li>
<li><b>username</b> - name of the user (who submitted the log entry),</li>
<li><b>profile_url</b> - URL of the profile page of the user,</li>
</ul>
</li>
<li>
<p><b>type</b> - string; log type. One of the values documented
below.</p>
<p>Primary types, commonly used by all Opencaching installations:</p>
<p>Primary types, commonly used by all Opencaching installations:</p>
<ul>
<li>"Found it" - a user found the cache (Non-Event caches).</li>
<li>"Didn't find it" - a user searched for, but couldn't find the cache (Non-Event caches).</li>
<li>"Comment".</li>
<li>"Will attend" - a user is planning to attend the event (for Event caches only).</li>
<li>"Attended" - a user has attended the event (for Event caches only).</li>
</ul>
<ul>
<li>"Found it" - a user found the cache (Non-Event caches).</li>
<li>"Didn't find it" - a user searched for, but couldn't find the cache (Non-Event caches).</li>
<li>"Comment".</li>
<li>"Will attend" - a user is planning to attend the event (for Event caches only).</li>
<li>"Attended" - a user has attended the event (for Event caches only).</li>
</ul>
<p>Types which indicate a change of state of the geocache or confirm the
current state (used only by some Opencaching installations):</p>
<p>Types which indicate a change of state of the geocache or confirm the
current state (used only by some Opencaching installations):</p>
<ul>
<li>"Temporarily unavailable" - probably the cache cannot be found,
but it may be repaired (then, "Ready to search" will be submitted).</li>
<li>"Ready to search" - the cache can be found again.</li>
<li>"Archived" - the cache cannot be found and probably won't be
repaired.</li>
<li>"Locked" - the cache has been archived and can no longer be logged.</li>
</ul>
<ul>
<li>"Temporarily unavailable" - probably the cache cannot be found,
but it may be repaired (then, "Ready to search" will be submitted).</li>
<li>"Ready to search" - the cache can be found again.</li>
<li>"Archived" - the cache cannot be found and probably won't be
repaired.</li>
<li>"Locked" - the cache has been archived and can no longer be logged.</li>
</ul>
<p>Other types (used only by some Opencaching installations):</p>
<p>Other types (used only by some Opencaching installations):</p>
<ul>
<li>"Needs maintenance" - a user states that the cache is
in need of maintenance.</li>
<li>"Maintenance performed" - the cache owner states that he
had performed the maintenance.</li>
<li>"Moved" - the cache has been moved to a different location.</li>
<li>"OC Team comment" - a comment made by the official OC Team
member.</li>
<li><i>(to be continued)</i> - this list MAY expand in time!
Your application should accept unknown log types (you may
treat them as "Comment"s).</li>
</ul>
</li>
<li>
<p><b>oc_team_entry</b> - <b>true</b> if the log entry has been made by an
official OC team member and marked as administrative log; <b>false</b> if it has
not been marked.</p>
<p>Note: <b>false</b> does NOT mean that it is no administrative log,
because this flag can be missing for (mostly old) admin logs.</p>
</li>
<li>
<p><b>was_recommended</b> - <b>true</b>, if the author included his recommendation
in this log entry,</p>
</li>
<li><b>comment</b> - <a href='%OKAPI:docurl:html%'>HTML string</a>, text entered
with the log entry,</li>
<li>
<p><b>images</b> - list of dictionaries, each dictionary represents one
image saved along with the log; each dictionary has the following
structure:</p>
<ul>
<li><b>uuid</b> - UUID of the image,</li>
<li><b>url</b> - URL of the image,</li>
<li><b>thumb_url</b> - URL of a small (thumb) version of the image,</li>
<li><b>caption</b> - plain-text string, caption of the image,</li>
<li><b>is_spoiler</b> - boolean, if <b>true</b> then the image is
a spoiler image and should not be displayed to the user unless
the user explicitly asks for it.</li>
</ul>
</li>
<li>
<p><b>internal_id</b> - undocumented, you <u>should not</u> use
this unless you really know you need to. Internal IDs are
<b>not</b> unique across various OKAPI installations.
Try to use UUIDs instead.</p>
</li>
</ul>
<ul>
<li>"Needs maintenance" - a user states that the cache is
in need of maintenance.</li>
<li>"Maintenance performed" - the cache owner states that he
had performed the maintenance.</li>
<li>"Moved" - the cache has been moved to a different location.</li>
<li>"OC Team comment" - a comment made by the official OC Team
member.</li>
<li><i>(to be continued)</i> - this list MAY expand in time!
Your application should accept unknown log types (you may
treat them as "Comment"s).</li>
</ul>
</li>
<li>
<p><b>oc_team_entry</b> - <b>true</b> if the log entry has been made by an
official OC team member and marked as administrative log; <b>false</b> if it has
not been marked.</p>
<p>Note: <b>false</b> does NOT mean that it is no administrative log,
because this flag can be missing for (mostly old) admin logs.</p>
</li>
<li>
<p><b>was_recommended</b> - <b>true</b>, if the author included his recommendation
in this log entry,</p>
</li>
<li><b>comment</b> - <a href='%OKAPI:docurl:html%'>HTML string</a>, text entered
with the log entry,</li>
<li>
<p><b>images</b> - list of dictionaries, each dictionary represents one
image saved along with the log; each dictionary has the following
structure:</p>
<ul>
<li><b>uuid</b> - UUID of the image,</li>
<li><b>url</b> - URL of the image,</li>
<li><b>thumb_url</b> - URL of a small (thumb) version of the image,</li>
<li><b>caption</b> - plain-text string, caption of the image,</li>
<li><b>is_spoiler</b> - boolean, if <b>true</b> then the image is
a spoiler image and should not be displayed to the user unless
the user explicitly asks for it.</li>
</ul>
</li>
<li>
<p><b>internal_id</b> - undocumented, you <u>should not</u> use
this unless you really know you need to. Internal IDs are
<b>not</b> unique across various OKAPI installations.
Try to use UUIDs instead.</p>
</li>
</ul>
<p>Note, that some fields can change in time (users can edit/delete
their log entries).</p>
<p>Note, that some fields can change in time (users can edit/delete
their log entries).</p>
<p>If given log entry does not exist, the method will
respond with an HTTP 400 error.</p>
</returns>
<p>If given log entry does not exist, the method will
respond with an HTTP 400 error.</p>
</returns>
</xml>

View File

@ -15,58 +15,69 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
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";
public static function options()
{
return array(
'min_auth_level' => 1
);
}
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'.");
$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).
# 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 = 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.
# Cache exists. Getting the uuids of its logs.
$log_uuids = Db::select_column("
select uuid
from cache_logs
where
cache_id = '".mysql_real_escape_string($cache['internal_id'])."'
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "deleted = 0" : "true")."
order by date desc
limit $offset, $limit
");
$log_uuids = Db::select_column("
select uuid
from cache_logs
where
cache_id = '".mysql_real_escape_string($cache['internal_id'])."'
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "deleted = 0" : "true")."
order by date desc
limit $offset, $limit
");
# Getting the logs themselves. Formatting as an ordered list.
# 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];
$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];
return Okapi::formatted_response($request, $results);
}
/* 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);
}
}

View File

@ -1,31 +1,31 @@
<xml>
<brief>Retrieve all log entries for the specified geocache</brief>
<issue-id>41</issue-id>
<desc>
<p>Retrieve the log entries for the specified geocache. Use the offset and
limit parameters for pagination. If you want only the latest entries, you
may also use the <b>latest_logs</b> field in the services/caches/geocache method.</p>
<p>Log entries are ordered by a descending date of the entry.</p>
</desc>
<req name='cache_code'>
<p>Code of the geocache.</p>
</req>
<opt name='fields' default='uuid|date|user|type|comment'>
<p>Same as in the services/logs/entry method. Pipe-separated list
of field names which you are interested with.
See services/logs/entry method for a list of available values.</p>
</opt>
<opt name='offset' default='0'>
<p>Number of entries to skip at the beginning. Use this along the <b>limit</b> parameter
for pagination.</p>
</opt>
<opt name='limit' default='none'>
<p>Maximum number of entries to return or <b>none</b>
if you want all the entries.</p>
</opt>
<common-format-params/>
<returns>
<p>A list of log entries, ordered by date. Each log entry is a dictionary of a format
described in the "entry" method.</p>
</returns>
<brief>Retrieve all log entries for the specified geocache</brief>
<issue-id>41</issue-id>
<desc>
<p>Retrieve the log entries for the specified geocache. Use the offset and
limit parameters for pagination. If you want only the latest entries, you
may also use the <b>latest_logs</b> field in the services/caches/geocache method.</p>
<p>Log entries are ordered by a descending date of the entry.</p>
</desc>
<req name='cache_code'>
<p>Code of the geocache.</p>
</req>
<opt name='fields' default='uuid|date|user|type|comment'>
<p>Same as in the services/logs/entry method. Pipe-separated list
of field names which you are interested with.
See services/logs/entry method for a list of available values.</p>
</opt>
<opt name='offset' default='0'>
<p>Number of entries to skip at the beginning. Use this along the <b>limit</b> parameter
for pagination.</p>
</opt>
<opt name='limit' default='none'>
<p>Maximum number of entries to return or <b>none</b>
if you want all the entries.</p>
</opt>
<common-format-params/>
<returns>
<p>A list of log entries, ordered by date. Each log entry is a dictionary of a format
described in the "entry" method.</p>
</returns>
</xml>

File diff suppressed because it is too large Load Diff

View File

@ -1,110 +1,110 @@
<xml>
<brief>Submit a log entry</brief>
<issue-id>42</issue-id>
<desc>
<p>Submit a log entry for the geocache. Please note, that you won't be
able to use this method until you learn to handle OAuth.</p>
</desc>
<req name='cache_code'>
<p>Code of the geocache.</p>
</req>
<req name='logtype'>
<p>Type of an entry. This should be one of:</p>
<ul>
<li>
<i>Will attend</i>, <i>Attended</i> or <i>Comment</i> for Event caches;
</li>
<li>
<i>Found it</i>, <i>Didn't find it</i> or <i>Comment</i> for all other
cache types.
</li>
</ul>
</req>
<opt name='comment'>
<p>Text to be submitted with the log entry.</p>
</opt>
<opt name='comment_format' default='auto'>
<p>Indicates the format of your <b>comment</b>. Three values allowed:
<b>auto</b>, <b>html</b> or <b>plaintext</b>. Usually, you should <b>not</b>
use the <b>auto</b> option, because its exact behavior is unspecified
and may depend on the installation
(<a href='https://code.google.com/p/opencaching-api/issues/detail?id=124'>more info</a>).</p>
<brief>Submit a log entry</brief>
<issue-id>42</issue-id>
<desc>
<p>Submit a log entry for the geocache. Please note, that you won't be
able to use this method until you learn to handle OAuth.</p>
</desc>
<req name='cache_code'>
<p>Code of the geocache.</p>
</req>
<req name='logtype'>
<p>Type of an entry. This should be one of:</p>
<ul>
<li>
<i>Will attend</i>, <i>Attended</i> or <i>Comment</i> for Event caches;
</li>
<li>
<i>Found it</i>, <i>Didn't find it</i> or <i>Comment</i> for all other
cache types.
</li>
</ul>
</req>
<opt name='comment'>
<p>Text to be submitted with the log entry.</p>
</opt>
<opt name='comment_format' default='auto'>
<p>Indicates the format of your <b>comment</b>. Three values allowed:
<b>auto</b>, <b>html</b> or <b>plaintext</b>. Usually, you should <b>not</b>
use the <b>auto</b> option, because its exact behavior is unspecified
and may depend on the installation
(<a href='https://code.google.com/p/opencaching-api/issues/detail?id=124'>more info</a>).</p>
<p><b>Important note:</b> The subset of allowed HTML elements is left undefined
and may change in the future. For future-compatibility, you should use only
basic formatting tags.</p>
</opt>
<opt name='when'>
<p>A date and time string. This should be in ISO 8601 format (currently any
format acceptable by PHP's <a href='http://pl2.php.net/strtotime'>strtotime</a>
function also will do, but most of them don't handle time zones properly,
try to use ISO 8601!).</p>
<p>Indicates when the cache was found. If given, the log will be published
with the given date and time. If not, log will be published using the current
date and time.</p>
</opt>
<opt name='password'>
<p>Some caches require a password in order to submit a "Found it" log entry.
You may check if this cache requires password with <b>req_passwd</b> field
of the services/caches/geocache method.</p>
</opt>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for error messages.</p>
</opt>
<opt name='on_duplicate' default='silent_success'>
<p>How should OKAPI react when you are trying to submit a duplicate entry?
One of the following values:</p>
<ul>
<li><b>silent_success</b> - try to respond with success=true, but don't
add a new log entry (existing log_uuid will be returned),</li>
<li><b>user_error</b> - respond with success=false and a proper user message,</li>
<li><b>continue</b> - don't detect duplicates (note, that this will still
fail in some cases, i.e. when you're trying to submit a "Found it" entry for an
already found cache).</li>
</ul>
<p>Note, that duplicate detection may take the <b>when</b> parameter into account.
When you don't supply it, "when" is generated for you. This means that you may
have to supply the "when" parameter if you want duplicate detection to work.</p>
</opt>
<opt name='rating'>
<p>An integer in range between 1 and 5 - user's optional rating of a found cache.</p>
<p>Important: <b>logtype</b> has to be "Found it" in order to use this argument.</p>
<p>Note: You should allow your user to <b>not</b> rate a found cache.</p>
<p>Note: Currently, some OC installations do not support cache ratings. On such installations
user's rating will be <b>ignored</b> (if you include the rating, log entry will be posted
successfully, but rating will be ignored).</p>
</opt>
<opt name='recommend' default='false'>
<p>Set to <b>true</b> if the user wants to recommend this cache.</p>
<p>Important: <b>logtype</b> has to be "Found it" or "Attended" in order to
use this argument. However, some installations do not support recommending
event caches (if you include a recommendation, the log entry will be posted
successfully, but the recommendation will be <b>ignored</b>).</p>
<p>Recommending may only succeed when the user meets certain criteria
set on him by the OC site. If criteria are not met, the request will
end with user error (HTTP 200, success=false).</p>
</opt>
<opt name='needs_maintenance' default='false'>
<p>Set to <b>true</b> if the user thinks that the cache needs some special attension
of its owner. Users should describe the reason for maintenance in their comments.</p>
<p>Note: Depending on OC installation and user's log entry type, OKAPI may actually
publish two separate log entries when you check this flag (one of them with empty
comment). Even then, you will still receive only one UUID reference (pointing to
only one of those entries). Moreover, on some OC servers this flag might be
<b>completely ignored</b> (not all OC servers support this feature).</p>
<p>Note: Currently, this is not allowed for Event Caches (you will get a HTTP 200
"user friendly" response).</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>success</b> - true, if the log entry was submitted successfully,</li>
<li><b>message</b> - plain-text string, a message for the user, which acknowledges success
or describes an error (usually you want to display this only when success is false),</li>
<li><b>log_uuid</b> - ID of the newly created log entry, <b>or null</b>
in case of an error.</li>
</ul>
</returns>
<p><b>Important note:</b> The subset of allowed HTML elements is left undefined
and may change in the future. For future-compatibility, you should use only
basic formatting tags.</p>
</opt>
<opt name='when'>
<p>A date and time string. This should be in ISO 8601 format (currently any
format acceptable by PHP's <a href='http://pl2.php.net/strtotime'>strtotime</a>
function also will do, but most of them don't handle time zones properly,
try to use ISO 8601!).</p>
<p>Indicates when the cache was found. If given, the log will be published
with the given date and time. If not, log will be published using the current
date and time.</p>
</opt>
<opt name='password'>
<p>Some caches require a password in order to submit a "Found it" log entry.
You may check if this cache requires password with <b>req_passwd</b> field
of the services/caches/geocache method.</p>
</opt>
<opt name='langpref' default='en'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for error messages.</p>
</opt>
<opt name='on_duplicate' default='silent_success'>
<p>How should OKAPI react when you are trying to submit a duplicate entry?
One of the following values:</p>
<ul>
<li><b>silent_success</b> - try to respond with success=true, but don't
add a new log entry (existing log_uuid will be returned),</li>
<li><b>user_error</b> - respond with success=false and a proper user message,</li>
<li><b>continue</b> - don't detect duplicates (note, that this will still
fail in some cases, i.e. when you're trying to submit a "Found it" entry for an
already found cache).</li>
</ul>
<p>Note, that duplicate detection may take the <b>when</b> parameter into account.
When you don't supply it, "when" is generated for you. This means that you may
have to supply the "when" parameter if you want duplicate detection to work.</p>
</opt>
<opt name='rating'>
<p>An integer in range between 1 and 5 - user's optional rating of a found cache.</p>
<p>Important: <b>logtype</b> has to be "Found it" in order to use this argument.</p>
<p>Note: You should allow your user to <b>not</b> rate a found cache.</p>
<p>Note: Currently, some OC installations do not support cache ratings. On such installations
user's rating will be <b>ignored</b> (if you include the rating, log entry will be posted
successfully, but rating will be ignored).</p>
</opt>
<opt name='recommend' default='false'>
<p>Set to <b>true</b> if the user wants to recommend this cache.</p>
<p>Important: <b>logtype</b> has to be "Found it" or "Attended" in order to
use this argument. However, some installations do not support recommending
event caches (if you include a recommendation, the log entry will be posted
successfully, but the recommendation will be <b>ignored</b>).</p>
<p>Recommending may only succeed when the user meets certain criteria
set on him by the OC site. If criteria are not met, the request will
end with user error (HTTP 200, success=false).</p>
</opt>
<opt name='needs_maintenance' default='false'>
<p>Set to <b>true</b> if the user thinks that the cache needs some special attension
of its owner. Users should describe the reason for maintenance in their comments.</p>
<p>Note: Depending on OC installation and user's log entry type, OKAPI may actually
publish two separate log entries when you check this flag (one of them with empty
comment). Even then, you will still receive only one UUID reference (pointing to
only one of those entries). Moreover, on some OC servers this flag might be
<b>completely ignored</b> (not all OC servers support this feature).</p>
<p>Note: Currently, this is not allowed for Event Caches (you will get a HTTP 200
"user friendly" response).</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li><b>success</b> - true, if the log entry was submitted successfully,</li>
<li><b>message</b> - plain-text string, a message for the user, which acknowledges success
or describes an error (usually you want to display this only when success is false),</li>
<li><b>log_uuid</b> - ID of the newly created log entry, <b>or null</b>
in case of an error.</li>
</ul>
</returns>
</xml>

View File

@ -15,62 +15,62 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
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'");
public static function options()
{
return array(
'min_auth_level' => 1
);
}
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')));
# 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.
# User exists. Retrieving logs.
$rs = Db::query("
select cl.id, cl.uuid, cl.type, unix_timestamp(cl.date) as date, cl.text,
c.wp_oc as cache_code
from cache_logs cl, caches c
where
cl.user_id = '".mysql_real_escape_string($user['internal_id'])."'
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "cl.deleted = 0" : "true")."
and c.status in (1,2,3)
and cl.cache_id = c.cache_id
order by cl.date desc
limit $offset, $limit
");
$results = array();
while ($row = mysql_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']
);
}
$rs = Db::query("
select cl.id, cl.uuid, cl.type, unix_timestamp(cl.date) as date, cl.text,
c.wp_oc as cache_code
from cache_logs cl, caches c
where
cl.user_id = '".mysql_real_escape_string($user['internal_id'])."'
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "cl.deleted = 0" : "true")."
and c.status in (1,2,3)
and cl.cache_id = c.cache_id
order by cl.date desc
limit $offset, $limit
");
$results = array();
while ($row = mysql_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);
}
return Okapi::formatted_response($request, $results);
}
}

View File

@ -1,41 +1,45 @@
<xml>
<brief>Retrieve log entries of a specified user</brief>
<issue-id>80</issue-id>
<desc>
<p>Retrieve log entries of a specified user.</p>
</desc>
<req name='user_uuid'>
<p>ID of the user. (Use services/users/by_username to get it.)</p>
</req>
<opt name='limit' default='20'>
<p>Integer N. If given, no more than N logs will be returned (the most recent ones).
Maximum allowed value is 1000.</p>
<p>Note: Some users have thousands of log entries!</p>
</opt>
<opt name='offset' default='0'>
<p>Combined with the <b>limit</b> argument, this gives you an abbility to get
<b>all</b> log entries of the user (with multiple requests).</p>
</opt>
<common-format-params/>
<returns>
<p>Log entries. A dictionary of the following format:</p>
<ul>
<li><b>uuid</b> - ID of the log entry,</li>
<li>
<p><b>date</b> - date and time (ISO 8601) when the log entry was submitted.</p>
<p>Please note that log entries often contain dates only (with the times
truncated to midnight, as in the local timezone). In such cases, you may
want to avoid displaying the time. You may assume that if the <b>date</b>
value contains the "00:00:00" string, then it is date-only.</p>
</li>
<li><b>cache_code</b> - code of the geocache,</li>
<li>
<p><b>type</b> - string; log type. This could be <b>pretty much
everything</b>, but there are some primary types (see logs/entry
method for more info).</p>
</li>
<li><b>comment</b> - <a href='%OKAPI:docurl:html%'>HTML string</a>, text entered
with the log entry.</li>
</ul>
</returns>
<brief>Retrieve log entries of a specified user</brief>
<issue-id>80</issue-id>
<desc>
<p>Retrieve log entries of a specified user.</p>
</desc>
<req name='user_uuid'>
<p>ID of the user. (Use services/users/by_username to get it.)</p>
</req>
<opt name='limit' default='20'>
<p>Integer N. If given, no more than N logs will be returned (the most recent ones).
Maximum allowed value is 1000.</p>
<p>Note: Some users have thousands of log entries!</p>
</opt>
<opt name='offset' default='0'>
<p>Combined with the <b>limit</b> argument, this gives you an abbility to get
<b>all</b> log entries of the user (with multiple requests).</p>
</opt>
<common-format-params/>
<returns>
<p>A list of log entries, ordered by descending date. Each entry is a
dictionary of the following format:</p>
<ul>
<li><b>uuid</b> - ID of the log entry,</li>
<li>
<p><b>date</b> - date and time (ISO 8601) when the log entry was submitted.</p>
<p>Please note that log entries often contain dates only (with the times
truncated to midnight, as in the local timezone). In such cases, you may
want to avoid displaying the time. You may assume that if the <b>date</b>
value contains the "00:00:00" string, then it is date-only.</p>
</li>
<li><b>cache_code</b> - code of the geocache,</li>
<li>
<p><b>type</b> - string; log type. This could be <b>pretty much
everything</b>, but there are some primary types (see logs/entry
method for more info).</p>
</li>
<li>
<b>comment</b> - <a href='%OKAPI:docurl:html%'>HTML string</a>, text entered
with the log entry.
</li>
</ul>
</returns>
</xml>

View File

@ -10,28 +10,28 @@ use okapi\InvalidParam;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 3,
'token_type' => 'request'
);
}
public static function options()
{
return array(
'min_auth_level' => 3,
'token_type' => 'request'
);
}
public static function call(OkapiRequest $request)
{
$verifier = $request->get_parameter('oauth_verifier');
if (!$verifier)
{
# We require the 1.0a flow (throw an error when there is no oauth_verifier).
throw new ParamMissing("oauth_verifier");
}
public static function call(OkapiRequest $request)
{
$verifier = $request->get_parameter('oauth_verifier');
if (!$verifier)
{
# We require the 1.0a flow (throw an error when there is no oauth_verifier).
throw new ParamMissing("oauth_verifier");
}
$new_token = Okapi::$data_store->new_access_token($request->token, $request->consumer, $verifier);
$new_token = Okapi::$data_store->new_access_token($request->token, $request->consumer, $verifier);
$response = new OkapiHttpResponse();
$response->content_type = "text/plain; charset=utf-8";
$response->body = $new_token;
return $response;
}
$response = new OkapiHttpResponse();
$response->content_type = "text/plain; charset=utf-8";
$response->body = $new_token;
return $response;
}
}

View File

@ -1,20 +1,20 @@
<xml>
<brief>Exchange authorized Request Token for an Access Token</brief>
<issue-id>21</issue-id>
<desc>
<p>Exchange authorized Request Token for an Access Token.
Access Token can be used to access resources of a user who
authorized the Request Token.</p>
<p>Standard OAuth "Consumer &amp; Request Token" Authorization arguments
are required.</p>
</desc>
<req name='oauth_verifier'>
Consult <a href='http://oauth.net/documentation/spec/'>OAuth documentation</a> for details.
</req>
<returns>
<p>Standard OAuth 1.0a Token response - a string in a form-encoded format:</p>
<pre>oauth_token=...&amp;oauth_token_secret=...</pre>
<p>You <b>must</b> be prepared that there might be more parameters returned
in the future (you should ignore them gracefully).</p>
</returns>
<brief>Exchange authorized Request Token for an Access Token</brief>
<issue-id>21</issue-id>
<desc>
<p>Exchange authorized Request Token for an Access Token.
Access Token can be used to access resources of a user who
authorized the Request Token.</p>
<p>Standard OAuth "Consumer &amp; Request Token" Authorization arguments
are required.</p>
</desc>
<req name='oauth_verifier'>
Consult <a href='http://oauth.net/documentation/spec/'>OAuth documentation</a> for details.
</req>
<returns>
<p>Standard OAuth 1.0a Token response - a string in a form-encoded format:</p>
<pre>oauth_token=...&amp;oauth_token_secret=...</pre>
<p>You <b>must</b> be prepared that there might be more parameters returned
in the future (you should ignore them gracefully).</p>
</returns>
</xml>

View File

@ -11,31 +11,31 @@ use okapi\InvalidParam;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function options()
{
return array(
'min_auth_level' => 0
);
}
public static function call(OkapiRequest $request)
{
$token_key = $request->get_parameter('oauth_token');
if (!$token_key)
throw new ParamMissing("oauth_token");
$langpref = $request->get_parameter('langpref');
$interactivity = $request->get_parameter('interactivity');
if (!$interactivity) $interactivity = 'minimal';
if (!in_array($interactivity, array('minimal', 'confirm_user')))
throw new InvalidParam('interactivity', $interactivity);
public static function call(OkapiRequest $request)
{
$token_key = $request->get_parameter('oauth_token');
if (!$token_key)
throw new ParamMissing("oauth_token");
$langpref = $request->get_parameter('langpref');
$interactivity = $request->get_parameter('interactivity');
if (!$interactivity) $interactivity = 'minimal';
if (!in_array($interactivity, array('minimal', 'confirm_user')))
throw new InvalidParam('interactivity', $interactivity);
# Redirect to the "apps" folder. This is done there (not here)
# because: 1) we don't want any cookie or session-handling
# done in the "services" folder. 2) "services" don't display
# any interactive webpages, they just return the result.
# Redirect to the "apps" folder. This is done there (not here)
# because: 1) we don't want any cookie or session-handling
# done in the "services" folder. 2) "services" don't display
# any interactive webpages, they just return the result.
return new OkapiRedirectResponse(Settings::get('SITE_URL')."okapi/apps/authorize".
"?oauth_token=".$token_key.(($langpref != null) ? "&langpref=".$langpref : "").
"&interactivity=".$interactivity);
}
return new OkapiRedirectResponse(Settings::get('SITE_URL')."okapi/apps/authorize".
"?oauth_token=".$token_key.(($langpref != null) ? "&langpref=".$langpref : "").
"&interactivity=".$interactivity);
}
}

View File

@ -1,66 +1,66 @@
<xml>
<brief>Authorize the Request Token</brief>
<issue-id>22</issue-id>
<desc>
<p>Unlike other methods, the <b>authorize</b> method is to be executed
inside the User's browser. Consumer's role is to <b>redirect</b> the User to
this URL, then wait if he ever comes back with a callback request.</p>
<p>Once the User is redirected to this URL, several things will happen:</p>
<ul>
<li>If he's not already logged in, he will be asked to do so.</li>
<li>OKAPI will check if the User haven't previously granted your
application access to his Opencaching account.</li>
<li>If User did not previously authorize your application, OKAPI
will display an "Authorization Request" form to the User. User
will be presented with a choice to allow or not to allow your
application access to his account.</li>
<li>If the User clicks one of these two options ("allow" or "don't allow"),
he's browser will be redirected to the <b>callback_url</b> you defined
while getting your Request Token.
If you did not provide a callback (in other word, provided "oob"),
user will be redirected to a default "authorized" page, where he
will be presented with an oauth_verifier (user will know it
by name of a <b>PIN code</b>) and asked to type it into your application.</li>
</ul>
</desc>
<req name='oauth_token'>
Consult <a href='http://oauth.net/documentation/spec/'>OAuth documentation</a> for details.
</req>
<opt name='interactivity' default='minimal'>
<p>Currently, one of the following values:</p>
<ul>
<li><b>minimal</b> - OKAPI will use as little interactivity as it can.
It will assume that currently logged in user is the user which you
want to authorize. If the user has already authorized your application,
he will not be asked to do this again.</li>
<li><b>confirm_user</b> - even if a user is logged in, OKAPI will NOT
assume that this is the user who wants to be authorized. OKAPI will
offer to authorize a different user (e.g. by automatically logging out
the user who is currently logged in).</li>
</ul>
</opt>
<opt name='langpref'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which the language will be chosen for the authorization page.</p>
<p>By default, OKAPI will display the page in the primary native language of local
Opencaching installation.</p>
</opt>
<returns>
<p>Technically, an HTTP 302 Redirect - it will direct user's browser to the OKAPI apps
authorization page.</p>
<p>Whether with callback_url or with a manual user entry - you will get
your <b>oauth_verifier</b>, which allows you to continue the 3-legged
authentication dance.</p>
<p>If you used <b>callback_url</b>, you should wait for an HTTP GET request,
with one additional GET parameter appended:</p>
<ul>
<li><b>oauth_token</b> - the Request Token that has been just authorized,</li>
<li><b>oauth_verifier</b> - the PIN code required to get an Access Token.</li>
</ul>
<p>OR, in case when user denied the request:</p>
<ul>
<li><b>oauth_token</b> - the Request Token,</li>
<li><b>error</b> - codename of an error - <b>access_denied</b>.</li>
</ul>
</returns>
<brief>Authorize the Request Token</brief>
<issue-id>22</issue-id>
<desc>
<p>Unlike other methods, the <b>authorize</b> method is to be executed
inside the User's browser. Consumer's role is to <b>redirect</b> the User to
this URL, then wait if he ever comes back with a callback request.</p>
<p>Once the User is redirected to this URL, several things will happen:</p>
<ul>
<li>If he's not already logged in, he will be asked to do so.</li>
<li>OKAPI will check if the User haven't previously granted your
application access to his Opencaching account.</li>
<li>If User did not previously authorize your application, OKAPI
will display an "Authorization Request" form to the User. User
will be presented with a choice to allow or not to allow your
application access to his account.</li>
<li>If the User clicks one of these two options ("allow" or "don't allow"),
he's browser will be redirected to the <b>callback_url</b> you defined
while getting your Request Token.
If you did not provide a callback (in other word, provided "oob"),
user will be redirected to a default "authorized" page, where he
will be presented with an oauth_verifier (user will know it
by name of a <b>PIN code</b>) and asked to type it into your application.</li>
</ul>
</desc>
<req name='oauth_token'>
Consult <a href='http://oauth.net/documentation/spec/'>OAuth documentation</a> for details.
</req>
<opt name='interactivity' default='minimal'>
<p>Currently, one of the following values:</p>
<ul>
<li><b>minimal</b> - OKAPI will use as little interactivity as it can.
It will assume that currently logged in user is the user which you
want to authorize. If the user has already authorized your application,
he will not be asked to do this again.</li>
<li><b>confirm_user</b> - even if a user is logged in, OKAPI will NOT
assume that this is the user who wants to be authorized. OKAPI will
offer to authorize a different user (e.g. by automatically logging out
the user who is currently logged in).</li>
</ul>
</opt>
<opt name='langpref'>
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which the language will be chosen for the authorization page.</p>
<p>By default, OKAPI will display the page in the primary native language of local
Opencaching installation.</p>
</opt>
<returns>
<p>Technically, an HTTP 302 Redirect - it will direct user's browser to the OKAPI apps
authorization page.</p>
<p>Whether with callback_url or with a manual user entry - you will get
your <b>oauth_verifier</b>, which allows you to continue the 3-legged
authentication dance.</p>
<p>If you used <b>callback_url</b>, you should wait for an HTTP GET request,
with one additional GET parameter appended:</p>
<ul>
<li><b>oauth_token</b> - the Request Token that has been just authorized,</li>
<li><b>oauth_verifier</b> - the PIN code required to get an Access Token.</li>
</ul>
<p>OR, in case when user denied the request:</p>
<ul>
<li><b>oauth_token</b> - the Request Token,</li>
<li><b>error</b> - codename of an error - <b>access_denied</b>.</li>
</ul>
</returns>
</xml>

View File

@ -10,27 +10,27 @@ use okapi\InvalidParam;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 2
);
}
public static function options()
{
return array(
'min_auth_level' => 2
);
}
public static function call(OkapiRequest $request)
{
$callback = $request->get_parameter('oauth_callback');
if (!$callback)
{
# We require the 1.0a flow (throw an error when there is no oauth_callback).
throw new ParamMissing("oauth_callback");
}
public static function call(OkapiRequest $request)
{
$callback = $request->get_parameter('oauth_callback');
if (!$callback)
{
# We require the 1.0a flow (throw an error when there is no oauth_callback).
throw new ParamMissing("oauth_callback");
}
$new_token = Okapi::$data_store->new_request_token($request->consumer, $callback);
$new_token = Okapi::$data_store->new_request_token($request->consumer, $callback);
$response = new OkapiHttpResponse();
$response->content_type = "text/plain; charset=utf-8";
$response->body = $new_token."&oauth_callback_confirmed=true";
return $response;
}
$response = new OkapiHttpResponse();
$response->content_type = "text/plain; charset=utf-8";
$response->body = $new_token."&oauth_callback_confirmed=true";
return $response;
}
}

View File

@ -1,24 +1,24 @@
<xml>
<brief>Get a new unauthorized OAuth Request Token</brief>
<issue-id>23</issue-id>
<desc>
<p>Get a new unauthorized OAuth Request Token. This token is bound to
the Consumer it was issued to. It has a short expiration date and
can be used in one way only - first it has to get authorized by
the user, then it has to be exchanged for an Access Token.</p>
</desc>
<req name='oauth_callback'>
<p>URL which you want a User to be redirected to after a
successful Request Token Authorization (see "authorize" method).
If the client is unable to receive callbacks, the parameter
must be set to "oob", OKAPI will provide a user with a
PIN code (oauth_verifier) in this case.</p>
Consult <a href='http://oauth.net/documentation/spec/'>OAuth documentation</a> for details.
</req>
<returns>
<p>Standard OAuth 1.0a Token response - a string in a form-encoded format:</p>
<pre>oauth_token=...&amp;oauth_token_secret=...&amp;oauth_callback_confirmed=true</pre>
<p>You <b>must</b> be prepared that there might be more parameters returned
in the future (you should ignore them gracefully).</p>
</returns>
<brief>Get a new unauthorized OAuth Request Token</brief>
<issue-id>23</issue-id>
<desc>
<p>Get a new unauthorized OAuth Request Token. This token is bound to
the Consumer it was issued to. It has a short expiration date and
can be used in one way only - first it has to get authorized by
the user, then it has to be exchanged for an Access Token.</p>
</desc>
<req name='oauth_callback'>
<p>URL which you want a User to be redirected to after a
successful Request Token Authorization (see "authorize" method).
If the client is unable to receive callbacks, the parameter
must be set to "oob", OKAPI will provide a user with a
PIN code (oauth_verifier) in this case.</p>
Consult <a href='http://oauth.net/documentation/spec/'>OAuth documentation</a> for details.
</req>
<returns>
<p>Standard OAuth 1.0a Token response - a string in a form-encoded format:</p>
<pre>oauth_token=...&amp;oauth_token_secret=...&amp;oauth_callback_confirmed=true</pre>
<p>You <b>must</b> be prepared that there might be more parameters returned
in the future (you should ignore them gracefully).</p>
</returns>
</xml>

View File

@ -17,37 +17,37 @@ use okapi\services\replicate\ReplicateCommon;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
require_once('replicate_common.inc.php');
public static function call(OkapiRequest $request)
{
require_once('replicate_common.inc.php');
$since = $request->get_parameter('since');
if ($since === null) throw new ParamMissing('since');
if ((int)$since != $since) throw new InvalidParam('since');
$since = $request->get_parameter('since');
if ($since === null) throw new ParamMissing('since');
if ((int)$since != $since) throw new InvalidParam('since');
# Let's check the $since parameter.
# Let's check the $since parameter.
if (!ReplicateCommon::check_since_param($since))
throw new BadRequest("The 'since' parameter is too old. You must update your database more frequently.");
if (!ReplicateCommon::check_since_param($since))
throw new BadRequest("The 'since' parameter is too old. You must update your database more frequently.");
# Select a best chunk for the given $since, get the chunk from the database (or cache).
# Select a best chunk for the given $since, get the chunk from the database (or cache).
list($from, $to) = ReplicateCommon::select_best_chunk($since);
$clog_entries = ReplicateCommon::get_chunk($from, $to);
list($from, $to) = ReplicateCommon::select_best_chunk($since);
$clog_entries = ReplicateCommon::get_chunk($from, $to);
$result = array(
'changelog' => &$clog_entries,
'revision' => $to + 0,
'more' => $to < ReplicateCommon::get_revision(),
);
$result = array(
'changelog' => &$clog_entries,
'revision' => $to + 0,
'more' => $to < ReplicateCommon::get_revision(),
);
return Okapi::formatted_response($request, $result);
}
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,133 +1,140 @@
<xml>
<brief>Get the list of changes for your database</brief>
<issue-id>109</issue-id>
<desc>
<p><b>Beta status.</b> Get the list of changes to be replayed on your own copy
of Opencaching database. Use this method periodically (ex. once per hour) to
keep your database in sync with ours.</p>
<brief>Get the list of changes for your database</brief>
<issue-id>109</issue-id>
<desc>
<p>Get the list of changes to be <u>replayed on your own copy
of Opencaching database</u>. Use this method periodically (e.g. every 5 minutes) to
keep your database in sync with ours.</p>
<p>For some applications it might be desireable to have a quick access to the entire
Opencaching database (instead of querying for specific portions of it). You may use
OKAPI's <b>replicate</b> module to achive this effect. The <b>changelog</b> method
is the primary replication service which you will use. However, to correctly set up
your first database copy, you will need to use the <b>fulldump</b> method.</p>
<p>For some applications it might be desireable to have a quick access to the entire
Opencaching database (instead of querying for specific portions of it). You may use
OKAPI's <b>replicate</b> module to achive this effect. The <b>changelog</b> method
is the primary replication service which you will use. However, to correctly set up
your first database copy, you will need to use the <b>fulldump</b> method.</p>
<p>A couple of things for you to remember:</p>
<ul>
<li>You <b>must</b> update your database frequently for this method to work.
We don't keep the changelog indefinitelly. You must update at least once every week.</li>
<li>You <b>should not</b> update your database more frequently than once every
5 minutes. This won't do you any good, since we update the changelog only once
every 5 minutes anyway.</li>
</ul>
<p>A couple of things for you to remember:</p>
<ul>
<li>You <b>must</b> update your database frequently for this method to work.
We don't keep the changelog indefinitelly. You must update at least once a week!</li>
<p>Let's assume that you already have a copy of OKAPI database, but it's
already 2 days old. You want to use the <b>changelog</b> method to update
your copy to the most current state.</p>
<li>You <b>should not</b> update your database more frequently than once per
5 minutes. This could kill our servers, and it wouldn't do you any good, since
we update the changelog only once every 5 minutes anyway.</li>
</ul>
<p>Changelog is the list of all changes which appeared in the OKAPI database
since the last time you downloaded it. What you have to do is to download this
list of changes and to replay them on your copy of our database. After you do
this, your database is up-to-date.</p>
<p>Let's assume that you already have a copy of OKAPI database, but it's
already 2 days old. You want to use the <b>changelog</b> method to update
your copy to the most current state.</p>
<p>We use <b>revision</b> numbers to keep track of all the versions of the
database. Every time you update a database, you receive the <b>revision</b>
number along with it. You must keep this number carefully, because you need
it in order for us to generate a proper changelog for you next time you ask
for it.</p>
<p>Changelog is the list of all changes which appeared in the OKAPI database
since the last time you downloaded it. What you have to do is to download this
list of changes and to replay them on your copy of our database. After you do
this, your database is up-to-date.</p>
<p>Example. This is a valid list of requests you should issue and their
responses:</p>
<ul>
<li><b>fulldump</b> - you receive a fulldump of our database with the
revision number 238004. <b>You will call this method only once, never again.</b></li>
<li><b>changelog</b>?since=238004 - OKAPI checks if there were any changes
recorded since revision 238004. It responds with the list of changes and the
new revision number 238017. You replay the changes on your database.</li>
<li>You wait for some time between requesting the changelog again.</li>
<li><b>changelog</b>?since=238017 - etc.</li>
</ul>
</desc>
<req name='since'>
<p>Current revision of your database. This should be the same as the value
of <b>revision</b> attribute, which you received with your previous update.</p>
<p>We use <b>revision</b> numbers to keep track of all the versions of the
database. Every time we update a database, the <b>revision</b> is increased.
You will also receive this number every time you fetch a changelog or a fulldump
of our database. You must keep this number carefully, because you need
it in order for us to generate a proper changelog for you next time you ask
for it!</p>
<p>Old revisions are deleted, the <b>since</b> argument MUST referer to a revision no older
than 10 days. You will have to download a fulldump if you have an older copy
(shame on you!).</p>
</req>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li>
<p><b>changelog</b> - a <b>list</b> of dictionaries. Each dictionary has the following structure:</p>
<ul>
<li>
<p><b>object_type</b> - string, object type to which the change refers to. One of the following values:</p>
<ul>
<li><b>geocache</b> - this change refers to a geocache object,</li>
<li><b>log_entry</b> - this change refers to a log entry.</li>
</ul>
<p>More object types will come in the future. You should ignore all changelog
entries with an unknown object_type.</p>
</li>
<li>
<p><b>object_key</b> - a dictionary of fields which compose the primary key for the object.
This will be the <b>code</b> field for the <b>geocache</b> object, and <b>uuid</b> field
for the <b>log_entry</b> object.</p>
</li>
<li>
<p><b>change_type</b> - string, the type of the change. One of the following values:</p>
<ul>
<li><b>replace</b> - the object was inserted or updated. You should check if you
already have the object in your database. If you have it, you should update its
fields accordingly. If you don't, you should create it.</li>
<li><b>delete</b> - the object was deleted. You should check if you already have
the object in your database. If you do, you should delete it.</li>
</ul>
</li>
<li>
<p><b>fields</b> - a dictionary of fields associated with the object (present only
if <b>change_type</b> equals <b>replace</b>).</p>
<ul>
<li>For <b>geocache</b> objects, this might be any subset of fields described
in the services/caches/geocache method. Note that not all of these fields will
be included here (i.e. latest_logs will not).</li>
<li>For <b>log_entry</b> objects, this might be any subset of fields described
in the services/logs/logs method, <b>plus</b> additional <b>cache_code</b> field,
the code of the geocache to which the log entry refers to.</li>
</ul>
<p>In theory this dictionary should contain only these fields which actually changed.
In truth, it MAY contain all the other fields too. This behavior may change in future.</p>
</li>
</ul>
<p>You should iterate through this changelog sequentially. All the changes should be applied
in the same order as they were listed in the changelog. Single object may appear multiple times
inside the changelog. Changelog will contain all the changes which occured since the time you
specified in the <b>since</b> parameter and it MAY contain some more, which were submitted
<b>before</b> this date (see below). The changes which are unnecessary MAY be skipped (ex. when
cache description changes multiple times, we may want to include only the last change).</p>
</li>
<li>
<p><b>revision</b> - this is the revision number which you should use in the <b>since</b> parameter
when you call this method next time.</p>
</li>
<li>
<p><b>more</b> - boolean. If <b>false</b>, then it means that the entire changelog had been
pulled. If <b>true</b>, then there are more items waiting to be pulled - you should rerun this
method again (with the value of <b>revision</b> inserted in the <b>since</b> parameter).</p>
<p>The changelog is usually tiny, but it also might be huge at times. It may even
contain all the caches in the database (i.e. if we decide to do some changes on all
caches in a bulk). This is the reason why we introduced the <b>more</b> attribute. It makes it
easier to parse the changelog sequentially.</p>
</li>
</ul>
<p>The response MAY contain changes recorded before the revision which you stated in your <b>since</b>
parameter. This is due to caching. If we already have a response ready, that was produced for a
"since" parameter which was a little more in the past, we may want to return this cached copy,
instead of producing a new one. It will be faster for us, and you shouldn't even notice. We will
still make sure to set the <b>revision</b> and <b>more</b> attributes in a correct way, which
in turn will make you query us again, if you need to.</p>
</returns>
<p><b>Example.</b> This is a valid list of requests you should issue and their
responses:</p>
<ul>
<li><b>fulldump</b> - you receive a fulldump of our database with the
revision number 238004. <b>You will call this method only once</b> (to
initiate your copy of the database).</li>
<li><b>changelog</b>?since=238004 - OKAPI checks if there were any changes
recorded since revision 238004. It responds with the list of changes and the
new revision number 238017. You replay the changes on your database.</li>
<li>You wait for some time between requesting the changelog again.</li>
<li>Upon your next update, you'll ask for <b>changelog</b>?since=238017, etc.</li>
</ul>
</desc>
<req name='since'>
<p>Current revision of your database. This should be the same as the value
of <b>revision</b> attribute, which you received with your previous update.</p>
<p>Old revisions are deleted, the <b>since</b> argument MUST referer to a revision no older
than 10 days. You will have to download a fulldump if you have an older copy
(shame on you!).</p>
</req>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li>
<p><b>changelog</b> - a <b>list</b> of dictionaries. Each dictionary has the following structure:</p>
<ul>
<li>
<p><b>object_type</b> - string, object type to which the change refers to. One of the following values:</p>
<ul>
<li><b>geocache</b> - this change refers to a geocache object,</li>
<li><b>log_entry</b> - this change refers to a log entry.</li>
</ul>
<p>More object types will come in the future. You should ignore all changelog
entries with an unknown object_type.</p>
</li>
<li>
<p><b>object_key</b> - a dictionary of fields which compose the primary key for the object.
This will be the <b>code</b> field for the <b>geocache</b> object, and <b>uuid</b> field
for the <b>log_entry</b> object.</p>
</li>
<li>
<p><b>change_type</b> - string, the type of the change. One of the following values:</p>
<ul>
<li><b>replace</b> - the object was inserted or updated. You should check if you
already have the object in your database. If you have it, you should update its
fields accordingly. If you don't, you should create it.</li>
<li><b>delete</b> - the object was deleted. You should check if you already have
the object in your database. If you do, you should delete it.</li>
</ul>
</li>
<li>
<p><b>data</b> - a dictionary of fields associated with the object (present only
if <b>change_type</b> equals <b>replace</b>).</p>
<ul>
<li>For <b>geocache</b> objects, this might be any subset of fields described
in the services/caches/geocache method. Note that not all of these fields will
be included here (i.e. latest_logs will not).</li>
<li>For <b>log_entry</b> objects, this might be any subset of fields described
in the services/logs/logs method, <b>plus</b> additional <b>cache_code</b> field,
the code of the geocache to which the log entry refers to.</li>
</ul>
<p>In theory this dictionary should contain only these fields which actually changed.
In truth, it MAY contain all the other fields too. This behavior may change in future.</p>
</li>
</ul>
<p>You should iterate through this changelog sequentially. All the changes should be applied
in the same order as they were listed in the changelog. Single object may appear multiple times
inside the changelog. Changelog will contain all the changes which occured since the time you
specified in the <b>since</b> parameter and it MAY contain some more, which were submitted
<b>before</b> this date (see below). The changes which are unnecessary MAY be skipped (ex. when
cache description changes multiple times, we may want to include only the last change).</p>
</li>
<li>
<p><b>revision</b> - this is the revision number which you should use in the <b>since</b> parameter
when you call this method next time.</p>
</li>
<li>
<p><b>more</b> - boolean. If <b>false</b>, then it means that the entire changelog had been
pulled. If <b>true</b>, then there are more items waiting to be pulled - you should rerun this
method again (with the value of <b>revision</b> inserted in the <b>since</b> parameter).</p>
<p>The changelog is usually tiny, but it also might be huge at times. It may even
contain all the caches in the database (i.e. if we decide to do some changes on all
caches in a bulk). This is the reason why we introduced the <b>more</b> attribute. It makes it
easier to parse the changelog sequentially.</p>
</li>
</ul>
<p>The response MAY contain changes recorded before the revision which you stated in your <b>since</b>
parameter. This is due to caching. If we already have a response ready, that was produced for a
"since" parameter which was a little more in the past, we may want to return this cached copy,
instead of producing a new one. It will be faster for us, and you shouldn't even notice. We will
still make sure to set the <b>revision</b> and <b>more</b> attributes in a correct way, which
in turn will make you query us again, if you need to.</p>
</returns>
</xml>

View File

@ -14,67 +14,67 @@ use okapi\BadRequest;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
private static function count_calls($consumer_key, $days)
{
return (
Db::select_value("
select count(*)
from okapi_stats_temp
where
consumer_key = '".mysql_real_escape_string($consumer_key)."'
and service_name='services/replicate/fulldump'
")
+
Db::select_value("
select sum(total_calls)
from okapi_stats_hourly
where
consumer_key = '".mysql_real_escape_string($consumer_key)."'
and service_name='services/replicate/fulldump'
and period_start > date_add(now(), interval -$days day)
limit 1
")
);
}
private static function count_calls($consumer_key, $days)
{
return (
Db::select_value("
select count(*)
from okapi_stats_temp
where
consumer_key = '".mysql_real_escape_string($consumer_key)."'
and service_name='services/replicate/fulldump'
")
+
Db::select_value("
select sum(total_calls)
from okapi_stats_hourly
where
consumer_key = '".mysql_real_escape_string($consumer_key)."'
and service_name='services/replicate/fulldump'
and period_start > date_add(now(), interval -$days day)
limit 1
")
);
}
public static function call(OkapiRequest $request)
{
require_once('replicate_common.inc.php');
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.");
$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
# 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...");
}
$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;
}
$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;
}
}

View File

@ -1,63 +1,68 @@
<xml>
<brief>Download OKAPI database snapshot</brief>
<issue-id>110</issue-id>
<desc>
<p>Download the latest snapshot of OKAPI database. You should call this method
only once.</p>
<brief>Download OKAPI database snapshot</brief>
<issue-id>110</issue-id>
<desc>
<p>Download the latest snapshot of OKAPI database. You should call this method
only once.</p>
<p>For some applications it might be desireable to have a quick access to the entire
Opencaching database (instead of querying for specific portions of it). You may use
OKAPI's <b>replicate</b> module to achive this condition. The <b>changelog</b> method
is the primary replication service which you will use. However, to correctly set up
your first database copy, you will need to use the <b>fulldump</b> method.</p>
<p>For some applications it might be desireable to have a quick access to the entire
Opencaching database (instead of querying for specific portions of it). You may use
OKAPI's <b>replicate</b> module to achive this state. The <b>changelog</b> method
is the primary replication service which you will use. However, to correctly set up
your first database copy, you will need to use the <b>fulldump</b> method.</p>
<p><b>Important:</b> There is a remote possibility that this method MAY change in
a non-backward-compatible way or it might even get removed. We don't plan on doing
this, but we might be forced to (i.e. to prevent abuse).</p>
<p>A couple of things for you to remember:</p>
<p><b>Note:</b> The cache descriptions will be generated using the <b>attribution_append=static</b>
parameter (see the geocache method). Full attributions are not always suitable for replication,
since they may contain dates on some installations
(<a href='https://code.google.com/p/opencaching-api/issues/detail?id=178'>why?</a>).
To make sure that static attributions are enough, consult the local Data
Licence (the Sign Up page).</p>
<ul>
<li>Currently, this method is available <b>for developers only</b>,
not for the individual users. We don't want users to download a fresh snapshot of
the entire database every time they want, this could kill our servers.
If you want to enable such features for your users, you can do it, but you
must use <b>your own server</b> for serving fulldump requests. (You don't
have to use your server to relay changelog requests.)</li>
<p>A couple of things for you to remember:</p>
<li>Fulldump is a copy of the entire database. We generate such copy once every couple of
days. This copy if intended for you to make a fresh start only. Later, you
must use the <b>changelog</b> method to keep your data up-to-date.</li>
<ul>
<li>Currently, this functionality is available <b>for developers only</b>,
NOT for the individual users. We don't want users to download a fresh snapshot of
the entire database every time they want, this could kill our servers.
If you want to enable such features for your users, you can do it, but you
must use <b>your own server</b> for data traffic (especially, fulldump requests).</li>
<li>Every time our database is extended (new fields or new object types), and you want to
make use of these new fields, you are of course allowed to download a fulldump copy again.</li>
<li>Fulldump is a copy of the entire database. We generate such copy once every couple of
days. This copy if intended for you to start only, later you must use the changelog to
keep it up-to-date.</li>
<li>There is no XMLMAP version of this file. JSON only.</li>
</ul>
<li>Every time our database is extended (new fields or new object types), and you want to
make use of these new fields, you are of course allowed to download a fulldump copy again.</li>
<p><b>Additional notes on data attribution:</b> Cache descriptions will be
generated using the <b>attribution_append=static</b> parameter (see the
geocache method). This is because the full attributions are not always suitable
for replication, since they may contain dynamically changing dates on some
installations (<a href='https://code.google.com/p/opencaching-api/issues/detail?id=178'>why?</a>).
To make sure that static attributions are enough, consult the local Data
Licence (the Sign Up page).</p>
</desc>
<returns>
<p>Compressed archive with JSON-encoded files. File named <b>index.json</b> will
contain a dictionary of the following structure:</p>
<li>There is no XMLMAP version of this file.</li>
</ul>
</desc>
<returns>
<p>Archive with JSON-encoded files. File named <b>index.json</b> will contain a dictionary
of the following structure:</p>
<ul>
<li><b>revision</b> - revision of the database snapshot contained in the archive,</li>
<li><b>data_files</b> - list of filenames which contain the changelog entries for
you to parse. Each file contains a JSON-encoded list of changelog entries, in format
described in the <b>changelog</b> method ("replace" only).</li>
<li><b>meta</b> - a dictionary of other meta data, not important.</li>
</ul>
<p>Note: We use TGZ or TBZ2 format to encode this archive:</p>
<ul>
<li><b>TGZ</b> archive (more commonly known as <b>.tar.gz</b> archive) is a TAR
archive compressed with GZIP.</li>
<li><b>TBZ2</b> archive (more commonly known as <b>.tar.bz2</b> archive) is a TAR
archive compressed with BZIP2.</li>
</ul>
<p>There are many tools available for handling these archives. In Linux, try "tar -xf filename".</p>
</returns>
<ul>
<li><b>revision</b> - revision of the database snapshot contained in the archive,</li>
<li><b>data_files</b> - list of filenames which contain the changelog entries for
you to parse. Each file contains a JSON-encoded list of changelog entries, in format
described in the <b>changelog</b> method ("replace" only).</li>
<li><b>meta</b> - a dictionary of other meta data, not important.</li>
</ul>
<p>Note: We use TGZ or TBZ2 format to encode this archive:</p>
<ul>
<li><b>TGZ</b> archive (also known as <b>.tar.gz</b>) is a TAR
archive compressed with GZIP.</li>
<li><b>TBZ2</b> archive (also known as <b>.tar.bz2</b>) is a TAR
archive compressed with BZIP2.</li>
</ul>
<p>There are many tools available for handling these archives. In Linux, try "tar -xf filename".</p>
</returns>
</xml>

View File

@ -18,35 +18,35 @@ use okapi\services\replicate\ReplicateCommon;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
require_once('replicate_common.inc.php');
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;
}
$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);
}
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,31 +1,31 @@
<xml>
<brief>Get information on current changelog and fulldump state</brief>
<issue-id>111</issue-id>
<desc>
<p><b>Beta status.</b> Get information on current changelog and fulldump state.</p>
</desc>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li>
<p><b>changelog</b> - a dictionary of the following structure:</p>
<ul>
<li><b>min_since</b> - the lowest allowed value for the <b>since</b> attribute
(the ID of the last changelog entry was already removed from our database),</li>
<li><b>revision</b> - the current revision of the database (the ID of the
newest changelog entry kept in our database),</li>
</ul>
</li>
<li>
<p><b>latest_fulldump</b> - a dictionary of the following structure:</p>
<ul>
<li><b>revision</b> - revision of the database saved in the latest fulldump,</li>
<li><b>generated_at</b> - date and time (ISO 8601) when the fulldump was generated,</li>
<li><b>size</b> - size of the file, in bytes,</li>
<li><b>size_uncompressed</b> - approximate size of the uncompressed contents, in bytes.</li>
</ul>
</li>
</ul>
</returns>
<brief>Get information on current changelog and fulldump state</brief>
<issue-id>111</issue-id>
<desc>
<p><b>Beta status.</b> Get information on current changelog and fulldump state.</p>
</desc>
<common-format-params/>
<returns>
<p>A dictionary of the following structure:</p>
<ul>
<li>
<p><b>changelog</b> - a dictionary of the following structure:</p>
<ul>
<li><b>min_since</b> - the lowest allowed value for the <b>since</b> attribute
(the ID of the last changelog entry was already removed from our database),</li>
<li><b>revision</b> - the current revision of the database (the ID of the
newest changelog entry kept in our database),</li>
</ul>
</li>
<li>
<p><b>latest_fulldump</b> - a dictionary of the following structure:</p>
<ul>
<li><b>revision</b> - revision of the database saved in the latest fulldump,</li>
<li><b>generated_at</b> - date and time (ISO 8601) when the fulldump was generated,</li>
<li><b>size</b> - size of the file, in bytes,</li>
<li><b>size_uncompressed</b> - approximate size of the uncompressed contents, in bytes.</li>
</ul>
</li>
</ul>
</returns>
</xml>

File diff suppressed because it is too large Load Diff

View File

@ -13,27 +13,27 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
$internal_id = $request->get_parameter('internal_id');
if (!$internal_id) throw new ParamMissing('internal_id');
$fields = $request->get_parameter('fields');
public static function call(OkapiRequest $request)
{
$internal_id = $request->get_parameter('internal_id');
if (!$internal_id) throw new ParamMissing('internal_id');
$fields = $request->get_parameter('fields');
# There's no need to validate the fields parameter.
# There's no need to validate the fields parameter.
$results = OkapiServiceRunner::call('services/users/by_internal_ids', new OkapiInternalRequest(
$request->consumer, $request->token, array('internal_ids' => $internal_id,
'fields' => $fields)));
$result = $results[$internal_id];
if ($result == null)
throw new InvalidParam('internal_id', "There is no user by this internal_id.");
return Okapi::formatted_response($request, $result);
}
$results = OkapiServiceRunner::call('services/users/by_internal_ids', new OkapiInternalRequest(
$request->consumer, $request->token, array('internal_ids' => $internal_id,
'fields' => $fields)));
$result = $results[$internal_id];
if ($result == null)
throw new InvalidParam('internal_id', "There is no user by this internal_id.");
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,18 +1,18 @@
<xml>
<brief>Find single user, by his internal_id</brief>
<issue-id>44</issue-id>
<desc>
<p>Retrieve information on a single user, referencing him by his <b>internal</b> ID.</p>
<p>In general, you should not use this method. Reference your users by their
normal ID - <b>user_uuid</b>. Also, internal IDs are not universally unique and are
a poor choice for a key.</p>
</desc>
<req name='internal_id'>Internal ID</req>
<req name='fields'>
<p>See services/users/user method.</p>
</req>
<common-format-params/>
<returns>
<p>See services/users/user method.</p>
</returns>
<brief>Find single user, by his internal_id</brief>
<issue-id>44</issue-id>
<desc>
<p>Retrieve information on a single user, referencing him by his <b>internal</b> ID.</p>
<p>In general, you should not use this method. Reference your users by their
normal ID - <b>user_uuid</b>. Also, internal IDs are not universally unique and are
a poor choice for a key.</p>
</desc>
<req name='internal_id'>Internal ID</req>
<req name='fields'>
<p>See services/users/user method.</p>
</req>
<common-format-params/>
<returns>
<p>See services/users/user method.</p>
</returns>
</xml>

View File

@ -13,56 +13,56 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
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');
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).
# 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("
select user_id, uuid
from user
where user_id in ('".implode("','", array_map('mysql_real_escape_string', $internal_ids))."')
");
$internalid2useruuid = array();
while ($row = mysql_fetch_assoc($rs))
{
$internalid2useruuid[$row['user_id']] = $row['uuid'];
}
mysql_free_result($rs);
$rs = Db::query("
select user_id, uuid
from user
where user_id in ('".implode("','", array_map('mysql_real_escape_string', $internal_ids))."')
");
$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)));
# 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]];
}
# 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);
}
return Okapi::formatted_response($request, $results);
}
}

View File

@ -1,20 +1,20 @@
<xml>
<brief>Find multiple users, by their internal IDs</brief>
<issue-id>45</issue-id>
<desc>
<p>This method works like the services/users/by_internal_id method, but works
with multiple users (instead of only one).</p>
</desc>
<req name='internal_ids'>
<p>Pipe-separated list of internal user IDs. No more than 500 are allowed.</p>
</req>
<req name='fields'>
<p>See services/users/user method.</p>
</req>
<common-format-params/>
<returns>
<p>A dictionary. Internal IDs you provide will be mapped to dictionary keys,
and each value will be a dictionary of fields you have selected.</p>
<p>Value of <b>null</b> means that the given internal ID haven't been found.</p>
</returns>
<brief>Find multiple users, by their internal IDs</brief>
<issue-id>45</issue-id>
<desc>
<p>This method works like the services/users/by_internal_id method, but works
with multiple users (instead of only one).</p>
</desc>
<req name='internal_ids'>
<p>Pipe-separated list of internal user IDs. No more than 500 are allowed.</p>
</req>
<req name='fields'>
<p>See services/users/user method.</p>
</req>
<common-format-params/>
<returns>
<p>A dictionary. Internal IDs you provide will be mapped to dictionary keys,
and each value will be a dictionary of fields you have selected.</p>
<p>Value of <b>null</b> means that the given internal ID haven't been found.</p>
</returns>
</xml>

View File

@ -16,27 +16,27 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
$username = $request->get_parameter('username');
if (!$username) throw new ParamMissing('username');
$fields = $request->get_parameter('fields');
public static function call(OkapiRequest $request)
{
$username = $request->get_parameter('username');
if (!$username) throw new ParamMissing('username');
$fields = $request->get_parameter('fields');
# There's no need to validate the fields parameter.
# There's no need to validate the fields parameter.
$results = OkapiServiceRunner::call('services/users/by_usernames', new OkapiInternalRequest(
$request->consumer, $request->token, array('usernames' => $username,
'fields' => $fields)));
$result = $results[$username];
if ($result == null)
throw new InvalidParam('username', "There is no user by this username.");
return Okapi::formatted_response($request, $result);
}
$results = OkapiServiceRunner::call('services/users/by_usernames', new OkapiInternalRequest(
$request->consumer, $request->token, array('usernames' => $username,
'fields' => $fields)));
$result = $results[$username];
if ($result == null)
throw new InvalidParam('username', "There is no user by this username.");
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,22 +1,22 @@
<xml>
<brief>Find single user, by his/her username</brief>
<issue-id>24</issue-id>
<desc>
<p>Retrieve information on a single user, referencing him by his username.</p>
<p><b>IMPORTANT:</b> user IDs don't change, but usernames <b>may</b> change.</p>
<p>Usually, you'll want to run this method only once, in order to get the IDs
of referenced users. You should not store usernames in your database, it is
much safer to reference them by their IDs.</p>
</desc>
<req name='username'>Name of the user (case insensitive).</req>
<req name='fields'>
<p>Same as in the services/users/user method. Pipe-separated list
of field names which you are interested with.
See services/users/user method for a list available values.</p>
</req>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected.</p>
<p>If given user does not exist, the method will respond with an HTTP 400 error.</p>
</returns>
<brief>Find single user, by his/her username</brief>
<issue-id>24</issue-id>
<desc>
<p>Retrieve information on a single user, referencing him by his username.</p>
<p><b>IMPORTANT:</b> user IDs don't change, but usernames <b>may</b> change.</p>
<p>Usually, you'll want to run this method only once, in order to get the IDs
of referenced users. You should not store usernames in your database, it is
much safer to reference them by their IDs.</p>
</desc>
<req name='username'>Name of the user (case insensitive).</req>
<req name='fields'>
<p>Same as in the services/users/user method. Pipe-separated list
of field names which you are interested with.
See services/users/user method for a list available values.</p>
</req>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected.</p>
<p>If given user does not exist, the method will respond with an HTTP 400 error.</p>
</returns>
</xml>

View File

@ -13,63 +13,63 @@ use okapi\OkapiInternalRequest;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
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');
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).
# 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("
select username, uuid
from user
where username collate utf8_general_ci in ('".implode("','", array_map('mysql_real_escape_string', $usernames))."')
");
$lower_username2useruuid = array();
while ($row = mysql_fetch_assoc($rs))
{
$lower_username2useruuid[mb_strtolower($row['username'], 'utf-8')] = $row['uuid'];
}
mysql_free_result($rs);
$rs = Db::query("
select username, uuid
from user
where username collate utf8_general_ci in ('".implode("','", array_map('mysql_real_escape_string', $usernames))."')
");
$lower_username2useruuid = array();
while ($row = mysql_fetch_assoc($rs))
{
$lower_username2useruuid[mb_strtolower($row['username'], 'utf-8')] = $row['uuid'];
}
mysql_free_result($rs);
# Retrieve data for the found user_uuids.
# 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();
}
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.
# 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')]];
}
$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);
}
return Okapi::formatted_response($request, $results);
}
}

View File

@ -1,24 +1,24 @@
<xml>
<brief>Find multiple users, by their usernames</brief>
<issue-id>25</issue-id>
<desc>
<p>This method works like the services/users/by_username method, but works
with multiple users (instead of only one).</p>
</desc>
<req name='usernames'>
<p>Pipe-separated list of usernames. No more than 500 are allowed.</p>
</req>
<req name='fields'>
<p>Same as in the services/users/user method. Pipe-separated list
of field names which you are interested with.
See services/users/user method for a list available values.</p>
</req>
<common-format-params/>
<returns>
<p>A dictionary. Usernames you provide will be mapped to dictionary keys,
and each value will be a dictionary of fields you have selected.</p>
<p>Value of <b>null</b> means that the given user haven't been found.
(This behavior is different than in the services/users/by_username method, which
responds with an HTTP 400 error in such case.)</p>
</returns>
<brief>Find multiple users, by their usernames</brief>
<issue-id>25</issue-id>
<desc>
<p>This method works like the services/users/by_username method, but works
with multiple users (instead of only one).</p>
</desc>
<req name='usernames'>
<p>Pipe-separated list of usernames. No more than 500 are allowed.</p>
</req>
<req name='fields'>
<p>Same as in the services/users/user method. Pipe-separated list
of field names which you are interested with.
See services/users/user method for a list available values.</p>
</req>
<common-format-params/>
<returns>
<p>A dictionary. Usernames you provide will be mapped to dictionary keys,
and each value will be a dictionary of fields you have selected.</p>
<p>Value of <b>null</b> means that the given user haven't been found.
(This behavior is different than in the services/users/by_username method, which
responds with an HTTP 400 error in such case.)</p>
</returns>
</xml>

View File

@ -16,41 +16,41 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function call(OkapiRequest $request)
{
$user_uuid = $request->get_parameter('user_uuid');
if (!$user_uuid)
{
if ($request->token)
{
$tmp = OkapiServiceRunner::call('services/users/by_internal_id', new OkapiInternalRequest(
$request->consumer, null, array('internal_id' => $request->token->user_id, 'fields' => 'uuid')));
$user_uuid = $tmp['uuid'];
}
else
{
throw new BadRequest("You must either: 1. supply the user_uuid argument, or "
."2. sign your request with an Access Token.");
}
}
$fields = $request->get_parameter('fields');
public static function call(OkapiRequest $request)
{
$user_uuid = $request->get_parameter('user_uuid');
if (!$user_uuid)
{
if ($request->token)
{
$tmp = OkapiServiceRunner::call('services/users/by_internal_id', new OkapiInternalRequest(
$request->consumer, null, array('internal_id' => $request->token->user_id, 'fields' => 'uuid')));
$user_uuid = $tmp['uuid'];
}
else
{
throw new BadRequest("You must either: 1. supply the user_uuid argument, or "
."2. sign your request with an Access Token.");
}
}
$fields = $request->get_parameter('fields');
# There's no need to validate the fields parameter as the 'users'
# method does this (it will raise a proper exception on invalid values).
# There's no need to validate the fields parameter as the 'users'
# method does this (it will raise a proper exception on invalid values).
$results = OkapiServiceRunner::call('services/users/users', new OkapiInternalRequest(
$request->consumer, $request->token, array('user_uuids' => $user_uuid,
'fields' => $fields)));
$result = $results[$user_uuid];
if ($result == null)
throw new InvalidParam('user_uuid', "There is no user by this ID.");
return Okapi::formatted_response($request, $result);
}
$results = OkapiServiceRunner::call('services/users/users', new OkapiInternalRequest(
$request->consumer, $request->token, array('user_uuids' => $user_uuid,
'fields' => $fields)));
$result = $results[$user_uuid];
if ($result == null)
throw new InvalidParam('user_uuid', "There is no user by this ID.");
return Okapi::formatted_response($request, $result);
}
}

View File

@ -1,45 +1,45 @@
<xml>
<brief>Retrieve information on a single user</brief>
<issue-id>26</issue-id>
<desc>
Retrieve information on a single user. This method might be also used
to retrieve data of the user in who's name the Access Token have been
issued. To do this, include the Access Token in your request, and <b>don't</b>
include the user_uuid argument.
</desc>
<req name='fields'>
<p>Pipe-separated list of field names which you are interested with.
Selected fields will be included in the response. See below for the
list of available fields.</p>
</req>
<opt name='user_uuid'>
<p>ID of the user.</p>
<p>This parameter is optional only when you sign your
request with an Access Token (Level 3 Authentication). Otherwise,
it is <b>required</b>.</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected. Currently available fields:</p>
<brief>Retrieve information on a single user</brief>
<issue-id>26</issue-id>
<desc>
Retrieve information on a single user. This method might be also used
to retrieve data of the user in who's name the Access Token have been
issued. To do this, include the Access Token in your request, and <b>don't</b>
include the user_uuid argument.
</desc>
<req name='fields'>
<p>Pipe-separated list of field names which you are interested with.
Selected fields will be included in the response. See below for the
list of available fields.</p>
</req>
<opt name='user_uuid'>
<p>ID of the user.</p>
<p>This parameter is optional only when you sign your
request with an Access Token (Level 3 Authentication). Otherwise,
it is <b>required</b>.</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected. Currently available fields:</p>
<ul>
<li><b>uuid</b> - ID of the user,</li>
<li><b>username</b> - username (login) of the user,</li>
<li><b>profile_url</b> - URL of the user's Opencaching profile page,</li>
<li>
<p><b>is_admin</b> - boolean; true is user has admin privileges.</p>
<p>This value can be accessed only with <b>Level 3</b> Authentication
and only for the user of your Access Token. For all other reads, is_admin
will equal <b>null</b>.</p>
</li>
<li><b>internal_id</b> - internal ID of the user (<b>DO NOT</b> use this!
use the <b>uuid</b> as the user identifier),</li>
<li><b>caches_found</b> - number of "Found it" and "Attended" log entries,</li>
<li><b>caches_notfound</b> - number of "Didn't find it" log entries,</li>
<li><b>caches_hidden</b> - number of caches owned,</li>
<li><b>rcmds_given</b> - number of recommendations given.</li>
</ul>
<ul>
<li><b>uuid</b> - ID of the user,</li>
<li><b>username</b> - username (login) of the user,</li>
<li><b>profile_url</b> - URL of the user's Opencaching profile page,</li>
<li>
<p><b>is_admin</b> - boolean; true is user has admin privileges.</p>
<p>This value can be accessed only with <b>Level 3</b> Authentication
and only for the user of your Access Token. For all other reads, is_admin
will equal <b>null</b>.</p>
</li>
<li><b>internal_id</b> - internal ID of the user (<b>DO NOT</b> use this!
use the <b>uuid</b> as the user identifier),</li>
<li><b>caches_found</b> - number of "Found it" and "Attended" log entries,</li>
<li><b>caches_notfound</b> - number of "Didn't find it" log entries,</li>
<li><b>caches_hidden</b> - number of caches owned,</li>
<li><b>rcmds_given</b> - number of recommendations given.</li>
</ul>
<p>If given user does not exist, the method will respond with an HTTP 400 error.</p>
</returns>
<p>If given user does not exist, the method will respond with an HTTP 400 error.</p>
</returns>
</xml>

View File

@ -13,154 +13,154 @@ use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
public static function options()
{
return array(
'min_auth_level' => 1
);
}
private static $valid_field_names = array('uuid', 'username', 'profile_url', 'internal_id', 'is_admin',
'caches_found', 'caches_notfound', 'caches_hidden', 'rcmds_given');
private static $valid_field_names = array('uuid', 'username', 'profile_url', 'internal_id', 'is_admin',
'caches_found', 'caches_notfound', 'caches_hidden', 'rcmds_given');
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("
select user_id, uuid, username, admin
from user
where uuid in ('".implode("','", array_map('mysql_real_escape_string', $user_uuids))."')
");
$results = array();
$id2uuid = array();
$uuid2id = array();
while ($row = mysql_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 'caches_found': /* handled separately */ break;
case 'caches_notfound': /* handled separately */ break;
case 'caches_hidden': /* handled separately */ break;
case 'rcmds_given': /* handled separately */ break;
default: throw new Exception("Missing field case: ".$field);
}
}
$results[$row['uuid']] = $entry;
}
mysql_free_result($rs);
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("
select user_id, uuid, username, admin
from user
where uuid in ('".implode("','", array_map('mysql_real_escape_string', $user_uuids))."')
");
$results = array();
$id2uuid = array();
$uuid2id = array();
while ($row = mysql_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 'caches_found': /* handled separately */ break;
case 'caches_notfound': /* handled separately */ break;
case 'caches_hidden': /* handled separately */ break;
case 'rcmds_given': /* handled separately */ break;
default: throw new Exception("Missing field case: ".$field);
}
}
$results[$row['uuid']] = $entry;
}
mysql_free_result($rs);
# caches_found, caches_notfound, caches_hidden
# 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.
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();
$extras = array();
if (Settings::get('OC_BRANCH') == 'oc.pl')
{
# OCPL stores user stats in 'user' table.
if (Settings::get('OC_BRANCH') == 'oc.pl')
{
# OCPL stores user stats in 'user' table.
$rs = Db::query("
select user_id, founds_count, notfounds_count, hidden_count
from user
where user_id in ('".implode("','", array_map('mysql_real_escape_string', array_keys($id2uuid)))."')
");
}
else
{
# OCDE stores user stats in 'stat_user' table.
$rs = Db::query("
select user_id, founds_count, notfounds_count, hidden_count
from user
where user_id in ('".implode("','", array_map('mysql_real_escape_string', array_keys($id2uuid)))."')
");
}
else
{
# OCDE stores user stats in 'stat_user' table.
$rs = Db::query("
select
u.user_id,
ifnull(su.found, 0) as founds_count,
ifnull(su.notfound, 0) as notfounds_count,
ifnull(su.hidden, 0) as hidden_count
from
user u
left join stat_user su
on su.user_id = u.user_id
where u.user_id in ('".implode("','", array_map('mysql_real_escape_string', array_keys($id2uuid)))."')
");
}
$rs = Db::query("
select
u.user_id,
ifnull(su.found, 0) as founds_count,
ifnull(su.notfound, 0) as notfounds_count,
ifnull(su.hidden, 0) as hidden_count
from
user u
left join stat_user su
on su.user_id = u.user_id
where u.user_id in ('".implode("','", array_map('mysql_real_escape_string', array_keys($id2uuid)))."')
");
}
while ($row = mysql_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'];
}
mysql_free_result($rs);
while ($row = mysql_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'];
}
mysql_free_result($rs);
if (in_array('rcmds_given', $fields))
{
$rs = Db::query("
select user_id, count(*) as rcmds_given
from cache_rating
where user_id in ('".implode("','", array_map('mysql_real_escape_string', array_keys($id2uuid)))."')
group by user_id
");
$rcmds_counts = array();
while ($row = mysql_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;
}
}
if (in_array('rcmds_given', $fields))
{
$rs = Db::query("
select user_id, count(*) as rcmds_given
from cache_rating
where user_id in ('".implode("','", array_map('mysql_real_escape_string', array_keys($id2uuid)))."')
group by user_id
");
$rcmds_counts = array();
while ($row = mysql_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.
# "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];
}
}
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.
# 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;
foreach ($user_uuids as $user_uuid)
if (!isset($results[$user_uuid]))
$results[$user_uuid] = null;
return Okapi::formatted_response($request, $results);
}
return Okapi::formatted_response($request, $results);
}
}

Some files were not shown because too many files have changed in this diff Show More