Added Other AP

This commit is contained in:
dev-chiefworks
2022-04-26 11:30:34 -04:00
parent 5e006a6a21
commit 47f4fad75c
251 changed files with 29298 additions and 4 deletions
+3 -3
View File
@@ -13,11 +13,11 @@ class Home extends BaseController
$results = $query->getResult();
foreach ($results as $row) {
echo $row->id;
echo $row->bank_name;
// echo $row->id;
// echo $row->bank_name;
}
echo 'Total Results: ' . count($results);
//echo 'Total Results: ' . count($results);
return view('welcome_message',$data);
}
+2 -1
View File
@@ -466,7 +466,8 @@ $vl='';
$pieces = explode("/", $uri);
$endpoint = $pieces[6];
$this->logArray($pieces);
echo 'Ameye';
var_dump($pieces);
return [];
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

@@ -0,0 +1 @@
7468EFC34890E27C9CC2D4518EF3340FF6F8BA1E9478D5CDD5DD8E475FF5D38F comodoca.com 5cb548574b285
+27
View File
@@ -0,0 +1,27 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /SAVVY/advice/
#RewriteBase /
#Checks to
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]
</IfModule>
<IfModule !mod_rewrite.c>
# If we don't have mod_rewrite installed, all 404's
# can be sent to index.php, and everything works as normal.
# Submitted by: ElliotHaughin
ErrorDocument 404 /index.php
</IfModule>
#Header add Access-Control-Allow-Origin "*"
#Header add Access-Control-Expose-Headers "Access-Control-Allow-Origin"
#Header add Access-Control-Allow-Headers "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With"
#Header add Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS"
#Header add Content-type "application/json"
+347
View File
@@ -0,0 +1,347 @@
<?php
class Destinations {
const TIME_DELTA = 180; // min (3 hrs)
const TIME_LIMIT = 1262322000; // 2010-01-01
const DEFAULT_COUNTRY = 'SG';
const DEFAULT_OPTIONS = 5;
const CITY_CURVATURE = 1.25;
const CITY_SPEED = 0.667; // km/min (0.0667 = 40 km/h)
const MIN_ACTIVITY_DISTANCE = 0.05; // 50m
const MAX_ACTIVITY_DISTANCE = 50.0; // 50km
const ACTIVITY_RADIUS = 100000; // 100km
public static function byActivity($db, $gpsdb, $member_id, $lat, $lng, $radius, $time, $time_delta=Destinations::TIME_DELTA, $country=Destinations::DEFAULT_COUNTRY) {
syslog(LOG_WARNING,"Activity::getActivity(\$db, \$gpsdb, $member_id, $lat, $lng, $radius, $time, $time_delta, $country)");
$all_time_days = Destinations::getAllTimeDays();
$res = [];
list($res1, $err1) = Destinations::activityByRangeAndTimeAndRadius(
$gpsdb, $db, $member_id, $all_time_days, $lat, $lng, $radius, 'start', $time, $time_delta);
if ($res1 && count($res1)>0) {
$res = Destinations::uniqueTrips(array_merge($res,$res1));
if (count($res)>=Destinations::DEFAULT_OPTIONS) {
return [$res, NULL];
}
}
syslog(LOG_WARNING,$err1);
list($res2, $err2) = Destinations::activityByRangeAndTimeAndRadius(
$gpsdb, $db, $member_id, $all_time_days, $lat, $lng, $radius, 'end', $time, $time_delta);
if ($res2 && count($res2)>0) {
$res = Destinations::uniqueTrips(array_merge($res,$res2));
if (count($res)>=Destinations::DEFAULT_OPTIONS) {
return [$res, NULL];
}
}
syslog(LOG_WARNING,$err2);
list($res3, $err3) = Destinations::activityByRangeAndRadius(
$gpsdb, $db, $member_id, $all_time_days, $lat, $lng, $radius, 'start');
if ($res3 && count($res3)>0) {
$res = Destinations::uniqueTrips(array_merge($res,$res3));
if (count($res)>=Destinations::DEFAULT_OPTIONS) {
return [$res, NULL];
}
}
syslog(LOG_WARNING,$err3);
list($res4, $err4) = Destinations::activityByRangeAndRadius(
$gpsdb, $db, $member_id, $all_time_days, $lat, $lng, $radius, 'end');
if ($res4 && count($res4)>0) {
$res = Destinations::uniqueTrips(array_merge($res,$res4));
if (count($res)>=Destinations::DEFAULT_OPTIONS) {
return [$res, NULL];
}
}
syslog(LOG_WARNING,$err4);
if ($country=='SG') {
// For US it may produce the results too far apart
list($res1, $err1) = Destinations::activityByRangeAndTime($db, $member_id, 7, $time, $time_delta, $country);
if ($res1 && count($res1)>0) {
$res = Destinations::uniqueTrips(array_merge($res,$res1));
if (count($res)>=Destinations::DEFAULT_OPTIONS) {
return [$res, NULL];
}
}
list($res2, $err2) = Destinations::activityByRangeAndTime($db, $member_id, 30, $time, $time_delta, $country);
if ($res2 && count($res2)>0) {
$res = Destinations::uniqueTrips(array_merge($res,$res2));
if (count($res)>=Destinations::DEFAULT_OPTIONS) {
return [$res, NULL];
}
}
list($res3, $err3) = Destinations::activityByRangeAndTime($db, $member_id, $all_time_days, $time, $time_delta, $country);
if ($res3 && count($res3)>0) {
$res = Destinations::uniqueTrips(array_merge($res,$res3));
if (count($res)>=Destinations::DEFAULT_OPTIONS) {
return [$res, NULL];
}
}
list($res4, $err4) = Destinations::activityByRange($db, $member_id, 7, $country);
if ($res4 && count($res4)>0) {
$res = Destinations::uniqueTrips(array_merge($res,$res4));
if (count($res)>=Destinations::DEFAULT_OPTIONS) {
return [$res, NULL];
}
}
list($res5, $err5) = Destinations::activityByRange($db, $member_id, 30, $country);
if ($res5 && count($res5)>0) {
$res = Destinations::uniqueTrips(array_merge($res,$res5));
if (count($res)>=Destinations::DEFAULT_OPTIONS) {
return [$res, NULL];
}
}
list($res6, $err6) = Destinations::activityByRange($db, $member_id, $all_time_days, $country);
if ($res6 && count($res6)>0) {
$res = Destinations::uniqueTrips(array_merge($res,$res6));
if (count($res)>=Destinations::DEFAULT_OPTIONS) {
return [$res, NULL];
}
}
}
//*/
if ($res && count($res)>0) {
return [$res, NULL];
}
return [NULL, "We could not find any recent activity in this area"];
}
public static function getAddressActivity($db, $address, $member_id, $time, $time_delta=Destinations::TIME_DELTA) {
// TODO: radius search
}
/*
id | bigint | not null default nextval('address_id_seq'::regclass)
address | character varying(200) |
latitude | numeric |
longitude | numeric |
timezone | integer |
geocoding_date | date |
postal | character varying(40) |
country | character varying(2) | default 'SG'::character varying
geometry | geography(Point,4326) |
description | character varying(100) |
city_id | integer |
*/
public static function activityByRange($db, $member_id, $days, $country=Destinations::DEFAULT_COUNTRY) {
$db_time = strtotime($time);
$db_days = date("w",$db_time) + (int)$days;
$db_country = pg_escape_string($country);
$q = "SELECT b.*, 60*date_part('hour', b.travel_date)+date_part('minute',b.travel_date) AS dm ";
/////// Start Locations ///////
$q.= ", c.address AS location_start_address, c.latitude AS location_start_lat, c.longitude AS location_start_lng";
$q.= ", c.timezone AS location_start_timezone, c.postal AS location_start_postal, c.country AS location_start_country";
$q.= ", c.description AS location_start_description, c.city_id AS location_start_city_id, c.geometry AS location_start_geometry ";
$q.= ", d.address AS location_end_address, d.latitude AS location_end_lat, d.longitude AS location_end_lng";
$q.= ", d.timezone AS location_end_timezone, d.postal AS location_end_postal, d.country AS location_end_country";
$q.= ", d.description AS location_end_description, d.city_id AS location_end_city_id, d.geometry AS location_end_geometry ";
/////// End Locations ///////
$q.= " FROM trackedemail_item a, parsedemail_item b ";
$q.= " LEFT JOIN address c ON (c.id=b.location_start_id) ";
$q.= " LEFT JOIN address d ON (d.id=b.location_end_id) ";
$q.= " WHERE a.id=b.trackedemail_item_id AND a.member_id=".((int)$member_id);
$q.= " AND b.dup_id IS NULL AND b.travel_date_end > (now() - interval '${db_days} days') ";
$q.= " AND (c.country='${db_country}' OR d.country='${db_country}') ";
$q.= " ORDER BY b.travel_date DESC LIMIT ".Destinations::DEFAULT_OPTIONS;
syslog(LOG_WARNING,$q);
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
$result = [];
while ($f=pg_fetch_assoc($r)) {
$f["location_start_address"] = html_entity_decode ($f["location_start_address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
$f["location_end_address"] = html_entity_decode ($f["location_end_address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
$result[] = $f;
}
return [$result, NULL];
}
return [NULL,pg_last_error()];
}
public static function activityByRangeAndRadius($gpsdb, $db, $member_id, $days, $lat, $lng, $radius, $what) {
syslog(LOG_WARNING,"Activity::activityByRangeAndRadius(\$gpsdb, \$db, $member_id, $days, $lat, $lng, $radius, $what)");
$limit_lat = (float)$lat;
$limit_lng = (float)$lng;
$limit_rad = $radius;
$db_days = (int)$days;
$q = "SELECT b.*, 60*date_part('hour', b.travel_date)+date_part('minute',b.travel_date) AS dm ";
/////// Start Locations ///////
$q.= ", c.address AS location_start_address, c.latitude AS location_start_lat, c.longitude AS location_start_lng";
$q.= ", c.timezone AS location_start_timezone, c.postal AS location_start_postal, c.country AS location_start_country";
$q.= ", c.description AS location_start_description, c.city_id AS location_start_city_id, c.geometry AS location_start_geometry ";
$q.= ", d.address AS location_end_address, d.latitude AS location_end_lat, d.longitude AS location_end_lng";
$q.= ", d.timezone AS location_end_timezone, d.postal AS location_end_postal, d.country AS location_end_country";
$q.= ", d.description AS location_end_description, d.city_id AS location_end_city_id, d.geometry AS location_end_geometry ";
/////// End Locations ///////
$q.= " FROM trackedemail_item a, parsedemail_item b ";
$q.= " LEFT JOIN address c ON (c.id=b.location_start_id) ";
$q.= " LEFT JOIN address d ON (d.id=b.location_end_id) ";
$q.= " WHERE a.id=b.trackedemail_item_id AND a.member_id=".((int)$member_id);
$q.= " AND b.dup_id IS NULL AND b.travel_date_end > (now() - interval '${db_days} days') ";
//$q.= " AND (b.location_start_id IN (%d,%d) OR b.location_end_id IN (%d,%d)) ";
$q.= " AND (";
$q.= "ST_DWithin(c.geometry,ST_SetSRID(ST_MakePoint(${limit_lng},${limit_lat}),4326)::geography,${limit_rad})";
$q.= " OR ";
$q.= "ST_DWithin(d.geometry,ST_SetSRID(ST_MakePoint(${limit_lng},${limit_lat}),4326)::geography,${limit_rad})";
$q.= ") ORDER BY b.travel_date DESC LIMIT ".Destinations::DEFAULT_OPTIONS;
syslog(LOG_WARNING,$q);
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
$cache_from = [];
$cache_to = [];
$result = [];
while ($f = pg_fetch_assoc($r)) {
if (array_key_exists($f["location_start_geometry"],$cache_from) && $f["location_end_geometry"]==$cache_from[$f["location_start_geometry"]]) {
continue; // We have similar trip in result set - let's skip it
}
if (array_key_exists($f["location_end_geometry"],$cache_to) && $f["location_start_geometry"]==$cache_to[$f["location_end_geometry"]]) {
continue; // We have similar trip in result set - let's skip it
}
$f["location_start_address"] = html_entity_decode ($f["location_start_address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
$f["location_end_address"] = html_entity_decode ($f["location_end_address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
$result[] = $f;
$cache_from[$f["location_start_geometry"]] = $f["location_end_geometry"];
$cache_to[$f["location_end_geometry"]] = $f["location_start_geometry"];
}
unset($cache_from); // clear
unset($cache_to); // clear
return [$result, NULL];
}
return [NULL,pg_last_error()];
}
public static function activityByRangeAndTimeAndRadius($gpsdb, $db, $member_id, $days, $lat, $lng, $radius, $what, $time, $time_delta=Destinations::TIME_DELTA) {
syslog(LOG_WARNING,"Activity::activityByRangeAndTimeAndRadius(\$gpsdb, \$db, $member_id, $days, $lat, $lng, $radius, $what, $time, $time_delta)");
$limit_lat = (float)$lat;
$limit_lng = (float)$lng;
$limit_rad = $radius;
$db_time = strtotime($time);
$db_days = date("w",$db_time) + (int)$days;
$db_time_delta = (int)$time_delta;
syslog(LOG_WARNING,"${db_time}=".date("Y-m-d H:i",$db_time));
// Ranges
$dh = 60*date("G",$db_time)+date("i",$db_time);
$d1 = $dh - $db_time_delta;
$d2 = $dh + $db_time_delta;
syslog(LOG_WARNING,"${d1} <= x <= ${d2}");
$q = "SELECT e.* FROM (SELECT b.*, 60*date_part('hour', b.travel_date)+date_part('minute',b.travel_date) AS dm ";
/////// Start Locations ///////
$q.= ", c.address AS location_start_address, c.latitude AS location_start_lat, c.longitude AS location_start_lng";
$q.= ", c.timezone AS location_start_timezone, c.postal AS location_start_postal, c.country AS location_start_country";
$q.= ", c.description AS location_start_description, c.city_id AS location_start_city_id, c.geometry AS location_start_geometry ";
$q.= ", d.address AS location_end_address, d.latitude AS location_end_lat, d.longitude AS location_end_lng";
$q.= ", d.timezone AS location_end_timezone, d.postal AS location_end_postal, d.country AS location_end_country";
$q.= ", d.description AS location_end_description, d.city_id AS location_end_city_id, d.geometry AS location_end_geometry ";
/////// End Locations ///////
$q.= " FROM trackedemail_item a, parsedemail_item b ";
$q.= " LEFT JOIN address c ON (c.id=b.location_start_id) ";
$q.= " LEFT JOIN address d ON (d.id=b.location_end_id) ";
$q.= " WHERE a.id=b.trackedemail_item_id AND a.member_id=".((int)$member_id);
$q.= " AND b.dup_id IS NULL AND b.travel_date_end > (now() - interval '${db_days} days') ";
$q.= " AND (";
$q.= "ST_DWithin(c.geometry,ST_SetSRID(ST_MakePoint(${limit_lng},${limit_lat}),4326)::geography,${limit_rad})";
$q.= " OR ";
$q.= "ST_DWithin(d.geometry,ST_SetSRID(ST_MakePoint(${limit_lng},${limit_lat}),4326)::geography,${limit_rad})";
$q.= ")) AS e ";
$q.= " WHERE e.dm>=${d1} AND e.dm<=${d2} ";
$q.= " ORDER BY e.travel_date DESC LIMIT ".Destinations::DEFAULT_OPTIONS;
syslog(LOG_WARNING,$q);
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
syslog(LOG_WARNING,"parsedemail_item(s) to process: ".pg_num_rows($r));
$cache_from = [];
$cache_to = [];
$result = [];
while ($f=pg_fetch_assoc($r)) {
if (array_key_exists($f["location_start_geometry"],$cache_from) && $f["location_end_geometry"]==$cache_from[$f["location_start_geometry"]]) {
continue; // We have similar trip in result set - let's skip it
}
if (array_key_exists($f["location_end_geometry"],$cache_to) && $f["location_start_geometry"]==$cache_to[$f["location_end_geometry"]]) {
continue; // We have similar trip in result set - let's skip it
}
$f["location_start_address"] = html_entity_decode ($f["location_start_address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
$f["location_end_address"] = html_entity_decode ($f["location_end_address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
$result[] = $f;
}
unset($cache_from); // clear
unset($cache_to); // clear
return [$result, NULL];
}
return [NULL,pg_last_error()];
}
public static function activityByRangeAndTime($db, $member_id, $days, $time, $time_delta=Destinations::TIME_DELTA, $country=Destinations::DEFAULT_COUNTRY) {
syslog(LOG_WARNING,"Activity::activityByRangeAndTime(\$db, $member_id, $days, $time, $time_delta");
$db_time = strtotime($time);
$db_days = date("w",$db_time) + (int)$days;
$db_time_delta = (int)$time_delta;
$db_country = pg_escape_string($country);
syslog(LOG_WARNING,"${db_time}=".date("Y-m-d H:i",$db_time));
// Ranges
$dh = 60*date("G",$db_time)+date("i",$db_time);
$d1 = $dh - $db_time_delta;
$d2 = $dh + $db_time_delta;
syslog(LOG_WARNING,"${d1} <= x <= ${d2}");
$q = "SELECT e.* FROM (SELECT b.*, 60*date_part('hour', b.travel_date)+date_part('minute',b.travel_date) AS dm ";
/////// Start Locations ///////
$q.= ", c.address AS location_start_address, c.latitude AS location_start_lat, c.longitude AS location_start_lng";
$q.= ", c.timezone AS location_start_timezone, c.postal AS location_start_postal, c.country AS location_start_country";
$q.= ", c.description AS location_start_description, c.city_id AS location_start_city_id, c.geometry AS location_start_geometry ";
$q.= ", d.address AS location_end_address, d.latitude AS location_end_lat, d.longitude AS location_end_lng";
$q.= ", d.timezone AS location_end_timezone, d.postal AS location_end_postal, d.country AS location_end_country";
$q.= ", d.description AS location_end_description, d.city_id AS location_end_city_id, d.geometry AS location_end_geometry ";
/////// End Locations ///////
$q.= " FROM trackedemail_item a, parsedemail_item b ";
$q.= " LEFT JOIN address c ON (c.id=b.location_start_id) ";
$q.= " LEFT JOIN address d ON (d.id=b.location_end_id) ";
$q.= " WHERE a.id=b.trackedemail_item_id AND a.member_id=".((int)$member_id);
$q.= " AND (c.country='${db_country}' OR d.country='${db_country}') ";
$q.= " AND b.dup_id IS NULL AND b.travel_date_end > (now() - interval '${db_days} days')) AS e ";
$q.= " WHERE e.dm>=${d1} AND e.dm<=${d2} ";
$q.= " ORDER BY e.travel_date DESC LIMIT ".Destinations::DEFAULT_OPTIONS;
syslog(LOG_WARNING,$q);
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
$result = [];
while ($f=pg_fetch_assoc($r)) {
$f["location_start_address"] = html_entity_decode ($f["location_start_address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
$f["location_end_address"] = html_entity_decode ($f["location_end_address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
$result[] = $f;
}
return [$result, NULL];
}
return [NULL,pg_last_error()];
}
protected static function getAllTimeDays() {
$datediff = time() - Destinations::TIME_LIMIT;
return round($datediff / (60 * 60 * 24));
}
protected static function uniqueTrips($res) {
$cache = [];
$result = [];
foreach ($res as $trip) {
$key1ids = $trip["location_start_id"]."_".$trip["location_end_id"];
$key1gps = $trip["location_start_lat"]."_".$trip["location_start_lng"]."_".$trip["location_end_lat"]."_".$trip["location_end_lng"];
$key2ids = $trip["location_end_id"]."_".$trip["location_start_id"];
$key2gps = $trip["location_end_lat"]."_".$trip["location_end_lng"]."_".$trip["location_start_lat"]."_".$trip["location_start_lng"];
if (array_key_exists($key1ids, $cache) || array_key_exists($key2ids, $cache)) {
continue;
}
if (array_key_exists($key1gps, $cache) || array_key_exists($key2gps, $cache)) {
continue;
}
$cache[$key1ids] = $key2ids;
$cache[$key1gps] = $key2gps;
$result[] = $trip;
}
return $result;
}
}
// vi:ts=2
@@ -0,0 +1,198 @@
<?php
class DestinationsApi extends Api
{
public $apiName = 'destinations';
public function indexAction()
{
return $this->response(
array(
'error' => 'Data not found'
), 404);
}
/**
* Method GET
* Get single record (by id)
* http://DOMAIN/destinations/1
* @return string
*/
public function viewAction()
{
return $this->response(
array(
'error'=> 'Data not found'
), 404);
}
public function createAction()
{
syslog(LOG_WARNING,'DestinationsApi::createAction()');
$message = 'We could not find any recent activity in this area';
$code = 404;
$time = $this->requestParams['time'] ?? '';
$location = $this->requestParams['location'] ?? [];
$country = $this->requestParams['country'] ?? '';
$member_id = $this->requestParams['member_id'] ?? 0;
try {
$db = new Db();
$country = Geocode::getCountryByGPS($db->getConnect(), $location, $country, Destinations::DEFAULT_COUNTRY);
syslog(LOG_WARNING,'country='.$country);
$country = Geocode::mockGPSCountry($db->getConnect(), $member_id, $country, 'default');
$gps_country_code = $country; // Guessed
if ($country!='US' && $country!='SG') {
throw new RuntimeException('Trips has not yet launched in your country. You can still track your travel activity and access exclusive deals. Start exploring!',500);
}
if ($member_id<1) {
throw new RuntimeException('Invalid member ID',500);
}
if ($time=='' || strtotime($time)<Destinations::TIME_LIMIT) {
throw new RuntimeException('Invalid time',500);
}
$address = NULL;
$street_address = "";
syslog(LOG_WARNING, "location => " . json_encode($location));
if (is_array($location) && isset($location["lat"]) && isset($location["lng"])) {
// DEBUG
list($location["lat"],$location["lng"]) = Geocode::mockGPSLocation(
$db->getConnect(), $member_id, $location["lat"], $location["lng"], 'default');
syslog(LOG_WARNING,"lat=".$location["lat"].",lng=".$location["lng"]);
list($street_address,$err) = GeocodeApi::reverseGeocode($db, $location["lat"], $location["lng"]);
if ($street_address=="") {
list($address,$err) = GeofenceApi::getAnchor($db, $location["lat"], $location["lng"]);
if (is_array($address) && array_key_exists("address",$address) && $address["address"]!="") {
$street_address = $address["address"];
}
}
if ($street_address=="") {
syslog(LOG_WARNING,'Reverse geocoder failed for ('.$location["lat"].', '.$location["lng"].')');
throw new RuntimeException($err?"Geocoder failed: $err":'Cannot geocode your GPS location',500);
}
syslog(LOG_WARNING,"street_address=".json_encode($street_address));
if (!is_array($address) || !array_key_exists("address",$address) || $address["address"]=="") {
list($address,$err) = Geocode::checkLatLngByAddress($db->getConnect(), $street_address, $country);
}
if (!is_array($address) || !isset($address["address"])) {
syslog(LOG_WARNING,'Geocoder failed for "'.$street_address.'"');
throw new RuntimeException($err?"Geocoder failed: $err":'Cannot get address for your GPS location '.$street_address,500);
}
// Adjust GPS coordinates if any
if (array_key_exists("latitude", $address) && $address["latitude"]!=null &&
array_key_exists("longitude",$address) && $address["longitude"]!=null &&
$address["latitude"]!=0 && $address["longitude"]!=0) {
$location["lat"] = $address["latitude"];
$location["lng"] = $address["longitude"];
$address["lat"] = $address["latitude"];
$address["lng"] = $address["longitude"];
} else if (array_key_exists("lat",$address) && $address["lat"]!=null &&
array_key_exists("lng",$address) && $address["lng"]!=null &&
$address["lat"]!=0 && $address["lng"]!=0) {
$location["lat"] = $address["lat"];
$location["lng"] = $address["lng"];
$address["latitude"] = $address["lat"];
$address["longitude"] = $address["lng"];
}
list($location,$address) = DestinationsApi::adjustAddressLocationGPS($location,$address);
if (is_array($address) && array_key_exists("country",$address)) {
$gps_country_code = $address["country"]; // Geocoded
}
}
// Step 0. Check if we are in the service area
/* GeocodeApi::checkWithinTheServiceArea($country,[
[
"type"=>1,
"geocode" => [
"lat" => $location["lat"],
"lng" => $location["lng"]
]
]
]); //*/
syslog(LOG_WARNING, "location => " . json_encode($location));
// Step 1. Get activities
list($res,$err) = Destinations::byActivity(
$db->getConnect(), $db->getConnectGPS(), $member_id,
$location["lat"], $location["lng"], Destinations::ACTIVITY_RADIUS,
$time, Destinations::TIME_DELTA, $country);
if (!$res || count($res)<1) {
if ($err!="") {
throw new RuntimeException($err, 500);
}
throw new RuntimeException($message, 404);
}
syslog(LOG_WARNING,'Recent activity has '.count($res).' trips!');
//DestinationsApi::debugTrips($res,'0');
return $this->response(
array(
"trips" => $res,
"address" => $address,
"country" => $country,
"gps_country_code" => $gps_country_code,
"street_address" => html_entity_decode($street_address)
), 200);
} catch (RuntimeException $e) {
$message = $e->getMessage();
$code = $e->getCode();
syslog(LOG_WARNING,$message);
}
return $this->response(
array(
"error" => $message
), $code);
}
private static function adjustAddressLocationGPS($location,$address) {
syslog(LOG_WARNING,'DestinationsApi::adjustAddressLocationGPS($location,$address)');
if (array_key_exists("latitude", $address) && $address["latitude"]!=null &&
array_key_exists("longitude",$address) && $address["longitude"]!=null &&
$address["latitude"]!=0 && $address["longitude"]!=0) {
$location["lat"] = $address["latitude"];
$location["lng"] = $address["longitude"];
$address["lat"] = $address["latitude"];
$address["lng"] = $address["longitude"];
} else if (array_key_exists("lat",$address) && $address["lat"]!=null &&
array_key_exists("lng",$address) && $address["lng"]!=null &&
$address["lat"]!=0 && $address["lng"]!=0) {
$location["lat"] = $address["lat"];
$location["lng"] = $address["lng"];
$address["latitude"] = $address["lat"];
$address["longitude"] = $address["lng"];
}
//syslog(LOG_WARNING,json_encode($address));
//syslog(LOG_WARNING,"location => " . json_encode($location));
return [$location,$address];
}
public static function debugTrips($trips,$what='0') {
return; /*
foreach ($trips as $trip) {
$leg_fare = $trip['multimodal']['options']['leg_fare'];
syslog(LOG_WARNING,'>>>>>>> '.$what.' >>>>>>> '.json_encode($leg_fare));
}*/
}
public function updateAction()
{
return $this->response(
array(
"error" => "Update error"
), 400);
}
public function deleteAction()
{
return $this->response(
array(
"error" => "Delete error"
), 500);
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
require_once('../../core/backend.php');
require_once('../constants.php');
require_once('../common/vendor/autoload.php');
require_once('../common/Api.php');
require_once('../common/Db.php');
require_once('../common/GoogleKMS.php');
require_once('../common/Logger.php');
require_once('../common/Utilities.php');
require_once('Destinations.php');
require_once('DestinationsApi.php');
require_once('../trips/Address.php');
require_once('../trips/Geocode.php');
require_once('../trips/GeocodeApi.php');
require_once('../trips/Geofence.php');
require_once('../trips/GeofenceApi.php');
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
header("Access-Control-Allow-Origin: *");
header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
header("Access-Control-Allow-Headers: Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, client_id");
header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS");
header('Content-type: application/json');
if ("OPTIONS" === $_SERVER['REQUEST_METHOD']) {
exit();
}
$headers = getallheaders();
if ((!isset($headers["Authorization"]) || substr($headers["Authorization"],-strlen($httpAuthToken))!=$httpAuthToken) &&
(!isset($headers["authorization"]) || substr($headers["authorization"],-strlen($httpAuthToken))!=$httpAuthToken)) {
header('HTTP/1.1 401 Unauthorized');
header('Status: 401 Unauthorized');
echo "{\"status\":\"Missing authorization\"}";
exit();
}
try {
if (strpos($_SERVER['REQUEST_URI'],'/api/')===false) {
throw new Exception("Invalid API request");
}
$requestUri = explode('/', trim($_SERVER['REQUEST_URI'],'/'));
while (array_shift($requestUri) !== 'api') {
};
if ($requestUri[0]=='destinations') {
$api = new DestinationsApi($requestUri);
}
else {
echo json_encode(Array('error' => 'Invalid API request'));
}
echo $api->run();
}
catch (Exception $e) {
echo json_encode(Array('error' => $e->getMessage()));
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

@@ -0,0 +1,60 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="swagger-ui.css" >
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="swagger-ui-bundle.js"> </script>
<script src="swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "../swagger.php",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
<?php
require('../../../adminsavvy/vendor/autoload.php');
$openapi = \OpenApi\scan('.');
header('Content-Type: application/json');
echo $openapi->toJson();
?>
+27
View File
@@ -0,0 +1,27 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /SAVVY/banklogin/
#RewriteBase /
#Checks to
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]
</IfModule>
<IfModule !mod_rewrite.c>
# If we don't have mod_rewrite installed, all 404's
# can be sent to index.php, and everything works as normal.
# Submitted by: ElliotHaughin
ErrorDocument 404 /index.php
</IfModule>
#Header add Access-Control-Allow-Origin "*"
#Header add Access-Control-Expose-Headers "Access-Control-Allow-Origin"
#Header add Access-Control-Allow-Headers "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With"
#Header add Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS"
#Header add Content-type "application/json"
@@ -0,0 +1,10 @@
<?php
class Banklogin {
}
// vi:ts=2
@@ -0,0 +1,106 @@
<?php
class BankloginApi extends Api
{
public $apiName = 'banklogin';
public function indexAction()
{
return $this->response(
array(
"error" => "Data not found"
), 400);
}
/**
* Method GET
* Get single record (by id)
* http://DOMAIN/geocode/1
* @return string
*/
public function viewAction()
{
return $this->response(
array(
'error'=> 'Data not found'
), 404);
}
public function createAction()
{
global $savvyext;
$path = $this->requestParams['path'];
$method = $this->requestParams['method'];
$authToken = $this->requestParams['auth_token'] ?? "";
$data = $this->requestParams['data'] ?? array();
$url = $savvyext->cfgReadChar('microservices.account') . "/api/v1/" . rtrim($path, '/') . '/';
$opts = array(
'http' => array(
'method' => $method,
'protocol_version' => 1.1,
'header' =>
"Accept: application/json\r\n"
),
"ssl" => array(
"verify_peer"=>false,
"verify_peer_name"=>false,
)
);
if(!empty($authToken)){
$opts['http']['header'] .= "Authorization: Token ${authToken}\r\n";
}
if($method == 'GET') {
if(is_array($data)) {
$data = "?" . http_build_query($data);
}
$url .= $data;
$opts['http']['header'] .= "Content-Type: application/x-www-form-urlencoded\r\n";
}
else {
$opts['http']['content'] = json_encode($data);
$opts['http']['header'] .= "Content-Type: application/json\r\nConnection: close\r\n";
$opts['http']['header'] .= "Content-length: " . strlen($opts['http']['content']);
}
$context = stream_context_create($opts);
try {
$body = file_get_contents($url, false, $context);
$result = json_decode($body,true);
return $this->response(
array(
'data'=>$result,
'options'=>""), 200);
} catch (Exception $e) {
error_log(json_encode($e));
$message = $e->getMessage();
}
return $this->response(
array(
"error" => $message
), 500);
}
public function updateAction()
{
return $this->response(
array(
"error" => "Update error"
), 400);
}
public function deleteAction()
{
return $this->response(
array(
"error" => "Delete error"
), 500);
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
require_once('../../core/backend.php');
require_once('../constants.php');
require_once('../common/Api.php');
require_once('../common/Db.php');
require_once('Banklogin.php');
require_once('BankloginApi.php');
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
$bankloginAuthToken = $savvyext->cfgReadChar('system.bank_token');
header("Access-Control-Allow-Origin: *");
header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
header("Access-Control-Allow-Headers: Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, client_id");
header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS");
header('Content-type: application/json');
if ("OPTIONS" === $_SERVER['REQUEST_METHOD']) {
exit();
}
$headers = getallheaders();
if ((!isset($headers["Authorization"]) || substr($headers["Authorization"],-strlen($httpAuthToken))!=$httpAuthToken) &&
(!isset($headers["authorization"]) || substr($headers["authorization"],-strlen($httpAuthToken))!=$httpAuthToken)) {
header('HTTP/1.1 401 Unauthorized');
header('Status: 401 Unauthorized');
echo "{\"status\":\"Missing authorization\"}";
exit();
}
try {
if (strpos($_SERVER['REQUEST_URI'],'/api/')===false) {
throw new Exception("Invalid API request");
}
$requestUri = explode('/', trim($_SERVER['REQUEST_URI'],'/'));
while (array_shift($requestUri) !== 'api') {
};
if ($requestUri[0]=='banklogin') {
$api = new BankloginApi($requestUri);
}
else {
echo json_encode(Array('error' => 'Invalid API request'));
}
echo $api->run();
}
catch (Exception $e) {
echo json_encode(Array('error' => $e->getMessage()));
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

@@ -0,0 +1,60 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="swagger-ui.css" >
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="swagger-ui-bundle.js"> </script>
<script src="swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "../swagger.php",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
<?php
require('../../../adminsavvy/vendor/autoload.php');
$openapi = \OpenApi\scan('.');
header('Content-Type: application/json');
echo $openapi->toJson();
?>
+27
View File
@@ -0,0 +1,27 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /SAVVY/blog/
#RewriteBase /
#Checks to
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]
</IfModule>
<IfModule !mod_rewrite.c>
# If we don't have mod_rewrite installed, all 404's
# can be sent to index.php, and everything works as normal.
# Submitted by: ElliotHaughin
ErrorDocument 404 /index.php
</IfModule>
#Header add Access-Control-Allow-Origin "*"
#Header add Access-Control-Expose-Headers "Access-Control-Allow-Origin"
#Header add Access-Control-Allow-Headers "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With"
#Header add Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS"
#Header add Content-type "application/json"
+10
View File
@@ -0,0 +1,10 @@
<?php
class Blog {
}
// vi:ts=2
+107
View File
@@ -0,0 +1,107 @@
<?php
class BlogApi extends Api
{
public $apiName = 'blog';
public function __construct($requestUri, $encryption=true) {
$this->cacheWhitelist = [
"viewAction" => ['ttl' => 900], // 900 sec. = 15 min.
"indexAction" => ['ttl' => 900]
];
parent::__construct($requestUri, $encryption);
}
public function indexAction()
{
global $savvyext;
$message = "Data not found";
$limit = trim($this->requestParams["limit"] ?? "");
$page = trim($this->requestParams["page"] ?? "");
$params =[
'limit' => $limit,
'page' => $page
];
$url = $savvyext->cfgReadChar('system.blog_api_url') . "/latest-articles?".http_build_query($params);
try {
$body = file_get_contents($url, false);
$result = json_decode($body, true);
return $this->response(
array(
'data' => $result,
'error' => ""),
200);
} catch (Exception $e) {
error_log(json_encode($e));
$message = $e->getMessage();
}
return $this->response(
array(
"error" => $message,
), 500);
}
/**
* Method GET
* Get single record (by id)
* http://DOMAIN/blog/679
* @return string
*/
public function viewAction()
{
global $savvyext;
$message = "Data not found";
$id = array_shift($this->requestUri);
if(!empty($id)){
$url = $savvyext->cfgReadChar('system.blog_api_url') . "/articles/" . intval($id);
try {
$body = file_get_contents($url, false);
$result = json_decode($body, true);
return $this->response(
array(
'data' => $result,
'error' => ""), 200);
} catch (Exception $e) {
error_log(json_encode($e));
$message = $e->getMessage();
}
}
return $this->response(
array(
"error" => $message,
), 500);
}
public function createAction()
{
return $this->response(
array(
"error" => "Data not found",
), 400);
}
public function updateAction()
{
return $this->response(
array(
"error" => "Update error",
), 400);
}
public function deleteAction()
{
return $this->response(
array(
"error" => "Delete error",
), 500);
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
require_once('../../core/backend.php');
require_once('../constants.php');
require_once('../common/Api.php');
require_once('../common/Db.php');
require_once('Blog.php');
require_once('BlogApi.php');
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
$bankloginAuthToken = $savvyext->cfgReadChar('system.bank_token');
header("Access-Control-Allow-Origin: *");
header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
header("Access-Control-Allow-Headers: Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, client_id");
header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS");
header('Content-type: application/json');
if ("OPTIONS" === $_SERVER['REQUEST_METHOD']) {
exit();
}
$headers = getallheaders();
if ((!isset($headers["Authorization"]) || substr($headers["Authorization"],-strlen($httpAuthToken))!=$httpAuthToken) &&
(!isset($headers["authorization"]) || substr($headers["authorization"],-strlen($httpAuthToken))!=$httpAuthToken)) {
header('HTTP/1.1 401 Unauthorized');
header('Status: 401 Unauthorized');
echo "{\"status\":\"Missing authorization\"}";
exit();
}
try {
if (strpos($_SERVER['REQUEST_URI'],'/api/')===false) {
throw new Exception("Invalid API request");
}
$requestUri = explode('/', trim($_SERVER['REQUEST_URI'],'/'));
while (array_shift($requestUri) !== 'api') {
};
if ($requestUri[0]=='blog') {
$api = new BlogApi($requestUri);
}
else {
echo json_encode(Array('error' => 'Invalid API request'));
}
echo $api->run();
}
catch (Exception $e) {
echo json_encode(Array('error' => $e->getMessage()));
}
+27
View File
@@ -0,0 +1,27 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /SAVVY/booking/
#RewriteBase /
#Checks to
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]
</IfModule>
<IfModule !mod_rewrite.c>
# If we don't have mod_rewrite installed, all 404's
# can be sent to index.php, and everything works as normal.
# Submitted by: ElliotHaughin
ErrorDocument 404 /index.php
</IfModule>
#Header add Access-Control-Allow-Origin "*"
#Header add Access-Control-Expose-Headers "Access-Control-Allow-Origin"
#Header add Access-Control-Allow-Headers "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With"
#Header add Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS"
#Header add Content-type "application/json"
File diff suppressed because it is too large Load Diff
+297
View File
@@ -0,0 +1,297 @@
<?php
class Booking {
const STATUS_ERROR = -1;
const STATUS_INIT = 1;
const STATUS_BOOKED = 2;
const STATUS_CANCELED = 3;
const STATUS_VENDOR_CANCELED = 4;
const STATUS_VEHICLE_ARRIVED = 5;
const STATUS_COMPLETED = 6;
const STATUS_IN_PROGRESS = 7;
const STATUS_DISPATCHED = 8;
const STATUS_LOCATION_UPDATE = 9;
/*
CREATE TABLE booking (
id serial not null constraint booking_pkey primary key,
quote_id bigint not null,
provider_booking_ref varchar(200) not null,
details json,
created timestamp default now(),
updated timestamp,
completed timestamp,
status smallint default 0,
message text,
cost numeric default 0
);
alter table booking
add constraint booking_quotes_id_fk
foreign key (quote_id) references quotes;
CREATE TABLE booking_details (
id serial not null constraint booking_details_pkey primary key,
booking_id bigint not null,
action varchar(64),
details json,
created timestamp default now(),
message text,
request json
);
alter table booking_details
add constraint booking_details_booking_id_fk
foreign key (booking_id) references booking;*/
public static function getById($db, $id) {
syslog( LOG_WARNING, "Booking::getById(\$db, $id)" );
Logger::debug( "Booking::getById(\$db, $id)" );
$result = [];
// $q = "SELECT * FROM booking WHERE id=${id}";
$q = "SELECT b.*,
a_s.address as location_start, a_s.latitude as location_start_lat, a_s.longitude as location_start_lng,
a_e.address as location_end, a_e.latitude as location_end_lat, a_e.longitude as location_end_lng,
a_s.country as location_country
FROM booking b
LEFT JOIN quotes q on b.quote_id = q.id
LEFT JOIN address a_s on q.location_start_id = a_s.id
LEFT JOIN address a_e on q.location_end_id = a_e.id
WHERE b.id=${id}";
$r = pg_query( $db, $q );
//syslog(LOG_WARNING,$q);
if ( $r && pg_num_rows( $r ) && $f = pg_fetch_assoc( $r ) ) {
$result = $f;
}
return $result;
}
public static function getAll($db, $params = []) {
syslog( LOG_WARNING, "Booking::getAll(\$db, $params)" );
Logger::debug( "Booking::getAll(\$db, $params)" );
$result = [];
// $q = "SELECT * FROM booking ";
$q = "SELECT b.*,
a.address as location_end, a.latitude as location_end_lat, a.longitude as location_end_lng,
a.country as location_country
FROM booking b
LEFT JOIN quotes q on b.quote_id = q.id
LEFT JOIN address a on q.location_end_id = a.id ";
if (count($params)) {
if (array_key_exists('active', $params) && (bool)$params['active'] == true) {
$q .= " WHERE b.status = ". Booking::STATUS_BOOKED. " OR b.status = ". Booking::STATUS_IN_PROGRESS. " OR b.status = ". Booking::STATUS_DISPATCHED ;
} else if (array_key_exists('status', $params) && isset($params['status'])) {
//$statuses = explode(',', $params['status']);
$q .= " WHERE b.status IN(". $params['status'] .")";
}
if (array_key_exists('limit', $params) && array_key_exists('offset', $params)) {
$limit = $params['limit'];
$offset = $params['offset'];
$q .= " ORDER BY created DESC LIMIT ${limit} OFFSET ${offset} ";
} else {
$q .= " ORDER BY created DESC LIMIT 10 OFFSET 0";
}
}
$r = pg_query( $db, $q );
$result = [];
if ( $r && pg_num_rows( $r ) ) {
while ($row = pg_fetch_assoc($r)) {
array_push($result, $row);
}
}
return $result;
}
public static function getByBookingRef($db, $bookingRef) {
syslog( LOG_WARNING, "Booking::getByBookingRef(\$db, $bookingRef)" );
Logger::debug( "Booking::getById(\$db, $bookingRef)" );
$result = [];
$q = "SELECT * FROM booking WHERE provider_booking_ref='${bookingRef}'";
$r = pg_query( $db, $q );
//syslog(LOG_WARNING,$q);
if ( $r && pg_num_rows( $r ) && $f = pg_fetch_assoc( $r ) ) {
$result = $f;
}
return $result;
}
public static function getByMemberId($db, $id, $params = []) {
syslog( LOG_WARNING, "Booking::getByMemberId(\$db, $id)" );
Logger::debug( "Booking::getByMemberId(\$db, $id)" );
$result = [];
// $q = "SELECT * FROM booking WHERE member_id=${id} ";
$q = "SELECT b.*,
a.address as location_end, a.latitude as location_end_lat, a.longitude as location_end_lng,
a.country as location_country
FROM booking b
LEFT JOIN quotes q on b.quote_id = q.id
LEFT JOIN address a on q.location_end_id = a.id
WHERE b.member_id=${id}";
if (count($params)) {
if (array_key_exists('active', $params) && (bool)$params['active'] == true) {
$q .= " AND b.status = ". Booking::STATUS_BOOKED. " OR b.status = ". Booking::STATUS_IN_PROGRESS. " OR b.status = ". Booking::STATUS_DISPATCHED ;
} else if (array_key_exists('status', $params) && isset($params['status'])) {
//$statuses = explode(',', $params['status']);
$q .= " AND b.status IN(". $params['status'] .")";
}
if (array_key_exists('limit', $params) && array_key_exists('offset', $params)) {
$limit = $params['limit'];
$offset = $params['offset'];
$q .= " ORDER BY created DESC LIMIT ${limit} OFFSET ${offset} ";
} else {
$q .= " ORDER BY created DESC LIMIT 10 OFFSET 0";
}
}
$r = pg_query( $db, $q );
$result = [];
if ( $r && pg_num_rows( $r ) ) {
while ($row = pg_fetch_assoc($r)) {
array_push($result, $row);
}
}
return $result;
}
public static function getLastNotCompletedBookingForMember($db, $member_id) {
syslog( LOG_WARNING, "Booking::getLastNotCompletedBookingForMember(\$db, $member_id)" );
Logger::debug( "Booking::getLastNotCompletedBookingForMember(\$db, $member_id)" );
$result = [];
$q = "SELECT * FROM booking WHERE member_id=${member_id} AND completed IS NULL AND status = ". Booking::STATUS_BOOKED ." AND cost > 0";
$r = pg_query( $db, $q );
//syslog(LOG_WARNING,$q);
if ( $r && pg_num_rows( $r ) && $f = pg_fetch_assoc( $r ) ) {
$result = $f;
}
return $result;
}
public static function create($db, $data) {
syslog(LOG_WARNING,'Booking::create($db, $data)');
$quote_id = $data["quote_id"];
$provider_booking_ref = $data["provider_booking_ref"];
$details = $data["details"];
$status = (int)$data["status"];
$memberId = (int)$data["member_id"];
$cost = (int)$data["cost"];
$q = "INSERT INTO booking(quote_id, provider_booking_ref, details, status, member_id, cost) VALUES (${quote_id}, '${provider_booking_ref}', '${details}', ${status}, ${memberId}, ${cost}) RETURNING id";
$log = [
'message' => 'Insert Booking',
'data' =>$data,
'query' =>$q
];
Logger::debug($log);
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
return [self::getById($db, $f["id"]),NULL];
}
syslog(LOG_WARNING,pg_last_error($db));
return [NULL,pg_last_error($db)];
}
public static function update($db, $data) {
syslog(LOG_WARNING,'Booking::update(): '.json_encode($data));
$q = self::buildUpdateSQL($data);
syslog(LOG_WARNING,$q);
$r = pg_query($db, $q);
$log = [
'affected_row' => pg_affected_rows($r),
'message' => 'Update Booking',
'data' =>$data,
'query' =>$q
];
Logger::debug($log);
if ($r && pg_affected_rows($r)) {
return array(self::getById($db, $data["id"]),NULL);
}
return array(NULL, pg_last_error($db));
}
public static function buildUpdateSQL($data) {
$q = "UPDATE booking SET";
foreach ($data as $key => $value) {
if ($key != 'id') {
if (is_int($value) || "now()" == $value) {
$q .= " ${key}=${value},";
} else {
$q .= " ${key}='".pg_escape_string($value)."',";
}
}
}
$q = substr($q, 0, -1);
$q .= " WHERE id=".$data["id"];
return $q;
}
public static function createDetails($db, $data) {
syslog(LOG_WARNING,'Booking::createDetails($db, $data)');
$booking_id = $data['booking_id'];
$action = $data['action'];
$details = $data['details'];
$message = $data['message'];
$request = $data['request'];
$q = "INSERT INTO booking_details(booking_id, action, details, message, request) VALUES (${booking_id}, '${action}', '${details}', '${message}', '${request}') RETURNING id";
$log = [
'message' => 'Insert Booking Details',
'data' =>$data,
'query' =>$q
];
Logger::debug($log);
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
return [self::getDetailsById($db, $f["id"]),NULL];
}
syslog(LOG_WARNING,pg_last_error($db));
return [NULL,pg_last_error($db)];
}
public static function getDetailsById($db, $id) {
syslog( LOG_WARNING, "Booking::getDetailsById(\$db, $id)" );
Logger::debug( "Booking::getDetailsById(\$db, $id)" );
$result = [];
$q = "SELECT id, booking_id, action, details, message FROM booking_details WHERE id=${id}";
$r = pg_query( $db, $q );
if ( $r && pg_num_rows( $r ) && $f = pg_fetch_assoc( $r ) ) {
$result = $f;
}
return $result;
}
public static function getDetailsByBookingId($db, $booking_id) {
syslog( LOG_WARNING, "Booking::getDetailsByBookingId(\$db, $booking_id)" );
Logger::debug( "Booking::getDetailsByBookingId(\$db, $booking_id)" );
$result = [];
$q = "SELECT id, booking_id, action, details, message FROM booking_details WHERE booking_id=${booking_id} ORDER BY id";
$r = pg_query( $db, $q );
$result = [];
if ( $r && pg_num_rows( $r ) ) {
while ($row = pg_fetch_assoc($r)) {
array_push($result, $row);
}
}
return $result;
}
public static function getMemberBySessionId($db, $sessionId) {
syslog( LOG_WARNING, "Booking::getMemberBySessionId(\$db, $sessionId)" );
Logger::debug( "Booking::getMemberBySessionId(\$db, $sessionId)" );
$result = [];
$q = "SELECT * FROM public.members_session WHERE session = ${sessionId} LIMIT 1 OFFSET 0";
$r = pg_query( $db, $q );
if ( $r && pg_num_rows( $r ) && $f = pg_fetch_assoc( $r )) {
return [$f, NULL];
}
syslog(LOG_WARNING,pg_last_error($db));
return [NULL,pg_last_error($db)];
}
}
+433
View File
@@ -0,0 +1,433 @@
<?php
class Options {
public static function getTransportOptions($db, $origin, $destination) {
return Options::getServicesByLatitudeLongitude($db, $origin["lat"],$origin["lng"]);
}
public static function getServicesByLatitudeLongitude($db, $lat, $lng) {
// $lat,$lng => geofence_area_country
$geofence_area_country = [];
$q = "SELECT * FROM (SELECT *,ST_DistanceSphere(location::geometry,ST_SetSRID(ST_MakePoint(";
$q.= $lng.",".$lat."),4326)::geometry) AS distance FROM geofence_area_country ";
$q.= " WHERE ST_DWithin(location, ST_SetSRID(ST_MakePoint(";
$q.= $lng.",".$lat."),4326)::geography,radius)) AS a ORDER BY a.distance ASC LIMIT 1";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$geofence_area_country = $f;
} else {
// Fatal
$err = pg_last_error($db);
$err = $err ? $err : $q;
error_log('Options::getTransportOptions => '.$err);
throw new Exception("Failed to match input origin to a country");
}
// geofence_area_country => country_servies
$country_services = [];
if (array_key_exists("id",$geofence_area_country) && $geofence_area_country["id"]>0) {
$q = "SELECT * FROM country_services WHERE country_id=".$geofence_area_country["id"]." AND status=1";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
while ($f=pg_fetch_assoc($r)) {
$country_services[] = $f;
}
} else {
// Non-fatal, try to get city services next
$err = pg_last_error($db);
$err = $err ? $err : $q;
error_log('Options::getTransportOptions => '.$err);
}
}
// $lat,$lng => geofence_area_city
$geofence_area_city = [];
$q = "SELECT * FROM (SELECT *,ST_DistanceSphere(location::geometry,ST_SetSRID(ST_MakePoint(";
$q.= $lng.",".$lat."),4326)::geometry) AS distance FROM geofence_area_city ";
$q.= " WHERE ST_DWithin(location, ST_SetSRID(ST_MakePoint(";
$q.= $lng.",".$lat."),4326)::geography,radius)) AS a ORDER BY a.distance ASC LIMIT 1";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$geofence_area_city = $f;
} else {
// Non-fatal, at least we know the country
$err = pg_last_error($db);
$err = $err ? $err : $q;
error_log('Options::getTransportOptions => '.$err);
}
// geofence_area_city => city_services
$city_services = [];
if (array_key_exists("id",$geofence_area_city) && $geofence_area_city["id"]>0) {
$q = "SELECT * FROM city_services WHERE city_id=".$geofence_area_city["id"]." AND status=1";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
while ($f=pg_fetch_assoc($r)) {
$city_services[] = $f;
}
} else {
// Non-fatal, if we have country services (let upstream decide what to do)
$err = pg_last_error($db);
$err = $err ? $err : $q;
error_log('Options::getTransportOptions => '.$err);
}
}
if (is_array($country_services) && count($country_services)>0 && (!is_array($city_services) || count($city_services)<1)) {
$services = $country_services;
} else if (is_array($city_services) && count($city_services)>0 && (!is_array($country_services) || count($country_services)<1)) {
$services = $city_services;
} else {
$services = $city_services;
foreach ($country_services as $country_service) {
$exists = false;
foreach($services as $service) {
if ($service["transport_provider_id"]>0
&& $service["transport_provider_id"]==$country_service["transport_provider_id"]) {
// The service is available on city level - skip
$exists = true;
break;
}
}
if (!$exists) {
// Add country service
$services[] = $country_service;
}
}
}
return [
$services,
[
'geofence_area_country' => $geofence_area_country,
'country_services' => $country_services,
'geofence_area_city' => $geofence_area_city,
'city_services' => $city_services
]
];
}
public static function processGooglePlace($db, $data) {
// data.address
// data.place
// data.lat
// data.lng
// data.utc_offset_minutes
// data.postal
// data.vicinity
// data.city
// data.country
$db_address = pg_escape_string(html_entity_decode($data["address"],ENT_QUOTES|ENT_HTML5,"UTF-8"));
$db_lat = floatval($data["lat"]);
$db_lng = floatval($data["lng"]);
$db_city = pg_escape_string($data["city"]);
$db_postal = pg_escape_string($data["postal"]);
$db_country = pg_escape_string($data["country"]);
// Step 1: Check address
$q = "SELECT a.*,b.timezone AS \"timeZoneId\" FROM address a LEFT JOIN address_timezone b ON (b.id=a.timezone) ";
$q .= " WHERE LOWER(a.address)=LOWER('${db_address}')";
$q .= "AND latitude<>0 AND longitude<>0 AND geocoding_date IS NOT NULL AND postal IS NOT NULL ";
$q .= "ORDER BY geocoding_date DESC LIMIT 1";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
$f["address"] = html_entity_decode ($f["address"],ENT_QUOTES|ENT_HTML5,"UTF-8");
foreach ($f as $key=>$val) {
$data[$key] = $val;
}
return $data;
}
// Step 2: Get timezone
date_default_timezone_set('UTC');
$seconds = $data["utc_offset_minutes"] * 60;
$timezone = timezone_name_from_abbr('', $seconds, 1);
$q = "SELECT * FROM address_timezone WHERE LOWER(timezone)=LOWER('${timezone}')";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$data["timezone"] = $f["id"];
$data["timeZoneId"] = $f["timezone"];
} else {
$q = "INSERT INTO address_timezone (timezone) VALUES('".pg_escape_string($timezone)."') RETURNING *";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$data["timezone"] = $f["id"];
$data["timeZoneId"] = $f["timezone"];
} else { // Most likely this is very wrong...
$data["timezone"] = 1; // Singapore
$data["timeZoneId"] = 'Asia/Singapore';
}
}
// Step 3: Get city
$q = "SELECT id FROM geofence_area_city WHERE LOWER(city) ILIKE '" . strtolower($db_city) . "' AND country='${db_country}' LIMIT 1";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f = pg_fetch_row($r)) {
$data["city_id"] = $f[0];
} else {
$q = "INSERT INTO geofence_area_city (city,country,latitude,longitude,location) VALUES (";
$q.= "'${db_city}','${db_country}',${db_lat},${db_lng},ST_SetSRID(ST_MakePoint(${db_lng},${db_lat}), 4326)) RETURNING id";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f = pg_fetch_row($r)) {
$data["city_id"] = $f[0];
} else {
// Most likely this is very wrong...
$data["city_id"] = 1; // Singapore
}
}
// Step 4: Get closest postal if missing...
$db_tz = $data["timezone"];
if (empty($db_postal)) {
$distance = 50000; // 50 km
$q = "SELECT postal,
ST_DistanceSphere(geometry::geometry, ST_SetSRID(ST_MakePoint(${db_lng},${db_lat}),4326)) AS distance
FROM geoname_postal_code
WHERE ST_DistanceSphere(geometry::geometry, ST_SetSRID(ST_MakePoint(${db_lng},${db_lat}),4326)) BETWEEN 0 AND ${distance} AND country='${db_country}'
ORDER BY distance
LIMIT 1;";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
$db_postal = $f["postal"];
$data["postal"] = $f["postal"];
}
}
// Step 5: Save address
$q = "INSERT INTO address (address,latitude,longitude,timezone,geocoding_date,postal,country,geometry, city_id) VALUES (";
$q.= "'${db_address}',${db_lat},${db_lng}," . ($db_tz == null ? "NULL" : $db_tz);
$q.= ",now(),'${db_postal}','${db_country}',ST_SetSRID(ST_MakePoint(${db_lng},${db_lat}), 4326),'".$data["city_id"]."')";
$q.= " RETURNING id, address, latitude as lat, longitude as lng, postal, country";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) {
$data["id"] = $f["id"];
$data["address"] = $f["address"];
$data["lat"] = $f["lat"];
$data["lng"] = $f["lng"];
$data["geometry"] = $f["geometry"];
$data["postal"] = $f["postal"];
}
return $data;
}
public static function checkValidLocations($db, $origin, $destination) {
// Check for Google places result
if (is_array($origin) && array_key_exists('place',$origin) && is_array($origin['place'])) {
$origin = Options::processGooglePlace($db, $origin);
}
// Basic input sanity check: origin
if (!is_array($origin) || !array_key_exists('address',$origin)
|| !array_key_exists('lat',$origin) || !array_key_exists('lng',$origin)
|| !array_key_exists('id',$origin) || $origin['id']<1) {
throw new Exception('Invalid input origin');
}
// Check for Google places result
if (is_array($destination) && array_key_exists('place',$destination) && is_array($destination['place'])) {
$destination = Options::processGooglePlace($db, $destination);
}
// Basic input sanity check: destination
if (!is_array($destination) || !array_key_exists('address',$destination)
|| !array_key_exists('lat',$destination) || !array_key_exists('lng',$destination)
|| !array_key_exists('id',$destination) || $destination['id']<1) {
throw new Exception('Invalid input destination');
}
// Basic input sanity check: GPS coordinates
if ($origin['lat']==null || $origin['lng']==null || $destination['lat']==null || $destination['lng']==null
|| ($origin['lat']==0 && $origin['lng']==0) || ($destination['lat']==0 && $destination['lng']==0)) {
throw new Exception('Invalid origin and/or destination coordinates');
}
return [$origin, $destination];
}
public static function checkValidOption($db, $option) {
// Basic input sanity check: option
if (!is_array($option) || !array_key_exists('name',$option)
|| !array_key_exists('transport_provider_id',$option) || $option['transport_provider_id']<1
|| !array_key_exists('id',$option) || $option['id']<1) {
throw new Exception('Invalid transport option');
}
}
public static function checkValidTrip($db, $trip) {
// TODO!
}
public static function createTrip($db, $country, $duration, $distance, $location_start_id, $location_end_id) {
error_log("Options::createTrip(\$db, $country, $duration, $distance, $location_start_id, $location_end_id)");
$data = [
'travel_date' => Options::getDate($country),
'duration' => ((int)($duration/60)),
'cost_raw' => NULL,
'trackedemail_item_id' => NULL,
'cost' => NULL,
'distance' => sprintf("%0.02f",$distance/1000.0),
'transport_provider_id' => NULL,
'scheduled' => NULL,
'travel_date_end' => Options::getDate($country),
'dup_id' => NULL,
'location_start_id' => $location_start_id,
'location_end_id' => $location_end_id,
'private' => 't', /* this is a private entry without the source e-mail */
'booking' => 't' /* this is a booking entry */
];
list($id, $err) = Options::saveTrip($db->getConnect(), $data);
if ($id && $id>0) {
$trip = Options::getTripById($db->getConnect(), $id);
return $trip;
}
return NULL; // WTF?
}
public static function getTripById($db, $id) {
$result = array();
$q = "SELECT a.*,
b.address AS location_start, b.latitude AS location_start_lat, b.longitude AS location_start_lng,
c.address AS location_end, c.latitude AS location_end_lat, c.longitude AS location_end_lng,
a.id AS parsedemail_item_id
FROM parsedemail_item a
LEFT JOIN address b ON (b.id=a.location_start_id)
LEFT JOIN address c ON (c.id=a.location_end_id)
WHERE a.id=${id}";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$f["location_start"] = html_entity_decode ($f["location_start"],ENT_QUOTES|ENT_HTML5,"UTF-8");
$f["location_end"] = html_entity_decode ($f["location_end"],ENT_QUOTES|ENT_HTML5,"UTF-8");
$result = $f;
}
return $result;
}
public static function saveTrip($db, $data) {
$fields = array(
'travel_date' => false,
'duration' => false,
'cost_raw' => false,
'trackedemail_item_id' => false,
'cost' => false,
'updated' => false,
'distance' => false,
'transport_provider_id' => false,
'scheduled' => false,
'travel_date_end' => false,
'dup_id' => false,
'location_start_id' => true,
'location_end_id' => true,
'private' => false,
'booking' => false
);
$field_key_list = [];
$field_val_list = [];
foreach ($fields as $key=>$required) {
if ($required && !isset($data[$key])) {
return [NULL, "Missing required field '${key}'"];
}
if (!isset($data[$key])) continue;
$field_key_list[] = "${key}";
$field_val_list[] = (($data[$key]==NULL || $data[$key]=="")?"NULL":"'".pg_escape_string($data[$key])."'");
}
$q = "INSERT INTO parsedemail_item (".implode(",",$field_key_list).")
VALUES(".implode(",",$field_val_list).") RETURNING id";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
$f = pg_fetch_row($r);
return [$f[0], NULL];
}
return [NULL, pg_last_error($db)];
}
public static function validateTrip($db, $trip) {
if (!is_array($trip) || !array_key_exists("id",$trip) || $trip["id"]<1) {
return false;
}
if (!array_key_exists("options",$trip) || !is_array($trip["options"]) || !array_key_exists("legs",$trip["options"])
|| !is_array($trip["options"]["legs"]) || count($trip["options"]["legs"])<1
|| !array_key_exists("id",$trip["options"]["legs"][0]) || $trip["options"]["legs"][0]["id"]<1) {
return false;
}
return true;
}
public static function saveTripAdvice($db, $trip) {
error_log('Options::saveTripAdvice($db, $trip)');
if (!is_array($trip) || !array_key_exists("id",$trip) || $trip["id"]<1) {
return NULL;
}
$q = "INSERT INTO parsedemail_item_advice_google (parsedemail_item_id,routes,source) VALUES (".$trip["id"].",1,'".$trip["source"]."') RETURNING id";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) {
return $f[0];
}
return NULL;
}
public static function saveTripAdviceLeg($db, $trip, $leg) {
error_log('Options::saveTripAdviceLeg($db, $trip, $leg)');
$fare_raw = "NULL";
if (array_key_exists("fare_raw",$leg) && $leg["fare_raw"]>0) {
$fare_raw = (int)$leg["fare_raw"];
}
$q = "INSERT INTO google_directions_legs (parsedemail_item_advice_google_id,arrival_time,arrival_time_zone,";
$q.= "departure_time,departure_time_zone,distance,duration,steps,fare_raw,polyline) VALUES(";
$q.= $leg["parsedemail_item_advice_google_id"].",'".pg_escape_string($leg["arrival_time"])."',".((int)$leg["arrival_time_zone"]);
$q.= ",'".pg_escape_string($leg["departure_time"])."',".((int)$leg["departure_time_zone"]).",";
$q.= ((int)$leg["distance"]).",".((int)$leg["duration"]).",1,${fare_raw},'".pg_escape_string($leg["polyline"])."') RETURNING id";
$r = pg_query($db, $q);
error_log($q);
if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) {
return $f[0];
}
return NULL;
}
public static function saveTripAdviceLegStep($db, $trip, $leg, $step) {
error_log('Options::saveTripAdviceLegStep($db, $trip, $leg, $step)');
$q = "INSERT INTO google_directions_leg_steps (google_directions_leg_id,distance,duration,travel_mode,";
$q.= "location_start_lat,location_start_lng,location_end_lat,location_end_lng,html_instructions,polyline) VALUES (";
$q.= $leg["id"].",".((int)$step["distance"]).",".((int)$step["duration"]).",";
$q.= "'".pg_escape_string($step["travel_mode"])."',".$step["location_start_lat"].",".$step["location_start_lng"].",";
$q.= $step["location_end_lat"].",".$step["location_end_lng"].",'".pg_escape_string($step["html_instructions"])."',";
$q.= "'".pg_escape_string($step["polyline"])."') RETURNING id";
$r = pg_query($db, $q);
error_log($q);
if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) {
return $f[0];
}
return NULL;
}
public static function saveTripAdviceLegStepQuote($db, $trip, $leg, $step) {
$fare_raw = NULL;
if (array_key_exists("fare_raw",$step) && $step["fare_raw"]>0) {
$fare_raw = (int)$step["fare_raw"];
}
if ($fare_raw>0) {
$q = "INSERT INTO leg_step_quote (google_directions_leg_step_id,name,service,board,alight,";
$q.= "distance,fare,fare_raw,distance_raw) VALUES (".$step["id"].",";
$q.= "'".pg_escape_string($trip["source"])."','".pg_escape_string($trip["source"])."',";
$q.= "'".pg_escape_string($trip["location_start"])."','".pg_escape_string($trip["location_end"])."',";
$q.= "'".sprintf("%0.02f",$step["distance"]/1000.0)."','".sprintf("%0.02f",$fare_raw/100.0)."',";
$q.= ((int)$fare_raw).",".((int)$step["distance"]).") RETURNING id";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) {
return $f[0];
}
}
return NULL;
}
public static function getDate($country) {
$tz = 'Asia/Singapore';
if ($country=='US') {
$tz = 'America/Los_Angeles';
}
$timestamp = time();
$dt = new DateTime("now", new DateTimeZone($tz)); //first argument "must" be a string
$dt->setTimestamp($timestamp); //adjust the object to correct timestamp
return $dt->format('Y-m-d, H:i:s');
}
public static function getTimezoneById($db, $id, $def='Asia/Singapore') {
$q = "SELECT timezone FROM address_timezone WHERE id=".((int)$id);
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
return $f['timezone'];
}
return $def; //$id;
}
}
// vi:ts=2
+110
View File
@@ -0,0 +1,110 @@
<?php
class OptionsApi extends Api
{
public $apiName = 'options';
public function indexAction()
{
$message = "Unexpected options error";
$latitude = $this->requestParams["latitude"] ?? 0;
$longitude = $this->requestParams["longitude"] ?? 0;
try {
if ($latitude==null || $longitude==null || ($latitude==0 || $longitude==0)) {
throw new Exception('Invalid location: '.$latitude.",".$longitude);
}
$db = new Db();
list ($res, $details) = Options::getServicesByLatitudeLongitude($db->getConnect(), $latitude, $longitude);
if (is_array($res)) {
return $this->response([
'services' => $res,
'details' => $details
], 200);
} else {
throw new Exception("Failed to get transport options for origin");
}
} catch (Exception $e) {
$message = $e->getMessage();
}
return $this->response(
array(
"error" => $message
), 500);
}
/**
* Method GET
* Get single record (by id)
* http://DOMAIN/remove/1
* @return string
*/
public function viewAction()
{
//id must be the first parameter after /remove/x
return $this->response(
array(
'error'=> 'Data not found'
), 404);
}
public function createAction()
{
$message = "Unexpected options error";
$origin = $this->requestParams["origin"] ?? [];
$destination = $this->requestParams["destination"] ?? [];
$create = $this->requestParams["create"] ?? false;
$country = $this->requestParams["country"] ?? NULL;
$duration = $this->requestParams["duration"] ?? 0;
$distance = $this->requestParams["distance"] ?? 0;
$member_id = $this->requestParams["member_id"] ?? 0;
try {
$db = new Db();
list ($origin, $destination) = Options::checkValidLocations($db->getConnect(), $origin, $destination);
list ($res, $details) = Options::getTransportOptions($db->getConnect(), $origin, $destination);
if (is_array($res)) {
$trip = NULL;
if ($create) {
$country = $origin["country"];
$trip = Options::createTrip($db, $country, $duration, $distance, $origin["id"], $destination["id"]);
}
return $this->response([
'services' => $res,
'details' => $details,
'trip' => $trip
], 200);
} else {
throw new Exception("Failed to get transport options for origin");
}
} catch (Exception $e) {
$message = $e->getMessage();
}
return $this->response(
array(
"error" => $message
), 500);
}
public function updateAction()
{
return $this->response(
array(
"error" => "Update error"
), 400);
}
public function deleteAction()
{
return $this->response(
array(
"error" => "Delete error"
), 500);
}
}
+643
View File
@@ -0,0 +1,643 @@
<?php
class Quote {
const QUOTE_CHECK_RETRIES = 10;
const QUOTE_CHECK_TIMEOUT = 3;
public static function getTransportQuote($db, $origin, $destination, $option, $trip, $route) {
$quote = [];
switch ($option["transport_provider_id"]) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 8:
list($option, $trip, $quote) = Quote::rideshareQuote($db, $origin, $destination, $option, $trip);
break;
case 11: // BART
list($option, $trip, $quote) = Quote::bartQuote($db, $origin, $destination, $option, $trip);
break;
case 12: // Muni
list($option, $trip, $quote) = Quote::muniQuote($db, $origin, $destination, $option, $trip);
break;
case 13: // LTA
list($option, $trip, $quote) = Quote::ltaQuote($db, $origin, $destination, $option, $trip);
break;
case 14: // MARTA
list($option, $trip, $quote) = Quote::martaQuote($db, $origin, $destination, $option, $trip);
break;
}
return [$option, $trip, $quote];
}
public static function rideshareQuote($db, $origin, $destination, $option, $trip) {
$res = [];
$input = [
"origin" => $origin,
"destination" => $destination,
"member_id" => $option["member_id"],
"country" => $origin["country"],
"transport_provider_id" => $option["transport_provider_id"],
"group_quote_id" => 0,
"trackedemail_item_id" => 0,
"prefill" => 'f'
];
list($res,$body) = Quote::postServiceCall("/trips/api/quote", json_encode($input));
$log = [
'message' => 'BookingQuote::rideshareQuote',
'function' =>'Quote::postServiceCall()',
'data' =>$input,
'response' => $res
];
Logger::debug($log);
if (!is_array($res) || !array_key_exists("id",$res) || $res["id"]<1) {
if (!is_array($res)) {
error_log($res);
}
error_log($body);
throw new Exception("Failed to schedule quote");
}
if (array_key_exists("complete",$res) && $res["complete"]!=NULL && $res["complete"]!="") {
if (array_key_exists("cost",$res) && $res["cost"]>0) {
$trip = Quote::patchRideshareTrip($trip, $res["cost"]);
return [$option, $trip, $res];
} else {
throw new Exception("Failed to get the quote");
// $res["message"] ?
// return [$option, $trip, $res]; // Do we want to recover?
}
}
// Will try for 30 seconds?
$endpoint = "/trips/api/quote/".$res["id"];
$i = 0;
while ($i<Quote::QUOTE_CHECK_RETRIES) {
sleep(Quote::QUOTE_CHECK_TIMEOUT);
list($res,$body) = Quote::getServiceCall($endpoint);
$log = [
'message' => 'BookingQuote::rideshareQuote',
'function' =>'Quote::getServiceCall()',
'data' =>$endpoint,
'response' => $res
];
Logger::debug($log);
if (!is_array($res) || !array_key_exists("id",$res) || $res["id"]<1) {
continue; // Try again...
}
if (array_key_exists("complete",$res) && $res["complete"]!=NULL && $res["complete"]!="") {
break; // No mater the result, maybe client will want to recover
}
$i++;
}
if (!is_array($res) || !array_key_exists("cost",$res) || !isset($res["cost"]) || $res["cost"] == 0) {
$avgPriceInfo = Quote::averagePrice($db, $origin, $destination, $option["transport_provider_id"]);
if (isset($avgPriceInfo) && array_key_exists('cost', $avgPriceInfo)) {
$res["cost"] = $avgPriceInfo['cost'];
$res["is_average_cost"] = true;
};
}
$trip = Quote::patchRideshareTrip($trip, $res["cost"]);
Quote::updateRideshareCosts($db, $trip);
return [$option, $trip, $res]; // last check quote result
}
public static function patchRideshareTrip($trip, $cost) {
// Do we save the quote?
$fare_raw = ((int)(100.0*$cost));
if (array_key_exists("options",$trip)) {
$gid = $trip["options"]["gid"];
$trip["options"]["legs"][0]["fare_raw"] = $fare_raw;
$trip["options"]["leg_fare"][$gid] = $fare_raw;
$trip["options"]["leg_steps"][$gid][0]["fare_raw"] = $fare_raw;
} else {
$trip["options"] = [
"gid" => "1",
"routes" => 1,
"legs" => [
[
"id" => 1,
"fare_raw" => $fare_raw
]
],
"leg_steps" => [
1 => [
"fare_raw" => $fare_raw
]
],
"leg_fare" => [
1 => $fare_raw
]
];
}
return $trip;
}
public static function updateRideshareCosts($db, $trip) {
if (array_key_exists("options",$trip)) {
$gid = $trip["options"]["legs"][0]["id"];
if ($gid>1 && $trip["options"]["legs"][0]["fare_raw"]>0) {
$q = "UPDATE google_directions_legs SET fare_raw=".((int)$trip["options"]["legs"][0]["fare_raw"]);
$q.= " WHERE id=".((int)$gid);
$r = pg_query($db, $q);
Options::saveTripAdviceLegStepQuote(
$db, $trip, $trip["options"]["legs"][0], $trip["options"]["leg_steps"][$gid][0]);
}
}
}
/*
{
"id": "224583",
"transport_provider_id": "4",
"automation_id": "206441",
"cost_raw": null,
"cost": null,
"created": "2020-01-09 13:09:01.072305",
"completed": null,
"member_id": "13",
"location_start_id": "4426",
"location_end_id": "4386",
"quote_group_id": "0",
"prefill": "f",
"location_start": "97 Meyer Rd, 93, Singapore 437918",
"location_start_lat": "1.2970849",
"location_start_lng": "103.8925712",
"location_start_tz": "1",
"location_geocoding_date": "2019-10-11",
"location_end": "1 Fusionopolis Way, #01-07 & #02-14, Connexis, Singapore 138632",
"location_end_lat": "1.2987049",
"location_end_lng": "103.7875699",
"deeplink": "ComfortDelGroTaxi:\/\/\/?action=setBooking&endingLat=1.298705&endingLong=103.787570&startingLat=1.297085&startingLong=103.892571"
}
*/
/*
{
"id": 224583,
"location_start": "97 Meyer Rd, 93, Singapore 437918",
"location_end": "1 Fusionopolis Way, #01-07 & #02-14, Connexis, Singapore 138632",
"cost_raw": "14.6",
"trackedemail_item_id": 0,
"cost": "14.60",
"transport_provider_id": 4,
"location_start_lat": "1.2970576",
"location_start_lng": "103.8925856",
"location_end_lat": "1.2987049",
"location_end_lng": "103.7875699",
"request_date": "2020-01-09T13:09:00.955Z",
"started": "2020-01-09T13:09:04.203Z",
"complete": "2020-01-09T13:09:04.743Z",
"status": 0,
"message": "android_automation_job_detail",
"attempts": 0,
"automation_id": 206441,
"completed": "2020-01-09T13:09:04.743Z",
"created": "2020-01-09 13:09:01.072305",
"location_start_id": "4426",
"location_end_id": "4386",
"member_id": "13",
"deeplink": "ComfortDelGroTaxi:\/\/\/?action=setBooking&endingLat=1.298705&endingLong=103.787570&startingLat=1.297085&startingLong=103.892571"
}
*/
public static function bartQuote($db, $origin, $destination, $option, $trip) {
$quote = [];
$quote["cost_raw"] = NULL;
$quote["transport_provider_id"] = $option["transport_provider_id"];
$quote["deeplink"] = NULL;
//$quote["leg_fare"] = $trip["options"]["leg_fare"];
$leg = [
"fare_raw" => 1
];
// Select leg
//error_log(">>>>>>>>> ".json_encode($trip));
foreach ($trip["options"]["legs"] as $leg) {
break;
}
$quote["cost"] = sprintf("%0.02f",$leg["fare_raw"]/100.0);
return [$option, $trip, $quote];
}
public static function muniQuote($db, $origin, $destination, $option, $trip) {
$quote = [];
$quote["cost_raw"] = NULL;
$quote["transport_provider_id"] = $option["transport_provider_id"];
$quote["deeplink"] = NULL;
//$quote["leg_fare"] = $trip["options"]["leg_fare"];
$leg = [
"fare_raw" => 1
];
// Select leg
//error_log(">>>>>>>>> ".json_encode($trip));
foreach ($trip["options"]["legs"] as $leg) {
break;
}
$quote["cost"] = sprintf("%0.02f",$leg["fare_raw"]/100.0);
return [$option, $trip, $quote];
}
public static function ltaQuote($db, $origin, $destination, $option, $trip) {
$quote = [];
$cost = 0;
$quote["cost_raw"] = NULL;
$quote["transport_provider_id"] = $option["transport_provider_id"];
$quote["deeplink"] = NULL;
$legs = $trip["options"]["legs"];
$leg_steps = $trip["options"]["leg_steps"];
$leg_fare = $trip["options"]["leg_fare"];
foreach ($legs as $i=>$leg) {
$leg_id = $leg["id"];
if (array_key_exists($leg_id,$leg_fare) && $leg_fare[$leg_id]>0) {
if ($cost==0) $cost = $leg["fare_raw"];
continue;
}
if (array_key_exists("fare_raw",$leg) && $leg["fare_raw"]>0) {
if ($cost==0) $cost = $leg["fare_raw"];
continue;
}
list ($leg, $leg_steps) = Quote::legStepsQuote($db, $origin, $destination, $option, $trip, $leg, $leg_steps);
$leg_fare[$leg_id] = $leg["fare_raw"];
if ($cost==0 && $leg["fare_raw"]>0) $cost = $leg["fare_raw"];
$legs[$i] = $leg;
}
$trip["options"]["legs"] = $legs;
$trip["options"]["leg_steps"] = $leg_steps;
$trip["options"]["leg_fare"] = $leg_fare;
$quote["cost"] = $cost;
//$quote["leg_fare"] = $leg_fare;
// Update model to save the quote
Quote::updateQuotes($db, $trip);
return [$option, $trip, $quote];
}
public static function martaQuote($db, $origin, $destination, $option, $trip) {
$quote = [];
$quote["cost_raw"] = NULL;
$quote["transport_provider_id"] = $option["transport_provider_id"];
$quote["deeplink"] = NULL;
//$quote["leg_fare"] = $trip["options"]["leg_fare"];
$leg = [
"fare_raw" => 1
];
// Select leg
//error_log(">>>>>>>>> ".json_encode($trip));
foreach ($trip["options"]["legs"] as $leg) {
break;
}
$quote["cost"] = sprintf("%0.02f",$leg["fare_raw"]/100.0);
return [$option, $trip, $quote];
}
public static function updateQuotes($db, $trip) {
$legs = $trip["options"]["legs"];
$leg_steps = $trip["options"]["leg_steps"];
$leg_fare = $trip["options"]["leg_fare"];
foreach ($legs as $leg) {
$leg_id = $leg["id"];
$steps = $leg_steps[$leg_id];
foreach ($steps as $step) {
if (array_key_exists("fare_raw",$step) && $step["fare_raw"]>0) {
$q = "SELECT id FROM leg_step_quote WHERE google_directions_leg_step_id=".$step["id"];
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) {
$q = "UPDATE leg_step_quote SET fare_raw=".((int)$step["fare_raw"])." WHERE id=".$f[0];
$r = pg_query($db, $q);
} else {
$q = "INSERT INTO leg_step_quote (google_directions_leg_step_id,name,service,board,alight,distance,fare,fare_raw,distance_raw) VALUES(";
$q.= $step["id"].",'".pg_escape_string($step["short_line"])."','".pg_escape_string($step["line"])."',";
$q.= "'".pg_escape_string($step["departure_stop"])."','".pg_escape_string($step["arrival_stop"])."',";
$q.= "'".sprintf("%0.02f",$step["distance"]/100.0)."','".sprintf("%0.02f",$step["fare_raw"]/100.0)."',";
$q.= ((int)$step["fare_raw"]).",".((int)$step["distance"]).")";
$r = pg_query($db, $q);
}
}
}
if (array_key_exists("fare_raw",$leg) && $leg["fare_raw"]>0) {
$q = "UPDATE google_directions_legs SET fare_raw=".((int)$leg["fare_raw"])." WHERE id=${leg_id}";
$r = pg_query($db, $q);
continue;
}
if (array_key_exists($leg_id,$leg_fare) && $leg_fare[$leg_id]>0) {
$q = "UPDATE google_directions_legs SET fare_raw=".((int)$leg["fare_raw"])." WHERE id=${leg_id}";
$r = pg_query($db, $q);
}
}
}
public static function legStepsQuote($db, $origin, $destination, $option, $trip, $leg, $leg_steps) {
$leg_id = $leg["id"];
$steps = $leg_steps[$leg_id];
$fare_raw = 0;
foreach ($steps as $i=>$step) {
if (array_key_exists("fare_raw",$step) && $step["fare_raw"]>0) {
$fare_raw += $step["fare_raw"];
continue;
}
$step = Quote::stepQuote($db, $origin, $destination, $option, $trip, $step);
if (array_key_exists("fare_raw",$step) && $step["fare_raw"]>0) {
$fare_raw += $step["fare_raw"];
}
$steps[$i] = $step;
}
$leg["fare_raw"] = $fare_raw;
$leg_steps[$leg_id] = $steps;
return [$leg, $leg_steps];
}
public static function stepQuote($db, $origin, $destination, $option, $trip, $step) {
switch ($step["travel_mode"]) {
case "": break;
case "BIKE":
$bike_time = $step['duration'];
$country = $origin['country'];
list($price,$err) = SGBikeApi::quote($db, $bike_time, $country,$step);
if (!empty($price) && $price>0) {
$step["fare_raw"] = $price;
}
break;
case "WALKING": break;
case "NUS_TRANSIT":
// NUS Internal Shuttle Buses are free of charge
// http://www.nus.edu.sg/celc/symposium/Symposium%20Documents/Symposium%202016%20Visitors'%20Guide.pdf
break;
case "PARK": break;
case "SCOOTER":
// No scooter quotes right now
// list($res,$err) = ScooterApi::quote($db, $scooter_time, $country);
break;
case "TAXI":
// We do not know how to quote "TAXI" in another countries
if ($origin["country"]=="SG") {
// Reverse geocode the "TAXI" step
$input = [
"latitude" => $step["location_start_lat"],
"longitude" => $step["location_start_lng"],
"country" => $origin["country"]
];
list($originTaxi, $body) = Quote::putServiceCall("/trips/api/geocode",$input);
$input = [
"latitude" => $step["location_end_lat"],
"longitude" => $step["location_end_lng"],
"country" => $destination["country"]
];
list($destinationTaxi, $body) = Quote::putServiceCall("/trips/api/geocode",$input);
if (!is_array($originTaxi) || !array_key_exists("address",$originTaxi)
|| !is_array($destinationTaxi) || !array_key_exists("address",$destinationTaxi)) {
break;
}
$option["transport_provider_id"] = 4; // ComfortDelGro
list($optionTaxi, $tripTaxi, $quoteTaxi) =
Quote::rideshareQuote($db, $originTaxi, $destinationTaxi, $option, $trip);
if (is_array($quoteTaxi) && array_key_exists("cost",$quoteTaxi) && $quoteTaxi["cost"]>0) {
$step["fare_raw"] = (int)(100.0*$quoteTaxi["cost"]); // fare_raw is in cents
}
}
break;
case "TRANSIT":
// We do not know how to quote "TRANSIT" in another countries from here (must be specific)
if ($origin["country"]=="SG") {
$step_id = $step["id"];
$result = Quote::mytansportsgServiceStepQuote($step_id);
error_log('***********************************************************************');
error_log(json_encode($result));
error_log('***********************************************************************');
// {"code":0,"data":{"307575":{"total":92}}}
if (is_array($result) && array_key_exists("code",$result) && $result["code"]===0 && array_key_exists("data",$result)) {
$data = $result["data"];
if (is_array($data) && array_key_exists($step_id,$data) && is_array($data[$step_id]) && array_key_exists('total',$data[$step_id]) && $data[$step_id]['total']>0) {
// Conversion ???
$step["fare_raw"] = (int)$data[$step_id]['total']; // fare_raw is in cents
}
}
}
break;
}
return $step;
}
/*
BIKE | 4743
NUS_TRANSIT | 367
PARK | 1796
SCOOTER | 1023
TAXI | 2060
TRANSIT | 88781
WALKING | 154766
| 3
*/
public static function postServiceCall($endpoint, $payload) {
global $savvyext;
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
$encryptionAlg = $savvyext->cfgReadChar('encryption.algorithm');
$encryptionKey = $savvyext->cfgReadChar('encryption.key');
$encryptionIV = $savvyext->cfgReadChar('encryption.iv');
$api_url = $savvyext->cfgReadChar('system.api_url');
$encrypted_payload = bin2hex(
openssl_encrypt(
$payload, $encryptionAlg, $encryptionKey, OPENSSL_RAW_DATA, $encryptionIV
));
$postdata = "{\"encrypted_payload\": \"${encrypted_payload}\"}";
$url = $api_url . $endpoint;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_VERBOSE, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Authorization: Server-Token ' . $httpAuthToken)
);
$body = curl_exec($ch);
$result = json_decode($body, true);
$payload = openssl_decrypt(
hex2bin(
$result['payload']
), $encryptionAlg, $encryptionKey, OPENSSL_RAW_DATA, $encryptionIV
);
$payload = json_decode($payload, true);
return [$payload, $body];
}
public static function putServiceCall($endpoint, $payload) {
global $savvyext;
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
$encryptionAlg = $savvyext->cfgReadChar('encryption.algorithm');
$encryptionKey = $savvyext->cfgReadChar('encryption.key');
$encryptionIV = $savvyext->cfgReadChar('encryption.iv');
$api_url = $savvyext->cfgReadChar('system.api_url');
/* $encrypted_payload = bin2hex(
openssl_encrypt(
$payload, $encryptionAlg, $encryptionKey, OPENSSL_RAW_DATA, $encryptionIV
));
$postdata = "{\"encrypted_payload\": \"${encrypted_payload}\"}"; */
$postdata = http_build_query($payload);
$url = $api_url . $endpoint;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_VERBOSE, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/x-www-form-urlencoded',
'Authorization: Server-Token ' . $httpAuthToken)
);
$body = curl_exec($ch);
$result = json_decode($body, true);
$payload = openssl_decrypt(
hex2bin(
$result['payload']
), $encryptionAlg, $encryptionKey, OPENSSL_RAW_DATA, $encryptionIV
);
$payload = json_decode($payload, true);
return [$payload, $body];
}
public static function getServiceCall($endpoint, $shouldDecrypt = true) {
global $savvyext;
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
$encryptionAlg = $savvyext->cfgReadChar('encryption.algorithm');
$encryptionKey = $savvyext->cfgReadChar('encryption.key');
$encryptionIV = $savvyext->cfgReadChar('encryption.iv');
$api_url = $savvyext->cfgReadChar('system.api_url');
$url = $api_url . $endpoint;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_VERBOSE, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Authorization: Server-Token ' . $httpAuthToken)
);
$body = curl_exec($ch);
if ($shouldDecrypt) {
$result = json_decode($body,true);
$payload = openssl_decrypt(
hex2bin(
$result['payload']
),
$encryptionAlg,
$encryptionKey,
OPENSSL_RAW_DATA,
$encryptionIV
);
} else {
$payload = $body;
}
$payload = json_decode($payload, true);
return [$payload, $body];
}
public static function mytansportsgServiceStepQuote($id) {
global $savvyext;
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
$oauth2_url = $savvyext->cfgReadChar('system.oauth2_url');
$url = $oauth2_url."mytransportsg/?step_id=" . $id;
$opts = array(
'http' => array(
'method' => "GET",
'timeout' => 60, /* 1 minute */
'header' =>
"Content-Type: application/x-www-form-urlencoded\r\n" .
"Accept: application/json\r\n" .
"Authorization: Server-Token ${httpAuthToken}\r\n",
),
"ssl" => array(
"verify_peer"=>false,
"verify_peer_name"=>false,
)
);
$context = stream_context_create($opts);
$body = file_get_contents($url, false, $context);
$advice = json_decode($body,true);
return $advice;
}
public static function averagePrice($db, $origin, $destination, $providerId) {
if (!array_key_exists('id', $origin) || !isset($origin['id'])
|| !array_key_exists('country', $origin) || !isset($origin['country'])
|| !array_key_exists('timeZoneId', $origin) || !isset($origin['timeZoneId'])
|| !array_key_exists('id', $destination) || !isset($destination['id']) ) {
throw new Exception('Quote::averagePrices. Invalid input origin or destination');
}
$result = ['cost'=>0];
$timeZoneId = $origin['timeZoneId'];
$country = $origin['country'];
$originId = $origin['id'];
$destinationId = $destination['id'];
$endpoint = "/geofencearea/api/addresstoarea/?country=${country}&address_id=";
list($res,$body) = Quote::getServiceCall($endpoint.$originId, false);
if(count($res) == 0 || array_key_exists('error', $res) && isset($res['error'])) {
return $result;
}
$originAreaId = $res['areas'][0]['id'];
list($res,$body) = Quote::getServiceCall($endpoint.$destinationId, false);
if(count($res) == 0 || array_key_exists('error', $res) && isset($res['error'])) {
return $result;
}
$destinationAreaId = $res['areas'][0]['id'];
$dt = new DateTime('now', new DateTimeZone($timeZoneId));
$currentHour = $dt->format('G');
$q = "SELECT average_cost, hour, last_quotes_id, last_updated FROM geofence_area_average_quotes
WHERE transport_provider_id=${providerId}
AND area_start_id=${originAreaId}
AND area_end_id=${destinationAreaId}
AND hour=${currentHour}
";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$result['cost'] = $f['average_cost'];
}
return $result;
}
}
// vi:ts=2
+110
View File
@@ -0,0 +1,110 @@
<?php
class QuoteApi extends Api
{
public $apiName = 'quote';
protected $hasPrice = true;
public function __construct($requestUri, $encryption=true) {
$this->cacheWhitelist = [
"createAction" => ['ttl' => 300] // 300 sec. = 5 min.
];
parent::__construct($requestUri, $encryption);
}
public function indexAction()
{
return $this->response(
array(
'error' => 'Data not found'
), 404);
}
/**
* Method GET
* Get single record (by id)
* http://DOMAIN/remove/1
* @return string
*/
public function viewAction()
{
//id must be the first parameter after /remove/x
return $this->response(
array(
'error'=> 'Data not found'
), 404);
}
public function createAction()
{
$message = "Unexpected options error";
$origin = $this->requestParams["origin"] ?? [];
$destination = $this->requestParams["destination"] ?? [];
$option = $this->requestParams["option"] ?? [];
$trip = $this->requestParams["trip"] ?? [];
$route = $this->requestParams["route"] ?? [];
$member_id = $this->requestParams["member_id"] ?? 0;
try {
$db = new Db();
list ($origin, $destination) = Options::checkValidLocations($db->getConnect(), $origin, $destination);
Options::checkValidOption($db->getConnect(), $option);
Options::checkValidTrip($db->getConnect(), $trip);
Route::checkValidRoute($db->getConnect(), $route);
$option["member_id"] = $member_id;
$trip["member_id"] = $member_id;
list ($option, $trip, $quote) = Quote::getTransportQuote($db->getConnect(), $origin, $destination, $option, $trip, $route);
if (is_array($quote)) {
$this->hasPrice = array_key_exists('cost', $quote) && isset($quote['cost']) && intval($quote['cost']) > 0;
return $this->response([
'option' => $option,
'trip' => $trip,
'quote' => $quote,
'route' => $route
], 200);
} else {
throw new Exception("Failed to get transport quote for origin");
}
} catch (Exception $e) {
$message = $e->getMessage();
}
return $this->response(
array(
"error" => $message
), 500);
}
public function updateAction()
{
return $this->response(
array(
"error" => "Update error"
), 400);
}
public function deleteAction()
{
return $this->response(
array(
"error" => "Delete error"
), 500);
}
protected function storeInCache() {
return http_response_code() === 200 && $this->hasPrice;
}
protected function getCacheKey() {
$origin = json_encode($this->requestParams["origin"]);
$destination = json_encode($this->requestParams["destination"]);
$option = $this->requestParams["option"];
$transport_provider_id = $option["transport_provider_id"];
return hash('md5', $this->method.'|'.$this->apiName.'|'.$this->action.'|'.$origin.'|'.$destination.'|'.$transport_provider_id);
}
}
+633
View File
@@ -0,0 +1,633 @@
<?php
class Route {
/*
savvy=> select id,name from transport_providers;
id | name
----+---------------
1 | Uber
2 | Lyft
3 | Grab
4 | ComfortDelGro
5 | GOJEK
6 | Turo
7 | Getaround
10 | GrabWheels
11 | BART
12 | Muni
13 | LTA
(11 rows)
*/
public static function getTransportRoute($db, $origin, $destination, $option, $trip) {
$route = [];
switch ($option["transport_provider_id"]) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 8:
list($option, $trip, $route) = Route::drivingDirections($db, $origin, $destination, $option, $trip);
break;
case 11: // BART
list($option, $trip, $route) = Route::bartDirections($db, $origin, $destination, $option, $trip);
break;
case 12: // Muni
list($option, $trip, $route) = Route::muniDirections($db, $origin, $destination, $option, $trip);
break;
case 13: // LTA
list($option, $trip, $route) = Route::ltaDirections($db, $origin, $destination, $option, $trip);
break;
case 14: // MARTA
list($option, $trip, $route) = Route::martaDirections($db, $origin, $destination, $option, $trip);
break;
}
error_log(json_encode($trip));
return [$option, $trip, $route];
}
public static function drivingDirections($db, $origin, $destination, $option, $trip) {
list($option, $trip, $route) = Route::processRideshareOption($db, $option, $origin, $destination, $trip);
$option["travel_time"] = (int)$trip["duration"];
$option["travel_distance"] = (int)$trip["distance"];
$trip["source"] = strtolower($option["name"]);
return [$option, $trip, $route];
}
public static function drivingDirectionsOriginal($db, $origin, $destination, $option, $trip) {
$options = [];
$trip = [];
$option["travel_time"] = 0;
$option["travel_distance"] = 0;
$fromLat = $origin['lat'];
$fromLng = $origin['lng'];
$toLat = $destination['lat'];
$toLng = $destination['lng'];
// This is paranoid redudant check, but since the function is "public"...
if ($fromLat==null || $fromLng==null || $toLat==null || $toLng==null
|| ($fromLat==0 && $fromLng==0) || ($toLat==0 && $toLng==0)) {
throw new Exception('Invalid origin and/or destination coordinates');
}
list($res, $err) = Route::serviceRoute($db, $fromLat, $fromLng, $toLat, $toLng);
if ($err!=NULL || !is_array($res) || !isset($res["routes"]) || count($res["routes"])<1) {
$options['error'] = $err!=NULL ? $err : 'No available routes';
return [$option, $trip, $options];
}
$options["route_overlay"] = [];
$travel_time = PHP_INT_MAX;
$travel_distance = PHP_INT_MAX;
foreach ($res["routes"] as $route) {
$r = Route::processRoute($route);
if ($travel_time>$r['duration']) {
$travel_time = $r['duration'];
$travel_distance = $r['distance'];
}
$options["route_overlay"][] = $r;
}
$options["travel_time"] = $travel_time==PHP_INT_MAX ? NULL : $travel_time;
$options["travel_distance"] = $travel_distance==PHP_INT_MAX ? NULL : $travel_distance;
$option["travel_time"] = (int)$options["travel_time"];
$option["travel_distance"] = (int)$options["travel_distance"];
//https://developers.google.com/maps/documentation/utilities/polylinealgorithm
//overview_polyline
//bounds contains the viewport bounding box of the overview_polyline.
return [$option, $trip, $options];
}
public static function bartDirections($db, $origin, $destination, $option, $trip) {
$route = [];
$option["travel_time"] = 0;
$option["travel_distance"] = 0;
$route["travel_time"] = 0;
$route["travel_distance"] = 0;
$route["route_overlay"] = [];
$country = $origin["country"];
$member_id = $option["member_id"];
// New directions
$tripService = Route::tripService($trip["id"], $country, 'BART');
//error_log(json_encode($tripService));
if (is_array($tripService) && isset($tripService["travel_date"])) {
$tripService["member_id"] = $member_id; // Pass along
$tripService["country"] = $country;
$tripService = MultiModalFilter::byDuration($tripService);
//$tripService = MultiModalFilter::byCost($tripService);
$trip = $tripService;
$option["travel_time"] = (int)$trip["options"]["legs"][0]["duration"];
$option["travel_distance"] = (int)$trip["options"]["legs"][0]["distance"];
$route = Route::routeFromTrip($db, $origin, $destination, $option, $trip);
}
return [$option, $trip, $route];
}
public static function muniDirections($db, $origin, $destination, $option, $trip) {
$route = [];
$option["travel_time"] = 0;
$option["travel_distance"] = 0;
$country = $origin["country"];
$member_id = $option["member_id"];
// New directions
$tripService = Route::tripService($trip["id"], $country, 'MUNI');
//error_log(json_encode($tripService));
if (is_array($tripService) && isset($tripService["travel_date"])) {
$tripService["member_id"] = $member_id; // Pass along
$tripService["country"] = $country;
$tripService = MultiModalFilter::byDuration($tripService);
//$tripService = MultiModalFilter::byCost($tripService);
$trip = $tripService;
$option["travel_time"] = (int)$trip["options"]["legs"][0]["duration"];
$option["travel_distance"] = (int)$trip["options"]["legs"][0]["distance"];
$route = Route::routeFromTrip($db, $origin, $destination, $option, $trip);
}
return [$option, $trip, $route];
}
public static function ltaDirections($db, $origin, $destination, $option, $trip) {
$route = [];
$option["travel_time"] = 0;
$option["travel_distance"] = 0;
$country = $origin["country"];
$member_id = $option["member_id"];
// New directions
$tripService = Route::tripService($trip["id"], $country, 'LTA');
//error_log(json_encode($tripService));
if (is_array($tripService) && isset($tripService["travel_date"])) {
$tripService["member_id"] = $member_id; // Pass along
$tripService["country"] = $country;
$tripService = MultiModalFilter::byDuration($tripService);
//$tripService = MultiModalFilter::byCost($tripService);
$trip = $tripService;
$option["travel_time"] = (int)$trip["options"]["legs"][0]["duration"];
$option["travel_distance"] = (int)$trip["options"]["legs"][0]["distance"];
$route = Route::routeFromTrip($db, $origin, $destination, $option, $trip);
}
return [$option, $trip, $route];
}
public static function martaDirections($db, $origin, $destination, $option, $trip) {
$route = [];
$option["travel_time"] = 0;
$option["travel_distance"] = 0;
$country = $origin["country"];
$member_id = $option["member_id"];
// New directions
$tripService = Route::tripService($trip["id"], $country, 'MARTA');
//error_log(json_encode($tripService));
if (is_array($tripService) && isset($tripService["travel_date"])) {
$tripService["member_id"] = $member_id; // Pass along
$tripService["country"] = $country;
$tripService = MultiModalFilter::byDuration($tripService);
$trip = $tripService;
$option["travel_time"] = (int)$trip["options"]["legs"][0]["duration"];
$option["travel_distance"] = (int)$trip["options"]["legs"][0]["distance"];
$route = Route::routeFromTrip($db, $origin, $destination, $option, $trip);
}
return [$option, $trip, $route];
}
public static function routeFromTrip($db, $origin, $destination, $option, $trip) {
$leg = [
"distance" => 0,
"duration" => 0,
"polyline" => NULL
];
// Select leg
foreach ($trip["options"]["legs"] as $leg) {
break;
}
$route["travel_time"] = (int)$leg["duration"];
$route["travel_distance"] = (int)$leg["distance"];
// TODO: BART fix polyline!
$route["route_overlay"][] = [
"polyline" => [],
"distance" => $leg["distance"],
"duration" => $leg["duration"],
"summary" => "Generated option",
"overview_polyline" => [
"points" => Route::processPolyline($leg["polyline"])
],
"bounds" => [
"northeast" => [
"lat" => $origin["lat"],
"lng" => $origin["lng"]
],
"southwest" => [
"lat" => $destination["lat"],
"lng" => $destination["lng"]
]
]
];
return $route;
}
public static function checkValidRoute($db, $route) {
// Basic input sanity check: route
if (!is_array($route) || !array_key_exists('travel_time',$route) || !is_numeric($route['travel_time'])
|| !array_key_exists('travel_distance',$route) || !is_numeric($route['travel_distance'])
|| !array_key_exists('route_overlay',$route) || !is_array($route['route_overlay'])) {
throw new Exception('Invalid transport route');
}
}
public static function serviceRoute($db, $fromLat, $fromLng, $toLat, $toLng, $mode='driving',$waypoints=[]) {
global $savvyext;
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
$oauth2_url = $savvyext->cfgReadChar('system.oauth2_url');
error_log("Route::serviceRoute(\$db, $fromLat, $fromLng, $toLat, $toLng, $mode, \$waypoints=[])");
// Call geocoding service
$data = http_build_query(
array(
'gps' => implode(",",array($fromLat, $fromLng, $toLat, $toLng)),
'mode' => $mode,
'waypoints' => implode(",",$waypoints)
)
);
$url = $oauth2_url."route?" . $data;
$opts = array(
'http' => array(
'method' => "GET",
'timeout' => 60, /* 1 minute */
'header' =>
"Content-Type: application/x-www-form-urlencoded\r\n" .
"Accept: application/json\r\n" .
"Authorization: Server-Token ${httpAuthToken}\r\n",
),
"ssl" => array(
"verify_peer"=>false,
"verify_peer_name"=>false,
)
);
$context = stream_context_create($opts);
$body = file_get_contents($url, false, $context);
$geocoded = json_decode($body,true);
if (is_array($geocoded) && is_array($geocoded["data"]) && !isset($geocoded["error"])) {
return array($geocoded["data"], NULL);
} else if (is_array($geocoded) && isset($geocoded["error"])) {
$body = $geocoded["error"];
}
return array(NULL, "Routing service call error: ".$body);
}
public static function processRoute($route) {
require_once('../common/Polyline.php');
$result = [
'polyline' => array(),
'distance' => NULL,
'duration' => NULL,
'summary' => $route["summary"],
'overview_polyline' => $route["overview_polyline"],
'bounds' => $route["bounds"]
];
$distance = 0;
$duration = 0;
foreach ($route["legs"] as $leg) {
$distance += $leg["distance"]["value"];
$duration += $leg["duration"]["value"];
foreach ($leg["steps"] as $step) {
$points = Polyline::decode($step["polyline"]["points"]);
$result['polyline'] = array_merge($result['polyline'],Polyline::pair($points));
}
}
$result['distance'] = $distance;
$result['duration'] = $duration;
return $result;
}
public static function processRideshareOption($db, $option, $origin, $destination, $trip) {
error_log("Options::processRideshareOption(\$db, \$option, \$origin, \$destination, \$trip)");
$route = [];
$route["travel_time"] = 0;
$route["travel_distance"] = 0;
$route["route_overlay"] = [];
$member_id = $option["member_id"];
$country = $origin["country"];
$rideshare = $option["name"];
$transport_provider_id = $option["transport_provider_id"];
if (array_key_exists("timeZoneId",$origin) && $origin["timeZoneId"]!="") {
$tz = $origin["timeZoneId"];
} else if (array_key_exists("timeZoneId",$destination) && $destination["timeZoneId"]!="") {
$tz = $destination["timeZoneId"];
} else {
$tz = Options::getTimezoneById($db, $origin["timezone"], "Asia/Singapore");
}
//"trips"=>$trips;
if (!is_array($trip) && count($trip)<1) {
syslog(LOG_WARNING,"Invalid trip");
return [$option, $trip, $route]; // No idea how to handle it
}
/*if (!array_key_exists('trip',$trip) || !is_array($trip['trip']) || count($trip['trip'])<10) {
syslog(LOG_WARNING,"Invalid trip[0]!");
return $trips; // No idea how to handle it
} //*/
$oldTrip = $trip; //['trip'];
$origin['latitude'] = $origin['lat'];
$origin['longitude'] = $origin['lng'];
$destination['latitude'] = $destination['lat'];
$destination['longitude'] = $destination['lng'];
list($res, $err) = Route::serviceRoute($db,
$origin['lat'], $origin['lng'],
$destination['lat'], $destination['lng'], 'driving'
);
if ($err!=NULL || !is_array($res) || !isset($res["routes"]) || count($res["routes"])<1) {
error_log('No available routes!');
error_log($err);
return [$option, $trip, $route]; // We cannot route driving!
}
$route_overlay = [];
$travel_time = PHP_INT_MAX;
$travel_distance = PHP_INT_MAX;
foreach ($res["routes"] as $item) {
$r = Route::processRoute($item);
if ($travel_time>$r['duration']) {
$travel_time = $r['duration'];
$travel_distance = $r['distance'];
}
$route_overlay[] = $r;
}
$oldTrip["duration"] = $travel_time==PHP_INT_MAX ? NULL : $travel_time;
$oldTrip["distance"] = $travel_distance==PHP_INT_MAX ? NULL : $travel_distance;
$route["travel_time"] = (int)$oldTrip["duration"];
$route["travel_distance"] = (int)$oldTrip["distance"];
$route["route_overlay"] = $route_overlay;
/*
$result = [
'polyline' => array(),
'distance' => NULL,
'duration' => NULL,
'summary' => $route["summary"],
'overview_polyline' => $route["overview_polyline"],
'bounds' => $route["bounds"]
];
*/
//syslog(LOG_WARNING,json_encode($res));
$route_0 = $res["routes"][0];
$route_leg = $route_0["legs"][0];
$route_leg_steps = $route_leg["steps"];
$overview_polyline = Route::processPolyline($route_0["overview_polyline"]["points"]);
$leg_steps = [];
$distance = 0;
$duration = 0;
$location_start_lat = 0;
$location_start_lng = 0;
$location_end_lat = 0;
$location_end_lng = 0;
$polyline = [];
$i = 0; $j=1; $n = count($route_leg_steps);
for (; $i<$n; $i++) {
$step = $route_leg_steps[$i];
if ($step["travel_mode"] == "DRIVING") {
$location_start_lat = $step["start_location"]["lat"];
$location_start_lng = $step["start_location"]["lng"];
break;
}
$leg_steps[] = [
'distance' => $step["distance"]["value"],
'duration' => $step["duration"]["value"],
'html_instructions' => $step["html_instructions"],
'location_end_lat' => $step["end_location"]["lat"],
'location_end_lng' => $step["end_location"]["lng"],
'location_start_lat' => $step["start_location"]["lat"],
'location_start_lng' => $step["start_location"]["lng"],
'polyline' => $step["polyline"]["points"], /*"yz|Fm_nxRi@e@_@P",*/
'sid' => $j++,
'travel_mode' => $step["travel_mode"]
];
}
for (;$i<$n; $i++) {
$step = $route_leg_steps[$i];
if ($step["travel_mode"] != "DRIVING") {
break;
}
$distance += $step["distance"]["value"];
$duration += $step["duration"]["value"];
$location_end_lat = $step["end_location"]["lat"];
$location_end_lng = $step["end_location"]["lng"];
$polyline[] = $step["polyline"]["points"];
}
$leg_steps[] = [
'distance' => $distance,
'duration' => $duration,
'fare_quote' => null,
'fare_raw' => null,
'html_instructions' => "Take ".$rideshare." to ".$destination["address"],
'location_end_lat' => $location_end_lat,
'location_end_lng' => $location_end_lng,
'location_start_lat' => $location_start_lat,
'location_start_lng' => $location_start_lng,
'polyline' => Route::processPolyline($polyline),
'quote_group_id' => null,
'sid' => $j++,
'travel_mode' => strtoupper($rideshare)
];
for (;$i<$n; $i++) {
$step = $route_leg_steps[$i];
$leg_steps[] = [
'distance' => $step["distance"]["value"],
'duration' => $step["duration"]["value"],
'html_instructions' => $step["html_instructions"],
'location_end_lat' => $step["end_location"]["lat"],
'location_end_lng' => $step["end_location"]["lng"],
'location_start_lat' => $step["start_location"]["lat"],
'location_start_lng' => $step["start_location"]["lng"],
'polyline' => $step["polyline"]["points"], /*"yz|Fm_nxRi@e@_@P",*/
'sid' => $j++,
'travel_mode' => $step["travel_mode"]
];
}
$cost = null;
$rideshare = [
'cost' => $cost,
'cost_raw' => $cost,
'count' => 1,
'country' => $country,
'distance' => $oldTrip['distance'],
'dup_id' => null,
'duration' => $oldTrip['duration'],
'id' => $oldTrip['id'],
'location_end' => $destination['address'],
'location_end_id' => $oldTrip['location_end_id'],
'location_end_lat' => $destination['latitude'],
'location_end_lng' => $destination['longitude'],
'location_end_tz' => $destination['timezone'],
'location_start' => $origin['address'],
'location_start_id' => $oldTrip['location_start_id'],
'location_start_lat' => $origin['latitude'],
'location_start_lng' => $origin['longitude'],
'location_start_tz' => $origin['timezone'],
'member_id' => $member_id,
'options' => [],
'parsedemail_item_id' => null, /* $trip['id'] ??? */
'private' => 't',
'scheduled' => null,
'trackedemail_item_id' => null,
'transport_provider_id' => $transport_provider_id,
'travel_date' => $oldTrip['travel_date'],
'travel_date_end' => $oldTrip['travel_date_end'],
'updated' => $oldTrip['updated']
];
// Create trip advice
$rideshare["source"] = strtolower($option["name"]);
$parsedemail_item_advice_google_id = Options::saveTripAdvice($db, $rideshare);
error_log(json_encode($parsedemail_item_advice_google_id));
$leg_fare = $cost;
// Get departure and arrival times...
$date = new DateTime("now", new DateTimeZone($tz));
$date->add(new DateInterval('PT15M'));
$departure_time = $date->getTimestamp();
$duration = Route::getDuration(array_key_exists('duration',$oldTrip)?$oldTrip['duration']:900);
$date->add(new DateInterval('PT'.$duration.'M'));
$arrival_time = $date->getTimestamp();
$leg = [
'id' => NULL,
'parsedemail_item_advice_google_id' => $parsedemail_item_advice_google_id,
'arrival_time' => $arrival_time,
'arrival_time_zone' => $destination['timezone'],
'arrival_timezone' => $tz,
'departure_time' => $departure_time,
'departure_time_zone' => $origin['timezone'],
'departure_timezone' => $tz,
'distance' => $oldTrip['distance'],
'duration' => $oldTrip['duration'],
'fare_raw' => $cost,
'polyline' => $overview_polyline,
'quote' => $cost,
'steps' => count($leg_steps),
];
// Create leg
$google_directions_leg_id = Options::saveTripAdviceLeg($db, $rideshare, $leg);
$leg['id'] = $google_directions_leg_id;
foreach ($leg_steps as $i=>$step) {
$step['google_directions_leg_id'] = $google_directions_leg_id;
$google_directions_leg_step_id = Options::saveTripAdviceLegStep($db, $rideshare, $leg, $step);
$step['id'] = $google_directions_leg_step_id;
$leg_steps[$i] = $step;
}
$options = [
'gid' => $google_directions_leg_id, /* ??? */
'leg_fare' => [$google_directions_leg_id => $leg_fare],
'leg_steps' => [$google_directions_leg_id => $leg_steps],
'legs' => [$leg],
'routes' => 1
];
$rideshare['options'] = $options;
return [$option, $rideshare, $route];
}
protected static function getDuration($duration=900) {
if ($duration==NULL) {
return 15;
}
if ($duration>60) {
return (int)($duration/60);
}
return 1;
}
public static function processPolyline($data) {
if (!is_array($data)) {
return $data;
}
require_once('../common/Polyline.php');
$points = [];
foreach ($data as $item) {
$points = array_merge($points, Polyline::decode($item));
//$points = array_merge(Polyline::decode($item),$points);
break;
}
return Polyline::encode($points);
}
public static function tripService($id, $country, $mode) {
global $savvyext;
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
$oauth2_url = $savvyext->cfgReadChar('system.oauth2_url');
error_log("Route::tripService($id)");
if ($country!='SG' && $country!='US') {
throw new Exception('Unsupported country: '.$country);
}
// curl -d 'id=44254' -d 'mode=BART' -d 'country=US' -H 'Authorization: Server-Token 99dfe35fcb7de1ee' -X POST {$oauth2_url}modetrips
$postdata = http_build_query(
array(
'id'=> $id,
'mode' => $mode,
'country' => $country
)
);
$url = $oauth2_url."modetrips";
$opts = array(
'http' => array(
'method' => "POST",
'timeout' => 60, /* 1 minute */
'header' =>
"Content-Type: application/x-www-form-urlencoded\r\n" .
"Accept: application/json\r\n" .
"Authorization: Server-Token ${httpAuthToken}\r\n",
'content' => $postdata
),
"ssl" => array(
"verify_peer"=>false,
"verify_peer_name"=>false,
)
);
$context = stream_context_create($opts);
error_log("started trips service call");
$t = time();
$body = file_get_contents($url, false, $context);
error_log("complete trips service call: ".(time()-$t)." seconds");
$trip = json_decode($body,true);
return $trip;
}
}
/*
["id"]=>
string(1) "5"
["city_id"]=>
string(1) "2"
["name"]=>
string(4) "Uber"
["transport_provider_id"]=>
string(1) "1"
["custom"]=>
NULL
*/
// vi:ts=2
+83
View File
@@ -0,0 +1,83 @@
<?php
class RouteApi extends Api
{
public $apiName = 'route';
public function indexAction()
{
return $this->response(
array(
'error' => 'Data not found'
), 404);
}
/**
* Method GET
* Get single record (by id)
* http://DOMAIN/remove/1
* @return string
*/
public function viewAction()
{
//id must be the first parameter after /remove/x
return $this->response(
array(
'error'=> 'Data not found'
), 404);
}
public function createAction()
{
$message = "Unexpected options error";
$origin = $this->requestParams["origin"] ?? [];
$destination = $this->requestParams["destination"] ?? [];
$option = $this->requestParams["option"] ?? [];
$trip = $this->requestParams["trip"] ?? [];
$member_id = $this->requestParams["member_id"] ?? 0;
try {
$db = new Db();
list ($origin, $destination) = Options::checkValidLocations($db->getConnect(), $origin, $destination);
Options::checkValidOption($db->getConnect(), $option);
Options::checkValidTrip($db->getConnect(), $trip);
$option["member_id"] = $member_id;
$trip["member_id"] = $member_id;
list ($res, $trip, $route) = Route::getTransportRoute($db->getConnect(), $origin, $destination, $option, $trip);
if (is_array($res)) {
return $this->response([
'option' => $res,
'trip' => $trip,
'route' => $route
], 200);
} else {
throw new Exception("Failed to get transport route for origin");
}
} catch (Exception $e) {
$message = $e->getMessage();
}
return $this->response(
array(
"error" => $message
), 500);
}
public function updateAction()
{
return $this->response(
array(
"error" => "Update error"
), 400);
}
public function deleteAction()
{
return $this->response(
array(
"error" => "Delete error"
), 500);
}
}
@@ -0,0 +1,431 @@
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\RequestException;
class Autocab {
const TIMEOUT = 60;
const DEBUG = false;
const SSL_VERIFY = false;
const REASON_NOT_REQUIRED = "NotRequired";
const REASON_PRICE_CHANGED = "PriceChanged";
private $httpClient;
private $agentId;
private $agentPassword;
private $currency;
private $vendorId;
private $templatesDir;
private $config = [];
private $templates = [];
public function __construct($config) {
if (empty($config)) {
throw new Exception("Configuration is not provided");
}
$this->setConfig($config);
if (!isset($config["baseUrl"]) || empty($config["baseUrl"])) {
throw new Exception("The API URL is not specified");
}
$this->httpClient = new Client(['base_uri' => $config["baseUrl"]]);
}
public function setConfig($config) {
global $savvyext;
$this->config = $config;
$this->agentId = $config["agentId"] ;
$this->agentPassword = $config["agentPassword"];
$this->currency = $config["currency"];
$this->templatesDir = $config["templatesDir"];
$this->loadTemplates($config);
}
public function loadTemplates($config) {
if ( !array_key_exists('templatesDir', $config) || empty($config["templatesDir"])) {
throw new Exception("The templates dir is not specified");
}
$files = scandir($this->templatesDir);
if ($files) {
foreach ($files as $templateFile) {
$template = file_get_contents($this->templatesDir."/".$templateFile);
if ($template) {
$this->templates[$templateFile] = $template;
}
}
}
if (count($this->templates) == 0) {
throw new Exception("Any one template is not initialized");
}
}
public function getTemplate($name, $oldRootTag=NULL, $newRootTag=NULL) {
if (!isset($this->templates[$name])) {
return false;
}
$template = $this->templates[$name];
if (isset($oldRootTag) && isset($newRootTag)) {
$template = str_replace($oldRootTag, $newRootTag, $template);
}
$xmlElement = new SimpleXMLElement($template);
$xmlElement->Agent->attributes()->Id = $this->config["agentId"];
$xmlElement->Agent->Password = $this->config["agentPassword"];
return $xmlElement;
}
public function getHeaders($extraHeaders = []) {
$headers = [
'content-type' => 'text/xml',
];
return array_merge($headers,$extraHeaders);
}
public function getAvailability($source, $destination) {
if (!isset($this->templates['autocab_agent_bid_request.xml'])) {
throw new Exception("Bid request template is not initialized");
}
$uri = '/api/agent';
$xmlElement = $this->getTemplate('autocab_agent_bid_request.xml');
$xmlElement->Agent->Time = gmdate("Y-m-d\TH:i:s.v\Z");
$xmlElement->BidParameters->Journey->From->Data = $source["address"];
$xmlElement->BidParameters->Journey->From->Coordinate->Latitude = $source["latitude"];
$xmlElement->BidParameters->Journey->From->Coordinate->Longitude = $source["longitude"];
$xmlElement->BidParameters->Journey->To->Data = $destination["address"];
$xmlElement->BidParameters->Journey->To->Coordinate->Latitude = $destination["latitude"];
$xmlElement->BidParameters->Journey->To->Coordinate->Longitude = $destination["longitude"];
$xml = $xmlElement->asXML();
$result = $this->genericPost($uri, $xml);
if ($result["error"]) {
return $result;
}
if ($result["code"] != 200) {
var_dump($result["data"]);
}
$xmlResponse = new SimpleXMLElement($result["data"]);
$error = self::processError($xmlResponse);
if($error) {
return ["error"=>1, "data" => $error, "request"=>$xmlElement];
}
$costs = [];
foreach ($xmlResponse->Offers->Offer as $offer) {
$item = self::processBidOffer($offer);
array_push($costs, $item);
}
return array("error"=>0, "data"=>$costs, "raw_data"=>$xmlResponse, "request"=>$xmlElement);
}
public function getBookingAvailability($source, $destination, $vendor) {
if (!isset($this->templates['autocab_agent_booking_availability_request.xml'])) {
throw new Exception("Booking Availability request template is not initialized");
}
$uri = '/api/agent';
$xmlElement = $this->getTemplate('autocab_agent_booking_availability_request.xml');
$xmlElement->Vendor->attributes()->Id = $vendor;
$xmlElement->Agent->Time = gmdate("Y-m-d\TH:i:s.v\Z");
$xmlElement->BookingParameters->Journey->From->Data = $source["address"] ?? "No Address";
$xmlElement->BookingParameters->Journey->From->Coordinate->Latitude = $source["latitude"];
$xmlElement->BookingParameters->Journey->From->Coordinate->Longitude = $source["longitude"];
$xmlElement->BookingParameters->Journey->To->Data = $destination["address"] ?? "No Address";
$xmlElement->BookingParameters->Journey->To->Coordinate->Latitude = $destination["latitude"];
$xmlElement->BookingParameters->Journey->To->Coordinate->Longitude = $destination["longitude"];
$xml = $xmlElement->asXML();
$result = $this->genericPost($uri, $xml);
if ($result["error"]) {
return $result;
}
if ($result["code"] != 200) {
var_dump($result["data"]);
}
$xmlResponse = new SimpleXMLElement($result["data"]);
$error = self::processError($xmlResponse);
if($error) {
return ["error"=>1, "data" => $error, "request"=>$xmlElement];
}
$cost = null;
if(isset($xmlResponse->Pricing)) {
$cost = [
'currency' => (string)$xmlResponse->Pricing->Currency,
'availability_ref' => (string)$xmlResponse->AvailabilityReference,
'display_name' => (string)$xmlResponse->VendorDetails->Name,
'high_estimate' => (int)$xmlResponse->Pricing->Price,
'low_estimate' => (int)$xmlResponse->Pricing->Price,
'distance_estimate' => (int) $xmlResponse->EstimatedJourney->Distance,
'duration_estimate' => (int) $xmlResponse->EstimatedJourney->Duration * 60,
'vendor_id' => (string) $xmlResponse->Vendor->attributes()->Id,
];
}
return ["error"=>0, "data"=>$cost, "raw_data"=>$xmlResponse, "request"=>$xmlElement];
}
public function cancelAvailability($availabilityRef, $vendor, $reason = self::REASON_NOT_REQUIRED) {
if (!isset($this->templates['autocab_agent_booking_not_authorized_request.xml'])) {
throw new Exception("BookingNotAuthorizedRequest template is not initialized");
}
if (!isset($availabilityRef)) {
throw new Exception("Invalid availability reference");
}
$uri = '/api/agent';
$xmlElement = $this->getTemplate('autocab_agent_booking_not_authorized_request.xml');
$xmlElement->Vendor->attributes()->Id = $vendor;
$xmlElement->Agent->Time = gmdate("Y-m-d\TH:i:s.v\Z");
$xmlElement->AvailabilityReference = $availabilityRef;
$xmlElement->Reason = $reason;
$xml = $xmlElement->asXML();
$result = $this->genericPost($uri, $xml);
if ($result["error"]) {
return $result;
}
if ($result["code"] != 200) {
var_dump($result["data"]);
}
$xmlResponse = new SimpleXMLElement($result["data"]);
$error = self::processError($xmlResponse);
if($error) {
return ["error"=>1, "data" => $error, "request"=>$xmlElement];
}
return ["error"=>0, "data"=>[], "raw_data"=>$xmlResponse, "request"=>$xmlElement];
}
public function booking($availabilityRef, $passengers=[], $vendor) {
if (!isset($this->templates['autocab_agent_booking_athorization_request.xml'])) {
throw new Exception("BookingAuthorization template is not initialized");
}
if (!isset($availabilityRef)) {
throw new Exception("Invalid availability reference");
}
if (!is_array($passengers) || count($passengers) < 1
|| !array_key_exists("name", $passengers[0]) || empty($passengers[0]["name"])
) {
throw new Exception("Invalid passengers information");
}
$uri = '/api/agent';
$xmlElement = $this->getTemplate('autocab_agent_booking_athorization_request.xml');
$xmlElement->Vendor->attributes()->Id = $vendor;
$xmlElement->Agent->Time = gmdate("Y-m-d\TH:i:s.v\Z");
$xmlElement->AvailabilityReference = $availabilityRef;
foreach ($passengers as $key => $passenger) {
$xmlPassenger = $xmlElement->Passengers->addChild("PassengerDetails");
$isLead = ($key === array_keys($passengers)[0]) ? "true" : "false";
$xmlPassenger->addAttribute("IsLead", "$isLead");
$xmlPassenger->addChild("Name", $passenger["name"]);
if (array_key_exists("phone", $passenger) && isset($passenger["phone"])) {
$xmlPassenger->addChild( "TelephoneNumber", $passenger["phone"] );
}
if (array_key_exists("email", $passenger) && isset($passenger["email"])) {
$xmlPassenger->addChild("EmailAddress", $passenger["email"]);
}
}
$xml = $xmlElement->asXML();
$result = $this->genericPost($uri, $xml);
if ($result["error"]) {
return $result;
}
if ($result["code"] != 200) {
var_dump($result["data"]);
}
$xmlResponse = new SimpleXMLElement($result["data"]);
$error = self::processError($xmlResponse);
if($error) {
return ["error"=>1, "data" => $error];
}
$resp = [];
if(isset($xmlResponse->AuthorizationReference)) {
$resp = [
'authorization_ref' => (string)$xmlResponse->AuthorizationReference,
'booking_ref' => (string)$xmlResponse->BookingReference,
];
}
return ["error"=>0, "data"=>$resp, "raw_data"=>$xmlResponse, "request"=>$xmlElement];
}
public function bookingStatus($authorizationRef, $vendor) {
if (!isset($this->templates['autocab_agent_booking_status_request.xml'])) {
throw new Exception("BookingStatusRequest template is not initialized");
}
if (!isset($authorizationRef)) {
throw new Exception("Invalid authorization reference");
}
$uri = '/api/agent';
$xmlElement = $this->getTemplate('autocab_agent_booking_status_request.xml');
$xmlElement->Vendor->attributes()->Id = $vendor;
$xmlElement->Agent->Time = gmdate("Y-m-d\TH:i:s.v\Z");
$xmlElement->AuthorizationReference = $authorizationRef;
$xml = $xmlElement->asXML();
$result = $this->genericPost($uri, $xml);
if ($result["error"]) {
return $result;
}
if ($result["code"] != 200) {
var_dump($result["data"]);
}
$xmlResponse = new SimpleXMLElement($result["data"]);
$error = self::processError($xmlResponse);
if($error) {
return ["error"=>1, "data" => $error, "request"=>$xmlElement];
}
$resp = [
'booking_ref' => (string)$xmlResponse->BookingReference,
'status' => (string)$xmlResponse->Status,
'cancellation_reason' => (string)$xmlResponse->CancellationReason
];
return ["error"=>0, "data"=>$resp, "raw_data"=>$xmlResponse, "request"=>$xmlElement];
}
public function bookingCancellation($authorizationRef, $vendor) {
if (!isset($this->templates['autocab_agent_booking_cancellation_request.xml'])) {
throw new Exception("BookingCancellationRequest template is not initialized");
}
if (!isset($authorizationRef)) {
throw new Exception("Invalid authorization reference");
}
$uri = '/api/agent';
$xmlElement = $this->getTemplate('autocab_agent_booking_cancellation_request.xml');
$xmlElement->Vendor->attributes()->Id = $vendor;
$xmlElement->Agent->Time = gmdate("Y-m-d\TH:i:s.v\Z");
$xmlElement->AuthorizationReference = $authorizationRef;
$xml = $xmlElement->asXML();
$result = $this->genericPost($uri, $xml);
if ($result["error"]) {
return $result;
}
if ($result["code"] != 200) {
var_dump($result["data"]);
}
$xmlResponse = new SimpleXMLElement($result["data"]);
$error = self::processError($xmlResponse);
if($error) {
return ["error"=>1, "data" => $error, "code" => 500, "request"=>$xmlElement];
}
$resp = [];
return ["error"=>0, "data"=>$resp, "code" => 200, "raw_data"=>$xmlResponse, "request"=>$xmlElement];
}
private function genericPost($uri, $xml, $extraHeaders=[]) {
$code = 0;
$data = [];
$error = 0;
try {
$json = json_encode($data);
$length = strlen($json);
$response = $this->httpClient->request('POST', $uri, [
'decode_content' => true,
'connect_timeout' => SELF::TIMEOUT,
'debug' => SELF::DEBUG,
'verify' => SELF::SSL_VERIFY,
'headers' => $this->getHeaders(),
'body' => $xml
]);
$code = $response->getStatusCode();
$data = $response->getBody();
} catch (RequestException $e) {
if (SELF::DEBUG) {
echo Psr7\str($e->getRequest());
if ($e->hasResponse()) {
echo Psr7\str($e->getResponse());
}
}
if ($e->hasResponse()) {
$code = (int) $e->getResponse()->getStatusCode();
}
$data = ["error" => "exception", "error_description" => $e->getMessage()];
$error = 1;
} catch (ClientException $e) {
if (SELF::DEBUG) {
echo Psr7\str($e->getRequest());
echo Psr7\str($e->getResponse());
}
$code = (int)$e->getResponse()->getStatusCode();
$data = ["error" => "exception", "error_description" => $e->getMessage()];
$error = 1;
} catch (Exception $e) {
if (SELF::DEBUG) {
echo Psr7\str($e->getRequest());
echo Psr7\str($e->getResponse());
}
$code = (int)$e->getResponse()->getStatusCode();
$data = ["error" => "exception", "error_description" => $e->getMessage()];
$error = 1;
}
return ["error" => $error, "data" => $data, "code" => $code];
}
public static function processError($xmlResponse) {
if ((string)$xmlResponse->Result->Success == "true") {
return false;
}
return ["error"=>"API error", "error_description" => (string) $xmlResponse->Result->FailureReason, "error_code" => (string) $xmlResponse->Result->FailureCode];
}
public static function processBidOffer($offer) {
$item = [
'currency' => (string)$offer->Pricing->Currency,
'transport_product' => (string)$offer['Reference'],
'display_name' => (string)$offer->VendorDetails->Name,
'high_estimate' => (int)$offer->Pricing->Price,
'low_estimate' => (int)$offer->Pricing->Price,
'distance_estimate' => (int) $offer->EstimatedJourney->Distance,
'duration_estimate' => (int) $offer->EstimatedJourney->Duration * 60,
];
return $item;
}
}
@@ -0,0 +1,33 @@
<AgentBidRequest>
<Agent Id="{{agent_id}}">
<Password>{{agent_password}}</Password>
<Reference>AgentRef</Reference>
<Time>{{agent_time}}</Time>
</Agent>
<BidParameters>
<Pricing>
<Currency>USD</Currency>
<PaymentType>Cash</PaymentType>
</Pricing>
<Journey>
<From>
<Type>Address</Type>
<Data>1563 Van Dyke Ave, San Francisco, CA 94124, USA</Data>
<Coordinate>
<Latitude>37.7286068</Latitude>
<Longitude>-122.3919092</Longitude>
</Coordinate>
</From>
<To>
<Type>Address</Type>
<Data>536 Edinburgh St, San Francisco, CA 94112, USA</Data>
<Coordinate>
<Latitude>37.7206606</Latitude>
<Longitude>-122.4326099</Longitude>
</Coordinate>
</To>
</Journey>
<Ride Type="Passenger">
</Ride>
</BidParameters>
</AgentBidRequest>
@@ -0,0 +1,16 @@
<AgentBookingAuthorizationRequest>
<Agent Id="{{agent_id}}">
<Password>{{agent_password}}</Password>
<Reference>AgentRef</Reference>
<Time>{{agent_time}}</Time>
</Agent>
<Vendor Id="{{vendor_id}}" />
<AvailabilityReference>{{availability_reference}}</AvailabilityReference>
<Passengers>
</Passengers>
<Notifications>
<VendorEvents>BookingDispatched NoFare BookingCompleted BookingCancelled</VendorEvents>
<AlertMethod>None</AlertMethod>
<AgentEvents>BookingDispatched NoFare BookingCompleted BookingCancelled LocationUpdate VehicleArrived PassengerOnBoard</AgentEvents>
</Notifications>
</AgentBookingAuthorizationRequest>
@@ -0,0 +1,34 @@
<AgentBookingAvailabilityRequest>
<Agent Id="{{agent_id}}">
<Password>{{agent_password}}</Password>
<Reference>AgentRef</Reference>
<Time>{{agent_time}}</Time>
</Agent>
<Vendor Id="{{vendor_id}}" />
<BookingParameters>
<Pricing>
<Currency>USD</Currency>
<PaymentType>Cash</PaymentType>
</Pricing>
<Journey>
<From>
<Type>Address</Type>
<Data>1563 Van Dyke Ave, San Francisco, CA 94124, USA</Data>
<Coordinate>
<Latitude>37.7286068</Latitude>
<Longitude>-122.3919092</Longitude>
</Coordinate>
</From>
<To>
<Type>Address</Type>
<Data>536 Edinburgh St, San Francisco, CA 94112, USA</Data>
<Coordinate>
<Latitude>37.7206606</Latitude>
<Longitude>-122.4326099</Longitude>
</Coordinate>
</To>
</Journey>
<Ride Type="Passenger">
</Ride>
</BookingParameters>
</AgentBookingAvailabilityRequest>
@@ -0,0 +1,9 @@
<AgentBookingCancellationRequest>
<Agent Id="{{agent_id}}">
<Password>{{agent_password}}</Password>
<Reference>AgentRef</Reference>
<Time>{{agent_time}}</Time>
</Agent>
<Vendor Id="{{vendor_id}}" />
<AuthorizationReference>{{authorization_reference}}</AuthorizationReference>
</AgentBookingCancellationRequest>
@@ -0,0 +1,13 @@
<AgentBookingCancelledEventResponse>
<Vendor Id="{{vendor_id}}">
<Reference>{{vendor_ref}}</Reference>
<Time>{{vendor_time}}</Time>
</Vendor>
<Agent Id="{{agent_id}}"/>
<Result>
<Success>true</Success>
<FailureCode/>
<FailureReason/>
</Result>
<AcknowledgementReference>{{acknowledgement_ref}}</AcknowledgementReference>
</AgentBookingCancelledEventResponse>
@@ -0,0 +1,10 @@
<AgentBookingNotAuthorizedRequest>
<Agent Id="{{agent_id}}">
<Password>{{agent_password}}</Password>
<Reference>AgentRef</Reference>
<Time>{{agent_time}}</Time>
</Agent>
<Vendor Id="{{vendor_id}}" />
<AvailabilityReference>{{availability_reference}}</AvailabilityReference>
<Reason>NotRequired</Reason>
</AgentBookingNotAuthorizedRequest>
@@ -0,0 +1,9 @@
<AgentBookingStatusRequest>
<Agent Id="{{agent_id}}">
<Password>{{agent_password}}</Password>
<Reference>AgentRef</Reference>
<Time>{{agent_time}}</Time>
</Agent>
<Vendor Id="{{vendor_id}}" />
<AuthorizationReference>{{authorization_reference}}</AuthorizationReference>
</AgentBookingStatusRequest>
@@ -0,0 +1,13 @@
<AgentVehicleArrivedEventResponse>
<Vendor Id="{{vendor_id}}">
<Reference>{{vendor_ref}}</Reference>
<Time>{{vendor_time}}</Time>
</Vendor>
<Agent Id="{{agent_id}}"/>
<Result>
<Success>true</Success>
<FailureCode/>
<FailureReason/>
</Result>
<AcknowledgementReference>{{acknowledgement_ref}}</AcknowledgementReference>
</AgentVehicleArrivedEventResponse>
@@ -0,0 +1,13 @@
<AgentEventResponse>
<Vendor Id="{{vendor_id}}">
<Reference>{{vendor_ref}}</Reference>
<Time>{{vendor_time}}</Time>
</Vendor>
<Agent Id="{{agent_id}}"/>
<Result>
<Success>true</Success>
<FailureCode/>
<FailureReason/>
</Result>
<AcknowledgementReference>{{acknowledgement_ref}}</AcknowledgementReference>
</AgentEventResponse>
+80
View File
@@ -0,0 +1,80 @@
<?php
$profile = getenv("SAVVY_PROFILE");
if ($profile) {
require_once("../../core/backend.${profile}.php");
} else {
require_once( '../../core/backend.php' );
}
include_once '../config.php';
require_once('../constants.php');
require_once('../common/vendor/autoload.php');
require_once('../common/Api.php');
require_once('../common/Db.php');
require_once('../common/GoogleKMS.php');
require_once('../common/Logger.php');
require_once('../trips/MultiModalFilter.php');
require_once('Options.php');
require_once('OptionsApi.php');
require_once('Quote.php');
require_once('QuoteApi.php');
require_once('Route.php');
require_once('RouteApi.php');
require_once('AutocabApi.php');
require_once('autocab/Autocab.php');
require_once('Booking.php');
require_once('../trips/SGBikeApi.php');
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
header("Access-Control-Allow-Origin: *");
header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
header("Access-Control-Allow-Headers: Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, client_id");
header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS");
header('Content-type: application/json');
if ("OPTIONS" === $_SERVER['REQUEST_METHOD']) {
exit();
}
$headers = getallheaders();
if ((!isset($headers["Authorization"]) || substr($headers["Authorization"],-strlen($httpAuthToken))!=$httpAuthToken) &&
(!isset($headers["authorization"]) || substr($headers["authorization"],-strlen($httpAuthToken))!=$httpAuthToken)) {
header('HTTP/1.1 401 Unauthorized');
header('Status: 401 Unauthorized');
echo "{\"status\":\"Missing authorization\"}";
exit();
}
try {
if (strpos($_SERVER['REQUEST_URI'],'/api/')===false) {
throw new Exception("Invalid API request");
}
$requestUri = explode('/', trim($_SERVER['REQUEST_URI'],'/'));
while (array_shift($requestUri) !== 'api') {
};
if ($requestUri[0]=='options') {
$api = new OptionsApi($requestUri);
}
else if ($requestUri[0]=='route') {
$api = new RouteApi($requestUri);
}
else if ($requestUri[0]=='quote') {
$api = new QuoteApi($requestUri);
}
else if ($requestUri[0]=='autocab') {
$api = new AutocabApi($requestUri);
}
else {
echo json_encode(Array('error' => 'Invalid API request'));
}
echo $api->run();
}
catch (Exception $e) {
echo json_encode(Array('error' => $e->getMessage()));
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

@@ -0,0 +1,60 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="swagger-ui.css" >
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="swagger-ui-bundle.js"> </script>
<script src="swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "../swagger.php",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
<?php
require('../../../adminsavvy/vendor/autoload.php');
$openapi = \OpenApi\scan('.');
header('Content-Type: application/json');
echo $openapi->toJson();
?>
+27
View File
@@ -0,0 +1,27 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /SAVVY/callback/
#RewriteBase /
#Checks to
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]
</IfModule>
<IfModule !mod_rewrite.c>
# If we don't have mod_rewrite installed, all 404's
# can be sent to index.php, and everything works as normal.
# Submitted by: ElliotHaughin
ErrorDocument 404 /index.php
</IfModule>
#Header add Access-Control-Allow-Origin "*"
#Header add Access-Control-Expose-Headers "Access-Control-Allow-Origin"
#Header add Access-Control-Allow-Headers "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With"
#Header add Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS"
#Header add Content-type "application/json"
+129
View File
@@ -0,0 +1,129 @@
<?php
class AccountApi extends Api
{
public $apiName = 'account';
public function indexAction()
{
return $this->response(
array(
'error' => 'Data not found'
), 404);
}
/**
* Method GET
* Get member record (by id)
* http://DOMAIN/trips/1
* @return string
*/
/**
* @OA\Get(
* path="/SAVVY/callback/api/account/:id",
* summary="Get member account data by its unique ID",
* @OA\Parameter(
* name="id",
* in="path",
* required=true,
* @OA\Schema(ref="#/components/schemas/member_id")
* ),
* @OA\Response(
* response="200",
* description="Members data",
* @OA\JsonContent(
* type="object",
* @OA\Schema(ref="#/components/schemas/member_data")
* )
* )
* )
*/
public function viewAction()
{
//id must be the first parameter after /account/x
$id = array_shift($this->requestUri);
if($id && (int)$id>0){
$db = new Db();
$member = Callback::getMemberById($db->getConnect(), (int)$id);
if(is_array($member) && count($member)>0){
return $this->response($member, 200);
}
}
return $this->response(
array(
'error'=> 'Data not found'
), 404);
}
// * description = "curl -d '{\"member_id\":22,\"last_acct\":\"2019-05-06\",\"count_acct\":1}' -X POST https://svrsavvy.sworks.float.sg/SAVVY/callback/api/account",
/**
* @OA\Post(
* path="/SAVVY/callback/api/account",
* summary="Save account data within members record",
* @OA\Parameter(
* name="",
* description = "Account callback data",
* in="body",
* required=true,
* @OA\Schema(ref="#/components/schemas/account_request")
* ),
* @OA\Response(
* response="200",
* description="Members data",
* @OA\JsonContent(
* type="object",
* @OA\Schema(ref="#/components/schemas/member_data")
* )
* )
* )
*/
public function createAction()
{
$message = "Failed to save data";
$member_id = $this->requestParams["member_id"] ?? 0;
$last_acct = $this->requestParams["last_acct"] ?? "";
$count_acct = $this->requestParams["count_acct"] ?? 0;
if ($member_id>0 && $last_acct!="" && strtotime($last_acct)>0 && $count_acct>0) {
$db = new Db();
$member = Callback::getMemberById($db->getConnect(), (int)$member_id);
if (isset($member["id"]) && $member["id"]>0) {
$result = Callback::updateMember(
$db->getConnect(),
(int)$member_id,
$last_acct,
$count_acct);
if (is_array($result) && count($result)>0) {
return $this->response($result, 200);
} else {
$message = "Failed to update member";
}
} else {
$message = "Invalid member id";
}
} else {
$message = "Invalid input $member_id>0 && $last_acct!= $count_acct";
}
return $this->response(
array(
"error" => $message
), 500);
}
public function updateAction()
{
return $this->response(
array(
"error" => "Update error"
), 400);
}
public function deleteAction()
{
return $this->response(
array(
"error" => "Delete error"
), 500);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
class Callback {
public function getMemberById($db, $id) {
$result = array();
$db_id = (int)$id;
$q = "SELECT * FROM members WHERE id=${db_id}";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$result = $f;
}
return $result;
}
public function getMemberByEmail($db, $email) {
$result = array();
$db_email = pg_escape_string(strtolower($email));
$q = "SELECT * FROM members WHERE lower(email)='${db_email}' OR lower(username)='${db_email}'";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$result = $f;
}
return $result;
}
public function updateMember($db, $member_id, $last_acct, $count_acct) {
$result = array();
$id = (int)$member_id;
$db_last_acct = date("Y-m-d H:i:s",strtotime($last_acct));
$db_count_acct = (int)$count_acct;
$q = "UPDATE members SET last_acct='${db_last_acct}',count_acct=${db_count_acct} WHERE id=${id}";
$r = pg_query($db, $q);
if ($r && pg_affected_rows($r)) {
return Callback::getMemberById($db, $id);
}
return $result;
}
}
// vi:ts=2
+121
View File
@@ -0,0 +1,121 @@
<?php
require_once('../../core/backend.php');
require_once('../constants.php');
require_once('../common/Api.php');
require_once('../common/Db.php');
require_once('Callback.php');
require_once('AccountAPI.php');
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
header("Access-Control-Allow-Origin: *");
header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
header("Access-Control-Allow-Headers: Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, client_id");
header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS");
header('Content-type: application/json');
if ("OPTIONS" === $_SERVER['REQUEST_METHOD']) {
exit();
}
$headers = getallheaders();
if ((!isset($headers["Authorization"]) || substr($headers["Authorization"],-strlen($httpAuthToken))!=$httpAuthToken) &&
(!isset($headers["authorization"]) || substr($headers["authorization"],-strlen($httpAuthToken))!=$httpAuthToken)) {
header('HTTP/1.1 401 Unauthorized');
header('Status: 401 Unauthorized');
echo "{\"status\":\"Missing authorization\"}";
exit();
}
try {
if (strpos($_SERVER['REQUEST_URI'],'/api/')===false) {
throw new Exception("Invalid API request");
}
$requestUri = explode('/', trim($_SERVER['REQUEST_URI'],'/'));
while (array_shift($requestUri) !== 'api') {
};
if ($requestUri[0]=='account') {
$api = new AccountApi($requestUri);
}
else {
echo json_encode(Array('error' => 'Invalid API request'));
}
echo $api->run();
}
catch (Exception $e) {
echo json_encode(Array('error' => $e->getMessage()));
}
/**
* @OA\Info(
* title="Call Backend Endpoint API",
* version="0.1",
* @OA\Contact(
* email="support@float.sg"
* )
* )
*/
/**
* @OA\Schema(
* schema="member_id",
* type="integer",
* format="int64",
* description="The unique identifier of a member in our system"
* )
*/
/**
* @OA\Schema(
* schema="account_request",
* type="object",
* @OA\Property(
* property="member_id",
* description="The unique identifier of a member in our system",
* type="int",
* format="int64",
* example=22
* ),
* @OA\Property(
* property="last_acct",
* description="Last date of the account connection",
* type="string",
* format="date-time",
* example="2019-05-06"
* ),
* @OA\Property(
* property="count_acct",
* description="Count of the account items",
* type="int",
* format="int64",
* example=1
* ),
* )
*/
/**
* @OA\Schema(
* schema="member_data",
* type="object",
* @OA\Property(
* property="id",
* description="The unique identifier of a member in our system",
* type="int",
* format="int64",
* example=22
* ),
* @OA\Property(
* property="last_acct",
* description="Last date of the account connection",
* type="string",
* format="date-time",
* example="2019-05-06"
* ),
* @OA\Property(
* property="count_acct",
* description="Count of the account items",
* type="int",
* format="int64",
* example=1
* ),
* )
*/
Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

