コード例 #1
0
ファイル: issue.php プロジェクト: PaulinaKowalczuk/oc-server3
 public static function call(OkapiRequest $request)
 {
     $issue_id = $request->get_parameter('issue_id');
     if (!$issue_id) {
         throw new ParamMissing('issue_id');
     }
     if (!preg_match("/^[0-9]+\$/", $issue_id) || strlen($issue_id) > 6) {
         throw new InvalidParam('issue_id');
     }
     $cache_key = "apiref/issue#" . $issue_id;
     $result = Cache::get($cache_key);
     if ($result == null) {
         # Download the number of comments from GitHub Issue Tracker.
         try {
             $extra_headers = array();
             $extra_headers[] = "Accept: application/vnd.github.v3.html+json";
             $extra_headers[] = "User-Agent: https://github.com/opencaching/okapi/";
             if (Settings::get('GITHUB_ACCESS_TOKEN')) {
                 $extra_headers[] = "Authorization: token " . Settings::get('GITHUB_ACCESS_TOKEN');
             }
             $opts = array('http' => array('method' => "GET", 'timeout' => 2.0, 'header' => implode("\r\n", $extra_headers)));
             $context = stream_context_create($opts);
             $json = file_get_contents("https://api.github.com/repos/opencaching/okapi/issues/{$issue_id}", false, $context);
         } catch (ErrorException $e) {
             throw new BadRequest("Sorry, we could not retrieve issue stats from the GitHub site. " . "This is probably due to a temporary connection problem. Try again later or contact " . "us if this seems permanent.");
         }
         $doc = json_decode($json, true);
         $result = array('id' => $issue_id + 0, 'last_updated' => $doc['updated_at'], 'title' => $doc['title'], 'url' => $doc['html_url'], 'comment_count' => $doc['comments']);
         # On one hand, we want newly added comments to show up quickly.
         # On the other, we don't want OKAPI to spam GitHub with queries.
         # So it's difficult to choose the best timeout for this.
         Cache::set($cache_key, $result, 3600);
     }
     return Okapi::formatted_response($request, $result);
 }
コード例 #2
0
ファイル: delete.php プロジェクト: kratenko/oc-server3
 static function call(OkapiRequest $request)
 {
     require_once 'log_images_common.inc.php';
     list($image_uuid, $log_internal_id) = LogImagesCommon::validate_image_uuid($request);
     $image_uuid_escaped = Db::escape_string($image_uuid);
     Db::execute('start transaction');
     $image_row = Db::select_row("\n            select id, node, url, local\n            from pictures\n            where uuid = '" . $image_uuid_escaped . "'\n        ");
     Db::execute("\n            delete from pictures where uuid = '" . $image_uuid_escaped . "'\n        ");
     # Remember that OCPL picture sequence numbers are always 1, and
     # OCDE sequence numbers may have gaps. So we do not need to adjust
     # any numbers after deleting from table 'pictures'.
     if (Settings::get('OC_BRANCH') == 'oc.de') {
         # OCDE does all the housekeeping by triggers
     } else {
         Db::execute("\n                INSERT INTO removed_objects (\n                    localID, uuid, type, removed_date, node\n                )\n                VALUES (\n                    " . $image_row['id'] . "\n                    '" . $image_uuid_escaped . "',\n                    6,\n                    NOW(),\n                    " . $image_row['node'] . "\n                )\n            ");
         # This will also update cache_logs.okapi_syncbase, so that replication
         # can output the updated log entry with one image less. For OCDE
         # that's done by DB trigges.
         Db::execute("\n                update cache_logs\n                set\n                    picturescount = greatest(0, picturescount - 1),\n                    last_modified = NOW()\n                where id = '" . Db::escape_string($log_internal_id) . "'\n            ");
     }
     Db::execute('commit');
     if ($image_row['local']) {
         $filename = basename($image_row['url']);
         unlink(Settings::get('IMAGES_DIR') . '/' . $filename);
     }
     $result = array('success' => true);
     return Okapi::formatted_response($request, $result);
 }
コード例 #3
0
ファイル: index.php プロジェクト: PaulinaKowalczuk/oc-server3
 public static function call()
 {
     $langpref = isset($_GET['langpref']) ? $_GET['langpref'] : Settings::get('SITELANG');
     $langprefs = explode("|", $langpref);
     # Determine which user is logged in to OC.
     require_once $GLOBALS['rootpath'] . "okapi/lib/oc_session.php";
     $OC_user_id = OCSession::get_user_id();
     if ($OC_user_id == null) {
         $after_login = "******" . ($langpref != Settings::get('SITELANG') ? "?langpref=" . $langpref : "");
         $login_url = Settings::get('SITE_URL') . "login.php?target=" . urlencode($after_login);
         return new OkapiRedirectResponse($login_url);
     }
     # Get the list of authorized apps.
     $rs = Db::query("\n            select c.`key`, c.name, c.url\n            from\n                okapi_consumers c,\n                okapi_authorizations a\n            where\n                a.user_id = '" . mysql_real_escape_string($OC_user_id) . "'\n                and c.`key` = a.consumer_key\n            order by c.name\n        ");
     $vars = array();
     $vars['okapi_base_url'] = Settings::get('SITE_URL') . "okapi/";
     $vars['site_url'] = Settings::get('SITE_URL');
     $vars['site_name'] = Okapi::get_normalized_site_name();
     $vars['site_logo'] = Settings::get('SITE_LOGO');
     $vars['apps'] = array();
     while ($row = mysql_fetch_assoc($rs)) {
         $vars['apps'][] = $row;
     }
     mysql_free_result($rs);
     $response = new OkapiHttpResponse();
     $response->content_type = "text/html; charset=utf-8";
     ob_start();
     Okapi::gettext_domain_init($langprefs);
     include 'index.tpl.php';
     $response->body = ob_get_clean();
     Okapi::gettext_domain_restore();
     return $response;
 }
