requestParams["data"] ?? []; error_log(json_encode( $this->requestParams)); try { if (!is_array($data) || count($data)<1) { throw new Exception('Data not found'); } $db = new Db(); foreach ($data as $item) { $id = $item['quote_group_id']; if ($id<1) { $item['quote_group_error'] = 'Missing or invalid quote group ID'; $item['quote_group'] = NULL; $data[$key] = $item; continue; } list($message,$code,$action,$data) = self::viewReal($db, $id); if ($code==200) { $item['quote_group_error'] = NULL; $item['quote_group'] = $data; } else { $item['quote_group_error'] = $message; $item['quote_group'] = NULL; } $result[] = $item; } return $this->response($result, 200); } catch (Exception $e) { $message = $e->getMessage(); error_log('EXCEPTION: '.$message); } return $this->response( array( 'error' => $message ), 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); $db = new Db(); list($message,$code,$action,$data) = self::viewReal($db, $id); if ($code==200) { return $this->response($data, 200); } return $this->response( array( 'error'=> $message ), $code); } public static function viewReal($db, $id) { error_log('QuoteGroupApi::viewReal()'); $message = 'Data not found'; $code = 404; $action = ''; $data = []; try { if(!$id || (int)$id<1) { throw new Exception('Invalid quote group ID'); } $quoteGroup = QuoteGroup::getById($db->getConnect(), (int)$id); if(!is_array($quoteGroup) || count($quoteGroup)<1) { throw new Exception('Quote group was not found'); } // Check "completed" if ($quoteGroup["completed"]!="" && (int)$quoteGroup["cost"]!=0) { error_log('Quote group #'.$id.' was completed with the cost '.$quoteGroup["cost"]); list($quotes,$err) = Quotes::getByQuoteGroupId($db->getConnect(), (int)$id); if ((!is_array($quotes) || count($quotes)<1) && $quoteGroup["quote_id"]>0) { $quotes = []; array_push($quotes, Quotes::getById($db->getConnect(), $quoteGroup["quote_id"])); } $quoteGroup["quotes"] = $quotes; // Does not matter much if we have "cost" $quoteGroup["deeplink"] = Quotes::deeplink($db->getConnect(), $quoteGroup); return ["OK",200,"",$quoteGroup]; // OK! } else { error_log('Quote group #'.$id.' was completed yet...'); } // Load quotes list($quotes,$err) = Quotes::getByQuoteGroupId($db->getConnect(), (int)$id); if (!is_array($quotes) || count($quotes)<1) { if ($quoteGroup["quote_id"]>0) { $quotes = []; array_push($quotes, Quotes::getById($db->getConnect(), $quoteGroup["quote_id"])); error_log(json_encode($quotes)); } else { throw new Exception ($err!=''?$err:'Failed to load quotes'); } } else { error_log(json_encode($quotes)); } // Check each quote $completed = 0; $lowestQuote = NULL; $cost = PHP_INT_MAX; foreach ($quotes as $quote) { for ($i=0; $i<3; $i++) { if ($quote["completed"]=="" && $quote["travel_date"]=="" && $quote["automation_id"]>0) { if ($i>0) { sleep(3); // wait... } // Call service if needed list($quoteService, $err) = QuoteAPI::checkQuote($quote["automation_id"]); // Update if (is_array($quoteService) && $quoteService["complete"]!="") { $quoteService["automation_id"] = $quoteService["id"]; $quoteService["id"] = (int)$quote["id"]; $quoteService["completed"] = $quoteService["complete"]; $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"] = Quotes::deeplink($db->getConnect(), $quote); list ($res, $err) = Quotes::update($db->getConnect(), $quoteService); if ($quoteService["cost"]==0 && $quoteService["message"]=="android_automation_job_detail") { // We must take the details $quoteService = QuoteApi::quoteDetails($db, $quoteService); } $quote = $quoteService; } } else { break; } } // Check "completed" if ($quote["completed"]!="" && $quote["travel_date"]!="" && (int)$quote["cost"]!=0) { error_log('Completed!'); $completed++; if ($quote["cost"] < $cost) { $cost = $quote["cost"]; $lowestQuote = $quote; } } } $quoteGroup["cost"] = $cost==PHP_INT_MAX ? NULL : $cost; $quoteGroup["transport_provider_id"] = is_array($lowestQuote) ? $lowestQuote["transport_provider_id"] : NULL; $quoteGroup["quote_id"] = is_array($lowestQuote) ? $lowestQuote["id"] : NULL; $quoteGroup["processed"] = $completed; // All completed? if ($completed>=$quoteGroup["transport_providers"]) { // All quotes completed - hurray! $quoteGroup["completed"] = date("Y-m-d H:i:s"); } else if (is_array($lowestQuote)) { // We will update since we have at least one quote completed $quoteGroup["completed"] = NULL; } else { // Do not update, as nothing has completed $quoteGroup["quotes"] = $quotes; return ["OK",200,"",$quoteGroup]; } list($res,$err) = QuoteGroup::update($db->getConnect(), $quoteGroup); if ($res==NULL || !array_key_exists("id",$res)) { error_log('QuoteGroup::update() failed?'); error_log(json_encode($res)); $res = $quoteGroup; } $res["quotes"] = $quotes; if (isset($res["transport_provider_id"]) && $res["transport_provider_id"]>0) { $res["deeplink"] = Quotes::deeplink($db->getConnect(), $res); } error_log(json_encode($res)); return ["OK",200,"",$res]; } catch (Exception $e) { $code = 500; $message = $e->getMessage(); error_log('EXCEPTION: '.$message); } return [$message,$code,$action,$data]; } public function 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_providers = $this->requestParams["transport_providers"] ?? []; $db = new Db(); // DEBUG $country = Geocode::mockGPSCountry($db->getConnect(), $member_id, $country, 'default'); list($message,$code,$action,$data) = QuoteGroupApi::createReal( $db, $origin, $destination, $member_id, $transport_providers, $country ); 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 static function createReal($db, $origin, $destination, $member_id, $transport_providers, $country=GeocodeApi::DEFAULT_COUNTRY_CODE) { syslog(LOG_WARNING,"QuoteGroupApi::createReal(\$db, $origin, $destination, $member_id, \$transport_providers, $country)"); $message = "Failed to schedule quotes"; $code = 500; $action = ""; $data = []; $city = ''; try { foreach ($transport_providers as $key=>$transport_provider_id) { if (!in_array($transport_provider_id, [1,2,3,4,5,8])) { unset($transport_providers[$key]); } if (in_array($transport_provider_id, [3,4,5])) { $city = 'Singapore'; } if (in_array($transport_provider_id, [1,2,8])) { $city = 'Other'; } } if (count($transport_providers)<1) { throw new Exception('Unsupported transport provider IDs'); } if (!$destination || !$origin) { throw new Exception('Invalid origin and/or destination address'); } // Check if exists list($total, $quoteGroups) = QuoteGroup::getByOriginDestinationId( $db->getConnect(), $origin, $destination, 0 /* transport_provider_id */, 1, 0); // LIMIT # OFFSET # if (is_array($quoteGroups) && count($quoteGroups)>0) { $quoteGroup = $quoteGroups[0]; $id = $quoteGroup["id"]; // Will return the last quote $completed = $quoteGroup["completed"]; // Completed with the SELF::CACHE_TIMEOUT if (time()-strtotime($completed)$id]]; } } // Geocode origin and destination $headers = getallheaders(); $client_id = isset($headers['client_id'])?$headers['client_id']:"Unknown"; list($originData, $err) = GeocodeAPI::geocode($db, $origin, $country,$client_id); $log = [ 'message' => 'QuoteGroupApi::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); list($destinationData, $err) = GeocodeAPI::geocode($db, $destination, $country, $client_id); $log = [ 'message' => 'QuoteGroupApi::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); $isOriginWithin = false; $isDestinationWithin = false; // Check if we are within Singapore: for Grab, Gojek, CDG if ($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: no check required if ($city=='Other') { $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 the service area!"); } // Create quote group $requestData = [ "location_start_lat" => $originData["lat"], "location_start_lng" => $originData["lng"], "location_start" => $originData["address"] ?? $origin, "location_start_country" => $originData["country"], "location_end_lat" => $destinationData["lat"], "location_end_lng" => $destinationData["lng"], "location_end" => $destinationData["address"] ?? $destination, "location_end_country" => $destinationData["country"], "transport_providers" => $transport_providers ]; list($quoteGroup,$err) = QuoteGroup::create($db->getConnect(), $requestData); if ($quoteGroup==NULL || !isset($quoteGroup["id"])) { throw new Exception("Quote group creation failed: " . ($err ?? $message)); } $id = $quoteGroup["id"]; syslog(LOG_WARNING,'New QuoteGroup ID: '.$id); // Schedule the quotes //$quoteApi = new QuoteApi($this->requestUri, false /* no encryption for internal call */); $quotes = []; foreach ($transport_providers as $transport_provider_id) { //$quoteApi->requestParams["transport_provider_id"] = $transport_provider_id; list($message,$code,$action,$data) = QuoteApi::createReal( $db, $origin, $destination, $member_id, $transport_provider_id, $id, 0, $country, 'f' ); $quote_id = 0; if ($code==200 && ($action=="view" || $action=="response")) { $quote_id = $data["id"]; $quotes[] = $quote_id; } else { syslog(LOG_WARNING,'ERROR: '.$message); } syslog(LOG_WARNING,'QUOTE: '.json_encode($data)); } $quoteGroup["cost"] = NULL; $quoteGroup["transport_provider_id"] = count($quotes)==1 ? $transport_provider_id : NULL; $quoteGroup["quote_id"] = count($quotes)==1 ? $quotes[0] : NULL; $quoteGroup["completed"] = NULL; if (count($quotes)<1) { $quoteGroup["completed"] = date("Y-m-d H:i:s"); $quoteGroup["processed"] = 0; } list($res,$err) = QuoteGroup::update($db->getConnect(), $quoteGroup); if ($res==NULL || !isset($res["id"])) { $res = $quoteGroup; } $res["quotes"] = $quotes; return ["Group quote scheduled",200,"response",$res]; } catch (Exception $e) { syslog(LOG_WARNING,json_encode($e)); $message = $e->getMessage(); syslog(LOG_WARNING,'EXCEPTION: '.$message); } return [$message,$code,$action,$data]; } public function updateAction() { return $this->response( array( "error" => "Update error" ), 400); } public function deleteAction() { return $this->response( array( "error" => "Delete error" ), 500); } }