123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- declare(strict_types=1);
- namespace Magento\Quote\Model\ResourceModel\Quote\Item;
- use Magento\Catalog\Api\Data\ProductInterface;
- use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
- use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
- use Magento\Quote\Model\Quote;
- use Magento\Quote\Model\Quote\Item as QuoteItem;
- use Magento\Quote\Model\ResourceModel\Quote\Item as ResourceQuoteItem;
- /**
- * Quote item resource collection
- *
- * @api
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- * @since 100.0.2
- */
- class Collection extends \Magento\Framework\Model\ResourceModel\Db\VersionControl\Collection
- {
- /**
- * Collection quote instance
- *
- * @var \Magento\Quote\Model\Quote
- */
- protected $_quote;
- /**
- * Product Ids array
- *
- * @var int[]
- */
- protected $_productIds = [];
- /**
- * @var \Magento\Quote\Model\ResourceModel\Quote\Item\Option\CollectionFactory
- */
- protected $_itemOptionCollectionFactory;
- /**
- * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
- */
- protected $_productCollectionFactory;
- /**
- * @var \Magento\Quote\Model\Quote\Config
- */
- protected $_quoteConfig;
- /**
- * @var \Magento\Store\Model\StoreManagerInterface|null
- */
- private $storeManager;
- /**
- * @var bool $recollectQuote
- */
- private $recollectQuote = false;
- /**
- * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
- * @param \Psr\Log\LoggerInterface $logger
- * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
- * @param \Magento\Framework\Event\ManagerInterface $eventManager
- * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot
- * @param Option\CollectionFactory $itemOptionCollectionFactory
- * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
- * @param \Magento\Quote\Model\Quote\Config $quoteConfig
- * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
- * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource
- * @param \Magento\Store\Model\StoreManagerInterface|null $storeManager
- * @SuppressWarnings(PHPMD.ExcessiveParameterList)
- */
- public function __construct(
- \Magento\Framework\Data\Collection\EntityFactory $entityFactory,
- \Psr\Log\LoggerInterface $logger,
- \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
- \Magento\Framework\Event\ManagerInterface $eventManager,
- \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot,
- \Magento\Quote\Model\ResourceModel\Quote\Item\Option\CollectionFactory $itemOptionCollectionFactory,
- \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory,
- \Magento\Quote\Model\Quote\Config $quoteConfig,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
- \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null,
- \Magento\Store\Model\StoreManagerInterface $storeManager = null
- ) {
- parent::__construct(
- $entityFactory,
- $logger,
- $fetchStrategy,
- $eventManager,
- $entitySnapshot,
- $connection,
- $resource
- );
- $this->_itemOptionCollectionFactory = $itemOptionCollectionFactory;
- $this->_productCollectionFactory = $productCollectionFactory;
- $this->_quoteConfig = $quoteConfig;
- // Backward compatibility constructor parameters
- $this->storeManager = $storeManager ?:
- \Magento\Framework\App\ObjectManager::getInstance()->get(\Magento\Store\Model\StoreManagerInterface::class);
- }
- /**
- * Initialize resource model
- *
- * @return void
- */
- protected function _construct()
- {
- $this->_init(QuoteItem::class, ResourceQuoteItem::class);
- }
- /**
- * Retrieve store Id (From Quote)
- *
- * @return int
- */
- public function getStoreId(): int
- {
- // Fallback to current storeId if no quote is provided
- // (see https://github.com/magento/magento2/commit/9d3be732a88884a66d667b443b3dc1655ddd0721)
- return $this->_quote === null ?
- (int) $this->storeManager->getStore()->getId() : (int) $this->_quote->getStoreId();
- }
- /**
- * Set Quote object to Collection.
- *
- * @param Quote $quote
- * @return $this
- */
- public function setQuote($quote): self
- {
- $this->_quote = $quote;
- $quoteId = $quote->getId();
- if ($quoteId) {
- $this->addFieldToFilter('quote_id', $quote->getId());
- } else {
- $this->_totalRecords = 0;
- $this->_setIsLoaded(true);
- }
- return $this;
- }
- /**
- * Reset the collection and inner join it to quotes table.
- *
- * Optionally can select items with specified product id only
- *
- * @param string $quotesTableName
- * @param int $productId
- * @return $this
- */
- public function resetJoinQuotes($quotesTableName, $productId = null): self
- {
- $this->getSelect()->reset()->from(
- ['qi' => $this->getResource()->getMainTable()],
- ['item_id', 'qty', 'quote_id']
- )->joinInner(
- ['q' => $quotesTableName],
- 'qi.quote_id = q.entity_id',
- ['store_id', 'items_qty', 'items_count']
- );
- if ($productId) {
- $this->getSelect()->where('qi.product_id = ?', (int)$productId);
- }
- return $this;
- }
- /**
- * After load processing.
- *
- * @return $this
- */
- protected function _afterLoad(): self
- {
- parent::_afterLoad();
- $productIds = [];
- foreach ($this as $item) {
- // Assign parent items
- if ($item->getParentItemId()) {
- $item->setParentItem($this->getItemById($item->getParentItemId()));
- }
- if ($this->_quote) {
- $item->setQuote($this->_quote);
- }
- // Collect quote products ids
- $productIds[] = (int)$item->getProductId();
- }
- $this->_productIds = array_merge($this->_productIds, $productIds);
- $this->removeItemsWithAbsentProducts();
- /**
- * Assign options and products
- */
- $this->_assignOptions();
- $this->_assignProducts();
- $this->resetItemsDataChanged();
- return $this;
- }
- /**
- * Add options to items.
- *
- * @return $this
- */
- protected function _assignOptions(): self
- {
- $itemIds = array_keys($this->_items);
- $optionCollection = $this->_itemOptionCollectionFactory->create()->addItemFilter($itemIds);
- foreach ($this as $item) {
- $item->setOptions($optionCollection->getOptionsByItem($item));
- }
- $productIds = $optionCollection->getProductIds();
- $this->_productIds = array_merge($this->_productIds, $productIds);
- return $this;
- }
- /**
- * Add products to items and item options.
- *
- * @return $this
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- */
- protected function _assignProducts(): self
- {
- \Magento\Framework\Profiler::start('QUOTE:' . __METHOD__, ['group' => 'QUOTE', 'method' => __METHOD__]);
- $productCollection = $this->_productCollectionFactory->create()->setStoreId(
- $this->getStoreId()
- )->addIdFilter(
- $this->_productIds
- )->addAttributeToSelect(
- $this->_quoteConfig->getProductAttributes()
- );
- $this->skipStockStatusFilter($productCollection);
- $productCollection->addOptionsToResult()->addStoreFilter()->addUrlRewrite();
- $this->_eventManager->dispatch(
- 'prepare_catalog_product_collection_prices',
- ['collection' => $productCollection, 'store_id' => $this->getStoreId()]
- );
- $this->_eventManager->dispatch(
- 'sales_quote_item_collection_products_after_load',
- ['collection' => $productCollection]
- );
- foreach ($this as $item) {
- /** @var ProductInterface $product */
- $product = $productCollection->getItemById($item->getProductId());
- $qtyOptions = [];
- if ($product && $this->isValidProduct($product)) {
- $product->setCustomOptions([]);
- $optionProductIds = $this->getOptionProductIds($item, $product, $productCollection);
- foreach ($optionProductIds as $optionProductId) {
- $qtyOption = $item->getOptionByCode('product_qty_' . $optionProductId);
- if ($qtyOption) {
- $qtyOptions[$optionProductId] = $qtyOption;
- }
- }
- } else {
- $item->isDeleted(true);
- $this->recollectQuote = true;
- }
- if (!$item->isDeleted()) {
- $item->setQtyOptions($qtyOptions)->setProduct($product);
- $item->checkData();
- }
- }
- if ($this->recollectQuote && $this->_quote) {
- $this->_quote->collectTotals();
- }
- \Magento\Framework\Profiler::stop('QUOTE:' . __METHOD__);
- return $this;
- }
- /**
- * Get product Ids from option.
- *
- * @param QuoteItem $item
- * @param ProductInterface $product
- * @param ProductCollection $productCollection
- * @return array
- */
- private function getOptionProductIds(
- QuoteItem $item,
- ProductInterface $product,
- ProductCollection $productCollection
- ): array {
- $optionProductIds = [];
- foreach ($item->getOptions() as $option) {
- /**
- * Call type-specific logic for product associated with quote item
- */
- $product->getTypeInstance()->assignProductToOption(
- $productCollection->getItemById($option->getProductId()),
- $option,
- $product
- );
- if (is_object($option->getProduct()) && $option->getProduct()->getId() != $product->getId()) {
- $isValidProduct = $this->isValidProduct($option->getProduct());
- if (!$isValidProduct && !$item->isDeleted()) {
- $item->isDeleted(true);
- $this->recollectQuote = true;
- continue;
- }
- $optionProductIds[$option->getProduct()->getId()] = $option->getProduct()->getId();
- }
- }
- return $optionProductIds;
- }
- /**
- * Check is valid product.
- *
- * @param ProductInterface $product
- * @return bool
- */
- private function isValidProduct(ProductInterface $product): bool
- {
- $result = ($product && (int)$product->getStatus() !== ProductStatus::STATUS_DISABLED);
- return $result;
- }
- /**
- * Prevents adding stock status filter to the collection of products.
- *
- * @param ProductCollection $productCollection
- * @return void
- *
- * @see \Magento\CatalogInventory\Helper\Stock::addIsInStockFilterToCollection
- */
- private function skipStockStatusFilter(ProductCollection $productCollection): void
- {
- $productCollection->setFlag('has_stock_status_filter', true);
- }
- /**
- * Find and remove quote items with non existing products
- *
- * @return void
- */
- private function removeItemsWithAbsentProducts(): void
- {
- if (count($this->_productIds) === 0) {
- return;
- }
- $productCollection = $this->_productCollectionFactory->create()->addIdFilter($this->_productIds);
- $existingProductsIds = $productCollection->getAllIds();
- $absentProductsIds = array_diff($this->_productIds, $existingProductsIds);
- // Remove not existing products from items collection
- if (!empty($absentProductsIds)) {
- foreach ($absentProductsIds as $productIdToExclude) {
- /** @var \Magento\Quote\Model\Quote\Item $quoteItem */
- $quoteItem = $this->getItemByColumnValue('product_id', $productIdToExclude);
- $this->removeItemByKey($quoteItem->getId());
- }
- }
- }
- }
|