OKAPI update, revision 1075

This commit is contained in:
following
2015-05-22 14:55:37 +02:00
parent 891d2349bd
commit 89d4645e17
35 changed files with 506 additions and 322 deletions

View File

@@ -784,31 +784,35 @@ class OkapiRedirectResponse extends OkapiHttpResponse
}
}
require_once ($GLOBALS['rootpath'].'okapi/lib/tbszip.php');
use \clsTbsZip;
class OkapiZIPHttpResponse extends OkapiHttpResponse
{
public $zip;
public function __construct()
{
require_once ($GLOBALS['rootpath'].'okapi/lib/tbszip.php');
$this->zip = new \clsTbsZip();
$this->zip = new clsTbsZip();
$this->zip->CreateNew();
}
public function print_body()
{
$this->zip->Flush(TBSZIP_DOWNLOAD|TBSZIP_NOHEADER);
$this->zip->Flush(clsTbsZip::TBSZIP_DOWNLOAD|clsTbsZip::TBSZIP_NOHEADER);
}
public function get_body()
{
$this->zip->Flush(TBSZIP_STRING);
return $this->zip->OutputSrc;
$this->zip->Flush(clsTbsZip::TBSZIP_STRING);
return $this->zip->OutputSrc;
}
public function get_length()
{
# The _EstimateNewArchSize() method returns *false* if archive
# size can not be calculated *exactly*, which causes display()
# method to skip Content-Length header, and triggers chunking
return $this->zip->_EstimateNewArchSize();
}
@@ -890,7 +894,7 @@ class Okapi
{
public static $data_store;
public static $server;
public static $revision = 1055; # This gets replaced in automatically deployed packages
public static $revision = 1075; # 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. */

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi\cronjobs;

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi;
@@ -103,7 +103,7 @@ class Facade
'caches.status in (1,2,3)',
);
return \okapi\services\caches\search\save\WebService::get_set(
$tables, $where_conds, $min_store, $max_ref_age
$tables, array() /* joins */, $where_conds, $min_store, $max_ref_age
);
}

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi;

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi;

View File

