state * so the API can return a more complete response. */ public const STATELESS_CITIES = [ "LOS ANGELES" => "CA", "SAN FRANCISCO" => "CA", "NEW YORK" => "NY", "CHICAGO" => "IL", "MIAMI" => "FL" ]; /** * * @param string $code * @return \TrackingInfo * @throws TrackingException */ public static function track(string $code, string $carrier = ""): TrackingInfo { $barcode = new TrackingBarcode($code); try { $track = new \SimpleXMLElement(""); $track->addAttribute('USERID', env("usps_user_id")); $track->addChild('Revision', '1'); $track->addChild('ClientIp', $_SERVER['REMOTE_ADDR']); $track->addChild('SourceId', env("usps_source_id", "selfhosted-opensource-default-value.netsyms.net")); $pack = $track->addChild('TrackID'); $pack->addAttribute('ID', $barcode->getSanitized()); $url = 'https://secure.shippingapis.com/ShippingApi.dll?API=TrackV2&XML=' . $track->asXML(); $xml = simplexml_load_file($url); if ($xml->getName() == "Error") { if (!empty($xml->Description)) { throw new TrackingException("The USPS tracking system is having problems: \"" . trim($xml->Description) . "\""); } throw new TrackingException("The USPS tracking system is having problems. Try again later."); } if (!empty($xml->TrackInfo)) { $trackinfo = $xml->TrackInfo; } if (!empty($xml->TrackInfo->Error)) { throw new TrackingException(str_replace("®", "®", (string) $xml->TrackInfo->Error->Description)); } } catch (TrackingException $ex) { throw $ex; } catch (Exception $ex) { throw new TrackingException("There was a server problem. This code cannot be tracked right now."); } $info = new TrackingInfo(); try { $info->setCode($trackinfo->attributes()["ID"]); } catch (Exception $ex) { throw new TrackingException("The USPS tracking system returned an invalid response. Try again later."); } $info->setCarrier("usps"); $info->setService(new Service((string) $trackinfo->ClassofMailCode, (string) $trackinfo->Class)); $info->setCarrierAttributionText(CarrierAssets::getAttribution(Carriers::getCarrierCode($info->getCarrier()))); $info->setCarrierLogo(CarrierAssets::getLogo(Carriers::getCarrierCode($info->getCarrier()))); $current_status = new TrackingEntry( TrackingStatus::USPSEventCodeToStatus($trackinfo->TrackSummary->EventCode), ($trackinfo->StatusSummary ?? "Unknown") . (TrackingStatus::USPSEventCodeToStatus($trackinfo->TrackSummary->EventCode) == TrackingStatus::TRACKING_STATUS_UNKNOWN ? " " . $trackinfo->TrackSummary->EventCode : ""), $trackinfo->TrackSummary->EventDate . " " . $trackinfo->TrackSummary->EventTime, null, TrackingStatus::isUSPSEventCodeContainerScan($trackinfo->TrackSummary->EventCode) ); $current_location = new Location(); $current_location->city = (string) $trackinfo->TrackSummary->EventCity ?? ""; $current_location->state = (string) $trackinfo->TrackSummary->EventState ?? ""; $current_location->zip = (string) $trackinfo->TrackSummary->EventZIPCode ?? ""; $current_location->country = (string) $trackinfo->TrackSummary->EventCountry ?? ""; /* * Fill in state from list above when it's missing from the API response */ if ($current_location->state == "" && $current_location->zip == "") { if (array_key_exists(strtoupper($current_location->city), self::STATELESS_CITIES)) { $current_location->state = self::STATELESS_CITIES[$current_location->city]; } } $current_status->setLocation($current_location); $info->setCurrentStatus($current_status); // USPS doesn't put the latest entry in the history $info->appendHistoryEntry($current_status); $from = new Location(); $from->city = (string) $trackinfo->OriginCity ?? ""; $from->state = (string) $trackinfo->OriginState ?? ""; $from->zip = (string) $trackinfo->OriginZip ?? ""; $from->country = (string) $trackinfo->OriginCountryCode ?? ""; $info->setFrom($from); $to = new Location(); $to->city = (string) $trackinfo->DestinationCity ?? ""; $to->state = (string) $trackinfo->DestinationState ?? ""; $to->zip = (string) $trackinfo->DestinationZip ?? ""; $to->country = (string) $trackinfo->DestinationCountryCode ?? ""; $info->setTo($to); for ($i = 0; $i < count($trackinfo->TrackDetail); $i++) { $history = $trackinfo->TrackDetail[$i]; $location = new Location(); $location->city = (string) $history->EventCity ?? ""; $location->state = (string) $history->EventState ?? ""; $location->zip = (string) $history->EventZIPCode ?? ""; $location->country = (string) $history->EventCountry ?? ""; /* * Fill in state from list above when it's missing from the API response */ if ($location->state == "" && $location->zip == "") { if (array_key_exists(strtoupper($location->city), self::STATELESS_CITIES)) { $location->state = self::STATELESS_CITIES[$location->city]; } } if ((empty($history->EventDate) || empty($history->EventTime)) && $i < count($trackinfo->TrackDetail) - 1) { // If there's no date/time for some reason (yes this happens sometimes apparently), // just make it 60 seconds after the previous event. // This way it'll be in the correct order if the events are displayed // after being sorted by date/time. // Because events are ordered latest first, we get $i+1 to get the previous event. $datetime = date("Y-m-d H:i:s", strtotime($trackinfo->TrackDetail[$i + 1]->EventDate . " " . $trackinfo->TrackDetail[$i + 1]->EventTime) + 60); } else { $datetime = $history->EventDate . " " . $history->EventTime; } $info->appendHistoryEntry(new TrackingEntry( TrackingStatus::USPSEventCodeToStatus((string) $history->EventCode), ((string) $history->Event) . (TrackingStatus::USPSEventCodeToStatus((string) $history->EventCode) == TrackingStatus::TRACKING_STATUS_UNKNOWN ? " " . (string) $history->EventCode : ""), $datetime, $location, TrackingStatus::isUSPSEventCodeContainerScan((string) $history->EventCode))); } return $info; } }