PageRenderTime 38ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/sale/lib/delivery/tracking/rus_post.php

https://gitlab.com/alexprowars/bitrix
PHP | 479 lines | 333 code | 63 blank | 83 comment | 35 complexity | 5d0dc5b728b9d47888362f4c81f4210d MD5 | raw file
  1. <?php
  2. namespace Bitrix\Sale\Delivery\Tracking;
  3. use Bitrix\Main\Error;
  4. use Bitrix\Sale\Result;
  5. use Bitrix\Main\Text\Encoding;
  6. use Bitrix\Main\Localization\Loc;
  7. Loc::loadMessages(__FILE__);
  8. /**
  9. * Class RusPost
  10. * @package Bitrix\Sale\Delivery\Tracking
  11. * https://tracking.pochta.ru/specification
  12. */
  13. class RusPost extends Base
  14. {
  15. /**
  16. * @return string
  17. */
  18. public function getClassTitle()
  19. {
  20. return Loc::getMessage("SALE_DELIVERY_TRACKING_RUS_POST_TITLE");
  21. }
  22. /**
  23. * @return string
  24. */
  25. public function getClassDescription()
  26. {
  27. return Loc::getMessage(
  28. "SALE_DELIVERY_TRACKING_RUS_POST_DESCRIPTION",
  29. array(
  30. '#A1#' => '<a href="https://tracking.pochta.ru/">',
  31. '#A2#' => '</a>'
  32. )
  33. );
  34. }
  35. /**
  36. * @param $trackingNumber
  37. * @return \Bitrix\Sale\Delivery\Tracking\StatusResult.
  38. */
  39. public function getStatus($trackingNumber)
  40. {
  41. $trackingNumber = trim($trackingNumber);
  42. $result = new StatusResult();
  43. if(!$this->checkTrackNumberFormat($trackingNumber))
  44. $result->addError(new Error(Loc::getMessage('SALE_DELIVERY_TRACKING_RUS_POST_ERROR_TRNUM_FORMAT')));
  45. if(empty($this->params['LOGIN']))
  46. $result->addError(new Error(Loc::getMessage("SALE_DELIVERY_TRACKING_RUS_POST_LOGIN_ERROR")));
  47. if(empty($this->params['PASSWORD']))
  48. $result->addError(new Error(Loc::getMessage("SALE_DELIVERY_TRACKING_RUS_POST_PASSWORD_ERROR")));
  49. if($result->isSuccess())
  50. {
  51. $t = new RusPostSingle(
  52. $this->params['LOGIN'],
  53. $this->params['PASSWORD']
  54. );
  55. $result = $t->getOperationHistory($trackingNumber);
  56. }
  57. return $result;
  58. }
  59. /**
  60. * @param array $trackingNumbers
  61. * @return StatusResult[]
  62. * todo: by package of 3000 items
  63. */
  64. public function getStatuses(array $trackingNumbers)
  65. {
  66. $data = array();
  67. foreach($trackingNumbers as $number)
  68. $data[$number] = $this->getStatus($number);
  69. return $data;
  70. }
  71. /**
  72. * @return array
  73. */
  74. public function getParamsStructure()
  75. {
  76. return array(
  77. "LOGIN" => array(
  78. 'TYPE' => 'STRING',
  79. 'LABEL' => Loc::getMessage("SALE_DELIVERY_TRACKING_RUS_POST_LOGIN")
  80. ),
  81. "PASSWORD" => array(
  82. 'TYPE' => 'STRING',
  83. 'LABEL' => Loc::getMessage("SALE_DELIVERY_TRACKING_RUS_POST_PASSWORD")
  84. )
  85. );
  86. }
  87. /**
  88. * Checks if tracknumber matches to required format.
  89. * 14 - digit,
  90. * 13 symbols like pattern XX123456789YY (UPU-S10)
  91. * @param string $trackNumber
  92. * @return bool
  93. */
  94. protected function checkTrackNumberFormat($trackNumber)
  95. {
  96. if(mb_strlen($trackNumber) == 13)
  97. return preg_match('/^[A-Z]{2}?\d{9}?[A-Z]{2}$/i', $trackNumber) == 1;
  98. elseif(mb_strlen($trackNumber) == 14)
  99. return preg_match('/^\d{14}?$/', $trackNumber) == 1;
  100. else
  101. return false;
  102. }
  103. /**
  104. * @param string $trackingNumber
  105. * @return string Url were we can see tracking information
  106. */
  107. public function getTrackingUrl($trackingNumber = '')
  108. {
  109. return 'https://pochta.ru/tracking'.($trackingNumber <> '' ? '#'.$trackingNumber : '');
  110. }
  111. }
  112. /**
  113. * Class RusPostSingle
  114. * @package Bitrix\Sale\Delivery\Tracking
  115. */
  116. class RusPostSingle
  117. {
  118. const LANG_RUS = "RUS";
  119. const LANG_ENG = "ENG";
  120. protected $client = null;
  121. protected $lang = "";
  122. protected $login = "";
  123. protected $password = "";
  124. protected static $url = 'https://tracking.russianpost.ru/rtm34';
  125. /**
  126. * @param string $login
  127. * @param string $password
  128. * @param string $lang
  129. */
  130. public function __construct($login, $password, $lang = self::LANG_RUS)
  131. {
  132. $this->httpClient = new \Bitrix\Main\Web\HttpClient(array(
  133. "version" => "1.1",
  134. "socketTimeout" => 15,
  135. "streamTimeout" => 15,
  136. "redirect" => true,
  137. "redirectMax" => 5,
  138. ));
  139. $this->httpClient->setHeader("Content-Type", "application/soap+xml; charset=utf-8");
  140. $this->lang = $lang;
  141. $this->login = $login;
  142. $this->password = $password;
  143. }
  144. public function sendRequest($requestData)
  145. {
  146. $result = new Result();
  147. if(mb_strtolower(SITE_CHARSET) != 'utf-8')
  148. $requestData = Encoding::convertEncoding($requestData, SITE_CHARSET, 'UTF-8');
  149. $httpRes = $this->httpClient->post(self::$url, $requestData);
  150. $errors = $this->httpClient->getError();
  151. if (!$httpRes && !empty($errors))
  152. {
  153. $strError = "";
  154. foreach($errors as $errorCode => $errMes)
  155. $strError .= $errorCode.": ".$errMes;
  156. $result->addError(new Error($strError));
  157. }
  158. else
  159. {
  160. $status = $this->httpClient->getStatus();
  161. if(mb_strtolower(SITE_CHARSET) != 'utf-8')
  162. $httpRes = Encoding::convertEncoding($httpRes, 'UTF-8', SITE_CHARSET);
  163. $objXML = new \CDataXML();
  164. $objXML->LoadString($httpRes);
  165. $data = $objXML->GetArray();
  166. $result->addData($data);
  167. if ($status != 200)
  168. {
  169. $result->addError(new Error(Loc::getMessage('SALE_DELIVERY_TRACKING_RUS_POST_ERROR_HTTP_STATUS').': '.$status));
  170. if(!empty($data['Envelope']['#']['Body'][0]['#']['Fault'][0]['#']['Reason'][0]['#']['Text'][0]['#']))
  171. $result->addError(new Error($data['Envelope']['#']['Body'][0]['#']['Fault'][0]['#']['Reason'][0]['#']['Text'][0]['#']));
  172. if(!empty($data['Envelope']['#']['Body'][0]['#']['Fault'][0]['#']['Detail'][0]['#']['AuthorizationFaultReason'][0]['#']))
  173. $result->addError(new Error($data['Envelope']['#']['Body'][0]['#']['Fault'][0]['#']['Detail'][0]['#']['AuthorizationFaultReason'][0]['#']));
  174. }
  175. }
  176. return $result;
  177. }
  178. /**
  179. * @param string $trackingNumber
  180. * @return StatusResult
  181. */
  182. public function getOperationHistory($trackingNumber)
  183. {
  184. $result = new StatusResult();
  185. $requestData = '
  186. <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:oper="http://russianpost.org/operationhistory" xmlns:data="http://russianpost.org/operationhistory/data" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  187. <soap:Header/>
  188. <soap:Body>
  189. <oper:getOperationHistory>
  190. <data:OperationHistoryRequest>
  191. <data:Barcode>'.$trackingNumber.'</data:Barcode>
  192. <data:MessageType>0</data:MessageType>
  193. <data:Language>'.$this->lang.'</data:Language>
  194. </data:OperationHistoryRequest>
  195. <data:AuthorizationHeader soapenv:mustUnderstand="1">
  196. <data:login>'.$this->login.'</data:login>
  197. <data:password>'.$this->password.'</data:password>
  198. </data:AuthorizationHeader>
  199. </oper:getOperationHistory>
  200. </soap:Body>
  201. </soap:Envelope>
  202. ';
  203. $res = $this->sendRequest($requestData);
  204. if(!$res->isSuccess())
  205. {
  206. $result->addErrors($res->getErrors());
  207. return $result;
  208. }
  209. $lastOperation = $this->getLastOperation($res->getData());
  210. if(!$lastOperation)
  211. {
  212. $result->addError(new Error(Loc::getMessage('SALE_DELIVERY_TRACKING_RUS_POST_ERROR_LAST_OP')));
  213. }
  214. else
  215. {
  216. $result->status = $this->extractStatus($lastOperation);
  217. $result->description = $this->createDescription($trackingNumber);
  218. $lastOperationTS = $this->extractLastChangeDate($lastOperation);
  219. if($lastOperationTS > 0)
  220. $result->lastChangeTimestamp = $this->extractLastChangeDate($lastOperation);
  221. $result->trackingNumber = $trackingNumber;
  222. }
  223. return $result;
  224. }
  225. /**
  226. * @param $lastOperation
  227. * @return int
  228. */
  229. protected function extractLastChangeDate($lastOperation)
  230. {
  231. if(empty($lastOperation['#']['OperationParameters'][0]['#']['OperDate'][0]['#']))
  232. return 0;
  233. $date = new \DateTime($lastOperation['#']['OperationParameters'][0]['#']['OperDate'][0]['#']);
  234. return $date->getTimestamp();
  235. }
  236. /**
  237. * @param $answer
  238. * @return mixed|null
  239. */
  240. protected function getLastOperation(array $answer)
  241. {
  242. $history = $answer['Envelope']['#']['Body'][0]['#']['getOperationHistoryResponse'][0]['#']['OperationHistoryData'][0]['#']['historyRecord'];
  243. if(!is_array($history) || empty($history))
  244. return null;
  245. if(!$lastOperation = end($history))
  246. return null;
  247. return $lastOperation;
  248. }
  249. /**
  250. * @param string $trackingNumber
  251. * @return string
  252. */
  253. protected function createDescription($trackingNumber)
  254. {
  255. $link = 'https://pochta.ru/tracking#'.$trackingNumber;
  256. return Loc::getMessage('SALE_DELIVERY_TRACKING_RUS_POST_STATUS_DESCR').': '.'<a href="'.$link.'">'.$link.'</a>';
  257. }
  258. /**
  259. * @param $lastOperation
  260. * @return int
  261. */
  262. protected function extractStatus(array $lastOperation)
  263. {
  264. if(!isset($lastOperation['#']['OperationParameters']['0']['#']['OperType']['0']['#']['Id']['0']['#']))
  265. return Statuses::UNKNOWN;
  266. if(!isset($lastOperation['#']['OperationParameters'][0]['#']['OperAttr'][0]['#']['Id'][0]['#']))
  267. return Statuses::UNKNOWN;
  268. $oper = $lastOperation['#']['OperationParameters'][0]['#']['OperType'][0]['#']['Id'][0]['#'];
  269. $att = $lastOperation['#']['OperationParameters'][0]['#']['OperAttr'][0]['#']['Id'][0]['#'];
  270. return $this->mapStatus($oper, $att);
  271. }
  272. /**
  273. * Maps outer operationCode and attributeCode to inner status enumerated in class Statuses
  274. * @param $oper
  275. * @param $attr
  276. * @return int
  277. */
  278. protected function mapStatus($oper, $attr)
  279. {
  280. if($oper == '')
  281. return Statuses::UNKNOWN;
  282. /*
  283. * if innerStatus1 != innerStatus2 != .......
  284. *
  285. * opCode1 => array (
  286. * attrCode1 => innerStatus1
  287. * attrCode2 => innerStatus2
  288. * ...
  289. * )
  290. *
  291. * if innerStatus1 == innerStatus2 == .......
  292. *
  293. * opCode => innerStatus
  294. *
  295. */
  296. $rusPostStatuses = array(
  297. 1 => Statuses::WAITING_SHIPMENT,
  298. 2 => array(
  299. 1 => Statuses::HANDED,
  300. 2 => Statuses::RETURNED,
  301. 3 => Statuses::HANDED,
  302. 4 => Statuses::RETURNED,
  303. 5 => Statuses::HANDED,
  304. 6 => Statuses::HANDED,
  305. 7 => Statuses::RETURNED,
  306. 8 => Statuses::HANDED,
  307. 9 => Statuses::RETURNED,
  308. 10 => Statuses::HANDED,
  309. 11 => Statuses::HANDED,
  310. 12 => Statuses::HANDED,
  311. ),
  312. 3 => Statuses::PROBLEM,
  313. 4 => Statuses::ON_THE_WAY,
  314. 5 => array(
  315. 1 => Statuses::PROBLEM,
  316. 2 => Statuses::PROBLEM,
  317. 3 => Statuses::PROBLEM,
  318. 8 => Statuses::PROBLEM,
  319. 9 => Statuses::PROBLEM
  320. ),
  321. 6 => array(
  322. 1 => Statuses::ARRIVED,
  323. 2 => Statuses::ARRIVED,
  324. 3 => Statuses::ARRIVED,
  325. 4 => Statuses::ARRIVED,
  326. 5 => Statuses::ON_THE_WAY,
  327. ),
  328. 7 => Statuses::PROBLEM,
  329. 8 => array(
  330. 0 => Statuses::ON_THE_WAY,
  331. 1 => Statuses::ON_THE_WAY,
  332. 2 => Statuses::ARRIVED,
  333. 3 => Statuses::ON_THE_WAY,
  334. 4 => Statuses::ON_THE_WAY,
  335. 5 => Statuses::ON_THE_WAY,
  336. 6 => Statuses::ON_THE_WAY,
  337. 7 => Statuses::ON_THE_WAY,
  338. 8 => Statuses::ON_THE_WAY,
  339. 9 => Statuses::ARRIVED,
  340. 10 => Statuses::ARRIVED,
  341. 11 => Statuses::ON_THE_WAY,
  342. 12 => Statuses::ARRIVED,
  343. 13 => Statuses::ON_THE_WAY,
  344. 14 => Statuses::ARRIVED,
  345. 15 => Statuses::ON_THE_WAY,
  346. 16 => Statuses::ON_THE_WAY,
  347. 17 => Statuses::ON_THE_WAY,
  348. 18 => Statuses::ON_THE_WAY,
  349. 19 => Statuses::ON_THE_WAY,
  350. ),
  351. 9 => Statuses::ON_THE_WAY,
  352. 10 => Statuses::ON_THE_WAY,
  353. 11 => Statuses::ON_THE_WAY,
  354. 12 => array(
  355. 1 => Statuses::ARRIVED,
  356. 2 => Statuses::ARRIVED,
  357. 3 => Statuses::PROBLEM,
  358. 4 => Statuses::PROBLEM,
  359. 5 => Statuses::PROBLEM,
  360. 6 => Statuses::PROBLEM,
  361. 7 => Statuses::PROBLEM,
  362. 8 => Statuses::PROBLEM,
  363. 9 => Statuses::ARRIVED,
  364. 10 => Statuses::PROBLEM,
  365. 11 => Statuses::ARRIVED,
  366. 12 => Statuses::PROBLEM,
  367. 13 => Statuses::PROBLEM,
  368. 14 => Statuses::PROBLEM,
  369. 15 => Statuses::ARRIVED,
  370. 16 => Statuses::PROBLEM,
  371. 17 => Statuses::ARRIVED,
  372. 18 => Statuses::ARRIVED,
  373. 19 => Statuses::PROBLEM,
  374. 20 => Statuses::PROBLEM,
  375. 21 => Statuses::PROBLEM,
  376. 22 => Statuses::ARRIVED,
  377. 23 => Statuses::PROBLEM,
  378. 24 => Statuses::PROBLEM,
  379. 25 => Statuses::ARRIVED,
  380. 26 => Statuses::PROBLEM,
  381. 27 => Statuses::ARRIVED,
  382. 28 => Statuses::PROBLEM,
  383. ),
  384. 13 => Statuses::ON_THE_WAY,
  385. 14 => Statuses::ON_THE_WAY,
  386. 15 => Statuses::ARRIVED,
  387. 16 => Statuses::PROBLEM,
  388. 17 => Statuses::ARRIVED,
  389. 18 => Statuses::PROBLEM,
  390. 19 => Statuses::ON_THE_WAY,
  391. 20 => Statuses::ON_THE_WAY,
  392. 21 => Statuses::ON_THE_WAY,
  393. 22 => Statuses::PROBLEM,
  394. 23 => Statuses::ON_THE_WAY,
  395. 24 => Statuses::PROBLEM,
  396. 25 => Statuses::ON_THE_WAY,
  397. 26 => Statuses::PROBLEM,
  398. 27 => Statuses::ON_THE_WAY,
  399. 28 => Statuses::NO_INFORMATION,
  400. 29 => Statuses::ON_THE_WAY,
  401. 30 => Statuses::ON_THE_WAY,
  402. 31 => Statuses::ON_THE_WAY,
  403. 32 => Statuses::ON_THE_WAY,
  404. 33 => Statuses::ON_THE_WAY,
  405. );
  406. if(!isset($rusPostStatuses[$oper]))
  407. return Statuses::UNKNOWN;
  408. if(!is_array($rusPostStatuses[$oper]))
  409. return $rusPostStatuses[$oper];
  410. if($attr == '')
  411. return Statuses::UNKNOWN;
  412. if(!isset($rusPostStatuses[$oper][$attr]))
  413. return Statuses::UNKNOWN;
  414. return $rusPostStatuses[$oper][$attr];
  415. }
  416. }