Merge pull request #57 from wrygiel/okapi

OKAPI Project update (r554).
This commit is contained in:
following5
2013-01-30 03:00:43 -08:00
64 changed files with 1046 additions and 977 deletions

View File

@@ -759,7 +759,7 @@ class Okapi
{
public static $data_store;
public static $server;
public static $revision = 500; # This gets replaced in automatically deployed packages
public static $revision = 554; # 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. */
@@ -1079,6 +1079,7 @@ class Okapi
if ($init_made)
return;
ini_set('memory_limit', '128M');
Db::connect();
if (!self::$data_store)
self::$data_store = new OkapiDataStore();
if (!self::$server)
@@ -1223,9 +1224,11 @@ class Okapi
return $names[round(($b / 360.0) * 16.0) % 16];
}
/** Escape string for use with XML. */
public static function xmlentities($string)
/** Escape string for use with XML. See issue 169. */
public static function xmlescape($string)
{
static $pattern = '/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u';
$string = preg_replace($pattern, '', $string);
return strtr($string, array("<" => "&lt;", ">" => "&gt;", "\"" => "&quot;", "'" => "&apos;", "&" => "&amp;"));
}
@@ -1296,7 +1299,7 @@ class Okapi
if (is_string($obj))
{
$chunks[] = "<string>";
$chunks[] = self::xmlentities($obj);
$chunks[] = self::xmlescape($obj);
$chunks[] = "</string>";
}
elseif (is_int($obj))
@@ -1336,7 +1339,7 @@ class Okapi
$chunks[] = "<dict>";
foreach ($obj as $key => &$item_ref)
{
$chunks[] = "<item key=\"".self::xmlentities($key)."\">";
$chunks[] = "<item key=\"".self::xmlescape($key)."\">";
self::_xmlmap_add($chunks, $item_ref);
$chunks[] = "</item>";
}
@@ -1352,11 +1355,11 @@ class Okapi
private static function _xmlmap2_add(&$chunks, &$obj, $key)
{
$attrs = ($key !== null) ? " key=\"".self::xmlentities($key)."\"" : "";
$attrs = ($key !== null) ? " key=\"".self::xmlescape($key)."\"" : "";
if (is_string($obj))
{
$chunks[] = "<string$attrs>";
$chunks[] = self::xmlentities($obj);
$chunks[] = self::xmlescape($obj);
$chunks[] = "</string>";
}
elseif (is_int($obj))
@@ -1436,15 +1439,17 @@ class Okapi
'oc.pl' => array(
# Primary types (documented, cannot change)
'Traditional' => 2, 'Multi' => 3, 'Quiz' => 7, 'Virtual' => 4,
'Event' => 6,
# Additional types (may get changed)
'Other' => 1, 'Webcam' => 5, 'Event' => 6,
'Other' => 1, 'Webcam' => 5,
'Moving' => 8, 'Podcast' => 9, 'Own' => 10,
),
'oc.de' => array(
# Primary types (documented, cannot change)
'Traditional' => 2, 'Multi' => 3, 'Quiz' => 7, 'Virtual' => 4,
'Event' => 6,
# Additional types (might get changed)
'Other' => 1, 'Webcam' => 5, 'Event' => 6,
'Other' => 1, 'Webcam' => 5,
'Math/Physics' => 8, 'Moving' => 9, 'Drive-In' => 10,
)
);
@@ -1568,6 +1573,8 @@ class Okapi
if ($name == 'Found it') return 1;
if ($name == "Didn't find it") return 2;
if ($name == 'Comment') return 3;
if ($name == 'Attended') return 7;
if ($name == 'Will attend') return 8;
if (($name == 'Needs maintenance') && (Settings::get('SUPPORTS_LOGTYPE_NEEDS_MAINTENANCE')))
return 5;
throw new Exception("logtype2id called with invalid log type argument: $name");
@@ -1584,6 +1591,8 @@ class Okapi
if ($id == 1) return "Found it";
if ($id == 2) return "Didn't find it";
if ($id == 3) return "Comment";
if ($id == 7) return "Attended";
if ($id == 8) return "Will attend";
static $other_types = null;
if ($other_types === null)

View File

@@ -49,6 +49,7 @@ class CronJobController
new LocaleChecker(),
new FulldumpGeneratorJob(),
new TileTreeUpdater(),
new SearchSetsCleanerJob(),
);
foreach ($cache as $cronjob)
if (!in_array($cronjob->get_type(), array('pre-request', 'cron-5')))
@@ -261,6 +262,24 @@ class OAuthCleanupCronJob extends PrerequestCronJob
}
}
/** Clean up the saved search tables, every 10 minutes. */
class SearchSetsCleanerJob extends Cron5Job
{
public function get_period() { return 600; }
public function execute()
{
Db::execute("
delete oss, osr
from
okapi_search_sets oss
left join okapi_search_results osr
on oss.id = osr.set_id
where
date_add(oss.expires, interval 60 second) < now()
");
}
}
/** Clean up the cache, once per hour. */
class CacheCleanupCronJob extends Cron5Job
{
@@ -413,7 +432,7 @@ class CheckCronTab2 extends PrerequestCronJob
# There was a ping during the last hour. Everything is okay.
# Reset the counter and return.
Cache::set('crontab_check_counter', 3, 86400);
Cache::set('crontab_check_counter', 5, 86400);
return;
}
@@ -421,7 +440,7 @@ class CheckCronTab2 extends PrerequestCronJob
$counter = Cache::get('crontab_check_counter');
if ($counter === null)
$counter = 3;
$counter = 5;
$counter--;
if ($counter > 0)
{
@@ -522,8 +541,11 @@ class TileTreeUpdater extends Cron5Job
# not working for more than 10 days. Or, just after OKAPI
# is installed (and this is the first time this cronjob
# if being run).
\okapi\services\caches\map\ReplicateListener::reset();
$mail_admins = ($tiletree_revision > 0);
\okapi\services\caches\map\ReplicateListener::reset($mail_admins);
Okapi::set_var('clog_followup_revision', $current_clog_revision);
break;
}
}
} else {

View File

@@ -31,6 +31,7 @@ use okapi\OkapiFacadeAccessToken;
require_once($GLOBALS['rootpath']."okapi/core.php");
OkapiErrorHandler::$treat_notices_as_errors = true;
require_once($GLOBALS['rootpath']."okapi/service_runner.php");
Okapi::init_internals();
/**
* Use this class to access OKAPI's services from external code (i.e. OC code).
@@ -73,4 +74,28 @@ class Facade
$response = OkapiServiceRunner::call($service_name, $request);
$response->display();
}
/**
* Create a search set from a temporary table. This is very similar to
* the "services/caches/search/save" method, but allows OC server to
* include its own result instead of using OKAPI's search options. The
* $temp_table should be a valid name of a temporary table with the
* following (or similar) structure:
*
* create temporary table temp_12345 (
* cache_id integer primary key
* ) engine=memory;
*/
public static function import_search_set($temp_table, $min_store, $max_ref_age)
{
require_once 'services/caches/search/save.php';
$tables = array('caches', $temp_table);
$where_conds = array(
$temp_table.".cache_id = caches.cache_id",
'caches.status in (1,2,3)',
);
return \okapi\services\caches\search\save\WebService::get_set(
$tables, $where_conds, $min_store, $max_ref_age
);
}
}

View File

@@ -30,9 +30,11 @@ class OkapiServiceRunner
'services/caches/search/bbox',
'services/caches/search/nearest',
'services/caches/search/by_urls',
'services/caches/search/save',
'services/caches/shortcuts/search_and_retrieve',
'services/caches/geocache',
'services/caches/geocaches',
'services/caches/mark',
'services/caches/formatters/gpx',
'services/caches/formatters/garmin',
'services/caches/map/tile',

View File