コード例 #4
0
 /**
  * OCDE supports arbitrary ordering of log images. The pictures table
  * contains sequence numbers, which are always > 0 and need not to be
  * consecutive (may have gaps). There is a unique index which prevents
  * inserting duplicate seq numbers for the same log.
  *
  * OCPL sequence numbers currently are always = 1.
  *
  * The purpose of this function is to bring the supplied 'position'
  * parameter into bounds, and to calculate an appropriate sequence number
  * from it.
  *
  * This function is always called when adding images. When editing images,
  * it is called only for OCDE and if the position parameter was supplied.
  */
 static function prepare_position($log_internal_id, $position, $end_offset)
 {
     if (Settings::get('OC_BRANCH') == 'oc.de' && $position !== null) {
         # Prevent race conditions when creating sequence numbers if a
         # user tries to upload multiple images simultaneously. With a
         # few picture uploads per hour - most of them probably witout
         # a 'position' parameter - the lock is neglectable.
         Db::execute('lock tables pictures write');
     }
     $log_images_count = Db::select_value("\n            select count(*)\n            from pictures\n            where object_type = 1 and object_id = '" . Db::escape_string($log_internal_id) . "'\n        ");
     if (Settings::get('OC_BRANCH') == 'oc.pl') {
         # Ignore the position parameter, always insert at end.
         # Remember that this function is NOT called when editing OCPL images.
         $position = $log_images_count;
         $seq = 1;
     } else {
         if ($position === null || $position >= $log_images_count) {
             $position = $log_images_count - 1 + $end_offset;
             $seq = Db::select_value("\n                    select max(seq)\n                    from pictures\n                    where object_type = 1 and object_id = '" . Db::escape_string($log_internal_id) . "'\n                ") + $end_offset;
         } else {
             if ($position <= 0) {
                 $position = 0;
                 $seq = 1;
             } else {
                 $seq = Db::select_value("\n                    select seq\n                    from pictures\n                    where object_type = 1 and object_id = '" . Db::escape_string($log_internal_id) . "'\n                    order by seq\n                    limit " . ($position + 0) . ", 1\n                ");
             }
         }
     }
     # $position may have become a string, as returned by database queries.
     return array($position + 0, $seq, $log_images_count);
 }
コード例 #5
0
ファイル: menu.inc.php プロジェクト: 4Vs/oc-server3
 public static function get_installations()
 {
     $installations = OkapiServiceRunner::call("services/apisrv/installations", new OkapiInternalRequest(new OkapiInternalConsumer(), null, array()));
     foreach ($installations as &$inst_ref) {
         $inst_ref['selected'] = $inst_ref['site_url'] == Settings::get('SITE_URL');
     }
     return $installations;
 }
コード例 #6
0
ファイル: installation.php プロジェクト: 4Vs/oc-server3
 public static function call(OkapiRequest $request)
 {
     $result = array();
     $result['site_url'] = Settings::get('SITE_URL');
     $result['okapi_base_url'] = $result['site_url'] . "okapi/";
     $result['site_name'] = Okapi::get_normalized_site_name();
     $result['okapi_revision'] = Okapi::$revision;
     return Okapi::formatted_response($request, $result);
 }
コード例 #7
0
ファイル: attr_helper.inc.php プロジェクト: 4Vs/oc-server3
 /**
  * Refresh all attributes from the given XML. Usually, this file is
  * downloaded from Google Code (using refresh_now).
  */
 public static function refresh_from_string($xml)
 {
     /* The attribute-definitions.xml file defines relationships between
      * attributes originating from various OC installations. Each
      * installation uses internal IDs of its own. Which "attribute schema"
      * is being used in THIS installation? */
     $my_schema = Settings::get('ORIGIN_URL');
     $doc = simplexml_load_string($xml);
     $cachedvalue = array('attr_dict' => array());
     # Build cache attributes dictionary
     $all_internal_ids = array();
     foreach ($doc->attr as $attrnode) {
         $attr = array('acode' => (string) $attrnode['acode'], 'gc_equivs' => array(), 'internal_id' => null, 'names' => array(), 'descriptions' => array(), 'is_discontinued' => true);
         foreach ($attrnode->groundspeak as $gsnode) {
             $attr['gc_equivs'][] = array('id' => (int) $gsnode['id'], 'inc' => in_array((string) $gsnode['inc'], array("true", "1")) ? 1 : 0, 'name' => (string) $gsnode['name']);
         }
         foreach ($attrnode->opencaching as $ocnode) {
             /* If it is used by at least one OC node, then it's NOT discontinued. */
             $attr['is_discontinued'] = false;
             if ((string) $ocnode['schema'] == $my_schema) {
                 /* It is used by THIS OC node. */
                 $internal_id = (int) $ocnode['id'];
                 if (isset($all_internal_ids[$internal_id])) {
                     throw new Exception("The internal attribute " . $internal_id . " has multiple assigments to OKAPI attributes.");
                 }
                 $all_internal_ids[$internal_id] = true;
                 if (!is_null($attr['internal_id'])) {
                     throw new Exception("There are multiple internal IDs for the " . $attr['acode'] . " attribute.");
                 }
                 $attr['internal_id'] = $internal_id;
             }
         }
         foreach ($attrnode->lang as $langnode) {
             $lang = (string) $langnode['id'];
             foreach ($langnode->name as $namenode) {
                 if (isset($attr['names'][$lang])) {
                     throw new Exception("Duplicate " . $lang . " name of attribute " . $attr['acode']);
                 }
                 $attr['names'][$lang] = (string) $namenode;
             }
             foreach ($langnode->desc as $descnode) {
                 if (isset($attr['descriptions'][$lang])) {
                     throw new Exception("Duplicate " . $lang . " description of attribute " . $attr['acode']);
                 }
                 $xml = $descnode->asxml();
                 /* contains "<desc>" and "</desc>" */
                 $innerxml = preg_replace("/(^[^>]+>)|(<[^<]+\$)/us", "", $xml);
                 $attr['descriptions'][$lang] = self::cleanup_string($innerxml);
             }
         }
         $cachedvalue['attr_dict'][$attr['acode']] = $attr;
     }
     $cache_key = "attrhelper/dict#" . Okapi::$revision . self::cache_key_suffix();
     Cache::set($cache_key, $cachedvalue, self::ttl());
     self::$attr_dict = $cachedvalue['attr_dict'];
 }
