VariationHandler.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\ConfigurableProduct\Model\Product;
  7. use Magento\Catalog\Model\Product\Type as ProductType;
  8. use Magento\Framework\Exception\LocalizedException;
  9. /**
  10. * Variation Handler
  11. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  12. * @api
  13. * @since 100.0.2
  14. */
  15. class VariationHandler
  16. {
  17. /**
  18. * @var \Magento\Catalog\Model\Product\Gallery\Processor
  19. * @since 100.1.0
  20. */
  21. protected $mediaGalleryProcessor;
  22. /**
  23. * @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable
  24. */
  25. protected $configurableProduct;
  26. /**
  27. * @var \Magento\Eav\Model\Entity\Attribute\SetFactory
  28. */
  29. protected $attributeSetFactory;
  30. /**
  31. * @var \Magento\Eav\Model\EntityFactory
  32. */
  33. protected $entityFactory;
  34. /**
  35. * @var \Magento\Catalog\Model\ProductFactory
  36. */
  37. protected $productFactory;
  38. /**
  39. * @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute[]
  40. */
  41. private $attributes;
  42. /**
  43. * @var \Magento\CatalogInventory\Api\StockConfigurationInterface
  44. * @deprecated 100.1.0
  45. */
  46. protected $stockConfiguration;
  47. /**
  48. * @param Type\Configurable $configurableProduct
  49. * @param \Magento\Eav\Model\Entity\Attribute\SetFactory $attributeSetFactory
  50. * @param \Magento\Eav\Model\EntityFactory $entityFactory
  51. * @param \Magento\Catalog\Model\ProductFactory $productFactory
  52. * @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration
  53. * @param \Magento\Catalog\Model\Product\Gallery\Processor $mediaGalleryProcessor
  54. */
  55. public function __construct(
  56. Type\Configurable $configurableProduct,
  57. \Magento\Eav\Model\Entity\Attribute\SetFactory $attributeSetFactory,
  58. \Magento\Eav\Model\EntityFactory $entityFactory,
  59. \Magento\Catalog\Model\ProductFactory $productFactory,
  60. \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration,
  61. \Magento\Catalog\Model\Product\Gallery\Processor $mediaGalleryProcessor
  62. ) {
  63. $this->configurableProduct = $configurableProduct;
  64. $this->attributeSetFactory = $attributeSetFactory;
  65. $this->entityFactory = $entityFactory;
  66. $this->productFactory = $productFactory;
  67. $this->stockConfiguration = $stockConfiguration;
  68. $this->mediaGalleryProcessor = $mediaGalleryProcessor;
  69. }
  70. /**
  71. * Generate simple products to link with configurable
  72. *
  73. * @param \Magento\Catalog\Model\Product $parentProduct
  74. * @param array $productsData
  75. * @return array
  76. * @throws \Magento\Framework\Exception\LocalizedException
  77. */
  78. public function generateSimpleProducts($parentProduct, $productsData)
  79. {
  80. $generatedProductIds = [];
  81. $this->attributes = null;
  82. $productsData = $this->duplicateImagesForVariations($productsData);
  83. foreach ($productsData as $simpleProductData) {
  84. $newSimpleProduct = $this->productFactory->create();
  85. if (isset($simpleProductData['configurable_attribute'])) {
  86. $configurableAttribute = json_decode($simpleProductData['configurable_attribute'], true);
  87. unset($simpleProductData['configurable_attribute']);
  88. } else {
  89. throw new LocalizedException(
  90. __('Contribution must have attributes specified. Enter attributes and try again.')
  91. );
  92. }
  93. $this->fillSimpleProductData(
  94. $newSimpleProduct,
  95. $parentProduct,
  96. array_merge($simpleProductData, $configurableAttribute)
  97. );
  98. $newSimpleProduct->save();
  99. $generatedProductIds[] = $newSimpleProduct->getId();
  100. }
  101. return $generatedProductIds;
  102. }
  103. /**
  104. * Prepare attribute set comprising all selected configurable attributes
  105. *
  106. * @deprecated 100.1.0
  107. * @param \Magento\Catalog\Model\Product $product
  108. * @return void
  109. */
  110. protected function prepareAttributeSetToBeBaseForNewVariations(\Magento\Catalog\Model\Product $product)
  111. {
  112. $this->prepareAttributeSet($product);
  113. }
  114. /**
  115. * Prepare attribute set comprising all selected configurable attributes
  116. *
  117. * @param \Magento\Catalog\Model\Product $product
  118. * @return void
  119. * @since 100.1.0
  120. */
  121. public function prepareAttributeSet(\Magento\Catalog\Model\Product $product)
  122. {
  123. $attributes = $this->configurableProduct->getUsedProductAttributes($product);
  124. $attributeSetId = $product->getNewVariationsAttributeSetId();
  125. /** @var $attributeSet \Magento\Eav\Model\Entity\Attribute\Set */
  126. $attributeSet = $this->attributeSetFactory->create()->load($attributeSetId);
  127. $attributeSet->addSetInfo(
  128. $this->entityFactory->create()->setType(\Magento\Catalog\Model\Product::ENTITY)->getTypeId(),
  129. $attributes
  130. );
  131. foreach ($attributes as $attribute) {
  132. /* @var $attribute \Magento\Catalog\Model\Entity\Attribute */
  133. if (!$attribute->isInSet($attributeSetId)) {
  134. $attribute->setAttributeSetId(
  135. $attributeSetId
  136. )->setAttributeGroupId(
  137. $attributeSet->getDefaultGroupId($attributeSetId)
  138. )->save();
  139. }
  140. }
  141. }
  142. /**
  143. * Fill simple product data during generation
  144. *
  145. * @param \Magento\Catalog\Model\Product $product
  146. * @param \Magento\Catalog\Model\Product $parentProduct
  147. * @param array $postData
  148. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  149. * @SuppressWarnings(PHPMD.NPathComplexity)
  150. *
  151. * @return void
  152. */
  153. protected function fillSimpleProductData(
  154. \Magento\Catalog\Model\Product $product,
  155. \Magento\Catalog\Model\Product $parentProduct,
  156. $postData
  157. ) {
  158. $typeId = isset($postData['weight']) && !empty($postData['weight'])
  159. ? ProductType::TYPE_SIMPLE
  160. : ProductType::TYPE_VIRTUAL;
  161. $product->setStoreId(
  162. \Magento\Store\Model\Store::DEFAULT_STORE_ID
  163. )->setTypeId(
  164. $typeId
  165. )->setAttributeSetId(
  166. $parentProduct->getNewVariationsAttributeSetId()
  167. );
  168. if ($this->attributes === null) {
  169. $this->attributes = $product->getTypeInstance()->getSetAttributes($product);
  170. }
  171. foreach ($this->attributes as $attribute) {
  172. if ($attribute->getIsUnique() ||
  173. $attribute->getAttributeCode() == 'url_key' ||
  174. $attribute->getFrontend()->getInputType() == 'gallery' ||
  175. $attribute->getFrontend()->getInputType() == 'media_image' ||
  176. !$attribute->getIsVisible()
  177. ) {
  178. continue;
  179. }
  180. $product->setData($attribute->getAttributeCode(), $parentProduct->getData($attribute->getAttributeCode()));
  181. }
  182. $keysFilter = ['item_id', 'product_id', 'stock_id', 'type_id', 'website_id'];
  183. $postData['stock_data'] = array_diff_key((array)$parentProduct->getStockData(), array_flip($keysFilter));
  184. if (!isset($postData['stock_data']['is_in_stock'])) {
  185. $stockStatus = $parentProduct->getQuantityAndStockStatus();
  186. if (isset($stockStatus['is_in_stock'])) {
  187. $postData['stock_data']['is_in_stock'] = $stockStatus['is_in_stock'];
  188. }
  189. }
  190. $postData = $this->processMediaGallery($product, $postData);
  191. $postData['status'] = isset($postData['status'])
  192. ? $postData['status']
  193. : \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED;
  194. $product->addData(
  195. $postData
  196. )->setWebsiteIds(
  197. $parentProduct->getWebsiteIds()
  198. )->setVisibility(
  199. \Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE
  200. );
  201. }
  202. /**
  203. * Duplicate images for variations
  204. *
  205. * @param array $productsData
  206. *
  207. * @return array
  208. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  209. */
  210. public function duplicateImagesForVariations($productsData)
  211. {
  212. $imagesForCopy = [];
  213. foreach ($productsData as $variationId => $simpleProductData) {
  214. if (!isset($simpleProductData['media_gallery']['images'])) {
  215. continue;
  216. }
  217. foreach ($simpleProductData['media_gallery']['images'] as $imageId => $image) {
  218. $image['variation_id'] = $variationId;
  219. if (isset($imagesForCopy[$imageId][0])) {
  220. // skip duplicate image for first product
  221. unset($imagesForCopy[$imageId][0]);
  222. }
  223. $imagesForCopy[$imageId][] = $image;
  224. }
  225. }
  226. foreach ($imagesForCopy as $imageId => $variationImages) {
  227. foreach ($variationImages as $image) {
  228. $file = $image['file'];
  229. $variationId = $image['variation_id'];
  230. $newFile = $this->mediaGalleryProcessor->duplicateImageFromTmp($file);
  231. $productsData[$variationId]['media_gallery']['images'][$imageId]['file'] = $newFile;
  232. foreach ($this->mediaGalleryProcessor->getMediaAttributeCodes() as $attribute) {
  233. if (isset($productsData[$variationId][$attribute])
  234. && $productsData[$variationId][$attribute] == $file
  235. ) {
  236. $productsData[$variationId][$attribute] = $newFile;
  237. }
  238. }
  239. }
  240. }
  241. return $productsData;
  242. }
  243. /**
  244. * Process media gallery for product
  245. *
  246. * @param \Magento\Catalog\Model\Product $product
  247. * @param array $productData
  248. *
  249. * @return array
  250. */
  251. public function processMediaGallery($product, $productData)
  252. {
  253. if (!empty($productData['image'])) {
  254. $image = $productData['image'];
  255. if (!isset($productData['media_gallery']['images'])) {
  256. $productData['media_gallery']['images'] = [];
  257. }
  258. if (false === array_search($image, array_column($productData['media_gallery']['images'], 'file'))) {
  259. $productData['small_image'] = $productData['thumbnail'] = $image;
  260. $productData['media_gallery']['images'][] = [
  261. 'position' => 1,
  262. 'file' => $image,
  263. 'disabled' => 0,
  264. 'label' => '',
  265. ];
  266. }
  267. }
  268. if ($product->getMediaGallery('images') && !empty($productData['media_gallery']['images'])) {
  269. $gallery = array_map(
  270. function ($image) {
  271. $image['removed'] = 1;
  272. return $image;
  273. },
  274. $product->getMediaGallery('images')
  275. );
  276. $gallery = array_merge($productData['media_gallery']['images'], $gallery);
  277. $productData['media_gallery']['images'] = $gallery;
  278. }
  279. return $productData;
  280. }
  281. }