@@ -96,17 +96,20 @@ class WebService
$referenced_method_info = OkapiServiceRunner::call('services/apiref/method',
new OkapiInternalRequest(new OkapiInternalConsumer(), null, array('name' => $referenced_methodname)));
$include_list = isset($attrs['params']) ? explode("|", $attrs['params']) : null;
$exclude_list = isset($attrs['except']) ? explode("|", $attrs['except']) : array();
foreach ($referenced_method_info['arguments'] as $arg)
{
if ($arg['class'] == 'common-formatting')
continue;
if ($include_list === null)
if (($include_list === null) && (count($exclude_list) == 0))
{
$arg['description'] = "<i>Inherited from <a href='".$referenced_method_info['ref_url'].
"'>".$referenced_method_info['name']."</a> method.</i>";
}
elseif (in_array($arg['name'], $include_list))
{
elseif (
(($include_list === null) || in_array($arg['name'], $include_list))
&& (!in_array($arg['name'], $exclude_list))
) {
$arg['description'] = "<i>Same as in the <a href='".$referenced_method_info['ref_url'].
"'>".$referenced_method_info['name']."</a> method.</i>";
} else {

View File

@@ -27,10 +27,10 @@
<ul>
<li><b>min_auth_level</b> - integer, in range from 0 to 3,
see Introduction page.</li>
<li><b>oauth_consumer</b> - "required" when min_auth_level >= 2,
"optional" otherwise,</li>
<li><b>oauth_token</b> - "required" when min_auth_level == 3,
"optional" otherwise.</li>
<li><b>oauth_consumer</b> - true, if requests are required to be signed
with OAuth Consumer Key (min_auth_level >= 2),</li>
<li><b>oauth_token</b> - true, if requests are required to include an
OAuth Token (min_auth_level == 3).</li>
</ul>
</li>
<li><b>arguments</b> - list of dictionaries, describes method

View File

@@ -24,12 +24,12 @@ class WebService
public static function call(OkapiRequest $request)
{
$cache_key = "api_ref/method_index";
$methodnames = OkapiServiceRunner::$all_names;
sort($methodnames);
$cache_key = "api_ref/method_index#".md5(implode("#", $methodnames));
$results = Cache::get($cache_key);
if ($results == null)
{
$methodnames = OkapiServiceRunner::$all_names;
sort($methodnames);
$results = array();
foreach ($methodnames as $methodname)
{

View File

@@ -3,6 +3,7 @@
namespace okapi\services\caches\formatters\garmin;
use okapi\Okapi;
use okapi\Cache;
use okapi\Settings;
use okapi\OkapiRequest;
use okapi\OkapiHttpResponse;
@@ -15,6 +16,7 @@ use okapi\OkapiAccessToken;
use okapi\services\caches\search\SearchAssistant;
use \ZipArchive;
use \Exception;
class WebService
{
@@ -45,7 +47,7 @@ class WebService
# Start creating ZIP archive.
$tempfilename = sys_get_temp_dir()."/garmin".time().rand(100000,999999).".zip";
$tempfilename = Okapi::get_var_dir()."/garmin".time().rand(100000,999999).".zip";
$zip = new ZipArchive();
if ($zip->open($tempfilename, ZIPARCHIVE::CREATE) !== true)
throw new Exception("ZipArchive class could not create temp file $tempfilename. Check permissions!");
@@ -87,6 +89,7 @@ class WebService
throw new InvalidParam('cache_codes', "The maximum number of caches allowed to be downloaded with this method is 50.");
if ($images != 'none')
{
$supported_extensions = array('jpg', 'jpeg', 'gif', 'png', 'bmp');
foreach ($caches as $cache_code => $dict)
{
$imgs = $dict['images'];
@@ -104,8 +107,18 @@ class WebService
continue;
if ($images == 'nonspoilers' && $img['is_spoiler'])
continue;
if (strtolower(substr($img['url'], strlen($img['url']) - 4)) != ".jpg")
continue;
$tmp = false;
foreach ($supported_extensions as $ext)
{
if (strtolower(substr($img['url'], strlen($img['url']) - strlen($ext) - 1)) != ".".$ext)
{
$tmp = true;
continue;
}
}
if (!$tmp)
continue; # unsupported file extension
if ($img['is_spoiler']) {
$zip->addEmptyDir($dir."/Spoilers");
$zippath = $dir."/Spoilers/".$img['unique_caption'].".jpg";
@@ -121,11 +134,50 @@ class WebService
# be accessed locally. But all the files have 'local' set to 1 anyway.
$syspath = Settings::get('IMAGES_DIR')."/".$img['uuid'].".jpg";
if (!file_exists($syspath))
continue;
$file = file_get_contents($syspath);
if ($file)
$zip->addFromString($zippath, $file);
if (file_exists($syspath))
{
$file = file_get_contents($syspath);
if ($file)
$zip->addFromString($zippath, $file);
}
else
{
# If file exists, but does not end with ".jpg", we will create
# JPEG version of it and store it in the cache.
$cache_key = "jpg#".$img['uuid'];
$jpeg_contents = Cache::get($cache_key);
if ($jpeg_contents === null)
{
foreach ($supported_extensions as $ext)
{
$syspath_other = Settings::get('IMAGES_DIR')."/".$img['uuid'].".".$ext;
if (file_exists($syspath_other))
{
try
{
$image = imagecreatefromstring(file_get_contents($syspath_other));
ob_start();
imagejpeg($image);
$jpeg_contents = ob_get_clean();
imagedestroy($image);
}
catch (Exception $e)
{
# GD couldn't parse the file. We will skip it, and cache
# the "false" value as the contents. This way, we won't
# attempt to parse it during the next 24 hours.
$jpeg_contents = false;
}
Cache::set($cache_key, $jpeg_contents, 86400);
break;
}
}
}
if ($jpeg_contents) # This can be "null" *or* "false"!
$zip->addFromString($zippath, $jpeg_contents);
}
}
}
}

View File

@@ -1,5 +1,9 @@
<?
namespace okapi\services\caches\formatters\gpx;
use okapi\Okapi;
echo '<?xml version="1.0" encoding="utf-8"?>'."\n";
?>
@@ -24,24 +28,24 @@ http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
<wpt lat="<?= $lat ?>" lon="<?= $lon ?>">
<time><?= $c['date_created'] ?></time>
<name><?= $c['code'] ?></name>
<desc><?= htmlspecialchars($c['name'], ENT_COMPAT, 'UTF-8') ?> <?= _("hidden by") ?> <?= htmlspecialchars($c['owner']['username'], ENT_COMPAT, 'UTF-8') ?> :: <?= ucfirst($c['type']) ?> Cache (<?= $c['difficulty'] ?>/<?= $c['terrain'] ?><? if ($c['size'] !== null) { echo "/".$c['size']; } else { echo "/X"; } ?>/<?= $c['rating'] ?>)</desc>
<desc><?= Okapi::xmlescape($c['name']) ?> <?= _("hidden by") ?> <?= Okapi::xmlescape($c['owner']['username']) ?> :: <?= ucfirst($c['type']) ?> Cache (<?= $c['difficulty'] ?>/<?= $c['terrain'] ?><? if ($c['size'] !== null) { echo "/".$c['size']; } else { echo "/X"; } ?>/<?= $c['rating'] ?>)</desc>
<url><?= $c['url'] ?></url>
<urlname><?= htmlspecialchars($c['name'], ENT_COMPAT, 'UTF-8') ?></urlname>
<urlname><?= Okapi::xmlescape($c['name']) ?></urlname>
<sym><?= ($vars['mark_found'] && $c['is_found']) ? "Geocache Found" : "Geocache" ?></sym>
<type>Geocache|<?= $vars['cache_GPX_types'][$c['type']] ?></type>
<? if ($vars['ns_ground']) { /* Does user want us to include Groundspeak's <cache> element? */ ?>
<groundspeak:cache archived="<?= ($c['status'] == 'Archived') ? "True" : "False" ?>" available="<?= ($c['status'] == 'Available') ? "True" : "False" ?>" id="<?= $c['internal_id'] ?>" xmlns:groundspeak="http://www.groundspeak.com/cache/1/0/1">
<groundspeak:name><?= htmlspecialchars($c['name'], ENT_COMPAT, 'UTF-8') ?></groundspeak:name>
<groundspeak:placed_by><?= htmlspecialchars($c['owner']['username'], ENT_COMPAT, 'UTF-8') ?></groundspeak:placed_by>
<groundspeak:owner id="<?= $c['owner']['uuid'] ?>"><?= htmlspecialchars($c['owner']['username'], ENT_COMPAT, 'UTF-8') ?></groundspeak:owner>
<groundspeak:name><?= Okapi::xmlescape($c['name']) ?></groundspeak:name>
<groundspeak:placed_by><?= Okapi::xmlescape($c['owner']['username']) ?></groundspeak:placed_by>
<groundspeak:owner id="<?= $c['owner']['uuid'] ?>"><?= Okapi::xmlescape($c['owner']['username']) ?></groundspeak:owner>
<groundspeak:type><?= $vars['cache_GPX_types'][$c['type']] ?></groundspeak:type>
<groundspeak:container><?= $vars['cache_GPX_sizes'][$c['size2']] ?></groundspeak:container>
<groundspeak:difficulty><?= $c['difficulty'] ?></groundspeak:difficulty>
<groundspeak:terrain><?= $c['terrain'] ?></groundspeak:terrain>
<groundspeak:long_description html="True">
&lt;p&gt;
&lt;a href="<?= $c['url'] ?>"&gt;<?= htmlspecialchars($c['name'], ENT_COMPAT, 'UTF-8') ?>&lt;/a&gt;
<?= _("hidden by") ?> &lt;a href='<?= $c['owner']['profile_url'] ?>'&gt;<?= htmlspecialchars($c['owner']['username'], ENT_COMPAT, 'UTF-8') ?>&lt;/a&gt;&lt;br/&gt;
&lt;a href="<?= $c['url'] ?>"&gt;<?= Okapi::xmlescape($c['name']) ?>&lt;/a&gt;
<?= _("hidden by") ?> &lt;a href='<?= $c['owner']['profile_url'] ?>'&gt;<?= Okapi::xmlescape($c['owner']['username']) ?>&lt;/a&gt;&lt;br/&gt;
<? if ($vars['recommendations'] == 'desc:count') { /* Does user want us to include recommendations count? */ ?>
<?= sprintf(ngettext("%d recommendation", "%d recommendations", $c['recommendations']), $c['recommendations']) ?>
(<?= sprintf(ngettext("found %d time", "found %d times", $c['founds']), $c['founds']) ?>).
@@ -51,7 +55,7 @@ http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
<? } ?>
&lt;/p&gt;
<? if (($vars['my_notes'] == 'desc:text') && ($c['my_notes'] != null)) { /* Does user want us to include personal notes? */ ?>
&lt;p&gt;&lt;b&gt;<?= _("Personal notes") ?>:&lt;/b&gt; <?= htmlspecialchars($c['my_notes'], ENT_COMPAT, 'UTF-8') ?>&lt;/p&gt;
&lt;p&gt;&lt;b&gt;<?= _("Personal notes") ?>:&lt;/b&gt; <?= Okapi::xmlescape($c['my_notes']) ?>&lt;/p&gt;
<? } ?>
<? if ($vars['attrs'] == 'desc:text' && count($c['attrnames']) > 0) { /* Does user want us to include attributes? */ ?>
@@ -62,11 +66,11 @@ http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
&lt;p&gt;<?= _("Trackables") ?>:&lt;/p&gt;
&lt;ul&gt;
<? foreach ($c['trackables'] as $t) { ?>
&lt;li&gt;&lt;a href='<?= htmlspecialchars($t['url'], ENT_COMPAT, 'UTF-8') ?>'&gt;<?= htmlspecialchars($t['name'], ENT_COMPAT, 'UTF-8') ?>&lt;/a&gt; (<?= $t['code'] ?>)&lt;/li&gt;
&lt;li&gt;&lt;a href='<?= Okapi::xmlescape($t['url']) ?>'&gt;<?= Okapi::xmlescape($t['name']) ?>&lt;/a&gt; (<?= $t['code'] ?>)&lt;/li&gt;
<? } ?>
&lt;/ul&gt;
<? } ?>
<?= htmlspecialchars($c['description'], ENT_COMPAT, 'UTF-8') ?>
<?= Okapi::xmlescape($c['description']) ?>
<? if ((strpos($vars['images'], "descrefs:") === 0) && count($c['images']) > 0) { /* Does user want us to include <img> references in cache descriptions? */ ?>
<?
# We will split images into two subcategories: spoilers and nonspoilers.
@@ -79,15 +83,15 @@ http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
<? if (count($nonspoilers) > 0) { ?>
&lt;h2&gt;<?= _("Images") ?> (<?= count($nonspoilers) ?>)&lt;/h2&gt;
<? foreach ($nonspoilers as $img) { ?>
&lt;p&gt;&lt;img src='<?= htmlspecialchars($img['url'], ENT_COMPAT, 'UTF-8') ?>'&gt;&lt;br&gt;
<?= htmlspecialchars($img['caption'], ENT_COMPAT, 'UTF-8') ?>&lt;/p&gt;
&lt;p&gt;&lt;img src='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/p&gt;
<? } ?>
<? } ?>
<? if (count($spoilers) > 0 && $vars['images'] == 'descrefs:all') { ?>
&lt;h2&gt;<?= _("Spoilers") ?> (<?= count($spoilers) ?>)&lt;/h2&gt;
<? foreach ($spoilers as $img) { ?>
&lt;p&gt;&lt;img src='<?= htmlspecialchars($img['url'], ENT_COMPAT, 'UTF-8') ?>'&gt;&lt;br&gt;
<?= htmlspecialchars($img['caption'], ENT_COMPAT, 'UTF-8') ?>&lt;/p&gt;
&lt;p&gt;&lt;img src='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/p&gt;
<? } ?>
<? } ?>
<? } ?>
@@ -95,20 +99,20 @@ http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
&lt;p&gt;<?= _("Image descriptions") ?>:&lt;/p&gt;
&lt;ul&gt;
<? foreach ($c['images'] as $no => $img) { ?>
&lt;li&gt;<?= $img['unique_caption'] ?>. <?= htmlspecialchars($img['caption'], ENT_COMPAT, 'UTF-8') ?>&lt;/li&gt;
&lt;li&gt;<?= $img['unique_caption'] ?>. <?= Okapi::xmlescape($img['caption']) ?>&lt;/li&gt;
<? } ?>
&lt;/ul&gt;
<? } ?>
</groundspeak:long_description>
<groundspeak:encoded_hints><?= htmlspecialchars($c['hint'], ENT_COMPAT, 'UTF-8') ?></groundspeak:encoded_hints>
<groundspeak:encoded_hints><?= Okapi::xmlescape($c['hint']) ?></groundspeak:encoded_hints>
<? if ($vars['latest_logs']) { /* Does user want us to include latest log entries? */ ?>
<groundspeak:logs>
<? foreach ($c['latest_logs'] as $log) { ?>
<groundspeak:log id="<?= $log['uuid'] ?>">
<groundspeak:date><?= $log['date'] ?></groundspeak:date>
<groundspeak:type><?= $log['type'] ?></groundspeak:type>
<groundspeak:finder id="<?= $log['user']['uuid'] ?>"><?= htmlspecialchars($log['user']['username'], ENT_COMPAT, 'UTF-8') ?></groundspeak:finder>
<groundspeak:text encoded="False"><?= htmlspecialchars($log['comment'], ENT_COMPAT, 'UTF-8') ?></groundspeak:text>
<groundspeak:finder id="<?= $log['user']['uuid'] ?>"><?= Okapi::xmlescape($log['user']['username']) ?></groundspeak:finder>
<groundspeak:text encoded="False"><?= Okapi::xmlescape($log['comment']) ?></groundspeak:text>
</groundspeak:log>
<? } ?>
</groundspeak:logs>
@@ -150,11 +154,11 @@ http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
<? list($lat, $lon) = explode("|", $wpt['location']); ?>
<wpt lat="<?= $lat ?>" lon="<?= $lon ?>">
<time><?= $c['date_created'] ?></time>
<name><?= htmlspecialchars($wpt['name'], ENT_COMPAT, 'UTF-8') ?></name>
<cmt><?= htmlspecialchars($wpt['description'], ENT_COMPAT, 'UTF-8') ?></cmt>
<desc><?= htmlspecialchars($wpt['description'], ENT_COMPAT, 'UTF-8') ?></desc>
<name><?= Okapi::xmlescape($wpt['name']) ?></name>
<cmt><?= Okapi::xmlescape($wpt['description']) ?></cmt>
<desc><?= Okapi::xmlescape($wpt['description']) ?></desc>
<url><?= $c['url'] ?></url>
<urlname><?= htmlspecialchars($c['name'], ENT_COMPAT, 'UTF-8') ?></urlname>
<urlname><?= Okapi::xmlescape($c['name']) ?></urlname>
<sym><?= $wpt['sym'] ?></sym>
<type>Waypoint|<?= $wpt['sym'] ?></type>
<? if ($vars['ns_gsak']) { ?>

View File

@@ -9,7 +9,7 @@
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for fields like
<b>name</b> and <b>description</b>.</p>
<p>Please note, that you may also access caches descriptions in all
<p>Please note, that you may also access caches' descriptions in all
available languages. If you want to do this, you should use fields
<b>names</b>, <b>descriptions</b> (etc.) instead of <b>name</b> and
<b>description</b> (etc.).</p>
@@ -34,9 +34,15 @@
<li><b>Traditional</b>,</li>
<li><b>Multi</b>,</li>
<li><b>Quiz</b>,</li>
<li><b>Virtual</b>.</li>
<li><b>Virtual</b>,</li>
<li><b>Event</b>,</li>
<li><i>(any other value is valid too)</i></li>
</ul>
<p><b>Event</b> is a peculiar type of geocache which is NOT a geocache
at all, but it <b>is</b> stored as a geocache in OC database (this design
decision is old as the OC network itself!). Just keep in mind, that
in case of Event Caches, some fields may have a little different meaning
than you would tell by their name.</p>
</li>
<li>
<p><b>status</b> - cache status. Valid cache status codes currently include:</p>
@@ -58,37 +64,62 @@
<li>
<p><b>distance</b> - float, the distance to a cache, in meters.
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of quering OKAPI.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>bearing</b> - float, the absolute bearing to the cache, measured in degrees from north,
<b>or null</b> if it cannot be calculated (i.e. when you are exactly at the target's location).
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of quering OKAPI.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>bearing2</b> - string, the absolute bearing to the cache, represented as a typical direction
string of length of 1 or 2 characters (ex. "N", "NE", "E", "SE", etc.), or "n/a" if it cannot be calculated.
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of quering OKAPI.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>bearing3</b> - string, the absolute bearing to the cache, represented as a typical directon
string of length of 1 or 2 or 3 characters (ex. "N", "NNE", "NE", "ENE", etc.), or "n/a" if it cannot be calculated.
This requires <b>my_location</b> parameter to be provided.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of quering OKAPI.</p>
<p>Please note, that sometimes it is faster to compute this yourself, on client-side, instead of querying OKAPI.</p>
</li>
<li>
<p><b>is_found</b> - boolean, true if user already found this cache. See also <b>found_status</b> parameter
of the services/caches/search/all method.</p>
<p><b>is_found</b> - boolean, true if the user already found this cache.
See also <b>found_status</b> parameter of the services/caches/search/all
method.</p>
<p>This field requires you to use the <b>user_uuid</b> parameter
(or Level 3 Authentication). Please note, that this will also return <b>true</b>
for attended Event Caches.</p>
</li>
<li>
<p><b>is_not_found</b> - boolean, true if the user has submitted "Didn't find it" log entry for this cache.</p>
<p>This field requires you to use the <b>user_uuid</b> parameter (or Level 3 Authentication).</p>
</li>
<li>
<p><b>is_not_found</b> - boolean, true if user has submitted "Didn't find it" log entry for this cache.</p>
<p>This field requires you to use the <b>user_uuid</b> parameter (or Level 3 Authentication).</p>
<p><b>is_watched</b> - boolean, true if the user is watching this cache. You may consider highlighting
such geocaches in some fashion, as the users may use this functionality to temporarily mark geocaches
of particular interest (i.e. geocaches they plan to find today).</p>
<p>This is private data. You will need Level 3 Authentication to access this field.</p>
</li>
<li>
<p><b>is_ignored</b> - boolean, true if the user is ignoring this cache. (See also <b>exclude_ignored</b>
parameter of all search methods.)</p>
<p>This is private data. You will need Level 3 Authentication to access this field.</p>
</li>
<li>
<p><b>founds</b> - number of times the geocache was successfully found
(or attended, in case of Event Caches),</p>
</li>
<li>
<p><b>notfounds</b> - number of times the geocache was not found
(in case of Event Caches this will always be zero),</p>
</li>
<li>
<p><b>willattends</b> - in case of Event Caches, this is the number of
"Will attend" log entries. In case of any other cache type, it will
be <b>null</b></p>
</li>
<li><b>founds</b> - number of times the geocache was successfully found,</li>
<li><b>notfounds</b> - number of times the geocache was not found,</li>
<li class='deprecated'>
<b>size</b> - deprecated
(<a href='http://code.google.com/p/opencaching-api/issues/detail?id=155'>why?</a>),
@@ -138,8 +169,8 @@
following structure:</p>
<ul>
<li><b>uuid</b> - UUID of the image,</li>
<li><b>url</b> - an URL of the image,</li>
<li><b>thumb_url</b> - an URL of a small (thumb) version of the image
<li><b>url</b> - URL of the image,</li>
<li><b>thumb_url</b> - URL of a small (thumb) version of the image
<b>or null</b> when no thumb is available (e.g. there are no thumbs
for spoiler images),</li>
<li><b>caption</b> - plain-text string, caption of the image,</li>
@@ -188,8 +219,11 @@
<li><b>name</b> - plain-text "codename" for the waypoint (not unique),</li>
<li><b>location</b> - location of the waypoint in the "lat|lon" format
(<i>lat</i> and <i>lon</i> are in full degrees with a dot as a decimal point),</li>
<li><b>sym</b> - string, one of globally-recognized waypoint symbol
names (such as "Flag, Green" or "Parking Area"),</li>
<li>
<b>sym</b> - string, one of commonly recognized waypoint symbol
names, originally introduced by Garmin in their devices and GPX
files (e.g. "Flag, Green" or "Parking Area"),
</li>
<li><b>description</b> - plain-text longer description of the waypoint.</li>
</ul>
</li>
@@ -244,6 +278,6 @@
query, the result might look something link this:</p>
<pre>{"type": "Traditional"}</pre>
<p>If given cache code does not exist, the method will
respond with a HTTP 400 error.</p>
respond with an HTTP 400 error.</p>
</returns>
</xml>

View File

@@ -30,7 +30,8 @@ class WebService
'rating', 'rating_votes', 'recommendations', 'req_passwd', 'description',
'descriptions', 'hint', 'hints', 'images', 'attrnames', 'latest_logs',
'my_notes', 'trackables_count', 'trackables', 'alt_wpts', 'last_found',
'last_modified', 'date_created', 'date_hidden', 'internal_id');
'last_modified', 'date_created', 'date_hidden', 'internal_id', 'is_watched',
'is_ignored', 'willattends');
public static function call(OkapiRequest $request)
{
@@ -202,8 +203,26 @@ class WebService
break;
case 'is_found': /* handled separately */ break;
case 'is_not_found': /* handled separately */ break;
case 'is_watched': /* handled separately */ break;
case 'is_ignored': /* handled separately */ break;
case 'founds': $entry['founds'] = $row['founds'] + 0; break;
case 'notfounds': $entry['notfounds'] = $row['notfounds'] + 0; break;
case 'notfounds':
if ($row['type'] != 6) { # non-event
$entry['notfounds'] = $row['notfounds'] + 0;
} else { # event
$entry['notfounds'] = 0;
}
break;
case 'willattends':
# OCPL stats count "Will attend" log entries as "notfounds"
# (just another pecularity regarding "event caches").
# I am not sure about OCDE branch though...
if ($row['type'] == 6) { # event
$entry['willattends'] = $row['notfounds'] + 0;
} else { # non-event
$entry['willattends'] = 0;
}
break;
case 'size':
# Deprecated. Leave it for backward-compatibility. See issue 155.
switch (Okapi::cache_sizeid_to_size2($row['size']))
@@ -293,8 +312,12 @@ class WebService
cache_logs cl
where
c.cache_id = cl.cache_id
and cl.type = '".mysql_real_escape_string(Okapi::logtypename2id("Found it"))."'
and cl.type in (
'".mysql_real_escape_string(Okapi::logtypename2id("Found it"))."',
'".mysql_real_escape_string(Okapi::logtypename2id("Attended"))."'
)
and cl.user_id = '".mysql_real_escape_string($user_id)."'
".((Settings::get('OC_BRANCH') == 'oc.pl') ? "and cl.deleted = 0" : "")."
");
$tmp2 = array();
foreach ($tmp as $cache_code)
@@ -318,6 +341,7 @@ class WebService
c.cache_id = cl.cache_id
and cl.type = '".mysql_real_escape_string(Okapi::logtypename2id("Didn't find it"))."'
and cl.user_id = '".mysql_real_escape_string($user_id)."'
".((Settings::get('OC_BRANCH') == 'oc.pl') ? "and cl.deleted = 0" : "")."
");
$tmp2 = array();
foreach ($tmp as $cache_code)
@@ -326,6 +350,50 @@ class WebService
$result_ref['is_not_found'] = isset($tmp2[$cache_code]);
}
# is_watched
if (in_array('is_watched', $fields))
{
if ($request->token == null)
throw new BadRequest("Level 3 Authentication is required to access 'is_watched' field.");
$tmp = Db::select_column("
select c.wp_oc
from
caches c,
cache_watches cw
where
c.cache_id = cw.cache_id
and cw.user_id = '".mysql_real_escape_string($request->token->user_id)."'
");
$tmp2 = array();
foreach ($tmp as $cache_code)
$tmp2[$cache_code] = true;
foreach ($results as $cache_code => &$result_ref)
$result_ref['is_watched'] = isset($tmp2[$cache_code]);
}
# is_ignored
if (in_array('is_ignored', $fields))
{
if ($request->token == null)
throw new BadRequest("Level 3 Authentication is required to access 'is_ignored' field.");
$tmp = Db::select_column("
select c.wp_oc
from
caches c,
cache_ignore ci
where
c.cache_id = ci.cache_id
and ci.user_id = '".mysql_real_escape_string($request->token->user_id)."'
");
$tmp2 = array();
foreach ($tmp as $cache_code)
$tmp2[$cache_code] = true;
foreach ($results as $cache_code => &$result_ref)
$result_ref['is_ignored'] = isset($tmp2[$cache_code]);
}
# Descriptions and hints.
if (in_array('description', $fields) || in_array('descriptions', $fields)

View File

@@ -15,7 +15,7 @@
<p>Pipe-separated list of ISO 639-1 language codes. This indicates the
order of preference in which language will be chosen for fields like
<b>name</b> and <b>description</b>.</p>
<p>Please note, that you may also access caches descriptions in all
<p>Please note, that you may also access caches' descriptions in all
available languages. If you want to do this, you should use fields
<b>names</b>, <b>descriptions</b> (etc.) instead of <b>name</b> and
<b>description</b> (etc.).</p>
@@ -40,6 +40,6 @@
<pre>{"OP3D96": {"type": "Traditional"}, "OC124": null}</pre>
<p>Value of <b>null</b> means that the given cache haven't been found.
(This behavior is different than in the services/caches/geocache method, which
responds with a HTTP 400 error in such case.)</p>
responds with an HTTP 400 error in such case.)</p>
</returns>
</xml>

View File

@@ -41,23 +41,28 @@ class ReplicateListener
}
}
public static function reset()
public static function reset($mail_admins = true)
{
# This will be called when there are "too many" entries in the changelog
# and the replicate module thinks it better to just reset the entire TileTree.
# For the first hours after such reset maps may work 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!"
);
if ($mail_admins)
{
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!\n\n".
"P.S. This may also happen if you didn't run OKAPI on this server\n".
"for a while (your server was down or OKAPI didn't work properly)."
);
}
Db::execute("delete from okapi_tile_status");
Db::execute("delete from okapi_tile_caches");
}

View File

@@ -17,13 +17,15 @@ use okapi\DoesNotExist;
use okapi\OkapiInternalRequest;
use okapi\OkapiInternalConsumer;
use okapi\OkapiServiceRunner;
use okapi\OkapiLock;
use okapi\services\caches\map\TileTree;
use okapi\services\caches\map\DefaultTileRenderer;
use okapi\services\caches\search\SearchAssistant;
require_once('tiletree.inc.php');
require_once('tilerenderer.inc.php');
require_once($GLOBALS['rootpath']."okapi/services/caches/search/searching.inc.php");
class WebService
{
@@ -48,7 +50,7 @@ class WebService
public static function options()
{
return array(
'min_auth_level' => 1
'min_auth_level' => 3
);
}
@@ -84,261 +86,96 @@ class WebService
if ($y >= 1<<$zoom)
throw new InvalidParam('y', "Should be in 0..".((1<<$zoom) - 1).".");
# status
# Now, we will create a search set (or use one previously created).
# Instead of creating a new OkapiInternalRequest object, we will pass
# the current request directly. We can do that, because we inherit all
# of the "save" method's parameters.
$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))."')";
$search_set = OkapiServiceRunner::call('services/caches/search/save', $request);
$set_id = $search_set['set_id'];
# type
# Get caches which are present in the result set AND within the tile
# (+ those around the borders).
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.
$rs = TileTree::query_fast($zoom, $x, $y, $set_id);
$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);
}
OkapiServiceRunner::save_stats_extra("caches/map/tile/checkpointA", null,
microtime(true) - $checkpointA_started);
$checkpointB_started = microtime(true);
# Compute a fast image fingerprint. This will be used both for ETags
# Add dynamic, user-related flags.
if (count($rows) > 0)
{
# Load user-related cache ids.
$cache_key = "tileuser/".$request->token->user_id;
$user = self::$USE_OTHER_CACHE ? Cache::get($cache_key) : null;
if ($user === null)
{
$user = array();
# Ignored caches.
$rs = Db::query("
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);
}
# Add extra flags to geocaches.
foreach ($rows as &$row_ref)
{
# Add the "found" flag (to indicate that this cache needs
# to be drawn as found) and the "own" flag (to indicate that
# the current user is the owner).
if (isset($user['found'][$row_ref[0]]))
$row_ref[6] |= TileTree::$FLAG_FOUND; # $row[6] is "flags"
if (isset($user['own'][$row_ref[0]]))
$row_ref[6] |= TileTree::$FLAG_OWN; # $row[6] is "flags"
}
}
# Compute the image hash/fingerprint. This will be used both for ETags
# and internal cache ($cache_key).
$tile = new DefaultTileRenderer($zoom, $rows);

View File

@@ -11,14 +11,7 @@
<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"/>
<import-params method="services/caches/search/save"/>
<returns>
The PNG image with the requested map tile.
</returns>

View File

@@ -34,7 +34,7 @@ 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;
private static $VERSION = 56;
/**
* Should be always true. You may temporarily set it to false, when you're
@@ -121,12 +121,20 @@ class DefaultTileRenderer implements TileRenderer
{
# 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)
try
{
# Miss again. Read the image from PNG.
$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)
throw new Exception("Not in cache");
# File cache hit. GD2 files are much faster to read than PNGs.
# This can throw an Exception (see bug#160).
$locmem_cache[$key] = imagecreatefromgd2($gd2_path);
}
catch (Exception $e)
{
# Miss again (or error decoding). Read the image from PNG.
$locmem_cache[$key] = imagecreatefrompng($GLOBALS['rootpath']."okapi/static/tilemap/$name.png");
@@ -151,11 +159,6 @@ class DefaultTileRenderer implements TileRenderer
$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];
}
@@ -185,9 +188,10 @@ class DefaultTileRenderer implements TileRenderer
private function draw_cache(&$cache_struct)
{
if ($this->zoom <= 8)
$capt = ($cache_struct[6] & TileTree::$FLAG_DRAW_CAPTION);
if (($this->zoom <= 8) && (!$capt))
$this->draw_cache_tiny($cache_struct);
elseif ($this->zoom <= 12)
elseif (($this->zoom <= 12) && (!$capt))
$this->draw_cache_medium($cache_struct);
else
$this->draw_cache_large($cache_struct);
@@ -219,7 +223,8 @@ class DefaultTileRenderer implements TileRenderer
$r = 0; $g = 0; $b = 0;
} elseif ($found) {
$key = 'large_outer_found';
$a = 1; $br = 40; $c = 20;
$a = ($flags & TileTree::$FLAG_DRAW_CAPTION) ? .7 : .35;
$br = 40; $c = 20;
//$a = 0.5; $br = 0; $c = 0;
$r = 0; $g = 0; $b = 0;
} elseif ($new) {
@@ -289,7 +294,7 @@ class DefaultTileRenderer implements TileRenderer
if ($found)
{
$icon = self::get_image("found", 0.7, $br, $c, $r, $g, $b);
$icon = self::get_image("found", 0.7*$a, $br, $c, $r, $g, $b);
imagecopy($this->im, $icon, $px - 2, $py - $center_y - 3, 0, 0, 16, 16);
}
@@ -428,10 +433,14 @@ class DefaultTileRenderer implements TileRenderer
$found = $flags & TileTree::$FLAG_FOUND;
$own = $flags & TileTree::$FLAG_OWN;
$new = $flags & TileTree::$FLAG_NEW;
if ($found && (!($flags & TileTree::$FLAG_DRAW_CAPTION)))
$a = .35;
else
$a = 1;
# Put the marker (indicates the type).
$marker = self::get_image("medium_".self::get_type_suffix($type, false));
$marker = self::get_image("medium_".self::get_type_suffix($type, false), $a);
$width = 14;
$height = 14;
$center_x = 7;
@@ -459,6 +468,24 @@ class DefaultTileRenderer implements TileRenderer
$py - ($center_y - $markercenter_y) - 8, 0, 0, 16, 16);
}
# Put small versions of rating icons.
if ($status == 1)
{
if ($rating >= 4.2)
{
if ($flags & TileTree::$FLAG_STAR) {
$icon = self::get_image("rating_grin_small", max(0.6, $a));
imagecopy($this->im, $icon, $px - 5, $py - $center_y - 1, 0, 0, 6, 6);
$icon = self::get_image("rating_star_small", max(0.6, $a));
imagecopy($this->im, $icon, $px - 2, $py - $center_y - 3, 0, 0, 10, 10);
} else {
$icon = self::get_image("rating_grin_small", max(0.6, $a));
imagecopy($this->im, $icon, $px - 3, $py - $center_y - 1, 0, 0, 6, 6);
}
}
}
if ($own)
{
# Mark own caches with additional overlay.
@@ -470,7 +497,7 @@ class DefaultTileRenderer implements TileRenderer
{
# Mark found caches with V.
$icon = self::get_image("found", 0.7);
$icon = self::get_image("found", 0.7*$a);
imagecopy($this->im, $icon, $px - ($center_x - $markercenter_x) - 7,
$py - ($center_y - $markercenter_y) - 9, 0, 0, 16, 16);
}

View File

@@ -46,9 +46,10 @@ class TileTree
and y = '".mysql_real_escape_string($y)."'
");
}
/**
* Return MySQL's result set iterator over all caches matched by your query.
* Return MySQL's result set iterator over all caches which are present
* in the given result set AND in the given tile.
*
* Each row is an array of the following format:
* list(cache_id, $pixel_x, $pixel_y, status, type, rating, flags, count).
@@ -57,10 +58,8 @@ class TileTree
* 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)
public static function query_fast($zoom, $x, $y, $set_id)
{
$time_started = microtime(true);
# First, we check if the cache-set for this tile was already computed
# (and if it was, was it empty).
@@ -68,16 +67,9 @@ class TileTree
if ($status === null) # Not yet computed.
{
# Note, that computing the tile does not involve taking any
# filtering parameters.
# search 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.
@@ -87,26 +79,25 @@ class TileTree
}
# 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
otc.cache_id,
cast(otc.z21x >> (21 - $zoom) as signed) - $tile_upper_x as px,
cast(otc.z21y >> (21 - $zoom) as signed) - $tile_leftmost_y as py,
otc.status, otc.type, otc.rating, otc.flags, count(*)
from
okapi_tile_caches otc,
okapi_search_results osr
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).")
and otc.cache_id = osr.cache_id
and osr.set_id = '".mysql_real_escape_string($set_id)."'
group by
z21x >> (3 + (21 - $zoom)),
z21y >> (3 + (21 - $zoom))
@@ -176,9 +167,6 @@ class TileTree
");
}
$status = 2;
OkapiServiceRunner::save_stats_extra("tiletree/compute_tile/rebuild-0",
null, microtime(true) - $time_started);
}
else
{
@@ -193,13 +181,6 @@ class TileTree
{
$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.

View File

@@ -0,0 +1,90 @@
<?php
namespace okapi\services\caches\mark;
use Exception;
use okapi\Okapi;
use okapi\Db;
use okapi\OkapiRequest;
use okapi\ParamMissing;
use okapi\InvalidParam;
use okapi\BadRequest;
use okapi\OkapiInternalRequest;
use okapi\OkapiServiceRunner;
use okapi\OkapiAccessToken;
use okapi\Settings;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 3
);
}
public static function call(OkapiRequest $request)
{
# User is already verified (via OAuth), but we need to verify the
# cache code (check if it exists). We will simply call a geocache method
# on it - this will also throw a proper exception if it doesn't exist.
$cache_code = $request->get_parameter('cache_code');
if ($cache_code == null)
throw new ParamMissing('cache_code');
$geocache = OkapiServiceRunner::call('services/caches/geocache', new OkapiInternalRequest(
$request->consumer, $request->token, array('cache_code' => $cache_code, 'fields' => 'internal_id')));
# watched
if ($tmp = $request->get_parameter('watched'))
{
if (!in_array($tmp, array('true', 'false', 'unchanged')))
throw new InvalidParam('watched', $tmp);
if ($tmp == 'true')
Db::execute("
insert ignore into cache_watches (cache_id, user_id)
values (
'".mysql_real_escape_string($geocache['internal_id'])."',
'".mysql_real_escape_string($request->token->user_id)."'
);
");
elseif ($tmp == 'false')
Db::execute("
delete from cache_watches
where
cache_id = '".mysql_real_escape_string($geocache['internal_id'])."'
and user_id = '".mysql_real_escape_string($request->token->user_id)."';
");
}
# ignored
if ($tmp = $request->get_parameter('ignored'))
{
if (!in_array($tmp, array('true', 'false', 'unchanged')))
throw new InvalidParam('ignored', $tmp);
if ($tmp == 'true')
Db::execute("
insert ignore into cache_ignore (cache_id, user_id)
values (
'".mysql_real_escape_string($geocache['internal_id'])."',
'".mysql_real_escape_string($request->token->user_id)."'
);
");
elseif ($tmp == 'false')
Db::execute("
delete from cache_ignore
where
cache_id = '".mysql_real_escape_string($geocache['internal_id'])."'
and user_id = '".mysql_real_escape_string($request->token->user_id)."'
");
}
$result = array(
'success' => true,
);
return Okapi::formatted_response($request, $result);
}
}

View File

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

View File

@@ -131,6 +131,13 @@
<p>User UUID. If given, the response will only include geocaches not found by
the given user.</p>
</opt>
<opt name='watched_only' default='false'>
<p><b>Notice:</b> This parameter may be used only for requests signed with an Access Token
(you will need to use <b>Level 3</b> Authentication).</p>
<p>Boolean. If set to <b>true</b>, only caches which the user has marked as watched
will be included in the result. This might be used to temporarily mark geocaches
of particular interest (e.g. "all the caches I plan to find today").</p>
</opt>
<opt name='exclude_ignored' default='false'>
<p><b>Notice:</b> This parameter may be used only for requests signed
with an Access Token (you will need to use <b>Level 3</b> Authentication).</p>
@@ -144,6 +151,22 @@
<p>Boolean. If set to <b>true</b>, caches which the user is an owner of will
not be included in the result.</p>
</opt>
<opt name='with_trackables_only' default='false'>
Boolean. If set to <b>true</b>, only caches with at least one trackable
will be included in the result.
</opt>
<opt name='ftf_hunter' default='false'>
Boolean. If set to <b>true</b>, only caches which have not yet been
found <b>by anyone</b> will be included.
</opt>
<opt name='set_and'>
<p>ID of a set previously created with the <b>search/save</b> method.
If given, the results are <a href='http://en.wikipedia.org/wiki/Logical_conjunction'>AND</a>ed
together with this set.</p>
<p>If you want to list the set contents only, please note the default
value of the <b>status</b> parameter! You may want to override it
in order to include unavailable and/or archived geocaches within the set.</p>
</opt>
<opt name='limit' default='100'>
<p>Integer in range 1..500. Maximum number of cache codes returned.</p>
</opt>

View File

@@ -0,0 +1,177 @@
<?php
namespace okapi\services\caches\search\save;
require_once('searching.inc.php');
use okapi\Okapi;
use okapi\OkapiRequest;
use okapi\ParamMissing;
use okapi\InvalidParam;
use okapi\Db;
use okapi\OkapiLock;
use okapi\services\caches\search\SearchAssistant;
class WebService
{
public static function options()
{
return array(
'min_auth_level' => 1
);
}
/**
* Get [set_id, date_created, expires] for the given params_hash
* (or [null, null, null] if not found).
*/
private static function find_param_set($params_hash, $ref_max_age)
{
$tmp = Db::select_row("
select
id as id,
unix_timestamp(date_created) as date_created,
unix_timestamp(expires) as expires
from okapi_search_sets
where
params_hash = '".mysql_real_escape_string($params_hash)."'
and date_add(date_created, interval '".mysql_real_escape_string($ref_max_age)."' second) > now()
order by id desc
limit 1
");
if ($tmp === null)
return array(null, null, null);
return array($tmp['id'], $tmp['date_created'], $tmp['expires']);
}
public static function call(OkapiRequest $request)
{
# "Cache control" parameters.
$tmp = $request->get_parameter('min_store');
if ($tmp === null) $tmp = "300";
$min_store = intval($tmp);
if (("$min_store" !== $tmp) ||($min_store < 0) || ($min_store > 64800))
throw new InvalidParam('min_store', "Has to be in the 0..64800 range.");
$tmp = $request->get_parameter('ref_max_age');
if ($tmp === null) $tmp = "300";
if ($tmp == "nolimit") $tmp = "9999999";
$ref_max_age = intval($tmp);
if (("$ref_max_age" !== $tmp) || ($ref_max_age < 300))
throw new InvalidParam('ref_max_age', "Has to be >=300.");
# Search params.
$search_params = SearchAssistant::get_common_search_params($request);
$tables = array_merge(
array('caches'),
$search_params['extra_tables']
);
$where_conds = array_merge(
array('caches.wp_oc is not null'),
$search_params['where_conds']
);
unset($search_params);
# Generate, or retrieve an existing set, and return the result.
$result = self::get_set($tables, $where_conds, $min_store, $ref_max_age);
return Okapi::formatted_response($request, $result);
}
public static function get_set($tables, $where_conds, $min_store, $ref_max_age)
{
# Compute the "params hash".
$params_hash = md5(serialize(array($tables, $where_conds)));
# Check if there exists an entry for this hash, which also meets the
# given freshness criteria.
list($set_id, $date_created, $expires) = self::find_param_set($params_hash, $ref_max_age);
if ($set_id === null)
{
# To avoid generating the same results by multiple threads at once
# (the "tile" method uses the "save" method, so the problem is
# quite real!), we will acquire a write-lock here.
$lock = OkapiLock::get("search-results-writer");
$lock->acquire();
try
{
# Make sure we were the first to acquire the lock.
list($set_id, $date_created, $expires) = self::find_param_set($params_hash, $ref_max_age);
if ($set_id === null)
{
# We are in the first thread which have acquired the lock.
# We will proceed with result-set creation. Other threads
# will be waiting until we finish.
Db::execute("
insert into okapi_search_sets (params_hash, date_created, expires)
values (
'processing in progress',
now(),
date_add(now(), interval '".mysql_real_escape_string($min_store + 60)."' second)
)
");
$set_id = Db::last_insert_id();
$date_created = time();
$expires = $date_created + $min_store + 60;
Db::execute("
insert into okapi_search_results (set_id, cache_id)
select distinct
'".mysql_real_escape_string($set_id)."',
caches.cache_id
from ".implode(", ", $tables)."
where (".implode(") and (", $where_conds).")
");
# Lock barrier, to make sure the data is visible by other
# sessions. See http://bugs.mysql.com/bug.php?id=36618
Db::execute("lock table okapi_search_results write");
Db::execute("unlock tables");
Db::execute("
update okapi_search_sets
set params_hash = '".mysql_real_escape_string($params_hash)."'
where id = '".mysql_real_escape_string($set_id)."'
");
} else {
# Some other thread acquired the lock before us and it has
# generated the result set. We don't need to do anything.
}
$lock->release();
}
catch (Exception $e)
{
# SQL error? Make sure the lock is released and rethrow.
$lock->release();
throw $e;
}
}
# If we got an old set, we may need to expand its lifetime in order to
# meet user's "min_store" criterium.
if (time() + $min_store > $expires)
{
Db::execute("
update okapi_search_sets
set expires = date_add(now(), interval '".mysql_real_escape_string($min_store + 60)."' second)
where id = '".mysql_real_escape_string($set_id)."'
");
}
return array(
'set_id' => "$set_id",
'generated_at' => date('c', $date_created),
'expires' => date('c', $expires),
);
}
}

View File

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

View File

@@ -232,10 +232,10 @@ class SearchAssistant
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);
$divisors = array(-999, -1.0, 0.1, 1.4, 2.2, 999);
$min = $divisors[$min - 1];
$max = $divisors[$max];
$where_conds[] = "(($X_SCORE between $min and $max) and ($X_VOTES >= 3))".
$where_conds[] = "($X_SCORE >= $min and $X_SCORE < $max and $X_VOTES >= 3)".
($allow_null ? " or ($X_VOTES < 3)" : "");
}
}
@@ -356,6 +356,27 @@ class SearchAssistant
$where_conds[] = "caches.cache_id not in ('".implode("','", array_map('mysql_real_escape_string', $found_cache_ids))."')";
}
#
# watched_only
#
if ($tmp = $request->get_parameter('watched_only'))
{
if ($request->token == null)
throw new InvalidParam('watched_only', "Might be used only for requests signed with an Access Token.");
if (!in_array($tmp, array('true', 'false')))
throw new InvalidParam('watched_only', "'$tmp'");
if ($tmp == 'true')
{
$watched_cache_ids = Db::select_column("
select cache_id
from cache_watches
where user_id = '".mysql_real_escape_string($request->token->user_id)."'
");
$where_conds[] = "cache_id in ('".implode("','", array_map('mysql_real_escape_string', $watched_cache_ids))."')";
}
}
#
# exclude_ignored
#
@@ -405,6 +426,59 @@ class SearchAssistant
$where_conds[] = "caches.name LIKE '".mysql_real_escape_string($tmp)."'";
}
#
# 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')
{
$where_conds[] = "
caches.wp_oc in (
select distinct wp
from gk_item_waypoint
)
";
}
}
#
# ftf_hunter
#
if ($tmp = $request->get_parameter('ftf_hunter'))
{
if (!in_array($tmp, array('true', 'false'), 1))
throw new InvalidParam('not_yet_found_only', "'$tmp'");
if ($tmp == 'true')
{
$where_conds[] = "caches.founds = 0";
}
}
#
# set_and
#
if ($tmp = $request->get_parameter('set_and'))
{
# Check if the set exists.
$exists = Db::select_value("
select 1
from okapi_search_sets
where id = '".mysql_real_escape_string($tmp)."'
");
if (!$exists)
throw new InvalidParam('set_and', "Couldn't find a set by given ID.");
$extra_tables[] = "okapi_search_results osr_and";
$where_conds[] = "osr_and.cache_id = caches.cache_id";
$where_conds[] = "osr_and.set_id = '".mysql_real_escape_string($tmp)."'";
}
#
# limit
#
@@ -464,6 +538,14 @@ class SearchAssistant
}
}
# To avoid join errors, put each of the $where_conds in extra paranthesis.
$tmp = array();
foreach($where_conds as $cond)
$tmp[] = "(".$cond.")";
$where_conds = $tmp;
unset($tmp);
$ret_array = array(
'where_conds' => $where_conds,
'offset' => (int)$offset,
@@ -537,7 +619,10 @@ class SearchAssistant
from cache_logs
where
user_id = '".mysql_real_escape_string($internal_user_id)."'
and type = 1
and type in (
'".mysql_real_escape_string(Okapi::logtypename2id("Found it"))."',
'".mysql_real_escape_string(Okapi::logtypename2id("Attended"))."'
)
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "deleted = 0" : "true")."
");
}