コード例 #8
0
 /**
  * Returns one of: array('cache_code', 'OPXXXX'), array('internal_id', '12345'),
  * array('uuid', 'A408C3...') or null.
  */
 private static function get_cache_key($url)
 {
     # Determine our own domain.
     static $host = null;
     static $length = null;
     if ($host == null) {
         $host = parse_url(Settings::get('SITE_URL'), PHP_URL_HOST);
         if (strpos($host, "www.") === 0) {
             $host = substr($host, 4);
         }
         $length = strlen($host);
     }
     # Parse the URL
     $uri = parse_url($url);
     if ($uri == false) {
         return null;
     }
     if (!isset($uri['scheme']) || !in_array($uri['scheme'], array('http', 'https'))) {
         return null;
     }
     if (!isset($uri['host']) || substr($uri['host'], -$length) != $host) {
         return null;
     }
     if (!isset($uri['path'])) {
         return null;
     }
     if (preg_match("#^/(O[A-Z][A-Z0-9]{4,5})\$#", $uri['path'], $matches)) {
         # Some servers allow "http://oc.xx/<cache_code>" shortcut.
         return array('cache_code', $matches[1]);
     }
     $parts = array();
     if (isset($uri['query'])) {
         $parts = array_merge($parts, explode('&', $uri['query']));
     }
     if (isset($uri['fragment'])) {
         $parts = array_merge($parts, explode('&', $uri['fragment']));
     }
     foreach ($parts as $param) {
         $item = explode('=', $param, 2);
         if (count($item) != 2) {
             continue;
         }
         $key = $item[0];
         $value = $item[1];
         if ($key == 'wp') {
             return array('cache_code', $value);
         }
         if ($key == 'cacheid') {
             return array('internal_id', $value);
         }
         if ($key == 'uuid') {
             return array('uuid', $value);
         }
     }
     return null;
 }
コード例 #9
0
 public static function call($methodname)
 {
     require_once $GLOBALS['rootpath'] . 'okapi/service_runner.php';
     if (!OkapiServiceRunner::exists($methodname)) {
         throw new BadRequest("Method '{$methodname}' does not exist. " . "See OKAPI docs at " . Settings::get('SITE_URL') . "okapi/");
     }
     $options = OkapiServiceRunner::options($methodname);
     $request = new OkapiHttpRequest($options);
     return OkapiServiceRunner::call($methodname, $request);
 }
コード例 #10
0
ファイル: stats.php プロジェクト: kratenko/oc-server3
 public static function call(OkapiRequest $request)
 {
     $cachekey = "apisrv/stats";
     $result = Cache::get($cachekey);
     if (!$result) {
         $result = array('cache_count' => 0 + Db::select_value("\n                    select count(*) from caches where status in (1,2,3)\n                "), 'user_count' => 0 + Db::select_value("\n                    select count(*) from (\n                        select distinct user_id\n                        from cache_logs\n                        where\n                            type in (1,2,7)\n                            and " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "deleted = 0" : "true") . "\n                        UNION DISTINCT\n                        select distinct user_id\n                        from caches\n                    ) as t;\n                "), 'apps_count' => 0 + Db::select_value("select count(*) from okapi_consumers;"), 'apps_active' => 0 + Db::select_value("\n                    select count(distinct s.consumer_key)\n                    from\n                        okapi_stats_hourly s,\n                        okapi_consumers c\n                    where\n                        s.consumer_key = c.`key`\n                        and s.period_start > date_add(now(), interval -30 day)\n                "));
         Cache::set($cachekey, $result, 86400);
         # cache it for one day
     }
     return Okapi::formatted_response($request, $result);
 }
コード例 #11
0
ファイル: geocache.php プロジェクト: kratenko/oc-server3
 public static function call(OkapiRequest $request)
 {
     $cache_code = $request->get_parameter('cache_code');
     if (!$cache_code) {
         throw new ParamMissing('cache_code');
     }
     if (strpos($cache_code, "|") !== false) {
         throw new InvalidParam('cache_code');
     }
     $langpref = $request->get_parameter('langpref');
     if (!$langpref) {
         $langpref = "en";
     }
     $langpref .= "|" . Settings::get('SITELANG');
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         $fields = "code|name|location|type|status";
     }
     $log_fields = $request->get_parameter('log_fields');
     if (!$log_fields) {
         $log_fields = "uuid|date|user|type|comment";
     }
     $lpc = $request->get_parameter('lpc');
     if (!$lpc) {
         $lpc = 10;
     }
     $attribution_append = $request->get_parameter('attribution_append');
     if (!$attribution_append) {
         $attribution_append = 'full';
     }
     $params = array('cache_codes' => $cache_code, 'langpref' => $langpref, 'fields' => $fields, 'attribution_append' => $attribution_append, 'lpc' => $lpc, 'log_fields' => $log_fields);
     $my_location = $request->get_parameter('my_location');
     if ($my_location) {
         $params['my_location'] = $my_location;
     }
     $user_uuid = $request->get_parameter('user_uuid');
     if ($user_uuid) {
         $params['user_uuid'] = $user_uuid;
     }
     # There's no need to validate the fields/lpc parameters as the 'geocaches'
     # method does this (it will raise a proper exception on invalid values).
     $results = OkapiServiceRunner::call('services/caches/geocaches', new OkapiInternalRequest($request->consumer, $request->token, $params));
     $result = $results[$cache_code];
     if ($result === null) {
         # Two errors messages (for OCDE). Makeshift solution for issue #350.
         $exists = Db::select_value("\n                select 1\n                from caches\n                where wp_oc='" . Db::escape_string($cache_code) . "'\n            ");
         if ($exists) {
             throw new InvalidParam('cache_code', "This cache is not accessible via OKAPI.");
         } else {
             throw new InvalidParam('cache_code', "This cache does not exist.");
         }
     }
     return Okapi::formatted_response($request, $result);
 }
