cacheWhitelist = [ // "viewAction" => ['ttl' => 300], // 300 sec. = 5 min. // "createAction" => ['ttl' => 300] // 300 sec. = 5 min. // ]; parent::__construct($requestUri, $encryption); } public function indexAction() { $origin = $this->requestParams["origin"] ?? ""; $destination = $this->requestParams["destination"] ?? ""; $transport_provider_id = $this->requestParams["transport_provider_id"] ?? ""; $limit = 10; $offset = 0; if ($destination && $origin) { $db =new Db(); list($total, $quotes) = Quotes::getByOriginDestinationId( $db->getConnect(), $origin, $destination, $transport_provider_id, $limit, $offset ); if(is_array($quotes) && count($quotes)>0) { return $this->response(array( 'origin' => $origin, 'destination' => $destination, 'count' => count($quotes), 'limit' => $limit, 'offset' => $offset, 'total' => $total, 'quotes' => $quotes ), 200); } } return $this->response( array( 'error' => 'Data not found' ), 404); } /** * Method GET * Get single record (by id) * http://DOMAIN/quote/1 * @return string */ public function viewAction() { //id must be the first parameter after /trips/x $id = array_shift($this->requestUri); $message = 'Data not found'; $code = 404; try { if($id && (int)$id>0) { $db = new Db(); $quote = QuoteApi::viewReal($db, $id); if (is_array($quote)) { return $this->response($quote, 200); } } } catch (Exception $e) { $code = 500; $messge = $e->getMessage(); } return $this->response( array( 'error'=> $message ), $code); } public static function viewReal($db, $id) { $quote = Quotes::getById($db->getConnect(), (int)$id); if(is_array($quote) && count($quote)>0) { $log = [ 'message' => 'viewReal quote', 'data' => $quote ]; Logger::debug($log); $quote["deeplink"] = Quotes::deeplink($db->getConnect(), $quote); // Check "completed" if ($quote["completed"]!="" && $quote['travel_date']!="" && (int)$quote["cost"]!=0) { /* $log = [ 'message' => 'completed quote', 'data' => $quote ]; Logger::debug($log); */ return $quote; // OK! } // Call service if needed if ($quote["automation_id"]>0) { list($quoteService, $err) = QuoteAPI::checkQuote($quote["automation_id"]); // Update if (is_array($quoteService) && $quoteService["complete"]!="") { $quoteService["automation_id"] = $quoteService["id"]; $quoteService["id"] = (int)$id; $quoteService["completed"] = "now()"; $timezone = Address::getTzByAddressId($db->getConnect(),$quote["location_start_id"]); $quoteService["travel_date"] = Utilities::convertUtcToLocal($quoteService["complete"],$timezone); $quoteService["created"] = $quote["created"]; $quoteService["location_start_id"] = $quote["location_start_id"]; $quoteService["location_end_id"] = $quote["location_end_id"]; $quoteService["member_id"] = $quote["member_id"]; $quoteService["deeplink"] = $quote["deeplink"]; $log = [ 'message' => 'QuoteApi::viewReal update', 'data' =>$quoteService ]; Logger::debug($log); list ($res, $err) = Quotes::update($db->getConnect(), $quoteService); /* $log = [ 'message' => 'QuoteApi::viewReal update response', 'data' =>$res, 'error' =>$err ]; Logger::debug($log); */ if ($quoteService["cost"]==0 && $quoteService["message"]=="android_automation_job_detail") { // We must take the details $quoteService = QuoteApi::quoteDetails($db, $quoteService); return $quoteService; } return $quoteService; } } /* $log = [ 'message' => 'can not checkquote from android_automation', 'data' =>$quote ]; Logger::debug($log); */ return $quote; } return null; } public function quoteDetails($db, $quoteService) { list($details,$err) = self::getQuoteDetails($quoteService["automation_id"]); if ($details==NULL) { error_log($err); $quoteService['error'] = $err; } //$quoteService['details'] = $details; $low_estimate = $quoteService['cost']; foreach ($details as $item) { $c_low_estimate = sprintf("%0.02f", 0.01*$item["low_estimate"]); // We show GrabShare only for now! if ($c_low_estimate==0 || $item["name"]!='JustGrab') continue; if ($low_estimate==0 || $low_estimate>$c_low_estimate) { error_log("quoteDetails: ".$item["name"]." => ".$c_low_estimate); $low_estimate = $c_low_estimate; } } $quoteService['cost'] = $low_estimate; // Update cost $log = [ 'message' => 'get automation detail and update quotes', 'data' =>$quoteService ]; Logger::debug($log); list ($res, $err) = Quotes::update($db->getConnect(), $quoteService); /* $log = [ 'message' => 'QuoteApi::getQuoteDetails update response', 'data' =>$res, 'error' =>$err ]; Logger::debug($log); */ return $quoteService; } public static function createReal($db, $origin, $destination, $member_id, $transport_provider_id, $quote_group_id, $trackedemail_item_id, $country=GeocodeApi::DEFAULT_COUNTRY_CODE, $prefill='f',$pool=1) { $originStr = is_array($origin) ? json_encode($origin) : $origin; $destinationStr = is_array($destination) ? json_encode($destination) : $destination; syslog(LOG_WARNING,"QuoteApi::createReal(\$db, $originStr, $destinationStr, $member_id, $transport_provider_id, $quote_group_id, $trackedemail_item_id, $country, $prefill, $pool"); $message = "Failed to schedule quote"; $code = 500; $action = ""; $data = []; try { if (!in_array($transport_provider_id, [1,2,3,4,5,8])) { throw new Exception('Unsupported transport provider ID'); } if (!$destination || !$origin) { throw new Exception('Invalid origin and/or destination address'); } $originData= NULL; $destinationData = NULL; if (is_array($origin)) { $address = $origin["address"]; if (0 === count(array_diff(['lat', 'lng', 'address', 'postal', 'timeZoneId', 'country'], array_keys($origin)))) { $originData = $origin; } $origin = $address; } if (is_array($destination)) { $address = $destination["address"]; if (0 === count(array_diff(['lat', 'lng', 'address', 'postal', 'timeZoneId', 'country'], array_keys($destination)))) { $destinationData = $destination; } $destination = $address; } // Check if exists if (isset($originData) && isset($destinationData)) { $quoteUpdate = Quotes::getLastQuoteByOriginDestinationCoordinates($db->getConnect(), $originData, $destinationData, $transport_provider_id,SELF::CACHE_TIMEOUT); } else { $quoteUpdate = Quotes::getLastQuoteByOriginDestinationId($db->getConnect(), $origin, $destination, $transport_provider_id,SELF::CACHE_TIMEOUT); } /* $log = [ 'message' => 'QuoteApi::createReal getLastQuoteByOriginDestinationId', 'quote' => $quoteUpdate ]; Logger::debug($log); */ if(isset($quoteUpdate)) { $id = $quoteUpdate["id"]; $completed = $quoteUpdate["completed"]; if(empty($quoteUpdate['travel_date']) && $completed != "") { if (isset($originData)) { $timezone = $originData['timeZoneId']; } else { $timezone = Address::getTzByAddressId($db->getConnect(),$quoteUpdate["location_start_id"]); } $quoteUpdate["travel_date"] = Utilities::convertUtcToLocal($completed,$timezone); list ($res, $err) = Quotes::update($db->getConnect(), $quoteUpdate); /* $log = [ 'message' => 'QuoteApi::createReal getQuoteFromCache update response', 'data' => $res, 'errror' => $err ]; Logger::debug($log); */ } return ["Quote from cache",200,"view",["id"=>$id]]; } // Geocode origin and destination $headers = getallheaders(); $client_id = isset($headers['client_id'])?$headers['client_id']:"Unknown"; if (!isset($originData)) { list($originData, $err) = GeocodeAPI::geocode($db, $origin, $country,$client_id); /* $log = [ 'message' => 'QuoteApi::geocode origin', 'data' => ['client_id'=>$client_id, 'address_input'=>$origin], 'errror' => $err ]; Logger::debug($log); */ if ($err || !isset($originData["lat"])) throw new Exception("Origin geocoding failed: ".$err); } if (!isset($destinationData)) { list($destinationData, $err) = GeocodeAPI::geocode($db, $destination, $country,$client_id); /* $log = [ 'message' => 'QuoteApi::geocode destination', 'data' => ['client_id'=>$client_id, 'address_input'=>$destination], 'errror' => $err ]; Logger::debug($log); */ if ($err || !isset($destinationData["lat"])) throw new Exception("Destination geocoding failed: ".$err); } $availableTransportProviders = Gis::getCityServicesAvailableForCoordinates($db->getConnect(), $originData["lat"], $originData["lng"],[$transport_provider_id]); if (count($availableTransportProviders) == 0) { $availableTransportProviders = Gis::getCountryServicesAvailableForCoordinates( $db->getConnect(), $originData["lat"], $originData["lng"],[$transport_provider_id]); } if (count($availableTransportProviders) == 0) { $message = "No transport providers available for origin coordinates (".$originData["lat"].",".$originData["lng"].")."; throw new Exception($message); } $distance = Gis::cosinesDistanceBetweenTwoGpsCoordinates($originData["lat"], $originData["lng"], $destinationData["lat"], $destinationData["lng"], "K"); $isDistanceRestricted = false; switch ($transport_provider_id) { case Quotes::VENDOR_UBER: $isDistanceRestricted = $distance > QuoteApi::UBER_DISTANCE_RESTRICTION; break; case Quotes::VENDOR_LYFT: $isDistanceRestricted = $distance > QuoteApi::LYFT_DISTANCE_RESTRICTION; break; case Quotes::VENDOR_GRAB: case Quotes::VENDOR_GOJEK: case Quotes::VENDOR_COMFORTDELGRO: $isDistanceRestricted = $distance > QuoteApi::SINGAPORE_DISTANCE_RESTRICTION; break; case Quotes::VENDOR_AUTOCAB: $isDistanceRestricted = $distance > QuoteApi::AUTOCAB_DISTANCE_RESTRICTION; break; } if ($isDistanceRestricted) { $message = "Distance restriction. Could not get quote for (".$originData["lat"].",".$originData["lng"].") and (".$destinationData["lat"].",".$destinationData["lng"]."). The distance (".$distance." km) too long."; throw new Exception($message); } $isOriginWithin = false; $isDestinationWithin = false; // Check if we are within Singapore: for Grab, Gojek, CDG if ($transport_provider_id>2 && $transport_provider_id<6) { $city = 'Singapore'; $sgLat = 1.352083; $sgLng = 103.819836; $radius = 25; // km $isOriginWithin = GeocodeAPI::withinCity( $originData["address"],$originData["lat"],$originData["lng"], $city,$sgLat,$sgLng,$radius); $isDestinationWithin = GeocodeAPI::withinCity( $destinationData["address"],$destinationData["lat"],$destinationData["lng"], $city,$sgLat,$sgLng,$radius); } // Uber, Lyft and Autocab: no check required if (in_array($transport_provider_id, [1,2,8])) { $isOriginWithin = true; $isDestinationWithin = true; } if ($isOriginWithin && $isDestinationWithin) { syslog(LOG_WARNING,"'".$originData["address"]."' and '".$destinationData["address"]."' are within Singapore"); } else { throw new Exception("Origin and/or destination is not within service area!"); } // Schedule the quote $requestData = array( "location_start" => $originData["address"] ?? $origin, "location_end" => $destinationData["address"] ?? $destination, "trackedemail_item_id" => $trackedemail_item_id, "transport_provider_id" => $transport_provider_id, "location_start_lat" => $originData["lat"], "location_start_lng" => $originData["lng"], "location_start_country" => $originData["country"], "location_end_lat" => $destinationData["lat"], "location_end_lng" => $destinationData["lng"], "location_end_country" => $destinationData["country"], "pool" => $pool ); list($res, $err) = QuoteAPI::scheduleQuote($requestData); if ($res==NULL || !isset($res["id"])) { throw new Exception("Quote schedule failed: " . ($err ?? $message)); } // Save to DB $requestData["location_start_tz"] = $originData["timeZoneId"]; $requestData["location_start_postal"] = $originData["postal"]; $requestData["location_start_country"] = $originData["country"]; $requestData["location_end_tz"] = $destinationData["timeZoneId"]; $requestData["location_end_postal"] = $destinationData["postal"]; $requestData["location_end_country"] = $destinationData["country"]; $requestData["automation_id"] = $res["id"]; $requestData["member_id"] = (int)$member_id; $requestData["quote_group_id"] = (int)$quote_group_id; $requestData["prefill"] = $prefill; $requestData["pool"] = $pool; error_log('quote_group_id='.((int)$quote_group_id)); unset($requestData["trackedemail_item_id"]); $log = [ 'message' => 'createReal quote', 'data' =>$requestData ]; Logger::debug($log); list($quote,$err) = Quotes::create($db->getConnect(), $requestData); /* $log = [ 'message' => 'createReal Quotes::create response', 'quote' => $quote ]; Logger::debug($log); */ if ($quote!=NULL && is_array($quote)) { $quote["deeplink"] = Quotes::deeplink($db->getConnect(), $quote, $requestData); return ["Quote scheduled",200,"response",$quote]; } else { $message = "Failed to create quote record: ".$err; } } catch (Exception $e) { syslog(LOG_WARNING,json_encode($e)); $message = $e->getMessage(); } syslog(LOG_WARNING,$message); return [$message,$code,$action,$data]; } public function createAction() { syslog(LOG_WARNING,"QuoteApi::createAction()"); $origin = $this->requestParams["origin"] ?? ""; $destination = $this->requestParams["destination"] ?? ""; $member_id = $this->requestParams["member_id"] ?? 0; $country = $this->requestParams["country"] ?? GeocodeApi::DEFAULT_COUNTRY_CODE; $transport_provider_id = $this->requestParams["transport_provider_id"] ?? 0; $group_quote_id = $this->requestParams["group_quote_id"] ?? 0; $trackedemail_item_id = $this->requestParams["trackedemail_item_id"] ?? 0; $prefill = $this->requestParams["prefill"] ?? 'f'; $pool = $this->requestParams["pool"] ?? 1; if ($prefill!='t') $prefill = 'f'; // if ($pool!=2) $pool = 1; // 1 - main pool, 2 - dedicated pool $db = new Db(); // DEBUG $country = Geocode::mockGPSCountry($db->getConnect(), $member_id, $country, 'default'); list($message,$code,$action,$data) = QuoteApi::createReal( $db, $origin, $destination, $member_id, $transport_provider_id, $group_quote_id, $trackedemail_item_id, $country, $prefill, $pool ); syslog(LOG_WARNING,"message=${message}"); syslog(LOG_WARNING,"code=${code}"); syslog(LOG_WARNING,"action=${action}"); syslog(LOG_WARNING,"data=".json_encode($data)); if ($code==200) { if ($action=="view") { $id = $data["id"]; array_unshift($this->requestUri, $id); return $this->viewAction(); } else if ($action=="response") { return $this->response($data, 200); } } return $this->response( array( "error" => $message ), $code); } public function updateAction() { return $this->response( array( "error" => "Update error" ), 400); } public function deleteAction() { return $this->response( array( "error" => "Delete error" ), 500); } public static function getRideshareQuote($db, $origin, $destination, $member_id, $transport_provider_id, $country) { $group_quote_id = 0; $trackedemail_item_id = 0; $prefill = 'f'; $id = 0; list($message,$code,$action,$data) = QuoteApi::createReal( $db, $origin, $destination, $member_id, $transport_provider_id, $group_quote_id, $trackedemail_item_id, $country, $prefill ); if ($code==200) { // Do we have a cost? Most likely not now... if (is_array($data) && array_key_exists("cost",$data)) { return [$data["cost"],$data["id"]]; } if ($action=="view") { // "View" quote sleep(5); $quote = QuoteApi::viewReal($db, $data["id"]); if (is_array($quote) && array_key_exists("cost",$quote)) { return [$quote["cost"],$data["id"]]; } } if (is_array($data) && array_key_exists("id",$data) && $data["id"]>0) { $id = $data["id"]; // No id - no way to check what is going on sleep(5); // wait a second... // Try to "view" quote $quote = QuoteApi::viewReal($db, $data["id"]); if (is_array($quote) && array_key_exists("cost",$quote)) { return [$quote["cost"],$data["id"]]; } } } return [0,$id]; } public function checkQuote($id) { return Quotes::checkQuote($id); } public function getQuoteDetails($id) { return Quotes::getQuoteDetails($id); } /* curl -d '{ "location_start":"Marina Bay Sands, 10 Bayfront Ave, Singapore 018956", "location_end":"97 Meyer Road, Singapore", "trackedemail_item_id":"15549", "transport_provider_id":"3", "location_start_lat":"1.2833754", "location_start_lng":"103.8607264", "location_end_lat":"1.2967624", "location_end_lng":"103.8927854" }' \ -H "Authorization: Server-Token 99dfe35fcb7de1ee" -H "Content-Type: application/json" \ -X POST http://automation.float.sg/api/automation-job */ public function scheduleQuote($data) { syslog(LOG_WARNING,'QuoteApi::scheduleQuote($data)'); global $savvyext; $httpAuthToken = $savvyext->cfgReadChar('system.automation_api_token'); $automation_api_url = $savvyext->cfgReadChar('system.automation_api_url'); $url = $automation_api_url."automation-job/"; $opts = array( 'http' => array( 'method' => "POST", 'header' => "Content-Type: application/json\r\n" . "Accept: application/json\r\n" . "Authorization: Server-Token ${httpAuthToken}\r\n", 'content' => json_encode($data) ), "ssl" => array( "verify_peer"=>false, "verify_peer_name"=>false, ) ); $context = stream_context_create($opts); $body = file_get_contents($url, false, $context); $schedule = json_decode($body,true); if (is_array($schedule) && isset($schedule["id"])) { return array($schedule, NULL); } else if (is_array($schedule) && isset($schedule["message"])) { $body = $schedule["message"]; syslog(LOG_WARNING,isset($schedule["error"]) ? json_encode($schedule["error"]) : $schedule["message"]); } syslog(LOG_WARNING,$body); return array(NULL, $body); } }