View File

@@ -22,7 +22,10 @@ class WebService
);
}
private static $valid_field_names = array('uuid', 'cache_code', 'date', 'user', 'type', 'comment');
private static $valid_field_names = array(
'uuid', 'cache_code', 'date', 'user', 'type', 'was_recommended', 'comment',
'internal_id',
);
public static function call(OkapiRequest $request)
{
@@ -35,7 +38,7 @@ class WebService
else
$log_uuids = explode("|", $log_uuids);
if (count($log_uuids) > 500)
if ((count($log_uuids) > 500) && (!$request->skip_limits))
throw new InvalidParam('log_uuids', "Maximum allowed number of referenced ".
"log entries is 500. You provided ".count($log_uuids)." UUIDs.");
if (count($log_uuids) != count(array_unique($log_uuids)))
@@ -48,9 +51,18 @@ class WebService
throw new InvalidParam('fields', "'$field' is not a valid field code.");
$rs = Db::query("
select cl.id, c.wp_oc as cache_code, cl.uuid, cl.type, unix_timestamp(cl.date) as date, cl.text,
u.uuid as user_uuid, u.username, u.user_id
from cache_logs cl, user u, caches c
select
cl.id, c.wp_oc as cache_code, cl.uuid, cl.type,
unix_timestamp(cl.date) as date, cl.text,
u.uuid as user_uuid, u.username, u.user_id,
if(cr.user_id is null, 0, 1) as was_recommended
from
(cache_logs cl,
user u,
caches c)
left join cache_rating cr
on cr.user_id = u.user_id
and cr.cache_id = c.cache_id
where
cl.uuid in ('".implode("','", array_map('mysql_real_escape_string', $log_uuids))."')
and ".((Settings::get('OC_BRANCH') == 'oc.pl') ? "cl.deleted = 0" : "true")."
@@ -71,7 +83,9 @@ class WebService
'profile_url' => Settings::get('SITE_URL')."viewprofile.php?userid=".$row['user_id'],
),
'type' => Okapi::logtypeid2name($row['type']),
'comment' => $row['text']
'was_recommended' => $row['was_recommended'] ? true : false,
'comment' => $row['text'],
'internal_id' => $row['id'],
);
}
mysql_free_result($rs);