コード例 #12
0
 public static function call()
 {
     require_once $GLOBALS['rootpath'] . 'okapi/service_runner.php';
     require_once $GLOBALS['rootpath'] . 'okapi/views/menu.inc.php';
     $vars = array('menu' => OkapiMenu::get_menu_html("examples.html"), 'okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'site_url' => Settings::get('SITE_URL'), 'installations' => OkapiMenu::get_installations(), 'okapi_rev' => Okapi::$version_number, 'site_name' => Okapi::get_normalized_site_name());
     $response = new OkapiHttpResponse();
     $response->content_type = "text/html; charset=utf-8";
     ob_start();
     include 'examples.tpl.php';
     $response->body = ob_get_clean();
     return $response;
 }
コード例 #13
0
 public static function call(OkapiRequest $request)
 {
     $result = array();
     $result['site_url'] = Settings::get('SITE_URL');
     $result['okapi_base_url'] = $result['site_url'] . "okapi/";
     $result['site_name'] = Okapi::get_normalized_site_name();
     $result['okapi_version_number'] = Okapi::$version_number;
     $result['okapi_revision'] = Okapi::$version_number;
     /* Important for backward-compatibility! */
     $result['okapi_git_revision'] = Okapi::$git_revision;
     return Okapi::formatted_response($request, $result);
 }
コード例 #14
0
ファイル: http404.php プロジェクト: 4Vs/oc-server3
 public static function call()
 {
     require_once 'menu.inc.php';
     $vars = array('okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'menu' => OkapiMenu::get_menu_html(), 'installations' => OkapiMenu::get_installations(), 'okapi_rev' => Okapi::$revision);
     $response = new OkapiHttpResponse();
     $response->status = "404 Not Found";
     $response->content_type = "text/html; charset=utf-8";
     ob_start();
     include 'http404.tpl.php';
     $response->body = ob_get_clean();
     return $response;
 }
コード例 #15
0
 public static function call()
 {
     require_once $GLOBALS['rootpath'] . 'okapi/service_runner.php';
     require_once $GLOBALS['rootpath'] . 'okapi/views/menu.inc.php';
     $vars = array('menu' => OkapiMenu::get_menu_html("introduction.html"), 'okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'site_url' => Settings::get('SITE_URL'), 'method_index' => OkapiServiceRunner::call('services/apiref/method_index', new OkapiInternalRequest(new OkapiInternalConsumer(), null, array())), 'installations' => OkapiMenu::get_installations(), 'okapi_rev' => Okapi::$version_number);
     $response = new OkapiHttpResponse();
     $response->content_type = "text/html; charset=utf-8";
     ob_start();
     include 'introduction.tpl.php';
     $response->body = ob_get_clean();
     return $response;
 }
コード例 #16
0
ファイル: changelog.php プロジェクト: kratenko/oc-server3
 public static function call()
 {
     require_once $GLOBALS['rootpath'] . 'okapi/views/menu.inc.php';
     require_once $GLOBALS['rootpath'] . 'okapi/views/changelog_helper.inc.php';
     $changelog = new Changelog();
     $vars = array('menu' => OkapiMenu::get_menu_html("changelog.html"), 'okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'site_url' => Settings::get('SITE_URL'), 'installations' => OkapiMenu::get_installations(), 'okapi_rev' => Okapi::$version_number, 'site_name' => Okapi::get_normalized_site_name(), 'changes' => array('unavailable' => $changelog->unavailable_changes, 'available' => $changelog->available_changes));
     $response = new OkapiHttpResponse();
     $response->content_type = "text/html; charset=utf-8";
     ob_start();
     include 'changelog.tpl.php';
     $response->body = ob_get_clean();
     return $response;
 }
コード例 #17
0
 public static function call()
 {
     require_once $GLOBALS['rootpath'] . 'okapi/views/changelog_helper.inc.php';
     $changelog = new Changelog();
     $changes = array_merge($changelog->unavailable_changes, $changelog->available_changes);
     $changes = array_slice($changes, 0, 20);
     $vars = array('changes' => $changes, 'site_url' => Settings::get('SITE_URL'));
     $response = new OkapiHttpResponse();
     $response->content_type = "application/rss+xml; charset=utf-8";
     ob_start();
     include 'changelog_feed.tpl.php';
     $response->body = ob_get_clean();
     return $response;
 }
コード例 #18
0
ファイル: logs.php プロジェクト: kratenko/oc-server3
 public static function call(OkapiRequest $request)
 {
     $cache_code = $request->get_parameter('cache_code');
     if (!$cache_code) {
         throw new ParamMissing('cache_code');
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         $fields = "uuid|date|user|type|comment";
     }
     $offset = $request->get_parameter('offset');
     if (!$offset) {
         $offset = "0";
     }
     if ((int) $offset != $offset || (int) $offset < 0) {
         throw new InvalidParam('offset', "Expecting non-negative integer.");
     }
     $limit = $request->get_parameter('limit');
     if (!$limit) {
         $limit = "none";
     }
     if ($limit == "none") {
         $limit = "999999999";
     }
     if ((int) $limit != $limit || (int) $limit < 0) {
         throw new InvalidParam('limit', "Expecting non-negative integer or 'none'.");
     }
     # Check if code exists and retrieve cache ID (this will throw
     # a proper exception on invalid code).
     $cache = OkapiServiceRunner::call('services/caches/geocache', new OkapiInternalRequest($request->consumer, null, array('cache_code' => $cache_code, 'fields' => 'internal_id')));
     # Cache exists. Getting the uuids of its logs.
     $log_uuids = Db::select_column("\n            select uuid\n            from cache_logs\n            where\n                cache_id = '" . Db::escape_string($cache['internal_id']) . "'\n                and " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "deleted = 0" : "true") . "\n            order by date desc\n            limit {$offset}, {$limit}\n        ");
     # Getting the logs themselves. Formatting as an ordered list.
     $internal_request = new OkapiInternalRequest($request->consumer, $request->token, array('log_uuids' => implode("|", $log_uuids), 'fields' => $fields));
     $internal_request->skip_limits = true;
     $logs = OkapiServiceRunner::call('services/logs/entries', $internal_request);
     $results = array();
     foreach ($log_uuids as $log_uuid) {
         $results[] = $logs[$log_uuid];
     }
     /* Handle OCPL's "access logs" feature. */
     if (Settings::get('OC_BRANCH') == 'oc.pl' && Settings::get('OCPL_ENABLE_GEOCACHE_ACCESS_LOGS') && count($log_uuids) > 0) {
         require_once $GLOBALS['rootpath'] . 'okapi/lib/ocpl_access_logs.php';
         \okapi\OCPLAccessLogs::log_geocache_access($request, $cache['internal_id']);
     }
     return Okapi::formatted_response($request, $results);
 }
コード例 #19
0
 public static function call(OkapiRequest $request)
 {
     $cache_code = $request->get_parameter('cache_code');
     if (!$cache_code) {
         throw new ParamMissing('cache_code');
     }
     if (strpos($cache_code, "|") !== false) {
         throw new InvalidParam('cache_code');
     }
     $langpref = $request->get_parameter('langpref');
     if (!$langpref) {
         $langpref = "en|" . Settings::get('SITELANG');
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         $fields = "code|name|location|type|status";
     }
     $log_fields = $request->get_parameter('log_fields');
     if (!$log_fields) {
         $log_fields = "uuid|date|user|type|comment";
     }
     $lpc = $request->get_parameter('lpc');
     if (!$lpc) {
         $lpc = 10;
     }
     $attribution_append = $request->get_parameter('attribution_append');
     if (!$attribution_append) {
         $attribution_append = 'full';
     }
     $params = array('cache_codes' => $cache_code, 'langpref' => $langpref, 'fields' => $fields, 'attribution_append' => $attribution_append, 'lpc' => $lpc, 'log_fields' => $log_fields);
     $my_location = $request->get_parameter('my_location');
     if ($my_location) {
         $params['my_location'] = $my_location;
     }
     $user_uuid = $request->get_parameter('user_uuid');
     if ($user_uuid) {
         $params['user_uuid'] = $user_uuid;
     }
     # There's no need to validate the fields/lpc parameters as the 'geocaches'
     # method does this (it will raise a proper exception on invalid values).
     $results = OkapiServiceRunner::call('services/caches/geocaches', new OkapiInternalRequest($request->consumer, $request->token, $params));
     $result = $results[$cache_code];
     if ($result === null) {
         throw new InvalidParam('cache_code', "This cache does not exist.");
     }
     return Okapi::formatted_response($request, $result);
 }
コード例 #20
0
ファイル: installation.php プロジェクト: kratenko/oc-server3
 public static function call(OkapiRequest $request)
 {
     $result = array();
     $result['site_url'] = Settings::get('SITE_URL');
     $result['okapi_base_url'] = Okapi::get_recommended_base_url();
     $result['okapi_base_urls'] = Okapi::get_allowed_base_urls();
     $result['site_name'] = Okapi::get_normalized_site_name();
     $result['okapi_version_number'] = Okapi::$version_number;
     $result['okapi_revision'] = Okapi::$version_number;
     /* Important for backward-compatibility! */
     $result['okapi_git_revision'] = Okapi::$git_revision;
     $result['registration_url'] = Settings::get('REGISTRATION_URL');
     $result['mobile_registration_url'] = Settings::get('MOBILE_REGISTRATION_URL');
     $result['image_max_upload_size'] = Settings::get('IMAGE_MAX_UPLOAD_SIZE');
     $result['image_rcmd_max_pixels'] = Settings::get('IMAGE_MAX_PIXEL_COUNT');
     return Okapi::formatted_response($request, $result);
 }
コード例 #21
0
ファイル: method_doc.php プロジェクト: 4Vs/oc-server3
 public static function call($methodname)
 {
     require_once $GLOBALS['rootpath'] . 'okapi/service_runner.php';
     require_once $GLOBALS['rootpath'] . 'okapi/views/menu.inc.php';
     try {
         $method = OkapiServiceRunner::call('services/apiref/method', new OkapiInternalRequest(null, null, array('name' => $methodname)));
     } catch (BadRequest $e) {
         throw new Http404();
     }
     $vars = array('method' => $method, 'menu' => OkapiMenu::get_menu_html($methodname . ".html"), 'okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'installations' => OkapiMenu::get_installations(), 'okapi_rev' => Okapi::$revision);
     $response = new OkapiHttpResponse();
     $response->content_type = "text/html; charset=utf-8";
     ob_start();
     include 'method_doc.tpl.php';
     $response->body = ob_get_clean();
     return $response;
 }
コード例 #22
0
ファイル: revoke_access.php プロジェクト: kratenko/oc-server3
 public static function call()
 {
     # Determine which user is logged in to OC.
     require_once $GLOBALS['rootpath'] . "okapi/lib/oc_session.php";
     $OC_user_id = OCSession::get_user_id();
     # Ensure a user is logged in.
     if ($OC_user_id == null) {
         $after_login = "******";
         # it is correct, if you're wondering
         $login_url = Settings::get('SITE_URL') . "login.php?target=" . urlencode($after_login);
         return new OkapiRedirectResponse($login_url);
     }
     $consumer_key = isset($_REQUEST['consumer_key']) ? $_REQUEST['consumer_key'] : '';
     # Just remove app (if it doesn't exist - nothing wrong will happen anyway).
     Db::execute("\n            delete from okapi_tokens\n            where\n                user_id = '" . Db::escape_string($OC_user_id) . "'\n                and consumer_key = '" . Db::escape_string($consumer_key) . "'\n        ");
     Db::execute("\n            delete from okapi_authorizations\n            where\n                user_id = '" . Db::escape_string($OC_user_id) . "'\n                and consumer_key = '" . Db::escape_string($consumer_key) . "'\n        ");
     # Redirect back to the apps page.
     return new OkapiRedirectResponse(Settings::get('SITE_URL') . "okapi/apps/");
 }
コード例 #23
0
ファイル: menu.inc.php プロジェクト: kratenko/oc-server3
 public static function get_installations()
 {
     $installations = OkapiServiceRunner::call("services/apisrv/installations", new OkapiInternalRequest(new OkapiInternalConsumer(), null, array()));
     $site_url = Settings::get('SITE_URL');
     foreach ($installations as &$inst_ref) {
         # $inst_ref['site_url'] and $site_url can have different protocols
         # (http / https). We compare only the domain parts and use
         # $site_url (which has the current request's protocol) for the menu
         # so that the menu works properly.
         if (self::domains_are_equal($inst_ref['site_url'], $site_url)) {
             $inst_ref['site_url'] = $site_url;
             $inst_ref['okapi_base_url'] = $site_url . 'okapi/';
             $inst_ref['selected'] = true;
         } else {
             $inst_ref['selected'] = false;
         }
     }
     return $installations;
 }
コード例 #24
0
 public static function call()
 {
     if (isset($_REQUEST['posted'])) {
         $appname = isset($_REQUEST['appname']) ? $_REQUEST['appname'] : "";
         $appname = trim($appname);
         $appurl = isset($_REQUEST['appurl']) ? $_REQUEST['appurl'] : "";
         $email = isset($_REQUEST['email']) ? $_REQUEST['email'] : "";
         $accepted_terms = isset($_REQUEST['terms']) ? $_REQUEST['terms'] : "";
         $ok = false;
         if (!$appname) {
             $notice = "Please provide a valid application name.";
         } elseif (mb_strlen($appname) > 100) {
             $notice = "Application name should be less than 100 characters long.";
         } elseif (mb_strlen($appurl) > 250) {
             $notice = "Application URL should be less than 250 characters long.";
         } elseif ($appurl && substr($appurl, 0, 7) != "http://" && substr($appurl, 0, 8) != "https://") {
             $notice = "Application homepage URL should start with http(s)://. (Note: this URL is OPTIONAL and it is NOT for OAuth callback.)";
         } elseif (!$email) {
             $notice = "Please provide a valid email address.";
         } elseif (mb_strlen($email) > 70) {
             $notice = "Email address should be less than 70 characters long.";
         } elseif (!$accepted_terms) {
             $notice = "You have to read and accept OKAPI Terms of Use.";
         } else {
             $ok = true;
             Okapi::register_new_consumer($appname, $appurl, $email);
             $notice = "Consumer Key generated successfully.\nCheck your email!";
         }
         $response = new OkapiHttpResponse();
         $response->content_type = "application/json; charset=utf-8";
         $response->body = json_encode(array('ok' => $ok, 'notice' => $notice));
         return $response;
     }
     require_once $GLOBALS['rootpath'] . 'okapi/service_runner.php';
     require_once $GLOBALS['rootpath'] . 'okapi/views/menu.inc.php';
     $vars = array('menu' => OkapiMenu::get_menu_html("signup.html"), 'okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'site_url' => Settings::get('SITE_URL'), 'site_name' => Okapi::get_normalized_site_name(), 'installations' => OkapiMenu::get_installations(), 'okapi_rev' => Okapi::$version_number, 'data_license_html' => Settings::get('DATA_LICENSE_URL') ? "<a href='" . Settings::get('DATA_LICENSE_URL') . "'>Data License</a>" : "Data License");
     $response = new OkapiHttpResponse();
     $response->content_type = "text/html; charset=utf-8";
     ob_start();
     include 'signup.tpl.php';
     $response->body = ob_get_clean();
     return $response;
 }
コード例 #25
0
 public static function call(OkapiRequest $request)
 {
     $token_key = $request->get_parameter('oauth_token');
     if (!$token_key) {
         throw new ParamMissing("oauth_token");
     }
     $langpref = $request->get_parameter('langpref');
     $interactivity = $request->get_parameter('interactivity');
     if (!$interactivity) {
         $interactivity = 'minimal';
     }
     if (!in_array($interactivity, array('minimal', 'confirm_user'))) {
         throw new InvalidParam('interactivity', $interactivity);
     }
     # Redirect to the "apps" folder. This is done there (not here)
     # because: 1) we don't want any cookie or session-handling
     # done in the "services" folder. 2) "services" don't display
     # any interactive webpages, they just return the result.
     return new OkapiRedirectResponse(Settings::get('SITE_URL') . "okapi/apps/authorize" . "?oauth_token=" . $token_key . ($langpref != null ? "&langpref=" . $langpref : "") . "&interactivity=" . $interactivity);
 }
コード例 #26
0
ファイル: by_usernames.php プロジェクト: kratenko/oc-server3
 public static function call(OkapiRequest $request)
 {
     $usernames = $request->get_parameter('usernames');
     if (!$usernames) {
         throw new ParamMissing('usernames');
     }
     $usernames = explode("|", $usernames);
     if (count($usernames) > 500) {
         throw new InvalidParam('usernames', "Maximum allowed number of referenced users " . "is 500. You provided " . count($usernames) . " usernames.");
     }
     $fields = $request->get_parameter('fields');
     if (!$fields) {
         throw new ParamMissing('fields');
     }
     # There's no need to validate the fields parameter as the 'users'
     # method does this (it will raise a proper exception on invalid values).
     $rs = Db::query("\n            select username, uuid\n            from user\n            where username collate " . Settings::get('DB_CHARSET') . "_general_ci in ('" . implode("','", array_map('\\okapi\\Db::escape_string', $usernames)) . "')\n        ");
     $lower_username2useruuid = array();
     while ($row = Db::fetch_assoc($rs)) {
         $lower_username2useruuid[mb_strtolower($row['username'], 'utf-8')] = $row['uuid'];
     }
     Db::free_result($rs);
     # Retrieve data for the found user_uuids.
     if (count($lower_username2useruuid) > 0) {
         $id_results = OkapiServiceRunner::call('services/users/users', new OkapiInternalRequest($request->consumer, $request->token, array('user_uuids' => implode("|", array_values($lower_username2useruuid)), 'fields' => $fields)));
     } else {
         $id_results = array();
     }
     # Map user_uuids back to usernames. Also check which usernames were not found
     # and mark them with null.
     $results = array();
     foreach ($usernames as $username) {
         if (!isset($lower_username2useruuid[mb_strtolower($username, 'utf-8')])) {
             $results[$username] = null;
         } else {
             $results[$username] = $id_results[$lower_username2useruuid[mb_strtolower($username, 'utf-8')]];
         }
     }
     return Okapi::formatted_response($request, $results);
 }
コード例 #27
0
 public static function call()
 {
     $token_key = isset($_GET['oauth_token']) ? $_GET['oauth_token'] : '';
     $verifier = isset($_GET['oauth_verifier']) ? $_GET['oauth_verifier'] : '';
     $langpref = isset($_GET['langpref']) ? $_GET['langpref'] : Settings::get('SITELANG');
     $langprefs = explode("|", $langpref);
     $token = Db::select_row("\n            select\n                c.`key` as consumer_key,\n                c.name as consumer_name,\n                c.url as consumer_url,\n                t.verifier\n            from\n                okapi_consumers c,\n                okapi_tokens t\n            where\n                t.`key` = '" . mysql_real_escape_string($token_key) . "'\n                and t.consumer_key = c.`key`\n        ");
     if (!$token) {
         # Probably Request Token has expired or it was already used. We'll
         # just redirect to the Opencaching main page.
         return new OkapiRedirectResponse(Settings::get('SITE_URL'));
     }
     $vars = array('okapi_base_url' => Settings::get('SITE_URL') . "okapi/", 'token' => $token, 'verifier' => $verifier, 'site_name' => Okapi::get_normalized_site_name(), 'site_url' => Settings::get('SITE_URL'), 'site_logo' => Settings::get('SITE_LOGO'));
     $response = new OkapiHttpResponse();
     $response->content_type = "text/html; charset=utf-8";
     ob_start();
     Okapi::gettext_domain_init($langprefs);
     include 'authorized.tpl.php';
     $response->body = ob_get_clean();
     Okapi::gettext_domain_restore();
     return $response;
 }
コード例 #28
0
ファイル: userlogs.php プロジェクト: kratenko/oc-server3
 public static function call(OkapiRequest $request)
 {
     $user_uuid = $request->get_parameter('user_uuid');
     if (!$user_uuid) {
         throw new ParamMissing('user_uuid');
     }
     $limit = $request->get_parameter('limit');
     if (!$limit) {
         $limit = "20";
     }
     if (!is_numeric($limit)) {
         throw new InvalidParam('limit', "'{$limit}'");
     }
     $limit = intval($limit);
     if ($limit < 1 || $limit > 1000) {
         throw new InvalidParam('limit', "Has to be in range 1..1000.");
     }
     $offset = $request->get_parameter('offset');
     if (!$offset) {
         $offset = "0";
     }
     if (!is_numeric($offset)) {
         throw new InvalidParam('offset', "'{$offset}'");
     }
     $offset = intval($offset);
     if ($offset < 0) {
         throw new InvalidParam('offset', "'{$offset}'");
     }
     # Check if user exists and retrieve user's ID (this will throw
     # a proper exception on invalid UUID).
     $user = OkapiServiceRunner::call('services/users/user', new OkapiInternalRequest($request->consumer, null, array('user_uuid' => $user_uuid, 'fields' => 'internal_id')));
     # User exists. Retrieving logs.
     $rs = Db::query("\n            select cl.id, cl.uuid, cl.type, unix_timestamp(cl.date) as date, cl.text,\n                c.wp_oc as cache_code\n            from cache_logs cl, caches c\n            where\n                cl.user_id = '" . Db::escape_string($user['internal_id']) . "'\n                and " . (Settings::get('OC_BRANCH') == 'oc.pl' ? "cl.deleted = 0" : "true") . "\n                and c.status in (1,2,3)\n                and cl.cache_id = c.cache_id\n            order by cl.date desc\n            limit {$offset}, {$limit}\n        ");
     $results = array();
     while ($row = Db::fetch_assoc($rs)) {
         $results[] = array('uuid' => $row['uuid'], 'date' => date('c', $row['date']), 'cache_code' => $row['cache_code'], 'type' => Okapi::logtypeid2name($row['type']), 'comment' => $row['text']);
     }
     return Okapi::formatted_response($request, $results);
 }
コード例 #29
0
 public static function call(OkapiRequest $request)
 {
     $cache_codes = $request->get_parameter('cache_codes');
     if ($cache_codes === null) {
         throw new ParamMissing('cache_codes');
     }
     # Issue 106 requires us to allow empty list of cache codes to be passed into this method.
     # All of the queries below have to be ready for $cache_codes to be empty!
     $langpref = $request->get_parameter('langpref');
     if (!$langpref) {
         $langpref = "en|" . Settings::get('SITELANG');
     }
     $images = $request->get_parameter('images');
     if (!$images) {
         $images = "all";
     }
     if (!in_array($images, array("none", "all", "spoilers", "nonspoilers"))) {
         throw new InvalidParam('images');
     }
     $format = $request->get_parameter('caches_format');
     if (!$format) {
         $format = "gpx";
     }
     if (!in_array($format, array("gpx", "ggz"))) {
         throw new InvalidParam('caches_format');
     }
     $location_source = $request->get_parameter('location_source');
     $location_change_prefix = $request->get_parameter('location_change_prefix');
     # Start creating ZIP archive.
     $response = new OkapiZIPHttpResponse();
     # Include a GPX/GGZ file compatible with Garmin devices. It should include all
     # Geocaching.com (groundspeak:) and Opencaching.com (ox:) extensions. It will
     # also include image references (actual images will be added as separate files later)
     # and personal data (if the method was invoked using Level 3 Authentication).
     switch ($format) {
         case 'gpx':
             $data_filename = "Garmin/GPX/opencaching" . time() . rand(100000, 999999) . ".gpx";
             $data_method = 'services/caches/formatters/gpx';
             $data_use_compression = true;
             break;
         case 'ggz':
             $data_filename = "Garmin/GGZ/opencaching" . time() . rand(100000, 999999) . ".ggz";
             $data_method = 'services/caches/formatters/ggz';
             $data_use_compression = false;
             break;
     }
     $response->zip->FileAdd($data_filename, OkapiServiceRunner::call($data_method, new OkapiInternalRequest($request->consumer, $request->token, array('cache_codes' => $cache_codes, 'langpref' => $langpref, 'ns_ground' => 'true', 'ns_ox' => 'true', 'images' => 'ox:all', 'attrs' => 'ox:tags', 'trackables' => 'desc:count', 'alt_wpts' => 'true', 'recommendations' => 'desc:count', 'latest_logs' => 'true', 'lpc' => 'all', 'my_notes' => $request->token != null ? "desc:text" : "none", 'location_source' => $location_source, 'location_change_prefix' => $location_change_prefix)))->get_body(), clsTbsZip::TBSZIP_STRING, $data_use_compression);
     # Then, include all the images.
     $caches = OkapiServiceRunner::call('services/caches/geocaches', new OkapiInternalRequest($request->consumer, $request->token, array('cache_codes' => $cache_codes, 'langpref' => $langpref, 'fields' => "images")));
     if (count($caches) > 50) {
         throw new InvalidParam('cache_codes', "The maximum number of caches allowed to be downloaded with this method is 50.");
     }
     if ($images != 'none') {
         $supported_extensions = array('jpg', 'jpeg', 'gif', 'png', 'bmp');
         foreach ($caches as $cache_code => $dict) {
             $imgs = $dict['images'];
             if (count($imgs) == 0) {
                 continue;
             }
             $dir = "Garmin/GeocachePhotos/" . $cache_code[strlen($cache_code) - 1];
             $dir .= "/" . $cache_code[strlen($cache_code) - 2];
             $dir .= "/" . $cache_code;
             foreach ($imgs as $no => $img) {
                 if ($images == 'spoilers' && !$img['is_spoiler']) {
                     continue;
                 }
                 if ($images == 'nonspoilers' && $img['is_spoiler']) {
                     continue;
                 }
                 $tmp = false;
                 foreach ($supported_extensions as $ext) {
                     if (strtolower(substr($img['url'], strlen($img['url']) - strlen($ext) - 1)) != "." . $ext) {
                         $tmp = true;
                         continue;
                     }
                 }
                 if (!$tmp) {
                     continue;
                 }
                 # unsupported file extension
                 if ($img['is_spoiler']) {
                     $zippath = $dir . "/Spoilers/" . $img['unique_caption'] . ".jpg";
                 } else {
                     $zippath = $dir . "/" . $img['unique_caption'] . ".jpg";
                 }
                 # The safest way would be to use the URL, but that would be painfully slow!
                 # That's why we're trying to access files directly (and fail silently on error).
                 # This was tested on OCPL server only.
                 # Note: Oliver Dietz (oc.de) replied that images with 'local' set to 0 could not
                 # be accessed locally. But all the files have 'local' set to 1 anyway.
                 $syspath = Settings::get('IMAGES_DIR') . "/" . $img['uuid'] . ".jpg";
                 if (file_exists($syspath)) {
                     $response->zip->FileAdd($zippath, $syspath, clsTbsZip::TBSZIP_FILE, false);
                 } 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"!
                         $response->zip->FileAdd($zippath, $jpeg_contents, clsTbsZip::TBSZIP_STRING, false);
                     }
                 }
             }
         }
     }
     # The result could be big, but it's created and streamed right
     # to the browser, so it shouldn't hit our memory limit. We also
     # should set a higher time limit, because downloading this response
     # may take some time over slow network connections (and I'm not sure
     # what is the PHP's default way of handling such scenario).
     set_time_limit(600);
     $response->content_type = "application/zip";
     $response->content_disposition = 'attachment; filename="results.zip"';
     return $response;
 }
コード例 #30
0
 public static function call()
 {
     # This is a hidden page for OKAPI developers. It will output a complete
     # structure of the database. This is useful for making OKAPI compatible
     # across different OC installations.
     $user = Settings::get('DB_USERNAME');
     $password = Settings::get('DB_PASSWORD');
     $dbname = Settings::get('DB_NAME');
     $dbserver = Settings::get('DB_SERVER');
     # Some security measures are taken to hinder us from accidentally dumping
     # database contents:
     #  - try to set memory limit so that no big data chunk can be stored
     #  - reassure that we use the --no-data option
     #  - plausibility test for data amount
     #  - verify that the output does not contain table contents
     ini_set('memory_limit', '16M');
     $shell_arguments = "mysqldump --no-data -h{$dbserver} -u{$user} -p{$password} {$dbname}";
     if (!strpos($shell_arguments, "--no-data")) {
         throw new Exception("wrong database dump arguments");
     }
     $struct = shell_exec($shell_arguments);
     if (strlen($struct) > 1000000) {
         throw new Exception("something went terribly wrong while dumping table structures");
     }
     if (stripos($struct, "dumping data") !== FALSE) {
         throw new Exception("something went terribly wrong while dumping table structures");
     }
     # Remove the "AUTO_INCREMENT=..." values. They break the diffs.
     $struct = preg_replace("/ AUTO_INCREMENT=([0-9]+)/i", "", $struct);
     # Discard local tables that are not part of the OC installation
     if (Settings::get('OC_BRANCH') == 'oc.de') {
         $struct = preg_replace("/structure for table `_.*?\n-- Table /s", "", $struct);
     }
     # This method can be invoked with "compare_to" parameter, which points to
     # an alternate database structure (generated by the same script in other
     # *public* OKAPI instance). When invoked this way, we will attempt to
     # generate SQL script which alters LOCAL database is such a way that it
     # will become the other (public) database.
     $response = new OkapiHttpResponse();
     $response->content_type = "text/plain; charset=utf-8";
     if (isset($_GET['compare_to'])) {
         self::requireSafe($_GET['compare_to']);
         $scheme = parse_url($_GET['compare_to'], PHP_URL_SCHEME);
         if (in_array($scheme, array('http', 'https'))) {
             try {
                 $alternate_struct = @file_get_contents($_GET['compare_to']);
             } catch (Exception $e) {
                 throw new BadRequest("Failed to load " . $_GET['compare_to']);
             }
             $response->body = "-- Automatically generated database diff. Use with caution!\n" . "-- Differences obtained with help of cool library by Kirill Gerasimenko.\n\n" . "-- Note: The following script has some limitations. It will render database\n" . "-- structure compatible, but not necessarilly EXACTLY the same. It might be\n" . "-- better to use manual diff instead.\n\n";
             require_once "comparator.inc.php";
             $updater = new \dbStructUpdater();
             if (isset($_GET['reverse']) && $_GET['reverse'] == 'true') {
                 $response->body .= "-- REVERSE MODE. The following will alter [2], so that it has the structure of [1].\n" . "-- 1. " . Settings::get('SITE_URL') . "okapi/devel/dbstruct (" . md5($struct) . ")\n" . "-- 2. " . $_GET['compare_to'] . " (" . md5($alternate_struct) . ")\n\n";
                 $alters = $updater->getUpdates($alternate_struct, $struct);
             } else {
                 $response->body .= "-- The following will alter [1], so that it has the structure of [2].\n" . "-- 1. " . Settings::get('SITE_URL') . "okapi/devel/dbstruct (" . md5($struct) . ")\n" . "-- 2. " . $_GET['compare_to'] . " (" . md5($alternate_struct) . ")\n\n";
                 $alters = $updater->getUpdates($struct, $alternate_struct);
             }
             # Add semicolons
             foreach ($alters as &$alter_ref) {
                 $alter_ref .= ";";
             }
             # Comment out all differences containing "okapi_". These should be executed
             # by OKAPI update scripts.
             foreach ($alters as &$alter_ref) {
                 if (strpos($alter_ref, "okapi_") !== false) {
                     $lines = explode("\n", $alter_ref);
                     $alter_ref = "-- Probably you should NOT execute this one. Use okapi/update instead.\n-- {{{\n--   " . implode("\n--   ", $lines) . "\n-- }}}";
                 }
             }
             if (count($alters) > 0) {
                 $response->body .= implode("\n", $alters) . "\n";
             } else {
                 $response->body .= "-- No differences found\n";
             }
         } else {
             $response->body = "HTTP(S) only!";
         }
     } else {
         $response->body = $struct;
     }
     return $response;
 }