123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\BundleImportExport\Model\Export;
- use Magento\Catalog\Model\ResourceModel\Product\Collection;
- use Magento\CatalogImportExport\Model\Export\RowCustomizerInterface;
- use Magento\CatalogImportExport\Model\Import\Product as ImportProductModel;
- use Magento\Bundle\Model\ResourceModel\Selection\Collection as SelectionCollection;
- use Magento\ImportExport\Model\Import as ImportModel;
- use Magento\Catalog\Model\Product\Type\AbstractType;
- use Magento\Store\Model\StoreManagerInterface;
- /**
- * Class RowCustomizer
- */
- class RowCustomizer implements RowCustomizerInterface
- {
- const BUNDLE_PRICE_TYPE_COL = 'bundle_price_type';
- const BUNDLE_SKU_TYPE_COL = 'bundle_sku_type';
- const BUNDLE_PRICE_VIEW_COL = 'bundle_price_view';
- const BUNDLE_WEIGHT_TYPE_COL = 'bundle_weight_type';
- const BUNDLE_VALUES_COL = 'bundle_values';
- const VALUE_FIXED = 'fixed';
- const VALUE_DYNAMIC = 'dynamic';
- const VALUE_PERCENT = 'percent';
- const VALUE_PRICE_RANGE = 'Price range';
- const VALUE_AS_LOW_AS = 'As low as';
- /**
- * Mapping for bundle types
- *
- * @var array
- */
- protected $typeMapping = [
- '0' => self::VALUE_DYNAMIC,
- '1' => self::VALUE_FIXED
- ];
- /**
- * Mapping for price views
- *
- * @var array
- */
- protected $priceViewMapping = [
- '0' => self::VALUE_PRICE_RANGE,
- '1' => self::VALUE_AS_LOW_AS
- ];
- /**
- * Mapping for price types
- *
- * @var array
- */
- protected $priceTypeMapping = [
- '0' => self::VALUE_FIXED,
- '1' => self::VALUE_PERCENT
- ];
- /**
- * Bundle product columns
- *
- * @var array
- */
- protected $bundleColumns = [
- self::BUNDLE_PRICE_TYPE_COL,
- self::BUNDLE_SKU_TYPE_COL,
- self::BUNDLE_PRICE_VIEW_COL,
- self::BUNDLE_WEIGHT_TYPE_COL,
- self::BUNDLE_VALUES_COL
- ];
- /**
- * Product's bundle data
- *
- * @var array
- */
- protected $bundleData = [];
- /**
- * Column name for shipment_type attribute
- *
- * @var string
- */
- private $shipmentTypeColumn = 'bundle_shipment_type';
- /**
- * Mapping for shipment type
- *
- * @var array
- */
- private $shipmentTypeMapping = [
- AbstractType::SHIPMENT_TOGETHER => 'together',
- AbstractType::SHIPMENT_SEPARATELY => 'separately',
- ];
- /**
- * @var \Magento\Bundle\Model\ResourceModel\Option\Collection[]
- */
- private $optionCollections = [];
- /**
- * @var array
- */
- private $storeIdToCode = [];
- /**
- * @var string
- */
- private $optionCollectionCacheKey = '_cache_instance_options_collection';
- /**
- * @var StoreManagerInterface
- */
- private $storeManager;
- /**
- * @param StoreManagerInterface $storeManager
- */
- public function __construct(StoreManagerInterface $storeManager)
- {
- $this->storeManager = $storeManager;
- }
- /**
- * Retrieve list of bundle specific columns
- * @return array
- */
- private function getBundleColumns()
- {
- return array_merge($this->bundleColumns, [$this->shipmentTypeColumn]);
- }
- /**
- * Prepare data for export
- *
- * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection
- * @param int[] $productIds
- * @return $this
- */
- public function prepareData($collection, $productIds)
- {
- $productCollection = clone $collection;
- $productCollection->addAttributeToFilter(
- 'entity_id',
- ['in' => $productIds]
- )->addAttributeToFilter(
- 'type_id',
- ['eq' => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE]
- );
- return $this->populateBundleData($productCollection);
- }
- /**
- * Set headers columns
- *
- * @param array $columns
- * @return array
- */
- public function addHeaderColumns($columns)
- {
- $columns = array_merge($columns, $this->getBundleColumns());
- return $columns;
- }
- /**
- * Add data for export
- *
- * @param array $dataRow
- * @param int $productId
- * @return array
- */
- public function addData($dataRow, $productId)
- {
- if (!empty($this->bundleData[$productId])) {
- $dataRow = array_merge($this->cleanNotBundleAdditionalAttributes($dataRow), $this->bundleData[$productId]);
- }
- return $dataRow;
- }
- /**
- * Calculate the largest links block
- *
- * @param array $additionalRowsCount
- * @param int $productId
- * @return array
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
- */
- public function getAdditionalRowsCount($additionalRowsCount, $productId)
- {
- return $additionalRowsCount;
- }
- /**
- * Populate bundle product data
- *
- * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection
- * @return $this
- */
- protected function populateBundleData($collection)
- {
- foreach ($collection as $product) {
- $id = $product->getEntityId();
- $this->bundleData[$id][self::BUNDLE_PRICE_TYPE_COL] = $this->getTypeValue($product->getPriceType());
- $this->bundleData[$id][$this->shipmentTypeColumn] = $this->getShipmentTypeValue(
- $product->getShipmentType()
- );
- $this->bundleData[$id][self::BUNDLE_SKU_TYPE_COL] = $this->getTypeValue($product->getSkuType());
- $this->bundleData[$id][self::BUNDLE_PRICE_VIEW_COL] = $this->getPriceViewValue($product->getPriceView());
- $this->bundleData[$id][self::BUNDLE_WEIGHT_TYPE_COL] = $this->getTypeValue($product->getWeightType());
- $this->bundleData[$id][self::BUNDLE_VALUES_COL] = $this->getFormattedBundleOptionValues($product);
- }
- return $this;
- }
- /**
- * Retrieve formatted bundle options
- *
- * @param \Magento\Catalog\Model\Product $product
- * @return string
- */
- protected function getFormattedBundleOptionValues(\Magento\Catalog\Model\Product $product): string
- {
- $optionCollections = $this->getProductOptionCollection($product);
- $bundleData = '';
- $optionTitles = $this->getBundleOptionTitles($product);
- foreach ($optionCollections->getItems() as $option) {
- $optionValues = $this->getFormattedOptionValues($option, $optionTitles);
- $bundleData .= $this->getFormattedBundleSelections(
- $optionValues,
- $product->getTypeInstance()
- ->getSelectionsCollection([$option->getId()], $product)
- ->setOrder('position', Collection::SORT_ORDER_ASC)
- );
- }
- return rtrim($bundleData, ImportProductModel::PSEUDO_MULTI_LINE_SEPARATOR);
- }
- /**
- * Retrieve formatted bundle selections
- *
- * @param string $optionValues
- * @param SelectionCollection $selections
- * @return string
- */
- protected function getFormattedBundleSelections($optionValues, SelectionCollection $selections)
- {
- $bundleData = '';
- $selections->addAttributeToSort('position');
- foreach ($selections as $selection) {
- $selectionData = [
- 'sku' => $selection->getSku(),
- 'price' => $selection->getSelectionPriceValue(),
- 'default' => $selection->getIsDefault(),
- 'default_qty' => $selection->getSelectionQty(),
- 'price_type' => $this->getPriceTypeValue($selection->getSelectionPriceType()),
- 'can_change_qty' => $selection->getSelectionCanChangeQty(),
- ];
- $bundleData .= $optionValues
- . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
- . implode(
- ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
- array_map(
- function ($value, $key) {
- return $key . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR . $value;
- },
- $selectionData,
- array_keys($selectionData)
- )
- )
- . ImportProductModel::PSEUDO_MULTI_LINE_SEPARATOR;
- }
- return $bundleData;
- }
- /**
- * Retrieve option value of bundle product
- *
- * @param \Magento\Bundle\Model\Option $option
- * @param string[] $optionTitles
- * @return string
- */
- protected function getFormattedOptionValues(
- \Magento\Bundle\Model\Option $option,
- array $optionTitles = []
- ): string {
- $names = implode(ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_map(
- function ($title, $storeName) {
- return $storeName . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR . $title;
- },
- $optionTitles[$option->getOptionId()],
- array_keys($optionTitles[$option->getOptionId()])
- ));
- return $names . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
- . 'type' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
- . $option->getType() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
- . 'required' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
- . $option->getRequired();
- }
- /**
- * Retrieve bundle type value by code
- *
- * @param string $type
- * @return string
- */
- protected function getTypeValue($type)
- {
- return $this->typeMapping[$type] ?? self::VALUE_DYNAMIC;
- }
- /**
- * Retrieve bundle price view value by code
- *
- * @param string $type
- * @return string
- */
- protected function getPriceViewValue($type)
- {
- return $this->priceViewMapping[$type] ?? self::VALUE_PRICE_RANGE;
- }
- /**
- * Retrieve bundle price type value by code
- *
- * @param string $type
- * @return string
- */
- protected function getPriceTypeValue($type)
- {
- return $this->priceTypeMapping[$type] ?? null;
- }
- /**
- * Retrieve bundle shipment type value by code
- *
- * @param string $type
- * @return string
- */
- private function getShipmentTypeValue($type)
- {
- return $this->shipmentTypeMapping[$type] ?? null;
- }
- /**
- * Remove bundle specified additional attributes as now they are stored in specified columns
- *
- * @param array $dataRow
- * @return array
- */
- protected function cleanNotBundleAdditionalAttributes($dataRow)
- {
- if (!empty($dataRow['additional_attributes'])) {
- $additionalAttributes = $this->parseAdditionalAttributes($dataRow['additional_attributes']);
- $dataRow['additional_attributes'] = $this->getNotBundleAttributes($additionalAttributes);
- }
- return $dataRow;
- }
- /**
- * Retrieve not bundle additional attributes
- *
- * @param array $additionalAttributes
- * @return string
- */
- protected function getNotBundleAttributes($additionalAttributes)
- {
- $filteredAttributes = [];
- foreach ($additionalAttributes as $code => $value) {
- if (!in_array('bundle_' . $code, $this->getBundleColumns())) {
- $filteredAttributes[] = $code . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR . $value;
- }
- }
- return implode(ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $filteredAttributes);
- }
- /**
- * Retrieves additional attributes as array code=>value.
- *
- * @param string $additionalAttributes
- * @return array
- */
- private function parseAdditionalAttributes($additionalAttributes)
- {
- $attributeNameValuePairs = explode(ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalAttributes);
- $preparedAttributes = [];
- $code = '';
- foreach ($attributeNameValuePairs as $attributeData) {
- //process case when attribute has ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR inside its value
- if (strpos($attributeData, ImportProductModel::PAIR_NAME_VALUE_SEPARATOR) === false) {
- if (!$code) {
- continue;
- }
- $preparedAttributes[$code] .= ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR . $attributeData;
- continue;
- }
- list($code, $value) = explode(ImportProductModel::PAIR_NAME_VALUE_SEPARATOR, $attributeData, 2);
- $preparedAttributes[$code] = $value;
- }
- return $preparedAttributes;
- }
- /**
- * Get product options titles.
- *
- * Values for all store views (default) should be specified with 'name' key.
- * If user want to specify value or change existing for non default store views it should be specified with
- * 'name_' prefix and needed store view suffix.
- *
- * For example:
- * - 'name=All store views name' for all store views
- * - 'name_specific_store=Specific store name' for store view with 'specific_store' store code
- *
- * @param \Magento\Catalog\Model\Product $product
- * @return array
- */
- private function getBundleOptionTitles(\Magento\Catalog\Model\Product $product): array
- {
- $optionCollections = $this->getProductOptionCollection($product);
- $optionsTitles = [];
- /** @var \Magento\Bundle\Model\Option $option */
- foreach ($optionCollections->getItems() as $option) {
- $optionsTitles[$option->getId()]['name'] = $option->getTitle();
- }
- $storeIds = $product->getStoreIds();
- if (count($storeIds) > 1) {
- foreach ($storeIds as $storeId) {
- $optionCollections = $this->getProductOptionCollection($product, (int)$storeId);
- /** @var \Magento\Bundle\Model\Option $option */
- foreach ($optionCollections->getItems() as $option) {
- $optionTitle = $option->getTitle();
- if ($optionsTitles[$option->getId()]['name'] != $optionTitle) {
- $optionsTitles[$option->getId()]['name_' . $this->getStoreCodeById((int)$storeId)] =
- $optionTitle;
- }
- }
- }
- }
- return $optionsTitles;
- }
- /**
- * Get product options collection by provided product model.
- *
- * Set given store id to the product if it was defined (default store id will be set if was not).
- *
- * @param \Magento\Catalog\Model\Product $product $product
- * @param int $storeId
- * @return \Magento\Bundle\Model\ResourceModel\Option\Collection
- */
- private function getProductOptionCollection(
- \Magento\Catalog\Model\Product $product,
- int $storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID
- ): \Magento\Bundle\Model\ResourceModel\Option\Collection {
- $productSku = $product->getSku();
- if (!isset($this->optionCollections[$productSku][$storeId])) {
- $product->unsetData($this->optionCollectionCacheKey);
- $product->setStoreId($storeId);
- $this->optionCollections[$productSku][$storeId] = $product->getTypeInstance()
- ->getOptionsCollection($product)
- ->setOrder('position', Collection::SORT_ORDER_ASC);
- }
- return $this->optionCollections[$productSku][$storeId];
- }
- /**
- * Retrieve store code by it's ID.
- *
- * Collect store id in $storeIdToCode[] private variable if it was not initialized earlier.
- *
- * @param int $storeId
- * @return string
- */
- private function getStoreCodeById(int $storeId): string
- {
- if (!isset($this->storeIdToCode[$storeId])) {
- $this->storeIdToCode[$storeId] = $this->storeManager->getStore($storeId)->getCode();
- }
- return $this->storeIdToCode[$storeId];
- }
- }
|