View File

@@ -22,6 +22,6 @@
and each value will be a dictionary of fields you have selected.</p>
<p>Value of <b>null</b> means that the given UUID haven't been found.
(This behavior is different than in the services/logs/entry method, which
responds with a HTTP 400 error in such case.)</p>
responds with an HTTP 400 error in such case.)</p>
</returns>
</xml>

View File

@@ -27,16 +27,33 @@
</li>
<li>
<p><b>type</b> - string; log type. This could be <b>pretty much everything</b>, but
there are three primary types: "Found it", "Didn't find it" or "Comment".
You may treat every other string as "Comment".</p>
there are five primary types:</p>
<ul>
<li>"Found it" and "Didn't find it" - for all Non-Event Caches,</li>
<li>"Will attend" and "Attended" - for Event Caches,</li>
<li>"Comment" - for all cache types.</li>
</ul>
<p>You may treat every other string as "Comment".</p>
</li>
<li>
<p><b>was_recommended</b> - true, if the author included his recommendation
in this log entry,</p>
</li>
<li><b>comment</b> - HTML string, text entered with the log entry,</li>
<li>
<p><b>internal_id</b> - undocumented, you <u>should not</u> use
this unless you really know you need to. Internal IDs are
<b>not</b> unique across various OKAPI installations.
Try to use UUIDs instead.</p>
</li>
<li><b>comment</b> - HTML string, text entered with the log entry.</li>
</ul>
<p>Note, that some fields can change in time (users can edit/delete
their log entries).</p>
</opt>
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected.</p>
<p>If given log entry does not exist, the method will
respond with a HTTP 400 error.</p>
respond with an HTTP 400 error.</p>
</returns>
</xml>

