Session.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Checkout\Model;
  7. use Magento\Customer\Api\Data\CustomerInterface;
  8. use Magento\Quote\Model\Quote;
  9. use Magento\Quote\Model\QuoteIdMaskFactory;
  10. /**
  11. * @api
  12. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  13. * @since 100.0.2
  14. */
  15. class Session extends \Magento\Framework\Session\SessionManager
  16. {
  17. /**
  18. * Checkout state begin
  19. */
  20. const CHECKOUT_STATE_BEGIN = 'begin';
  21. /**
  22. * Quote instance
  23. *
  24. * @var Quote
  25. */
  26. protected $_quote;
  27. /**
  28. * Customer Data Object
  29. *
  30. * @var CustomerInterface|null
  31. */
  32. protected $_customer;
  33. /**
  34. * Whether load only active quote
  35. *
  36. * @var bool
  37. */
  38. protected $_loadInactive = false;
  39. /**
  40. * Loaded order instance
  41. *
  42. * @var \Magento\Sales\Model\Order
  43. */
  44. protected $_order;
  45. /**
  46. * @var \Magento\Sales\Model\OrderFactory
  47. */
  48. protected $_orderFactory;
  49. /**
  50. * @var \Magento\Customer\Model\Session
  51. */
  52. protected $_customerSession;
  53. /**
  54. * @var \Magento\Quote\Api\CartRepositoryInterface
  55. */
  56. protected $quoteRepository;
  57. /**
  58. * @var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress
  59. */
  60. protected $_remoteAddress;
  61. /**
  62. * @var \Magento\Framework\Event\ManagerInterface
  63. */
  64. protected $_eventManager;
  65. /**
  66. * @var \Magento\Store\Model\StoreManagerInterface
  67. */
  68. protected $_storeManager;
  69. /**
  70. * @var \Magento\Customer\Api\CustomerRepositoryInterface
  71. */
  72. protected $customerRepository;
  73. /**
  74. * @param QuoteIdMaskFactory
  75. */
  76. protected $quoteIdMaskFactory;
  77. /**
  78. * @param bool
  79. */
  80. protected $isQuoteMasked;
  81. /**
  82. * @var \Magento\Quote\Model\QuoteFactory
  83. */
  84. protected $quoteFactory;
  85. /**
  86. * @param \Magento\Framework\App\Request\Http $request
  87. * @param \Magento\Framework\Session\SidResolverInterface $sidResolver
  88. * @param \Magento\Framework\Session\Config\ConfigInterface $sessionConfig
  89. * @param \Magento\Framework\Session\SaveHandlerInterface $saveHandler
  90. * @param \Magento\Framework\Session\ValidatorInterface $validator
  91. * @param \Magento\Framework\Session\StorageInterface $storage
  92. * @param \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager
  93. * @param \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory
  94. * @param \Magento\Framework\App\State $appState
  95. * @param \Magento\Sales\Model\OrderFactory $orderFactory
  96. * @param \Magento\Customer\Model\Session $customerSession
  97. * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
  98. * @param \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress
  99. * @param \Magento\Framework\Event\ManagerInterface $eventManager
  100. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  101. * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository
  102. * @param QuoteIdMaskFactory $quoteIdMaskFactory
  103. * @param \Magento\Quote\Model\QuoteFactory $quoteFactory
  104. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  105. */
  106. public function __construct(
  107. \Magento\Framework\App\Request\Http $request,
  108. \Magento\Framework\Session\SidResolverInterface $sidResolver,
  109. \Magento\Framework\Session\Config\ConfigInterface $sessionConfig,
  110. \Magento\Framework\Session\SaveHandlerInterface $saveHandler,
  111. \Magento\Framework\Session\ValidatorInterface $validator,
  112. \Magento\Framework\Session\StorageInterface $storage,
  113. \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager,
  114. \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory,
  115. \Magento\Framework\App\State $appState,
  116. \Magento\Sales\Model\OrderFactory $orderFactory,
  117. \Magento\Customer\Model\Session $customerSession,
  118. \Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
  119. \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress,
  120. \Magento\Framework\Event\ManagerInterface $eventManager,
  121. \Magento\Store\Model\StoreManagerInterface $storeManager,
  122. \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
  123. QuoteIdMaskFactory $quoteIdMaskFactory,
  124. \Magento\Quote\Model\QuoteFactory $quoteFactory
  125. ) {
  126. $this->_orderFactory = $orderFactory;
  127. $this->_customerSession = $customerSession;
  128. $this->quoteRepository = $quoteRepository;
  129. $this->_remoteAddress = $remoteAddress;
  130. $this->_eventManager = $eventManager;
  131. $this->_storeManager = $storeManager;
  132. $this->customerRepository = $customerRepository;
  133. $this->quoteIdMaskFactory = $quoteIdMaskFactory;
  134. $this->quoteFactory = $quoteFactory;
  135. parent::__construct(
  136. $request,
  137. $sidResolver,
  138. $sessionConfig,
  139. $saveHandler,
  140. $validator,
  141. $storage,
  142. $cookieManager,
  143. $cookieMetadataFactory,
  144. $appState
  145. );
  146. }
  147. /**
  148. * Set customer data.
  149. *
  150. * @param CustomerInterface|null $customer
  151. * @return \Magento\Checkout\Model\Session
  152. * @codeCoverageIgnore
  153. */
  154. public function setCustomerData($customer)
  155. {
  156. $this->_customer = $customer;
  157. return $this;
  158. }
  159. /**
  160. * Check whether current session has quote
  161. *
  162. * @return bool
  163. * @codeCoverageIgnore
  164. */
  165. public function hasQuote()
  166. {
  167. return isset($this->_quote);
  168. }
  169. /**
  170. * Set quote to be loaded even if inactive
  171. *
  172. * @param bool $load
  173. * @return $this
  174. * @codeCoverageIgnore
  175. */
  176. public function setLoadInactive($load = true)
  177. {
  178. $this->_loadInactive = $load;
  179. return $this;
  180. }
  181. /**
  182. * Get checkout quote instance by current session
  183. *
  184. * @return Quote
  185. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  186. * @SuppressWarnings(PHPMD.NPathComplexity)
  187. */
  188. public function getQuote()
  189. {
  190. $this->_eventManager->dispatch('custom_quote_process', ['checkout_session' => $this]);
  191. if ($this->_quote === null) {
  192. $quote = $this->quoteFactory->create();
  193. if ($this->getQuoteId()) {
  194. try {
  195. if ($this->_loadInactive) {
  196. $quote = $this->quoteRepository->get($this->getQuoteId());
  197. } else {
  198. $quote = $this->quoteRepository->getActive($this->getQuoteId());
  199. }
  200. /**
  201. * If current currency code of quote is not equal current currency code of store,
  202. * need recalculate totals of quote. It is possible if customer use currency switcher or
  203. * store switcher.
  204. */
  205. if ($quote->getQuoteCurrencyCode() != $this->_storeManager->getStore()->getCurrentCurrencyCode()) {
  206. $quote->setStore($this->_storeManager->getStore());
  207. $this->quoteRepository->save($quote->collectTotals());
  208. /*
  209. * We mast to create new quote object, because collectTotals()
  210. * can to create links with other objects.
  211. */
  212. $quote = $this->quoteRepository->get($this->getQuoteId());
  213. }
  214. } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
  215. $this->setQuoteId(null);
  216. }
  217. }
  218. if (!$this->getQuoteId()) {
  219. if ($this->_customerSession->isLoggedIn() || $this->_customer) {
  220. $customerId = $this->_customer
  221. ? $this->_customer->getId()
  222. : $this->_customerSession->getCustomerId();
  223. try {
  224. $quote = $this->quoteRepository->getActiveForCustomer($customerId);
  225. $this->setQuoteId($quote->getId());
  226. } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
  227. }
  228. } else {
  229. $quote->setIsCheckoutCart(true);
  230. $this->_eventManager->dispatch('checkout_quote_init', ['quote' => $quote]);
  231. }
  232. }
  233. if ($this->_customer) {
  234. $quote->setCustomer($this->_customer);
  235. } elseif ($this->_customerSession->isLoggedIn()) {
  236. $quote->setCustomer($this->customerRepository->getById($this->_customerSession->getCustomerId()));
  237. }
  238. $quote->setStore($this->_storeManager->getStore());
  239. $this->_quote = $quote;
  240. }
  241. if (!$this->isQuoteMasked() && !$this->_customerSession->isLoggedIn() && $this->getQuoteId()) {
  242. $quoteId = $this->getQuoteId();
  243. /** @var $quoteIdMask \Magento\Quote\Model\QuoteIdMask */
  244. $quoteIdMask = $this->quoteIdMaskFactory->create()->load($quoteId, 'quote_id');
  245. if ($quoteIdMask->getMaskedId() === null) {
  246. $quoteIdMask->setQuoteId($quoteId)->save();
  247. }
  248. $this->setIsQuoteMasked(true);
  249. }
  250. $remoteAddress = $this->_remoteAddress->getRemoteAddress();
  251. if ($remoteAddress) {
  252. $this->_quote->setRemoteIp($remoteAddress);
  253. $xForwardIp = $this->request->getServer('HTTP_X_FORWARDED_FOR');
  254. $this->_quote->setXForwardedFor($xForwardIp);
  255. }
  256. return $this->_quote;
  257. }
  258. /**
  259. * @return string
  260. * @codeCoverageIgnore
  261. */
  262. protected function _getQuoteIdKey()
  263. {
  264. return 'quote_id_' . $this->_storeManager->getStore()->getWebsiteId();
  265. }
  266. /**
  267. * @param int $quoteId
  268. * @return void
  269. * @codeCoverageIgnore
  270. */
  271. public function setQuoteId($quoteId)
  272. {
  273. $this->storage->setData($this->_getQuoteIdKey(), $quoteId);
  274. }
  275. /**
  276. * @return int
  277. * @codeCoverageIgnore
  278. */
  279. public function getQuoteId()
  280. {
  281. return $this->getData($this->_getQuoteIdKey());
  282. }
  283. /**
  284. * Load data for customer quote and merge with current quote
  285. *
  286. * @return $this
  287. */
  288. public function loadCustomerQuote()
  289. {
  290. if (!$this->_customerSession->getCustomerId()) {
  291. return $this;
  292. }
  293. $this->_eventManager->dispatch('load_customer_quote_before', ['checkout_session' => $this]);
  294. try {
  295. $customerQuote = $this->quoteRepository->getForCustomer($this->_customerSession->getCustomerId());
  296. } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
  297. $customerQuote = $this->quoteFactory->create();
  298. }
  299. $customerQuote->setStoreId($this->_storeManager->getStore()->getId());
  300. if ($customerQuote->getId() && $this->getQuoteId() != $customerQuote->getId()) {
  301. if ($this->getQuoteId()) {
  302. $this->quoteRepository->save(
  303. $customerQuote->merge($this->getQuote())->collectTotals()
  304. );
  305. }
  306. $this->setQuoteId($customerQuote->getId());
  307. if ($this->_quote) {
  308. $this->quoteRepository->delete($this->_quote);
  309. }
  310. $this->_quote = $customerQuote;
  311. } else {
  312. $this->getQuote()->getBillingAddress();
  313. $this->getQuote()->getShippingAddress();
  314. $this->getQuote()->setCustomer($this->_customerSession->getCustomerDataObject())
  315. ->setTotalsCollectedFlag(false)
  316. ->collectTotals();
  317. $this->quoteRepository->save($this->getQuote());
  318. }
  319. return $this;
  320. }
  321. /**
  322. * @param string $step
  323. * @param array|string $data
  324. * @param bool|string|null $value
  325. * @return $this
  326. */
  327. public function setStepData($step, $data, $value = null)
  328. {
  329. $steps = $this->getSteps();
  330. if ($value === null) {
  331. if (is_array($data)) {
  332. $steps[$step] = $data;
  333. }
  334. } else {
  335. if (!isset($steps[$step])) {
  336. $steps[$step] = [];
  337. }
  338. if (is_string($data)) {
  339. $steps[$step][$data] = $value;
  340. }
  341. }
  342. $this->setSteps($steps);
  343. return $this;
  344. }
  345. /**
  346. * @param string|null $step
  347. * @param string|null $data
  348. * @return array|string|bool
  349. */
  350. public function getStepData($step = null, $data = null)
  351. {
  352. $steps = $this->getSteps();
  353. if ($step === null) {
  354. return $steps;
  355. }
  356. if (!isset($steps[$step])) {
  357. return false;
  358. }
  359. if ($data === null) {
  360. return $steps[$step];
  361. }
  362. if (!is_string($data) || !isset($steps[$step][$data])) {
  363. return false;
  364. }
  365. return $steps[$step][$data];
  366. }
  367. /**
  368. * Destroy/end a session
  369. * Unset all data associated with object
  370. *
  371. * @return $this
  372. */
  373. public function clearQuote()
  374. {
  375. $this->_eventManager->dispatch('checkout_quote_destroy', ['quote' => $this->getQuote()]);
  376. $this->_quote = null;
  377. $this->setQuoteId(null);
  378. $this->setLastSuccessQuoteId(null);
  379. return $this;
  380. }
  381. /**
  382. * Unset all session data and quote
  383. *
  384. * @return $this
  385. */
  386. public function clearStorage()
  387. {
  388. parent::clearStorage();
  389. $this->_quote = null;
  390. return $this;
  391. }
  392. /**
  393. * Clear misc checkout parameters
  394. *
  395. * @return void
  396. */
  397. public function clearHelperData()
  398. {
  399. $this->setRedirectUrl(null)->setLastOrderId(null)->setLastRealOrderId(null)->setAdditionalMessages(null);
  400. }
  401. /**
  402. * @return $this
  403. * @codeCoverageIgnore
  404. */
  405. public function resetCheckout()
  406. {
  407. $this->setCheckoutState(self::CHECKOUT_STATE_BEGIN);
  408. return $this;
  409. }
  410. /**
  411. * @param Quote $quote
  412. * @return $this
  413. */
  414. public function replaceQuote($quote)
  415. {
  416. $this->_quote = $quote;
  417. $this->setQuoteId($quote->getId());
  418. return $this;
  419. }
  420. /**
  421. * Get order instance based on last order ID
  422. *
  423. * @return \Magento\Sales\Model\Order
  424. */
  425. public function getLastRealOrder()
  426. {
  427. $orderId = $this->getLastRealOrderId();
  428. if ($this->_order !== null && $orderId == $this->_order->getIncrementId()) {
  429. return $this->_order;
  430. }
  431. $this->_order = $this->_orderFactory->create();
  432. if ($orderId) {
  433. $this->_order->loadByIncrementId($orderId);
  434. }
  435. return $this->_order;
  436. }
  437. /**
  438. * Restore last active quote
  439. *
  440. * @return bool True if quote restored successfully, false otherwise
  441. */
  442. public function restoreQuote()
  443. {
  444. /** @var \Magento\Sales\Model\Order $order */
  445. $order = $this->getLastRealOrder();
  446. if ($order->getId()) {
  447. try {
  448. $quote = $this->quoteRepository->get($order->getQuoteId());
  449. $quote->setIsActive(1)->setReservedOrderId(null);
  450. $this->quoteRepository->save($quote);
  451. $this->replaceQuote($quote)->unsLastRealOrderId();
  452. $this->_eventManager->dispatch('restore_quote', ['order' => $order, 'quote' => $quote]);
  453. return true;
  454. } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
  455. }
  456. }
  457. return false;
  458. }
  459. /**
  460. * @param $isQuoteMasked bool
  461. * @return void
  462. * @codeCoverageIgnore
  463. */
  464. protected function setIsQuoteMasked($isQuoteMasked)
  465. {
  466. $this->isQuoteMasked = $isQuoteMasked;
  467. }
  468. /**
  469. * @return bool|null
  470. * @codeCoverageIgnore
  471. */
  472. protected function isQuoteMasked()
  473. {
  474. return $this->isQuoteMasked;
  475. }
  476. }