189 lines
6.2 KiB
PHP
189 lines
6.2 KiB
PHP
<?
|
|
|
|
namespace okapi;
|
|
|
|
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/info',
|
|
'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',
|
|
);
|
|
|
|
/** 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 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();
|
|
|
|
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.");
|
|
}
|
|
|
|
$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);
|
|
|
|
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);
|
|
}
|
|
|
|
private static function save_stats($service_name, $request, $runtime)
|
|
{
|
|
# Getting rid of nulls. MySQL PRIMARY keys cannot contain nullable columns.
|
|
# Temp table doesn't have primary key, but other stats tables (which are
|
|
# dependant on stats table) - do.
|
|
|
|
if ($request !== null) {
|
|
$consumer_key = ($request->consumer != null) ? $request->consumer->key : 'anonymous';
|
|
$user_id = (($request->token != null) && ($request->token instanceof OkapiAccessToken)) ? $request->token->user_id : -1;
|
|
if ($request->is_http_request() && ($service_name[0] == 's')) # 's' for "services/", we don't want "extra/" included
|
|
$calltype = 'http';
|
|
else
|
|
$calltype = 'internal';
|
|
} else {
|
|
$consumer_key = 'internal';
|
|
$user_id = -1;
|
|
$calltype = 'internal';
|
|
}
|
|
|
|
Db::execute("
|
|
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)."'
|
|
);
|
|
");
|
|
}
|
|
} |