@@ -10,12 +10,12 @@ for TinyButStrong Template Engine (TBS). OpenTbs makes TBS able to merge OpenOff
Visit http://www.tinybutstrong.com
*/
define('TBSZIP_DOWNLOAD',1); // download (default)
define('TBSZIP_NOHEADER',4); // option to use with DOWNLOAD: no header is sent
define('TBSZIP_FILE',8); // output to file , or add from file
define('TBSZIP_STRING',32); // output to string, or add from string
class clsTbsZip {
const TBSZIP_DOWNLOAD = 1; // download (default)
const TBSZIP_NOHEADER = 4; // option to use with DOWNLOAD: no header is sent
const TBSZIP_FILE = 8; // output to file , or add from file
const TBSZIP_STRING = 32; // output to string, or add from string
function __construct() {
$this->Meth8Ok = extension_loaded('zlib'); // check if Zlib extension is available. This is need for compress and uncompress with method 8.
@@ -77,7 +77,7 @@ class clsTbsZip {
$this->AddInfo = array();
}
function FileAdd($Name, $Data, $DataType=TBSZIP_STRING, $Compress=true) {
function FileAdd($Name, $Data, $DataType=self::TBSZIP_STRING, $Compress=true) {
if ($Data===false) return $this->FileCancelModif($Name, false); // Cancel a previously added file
@@ -389,7 +389,7 @@ class clsTbsZip {
}
function FileReplace($NameOrIdx, $Data, $DataType=TBSZIP_STRING, $Compress=true) {
function FileReplace($NameOrIdx, $Data, $DataType=self::TBSZIP_STRING, $Compress=true) {
// Store replacement information.
$idx = $this->FileGetIdx($NameOrIdx);
@@ -472,9 +472,9 @@ class clsTbsZip {
}
function Flush($Render=TBSZIP_DOWNLOAD, $File='', $ContentType='') {
function Flush($Render=self::TBSZIP_DOWNLOAD, $File='', $ContentType='') {
if ( ($File!=='') && ($this->ArchFile===$File) && ($Render==TBSZIP_FILE) ) {
if ( ($File!=='') && ($this->ArchFile===$File) && ($Render==self::TBSZIP_FILE) ) {
$this->RaiseError('Method Flush() cannot overwrite the current opened archive: \''.$File.'\''); // this makes corrupted zip archives without PHP error.
return false;
}
@@ -627,21 +627,21 @@ class clsTbsZip {
function OutputOpen($Render, $File, $ContentType) {
if (($Render & TBSZIP_FILE)==TBSZIP_FILE) {
$this->OutputMode = TBSZIP_FILE;
if (($Render & self::TBSZIP_FILE)==self::TBSZIP_FILE) {
$this->OutputMode = self::TBSZIP_FILE;
if (''.$File=='') $File = basename($this->ArchFile).'.zip';
$this->OutputHandle = @fopen($File, 'w');
if ($this->OutputHandle===false) {
return $this->RaiseError('Method Flush() cannot overwrite the target file \''.$File.'\'. This may not be a valid file path or the file may be locked by another process or because of a denied permission.');
}
} elseif (($Render & TBSZIP_STRING)==TBSZIP_STRING) {
$this->OutputMode = TBSZIP_STRING;
} elseif (($Render & self::TBSZIP_STRING)==self::TBSZIP_STRING) {
$this->OutputMode = self::TBSZIP_STRING;
$this->OutputSrc = '';
} elseif (($Render & TBSZIP_DOWNLOAD)==TBSZIP_DOWNLOAD) {
$this->OutputMode = TBSZIP_DOWNLOAD;
} elseif (($Render & self::TBSZIP_DOWNLOAD)==self::TBSZIP_DOWNLOAD) {
$this->OutputMode = self::TBSZIP_DOWNLOAD;
// Output the file
if (''.$File=='') $File = basename($this->ArchFile);
if (($Render & TBSZIP_NOHEADER)==TBSZIP_NOHEADER) {
if (($Render & self::TBSZIP_NOHEADER)==self::TBSZIP_NOHEADER) {
} else {
header ('Pragma: no-cache');
if ($ContentType!='') header ('Content-Type: '.$ContentType);
@@ -677,17 +677,17 @@ class clsTbsZip {
}
function OutputFromString($data) {
if ($this->OutputMode===TBSZIP_DOWNLOAD) {
if ($this->OutputMode===self::TBSZIP_DOWNLOAD) {
echo $data; // donwload
} elseif ($this->OutputMode===TBSZIP_STRING) {
} elseif ($this->OutputMode===self::TBSZIP_STRING) {
$this->OutputSrc .= $data; // to string
} elseif (TBSZIP_FILE) {
} elseif (self::TBSZIP_FILE) {
fwrite($this->OutputHandle, $data); // to file
}
}
function OutputClose() {
if ( ($this->OutputMode===TBSZIP_FILE) && ($this->OutputHandle!==false) ) {
if ( ($this->OutputMode===self::TBSZIP_FILE) && ($this->OutputHandle!==false) ) {
fclose($this->OutputHandle);
$this->OutputHandle = false;
}
@@ -908,7 +908,7 @@ class clsTbsZip {
}
// TODO support for data taken from okapi cache
if ($DataType==TBSZIP_STRING) {
if ($DataType==self::TBSZIP_STRING) {
$path = false;
if ($Compress) {
// we compress now in order to save PHP memory
@@ -956,9 +956,10 @@ class clsTbsZip {
}
}
}
/**
* Return the size of the new archive, or false if it cannot be calculated (because of external file that must be compressed before to be insered)
*/
function _EstimateNewArchSize($Optim=true) {
// Return the size of the new archive, or false if it cannot be calculated (because of external file that must be compressed before to be insered)
if ($this->ArchIsNew) {
$Len = strlen($this->CdInfo['bin']);

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi;

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: OKAPI\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-01-23 16:05+0100\n"
"PO-Revision-Date: 2014-11-01 00:36+0100\n"
"PO-Revision-Date: 2015-03-22 14:48+0100\n"
"Last-Translator: Harrie Klomp <harrie@harrieklomp.be>\n"
"Language-Team: \n"
"Language: nl_NL\n"
@@ -359,9 +359,8 @@ msgstr ""
"\n"
"<p>Dit is een lijst met toegestane toepassingen op jouw <b>%s</b> account.\n"
"Op deze pagina kun je alle toestemmingen intrekken die gegeven zijn.\n"
"Met een klik op \"verwijderen\" zal de toepassing verwijderen en is dan ook "
"niet meer beschikbaar\n"
"oor anderen.</p>"
"Met een klik op \"verwijderen\" zal de toestemming ingetrokken worden en\n"
"niet meer namens jou gebruikt kunnen worden.</p>"
#: okapi/views/apps/index.tpl.php:45
msgid "remove"

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi;

View File

@@ -45,6 +45,10 @@ class WebService
$context = stream_context_create($opts);
$xml = file_get_contents("http://opencaching-api.googlecode.com/svn/trunk/etc/installations.xml",
false, $context);
$doc = simplexml_load_string($xml);
if (!$doc) {
throw new ErrorException(); # just to get to the catch block
}
}
catch (ErrorException $e)
{
@@ -71,7 +75,6 @@ class WebService
return Okapi::formatted_response($request, $results);
}
$doc = simplexml_load_string($xml);
$results = array();
$i_was_included = false;
foreach ($doc->installation as $inst)

View File

@@ -88,7 +88,7 @@ It also defines attribute names and descriptions in several languages.
<attr acode="A2" categories="de-cache-types">
<opencaching schema="http://opencaching.pl/" id="54" />
<opencaching schema="http://www.opencaching.nl/" id="61" />
<opencaching schema="http://www.opencaching.nl/" id="54" />
<lang id="en">
<name>Near a Survey Marker</name>
<desc>
@@ -1008,6 +1008,7 @@ It also defines attribute names and descriptions in several languages.
<attr acode="A29" categories="de-location">
<opencaching schema="http://opencaching.pl/" id="61" />
<opencaching schema="http://www.opencaching.nl/" id="61" />
<lang id="en">
<name>Historic site</name>
<desc>
@@ -1029,11 +1030,17 @@ It also defines attribute names and descriptions in several languages.
einer Burg, einem Friedhof, einer ehemaligem Militäranlage etc.
</desc>
</lang>
<lang id="nl">
<name>Lost Place</name>
<desc>
Deze cache bevind zich op of rond een verlaten plek. Zoals een,
verlaten kasteel, oude bunker, industrie, etc.
</desc>
</lang>
</attr>
<attr acode="A30" categories="de-location">
<opencaching schema="http://www.opencaching.de/" id="30" />
<opencaching schema="http://www.opencaching.nl/" id="30" />
<lang id="en">
<name>Point of interest</name>
<desc>
@@ -2652,4 +2659,23 @@ It also defines attribute names and descriptions in several languages.
</desc>
</lang>
</attr>
<attr acode="A74" categories="de-dangers">
<groundspeak id="40" inc="true" name="Stealth required" />
<opencaching schema="http://www.opencaching.nl/" id="45" />
<lang id="en">
<name>Stealth required</name>
<desc>
The cache is hidden in an area where other non-geocaching people might
see it. You should be extra careful to avoid this unwanted attention.
</desc>
</lang>
<lang id="nl">
<name>Stealth vereist</name>
<desc>
Deze cache dient ongezien gelogd te worden.
</desc>
</lang>
</attr>
</xml>

View File

@@ -46,7 +46,7 @@ class WebService
$format = $request->get_parameter('caches_format');
if (!$format) $format = "gpx";
if (!in_array($format, array("gpx", "ggz")))
throw new InvalidParam('format');
throw new InvalidParam('caches_format');
$location_source = $request->get_parameter('location_source');
$location_change_prefix = $request->get_parameter('location_change_prefix');
@@ -89,7 +89,7 @@ class WebService
'my_notes' => ($request->token != null) ? "desc:text" : "none",
'location_source' => $location_source,
'location_change_prefix' => $location_change_prefix
)))->get_body(), TBSZIP_STRING, $data_use_compression);
)))->get_body(), clsTbsZip::TBSZIP_STRING, $data_use_compression);
# Then, include all the images.
@@ -143,7 +143,7 @@ class WebService
$syspath = Settings::get('IMAGES_DIR')."/".$img['uuid'].".jpg";
if (file_exists($syspath))
{
$response->zip->FileAdd($zippath, $syspath, TBSZIP_FILE, false);
$response->zip->FileAdd($zippath, $syspath, clsTbsZip::TBSZIP_FILE, false);
}
else
{
@@ -181,7 +181,7 @@ class WebService
}
}
if ($jpeg_contents) # This can be "null" *or* "false"!
$response->zip->FileAdd($zippath, $jpeg_contents, TBSZIP_STRING, false);
$response->zip->FileAdd($zippath, $jpeg_contents, clsTbsZip::TBSZIP_STRING, false);
}
}
}

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi\services\caches\formatters\ggz;
@@ -9,12 +9,12 @@ echo '<?xml version="1.0" encoding="utf-8"?>'."\n";
?>
<ggz xmlns="http://www.opencaching.com/xmlschemas/ggz/1/0">
<time><?= date('c') ?></time>
<? foreach ($vars['files'] as $f) { ?>
<?php foreach ($vars['files'] as $f) { ?>
<file>
<name><?= $f['name'] ?></name>
<crc><?= $f['crc32'] ?></crc>
<time><?= date('c') ?></time>
<?
<?php
foreach ($f['caches'] as $c) {
?><gch>
<code><?= $c['code'] ?></code>
@@ -24,20 +24,20 @@ echo '<?xml version="1.0" encoding="utf-8"?>'."\n";
<lon><?= $c['lon'] ?></lon>
<file_pos><?= $c['file_pos'] ?></file_pos>
<file_len><?= $c['file_len'] ?></file_len>
<? if (isset($c['ratings'])) {
<?php if (isset($c['ratings'])) {
?><ratings>
<?
<?php
foreach ($c['ratings'] as $rating_key => $rating_val){
echo "<$rating_key>$rating_val</$rating_key>\n";
}
?>
</ratings><?
</ratings><?php
}
if (isset($c['found']) && $c['found']) { ?>
<found>true</found>
<? } ?>
<?php } ?>
</gch>
<? } ?>
<?php } ?>
</file>
<? } ?>
<?php } ?>
</ggz>

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi\services\caches\formatters\gpx;
@@ -22,8 +22,8 @@ http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
<url><?= $vars['installation']['site_url'] ?></url>
<urlname><?= $vars['installation']['site_name'] ?></urlname>
<time><?= date('c') ?></time>
<? foreach ($vars['caches'] as &$cache_ref) { ?>
<?
<?php foreach ($vars['caches'] as &$cache_ref) { ?>
<?php
if (isset($cache_ref['ggz_entry'])) {
/* The base parts of the GGZ index are the offset and length of the entry
* in the GPX file. This needs to be calculated here. */
@@ -35,21 +35,21 @@ 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><?= Okapi::xmlescape(isset($c['name_2']) ? $c['name_2'] : $c['name']) ?> <?= _("hidden by") ?> <?= Okapi::xmlescape($c['owner']['username']) ?> :: <?= ucfirst($c['type']) ?> Cache (<?= $c['difficulty'] ?>/<?= $c['terrain'] ?><? if ($c['size'] !== null) { echo "/".$c['size']; } else { echo "/X"; } ?>/<?= $c['rating'] ?>)</desc>
<desc><?= Okapi::xmlescape(isset($c['name_2']) ? $c['name_2'] : $c['name']) ?> <?= _("hidden by") ?> <?= Okapi::xmlescape($c['owner']['username']) ?> :: <?= ucfirst($c['type']) ?> Cache (<?= $c['difficulty'] ?>/<?= $c['terrain'] ?><?php if ($c['size'] !== null) { echo "/".$c['size']; } else { echo "/X"; } ?>/<?= $c['rating'] ?>)</desc>
<url><?= $c['url'] ?></url>
<urlname><?= Okapi::xmlescape($c['name']) ?></urlname>
<sym><?= ($vars['mark_found'] && $c['is_found']) ? "Geocache Found" : "Geocache" ?></sym>
<type>Geocache|<?= $vars['cache_GPX_types'][$c['type']] ?></type>
<? if ($vars['ns_ground']) { /* Does user want us to include Groundspeak's <cache> element? */ ?>
<?php if ($vars['ns_ground']) { /* Does user want us to include Groundspeak's <cache> element? */ ?>
<groundspeak:cache archived="<?= ($c['status'] == 'Archived') ? "True" : "False" ?>" available="<?= ($c['status'] == 'Available') ? "True" : "False" ?>" id="<?= $c['internal_id'] ?>" xmlns:groundspeak="http://www.groundspeak.com/cache/1/0/1">
<groundspeak:name><?= Okapi::xmlescape(isset($c['name_2']) ? $c['name_2'] : $c['name']) ?></groundspeak:name>
<groundspeak:placed_by><?= Okapi::xmlescape($c['owner']['username']) ?></groundspeak:placed_by>
<groundspeak:owner id="<?= $vars['user_uuid_to_internal_id'][$c['owner']['uuid']] ?>"><?= Okapi::xmlescape($c['owner']['username']) ?></groundspeak:owner>
<groundspeak:type><?= $vars['cache_GPX_types'][$c['type']] ?></groundspeak:type>
<groundspeak:container><?= $vars['cache_GPX_sizes'][$c['size2']] ?></groundspeak:container>
<? if ($vars['gc_attrs'] || $vars['gc_ocde_attrs']) { /* Does user want us to include groundspeak:attributes? */ ?>
<?php if ($vars['gc_attrs'] || $vars['gc_ocde_attrs']) { /* Does user want us to include groundspeak:attributes? */ ?>
<groundspeak:attributes>
<?
<?php
foreach ($c['gc_attrs'] as $gc_id => $gc_attr) {
print "<groundspeak:attribute id=\"".$gc_id."\" ";
print "inc=\"".$gc_attr['inc']."\">";
@@ -58,54 +58,54 @@ http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
}
?>
</groundspeak:attributes>
<? } ?>
<?php } ?>
<groundspeak:difficulty><?= $c['difficulty'] ?></groundspeak:difficulty>
<groundspeak:terrain><?= $c['terrain'] ?></groundspeak:terrain>
<? if ($c['short_description']) { ?>
<?php if ($c['short_description']) { ?>
<groundspeak:short_description html="False"><?= Okapi::xmlescape($c['short_description']) ?></groundspeak:short_description>
<? } ?>
<?php } ?>
<groundspeak:long_description html="True">
<? if (isset($c['warning_prefix'])) { ?>
<?php if (isset($c['warning_prefix'])) { ?>
&lt;p style='font-size: 120%'&gt;<?= Okapi::xmlescape($c['warning_prefix']) ?>&lt;/p&gt;
<? } ?>
<?php } ?>
&lt;p&gt;
&lt;a href="<?= $c['url'] ?>"&gt;<?= Okapi::xmlescape($c['name']) ?>&lt;/a&gt;
<?= _("hidden by") ?> &lt;a href='<?= $c['owner']['profile_url'] ?>'&gt;<?= Okapi::xmlescape($c['owner']['username']) ?>&lt;/a&gt;&lt;br/&gt;
<? if ($vars['recommendations'] == 'desc:count') { /* Does user want us to include recommendations count? */ ?>
<?php if ($vars['recommendations'] == 'desc:count') { /* Does user want us to include recommendations count? */ ?>
<?= sprintf(ngettext("%d recommendation", "%d recommendations", $c['recommendations']), $c['recommendations']) ?>
(<?= sprintf(ngettext("found %d time", "found %d times", $c['founds']), $c['founds']) ?>).
<? } ?>
<? if ($vars['trackables'] == 'desc:count') { /* Does user want us to include trackables count? */ ?>
<?php } ?>
<?php if ($vars['trackables'] == 'desc:count') { /* Does user want us to include trackables count? */ ?>
<?= sprintf(ngettext("%d trackable", "%d trackables", $c['trackables_count']), $c['trackables_count']) ?>.
<? } ?>
<?php } ?>
&lt;/p&gt;
<? if ((in_array('desc:text', $vars['my_notes'])) && ($c['my_notes'] != null)) { /* Does user want us to include personal notes? */ ?>
<?php if ((in_array('desc:text', $vars['my_notes'])) && ($c['my_notes'] != null)) { /* Does user want us to include personal notes? */ ?>
&lt;p&gt;&lt;b&gt;<?= _("Personal notes") ?>:&lt;/b&gt;&lt;br&gt;<?= Okapi::xmlescape(nl2br($c['my_notes'])) ?>&lt;/p&gt;
<? } ?>
<?php } ?>
<? if (in_array('desc:text', $vars['attrs']) && count($c['attrnames']) > 0) { /* Does user want us to include attributes? */ ?>
<?php if (in_array('desc:text', $vars['attrs']) && count($c['attrnames']) > 0) { /* Does user want us to include attributes? */ ?>
&lt;p&gt;<?= _("Attributes") ?>:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;<?= implode("&lt;/li&gt;&lt;li&gt;", $c['attrnames']) ?>&lt;/li&gt;&lt;/ul&gt;
<? } ?>
<? if ($vars['trackables'] == 'desc:list' && count($c['trackables']) > 0) { /* Does user want us to include trackables list? */ ?>
<?php } ?>
<?php if ($vars['trackables'] == 'desc:list' && count($c['trackables']) > 0) { /* Does user want us to include trackables list? */ ?>
&lt;p&gt;<?= _("Trackables") ?>:&lt;/p&gt;
&lt;ul&gt;
<? foreach ($c['trackables'] as $t) { ?>
<?php foreach ($c['trackables'] as $t) { ?>
&lt;li&gt;&lt;a href='<?= Okapi::xmlescape($t['url']) ?>'&gt;<?= Okapi::xmlescape($t['name']) ?>&lt;/a&gt; (<?= $t['code'] ?>)&lt;/li&gt;
<? } ?>
<?php } ?>
&lt;/ul&gt;
<? } ?>
<?php } ?>
<?= 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? */
<?php if ((strpos($vars['images'], "descrefs:") === 0) && count($c['images']) > 0) { /* Does user want us to include <img> references in cache descriptions? */
if ($vars['images'] == "descrefs:thumblinks") { ?>
&lt;h2&gt;<?= _("Images") ?> (<?= count($c['images']) ?>)&lt;/h2&gt;
&lt;div&gt;
<? foreach ($c['images'] as $img) { ?>
<?php foreach ($c['images'] as $img) { ?>
&lt;div style='float:left; padding:6px'&gt;&lt;a href='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;img src='<?= Okapi::xmlescape($img['thumb_url']) ?>'&gt;&lt;/a&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/div&gt;
<? } ?>
<?php } ?>
&lt;/div&gt;
<? } else {
<?php } else {
# We will split images into two subcategories: spoilers and nonspoilers.
$spoilers = array();
$nonspoilers = array();
@@ -113,91 +113,91 @@ http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
if ($img['is_spoiler']) $spoilers[] = $img;
else $nonspoilers[] = $img;
?>
<? if (count($nonspoilers) > 0) { ?>
<?php if (count($nonspoilers) > 0) { ?>
&lt;h2&gt;<?= _("Images") ?> (<?= count($nonspoilers) ?>)&lt;/h2&gt;
<? foreach ($nonspoilers as $img) { ?>
<?php foreach ($nonspoilers as $img) { ?>
&lt;p&gt;&lt;img src='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/p&gt;
<? } ?>
<? } ?>
<? if (count($spoilers) > 0 && $vars['images'] == 'descrefs:all') { ?>
<?php } ?>
<?php } ?>
<?php if (count($spoilers) > 0 && $vars['images'] == 'descrefs:all') { ?>
&lt;h2&gt;<?= _("Spoilers") ?> (<?= count($spoilers) ?>)&lt;/h2&gt;
<? foreach ($spoilers as $img) { ?>
<?php foreach ($spoilers as $img) { ?>
&lt;p&gt;&lt;img src='<?= Okapi::xmlescape($img['url']) ?>'&gt;&lt;br&gt;
<?= Okapi::xmlescape($img['caption']) ?>&lt;/p&gt;
<? } ?>
<? } ?>
<? } ?>
<? } ?>
<? if ((strpos($vars['images'], "ox:") === 0) && count($c['images']) > 0) { /* Include image descriptions (for ox:image numbers)? */ ?>
<?php } ?>
<?php } ?>
<?php } ?>
<?php } ?>
<?php if ((strpos($vars['images'], "ox:") === 0) && count($c['images']) > 0) { /* Include image descriptions (for ox:image numbers)? */ ?>
&lt;p&gt;<?= _("Image descriptions") ?>:&lt;/p&gt;
&lt;ul&gt;
<? foreach ($c['images'] as $no => $img) { ?>
<?php foreach ($c['images'] as $no => $img) { ?>
&lt;li&gt;<?= $img['unique_caption'] ?>. <?= Okapi::xmlescape($img['caption']) ?>&lt;/li&gt;
<? } ?>
<?php } ?>
&lt;/ul&gt;
<? } ?>
<? if ($vars['protection_areas'] == 'desc:text' && count($c['protection_areas'])) { ?>
<?php } ?>
<?php if ($vars['protection_areas'] == 'desc:text' && count($c['protection_areas'])) { ?>
&lt;p&gt;<?= _("The cache probably is located in the following protection areas:") ?>&lt;/p&gt;
&lt;ul&gt;
<? foreach($c['protection_areas'] as $protection_area) { ?>
<?php foreach($c['protection_areas'] as $protection_area) { ?>
&lt;li&gt;<?= Okapi::xmlescape($protection_area['type'])." - ".Okapi::xmlescape($protection_area['name']) ?>&lt;/li&gt;
<? } ?>
<?php } ?>
&lt;/ul;&gt;
<? } ?>
<?php } ?>
</groundspeak:long_description>
<groundspeak:encoded_hints><?= Okapi::xmlescape($c['hint2']) ?></groundspeak:encoded_hints>
<? if ((in_array('gc:personal_note', $vars['my_notes'])) && ($c['my_notes'] != null)) { /* Does user want us to include personal notes? -> Issue 294 */ ?>
<?php if ((in_array('gc:personal_note', $vars['my_notes'])) && ($c['my_notes'] != null)) { /* Does user want us to include personal notes? -> Issue 294 */ ?>
<groundspeak:personal_note><?= Okapi::xmlescape($c['my_notes']) ?></groundspeak:personal_note>
<? } ?>
<? if ($vars['latest_logs']) { /* Does user want us to include latest log entries? */ ?>
<?php } ?>
<?php if ($vars['latest_logs']) { /* Does user want us to include latest log entries? */ ?>
<groundspeak:logs>
<? foreach ($c['latest_logs'] as $log) { ?>
<?php foreach ($c['latest_logs'] as $log) { ?>
<groundspeak:log id="<?= $log['internal_id'] ?>">
<groundspeak:date><?= $log['date'] ?></groundspeak:date>
<groundspeak:type><?= $log['type'] ?></groundspeak:type>
<groundspeak:finder id="<?= $vars['user_uuid_to_internal_id'][$log['user']['uuid']] ?>"><?= Okapi::xmlescape($log['user']['username']) ?></groundspeak:finder>
<groundspeak:text encoded="False"><?= $log['was_recommended'] ? "(*) ": "" ?><?= Okapi::xmlescape($log['comment']) ?></groundspeak:text>
</groundspeak:log>
<? } ?>
<?php } ?>
</groundspeak:logs>
<? } ?>
<?php } ?>
</groundspeak:cache>
<? } ?>
<? if ($vars['ns_ox']) { /* Does user want us to include Garmin's <opencaching> element? */ ?>
<?php } ?>
<?php if ($vars['ns_ox']) { /* Does user want us to include Garmin's <opencaching> element? */ ?>
<ox:opencaching xmlns:ox="http://www.opencaching.com/xmlschemas/opencaching/1/0">
<ox:ratings>
<? if ($c['rating'] !== null) { ?><ox:awesomeness><?= $c['rating'] ?></ox:awesomeness><? } ?>
<?php if ($c['rating'] !== null) { ?><ox:awesomeness><?= $c['rating'] ?></ox:awesomeness><?php } ?>
<ox:difficulty><?= $c['difficulty'] ?></ox:difficulty>
<? if ($c['oxsize'] !== null) { ?><ox:size><?= $c['oxsize'] ?></ox:size><? } ?>
<?php if ($c['oxsize'] !== null) { ?><ox:size><?= $c['oxsize'] ?></ox:size><?php } ?>
<ox:terrain><?= $c['terrain'] ?></ox:terrain>
</ox:ratings>
<? if (in_array('ox:tags', $vars['attrs']) && count($c['attrnames']) > 0) { /* Does user want us to include ox:tags? */ ?>
<?php if (in_array('ox:tags', $vars['attrs']) && count($c['attrnames']) > 0) { /* Does user want us to include ox:tags? */ ?>
<ox:tags><ox:tag><?= implode("</ox:tag><ox:tag>", $c['attrnames']) ?></ox:tag></ox:tags>
<? } ?>
<? if ((strpos($vars['images'], "ox:") === 0) && count($c['images']) > 0) { /* Does user want us to include ox:image references? */ ?>
<?php } ?>
<?php if ((strpos($vars['images'], "ox:") === 0) && count($c['images']) > 0) { /* Does user want us to include ox:image references? */ ?>
<ox:images>
<? foreach ($c['images'] as $no => $img) { ?>
<?php foreach ($c['images'] as $no => $img) { ?>
<ox:image>
<ox:name><?= $img['unique_caption'] ?>.jpg</ox:name>
<ox:size>0</ox:size>
<ox:required>false</ox:required>
<ox:spoiler><?= ($img['is_spoiler'] ? "true" : "false") ?></ox:spoiler>
</ox:image>
<? } ?>
<?php } ?>
</ox:images>
<? } ?>
<?php } ?>
</ox:opencaching>
<? } ?>
<?php } ?>
</wpt>
<?
<?php
if (isset($cache_ref['ggz_entry'])) {
$cache_ref['ggz_entry']['file_len'] = ob_get_length() - $cache_ref['ggz_entry']['file_pos'];
}
?>
<? if ($vars['alt_wpts']) { ?>
<? foreach ($cache_ref['alt_wpts'] as &$wpt_ref) { ?>
<?
<?php if ($vars['alt_wpts']) { ?>
<?php foreach ($cache_ref['alt_wpts'] as &$wpt_ref) { ?>
<?php
if (isset($wpt_ref['ggz_entry'])) {
$wpt_ref['ggz_entry']['file_pos'] = ob_get_length();
}
@@ -213,18 +213,18 @@ http://www.gsak.net/xmlv1/5 http://www.gsak.net/xmlv1/5/gsak.xsd
<urlname><?= Okapi::xmlescape($c['name']) ?></urlname>
<sym><?= $wpt['sym'] ?></sym>
<type>Waypoint|<?= $wpt['sym'] ?></type>
<? if ($vars['ns_gsak']) { ?>
<?php if ($vars['ns_gsak']) { ?>
<gsak:wptExtension xmlns:gsak="http://www.gsak.net/xmlv1/5">
<gsak:Parent><?= $c['code'] ?></gsak:Parent>
</gsak:wptExtension>
<? } ?>
<?php } ?>
</wpt>
<?
<?php
if (isset($wpt_ref['ggz_entry'])){
$wpt_ref['ggz_entry']['file_len'] = ob_get_length() - $wpt_ref['ggz_entry']['file_pos'];
}
?>
<? } ?>
<? } ?>
<? } ?>
<?php } ?>
<?php } ?>
<?php } ?>
</gpx>

View File

@@ -187,6 +187,7 @@ class WebService
$response->content_type = $tile->get_content_type();
$response->cache_control = "Cache-Control: private, max-age=600";
$response->etag = 'W/"'.$image_fingerprint.'"';
$response->allow_gzip = false; // images are usually compressed, prevent compression at Apache level
# Check if the request didn't include the same ETag.

View File

@@ -160,6 +160,15 @@
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='powertrail_only' default='false'>
<p>BETA. Boolean. If set to <b>true</b>, only caches which belong to at
least one <b>active</b> Power Trail will be included.</p>
<p>Power Trails are sets of geocaches. Only some Opencaching nodes
implement this feature. If this node does not implement it, and you
will set this parameter to <b>true</b>, then you will <b>always</b>
receive an empty result.</p>
</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

View File

@@ -57,18 +57,28 @@ class WebService
$search_assistant->prepare_location_search_params();
$where_conds = array();
$where_conds[] = $search_assistant->get_latitude_expr()." between '".mysql_real_escape_string($bbsouth)."' and '".mysql_real_escape_string($bbnorth)."'";
$lat = $search_assistant->get_latitude_expr();
$lon = $search_assistant->get_longitude_expr();
$where_conds[] = "(
$lat >= '".mysql_real_escape_string($bbsouth)."'
and $lat < '".mysql_real_escape_string($bbnorth)."'
)";
if ($bbeast > $bbwest)
{
# Easy one.
$where_conds[] = $search_assistant->get_longitude_expr()." between '".mysql_real_escape_string($bbwest)."' and '".mysql_real_escape_string($bbeast)."'";
$where_conds[] = "(
$lon >= '".mysql_real_escape_string($bbwest)."'
and $lon < '".mysql_real_escape_string($bbeast)."'
)";
}
else
{
# We'll have to assume that this box goes through the 180-degree meridian.
# We'll have to assume that this bbox goes through the 180-degree meridian.
# For example, $bbwest = 179 and $bbeast = -179.
$where_conds[] = "(".$search_assistant->get_longitude_expr()." > '".mysql_real_escape_string($bbwest)
."' or ".$search_assistant->get_longitude_expr()." < '".mysql_real_escape_string($bbeast)."')";
$where_conds[] = "(
$lon >= '".mysql_real_escape_string($bbwest)."'
or $lon < '".mysql_real_escape_string($bbeast)."'
)";
}
#

View File

@@ -74,12 +74,18 @@ class WebService
array('caches.wp_oc is not null'),
$search_params['where_conds']
);
if (isset($search_params['extra_joins']) && is_array($search_params['extra_joins']))
$joins = $search_params['extra_joins'];
else
$joins = array();
unset($search_params);
# Generate, or retrieve an existing set, and return the result.
# All user-supplied data in $tables and $where_conds MUST be escaped!
$result = self::get_set($tables, $where_conds, $min_store, $ref_max_age);
$result = self::get_set($tables, $joins, $where_conds, $min_store, $ref_max_age);
return Okapi::formatted_response($request, $result);
}
@@ -87,11 +93,11 @@ class WebService
* Important: YOU HAVE TO make sure $tables and $where_conds don't contain
* unescaped user-supplied data!
*/
public static function get_set($tables, $where_conds, $min_store, $ref_max_age)
public static function get_set($tables, $joins, $where_conds, $min_store, $ref_max_age)
{
# Compute the "params hash".
$params_hash = md5(serialize(array($tables, $where_conds)));
$params_hash = md5(serialize(array($tables, $joins, $where_conds)));
# Check if there exists an entry for this hash, which also meets the
# given freshness criteria.
@@ -133,7 +139,9 @@ class WebService
select distinct
'".mysql_real_escape_string($set_id)."',
caches.cache_id
from ".implode(", ", $tables)."
from
".implode(", ", $tables)."
".implode(" ", $joins)."
where (".implode(") and (", $where_conds).")
");

View File

@@ -514,13 +514,35 @@ class SearchAssistant
if ($tmp = $this->request->get_parameter('ftf_hunter'))
{
if (!in_array($tmp, array('true', 'false'), 1))
throw new InvalidParam('not_yet_found_only', "'$tmp'");
throw new InvalidParam('ftf_hunter', "'$tmp'");
if ($tmp == 'true')
{
$where_conds[] = "$X_FOUNDS = 0";
}
}
#
# powertrail_only
#
if (!is_null($tmp = $this->request->get_parameter('powertrail_only')))
{
if (!in_array($tmp, array('true', 'false'), 1))
throw new InvalidParam('powertrail_only', "'$tmp'");
if ($tmp == 'true')
{
if (Settings::get('OC_BRANCH') == 'oc.pl') {
$extra_joins[] = 'inner join powerTrail_caches on powerTrail_caches.cacheId = caches.cache_id';
#
# Filter out caches from inactive powerTrails as well
#
$extra_joins[] = 'inner join PowerTrail on PowerTrail.id = powerTrail_caches.powerTrailId';
$where_conds[] = 'PowerTrail.status = 1';
} else {
$where_conds[] = "0=1";
}
}
}
#
# set_and
#

View File

@@ -30,6 +30,13 @@
and may depend on the installation
(<a href='https://code.google.com/p/opencaching-api/issues/detail?id=124'>more info</a>).</p>
<p><b>Important note:</b> Unfortunatelly, some OC nodes don't support this
parameter properly - regardless of what you choose, you may end up with
<a href='https://code.google.com/p/opencaching-api/issues/detail?id=324'>unexpected
results</a>. Currently, there is nothing OKAPI developers can do
to fix this, but you should use this parameter either way (to indicate how
you <b>expect</b> it to behave) - we hope this will be fixed, eventually.</p>
<p><b>Important note:</b> The subset of allowed HTML elements is left undefined
and may change in the future. For future-compatibility, you should use only
basic formatting tags.</p>

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi\services\oauth\request_token;

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi;

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi;

View File

@@ -137,7 +137,10 @@ class View
# back to the Consumer application with an error.
if ($token['callback']) {
return new OkapiRedirectResponse($token['callback'].$callback_concat_char."error=access_denied");
return new OkapiRedirectResponse(
$token['callback'].$callback_concat_char."error=access_denied".
"&oauth_token=".$token['key']
);
} else {
# Consumer did not provide a callback URL (oauth_callback=oob).
# We'll have to redirect to the Opencaching main page then...

View File

@@ -36,16 +36,16 @@
<div style='float: right; clear: right; margin: 10px 0 10px 30px'>
Choose your language:
<select id='langpref' style='border: 1px solid #ccc'>
<? foreach ($vars['locales'] as $locale => $attrs) { ?>
<?php foreach ($vars['locales'] as $locale => $attrs) { ?>
<option value='<?= $attrs['lang'] ?>' <?= ($attrs['locale'] == $vars['locale_displayed']) ? "selected" : "" ?>><?= $attrs['name'] ?></option>
<? } ?>
<?php } ?>
</select>
</div>
<? if (isset($vars['token_expired']) && $vars['token_expired']) { ?>
<?php if (isset($vars['token_expired']) && $vars['token_expired']) { ?>
<h1 style='clear: both'><?= _("Expired request") ?></h1>
<p><?= _("Unfortunately, the request has expired. Please try again.") ?></p>
<? } elseif ($vars['token']) { ?>
<?php } elseif ($vars['token']) { ?>
<h1 style='clear: both'><?= _("External application is requesting access...") ?></h1>
<p><?= sprintf(_("<b>%s</b> wants to access your <b>%s</b> account. Do you agree to grant access to this application?"), htmlentities($vars['token']['consumer_name']), $vars['site_name']) ?></p>
<form id='authform' method='POST' class='form'>
@@ -61,7 +61,7 @@
by the OKAPI Framework, i.e. post log entries on geocaches in your name.
You can revoke this permission at any moment.</p>
"), $vars['okapi_base_url']."apps/", $vars['okapi_base_url']) ?>
<? } ?>
<?php } ?>
</div>
</body>

View File

@@ -27,7 +27,7 @@
<a href='<?= $vars['site_url'] ?>' class='opencaching'><?= $vars['site_name'] ?></a>
<h1 style='clear: both'><?= _("Your external applications") ?></h1>
<? if (count($vars['apps']) > 0) { ?>
<?php if (count($vars['apps']) > 0) { ?>
<?= sprintf(_("
<p>This is the list of applications which you granted access to your <b>%s</b> account.
This page gives you the abbility to revoke all previously granted privileges.
@@ -35,24 +35,24 @@
actions on your behalf.</p>
"), $vars['site_name']) ?>
<ul>
<? foreach ($vars['apps'] as $app) { ?>
<?php foreach ($vars['apps'] as $app) { ?>
<li>
<? if ($app['url']) { ?>
<?php if ($app['url']) { ?>
<a href='<?= htmlspecialchars($app['url'], ENT_QUOTES, 'utf-8') ?>'><?= htmlspecialchars($app['name'], ENT_QUOTES, 'utf-8') ?></a>
<? } else { ?>
<?php } else { ?>
<?= htmlspecialchars($app['name'], ENT_QUOTES, 'utf-8') ?>
<? } ?>
<?php } ?>
- <a href='<?= $vars['okapi_base_url'] ?>apps/revoke_access?consumer_key=<?= $app['key'] ?>'><?= _("remove") ?></a>
</li>
<? } ?>
<?php } ?>
</ul>
<? } else { ?>
<?php } else { ?>
<?= sprintf(_("
<p>Thanks to the <a href='%s'>OKAPI Framework</a> you can grant external applications
access to your <b>%s</b> account. Currently no applications are authorized to act
on your behalf. Once you start using external Opencaching applications, they will appear here.</p>
"), $vars['okapi_base_url'], $vars['site_name']) ?>
<? } ?>
<?php } ?>
</div>
</body>

View File

@@ -1,4 +1,4 @@
<?
<?php
/**
TODO: make it work even without ';' delimiters or at least warn about that
TODO: better parse error reporting

View File

@@ -20,7 +20,7 @@
<body class='api'>
<div class='okd_mid'>
<div class='okd_top'>
<? include 'installations_box.tpl.php'; ?>
<?php include 'installations_box.tpl.php'; ?>
<table cellspacing='0' cellpadding='0'><tr>
<td class='apimenu'>
<?= $vars['menu'] ?>

View File

@@ -9,7 +9,7 @@
<body class='api'>
<div class='okd_mid'>
<div class='okd_top'>
<? include 'installations_box.tpl.php'; ?>
<?php include 'installations_box.tpl.php'; ?>
<table cellspacing='0' cellpadding='0'><tr>
<td class='apimenu'>
<?= $vars['menu'] ?>

View File

@@ -1,9 +1,9 @@
<div class='okd_top_pad'>
<div class='okd_switcher'>
<select id='switcher'>
<? foreach ($vars['installations'] as $inst) { ?>
<?php foreach ($vars['installations'] as $inst) { ?>
<option value='<?= $inst['okapi_base_url'] ?>'<?= $inst['selected'] ? " selected current='true'" : "" ?>><?= $inst['site_name'] ?></option>
<? } ?>
<?php } ?>
</select>
</div>
</div>

View File

@@ -14,7 +14,7 @@
<body class='api'>
<div class='okd_mid'>
<div class='okd_top'>
<? include 'installations_box.tpl.php'; ?>
<?php include 'installations_box.tpl.php'; ?>
<table cellspacing='0' cellpadding='0'><tr>
<td class='apimenu'>
<?= $vars['menu'] ?>
@@ -26,53 +26,66 @@
<div class='subh1'>:: <b>Opencaching API</b> Reference</div>
</h1>
<p><b>OKAPI</b> is a <b>public API</b> project for National Opencaching sites (also known as
Opencaching Nodes).</p>
<p><b>OKAPI</b> is a publically available
<a href='http://en.wikipedia.org/wiki/Application_programming_interface'>API</a>
for "National Opencaching" sites (also known as Opencaching National Nodes).</p>
<ul>
<li>It provides OC site with a set of useful well-documented API methods,</li>
<li>Allows external developers to easily <b>read public</b> Opencaching data,</li>
<li>Allows <b>read and write private</b> (user-related) data with OAuth 3-legged authentication.</li>
<li>Allows <b>read and write private</b> (user-related) data with OAuth Authentication.</li>
</ul>
<p>The project is aiming to become a standard API for all National Opencaching.<i>xx</i> sites.
This OKAPI installation provides API for the
<p>The project has grown to become a standard and common API for all National
Opencaching.<i>xx</i> sites. This OKAPI installation provides services for the
<a href='<?= $vars['site_url']; ?>'><?= $vars['site_url']; ?></a> site.
Check out other OKAPI installations:</p>
Here is the list of other OKAPI installations:</p>
<ul>
<? foreach ($vars['installations'] as $inst) { ?>
<li><?= $inst['site_name'] ?> - <a href='<?= $inst['okapi_base_url'] ?>'><?= $inst['okapi_base_url'] ?></a></li>
<? } ?>
<?php foreach ($vars['installations'] as $inst) { ?>
<li>
<?= $inst['site_name'] ?><? if ($inst['site_name'] == "Opencaching.DE") { print "*"; } ?>
- <a href='<?= $inst['okapi_base_url'] ?>'><?= $inst['okapi_base_url'] ?></a>
</li>
<?php } ?>
</ul>
<p>Opencaching.DE includes the sites Opencaching.IT and OpencachingSpain.ES,
which are "national views" of Opencaching.DE. All three share one
database, so you can access all their data through the Opencaching.DE
OKAPI installation and select Italian or Spanish language.</p>
<p>Other links you might want to check out:</p>
<p>And also:</p>
<ul>
<li>OKAPI Project Homepage - <a href='http://code.google.com/p/opencaching-api/'>http://code.google.com/p/opencaching-api/</a></li>
<li>OKAPI News blog - <a href='http://opencaching-api.blogspot.com/'>http://opencaching-api.blogspot.com/</a></li>
</ul>
<p>* - Opencaching.DE includes other sites - Opencaching.IT and
OpencachingSpain.ES - which are in fact the one site visible on multiple
domains. All three share one database, so you can access all their data through
Opencaching.DE OKAPI installation.</p>
<div class='issue-comments' issue_id='28'></div>
<h2 id='howto'>How can I use OKAPI?</h2>
<p>We assume that you're a software developer and you know the basics.</p>
<p><b>OKAPI is a set of simple (REST) web services.</b> Basicly, you make a proper HTTP request,
and you receive a JSON-formatted data, that you may parse and use within your own application.</p>
<p><b>Example.</b> Click the following link to run a method that prints out the list of
all available methods:</p>
<p>OKAPI is a set of simple web services. Basicly, you make a proper HTTP
request, and you receive a JSON-formatted response, that you may parse and use
within your application.</p>
<p><b>Example.</b> Click the following link to run a method that prints out the
list of all available methods:</p>
<ul>
<li>
<p><a href='<?= $vars['site_url'] ?>okapi/services/apiref/method_index'><?= $vars['site_url'] ?>okapi/services/apiref/method_index</a></p>
<p>Note: You need to install a proper <a href='https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc'>Chrome</a>
or <a href='https://addons.mozilla.org/en-US/firefox/addon/jsonview/'>Firefox</a> extension
in order to view JSON directly in your browser.</p>
<p>Note: You might need to install a proper browser addon (like
<a href='https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc'>this one</a>
or <a href='https://addons.mozilla.org/en-US/firefox/addon/jsonview/'>this one</a>)
in order to view formatted JSON data directly in your browser.</p>
</li>
</ul>
<p>You've made your first OKAPI request! This method was a simple one.
<p>You've made your first OKAPI request! This method was a simple one though.
It didn't require any arguments and it didn't require you to use a Consumer Key.
Other methods are more complex and require you to use
<a href='<?= $vars['site_url'] ?>okapi/signup.html'>your own API key</a>.</p>
@@ -80,26 +93,33 @@ Other methods are more complex and require you to use
<h2 id='auth_levels'>Authentication Levels</h2>
<p>Each OKAPI method has a <b>minimum authentication level</b>.</p>
<p>This means, that if you want to call a method which requires "Level 1"
authentication, you have to use "Level 1" authentication <b>or higher</b>
("Level 2" or "Level 3" will also work).</p>
<p><b>Important:</b> Most developers will only need to use "Level 1" authentication
and don't have to care about OAuth.</p>
<p><b>Important:</b> Most developers will only need to use "Level 1"
authentication and don't have learn OAuth.</p>
<ul>
<li>
<p><b>Level 0.</b> Anonymous. You may call this method with no extra
arguments.</p>
<p><b>Level 0.</b> Anonymous. You may call such methods with no extra
authentication-related arguments.</p>
<p><code>some_method?arg=44</code></p>
</li>
<li>
<p><b>Level 1.</b> Simple Consumer Authentication. You must call this
method with <b>consumer_key</b> argument and provide the key which has
been generated for your application on the <a href='<?= $vars['site_url'] ?>okapi/signup.html'>Sign up</a> page.</p>
been generated for your application on the
<a href='<?= $vars['site_url'] ?>okapi/signup.html'>Sign up</a> page.</p>
<p><code>some_method?arg=44&consumer_key=a7Lkeqf8CjNQTL522dH8</code></p>
</li>
<li>
<p><b>Level 2.</b> OAuth Consumer Signature. You must call this method
with proper OAuth Consumer signature (based on your Consumer Secret).</p>
<p><code>some_method<br>
?arg=44<br>
&oauth_consumer_key=a7Lkeqf8CjNQTL522dH8<br>
@@ -110,9 +130,10 @@ and don't have to care about OAuth.</p>
&oauth_signature=mWEpK2e%2fm8QYZk1BMm%2fRR74B3Co%3d</code></p>
</li>
<li>
<p><b>Level 3.</b> OAuth Consumer+Token Signature. You must call this method
with proper OAuth Consumer+Token signature (based on both Consumer Secret and
Token Secret).</p>
<p><b>Level 3.</b> OAuth Consumer+Token Signature. You must call this
method with proper OAuth Consumer+Token signature (based on both
Consumer Secret and Token Secret).</p>
<p><code>some_method<br>
?arg=44<br>
&oauth_consumer_key=a7Lkeqf8CjNQTL522dH8<br>
@@ -129,72 +150,86 @@ and don't have to care about OAuth.</p>
<h2 id='http_methods'>GET or POST?</h2>
<p>Whichever you want. OKAPI will treat GET and POST requests as equal.
You may also use the HTTP <code>Authorization</code> header for passing OAuth arguments.
OKAPI does not allow usage of PUT and DELETE requests.</p>
<p>Whichever you want. OKAPI will treat GET and POST requests as equal. You may
also use the HTTP <code>Authorization</code> header for passing OAuth
arguments. OKAPI does not support other HTTP request types (such as PUT or
DELETE).</p>
<h2 id='html'>About HTML fields</h2>
<h2 id='html'>A warning about HTML fields</h2>
<p>There are many HTML-formatted fields in OKAPI. However, most of them come directly
from the underlying Opencaching database. These fields are <b>not validated by OKAPI</b>.
They <b>may</b> be validated by some other Opencaching code
(prior to inserting it to the database), but we cannot guarantee it. And you shouldn't
count on it too. You must assume that HTML content may contain anything, e.g.
<p>Many of the fields returned in OKAPI responses are formatted in HTML.
However, most of these fields come directly from the underlying Opencaching
database. Currently, these fields are <b>not validated by OKAPI</b>. They
<b>should</b> be validated by the Opencaching site itself (prior to inserting
it to the database), but we cannot guarantee that they will be. And you
shouldn't count on it too.</p>
<p>You must assume that our HTML content may contain anything, including
invalid HTML markup, tracking images (pixels), or even
<a href='http://en.wikipedia.org/wiki/Cross-site_scripting'>XSS vectors</a>.
This also applies to the descriptions included in the GPX files.</p>
<h2 id='common-formatting'>Common formatting parameters</h2>
<p>Most of the methods return simple objects, such as lists and dictionaries
of strings and integers. Such objects can be formatted in several ways using
<i>common formatting parameters</i> (supplied by you along all the other
parameters required for the method to run):</p>
<p>Most of the methods return simple objects, such as lists and dictionaries of
strings and integers. We may format such objects for you in several ways. If
you want to change the default (JSON) then you should include <i>common
formatting parameters</i> in your request:</p>
<ul>
<li>
<p><b>format</b> - name of the format in which you'd like your result
to be returned in. Currently supported output formats include:</p>
<ul>
<li>
<p><b>json</b> - <a href='http://en.wikipedia.org/wiki/JSON'>JSON</a> format (default),</p>
<p>Use <a href='https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc'>Chrome</a>
or <a href='https://addons.mozilla.org/en-US/firefox/addon/jsonview/'>Firefox</a> extensions
to view JSON results directly in your browser. This simplifies debugging a lot!</p>
<p><b>json</b> - <a href='http://en.wikipedia.org/wiki/JSON'>JSON</a>
format (default),</p>
</li>
<li>
<b>jsonp</b> - <a href='http://en.wikipedia.org/wiki/JSONP'>JSONP</a>
format (if you choose this one then you have to specify the
<b>callback</b> parameter too),
</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
data types to XML elements.
</li>
<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 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>
</li>
<li>
<b>callback</b> - (when using JSONP output format) name of the JavaScript function
to be executed with the result as its parameter.
<b>callback</b> - (when using JSONP output format) name of the
JavaScript function to be executed with the result as its parameter.
</li>
</ul>
<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 occurrences if it doesn't understand them!</p>
<p>Some methods expose formatting of their own, for example, they may return a
JPEG or GPX file. Such methods do not accept <i>common formatting
parameters</i>.</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>
<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 occurrences if it doesn't understand them!</p>
<div class='issue-comments' issue_id='30'></div>
<h2 id='oauth'>OAuth Dance URLs</h2>
<p>If you want to use <b>Level 3</b> methods, you will have to make "the OAuth dance" (series of
method calls and redirects which provide you with an Access Token).</p>
<p>The three OAuth request URLs defined in the <a href='http://oauth.net/core/1.0a/'>OAuth specification</a> are:</p>
<p>If you want to use <b>Level 3</b> methods, you will first have to perform
"the OAuth dance" (series of method calls and redirects which provide you with
an Access Token).</p>
<p>The three OAuth request URLs defined in the
<a href='http://oauth.net/core/1.0a/'>OAuth specification</a> are:</p>
<ul>
<li>
<a href='<?= $vars['site_url'] ?>okapi/services/oauth/request_token'><?= $vars['site_url'] ?>okapi/services/oauth/request_token</a>
@@ -211,26 +246,35 @@ method calls and redirects which provide you with an Access Token).</p>
</ul>
<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>
<p>As the OAuth 1.0a specification states, it should be set to "<i>oob</i>" or a callback URL
(this usually starts with http:// or https://, but you can use any other myapp:// scheme).</p>
<p>The <b>oauth_callback</b> argument of the <b>request_token</b>
method is <b>required</b>.</p>
<p>As the OAuth 1.0a specification states, it should be set to
"<i>oob</i>" or a callback URL (this usually starts with http:// or
https://, but you can use any other myapp:// scheme).</p>
<p>For most OAuth client libraries, you just should provide
"<i><?= $vars['site_url'] ?>okapi/services/oauth/request_token?oauth_callback=oob</i>"
as the request_token URL, to get it started. Later, probably you'd want to switch "oob"
to something more useful.</p>
as the request_token URL, to get it started. Later, probably you'd want
to switch "oob" to something more useful.</p>
</li>
<li>
<p>The <b>oauth_verifier</b> argument of the <b>access_token</b> method is also <b>required</b>.</p>
<p>When user authorizes your application, he will receive a PIN code (OAuth verifier). You
have to use this code to receive your Access Token.</p>
<p>The <b>oauth_verifier</b> argument of the <b>access_token</b> method
is also <b>required</b>.</p>
<p>When user authorizes your application, he will receive a PIN code
(OAuth verifier). You have to use this code to receive your Access
Token.</p>
</li>
<li>
<p><b>Access Tokens do not expire</b> (but can be revoked). This means, that once the user
authorizes your application, you receive a "lifetime access" to his/her account.
User may still <b>revoke access</b> to his account from your
application - when this happens, you will have to redo the authorization dance.</p>
<p><b>Access Tokens do not expire</b> (but can be revoked). This means,
that once the user authorizes your application, you receive a "lifetime
access" to his/her account. User may still <b>revoke access</b> to his
account from your application - when this happens, you will have to
redo the authorization dance.</p>
</li>
</ul>
@@ -239,145 +283,192 @@ method calls and redirects which provide you with an Access Token).</p>
<h2 id='errors'>Advanced error handling</h2>
<p>Basic rules apply:</p>
<ul>
<li>If all goes well, OKAPI will respond with a <b>HTTP 200</b> status.</li>
<li>If there is something wrong with your request, you will get a <b>HTTP 4xx</b>
response (with a JSON object described below). These kind of responses should
trigger some kind of an exception inside your application.</li>
<li>If there is something wrong with your request, you will get a <b>HTTP
4xx</b> response. The body will contain a JSON object describing the error
(see below). These kind of responses should trigger some kind of an
exception inside your application.</li>
<li>If something goes wrong <b>on our part</b>, you will get a <b>HTTP 5xx</b> response.
We will try to fix such errors as soon as possible.</li>
<li>If something goes wrong <b>on our part</b>, you will get a <b>HTTP
5xx</b> response. Usually you should treat such errors as other I/O errors
(e.g. display a "No connection, try later" notice). We will try to fix such
errors as soon as possible.</li>
<li>Sometimes, due to invalid server configuration, you may receive <b>HTTP 200</b>
instead of <b>HTTP 500</b>. We know that's "unprofessional", but we cannot guarantee
that all OC servers are configured properly
<li>Sometimes, due to invalid server configuration, you may receive <b>HTTP
200</b> instead of <b>HTTP 500</b>. We know that's "unprofessional", but we
cannot guarantee that all OC servers are configured properly
(<a href='https://code.google.com/p/opencaching-api/issues/detail?id=293'>example</a>).
If you get <b>HTTP 200</b> <u>and</u> you cannot parse the server response, you should
treat it as <b>HTTP 500</b>.</li>
If you get <b>HTTP 200</b> <u>and</u> you cannot parse the server response,
you should treat it as <b>HTTP 500</b>.</li>
</ul>
<p>Each <b>HTTP 4xx</b> error will be properly described in the response, using a <b>JSON error
response</b>. You may retrieve the body of such response and use it inside your application
(for example, to construct various exception subclasses). In most of the cases, only OAuth applications
need to do this. All other applications are fine with threating all HTTP 4xx errors the same.</p>
<p>Every <b>HTTP 4xx</b> error will contain an additional description in the
response body. This description will be <b>always</b> formatted in JSON
(regardless of the <b>format</b> parameter you might have used in your
request). You can use this information to pinpoint the source of the error.</p>
<p>The error response is a dictionary with a single <b>error</b> key. Its value
contains <b>at least</b> the following keys:</p>
<p>The error response is a dictionary with a single <b>error</b> key. Its value contains
<b>at least</b> the following keys:</p>
<ul>
<li><b>developer_message</b> - description of the error,</li>
<li><b>reason_stack</b> - list of keywords (see below for valid values) which may be
use to subclass exceptions,</li>
<li><b>status</b> - HTTP status code (the same which you'll get in response headers),
<li><b>more_info</b> - url pointing to a more detailed description of the error
(or, more probably, to the page you're now reading).</li>
<li>
<b>reason_stack</b> - a list of keywords which depicts the exception's
position in our exception class hierarchy (see below for valid values),
</li>
<li>
<b>status</b> - HTTP status code (the same which you'll get in response
HTTP header),
</li>
<li>
<b>more_info</b> - url pointing to a more detailed description of the
error (or, more probably, to the page you're now reading).
</li>
</ul>
<p>Depending on the values on the <b>reason_stack</b>, the <b>error</b> dictionary may
contain additional keys. Possible values of the <b>reason_stack</b> include:</p>
<p>Depending on the values on the <b>reason_stack</b>, the <b>error</b>
dictionary may contain additional keys. Currently possible values of the
<b>reason_stack</b> include:</p>
<ul>
<li>
<p><b>["bad_request"]</b> - you've made a bad request.
<p>Subclasses:</p>
<ul>
<li>
<p><b>[ ... , "missing_parameter"]</b> - you didn't supply a required
parameter. Extra keys:</p>
<p><b>[ "bad_request", "missing_parameter"]</b> - you didn't
supply a required parameter. Extra keys:</p>
<ul>
<li><b>parameter</b> - the name of the missing parameter.</li>
</ul>
</li>
<li>
<p><b>[ ... , "invalid_parameter"]</b> - one of your parameters
has invalid value. Extra keys:</p>
<p><b>[ "bad_request", "invalid_parameter"]</b> - one of your
parameters has an invalid value. Extra keys:</p>
<ul>
<li><b>parameter</b> - the name of the parameter,</li>
<li><b>whats_wrong_about_it</b> - description of what was wrong about it.</li>
<li>
<b>whats_wrong_about_it</b> - a string, pretty
self-explanatory,
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><b>["invalid_oauth_request"]</b> - you've tried to use OAuth, but your request
was invalid.</p>
<p><b>["invalid_oauth_request"]</b> - you've tried to use OAuth, but
your request was invalid.</p>
<p>Subclasses:</p>
<ul>
<li>
<p><b>[ ... , "unsupported_oauth_version"]</b> - you tried
to use unsupported OAuth version (OKAPI requires OAuth 1.0a).</p>
<p><b>[ "invalid_oauth_request", "unsupported_oauth_version"]</b>
- you have tried to use unsupported OAuth version (OKAPI
requires OAuth 1.0a).</p>
</li>
<li>
<p><b>[ ... , "missing_parameter"]</b> - you didn't supply
a required parameter. Extra keys:</p>
<p><b>[ "invalid_oauth_request", "missing_parameter"]</b> - you
didn't supply a required parameter. Extra keys:</p>
<ul>
<li><b>parameter</b> - the name of the missing parameter.</li>
</ul>
</li>
<li>
<p><b>[ ... , "unsupported_signature_method"]</b> - you
tried to use an unsupported OAuth signature method (OKAPI requires
HMAC-SHA1).</p>
<p><b>[ "invalid_oauth_request", "unsupported_signature_method"]</b>
- you tried to use an unsupported OAuth signature method (OKAPI
requires HMAC-SHA1).</p>
</li>
<li>
<p><b>[ ... , "invalid_consumer"]</b> - your consumer
does not exist.</p>
<p><b>[ "invalid_oauth_request", "invalid_consumer"]</b> - your
consumer does not exist.</p>
</li>
<li>
<p><b>[ ... , "invalid_token"]</b> - your token
does not exist. This is pretty common, it may have expired (in case
of request tokens) or may have been revoked (in case of access tokens).
You should ask your user to redo the authorization dance.</p>
<p><b>[ "invalid_oauth_request", "invalid_token"]</b> - your
token does not exist. This error is pretty common! Your token
may have expired (in case of request tokens) or may have been
revoked (in case of access tokens). You should ask your user to
redo the authorization dance.</p>
</li>
<li>
<p><b>[ ... , "invalid_signature"]</b> - your request
signature was invalid.</p>
<p><b>[ "invalid_oauth_request", "invalid_signature"]</b> -
your request signature was invalid.</p>
</li>
<li>
<p><b>[ ... , "invalid_timestamp"]</b> - you used a timestamp
which was too far off, compared to the current server time. This is
pretty common, especially when your app is for mobile phones. You should
ask your user to fix the clock or use the provided extra keys to adjust
it yourself. Extra keys:</p>
<p><b>[ "invalid_oauth_request", "invalid_timestamp"]</b> - you
used a timestamp which was too far off, compared to the current
server time. This error is pretty common, especially if your
app is for mobile phones! You may ask your user to fix his
clock, <b><u>or</u></b> you could use the provided extra keys
to adjust your <b>oauth_timestamp</b> parameter yourself. Extra
keys:</p>
<ul>
<li>
<b>yours</b> - timestamp you have supplied (this used to be a
string, but now it is being casted to an integer, see
<b>yours</b> - UNIX timestamp you have supplied (this
used to be a string, but now it is being casted to an
integer, see
<a href='https://code.google.com/p/opencaching-api/issues/detail?id=314'>here</a>),
</li>
<li><b>ours</b> - timestamp on our server,</li>
<li><b>difference</b> - the difference (to be added to your clock),</li>
<li><b>ours</b> - UNIX timestamp on our server,</li>
<li>
<b>difference</b> - the difference, in seconds (to be
added to your clock),
</li>
<li><b>threshold</b> - the maximum allowed difference.</li>
</ul>
</li>
<li>
<p><b>[ ... , "nonce_already_used"]</b> - most probably,
you have supplied the same request twice (user double-clicked something?).
Or, you have some error in the nonce generator in your OAuth client.</p>
<p><b>[ "invalid_oauth_request", "nonce_already_used"]</b> -
most probably, you have supplied the same request twice (user
double-clicked something?). Or, you have some error in the
nonce generator in your OAuth client.</p>
</li>
</ul>
</li>
</ul>
<p>Almost always, you should be fine with catching just three of those:</p>
<p>In most cases, you should be fine with catching just the following (order
significant):</p>
<ul>
<li><b>["invalid_oauth_request", "invalid_token"]</b> (reauthorize),</li>
<li><b>["invalid_oauth_request", "invalid_timestamp"]</b> (adjust the timestamp),</li>
<li>and <i>"any other 4xx status exception"</i> (send yourself a bug report).</li>
<li><i>any other 4xx status exception</i> (send yourself a bug report).</li>
<li>
All other errors - including HTTP 5xx responses, JSON parsing errors,
timeouts etc. - should be treated as connection errors (ask the user to
try later).
</li>
</ul>
<p><b>Important backward compatibility note:</b> If Z is a subclass of X, then
it will forever stay a subclass of X. However, class X won't necessarilly stay
Z's <i>direct</i> parent. This means that <b>["x", "z"]</b> may become <b>["x",
"y", "z"]</b> or even <b>["w", "x", "y", "z"]</b> one day. In other words,
instead writing <code>if (reason_stack == ["x", "z"]) { ... }</code> you should
rather use <code>if ("z" in reason_stack) { ... }</code>.</p>
<div class='issue-comments' issue_id='117'></div>
<h2 id='participate'>How can I participate in OKAPI development?</h2>
<p>OKAPI is Open Source and everyone is welcome to participate in the development.
In fact, if you'd like a particular method to exist, we encourage you to
submit your patches.</p>
<p>OKAPI is Open Source and everyone is welcome to participate in the development.</p>
<p>We have our <a href='http://code.google.com/p/opencaching-api/issues/list'>Issue tracker</a>.
You can use it to contact us!<br>You may also contact some of
<a href='http://code.google.com/p/opencaching-api/people/list'>the developers</a> directly,
if you want.</p>
<a href='http://code.google.com/p/opencaching-api/people/list'>the developers</a>
directly, if you want.</p>
<p>Visit <b>project homepage</b> for details:
<a href='http://code.google.com/p/opencaching-api/'>http://code.google.com/p/opencaching-api/</a></p>
@@ -388,9 +479,9 @@ if you want.</p>
<p>OKAPI web services (methods) currently available on this server:</p>
<ul>
<? foreach ($vars['method_index'] as $method_info) { ?>
<?php foreach ($vars['method_index'] as $method_info) { ?>
<li><a href='<?= $vars['site_url']."okapi/".$method_info['name'].".html" ?>'><?= $method_info['name'] ?></a> - <?= $method_info['brief_description'] ?></li>
<? } ?>
<?php } ?>
</ul>

View File

@@ -1,4 +1,4 @@
<?
<?php
namespace okapi\views\menu;

View File

@@ -1,4 +1,4 @@
<?
<?php
# Shortcuts
$m = $vars['method'];
@@ -20,7 +20,7 @@ $m = $vars['method'];
<body class='api'>
<div class='okd_mid'>
<div class='okd_top'>
<? include 'installations_box.tpl.php'; ?>
<?php include 'installations_box.tpl.php'; ?>
<table cellspacing='0' cellpadding='0'><tr>
<td class='apimenu'>
<?= $vars['menu'] ?>
@@ -50,30 +50,30 @@ $m = $vars['method'];
<?= $m['description'] ?>
</td>
</tr>
<? foreach ($m['arguments'] as $arg) { ?>
<?php foreach ($m['arguments'] as $arg) { ?>
<tr class='<?= $arg['class'] ?>' id='<?= 'arg_'.$arg['name'] ?>'>
<td class='argname'><?= $arg['name'] ?></td>
<td class='<? echo $arg['is_required'] ? 'required' : 'optional'; ?>'><? echo $arg['is_required'] ? 'required' : 'optional'; ?></td>
<td class='<?php echo $arg['is_required'] ? 'required' : 'optional'; ?>'><?php echo $arg['is_required'] ? 'required' : 'optional'; ?></td>
<td class='argdesc'>
<?= $arg['description'] ?>
</td>
</tr>
<? } ?>
<?php } ?>
<tr>
<td colspan='3' class='oauth_args'>
<? if ($m['auth_options']['min_auth_level'] == 0) { ?>
<?php if ($m['auth_options']['min_auth_level'] == 0) { ?>
No additional authentication parameters are required.
<? } elseif ($m['auth_options']['min_auth_level'] == 1) { ?>
<?php } elseif ($m['auth_options']['min_auth_level'] == 1) { ?>
<b>Plus required</b> <i>consumer_key</i> argument, assigned for your application.
<? } else { ?>
<?php } else { ?>
<b>Plus required</b>
standard OAuth Consumer signing arguments:
<i>oauth_consumer_key, oauth_nonce, oauth_timestamp, oauth_signature,
oauth_signature_method, oauth_version</i>.
<? if ($m['auth_options']['min_auth_level'] == 3) { ?>
<?php if ($m['auth_options']['min_auth_level'] == 3) { ?>
<b>Plus required</b> <i>oauth_token</i> for Token authorization.
<? } ?>
<? } ?>
<?php } ?>
<?php } ?>
</td>
</tr>
<tr><td colspan='3' class='returns'>
@@ -81,9 +81,9 @@ $m = $vars['method'];
<?= $m['returns'] ?>
</td></tr>
</table>
<? if ($m['issue_id']) { ?>
<?php if ($m['issue_id']) { ?>
<div class='issue-comments' issue_id='<?= $m['issue_id'] ?>'></div>
<? } ?>
<?php } ?>
</td>
</tr></table>
</div>

View File

@@ -38,7 +38,7 @@
<body class='api'>
<div class='okd_mid'>
<div class='okd_top'>
<? include 'installations_box.tpl.php'; ?>
<?php include 'installations_box.tpl.php'; ?>
<table cellspacing='0' cellpadding='0'><tr>
<td class='apimenu'>
<?= $vars['menu'] ?>