OKAPI Project update (r483).
@@ -4,6 +4,7 @@ namespace okapi;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\views\menu\OkapiMenu;
|
||||
|
||||
#
|
||||
@@ -14,26 +15,12 @@ use okapi\views\menu\OkapiMenu;
|
||||
# To learn more about OKAPI, see core.php.
|
||||
#
|
||||
|
||||
$GLOBALS['rootpath'] = '../'; # this is for OC-code compatibility, OC requires this
|
||||
$GLOBALS['no-session'] = true; # turn off OC-code session starting
|
||||
$GLOBALS['no-ob'] = true; # turn off OC-code GZIP output buffering
|
||||
$GLOBALS['rootpath'] = '../'; # this is for OC-code compatibility
|
||||
|
||||
require_once($GLOBALS['rootpath'].'okapi/core.php');
|
||||
OkapiErrorHandler::$treat_notices_as_errors = true;
|
||||
require_once($GLOBALS['rootpath'].'okapi/urls.php');
|
||||
|
||||
# OKAPI does not use sessions. The following statement will allow concurrent
|
||||
# requests to be fired from browser.
|
||||
if (session_id())
|
||||
{
|
||||
# WRTODO: Move this to some kind of cronjob, to prevent admin-spamming in case on an error.
|
||||
throw new Exception("Session started when should not be! You have to patch your OC installation. ".
|
||||
"You have to check \"if ((!isset(\$GLOBALS['no-session'])) || (\$GLOBALS['no-session'] == false))\" ".
|
||||
"before executing session_start.");
|
||||
}
|
||||
|
||||
# Make sure OC did not start anything suspicious, like ob_start('ob_gzhandler').
|
||||
# OKAPI makes it's own decisions whether "to gzip or not to gzip".
|
||||
if (ob_list_handlers() == array('default output handler'))
|
||||
{
|
||||
# We will assume that this one comes from "output_buffering" being turned on
|
||||
@@ -43,13 +30,6 @@ if (ob_list_handlers() == array('default output handler'))
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
if (count(ob_list_handlers()) > 0)
|
||||
{
|
||||
# WRTODO: Move this to some kind of cronjob, to prevent admin-spamming in case on an error.
|
||||
throw new Exception("Output buffering started while it should not be! You have to patch you OC ".
|
||||
"installation (probable lib/common.inc.php file). You have to check \"if ((!isset(\$GLOBALS['no-ob'])) ".
|
||||
"|| (\$GLOBALS['no-ob'] == false))\" before executing ob_start. Refer to installation docs.");
|
||||
}
|
||||
|
||||
class OkapiScriptEntryPointController
|
||||
{
|
||||
@@ -92,7 +72,7 @@ class OkapiScriptEntryPointController
|
||||
# Pattern matched! Moving on to the proper View...
|
||||
|
||||
array_shift($matches);
|
||||
require_once "views/$namespace.php";
|
||||
require_once($GLOBALS['rootpath']."okapi/views/$namespace.php");
|
||||
$response = call_user_func_array(array('\\okapi\\views\\'.
|
||||
str_replace('/', '\\', $namespace).'\\View', 'call'), $matches);
|
||||
if ($response)
|
||||
@@ -108,7 +88,7 @@ class OkapiScriptEntryPointController
|
||||
|
||||
# None of the patterns matched OR method threw the Http404 exception.
|
||||
|
||||
require_once "views/http404.php";
|
||||
require_once($GLOBALS['rootpath']."okapi/views/http404.php");
|
||||
$response = \okapi\views\http404\View::call();
|
||||
$response->display();
|
||||
}
|
||||
|
||||
@@ -4,19 +4,8 @@ namespace okapi;
|
||||
|
||||
# OKAPI Framework -- Wojciech Rygielski <rygielski@mimuw.edu.pl>
|
||||
|
||||
# Including this file will initialize OKAPI Framework with its default
|
||||
# exception and error handlers. OKAPI is strict about PHP warnings and
|
||||
# notices. You might need to temporarily disable the error handler in
|
||||
# order to get it to work in some legacy code. Do this by calling
|
||||
# OkapiErrorHandler::disable() BEFORE calling the "buggy" code, and
|
||||
# OkapiErrorHandler::reenable() AFTER returning from it.
|
||||
|
||||
# When I was installing it for the first time on the opencaching.pl
|
||||
# site, I needed to change some minor things in order to get it to work
|
||||
# properly, i.e., turn some "die()"-like statements into exceptions.
|
||||
# Contact me for details.
|
||||
|
||||
# I hope this will come in handy... - WR.
|
||||
# If you want to include_once/require_once OKAPI in your code,
|
||||
# see facade.php. You should not rely on any other file, never!
|
||||
|
||||
use Exception;
|
||||
use ErrorException;
|
||||
@@ -34,19 +23,19 @@ use okapi\cronjobs\CronJobController;
|
||||
/** Return an array of email addresses which always get notified on OKAPI errors. */
|
||||
function get_admin_emails()
|
||||
{
|
||||
$emails = array(
|
||||
isset($GLOBALS['sql_errormail']) ? $GLOBALS['sql_errormail'] : 'root@localhost',
|
||||
);
|
||||
$emails = array();
|
||||
if (class_exists("okapi\\Settings"))
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (Settings::get('EXTRA_ADMINS') as $email)
|
||||
foreach (Settings::get('ADMINS') as $email)
|
||||
if (!in_array($email, $emails))
|
||||
$emails[] = $email;
|
||||
}
|
||||
catch (Exception $e) { /* pass */ }
|
||||
}
|
||||
if (count($emails) == 0)
|
||||
$emails[] = 'root@localhost';
|
||||
return $emails;
|
||||
}
|
||||
|
||||
@@ -66,7 +55,7 @@ class BadRequest extends Exception {
|
||||
'reason_stack' => array(),
|
||||
);
|
||||
$this->provideExtras($extras);
|
||||
$extras['more_info'] = $GLOBALS['absolute_server_URI']."okapi/introduction.html#errors";
|
||||
$extras['more_info'] = Settings::get('SITE_URL')."okapi/introduction.html#errors";
|
||||
return json_encode(array("error" => $extras));
|
||||
}
|
||||
}
|
||||
@@ -96,7 +85,7 @@ class OkapiExceptionHandler
|
||||
else
|
||||
header("HTTP/1.0 401 Unauthorized");
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: text/plain; charset=utf-8");
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
print $e->getOkapiJSON();
|
||||
}
|
||||
@@ -108,7 +97,7 @@ class OkapiExceptionHandler
|
||||
|
||||
header("HTTP/1.0 400 Bad Request");
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: text/plain; charset=utf-8");
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
print $e->getOkapiJSON();
|
||||
}
|
||||
@@ -133,7 +122,7 @@ class OkapiExceptionHandler
|
||||
|
||||
$exception_info = self::get_exception_info($e);
|
||||
|
||||
if (isset($GLOBALS['debug_page']) && $GLOBALS['debug_page'])
|
||||
if (class_exists("okapi\\Settings") && (Settings::get('DEBUG')))
|
||||
{
|
||||
print "\n\nBUT! Since the DEBUG flag is on, then you probably ARE a developer yourself.\n";
|
||||
print "Let's cut to the chase then:";
|
||||
@@ -146,22 +135,50 @@ class OkapiExceptionHandler
|
||||
}
|
||||
else
|
||||
{
|
||||
$admin_email = implode(", ", get_admin_emails());
|
||||
$sender_email = isset($GLOBALS['emailaddr']) ? $GLOBALS['emailaddr'] : 'root@localhost';
|
||||
mail(
|
||||
$admin_email,
|
||||
"OKAPI Method Error - ".(
|
||||
isset($GLOBALS['absolute_server_URI'])
|
||||
? $GLOBALS['absolute_server_URI'] : "unknown location"
|
||||
),
|
||||
$subject = "OKAPI Method Error - ".substr(
|
||||
$_SERVER['REQUEST_URI'], 0, strpos(
|
||||
$_SERVER['REQUEST_URI'].'?', '?'));
|
||||
|
||||
$message = (
|
||||
"OKAPI caught the following exception while executing API method request.\n".
|
||||
"This is an error in OUR code and should be fixed. Please contact the\n".
|
||||
"developer of the module that threw this error. Thanks!\n\n".
|
||||
$exception_info,
|
||||
"Content-Type: text/plain; charset=utf-8\n".
|
||||
"From: OKAPI <$sender_email>\n".
|
||||
"Reply-To: $sender_email\n"
|
||||
$exception_info
|
||||
);
|
||||
try
|
||||
{
|
||||
Okapi::mail_admins($subject, $message);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
# Unable to use full-featured mail_admins version. We'll use a backup.
|
||||
# We need to make sure we're not spamming.
|
||||
|
||||
$lock_file = "/tmp/okapi-fatal-error-mode";
|
||||
$last_email = false;
|
||||
if (file_exists($lock_file))
|
||||
$last_email = filemtime($lock_file);
|
||||
if ($last_email === false) {
|
||||
# Assume this is the first email.
|
||||
$last_email = 0;
|
||||
}
|
||||
if (time() - $last_email < 60) {
|
||||
# Send no more than one per minute.
|
||||
return;
|
||||
}
|
||||
@touch($lock_file);
|
||||
|
||||
$admin_email = implode(", ", get_admin_emails());
|
||||
$sender_email = class_exists("okapi\\Settings") ? Settings::get('FROM_FIELD') : 'root@localhost';
|
||||
$subject = "Fatal error mode: ".$subject;
|
||||
$message = "Fatal error mode: OKAPI will send at most ONE message per minute.\n\n".$message;
|
||||
$headers = (
|
||||
"Content-Type: text/plain; charset=utf-8\n".
|
||||
"From: OKAPI <$sender_email>\n".
|
||||
"Reply-To: $sender_email\n"
|
||||
);
|
||||
mail($admin_email, $subject, $message, $headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,6 +321,20 @@ class DbException extends Exception {}
|
||||
/** Database access class. Use this instead of mysql_query, sql or sqlValue. */
|
||||
class Db
|
||||
{
|
||||
private static $connected = false;
|
||||
|
||||
public static function connect()
|
||||
{
|
||||
if (mysql_connect(Settings::get('DB_SERVER'), Settings::get('DB_USERNAME'), Settings::get('DB_PASSWORD')))
|
||||
{
|
||||
mysql_select_db(Settings::get('DB_NAME'));
|
||||
mysql_query("set names 'utf8'");
|
||||
self::$connected = true;
|
||||
}
|
||||
else
|
||||
throw new Exception("Could not connect to MySQL: ".mysql_error());
|
||||
}
|
||||
|
||||
public static function select_row($query)
|
||||
{
|
||||
$rows = self::select_all($query);
|
||||
@@ -379,6 +410,8 @@ class Db
|
||||
|
||||
public static function query($query)
|
||||
{
|
||||
if (!self::$connected)
|
||||
self::connect();
|
||||
$rs = mysql_query($query);
|
||||
if (!$rs)
|
||||
{
|
||||
@@ -392,7 +425,7 @@ class Db
|
||||
# Including OAuth internals. Preparing OKAPI Consumer and Token classes.
|
||||
#
|
||||
|
||||
require_once('oauth.php');
|
||||
require_once($GLOBALS['rootpath']."okapi/oauth.php");
|
||||
|
||||
class OkapiConsumer extends OAuthConsumer
|
||||
{
|
||||
@@ -556,16 +589,18 @@ class OkapiOAuthServer extends OAuthServer
|
||||
|
||||
# Including local datastore and settings (connecting SQL database etc.).
|
||||
|
||||
require_once('settings.php');
|
||||
require_once('datastore.php');
|
||||
require_once($GLOBALS['rootpath']."okapi/settings.php");
|
||||
require_once($GLOBALS['rootpath']."okapi/datastore.php");
|
||||
|
||||
class OkapiHttpResponse
|
||||
{
|
||||
public $status = "200 OK";
|
||||
public $cache_control = "no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0";
|
||||
public $content_type = "text/plain; charset=utf-8";
|
||||
public $content_disposition = null;
|
||||
public $allow_gzip = true;
|
||||
public $connection_close = false;
|
||||
public $etag = null;
|
||||
|
||||
/** Use this only as a setter, use get_body or print_body for reading! */
|
||||
public $body;
|
||||
@@ -616,11 +651,13 @@ class OkapiHttpResponse
|
||||
header("HTTP/1.1 ".$this->status);
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: ".$this->content_type);
|
||||
header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0");
|
||||
header("Cache-Control: ".$this->cache_control);
|
||||
if ($this->connection_close)
|
||||
header("Connection: close");
|
||||
if ($this->content_disposition)
|
||||
header("Content-Disposition: ".$this->content_disposition);
|
||||
if ($this->etag)
|
||||
header("ETag: $this->etag");
|
||||
|
||||
# Make sure that gzip is supported by the client.
|
||||
$try_gzip = $this->allow_gzip;
|
||||
@@ -660,8 +697,16 @@ class OkapiRedirectResponse extends OkapiHttpResponse
|
||||
|
||||
class OkapiLock
|
||||
{
|
||||
private $lockfile;
|
||||
private $lock;
|
||||
|
||||
/** Note: This does NOT tell you if someone currently locked it! */
|
||||
public static function exists($name)
|
||||
{
|
||||
$lockfile = Okapi::get_var_dir()."/okapi-lock-".$name;
|
||||
return file_exists($lockfile);
|
||||
}
|
||||
|
||||
public static function get($name)
|
||||
{
|
||||
return new OkapiLock($name);
|
||||
@@ -677,13 +722,13 @@ class OkapiLock
|
||||
}
|
||||
else
|
||||
{
|
||||
$lockfile = Okapi::get_var_dir()."/okapi-lock-".$name;
|
||||
if (!file_exists($lockfile))
|
||||
$this->lockfile = Okapi::get_var_dir()."/okapi-lock-".$name;
|
||||
if (!file_exists($this->lockfile))
|
||||
{
|
||||
$fp = fopen($lockfile, "wb");
|
||||
$fp = fopen($this->lockfile, "wb");
|
||||
fclose($fp);
|
||||
}
|
||||
$this->lock = sem_get(fileinode($lockfile));
|
||||
$this->lock = sem_get(fileinode($this->lockfile));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,6 +743,15 @@ class OkapiLock
|
||||
if ($this->lock !== null)
|
||||
sem_release($this->lock);
|
||||
}
|
||||
|
||||
public function remove()
|
||||
{
|
||||
if ($this->lock !== null)
|
||||
{
|
||||
sem_remove($this->lock);
|
||||
unlink($this->lockfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Container for various OKAPI functions. */
|
||||
@@ -705,7 +759,7 @@ class Okapi
|
||||
{
|
||||
public static $data_store;
|
||||
public static $server;
|
||||
public static $revision = 424; # This gets replaced in automatically deployed packages
|
||||
public static $revision = 483; # This gets replaced in automatically deployed packages
|
||||
private static $okapi_vars = null;
|
||||
|
||||
/** Get a variable stored in okapi_vars. If variable not found, return $default. */
|
||||
@@ -743,16 +797,54 @@ class Okapi
|
||||
self::$okapi_vars[$varname] = $value;
|
||||
}
|
||||
|
||||
/** Return true if the server is running in a debug mode. */
|
||||
public static function debug_mode()
|
||||
{
|
||||
return (isset($GLOBALS['debug_page']) && $GLOBALS['debug_page']);
|
||||
}
|
||||
|
||||
/** Send an email message to local OKAPI administrators. */
|
||||
public static function mail_admins($subject, $message)
|
||||
{
|
||||
self::mail_from_okapi(get_admin_emails(), $subject, $message);
|
||||
# First, make sure we're not spamming.
|
||||
|
||||
$cache_key = 'mail_admins_counter/'.(floor(time() / 3600) * 3600).'/'.md5($subject);
|
||||
try {
|
||||
$counter = Cache::get($cache_key);
|
||||
} catch (DbException $e) {
|
||||
# Why catching exceptions here? See bug#156.
|
||||
$counter = null;
|
||||
}
|
||||
if ($counter === null)
|
||||
$counter = 0;
|
||||
$counter++;
|
||||
try {
|
||||
Cache::set($cache_key, $counter, 3600);
|
||||
} catch (DbException $e) {
|
||||
# Why catching exceptions here? See bug#156.
|
||||
}
|
||||
if ($counter <= 5)
|
||||
{
|
||||
# We're not spamming yet.
|
||||
|
||||
self::mail_from_okapi(get_admin_emails(), $subject, $message);
|
||||
}
|
||||
else
|
||||
{
|
||||
# We are spamming. Prevent sending more emails.
|
||||
|
||||
$content_cache_key_prefix = 'mail_admins_spam/'.(floor(time() / 3600) * 3600).'/';
|
||||
$timeout = 86400;
|
||||
if ($counter == 6)
|
||||
{
|
||||
self::mail_from_okapi(get_admin_emails(), "Anti-spam mode activated for '$subject'",
|
||||
"OKAPI has activated an \"anti-spam\" mode for the following subject:\n\n".
|
||||
"\"$subject\"\n\n".
|
||||
"Anti-spam mode is activiated when more than 5 messages with\n".
|
||||
"the same subject are sent within one hour.\n\n".
|
||||
"Additional debug information:\n".
|
||||
"- counter cache key: $cache_key\n".
|
||||
"- content prefix: $content_cache_key_prefix<n>\n".
|
||||
"- content timeout: $timeout\n"
|
||||
);
|
||||
}
|
||||
$content_cache_key = $content_cache_key_prefix.$counter;
|
||||
Cache::set($content_cache_key, $message, $timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/** Send an email message from OKAPI to the given recipients. */
|
||||
@@ -766,7 +858,7 @@ class Okapi
|
||||
}
|
||||
if (!is_array($email_addresses))
|
||||
$email_addresses = array($email_addresses);
|
||||
$sender_email = isset($GLOBALS['emailaddr']) ? $GLOBALS['emailaddr'] : 'root@localhost';
|
||||
$sender_email = class_exists("okapi\\Settings") ? Settings::get('FROM_FIELD') : 'root@localhost';
|
||||
mail(implode(", ", $email_addresses), $subject, $message,
|
||||
"Content-Type: text/plain; charset=utf-8\n".
|
||||
"From: OKAPI <$sender_email>\n".
|
||||
@@ -780,7 +872,7 @@ class Okapi
|
||||
$dir = Settings::get('VAR_DIR');
|
||||
if ($dir != null)
|
||||
return rtrim($dir, "/");
|
||||
return isset($GLOBALS['dynbasepath']) ? $GLOBALS['dynbasepath'] : "/tmp";
|
||||
throw new Exception("You need to set a valid VAR_DIR.");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -826,7 +918,7 @@ class Okapi
|
||||
public static function get_normalized_site_name($site_url = null)
|
||||
{
|
||||
if ($site_url == null)
|
||||
$site_url = $GLOBALS['absolute_server_URI'];
|
||||
$site_url = Settings::get('SITE_URL');
|
||||
$matches = null;
|
||||
if (preg_match("#^https?://(www.)?opencaching.([a-z.]+)/$#", $site_url, $matches)) {
|
||||
return "OpenCaching.".strtoupper($matches[2]);
|
||||
@@ -881,7 +973,7 @@ class Okapi
|
||||
$nearest_event = Okapi::get_var("cron_nearest_event");
|
||||
if ($nearest_event + 0 <= time())
|
||||
{
|
||||
require_once 'cronjobs.php';
|
||||
require_once($GLOBALS['rootpath']."okapi/cronjobs.php");
|
||||
$nearest_event = CronJobController::run_jobs('pre-request');
|
||||
Okapi::set_var("cron_nearest_event", $nearest_event);
|
||||
}
|
||||
@@ -898,7 +990,7 @@ class Okapi
|
||||
{
|
||||
set_time_limit(0);
|
||||
ignore_user_abort(true);
|
||||
require_once 'cronjobs.php';
|
||||
require_once($GLOBALS['rootpath']."okapi/cronjobs.php");
|
||||
$nearest_event = CronJobController::run_jobs('cron-5');
|
||||
Okapi::set_var("cron_nearest_event", $nearest_event);
|
||||
}
|
||||
@@ -1023,7 +1115,7 @@ class Okapi
|
||||
*/
|
||||
public static function register_new_consumer($appname, $appurl, $email)
|
||||
{
|
||||
include_once 'service_runner.php';
|
||||
require_once($GLOBALS['rootpath']."okapi/service_runner.php");
|
||||
$consumer = new OkapiConsumer(Okapi::generate_key(20), Okapi::generate_key(40),
|
||||
$appname, $appurl, $email);
|
||||
$sample_cache = OkapiServiceRunner::call("services/caches/search/all",
|
||||
@@ -1032,7 +1124,6 @@ class Okapi
|
||||
$sample_cache_code = $sample_cache['results'][0];
|
||||
else
|
||||
$sample_cache_code = "CACHECODE";
|
||||
$sender_email = isset($GLOBALS['emailaddr']) ? $GLOBALS['emailaddr'] : 'root@localhost';
|
||||
|
||||
# Message for the Consumer.
|
||||
ob_start();
|
||||
@@ -1042,7 +1133,7 @@ class Okapi
|
||||
print "Note: Consumer Secret is needed only when you intend to use OAuth.\n";
|
||||
print "You don't need Consumer Secret for Level 1 Authentication.\n\n";
|
||||
print "Now you may easily access Level 1 methods of OKAPI! For example:\n";
|
||||
print $GLOBALS['absolute_server_URI']."okapi/services/caches/geocache?cache_code=$sample_cache_code&consumer_key=$consumer->key\n\n";
|
||||
print Settings::get('SITE_URL')."okapi/services/caches/geocache?cache_code=$sample_cache_code&consumer_key=$consumer->key\n\n";
|
||||
print "If you plan on using OKAPI for a longer time, then you should subscribe\n";
|
||||
print "to the OKAPI News blog to stay up-to-date. Check it out here:\n";
|
||||
print "http://opencaching-api.blogspot.com/\n\n";
|
||||
@@ -1347,14 +1438,14 @@ class Okapi
|
||||
'Traditional' => 2, 'Multi' => 3, 'Quiz' => 7, 'Virtual' => 4,
|
||||
# Additional types (may get changed)
|
||||
'Other' => 1, 'Webcam' => 5, 'Event' => 6,
|
||||
'Moving' => 8, 'Own' => 9,
|
||||
'Moving' => 8, 'Podcast' => 9, 'Own' => 10,
|
||||
),
|
||||
'oc.de' => array(
|
||||
# Primary types (documented, cannot change)
|
||||
'Traditional' => 2, 'Multi' => 3, 'Quiz' => 7, 'Virtual' => 4,
|
||||
# Additional types (might get changed)
|
||||
'Other' => 1, 'Webcam' => 5, 'Event' => 6,
|
||||
'Math/Physics-Cache' => 8, 'Moving' => 9, 'Drive-In' => 10,
|
||||
'Math/Physics' => 8, 'Moving' => 9, 'Drive-In' => 10,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1485,7 +1576,7 @@ class Cache
|
||||
{
|
||||
/**
|
||||
* Save object $value under the key $key. Store this object for
|
||||
* $timeout seconds. $key must be a string of max 32 characters in length.
|
||||
* $timeout seconds. $key must be a string of max 64 characters in length.
|
||||
* $value might be any serializable PHP object.
|
||||
*/
|
||||
public static function set($key, $value, $timeout)
|
||||
@@ -1500,6 +1591,23 @@ class Cache
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Scored version of set. Elements set up this way will expire when they're
|
||||
* not used.
|
||||
*/
|
||||
public static function set_scored($key, $value)
|
||||
{
|
||||
Db::execute("
|
||||
replace into okapi_cache (`key`, value, expires, score)
|
||||
values (
|
||||
'".mysql_real_escape_string($key)."',
|
||||
'".mysql_real_escape_string(gzdeflate(serialize($value)))."',
|
||||
date_add(now(), interval 120 day),
|
||||
1.0
|
||||
);
|
||||
");
|
||||
}
|
||||
|
||||
/** Do 'set' on many keys at once. */
|
||||
public static function set_many($dict, $timeout)
|
||||
{
|
||||
@@ -1526,15 +1634,23 @@ class Cache
|
||||
*/
|
||||
public static function get($key)
|
||||
{
|
||||
$blob = Db::select_value("
|
||||
select value
|
||||
$rs = Db::query("
|
||||
select value, score
|
||||
from okapi_cache
|
||||
where
|
||||
`key` = '".mysql_real_escape_string($key)."'
|
||||
and expires > now()
|
||||
");
|
||||
list($blob, $score) = mysql_fetch_array($rs);
|
||||
if (!$blob)
|
||||
return null;
|
||||
if ($score != null) # Only non-null entries are scored.
|
||||
{
|
||||
Db::execute("
|
||||
insert into okapi_cache_reads (`cache_key`)
|
||||
values ('".mysql_real_escape_string($key)."')
|
||||
");
|
||||
}
|
||||
return unserialize(gzinflate($blob));
|
||||
}
|
||||
|
||||
@@ -1592,6 +1708,34 @@ class Cache
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes it is desireable to get the cached contents in a file,
|
||||
* instead in a string (i.e. for imagecreatefromgd2). In such cases, you
|
||||
* may use this class instead of the Cache class.
|
||||
*/
|
||||
class FileCache
|
||||
{
|
||||
public static function get_file_path($key)
|
||||
{
|
||||
$filename = Okapi::get_var_dir()."/okapi_filecache_".md5($key);
|
||||
if (!file_exists($filename))
|
||||
return null;
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note, there is no $timeout (time to live) parameter. Currently,
|
||||
* OKAPI will delete every old file after certain amount of time.
|
||||
* See CacheCleanupCronJob for details.
|
||||
*/
|
||||
public static function set($key, $value)
|
||||
{
|
||||
$filename = Okapi::get_var_dir()."/okapi_filecache_".md5($key);
|
||||
file_put_contents($filename, $value);
|
||||
return $filename;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an OKAPI web method request.
|
||||
*
|
||||
@@ -1607,6 +1751,14 @@ abstract class OkapiRequest
|
||||
{
|
||||
public $consumer;
|
||||
public $token;
|
||||
public $etag; # see: http://en.wikipedia.org/wiki/HTTP_ETag
|
||||
|
||||
/**
|
||||
* Set this to true, for some method to allow you to set higher "limit"
|
||||
* parameter than usually allowed. This should be used ONLY by trusted,
|
||||
* fast and *cacheable* code!
|
||||
*/
|
||||
public $skip_limits = false;
|
||||
|
||||
/**
|
||||
* Return request parameter, or NULL when not found. Use this instead of
|
||||
@@ -1614,6 +1766,13 @@ abstract class OkapiRequest
|
||||
*/
|
||||
public abstract function get_parameter($name);
|
||||
|
||||
/**
|
||||
* Return the list of all request parameters. You should use this method
|
||||
* ONLY when you use <import-params/> in your documentation and you want
|
||||
* to pass all unknown parameters onto the other method.
|
||||
*/
|
||||
public abstract function get_all_parameters_including_unknown();
|
||||
|
||||
/** Return true, if this requests is to be logged as HTTP request in okapi_stats. */
|
||||
public abstract function is_http_request();
|
||||
}
|
||||
@@ -1657,6 +1816,11 @@ class OkapiInternalRequest extends OkapiRequest
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_all_parameters_including_unknown()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function is_http_request() { return $this->perceive_as_http_request; }
|
||||
}
|
||||
|
||||
@@ -1707,7 +1871,7 @@ class OkapiHttpRequest extends OkapiRequest
|
||||
# Enables debugging Level 2 and Level 3 methods. Should not be committed
|
||||
# at any time! If run on production server, make it an error.
|
||||
|
||||
if (!Okapi::debug_mode())
|
||||
if (!Settings::get('DEBUG'))
|
||||
{
|
||||
throw new Exception("Attempted to set DEBUG_AS_USERNAME set in ".
|
||||
"non-debug environment. Accidental commit?");
|
||||
@@ -1785,6 +1949,11 @@ class OkapiHttpRequest extends OkapiRequest
|
||||
$this->consumer = new OkapiDebugConsumer();
|
||||
$this->token = new OkapiDebugAccessToken($debug_user_id);
|
||||
}
|
||||
|
||||
# Read the ETag.
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_NONE_MATCH']))
|
||||
$this->etag = $_SERVER['HTTP_IF_NONE_MATCH'];
|
||||
}
|
||||
|
||||
private function init_request()
|
||||
@@ -1815,5 +1984,10 @@ class OkapiHttpRequest extends OkapiRequest
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function get_all_parameters_including_unknown()
|
||||
{
|
||||
return $this->request->get_parameters();
|
||||
}
|
||||
|
||||
public function is_http_request() { return true; }
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace okapi\cronjobs;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\OkapiLock;
|
||||
use okapi\OkapiExceptionHandler;
|
||||
use okapi\Db;
|
||||
@@ -45,6 +46,7 @@ class CronJobController
|
||||
new AdminStatsSender(),
|
||||
new LocaleChecker(),
|
||||
new FulldumpGeneratorJob(),
|
||||
new TileTreeUpdater(),
|
||||
);
|
||||
foreach ($cache as $cronjob)
|
||||
if (!in_array($cronjob->get_type(), array('pre-request', 'cron-5')))
|
||||
@@ -59,7 +61,7 @@ class CronJobController
|
||||
*/
|
||||
public static function run_jobs($type)
|
||||
{
|
||||
require_once $GLOBALS['rootpath'].'okapi/service_runner.php';
|
||||
require_once($GLOBALS['rootpath'].'okapi/service_runner.php');
|
||||
|
||||
# We don't want other cronjobs of the same time to run simultanously.
|
||||
$lock = OkapiLock::get('cronjobs-'.$type);
|
||||
@@ -108,6 +110,8 @@ class CronJobController
|
||||
*/
|
||||
public static function force_run($job_name)
|
||||
{
|
||||
require_once($GLOBALS['rootpath'].'okapi/service_runner.php');
|
||||
|
||||
foreach (self::get_enabled_cronjobs() as $cronjob)
|
||||
{
|
||||
if (($cronjob->get_name() == $job_name) || ($cronjob->get_name() == "okapi\\cronjobs\\".$job_name))
|
||||
@@ -241,16 +245,75 @@ class OAuthCleanupCronJob extends PrerequestCronJob
|
||||
}
|
||||
}
|
||||
|
||||
/** Deletes all expired cache elements, once per hour. */
|
||||
/** Clean up the cache, once per hour. */
|
||||
class CacheCleanupCronJob extends Cron5Job
|
||||
{
|
||||
public function get_period() { return 3600; } # 1 hour
|
||||
public function get_period() { return 3600; }
|
||||
public function execute()
|
||||
{
|
||||
# Delete all expired elements.
|
||||
|
||||
Db::execute("
|
||||
delete from okapi_cache
|
||||
where expires < now()
|
||||
");
|
||||
|
||||
# Update the "score" stats.
|
||||
|
||||
$multiplier = 0.9; # Every hour, all scores are multiplied by this.
|
||||
$limit = 0.01; # When a score reaches this limit, the entry is deleted.
|
||||
|
||||
# Every time the entry is read, its score is incread by 1. If an entry
|
||||
# is saved, but never read, it will be deleted after log(L,M) hours
|
||||
# (log(0.01, 0.9) = 43h). If an entry is read 1000000 times and then
|
||||
# never read anymore, it will be deleted after log(1000000/L, 1/M)
|
||||
# hours (log(1000000/0.01, 1/0.9) = 174h = 7 days).
|
||||
|
||||
Db::execute("
|
||||
update okapi_cache
|
||||
set score = score * $multiplier
|
||||
where score is not null
|
||||
");
|
||||
Db::execute("
|
||||
update
|
||||
okapi_cache c,
|
||||
(
|
||||
select cache_key, count(*) as count
|
||||
from okapi_cache_reads
|
||||
group by cache_key
|
||||
) cr
|
||||
set c.score = c.score + cr.count
|
||||
where
|
||||
c.`key` = cr.cache_key
|
||||
and c.score is not null
|
||||
");
|
||||
Db::execute("truncate okapi_cache_reads");
|
||||
|
||||
# Delete elements with the lowest score. Entries which have been set
|
||||
# but never read will be removed after 36 hours (0.9^36 < 0.02 < 0.9^35).
|
||||
|
||||
Db::execute("
|
||||
delete from okapi_cache
|
||||
where
|
||||
score is not null
|
||||
and score < $limit
|
||||
");
|
||||
Db::query("optimize table okapi_cache");
|
||||
|
||||
# FileCache does not have an expiry date. We will delete all files older
|
||||
# than 24 hours.
|
||||
|
||||
$dir = Okapi::get_var_dir();
|
||||
if ($dh = opendir($dir)) {
|
||||
while (($file = readdir($dh)) !== false) {
|
||||
if (strpos($file, "okapi_filecache_") === 0) {
|
||||
if (filemtime("$dir/$file") < time() - 86400) {
|
||||
unlink("$dir/$file");
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($dh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +418,7 @@ class CheckCronTab2 extends PrerequestCronJob
|
||||
"Hello. OKAPI detected, that it's crontab is not working properly.\n".
|
||||
"Please check your configuration or contact OKAPI developers.\n\n".
|
||||
"This line should be present among your crontab entries:\n\n".
|
||||
"*/5 * * * * wget -O - -q -t 1 ".$GLOBALS['absolute_server_URI']."okapi/cron5\n\n".
|
||||
"*/5 * * * * wget -O - -q -t 1 ".Settings::get('SITE_URL')."okapi/cron5\n\n".
|
||||
"If you're receiving this in Virtual Machine development environment, then\n".
|
||||
"ignore it. Probably you just paused (or switched off) your VM for some time\n".
|
||||
"(which would be considered an error in production environment)."
|
||||
@@ -378,7 +441,7 @@ class ChangeLogWriterJob extends Cron5Job
|
||||
public function get_period() { return 300; }
|
||||
public function execute()
|
||||
{
|
||||
require_once 'services/replicate/replicate_common.inc.php';
|
||||
require_once($GLOBALS['rootpath']."okapi/services/replicate/replicate_common.inc.php");
|
||||
ReplicateCommon::update_clog_table();
|
||||
}
|
||||
}
|
||||
@@ -391,18 +454,53 @@ class FulldumpGeneratorJob extends Cron5Job
|
||||
public function get_period() { return 7*86400; }
|
||||
public function execute()
|
||||
{
|
||||
require_once 'services/replicate/replicate_common.inc.php';
|
||||
require_once($GLOBALS['rootpath']."okapi/services/replicate/replicate_common.inc.php");
|
||||
ReplicateCommon::generate_fulldump();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for changelog updates. Update okapi_tile_caches accordingly.
|
||||
*/
|
||||
class TileTreeUpdater extends Cron5Job
|
||||
{
|
||||
public function get_period() { return 5*60; }
|
||||
public function execute()
|
||||
{
|
||||
$current_clog_revision = Okapi::get_var('clog_revision', 0);
|
||||
$tiletree_revision = Okapi::get_var('clog_followup_revision', 0);
|
||||
if ($tiletree_revision === $current_clog_revision) {
|
||||
# No update necessary.
|
||||
} elseif ($tiletree_revision < $current_clog_revision) {
|
||||
require_once($GLOBALS['rootpath']."okapi/services/caches/map/replicate_listener.inc.php");
|
||||
if ($current_clog_revision - $tiletree_revision < 100000) # In the middle of 2012, OCPL generated 30000 entries per week
|
||||
{
|
||||
for ($i=0; $i<100; $i++) # This gives us no more than 20000 (?) at a time.
|
||||
{
|
||||
$response = OkapiServiceRunner::call('services/replicate/changelog', new OkapiInternalRequest(
|
||||
new OkapiInternalConsumer(), null, array('since' => $tiletree_revision)));
|
||||
\okapi\services\caches\map\ReplicateListener::receive($response['changelog']);
|
||||
$tiletree_revision = $response['revision'];
|
||||
Okapi::set_var('clog_followup_revision', $tiletree_revision);
|
||||
if (!$response['more'])
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
# Some kind of bigger update. Resetting TileTree might be a better option.
|
||||
\okapi\services\caches\map\ReplicateListener::reset();
|
||||
Okapi::set_var('clog_followup_revision', $current_clog_revision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Once per day, removes all revisions older than 10 days from okapi_clog table. */
|
||||
class ChangeLogCleanerJob extends Cron5Job
|
||||
{
|
||||
public function get_period() { return 86400; }
|
||||
public function execute()
|
||||
{
|
||||
require_once 'services/replicate/replicate_common.inc.php';
|
||||
require_once($GLOBALS['rootpath']."okapi/services/replicate/replicate_common.inc.php");
|
||||
$max_revision = ReplicateCommon::get_revision();
|
||||
$cache_key = 'clog_revisions_daily';
|
||||
$data = Cache::get($cache_key);
|
||||
@@ -423,6 +521,7 @@ class ChangeLogCleanerJob extends Cron5Job
|
||||
where id < '".mysql_real_escape_string($new_min_revision)."'
|
||||
");
|
||||
Cache::set($cache_key, $new_data, 10*86400);
|
||||
Db::query("optimize table okapi_clog");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +665,7 @@ class LocaleChecker extends Cron5Job
|
||||
public function get_period() { return 7*86400; }
|
||||
public function execute()
|
||||
{
|
||||
require_once 'locale/locales.php';
|
||||
require_once($GLOBALS['rootpath']."okapi/locale/locales.php");
|
||||
$required = Locales::get_required_locales();
|
||||
$installed = Locales::get_installed_locales();
|
||||
$missing = array();
|
||||
|
||||
@@ -4,8 +4,6 @@ namespace okapi;
|
||||
|
||||
use OAuthDataStore;
|
||||
|
||||
require_once($rootpath.'lib/common.inc.php');
|
||||
|
||||
class OkapiDataStore extends OAuthDataStore
|
||||
{
|
||||
public function lookup_consumer($consumer_key)
|
||||
|
||||
@@ -2,23 +2,38 @@
|
||||
|
||||
namespace okapi;
|
||||
|
||||
# OKAPI Framework -- Wojciech Rygielski <rygielski@mimuw.edu.pl>
|
||||
|
||||
# Include this file if you want to use OKAPI's services with any
|
||||
# external code (your service calls will appear under the name "Facade"
|
||||
# in the weekly OKAPI usage report).
|
||||
|
||||
# Note, that his is the *ONLY* internal OKAPI file that is guaranteed
|
||||
# to stay backward-compatible (I'm speaking about INTERNAL files here,
|
||||
# all OKAPI methods will stay compatible forever). If you want to use
|
||||
# something that has not been exposed through the Facade class, contact
|
||||
# OKAPI developers, we will add it here.
|
||||
|
||||
# Including this file will initialize OKAPI Framework with its default
|
||||
# exception and error handlers. OKAPI is strict about PHP warnings and
|
||||
# notices. You might need to temporarily disable the error handler in
|
||||
# order to get it to work with some legacy code. Do this by calling
|
||||
# OkapiErrorHandler::disable() BEFORE calling the "buggy" code, and
|
||||
# OkapiErrorHandler::reenable() AFTER returning from it.
|
||||
|
||||
|
||||
use Exception;
|
||||
use okapi\OkapiServiceRunner;
|
||||
use okapi\OkapiInternalRequest;
|
||||
use okapi\OkapiFacadeConsumer;
|
||||
use okapi\OkapiFacadeAccessToken;
|
||||
|
||||
require_once('core.php');
|
||||
require_once('service_runner.php');
|
||||
require_once($GLOBALS['rootpath']."okapi/core.php");
|
||||
OkapiErrorHandler::$treat_notices_as_errors = true;
|
||||
require_once($GLOBALS['rootpath']."okapi/service_runner.php");
|
||||
|
||||
/**
|
||||
* Use this class to access OKAPI from OC code. This is the *ONLY* internal OKAPI file that is
|
||||
* guaranteed to stay backward-compatible*! You SHOULD NOT include any other okapi file in your
|
||||
* code. If you want to use something that has not been exposed through the Facade class,
|
||||
* inform OKAPI developers, we will add it.
|
||||
*
|
||||
* * - notice that we are talking about INTERNAL files here. Of course, all OKAPI methods
|
||||
* (accessed via HTTP) will stay compatible forever (if possible).
|
||||
* Use this class to access OKAPI's services from external code (i.e. OC code).
|
||||
*/
|
||||
class Facade
|
||||
{
|
||||
@@ -30,7 +45,6 @@ class Facade
|
||||
*/
|
||||
public static function service_call($service_name, $user_id_or_null, $parameters)
|
||||
{
|
||||
// WRTODO: make this count as HTTP call?
|
||||
$request = new OkapiInternalRequest(
|
||||
new OkapiFacadeConsumer(),
|
||||
($user_id_or_null !== null) ? new OkapiFacadeAccessToken($user_id_or_null) : null,
|
||||
@@ -39,4 +53,24 @@ class Facade
|
||||
$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();
|
||||
}
|
||||
}
|
||||
|
||||
29
htdocs/okapi/lib/oc_session.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?
|
||||
|
||||
namespace okapi;
|
||||
|
||||
/**
|
||||
* Use this class to access OC session variables. This is especially useful if
|
||||
* you want to determine which user is currently logged in to OC.
|
||||
*/
|
||||
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;
|
||||
|
||||
$cookie_name = Settings::get('OC_COOKIE_NAME');
|
||||
if (!isset($_COOKIE[$cookie_name]))
|
||||
return null;
|
||||
$OC_data = unserialize(base64_decode($_COOKIE[$cookie_name]));
|
||||
$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)."'");
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ class OkapiServiceRunner
|
||||
'services/caches/geocaches',
|
||||
'services/caches/formatters/gpx',
|
||||
'services/caches/formatters/garmin',
|
||||
'services/caches/map/tile',
|
||||
'services/logs/entries',
|
||||
'services/logs/entry',
|
||||
'services/logs/logs',
|
||||
@@ -62,7 +63,7 @@ class OkapiServiceRunner
|
||||
{
|
||||
if (!self::exists($service_name))
|
||||
throw new Exception();
|
||||
require_once "$service_name.php";
|
||||
require_once($GLOBALS['rootpath']."okapi/$service_name.php");
|
||||
try
|
||||
{
|
||||
return call_user_func(array('\\okapi\\'.
|
||||
@@ -123,7 +124,7 @@ class OkapiServiceRunner
|
||||
Okapi::gettext_domain_init();
|
||||
try
|
||||
{
|
||||
require_once "$service_name.php";
|
||||
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();
|
||||
@@ -140,19 +141,35 @@ class OkapiServiceRunner
|
||||
return $response;
|
||||
}
|
||||
|
||||
private static function save_stats($service_name, OkapiRequest $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.
|
||||
|
||||
$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())
|
||||
$calltype = 'http';
|
||||
else
|
||||
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)
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace okapi\services\apiref\method;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\ParamMissing;
|
||||
use okapi\InvalidParam;
|
||||
@@ -59,7 +60,7 @@ class WebService
|
||||
$result = array(
|
||||
'name' => $methodname,
|
||||
'short_name' => end($exploded),
|
||||
'ref_url' => $GLOBALS['absolute_server_URI']."okapi/$methodname.html",
|
||||
'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,
|
||||
@@ -93,12 +94,23 @@ class WebService
|
||||
$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;
|
||||
foreach ($referenced_method_info['arguments'] as $arg)
|
||||
{
|
||||
if ($arg['class'] == 'common-formatting')
|
||||
continue;
|
||||
$arg['description'] = "<i>Inherited from <a href='".$referenced_method_info['ref_url'].
|
||||
"'>".$referenced_method_info['name']."</a> method.</i>";
|
||||
if ($include_list === null)
|
||||
{
|
||||
$arg['description'] = "<i>Inherited from <a href='".$referenced_method_info['ref_url'].
|
||||
"'>".$referenced_method_info['name']."</a> method.</i>";
|
||||
}
|
||||
elseif (in_array($arg['name'], $include_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;
|
||||
}
|
||||
@@ -109,13 +121,13 @@ class WebService
|
||||
'name' => 'format',
|
||||
'is_required' => false,
|
||||
'class' => 'common-formatting',
|
||||
'description' => "<i>Standard <a href='".$GLOBALS['absolute_server_URI']."okapi/introduction.html#common-formatting'>common formatting</a> argument.</i>"
|
||||
'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,
|
||||
'class' => 'common-formatting',
|
||||
'description' => "<i>Standard <a href='".$GLOBALS['absolute_server_URI']."okapi/introduction.html#common-formatting'>common formatting</a> argument.</i>"
|
||||
'description' => "<i>Standard <a href='".Settings::get('SITE_URL')."okapi/introduction.html#common-formatting'>common formatting</a> argument.</i>"
|
||||
);
|
||||
}
|
||||
if (!$docs->returns)
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace okapi\services\apisrv\installation;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\ParamMissing;
|
||||
use okapi\InvalidParam;
|
||||
@@ -22,7 +23,7 @@ class WebService
|
||||
public static function call(OkapiRequest $request)
|
||||
{
|
||||
$result = array();
|
||||
$result['site_url'] = $GLOBALS['absolute_server_URI'];
|
||||
$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;
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace okapi\services\apisrv\installations;
|
||||
use Exception;
|
||||
use ErrorException;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\Cache;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\ParamMissing;
|
||||
@@ -61,9 +62,9 @@ class WebService
|
||||
|
||||
$results = array(
|
||||
array(
|
||||
'site_url' => $GLOBALS['absolute_server_URI'],
|
||||
'site_url' => Settings::get('SITE_URL'),
|
||||
'site_name' => "Unable to retrieve!",
|
||||
'okapi_base_url' => $GLOBALS['absolute_server_URI']."okapi/",
|
||||
'okapi_base_url' => Settings::get('SITE_URL')."okapi/",
|
||||
)
|
||||
);
|
||||
Cache::set($cachekey, $results, 12 * 3600); # so to retry no earlier than after 12 hours
|
||||
@@ -89,7 +90,7 @@ class WebService
|
||||
'site_name' => $site_name,
|
||||
'okapi_base_url' => $okapi_base_url,
|
||||
);
|
||||
if ($site_url == $GLOBALS['absolute_server_URI'])
|
||||
if ($site_url == Settings::get('SITE_URL'))
|
||||
$i_was_included = true;
|
||||
}
|
||||
|
||||
@@ -99,9 +100,9 @@ class WebService
|
||||
if (!$i_was_included)
|
||||
{
|
||||
$results[] = array(
|
||||
'site_url' => $GLOBALS['absolute_server_URI'],
|
||||
'site_url' => Settings::get('SITE_URL'),
|
||||
'site_name' => "DEVELSITE",
|
||||
'okapi_base_url' => $GLOBALS['absolute_server_URI']."okapi/",
|
||||
'okapi_base_url' => Settings::get('SITE_URL')."okapi/",
|
||||
);
|
||||
# Contact OKAPI developers in order to get added to the official sites list!
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace okapi\services\caches\formatters\garmin;
|
||||
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\OkapiHttpResponse;
|
||||
use okapi\OkapiInternalRequest;
|
||||
@@ -119,7 +120,7 @@ class WebService
|
||||
# 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 = $GLOBALS['picdir']."/".$img['uuid'].".jpg";
|
||||
$syspath = Settings::get('IMAGES_DIR')."/".$img['uuid'].".jpg";
|
||||
if (!file_exists($syspath))
|
||||
continue;
|
||||
$file = file_get_contents($syspath);
|
||||
@@ -140,7 +141,7 @@ class WebService
|
||||
set_time_limit(600);
|
||||
$response = new OkapiHttpResponse();
|
||||
$response->content_type = "application/zip";
|
||||
$response->content_disposition = 'Content-Disposition: attachment; filename="results.zip"';
|
||||
$response->content_disposition = 'attachment; filename="results.zip"';
|
||||
$response->stream_length = filesize($tempfilename);
|
||||
$response->body = fopen($tempfilename, "rb");
|
||||
$response->allow_gzip = false;
|
||||
|
||||
@@ -153,7 +153,7 @@ class WebService
|
||||
|
||||
$response = new OkapiHttpResponse();
|
||||
$response->content_type = "text/xml; charset=utf-8";
|
||||
$response->content_disposition = 'Content-Disposition: attachment; filename="results.gpx"';
|
||||
$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';
|
||||
|
||||
@@ -23,6 +23,7 @@ class WebService
|
||||
{
|
||||
$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');
|
||||
|
||||
@@ -45,7 +45,7 @@ class WebService
|
||||
else
|
||||
$cache_codes = explode("|", $cache_codes);
|
||||
|
||||
if (count($cache_codes) > 500)
|
||||
if ((count($cache_codes) > 500) && (!$request->skip_limits))
|
||||
throw new InvalidParam('cache_codes', "Maximum allowed number of referenced ".
|
||||
"caches is 500. You provided ".count($cache_codes)." cache codes.");
|
||||
if (count($cache_codes) != count(array_unique($cache_codes)))
|
||||
@@ -121,7 +121,7 @@ class WebService
|
||||
select
|
||||
c.cache_id, c.name, c.longitude, c.latitude, c.last_modified,
|
||||
c.date_created, c.type, c.status, c.date_hidden, c.size, c.difficulty,
|
||||
c.terrain, c.wp_oc, c.logpw, u.uuid as user_uuid, u.username, u.user_id,
|
||||
c.terrain, c.wp_oc, c.logpw, c.user_id,
|
||||
|
||||
ifnull(sc.toprating, 0) as topratings,
|
||||
ifnull(sc.found, 0) as founds,
|
||||
@@ -131,10 +131,9 @@ class WebService
|
||||
-- SEE ALSO OC.PL BRANCH BELOW
|
||||
from
|
||||
caches c
|
||||
inner join user u on c.user_id = u.user_id
|
||||
left join stat_caches as sc on c.cache_id = sc.cache_id
|
||||
where
|
||||
binary wp_oc in ('".implode("','", array_map('mysql_real_escape_string', $cache_codes))."')
|
||||
wp_oc in ('".implode("','", array_map('mysql_real_escape_string', $cache_codes))."')
|
||||
and status in (1,2,3)
|
||||
");
|
||||
}
|
||||
@@ -148,7 +147,7 @@ class WebService
|
||||
select
|
||||
c.cache_id, c.name, c.longitude, c.latitude, c.last_modified,
|
||||
c.date_created, c.type, c.status, c.date_hidden, c.size, c.difficulty,
|
||||
c.terrain, c.wp_oc, c.logpw, u.uuid as user_uuid, u.username, u.user_id,
|
||||
c.terrain, c.wp_oc, c.logpw, c.user_id,
|
||||
|
||||
c.topratings,
|
||||
c.founds,
|
||||
@@ -157,17 +156,16 @@ class WebService
|
||||
c.votes, c.score
|
||||
-- SEE ALSO OC.DE BRANCH ABOVE
|
||||
from
|
||||
caches c,
|
||||
user u
|
||||
caches c
|
||||
where
|
||||
binary wp_oc in ('".implode("','", array_map('mysql_real_escape_string', $cache_codes))."')
|
||||
and c.user_id = u.user_id
|
||||
wp_oc in ('".implode("','", array_map('mysql_real_escape_string', $cache_codes))."')
|
||||
and c.status in (1,2,3)
|
||||
");
|
||||
}
|
||||
|
||||
$results = array();
|
||||
$cacheid2wptcode = array();
|
||||
$owner_ids = array();
|
||||
while ($row = mysql_fetch_assoc($rs))
|
||||
{
|
||||
$entry = array();
|
||||
@@ -182,13 +180,10 @@ class WebService
|
||||
case 'location': $entry['location'] = round($row['latitude'], 6)."|".round($row['longitude'], 6); break;
|
||||
case 'type': $entry['type'] = Okapi::cache_type_id2name($row['type']); break;
|
||||
case 'status': $entry['status'] = Okapi::cache_status_id2name($row['status']); break;
|
||||
case 'url': $entry['url'] = $GLOBALS['absolute_server_URI']."viewcache.php?wp=".$row['wp_oc']; break;
|
||||
case 'url': $entry['url'] = Settings::get('SITE_URL')."viewcache.php?wp=".$row['wp_oc']; break;
|
||||
case 'owner':
|
||||
$entry['owner'] = array(
|
||||
'uuid' => $row['user_uuid'],
|
||||
'username' => $row['username'],
|
||||
'profile_url' => $GLOBALS['absolute_server_URI']."viewprofile.php?userid=".$row['user_id']
|
||||
);
|
||||
$owner_ids[$row['wp_oc']] = $row['user_id'];
|
||||
/* continued later */
|
||||
break;
|
||||
case 'distance':
|
||||
$entry['distance'] = (int)Okapi::get_distance($center_lat, $center_lon, $row['latitude'], $row['longitude']);
|
||||
@@ -246,6 +241,29 @@ class WebService
|
||||
}
|
||||
mysql_free_result($rs);
|
||||
|
||||
# owner
|
||||
|
||||
if (in_array('owner', $fields) && (count($results) > 0))
|
||||
{
|
||||
$rs = Db::query("
|
||||
select user_id, uuid, username
|
||||
from user
|
||||
where user_id in ('".implode("','", array_map('mysql_real_escape_string', array_values($owner_ids)))."')
|
||||
");
|
||||
$tmp = array();
|
||||
while ($row = mysql_fetch_assoc($rs))
|
||||
$tmp[$row['user_id']] = $row;
|
||||
foreach ($results as $cache_code => &$result_ref)
|
||||
{
|
||||
$row = $tmp[$owner_ids[$cache_code]];
|
||||
$result_ref['owner'] = array(
|
||||
'uuid' => $row['uuid'],
|
||||
'username' => $row['username'],
|
||||
'profile_url' => Settings::get('SITE_URL')."viewprofile.php?userid=".$row['user_id']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# is_found
|
||||
|
||||
if (in_array('is_found', $fields))
|
||||
@@ -466,7 +484,7 @@ class WebService
|
||||
'user' => array(
|
||||
'uuid' => $row['user_uuid'],
|
||||
'username' => $row['username'],
|
||||
'profile_url' => $GLOBALS['absolute_server_URI']."viewprofile.php?userid=".$row['user_id'],
|
||||
'profile_url' => Settings::get('SITE_URL')."viewprofile.php?userid=".$row['user_id'],
|
||||
),
|
||||
'type' => Okapi::logtypeid2name($row['type']),
|
||||
'comment' => $row['text']
|
||||
@@ -706,7 +724,7 @@ class WebService
|
||||
|
||||
public static function get_cache_attribution_note($cache_id, $lang)
|
||||
{
|
||||
$site_url = $GLOBALS['absolute_server_URI'];
|
||||
$site_url = Settings::get('SITE_URL');
|
||||
$site_name = Okapi::get_normalized_site_name();
|
||||
$cache_url = $site_url."viewcache.php?cacheid=$cache_id";
|
||||
|
||||
|
||||
260
htdocs/okapi/services/caches/map/replicate_listener.inc.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace okapi\services\caches\map;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\Cache;
|
||||
use okapi\Db;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\OkapiHttpResponse;
|
||||
use okapi\ParamMissing;
|
||||
use okapi\InvalidParam;
|
||||
use okapi\BadRequest;
|
||||
use okapi\DoesNotExist;
|
||||
use okapi\OkapiInternalRequest;
|
||||
use okapi\OkapiInternalConsumer;
|
||||
use okapi\OkapiServiceRunner;
|
||||
|
||||
use okapi\services\caches\map\TileTree;
|
||||
|
||||
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).
|
||||
|
||||
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()
|
||||
{
|
||||
# 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 very slow!
|
||||
|
||||
Okapi::mail_admins("OKAPI TileMap database reset",
|
||||
"Hello,\n\n".
|
||||
"OKAPI's 'replicate' module detected a big database update. As result\n".
|
||||
"of this, OKAPI decided to reset the TileMap cache. This may\n".
|
||||
"temporarily influence TileMap performance. The map may work much\n".
|
||||
"slower during the next few hours or days, while the cache is being\n".
|
||||
"rebuilt.\n\n".
|
||||
"If this happens frequently, please contact OKAPI developers. It may\n".
|
||||
"indicate a bug in OKAPI's 'replicate' module or cronjob settings.\n\n".
|
||||
"Thanks!"
|
||||
);
|
||||
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.
|
||||
|
||||
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... ;)
|
||||
|
||||
self::add_geocache_to_cached_tiles($theirs);
|
||||
}
|
||||
elseif (($ours[1] != $theirs[1]) || ($ours[2] != $theirs[2])) # z21x & z21y fields
|
||||
{
|
||||
# Location changed.
|
||||
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Db::execute("
|
||||
delete from okapi_tile_caches
|
||||
where cache_id = '".mysql_real_escape_string($cache_id)."'
|
||||
");
|
||||
|
||||
# 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.
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
$tiles_to_update = array();
|
||||
|
||||
# We will begin at zoom 21 and then go down to zoom 0.
|
||||
|
||||
$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.
|
||||
|
||||
$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);
|
||||
|
||||
foreach ($tiles_in_this_region as $coords)
|
||||
{
|
||||
list($x, $y) = $coords;
|
||||
|
||||
$scale = 8 + 21 - $zoom;
|
||||
$margin = 1 << ($scale - 3); # 32px of current $zoom level, measured in z21 pixels.
|
||||
|
||||
$left_z21x = ($x << $scale) - $margin;
|
||||
$right_z21x = (($x + 1) << $scale) + $margin;
|
||||
$top_z21y = ($y << $scale) - $margin;
|
||||
$bottom_z21y = (($y + 1) << $scale) + $margin;
|
||||
|
||||
if ($z21x < $left_z21x)
|
||||
continue;
|
||||
if ($z21x > $right_z21x)
|
||||
continue;
|
||||
if ($z21y < $top_z21y)
|
||||
continue;
|
||||
if ($z21y > $bottom_z21y)
|
||||
continue;
|
||||
|
||||
# We found a match. Store it for later.
|
||||
|
||||
$tiles_to_update[] = array($zoom, $x, $y);
|
||||
}
|
||||
}
|
||||
|
||||
# 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.
|
||||
|
||||
$alternatives = array();
|
||||
foreach ($tiles_to_update as $coords)
|
||||
{
|
||||
list($z, $x, $y) = $coords;
|
||||
$alternatives[] = "(
|
||||
z = '".mysql_real_escape_string($z)."'
|
||||
and x = '".mysql_real_escape_string($x)."'
|
||||
and y = '".mysql_real_escape_string($y)."'
|
||||
)";
|
||||
}
|
||||
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" : $row[5]).",
|
||||
'".mysql_real_escape_string($row[6])."'
|
||||
from okapi_tile_status
|
||||
where
|
||||
(".implode(" or ", $alternatives).")
|
||||
and status in (1,2)
|
||||
");
|
||||
|
||||
# We might have just filled some empty tiles (status 1) with data.
|
||||
# We need to update their status to 2.
|
||||
|
||||
Db::execute("
|
||||
update okapi_tile_status
|
||||
set status=2
|
||||
where
|
||||
(".implode(" or ", $alternatives).")
|
||||
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" : $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);
|
||||
}
|
||||
}
|
||||
391
htdocs/okapi/services/caches/map/tile.php
Normal file
@@ -0,0 +1,391 @@
|
||||
<?php
|
||||
|
||||
namespace okapi\services\caches\map\tile;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\Cache;
|
||||
use okapi\FileCache;
|
||||
use okapi\Db;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\OkapiHttpResponse;
|
||||
use okapi\ParamMissing;
|
||||
use okapi\InvalidParam;
|
||||
use okapi\BadRequest;
|
||||
use okapi\DoesNotExist;
|
||||
use okapi\OkapiInternalRequest;
|
||||
use okapi\OkapiInternalConsumer;
|
||||
use okapi\OkapiServiceRunner;
|
||||
|
||||
use okapi\services\caches\map\TileTree;
|
||||
use okapi\services\caches\map\DefaultTileRenderer;
|
||||
|
||||
|
||||
require_once('tiletree.inc.php');
|
||||
require_once('tilerenderer.inc.php');
|
||||
|
||||
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_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;
|
||||
|
||||
public static function options()
|
||||
{
|
||||
return array(
|
||||
'min_auth_level' => 1
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
# 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.");
|
||||
|
||||
# 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).".");
|
||||
|
||||
# status
|
||||
|
||||
$filter_conds = array();
|
||||
$tmp = $request->get_parameter('status');
|
||||
if ($tmp == null) $tmp = "Available";
|
||||
$allowed_status_codes = array();
|
||||
foreach (explode("|", $tmp) as $name)
|
||||
{
|
||||
try
|
||||
{
|
||||
$allowed_status_codes[] = Okapi::cache_status_name2id($name);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw new InvalidParam('status', "'$name' is not a valid cache status.");
|
||||
}
|
||||
}
|
||||
sort($allowed_status_codes);
|
||||
if (count($allowed_status_codes) == 0)
|
||||
throw new InvalidParam('status');
|
||||
if (count($allowed_status_codes) < 3)
|
||||
$filter_conds[] = "status in ('".implode("','", array_map('mysql_real_escape_string', $allowed_status_codes))."')";
|
||||
|
||||
# type
|
||||
|
||||
if ($tmp = $request->get_parameter('type'))
|
||||
{
|
||||
$operator = "in";
|
||||
if ($tmp[0] == '-')
|
||||
{
|
||||
$tmp = substr($tmp, 1);
|
||||
$operator = "not in";
|
||||
}
|
||||
$types = array();
|
||||
foreach (explode("|", $tmp) as $name)
|
||||
{
|
||||
try
|
||||
{
|
||||
$id = Okapi::cache_type_name2id($name);
|
||||
$types[] = $id;
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw new InvalidParam('type', "'$name' is not a valid cache type.");
|
||||
}
|
||||
}
|
||||
sort($types);
|
||||
|
||||
# Check if all cache types were selected. Since we're running
|
||||
# on various OC installations, we don't know which caches types
|
||||
# are "all" here. We have to check.
|
||||
|
||||
$all = self::$USE_OTHER_CACHE ? Cache::get('all_cache_types') : null;
|
||||
if ($all === null)
|
||||
{
|
||||
$all = Db::select_column("
|
||||
select distinct type
|
||||
from caches
|
||||
where status in (1,2,3)
|
||||
");
|
||||
Cache::set('all_cache_types', $all, 86400);
|
||||
}
|
||||
$all_included = true;
|
||||
foreach ($all as $type)
|
||||
if (!in_array($type, $types))
|
||||
{
|
||||
$all_included = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($all_included && ($operator == "in"))
|
||||
{
|
||||
# All cache types are to be included. This is common.
|
||||
}
|
||||
else
|
||||
{
|
||||
$filter_conds[] = "type $operator ('".implode("','", array_map('mysql_real_escape_string', $types))."')";
|
||||
}
|
||||
}
|
||||
|
||||
# User-specific geocaches (cached together).
|
||||
|
||||
$cache_key = "tileuser/".$request->token->user_id;
|
||||
$user = self::$USE_OTHER_CACHE ? Cache::get($cache_key) : null;
|
||||
if ($user === null)
|
||||
{
|
||||
$user = array();
|
||||
|
||||
# Ignored caches.
|
||||
|
||||
$rs = Db::query("
|
||||
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.
|
||||
|
||||
$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.
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
# exclude_ignored
|
||||
|
||||
$tmp = $request->get_parameter('exclude_ignored');
|
||||
if ($tmp === null) $tmp = "false";
|
||||
if (!in_array($tmp, array('true', 'false'), true))
|
||||
throw new InvalidParam('exclude_ignored', "'$tmp'");
|
||||
if ($tmp == 'true')
|
||||
{
|
||||
$excluded_dict = $user['ignored'];
|
||||
} else {
|
||||
$excluded_dict = array();
|
||||
}
|
||||
|
||||
# exclude_my_own
|
||||
|
||||
if ($tmp = $request->get_parameter('exclude_my_own'))
|
||||
{
|
||||
if (!in_array($tmp, array('true', 'false'), 1))
|
||||
throw new InvalidParam('exclude_my_own', "'$tmp'");
|
||||
if (($tmp == 'true') && (count($user['own']) > 0))
|
||||
{
|
||||
foreach ($user['own'] as $cache_id => $v)
|
||||
$excluded_dict[$cache_id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
# found_status
|
||||
|
||||
if ($tmp = $request->get_parameter('found_status'))
|
||||
{
|
||||
if (!in_array($tmp, array('found_only', 'notfound_only', 'either')))
|
||||
throw new InvalidParam('found_status', "'$tmp'");
|
||||
if ($tmp == 'either') {
|
||||
# Do nothing.
|
||||
} elseif ($tmp == 'notfound_only') {
|
||||
# Easy.
|
||||
foreach ($user['found'] as $cache_id => $v)
|
||||
$excluded_dict[$cache_id] = true;
|
||||
} else {
|
||||
# Found only. This will slow down queries somewhat. But it is rare.
|
||||
$filter_conds[] = "cache_id in ('".implode("','", array_map('mysql_real_escape_string', array_keys($user['found'])))."')";
|
||||
}
|
||||
}
|
||||
|
||||
# with_trackables_only
|
||||
|
||||
if ($tmp = $request->get_parameter('with_trackables_only'))
|
||||
{
|
||||
if (!in_array($tmp, array('true', 'false'), 1))
|
||||
throw new InvalidParam('with_trackables_only', "'$tmp'");
|
||||
if ($tmp == 'true')
|
||||
{
|
||||
$filter_conds[] = "flags & ".TileTree::$FLAG_HAS_TRACKABLES;
|
||||
}
|
||||
}
|
||||
|
||||
# not_yet_found_only
|
||||
|
||||
if ($tmp = $request->get_parameter('not_yet_found_only')) # ftf hunter
|
||||
{
|
||||
if (!in_array($tmp, array('true', 'false'), 1))
|
||||
throw new InvalidParam('not_yet_found_only', "'$tmp'");
|
||||
if ($tmp == 'true')
|
||||
{
|
||||
$filter_conds[] = "flags & ".TileTree::$FLAG_NOT_YET_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
# rating
|
||||
|
||||
if ($tmp = $request->get_parameter('rating'))
|
||||
{
|
||||
if (!preg_match("/^[1-5]-[1-5](\|X)?$/", $tmp))
|
||||
throw new InvalidParam('rating', "'$tmp'");
|
||||
list($min, $max) = explode("-", $tmp);
|
||||
if (strpos($max, "|X") !== false)
|
||||
{
|
||||
$max = $max[0];
|
||||
$allow_null = true;
|
||||
} else {
|
||||
$allow_null = false;
|
||||
}
|
||||
if ($min > $max)
|
||||
throw new InvalidParam('rating', "'$tmp'");
|
||||
if (($min == 1) && ($max == 5) && $allow_null) {
|
||||
/* no extra condition necessary */
|
||||
} else {
|
||||
$filter_conds[] = "(rating between $min and $max)".
|
||||
($allow_null ? " or rating is null" : "");
|
||||
}
|
||||
}
|
||||
|
||||
# Filter out caches in $excluded_dict.
|
||||
|
||||
if (count($excluded_dict) > 0)
|
||||
{
|
||||
$filter_conds[] = "cache_id not in ('".implode("','", array_keys($excluded_dict))."')";
|
||||
}
|
||||
|
||||
# Get caches within the tile (+ those around the borders). All filtering
|
||||
# options need to be applied here.
|
||||
|
||||
$rs = TileTree::query_fast($zoom, $x, $y, $filter_conds);
|
||||
OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointA", null,
|
||||
microtime(true) - $checkpointA_started);
|
||||
$checkpointB_started = microtime(true);
|
||||
|
||||
# Read the rows and add extra flags to them.
|
||||
|
||||
$rows = array();
|
||||
if ($rs !== null)
|
||||
{
|
||||
while ($row = mysql_fetch_row($rs))
|
||||
{
|
||||
# Add the "found" flag, to indicate that this cache needs
|
||||
# to be drawn as found.
|
||||
|
||||
if (isset($user['found'][$row[0]]))
|
||||
$row[6] |= TileTree::$FLAG_FOUND; # $row[6] is "flags"
|
||||
if (isset($user['own'][$row[0]]))
|
||||
$row[6] |= TileTree::$FLAG_OWN; # $row[6] is "flags"
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
unset($row);
|
||||
}
|
||||
|
||||
# Compute a fast image fingerprint. This will be used both for ETags
|
||||
# and internal cache ($cache_key).
|
||||
|
||||
$tile = new DefaultTileRenderer($zoom, $rows);
|
||||
$image_fingerprint = $tile->get_unique_hash();
|
||||
|
||||
# Start creating response.
|
||||
|
||||
$response = new OkapiHttpResponse();
|
||||
$response->content_type = $tile->get_content_type();
|
||||
$response->cache_control = "Cache-Control: private, max-age=600";
|
||||
$response->etag = 'W/"'.$image_fingerprint.'"';
|
||||
|
||||
# Check if the request didn't include the same ETag.
|
||||
|
||||
OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointB", null,
|
||||
microtime(true) - $checkpointB_started);
|
||||
$checkpointC_started = microtime(true);
|
||||
if (self::$USE_ETAGS_CACHE && ($request->etag == $response->etag))
|
||||
{
|
||||
# Hit. Report the content was unmodified.
|
||||
|
||||
$response->etag = null;
|
||||
$response->status = "304 Not Modified";
|
||||
return $response;
|
||||
}
|
||||
|
||||
# Check if the image was recently rendered and is kept in image cache.
|
||||
|
||||
$cache_key = "tile/".$image_fingerprint;
|
||||
$response->body = self::$USE_IMAGE_CACHE ? Cache::get($cache_key) : null;
|
||||
OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointC", null,
|
||||
microtime(true) - $checkpointC_started);
|
||||
$checkpointD_started = microtime(true);
|
||||
if ($response->body !== null)
|
||||
{
|
||||
# Hit. We will use the cached version of the image.
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
# Miss. Render the image. Cache the result.
|
||||
|
||||
$response->body = $tile->render();
|
||||
Cache::set_scored($cache_key, $response->body);
|
||||
OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointD", null,
|
||||
microtime(true) - $checkpointD_started);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
25
htdocs/okapi/services/caches/map/tile.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<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>
|
||||
<opt name='with_trackables_only' default='false'>
|
||||
All geocaches without trackables will be skipped.
|
||||
</opt>
|
||||
<opt name='not_yet_found_only' default='false'>
|
||||
All geocaches which have been already found by someone, will be skipped.
|
||||
</opt>
|
||||
<import-params method="services/caches/search/all"
|
||||
params="status|type|exclude_ignored|exclude_my_own|found_status|rating"/>
|
||||
<returns>
|
||||
The PNG image with the requested map tile.
|
||||
</returns>
|
||||
</xml>
|
||||
601
htdocs/okapi/services/caches/map/tilerenderer.inc.php
Normal file
@@ -0,0 +1,601 @@
|
||||
<?php
|
||||
|
||||
namespace okapi\services\caches\map;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\Cache;
|
||||
use okapi\Db;
|
||||
use okapi\FileCache; // WRTODO
|
||||
|
||||
interface TileRenderer
|
||||
{
|
||||
/**
|
||||
* Return the unique hash of the tile being rendered. This method will be
|
||||
* called only once, prior the render method. You may (but don't have to)
|
||||
* throw an Exception on every subsequent call.
|
||||
*/
|
||||
public function get_unique_hash();
|
||||
|
||||
/** Get the content type of the data returned by the render method. */
|
||||
public function get_content_type();
|
||||
|
||||
/**
|
||||
* Render the image. This function will be called only once, after calling
|
||||
* get_unique_hash method.
|
||||
*/
|
||||
public function render();
|
||||
}
|
||||
|
||||
class DefaultTileRenderer implements TileRenderer
|
||||
{
|
||||
/**
|
||||
* Changing this will affect all generated hashes. You should increment it
|
||||
* whenever you alter anything in the drawing algorithm.
|
||||
*/
|
||||
private static $VERSION = 41;
|
||||
|
||||
/**
|
||||
* Should be always true. You may temporarily set it to false, when you're
|
||||
* testing/debugging a new set of static icons.
|
||||
*/
|
||||
private static $USE_STATIC_IMAGE_CACHE = true;
|
||||
|
||||
/**
|
||||
* Should be always true. You may temporarily set it to false, when you're
|
||||
* testing/debugging a new captions renderer.
|
||||
*/
|
||||
private static $USE_CAPTIONS_CACHE = true;
|
||||
|
||||
private $zoom;
|
||||
private $rows_ref;
|
||||
private $im;
|
||||
|
||||
/**
|
||||
* Takes the zoom level and the list of geocache descriptions. Note, that
|
||||
* $rows_ref can be altered after calling render. If you don't want it to,
|
||||
* you should pass a deep copy.
|
||||
*/
|
||||
public function __construct($zoom, &$rows_ref)
|
||||
{
|
||||
$this->zoom = $zoom;
|
||||
$this->rows_ref = &$rows_ref;
|
||||
}
|
||||
|
||||
public function get_unique_hash()
|
||||
{
|
||||
return md5(json_encode(array(
|
||||
"DefaultTileRenderer",
|
||||
self::$VERSION,
|
||||
$this->zoom,
|
||||
$this->rows_ref
|
||||
)));
|
||||
}
|
||||
|
||||
public function get_content_type()
|
||||
{
|
||||
return "image/png";
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
# Preprocess the rows.
|
||||
|
||||
if ($this->zoom >= 5)
|
||||
$this->decide_which_get_captions();
|
||||
|
||||
# Make a background.
|
||||
|
||||
$this->im = imagecreatetruecolor(256, 256);
|
||||
imagealphablending($this->im, false);
|
||||
if ($this->zoom >= 13) $opacity = 15;
|
||||
elseif ($this->zoom <= 12) $opacity = max(0, $this->zoom * 2 - 14);
|
||||
$transparent = imagecolorallocatealpha($this->im, 0, 0, 0, 127 - $opacity);
|
||||
imagefilledrectangle($this->im, 0, 0, 256, 256, $transparent);
|
||||
imagealphablending($this->im, true);
|
||||
|
||||
# Draw the caches.
|
||||
|
||||
foreach ($this->rows_ref as &$row_ref)
|
||||
$this->draw_cache($row_ref);
|
||||
|
||||
# Return the result.
|
||||
|
||||
ob_start();
|
||||
imagesavealpha($this->im, true);
|
||||
imagepng($this->im);
|
||||
imagedestroy($this->im);
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
private static function get_image($name, $opacity=1, $brightness=0,
|
||||
$contrast=0, $r=0, $g=0, $b=0)
|
||||
{
|
||||
static $locmem_cache = array();
|
||||
|
||||
# Check locmem cache.
|
||||
|
||||
$key = "$name/$opacity/$brightness/$contrast/$r/$g/$b";
|
||||
if (!isset($locmem_cache[$key]))
|
||||
{
|
||||
# Miss. Check default cache. WRTODO: upgrade to normal Cache?
|
||||
|
||||
$cache_key = "tilesrc/".Okapi::$revision."/".self::$VERSION."/".$key;
|
||||
$gd2_path = self::$USE_STATIC_IMAGE_CACHE
|
||||
? FileCache::get_file_path($cache_key) : null;
|
||||
if ($gd2_path === null)
|
||||
{
|
||||
# Miss again. Read the image from PNG.
|
||||
|
||||
$locmem_cache[$key] = imagecreatefrompng($GLOBALS['rootpath']."okapi/static/tilemap/$name.png");
|
||||
|
||||
# Apply all wanted effects.
|
||||
|
||||
if ($opacity != 1)
|
||||
self::change_opacity($locmem_cache[$key], $opacity);
|
||||
if ($contrast != 0)
|
||||
imagefilter($locmem_cache[$key], IMG_FILTER_CONTRAST, $contrast);
|
||||
if ($brightness != 0)
|
||||
imagefilter($locmem_cache[$key], IMG_FILTER_BRIGHTNESS, $brightness);
|
||||
if (($r != 0) || ($g != 0) || ($b != 0))
|
||||
{
|
||||
imagefilter($locmem_cache[$key], IMG_FILTER_GRAYSCALE);
|
||||
imagefilter($locmem_cache[$key], IMG_FILTER_COLORIZE, $r, $g, $b);
|
||||
}
|
||||
|
||||
# Cache the result.
|
||||
|
||||
ob_start();
|
||||
imagegd2($locmem_cache[$key]);
|
||||
$gd2 = ob_get_clean();
|
||||
FileCache::set($cache_key, $gd2);
|
||||
}
|
||||
else
|
||||
{
|
||||
# File cache hit. GD2 files are much faster to read than PNGs.
|
||||
$locmem_cache[$key] = imagecreatefromgd2($gd2_path);
|
||||
}
|
||||
}
|
||||
return $locmem_cache[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extremely slow! Remember to cache the result!
|
||||
*/
|
||||
private static function change_opacity($im, $ratio)
|
||||
{
|
||||
imagealphablending($im, false);
|
||||
|
||||
$w = imagesx($im);
|
||||
$h = imagesy($im);
|
||||
|
||||
for($x = 0; $x < $w; $x++)
|
||||
{
|
||||
for($y = 0; $y < $h; $y++)
|
||||
{
|
||||
$color = imagecolorat($im, $x, $y);
|
||||
$new_color = ((max(0, floor(127 - ((127 - (($color >> 24) & 0x7f)) * $ratio))) & 0x7f) << 24) | ($color & 0x80ffffff);
|
||||
imagesetpixel($im, $x, $y, $new_color);
|
||||
}
|
||||
}
|
||||
|
||||
imagealphablending($im, true);
|
||||
}
|
||||
|
||||
private function draw_cache(&$cache_struct)
|
||||
{
|
||||
if ($this->zoom <= 8)
|
||||
$this->draw_cache_tiny($cache_struct);
|
||||
elseif ($this->zoom <= 12)
|
||||
$this->draw_cache_medium($cache_struct);
|
||||
else
|
||||
$this->draw_cache_large($cache_struct);
|
||||
|
||||
# Put caption (this flag is set only when there is plenty of space around).
|
||||
|
||||
if ($cache_struct[6] & TileTree::$FLAG_DRAW_CAPTION)
|
||||
{
|
||||
$caption = $this->get_caption($cache_struct[0]);
|
||||
imagecopy($this->im, $caption, $cache_struct[1] - 32, $cache_struct[2] + 6, 0, 0, 64, 26);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function draw_cache_large(&$cache_struct)
|
||||
{
|
||||
list($cache_id, $px, $py, $status, $type, $rating, $flags, $count) = $cache_struct;
|
||||
|
||||
$found = $flags & TileTree::$FLAG_FOUND;
|
||||
$own = $flags & TileTree::$FLAG_OWN;
|
||||
$new = $flags & TileTree::$FLAG_NEW;
|
||||
|
||||
# Prepare vars.
|
||||
|
||||
if ($own) {
|
||||
$key = 'large_outer_own';
|
||||
$a = 1; $br = 0; $c = 0;
|
||||
$r = 0; $g = 0; $b = 0;
|
||||
} elseif ($found) {
|
||||
$key = 'large_outer_found';
|
||||
$a = 1; $br = 40; $c = 20;
|
||||
//$a = 0.5; $br = 0; $c = 0;
|
||||
$r = 0; $g = 0; $b = 0;
|
||||
} elseif ($new) {
|
||||
$key = 'large_outer_new';
|
||||
$a = 1; $br = 0; $c = 0;
|
||||
$r = 0; $g = 0; $b = 0;
|
||||
} else {
|
||||
$key = 'large_outer';
|
||||
$a = 1; $br = 0; $c = 0;
|
||||
$r = 0; $g = 0; $b = 0;
|
||||
}
|
||||
|
||||
# Put the outer marker (indicates the found/new/own status).
|
||||
|
||||
$outer_marker = self::get_image($key, $a);
|
||||
|
||||
$width = 40;
|
||||
$height = 32;
|
||||
$center_x = 12;
|
||||
$center_y = 26;
|
||||
$markercenter_x = 12;
|
||||
$markercenter_y = 12;
|
||||
|
||||
if ($count > 1)
|
||||
imagecopy($this->im, $outer_marker, $px - $center_x + 3, $py - $center_y - 2, 0, 0, $width, $height);
|
||||
imagecopy($this->im, $outer_marker, $px - $center_x, $py - $center_y, 0, 0, $width, $height);
|
||||
|
||||
# Put the inner marker (indicates the type).
|
||||
|
||||
$inner_marker = self::get_image("large_inner_".self::get_type_suffix(
|
||||
$type, true), $a, $br, $c, $r, $g, $b);
|
||||
imagecopy($this->im, $inner_marker, $px - 7, $py - 22, 0, 0, 16, 16);
|
||||
|
||||
# If the cache is unavailable, mark it with X.
|
||||
|
||||
if (($status != 1) && ($count == 1))
|
||||
{
|
||||
$icon = self::get_image(($status == 2) ? "status_unavailable"
|
||||
: "status_archived", $a);
|
||||
imagecopy($this->im, $icon, $px - 1, $py - $center_y - 4, 0, 0, 16, 16);
|
||||
}
|
||||
|
||||
# Put the rating smile. :)
|
||||
|
||||
if ($status == 1)
|
||||
{
|
||||
if ($rating >= 4.2)
|
||||
{
|
||||
if ($flags & TileTree::$FLAG_STAR) {
|
||||
$icon = self::get_image("rating_grin", $a, $br, $c, $r, $g, $b);
|
||||
imagecopy($this->im, $icon, $px - 7 - 6, $py - $center_y - 8, 0, 0, 16, 16);
|
||||
$icon = self::get_image("rating_star", $a, $br, $c, $r, $g, $b);
|
||||
imagecopy($this->im, $icon, $px - 7 + 6, $py - $center_y - 8, 0, 0, 16, 16);
|
||||
} else {
|
||||
$icon = self::get_image("rating_grin", $a, $br, $c, $r, $g, $b);
|
||||
imagecopy($this->im, $icon, $px - 7, $py - $center_y - 8, 0, 0, 16, 16);
|
||||
}
|
||||
}
|
||||
# This was commented out because users complained about too many smiles ;)
|
||||
// elseif ($rating >= 3.6) {
|
||||
// $icon = self::get_image("rating_smile", $a, $br, $c, $r, $g, $b);
|
||||
// imagecopy($this->im, $icon, $px - 7, $py - $center_y - 8, 0, 0, 16, 16);
|
||||
// }
|
||||
}
|
||||
|
||||
# Mark found caches with V.
|
||||
|
||||
if ($found)
|
||||
{
|
||||
$icon = self::get_image("found", 0.7, $br, $c, $r, $g, $b);
|
||||
imagecopy($this->im, $icon, $px - 2, $py - $center_y - 3, 0, 0, 16, 16);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Split lines so that they fit inside the specified width.
|
||||
*/
|
||||
private static function wordwrap($font, $size, $maxWidth, $text)
|
||||
{
|
||||
$words = explode(" ", $text);
|
||||
$lines = array();
|
||||
$line = "";
|
||||
$nextBonus = "";
|
||||
for ($i=0; ($i<count($words)) || (mb_strlen($nextBonus)>0); $i++) {
|
||||
$word = isset($words[$i])?$words[$i]:"";
|
||||
if (mb_strlen($nextBonus) > 0)
|
||||
$word = $nextBonus." ".$word;
|
||||
$nextBonus = "";
|
||||
while (true) {
|
||||
$bbox = imagettfbbox($size, 0, $font, $line.$word);
|
||||
$width = $bbox[2]-$bbox[0];
|
||||
if ($width <= $maxWidth) {
|
||||
$line .= $word." ";
|
||||
continue 2;
|
||||
}
|
||||
if (mb_strlen($line) > 0) {
|
||||
$lines[] = trim($line);
|
||||
$line = "";
|
||||
continue;
|
||||
}
|
||||
$nextBonus = $word[mb_strlen($word)-1].$nextBonus;
|
||||
$word = mb_substr($word, 0, mb_strlen($word)-1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (mb_strlen($line) > 0)
|
||||
$lines[] = trim($line);
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return 64x26 bitmap with the caption (name) for the given geocache.
|
||||
*/
|
||||
private function get_caption($cache_id)
|
||||
{
|
||||
# Check cache.
|
||||
|
||||
$cache_key = "tilecaption/".self::$VERSION."/".$cache_id;
|
||||
$gd2 = self::$USE_CAPTIONS_CACHE ? Cache::get($cache_key) : null;
|
||||
if ($gd2 === null)
|
||||
{
|
||||
# We'll work with 16x bigger image to get smoother interpolation.
|
||||
|
||||
$im = imagecreatetruecolor(64*4, 26*4);
|
||||
imagealphablending($im, false);
|
||||
$transparent = imagecolorallocatealpha($im, 255, 255, 255, 127);
|
||||
imagefilledrectangle($im, 0, 0, 64*4, 26*4, $transparent);
|
||||
imagealphablending($im, true);
|
||||
|
||||
# Get the name of the cache.
|
||||
|
||||
$name = Db::select_value("
|
||||
select name
|
||||
from caches
|
||||
where cache_id = '".mysql_real_escape_string($cache_id)."'
|
||||
");
|
||||
|
||||
# Split the name into a couple of lines.
|
||||
|
||||
//$font = $GLOBALS['rootpath'].'util.sec/bt.ttf';
|
||||
$font = $GLOBALS['rootpath'].'okapi/static/tilemap/tahoma.ttf';
|
||||
$size = 25;
|
||||
$lines = explode("\n", self::wordwrap($font, $size, 64*4 - 6*2, $name));
|
||||
|
||||
# For each line, compute its (x, y) so that the text is centered.
|
||||
|
||||
$y = 0;
|
||||
$positions = array();
|
||||
foreach ($lines as $line)
|
||||
{
|
||||
$bbox = imagettfbbox($size, 0, $font, $line);
|
||||
$width = $bbox[2]-$bbox[0];
|
||||
$x = 128 - ($width >> 1);
|
||||
$positions[] = array($x, $y);
|
||||
$y += 36;
|
||||
}
|
||||
$drawer = function($x, $y, $color) use (&$lines, &$positions, &$im, &$size, &$font)
|
||||
{
|
||||
$len = count($lines);
|
||||
for ($i=0; $i<$len; $i++)
|
||||
{
|
||||
$line = $lines[$i];
|
||||
list($offset_x, $offset_y) = $positions[$i];
|
||||
imagettftext($im, $size, 0, $offset_x + $x, $offset_y + $y, $color, $font, $line);
|
||||
}
|
||||
};
|
||||
|
||||
# Draw an outline.
|
||||
|
||||
$outline_color = imagecolorallocatealpha($im, 255, 255, 255, 80);
|
||||
for ($x=0; $x<=12; $x+=3)
|
||||
for ($y=$size-3; $y<=$size+9; $y+=3)
|
||||
$drawer($x, $y, $outline_color);
|
||||
|
||||
# Add a slight shadow effect (on top of the outline).
|
||||
|
||||
$drawer(9, $size + 3, imagecolorallocatealpha($im, 0, 0, 0, 110));
|
||||
|
||||
# Draw the caption.
|
||||
|
||||
$drawer(6, $size + 3, imagecolorallocatealpha($im, 150, 0, 0, 40));
|
||||
|
||||
# Resample.
|
||||
|
||||
imagealphablending($im, false);
|
||||
$small = imagecreatetruecolor(64, 26);
|
||||
imagealphablending($small, false);
|
||||
imagecopyresampled($small, $im, 0, 0, 0, 0, 64, 26, 64*4, 26*4);
|
||||
|
||||
# Cache it!
|
||||
|
||||
ob_start();
|
||||
imagegd2($small);
|
||||
$gd2 = ob_get_clean();
|
||||
Cache::set_scored($cache_key, $gd2);
|
||||
}
|
||||
|
||||
return imagecreatefromstring($gd2);
|
||||
}
|
||||
|
||||
private function draw_cache_medium(&$cache_struct)
|
||||
{
|
||||
list($cache_id, $px, $py, $status, $type, $rating, $flags, $count) = $cache_struct;
|
||||
|
||||
$found = $flags & TileTree::$FLAG_FOUND;
|
||||
$own = $flags & TileTree::$FLAG_OWN;
|
||||
$new = $flags & TileTree::$FLAG_NEW;
|
||||
|
||||
# Put the marker (indicates the type).
|
||||
|
||||
$marker = self::get_image("medium_".self::get_type_suffix($type, false));
|
||||
$width = 14;
|
||||
$height = 14;
|
||||
$center_x = 7;
|
||||
$center_y = 8;
|
||||
$markercenter_x = 7;
|
||||
$markercenter_y = 8;
|
||||
|
||||
if ($count > 1)
|
||||
{
|
||||
imagecopy($this->im, $marker, $px - $center_x + 3, $py - $center_y - 2, 0, 0, $width, $height);
|
||||
imagecopy($this->im, $marker, $px - $center_x, $py - $center_y, 0, 0, $width, $height);
|
||||
}
|
||||
elseif ($status == 1) # don't put the marker for unavailable caches (X only)
|
||||
{
|
||||
imagecopy($this->im, $marker, $px - $center_x, $py - $center_y, 0, 0, $width, $height);
|
||||
}
|
||||
|
||||
# If the cache is unavailable, mark it with X.
|
||||
|
||||
if (($status != 1) && ($count == 1))
|
||||
{
|
||||
$icon = self::get_image(($status == 2) ? "status_unavailable"
|
||||
: "status_archived");
|
||||
imagecopy($this->im, $icon, $px - ($center_x - $markercenter_x) - 6,
|
||||
$py - ($center_y - $markercenter_y) - 8, 0, 0, 16, 16);
|
||||
}
|
||||
|
||||
if ($own)
|
||||
{
|
||||
# Mark own caches with additional overlay.
|
||||
|
||||
$overlay = self::get_image("medium_overlay_own");
|
||||
imagecopy($this->im, $overlay, $px - $center_x, $py - $center_y, 0, 0, $width, $height);
|
||||
}
|
||||
elseif ($found)
|
||||
{
|
||||
# Mark found caches with V.
|
||||
|
||||
$icon = self::get_image("found", 0.7);
|
||||
imagecopy($this->im, $icon, $px - ($center_x - $markercenter_x) - 7,
|
||||
$py - ($center_y - $markercenter_y) - 9, 0, 0, 16, 16);
|
||||
}
|
||||
elseif ($new)
|
||||
{
|
||||
# Mark new caches with additional overlay.
|
||||
|
||||
$overlay = self::get_image("medium_overlay_new");
|
||||
imagecopy($this->im, $overlay, $px - $center_x, $py - $center_y, 0, 0, $width, $height);
|
||||
}
|
||||
}
|
||||
|
||||
private static function get_type_suffix($type, $extended_set)
|
||||
{
|
||||
switch ($type) {
|
||||
case 2: return 'traditional';
|
||||
case 3: return 'multi';
|
||||
case 6: return 'event';
|
||||
case 7: return 'quiz';
|
||||
case 4: return 'virtual';
|
||||
case 1: return 'unknown';
|
||||
}
|
||||
if ($extended_set)
|
||||
{
|
||||
switch ($type) {
|
||||
case 10: return 'own';
|
||||
case 8: return 'moving';
|
||||
case 5: return 'webcam';
|
||||
}
|
||||
}
|
||||
return 'other';
|
||||
}
|
||||
|
||||
private function draw_cache_tiny(&$cache_struct)
|
||||
{
|
||||
list($cache_id, $px, $py, $status, $type, $rating, $flags, $count) = $cache_struct;
|
||||
|
||||
$found = $flags & TileTree::$FLAG_FOUND;
|
||||
$own = $flags & TileTree::$FLAG_OWN;
|
||||
$new = $flags & TileTree::$FLAG_NEW;
|
||||
|
||||
$marker = self::get_image("tiny_".self::get_type_suffix($type, false));
|
||||
$width = 10;
|
||||
$height = 10;
|
||||
$center_x = 5;
|
||||
$center_y = 6;
|
||||
$markercenter_x = 5;
|
||||
$markercenter_y = 6;
|
||||
|
||||
# Put the marker. If cache covers more caches, then put two markers instead of one.
|
||||
|
||||
if ($count > 1)
|
||||
{
|
||||
imagecopy($this->im, $marker, $px - $center_x + 3, $py - $center_y - 2, 0, 0, $width, $height);
|
||||
imagecopy($this->im, $marker, $px - $center_x, $py - $center_y, 0, 0, $width, $height);
|
||||
}
|
||||
elseif ($status == 1)
|
||||
{
|
||||
imagecopy($this->im, $marker, $px - $center_x, $py - $center_y, 0, 0, $width, $height);
|
||||
}
|
||||
|
||||
# If the cache is unavailable, mark it with X.
|
||||
|
||||
if (($status != 1) && ($count == 1))
|
||||
{
|
||||
$icon = self::get_image(($status == 2) ? "status_unavailable"
|
||||
: "status_archived");
|
||||
imagecopy($this->im, $icon, $px - ($center_x - $markercenter_x) - 6,
|
||||
$py - ($center_y - $markercenter_y) - 8, 0, 0, 16, 16);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine all rows and decide which of them will get captions.
|
||||
* Mark selected rows with TileTree::$FLAG_DRAW_CAPTION.
|
||||
*
|
||||
* Note: Calling this will alter the rows!
|
||||
*/
|
||||
private function decide_which_get_captions()
|
||||
{
|
||||
# We will split the tile (along with its margins) into 12x12 squares.
|
||||
# A single geocache placed in square (x, y) gets the caption only
|
||||
# when there are no other geocaches in any of the adjacent squares.
|
||||
# This is efficient and yields acceptable results.
|
||||
|
||||
$matrix = array();
|
||||
for ($i=0; $i<12; $i++)
|
||||
$matrix[] = array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
foreach ($this->rows_ref as &$row_ref)
|
||||
{
|
||||
$mx = ($row_ref[1] + 64) >> 5;
|
||||
$my = ($row_ref[2] + 64) >> 5;
|
||||
if (($mx >= 12) || ($my >= 12)) continue;
|
||||
if (($matrix[$mx][$my] === 0) && ($row_ref[7] == 1)) # 7 is count
|
||||
$matrix[$mx][$my] = $row_ref[0]; # 0 is cache_id
|
||||
else
|
||||
$matrix[$mx][$my] = -1;
|
||||
}
|
||||
$selected_cache_ids = array();
|
||||
for ($mx=1; $mx<11; $mx++)
|
||||
{
|
||||
for ($my=1; $my<11; $my++)
|
||||
{
|
||||
if ($matrix[$mx][$my] > 0) # cache_id
|
||||
{
|
||||
# Check all adjacent squares.
|
||||
|
||||
if ( ($matrix[$mx-1][$my-1] === 0)
|
||||
&& ($matrix[$mx-1][$my ] === 0)
|
||||
&& ($matrix[$mx-1][$my+1] === 0)
|
||||
&& ($matrix[$mx ][$my-1] === 0)
|
||||
&& ($matrix[$mx ][$my+1] === 0)
|
||||
&& ($matrix[$mx+1][$my-1] === 0)
|
||||
&& ($matrix[$mx+1][$my ] === 0)
|
||||
&& ($matrix[$mx+1][$my+1] === 0)
|
||||
)
|
||||
$selected_cache_ids[] = $matrix[$mx][$my];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->rows_ref as &$row_ref)
|
||||
if (in_array($row_ref[0], $selected_cache_ids))
|
||||
$row_ref[6] |= TileTree::$FLAG_DRAW_CAPTION;
|
||||
}
|
||||
|
||||
}
|
||||
310
htdocs/okapi/services/caches/map/tiletree.inc.php
Normal file
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
namespace okapi\services\caches\map;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\Cache;
|
||||
use okapi\Db;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\OkapiHttpResponse;
|
||||
use okapi\ParamMissing;
|
||||
use okapi\InvalidParam;
|
||||
use okapi\BadRequest;
|
||||
use okapi\DoesNotExist;
|
||||
use okapi\OkapiInternalRequest;
|
||||
use okapi\OkapiInternalConsumer;
|
||||
use okapi\OkapiServiceRunner;
|
||||
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;
|
||||
|
||||
# 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 MySQL's result set iterator over all caches matched by your query.
|
||||
*
|
||||
* 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, $filter_conds)
|
||||
{
|
||||
$time_started = microtime(true);
|
||||
|
||||
# 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
|
||||
# filtering parameters.
|
||||
|
||||
$status = self::compute_tile($zoom, $x, $y);
|
||||
OkapiServiceRunner::save_stats_extra("tiletree/query_fast/preparetile-miss",
|
||||
null, microtime(true) - $time_started);
|
||||
}
|
||||
else
|
||||
{
|
||||
OkapiServiceRunner::save_stats_extra("tiletree/query_fast/preparetile-hit",
|
||||
null, microtime(true) - $time_started);
|
||||
}
|
||||
|
||||
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).
|
||||
# Since all search parameters are aggregated, we just need a simple
|
||||
# SQL query to get the filtered result.
|
||||
|
||||
$tile_upper_x = $x << 8;
|
||||
$tile_leftmost_y = $y << 8;
|
||||
|
||||
if (count($filter_conds) == 0)
|
||||
$filter_conds[] = "true";
|
||||
return Db::query("
|
||||
select
|
||||
cache_id,
|
||||
cast(z21x >> (21 - $zoom) as signed) - $tile_upper_x as px,
|
||||
cast(z21y >> (21 - $zoom) as signed) - $tile_leftmost_y as py,
|
||||
status, type, rating, flags, count(*)
|
||||
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)."'
|
||||
and (".implode(") and (", $filter_conds).")
|
||||
group by
|
||||
z21x >> (3 + (21 - $zoom)),
|
||||
z21y >> (3 + (21 - $zoom))
|
||||
order by
|
||||
z21y >> (3 + (21 - $zoom)),
|
||||
z21x >> (3 + (21 - $zoom))
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
$status = self::get_tile_status($zoom, $x, $y);
|
||||
if ($status !== null)
|
||||
return $status;
|
||||
|
||||
if ($zoom === 0)
|
||||
{
|
||||
# When computing zoom zero, we don't have a parent to speed up
|
||||
# the computation. We need to use the caches table. Note, that
|
||||
# zoom level 0 contains *entire world*, so we don't have to use
|
||||
# any WHERE condition in the following query.
|
||||
|
||||
# This can be done a little faster (without the use of internal requests),
|
||||
# but there is *no need* to - this query is run seldom and is cached.
|
||||
|
||||
$params = array();
|
||||
$params['status'] = "Available|Temporarily unavailable|Archived"; # we want them all
|
||||
$params['limit'] = "10000000"; # no limit
|
||||
|
||||
$internal_request = new OkapiInternalRequest(new OkapiInternalConsumer(), null, $params);
|
||||
$internal_request->skip_limits = true;
|
||||
$response = OkapiServiceRunner::call("services/caches/search/all", $internal_request);
|
||||
$cache_codes = $response['results'];
|
||||
|
||||
$internal_request = new OkapiInternalRequest(new OkapiInternalConsumer(), null, array(
|
||||
'cache_codes' => implode('|', $cache_codes),
|
||||
'fields' => 'internal_id|code|name|location|type|status|rating|recommendations|founds|trackables_count'
|
||||
));
|
||||
$internal_request->skip_limits = true;
|
||||
$caches = OkapiServiceRunner::call("services/caches/geocaches", $internal_request);
|
||||
|
||||
foreach ($caches as $cache)
|
||||
{
|
||||
$row = self::generate_short_row($cache);
|
||||
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" : $row[5]).",
|
||||
'".mysql_real_escape_string($row[6])."'
|
||||
);
|
||||
");
|
||||
}
|
||||
$status = 2;
|
||||
|
||||
OkapiServiceRunner::save_stats_extra("tiletree/compute_tile/rebuild-0",
|
||||
null, microtime(true) - $time_started);
|
||||
}
|
||||
else
|
||||
{
|
||||
# We will use the parent tile to compute the contents of this tile.
|
||||
|
||||
$parent_zoom = $zoom - 1;
|
||||
$parent_x = $x >> 1;
|
||||
$parent_y = $y >> 1;
|
||||
|
||||
$status = self::get_tile_status($parent_zoom, $parent_x, $parent_y);
|
||||
if ($status === null) # Not computed.
|
||||
{
|
||||
$time_started = microtime(true);
|
||||
$status = self::compute_tile($parent_zoom, $parent_x, $parent_y);
|
||||
OkapiServiceRunner::save_stats_extra("tiletree/compute_tile/build",
|
||||
null, microtime(true) - $time_started);
|
||||
}
|
||||
else
|
||||
{
|
||||
OkapiServiceRunner::save_stats_extra("tiletree/compute_tile/hit",
|
||||
null, microtime(true) - $time_started);
|
||||
}
|
||||
|
||||
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|
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ use okapi\ParamMissing;
|
||||
use okapi\InvalidParam;
|
||||
use okapi\services\caches\search\SearchAssistant;
|
||||
|
||||
require_once 'searching.inc.php';
|
||||
require_once('searching.inc.php');
|
||||
|
||||
class WebService
|
||||
{
|
||||
|
||||
@@ -62,14 +62,17 @@
|
||||
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.</p>
|
||||
(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='rating'>
|
||||
<p>A string "X-Y", where X and Y are integers between 1 and 5, and X <= 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.</p>
|
||||
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>
|
||||
@@ -118,6 +121,12 @@
|
||||
<p>User UUID. If given, the response will only include geocaches not found by
|
||||
the given user.</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).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace okapi\services\caches\search\bbox;
|
||||
|
||||
require_once 'searching.inc.php';
|
||||
require_once('searching.inc.php');
|
||||
|
||||
use okapi\Okapi;
|
||||
use okapi\OkapiRequest;
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
namespace okapi\services\caches\search\by_urls;
|
||||
|
||||
require_once 'searching.inc.php';
|
||||
require_once('searching.inc.php');
|
||||
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\ParamMissing;
|
||||
use okapi\InvalidParam;
|
||||
@@ -31,7 +32,7 @@ class WebService
|
||||
static $length = null;
|
||||
if ($host == null)
|
||||
{
|
||||
$host = parse_url($GLOBALS['absolute_server_URI'], PHP_URL_HOST);
|
||||
$host = parse_url(Settings::get('SITE_URL'), PHP_URL_HOST);
|
||||
if (strpos($host, "www.") === 0)
|
||||
$host = substr($host, 4);
|
||||
$length = strlen($host);
|
||||
@@ -46,6 +47,13 @@ class WebService
|
||||
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']));
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace okapi\services\caches\search\nearest;
|
||||
|
||||
require_once 'searching.inc.php';
|
||||
require_once('searching.inc.php');
|
||||
|
||||
use okapi\Okapi;
|
||||
use okapi\OkapiRequest;
|
||||
|
||||
@@ -152,30 +152,58 @@ class SearchAssistant
|
||||
{
|
||||
if ($tmp = $request->get_parameter($param_name))
|
||||
{
|
||||
if (!preg_match("/^[1-5]-[1-5]$/", $tmp))
|
||||
if (!preg_match("/^[1-5]-[1-5](\|X)?$/", $tmp))
|
||||
throw new InvalidParam($param_name, "'$tmp'");
|
||||
list($min, $max) = explode("-", $tmp);
|
||||
if (strpos($max, "|X") !== false)
|
||||
{
|
||||
$max = $max[0];
|
||||
$allow_null = true;
|
||||
} else {
|
||||
$allow_null = false;
|
||||
}
|
||||
if ($min > $max)
|
||||
throw new InvalidParam($param_name, "'$tmp'");
|
||||
switch ($param_name)
|
||||
{
|
||||
case 'terrain':
|
||||
$where_conds[] = "caches.terrain between 2*$min and 2*$max";
|
||||
if ($allow_null)
|
||||
throw new InvalidParam($param_name, "The '|X' suffix is not allowed here.");
|
||||
if (($min == 1) && ($max == 5)) {
|
||||
/* no extra condition necessary */
|
||||
} else {
|
||||
$where_conds[] = "caches.terrain between 2*$min and 2*$max";
|
||||
}
|
||||
break;
|
||||
case 'difficulty':
|
||||
$where_conds[] = "caches.difficulty between 2*$min and 2*$max";
|
||||
if ($allow_null)
|
||||
throw new InvalidParam($param_name, "The '|X' suffix is not allowed here.");
|
||||
if (($min == 1) && ($max == 5)) {
|
||||
/* no extra condition necessary */
|
||||
} else {
|
||||
$where_conds[] = "caches.difficulty between 2*$min and 2*$max";
|
||||
}
|
||||
break;
|
||||
case 'size':
|
||||
$where_conds[] = "caches.size between $min+1 and $max+1";
|
||||
if (($min == 1) && ($max == 5) && $allow_null) {
|
||||
/* no extra condition necessary */
|
||||
} else {
|
||||
$where_conds[] = "(caches.size between $min+1 and $max+1)".
|
||||
($allow_null ? " or caches.size=7" : "");
|
||||
}
|
||||
break;
|
||||
case 'rating':
|
||||
if (Settings::get('OC_BRANCH') == 'oc.pl')
|
||||
{
|
||||
$divisors = array(-3.0, -1.0, 0.1, 1.4, 2.2, 3.0);
|
||||
$min = $divisors[$min - 1];
|
||||
$max = $divisors[$max];
|
||||
$where_conds[] = "$X_SCORE between $min and $max";
|
||||
$where_conds[] = "$X_VOTES > 3";
|
||||
if (($min == 1) && ($max == 5) && $allow_null) {
|
||||
/* no extra condition necessary */
|
||||
} else {
|
||||
$divisors = array(-3.0, -1.0, 0.1, 1.4, 2.2, 3.0);
|
||||
$min = $divisors[$min - 1];
|
||||
$max = $divisors[$max];
|
||||
$where_conds[] = "(($X_SCORE between $min and $max) and ($X_VOTES >= 3))".
|
||||
($allow_null ? " or ($X_VOTES < 3)" : "");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -294,6 +322,26 @@ class SearchAssistant
|
||||
$where_conds[] = "caches.cache_id not in ('".implode("','", array_map('mysql_real_escape_string', $found_cache_ids))."')";
|
||||
}
|
||||
|
||||
#
|
||||
# exclude_ignored
|
||||
#
|
||||
|
||||
if ($tmp = $request->get_parameter('exclude_ignored'))
|
||||
{
|
||||
if ($request->token == null)
|
||||
throw new InvalidParam('exclude_ignored', "Might be used only for requests signed with an Access Token.");
|
||||
if (!in_array($tmp, array('true', 'false')))
|
||||
throw new InvalidParam('exclude_ignored', "'$tmp'");
|
||||
if ($tmp == 'true') {
|
||||
$ignored_cache_ids = Db::select_column("
|
||||
select cache_id
|
||||
from cache_ignore
|
||||
where user_id = '".mysql_real_escape_string($request->token->user_id)."'
|
||||
");
|
||||
$where_conds[] = "cache_id not in ('".implode("','", array_map('mysql_real_escape_string', $ignored_cache_ids))."')";
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# exclude_my_own
|
||||
#
|
||||
@@ -331,7 +379,7 @@ class SearchAssistant
|
||||
if ($limit == null) $limit = "100";
|
||||
if (!is_numeric($limit))
|
||||
throw new InvalidParam('limit', "'$limit'");
|
||||
if ($limit < 1 || $limit > 500)
|
||||
if ($limit < 1 || (($limit > 500) && (!$request->skip_limits)))
|
||||
throw new InvalidParam('limit', "Has to be between 1 and 500.");
|
||||
|
||||
#
|
||||
@@ -342,7 +390,7 @@ class SearchAssistant
|
||||
if ($offset == null) $offset = "0";
|
||||
if (!is_numeric($offset))
|
||||
throw new InvalidParam('offset', "'$offset'");
|
||||
if ($offset + $limit > 500)
|
||||
if (($offset + $limit > 500) && (!$request->skip_limits))
|
||||
throw new BadRequest("The sum of offset and limit may not exceed 500.");
|
||||
if ($offset < 0 || $offset > 499)
|
||||
throw new InvalidParam('offset', "Has to be between 0 and 499.");
|
||||
@@ -381,7 +429,7 @@ class SearchAssistant
|
||||
$order_clauses[] = "($cl) $dir";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$ret_array = array(
|
||||
'where_conds' => $where_conds,
|
||||
'offset' => (int)$offset,
|
||||
|
||||
@@ -68,7 +68,7 @@ class WebService
|
||||
'user' => array(
|
||||
'uuid' => $row['user_uuid'],
|
||||
'username' => $row['username'],
|
||||
'profile_url' => $GLOBALS['absolute_server_URI']."viewprofile.php?userid=".$row['user_id'],
|
||||
'profile_url' => Settings::get('SITE_URL')."viewprofile.php?userid=".$row['user_id'],
|
||||
),
|
||||
'type' => Okapi::logtypeid2name($row['type']),
|
||||
'comment' => $row['text']
|
||||
|
||||
@@ -95,7 +95,7 @@ class WebService
|
||||
if (!in_array($needs_maintenance, array('true', 'false')))
|
||||
throw new InvalidParam('needs_maintenance', "Unknown option: '$needs_maintenance'.");
|
||||
$needs_maintenance = ($needs_maintenance == 'true');
|
||||
if (!Settings::get('SUPPORTS_LOGTYPE_NEEDS_MAINTENANCE'))
|
||||
if ($needs_maintenance && (!Settings::get('SUPPORTS_LOGTYPE_NEEDS_MAINTENANCE')))
|
||||
{
|
||||
# If not supported, just ignore it.
|
||||
self::$success_message .= " ".sprintf(_("However, your \"needs maintenance\" flag was ignored, because %s does not support this feature."),
|
||||
@@ -115,15 +115,6 @@ class WebService
|
||||
|
||||
# Various integrity checks.
|
||||
|
||||
if (!in_array($cache['status'], array("Available", "Temporarily unavailable")))
|
||||
{
|
||||
# Only admins and cache owners may publish comments for Archived caches.
|
||||
if ($user['is_admin'] || ($user['uuid'] == $cache['owner']['uuid'])) {
|
||||
/* pass */
|
||||
} else {
|
||||
throw new CannotPublishException(_("This cache is archived. Only admins and the owner are allowed to add a log entry."));
|
||||
}
|
||||
}
|
||||
if ($cache['type'] == 'Event' && $logtype != 'Comment')
|
||||
throw new CannotPublishException(_('This cache is an Event cache. You cannot "Find it"! (But - you may "Comment" on it.)'));
|
||||
if ($logtype == 'Comment' && strlen(trim($comment)) == 0)
|
||||
@@ -381,11 +372,13 @@ class WebService
|
||||
");
|
||||
}
|
||||
|
||||
# Call OC's event handler.
|
||||
|
||||
require_once($GLOBALS['rootpath'].'lib/eventhandler.inc.php');
|
||||
event_new_log($cache['internal_id'], $user['internal_id']);
|
||||
# We need to delete the copy of stats-picture for this user. Otherwise,
|
||||
# the legacy OC code won't detect that the picture needs to be refreshed.
|
||||
|
||||
$filepath = Settings::get('VAR_DIR').'images/statpics/statpic'.$user['internal_id'].'.jpg';
|
||||
if (file_exists($filepath))
|
||||
unlink($filepath);
|
||||
|
||||
# Success. Return the uuid.
|
||||
|
||||
return $log_uuid;
|
||||
@@ -500,9 +493,20 @@ class WebService
|
||||
}
|
||||
}
|
||||
|
||||
private static function create_uuid()
|
||||
{
|
||||
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
|
||||
private static function insert_log_row($consumer_key, $cache_internal_id, $user_internal_id, $logtype, $when, $PSEUDOENCODED_comment)
|
||||
{
|
||||
$log_uuid = create_uuid();
|
||||
$log_uuid = self::create_uuid();
|
||||
Db::execute("
|
||||
insert into cache_logs (uuid, cache_id, user_id, type, date, text, last_modified, date_created, node)
|
||||
values (
|
||||
@@ -514,7 +518,7 @@ class WebService
|
||||
'".mysql_real_escape_string($PSEUDOENCODED_comment)."',
|
||||
now(),
|
||||
now(),
|
||||
'".mysql_real_escape_string($GLOBALS['oc_nodeid'])."'
|
||||
'".mysql_real_escape_string(Settings::get('OC_NODE_ID'))."'
|
||||
);
|
||||
");
|
||||
$log_internal_id = Db::last_insert_id();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace okapi\services\oauth\authorize;
|
||||
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\OkapiRedirectResponse;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\ParamMissing;
|
||||
@@ -33,7 +34,7 @@ class WebService
|
||||
# done in the "services" folder. 2) "services" don't display
|
||||
# any interactive webpages, they just return the result.
|
||||
|
||||
return new OkapiRedirectResponse($GLOBALS['absolute_server_URI']."okapi/apps/authorize".
|
||||
return new OkapiRedirectResponse(Settings::get('SITE_URL')."okapi/apps/authorize".
|
||||
"?oauth_token=".$token_key.(($langpref != null) ? "&langpref=".$langpref : "").
|
||||
"&interactivity=".$interactivity);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class WebService
|
||||
|
||||
public static function call(OkapiRequest $request)
|
||||
{
|
||||
require_once 'replicate_common.inc.php';
|
||||
require_once('replicate_common.inc.php');
|
||||
|
||||
$since = $request->get_parameter('since');
|
||||
if ($since === null) throw new ParamMissing('since');
|
||||
|
||||
@@ -46,7 +46,7 @@ class WebService
|
||||
|
||||
public static function call(OkapiRequest $request)
|
||||
{
|
||||
require_once 'replicate_common.inc.php';
|
||||
require_once('replicate_common.inc.php');
|
||||
|
||||
$data = Cache::get("last_fulldump");
|
||||
if ($data == null)
|
||||
@@ -71,7 +71,7 @@ class WebService
|
||||
|
||||
$response = new OkapiHttpResponse();
|
||||
$response->content_type = $data['meta']['content_type'];
|
||||
$response->content_disposition = 'Content-Disposition: attachment; filename="'.$data['meta']['public_filename'].'"';
|
||||
$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;
|
||||
|
||||
@@ -27,7 +27,7 @@ class WebService
|
||||
|
||||
public static function call(OkapiRequest $request)
|
||||
{
|
||||
require_once 'replicate_common.inc.php';
|
||||
require_once('replicate_common.inc.php');
|
||||
|
||||
$result = array();
|
||||
$result['changelog'] = array(
|
||||
|
||||
@@ -140,7 +140,7 @@ class ReplicateCommon
|
||||
'uuid', $DELETED_uuids, self::$logged_log_entry_fields, false, true, 3600);
|
||||
}
|
||||
|
||||
# Update state variables and release DB lock.
|
||||
# Update state variables.
|
||||
|
||||
Okapi::set_var("last_clog_update", $now);
|
||||
$revision = Db::select_value("select max(id) from okapi_clog");
|
||||
@@ -173,7 +173,7 @@ class ReplicateCommon
|
||||
# Get the current values for objects. Compare them with their previous versions
|
||||
# and generate changelog entries.
|
||||
|
||||
require_once $GLOBALS['rootpath'].'okapi/service_runner.php';
|
||||
require_once($GLOBALS['rootpath'].'okapi/service_runner.php');
|
||||
$current_values = OkapiServiceRunner::call($feeder_method, new OkapiInternalRequest(
|
||||
new OkapiInternalConsumer(), null, array($feeder_keys_param => implode("|", $key_values),
|
||||
'fields' => $fields)));
|
||||
@@ -379,9 +379,7 @@ class ReplicateCommon
|
||||
if ($entry['change_type'] == 'replace')
|
||||
$filtered[] = $entry;
|
||||
unset($entries);
|
||||
$fp = fopen("$dir/$basename.json", "wb");
|
||||
fwrite($fp, json_encode($filtered));
|
||||
fclose($fp);
|
||||
file_put_contents("$dir/$basename.json", json_encode($filtered));
|
||||
unset($filtered);
|
||||
$i++;
|
||||
}
|
||||
@@ -416,9 +414,7 @@ class ReplicateCommon
|
||||
if ($entry['change_type'] == 'replace')
|
||||
$filtered[] = $entry;
|
||||
unset($entries);
|
||||
$fp = fopen("$dir/$basename.json", "wb");
|
||||
fwrite($fp, json_encode($filtered));
|
||||
fclose($fp);
|
||||
file_put_contents("$dir/$basename.json", json_encode($filtered));
|
||||
unset($filtered);
|
||||
$i++;
|
||||
}
|
||||
@@ -435,9 +431,7 @@ class ReplicateCommon
|
||||
'generated_at' => $generated_at,
|
||||
),
|
||||
);
|
||||
$fp = fopen("$dir/index.json", "wb");
|
||||
fwrite($fp, json_encode($metadata));
|
||||
fclose($fp);
|
||||
file_put_contents("$dir/index.json", json_encode($metadata));
|
||||
|
||||
# Compute uncompressed size.
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ namespace okapi\services\users\users;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\Db;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\ParamMissing;
|
||||
use okapi\InvalidParam;
|
||||
use okapi\Settings;
|
||||
use okapi\services\caches\search\SearchAssistant;
|
||||
|
||||
class WebService
|
||||
@@ -57,7 +57,7 @@ class WebService
|
||||
{
|
||||
case 'uuid': $entry['uuid'] = $row['uuid']; break;
|
||||
case 'username': $entry['username'] = $row['username']; break;
|
||||
case 'profile_url': $entry['profile_url'] = $GLOBALS['absolute_server_URI']."viewprofile.php?userid=".$row['user_id']; 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;
|
||||
|
||||
@@ -9,14 +9,8 @@ use okapi\Locales;
|
||||
# http://code.google.com/p/opencaching-api/source/browse/trunk/okapi/settings.php
|
||||
#
|
||||
# HOW TO MODIFY OKAPI SETTINGS: If you want a setting X to have a value of Y,
|
||||
# add following lines to your OC's lib/settings.inc.php file:
|
||||
#
|
||||
# $OKAPI_SETTINGS = array(
|
||||
# 'X' => 'Y',
|
||||
# // ... etc ...
|
||||
# );
|
||||
#
|
||||
# // E.g. $OKAPI_SETTINGS = array('OC_BRANCH' => 'oc.de', 'SITELANG' => 'de');
|
||||
# create/edit the "<rootpath>/okapi_settings.php" file. See example here:
|
||||
# http://code.google.com/p/opencaching-pl/source/browse/trunk/okapi_settings.php
|
||||
#
|
||||
# This file provides documentation and DEFAULT values for those settings.
|
||||
#
|
||||
@@ -28,6 +22,15 @@ final class Settings
|
||||
/** Default values for setting keys. */
|
||||
private static $DEFAULT_SETTINGS = array(
|
||||
|
||||
/**
|
||||
* List of administrator email addresses. OKAPI will send important messages
|
||||
* to this addresses. You should replace this with your true email address.
|
||||
*/
|
||||
'ADMINS' => array(),
|
||||
|
||||
/** Set this to true on development machines. */
|
||||
'DEBUG' => false,
|
||||
|
||||
/**
|
||||
* Currently there are two mainstream branches of OpenCaching code.
|
||||
* Which branch is you installation using?
|
||||
@@ -46,6 +49,9 @@ final class Settings
|
||||
*/
|
||||
'SITELANG' => "en",
|
||||
|
||||
/** Email address to use in the "From:" when sending messages. */
|
||||
'FROM_FIELD' => 'root@localhost',
|
||||
|
||||
/**
|
||||
* All OKAPI documentation pages should remain English-only, but some
|
||||
* other pages (and results) might be translated to their localized
|
||||
@@ -64,13 +70,6 @@ final class Settings
|
||||
*/
|
||||
'GETTEXT_DOMAIN' => 'okapi_messages',
|
||||
|
||||
/**
|
||||
* By default, OKAPI sends messages to email address defined in $GLOBALS['sql_errormail'].
|
||||
* However, there can be only one address defined there. If you want to add more, you may
|
||||
* use this setting to provide a list of additional email addresses.
|
||||
*/
|
||||
'EXTRA_ADMINS' => array(),
|
||||
|
||||
/**
|
||||
* Where should OKAPI store dynamically generated cache files? If you leave it at null,
|
||||
* OKAPI will try to guess (not recommended). If you move this directory, it's better
|
||||
@@ -78,6 +77,19 @@ final class Settings
|
||||
*/
|
||||
'VAR_DIR' => null,
|
||||
|
||||
/**
|
||||
* Where to store uploaded images? This directory needs to be shared among
|
||||
* both OKAPI and OC code (see $picdir in your settings.inc.php).
|
||||
*/
|
||||
'IMAGES_DIR' => null,
|
||||
|
||||
/**
|
||||
* Name of the cookie within which OC stores serialized session id, etc.
|
||||
* OKAPI requires to access this in order to make sure which user is logged
|
||||
* in.
|
||||
*/
|
||||
'OC_COOKIE_NAME' => null,
|
||||
|
||||
/**
|
||||
* Set to true, if your installation supports "Needs maintenance" log type (with
|
||||
* log type id == 5). If your users are not allowed to submit "Needs maintenance"
|
||||
@@ -98,6 +110,25 @@ final class Settings
|
||||
* performance and data integrity!
|
||||
*/
|
||||
'DEBUG_PREVENT_SEMAPHORES' => false,
|
||||
|
||||
/* Database settings */
|
||||
|
||||
'DB_SERVER' => 'localhost',
|
||||
'DB_NAME' => null,
|
||||
'DB_USERNAME' => null,
|
||||
'DB_PASSWORD' => null,
|
||||
|
||||
/** URL of the OC site (with slash, and without the "/okapi" part). */
|
||||
'SITE_URL' => null,
|
||||
|
||||
/** OKAPI needs this when inserting new data to cache_logs table. */
|
||||
'OC_NODE_ID' => null,
|
||||
|
||||
/**
|
||||
* Your OC sites data licencing document. All OKAPI Consumers will be
|
||||
* required to accept this.
|
||||
*/
|
||||
'DATA_LICENSE_URL' => null,
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -111,44 +142,50 @@ final class Settings
|
||||
*/
|
||||
private static function load_settings()
|
||||
{
|
||||
# Check the settings.inc.php for overrides.
|
||||
|
||||
try {
|
||||
require_once($GLOBALS['rootpath']."okapi_settings.php");
|
||||
$ref = get_okapi_settings();
|
||||
} catch (Exception $e) {
|
||||
throw new Exception("Could not import <rootpath>/okapi_settings.php:\n".$e->getMessage());
|
||||
}
|
||||
self::$SETTINGS = self::$DEFAULT_SETTINGS;
|
||||
|
||||
$ref = null;
|
||||
if (isset($GLOBALS['OKAPI_SETTINGS']))
|
||||
{
|
||||
$ref = &$GLOBALS['OKAPI_SETTINGS'];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not locate OKAPI settings! Put your settings array in ".
|
||||
"\$GLOBALS['OKAPI_SETTINGS']]. Settings may be empty, but must exist.");
|
||||
}
|
||||
|
||||
foreach (self::$SETTINGS as $key => $_)
|
||||
{
|
||||
if (isset($ref[$key]))
|
||||
{
|
||||
self::$SETTINGS[$key] = $ref[$key];
|
||||
self::verify($key, self::$SETTINGS[$key]);
|
||||
}
|
||||
}
|
||||
self::verify(self::$SETTINGS);
|
||||
}
|
||||
|
||||
/** Throw an exception, if given $value is invalid for the given $key. */
|
||||
private static function verify($key, $value)
|
||||
private static function verify($dict)
|
||||
{
|
||||
$debug = (isset($GLOBALS['debug_page']) && $GLOBALS['debug_page']);
|
||||
if (($key == 'OC_BRANCH') && (!in_array($value, array('oc.pl', 'oc.de'))))
|
||||
if (!in_array($dict['OC_BRANCH'], array('oc.pl', 'oc.de')))
|
||||
throw new Exception("Currently, OC_BRANCH has to be either 'oc.pl' or 'oc.de'. Hint: Whom did you get your code from?");
|
||||
$boolean_keys = array('SUPPORTS_LOGTYPE_NEEDS_MAINTENANCE', 'DEBUG_PREVENT_EMAILS', 'DEBUG_PREVENT_SEMAPHORES');
|
||||
if (in_array($key, $boolean_keys) && (!in_array($value, array(true, false))))
|
||||
throw new Exception("Invalid value for $key.");
|
||||
if (($key == 'DEBUG_PREVENT_EMAILS') and ($value == true) and ($debug == false))
|
||||
throw new Exception("DEBUG_PREVENT_EMAILS might be used only when debugging. Sending emails is vital in production environment.");
|
||||
if (($key == 'DEBUG_PREVENT_SEMAPHORES') and ($value == true) and ($debug == false))
|
||||
throw new Exception("USE_SEMAPHORES might be used only when debugging. Semaphores are vital in production environment.");
|
||||
$boolean_keys = array('SUPPORTS_LOGTYPE_NEEDS_MAINTENANCE', 'DEBUG', 'DEBUG_PREVENT_EMAILS', 'DEBUG_PREVENT_SEMAPHORES');
|
||||
foreach ($boolean_keys as $key)
|
||||
if (!in_array($dict[$key], array(true, false)))
|
||||
throw new Exception("Invalid value for $key.");
|
||||
if (count($dict['ADMINS']) == 0)
|
||||
throw new Exception("ADMINS array has to filled (e.g. array('root@localhost')).");
|
||||
if ($dict['DEBUG'] == false)
|
||||
foreach ($dict as $k => $v)
|
||||
if ((strpos($k, 'DEBUG_') === 0) && $v == true)
|
||||
throw new Exception("When DEBUG is false, $k has to be false too.");
|
||||
if ($dict['VAR_DIR'] == null)
|
||||
throw new Exception("VAR_DIR cannot be null. Please provide a valid directory.");
|
||||
if ($dict['IMAGES_DIR'] == null)
|
||||
throw new Exception("IMAGES_DIR cannot be null. Please provide a valid directory.");
|
||||
foreach ($dict as $k => $v)
|
||||
if ((strpos($k, '_DIR') !== false) && ($k[strlen($k) - 1] == '/'))
|
||||
throw new Exception("All *_DIR settings may not end with a slash. Check $k.");
|
||||
$notnull = array('OC_COOKIE_NAME', 'DB_SERVER', 'DB_NAME', 'DB_USERNAME', 'SITE_URL', 'OC_NODE_ID');
|
||||
foreach ($notnull as $k)
|
||||
if ($dict[$k] === null)
|
||||
throw new Exception("$k cannot be null.");
|
||||
if ($dict['SITE_URL'][strlen($dict['SITE_URL']) - 1] != '/')
|
||||
throw new Exception("SITE_URL must end with a slash.");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +215,7 @@ final class Settings
|
||||
*/
|
||||
public static function default_gettext_init($langprefs)
|
||||
{
|
||||
require_once 'locale/locales.php';
|
||||
require_once($GLOBALS['rootpath']."okapi/locale/locales.php");
|
||||
$locale = Locales::get_best_locale($langprefs);
|
||||
putenv("LC_ALL=$locale");
|
||||
setlocale(LC_ALL, $locale);
|
||||
|
||||
BIN
htdocs/okapi/static/tilemap/found.png
Normal file
|
After Width: | Height: | Size: 558 B |
BIN
htdocs/okapi/static/tilemap/large_event.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
htdocs/okapi/static/tilemap/large_inner_event.png
Normal file
|
After Width: | Height: | Size: 506 B |
BIN
htdocs/okapi/static/tilemap/large_inner_moving.png
Normal file
|
After Width: | Height: | Size: 428 B |
BIN
htdocs/okapi/static/tilemap/large_inner_multi.png
Normal file
|
After Width: | Height: | Size: 469 B |
BIN
htdocs/okapi/static/tilemap/large_inner_other.png
Normal file
|
After Width: | Height: | Size: 353 B |
BIN
htdocs/okapi/static/tilemap/large_inner_own.png
Normal file
|
After Width: | Height: | Size: 411 B |
BIN
htdocs/okapi/static/tilemap/large_inner_quiz.png
Normal file
|
After Width: | Height: | Size: 618 B |
BIN
htdocs/okapi/static/tilemap/large_inner_traditional.png
Normal file
|
After Width: | Height: | Size: 496 B |
BIN
htdocs/okapi/static/tilemap/large_inner_unknown.png
Normal file
|
After Width: | Height: | Size: 531 B |
BIN
htdocs/okapi/static/tilemap/large_inner_virtual.png
Normal file
|
After Width: | Height: | Size: 622 B |
BIN
htdocs/okapi/static/tilemap/large_inner_webcam.png
Normal file
|
After Width: | Height: | Size: 516 B |
BIN
htdocs/okapi/static/tilemap/large_multi.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
htdocs/okapi/static/tilemap/large_other.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
htdocs/okapi/static/tilemap/large_outer.png
Normal file
|
After Width: | Height: | Size: 803 B |
BIN
htdocs/okapi/static/tilemap/large_outer_found.png
Normal file
|
After Width: | Height: | Size: 744 B |
BIN
htdocs/okapi/static/tilemap/large_outer_new.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
htdocs/okapi/static/tilemap/large_outer_own.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
htdocs/okapi/static/tilemap/large_quiz.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
htdocs/okapi/static/tilemap/large_traditional.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
htdocs/okapi/static/tilemap/large_virtual.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
htdocs/okapi/static/tilemap/legend_event.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
htdocs/okapi/static/tilemap/legend_moving.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
htdocs/okapi/static/tilemap/legend_multi.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
htdocs/okapi/static/tilemap/legend_other.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
htdocs/okapi/static/tilemap/legend_own.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
htdocs/okapi/static/tilemap/legend_quiz.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
htdocs/okapi/static/tilemap/legend_traditional.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
htdocs/okapi/static/tilemap/legend_unknown.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
htdocs/okapi/static/tilemap/legend_virtual.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
htdocs/okapi/static/tilemap/legend_webcam.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
htdocs/okapi/static/tilemap/medium_event.png
Normal file
|
After Width: | Height: | Size: 369 B |
BIN
htdocs/okapi/static/tilemap/medium_multi.png
Normal file
|
After Width: | Height: | Size: 378 B |
BIN
htdocs/okapi/static/tilemap/medium_other.png
Normal file
|
After Width: | Height: | Size: 365 B |
BIN
htdocs/okapi/static/tilemap/medium_overlay_new.png
Normal file
|
After Width: | Height: | Size: 226 B |
BIN
htdocs/okapi/static/tilemap/medium_overlay_own.png
Normal file
|
After Width: | Height: | Size: 206 B |
BIN
htdocs/okapi/static/tilemap/medium_quiz.png
Normal file
|
After Width: | Height: | Size: 383 B |
BIN
htdocs/okapi/static/tilemap/medium_traditional.png
Normal file
|
After Width: | Height: | Size: 387 B |
BIN
htdocs/okapi/static/tilemap/medium_unknown.png
Normal file
|
After Width: | Height: | Size: 353 B |
BIN
htdocs/okapi/static/tilemap/medium_virtual.png
Normal file
|
After Width: | Height: | Size: 375 B |
BIN
htdocs/okapi/static/tilemap/rating_grin.png
Normal file
|
After Width: | Height: | Size: 616 B |
BIN
htdocs/okapi/static/tilemap/rating_smile.png
Normal file
|
After Width: | Height: | Size: 580 B |
BIN
htdocs/okapi/static/tilemap/rating_star.png
Normal file
|
After Width: | Height: | Size: 865 B |
BIN
htdocs/okapi/static/tilemap/status_archived.png
Normal file
|
After Width: | Height: | Size: 535 B |
BIN
htdocs/okapi/static/tilemap/status_unavailable.png
Normal file
|
After Width: | Height: | Size: 572 B |
BIN
htdocs/okapi/static/tilemap/tahoma.ttf
Normal file
BIN
htdocs/okapi/static/tilemap/tiny_event.png
Normal file
|
After Width: | Height: | Size: 225 B |
BIN
htdocs/okapi/static/tilemap/tiny_multi.png
Normal file
|
After Width: | Height: | Size: 230 B |
BIN
htdocs/okapi/static/tilemap/tiny_other.png
Normal file
|
After Width: | Height: | Size: 232 B |
BIN
htdocs/okapi/static/tilemap/tiny_quiz.png
Normal file
|
After Width: | Height: | Size: 239 B |
BIN
htdocs/okapi/static/tilemap/tiny_traditional.png
Normal file
|
After Width: | Height: | Size: 233 B |
BIN
htdocs/okapi/static/tilemap/tiny_unknown.png
Normal file
|
After Width: | Height: | Size: 226 B |
BIN
htdocs/okapi/static/tilemap/tiny_virtual.png
Normal file
|
After Width: | Height: | Size: 230 B |
@@ -25,5 +25,9 @@ class OkapiUrls
|
||||
'^devel/attrlist$' => 'devel/attrlist',
|
||||
'^devel/dbstruct$' => 'devel/dbstruct',
|
||||
'^devel/cronreport$' => 'devel/cronreport',
|
||||
'^devel/tilereport$' => 'devel/tilereport',
|
||||
|
||||
# For debugging TileMap performance only.
|
||||
// '^tilestress$' => 'tilestress',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use okapi\OkapiHttpRequest;
|
||||
use okapi\OkapiRedirectResponse;
|
||||
use okapi\Settings;
|
||||
use okapi\Locales;
|
||||
use okapi\OCSession;
|
||||
|
||||
class View
|
||||
{
|
||||
@@ -53,7 +54,7 @@ class View
|
||||
# by the user, who knows nothing on tokens and OAuth. Let's be nice then!
|
||||
|
||||
$vars = array(
|
||||
'okapi_base_url' => $GLOBALS['absolute_server_URI']."okapi/",
|
||||
'okapi_base_url' => Settings::get('SITE_URL')."okapi/",
|
||||
'token' => $token,
|
||||
'token_expired' => true,
|
||||
'site_name' => Okapi::get_normalized_site_name(),
|
||||
@@ -69,9 +70,14 @@ class View
|
||||
return $response;
|
||||
}
|
||||
|
||||
# Determine which user is logged in to OC.
|
||||
|
||||
require_once($GLOBALS['rootpath']."okapi/lib/oc_session.php");
|
||||
$OC_user_id = OCSession::get_user_id();
|
||||
|
||||
# Ensure a user is logged in (or force re-login).
|
||||
|
||||
if ($force_relogin || ($GLOBALS['usr'] == false))
|
||||
if ($force_relogin || ($OC_user_id == null))
|
||||
{
|
||||
if ($force_relogin)
|
||||
{
|
||||
@@ -88,7 +94,7 @@ class View
|
||||
# We should be logged out now. Let's login again.
|
||||
|
||||
$after_login = "okapi/apps/authorize?oauth_token=$token_key".(($langpref != Settings::get('SITELANG'))?"&langpref=".$langpref:"");
|
||||
$login_url = $GLOBALS['absolute_server_URI']."login.php?target=".urlencode($after_login)
|
||||
$login_url = Settings::get('SITE_URL')."login.php?target=".urlencode($after_login)
|
||||
."&langpref=".$langpref;
|
||||
return new OkapiRedirectResponse($login_url);
|
||||
}
|
||||
@@ -101,7 +107,7 @@ class View
|
||||
select 1
|
||||
from okapi_authorizations
|
||||
where
|
||||
user_id = '".mysql_real_escape_string($GLOBALS['usr']['userid'])."'
|
||||
user_id = '".mysql_real_escape_string($OC_user_id)."'
|
||||
and consumer_key = '".mysql_real_escape_string($token['consumer_key'])."'
|
||||
", 0);
|
||||
|
||||
@@ -118,7 +124,7 @@ class View
|
||||
insert into okapi_authorizations (consumer_key, user_id)
|
||||
values (
|
||||
'".mysql_real_escape_string($token['consumer_key'])."',
|
||||
'".mysql_real_escape_string($GLOBALS['usr']['userid'])."'
|
||||
'".mysql_real_escape_string($OC_user_id)."'
|
||||
);
|
||||
");
|
||||
$authorized = true;
|
||||
@@ -133,7 +139,7 @@ class View
|
||||
} else {
|
||||
# Consumer did not provide a callback URL (oauth_callback=oob).
|
||||
# We'll have to redirect to the OpenCaching main page then...
|
||||
return new OkapiRedirectResponse($GLOBALS['absolute_server_URI']."index.php");
|
||||
return new OkapiRedirectResponse(Settings::get('SITE_URL')."index.php");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +147,7 @@ class View
|
||||
{
|
||||
# Not yet authorized. Display an authorization request.
|
||||
$vars = array(
|
||||
'okapi_base_url' => $GLOBALS['absolute_server_URI']."okapi/",
|
||||
'okapi_base_url' => Settings::get('SITE_URL')."okapi/",
|
||||
'token' => $token,
|
||||
'site_name' => Okapi::get_normalized_site_name(),
|
||||
'locales' => $locales,
|
||||
@@ -161,7 +167,7 @@ class View
|
||||
|
||||
Db::execute("
|
||||
update okapi_tokens
|
||||
set user_id = '".mysql_real_escape_string($GLOBALS['usr']['userid'])."'
|
||||
set user_id = '".mysql_real_escape_string($OC_user_id)."'
|
||||
where `key` = '".mysql_real_escape_string($token_key)."';
|
||||
");
|
||||
|
||||
@@ -172,7 +178,7 @@ class View
|
||||
} else {
|
||||
# Consumer did not provide a callback URL (probably the user is using a desktop
|
||||
# or mobile application). We'll just have to display the verifier to the user.
|
||||
return new OkapiRedirectResponse($GLOBALS['absolute_server_URI']."okapi/apps/authorized?oauth_token=".$token_key
|
||||
return new OkapiRedirectResponse(Settings::get('SITE_URL')."okapi/apps/authorized?oauth_token=".$token_key
|
||||
."&oauth_verifier=".$token['verifier']."&langpref=".$langpref);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,11 +37,11 @@ class View
|
||||
{
|
||||
# Probably Request Token has expired or it was already used. We'll
|
||||
# just redirect to the OpenCaching main page.
|
||||
return new OkapiRedirectResponse($GLOBALS['absolute_server_URI']);
|
||||
return new OkapiRedirectResponse(Settings::get('SITE_URL'));
|
||||
}
|
||||
|
||||
$vars = array(
|
||||
'okapi_base_url' => $GLOBALS['absolute_server_URI']."okapi/",
|
||||
'okapi_base_url' => Settings::get('SITE_URL')."okapi/",
|
||||
'token' => $token,
|
||||
'verifier' => $verifier,
|
||||
'site_name' => Okapi::get_normalized_site_name()
|
||||
|
||||
@@ -9,6 +9,7 @@ use okapi\OkapiHttpResponse;
|
||||
use okapi\OkapiHttpRequest;
|
||||
use okapi\OkapiRedirectResponse;
|
||||
use okapi\Settings;
|
||||
use okapi\OCSession;
|
||||
|
||||
class View
|
||||
{
|
||||
@@ -17,12 +18,15 @@ class View
|
||||
$langpref = isset($_GET['langpref']) ? $_GET['langpref'] : Settings::get('SITELANG');
|
||||
$langprefs = explode("|", $langpref);
|
||||
|
||||
# Ensure a user is logged in.
|
||||
|
||||
if ($GLOBALS['usr'] == false)
|
||||
# Determine which user is logged in to OC.
|
||||
|
||||
require_once($GLOBALS['rootpath']."okapi/lib/oc_session.php");
|
||||
$OC_user_id = OCSession::get_user_id();
|
||||
|
||||
if ($OC_user_id == null)
|
||||
{
|
||||
$after_login = "okapi/apps/".(($langpref != Settings::get('SITELANG'))?"?langpref=".$langpref:"");
|
||||
$login_url = $GLOBALS['absolute_server_URI']."login.php?target=".urlencode($after_login);
|
||||
$login_url = Settings::get('SITE_URL')."login.php?target=".urlencode($after_login);
|
||||
return new OkapiRedirectResponse($login_url);
|
||||
}
|
||||
|
||||
@@ -34,12 +38,12 @@ class View
|
||||
okapi_consumers c,
|
||||
okapi_authorizations a
|
||||
where
|
||||
a.user_id = '".mysql_real_escape_string($GLOBALS['usr']['userid'])."'
|
||||
a.user_id = '".mysql_real_escape_string($OC_user_id)."'
|
||||
and c.`key` = a.consumer_key
|
||||
order by c.name
|
||||
");
|
||||
$vars = array();
|
||||
$vars['okapi_base_url'] = $GLOBALS['absolute_server_URI']."okapi/";
|
||||
$vars['okapi_base_url'] = Settings::get('SITE_URL')."okapi/";
|
||||
$vars['site_name'] = Okapi::get_normalized_site_name();
|
||||
$vars['apps'] = array();
|
||||
while ($row = mysql_fetch_assoc($rs))
|
||||
|
||||
@@ -4,21 +4,28 @@ namespace okapi\views\apps\revoke_access;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\Db;
|
||||
use okapi\OkapiHttpResponse;
|
||||
use okapi\OkapiHttpRequest;
|
||||
use okapi\OkapiRedirectResponse;
|
||||
use okapi\OCSession;
|
||||
|
||||
class View
|
||||
{
|
||||
public static function call()
|
||||
{
|
||||
# Determine which user is logged in to OC.
|
||||
|
||||
require_once($GLOBALS['rootpath']."okapi/lib/oc_session.php");
|
||||
$OC_user_id = OCSession::get_user_id();
|
||||
|
||||
# Ensure a user is logged in.
|
||||
|
||||
if ($GLOBALS['usr'] == false)
|
||||
if ($OC_user_id == null)
|
||||
{
|
||||
$after_login = "okapi/apps/"; # it is correct, if you're wondering
|
||||
$login_url = $GLOBALS['absolute_server_URI']."login.php?target=".urlencode($after_login);
|
||||
$login_url = Settings::get('SITE_URL')."login.php?target=".urlencode($after_login);
|
||||
return new OkapiRedirectResponse($login_url);
|
||||
}
|
||||
|
||||
@@ -29,18 +36,18 @@ class View
|
||||
Db::execute("
|
||||
delete from okapi_tokens
|
||||
where
|
||||
user_id = '".mysql_real_escape_string($GLOBALS['usr']['userid'])."'
|
||||
user_id = '".mysql_real_escape_string($OC_user_id)."'
|
||||
and consumer_key = '".mysql_real_escape_string($consumer_key)."'
|
||||
");
|
||||
Db::execute("
|
||||
delete from okapi_authorizations
|
||||
where
|
||||
user_id = '".mysql_real_escape_string($GLOBALS['usr']['userid'])."'
|
||||
user_id = '".mysql_real_escape_string($OC_user_id)."'
|
||||
and consumer_key = '".mysql_real_escape_string($consumer_key)."'
|
||||
");
|
||||
|
||||
# Redirect back to the apps page.
|
||||
|
||||
return new OkapiRedirectResponse($GLOBALS['absolute_server_URI']."okapi/apps/");
|
||||
return new OkapiRedirectResponse(Settings::get('SITE_URL')."okapi/apps/");
|
||||
}
|
||||
}
|
||||
|
||||
45
htdocs/okapi/views/attrlist.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace okapi\views\attrlist;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Cache;
|
||||
use okapi\Db;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\OkapiRedirectResponse;
|
||||
use okapi\OkapiHttpResponse;
|
||||
use okapi\ParamMissing;
|
||||
use okapi\InvalidParam;
|
||||
use okapi\OkapiServiceRunner;
|
||||
use okapi\OkapiInternalRequest;
|
||||
|
||||
class View
|
||||
{
|
||||
public static function call()
|
||||
{
|
||||
# This is a hidden page for OKAPI developers. It will list all
|
||||
# attributes defined in this OC installation.
|
||||
|
||||
$rs = Db::query("select id, language, text_long from cache_attrib order by id");
|
||||
$dict = array();
|
||||
while ($row = mysql_fetch_assoc($rs))
|
||||
$dict[$row['id']][strtolower($row['language'])] = $row['text_long'];
|
||||
$chunks = array();
|
||||
foreach ($dict as $internal_id => $langs)
|
||||
{
|
||||
$chunks[] = "<attr code='...' internal_id='$internal_id'";
|
||||
$langkeys = array_keys($langs);
|
||||
sort($langkeys);
|
||||
foreach ($langkeys as $langkey)
|
||||
$chunks[] = " $langkey='".$langs[$langkey]."'";
|
||||
$chunks[] = " />\n";
|
||||
}
|
||||
|
||||
$response = new OkapiHttpResponse();
|
||||
$response->content_type = "text/plain; charset=utf-8";
|
||||
$response->body = implode("", $chunks);
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class View
|
||||
# every 5 minutes (run 'crontab -e' to change or disable it) AND additionally
|
||||
# every time you visit http://yoursite/okapi/cron5
|
||||
|
||||
# require_once 'cronjobs.php'; CronJobController::force_run("JOB_NAME"); die();
|
||||
# require_once($GLOBALS['rootpath']."okapi/cronjobs.php"); CronJobController::force_run("JOB_NAME"); die();
|
||||
|
||||
Okapi::execute_cron5_cronjobs();
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class View
|
||||
$response->content_type = "text/plain; charset=utf-8";
|
||||
ob_start();
|
||||
|
||||
require_once 'cronjobs.php';
|
||||
require_once($GLOBALS['rootpath']."okapi/cronjobs.php");
|
||||
$schedule = Cache::get("cron_schedule");
|
||||
if ($schedule == null)
|
||||
$schedule = array();
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace okapi\views\devel\dbstruct;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\Cache;
|
||||
use okapi\Db;
|
||||
use okapi\OkapiRequest;
|
||||
@@ -22,9 +23,9 @@ class View
|
||||
# structure of the database. This is useful for making OKAPI compatible
|
||||
# across different OC installations.
|
||||
|
||||
$user = $GLOBALS['dbusername'];
|
||||
$password = $GLOBALS['dbpasswd'];
|
||||
$dbname = $GLOBALS['dbname'];
|
||||
$user = Settings::get('DB_USERNAME');
|
||||
$password = Settings::get('DB_PASSWORD');
|
||||
$dbname = Settings::get('DB_NAME');
|
||||
$struct = shell_exec("mysqldump --no-data -u$user -p$password $dbname");
|
||||
|
||||
# Remove the "AUTO_INCREMENT=..." values. They break the diffs.
|
||||
@@ -51,13 +52,13 @@ class View
|
||||
"-- Note: The following script has some limitations. It will render database\n".
|
||||
"-- structure compatible, but not necessarilly EXACTLY the same. It might be\n".
|
||||
"-- better to use manual diff instead.\n\n";
|
||||
require_once 'comparator.inc.php';
|
||||
require_once("comparator.inc.php");
|
||||
$updater = new \dbStructUpdater();
|
||||
if (isset($_GET['reverse']) && ($_GET['reverse'] == 'true'))
|
||||
{
|
||||
$response->body .=
|
||||
"-- REVERSE MODE. The following will alter [2], so that it has the structure of [1].\n".
|
||||
"-- 1. ".$GLOBALS['absolute_server_URI']."okapi/devel/dbstruct (".md5($struct).")\n".
|
||||
"-- 1. ".Settings::get('SITE_URL')."okapi/devel/dbstruct (".md5($struct).")\n".
|
||||
"-- 2. ".$_GET['compare_to']." (".md5($alternate_struct).")\n\n";
|
||||
$alters = $updater->getUpdates($alternate_struct, $struct);
|
||||
}
|
||||
@@ -65,7 +66,7 @@ class View
|
||||
{
|
||||
$response->body .=
|
||||
"-- The following will alter [1], so that it has the structure of [2].\n".
|
||||
"-- 1. ".$GLOBALS['absolute_server_URI']."okapi/devel/dbstruct (".md5($struct).")\n".
|
||||
"-- 1. ".Settings::get('SITE_URL')."okapi/devel/dbstruct (".md5($struct).")\n".
|
||||
"-- 2. ".$_GET['compare_to']." (".md5($alternate_struct).")\n\n";
|
||||
$alters = $updater->getUpdates($struct, $alternate_struct);
|
||||
}
|
||||
|
||||
162
htdocs/okapi/views/devel/tilereport.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace okapi\views\devel\tilereport;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Cache;
|
||||
use okapi\Db;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\OkapiRedirectResponse;
|
||||
use okapi\OkapiHttpResponse;
|
||||
use okapi\ParamMissing;
|
||||
use okapi\InvalidParam;
|
||||
use okapi\OkapiServiceRunner;
|
||||
use okapi\OkapiInternalRequest;
|
||||
use okapi\cronjobs\CronJobController;
|
||||
|
||||
class View
|
||||
{
|
||||
public static function call()
|
||||
{
|
||||
# Flush the stats, so the page is fresh upon every request.
|
||||
|
||||
require_once($GLOBALS['rootpath']."okapi/cronjobs.php");
|
||||
CronJobController::force_run("StatsWriterCronJob");
|
||||
|
||||
# When services/caches/map/tile method is called, it writes some extra
|
||||
# stats in the okapi_stats_hourly table. This page retrieves and
|
||||
# formats these stats in a readable manner (for debugging).
|
||||
|
||||
$response = new OkapiHttpResponse();
|
||||
$response->content_type = "text/plain; charset=utf-8";
|
||||
ob_start();
|
||||
|
||||
$start = isset($_GET['start']) ? $_GET['start'] : date(
|
||||
"Y-m-d 00:00:00", time() - 7*86400);
|
||||
$end = isset($_GET['end']) ? $_GET['end'] : date("Y-m-d 23:59:59");
|
||||
|
||||
print "From: $start\n";
|
||||
print " To: $end\n\n";
|
||||
|
||||
$rs = Db::query("
|
||||
select
|
||||
service_name,
|
||||
sum(total_calls),
|
||||
sum(total_runtime)
|
||||
from okapi_stats_hourly
|
||||
where
|
||||
period_start >= '".mysql_real_escape_string($start)."'
|
||||
and period_start < '".mysql_real_escape_string($end)."'
|
||||
and service_name like '%caches/map/tile%'
|
||||
group by service_name
|
||||
");
|
||||
|
||||
$total_calls = 0;
|
||||
$total_runtime = 0.0;
|
||||
$calls = array('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0);
|
||||
$runtime = array('A' => 0.0, 'B' => 0.0, 'C' => 0.0, 'D' => 0.0);
|
||||
|
||||
while (list($name, $c, $r) = mysql_fetch_array($rs))
|
||||
{
|
||||
if ($name == 'services/caches/map/tile')
|
||||
{
|
||||
$total_calls = $c;
|
||||
$total_runtime = $r;
|
||||
}
|
||||
elseif (strpos($name, 'extra/caches/map/tile/checkpoint') === 0)
|
||||
{
|
||||
$calls[$name[32]] = $c;
|
||||
$runtime[$name[32]] = $r;
|
||||
}
|
||||
}
|
||||
if ($total_calls != $calls['A'])
|
||||
{
|
||||
print "Partial results. Only ".$calls['A']." out of $total_calls are covered.\n";
|
||||
print "All other will count as \"unaccounted for\".\n\n";
|
||||
$total_calls = $calls['A'];
|
||||
}
|
||||
|
||||
$calls_left = $total_calls;
|
||||
$runtime_left = $total_runtime;
|
||||
|
||||
$perc = function($a, $b) { return ($b > 0) ? sprintf("%.1f", 100 * $a / $b)."%" : "(?)"; };
|
||||
$avg = function($a, $b) { return ($b > 0) ? sprintf("%.4f", $a / $b)."s" : "(?)"; };
|
||||
$get_stats = function() use (&$calls_left, &$runtime_left, &$total_calls, &$total_runtime, &$perc)
|
||||
{
|
||||
return (
|
||||
str_pad($perc($calls_left, $total_calls), 6, " ", STR_PAD_LEFT).
|
||||
str_pad($perc($runtime_left, $total_runtime), 7, " ", STR_PAD_LEFT)
|
||||
);
|
||||
};
|
||||
|
||||
print "%CALLS %TIME Description\n";
|
||||
print "====== ====== ======================================================================\n";
|
||||
print $get_stats()." $total_calls responses served. Total runtime: ".sprintf("%.2f", $total_runtime)."s\n";
|
||||
print "\n";
|
||||
print " All of these requests needed a TileTree build/lookup. The average runtime of\n";
|
||||
print " these lookups was ".$avg($runtime['A'], $total_calls).". ".$perc($runtime['A'], $total_runtime)." of total runtime was spent here.\n";
|
||||
print "\n";
|
||||
|
||||
$runtime_left -= $runtime['A'];
|
||||
|
||||
print $get_stats()." All calls passed here after ~".$avg($runtime['A'], $total_calls)."\n";
|
||||
|
||||
print "\n";
|
||||
print " Lookup result was then processed and \"image description\" was created. It was\n";
|
||||
print " passed on to the TileRenderer to compute the ETag hash string. The average runtime\n";
|
||||
print " of this part was ".$avg($runtime['B'], $total_calls).". ".$perc($runtime['B'], $total_runtime)." of total runtime was spent here.\n";
|
||||
print "\n";
|
||||
|
||||
$runtime_left -= $runtime['B'];
|
||||
|
||||
print $get_stats()." All calls passed here after ~".$avg($runtime['A'] + $runtime['B'], $total_calls)."\n";
|
||||
|
||||
$etag_hits = $calls['B'] - $calls['C'];
|
||||
|
||||
print "\n";
|
||||
print " $etag_hits of the requests matched the ETag and were served a HTTP 304 response.\n";
|
||||
print "\n";
|
||||
|
||||
$calls_left = $calls['C'];
|
||||
|
||||
print $get_stats()." $calls_left calls passed here after ~".$avg($runtime['A'] + $runtime['B'], $total_calls)."\n";
|
||||
|
||||
$imagecache_hits = $calls['C'] - $calls['D'];
|
||||
|
||||
print "\n";
|
||||
print " $imagecache_hits of these calls hit the server image cache.\n";
|
||||
print " ".$perc($runtime['C'], $total_runtime)." of total runtime was spent to find these.\n";
|
||||
print "\n";
|
||||
|
||||
$calls_left = $calls['D'];
|
||||
$runtime_left -= $runtime['C'];
|
||||
|
||||
print $get_stats()." $calls_left calls passed here after ~".$avg($runtime['A'] + $runtime['B'] + $runtime['C'], $total_calls)."\n";
|
||||
print "\n";
|
||||
print " These calls required the tile to be rendered. On average, it took\n";
|
||||
print " ".$avg($runtime['D'], $calls['D'])." to *render* a tile.\n";
|
||||
print " ".$perc($runtime['D'], $total_runtime)." of total runtime was spent here.\n";
|
||||
print "\n";
|
||||
|
||||
$runtime_left -= $runtime['D'];
|
||||
|
||||
print $perc($runtime_left, $total_runtime)." of runtime was unaccounted for (other processing).\n";
|
||||
print "Average response time was ".$avg($total_runtime, $total_calls).".\n\n";
|
||||
|
||||
print "Current okapi_cache score distribution:\n";
|
||||
$rs = Db::query("
|
||||
select floor(log2(score)), count(*), sum(length(value))
|
||||
from okapi_cache
|
||||
where score is not null
|
||||
group by floor(log2(score))
|
||||
");
|
||||
while (list($log2, $count, $size) = mysql_fetch_array($rs))
|
||||
{
|
||||
print $count." elements ($size bytes) with score between ".pow(2, $log2)." and ".pow(2, $log2 + 1).".\n";
|
||||
}
|
||||
|
||||
$response->body = ob_get_clean();
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace okapi\views\examples;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\OkapiHttpResponse;
|
||||
use okapi\ParamMissing;
|
||||
@@ -16,13 +17,13 @@ class View
|
||||
{
|
||||
public static function call()
|
||||
{
|
||||
require_once $GLOBALS['rootpath'].'okapi/service_runner.php';
|
||||
require_once $GLOBALS['rootpath'].'okapi/views/menu.inc.php';
|
||||
require_once($GLOBALS['rootpath'].'okapi/service_runner.php');
|
||||
require_once($GLOBALS['rootpath'].'okapi/views/menu.inc.php');
|
||||
|
||||
$vars = array(
|
||||
'menu' => OkapiMenu::get_menu_html("examples.html"),
|
||||
'okapi_base_url' => $GLOBALS['absolute_server_URI']."okapi/",
|
||||
'site_url' => $GLOBALS['absolute_server_URI'],
|
||||
'okapi_base_url' => Settings::get('SITE_URL')."okapi/",
|
||||
'site_url' => Settings::get('SITE_URL'),
|
||||
'installations' => OkapiMenu::get_installations(),
|
||||
'okapi_rev' => Okapi::$revision,
|
||||
'site_name' => Okapi::get_normalized_site_name(),
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace okapi\views\http404;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\OkapiHttpResponse;
|
||||
use okapi\views\menu\OkapiMenu;
|
||||
|
||||
@@ -11,10 +12,10 @@ class View
|
||||
{
|
||||
public static function call()
|
||||
{
|
||||
require_once 'menu.inc.php';
|
||||
require_once('menu.inc.php');
|
||||
|
||||
$vars = array(
|
||||
'okapi_base_url' => $GLOBALS['absolute_server_URI']."okapi/",
|
||||
'okapi_base_url' => Settings::get('SITE_URL')."okapi/",
|
||||
'menu' => OkapiMenu::get_menu_html(),
|
||||
'installations' => OkapiMenu::get_installations(),
|
||||
'okapi_rev' => Okapi::$revision,
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace okapi\views\index;
|
||||
|
||||
use Exception;
|
||||
use okapi\Okapi;
|
||||
use okapi\Settings;
|
||||
use okapi\OkapiRequest;
|
||||
use okapi\OkapiRedirectResponse;
|
||||
use okapi\ParamMissing;
|
||||
@@ -18,7 +19,7 @@ class View
|
||||
# This is called when someone displays "http://../okapi/" (with no
|
||||
# html path at the end). We will redirect to the introduction page.
|
||||
|
||||
return new OkapiRedirectResponse($GLOBALS['absolute_server_URI'].
|
||||
return new OkapiRedirectResponse(Settings::get('SITE_URL').
|
||||
"okapi/introduction.html");
|
||||
}
|
||||
}
|
||||
|
||||