Item.php 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Quote\Model\Quote;
  7. use Magento\Framework\Api\AttributeValueFactory;
  8. use Magento\Framework\Api\ExtensionAttributesFactory;
  9. /**
  10. * Sales Quote Item Model
  11. *
  12. * @api
  13. * @method string getCreatedAt()
  14. * @method \Magento\Quote\Model\Quote\Item setCreatedAt(string $value)
  15. * @method string getUpdatedAt()
  16. * @method \Magento\Quote\Model\Quote\Item setUpdatedAt(string $value)
  17. * @method int getStoreId()
  18. * @method \Magento\Quote\Model\Quote\Item setStoreId(int $value)
  19. * @method int getParentItemId()
  20. * @method \Magento\Quote\Model\Quote\Item setParentItemId(int $value)
  21. * @method int getIsVirtual()
  22. * @method \Magento\Quote\Model\Quote\Item setIsVirtual(int $value)
  23. * @method string getDescription()
  24. * @method \Magento\Quote\Model\Quote\Item setDescription(string $value)
  25. * @method string getAdditionalData()
  26. * @method \Magento\Quote\Model\Quote\Item setAdditionalData(string $value)
  27. * @method int getFreeShipping()
  28. * @method \Magento\Quote\Model\Quote\Item setFreeShipping(int $value)
  29. * @method int getIsQtyDecimal()
  30. * @method \Magento\Quote\Model\Quote\Item setIsQtyDecimal(int $value)
  31. * @method int getNoDiscount()
  32. * @method \Magento\Quote\Model\Quote\Item setNoDiscount(int $value)
  33. * @method float getWeight()
  34. * @method \Magento\Quote\Model\Quote\Item setWeight(float $value)
  35. * @method float getBasePrice()
  36. * @method \Magento\Quote\Model\Quote\Item setBasePrice(float $value)
  37. * @method float getCustomPrice()
  38. * @method float getTaxPercent()
  39. * @method \Magento\Quote\Model\Quote\Item setTaxPercent(float $value)
  40. * @method \Magento\Quote\Model\Quote\Item setTaxAmount(float $value)
  41. * @method \Magento\Quote\Model\Quote\Item setBaseTaxAmount(float $value)
  42. * @method \Magento\Quote\Model\Quote\Item setRowTotal(float $value)
  43. * @method \Magento\Quote\Model\Quote\Item setBaseRowTotal(float $value)
  44. * @method float getRowTotalWithDiscount()
  45. * @method \Magento\Quote\Model\Quote\Item setRowTotalWithDiscount(float $value)
  46. * @method float getRowWeight()
  47. * @method \Magento\Quote\Model\Quote\Item setRowWeight(float $value)
  48. * @method float getBaseTaxBeforeDiscount()
  49. * @method \Magento\Quote\Model\Quote\Item setBaseTaxBeforeDiscount(float $value)
  50. * @method float getTaxBeforeDiscount()
  51. * @method \Magento\Quote\Model\Quote\Item setTaxBeforeDiscount(float $value)
  52. * @method float getOriginalCustomPrice()
  53. * @method \Magento\Quote\Model\Quote\Item setOriginalCustomPrice(float $value)
  54. * @method string getRedirectUrl()
  55. * @method \Magento\Quote\Model\Quote\Item setRedirectUrl(string $value)
  56. * @method float getBaseCost()
  57. * @method \Magento\Quote\Model\Quote\Item setBaseCost(float $value)
  58. * @method \Magento\Quote\Model\Quote\Item setPriceInclTax(float $value)
  59. * @method float getBasePriceInclTax()
  60. * @method \Magento\Quote\Model\Quote\Item setBasePriceInclTax(float $value)
  61. * @method \Magento\Quote\Model\Quote\Item setRowTotalInclTax(float $value)
  62. * @method float getBaseRowTotalInclTax()
  63. * @method \Magento\Quote\Model\Quote\Item setBaseRowTotalInclTax(float $value)
  64. * @method int getGiftMessageId()
  65. * @method \Magento\Quote\Model\Quote\Item setGiftMessageId(int $value)
  66. * @method string getWeeeTaxApplied()
  67. * @method \Magento\Quote\Model\Quote\Item setWeeeTaxApplied(string $value)
  68. * @method float getWeeeTaxAppliedAmount()
  69. * @method \Magento\Quote\Model\Quote\Item setWeeeTaxAppliedAmount(float $value)
  70. * @method float getWeeeTaxAppliedRowAmount()
  71. * @method \Magento\Quote\Model\Quote\Item setWeeeTaxAppliedRowAmount(float $value)
  72. * @method float getBaseWeeeTaxAppliedAmount()
  73. * @method \Magento\Quote\Model\Quote\Item setBaseWeeeTaxAppliedAmount(float $value)
  74. * @method float getBaseWeeeTaxAppliedRowAmnt()
  75. * @method \Magento\Quote\Model\Quote\Item setBaseWeeeTaxAppliedRowAmnt(float $value)
  76. * @method float getWeeeTaxDisposition()
  77. * @method \Magento\Quote\Model\Quote\Item setWeeeTaxDisposition(float $value)
  78. * @method float getWeeeTaxRowDisposition()
  79. * @method \Magento\Quote\Model\Quote\Item setWeeeTaxRowDisposition(float $value)
  80. * @method float getBaseWeeeTaxDisposition()
  81. * @method \Magento\Quote\Model\Quote\Item setBaseWeeeTaxDisposition(float $value)
  82. * @method float getBaseWeeeTaxRowDisposition()
  83. * @method \Magento\Quote\Model\Quote\Item setBaseWeeeTaxRowDisposition(float $value)
  84. * @method float getDiscountTaxCompensationAmount()
  85. * @method \Magento\Quote\Model\Quote\Item setDiscountTaxCompensationAmount(float $value)
  86. * @method float getBaseDiscountTaxCompensationAmount()
  87. * @method \Magento\Quote\Model\Quote\Item setBaseDiscountTaxCompensationAmount(float $value)
  88. * @method null|bool getHasConfigurationUnavailableError()
  89. * @method \Magento\Quote\Model\Quote\Item setHasConfigurationUnavailableError(bool $value)
  90. * @method \Magento\Quote\Model\Quote\Item unsHasConfigurationUnavailableError()
  91. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  92. * @SuppressWarnings(PHPMD.ExcessivePublicCount)
  93. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  94. * @since 100.0.2
  95. */
  96. class Item extends \Magento\Quote\Model\Quote\Item\AbstractItem implements \Magento\Quote\Api\Data\CartItemInterface
  97. {
  98. /**
  99. * Prefix of model events names
  100. *
  101. * @var string
  102. */
  103. protected $_eventPrefix = 'sales_quote_item';
  104. /**
  105. * Parameter name in event
  106. *
  107. * In observe method you can use $observer->getEvent()->getObject() in this case
  108. *
  109. * @var string
  110. */
  111. protected $_eventObject = 'item';
  112. /**
  113. * Quote model object
  114. *
  115. * @var \Magento\Quote\Model\Quote
  116. */
  117. protected $_quote;
  118. /**
  119. * Item options array
  120. *
  121. * @var array
  122. */
  123. protected $_options = [];
  124. /**
  125. * Item options by code cache
  126. *
  127. * @var array
  128. */
  129. protected $_optionsByCode = [];
  130. /**
  131. * Not Represent options
  132. *
  133. * @var array
  134. */
  135. protected $_notRepresentOptions = ['info_buyRequest'];
  136. /**
  137. * Flag stating that options were successfully saved
  138. *
  139. */
  140. protected $_flagOptionsSaved;
  141. /**
  142. * Array of errors associated with this quote item
  143. *
  144. * @var \Magento\Sales\Model\Status\ListStatus
  145. */
  146. protected $_errorInfos;
  147. /**
  148. * @var \Magento\Framework\Locale\FormatInterface
  149. */
  150. protected $_localeFormat;
  151. /**
  152. * @var \Magento\Quote\Model\Quote\Item\OptionFactory
  153. */
  154. protected $_itemOptionFactory;
  155. /**
  156. * @var \Magento\Quote\Model\Quote\Item\Compare
  157. */
  158. protected $quoteItemCompare;
  159. /**
  160. * @var \Magento\CatalogInventory\Api\StockRegistryInterface
  161. * @deprecated 101.0.0
  162. */
  163. protected $stockRegistry;
  164. /**
  165. * Serializer interface instance.
  166. *
  167. * @var \Magento\Framework\Serialize\Serializer\Json
  168. */
  169. private $serializer;
  170. /**
  171. * @param \Magento\Framework\Model\Context $context
  172. * @param \Magento\Framework\Registry $registry
  173. * @param ExtensionAttributesFactory $extensionFactory
  174. * @param AttributeValueFactory $customAttributeFactory
  175. * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
  176. * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
  177. * @param \Magento\Sales\Model\Status\ListFactory $statusListFactory
  178. * @param \Magento\Framework\Locale\FormatInterface $localeFormat
  179. * @param Item\OptionFactory $itemOptionFactory
  180. * @param Item\Compare $quoteItemCompare
  181. * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
  182. * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
  183. * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
  184. * @param array $data
  185. *
  186. * @param \Magento\Framework\Serialize\Serializer\Json $serializer
  187. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  188. */
  189. public function __construct(
  190. \Magento\Framework\Model\Context $context,
  191. \Magento\Framework\Registry $registry,
  192. ExtensionAttributesFactory $extensionFactory,
  193. AttributeValueFactory $customAttributeFactory,
  194. \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
  195. \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency,
  196. \Magento\Sales\Model\Status\ListFactory $statusListFactory,
  197. \Magento\Framework\Locale\FormatInterface $localeFormat,
  198. \Magento\Quote\Model\Quote\Item\OptionFactory $itemOptionFactory,
  199. \Magento\Quote\Model\Quote\Item\Compare $quoteItemCompare,
  200. \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
  201. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  202. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  203. array $data = [],
  204. \Magento\Framework\Serialize\Serializer\Json $serializer = null
  205. ) {
  206. $this->_errorInfos = $statusListFactory->create();
  207. $this->_localeFormat = $localeFormat;
  208. $this->_itemOptionFactory = $itemOptionFactory;
  209. $this->quoteItemCompare = $quoteItemCompare;
  210. $this->stockRegistry = $stockRegistry;
  211. $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()
  212. ->get(\Magento\Framework\Serialize\Serializer\Json::class);
  213. parent::__construct(
  214. $context,
  215. $registry,
  216. $extensionFactory,
  217. $customAttributeFactory,
  218. $productRepository,
  219. $priceCurrency,
  220. $resource,
  221. $resourceCollection,
  222. $data
  223. );
  224. }
  225. /**
  226. * Initialize resource model
  227. *
  228. * @return void
  229. */
  230. protected function _construct()
  231. {
  232. $this->_init(\Magento\Quote\Model\ResourceModel\Quote\Item::class);
  233. }
  234. /**
  235. * Quote Item Before Save prepare data process
  236. *
  237. * @return $this
  238. */
  239. public function beforeSave()
  240. {
  241. parent::beforeSave();
  242. $this->setIsVirtual($this->getProduct()->getIsVirtual());
  243. if ($this->getQuote()) {
  244. $this->setQuoteId($this->getQuote()->getId());
  245. }
  246. return $this;
  247. }
  248. /**
  249. * Retrieve address model
  250. *
  251. * @return \Magento\Quote\Model\Quote\Address
  252. */
  253. public function getAddress()
  254. {
  255. if ($this->getQuote()->getItemsQty() == $this->getQuote()->getVirtualItemsQty()) {
  256. $address = $this->getQuote()->getBillingAddress();
  257. } else {
  258. $address = $this->getQuote()->getShippingAddress();
  259. }
  260. return $address;
  261. }
  262. /**
  263. * Declare quote model object
  264. *
  265. * @param \Magento\Quote\Model\Quote $quote
  266. * @return $this
  267. */
  268. public function setQuote(\Magento\Quote\Model\Quote $quote)
  269. {
  270. $this->_quote = $quote;
  271. $this->setQuoteId($quote->getId());
  272. $this->setStoreId($quote->getStoreId());
  273. return $this;
  274. }
  275. /**
  276. * Retrieve quote model object
  277. *
  278. * @codeCoverageIgnore
  279. *
  280. * @return \Magento\Quote\Model\Quote
  281. */
  282. public function getQuote()
  283. {
  284. return $this->_quote;
  285. }
  286. /**
  287. * Prepare quantity
  288. *
  289. * @param float|int $qty
  290. * @return int|float
  291. */
  292. protected function _prepareQty($qty)
  293. {
  294. $qty = $this->_localeFormat->getNumber($qty);
  295. $qty = $qty > 0 ? $qty : 1;
  296. return $qty;
  297. }
  298. /**
  299. * Adding quantity to quote item
  300. *
  301. * @param float $qty
  302. * @return $this
  303. */
  304. public function addQty($qty)
  305. {
  306. /**
  307. * We can't modify quantity of existing items which have parent
  308. * This qty declared just once during add process and is not editable
  309. */
  310. if (!$this->getParentItem() || !$this->getId()) {
  311. $qty = $this->_prepareQty($qty);
  312. $this->setQtyToAdd($qty);
  313. $this->setQty($this->getQty() + $qty);
  314. }
  315. return $this;
  316. }
  317. /**
  318. * Declare quote item quantity
  319. *
  320. * @param float $qty
  321. * @return $this
  322. */
  323. public function setQty($qty)
  324. {
  325. $qty = $this->_prepareQty($qty);
  326. $oldQty = $this->_getData(self::KEY_QTY);
  327. $this->setData(self::KEY_QTY, $qty);
  328. $this->_eventManager->dispatch('sales_quote_item_qty_set_after', ['item' => $this]);
  329. if ($this->getQuote() && $this->getQuote()->getIgnoreOldQty()) {
  330. return $this;
  331. }
  332. if ($this->getUseOldQty()) {
  333. $this->setData(self::KEY_QTY, $oldQty);
  334. }
  335. return $this;
  336. }
  337. /**
  338. * Retrieve option product with Qty
  339. *
  340. * Return array
  341. * 'qty' => the qty
  342. * 'product' => the product model
  343. *
  344. * @return array
  345. */
  346. public function getQtyOptions()
  347. {
  348. $qtyOptions = $this->getData('qty_options');
  349. if ($qtyOptions === null) {
  350. $productIds = [];
  351. $qtyOptions = [];
  352. foreach ($this->getOptions() as $option) {
  353. /** @var $option \Magento\Quote\Model\Quote\Item\Option */
  354. if (is_object($option->getProduct())
  355. && $option->getProduct()->getId() != $this->getProduct()->getId()
  356. ) {
  357. $productIds[$option->getProduct()->getId()] = $option->getProduct()->getId();
  358. }
  359. }
  360. foreach ($productIds as $productId) {
  361. $option = $this->getOptionByCode('product_qty_' . $productId);
  362. if ($option) {
  363. $qtyOptions[$productId] = $option;
  364. }
  365. }
  366. $this->setData('qty_options', $qtyOptions);
  367. }
  368. return $qtyOptions;
  369. }
  370. /**
  371. * Set option product with Qty
  372. *
  373. * @codeCoverageIgnore
  374. *
  375. * @param array $qtyOptions
  376. * @return $this
  377. */
  378. public function setQtyOptions($qtyOptions)
  379. {
  380. return $this->setData('qty_options', $qtyOptions);
  381. }
  382. /**
  383. * Setup product for quote item
  384. *
  385. * @param \Magento\Catalog\Model\Product $product
  386. * @return $this
  387. */
  388. public function setProduct($product)
  389. {
  390. if ($this->getQuote()) {
  391. $product->setStoreId($this->getQuote()->getStoreId());
  392. $product->setCustomerGroupId($this->getQuote()->getCustomerGroupId());
  393. }
  394. $this->setData('product', $product)
  395. ->setProductId($product->getId())
  396. ->setProductType($product->getTypeId())
  397. ->setSku($this->getProduct()->getSku())
  398. ->setName($product->getName())
  399. ->setWeight($this->getProduct()->getWeight())
  400. ->setTaxClassId($product->getTaxClassId())
  401. ->setBaseCost($product->getCost());
  402. $stockItem = $product->getExtensionAttributes()->getStockItem();
  403. $this->setIsQtyDecimal($stockItem ? $stockItem->getIsQtyDecimal() : false);
  404. $this->_eventManager->dispatch(
  405. 'sales_quote_item_set_product',
  406. ['product' => $product, 'quote_item' => $this]
  407. );
  408. return $this;
  409. }
  410. /**
  411. * Check product representation in item
  412. *
  413. * @param \Magento\Catalog\Model\Product $product
  414. * @return bool
  415. */
  416. public function representProduct($product)
  417. {
  418. $itemProduct = $this->getProduct();
  419. if (!$product || $itemProduct->getId() != $product->getId()) {
  420. return false;
  421. }
  422. /**
  423. * Check maybe product is planned to be a child of some quote item - in this case we limit search
  424. * only within same parent item
  425. */
  426. $stickWithinParent = $product->getStickWithinParent();
  427. if ($stickWithinParent) {
  428. if ($this->getParentItem() !== $stickWithinParent) {
  429. return false;
  430. }
  431. }
  432. // Check options
  433. $itemOptions = $this->getOptionsByCode();
  434. $productOptions = $product->getCustomOptions();
  435. if (!$this->compareOptions($itemOptions, $productOptions)) {
  436. return false;
  437. }
  438. if (!$this->compareOptions($productOptions, $itemOptions)) {
  439. return false;
  440. }
  441. return true;
  442. }
  443. /**
  444. * Check if two options array are identical
  445. * First options array is prerogative
  446. * Second options array checked against first one
  447. *
  448. * @param array $options1
  449. * @param array $options2
  450. * @return bool
  451. */
  452. public function compareOptions($options1, $options2)
  453. {
  454. foreach ($options1 as $option) {
  455. $code = $option->getCode();
  456. if (in_array($code, $this->_notRepresentOptions)) {
  457. continue;
  458. }
  459. if (!isset($options2[$code]) || $options2[$code]->getValue() != $option->getValue()) {
  460. return false;
  461. }
  462. }
  463. return true;
  464. }
  465. /**
  466. * Compare items
  467. *
  468. * @param \Magento\Quote\Model\Quote\Item $item
  469. * @return bool
  470. */
  471. public function compare($item)
  472. {
  473. return $this->quoteItemCompare->compare($this, $item);
  474. }
  475. /**
  476. * Get item product type
  477. *
  478. * @return string
  479. */
  480. public function getProductType()
  481. {
  482. $option = $this->getOptionByCode(self::KEY_PRODUCT_TYPE);
  483. if ($option) {
  484. return $option->getValue();
  485. }
  486. $product = $this->getProduct();
  487. if ($product) {
  488. return $product->getTypeId();
  489. }
  490. // $product should always exist or there will be an error in getProduct()
  491. return $this->_getData(self::KEY_PRODUCT_TYPE);
  492. }
  493. /**
  494. * Return real product type of item
  495. *
  496. * @codeCoverageIgnore
  497. *
  498. * @return string
  499. */
  500. public function getRealProductType()
  501. {
  502. return $this->_getData(self::KEY_PRODUCT_TYPE);
  503. }
  504. /**
  505. * Convert Quote Item to array
  506. *
  507. * @param array $arrAttributes
  508. * @return array
  509. */
  510. public function toArray(array $arrAttributes = [])
  511. {
  512. $data = parent::toArray($arrAttributes);
  513. $product = $this->getProduct();
  514. if ($product) {
  515. $data['product'] = $product->toArray();
  516. }
  517. return $data;
  518. }
  519. /**
  520. * Initialize quote item options
  521. *
  522. * @param array $options
  523. * @return $this
  524. */
  525. public function setOptions($options)
  526. {
  527. if (is_array($options)) {
  528. foreach ($options as $option) {
  529. $this->addOption($option);
  530. }
  531. }
  532. return $this;
  533. }
  534. /**
  535. * Get all item options
  536. *
  537. * @codeCoverageIgnore
  538. *
  539. * @return \Magento\Quote\Model\Quote\Item\Option[]
  540. */
  541. public function getOptions()
  542. {
  543. return $this->_options;
  544. }
  545. /**
  546. * Get all item options as array with codes in array key
  547. *
  548. * @codeCoverageIgnore
  549. *
  550. * @return array
  551. */
  552. public function getOptionsByCode()
  553. {
  554. return $this->_optionsByCode;
  555. }
  556. /**
  557. * Add option to item
  558. *
  559. * @param \Magento\Quote\Model\Quote\Item\Option|\Magento\Framework\DataObject $option
  560. * @return $this
  561. * @throws \Magento\Framework\Exception\LocalizedException
  562. */
  563. public function addOption($option)
  564. {
  565. if (is_array($option)) {
  566. $option = $this->_itemOptionFactory->create()->setData($option)->setItem($this);
  567. } elseif ($option instanceof \Magento\Framework\DataObject &&
  568. !$option instanceof \Magento\Quote\Model\Quote\Item\Option
  569. ) {
  570. $option = $this->_itemOptionFactory->create()->setData(
  571. $option->getData()
  572. )->setProduct(
  573. $option->getProduct()
  574. )->setItem(
  575. $this
  576. );
  577. } elseif ($option instanceof \Magento\Quote\Model\Quote\Item\Option) {
  578. $option->setItem($this);
  579. } else {
  580. throw new \Magento\Framework\Exception\LocalizedException(__('We found an invalid item option format.'));
  581. }
  582. $exOption = $this->getOptionByCode($option->getCode());
  583. if ($exOption) {
  584. $exOption->addData($option->getData());
  585. } else {
  586. $this->_addOptionCode($option);
  587. $this->_options[] = $option;
  588. }
  589. return $this;
  590. }
  591. /**
  592. * Can specify specific actions for ability to change given quote options values
  593. * Example: cataloginventory decimal qty validation may change qty to int,
  594. * so need to change quote item qty option value.
  595. *
  596. * @param \Magento\Framework\DataObject $option
  597. * @param int|float|null $value
  598. * @return $this
  599. */
  600. public function updateQtyOption(\Magento\Framework\DataObject $option, $value)
  601. {
  602. $optionProduct = $option->getProduct();
  603. $options = $this->getQtyOptions();
  604. if (isset($options[$optionProduct->getId()])) {
  605. $options[$optionProduct->getId()]->setValue($value);
  606. }
  607. $this->getProduct()->getTypeInstance()->updateQtyOption(
  608. $this->getOptions(),
  609. $option,
  610. $value,
  611. $this->getProduct()
  612. );
  613. return $this;
  614. }
  615. /**
  616. * Remove option from item options
  617. *
  618. * @param string $code
  619. * @return $this
  620. */
  621. public function removeOption($code)
  622. {
  623. $option = $this->getOptionByCode($code);
  624. if ($option) {
  625. $option->isDeleted(true);
  626. }
  627. return $this;
  628. }
  629. /**
  630. * Register option code
  631. *
  632. * @param \Magento\Quote\Model\Quote\Item\Option $option
  633. * @return $this
  634. * @throws \Magento\Framework\Exception\LocalizedException
  635. */
  636. protected function _addOptionCode($option)
  637. {
  638. if (!isset($this->_optionsByCode[$option->getCode()])) {
  639. $this->_optionsByCode[$option->getCode()] = $option;
  640. } else {
  641. throw new \Magento\Framework\Exception\LocalizedException(
  642. __('An item option with code %1 already exists.', $option->getCode())
  643. );
  644. }
  645. return $this;
  646. }
  647. /**
  648. * Get item option by code
  649. *
  650. * @param string $code
  651. * @return \Magento\Quote\Model\Quote\Item\Option || null
  652. */
  653. public function getOptionByCode($code)
  654. {
  655. if (isset($this->_optionsByCode[$code]) && !$this->_optionsByCode[$code]->isDeleted()) {
  656. return $this->_optionsByCode[$code];
  657. }
  658. return null;
  659. }
  660. /**
  661. * Checks that item model has data changes.
  662. * Call save item options if model isn't need to save in DB
  663. *
  664. * @return boolean
  665. */
  666. protected function _hasModelChanged()
  667. {
  668. if (!$this->hasDataChanges()) {
  669. return false;
  670. }
  671. return $this->_getResource()->hasDataChanged($this);
  672. }
  673. /**
  674. * Save item options
  675. *
  676. * @return $this
  677. */
  678. public function saveItemOptions()
  679. {
  680. foreach ($this->_options as $index => $option) {
  681. if ($option->isDeleted()) {
  682. $option->delete();
  683. unset($this->_options[$index]);
  684. unset($this->_optionsByCode[$option->getCode()]);
  685. } else {
  686. if (!$option->getItem() || !$option->getItem()->getId()) {
  687. $option->setItem($this);
  688. }
  689. $option->save();
  690. }
  691. }
  692. $this->_flagOptionsSaved = true;
  693. // Report to watchers that options were saved
  694. return $this;
  695. }
  696. /**
  697. * Mar option save requirement
  698. *
  699. * @codeCoverageIgnore
  700. *
  701. * @param bool $flag
  702. * @return void
  703. */
  704. public function setIsOptionsSaved($flag)
  705. {
  706. $this->_flagOptionsSaved = $flag;
  707. }
  708. /**
  709. * Were options saved
  710. *
  711. * @codeCoverageIgnore
  712. *
  713. * @return bool
  714. */
  715. public function isOptionsSaved()
  716. {
  717. return $this->_flagOptionsSaved;
  718. }
  719. /**
  720. * Save item options after item saved
  721. *
  722. * @return \Magento\Quote\Model\Quote\Item
  723. */
  724. public function afterSave()
  725. {
  726. $this->saveItemOptions();
  727. return parent::afterSave();
  728. }
  729. /**
  730. * Clone quote item
  731. *
  732. * @return $this
  733. */
  734. public function __clone()
  735. {
  736. parent::__clone();
  737. $options = $this->getOptions();
  738. $this->_quote = null;
  739. $this->_options = [];
  740. $this->_optionsByCode = [];
  741. foreach ($options as $option) {
  742. $this->addOption(clone $option);
  743. }
  744. return $this;
  745. }
  746. /**
  747. * Returns formatted buy request - object, holding request received from
  748. * product view page with keys and options for configured product
  749. *
  750. * @return \Magento\Framework\DataObject
  751. */
  752. public function getBuyRequest()
  753. {
  754. $option = $this->getOptionByCode('info_buyRequest');
  755. $data = $option ? $this->serializer->unserialize($option->getValue()) : [];
  756. $buyRequest = new \Magento\Framework\DataObject($data);
  757. // Overwrite standard buy request qty, because item qty could have changed since adding to quote
  758. $buyRequest->setOriginalQty($buyRequest->getQty())->setQty($this->getQty() * 1);
  759. return $buyRequest;
  760. }
  761. /**
  762. * Sets flag, whether this quote item has some error associated with it.
  763. *
  764. * @param bool $flag
  765. * @return \Magento\Quote\Model\Quote\Item
  766. */
  767. protected function _setHasError($flag)
  768. {
  769. return $this->setData('has_error', $flag);
  770. }
  771. /**
  772. * Sets flag, whether this quote item has some error associated with it.
  773. * When TRUE - also adds 'unknown' error information to list of quote item errors.
  774. * When FALSE - clears whole list of quote item errors.
  775. * It's recommended to use addErrorInfo() instead - to be able to remove error statuses later.
  776. *
  777. * @param bool $flag
  778. * @return $this
  779. * @see addErrorInfo()
  780. */
  781. public function setHasError($flag)
  782. {
  783. if ($flag) {
  784. $this->addErrorInfo();
  785. } else {
  786. $this->_clearErrorInfo();
  787. }
  788. return $this;
  789. }
  790. /**
  791. * Clears list of errors, associated with this quote item.
  792. * Also automatically removes error-flag from oneself.
  793. *
  794. * @return $this
  795. */
  796. protected function _clearErrorInfo()
  797. {
  798. $this->_errorInfos->clear();
  799. $this->_setHasError(false);
  800. return $this;
  801. }
  802. /**
  803. * Adds error information to the quote item.
  804. * Automatically sets error flag.
  805. *
  806. * @param string|null $origin Usually a name of module, that embeds error
  807. * @param int|null $code Error code, unique for origin, that sets it
  808. * @param string|null $message Error message
  809. * @param \Magento\Framework\DataObject|null $additionalData Any additional data, that caller would like to store
  810. * @return $this
  811. */
  812. public function addErrorInfo($origin = null, $code = null, $message = null, $additionalData = null)
  813. {
  814. $this->_errorInfos->addItem($origin, $code, $message, $additionalData);
  815. if ($message !== null) {
  816. $this->setMessage($message);
  817. }
  818. $this->_setHasError(true);
  819. return $this;
  820. }
  821. /**
  822. * Retrieves all error infos, associated with this item
  823. *
  824. * @return array
  825. */
  826. public function getErrorInfos()
  827. {
  828. return $this->_errorInfos->getItems();
  829. }
  830. /**
  831. * Removes error infos, that have parameters equal to passed in $params.
  832. * $params can have following keys (if not set - then any item is good for this key):
  833. * 'origin', 'code', 'message'
  834. *
  835. * @param array $params
  836. * @return $this
  837. */
  838. public function removeErrorInfosByParams($params)
  839. {
  840. $removedItems = $this->_errorInfos->removeItemsByParams($params);
  841. foreach ($removedItems as $item) {
  842. if ($item['message'] !== null) {
  843. $this->removeMessageByText($item['message']);
  844. }
  845. }
  846. if (!$this->_errorInfos->getItems()) {
  847. $this->_setHasError(false);
  848. }
  849. return $this;
  850. }
  851. /**
  852. * @codeCoverageIgnoreStart
  853. *
  854. * {@inheritdoc}
  855. */
  856. public function getItemId()
  857. {
  858. return $this->getData(self::KEY_ITEM_ID);
  859. }
  860. /**
  861. * {@inheritdoc}
  862. */
  863. public function setItemId($itemID)
  864. {
  865. return $this->setData(self::KEY_ITEM_ID, $itemID);
  866. }
  867. /**
  868. * {@inheritdoc}
  869. */
  870. public function getSku()
  871. {
  872. return $this->getData(self::KEY_SKU);
  873. }
  874. /**
  875. * {@inheritdoc}
  876. */
  877. public function setSku($sku)
  878. {
  879. return $this->setData(self::KEY_SKU, $sku);
  880. }
  881. /**
  882. * {@inheritdoc}
  883. */
  884. public function getQty()
  885. {
  886. return $this->getData(self::KEY_QTY);
  887. }
  888. /**
  889. * {@inheritdoc}
  890. */
  891. public function getName()
  892. {
  893. return $this->getData(self::KEY_NAME);
  894. }
  895. /**
  896. * {@inheritdoc}
  897. */
  898. public function setName($name)
  899. {
  900. return $this->setData(self::KEY_NAME, $name);
  901. }
  902. /**
  903. * {@inheritdoc}
  904. */
  905. public function getPrice()
  906. {
  907. return $this->getData(self::KEY_PRICE);
  908. }
  909. /**
  910. * {@inheritdoc}
  911. */
  912. public function setPrice($price)
  913. {
  914. return $this->setData(self::KEY_PRICE, $price);
  915. }
  916. /**
  917. * {@inheritdoc}
  918. */
  919. public function setProductType($productType)
  920. {
  921. return $this->setData(self::KEY_PRODUCT_TYPE, $productType);
  922. }
  923. /**
  924. * {@inheritdoc}
  925. */
  926. public function getQuoteId()
  927. {
  928. return $this->getData(self::KEY_QUOTE_ID);
  929. }
  930. /**
  931. * {@inheritdoc}
  932. */
  933. public function setQuoteId($quoteId)
  934. {
  935. return $this->setData(self::KEY_QUOTE_ID, $quoteId);
  936. }
  937. /**
  938. * Returns product option
  939. *
  940. * @return \Magento\Quote\Api\Data\ProductOptionInterface|null
  941. */
  942. public function getProductOption()
  943. {
  944. return $this->getData(self::KEY_PRODUCT_OPTION);
  945. }
  946. /**
  947. * Sets product option
  948. *
  949. * @param \Magento\Quote\Api\Data\ProductOptionInterface $productOption
  950. * @return $this
  951. */
  952. public function setProductOption(\Magento\Quote\Api\Data\ProductOptionInterface $productOption)
  953. {
  954. return $this->setData(self::KEY_PRODUCT_OPTION, $productOption);
  955. }
  956. //@codeCoverageIgnoreEnd
  957. /**
  958. * {@inheritdoc}
  959. *
  960. * @return \Magento\Quote\Api\Data\CartItemExtensionInterface|null
  961. */
  962. public function getExtensionAttributes()
  963. {
  964. return $this->_getExtensionAttributes();
  965. }
  966. /**
  967. * {@inheritdoc}
  968. *
  969. * @param \Magento\Quote\Api\Data\CartItemExtensionInterface $extensionAttributes
  970. * @return $this
  971. */
  972. public function setExtensionAttributes(\Magento\Quote\Api\Data\CartItemExtensionInterface $extensionAttributes)
  973. {
  974. return $this->_setExtensionAttributes($extensionAttributes);
  975. }
  976. }