View File

@@ -58,9 +58,11 @@ class WebService
# Getting the logs themselves. Formatting as an ordered list.
$logs = OkapiServiceRunner::call('services/logs/entries', new OkapiInternalRequest(
$internal_request = new OkapiInternalRequest(
$request->consumer, $request->token, array('log_uuids' => implode("|", $log_uuids),
'fields' => 'uuid|date|user|type|comment')));
'fields' => $fields));
$internal_request->skip_limits = true;
$logs = OkapiServiceRunner::call('services/logs/entries', $internal_request);
$results = array();
foreach ($log_uuids as $log_uuid)
$results[] = $logs[$log_uuid];

View File

@@ -171,6 +171,7 @@ class WebService
and type = '".mysql_real_escape_string(Okapi::logtypename2id($logtype))."'
and date = from_unixtime('".mysql_real_escape_string($when)."')
and text = '".mysql_real_escape_string($PSEUDOENCODED_comment)."'
".((Settings::get('OC_BRANCH') == 'oc.pl') ? "and deleted = 0" : "")."
limit 1
");
if ($duplicate_uuid != null)
@@ -375,7 +376,7 @@ class WebService
# 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';
$filepath = Okapi::get_var_dir().'/images/statpics/statpic'.$user['internal_id'].'.jpg';
if (file_exists($filepath))
unlink($filepath);

View File

@@ -10,7 +10,8 @@
</req>
<req name='logtype'>
<p>Type of an entry. This should be one of: <i>Found it</i>, <i>Didn't find it</i>
or <i>Comment</i>.</p>
or <i>Comment</i>. Currently this method does not support "Will attend" nor "Attended"
log types which are used for Event Caches (we are planning to add this).</p>
</req>
<opt name='comment'>
<p>Text to be submitted with the log entry. Plain-text (no HTML).</p>

View File

@@ -28,9 +28,9 @@
</li>
<li><b>cache_code</b> - code of the geocache,</li>
<li>
<p><b>type</b> - string; log type. This could be <b>pretty much everything</b>, but
there are three primary types: "Found it", "Didn't find it" or "Comment".
You may treat every other string as "Comment".</p>
<p><b>type</b> - string; log type. This could be <b>pretty much
everything</b>, but there are some primary types (see logs/entry
method for more info).</p>
</li>
<li><b>comment</b> - HTML string, text entered with the log entry.</li>
</ul>

View File

@@ -46,12 +46,12 @@
OpenCaching installation.</p>
</opt>
<returns>
<p>Technically, a HTTP 302 Redirect - it will direct user's browser to the OKAPI apps
<p>Technically, an HTTP 302 Redirect - it will direct user's browser to the OKAPI apps
authorization page.</p>
<p>Whether with callback_url or with a manual user entry - you will get
your <b>oauth_verifier</b>, which allows you to continue the 3-legged
authentication dance.</p>
<p>If you used <b>callback_url</b>, you should wait for a HTTP GET request,
<p>If you used <b>callback_url</b>, you should wait for an HTTP GET request,
with one additional GET parameter appended:</p>
<ul>
<li><b>oauth_token</b> - the Request Token that has been just authorized,</li>

View File

@@ -8,7 +8,7 @@
the user, then it has to be exchanged for an Access Token.</p>
</desc>
<req name='oauth_callback'>
<p>An URL which you want a User to be redirected to after a
<p>URL which you want a User to be redirected to after a
successful Request Token Authorization (see "authorize" method).
If the client is unable to receive callbacks, the parameter
must be set to "oob", OKAPI will provide a user with a

View File

@@ -7,7 +7,7 @@
keep your database in sync with ours.</p>
<p>For some applications it might be desireable to have a quick access to the entire
OpenCaching database (instead of quering for specific portions of it). You may use
OpenCaching database (instead of querying for specific portions of it). You may use
OKAPI's <b>replicate</b> module to achive this effect. The <b>changelog</b> method
is the primary replication service which you will use. However, to correctly set up
your first database copy, you will need to use the <b>fulldump</b> method.</p>

View File

@@ -6,7 +6,7 @@
only once in your lifetime.</p>
<p>For some applications it might be desireable to have a quick access to the entire
OpenCaching database (instead of quering for specific portions of it). You may use
OpenCaching database (instead of querying for specific portions of it). You may use
OKAPI's <b>replicate</b> module to achive this effect. The <b>changelog</b> method
is the primary replication service which you will use. However, to correctly set up
your first database copy, you will need to use the <b>fulldump</b> method.</p>

View File

@@ -20,8 +20,7 @@ class ReplicateCommon
{
private static $chunk_size = 200;
private static $logged_cache_fields = 'code|names|location|type|status|url|owner|founds|notfounds|size|size2|oxsize|difficulty|terrain|rating|rating_votes|recommendations|req_passwd|descriptions|hints|images|trackables_count|trackables|alt_wpts|last_found|last_modified|date_created|date_hidden';
private static $logged_log_entry_fields = 'uuid|cache_code|date|user|type|comment';
private static $logged_log_entry_fields = 'uuid|cache_code|date|user|type|was_recommended|comment';
/** Return current (greatest) changelog revision number. */
public static function get_revision()

View File

@@ -17,6 +17,6 @@
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected.</p>
<p>If given user does not exist, the method will respond with a HTTP 400 error.</p>
<p>If given user does not exist, the method will respond with an HTTP 400 error.</p>
</returns>
</xml>

View File

@@ -19,6 +19,6 @@
and each value will be a dictionary of fields you have selected.</p>
<p>Value of <b>null</b> means that the given user haven't been found.
(This behavior is different than in the services/users/by_username method, which
responds with a HTTP 400 error in such case.)</p>
responds with an HTTP 400 error in such case.)</p>
</returns>
</xml>

View File

@@ -38,6 +38,6 @@
<common-format-params/>
<returns>
<p>A dictionary of fields you have selected.</p>
<p>If given user does not exist, the method will respond with a HTTP 400 error.</p>
<p>If given user does not exist, the method will respond with an HTTP 400 error.</p>
</returns>
</xml>

View File

@@ -19,6 +19,6 @@
and each value will be a dictionary of fields you have selected.</p>
<p>Value of <b>null</b> means that the given user haven't been found.
(This behavior is different than in the services/users/user method, which
responds with a HTTP 400 error in such case.)</p>
responds with an HTTP 400 error in such case.)</p>
</returns>
</xml>

View File

