![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/magento/module-dhl/Model/ |
<?php /** * Copyright Ā© Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Dhl\Model; use Exception; use Laminas\Http\Request as HttpRequest; use Magento\Catalog\Model\Product\Type; use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\Dhl\Model\Validator\XmlValidator; use Magento\Directory\Helper\Data; use Magento\Directory\Model\CountryFactory; use Magento\Directory\Model\Currency; use Magento\Directory\Model\CurrencyFactory; use Magento\Directory\Model\RegionFactory; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ProductMetadataInterface; use Magento\Framework\Async\CallbackDeferred; use Magento\Framework\DataObject; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filesystem\Directory\ReadFactory; use Magento\Framework\HTTP\AsyncClient\HttpException; use Magento\Framework\HTTP\AsyncClient\HttpResponseDeferredInterface; use Magento\Framework\HTTP\AsyncClient\Request; use Magento\Framework\HTTP\AsyncClientInterface; use Magento\Framework\HTTP\LaminasClient; use Magento\Framework\HTTP\LaminasClientFactory; use Magento\Framework\Math\Division; use Magento\Framework\Measure\Length; use Magento\Framework\Measure\Weight; use Magento\Framework\Model\AbstractModel; use Magento\Framework\Module\Dir; use Magento\Framework\Module\Dir\Reader; use Magento\Framework\Stdlib\DateTime; use Magento\Framework\Stdlib\StringUtils; use Magento\Framework\Xml\Security; use Magento\Quote\Model\Quote\Address\RateRequest; use Magento\Quote\Model\Quote\Address\RateResult\Error; use Magento\Quote\Model\Quote\Address\RateResult\Method; use Magento\Quote\Model\Quote\Address\RateResult\MethodFactory; use Magento\Sales\Exception\DocumentValidationException; use Magento\Sales\Model\Order\Shipment; use Magento\Shipping\Model\Carrier\AbstractCarrier; use Magento\Shipping\Model\Carrier\CarrierInterface; use Magento\Shipping\Model\Rate\Result; use Magento\Shipping\Model\Rate\Result\ProxyDeferredFactory; use Magento\Shipping\Model\Shipment\Request as ShipmentRequest; use Magento\Shipping\Model\Simplexml\Element; use Magento\Shipping\Model\Simplexml\ElementFactory; use Magento\Shipping\Model\Tracking\Result\ErrorFactory; use Magento\Shipping\Model\Tracking\Result\StatusFactory; use Magento\Shipping\Model\Tracking\ResultFactory; use Magento\Store\Model\Information; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; use Psr\Log\LoggerInterface; use SimpleXMLElement; use Throwable; use const DATE_RFC3339; /** * DHL International (API v1.4) * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Carrier extends AbstractDhl implements CarrierInterface { /**#@+ * Carrier Product indicator */ public const DHL_CONTENT_TYPE_DOC = 'D'; public const DHL_CONTENT_TYPE_NON_DOC = 'N'; /**#@-*/ /**#@+ * Minimum allowed values for shipping package dimensions */ public const DIMENSION_MIN_CM = 3; public const DIMENSION_MIN_IN = 1; /**#@-*/ /** * Config path to UE country list */ public const XML_PATH_EU_COUNTRIES_LIST = 'general/country/eu_countries'; /** * Container types that could be customized * * @var string[] */ protected $_customizableContainerTypes = [self::DHL_CONTENT_TYPE_NON_DOC, self::DHL_CONTENT_TYPE_DOC]; /** * Code of the carrier */ public const CODE = 'dhl'; /** * DHL service prefixes used for message reference */ private const SERVICE_PREFIX_QUOTE = 'QUOT'; private const SERVICE_PREFIX_SHIPVAL = 'SHIP'; private const SERVICE_PREFIX_TRACKING = 'TRCK'; /** * Rate request data * * @var RateRequest|null */ protected $_request; /** * Rate result data * * @var Result|null */ protected $_result; /** * Countries parameters data * * @var Element|null */ protected $_countryParams; /** * Errors placeholder * * @var string[] */ protected $_errors = []; /** * Dhl rates result * * @var array */ protected $_rates = []; /** * Carrier's code * * @var string */ protected $_code = self::CODE; /** * Free Method config path * * @var string */ protected $_freeMethod = 'free_method_nondoc'; /** * Max weight without fee * * @var int */ protected $_maxWeight = 70; /** * Flag if response is for shipping label creating * * @var bool */ protected $_isShippingLabelFlag = false; /** * Request variables array * * @var array */ protected $_requestVariables = [ 'id' => ['code' => 'dhl_id', 'setCode' => 'id'], 'password' => ['code' => 'dhl_password', 'setCode' => 'password'], 'account' => ['code' => 'dhl_account', 'setCode' => 'account_nbr'], 'shipping_key' => ['code' => 'dhl_shipping_key', 'setCode' => 'shipping_key'], 'shipping_intlkey' => ['code' => 'dhl_shipping_intl_key', 'setCode' => 'shipping_intl_key'], 'shipment_type' => ['code' => 'dhl_shipment_type', 'setCode' => 'shipment_type'], 'dutiable' => ['code' => 'dhl_dutiable', 'setCode' => 'dutiable'], 'dutypaymenttype' => ['code' => 'dhl_duty_payment_type', 'setCode' => 'duty_payment_type'], 'contentdesc' => ['code' => 'dhl_content_desc', 'setCode' => 'content_desc'], ]; /** * Flag that shows if shipping is domestic * * @var bool */ protected $_isDomestic = false; /** * Core string * * @var StringUtils */ protected $string; /** * @var \Magento\Shipping\Helper\Carrier */ protected $_carrierHelper; /** * @var \Magento\Framework\Stdlib\DateTime\DateTime */ protected $_coreDate; /** * @var StoreManagerInterface */ protected $_storeManager; /** * @var Reader */ protected $_configReader; /** * @var Division */ protected $mathDivision; /** * @var ReadFactory */ protected $readFactory; /** * @var DateTime */ protected $_dateTime; /** * @var LaminasClientFactory * phpcs:ignore Magento2.Commenting.ClassAndInterfacePHPDocFormatting * @deprecated Use asynchronous client. * @see $httpClient */ protected $_httpClientFactory; /** * @var string[] */ protected $_debugReplacePrivateDataKeys = [ 'SiteID', 'Password' ]; /** * Xml response validator * * @var XmlValidator */ private $xmlValidator; /** * @var ProductMetadataInterface */ private $productMetadata; /** * @var AsyncClientInterface */ private $httpClient; /** * @var ProxyDeferredFactory */ private $proxyDeferredFactory; /** * @param ScopeConfigInterface $scopeConfig * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory * @param LoggerInterface $logger * @param Security $xmlSecurity * @param ElementFactory $xmlElFactory * @param \Magento\Shipping\Model\Rate\ResultFactory $rateFactory * @param MethodFactory $rateMethodFactory * @param ResultFactory $trackFactory * @param ErrorFactory $trackErrorFactory * @param StatusFactory $trackStatusFactory * @param RegionFactory $regionFactory * @param CountryFactory $countryFactory * @param CurrencyFactory $currencyFactory * @param Data $directoryData * @param StockRegistryInterface $stockRegistry * @param \Magento\Shipping\Helper\Carrier $carrierHelper * @param \Magento\Framework\Stdlib\DateTime\DateTime $coreDate * @param Reader $configReader * @param StoreManagerInterface $storeManager * @param StringUtils $string * @param Division $mathDivision * @param ReadFactory $readFactory * @param DateTime $dateTime * @param LaminasClientFactory $httpClientFactory * @param array $data * @param XmlValidator|null $xmlValidator * @param ProductMetadataInterface|null $productMetadata * @param AsyncClientInterface|null $httpClient * @param ProxyDeferredFactory|null $proxyDeferredFactory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( ScopeConfigInterface $scopeConfig, \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory, LoggerInterface $logger, Security $xmlSecurity, ElementFactory $xmlElFactory, \Magento\Shipping\Model\Rate\ResultFactory $rateFactory, MethodFactory $rateMethodFactory, ResultFactory $trackFactory, ErrorFactory $trackErrorFactory, StatusFactory $trackStatusFactory, RegionFactory $regionFactory, CountryFactory $countryFactory, CurrencyFactory $currencyFactory, Data $directoryData, StockRegistryInterface $stockRegistry, \Magento\Shipping\Helper\Carrier $carrierHelper, \Magento\Framework\Stdlib\DateTime\DateTime $coreDate, Reader $configReader, StoreManagerInterface $storeManager, StringUtils $string, Division $mathDivision, ReadFactory $readFactory, DateTime $dateTime, LaminasClientFactory $httpClientFactory, array $data = [], XmlValidator $xmlValidator = null, ProductMetadataInterface $productMetadata = null, ?AsyncClientInterface $httpClient = null, ?ProxyDeferredFactory $proxyDeferredFactory = null ) { $this->readFactory = $readFactory; $this->_carrierHelper = $carrierHelper; $this->_coreDate = $coreDate; $this->_storeManager = $storeManager; $this->_configReader = $configReader; $this->string = $string; $this->mathDivision = $mathDivision; $this->_dateTime = $dateTime; $this->_httpClientFactory = $httpClientFactory; parent::__construct( $scopeConfig, $rateErrorFactory, $logger, $xmlSecurity, $xmlElFactory, $rateFactory, $rateMethodFactory, $trackFactory, $trackErrorFactory, $trackStatusFactory, $regionFactory, $countryFactory, $currencyFactory, $directoryData, $stockRegistry, $data ); if ($this->getConfigData('content_type') == self::DHL_CONTENT_TYPE_DOC) { $this->_freeMethod = 'free_method_doc'; } $this->xmlValidator = $xmlValidator ?? ObjectManager::getInstance()->get(XmlValidator::class); $this->productMetadata = $productMetadata ?? ObjectManager::getInstance()->get(ProductMetadataInterface::class); $this->httpClient = $httpClient ?? ObjectManager::getInstance()->get(AsyncClientInterface::class); $this->proxyDeferredFactory = $proxyDeferredFactory ?? ObjectManager::getInstance()->get(ProxyDeferredFactory::class); } /** * Returns value of given variable * * @param string|int $origValue * @param string $pathToValue * @return string|int|null */ protected function _getDefaultValue($origValue, $pathToValue) { if (!$origValue) { $origValue = $this->_scopeConfig->getValue( $pathToValue, ScopeInterface::SCOPE_STORE, $this->getStore() ); } return $origValue; } /** * Collect and get rates * * @param RateRequest $request * @return bool|Result|Error */ public function collectRates(RateRequest $request) { if (!$this->canCollectRates()) { return $this->getErrorMessage(); } $requestDhl = clone $request; $this->setStore($requestDhl->getStoreId()); $origCompanyName = $this->_getDefaultValue( $requestDhl->getOrigCompanyName(), Information::XML_PATH_STORE_INFO_NAME ); $origCountryId = $this->_getDefaultValue($requestDhl->getOrigCountryId(), Shipment::XML_PATH_STORE_COUNTRY_ID); $origState = $this->_getDefaultValue($requestDhl->getOrigState(), Shipment::XML_PATH_STORE_REGION_ID); $origCity = $this->_getDefaultValue($requestDhl->getOrigCity(), Shipment::XML_PATH_STORE_CITY); $origPostcode = $this->_getDefaultValue($requestDhl->getOrigPostcode(), Shipment::XML_PATH_STORE_ZIP); $requestDhl->setOrigCompanyName($origCompanyName) ->setCountryId($origCountryId) ->setOrigState($origState) ->setOrigCity($origCity) ->setOrigPostal($origPostcode); $this->setRequest($requestDhl); //Loading quotes //Saving $result to use proper result with the callback $this->_result = $result = $this->_getQuotes(); //After quotes are loaded parsing the response. return $this->proxyDeferredFactory->create( [ 'deferred' => new CallbackDeferred( function () use ($request, $result) { $this->_result = $result; $this->_updateFreeMethodQuote($request); return $this->_result; } ) ] ); } /** * Set Free Method Request * * @param string $freeMethod * @return void */ protected function _setFreeMethodRequest($freeMethod) { $this->_rawRequest->setFreeMethodRequest(true); $freeWeight = $this->getTotalNumOfBoxes($this->_rawRequest->getFreeMethodWeight()); $this->_rawRequest->setWeight($freeWeight); $this->_rawRequest->setService($freeMethod); } /** * Returns request result * * @return Result|null */ public function getResult() { return $this->_result; } /** * Fills request object with Dhl config parameters * * @param DataObject $requestObject * @return DataObject */ protected function _addParams(DataObject $requestObject) { foreach ($this->_requestVariables as $code => $objectCode) { if ($this->_request->getDhlId()) { $value = $this->_request->getData($objectCode['code']); } else { $value = $this->getConfigData($code); } $requestObject->setData($objectCode['setCode'], $value); } return $requestObject; } /** * Prepare and set request in property of current instance * * @param DataObject $request * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ public function setRequest(DataObject $request) { $this->_request = $request; $this->setStore($request->getStoreId()); $requestObject = new DataObject(); $requestObject->setIsGenerateLabelReturn($request->getIsGenerateLabelReturn()); $requestObject->setStoreId($request->getStoreId()); if ($request->getLimitMethod()) { $requestObject->setService($request->getLimitMethod()); } $requestObject = $this->_addParams($requestObject); if ($request->getDestPostcode()) { $requestObject->setDestPostal($request->getDestPostcode()); } $requestObject->setOrigCountry( $this->_getDefaultValue($request->getOrigCountry(), Shipment::XML_PATH_STORE_COUNTRY_ID) )->setOrigCountryId( $this->_getDefaultValue($request->getOrigCountryId(), Shipment::XML_PATH_STORE_COUNTRY_ID) ); $shippingWeight = $request->getPackageWeight(); $destStreet = $request->getDestStreet() !== null ? str_replace("\n", '', $request->getDestStreet()) : ''; $requestObject->setValue(sprintf('%.2f', $request->getPackageValue())) ->setValueWithDiscount($request->getPackageValueWithDiscount()) ->setCustomsValue($request->getPackageCustomsValue()) ->setDestStreet($this->string->substr($destStreet, 0, 35)) ->setDestStreetLine2($request->getDestStreetLine2()) ->setDestCity($request->getDestCity()) ->setOrigCompanyName($request->getOrigCompanyName()) ->setOrigCity($request->getOrigCity()) ->setOrigPhoneNumber($request->getOrigPhoneNumber()) ->setOrigPersonName($request->getOrigPersonName()) ->setOrigEmail( $this->_scopeConfig->getValue( 'trans_email/ident_general/email', ScopeInterface::SCOPE_STORE, $requestObject->getStoreId() ) ) ->setOrigCity($request->getOrigCity()) ->setOrigPostal($request->getOrigPostal()) ->setOrigStreetLine2($request->getOrigStreetLine2()) ->setDestPhoneNumber($request->getDestPhoneNumber()) ->setDestPersonName($request->getDestPersonName()) ->setDestCompanyName($request->getDestCompanyName()); $originStreet2 = $this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_ADDRESS2, ScopeInterface::SCOPE_STORE, $requestObject->getStoreId() ); $requestObject->setOrigStreet($request->getOrigStreet() ? $request->getOrigStreet() : $originStreet2); if (is_numeric($request->getOrigState())) { $requestObject->setOrigState($this->_regionFactory->create()->load($request->getOrigState())->getCode()); } else { $requestObject->setOrigState($request->getOrigState()); } if ($request->getDestCountryId()) { $destCountry = $request->getDestCountryId(); } else { $destCountry = self::USA_COUNTRY_ID; } // for DHL, Puerto Rico state for US will assume as Puerto Rico country // for Puerto Rico, dhl will ship as international if ($destCountry == self::USA_COUNTRY_ID && ($request->getDestPostcode() == '00912' || $request->getDestRegionCode() == self::PUERTORICO_COUNTRY_ID) ) { $destCountry = self::PUERTORICO_COUNTRY_ID; } $requestObject->setDestCountryId($destCountry) ->setDestState($request->getDestRegionCode()) ->setWeight($shippingWeight) ->setFreeMethodWeight($request->getFreeMethodWeight()) ->setOrderShipment($request->getOrderShipment()); if ($request->getPackageId()) { $requestObject->setPackageId($request->getPackageId()); } $requestObject->setBaseSubtotalInclTax($request->getBaseSubtotalInclTax()); $this->setRawRequest($requestObject); return $this; } /** * Get allowed shipping methods * * @return string[] * @throws LocalizedException */ public function getAllowedMethods() { $contentType = $this->getConfigData('content_type'); if ($this->_isDomestic) { $allowedMethods = array_merge( explode(',', $this->getConfigData('doc_methods') ?? ''), explode(',', $this->getConfigData('nondoc_methods') ?? '') ); } else { switch ($contentType) { case self::DHL_CONTENT_TYPE_DOC: $allowedMethods = explode(',', $this->getConfigData('doc_methods') ?? ''); break; case self::DHL_CONTENT_TYPE_NON_DOC: $allowedMethods = explode(',', $this->getConfigData('nondoc_methods') ?? ''); break; default: throw new LocalizedException(__('Wrong Content Type')); } } $methods = []; foreach ($allowedMethods as $method) { $methods[$method] = $this->getDhlProductTitle($method); } return $methods; } /** * Get configuration data of carrier * * @param string $type * @param string $code * @return array|bool */ public function getCode($type, $code = '') { $codes = [ 'unit_of_measure' => ['L' => __('Pounds'), 'K' => __('Kilograms')], 'unit_of_dimension' => ['I' => __('Inches'), 'C' => __('Centimeters')], 'unit_of_dimension_cut' => ['I' => __('inch'), 'C' => __('cm')], 'dimensions' => ['HEIGHT' => __('Height'), 'DEPTH' => __('Depth'), 'WIDTH' => __('Width')], 'size' => ['0' => __('Regular'), '1' => __('Specific')], 'dimensions_variables' => [ 'L' => Weight::POUND, 'LB' => Weight::POUND, 'POUND' => Weight::POUND, 'K' => Weight::KILOGRAM, 'KG' => Weight::KILOGRAM, 'KILOGRAM' => Weight::KILOGRAM, 'I' => Length::INCH, 'IN' => Length::INCH, 'INCH' => Length::INCH, 'C' => Length::CENTIMETER, 'CM' => Length::CENTIMETER, 'CENTIMETER' => Length::CENTIMETER, ], ]; if (!isset($codes[$type])) { return false; } elseif ('' === $code) { return $codes[$type]; } $code = strtoupper($code); if (!isset($codes[$type][$code])) { return false; } else { return $codes[$type][$code]; } } /** * Returns DHL shipment methods (depending on package content type, if necessary) * * @param string $doc Package content type (doc/non-doc) see DHL_CONTENT_TYPE_* constants * @return array */ public function getDhlProducts($doc) { $docType = [ '2' => __('Easy shop'), '5' => __('Sprintline'), '6' => __('Secureline'), '7' => __('Express easy'), '9' => __('Europack'), 'B' => __('Break bulk express'), 'C' => __('Medical express'), 'D' => __('Express worldwide'), 'U' => __('Express worldwide'), 'K' => __('Express 9:00'), 'L' => __('Express 10:30'), 'G' => __('Domestic economy select'), 'W' => __('Economy select'), 'I' => __('Domestic express 9:00'), 'N' => __('Domestic express'), 'O' => __('Others'), 'R' => __('Globalmail business'), 'S' => __('Same day'), 'T' => __('Express 12:00'), 'X' => __('Express envelope'), ]; $nonDocType = [ '1' => __('Domestic express 12:00'), '3' => __('Easy shop'), '4' => __('Jetline'), '8' => __('Express easy'), 'P' => __('Express worldwide'), 'Q' => __('Medical express'), 'E' => __('Express 9:00'), 'F' => __('Freight worldwide'), 'H' => __('Economy select'), 'J' => __('Jumbo box'), 'M' => __('Express 10:30'), 'N' => __('Domestic express'), 'V' => __('Europack'), 'Y' => __('Express 12:00'), ]; if ($this->_isDomestic) { return $docType + $nonDocType; } if ($doc == self::DHL_CONTENT_TYPE_DOC) { // Documents shipping return $docType; } else { // Services for shipping non-documents cargo return $nonDocType; } } /** * Returns title of DHL shipping method by its code * * @param string $code One-symbol code (see getDhlProducts()) * @return bool * @SuppressWarnings(PHPMD.BooleanGetMethodName) */ public function getDhlProductTitle($code) { $contentType = $this->getConfigData('content_type'); $dhlProducts = $this->getDhlProducts($contentType); return $dhlProducts[$code] ?? false; } /** * Convert item weight to needed weight based on config weight unit dimensions * * @param float $weight * @param bool $maxWeight * @param string|bool $configWeightUnit * @return float */ protected function _getWeight($weight, $maxWeight = false, $configWeightUnit = false) { if ($maxWeight) { $configWeightUnit = Weight::KILOGRAM; } elseif ($configWeightUnit) { $configWeightUnit = $this->getCode('dimensions_variables', $configWeightUnit); } else { $configWeightUnit = $this->getCode( 'dimensions_variables', (string)$this->getConfigData('unit_of_measure') ); } $countryWeightUnit = $this->getCode('dimensions_variables', $this->_getWeightUnit()); if ($configWeightUnit != $countryWeightUnit) { $weight = $this->_carrierHelper->convertMeasureWeight( (float)$weight, $configWeightUnit, $countryWeightUnit ); } return sprintf('%.3f', $weight); } /** * Prepare items to pieces * * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * phpcs:disable Generic.Metrics.NestingLevel */ protected function _getAllItems() { $allItems = $this->_request->getAllItems(); $fullItems = []; foreach ($allItems as $item) { if ($item->getProductType() == Type::TYPE_BUNDLE && $item->getProduct()->getShipmentType()) { continue; } $qty = $item->getQty(); $changeQty = true; $checkWeight = true; $decimalItems = []; if ($item->getParentItem()) { if (!$item->getParentItem()->getProduct()->getShipmentType()) { continue; } if ($item->getIsQtyDecimal()) { $qty = $item->getParentItem()->getQty(); } else { $qty = $item->getParentItem()->getQty() * $item->getQty(); } } $itemWeight = $item->getWeight(); if ($item->getIsQtyDecimal() && $item->getProductType() != Type::TYPE_BUNDLE) { $productId = $item->getProduct()->getId(); $stockItemDo = $this->stockRegistry->getStockItem($productId, $item->getStore()->getWebsiteId()); $isDecimalDivided = $stockItemDo->getIsDecimalDivided(); if ($isDecimalDivided) { if ($stockItemDo->getEnableQtyIncrements() && $stockItemDo->getQtyIncrements() ) { $itemWeight = $itemWeight * $stockItemDo->getQtyIncrements(); $qty = round($item->getWeight() / $itemWeight * $qty); $changeQty = false; } else { $itemWeight = $this->_getWeight($itemWeight * $item->getQty()); $maxWeight = $this->_getWeight($this->_maxWeight, true); if ($itemWeight > $maxWeight) { $qtyItem = floor($itemWeight / $maxWeight); $decimalItems[] = ['weight' => $maxWeight, 'qty' => $qtyItem]; $weightItem = $this->mathDivision->getExactDivision($itemWeight, $maxWeight); if ($weightItem) { $decimalItems[] = ['weight' => $weightItem, 'qty' => 1]; } $checkWeight = false; } } } else { $itemWeight = $itemWeight * $item->getQty(); } } if ($checkWeight && $this->_getWeight($itemWeight) > $this->_getWeight($this->_maxWeight, true)) { return []; } if ($changeQty && !$item->getParentItem() && $item->getIsQtyDecimal() && $item->getProductType() != Type::TYPE_BUNDLE ) { $qty = 1; } if (!empty($decimalItems)) { foreach ($decimalItems as $decimalItem) { $fullItems[] = array_fill(0, $decimalItem['qty'] * $qty, $decimalItem['weight']); } } else { $fullItems[] = array_fill(0, $qty, $this->_getWeight($itemWeight)); } } $fullItems = array_merge([], ...$fullItems); sort($fullItems); return $fullItems; } //phpcs:enable /** * Make pieces * * @param Element $nodeBkgDetails * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _makePieces(Element $nodeBkgDetails) { $divideOrderWeight = (string)$this->getConfigData('divide_order_weight'); $nodePieces = $nodeBkgDetails->addChild('Pieces', '', ''); $items = $this->_getAllItems(); $numberOfPieces = 0; if ($divideOrderWeight && !empty($items)) { $maxWeight = $this->_getWeight($this->_maxWeight, true); $sumWeight = 0; $reverseOrderItems = $items; arsort($reverseOrderItems); foreach ($reverseOrderItems as $key => $weight) { if (!isset($items[$key])) { continue; } unset($items[$key]); $sumWeight = $weight; foreach ($items as $key => $weight) { if ($sumWeight + $weight < $maxWeight) { unset($items[$key]); $sumWeight += $weight; } elseif ($sumWeight + $weight > $maxWeight) { $numberOfPieces++; $nodePiece = $nodePieces->addChild('Piece', '', ''); $nodePiece->addChild('PieceID', $numberOfPieces); $this->_addDimension($nodePiece); $nodePiece->addChild('Weight', sprintf('%.3f', $sumWeight)); break; } else { unset($items[$key]); $numberOfPieces++; $sumWeight += $weight; $nodePiece = $nodePieces->addChild('Piece', '', ''); $nodePiece->addChild('PieceID', $numberOfPieces); $this->_addDimension($nodePiece); $nodePiece->addChild('Weight', sprintf('%.3f', $sumWeight)); $sumWeight = 0; break; } } } if ($sumWeight > 0) { $numberOfPieces++; $nodePiece = $nodePieces->addChild('Piece', '', ''); $nodePiece->addChild('PieceID', $numberOfPieces); $this->_addDimension($nodePiece); $nodePiece->addChild('Weight', sprintf('%.3f', $sumWeight)); } } else { $nodePiece = $nodePieces->addChild('Piece', '', ''); $nodePiece->addChild('PieceID', 1); $this->_addDimension($nodePiece); $nodePiece->addChild('Weight', $this->_getWeight($this->_rawRequest->getWeight())); } $handlingAction = $this->getConfigData('handling_action'); if ($handlingAction == AbstractCarrier::HANDLING_ACTION_PERORDER || !$numberOfPieces) { $numberOfPieces = 1; } $this->_numBoxes = $numberOfPieces; } /** * Convert item dimension to needed dimension based on config dimension unit of measure * * @param float $dimension * @param string|bool $configWeightUnit * @return float */ protected function _getDimension($dimension, $configWeightUnit = false) { if (!$configWeightUnit) { $configWeightUnit = $this->getCode( 'dimensions_variables', (string)$this->getConfigData('unit_of_measure') ); } else { $configWeightUnit = $this->getCode('dimensions_variables', $configWeightUnit); } if ($configWeightUnit == Weight::POUND) { $configDimensionUnit = Length::INCH; } else { $configDimensionUnit = Length::CENTIMETER; } $countryDimensionUnit = $this->getCode('dimensions_variables', $this->_getDimensionUnit()); if ($configDimensionUnit != $countryDimensionUnit) { $dimension = $this->_carrierHelper->convertMeasureDimension( (float)$dimension, $configDimensionUnit, $countryDimensionUnit ); } return round((float) $dimension, 3); } /** * Add dimension to piece * * @param Element $nodePiece * @return void */ protected function _addDimension($nodePiece) { $sizeChecker = (string)$this->getConfigData('size'); $height = $this->_getDimension((float)$this->getConfigData('height')); $depth = $this->_getDimension((float)$this->getConfigData('depth')); $width = $this->_getDimension((float)$this->getConfigData('width')); if ($sizeChecker && $height && $depth && $width) { $nodePiece->addChild('Height', $height); $nodePiece->addChild('Depth', $depth); $nodePiece->addChild('Width', $width); } } /** * Process response received from DHL's API for quotes. * * @param array $responsesData * @return Error|Result */ private function processQuotesResponses(array $responsesData) { usort( $responsesData, function (array $a, array $b): int { return $a['date'] <=> $b['date']; } ); /** @var string $lastResponse */ $lastResponse = ''; //Processing different dates foreach ($responsesData as $responseData) { $debugPoint = []; $debugPoint['request'] = $this->filterDebugData($responseData['request']); $debugPoint['response'] = $this->filterDebugData($responseData['body']); $debugPoint['from_cache'] = $responseData['from_cache']; $unavailable = false; try { //Getting availability $bodyXml = $this->_xmlElFactory->create(['data' => $responseData['body']]); $code = $bodyXml->xpath('//GetQuoteResponse/Note/Condition/ConditionCode'); if (isset($code[0]) && (int)$code[0] == self::CONDITION_CODE_SERVICE_DATE_UNAVAILABLE) { $debugPoint['info'] = sprintf( __("DHL service is not available at %s date")->render(), $responseData['date'] ); $unavailable = true; } } catch (Throwable $exception) { //Failed to read response $unavailable = true; $this->_errors[$exception->getCode()] = $exception->getMessage(); } if ($unavailable) { //Cannot get rates. $this->_debug($debugPoint); break; } //Caching rates $this->_setCachedQuotes($responseData['request'], $responseData['body']); $this->_debug($debugPoint); //Will only process rates available for the latest date possible. $lastResponse = $responseData['body']; } return $this->_parseResponse($lastResponse); } /** * Get shipping quotes * * @return AbstractModel|Result */ protected function _getQuotes() { $responseBodies = []; /** @var HttpResponseDeferredInterface[][] $deferredResponses */ $deferredResponses = []; $requestXml = $this->_buildQuotesRequestXml(); for ($offset = 0; $offset <= self::UNAVAILABLE_DATE_LOOK_FORWARD; $offset++) { $date = date(self::REQUEST_DATE_FORMAT, strtotime($this->_getShipDate() . " +{$offset} days")); $this->_setQuotesRequestXmlDate($requestXml, $date); $request = $requestXml->asXML(); $responseBody = $this->_getCachedQuotes($request); if ($responseBody === null) { $deferredResponses[] = [ 'deferred' => $this->httpClient->request( new Request( (string)$this->getConfigData('gateway_url'), Request::METHOD_POST, ['Content-Type' => 'application/xml'], mb_convert_encoding($request, 'UTF-8') ) ), 'date' => $date, 'request' => $request ]; } else { $responseBodies[] = [ 'body' => $responseBody, 'date' => $date, 'request' => $request, 'from_cache' => true ]; } } return $this->proxyDeferredFactory->create( [ 'deferred' => new CallbackDeferred( function () use ($deferredResponses, $responseBodies) { //Loading rates not found in cache foreach ($deferredResponses as $deferredResponseData) { $responseResult = null; try { $responseResult = $deferredResponseData['deferred']->get(); } catch (HttpException $exception) { $this->_logger->critical($exception); } $responseBody = $responseResult ? $responseResult->getBody() : ''; $responseBodies[] = [ 'body' => $responseBody, 'date' => $deferredResponseData['date'], 'request' => $deferredResponseData['request'], 'from_cache' => false ]; } return $this->processQuotesResponses($responseBodies); } ) ] ); } /** * Get shipping quotes from DHL service * * @param string $request * @return string * @deprecated 100.3.3 Use asynchronous client. * @see _getQuotes() */ protected function _getQuotesFromServer($request) { /** @var LaminasClient $client */ $client = $this->_httpClientFactory->create(); $client->setUri($this->getGatewayURL()); $client->setOptions(['maxredirects' => 0, 'timeout' => 30]); $client->setRawBody(mb_convert_encoding($request, 'UTF-8')); $client->setMethod(HttpRequest::METHOD_POST); return $client->send()->getBody(); } /** * Build quotes request XML object * * @return SimpleXMLElement */ protected function _buildQuotesRequestXml() { $rawRequest = $this->_rawRequest; $xmlStr = '<?xml version="1.0" encoding = "UTF-8"?>' . '<req:DCTRequest schemaVersion="2.0" ' . 'xmlns:req="http://www.dhl.com" ' . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' . 'xsi:schemaLocation="http://www.dhl.com DCT-req_global-2.0.xsd"/>'; $xml = $this->_xmlElFactory->create(['data' => $xmlStr]); $nodeGetQuote = $xml->addChild('GetQuote', '', ''); $nodeRequest = $nodeGetQuote->addChild('Request'); $nodeServiceHeader = $nodeRequest->addChild('ServiceHeader'); $nodeServiceHeader->addChild('MessageTime', $this->buildMessageTimestamp()); $nodeServiceHeader->addChild( 'MessageReference', $this->buildMessageReference(self::SERVICE_PREFIX_QUOTE) ); $nodeServiceHeader->addChild('SiteID', (string) $this->getConfigData('id')); $nodeServiceHeader->addChild('Password', (string) $this->getConfigData('password')); $nodeMetaData = $nodeRequest->addChild('MetaData'); $nodeMetaData->addChild('SoftwareName', $this->buildSoftwareName()); $nodeMetaData->addChild('SoftwareVersion', $this->buildSoftwareVersion()); $nodeFrom = $nodeGetQuote->addChild('From'); $nodeFrom->addChild('CountryCode', $rawRequest->getOrigCountryId()); $nodeFrom->addChild('Postalcode', $rawRequest->getOrigPostal()); $nodeFrom->addChild('City', $rawRequest->getOrigCity()); $nodeBkgDetails = $nodeGetQuote->addChild('BkgDetails'); $nodeBkgDetails->addChild('PaymentCountryCode', $rawRequest->getOrigCountryId()); $nodeBkgDetails->addChild( 'Date', (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT) ); $nodeBkgDetails->addChild('ReadyTime', 'PT' . (int)(string)$this->getConfigData('ready_time') . 'H00M'); $nodeBkgDetails->addChild('DimensionUnit', $this->_getDimensionUnit()); $nodeBkgDetails->addChild('WeightUnit', $this->_getWeightUnit()); $this->_makePieces($nodeBkgDetails); $nodeBkgDetails->addChild('PaymentAccountNumber', (string)$this->getConfigData('account')); $nodeTo = $nodeGetQuote->addChild('To'); $nodeTo->addChild('CountryCode', $rawRequest->getDestCountryId()); $nodeTo->addChild('Postalcode', $rawRequest->getDestPostal()); $nodeTo->addChild('City', $rawRequest->getDestCity()); if ($this->isDutiable($rawRequest->getOrigCountryId(), $rawRequest->getDestCountryId())) { // IsDutiable flag and Dutiable node indicates that cargo is not a documentation $nodeBkgDetails->addChild('IsDutiable', 'Y'); $nodeDutiable = $nodeGetQuote->addChild('Dutiable'); $baseCurrencyCode = $this->_storeManager ->getWebsite($this->_request->getWebsiteId()) ->getBaseCurrencyCode(); $nodeDutiable->addChild('DeclaredCurrency', $baseCurrencyCode); $nodeDutiable->addChild('DeclaredValue', sprintf("%.2F", $rawRequest->getValue())); } return $xml; } /** * Set pick-up date in request XML object * * @param SimpleXMLElement $requestXml * @param string $date * @return SimpleXMLElement */ protected function _setQuotesRequestXmlDate(SimpleXMLElement $requestXml, $date) { $requestXml->GetQuote->BkgDetails->Date = $date; return $requestXml; } /** * Parse response from DHL web service * * @param string $response * @return bool|DataObject|Result|Error * @throws LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _parseResponse($response) { $responseError = __('The response is in wrong format.'); try { $this->xmlValidator->validate($response); $xml = simplexml_load_string($response); if (isset($xml->GetQuoteResponse->BkgDetails->QtdShp)) { foreach ($xml->GetQuoteResponse->BkgDetails->QtdShp as $quotedShipment) { $this->_addRate($quotedShipment); } } elseif (isset($xml->AirwayBillNumber)) { return $this->_prepareShippingLabelContent($xml); } else { $this->_errors[] = $responseError; } } catch (DocumentValidationException $e) { if ($e->getCode() > 0) { $this->_errors[$e->getCode()] = $e->getMessage(); } else { $this->_errors[] = $e->getMessage(); } } /* @var $result Result */ $result = $this->_rateFactory->create(); if ($this->_rates) { foreach ($this->_rates as $rate) { $method = $rate['service']; $data = $rate['data']; /* @var $rate Method */ $rate = $this->_rateMethodFactory->create(); $rate->setCarrier(self::CODE); $rate->setCarrierTitle($this->getConfigData('title')); $rate->setMethod($method); $rate->setMethodTitle($data['term']); $rate->setCost($data['price_total']); $rate->setPrice($data['price_total']); $result->append($rate); } } else { if (!empty($this->_errors)) { if ($this->_isShippingLabelFlag) { throw new LocalizedException($responseError); } $this->debugErrors($this->_errors); } $result->append($this->getErrorMessage()); } return $result; } /** * Add rate to DHL rates array * * @param SimpleXMLElement $shipmentDetails * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _addRate(SimpleXMLElement $shipmentDetails) { if (isset($shipmentDetails->ProductShortName) && isset($shipmentDetails->ShippingCharge) && isset($shipmentDetails->GlobalProductCode) && isset($shipmentDetails->CurrencyCode) && array_key_exists((string)$shipmentDetails->GlobalProductCode, $this->getAllowedMethods()) ) { // DHL product code, e.g. '3', 'A', 'Q', etc. $dhlProduct = (string)$shipmentDetails->GlobalProductCode; $totalEstimate = (float)(string)$shipmentDetails->ShippingCharge; $currencyCode = (string)$shipmentDetails->CurrencyCode; $baseCurrencyCode = $this->_storeManager->getWebsite($this->_request->getWebsiteId()) ->getBaseCurrencyCode(); $dhlProductDescription = $this->getDhlProductTitle($dhlProduct); if ($currencyCode != $baseCurrencyCode) { /* @var $currency Currency */ $currency = $this->_currencyFactory->create(); $rates = $currency->getCurrencyRates($currencyCode, [$baseCurrencyCode]); if (!empty($rates) && isset($rates[$baseCurrencyCode])) { // Convert to store display currency using store exchange rate $totalEstimate = $totalEstimate * $rates[$baseCurrencyCode]; } else { $rates = $currency->getCurrencyRates($baseCurrencyCode, [$currencyCode]); if (!empty($rates) && isset($rates[$currencyCode])) { $totalEstimate = $totalEstimate / $rates[$currencyCode]; } if (!isset($rates[$currencyCode]) || !$totalEstimate) { $totalEstimate = false; $this->_errors[] = __( 'We had to skip DHL method %1 because we couldn\'t find exchange rate %2 (Base Currency).', $currencyCode, $baseCurrencyCode ); } } } if ($totalEstimate) { $data = [ 'term' => $dhlProductDescription, 'price_total' => $this->getMethodPrice($totalEstimate, $dhlProduct), ]; if (!empty($this->_rates)) { foreach ($this->_rates as $product) { if ($product['data']['term'] == $data['term'] && $product['data']['price_total'] == $data['price_total'] ) { return $this; } } } $this->_rates[] = ['service' => $dhlProduct, 'data' => $data]; } else { $this->_errors[] = __("Zero shipping charge for '%1'", $dhlProductDescription); } } else { $dhlProductDescription = false; if (isset($shipmentDetails->GlobalProductCode)) { $dhlProductDescription = $this->getDhlProductTitle((string)$shipmentDetails->GlobalProductCode); } $dhlProductDescription = $dhlProductDescription ? $dhlProductDescription : __("DHL"); $this->_errors[] = __("Zero shipping charge for '%1'", $dhlProductDescription); } return $this; } /** * Returns dimension unit (cm or inch) * * @return string * @throws LocalizedException */ protected function _getDimensionUnit() { $countryId = $this->_rawRequest->getOrigCountryId(); $measureUnit = $this->getCountryParams($countryId)->getMeasureUnit(); if (empty($measureUnit)) { throw new LocalizedException( __("Cannot identify measure unit for %1", $countryId) ); } return $measureUnit; } /** * Returns weight unit (kg or pound) * * @return string * @throws LocalizedException */ protected function _getWeightUnit() { $countryId = $this->_rawRequest->getOrigCountryId(); $weightUnit = $this->getCountryParams($countryId)->getWeightUnit(); if (empty($weightUnit)) { throw new LocalizedException( __("Cannot identify weight unit for %1", $countryId) ); } return $weightUnit; } /** * Get Country Params by Country Code * * @param string $countryCode * @return DataObject * * @see $countryCode ISO 3166 Codes (Countries) A2 */ protected function getCountryParams($countryCode) { if (empty($this->_countryParams)) { $etcPath = $this->_configReader->getModuleDir(Dir::MODULE_ETC_DIR, 'Magento_Dhl'); $directoryRead = $this->readFactory->create($etcPath); $countriesXml = $directoryRead->readFile('countries.xml'); $this->_countryParams = $this->_xmlElFactory->create(['data' => $countriesXml]); } if (isset($this->_countryParams->{$countryCode})) { $countryParams = new DataObject($this->_countryParams->{$countryCode}->asArray()); } return $countryParams ?? new DataObject(); } /** * Do shipment request to carrier web service, obtain Print Shipping Labels and process errors in response * * @param DataObject $request * @return DataObject */ protected function _doShipmentRequest(DataObject $request) { $this->_prepareShipmentRequest($request); $this->_mapRequestToShipment($request); $this->setRequest($request); return $this->_doRequest(); } /** * Processing additional validation to check is carrier applicable. * * @param DataObject $request * @return $this|DataObject|boolean * phpcs:disable Magento2.Annotation.MethodAnnotationStructure * @deprecated 100.2.3 * @see use processAdditionalValidation method instead */ public function proccessAdditionalValidation(DataObject $request) { return $this->processAdditionalValidation($request); } /** * Processing additional validation to check is carrier applicable. * * @param DataObject $request * @return $this|DataObject|boolean */ public function processAdditionalValidation(DataObject $request) { //Skip by item validation if there is no items in request if (empty($this->getAllItems($request))) { $this->_errors[] = __('There is no items in this order'); } $countryParams = $this->getCountryParams( $this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_COUNTRY_ID, ScopeInterface::SCOPE_STORE, $request->getStoreId() ) ); if (!$countryParams->getData()) { $this->_errors[] = __('Please specify origin country.'); } if (!empty($this->_errors)) { $this->debugErrors($this->_errors); return false; } return $this; } /** * Return container types of carrier * * @param DataObject|null $params * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getContainerTypes(DataObject $params = null) { return [ self::DHL_CONTENT_TYPE_DOC => __('Documents'), self::DHL_CONTENT_TYPE_NON_DOC => __('Non Documents') ]; } /** * Map request to shipment * * @param DataObject $request * @return void * @throws LocalizedException */ protected function _mapRequestToShipment(DataObject $request) { $request->setOrigCountryId($request->getShipperAddressCountryCode()); $this->setRawRequest($request); $customsValue = 0; $packageWeight = 0; $packages = $request->getPackages(); foreach ($packages as &$piece) { $params = $piece['params']; if ($params['width'] || $params['length'] || $params['height']) { $minValue = $this->_getMinDimension($params['dimension_units']); if ($params['width'] < $minValue || $params['length'] < $minValue || $params['height'] < $minValue) { $message = __('Height, width and length should be equal or greater than %1', $minValue); throw new LocalizedException($message); } } $weightUnits = $piece['params']['weight_units']; $piece['params']['height'] = $this->_getDimension($piece['params']['height'], $weightUnits); $piece['params']['length'] = $this->_getDimension($piece['params']['length'], $weightUnits); $piece['params']['width'] = $this->_getDimension($piece['params']['width'], $weightUnits); $piece['params']['dimension_units'] = $this->_getDimensionUnit(); $piece['params']['weight'] = $this->_getWeight($piece['params']['weight'], false, $weightUnits); $piece['params']['weight_units'] = $this->_getWeightUnit(); $customsValue += $piece['params']['customs_value']; $packageWeight += $piece['params']['weight']; } $request->setPackages($packages) ->setPackageWeight($packageWeight) ->setPackageValue($customsValue) ->setValueWithDiscount($customsValue) ->setPackageCustomsValue($customsValue) ->setFreeMethodWeight(0); } /** * Retrieve minimum allowed value for dimensions in given dimension unit * * @param string $dimensionUnit * @return int */ protected function _getMinDimension($dimensionUnit) { return $dimensionUnit == "CENTIMETER" ? self::DIMENSION_MIN_CM : self::DIMENSION_MIN_IN; } /** * Do rate request and handle errors * * @return Result|DataObject * @throws LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function _doRequest() { $rawRequest = $this->_request; $xmlStr = '<?xml version="1.0" encoding="UTF-8"?>' . '<req:ShipmentRequest' . ' xmlns:req="http://www.dhl.com"' . ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . ' xsi:schemaLocation="http://www.dhl.com ship-val-global-req.xsd"' . ' schemaVersion="10.0" />'; $xml = $this->_xmlElFactory->create(['data' => $xmlStr]); $nodeRequest = $xml->addChild('Request', '', ''); $nodeServiceHeader = $nodeRequest->addChild('ServiceHeader'); $nodeServiceHeader->addChild('MessageTime', $this->buildMessageTimestamp()); // MessageReference must be 28 to 32 chars. $nodeServiceHeader->addChild( 'MessageReference', $this->buildMessageReference(self::SERVICE_PREFIX_SHIPVAL) ); $nodeServiceHeader->addChild('SiteID', (string)$this->getConfigData('id')); $nodeServiceHeader->addChild('Password', (string)$this->getConfigData('password')); $nodeMetaData = $nodeRequest->addChild('MetaData'); $nodeMetaData->addChild('SoftwareName', $this->buildSoftwareName()); $nodeMetaData->addChild('SoftwareVersion', $this->buildSoftwareVersion()); $originRegion = $this->getCountryParams( $this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_COUNTRY_ID, ScopeInterface::SCOPE_STORE, $this->getStore() ) )->getRegion(); if ($originRegion) { $xml->addChild('RegionCode', $originRegion, ''); } $xml->addChild('RequestedPickupTime', 'N', ''); $xml->addChild('LanguageCode', 'EN', ''); /** Billing */ $nodeBilling = $xml->addChild('Billing', '', ''); $nodeBilling->addChild('ShipperAccountNumber', (string)substr($this->getConfigData('account'), 0, 9)); /** * Method of Payment: * S (Shipper) * R (Receiver) * T (Third Party) */ $nodeBilling->addChild('ShippingPaymentType', 'S'); /** * Shipment bill to account ā required if Shipping PaymentType is other than 'S' */ $nodeBilling->addChild('BillingAccountNumber', (string)substr($this->getConfigData('account'), 0, 9)); if ($this->isDutiable( $rawRequest->getShipperAddressCountryCode(), $rawRequest->getRecipientAddressCountryCode() )) { $nodeBilling->addChild('DutyAccountNumber', (string)substr($this->getConfigData('account'), 0, 9)); } /** Receiver */ $nodeConsignee = $xml->addChild('Consignee', '', ''); $companyName = $rawRequest->getRecipientContactCompanyName() ? $rawRequest ->getRecipientContactCompanyName() : $rawRequest ->getRecipientContactPersonName(); $nodeConsignee->addChild('CompanyName', is_string($companyName) ? substr($companyName, 0, 60) : ''); $address = $rawRequest->getRecipientAddressStreet1() . ' ' . $rawRequest->getRecipientAddressStreet2(); $address = $this->string->split($address, 45, false, true); if (is_array($address)) { $addressLineNumber = 1; foreach ($address as $addressLine) { if ($addressLineNumber > 3) { break; } $nodeConsignee->addChild('AddressLine'.$addressLineNumber, $addressLine); $addressLineNumber++; } } else { $nodeConsignee->addChild('AddressLine1', $address); } $nodeConsignee->addChild('City', $rawRequest->getRecipientAddressCity()); $recipientAddressStateOrProvinceCode = $rawRequest->getRecipientAddressStateOrProvinceCode(); if ($recipientAddressStateOrProvinceCode) { $nodeConsignee->addChild('Division', $recipientAddressStateOrProvinceCode); } $nodeConsignee->addChild('PostalCode', $rawRequest->getRecipientAddressPostalCode()); $nodeConsignee->addChild('CountryCode', $rawRequest->getRecipientAddressCountryCode()); $nodeConsignee->addChild( 'CountryName', $this->getCountryParams($rawRequest->getRecipientAddressCountryCode())->getName() ); $nodeContact = $nodeConsignee->addChild('Contact'); $recipientContactPersonName = is_string($rawRequest->getRecipientContactPersonName()) ? substr($rawRequest->getRecipientContactPersonName(), 0, 34) : ''; $recipientContactPhoneNumber = is_string($rawRequest->getRecipientContactPhoneNumber()) ? substr($rawRequest->getRecipientContactPhoneNumber(), 0, 24) : ''; $nodeContact->addChild('PersonName', $recipientContactPersonName); $nodeContact->addChild('PhoneNumber', $recipientContactPhoneNumber); /** * Commodity * The CommodityCode element contains commodity code for shipment contents. Its * value should lie in between 1 to 9999.This field is mandatory. */ $nodeCommodity = $xml->addChild('Commodity', '', ''); $nodeCommodity->addChild('CommodityCode', substr('01', 0, 18)); /** Dutiable */ if ($this->isDutiable( $rawRequest->getShipperAddressCountryCode(), $rawRequest->getRecipientAddressCountryCode() )) { $nodeDutiable = $xml->addChild('Dutiable', '', ''); $nodeDutiable->addChild( 'DeclaredValue', sprintf("%.2F", $rawRequest->getOrderShipment()->getOrder()->getSubtotal()) ); $baseCurrencyCode = $this->_storeManager->getWebsite($rawRequest->getWebsiteId())->getBaseCurrencyCode(); $nodeDutiable->addChild('DeclaredCurrency', $baseCurrencyCode); $nodeDutiable->addChild('TermsOfTrade', 'DAP'); /** Export Declaration */ $this->addExportDeclaration($xml, $rawRequest); } /** * Reference * This element identifies the reference information. It is an optional field in the * shipment validation request. Only the first reference will be taken currently. */ $nodeReference = $xml->addChild('Reference', '', ''); $nodeReference->addChild('ReferenceID', 'shipment reference'); $nodeReference->addChild('ReferenceType', 'St'); /** Shipment Details */ $this->_shipmentDetails($xml, $rawRequest); /** Shipper */ $nodeShipper = $xml->addChild('Shipper', '', ''); $nodeShipper->addChild('ShipperID', (string)substr($this->getConfigData('account'), 0, 9)); $nodeShipper->addChild('CompanyName', $rawRequest->getShipperContactCompanyName()); $nodeShipper->addChild('RegisteredAccount', (string)substr($this->getConfigData('account'), 0, 9)); $address = $rawRequest->getShipperAddressStreet1() . ' ' . $rawRequest->getShipperAddressStreet2(); $address = $this->string->split($address, 45, false, true); if (is_array($address)) { $addressLineNumber = 1; foreach ($address as $addressLine) { if ($addressLineNumber > 3) { break; } $nodeShipper->addChild('AddressLine'.$addressLineNumber, $addressLine); $addressLineNumber++; } } else { $nodeShipper->addChild('AddressLine1', $address); } $nodeShipper->addChild('City', $rawRequest->getShipperAddressCity()); $shipperAddressStateOrProvinceCode = $rawRequest->getShipperAddressStateOrProvinceCode(); if ($shipperAddressStateOrProvinceCode) { $nodeShipper->addChild('Division', $shipperAddressStateOrProvinceCode); } $nodeShipper->addChild('PostalCode', $rawRequest->getShipperAddressPostalCode()); $nodeShipper->addChild('CountryCode', $rawRequest->getShipperAddressCountryCode()); $nodeShipper->addChild( 'CountryName', $this->getCountryParams($rawRequest->getShipperAddressCountryCode())->getName() ); $nodeContact = $nodeShipper->addChild('Contact', '', ''); $shipperContactPersonName = is_string($rawRequest->getShipperContactPersonName()) ? substr($rawRequest->getShipperContactPersonName(), 0, 34) : ''; $shipperContactPhoneNumber = is_string($rawRequest->getShipperContactPhoneNumber()) ? substr($rawRequest->getShipperContactPhoneNumber(), 0, 24) : ''; $nodeContact->addChild('PersonName', $shipperContactPersonName); $nodeContact->addChild('PhoneNumber', $shipperContactPhoneNumber); $xml->addChild('LabelImageFormat', 'PDF', ''); $request = $xml->asXML(); if ($request && !(mb_detect_encoding($request) == 'UTF-8')) { $request = mb_convert_encoding($request, 'UTF-8'); } $responseBody = $this->_getCachedQuotes($request); if ($responseBody === null) { $debugData = ['request' => $this->filterDebugData($request)]; try { $response = $this->httpClient->request( new Request( $this->getGatewayURL(), Request::METHOD_POST, ['Content-Type' => 'application/xml'], $request ) ); $responseBody = mb_convert_encoding($response->get()->getBody(), 'ISO-8859-1', 'UTF-8'); $debugData['result'] = $this->filterDebugData($responseBody); $this->_setCachedQuotes($request, $responseBody); } catch (Exception $e) { $this->_errors[$e->getCode()] = $e->getMessage(); $responseBody = ''; } $this->_debug($debugData); } $this->_isShippingLabelFlag = true; return $this->_parseResponse($responseBody); } /** * Generation Shipment Details Node according to origin region * * @param Element $xml * @param RateRequest $rawRequest * @param string $originRegion * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _shipmentDetails($xml, $rawRequest, $originRegion = '') { $nodeShipmentDetails = $xml->addChild('ShipmentDetails', '', ''); $nodePieces = $nodeShipmentDetails->addChild('Pieces', '', ''); /* * Package type * EE (DHL Express Envelope), OD (Other DHL Packaging), CP (Custom Packaging) * DC (Document), DM (Domestic), ED (Express Document), FR (Freight) * BD (Jumbo Document), BP (Jumbo Parcel), JD (Jumbo Junior Document) * JP (Jumbo Junior Parcel), PA (Parcel), DF (DHL Flyer) */ $i = 0; foreach ($rawRequest->getPackages() as $package) { $nodePiece = $nodePieces->addChild('Piece', '', ''); $packageType = 'EE'; if ($package['params']['container'] == self::DHL_CONTENT_TYPE_NON_DOC) { $packageType = 'CP'; } $nodePiece->addChild('PieceID', ++$i); $nodePiece->addChild('PackageType', $packageType); $nodePiece->addChild('Weight', sprintf('%.3f', $package['params']['weight'])); $params = $package['params']; if ($params['width'] && $params['length'] && $params['height']) { $nodePiece->addChild('Width', (string) round((float) $params['width'])); $nodePiece->addChild('Height', (string) round((float) $params['height'])); $nodePiece->addChild('Depth', (string) round((float) $params['length'])); } $content = []; foreach ($package['items'] as $item) { $content[] = $item['name']; } $nodePiece->addChild('PieceContents', $this->string->substr(implode(',', $content), 0, 34)); } $nodeShipmentDetails->addChild('WeightUnit', substr($this->_getWeightUnit(), 0, 1)); $nodeShipmentDetails->addChild('GlobalProductCode', $rawRequest->getShippingMethod()); $nodeShipmentDetails->addChild('LocalProductCode', $rawRequest->getShippingMethod()); $nodeShipmentDetails->addChild( 'Date', $this->_coreDate->date('Y-m-d', strtotime('now + 1day')) ); $nodeShipmentDetails->addChild('Contents', 'DHL Parcel'); $nodeShipmentDetails->addChild('DimensionUnit', substr($this->_getDimensionUnit(), 0, 1)); $contentType = isset($package['params']['container']) ? $package['params']['container'] : ''; $packageType = $contentType === self::DHL_CONTENT_TYPE_NON_DOC ? 'CP' : 'EE'; $nodeShipmentDetails->addChild('PackageType', $packageType); if ($this->isDutiable( $rawRequest->getShipperAddressCountryCode(), $rawRequest->getRecipientAddressCountryCode() )) { $nodeShipmentDetails->addChild('IsDutiable', 'Y'); } $nodeShipmentDetails->addChild( 'CurrencyCode', $this->_storeManager->getWebsite($this->_request->getWebsiteId())->getBaseCurrencyCode() ); } /** * Get tracking * * @param string|string[] $trackings * @return \Magento\Shipping\Model\Tracking\Result|null */ public function getTracking($trackings) { if (!is_array($trackings)) { $trackings = [$trackings]; } $this->_getXMLTracking($trackings); return $this->_result; } /** * Send request for tracking * * @param string[] $trackings * @return void */ protected function _getXMLTracking($trackings) { $xmlStr = '<?xml version="1.0" encoding="UTF-8"?>' . '<req:KnownTrackingRequest' . ' xmlns:req="http://www.dhl.com"' . ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . ' xsi:schemaLocation="http://www.dhl.com TrackingRequestKnown-1.0.xsd"' . ' schemaVersion="1.0" />'; $xml = $this->_xmlElFactory->create(['data' => $xmlStr]); $requestNode = $xml->addChild('Request', '', ''); $serviceHeaderNode = $requestNode->addChild('ServiceHeader', '', ''); $serviceHeaderNode->addChild('MessageTime', $this->buildMessageTimestamp()); $serviceHeaderNode->addChild('MessageReference', $this->buildMessageReference(self::SERVICE_PREFIX_TRACKING)); $serviceHeaderNode->addChild('SiteID', (string)$this->getConfigData('id')); $serviceHeaderNode->addChild('Password', (string)$this->getConfigData('password')); $xml->addChild('LanguageCode', 'EN', ''); foreach ($trackings as $tracking) { $xml->addChild('AWBNumber', $tracking, ''); } /** * Checkpoint details selection flag * LAST_CHECK_POINT_ONLY * ALL_CHECK_POINTS */ $xml->addChild('LevelOfDetails', 'ALL_CHECK_POINTS', ''); /** * Value that indicates for getting the tracking details with the additional * piece details and its respective Piece Details, Piece checkpoints along with * Shipment Details if queried. * * S-Only Shipment Details * B-Both Shipment & Piece Details * P-Only Piece Details * Default is āSā */ //$xml->addChild('PiecesEnabled', 'ALL_CHECK_POINTS'); $request = $xml->asXML(); $request = mb_convert_encoding($request, 'UTF-8'); $responseBody = $this->_getCachedQuotes($request); if ($responseBody === null) { $debugData = ['request' => $this->filterDebugData($request)]; try { $response = $this->httpClient->request( new Request( $this->getGatewayURL(), Request::METHOD_POST, ['Content-Type' => 'application/xml'], $request ) ); $responseBody = $response->get()->getBody(); $debugData['result'] = $this->filterDebugData($responseBody); $this->_setCachedQuotes($request, $responseBody); } catch (Exception $e) { $this->_errors[$e->getCode()] = $e->getMessage(); $responseBody = ''; } $this->_debug($debugData); } $this->_parseXmlTrackingResponse($trackings, $responseBody); } /** * Parse xml tracking response * * @param string[] $trackings * @param string $response * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _parseXmlTrackingResponse($trackings, $response) { $errorTitle = __('Unable to retrieve tracking'); $resultArr = []; if (!empty(trim($response))) { $xml = $this->parseXml($response, Element::class); if (!is_object($xml)) { $errorTitle = __('Response is in the wrong format'); } if (is_object($xml) && (isset($xml->Response->Status->ActionStatus) && $xml->Response->Status->ActionStatus == 'Failure' || isset($xml->GetQuoteResponse->Note->Condition)) ) { if (isset($xml->Response->Status->Condition)) { $nodeCondition = $xml->Response->Status->Condition; } $code = isset($nodeCondition->ConditionCode) ? (string)$nodeCondition->ConditionCode : 0; $data = isset($nodeCondition->ConditionData) ? (string)$nodeCondition->ConditionData : ''; $this->_errors[$code] = __('Error #%1 : %2', $code, $data); } elseif (is_object($xml) && is_object($xml->AWBInfo)) { foreach ($xml->AWBInfo as $awbinfo) { $awbinfoData = []; $trackNum = isset($awbinfo->AWBNumber) ? (string)$awbinfo->AWBNumber : ''; if (!is_object($awbinfo) || !$awbinfo->ShipmentInfo) { $this->_errors[$trackNum] = __('Unable to retrieve tracking'); continue; } $shipmentInfo = $awbinfo->ShipmentInfo; if ($shipmentInfo->ShipmentDesc) { $awbinfoData['service'] = (string)$shipmentInfo->ShipmentDesc; } $awbinfoData['weight'] = (string)$shipmentInfo->Weight . ' ' . (string)$shipmentInfo->WeightUnit; $packageProgress = []; if (isset($shipmentInfo->ShipmentEvent)) { foreach ($shipmentInfo->ShipmentEvent as $shipmentEvent) { $shipmentEventArray = []; $shipmentEventArray['activity'] = (string)$shipmentEvent->ServiceEvent->EventCode . ' ' . (string)$shipmentEvent->ServiceEvent->Description; $shipmentEventArray['deliverydate'] = (string)$shipmentEvent->Date; $shipmentEventArray['deliverytime'] = (string)$shipmentEvent->Time; $shipmentEventArray['deliverylocation'] = (string)$shipmentEvent->ServiceArea ->Description . ' [' . (string)$shipmentEvent->ServiceArea->ServiceAreaCode . ']'; $packageProgress[] = $shipmentEventArray; } $awbinfoData['progressdetail'] = $packageProgress; } $resultArr[$trackNum] = $awbinfoData; } } } $result = $this->_trackFactory->create(); if (!empty($resultArr)) { foreach ($resultArr as $trackNum => $data) { $tracking = $this->_trackStatusFactory->create(); $tracking->setCarrier($this->_code); $tracking->setCarrierTitle($this->getConfigData('title')); $tracking->setTracking($trackNum); $tracking->addData($data); $result->append($tracking); } } if (!empty($this->_errors) || empty($resultArr)) { $resultArr = !empty($this->_errors) ? $this->_errors : $trackings; foreach ($resultArr as $trackNum => $err) { $error = $this->_trackErrorFactory->create(); $error->setCarrier($this->_code); $error->setCarrierTitle($this->getConfigData('title')); $error->setTracking(!empty($this->_errors) ? $trackNum : $err); $error->setErrorMessage(!empty($this->_errors) ? $err : $errorTitle); $result->append($error); } } $this->_errors = []; $this->_result = $result; } /** * Get final price for shipping method with handling fee per package * * @param float $cost * @param string $handlingType * @param float $handlingFee * @return float */ protected function _getPerpackagePrice($cost, $handlingType, $handlingFee) { if ($handlingType == AbstractCarrier::HANDLING_TYPE_PERCENT) { return $cost + $cost * $this->_numBoxes * $handlingFee / 100; } return $cost + $this->_numBoxes * $handlingFee; } /** * Do request to shipment * * @param ShipmentRequest $request * @return array|DataObject * @throws LocalizedException */ public function requestToShipment($request) { $packages = $request->getPackages(); if (!is_array($packages) || !$packages) { throw new LocalizedException(__('No packages for request')); } $result = $this->_doShipmentRequest($request); $response = new DataObject( [ 'info' => [ [ 'tracking_number' => $result->getTrackingNumber(), 'label_content' => $result->getShippingLabelContent(), ], ], ] ); $request->setMasterTrackingId($result->getTrackingNumber()); return $response; } /** * Check if shipping is domestic * * @param string $origCountryCode * @param string $destCountryCode * @return bool */ protected function _checkDomesticStatus($origCountryCode, $destCountryCode) { $this->_isDomestic = false; $origCountry = (string)$this->getCountryParams($origCountryCode)->getData('name'); $destCountry = (string)$this->getCountryParams($destCountryCode)->getData('name'); if (($origCountry == $destCountry) || ( $this->_carrierHelper->isCountryInEU($origCountryCode) && $this->_carrierHelper->isCountryInEU($destCountryCode) ) ) { $this->_isDomestic = true; } return $this->_isDomestic; } /** * Prepare shipping label data * * @param SimpleXMLElement $xml * @return DataObject * @throws LocalizedException */ protected function _prepareShippingLabelContent(SimpleXMLElement $xml) { $result = new DataObject(); try { if (!isset($xml->AirwayBillNumber) || !isset($xml->LabelImage->OutputImage)) { throw new LocalizedException(__('Unable to retrieve shipping label')); } $result->setTrackingNumber((string)$xml->AirwayBillNumber); $labelContent = (string)$xml->LabelImage->OutputImage; // phpcs:ignore Magento2.Functions.DiscouragedFunction $result->setShippingLabelContent(base64_decode($labelContent)); } catch (Exception $e) { throw new LocalizedException(__($e->getMessage())); } return $result; } /** * Verify if the shipment is dutiable * * @param string $origCountryId * @param string $destCountryId * * @return bool */ protected function isDutiable($origCountryId, $destCountryId): bool { $this->_checkDomesticStatus($origCountryId, $destCountryId); return !$this->_isDomestic; } /** * Builds a datetime string to be used as the MessageTime in accordance to the expected format. * * @param string|null $datetime * @return string */ private function buildMessageTimestamp(string $datetime = null): string { return $this->_coreDate->date(DATE_RFC3339, $datetime); } /** * Builds a string to be used as the MessageReference. * * @param string $servicePrefix * @return string * @throws LocalizedException */ private function buildMessageReference(string $servicePrefix): string { $validPrefixes = [ self::SERVICE_PREFIX_QUOTE, self::SERVICE_PREFIX_SHIPVAL, self::SERVICE_PREFIX_TRACKING ]; if (!in_array($servicePrefix, $validPrefixes)) { throw new LocalizedException( __("Invalid service prefix \"$servicePrefix\" provided while attempting to build MessageReference") ); } return str_replace('.', '', uniqid("MAGE_{$servicePrefix}_", true)); } /** * Builds a string to be used as the request SoftwareName. * * @return string */ private function buildSoftwareName(): string { return substr($this->productMetadata->getName(), 0, 30); } /** * Builds a string to be used as the request SoftwareVersion. * * @return string */ private function buildSoftwareVersion(): string { return substr($this->productMetadata->getVersion(), 0, 10); } /** * Get the gateway URL * * @return string */ private function getGatewayURL(): string { if ($this->getConfigData('sandbox_mode')) { return (string)$this->getConfigData('sandbox_url'); } else { return (string)$this->getConfigData('gateway_url'); } } /** * Generating Export Declaration Details * * @param Element $xml * @param ShipmentRequest $rawRequest * @return void */ private function addExportDeclaration(Element $xml, ShipmentRequest $rawRequest): void { $nodeExportDeclaration = $xml->addChild('ExportDeclaration', '', ''); $nodeExportDeclaration->addChild( 'InvoiceNumber', $rawRequest->getOrderShipment()->getOrder()->hasInvoices() ? $this->getInvoiceNumbers($rawRequest) : $rawRequest->getOrderShipment()->getOrder()->getIncrementId() ); $nodeExportDeclaration->addChild( 'InvoiceDate', date("Y-m-d", strtotime((string)$rawRequest->getOrderShipment()->getOrder()->getCreatedAt())) ); $exportItems = $rawRequest->getPackages(); foreach ($exportItems as $exportItem) { $itemWeightUnit = $exportItem['params']['weight_units'] ? substr( $exportItem['params']['weight_units'], 0, 1 ) : 'L'; foreach ($exportItem['items'] as $itemNo => $itemData) { $nodeExportItem = $nodeExportDeclaration->addChild('ExportLineItem', '', ''); $nodeExportItem->addChild('LineNumber', $itemNo); $nodeExportItem->addChild('Quantity', $itemData['qty']); $nodeExportItem->addChild('QuantityUnit', 'PCS'); $nodeExportItem->addChild('Description', $itemData['name']); $nodeExportItem->addChild('Value', $itemData['price']); $nodeItemWeight = $nodeExportItem->addChild('Weight', '', ''); $nodeItemWeight->addChild('Weight', $itemData['weight']); $nodeItemWeight->addChild('WeightUnit', $itemWeightUnit); $nodeItemGrossWeight = $nodeExportItem->addChild('GrossWeight'); $nodeItemGrossWeight->addChild('Weight', $itemData['weight']); $nodeItemGrossWeight->addChild('WeightUnit', $itemWeightUnit); $nodeExportItem->addChild('ManufactureCountryCode', $rawRequest->getShipperAddressCountryCode()); } } } /** * Fetching Shipment Order Invoice No * * @param ShipmentRequest $rawRequest * @return string */ private function getInvoiceNumbers(ShipmentRequest $rawRequest): string { $invoiceNumbers = []; $order = $rawRequest->getOrderShipment()->getOrder(); foreach ($order->getInvoiceCollection() as $invoice) { $invoiceNumbers[] = $invoice->getIncrementId(); } return implode(',', $invoiceNumbers); } }