Observer.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\ProductAlert\Model;
  7. /**
  8. * ProductAlert observer
  9. *
  10. * @author Magento Core Team <core@magentocommerce.com>
  11. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  12. */
  13. class Observer
  14. {
  15. /**
  16. * Error email template configuration
  17. */
  18. const XML_PATH_ERROR_TEMPLATE = 'catalog/productalert_cron/error_email_template';
  19. /**
  20. * Error email identity configuration
  21. */
  22. const XML_PATH_ERROR_IDENTITY = 'catalog/productalert_cron/error_email_identity';
  23. /**
  24. * 'Send error emails to' configuration
  25. */
  26. const XML_PATH_ERROR_RECIPIENT = 'catalog/productalert_cron/error_email';
  27. /**
  28. * Allow price alert
  29. *
  30. */
  31. const XML_PATH_PRICE_ALLOW = 'catalog/productalert/allow_price';
  32. /**
  33. * Allow stock alert
  34. *
  35. */
  36. const XML_PATH_STOCK_ALLOW = 'catalog/productalert/allow_stock';
  37. /**
  38. * Website collection array
  39. *
  40. * @var array
  41. */
  42. protected $_websites;
  43. /**
  44. * Warning (exception) errors array
  45. *
  46. * @var array
  47. */
  48. protected $_errors = [];
  49. /**
  50. * Catalog data
  51. *
  52. * @var \Magento\Catalog\Helper\Data
  53. */
  54. protected $_catalogData = null;
  55. /**
  56. * Core store config
  57. *
  58. * @var \Magento\Framework\App\Config\ScopeConfigInterface
  59. */
  60. protected $_scopeConfig;
  61. /**
  62. * @var \Magento\Store\Model\StoreManagerInterface
  63. */
  64. protected $_storeManager;
  65. /**
  66. * @var \Magento\ProductAlert\Model\ResourceModel\Price\CollectionFactory
  67. */
  68. protected $_priceColFactory;
  69. /**
  70. * @var \Magento\Customer\Api\CustomerRepositoryInterface
  71. */
  72. protected $customerRepository;
  73. /**
  74. * @var \Magento\Catalog\Api\ProductRepositoryInterface
  75. */
  76. protected $productRepository;
  77. /**
  78. * @var \Magento\Framework\Stdlib\DateTime\DateTimeFactory
  79. */
  80. protected $_dateFactory;
  81. /**
  82. * @var \Magento\ProductAlert\Model\ResourceModel\Stock\CollectionFactory
  83. */
  84. protected $_stockColFactory;
  85. /**
  86. * @var \Magento\Framework\Mail\Template\TransportBuilder
  87. */
  88. protected $_transportBuilder;
  89. /**
  90. * @var \Magento\ProductAlert\Model\EmailFactory
  91. */
  92. protected $_emailFactory;
  93. /**
  94. * @var \Magento\Framework\Translate\Inline\StateInterface
  95. */
  96. protected $inlineTranslation;
  97. /**
  98. * @var ProductSalability
  99. */
  100. protected $productSalability;
  101. /**
  102. * @param \Magento\Catalog\Helper\Data $catalogData
  103. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  104. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  105. * @param \Magento\ProductAlert\Model\ResourceModel\Price\CollectionFactory $priceColFactory
  106. * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository
  107. * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
  108. * @param \Magento\Framework\Stdlib\DateTime\DateTimeFactory $dateFactory
  109. * @param \Magento\ProductAlert\Model\ResourceModel\Stock\CollectionFactory $stockColFactory
  110. * @param \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder
  111. * @param \Magento\ProductAlert\Model\EmailFactory $emailFactory
  112. * @param \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation
  113. * @param ProductSalability|null $productSalability
  114. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  115. */
  116. public function __construct(
  117. \Magento\Catalog\Helper\Data $catalogData,
  118. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  119. \Magento\Store\Model\StoreManagerInterface $storeManager,
  120. \Magento\ProductAlert\Model\ResourceModel\Price\CollectionFactory $priceColFactory,
  121. \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
  122. \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
  123. \Magento\Framework\Stdlib\DateTime\DateTimeFactory $dateFactory,
  124. \Magento\ProductAlert\Model\ResourceModel\Stock\CollectionFactory $stockColFactory,
  125. \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder,
  126. \Magento\ProductAlert\Model\EmailFactory $emailFactory,
  127. \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation,
  128. ProductSalability $productSalability = null
  129. ) {
  130. $this->_catalogData = $catalogData;
  131. $this->_scopeConfig = $scopeConfig;
  132. $this->_storeManager = $storeManager;
  133. $this->_priceColFactory = $priceColFactory;
  134. $this->customerRepository = $customerRepository;
  135. $this->productRepository = $productRepository;
  136. $this->_dateFactory = $dateFactory;
  137. $this->_stockColFactory = $stockColFactory;
  138. $this->_transportBuilder = $transportBuilder;
  139. $this->_emailFactory = $emailFactory;
  140. $this->inlineTranslation = $inlineTranslation;
  141. $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
  142. $this->productSalability = $productSalability ?: $objectManager->get(ProductSalability::class);
  143. }
  144. /**
  145. * Retrieve website collection array
  146. *
  147. * @return array
  148. * @throws \Exception
  149. */
  150. protected function _getWebsites()
  151. {
  152. if ($this->_websites === null) {
  153. try {
  154. $this->_websites = $this->_storeManager->getWebsites();
  155. } catch (\Exception $e) {
  156. $this->_errors[] = $e->getMessage();
  157. throw $e;
  158. }
  159. }
  160. return $this->_websites;
  161. }
  162. /**
  163. * Process price emails
  164. *
  165. * @param \Magento\ProductAlert\Model\Email $email
  166. * @return $this
  167. * @throws \Exception
  168. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  169. * @SuppressWarnings(PHPMD.NPathComplexity)
  170. */
  171. protected function _processPrice(\Magento\ProductAlert\Model\Email $email)
  172. {
  173. $email->setType('price');
  174. foreach ($this->_getWebsites() as $website) {
  175. /* @var $website \Magento\Store\Model\Website */
  176. if (!$website->getDefaultGroup() || !$website->getDefaultGroup()->getDefaultStore()) {
  177. continue;
  178. }
  179. if (!$this->_scopeConfig->getValue(
  180. self::XML_PATH_PRICE_ALLOW,
  181. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  182. $website->getDefaultGroup()->getDefaultStore()->getId()
  183. )
  184. ) {
  185. continue;
  186. }
  187. try {
  188. $collection = $this->_priceColFactory->create()->addWebsiteFilter(
  189. $website->getId()
  190. )->setCustomerOrder();
  191. } catch (\Exception $e) {
  192. $this->_errors[] = $e->getMessage();
  193. throw $e;
  194. }
  195. $previousCustomer = null;
  196. $email->setWebsite($website);
  197. foreach ($collection as $alert) {
  198. $this->setAlertStoreId($alert, $email);
  199. try {
  200. if (!$previousCustomer || $previousCustomer->getId() != $alert->getCustomerId()) {
  201. $customer = $this->customerRepository->getById($alert->getCustomerId());
  202. if ($previousCustomer) {
  203. $email->send();
  204. }
  205. if (!$customer) {
  206. continue;
  207. }
  208. $previousCustomer = $customer;
  209. $email->clean();
  210. $email->setCustomerData($customer);
  211. } else {
  212. $customer = $previousCustomer;
  213. }
  214. $product = $this->productRepository->getById(
  215. $alert->getProductId(),
  216. false,
  217. $website->getDefaultStore()->getId()
  218. );
  219. $product->setCustomerGroupId($customer->getGroupId());
  220. if ($alert->getPrice() > $product->getFinalPrice()) {
  221. $productPrice = $product->getFinalPrice();
  222. $product->setFinalPrice($this->_catalogData->getTaxPrice($product, $productPrice));
  223. $product->setPrice($this->_catalogData->getTaxPrice($product, $product->getPrice()));
  224. $email->addPriceProduct($product);
  225. $alert->setPrice($productPrice);
  226. $alert->setLastSendDate($this->_dateFactory->create()->gmtDate());
  227. $alert->setSendCount($alert->getSendCount() + 1);
  228. $alert->setStatus(1);
  229. $alert->save();
  230. }
  231. } catch (\Exception $e) {
  232. $this->_errors[] = $e->getMessage();
  233. throw $e;
  234. }
  235. }
  236. if ($previousCustomer) {
  237. try {
  238. $email->send();
  239. } catch (\Exception $e) {
  240. $this->_errors[] = $e->getMessage();
  241. throw $e;
  242. }
  243. }
  244. }
  245. return $this;
  246. }
  247. /**
  248. * Process stock emails
  249. *
  250. * @param \Magento\ProductAlert\Model\Email $email
  251. * @return $this
  252. * @throws \Exception
  253. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  254. * @SuppressWarnings(PHPMD.NPathComplexity)
  255. */
  256. protected function _processStock(\Magento\ProductAlert\Model\Email $email)
  257. {
  258. $email->setType('stock');
  259. foreach ($this->_getWebsites() as $website) {
  260. /* @var $website \Magento\Store\Model\Website */
  261. if (!$website->getDefaultGroup() || !$website->getDefaultGroup()->getDefaultStore()) {
  262. continue;
  263. }
  264. if (!$this->_scopeConfig->getValue(
  265. self::XML_PATH_STOCK_ALLOW,
  266. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  267. $website->getDefaultGroup()->getDefaultStore()->getId()
  268. )
  269. ) {
  270. continue;
  271. }
  272. try {
  273. $collection = $this->_stockColFactory->create()->addWebsiteFilter(
  274. $website->getId()
  275. )->addStatusFilter(
  276. 0
  277. )->setCustomerOrder();
  278. } catch (\Exception $e) {
  279. $this->_errors[] = $e->getMessage();
  280. throw $e;
  281. }
  282. $previousCustomer = null;
  283. $email->setWebsite($website);
  284. foreach ($collection as $alert) {
  285. $this->setAlertStoreId($alert, $email);
  286. try {
  287. if (!$previousCustomer || $previousCustomer->getId() != $alert->getCustomerId()) {
  288. $customer = $this->customerRepository->getById($alert->getCustomerId());
  289. if ($previousCustomer) {
  290. $email->send();
  291. }
  292. if (!$customer) {
  293. continue;
  294. }
  295. $previousCustomer = $customer;
  296. $email->clean();
  297. $email->setCustomerData($customer);
  298. } else {
  299. $customer = $previousCustomer;
  300. }
  301. $product = $this->productRepository->getById(
  302. $alert->getProductId(),
  303. false,
  304. $website->getDefaultStore()->getId()
  305. );
  306. $product->setCustomerGroupId($customer->getGroupId());
  307. if ($this->productSalability->isSalable($product, $website)) {
  308. $email->addStockProduct($product);
  309. $alert->setSendDate($this->_dateFactory->create()->gmtDate());
  310. $alert->setSendCount($alert->getSendCount() + 1);
  311. $alert->setStatus(1);
  312. $alert->save();
  313. }
  314. } catch (\Exception $e) {
  315. $this->_errors[] = $e->getMessage();
  316. throw $e;
  317. }
  318. }
  319. if ($previousCustomer) {
  320. try {
  321. $email->send();
  322. } catch (\Exception $e) {
  323. $this->_errors[] = $e->getMessage();
  324. throw $e;
  325. }
  326. }
  327. }
  328. return $this;
  329. }
  330. /**
  331. * Send email to administrator if error
  332. *
  333. * @return $this
  334. */
  335. protected function _sendErrorEmail()
  336. {
  337. if (count($this->_errors)) {
  338. if (!$this->_scopeConfig->getValue(
  339. self::XML_PATH_ERROR_TEMPLATE,
  340. \Magento\Store\Model\ScopeInterface::SCOPE_STORE
  341. )
  342. ) {
  343. return $this;
  344. }
  345. $this->inlineTranslation->suspend();
  346. $transport = $this->_transportBuilder->setTemplateIdentifier(
  347. $this->_scopeConfig->getValue(
  348. self::XML_PATH_ERROR_TEMPLATE,
  349. \Magento\Store\Model\ScopeInterface::SCOPE_STORE
  350. )
  351. )->setTemplateOptions(
  352. [
  353. 'area' => \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE,
  354. 'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID,
  355. ]
  356. )->setTemplateVars(
  357. ['warnings' => join("\n", $this->_errors)]
  358. )->setFrom(
  359. $this->_scopeConfig->getValue(
  360. self::XML_PATH_ERROR_IDENTITY,
  361. \Magento\Store\Model\ScopeInterface::SCOPE_STORE
  362. )
  363. )->addTo(
  364. $this->_scopeConfig->getValue(
  365. self::XML_PATH_ERROR_RECIPIENT,
  366. \Magento\Store\Model\ScopeInterface::SCOPE_STORE
  367. )
  368. )->getTransport();
  369. $transport->sendMessage();
  370. $this->inlineTranslation->resume();
  371. $this->_errors[] = [];
  372. }
  373. return $this;
  374. }
  375. /**
  376. * Run process send product alerts
  377. *
  378. * @return $this
  379. */
  380. public function process()
  381. {
  382. /* @var $email \Magento\ProductAlert\Model\Email */
  383. $email = $this->_emailFactory->create();
  384. $this->_processPrice($email);
  385. $this->_processStock($email);
  386. $this->_sendErrorEmail();
  387. return $this;
  388. }
  389. /**
  390. * Set alert store id.
  391. *
  392. * @param \Magento\ProductAlert\Model\Price|\Magento\ProductAlert\Model\Stock $alert
  393. * @param Email $email
  394. * @return Observer
  395. */
  396. private function setAlertStoreId($alert, \Magento\ProductAlert\Model\Email $email) : Observer
  397. {
  398. $alertStoreId = $alert->getStoreId();
  399. if ($alertStoreId) {
  400. $email->setStoreId((int)$alertStoreId);
  401. }
  402. return $this;
  403. }
  404. }