PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/magento/module-catalog-staging/Model/ResourceModel/Product/Price/SpecialPrice.php

https://bitbucket.org/sergiu-tot-fb/vendors
PHP | 382 lines | 254 code | 30 blank | 98 comment | 21 complexity | 3bd439f86babbf33856c093aef756c61 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, MIT, Apache-2.0
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\CatalogStaging\Model\ResourceModel\Product\Price;
  7. /**
  8. * Special price persistence.
  9. */
  10. class SpecialPrice implements \Magento\Catalog\Api\SpecialPriceInterface
  11. {
  12. /**
  13. * Price storage table.
  14. *
  15. * @var string
  16. */
  17. private $priceTable = 'catalog_product_entity_decimal';
  18. /**
  19. * @var \Magento\Catalog\Model\ResourceModel\Attribute
  20. */
  21. private $attributeResource;
  22. /**
  23. * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface
  24. */
  25. private $attributeRepository;
  26. /**
  27. * Metadata pool.
  28. *
  29. * @var \Magento\Framework\EntityManager\MetadataPool
  30. */
  31. private $metadataPool;
  32. /**
  33. * @var \Magento\Catalog\Model\Product\Price\Validation\Result
  34. */
  35. private $validationResult;
  36. /**
  37. * @var \Magento\CatalogStaging\Model\Product\UpdateScheduler
  38. */
  39. private $updateScheduler;
  40. /**
  41. * Special Price attribute ID.
  42. *
  43. * @var int
  44. */
  45. private $priceAttributeId;
  46. /**
  47. * Items per operation.
  48. *
  49. * @var int
  50. */
  51. private $itemsPerOperation = 500;
  52. /**
  53. * @param \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource
  54. * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository
  55. * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
  56. * @param \Magento\Catalog\Model\Product\Price\Validation\Result $validationResult
  57. * @param \Magento\CatalogStaging\Model\Product\UpdateScheduler $updateScheduler
  58. */
  59. public function __construct(
  60. \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource,
  61. \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository,
  62. \Magento\Framework\EntityManager\MetadataPool $metadataPool,
  63. \Magento\Catalog\Model\Product\Price\Validation\Result $validationResult,
  64. \Magento\CatalogStaging\Model\Product\UpdateScheduler $updateScheduler
  65. ) {
  66. $this->attributeResource = $attributeResource;
  67. $this->attributeRepository = $attributeRepository;
  68. $this->metadataPool = $metadataPool;
  69. $this->validationResult = $validationResult;
  70. $this->updateScheduler = $updateScheduler;
  71. }
  72. /**
  73. * {@inheritdoc}
  74. */
  75. public function get(array $skus)
  76. {
  77. $products = $this->getProductsWithDisabledPreview($skus);
  78. $selectionIds = [];
  79. foreach ($products as $id) {
  80. $selectionIds[] = $id[$this->getEntityLinkField()];
  81. }
  82. $priceTable = $this->attributeResource->getTable($this->priceTable);
  83. $select = $this->attributeResource->getConnection()
  84. ->select()
  85. ->from($priceTable)
  86. ->where($priceTable . '.' . $this->getEntityLinkField() . ' IN (?)', $selectionIds)
  87. ->where($priceTable . '.attribute_id = ?', $this->getPriceAttributeId());
  88. $items = $this->attributeResource->getConnection()->fetchAll($select);
  89. $populatedItems = [];
  90. foreach ($items as $item) {
  91. foreach ($products as $product) {
  92. if ($product[$this->getEntityLinkField()] === $item[$this->getEntityLinkField()]
  93. && isset($item['value'])
  94. ) {
  95. $populatedItems[] = [
  96. $this->getEntityLinkField() => $item[$this->getEntityLinkField()],
  97. 'value' => $item['value'],
  98. 'store_id' => $item['store_id'],
  99. 'sku' => $product['sku'],
  100. 'price_from' => date('Y-m-d H:i:s', $product['created_in']),
  101. 'price_to' => date('Y-m-d H:i:s', $product['updated_in'])
  102. ];
  103. }
  104. }
  105. }
  106. return $populatedItems;
  107. }
  108. /**
  109. * {@inheritdoc}
  110. */
  111. public function update(array $prices)
  112. {
  113. foreach ($this->validationResult->getFailedRowIds() as $failedRowId) {
  114. unset($prices[$failedRowId]);
  115. }
  116. $newPrices = $this->retrieveNewPrices($prices);
  117. $this->createProductUpdates($newPrices);
  118. $connection = $this->attributeResource->getConnection();
  119. $connection->beginTransaction();
  120. try {
  121. $formattedPrices = [];
  122. /** @var \Magento\Catalog\Api\Data\SpecialPriceInterface $price */
  123. foreach ($prices as $price) {
  124. $productPreviews = $this->getProductsWithDisabledPreview([$price->getSku()]);
  125. foreach ($productPreviews as $productPreview) {
  126. if (date('Y-m-d H:i', strtotime($price->getPriceFrom()))
  127. == date('Y-m-d H:i', $productPreview['created_in'])
  128. && date('Y-m-d H:i', strtotime($price->getPriceTo()))
  129. == date('Y-m-d H:i', $productPreview['updated_in'])
  130. ) {
  131. $formattedPrices[] = [
  132. 'attribute_id' => $this->getPriceAttributeId(),
  133. 'store_id' => $price->getStoreId(),
  134. $this->getEntityLinkField() => $productPreview[$this->getEntityLinkField()],
  135. 'value' => $price->getPrice(),
  136. ];
  137. }
  138. }
  139. }
  140. $this->updateItems($formattedPrices, $this->priceTable);
  141. $connection->commit();
  142. } catch (\Exception $e) {
  143. $connection->rollBack();
  144. throw new \Magento\Framework\Exception\CouldNotSaveException(
  145. __('Could not save Prices.'),
  146. $e
  147. );
  148. }
  149. return true;
  150. }
  151. /**
  152. * {@inheritdoc}
  153. */
  154. public function delete(array $prices)
  155. {
  156. $skus = array_unique(
  157. array_map(function ($price) {
  158. return $price->getSku();
  159. }, $prices)
  160. );
  161. $existingPrices = $this->get($skus);
  162. $idsToDelete = [];
  163. foreach ($prices as $key => $price) {
  164. if (!$price->getPriceFrom()) {
  165. $this->validationResult->addFailedItem(
  166. $key,
  167. __(
  168. 'Invalid attribute %fieldName = %fieldValue.',
  169. ['fieldName' => '%fieldName', 'fieldValue' => '%fieldValue']
  170. ),
  171. ['fieldName' => 'Price From', 'fieldValue' => $price->getPriceFrom()]
  172. );
  173. break;
  174. }
  175. $priceExists = false;
  176. foreach ($existingPrices as $existingPrice) {
  177. if ($this->priceSelectionsAreEqual($price, $existingPrice)
  178. && $price->getPrice() == $existingPrice['value']
  179. ) {
  180. $idsToDelete[] = $existingPrice[$this->getEntityLinkField()];
  181. $priceExists = true;
  182. break;
  183. }
  184. }
  185. if (!$priceExists) {
  186. $this->validationResult->addFailedItem(
  187. $key,
  188. __('The requested price is not found.'),
  189. [
  190. 'price' => $price->getPrice(),
  191. 'sku' => $price->getSku(),
  192. 'store_id' => $price->getStoreId(),
  193. 'price_from' => $price->getPriceFrom(),
  194. 'price_to' => $price->getPriceTo()
  195. ]
  196. );
  197. }
  198. }
  199. $connection = $this->attributeResource->getConnection();
  200. $connection->beginTransaction();
  201. try {
  202. foreach (array_chunk($idsToDelete, $this->itemsPerOperation) as $idsBunch) {
  203. $this->attributeResource->getConnection()->delete(
  204. $this->attributeResource->getTable($this->priceTable),
  205. [
  206. 'attribute_id = ?' => $this->getPriceAttributeId(),
  207. $this->getEntityLinkField() . ' IN (?)' => $idsBunch
  208. ]
  209. );
  210. }
  211. $connection->commit();
  212. } catch (\Exception $e) {
  213. $connection->rollBack();
  214. throw new \Magento\Framework\Exception\CouldNotDeleteException(
  215. __('Could not delete Prices'),
  216. $e
  217. );
  218. }
  219. return true;
  220. }
  221. /**
  222. * Get link field.
  223. *
  224. * @return string
  225. */
  226. public function getEntityLinkField()
  227. {
  228. return $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
  229. ->getLinkField();
  230. }
  231. /**
  232. * Create new product updates.
  233. *
  234. * @param array $newPrices
  235. * @return void
  236. */
  237. private function createProductUpdates(array $newPrices)
  238. {
  239. foreach ($newPrices as $key => $newPrice) {
  240. $name = __(
  241. 'Update %1 from %2 to %3.',
  242. $newPrice->getSku(),
  243. $newPrice->getPriceFrom(),
  244. $newPrice->getPriceTo()
  245. );
  246. $stagingData = [
  247. 'name' => $name,
  248. 'start_time' => $newPrice->getPriceFrom(),
  249. 'end_time' => $newPrice->getPriceTo()
  250. ];
  251. try {
  252. $this->updateScheduler->schedule($newPrice->getSku(), $stagingData, $newPrice->getStoreId());
  253. } catch (\Exception $e) {
  254. $this->validationResult->addFailedItem($key, $e->getMessage());
  255. }
  256. }
  257. }
  258. /**
  259. * Update items in database.
  260. *
  261. * @param array $items
  262. * @param string $table
  263. * @return void
  264. */
  265. private function updateItems(array $items, $table)
  266. {
  267. foreach (array_chunk($items, $this->itemsPerOperation) as $itemsBunch) {
  268. $this->attributeResource->getConnection()->insertOnDuplicate(
  269. $this->attributeResource->getTable($table),
  270. $itemsBunch,
  271. ['value']
  272. );
  273. }
  274. }
  275. /**
  276. * Get attribute ID.
  277. *
  278. * @return int
  279. */
  280. private function getPriceAttributeId()
  281. {
  282. if (!$this->priceAttributeId) {
  283. $this->priceAttributeId = $this->attributeRepository->get('special_price')->getAttributeId();
  284. }
  285. return $this->priceAttributeId;
  286. }
  287. /**
  288. * Get products with disabled staging preview.
  289. *
  290. * @param array $skus
  291. * @return array
  292. */
  293. public function getProductsWithDisabledPreview(array $skus)
  294. {
  295. return $this->attributeResource->getConnection()->fetchAll(
  296. $this->attributeResource->getConnection()
  297. ->select()
  298. ->from(
  299. $this->attributeResource->getTable('catalog_product_entity'),
  300. [$this->getEntityLinkField(), 'sku', 'created_in', 'updated_in', 'entity_id']
  301. )
  302. ->where('sku IN (?)', $skus)
  303. ->where('created_in')
  304. ->setPart('disable_staging_preview', true)
  305. );
  306. }
  307. /**
  308. * Retrieve not existing prices.
  309. *
  310. * @param array $prices
  311. * @return array
  312. */
  313. private function retrieveNewPrices(array $prices)
  314. {
  315. $result = [];
  316. $skus = array_unique(
  317. array_map(function ($newPrice) {
  318. return $newPrice->getSku();
  319. }, $prices)
  320. );
  321. $existingPrices = $this->get($skus);
  322. foreach ($prices as $key => $price) {
  323. $priceExists = false;
  324. foreach ($existingPrices as $existingPrice) {
  325. if ($this->priceSelectionsAreEqual($price, $existingPrice)) {
  326. $priceExists = true;
  327. break;
  328. }
  329. }
  330. if (!$priceExists) {
  331. $result[$key] = $price;
  332. }
  333. }
  334. return $result;
  335. }
  336. /**
  337. * Check that prices are equal.
  338. *
  339. * @param \Magento\Catalog\Api\Data\SpecialPriceInterface $price
  340. * @param array $existingPrice
  341. * @return bool
  342. */
  343. private function priceSelectionsAreEqual(
  344. \Magento\Catalog\Api\Data\SpecialPriceInterface $price,
  345. array $existingPrice
  346. ) {
  347. return $price->getSku() == $existingPrice['sku']
  348. && $price->getStoreId() == $existingPrice['store_id']
  349. && $price->getPriceFrom() == $existingPrice['price_from']
  350. && $price->getPriceTo() == $existingPrice['price_to'];
  351. }
  352. }