@@ -0,0 +1,60 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="swagger-ui.css" >
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="swagger-ui-bundle.js"> </script>
<script src="swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "../swagger.php",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
<?php
require('../../../adminsavvy/vendor/autoload.php');
$openapi = \OpenApi\scan('.');
header('Content-Type: application/json');
echo $openapi->toJson();
?>
+27
View File
@@ -0,0 +1,27 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /SAVVY/carpool/
#RewriteBase /
#Checks to
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]
</IfModule>
<IfModule !mod_rewrite.c>
# If we don't have mod_rewrite installed, all 404's
# can be sent to index.php, and everything works as normal.
# Submitted by: ElliotHaughin
ErrorDocument 404 /index.php
</IfModule>
#Header add Access-Control-Allow-Origin "*"
#Header add Access-Control-Expose-Headers "Access-Control-Allow-Origin"
#Header add Access-Control-Allow-Headers "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With"
#Header add Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS"
#Header add Content-type "application/json"
@@ -0,0 +1,58 @@
<?php
class Carpool {
public function getCarpoolStatus($db, $gpsdb, $data) {
$member_id = (int) $data['member_id'];
if ($member_id < 1) {
return NULL;
}
// get user email
$q = "select username from members WHERE id=${member_id};";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
while ($f = pg_fetch_assoc($r)) {
// get user email
$user_email = "";
// if not found return error, weird
}
}
// get the sent invites
$q = "select c.id, c.member_id, c.card_id, c.pool, c.status, c.added, c.updated, cf.firstname, cf.lastname, cf.email, cf.status, cf.accept_status, cf.pool_status
from members_carpool c
join members_carpool_friends cf on c.id = cf.carpool_id
WHERE member_id = ${member_id};";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
while ($f = pg_fetch_assoc($r)) {
// get user sent invites
$sent_invites = array();
}
}
// get the received invites
$q = "select id, carpool_id,email, status, accept_status, pool_status, added, updated from members_carpool_friends where email = '${user_email}';";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
while ($f = pg_fetch_assoc($r)) {
// get user received invites
$received_invites = array();
}
}
return array(
'sent_invites' => $sent_invites,
'received_invites' => $received_invites,
);
}
}
+164
View File
@@ -0,0 +1,164 @@
<?php
/*
CARPOOL AND POINT
*/
include '../../core/backend.php';
include_once '../config.php';
include_once '../constants.php';
include '../formarter.php';
$endpoints = array(
'carpooltrack' => array('POST'),
'another' => array('POST')
);
//*
header("Access-Control-Allow-Origin: *");
header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
header("Access-Control-Allow-Headers: Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, client_id");
header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS");
header('Content-type: application/json');
//*/
if ("OPTIONS" === $_SERVER['REQUEST_METHOD']) {
exit();
}
$headers = getallheaders();
if ((!isset($headers["authorization"]) || substr($headers["authorization"], -strlen($httpAuthToken)) != $httpAuthToken) &&
(!isset($headers["Authorization"]) || substr($headers["Authorization"], -strlen($httpAuthToken)) != $httpAuthToken)) {
header('HTTP/1.1 401 Unauthorized');
header('Status: 401 Unauthorized');
echo "{\"status\":\"Missing authorization\"}";
exit();
}
$endpoint = strtolower(str_replace('/SAVVY/carpool/', '', strtok($_SERVER['REQUEST_URI'], '?')));
$id = 0; // update, get & delete actions require ID
if (substr($endpoint, 0, 19) == 'gettransportrequest' || substr($endpoint, 0, 13) == 'updateprofile') {
$endpoint = strtok($endpoint, '/');
$id = strtok('/');
}
if (!isset($endpoints[$endpoint])) {
header('HTTP/1.1 400 Bad Request');
header('Status: 400 Bad Request');
echo "{\"status\":\"Invalid endpoint url\"}";
exit();
}
$methods = $endpoints[$endpoint];
if (array_search($_SERVER['REQUEST_METHOD'], $methods) === false) {
header('HTTP/1.1 405 Method Not Allowed');
header('Status: 405 Method Not Allowed');
echo "{\"status\":\"Invalid request method\"}";
exit();
}
include '../rest_api.php';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if ($endpoint == "uploadfile") {
upload_file_call();
exit();
} else {
$raw_json = file_get_contents("php://input");
$raw_array = json_decode($raw_json, true);
$in = flatten($raw_array);
}
}
if ($_SERVER["REQUEST_METHOD"] == "PUT") {
parse_str(file_get_contents('php://input'), $in);
}
// Decrypt the input
if (isset($in['encrypted_payload'])) {
$payload = openssl_decrypt(hex2bin($in['encrypted_payload']), $encryptionAlg, $encryptionKey, OPENSSL_RAW_DATA, $encryptionIV);
unset($in['encrypted_payload']);
$in = array_merge($in, json_decode($payload, true));
}
// get who is connecting IP
$in["loc"] = getRemoteIpAddress(); // Do not use $_SERVER["REMOTE_ADDR"]; it is INVALID!!!
$in["pid"] = 100;
// override session parameter(s) with the header value
$in["session"] = $headers["x-session-id"];
$in["sessionid"] = $headers["x-session-id"];
$out = array();
$extension_call = true; // by defualt unless specified at the gate
switch ($endpoint) {
case 'carpooltrack': $in["action"] = SAVVY_CARPOOL_TRACK;
$out["status"] = "Got here anyway";
break;
}
$in["pid"] = 100;
//file_put_contents("in_debug.log", $in); // DEBUG
//external_internal_call($in, $out);
function Fextension_call($in, &$out) {
global $savvyext;
foreach ($in as $key=>$val) {
if ($val!="" && is_string($val)) {
$in[$key] = pg_escape_string($val);
}
}
$out = $savvyext->savvyext_api($in);
return $out["retval"];
}
if ($extension_call == true) {
Fextension_call($in, $out);
}
header("HTTP/1.1 200 OK");
header("Status: 200 OK");
//$out = array_merge($in, $out); // DEBUG
$payload = json_encode(processOutJson($in, $out));
echo $payload."\n";
//$encrypted_payload = bin2hex(openssl_encrypt($payload, $encryptionAlg, $encryptionKey, OPENSSL_RAW_DATA, $encryptionIV));
//echo "{\"payload\": \"${encrypted_payload}\"}";
exit();
function flatten($data, $parentkey = "") {
$result = array();
foreach ($data as $key => $val) {
if (is_array($val)) {
$result = array_merge($result, flatten($val, $parentkey . $key . "_"));
} else {
$result[$parentkey . $key] = $val;
}
}
return $result;
}
function getRemoteIpAddress() {
$ip = NULL;
if (!empty($_SERVER['HTTP_CLIENT_IP']) && filter_var($_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP)) {
$ip = trim($_SERVER['HTTP_CLIENT_IP']);
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && filter_var($_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP)) {
$ip = trim($_SERVER['HTTP_X_FORWARDED_FOR']);
} else {
// Will not make much sense since we are behind the WAF reverse proxy
$ip = trim($_SERVER['REMOTE_ADDR']);
}
putenv("REMOTE_ADDR=${ip}");
$_ENV["REMOTE_ADDR"] = $ip;
return $ip;
}
// vi:ts=2
+27
View File
@@ -0,0 +1,27 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /SAVVY/cityservices/
#RewriteBase /
#Checks to
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]
</IfModule>
<IfModule !mod_rewrite.c>
# If we don't have mod_rewrite installed, all 404's
# can be sent to index.php, and everything works as normal.
# Submitted by: ElliotHaughin
ErrorDocument 404 /index.php
</IfModule>
#Header add Access-Control-Allow-Origin "*"
#Header add Access-Control-Expose-Headers "Access-Control-Allow-Origin"
#Header add Access-Control-Allow-Headers "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With"
#Header add Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS"
#Header add Content-type "application/json"
@@ -0,0 +1,72 @@
<?php
class CityServiceApi extends Api {
public $apiName = 'cityservices';
public function __construct($requestUri, $encryption=true) {
$encryption = false; // We do not need encryption for this class
parent::__construct($requestUri, $encryption);
//$this->encryption = $encryption;
}
/**
* @OA\Get(
* path="/SAVVY/touristattraction/api/touristattraction?city={city}&country={country}",
* security={{"token": {}}},
* summary="Get geofenceareacity by city and country code",
* @OA\Response(
* response="200",
* description="Found geofence area city",
* @OA\JsonContent(
* type="object"
* )
* )
* )
*/
public function indexAction() {
$message = 'No locations found';
$city = $this->requestParams['city'] ?? '';
$results = [];
$res_status = 404;
try {
if (empty($city)) {
throw new Exception("Invalid city");
}
$db = new Db();
list($results, $err) = CityServiceModel::get( $db->getConnect(), $city );
if (!empty($err)) {
$result = [
"services" => NULL,
"error" => $err
];
} else {
$result = [
"services" => $results,
];
$res_status = 200;
}
} catch (Exception $e) {
$message = $e->getMessage();
$result = [
"services" => NULL,
"error" => $message
];
}
return $this->response($result, $res_status);
}
public function viewAction() {}
public function createAction() {}
public function updateAction() {}
public function deleteAction() {}
}
@@ -0,0 +1,19 @@
<?php
class CityServiceModel {
public static function get($db, $city) {
$city = (int)$city;
// Step 1: Load address
$q = "SELECT *FROM city_services WHERE city_id = ${city}";
$r = pg_query($db, $q);
if ($r && pg_num_rows($r)) {
$results = pg_fetch_all($r);
} else {
return [NULL, "Services not found"];
}
return [$results, NULL];
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
require_once '../../core/backend.php';
require_once '../common/Api.php';
require_once '../common/Db.php';
require_once 'CityServiceModel.php';
require_once 'CityServiceApi.php';
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
header("Access-Control-Allow-Origin: *");
header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
header("Access-Control-Allow-Headers: Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, client_id");
header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS");
header('Content-type: application/json');
if ("OPTIONS" === $_SERVER['REQUEST_METHOD']) {
exit();
}
$headers = getallheaders();
if ((!isset($headers["Authorization"]) || substr($headers["Authorization"], -strlen($httpAuthToken)) != $httpAuthToken) &&
(!isset($headers["authorization"]) || substr($headers["authorization"], -strlen($httpAuthToken)) != $httpAuthToken)) {
header('HTTP/1.1 401 Unauthorized');
header('Status: 401 Unauthorized');
echo "{\"status\":\"Missing authorization\"}";
exit();
}
try {
if (strpos($_SERVER['REQUEST_URI'], '/api/') === false) {
throw new Exception("Invalid API request");
}
$requestUri = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
while (array_shift($requestUri) !== 'api') {
};
if ($requestUri[0] == 'cityservices') {
$api = new CityServiceApi($requestUri, false);
}
echo $api->run();
} catch (Exception $e) {
echo json_encode(array('error' => $e->getMessage()));
}
/**
* @OA\Info(
* title="Geofence Area City Endpoint API",
* version="1.0",
* @OA\Contact(
* email="support@float.sg"
* )
* )
*/
/**
* @OA\SecurityScheme(
* securityScheme="token",
* type="apiKey",
* name="Authorization",
* in="header"
* )
*/
+415
View File
@@ -0,0 +1,415 @@
<?php
require_once('../common/vendor/autoload.php');
use Phpfastcache\CacheManager;
use Phpfastcache\Drivers\Redis\Config;
abstract class Api
{
public $apiName = ''; //trips
protected $method = ''; //GET|POST|PUT|DELETE
public $requestUri = [];
public $requestParams = [];
public $requestHeaders = [];
public $requestWhitelist = [];
public $clientIP = null;
protected $action = '';
//Method name to execute
protected $encryption = true;
public $cacheWhitelist = [];
public $cache;
public $cacheEnabled = false;
public function __construct($requestUri, $encryption=true) {
global $savvyext;
header("Access-Control-Allow-Orgin: *");
header("Access-Control-Allow-Methods: *");
header("Content-Type: application/json");
//GET parameter array separated by slash
//$this->requestUri = explode('/', trim($_SERVER['REQUEST_URI'],'/'));
$this->requestUri = $requestUri;
if (is_array($requestUri) && count($requestUri)>1) {
if (($pos=strpos($requestUri[1],'?'))!==false) {
$requestUri[1] = substr($requestUri[1],1+$pos);
}
parse_str($requestUri[1],$this->requestParams);
}
$this->encryption = $encryption;
//Define the request method
$this->method = $_SERVER['REQUEST_METHOD'];
if ($this->method == 'POST' && array_key_exists('HTTP_X_HTTP_METHOD', $_SERVER)) {
if ($_SERVER['HTTP_X_HTTP_METHOD'] == 'DELETE') {
$this->method = 'DELETE';
}
else if ($_SERVER['HTTP_X_HTTP_METHOD'] == 'PUT') {
$this->method = 'PUT';
}
else {
throw new Exception("Unexpected Header");
}
}
$this->cacheEnabled = ($savvyext->cfgReadLong('cache.enabled') != NULL && $savvyext->cfgReadLong('cache.enabled') == 1);
if ($this->cacheEnabled ) {
$hostsString = $savvyext->cfgReadChar( 'cache.servers' );
$serverConf = [];
if ( ! empty( $hostsString ) ) {
$hostsInfo = explode( ",", $hostsString );
foreach ( $hostsInfo as $hostInfo ) {
$hostInfoItems = explode( ":", $hostInfo );
if ( count( $hostInfoItems ) > 0 ) {
$serverConf = [
'host' => $hostInfoItems[0],
'port' => $hostInfoItems[1] ? intval( $hostInfoItems[1] ) : 6379,
];
break;
}
}
}
if ( count( $serverConf ) != 0 ) {
$conf = new Config($serverConf);
$this->cache = CacheManager::getInstance( 'redis', $conf );
} else {
$this->cacheEnabled = false;
}
}
if ($this->method=="POST") {
$raw_json = file_get_contents("php://input");
$this->requestParams = json_decode($raw_json, true);
}
if ($this->method == "PUT") {
// Do we do key=val&key=val ?
$raw_json = file_get_contents("php://input");
if (strpos($raw_json, 'encrypted_payload') !== false) {
$this->requestParams = json_decode($raw_json, true);
} else {
parse_str($raw_json, $this->requestParams);
}
}
// We can inspect the headers later on
$this->loadRequestHeaders();
// Decrypt the input
if (isset($this->requestParams['encrypted_payload'])) {
$encryptionAlg = $savvyext->cfgReadChar('encryption.algorithm');
$encryptionKey = $savvyext->cfgReadChar('encryption.key');
$encryptionIV = $savvyext->cfgReadChar('encryption.iv');
$payload = openssl_decrypt(
hex2bin(
$this->requestParams['encrypted_payload']
),
$encryptionAlg,
$encryptionKey,
OPENSSL_RAW_DATA,
$encryptionIV
);
unset($this->requestParams['encrypted_payload']);
if (is_array($this->requestParams) && count($this->requestParams)>0) {
$this->requestParams = array_merge($this->requestParams, json_decode($payload, true));
} else {
$data = json_decode($payload, true);
$this->requestParams = is_array($data) ? $data : [];
}
}
}
// No action taken YET!
// TODO: delegate the decision into controller, the default (unset) behaviour is to block
protected function checkRequestHeaders($db, $action) {
error_log('Checking '.$this->apiName.'::'.$action.'...');
if (array_key_exists($action,$this->requestWhitelist)) {
error_log('whitelisted!');
return true;
}
$sessionID = null;
$deviceToken = null;
if (array_key_exists("x-session-id",$this->requestHeaders)) {
$sessionID = $this->requestHeaders["x-session-id"];
}
if (array_key_exists("x-devicetoken",$this->requestHeaders)) {
$deviceToken = $this->requestHeaders["x-devicetoken"];
}
error_log('X-Session-ID: '.$sessionID);
error_log('X-DeviceToken: '.$deviceToken);
// Step 1a: Get member_id by X-DeviceToken
$header_member_id = 0;
$conn = $db->getConnect();
$q = "SELECT * FROM members_devices WHERE access_token='".pg_escape_string($deviceToken)."'";
$r = pg_query($conn, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$header_member_id = $f['member_id'];
$q = "UPDATE members_devices SET updated=now(), status=1 WHERE id=".((int)$f['id'])." RETURNING *";
$r = pg_query($conn, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
error_log('Status updated at: '.$f['updated']);
}
}
if ($header_member_id<1) {
//return false; //throw new RuntimeException('Invalid header member ID', 500);
}
// Step 1b: Get member_id by X-Session-ID
$session_member_id = 0;
$q = "SELECT * FROM members_session WHERE session='".pg_escape_string($sessionID)."'";
$r = pg_query($conn, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$session_member_id = $f['member_id'];
if ($header_member_id<1) {
$q = "UPDATE members_devices SET updated=now(), status=1 WHERE id=".((int)$f['id'])." RETURNING *";
$r = pg_query($conn, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
error_log('Status updated at: '.$f['updated']);
}
}
}
if ($session_member_id<1) {
//return false; //throw new RuntimeException('Invalid session member ID', 500);
}
// Step 2: Get member_id from $this->requestParams
$request_member_id = 0;
if (array_key_exists('member_id',$this->requestParams)) {
$request_member_id = (int)$this->requestParams['member_id'];
}
error_log('member_id[request] = '.$request_member_id);
error_log('member_id[token] = '.$header_member_id);
error_log('member_id[session] = '.$session_member_id);
if ($request_member_id != $session_member_id && $session_member_id>0) {
//$request_member_id = $session_member_id;
$this->requestParams['member_id'] = $session_member_id;
}
// Step 3a: Match Step 1 and 2 result
if ($request_member_id>0) {
// Step 3b: Fallback to X-Session-ID?
if ($request_member_id!=$header_member_id || $request_member_id!=$session_member_id) {
//return false; //throw new RuntimeException('Invalid request member ID', 500);
}
}
return true;
}
protected function loadRequestHeaders() {
$this->requestHeaders = [];
foreach (getallheaders() as $key=>$val) {
// https://cloud.google.com/load-balancing/docs/https/
// After September 30, HTTP(S) Load Balancers will convert HTTP/1.1 header names to lowercase
// in the request and response directions; header values will not be affected.
//error_log('DEBUG: "'.$key.'" => "'.$val.'"');
$this->requestHeaders[strtolower($key)] = $val;
}
return count($this->requestHeaders);
}
protected function checkThrottling($db) {
if (!empty($_SERVER['HTTP_CLIENT_IP']) && filter_var($_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP)) {
$ip = pg_escape_string($_SERVER['HTTP_CLIENT_IP']);
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && filter_var($_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP)) {
$ip = pg_escape_string($_SERVER['HTTP_X_FORWARDED_FOR']);
} else {
// Will not make much sense since we are behind the reverse proxy
$ip = pg_escape_string($_SERVER['REMOTE_ADDR']);
}
$this->clientIP = $ip;
$lastRec = NULL;
$rec = NULL;
$conn = $db->getConnect();
$q = "SELECT *,(EXTRACT(EPOCH FROM time_last) * 1000)::bigint AS last_ms FROM throttling_ip WHERE ip='${ip}'";
$r = pg_query($conn, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$lastRec = $f;
$q = "UPDATE throttling_ip SET total=total+1,time_last=NOW() WHERE ip='${ip}' RETURNING *,(EXTRACT(EPOCH FROM time_last) * 1000)::bigint AS last_ms";
$r = pg_query($conn, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$rec = $f;
}
} else {
$q = "INSERT INTO throttling_ip (ip) VALUES ('${ip}') RETURNING *,(EXTRACT(EPOCH FROM time_last) * 1000)::bigint AS last_ms";
$r = pg_query($conn, $q);
if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) {
$rec = $f;
}
}
if (!$lastRec && $rec && is_array($rec) && count($rec)>0) {
error_log('New IP spotted!');
return true; // New record - never seen this IP before...
}
if (!$lastRec && (!$rec || !is_array($rec) || !count($rec)<1)) {
// Failed to insert new record
error_log('Error: not throttling!');
return true; // TODO: should we fail?
}
// Compare $lastRec to $rec
if (($rec["last_ms"]-$lastRec["last_ms"]) > 10) {
error_log('Not throttle!');
return true;
} else {
error_log('Throttle: ' . $rec["last_ms"] . " - " . $lastRec["last_ms"] . " = ".($rec["last_ms"]-$lastRec["last_ms"]));
return true; // Throttle if less than a second within
}
return true; // OK
}
protected function isActionCached($action) {
error_log('Check is it '.$this->apiName.'::'.$action.' could be cached...');
if (array_key_exists($action,$this->cacheWhitelist) || in_array($action,$this->cacheWhitelist)) {
$ttl = $this->cacheWhitelist[$action]['ttl'] ?? 60;
if ($ttl == 0) {
return false;
}
error_log('whitelisted!');
return true;
}
else return false;
}
public function run() {
//The first 2 elemets of URI array must by "api" and table name
if(array_shift($this->requestUri) !== $this->apiName){
throw new RuntimeException('API Not Found', 404);
}
//Select the action to execute
$this->action = $this->getAction();
$db = new Db();
// Inspect throttling
if (!$this->checkThrottling($db)) {
unset($db);
throw new RuntimeException('Too Many Requests', 429);
}
// Inspect header
if (!$this->checkRequestHeaders($db, $this->action)) {
unset($db);
throw new RuntimeException('Request check failed', 500);
}
unset($db);
//If the method (action) defined in the child API class
if (method_exists($this, $this->action)) {
$result = null;
if( $this->cacheEnabled && $this->isActionCached($this->action)) {
$key = $this->getCacheKey();
$ttl = $this->cacheWhitelist[$this->action]['ttl'] ?? 300;
$cachedString = $this->cache->getItem($key);
$result = $cachedString->get();
if (!is_null($result)) {
return $result;
}
$result = $this->{
$this->action
}
();
if($this->storeInCache()) {
$cachedString->set($result)->expiresAfter($ttl);
$this->cache->save($cachedString);
}
return $result;
} else {
return $this->{
$this->action
}
();
}
}
else {
throw new RuntimeException('Invalid Method', 405);
}
}
protected function response($data, $status = 500) {
global $savvyext;
header("HTTP/1.1 " . $status . " " . $this->requestStatus($status));
if ($this->encryption) {
// encrypt data
$encryptionAlg = $savvyext->cfgReadChar('encryption.algorithm');
$encryptionKey = $savvyext->cfgReadChar('encryption.key');
$encryptionIV = $savvyext->cfgReadChar('encryption.iv');
$payload = json_encode($data);
$encrypted_payload = bin2hex(
openssl_encrypt(
$payload,
$encryptionAlg,
$encryptionKey,
OPENSSL_RAW_DATA,
$encryptionIV
)
);
$data = array(); // Comment out to see the data
$data["payload"] = $encrypted_payload;
}
return json_encode($data);
}
private function requestStatus($code) {
$status = array(
200 => 'OK',
404 => 'Not Found',
405 => 'Method Not Allowed',
500 => 'Internal Server Error',
);
return ($status[$code])?$status[$code]:$status[500];
}
protected function getAction()
{
$method = $this->method;
switch ($method) {
case 'GET':
$pos = strpos($this->requestUri[0],'?');
if ($pos!==false && $pos==0) {
// We want to get "all"
$this->requestUri[0] = "all".$this->requestUri[0];
}
$tok = strtok($this->requestUri[0],'?');
if($tok===false || $tok=='all'){
return 'indexAction';
} else {
return 'viewAction';
}
break;
case 'POST':
return 'createAction';
break;
case 'PUT':
return 'updateAction';
break;
case 'DELETE':
return 'deleteAction';
break;
default:
return null;
}
}
abstract protected function indexAction();
abstract protected function viewAction();
abstract protected function createAction();
abstract protected function updateAction();
abstract protected function deleteAction();
protected function getCacheKey() {
return hash('md5', $this->method.'|'.$this->apiName.'|'.$this->action.'|'.json_encode($this->requestParams));
}
protected function storeInCache() {
return http_response_code() === 200;
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
class Db {
private $conn;
private $conn_gps;
public function __construct() {
}
public function getConnect() {
global $savvyext;
if ($this->conn==NULL || !pg_version($this->conn)) {
$db_host = $savvyext->cfgReadChar('database.host');
$db_name = $savvyext->cfgReadChar('database.name');
$db_user = $savvyext->cfgReadChar('database.user');
$db_pass = $savvyext->cfgReadChar('database.pass');
$db_port = $savvyext->cfgReadLong('database.port');
$connstr = "host=${db_host} port=${db_port} dbname=${db_name} user=${db_user} password=${db_pass}";
$this->conn = pg_connect($connstr);
}
return $this->conn;
}
public function getConnectGPS() {
global $savvyext;
if ($this->conn_gps==NULL || !pg_version($this->conn_gps)) {
$db_host = $savvyext->cfgReadChar('gpsdatabase.host');
$db_name = $savvyext->cfgReadChar('gpsdatabase.name');
$db_user = $savvyext->cfgReadChar('gpsdatabase.user');
$db_pass = $savvyext->cfgReadChar('gpsdatabase.pass');
$db_port = $savvyext->cfgReadLong('gpsdatabase.port');
$connstr = "host=${db_host} port=${db_port} dbname=${db_name} user=${db_user} password=${db_pass}";
$this->conn_gps = pg_connect($connstr);
}
return $this->conn_gps;
}
function __destruct() {
if(!empty($this->conn)){
pg_close($this->conn);
}
if(!empty($this->conn_gps)){
pg_close($this->conn_gps);
}
}
}
// vi:ts=2
+83
View File
@@ -0,0 +1,83 @@
<?php
class Gis {
final public static function haversineDistanceBetweenTwoGpsCoordinates($lat1, $lon1, $lat2, $lon2, $unit="M") {
if (($lat1 == $lat2) && ($lon1 == $lon2)) {
return 0;
}
else {
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
$unit = strtoupper($unit);
if ($unit == "K") {
return ($miles * 1.609344);
} else if ($unit == "N") {
return ($miles * 0.8684);
} else {
return $miles;
}
}
}
final public static function cosinesDistanceBetweenTwoGpsCoordinates($lat1, $lon1, $lat2, $lon2, $unit="M") {
if (($lat1 == $lat2) && ($lon1 == $lon2)) {
return 0;
}
$dist = acos(sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($lon2 - $lon1))) * 6371;
$unit = strtoupper($unit);
if ($unit == "K") {
return $dist;
} else {
return $dist/1.609344;
}
}
final public static function getCityServicesAvailableForCoordinates($db_conn ,$lat, $lng, $providers=[]) {
return self::getServicesAvailableForCoordinates($db_conn, self::GET_CITY_PROVIDERS_IN_RADIUS, $lat, $lng, $providers);
}
final public static function getCountryServicesAvailableForCoordinates($db_conn ,$lat, $lng, $providers=[]) {
return self::getServicesAvailableForCoordinates($db_conn, self::GET_COUNTRY_PROVIDERS_IN_RADIUS, $lat, $lng, $providers);
}
final protected static function getServicesAvailableForCoordinates($db_conn, $q, $lat, $lng, $providers=[]) {
$result = [];
$params = [ $lng, $lat];
if (count($providers)) {
$params[]="{".implode(",", $providers). "}";
$q = $q."AND transport_provider_id = ANY($3)";
}
$req_result = pg_query_params($db_conn, $q, $params);
if (!$req_result) {
return $result;
}
while ($row = pg_fetch_assoc($req_result)) {
array_push($result, $row);
}
return $result;
}
const GET_CITY_PROVIDERS_IN_RADIUS = "
SELECT cs.* FROM geofence_area_city gac
LEFT JOIN city_services cs ON gac.id = cs.city_id
WHERE
ST_DistanceSphere(location::geometry, ST_SetSRID(ST_MakePoint($1,$2),4326)) <= radius
";
const GET_COUNTRY_PROVIDERS_IN_RADIUS = "
SELECT cs.* FROM geofence_area_country gac
LEFT JOIN country_services cs ON gac.id = cs.country_id
WHERE
ST_DistanceSphere(location::geometry, ST_SetSRID(ST_MakePoint($1,$2),4326)) <= radius
";
}
+109
View File
@@ -0,0 +1,109 @@
<?php
# Imports the Google Cloud client libraries
use Google\ApiCore\ApiException;
use Google\Cloud\Kms\V1\CryptoKey;
use Google\Cloud\Kms\V1\CryptoKey\CryptoKeyPurpose;
use Google\Cloud\Kms\V1\KeyManagementServiceClient;
use Google\Cloud\Kms\V1\KeyRing;
class GoogleKMS {
private $client;
private $projectId;
private $authFile;
private $keyRing = NULL;
private $keyRingId = NULL;
private $keyRingName = NULL;
private $keyName = NULL;
private $cryptoKey = NULL;
private $location = 'global';
public function __construct($projectId, $authFile, $keyRingId=NULL, $keyId=NULL) {
// Your Google Cloud Platform project ID
$this->projectId = $projectId; // 'float-app-224118';
// The file path to credentials JSON
//error_log($authFile);
putenv("GOOGLE_APPLICATION_CREDENTIALS=${authFile}");
apache_setenv("GOOGLE_APPLICATION_CREDENTIALS",$authFile,true);
$this->authFile = $authFile; // './float-app-224118-52ef1783d2c5.json';
// Instantiates a client
$this->client = new KeyManagementServiceClient([
'projectId' => $projectId,
'keyFile' => json_decode(file_get_contents($authFile), true)
]);
if ($keyRingId!=NULL) {
$this->createKeyring($keyRingId);
if ($keyId!=NULL) {
$this->createCryptokey($keyId);
}
}
}
public function createKeyring($keyRingId) {
try {
$locationName = $this->client::locationName(
$this->projectId,
$this->location
);
$keyRingName = $this->client::keyRingName(
$this->projectId,
$this->location,
$keyRingId
);
$this->keyRing = $this->client->getKeyRing($keyRingName);
$this->keyRingId = $keyRingId;
$this->keyRingName = $keyRingName;
} catch (ApiException $e) {
if ($e->getStatus() === 'NOT_FOUND') {
$this->keyRing = new KeyRing();
$this->keyRing->setName($keyRingName);
$this->client->createKeyRing(
$locationName,
$keyRingId,
$this->keyRing);
$this->keyRingId = $keyRingId;
$this->keyRingName = $keyRingName;
}
}
return $this->keyRing;
}
public function createCryptokey($keyId) {
try {
$keyName = $this->client::cryptoKeyName(
$this->projectId,
$this->location,
$this->keyRingId,
$keyId);
$this->cryptoKey = $this->client->getCryptoKey($keyName);
$this->keyName = $keyName;
} catch (ApiException $e) {
if ($e->getStatus() === 'NOT_FOUND') {
$this->cryptoKey = new CryptoKey();
$this->cryptoKey->setPurpose(CryptoKeyPurpose::ENCRYPT_DECRYPT);
$this->cryptoKey = $this->client->createCryptoKey(
$this->keyRingName,
$keyId,
$this->cryptoKey);
$this->keyName = $keyName;
}
}
return $this->cryptoKey;
}
public function encrypt($secret) {
$response = $this->client->encrypt($this->keyName, $secret);
$cipherText = $response->getCiphertext();
return $cipherText;
}
public function decrypt($cipherText) {
$response = $this->client->decrypt($this->keyName, $cipherText);
$plainText = $response->getPlaintext();
return $plainText;
}
}
+388
View File
@@ -0,0 +1,388 @@
<?php
class GooglePlacesClient
{
public function get($url)
{
$curl = curl_init();
$options = array(
CURLOPT_URL => $url,
CURLOPT_HEADER => false,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_RETURNTRANSFER => true,
);
curl_setopt_array($curl, $options);
$response = curl_exec($curl);
if ($error = curl_error($curl))
{
throw new \Exception('CURL Error: ' . $error);
}
curl_close($curl);
return $response;
}
}
class GooglePlaces
{
public $client = '';
public $sleep = 3;
private $key = '';
private $base_url = 'https://maps.googleapis.com/maps/api/place';
private $method = null;
private $response = null;
public $keyword = null;
public $language = 'en';
public $location = null;
public $output = 'json';
public $name = null;
public $pagetoken = null;
public $radius = null;
public $rankby = 'prominence';
public $sensor = false;
public $types = null;
public $placeid = null;
public $reference = null;
public $opennow = null;
public $input = null;
public $subradius = null;
public $getmax = true;
private $grid = null;
private $exceptions = array(
'base_url', 'client', 'exceptions', 'getmax', 'grid', 'method',
'output', 'pagetoken', 'response', 'sleep', 'subradius',
);
public function __construct($key, $client = false)
{
$this->key = $key;
$this->client = $client ? $client : new GooglePlacesClient();
}
function __set($variable, $value)
{
// Compensates for mixed variable naming
$variable = str_replace('_', '', strtolower($variable));
$this->$variable = $value;
}
public function __call($method, $arguments)
{
$this->output = strtolower($this->output);
if (!in_array($this->output, array('json', 'xml')))
{
throw new \Exception('Invalid output, please specify either "json" or "xml".');
}
$method = $this->method = strtolower($method);
$url = implode('/', array($this->base_url, $method, $this->output));
$parameters = array();
$parameters = $this->parameterBuilder($parameters);
$parameters = $this->methodChecker($parameters, $method);
if (!empty($this->subradius))
{
return $this->subdivide($url, $parameters);
}
if ($this->pagetoken !== null)
{
$parameters['pagetoken'] = $this->pagetoken;
}
$result = $this->queryGoogle($url, $parameters);
return $result;
}
/**
* Loops through all of our variables to make a parameter list
*/
private function parameterBuilder($parameters)
{
foreach (get_object_vars($this) as $variable => $value)
{
// Except these variables
if (!in_array($variable, $this->exceptions))
{
// Assuming it's not null
if ($value !== null)
{
// Converts boolean to string
if (is_bool($value))
{
$value = $value ? 'true' : 'false';
}
switch ($variable)
{
// Allows LatLng to be passed as an array
case 'location':
if (is_array($value))
{
// Just in case it's an associative array
$value = array_values($value);
$value = $value[0] . ',' . $value[1];
}
break;
// Checks that it's a value rank by value
case 'rankby':
$value = strtolower($value);
if (!in_array($value, array('prominence', 'distance')))
{
throw new \Exception('Invalid rank by value, please specify either "prominence" or "distance".');
}
break;
// Allows types to be passed as an array
case 'types':
if (is_array($value))
{
$value = implode('|', $value);
}
break;
}
$parameters[$variable] = $value;
}
}
}
return $parameters;
}
/**
* takes the parameters and method to throw exceptions or modify parameters as needed
* @todo Method to sanity check passed types
*/
private function methodChecker($parameters, $method)
{
if (!isset($parameters['pagetoken']))
{
switch ($method)
{
case 'nearbysearch':
if (!isset($parameters['location']))
{
throw new \Exception('You must specify a location before calling nearbysearch().');
}
elseif (isset($parameters['rankby']))
{
switch ($parameters['rankby'])
{
case 'distance':
if (!isset($parameters['keyword'])
&& !isset($parameters['name'])
&& !isset($parameters['types']))
{
throw new \Exception('You much specify at least one of the following: "keyword", "name", "types".');
}
if (isset($parameters['radius']))
{
unset($this->radius, $parameters['radius']);
}
break;
case 'prominence':
if (!isset($parameters['radius']))
{
throw new \Exception('You must specify a radius.');
}
break;
}
}
break;
case 'radarsearch':
if (!isset($parameters['location']))
{
throw new \Exception('You must specify a location before calling radarsearch().');
}
elseif (!isset($parameters['radius']))
{
throw new \Exception('You must specify a radius.');
}
elseif (empty($parameters['keyword'])
&& empty($parameters['name'])
&& empty($parameters['types']))
{
throw new \Exception('You much specify at least one of the following: "keyword", "name", "types".');
}
if (isset($parameters['rankby']))
{
unset($this->rankby, $parameters['rankby']);
}
break;
case 'details':
if (!(isset($parameters['reference'])
^ isset($parameters['placeid'])))
{
throw new \Exception('You must specify either a "placeid" or a "reference" (but not both) before calling details().');
}
if (isset($parameters['rankby']))
{
unset($this->rankby, $parameters['rankby']);
}
break;
case 'autocomplete':
/*if (!isset($parameters['location']))
{
throw new \Exception('You must specify a location before calling autocomplete().');
}
elseif (!isset($parameters['radius']))
{
throw new \Exception('You must specify a radius.');
}
else*/if (empty($parameters['input']))
{
throw new \Exception('You much specify the user entered input string.');
}
break;
}
}
return $parameters;
}
/**
* Submits request via curl, sets the response, then returns the response
*/
private function queryGoogle($url, $parameters)
{
if ($this->pagetoken !== null)
{
$parameters['pagetoken'] = $this->pagetoken;
sleep($this->sleep);
}
// Couldn't seem to get http_build_query() to work right so...
$querystring = '';
foreach ($parameters as $variable => $value)
{
if ($querystring != '')
{
$querystring .= '&';
}
$querystring .= $variable . '=' . urlencode($value);
}
$response = $this->client->get($url . '?' . $querystring);
if ($this->output == 'json')
{
$response = json_decode($response, true);
if ($response === null)
{
throw new \Exception('The returned JSON was malformed or nonexistent.');
}
}
else
{
throw new \Exception('XML is terrible, don\'t use it, ever.');
}
$this->response = $response;
return $this->response;
}
/**
* Returns the longitude equal to a given distance (meters) at a given latitude
*/
public function meters2lng($meters, $latitude)
{
return $meters / (cos(deg2rad($latitude)) * 40075160 / 360);
}
/**
* Returns the latitude equal to a given distance (meters)
*/
public function meters2lat($meters)
{
return $meters / (40075160 / 360);
}
/**
* Returns the aggregated responses for a subdivided search
*/
private function subdivide($url, $parameters)
{
if ($this->subradius < 200)
{
throw new \Exception('Subradius should be at least 200 meters.');
}
$quotient = $parameters['radius'] / $this->subradius;
if ($parameters['radius'] % $this->subradius || $quotient % 2)
{
throw new \Exception('Subradius should divide evenly into radius.');
}
$center = explode(',', $parameters['location']);
$centerlat = $center[0];
$centerlng = $center[1];
$count = $quotient;
$lati = $this->meters2lat($this->subradius * 2);
$this->grid['results'] = array();
for ($i = $count / 2 * -1; $i <= $count / 2; $i++)
{
$lat = $centerlat + $i * $lati;
$lngi = $this->meters2lng($this->subradius * 2, $lat);
for ($j = $count / 2 * -1; $j <= $count / 2; $j++)
{
$lng = $centerlng + $j * $lngi;
$loc = $lat . ',' . $lng;
$parameters['location'] = $loc;
$parameters['radius'] = $this->subradius;
$pagetoken = true;
while ($pagetoken)
{
$this->queryGoogle($url, $parameters);
$this->grid[$i][$j] = $this->response;
$this->grid['results'] = array_merge(
$this->grid['results'],
$this->response['results']
);
if (isset($this->response['next_page_token']))
{
$this->pagetoken = $this->response['next_page_token'];
}
else
{
$this->pagetoken = null;
$pagetoken = false;
}
}
}
}
return $this->grid;
}
}
+102
View File
@@ -0,0 +1,102 @@
<?php
class Logger
{
protected static $instance;
private static $logger;
const ERROR = 0;
const WARNING = 1;
const INFO = 2;
const DEBUG = 3;
const DEBUG1 = 4;
const DEBUG2 = 5;
const DEBUG3 = 6;
const DEBUG4 = 7;
const SQL = 8;
const FLOG_MAX = 9;
const LEVELS = [
'ERROR', 'WARNING', 'INFO', 'DEBUG', 'DEBUG1', 'DEBUG2', 'DEBUG3', 'DEBUG4', 'SQL', 'FLOG_MAX'
];
public function __construct()
{
}
public static function getLogger()
{
if (!self::$instance) {
self::initLogger();
}
return self::$instance;
}
public static function initLogger()
{
global $savvyext;
$fluent_host = $savvyext->cfgReadChar('phplogger.host');
$fluent_port = $savvyext->cfgReadLong('phplogger.port');
$log_enabled = $savvyext->cfgReadLong('phplogger.enabled');
if ($log_enabled == 1) {
self::$logger = new Fluent\Logger\FluentLogger($fluent_host, $fluent_port);
}
self::$instance = self::$logger;
}
public static function __callStatic($name, $arguments){
if(is_array($arguments) && count($arguments)>0 && in_array(strtoupper($name), Logger::LEVELS)) {
$data = $arguments[0];
$tag = NULL;
if (count($arguments)>1) {
$tag = $arguments[1];
}
if (count($arguments)>2) {
$name = $arguments[1];
$tag = $arguments[2];
}
$level = Logger::levelByName($name);
return Logger::log($level, $data, $name, $tag);
}
error_log("Logger::__callStatic($name, \$arguments) => Invalid method name!");
}
public static function levelByName($name) {
$val = strtoupper($name);
if(in_array($val, Logger::LEVELS)) {
return array_search($val, Logger::LEVELS, false);
} else {
return Logger::FLOG_MAX;
}
}
public static function nameByLevel($level) {
$level = (int)$level;
return Logger::ERROR < $level || $level > Logger::FLOG_MAX ? Logger::LEVELS[Logger::FLOG_MAX] : Logger::LEVELS[$level];
}
public static function log($level, $data = [], $name = NULL, $tag = NULL)
{
global $savvyext;
$clevel = $savvyext->cfgReadLong('phplogger.level');
$ilevel = (int)$level;
if ($ilevel > $clevel) {
error_log("Logger::log() invalid log level! '$level' => '$ilevel' > '$clevel'");
return;
}
$tag = $tag ?? $savvyext->cfgReadChar('phplogger.tag');
$name = $name ?? $savvyext->cfgReadChar('phplogger.name');
$data = [
"log" => $name,
"level" => Logger::nameByLevel($level),
"pid" => getmypid(),
"zz" => is_array($data) || is_object($data) ? json_encode($data) : $data,
];
if (self::getLogger()) {
self::getLogger()->post($tag, $data);
}
}
}
+152
View File
@@ -0,0 +1,152 @@
<?php
/**
* Polyline
*
* PHP Version 5.3
*
* A simple class to handle polyline-encoding for Google Maps
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Mapping
* @package Polyline
* @author E. McConville <emcconville@emcconville.com>
* @copyright 2009-2015 E. McConville
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3
* @version GIT: $Id$
* @link https://github.com/emcconville/google-map-polyline-encoding-tool
*/
/**
* Polyline encoding & decoding class
*
* Convert list of points to encoded string following Google's Polyline
* Algorithm.
*
* @category Mapping
* @package Polyline
* @author E. McConville <emcconville@emcconville.com>
* @license http://www.gnu.org/licenses/lgpl.html LGPL v3
* @link https://github.com/emcconville/google-map-polyline-encoding-tool
*/
class Polyline
{
/**
* Default precision level of 1e-5.
*
* Overwrite this property in extended class to adjust precision of numbers.
* !!!CAUTION!!!
* 1) Adjusting this value will not guarantee that third party
* libraries will understand the change.
* 2) Float point arithmetic IS NOT real number arithmetic. PHP's internal
* float precision may contribute to undesired rounding.
*
* @var int $precision
*/
protected static $precision = 5;
/**
* Apply Google Polyline algorithm to list of points.
*
* @param array $points List of points to encode. Can be a list of tuples,
* or a flat, one-dimensional array.
*
* @return string encoded string
*/
final public static function encode( $points )
{
$points = self::flatten($points);
$encodedString = '';
$index = 0;
$previous = array(0,0);
foreach ( $points as $number ) {
$number = (float)($number);
$number = (int)round($number * pow(10, static::$precision));
$diff = $number - $previous[$index % 2];
$previous[$index % 2] = $number;
$number = $diff;
$index++;
$number = ($number < 0) ? ~($number << 1) : ($number << 1);
$chunk = '';
while ( $number >= 0x20 ) {
$chunk .= chr((0x20 | ($number & 0x1f)) + 63);
$number >>= 5;
}
$chunk .= chr($number + 63);
$encodedString .= $chunk;
}
return $encodedString;
}
/**
* Reverse Google Polyline algorithm on encoded string.
*
* @param string $string Encoded string to extract points from.
*
* @return array points
*/
final public static function decode( $string )
{
$points = array();
$index = $i = 0;
$previous = array(0,0);
while ($i < strlen($string)) {
$shift = $result = 0x00;
do {
$bit = ord(substr($string, $i++)) - 63;
$result |= ($bit & 0x1f) << $shift;
$shift += 5;
} while ($bit >= 0x20);
$diff = ($result & 1) ? ~($result >> 1) : ($result >> 1);
$number = $previous[$index % 2] + $diff;
$previous[$index % 2] = $number;
$index++;
$points[] = $number * 1 / pow(10, static::$precision);
}
return $points;
}
/**
* Reduce multi-dimensional to single list
*
* @param array $array Subject array to flatten.
*
* @return array flattened
*/
final public static function flatten( $array )
{
$flatten = array();
array_walk_recursive(
$array, // @codeCoverageIgnore
function ($current) use (&$flatten) {
$flatten[] = $current;
}
);
return $flatten;
}
/**
* Concat list into pairs of points
*
* @param array $list One-dimensional array to segment into list of tuples.
*
* @return array pairs
*/
final public static function pair( $list )
{
return is_array($list) ? array_chunk($list, 2) : array();
}
}
@@ -0,0 +1,98 @@
<?php
//namespace Utils;
/**
* Class RandomStringGenerator
* @package Utils
*
* Solution taken from here:
* http://stackoverflow.com/a/13733588/1056679
*/
class RandomStringGenerator
{
/** @var string */
protected $alphabet;
/** @var int */
protected $alphabetLength;
/**
* @param string $alphabet
*/
public function __construct($alphabet = '')
{
if ('' !== $alphabet) {
$this->setAlphabet($alphabet);
} else {
$this->setAlphabet(
implode(range('a', 'z'))
. implode(range('A', 'Z'))
. implode(range(0, 9))
);
}
}
/**
* @param string $alphabet
*/
public function setAlphabet($alphabet)
{
$this->alphabet = $alphabet;
$this->alphabetLength = strlen($alphabet);
}
/**
* @param int $length
* @return string
*/
public function generate($length)
{
$token = '';
for ($i = 0; $i < $length; $i++) {
$randomKey = $this->getRandomInteger(0, $this->alphabetLength);
$token .= $this->alphabet[$randomKey];
}
return $token;
}
/**
* @param int $min
* @param int $max
* @return int
*/
protected function getRandomInteger($min, $max)
{
$range = ($max - $min);
if ($range < 0) {
// Not so random...
return $min;
}
$log = log($range, 2);
// Length in bytes.
$bytes = (int) ($log / 8) + 1;
// Length in bits.
$bits = (int) $log + 1;
// Set all lower bits to 1.
$filter = (int) (1 << $bits) - 1;
do {
$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
// Discard irrelevant bits.
$rnd = $rnd & $filter;
} while ($rnd >= $range);
return ($min + $rnd);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
class Utilities
{
public function __construct()
{
}
public static function convertUtcToLocal($datetime, $timezone, $format = 'Y-m-d H:i:s')
{
if (!empty($datetime) && !empty($timezone)) {
$datetime = date($format, strtotime($datetime));
$utc_date = DateTime::createFromFormat(
$format,
$datetime,
new DateTimeZone('UTC')
);
if ($utc_date) {
$utc_date->setTimeZone(new DateTimeZone($timezone));
return $utc_date->format($format);
}
}
return $datetime;
}
public static function getClientIpAddress()
{
$ip="";
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
//ip from share internet
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
//ip pass from proxy
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}
public static function startBenchmark(&$benchmark=[]){
$benchmark['start'] = microtime(true) * 1000;
}
public static function finishBenchmark(&$benchmark){
$benchmark['finish'] = microtime(true) * 1000;
$benchmark['duration'] = $benchmark['finish'] - $benchmark['start'];
error_log('benchmark'.json_encode($benchmark));
}
}
+403
View File
@@ -0,0 +1,403 @@
<?php
/**
* Class to validate the email address
*
* @author CodexWorld.com <contact@codexworld.com>
* @copyright Copyright (c) 2018, CodexWorld.com
* @url https://www.codexworld.com
*/
class VerifyEmail {
protected $stream = false;
/**
* SMTP port number
* @var int
*/
protected $port = 25;
/**
* Email address for request
* @var string
*/
protected $from = 'root@localhost';
/**
* The connection timeout, in seconds.
* @var int
*/
protected $max_connection_timeout = 30;
/**
* Timeout value on stream, in seconds.
* @var int
*/
protected $stream_timeout = 5;
/**
* Wait timeout on stream, in seconds.
* * 0 - not wait
* @var int
*/
protected $stream_timeout_wait = 0;
/**
* Whether to throw exceptions for errors.
* @type boolean
* @access protected
*/
protected $exceptions = false;
/**
* The number of errors encountered.
* @type integer
* @access protected
*/
protected $error_count = 0;
/**
* class debug output mode.
* @type boolean
*/
public $Debug = false;
/**
* How to handle debug output.
* Options:
* * `echo` Output plain-text as-is, appropriate for CLI
* * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
* * `log` Output to error log as configured in php.ini
* @type string
*/
public $Debugoutput = 'echo';
/**
* SMTP RFC standard line ending.
*/
const CRLF = "\r\n";
/**
* Holds the most recent error message.
* @type string
*/
public $ErrorInfo = '';
/**
* Constructor.
* @param boolean $exceptions Should we throw external exceptions?
*/
public function __construct($exceptions = false) {
$this->exceptions = (boolean) $exceptions;
}
/**
* Set email address for SMTP request
* @param string $email Email address
*/
public function setEmailFrom($email) {
if (!self::validate($email)) {
$this->set_error('Invalid address : ' . $email);
$this->edebug($this->ErrorInfo);
if ($this->exceptions) {
throw new verifyEmailException($this->ErrorInfo);
}
}
$this->from = $email;
}
/**
* Set connection timeout, in seconds.
* @param int $seconds
*/
public function setConnectionTimeout($seconds) {
if ($seconds > 0) {
$this->max_connection_timeout = (int) $seconds;
}
}
/**
* Sets the timeout value on stream, expressed in the seconds
* @param int $seconds
*/
public function setStreamTimeout($seconds) {
if ($seconds > 0) {
$this->stream_timeout = (int) $seconds;
}
}
public function setStreamTimeoutWait($seconds) {
if ($seconds >= 0) {
$this->stream_timeout_wait = (int) $seconds;
}
}
/**
* Validate email address.
* @param string $email
* @return boolean True if valid.
*/
public static function validate($email) {
return (boolean) filter_var($email, FILTER_VALIDATE_EMAIL);
}
/**
* Get array of MX records for host. Sort by weight information.
* @param string $hostname The Internet host name.
* @return array Array of the MX records found.
*/
public function getMXrecords($hostname) {
$mxhosts = array();
$mxweights = array();
if (getmxrr($hostname, $mxhosts, $mxweights) === FALSE) {
$this->set_error('MX records not found or an error occurred');
$this->edebug($this->ErrorInfo);
} else {
array_multisort($mxweights, $mxhosts);
}
/**
* Add A-record as last chance (e.g. if no MX record is there).
* Thanks Nicht Lieb.
* @link http://www.faqs.org/rfcs/rfc2821.html RFC 2821 - Simple Mail Transfer Protocol
*/
if (empty($mxhosts)) {
$mxhosts[] = $hostname;
}
return $mxhosts;
}
/**
* Parses input string to array(0=>user, 1=>domain)
* @param string $email
* @param boolean $only_domain
* @return string|array
* @access private
*/
public static function parse_email($email, $only_domain = TRUE) {
sscanf($email, "%[^@]@%s", $user, $domain);
return ($only_domain) ? $domain : array($user, $domain);
}
/**
* Add an error message to the error container.
* @access protected
* @param string $msg
* @return void
*/
protected function set_error($msg) {
$this->error_count++;
$this->ErrorInfo = $msg;
}
/**
* Check if an error occurred.
* @access public
* @return boolean True if an error did occur.
*/
public function isError() {
return ($this->error_count > 0);
}
/**
* Output debugging info
* Only generates output if debug output is enabled
* @see verifyEmail::$Debugoutput
* @see verifyEmail::$Debug
* @param string $str
*/
protected function edebug($str) {
if (!$this->Debug) {
return;
}
switch ($this->Debugoutput) {
case 'log':
//Don't output, just log
error_log($str);
break;
case 'html':
//Cleans up output a bit for a better looking, HTML-safe output
echo htmlentities(
preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, 'UTF-8'
)
. "<br>\n";
break;
case 'echo':
default:
//Normalize line breaks
$str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
"\n", "\n \t ", trim($str)
) . "\n";
}
}
/**
* Validate email
* @param string $email Email address
* @return boolean True if the valid email also exist
*/
public function check($email) {
$result = FALSE;
if (!self::validate($email)) {
$this->set_error("{$email} incorrect e-mail");
$this->edebug($this->ErrorInfo);
if ($this->exceptions) {
throw new verifyEmailException($this->ErrorInfo);
}
return FALSE;
}
$this->error_count = 0; // Reset errors
$this->stream = FALSE;
$mxs = $this->getMXrecords(self::parse_email($email));
$timeout = ceil($this->max_connection_timeout / count($mxs));
foreach ($mxs as $host) {
/**
* suppress error output from stream socket client...
* Thanks Michael.
*/
$this->stream = @stream_socket_client("tcp://" . $host . ":" . $this->port, $errno, $errstr, $timeout);
if ($this->stream === FALSE) {
if ($errno == 0) {
$this->set_error("Problem initializing the socket");
$this->edebug($this->ErrorInfo);
if ($this->exceptions) {
throw new verifyEmailException($this->ErrorInfo);
}
return FALSE;
} else {
$this->edebug($host . ":" . $errstr);
}
} else {
stream_set_timeout($this->stream, $this->stream_timeout);
stream_set_blocking($this->stream, 1);
if ($this->_streamCode($this->_streamResponse()) == '220') {
$this->edebug("Connection success {$host}");
break;
} else {
fclose($this->stream);
$this->stream = FALSE;
}
}
}
if ($this->stream === FALSE) {
$this->set_error("All connection fails");
$this->edebug($this->ErrorInfo);
if ($this->exceptions) {
throw new verifyEmailException($this->ErrorInfo);
}
return FALSE;
}
$this->_streamQuery("HELO " . self::parse_email($this->from));
$this->_streamResponse();
$this->_streamQuery("MAIL FROM: <{$this->from}>");
$this->_streamResponse();
$this->_streamQuery("RCPT TO: <{$email}>");
$code = $this->_streamCode($this->_streamResponse());
$this->_streamResponse();
$this->_streamQuery("RSET");
$this->_streamResponse();
$code2 = $this->_streamCode($this->_streamResponse());
$this->_streamQuery("QUIT");
fclose($this->stream);
$code = !empty($code2)?$code2:$code;
switch ($code) {
case '250':
/**
* http://www.ietf.org/rfc/rfc0821.txt
* 250 Requested mail action okay, completed
* email address was accepted
*/
case '450':
case '451':
case '452':
/**
* http://www.ietf.org/rfc/rfc0821.txt
* 450 Requested action not taken: the remote mail server
* does not want to accept mail from your server for
* some reason (IP address, blacklisting, etc..)
* Thanks Nicht Lieb.
* 451 Requested action aborted: local error in processing
* 452 Requested action not taken: insufficient system storage
* email address was greylisted (or some temporary error occured on the MTA)
* i believe that e-mail exists
*/
return TRUE;
case '550':
return FALSE;
default :
return FALSE;
}
}
/**
* writes the contents of string to the file stream pointed to by handle
* If an error occurs, returns FALSE.
* @access protected
* @param string $string The string that is to be written
* @return string Returns a result code, as an integer.
*/
protected function _streamQuery($query) {
$this->edebug($query);
return stream_socket_sendto($this->stream, $query . self::CRLF);
}
/**
* Reads all the line long the answer and analyze it.
* If an error occurs, returns FALSE
* @access protected
* @return string Response
*/
protected function _streamResponse($timed = 0) {
$reply = stream_get_line($this->stream, 1);
$status = stream_get_meta_data($this->stream);
if (!empty($status['timed_out'])) {
$this->edebug("Timed out while waiting for data! (timeout {$this->stream_timeout} seconds)");
}
if ($reply === FALSE && $status['timed_out'] && $timed < $this->stream_timeout_wait) {
return $this->_streamResponse($timed + $this->stream_timeout);
}
if ($reply !== FALSE && $status['unread_bytes'] > 0) {
$reply .= stream_get_line($this->stream, $status['unread_bytes'], self::CRLF);
}
$this->edebug($reply);
return $reply;
}
/**
* Get Response code from Response
* @param string $str
* @return string
*/
protected function _streamCode($str) {
preg_match('/^(?<code>[0-9]{3})(\s|-)(.*)$/ims', $str, $matches);
$code = isset($matches['code']) ? $matches['code'] : false;
return $code;
}
}
/**
* verifyEmail exception handler
*/
class verifyEmailException extends Exception {
/**
* Prettify error message output
* @return string
*/
public function errorMessage() {
$errorMsg = $this->getMessage();
return $errorMsg;
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"require": {
"fluent/logger": "^1.0",
"guzzlehttp/guzzle": "^6.3",
"google/apiclient": "^2.4",
"google/cloud-kms": "^1.9",
"ramsey/uuid": "^3.8",
"phpfastcache/phpfastcache": "^7.1"
}
}
Binary file not shown.
+49
View File
@@ -0,0 +1,49 @@
<?php
$base_url = 'https://' . str_replace("extlayer", "svrlayer", $_SERVER["SERVER_NAME"]);
$local_url = 'https://' . $_SERVER["SERVER_NAME"];
$target_url = $base_url . "/internal.php";
$tracklocation_dir = $savvyext->cfgReadChar('system.tracklocation_dir');
$gearmanServers = $savvyext->cfgReadChar('gearman.servers');
$encryptionAlg = $savvyext->cfgReadChar('encryption.algorithm');
$encryptionKey = $savvyext->cfgReadChar('encryption.key');
$encryptionIV = $savvyext->cfgReadChar('encryption.iv');
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
global $pgconn, $pgconn_gps;
//PostgreSQL
$gps_db_host = $savvyext->cfgReadChar('gpsdatabase.host');
$gps_db_port = $savvyext->cfgReadLong('gpsdatabase.port');
$gps_db_name = $savvyext->cfgReadChar('gpsdatabase.name');
$gps_db_user = $savvyext->cfgReadChar('gpsdatabase.user');
$gps_db_pass = $savvyext->cfgReadChar('gpsdatabase.pass');
$conn_string_gps = "host=${gps_db_host} port=${gps_db_port} dbname=${gps_db_name} user=${gps_db_user} password=${gps_db_pass}";
$pgconn_gps = pg_connect($conn_string_gps);
$db_host = $savvyext->cfgReadChar('database.host');
$db_port = $savvyext->cfgReadLong('database.port');
$db_name = $savvyext->cfgReadChar('database.name');
$db_user = $savvyext->cfgReadChar('database.user');
$db_pass = $savvyext->cfgReadChar('database.pass');
$conn_string = "host=${db_host} port=${db_port} dbname=${db_name} user=${db_user} password=${db_pass}";
$pgconn = pg_connect($conn_string);
if ($pgconn) {
// echo "Okay";
} else {
//echo pg_last_error($pgconn);
}
$autocabApiConfig = [
'baseUrl' => $savvyext->cfgReadChar('autocab.baseUrl'),
'agentId' => $savvyext->cfgReadChar('autocab.agentId'),
'agentPassword' => $savvyext->cfgReadChar('autocab.agentPassword'),
'currency' => $savvyext->cfgReadChar('autocab.currency'),
'vendorId' => $savvyext->cfgReadChar('autocab.vendorId'),
'templatesDir' => $savvyext->cfgReadChar('autocab.templatesDir')
];
+77
View File
@@ -0,0 +1,77 @@
<?php
$target_url = $base_url . "/internal.php";
if (defined('SITE_NAME')) {
// Inclided more than once?
} else {
define('SITE_NAME', 'Savvy');
define('SITE_EMAIL', 'info@savvy.com');
define('SITE_PHONE', '+1 911 9110');
define('SITE_FAX', '+1 9FX 9110');
define('PHP_API_OK', 0);
define('MAX_ADMIN_SESSION', 1200);
define('MOBIDELIV_UPLOADS', 4505);
define('MOBIDELIV_DOWNLOAD', 4506);
define('SAVVY_USER_CREATEACCOUNT', 22010);
define('SAVVY_USER_PSERSONALITY', 22011);
define('SAVVY_USER_RESETPASS', 22013);
define('SAVVY_USER_LOGINACCOUNT', 22015);
define('SAVVY_USER_TRACKLOCATION', 22020);
define('SAVVY_USER_SAVECARDPAYMT', 22025);
define('SAVVY_USER_GETCARDPMYLIST', 22030);
define('SAVVY_USER_MANAGEPAYLIST', 22035);
define('SAVVY_USER_LOADUSERPROFILE', 22040);
define('SAVVY_USER_ENABLEFEATURE', 22042);
define('SAVVY_USER_UPDATEUSERPROFILE', 22045);
define('SAVVY_USER_TRANSPORTLIST', 22047);
define('SAVVY_USER_TRANSPORTPROFILE', 22048);
define('SAVVY_USER_SAVEBUDGET', 22049);
define('SAVVY_USERSUSC_LOAD', 22051);
define('SAVVY_USERSUSC_STATUS', 22052);
define('SAVVY_USERSAPP_GETLIST', 22053);
define('SAVVY_USER_GETSAVEDTRIPS', 22070);
define('SAVVY_USER_SAVEUPDTTRIP', 22071);
define('SAVVY_USER_REFRESHSESSION', 22997);
define('SAVVY_USER_VERIFYSESSION', 22999);
define('SAVVY_USERSAPP_SLIDECARD', 44054);
define('SAVVY_USERSAPP_DEALSUB', 44056);
define('SAVVY_USERSAPP_CARPOOL', 44057);
define('SAVVY_USERSAPP_SURVEY', 44060);
define('SAVVY_USERSAPP_POINTSDEATAIL', 44061);
define('SAVVY_USERSAPP_LOADREDEEM', 44062);
define('SAVVY_USERSAPP_REDEEMPPOINTS', 44064);
define('SAVVY_USERSAPP_TRACKCARDCLICK', 44067);
define('SAVVY_USERCARD_DASHCARD', 40100);
define('SAVVY_USERSAVE_DASHCARD', 40110);
define('SAVVY_USERLOAD_SAVEDCARDS', 40115);
define('SAVVY_USERPROP_SETTINGSARRAY', 40007);
define('SAVVY_CARPOOL_SUBSCRIBE', 60010);
define('SAVVY_CARPOOL_STATUS', 60015);
define('SAVVY_CARPOOL_TRACK', 60020);
//cards
define('PHP_LOGIN_OK', 0);
define('PHP_API_BAD_PARAM', -1);
define('CARD_LOCATION_DEFAULT', 0);
define('CARD_ADD_DENIED', 0);
define('CARD_LOCATION_MAINFEED', 100);
define('CARD_ADD_ALLOWED', 100);
define('CARD_LOCATION_ACTIVITIES', 200);
}
+28
View File
@@ -0,0 +1,28 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /SAVVY/email/
#RewriteBase /
#Checks to
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]
</IfModule>
<IfModule !mod_rewrite.c>
# If we don't have mod_rewrite installed, all 404's
# can be sent to index.php, and everything works as normal.
# Submitted by: ElliotHaughin
ErrorDocument 404 /index.php
</IfModule>
#Header add Access-Control-Allow-Origin "*"
#Header add Access-Control-Expose-Headers "Access-Control-Allow-Origin"
#Header add Access-Control-Allow-Headers "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With"
#Header add Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS"
#Header add Content-type "application/json"
+152
View File
@@ -0,0 +1,152 @@
<?php
class Email {
static function createEmail($db, $to, $subject, $htmlBody) {
$result = array();
$status = '0';
$values = array(
"to_emails" => NULL,
"subject" => NULL,
"html_body" => NULL,
"status"=>"0",
"created_at"=>(new DateTime())->format('c'),
"updated_at"=>(new DateTime())->format('c')
);
if(isset($to)) {
$values["to_emails"] = Email::prepareEmailsQueryString($to);
}
if(isset($subject)) {
$values["subject"] = pg_escape_string($subject);
}
if(isset($htmlBody)) {
$values["html_body"] = pg_escape_string($htmlBody);
}
$values = array_filter($values, 'strlen');
$q = "INSERT INTO emails";
// implode keys of $values...
$q .= " (".implode(", ", array_keys($values)).")";
// implode values of $values...
$q .= " VALUES ('".implode("', '", $values)."') ";
$rs = pg_query($db, $q);
if (!$rs) {
return array("success" => false, "message" => "Something went wrong!");
exit;
}
return array("success" => (boolean)pg_affected_rows($rs), "message" => "Successfully create new email.");
}
static function getEmailById($db, $id) {
$result = [];
$q = "SELECT * from emails where id = '" . pg_escape_string($id) . "'";
$rs = pg_query($db, $q);
while ($row = pg_fetch_assoc($rs)) {
array_push($result, Email::parseData($row));
}
return array("success" => true, "data" => $result[0]);
}
static function findEmails($db, $filter, $limit=0)
{
$results = array();
$condition = [];
$q = "SELECT * FROM emails";
if (count($filter)) {
$q .= " where ";
if (isset($filter['status'])) {
$condition[] = "status = '" . pg_escape_string($filter['status']) . "'";
}
if (isset($filter['retries'])) {
$condition[] = "retries < ".((int)$filter['retries']);
}
}
$q .= implode(" AND ", $condition);
if ($limit>0) {
$q.= " LIMIT ".$limit;
}
$rs = pg_query($db, $q);
while ($row = pg_fetch_assoc($rs)) {
array_push($results, Email::parseData($row));
}
return $results ?? NULL;
}
public static function updateEmail($db, $id, $to, $subject, $htmlBody, $status) {
if (!isset($id)) {
return;
}
$q = "update emails set ";
$values = [
"updated_at = '" . (new DateTime())->format('c') ."'"
];
if (isset($to)) {
// $q .= "to_email = " . Email::prepareEmailsQueryString($to);
$values[] = "to_emails = '" . Email::prepareEmailsQueryString($to) . "'";
}
if (isset($subject)) {
$values[] = "subject = '" . pg_escape_string($subject) . "'";
}
if (isset($htmlBody)) {
$values[] = "html_body = '" . pg_escape_string($htmlBody) . "'";
}
if (isset($status)) {
$values[] = "status = '" . pg_escape_string($status) . "'";
}
$q .= implode(", ", $values);
$q .= " where id = " . pg_escape_string((string)$id);
$r = pg_query($db, $q);
if (!$r) {
return array("success" => false, "message" => "Something went wrong!");
exit;
}
return array("success" => true, "message" => "Successfully update email.");
}
private static function parseData($obj) {
$emails = $obj['to_emails'];
$emails = str_replace('{', '', $emails);
$emails = str_replace('}', '', $emails);
$emails = explode(',', $emails);
$obj['to_emails'] = $emails;
return $obj;
}
private static function prepareEmailsQueryString($to) {
$result = "{";
$result .= join(',', $to);
$result .= "}";
return $result;
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
require './vendor/autoload.php';
require '../../core/backend.php';
class EmailApi extends Api
{
public $apiName = 'email';
public $sg;
public function __construct($requestUri, $encryption=true) {
parent::__construct($requestUri, $encryption);
global $savvyext;
$apiKey = $savvyext->cfgReadChar('mailsend.api_key');
$sg = new \SendGrid($apiKey);
$this->sg = $sg;
}
public function indexAction() {
$results = $this->findNewEmails();
return $this->response(
array(
"success" => true,
"data" => $results
), 200 );
}
/**
* @OA\Post(
* path="/v1/email/api/email/new",
* summary="Send email to users",
* @OA\Parameter(
* name="name",
* description = "Name of email template",
* in="body",
* required=true,
* @OA\Schema(ref="#/components/schemas/create_params")
* )
* )
*/
public function createAction()
{
$db = new Db();
$message = "Failed to send email";
$subject = $this->requestParams['subject'] ?? NULL;
$receivers = $this->requestParams["to"] ?? NULL;
$htmlBody = $this->requestParams['html_body'] ?? NULL;
if (!isset($subject) || !isset($receivers) || !isset($htmlBody)) {
return $this->response(
array(
"success" => false,
"message" => 'Missing params'
), 400);
}
$result = Email::createEmail(
$db->getConnect(),
$receivers,
$subject,
$htmlBody
);
if (!isset($result)) {
return $this->response(
array(
"success" => false,
"message" => $message
), 500);
}
return $this->response($result, 200);
}
public function viewAction() {}
public function updateAction() {}
public function deleteAction() {}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
require './vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
class MailchimpAPI
{
private static function handleResponse($response)
{
$res = json_decode($response->getBody());
if (!isset($res)) {
return null;
}
return array_merge((array) $res);
}
public static function get($url, $params)
{
require_once('../../core/backend.php');
global $savvyext;
$token = $savvyext->cfgReadChar('mailchimp.api_key');
$baseUri = $savvyext->cfgReadChar('mailchimp.domain');
$client = new Client(['base_uri' => $baseUri]);
$header = ['Authorization' => "apikey $token"];
try {
$response = $client->request('GET', $url, [
'headers' => $header,
'query' => $params,
]);
} catch (ServerException $e) {
$response = $e->getResponse();
} catch (ClientException $e) {
$response = $e->getResponse();
}
return self::handleResponse($response);
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
require './vendor/autoload.php';
use \SendGrid\Mail\To;
use \SendGrid\Mail\Mail;
use \SendGrid\Mail\From;
use \SendGrid\Mail\HtmlContent;
use \SendGrid\Mail\Personalization;
class SendGridService {
public static function sendEmail($email) {
global $savvyext;
$apiKey = $savvyext->cfgReadChar('mailsend.api_key');
$sg = new \SendGrid($apiKey);
$response = $sg->send($email);
return $response;
}
public static function prepareSengridToObject($receiver = [], $substitutions = NULL) {
return new \SendGrid\Mail\To(
$receiver['email'],
$receiver['firstname'] ?? '',
$substitutions ?? NULL
);
}
public static function prepareEmail($receivers, $subject, $template, $globalSubstitutions) {
global $savvyext;
$fromEmail = $savvyext->cfgReadChar('mailsend.from');
$fromName = $savvyext->cfgReadChar('mailsend.name');
$email = new Mail(
new From($fromEmail, $fromName),
NULL,
$subject, // or array of subjects, these take precendence
$plainTextContent ?? NULL,
new HtmlContent($template),
$globalSubstitutions ?? NULL
);
foreach($receivers as $receiver) {
$personalization = SendGridService::createPersonalize($receiver);
$email->addPersonalization($personalization);
}
return $email;
}
public static function createPersonalize($to) {
$personalize = new Personalization();
$personalize->addTo($to);
return $personalize;
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"name": "vendor/email",
"authors": [
{
"name": "Jack",
"email": "jack@goldenowl.asia"
}
],
"require": {
"guzzlehttp/guzzle": "~6.0",
"sendgrid/sendgrid": "~7"
}
}
+122
View File
@@ -0,0 +1,122 @@
<?php
require_once('../../core/backend.php');
require_once('../common/Api.php');
require_once('../common/Db.php');
require_once('Email.php');
require_once('EmailApi.php');
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
header("Access-Control-Allow-Origin: *");
header("Access-Control-Expose-Headers: Access-Control-Allow-Origin");
header("Access-Control-Allow-Headers: Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With, client_id");
header("Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS");
header('Content-type: application/json');
if ("OPTIONS" === $_SERVER['REQUEST_METHOD']) {
exit();
}
$headers = getallheaders();
if ((!isset($headers["Authorization"]) || substr($headers["Authorization"],-strlen($httpAuthToken))!=$httpAuthToken) &&
(!isset($headers["authorization"]) || substr($headers["authorization"],-strlen($httpAuthToken))!=$httpAuthToken)) {
header('HTTP/1.1 401 Unauthorized');
header('Status: 401 Unauthorized');
echo "{\"status\":\"Missing authorization\"}";
exit();
}
try {
if (strpos($_SERVER['REQUEST_URI'], '/api/')===false) {
throw new Exception("Invalid API request");
}
$requestUri = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
while (array_shift($requestUri) !== 'api') {
};
if ($requestUri[0] == 'email') {
$api = new EmailApi($requestUri, false);
} else {
echo json_encode(Array("message" => 'Invalid API request'));
}
echo $api->run();
}
catch (Exception $e) {
echo json_encode(Array("message" => $e->getMessage()));
}
/**
* @OA\Info(
* title="Email Endpoint API",
* version="1.0",
* @OA\Contact(
* email="support@float.sg"
* )
* )
*/
/**
* @OA\Schema(
* schema="receivers",
* type="object",
* @OA\Property(
* property="email",
* description="Email of receiver",
* type="string",
* format="string",
* example="jack@goldenowl.asia"
* ),
* @OA\Property(
* property="name",
* description="Name of receiver",
* type="string",
* format="string",
* example="Jack Nguyen",
* ),
* @OA\Property(
* property="substitutions",
* description="Receiver's personal information for email"
* type="object"
* example="{ "{{key}}": "value" }"
* )
* )
*/
/**
* @OA\Schema(
* schema="create_params",
* type="object",
* @OA\Property(
* property="name",
* description="Name of email template",
* type="string",
* format="string",
* example="welcome"
* ),
* @OA\Property(
* property="data",
* description="Global dynamic data for the template",
* type="object",
* properties:
* key:
* type: string,
* format: "{{key_name}}"
* value:
* type: string
* example= {
* "{{contactus}}": "https://float.sg"
* }
* ),
* @OA\Property(
* property="receivers",
* description="List email's receivers",
* type="array",
* items="$ref: '#/components/schemas/receivers' "
*
* )
* )
*/

Some files were not shown because too many files have changed in this diff Show More