Guest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Sales\Helper;
  7. use Magento\Framework\App as App;
  8. use Magento\Framework\Exception\InputException;
  9. use Magento\Framework\Stdlib\Cookie\CookieSizeLimitReachedException;
  10. use Magento\Framework\Stdlib\Cookie\FailureToSendException;
  11. use \Magento\Sales\Model\Order;
  12. /**
  13. * Sales module base helper
  14. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  15. */
  16. class Guest extends \Magento\Framework\App\Helper\AbstractHelper
  17. {
  18. /**
  19. * Core registry
  20. *
  21. * @var \Magento\Framework\Registry
  22. */
  23. protected $coreRegistry;
  24. /**
  25. * @var \Magento\Customer\Model\Session
  26. */
  27. protected $customerSession;
  28. /**
  29. * @var \Magento\Framework\Stdlib\CookieManagerInterface
  30. */
  31. protected $cookieManager;
  32. /**
  33. * @var \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory
  34. */
  35. protected $cookieMetadataFactory;
  36. /**
  37. * @var \Magento\Framework\Message\ManagerInterface
  38. */
  39. protected $messageManager;
  40. /**
  41. * @var \Magento\Sales\Model\OrderFactory
  42. */
  43. protected $orderFactory;
  44. /**
  45. * @var \Magento\Framework\Controller\Result\RedirectFactory
  46. */
  47. protected $resultRedirectFactory;
  48. /**
  49. * @var \Magento\Sales\Api\OrderRepositoryInterface
  50. */
  51. private $orderRepository;
  52. /**
  53. * @var \Magento\Framework\Api\SearchCriteriaBuilder
  54. */
  55. private $searchCriteriaBuilder;
  56. /**
  57. * Cookie key for guest view
  58. */
  59. const COOKIE_NAME = 'guest-view';
  60. /**
  61. * Cookie path
  62. */
  63. const COOKIE_PATH = '/';
  64. /**
  65. * Cookie lifetime value
  66. */
  67. const COOKIE_LIFETIME = 600;
  68. /**
  69. * @var \Magento\Store\Model\StoreManagerInterface
  70. */
  71. private $storeManager;
  72. /**
  73. * @var string
  74. */
  75. private $inputExceptionMessage = 'You entered incorrect data. Please try again.';
  76. /**
  77. * @param App\Helper\Context $context
  78. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  79. * @param \Magento\Framework\Registry $coreRegistry
  80. * @param \Magento\Customer\Model\Session $customerSession
  81. * @param \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager
  82. * @param \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory
  83. * @param \Magento\Framework\Message\ManagerInterface $messageManager
  84. * @param \Magento\Sales\Model\OrderFactory $orderFactory
  85. * @param \Magento\Framework\Controller\Result\RedirectFactory $resultRedirectFactory
  86. * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository
  87. * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteria
  88. *
  89. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  90. */
  91. public function __construct(
  92. App\Helper\Context $context,
  93. \Magento\Store\Model\StoreManagerInterface $storeManager,
  94. \Magento\Framework\Registry $coreRegistry,
  95. \Magento\Customer\Model\Session $customerSession,
  96. \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager,
  97. \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory,
  98. \Magento\Framework\Message\ManagerInterface $messageManager,
  99. \Magento\Sales\Model\OrderFactory $orderFactory,
  100. \Magento\Framework\Controller\Result\RedirectFactory $resultRedirectFactory,
  101. \Magento\Sales\Api\OrderRepositoryInterface $orderRepository = null,
  102. \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteria = null
  103. ) {
  104. $this->coreRegistry = $coreRegistry;
  105. $this->storeManager = $storeManager;
  106. $this->customerSession = $customerSession;
  107. $this->cookieManager = $cookieManager;
  108. $this->cookieMetadataFactory = $cookieMetadataFactory;
  109. $this->messageManager = $messageManager;
  110. $this->orderFactory = $orderFactory;
  111. $this->resultRedirectFactory = $resultRedirectFactory;
  112. $this->orderRepository = $orderRepository ?: \Magento\Framework\App\ObjectManager::getInstance()
  113. ->get(\Magento\Sales\Api\OrderRepositoryInterface::class);
  114. $this->searchCriteriaBuilder = $searchCriteria?: \Magento\Framework\App\ObjectManager::getInstance()
  115. ->get(\Magento\Framework\Api\SearchCriteriaBuilder::class);
  116. parent::__construct(
  117. $context
  118. );
  119. }
  120. /**
  121. * Try to load valid order by $_POST or $_COOKIE
  122. *
  123. * @param App\RequestInterface $request
  124. * @return \Magento\Framework\Controller\Result\Redirect|bool
  125. * @throws \RuntimeException
  126. * @throws InputException
  127. * @throws CookieSizeLimitReachedException
  128. * @throws FailureToSendException
  129. */
  130. public function loadValidOrder(App\RequestInterface $request)
  131. {
  132. if ($this->customerSession->isLoggedIn()) {
  133. return $this->resultRedirectFactory->create()->setPath('sales/order/history');
  134. }
  135. $post = $request->getPostValue();
  136. $fromCookie = $this->cookieManager->getCookie(self::COOKIE_NAME);
  137. if (empty($post) && !$fromCookie) {
  138. return $this->resultRedirectFactory->create()->setPath('sales/guest/form');
  139. }
  140. // It is unique place in the class that process exception and only InputException. It is need because by
  141. // input data we found order and one more InputException could be throws deeper in stack trace
  142. try {
  143. $order = (!empty($post)
  144. && isset($post['oar_order_id'], $post['oar_type'])
  145. && !$this->hasPostDataEmptyFields($post))
  146. ? $this->loadFromPost($post) : $this->loadFromCookie($fromCookie);
  147. $this->coreRegistry->register('current_order', $order);
  148. return true;
  149. } catch (InputException $e) {
  150. $this->messageManager->addErrorMessage($e->getMessage());
  151. return $this->resultRedirectFactory->create()->setPath('sales/guest/form');
  152. }
  153. }
  154. /**
  155. * Get Breadcrumbs for current controller action
  156. *
  157. * @param \Magento\Framework\View\Result\Page $resultPage
  158. * @return void
  159. */
  160. public function getBreadcrumbs(\Magento\Framework\View\Result\Page $resultPage)
  161. {
  162. $breadcrumbs = $resultPage->getLayout()->getBlock('breadcrumbs');
  163. if (!$breadcrumbs) {
  164. return;
  165. }
  166. $breadcrumbs->addCrumb(
  167. 'home',
  168. [
  169. 'label' => __('Home'),
  170. 'title' => __('Go to Home Page'),
  171. 'link' => $this->storeManager->getStore()->getBaseUrl()
  172. ]
  173. );
  174. $breadcrumbs->addCrumb(
  175. 'cms_page',
  176. ['label' => __('Order Information'), 'title' => __('Order Information')]
  177. );
  178. }
  179. /**
  180. * Set guest-view cookie
  181. *
  182. * @param string $cookieValue
  183. * @return void
  184. * @throws InputException
  185. * @throws CookieSizeLimitReachedException
  186. * @throws FailureToSendException
  187. */
  188. private function setGuestViewCookie($cookieValue)
  189. {
  190. $metadata = $this->cookieMetadataFactory->createPublicCookieMetadata()
  191. ->setPath(self::COOKIE_PATH)
  192. ->setHttpOnly(true);
  193. $this->cookieManager->setPublicCookie(self::COOKIE_NAME, $cookieValue, $metadata);
  194. }
  195. /**
  196. * Load order from cookie
  197. *
  198. * @param string $fromCookie
  199. * @return Order
  200. * @throws InputException
  201. * @throws CookieSizeLimitReachedException
  202. * @throws FailureToSendException
  203. */
  204. private function loadFromCookie($fromCookie)
  205. {
  206. $cookieData = explode(':', base64_decode($fromCookie));
  207. $protectCode = isset($cookieData[0]) ? $cookieData[0] : null;
  208. $incrementId = isset($cookieData[1]) ? $cookieData[1] : null;
  209. if (!empty($protectCode) && !empty($incrementId)) {
  210. $order = $this->getOrderRecord($incrementId);
  211. if (hash_equals((string)$order->getProtectCode(), $protectCode)) {
  212. $this->setGuestViewCookie($fromCookie);
  213. return $order;
  214. }
  215. }
  216. throw new InputException(__($this->inputExceptionMessage));
  217. }
  218. /**
  219. * Load order data from post
  220. *
  221. * @param array $postData
  222. * @return Order
  223. * @throws InputException
  224. * @throws CookieSizeLimitReachedException
  225. * @throws FailureToSendException
  226. */
  227. private function loadFromPost(array $postData)
  228. {
  229. /** @var $order \Magento\Sales\Model\Order */
  230. $order = $this->getOrderRecord($postData['oar_order_id']);
  231. if (!$this->compareStoredBillingDataWithInput($order, $postData)) {
  232. throw new InputException(__('You entered incorrect data. Please try again.'));
  233. }
  234. $toCookie = base64_encode($order->getProtectCode() . ':' . $postData['oar_order_id']);
  235. $this->setGuestViewCookie($toCookie);
  236. return $order;
  237. }
  238. /**
  239. * Check that billing data from the order and from the input are equal
  240. *
  241. * @param Order $order
  242. * @param array $postData
  243. * @return bool
  244. */
  245. private function compareStoredBillingDataWithInput(Order $order, array $postData)
  246. {
  247. $type = $postData['oar_type'];
  248. $email = $postData['oar_email'];
  249. $lastName = $postData['oar_billing_lastname'];
  250. $zip = $postData['oar_zip'];
  251. $billingAddress = $order->getBillingAddress();
  252. return strtolower($lastName) === strtolower($billingAddress->getLastname()) &&
  253. ($type === 'email' && strtolower($email) === strtolower($billingAddress->getEmail()) ||
  254. $type === 'zip' && strtolower($zip) === strtolower($billingAddress->getPostcode()));
  255. }
  256. /**
  257. * Check post data for empty fields
  258. *
  259. * @param array $postData
  260. * @return bool
  261. */
  262. private function hasPostDataEmptyFields(array $postData)
  263. {
  264. return empty($postData['oar_order_id']) || empty($postData['oar_billing_lastname']) ||
  265. empty($postData['oar_type']) || empty($this->storeManager->getStore()->getId()) ||
  266. !in_array($postData['oar_type'], ['email', 'zip'], true) ||
  267. ('email' === $postData['oar_type'] && empty($postData['oar_email'])) ||
  268. ('zip' === $postData['oar_type'] && empty($postData['oar_zip']));
  269. }
  270. /**
  271. * Get order by increment_id and store_id
  272. *
  273. * @param string $incrementId
  274. * @return \Magento\Sales\Api\Data\OrderInterface
  275. * @throws InputException
  276. */
  277. private function getOrderRecord($incrementId)
  278. {
  279. $records = $this->orderRepository->getList(
  280. $this->searchCriteriaBuilder
  281. ->addFilter('increment_id', $incrementId)
  282. ->addFilter('store_id', $this->storeManager->getStore()->getId())
  283. ->create()
  284. );
  285. $items = $records->getItems();
  286. if (empty($items)) {
  287. throw new InputException(__($this->inputExceptionMessage));
  288. }
  289. return array_shift($items);
  290. }
  291. }