@@ -143,8 +143,14 @@ final class Settings
private static function load_settings()
{
try {
# This is an external code and it MAY generate E_NOTICEs.
# We have to temporarilly disable our default error handler.
OkapiErrorHandler::disable();
require_once($GLOBALS['rootpath']."okapi_settings.php");
$ref = get_okapi_settings();
OkapiErrorHandler::reenable();
} catch (Exception $e) {
throw new Exception("Could not import <rootpath>/okapi_settings.php:\n".$e->getMessage());
}

View File

@@ -1,185 +0,0 @@
.dp-highlighter
{
font-family: "Consolas", "Courier New", Courier, mono, serif;
font-size: 12px;
background-color: #E7E5DC;
width: 99%;
overflow: auto;
margin: 18px 0 18px 0 !important;
padding-top: 1px; /* adds a little border on top when controls are hidden */
}
/* clear styles */
.dp-highlighter ol,
.dp-highlighter ol li,
.dp-highlighter ol li span
{
margin: 0;
padding: 0;
border: none;
}
.dp-highlighter a,
.dp-highlighter a:hover
{
background: none;
border: none;
padding: 0;
margin: 0;
}
.dp-highlighter .bar
{
padding-left: 45px;
}
.dp-highlighter.collapsed .bar,
.dp-highlighter.nogutter .bar
{
padding-left: 0px;
}
.dp-highlighter ol
{
list-style: decimal; /* for ie */
background-color: #fff;
margin: 0px 0px 1px 45px !important; /* 1px bottom margin seems to fix occasional Firefox scrolling */
padding: 0px;
color: #5C5C5C;
}
.dp-highlighter.nogutter ol,
.dp-highlighter.nogutter ol li
{
list-style: none !important;
margin-left: 0px !important;
}
.dp-highlighter ol li,
.dp-highlighter .columns div
{
list-style: decimal-leading-zero; /* better look for others, override cascade from OL */
list-style-position: outside !important;
border-left: 3px solid #6CE26C;
background-color: #F8F8F8;
color: #5C5C5C;
padding: 0 3px 0 10px !important;
margin: 0 !important;
line-height: 14px;
}
.dp-highlighter.nogutter ol li,
.dp-highlighter.nogutter .columns div
{
border: 0;
}
.dp-highlighter .columns
{
background-color: #F8F8F8;
color: gray;
overflow: hidden;
width: 100%;
}
.dp-highlighter .columns div
{
padding-bottom: 5px;
}
.dp-highlighter ol li.alt
{
/*background-color: #FFF;*/
color: inherit;
}
.dp-highlighter ol li span
{
color: black;
background-color: inherit;
}
/* Adjust some properties when collapsed */
.dp-highlighter.collapsed ol
{
margin: 0px;
}
.dp-highlighter.collapsed ol li
{
display: none;
}
/* Additional modifications when in print-view */
.dp-highlighter.printing
{
border: none;
}
.dp-highlighter.printing .tools
{
display: none !important;
}
.dp-highlighter.printing li
{
display: list-item !important;
}
/* Styles for the tools */
.dp-highlighter .tools
{
padding: 3px 8px 3px 10px;
font: 9px Verdana, Geneva, Arial, Helvetica, sans-serif;
color: silver;
background-color: #f8f8f8;
padding-bottom: 10px;
border-left: 3px solid #6CE26C;
}
.dp-highlighter.nogutter .tools
{
border-left: 0;
}
.dp-highlighter.collapsed .tools
{
border-bottom: 0;
}
.dp-highlighter .tools a
{
font-size: 9px;
color: #a0a0a0;
background-color: inherit;
text-decoration: none;
margin-right: 10px;
}
.dp-highlighter .tools a:hover
{
color: red;
background-color: inherit;
text-decoration: underline;
}
/* About dialog styles */
.dp-about { background-color: #fff; color: #333; margin: 0px; padding: 0px; }
.dp-about table { width: 100%; height: 100%; font-size: 11px; font-family: Tahoma, Verdana, Arial, sans-serif !important; }
.dp-about td { padding: 10px; vertical-align: top; }
.dp-about .copy { border-bottom: 1px solid #ACA899; height: 95%; }
.dp-about .title { color: red; background-color: inherit; font-weight: bold; }
.dp-about .para { margin: 0 0 4px 0; }
.dp-about .footer { background-color: #ECEADB; color: #333; border-top: 1px solid #fff; text-align: right; }
.dp-about .close { font-size: 11px; font-family: Tahoma, Verdana, Arial, sans-serif !important; background-color: #ECEADB; color: #333; width: 60px; height: 22px; }
/* Language specific styles */
.dp-highlighter .comment, .dp-highlighter .comments { color: #008200; background-color: inherit; }
.dp-highlighter .string { color: blue; background-color: inherit; }
.dp-highlighter .keyword { color: #069; font-weight: bold; background-color: inherit; }
.dp-highlighter .preprocessor { color: gray; background-color: inherit; }

View File

@@ -1,10 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.CSharp=function()
{var keywords='abstract as base bool break byte case catch char checked class const '+'continue decimal default delegate do double else enum event explicit '+'extern false finally fixed float for foreach get goto if implicit in int '+'interface internal is lock long namespace new null object operator out '+'override params private protected public readonly ref return sbyte sealed set '+'short sizeof stackalloc static string struct switch this throw true try '+'typeof uint ulong unchecked unsafe ushort using virtual void while';this.regexList=[{regex:dp.sh.RegexLib.SingleLineCComments,css:'comment'},{regex:dp.sh.RegexLib.MultiLineCComments,css:'comment'},{regex:dp.sh.RegexLib.DoubleQuotedString,css:'string'},{regex:dp.sh.RegexLib.SingleQuotedString,css:'string'},{regex:new RegExp('^\\s*#.*','gm'),css:'preprocessor'},{regex:new RegExp(this.GetKeywords(keywords),'gm'),css:'keyword'}];this.CssClass='dp-c';this.Style='.dp-c .vars { color: #d00; }';}
dp.sh.Brushes.CSharp.prototype=new dp.sh.Highlighter();dp.sh.Brushes.CSharp.Aliases=['c#','c-sharp','csharp'];

View File

@@ -1,10 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.Cpp=function()
{var datatypes='ATOM BOOL BOOLEAN BYTE CHAR COLORREF DWORD DWORDLONG DWORD_PTR '+'DWORD32 DWORD64 FLOAT HACCEL HALF_PTR HANDLE HBITMAP HBRUSH '+'HCOLORSPACE HCONV HCONVLIST HCURSOR HDC HDDEDATA HDESK HDROP HDWP '+'HENHMETAFILE HFILE HFONT HGDIOBJ HGLOBAL HHOOK HICON HINSTANCE HKEY '+'HKL HLOCAL HMENU HMETAFILE HMODULE HMONITOR HPALETTE HPEN HRESULT '+'HRGN HRSRC HSZ HWINSTA HWND INT INT_PTR INT32 INT64 LANGID LCID LCTYPE '+'LGRPID LONG LONGLONG LONG_PTR LONG32 LONG64 LPARAM LPBOOL LPBYTE LPCOLORREF '+'LPCSTR LPCTSTR LPCVOID LPCWSTR LPDWORD LPHANDLE LPINT LPLONG LPSTR LPTSTR '+'LPVOID LPWORD LPWSTR LRESULT PBOOL PBOOLEAN PBYTE PCHAR PCSTR PCTSTR PCWSTR '+'PDWORDLONG PDWORD_PTR PDWORD32 PDWORD64 PFLOAT PHALF_PTR PHANDLE PHKEY PINT '+'PINT_PTR PINT32 PINT64 PLCID PLONG PLONGLONG PLONG_PTR PLONG32 PLONG64 POINTER_32 '+'POINTER_64 PSHORT PSIZE_T PSSIZE_T PSTR PTBYTE PTCHAR PTSTR PUCHAR PUHALF_PTR '+'PUINT PUINT_PTR PUINT32 PUINT64 PULONG PULONGLONG PULONG_PTR PULONG32 PULONG64 '+'PUSHORT PVOID PWCHAR PWORD PWSTR SC_HANDLE SC_LOCK SERVICE_STATUS_HANDLE SHORT '+'SIZE_T SSIZE_T TBYTE TCHAR UCHAR UHALF_PTR UINT UINT_PTR UINT32 UINT64 ULONG '+'ULONGLONG ULONG_PTR ULONG32 ULONG64 USHORT USN VOID WCHAR WORD WPARAM WPARAM WPARAM '+'char bool short int __int32 __int64 __int8 __int16 long float double __wchar_t '+'clock_t _complex _dev_t _diskfree_t div_t ldiv_t _exception _EXCEPTION_POINTERS '+'FILE _finddata_t _finddatai64_t _wfinddata_t _wfinddatai64_t __finddata64_t '+'__wfinddata64_t _FPIEEE_RECORD fpos_t _HEAPINFO _HFILE lconv intptr_t '+'jmp_buf mbstate_t _off_t _onexit_t _PNH ptrdiff_t _purecall_handler '+'sig_atomic_t size_t _stat __stat64 _stati64 terminate_function '+'time_t __time64_t _timeb __timeb64 tm uintptr_t _utimbuf '+'va_list wchar_t wctrans_t wctype_t wint_t signed';var keywords='break case catch class const __finally __exception __try '+'const_cast continue private public protected __declspec '+'default delete deprecated dllexport dllimport do dynamic_cast '+'else enum explicit extern if for friend goto inline '+'mutable naked namespace new noinline noreturn nothrow '+'register reinterpret_cast return selectany '+'sizeof static static_cast struct switch template this '+'thread throw true false try typedef typeid typename union '+'using uuid virtual void volatile whcar_t while';this.regexList=[{regex:dp.sh.RegexLib.SingleLineCComments,css:'comment'},{regex:dp.sh.RegexLib.MultiLineCComments,css:'comment'},{regex:dp.sh.RegexLib.DoubleQuotedString,css:'string'},{regex:dp.sh.RegexLib.SingleQuotedString,css:'string'},{regex:new RegExp('^ *#.*','gm'),css:'preprocessor'},{regex:new RegExp(this.GetKeywords(datatypes),'gm'),css:'datatypes'},{regex:new RegExp(this.GetKeywords(keywords),'gm'),css:'keyword'}];this.CssClass='dp-cpp';this.Style='.dp-cpp .datatypes { color: #2E8B57; font-weight: bold; }';}
dp.sh.Brushes.Cpp.prototype=new dp.sh.Highlighter();dp.sh.Brushes.Cpp.Aliases=['cpp','c','c++'];

View File

@@ -1,14 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.CSS=function()
{var keywords='ascent azimuth background-attachment background-color background-image background-position '+'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top '+'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color '+'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width '+'border-bottom-width border-left-width border-width border cap-height caption-side centerline clear clip color '+'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display '+'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font '+'height letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top '+'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans '+'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page '+'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position '+'quotes richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress '+'table-layout text-align text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em '+'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index';var values='above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero default digits disc dotted double '+'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow';var fonts='[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif';this.regexList=[{regex:dp.sh.RegexLib.MultiLineCComments,css:'comment'},{regex:dp.sh.RegexLib.DoubleQuotedString,css:'string'},{regex:dp.sh.RegexLib.SingleQuotedString,css:'string'},{regex:new RegExp('\\#[a-zA-Z0-9]{3,6}','g'),css:'value'},{regex:new RegExp('(-?\\d+)(\.\\d+)?(px|em|pt|\:|\%|)','g'),css:'value'},{regex:new RegExp('!important','g'),css:'important'},{regex:new RegExp(this.GetKeywordsCSS(keywords),'gm'),css:'keyword'},{regex:new RegExp(this.GetValuesCSS(values),'g'),css:'value'},{regex:new RegExp(this.GetValuesCSS(fonts),'g'),css:'value'}];this.CssClass='dp-css';this.Style='.dp-css .value { color: black; }'+'.dp-css .important { color: red; }';}
dp.sh.Highlighter.prototype.GetKeywordsCSS=function(str)
{return'\\b([a-z_]|)'+str.replace(/ /g,'(?=:)\\b|\\b([a-z_\\*]|\\*|)')+'(?=:)\\b';}
dp.sh.Highlighter.prototype.GetValuesCSS=function(str)
{return'\\b'+str.replace(/ /g,'(?!-)(?!:)\\b|\\b()')+'\:\\b';}
dp.sh.Brushes.CSS.prototype=new dp.sh.Highlighter();dp.sh.Brushes.CSS.Aliases=['css'];

View File

@@ -1,10 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.Delphi=function()
{var keywords='abs addr and ansichar ansistring array as asm begin boolean byte cardinal '+'case char class comp const constructor currency destructor div do double '+'downto else end except exports extended false file finalization finally '+'for function goto if implementation in inherited int64 initialization '+'integer interface is label library longint longword mod nil not object '+'of on or packed pansichar pansistring pchar pcurrency pdatetime pextended '+'pint64 pointer private procedure program property pshortstring pstring '+'pvariant pwidechar pwidestring protected public published raise real real48 '+'record repeat set shl shortint shortstring shr single smallint string then '+'threadvar to true try type unit until uses val var varirnt while widechar '+'widestring with word write writeln xor';this.regexList=[{regex:new RegExp('\\(\\*[\\s\\S]*?\\*\\)','gm'),css:'comment'},{regex:new RegExp('{(?!\\$)[\\s\\S]*?}','gm'),css:'comment'},{regex:dp.sh.RegexLib.SingleLineCComments,css:'comment'},{regex:dp.sh.RegexLib.SingleQuotedString,css:'string'},{regex:new RegExp('\\{\\$[a-zA-Z]+ .+\\}','g'),css:'directive'},{regex:new RegExp('\\b[\\d\\.]+\\b','g'),css:'number'},{regex:new RegExp('\\$[a-zA-Z0-9]+\\b','g'),css:'number'},{regex:new RegExp(this.GetKeywords(keywords),'gm'),css:'keyword'}];this.CssClass='dp-delphi';this.Style='.dp-delphi .number { color: blue; }'+'.dp-delphi .directive { color: #008284; }'+'.dp-delphi .vars { color: #000; }';}
dp.sh.Brushes.Delphi.prototype=new dp.sh.Highlighter();dp.sh.Brushes.Delphi.Aliases=['delphi','pascal'];

View File

@@ -1,10 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.JScript=function()
{var keywords='abstract boolean break byte case catch char class const continue debugger '+'default delete do double else enum export extends false final finally float '+'for function goto if implements import in instanceof int interface long native '+'new null package private protected public return short static super switch '+'synchronized this throw throws transient true try typeof var void volatile while with';this.regexList=[{regex:dp.sh.RegexLib.SingleLineCComments,css:'comment'},{regex:dp.sh.RegexLib.MultiLineCComments,css:'comment'},{regex:dp.sh.RegexLib.DoubleQuotedString,css:'string'},{regex:dp.sh.RegexLib.SingleQuotedString,css:'string'},{regex:new RegExp('^\\s*#.*','gm'),css:'preprocessor'},{regex:new RegExp(this.GetKeywords(keywords),'gm'),css:'keyword'}];this.CssClass='dp-c';}
dp.sh.Brushes.JScript.prototype=new dp.sh.Highlighter();dp.sh.Brushes.JScript.Aliases=['js','jscript','javascript'];

View File

@@ -1,10 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.Java=function()
{var keywords='abstract assert boolean break byte case catch char class const '+'continue default do double else enum extends '+'false final finally float for goto if implements import '+'instanceof int interface long native new null '+'package private protected public return '+'short static strictfp super switch synchronized this throw throws true '+'transient try void volatile while';this.regexList=[{regex:dp.sh.RegexLib.SingleLineCComments,css:'comment'},{regex:dp.sh.RegexLib.MultiLineCComments,css:'comment'},{regex:dp.sh.RegexLib.DoubleQuotedString,css:'string'},{regex:dp.sh.RegexLib.SingleQuotedString,css:'string'},{regex:new RegExp('\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b','gi'),css:'number'},{regex:new RegExp('(?!\\@interface\\b)\\@[\\$\\w]+\\b','g'),css:'annotation'},{regex:new RegExp('\\@interface\\b','g'),css:'keyword'},{regex:new RegExp(this.GetKeywords(keywords),'gm'),css:'keyword'}];this.CssClass='dp-j';this.Style='.dp-j .annotation { color: #646464; }'+'.dp-j .number { color: #C00000; }';}
dp.sh.Brushes.Java.prototype=new dp.sh.Highlighter();dp.sh.Brushes.Java.Aliases=['java'];

View File

@@ -1,10 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.Php=function()
{var funcs='abs acos acosh addcslashes addslashes '+'array_change_key_case array_chunk array_combine array_count_values array_diff '+'array_diff_assoc array_diff_key array_diff_uassoc array_diff_ukey array_fill '+'array_filter array_flip array_intersect array_intersect_assoc array_intersect_key '+'array_intersect_uassoc array_intersect_ukey array_key_exists array_keys array_map '+'array_merge array_merge_recursive array_multisort array_pad array_pop array_product '+'array_push array_rand array_reduce array_reverse array_search array_shift '+'array_slice array_splice array_sum array_udiff array_udiff_assoc '+'array_udiff_uassoc array_uintersect array_uintersect_assoc '+'array_uintersect_uassoc array_unique array_unshift array_values array_walk '+'array_walk_recursive atan atan2 atanh base64_decode base64_encode base_convert '+'basename bcadd bccomp bcdiv bcmod bcmul bindec bindtextdomain bzclose bzcompress '+'bzdecompress bzerrno bzerror bzerrstr bzflush bzopen bzread bzwrite ceil chdir '+'checkdate checkdnsrr chgrp chmod chop chown chr chroot chunk_split class_exists '+'closedir closelog copy cos cosh count count_chars date decbin dechex decoct '+'deg2rad delete ebcdic2ascii echo empty end ereg ereg_replace eregi eregi_replace error_log '+'error_reporting escapeshellarg escapeshellcmd eval exec exit exp explode extension_loaded '+'feof fflush fgetc fgetcsv fgets fgetss file_exists file_get_contents file_put_contents '+'fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype '+'floatval flock floor flush fmod fnmatch fopen fpassthru fprintf fputcsv fputs fread fscanf '+'fseek fsockopen fstat ftell ftok getallheaders getcwd getdate getenv gethostbyaddr gethostbyname '+'gethostbynamel getimagesize getlastmod getmxrr getmygid getmyinode getmypid getmyuid getopt '+'getprotobyname getprotobynumber getrandmax getrusage getservbyname getservbyport gettext '+'gettimeofday gettype glob gmdate gmmktime ini_alter ini_get ini_get_all ini_restore ini_set '+'interface_exists intval ip2long is_a is_array is_bool is_callable is_dir is_double '+'is_executable is_file is_finite is_float is_infinite is_int is_integer is_link is_long '+'is_nan is_null is_numeric is_object is_readable is_real is_resource is_scalar is_soap_fault '+'is_string is_subclass_of is_uploaded_file is_writable is_writeable mkdir mktime nl2br '+'parse_ini_file parse_str parse_url passthru pathinfo readlink realpath rewind rewinddir rmdir '+'round str_ireplace str_pad str_repeat str_replace str_rot13 str_shuffle str_split '+'str_word_count strcasecmp strchr strcmp strcoll strcspn strftime strip_tags stripcslashes '+'stripos stripslashes stristr strlen strnatcasecmp strnatcmp strncasecmp strncmp strpbrk '+'strpos strptime strrchr strrev strripos strrpos strspn strstr strtok strtolower strtotime '+'strtoupper strtr strval substr substr_compare';var keywords='and or xor __FILE__ __LINE__ array as break case '+'cfunction class const continue declare default die do else '+'elseif empty enddeclare endfor endforeach endif endswitch endwhile '+'extends for foreach function include include_once global if '+'new old_function return static switch use require require_once '+'var while __FUNCTION__ __CLASS__ '+'__METHOD__ abstract interface public implements extends private protected throw';this.regexList=[{regex:dp.sh.RegexLib.SingleLineCComments,css:'comment'},{regex:dp.sh.RegexLib.MultiLineCComments,css:'comment'},{regex:dp.sh.RegexLib.DoubleQuotedString,css:'string'},{regex:dp.sh.RegexLib.SingleQuotedString,css:'string'},{regex:new RegExp('\\$\\w+','g'),css:'vars'},{regex:new RegExp(this.GetKeywords(funcs),'gmi'),css:'func'},{regex:new RegExp(this.GetKeywords(keywords),'gm'),css:'keyword'}];this.CssClass='dp-c';}
dp.sh.Brushes.Php.prototype=new dp.sh.Highlighter();dp.sh.Brushes.Php.Aliases=['php'];

View File

@@ -1,11 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.Python=function()
{var keywords='and assert break class continue def del elif else '+'except exec finally for from global if import in is '+'lambda not or pass print raise return try yield while';var special='None True False self cls class_'
this.regexList=[{regex:dp.sh.RegexLib.SingleLinePerlComments,css:'comment'},{regex:new RegExp("^\\s*@\\w+",'gm'),css:'decorator'},{regex:new RegExp("(['\"]{3})([^\\1])*?\\1",'gm'),css:'comment'},{regex:new RegExp('"(?!")(?:\\.|\\\\\\"|[^\\""\\n\\r])*"','gm'),css:'string'},{regex:new RegExp("'(?!')*(?:\\.|(\\\\\\')|[^\\''\\n\\r])*'",'gm'),css:'string'},{regex:new RegExp("\\b\\d+\\.?\\w*",'g'),css:'number'},{regex:new RegExp(this.GetKeywords(keywords),'gm'),css:'keyword'},{regex:new RegExp(this.GetKeywords(special),'gm'),css:'special'}];this.CssClass='dp-py';this.Style='.dp-py .builtins { color: #ff1493; }'+'.dp-py .magicmethods { color: #808080; }'+'.dp-py .exceptions { color: brown; }'+'.dp-py .types { color: brown; font-style: italic; }'+'.dp-py .commonlibs { color: #8A2BE2; font-style: italic; }';}
dp.sh.Brushes.Python.prototype=new dp.sh.Highlighter();dp.sh.Brushes.Python.Aliases=['py','python'];

View File

@@ -1,11 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.Ruby=function()
{var keywords='alias and BEGIN begin break case class def define_method defined do each else elsif '+'END end ensure false for if in module new next nil not or raise redo rescue retry return '+'self super then throw true undef unless until when while yield';var builtins='Array Bignum Binding Class Continuation Dir Exception FalseClass File::Stat File Fixnum Fload '+'Hash Integer IO MatchData Method Module NilClass Numeric Object Proc Range Regexp String Struct::TMS Symbol '+'ThreadGroup Thread Time TrueClass'
this.regexList=[{regex:dp.sh.RegexLib.SingleLinePerlComments,css:'comment'},{regex:dp.sh.RegexLib.DoubleQuotedString,css:'string'},{regex:dp.sh.RegexLib.SingleQuotedString,css:'string'},{regex:new RegExp(':[a-z][A-Za-z0-9_]*','g'),css:'symbol'},{regex:new RegExp('(\\$|@@|@)\\w+','g'),css:'variable'},{regex:new RegExp(this.GetKeywords(keywords),'gm'),css:'keyword'},{regex:new RegExp(this.GetKeywords(builtins),'gm'),css:'builtin'}];this.CssClass='dp-rb';this.Style='.dp-rb .symbol { color: #a70; }'+'.dp-rb .variable { color: #a70; font-weight: bold; }';}
dp.sh.Brushes.Ruby.prototype=new dp.sh.Highlighter();dp.sh.Brushes.Ruby.Aliases=['ruby','rails','ror'];

View File

@@ -1,10 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.Sql=function()
{var funcs='abs avg case cast coalesce convert count current_timestamp '+'current_user day isnull left lower month nullif replace right '+'session_user space substring sum system_user upper user year';var keywords='absolute action add after alter as asc at authorization begin bigint '+'binary bit by cascade char character check checkpoint close collate '+'column commit committed connect connection constraint contains continue '+'create cube current current_date current_time cursor database date '+'deallocate dec decimal declare default delete desc distinct double drop '+'dynamic else end end-exec escape except exec execute false fetch first '+'float for force foreign forward free from full function global goto grant '+'group grouping having hour ignore index inner insensitive insert instead '+'int integer intersect into is isolation key last level load local max min '+'minute modify move name national nchar next no numeric of off on only '+'open option order out output partial password precision prepare primary '+'prior privileges procedure public read real references relative repeatable '+'restrict return returns revoke rollback rollup rows rule schema scroll '+'second section select sequence serializable set size smallint static '+'statistics table temp temporary then time timestamp to top transaction '+'translation trigger true truncate uncommitted union unique update values '+'varchar varying view when where with work';var operators='all and any between cross in join like not null or outer some';this.regexList=[{regex:new RegExp('--(.*)$','gm'),css:'comment'},{regex:dp.sh.RegexLib.DoubleQuotedString,css:'string'},{regex:dp.sh.RegexLib.SingleQuotedString,css:'string'},{regex:new RegExp(this.GetKeywords(funcs),'gmi'),css:'func'},{regex:new RegExp(this.GetKeywords(operators),'gmi'),css:'op'},{regex:new RegExp(this.GetKeywords(keywords),'gmi'),css:'keyword'}];this.CssClass='dp-sql';this.Style='.dp-sql .func { color: #ff1493; }'+'.dp-sql .op { color: #808080; }';}
dp.sh.Brushes.Sql.prototype=new dp.sh.Highlighter();dp.sh.Brushes.Sql.Aliases=['sql'];

View File

@@ -1,10 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.Vb=function()
{var keywords='AddHandler AddressOf AndAlso Alias And Ansi As Assembly Auto '+'Boolean ByRef Byte ByVal Call Case Catch CBool CByte CChar CDate '+'CDec CDbl Char CInt Class CLng CObj Const CShort CSng CStr CType '+'Date Decimal Declare Default Delegate Dim DirectCast Do Double Each '+'Else ElseIf End Enum Erase Error Event Exit False Finally For Friend '+'Function Get GetType GoSub GoTo Handles If Implements Imports In '+'Inherits Integer Interface Is Let Lib Like Long Loop Me Mod Module '+'MustInherit MustOverride MyBase MyClass Namespace New Next Not Nothing '+'NotInheritable NotOverridable Object On Option Optional Or OrElse '+'Overloads Overridable Overrides ParamArray Preserve Private Property '+'Protected Public RaiseEvent ReadOnly ReDim REM RemoveHandler Resume '+'Return Select Set Shadows Shared Short Single Static Step Stop String '+'Structure Sub SyncLock Then Throw To True Try TypeOf Unicode Until '+'Variant When While With WithEvents WriteOnly Xor';this.regexList=[{regex:new RegExp('\'.*$','gm'),css:'comment'},{regex:dp.sh.RegexLib.DoubleQuotedString,css:'string'},{regex:new RegExp('^\\s*#.*','gm'),css:'preprocessor'},{regex:new RegExp(this.GetKeywords(keywords),'gm'),css:'keyword'}];this.CssClass='dp-vb';}
dp.sh.Brushes.Vb.prototype=new dp.sh.Highlighter();dp.sh.Brushes.Vb.Aliases=['vb','vb.net'];

View File

@@ -1,19 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
dp.sh.Brushes.Xml=function()
{this.CssClass='dp-xml';this.Style='.dp-xml .cdata { color: #ff1493; }'+'.dp-xml .tag, .dp-xml .tag-name { color: #069; font-weight: bold; }'+'.dp-xml .attribute { color: red; }'+'.dp-xml .attribute-value { color: blue; }';}
dp.sh.Brushes.Xml.prototype=new dp.sh.Highlighter();dp.sh.Brushes.Xml.Aliases=['xml','xhtml','xslt','html','xhtml'];dp.sh.Brushes.Xml.prototype.ProcessRegexList=function()
{function push(array,value)
{array[array.length]=value;}
var index=0;var match=null;var regex=null;this.GetMatches(new RegExp('(\&lt;|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\&gt;|>)','gm'),'cdata');this.GetMatches(new RegExp('(\&lt;|<)!--\\s*.*?\\s*--(\&gt;|>)','gm'),'comments');regex=new RegExp('([:\\w-\.]+)\\s*=\\s*(".*?"|\'.*?\'|\\w+)*|(\\w+)','gm');while((match=regex.exec(this.code))!=null)
{if(match[1]==null)
{continue;}
push(this.matches,new dp.sh.Match(match[1],match.index,'attribute'));if(match[2]!=undefined)
{push(this.matches,new dp.sh.Match(match[2],match.index+match[0].indexOf(match[2]),'attribute-value'));}}
this.GetMatches(new RegExp('(\&lt;|<)/*\\?*(?!\\!)|/*\\?*(\&gt;|>)','gm'),'tag');regex=new RegExp('(?:\&lt;|<)/*\\?*\\s*([:\\w-\.]+)','gm');while((match=regex.exec(this.code))!=null)
{push(this.matches,new dp.sh.Match(match[1],match.index+match[0].indexOf(match[1]),'tag-name'));}}

View File

@@ -1,161 +0,0 @@
/*
* JsMin
* Javascript Compressor
* http://www.crockford.com/
* http://www.smallsharptools.com/
*/
var dp={sh:{Toolbar:{},Utils:{},RegexLib:{},Brushes:{},Strings:{AboutDialog:'<html><head><title>About...</title></head><body class="dp-about"><table cellspacing="0"><tr><td class="copy"><p class="title">dp.SyntaxHighlighter</div><div class="para">Version: {V}</p><p><a href="http://www.dreamprojections.com/syntaxhighlighter/?ref=about" target="_blank">http://www.dreamprojections.com/syntaxhighlighter</a></p>&copy;2004-2007 Alex Gorbatchev.</td></tr><tr><td class="footer"><input type="button" class="close" value="OK" onClick="window.close()"/></td></tr></table></body></html>'},ClipboardSwf:null,Version:'1.5.1'}};dp.SyntaxHighlighter=dp.sh;dp.sh.Toolbar.Commands={ExpandSource:{label:'+ expand source',check:function(highlighter){return highlighter.collapse;},func:function(sender,highlighter)
{sender.parentNode.removeChild(sender);highlighter.div.className=highlighter.div.className.replace('collapsed','');}},ViewSource:{label:'view plain',func:function(sender,highlighter)
{var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/</g,'&lt;');var wnd=window.open('','_blank','width=750, height=400, location=0, resizable=1, menubar=0, scrollbars=0');wnd.document.write('<textarea style="width:99%;height:99%">'+code+'</textarea>');wnd.document.close();}},CopyToClipboard:{label:'copy to clipboard',check:function(){return window.clipboardData!=null||dp.sh.ClipboardSwf!=null;},func:function(sender,highlighter)
{var code=dp.sh.Utils.FixForBlogger(highlighter.originalCode).replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');if(window.clipboardData)
{window.clipboardData.setData('text',code);}
else if(dp.sh.ClipboardSwf!=null)
{var flashcopier=highlighter.flashCopier;if(flashcopier==null)
{flashcopier=document.createElement('div');highlighter.flashCopier=flashcopier;highlighter.div.appendChild(flashcopier);}
flashcopier.innerHTML='<embed src="'+dp.sh.ClipboardSwf+'" FlashVars="clipboard='+encodeURIComponent(code)+'" width="0" height="0" type="application/x-shockwave-flash"></embed>';}
alert('The code is in your clipboard now');}},PrintSource:{label:'print',func:function(sender,highlighter)
{var iframe=document.createElement('IFRAME');var doc=null;iframe.style.cssText='position:absolute;width:0px;height:0px;left:-500px;top:-500px;';document.body.appendChild(iframe);doc=iframe.contentWindow.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write('<div class="'+highlighter.div.className.replace('collapsed','')+' printing">'+highlighter.div.innerHTML+'</div>');doc.close();iframe.contentWindow.focus();iframe.contentWindow.print();alert('Printing...');document.body.removeChild(iframe);}},About:{label:'?',func:function(highlighter)
{var wnd=window.open('','_blank','dialog,width=300,height=150,scrollbars=0');var doc=wnd.document;dp.sh.Utils.CopyStyles(doc,window.document);doc.write(dp.sh.Strings.AboutDialog.replace('{V}',dp.sh.Version));doc.close();wnd.focus();}}};dp.sh.Toolbar.Create=function(highlighter)
{var div=document.createElement('DIV');div.className='tools';for(var name in dp.sh.Toolbar.Commands)
{var cmd=dp.sh.Toolbar.Commands[name];if(cmd.check!=null&&!cmd.check(highlighter))
continue;div.innerHTML+='<a href="#" onclick="dp.sh.Toolbar.Command(\''+name+'\',this);return false;">'+cmd.label+'</a>';}
return div;}
dp.sh.Toolbar.Command=function(name,sender)
{var n=sender;while(n!=null&&n.className.indexOf('dp-highlighter')==-1)
n=n.parentNode;if(n!=null)
dp.sh.Toolbar.Commands[name].func(sender,n.highlighter);}
dp.sh.Utils.CopyStyles=function(destDoc,sourceDoc)
{var links=sourceDoc.getElementsByTagName('link');for(var i=0;i<links.length;i++)
if(links[i].rel.toLowerCase()=='stylesheet')
destDoc.write('<link type="text/css" rel="stylesheet" href="'+links[i].href+'"></link>');}
dp.sh.Utils.FixForBlogger=function(str)
{return(dp.sh.isBloggerMode==true)?str.replace(/<br\s*\/?>|&lt;br\s*\/?&gt;/gi,'\n'):str;}
dp.sh.RegexLib={MultiLineCComments:new RegExp('/\\*[\\s\\S]*?\\*/','gm'),SingleLineCComments:new RegExp('//.*$','gm'),SingleLinePerlComments:new RegExp('#.*$','gm'),DoubleQuotedString:new RegExp('"(?:\\.|(\\\\\\")|[^\\""\\n])*"','g'),SingleQuotedString:new RegExp("'(?:\\.|(\\\\\\')|[^\\''\\n])*'",'g')};dp.sh.Match=function(value,index,css)
{this.value=value;this.index=index;this.length=value.length;this.css=css;}
dp.sh.Highlighter=function()
{this.noGutter=false;this.addControls=true;this.collapse=false;this.tabsToSpaces=true;this.wrapColumn=80;this.showColumns=true;}
dp.sh.Highlighter.SortCallback=function(m1,m2)
{if(m1.index<m2.index)
return-1;else if(m1.index>m2.index)
return 1;else
{if(m1.length<m2.length)
return-1;else if(m1.length>m2.length)
return 1;}
return 0;}
dp.sh.Highlighter.prototype.CreateElement=function(name)
{var result=document.createElement(name);result.highlighter=this;return result;}
dp.sh.Highlighter.prototype.GetMatches=function(regex,css)
{var index=0;var match=null;while((match=regex.exec(this.code))!=null)
this.matches[this.matches.length]=new dp.sh.Match(match[0],match.index,css);}
dp.sh.Highlighter.prototype.AddBit=function(str,css)
{if(str==null||str.length==0)
return;var span=this.CreateElement('SPAN');str=str.replace(/ /g,'&nbsp;');str=str.replace(/</g,'&lt;');str=str.replace(/\n/gm,'&nbsp;<br>');if(css!=null)
{if((/br/gi).test(str))
{var lines=str.split('&nbsp;<br>');for(var i=0;i<lines.length;i++)
{span=this.CreateElement('SPAN');span.className=css;span.innerHTML=lines[i];this.div.appendChild(span);if(i+1<lines.length)
this.div.appendChild(this.CreateElement('BR'));}}
else
{span.className=css;span.innerHTML=str;this.div.appendChild(span);}}
else
{span.innerHTML=str;this.div.appendChild(span);}}
dp.sh.Highlighter.prototype.IsInside=function(match)
{if(match==null||match.length==0)
return false;for(var i=0;i<this.matches.length;i++)
{var c=this.matches[i];if(c==null)
continue;if((match.index>c.index)&&(match.index<c.index+c.length))
return true;}
return false;}
dp.sh.Highlighter.prototype.ProcessRegexList=function()
{for(var i=0;i<this.regexList.length;i++)
this.GetMatches(this.regexList[i].regex,this.regexList[i].css);}
dp.sh.Highlighter.prototype.ProcessSmartTabs=function(code)
{var lines=code.split('\n');var result='';var tabSize=4;var tab='\t';function InsertSpaces(line,pos,count)
{var left=line.substr(0,pos);var right=line.substr(pos+1,line.length);var spaces='';for(var i=0;i<count;i++)
spaces+=' ';return left+spaces+right;}
function ProcessLine(line,tabSize)
{if(line.indexOf(tab)==-1)
return line;var pos=0;while((pos=line.indexOf(tab))!=-1)
{var spaces=tabSize-pos%tabSize;line=InsertSpaces(line,pos,spaces);}
return line;}
for(var i=0;i<lines.length;i++)
result+=ProcessLine(lines[i],tabSize)+'\n';return result;}
dp.sh.Highlighter.prototype.SwitchToList=function()
{var html=this.div.innerHTML.replace(/<(br)\/?>/gi,'\n');var lines=html.split('\n');if(this.addControls==true)
this.bar.appendChild(dp.sh.Toolbar.Create(this));if(this.showColumns)
{var div=this.CreateElement('div');var columns=this.CreateElement('div');var showEvery=10;var i=1;while(i<=150)
{if(i%showEvery==0)
{div.innerHTML+=i;i+=(i+'').length;}
else
{div.innerHTML+='&middot;';i++;}}
columns.className='columns';columns.appendChild(div);this.bar.appendChild(columns);}
for(var i=0,lineIndex=this.firstLine;i<lines.length-1;i++,lineIndex++)
{var li=this.CreateElement('LI');var span=this.CreateElement('SPAN');li.className=(i%2==0)?'alt':'';span.innerHTML=lines[i]+'&nbsp;';li.appendChild(span);this.ol.appendChild(li);}
this.div.innerHTML='';}
dp.sh.Highlighter.prototype.Highlight=function(code)
{function Trim(str)
{return str.replace(/^\s*(.*?)[\s\n]*$/g,'$1');}
function Chop(str)
{return str.replace(/\n*$/,'').replace(/^\n*/,'');}
function Unindent(str)
{var lines=dp.sh.Utils.FixForBlogger(str).split('\n');var indents=new Array();var regex=new RegExp('^\\s*','g');var min=1000;for(var i=0;i<lines.length&&min>0;i++)
{if(Trim(lines[i]).length==0)
continue;var matches=regex.exec(lines[i]);if(matches!=null&&matches.length>0)
min=Math.min(matches[0].length,min);}
if(min>0)
for(var i=0;i<lines.length;i++)
lines[i]=lines[i].substr(min);return lines.join('\n');}
function Copy(string,pos1,pos2)
{return string.substr(pos1,pos2-pos1);}
var pos=0;if(code==null)
code='';this.originalCode=code;this.code=Chop(Unindent(code));this.div=this.CreateElement('DIV');this.bar=this.CreateElement('DIV');this.ol=this.CreateElement('OL');this.matches=new Array();this.div.className='dp-highlighter';this.div.highlighter=this;this.bar.className='bar';this.ol.start=this.firstLine;if(this.CssClass!=null)
this.ol.className=this.CssClass;if(this.collapse)
this.div.className+=' collapsed';if(this.noGutter)
this.div.className+=' nogutter';if(this.tabsToSpaces==true)
this.code=this.ProcessSmartTabs(this.code);this.ProcessRegexList();if(this.matches.length==0)
{this.AddBit(this.code,null);this.SwitchToList();this.div.appendChild(this.bar);this.div.appendChild(this.ol);return;}
this.matches=this.matches.sort(dp.sh.Highlighter.SortCallback);for(var i=0;i<this.matches.length;i++)
if(this.IsInside(this.matches[i]))
this.matches[i]=null;for(var i=0;i<this.matches.length;i++)
{var match=this.matches[i];if(match==null||match.length==0)
continue;this.AddBit(Copy(this.code,pos,match.index),null);this.AddBit(match.value,match.css);pos=match.index+match.length;}
this.AddBit(this.code.substr(pos),null);this.SwitchToList();this.div.appendChild(this.bar);this.div.appendChild(this.ol);}
dp.sh.Highlighter.prototype.GetKeywords=function(str)
{return'\\b'+str.replace(/ /g,'\\b|\\b')+'\\b';}
dp.sh.BloggerMode=function()
{dp.sh.isBloggerMode=true;}
dp.sh.HighlightAll=function(name,showGutter,showControls,collapseAll,firstLine,showColumns)
{function FindValue()
{var a=arguments;for(var i=0;i<a.length;i++)
{if(a[i]==null)
continue;if(typeof(a[i])=='string'&&a[i]!='')
return a[i]+'';if(typeof(a[i])=='object'&&a[i].value!='')
return a[i].value+'';}
return null;}
function IsOptionSet(value,list)
{for(var i=0;i<list.length;i++)
if(list[i]==value)
return true;return false;}
function GetOptionValue(name,list,defaultValue)
{var regex=new RegExp('^'+name+'\\[(\\w+)\\]$','gi');var matches=null;for(var i=0;i<list.length;i++)
if((matches=regex.exec(list[i]))!=null)
return matches[1];return defaultValue;}
function FindTagsByName(list,name,tagName)
{var tags=document.getElementsByTagName(tagName);for(var i=0;i<tags.length;i++)
if(tags[i].getAttribute('name')==name)
list.push(tags[i]);}
var elements=[];var highlighter=null;var registered={};var propertyName='innerHTML';FindTagsByName(elements,name,'pre');FindTagsByName(elements,name,'textarea');if(elements.length==0)
return;for(var brush in dp.sh.Brushes)
{var aliases=dp.sh.Brushes[brush].Aliases;if(aliases==null)
continue;for(var i=0;i<aliases.length;i++)
registered[aliases[i]]=brush;}
for(var i=0;i<elements.length;i++)
{var element=elements[i];var options=FindValue(element.attributes['class'],element.className,element.attributes['language'],element.language);var language='';if(options==null)
continue;options=options.split(':');language=options[0].toLowerCase();if(registered[language]==null)
continue;highlighter=new dp.sh.Brushes[registered[language]]();element.style.display='none';highlighter.noGutter=(showGutter==null)?IsOptionSet('nogutter',options):!showGutter;highlighter.addControls=(showControls==null)?!IsOptionSet('nocontrols',options):showControls;highlighter.collapse=(collapseAll==null)?IsOptionSet('collapse',options):collapseAll;highlighter.showColumns=(showColumns==null)?IsOptionSet('showcolumns',options):showColumns;var headNode=document.getElementsByTagName('head')[0];if(highlighter.Style&&headNode)
{var styleNode=document.createElement('style');styleNode.setAttribute('type','text/css');if(styleNode.styleSheet)
{styleNode.styleSheet.cssText=highlighter.Style;}
else
{var textNode=document.createTextNode(highlighter.Style);styleNode.appendChild(textNode);}
headNode.appendChild(styleNode);}
highlighter.firstLine=(firstLine==null)?parseInt(GetOptionValue('firstline',options,1)):firstLine;highlighter.Highlight(element[propertyName]);highlighter.source=element;element.parentNode.insertBefore(highlighter.div,element);}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

View File

@@ -31,3 +31,5 @@ class OkapiUrls
// '^tilestress$' => 'tilestress',
);
}
# This line is used for commit-hooks testing: .........>

View File

@@ -115,7 +115,7 @@ class View
$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 " $etag_hits of the requests matched the ETag and were served an HTTP 304 response.\n";
print "\n";
$calls_left = $calls['C'];

View File

@@ -4,20 +4,17 @@
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>OKAPI Examples</title>
<link rel="stylesheet" href="<?= $vars['okapi_base_url'] ?>static/common.css?<?= $vars['okapi_rev'] ?>">
<link type="text/css" rel="stylesheet" href="<?= $vars['okapi_base_url'] ?>static/syntax_highlighter/SyntaxHighlighter.css"></link>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'></script>
<script>
var okapi_base_url = "<?= $vars['okapi_base_url'] ?>";
</script>
<script src='<?= $vars['okapi_base_url'] ?>static/common.js?<?= $vars['okapi_rev'] ?>'></script>
<script language="javascript" src="<?= $vars['okapi_base_url'] ?>static/syntax_highlighter/shCore.js"></script>
<script language="javascript" src="<?= $vars['okapi_base_url'] ?>static/syntax_highlighter/shBrushPhp.js"></script>
<script language="javascript">
$(function() {
dp.SyntaxHighlighter.ClipboardSwf = '<?= $vars['okapi_base_url'] ?>static/syntax_highlighter/clipboard.swf';
dp.SyntaxHighlighter.HighlightAll('code');
$('h2').each(function() {
$('#toc').append($("<div></div>").append($("<a></a>")
.text($(this).text()).attr("href", "#" + $(this).attr('id'))));
});
});
</script>
<script src='<?= $vars['okapi_base_url'] ?>static/common.js?<?= $vars['okapi_rev'] ?>'></script>
</head>
<body class='api'>
<div class='okd_mid'>
@@ -29,11 +26,20 @@
</td>
<td class='article'>
<h1>Examples and libraries</h1>
<h1>Examples, libraries and tools</h1>
<p>Here you will find basic examples of OKAPI usage with popular programming languages.</p>
<div id='toc'></div>
<h2>Are there any client libraries?</h2>
<h2 id='tools'>Tools</h2>
If you're just getting to know OKAPI, you might want to check out
<a href='https://github.com/wrygiel/okapi-browser'>OKAPI Browser</a>
(<a href='https://raw.github.com/wrygiel/okapi-browser/master/extras/screenshot.png'>screenshot</a>),
a basic OAuth Console for OKAPI methods. It is an open-source project. You can
<a href='https://github.com/wrygiel/okapi-browser'>fork it</a> on GitHub or simply
<a href='http://usosphp.mimuw.edu.pl/~rygielski/okapi-browser/'>install it here</a>.
<h2 id='libraries'>Are there any client libraries?</h2>
<p>OKAPI <b>does not</b> require you to use any special libraries, usually you will want to
use OKAPI "as is", via basic HTTP requests and responses.</p>
@@ -53,65 +59,23 @@ protocol might be the safest choice.</p>
<div class='issue-comments' issue_id='96'></div>
<h2>PHP Example</h2>
<h2 id='php1'>PHP Example 1 - simple query</h2>
<p><b>Example 1.</b> This will print the number of users in the <?= $vars['site_name'] ?> installation:
<pre name="code" class="php:nogutter:nocontrols">
&lt;?
$json = file_get_contents("<?= $vars['okapi_base_url'] ?>services/apisrv/stats");
$data = json_decode($json);
print "Number of <?= $vars['site_name'] ?> users: ".$data->user_count;
?>
</pre>
<p><b>Example 2.</b> This will print the codes of some nearest unfound caches:</p>
<pre name="code" class="php:nogutter:nocontrols">
&lt;?
/* Enter your OKAPI's URL here. */
$okapi_base_url = "http://opencaching.pl/okapi/";
/* Enter your Consumer Key here. */
$consumer_key = "YOUR_KEY_HERE";
/* Username. Caches found by the given user will be excluded from the results. */
$username = "USERNAME_HERE";
/* Your location. */
$lat = 54.3;
$lon = 22.3;
/* 1. Get the UUID of the user. */
$json = @file_get_contents($okapi_base_url."services/users/by_username".
"?username=".$username."&amp;fields=uuid&amp;consumer_key=".$consumer_key);
if (!$json)
die("ERROR! Check your consumer_key and/or username!\n");
$user_uuid = json_decode($json)->uuid;
print "Your UUID: ".$user_uuid."\n";
/* 2. Search for caches. */
$json = @file_get_contents($okapi_base_url."services/caches/search/nearest".
"?center=".$lat."|".$lon."&amp;not_found_by=".$user_uuid."&amp;limit=5".
"&amp;consumer_key=".$consumer_key);
if (!$json)
die("ERROR!");
$cache_codes = json_decode($json)->results;
/* Display them. */
print "Five nearest unfound caches: ".implode(", ", $cache_codes)."\n";
?>
</pre>
<p>Please note that the above examples use very simple error checking routines.
<p>Please note that the examples below use very simple error checking routines.
If you want to be "professional", you should catch HTTP 400 Responses, read their
bodies (OKAPI error messages), and deal with them more gracefully.</p>
<h2>JavaScript Example</h2>
<p>This will print the number of users in the <?= $vars['site_name'] ?> installation:
<script src="https://gist.github.com/4231796.js?file=users.php"></script>
<h2 id='php2'>PHP Example 2 - search for nearest geocaches</h2>
<p>This will print the codes of some nearest unfound caches:</p>
<script src="https://gist.github.com/4231824.js?file=nearest_unfound.php"></script>
<h2 id='js1'>JavaScript Example</h2>
<p>It is possible to access OKAPI directly from user's browser, without the
need for server backend. OKAPI allows <a href='http://en.wikipedia.org/wiki/XMLHttpRequest#Cross-domain_requests'>Cross-domain
@@ -129,7 +93,17 @@ There are some limitations of both these techniques though.</p>
<p><a href='<?= $vars['okapi_base_url'] ?>static/examples/javascript_nearest.html' style='font-size: 130%; font-weight: bold'>Run this example</a></p>
<h2>Comments</h2>
<h2 id='cs1'>C# Example</h2>
<p><a href='https://github.com/wrygiel/okapi-browser'>OKAPI Browser</a>
(already mentioned in the Tools section) is an open-source project. Written in C#.NET, uses
the <a href='<?= $vars['okapi_base_url'] ?>services/apisrv/installations.html'>apisrv</a>
and <a href='<?= $vars['okapi_base_url'] ?>services/apiref/method_index.html'>apiref</a>
modules to dynamically retrieve the current list of OKAPI installations and methods.
<a href='https://github.com/wrygiel/okapi-browser'>Get the source</a> or
<a href='http://usosphp.mimuw.edu.pl/~rygielski/okapi-browser/'>try it</a> first.</p>
<h2 id='comments'>Comments</h2>
<div class='issue-comments' issue_id='36'></div>

View File

@@ -18,7 +18,7 @@
Page Not Found
<div class='subh1'>:: 404 Error</div>
</h1>
<p>The page you requested does not exist. Try one of the page in the side menu!</p>
<p>The page you requested does not exist. Try one of the pages in the side menu!</p>
</td>
</tr></table>
</div>

View File

@@ -148,7 +148,7 @@ parameters required for the method to run):</p>
<li><b>jsonp</b> - <a href='http://en.wikipedia.org/wiki/JSONP'>JSONP</a> format, if
you choose this one, you have to specify the <b>callback</b> parameter,</li>
<li class='deprecated'><b>xmlmap</b> - deprecated (<a href='http://code.google.com/p/opencaching-api/issues/detail?id=128'>why?</a>),</li>
<li><b>xmlmap2</b> - XML format. This is produced by mapping JSON datatypes to XML elements.
<li><b>xmlmap2</b> - XML format. This is produced by mapping JSON data types to XML elements.
Keep in mind, that XML format is larger than JSON and it takes more time to generate
and parse. Try to use JSON when it's possible.</li>
</ul>
@@ -159,12 +159,12 @@ parameters required for the method to run):</p>
</li>
</ul>
<p><b><u>Important:</u></b> Almost all of the returned datatypes are <b>extendible</b>. This means,
<p><b><u>Important:</u></b> Almost all of the returned data types are <b>extendible</b>. This means,
that (in future) they <b>may contain data that currently they don't</b>.
Such data will be included in backward-compatible manner, but still you should remember about
it in some cases (i.e. when iterating over attributes of an object). This additional data may
appear as extra elements in GPX files or extra keys in JSON responses.
Your software <b>must ignore</b> such occurances if it doesn't understand them!</p>
Your software <b>must ignore</b> such occurrences if it doesn't understand them!</p>
<p>Some methods expose some <b>special formatting</b> of their own, for example, they may return
a JPEG or a GPX file. Such methods do not accept <i>common formatting parameters</i>.</p>
@@ -192,7 +192,7 @@ method calls and redirects which provide you with an Access Token).</p>
</li>
</ul>
<p>Things you should pay attantion to:</p>
<p>Things you should pay attention to:</p>
<ul>
<li>
<p>The <b>oauth_callback</b> argument of the <b>request_token</b> method is <b>required</b>.</p>

View File

@@ -16,9 +16,9 @@ use okapi\OkapiServiceRunner;
use okapi\OkapiInternalRequest;
use okapi\Settings;
use okapi\OkapiLock;
use okapi\cronjobs\CronJobController;
require_once($GLOBALS['rootpath']."okapi/cronjobs.php");
class View
{
@@ -622,4 +622,32 @@ class View
private static function ver74() { Db::execute("update okapi_cache set score=1, expires=date_add(now(), interval 360 day) where `key` like 'tilecaption/%'"); }
private static function ver75() { Db::execute("alter table okapi_cache modify column score float default null"); }
private static function ver76() { Db::execute("update okapi_cache set expires=date_add(now(), interval 100 year) where `key` like 'clog#geocache#%'"); }
private static function ver77()
{
Db::execute("
CREATE TABLE okapi_search_sets (
id mediumint(6) unsigned not null auto_increment,
params_hash varchar(64) not null,
primary key (id),
key by_hash (params_hash, id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
");
}
private static function ver78()
{
Db::execute("
CREATE TABLE okapi_search_results (
set_id mediumint(6) unsigned not null,
cache_id mediumint(6) unsigned not null,
primary key (set_id, cache_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
");
}
private static function ver79() { Db::execute("alter table okapi_search_results engine=MyISAM"); }
private static function ver80() { Db::execute("alter table okapi_search_sets add column date_created datetime not null"); }
private static function ver81() { Db::execute("alter table okapi_search_sets add column expires datetime not null"); }
private static function ver82() { CronJobController::reset_job_schedule("FulldumpGeneratorJob"); }
}