src/Tracking.php
<?php
namespace Ups;
use DOMDocument;
use Exception;
use Psr\Log\LoggerInterface;
use SimpleXMLElement;
use stdClass;
use DateTime;
/**
* Tracking API Wrapper.
*/
class Tracking extends Ups
{
const ENDPOINT = '/Track';
/**
* @var RequestInterface
*/
private $request;
/**
*
* Workaround flag to handle Multiple shipment nodes in tracking response
* See GitHub Issue #117
*
* @todo: fix in next major release
*
* @var boolean
*/
protected $allowMultipleShipments = false;
/**
* @todo: make private
*
* @var ResponseInterface
*/
public $response;
/**
* @var string
*/
private $trackingNumber;
/**
* @var string
*/
private $referenceNumber;
/**
* @var string
*/
private $requestOption;
/**
* @var string
*/
private $shipperNumber;
/**
* @var \DateTime
*/
private $beginDate;
/**
* @var \DateTime
*/
private $endDate;
/**
* @param string|null $accessKey UPS License Access Key
* @param string|null $userId UPS User ID
* @param string|null $password UPS User Password
* @param bool $useIntegration Determine if we should use production or CIE URLs.
* @param RequestInterface|null $request
* @param LoggerInterface|null $logger PSR3 compatible logger (optional)
*/
public function __construct($accessKey = null, $userId = null, $password = null, $useIntegration = false, RequestInterface $request = null, LoggerInterface $logger = null)
{
if (null !== $request) {
$this->setRequest($request);
}
parent::__construct($accessKey, $userId, $password, $useIntegration, $logger);
}
/**
* Get package tracking information.
*
* @param string $trackingNumber The package's tracking number.
* @param string $requestOption Optional processing. For Mail Innovations the only valid options are Last Activity and All activity.
*
* @throws Exception
*
* @return stdClass
*/
public function track($trackingNumber, $requestOption = 'activity')
{
$this->trackingNumber = $trackingNumber;
$this->requestOption = $requestOption;
return $this->getFormattedResponse();
}
/**
* Get package tracking information.
*
* @param string $referenceNumber Reference numbers can be a purchase order number, job number, etc. Reference number can be added when creating a shipment.
* @param string $requestOption
*
* @throws Exception
*
* @return stdClass
*/
public function trackByReference($referenceNumber, $requestOption = 'activity')
{
$this->referenceNumber = $referenceNumber;
$this->requestOption = $requestOption;
return $this->getFormattedResponse();
}
/**
* Set shipper number
*
* @param string $shipperNumber
*
*/
public function setShipperNumber($shipperNumber)
{
$this->shipperNumber = $shipperNumber;
}
/**
* Set begin date
*
* @param DateTime $beginDate
*
*/
public function setBeginDate(DateTime $beginDate)
{
$this->beginDate = $beginDate;
}
/**
* Set end date
*
* @param DateTime $endDate
*
*/
public function setEndDate(DateTime $endDate)
{
$this->endDate = $endDate;
}
/**
* @return stdClass
* @throws Exception
*/
private function getFormattedResponse()
{
$this->response = $this->getRequest()->request(
$this->createAccess(),
$this->createRequest(),
$this->compileEndpointUrl(self::ENDPOINT)
);
$response = $this->response->getResponse();
if (null === $response) {
throw new Exception('Failure (0): Unknown error', 0);
}
if ($response instanceof SimpleXMLElement && $response->Response->ResponseStatusCode == 0) {
throw new Exception(
"Failure ({$response->Response->Error->ErrorSeverity}): {$response->Response->Error->ErrorDescription}",
(int)$response->Response->Error->ErrorCode
);
}
return $this->formatResponse($response);
}
/**
* Check if tracking number is for mail innovations.
*
* @return bool
*/
private function isMailInnovations()
{
$patterns = [
// UPS Mail Innovations tracking numbers
'/^MI\d{6}\d{1,22}$/',// MI 000000 00000000+
// USPS - Certified Mail
'/^94071\d{17}$/', // 9407 1000 0000 0000 0000 00
'/^7\d{19}$/', // 7000 0000 0000 0000 0000
// USPS - Collect on Delivery
'/^93033\d{17}$/', // 9303 3000 0000 0000 0000 00
'/^M\d{9}$/', // M000 0000 00
// USPS - Global Express Guaranteed
'/^82\d{10}$/', // 82 000 000 00
// USPS - Priority Mail Express International
'/^EC\d{9}US$/', // EC 000 000 000 US
// USPS Innovations Expedited
'/^927\d{23}$/', // 9270 8900 8900 8900 8900 8900 00
// USPS - Priority Mail Express
'/^927\d{19}$/', // 9270 1000 0000 0000 0000 00
'/^EA\d{9}US$/', // EA 000 000 000 US
// USPS - Priority Mail International
'/^CP\d{9}US$/', // CP 000 000 000 US
// USPS - Priority Mail
'/^92055\d{17}$/', // 9205 5000 0000 0000 0000 00
'/^14\d{18}$/', // 1400 0000 0000 0000 0000
// USPS - Registered Mail
'/^92088\d{17}$/', // 9208 8000 0000 0000 0000 00
'/^RA\d{9}US$/', // RA 000 000 000 US
// USPS - Signature Confirmation
'/^9202\d{16}US$/', // 9202 1000 0000 0000 0000 00
'/^23\d{16}US$/', // 2300 0000 0000 0000 0000
// USPS - Tracking
'/^94\d{20}$/', // 9400 1000 0000 0000 0000 00
'/^03\d{18}$/' // 0300 0000 0000 0000 0000
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $this->trackingNumber)) {
return true;
}
}
return false;
}
/**
* Create the Tracking request.
*
* @return string
*/
private function createRequest()
{
$xml = new DOMDocument();
$xml->formatOutput = true;
$trackRequest = $xml->appendChild($xml->createElement('TrackRequest'));
$trackRequest->setAttribute('xml:lang', 'en-US');
$request = $trackRequest->appendChild($xml->createElement('Request'));
$node = $xml->importNode($this->createTransactionNode(), true);
$request->appendChild($node);
$request->appendChild($xml->createElement('RequestAction', 'Track'));
if (null !== $this->requestOption) {
$request->appendChild($xml->createElement('RequestOption', $this->requestOption));
}
if (null !== $this->trackingNumber) {
$trackRequest->appendChild($xml->createElement('TrackingNumber', $this->trackingNumber));
}
if ($this->isMailInnovations()) {
$trackRequest->appendChild($xml->createElement('IncludeMailInnovationIndicator'));
}
if (null !== $this->referenceNumber) {
$trackRequest->appendChild($xml->createElement('ReferenceNumber'))->appendChild($xml->createElement('Value', $this->referenceNumber));
}
if (null !== $this->shipperNumber) {
$trackRequest->appendChild($xml->createElement('ShipperNumber', $this->shipperNumber));
}
if (null !== $this->beginDate || null !== $this->endDate) {
$DateRange = $xml->createElement('PickupDateRange');
if (null !== $this->beginDate) {
$beginDate = $this->beginDate->format('Ymd');
$DateRange->appendChild($xml->createElement('BeginDate', $beginDate));
}
if (null !== $this->endDate) {
$endDate = $this->endDate->format('Ymd');
$DateRange->appendChild($xml->createElement('EndDate', $endDate));
}
$trackRequest->appendChild($DateRange);
}
return $xml->saveXML();
}
/**
* Format the response.
*
* @param SimpleXMLElement $response
*
* @return stdClass
*/
private function formatResponse(SimpleXMLElement $response)
{
if ($this->allowMultipleShipments) {
$response = $this->convertXmlObject($response);
if (!is_array($response->Shipment)) {
$response->Shipment = [$response->Shipment];
}
return $response;
}
return $this->convertXmlObject($response->Shipment);
}
/**
* @return RequestInterface
*/
public function getRequest()
{
if (null === $this->request) {
$this->request = new Request($this->logger);
}
return $this->request;
}
/**
* @param RequestInterface $request
*
* @return $this
*/
public function setRequest(RequestInterface $request)
{
$this->request = $request;
return $this;
}
/**
* @return ResponseInterface
*/
public function getResponse()
{
return $this->response;
}
/**
* @param ResponseInterface $response
*
* @return $this
*/
public function setResponse(ResponseInterface $response)
{
$this->response = $response;
return $this;
}
/**
* @param bool $value
* @return $this
*/
public function allowMultipleShipments($value = true)
{
$this->allowMultipleShipments = $value ? true : false;
return $this;
}
}