Subscriber.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <?php
  2. namespace Dotdigitalgroup\Email\Model\Newsletter;
  3. use Magento\Framework\Exception\LocalizedException;
  4. /**
  5. * Sync subscribers.
  6. */
  7. class Subscriber
  8. {
  9. const STATUS_SUBSCRIBED = 1;
  10. const STATUS_NOT_ACTIVE = 2;
  11. const STATUS_UNSUBSCRIBED = 3;
  12. const STATUS_UNCONFIRMED = 4;
  13. /**
  14. * @var mixed
  15. */
  16. private $start;
  17. /**
  18. * Global number of subscriber updated.
  19. *
  20. * @var int
  21. */
  22. private $countSubscribers = 0;
  23. /**
  24. * @var \Dotdigitalgroup\Email\Helper\Data
  25. */
  26. private $helper;
  27. /**
  28. * @var \Dotdigitalgroup\Email\Model\ContactFactory
  29. */
  30. private $contactFactory;
  31. /**
  32. * @var \Dotdigitalgroup\Email\Model\ResourceModel\Order\CollectionFactory
  33. */
  34. private $orderCollection;
  35. /**
  36. * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
  37. */
  38. private $timezone;
  39. /**
  40. * @var \Dotdigitalgroup\Email\Model\ResourceModel\Contact
  41. */
  42. private $emailContactResource;
  43. /**
  44. * @var SubscriberWithSalesExporter
  45. */
  46. private $subscriberWithSalesExporter;
  47. /**
  48. * @var \Dotdigitalgroup\Email\Model\DateIntervalFactory
  49. */
  50. private $dateIntervalFactory;
  51. /**
  52. * @var SubscriberExporter
  53. */
  54. private $subscriberExporter;
  55. /**
  56. * Subscriber constructor.
  57. *
  58. * @param \Dotdigitalgroup\Email\Model\ContactFactory $contactFactory
  59. * @param \Dotdigitalgroup\Email\Helper\Data $helper
  60. * @param \Dotdigitalgroup\Email\Model\ResourceModel\Order\CollectionFactory $orderCollection
  61. * @param SubscriberExporter $subscriberExporter
  62. * @param SubscriberWithSalesExporter $subscriberWithSalesExporter
  63. * @param \Dotdigitalgroup\Email\Model\ResourceModel\Contact $contactResource
  64. * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone
  65. * @param \Dotdigitalgroup\Email\Model\DateIntervalFactory $dateIntervalFactory
  66. */
  67. public function __construct(
  68. \Dotdigitalgroup\Email\Model\ContactFactory $contactFactory,
  69. \Dotdigitalgroup\Email\Helper\Data $helper,
  70. \Dotdigitalgroup\Email\Model\ResourceModel\Order\CollectionFactory $orderCollection,
  71. \Dotdigitalgroup\Email\Model\Newsletter\SubscriberExporter $subscriberExporter,
  72. \Dotdigitalgroup\Email\Model\Newsletter\SubscriberWithSalesExporter $subscriberWithSalesExporter,
  73. \Dotdigitalgroup\Email\Model\ResourceModel\Contact $contactResource,
  74. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone,
  75. \Dotdigitalgroup\Email\Model\DateIntervalFactory $dateIntervalFactory
  76. ) {
  77. $this->dateIntervalFactory = $dateIntervalFactory;
  78. $this->helper = $helper;
  79. $this->contactFactory = $contactFactory;
  80. $this->orderCollection = $orderCollection;
  81. $this->subscriberExporter = $subscriberExporter;
  82. $this->subscriberWithSalesExporter = $subscriberWithSalesExporter;
  83. $this->emailContactResource = $contactResource;
  84. $this->timezone = $timezone;
  85. }
  86. /**
  87. * @return array
  88. */
  89. public function sync()
  90. {
  91. $response = ['success' => true, 'message' => ''];
  92. $this->start = microtime(true);
  93. $websites = $this->helper->getWebsites(true);
  94. foreach ($websites as $website) {
  95. $websiteId = $website->getId();
  96. //if subscriber is enabled and mapped
  97. $apiEnabled = $this->helper->isEnabled($websiteId);
  98. $addressBook = $this->helper->getSubscriberAddressBook($websiteId);
  99. $subscriberEnabled = $this->helper->isSubscriberSyncEnabled($websiteId);
  100. //enabled and mapped
  101. if ($apiEnabled && $addressBook && $subscriberEnabled) {
  102. //ready to start sync
  103. $numUpdated = $this->exportSubscribersPerWebsite($website);
  104. // show message for any number of customers
  105. if ($numUpdated) {
  106. $response['message'] .= $website->getName() . ', count = ' . $numUpdated;
  107. }
  108. }
  109. }
  110. //sync proccessed
  111. if ($this->countSubscribers) {
  112. $message = '----------- Subscribers sync ----------- : ' . gmdate('H:i:s', microtime(true) - $this->start) .
  113. ', updated = ' . $this->countSubscribers;
  114. $this->helper->log($message);
  115. $message .= $response['message'];
  116. $response['message'] = $message;
  117. }
  118. return $response;
  119. }
  120. /**
  121. * Export subscribers per website.
  122. *
  123. * @param \Magento\Store\Model\Website $website
  124. *
  125. * @return int
  126. *
  127. * @throws LocalizedException
  128. */
  129. public function exportSubscribersPerWebsite($website)
  130. {
  131. $isSubscriberSalesDataEnabled = $this->helper->getWebsiteConfig(
  132. \Dotdigitalgroup\Email\Helper\Config::XML_PATH_CONNECTOR_ENABLE_SUBSCRIBER_SALES_DATA,
  133. $website
  134. );
  135. $updated = 0;
  136. $limit = $this->helper->getSyncLimit($website->getId());
  137. //subscriber collection to import
  138. $emailContactModel = $this->contactFactory->create();
  139. //Customer Subscribers
  140. $subscribersAreCustomers = $emailContactModel->getSubscribersToImport($website, $limit);
  141. //Guest Subscribers
  142. $subscribersAreGuest = $emailContactModel->getSubscribersToImport($website, $limit, false);
  143. $subscribersGuestEmails = $subscribersAreGuest->getColumnValues('email');
  144. $existInSales = [];
  145. //Only if subscriber with sales data enabled
  146. if ($isSubscriberSalesDataEnabled && ! empty($subscribersGuestEmails)) {
  147. $existInSales = $this->checkInSales($subscribersGuestEmails);
  148. }
  149. $emailsNotInSales = array_diff($subscribersGuestEmails, $existInSales);
  150. $customerSubscribers = $subscribersAreCustomers->getColumnValues('email');
  151. $emailsWithNoSaleData = array_merge($emailsNotInSales, $customerSubscribers);
  152. //subscriber that are customer or/and the one that do not exist in sales order table.
  153. $subscribersWithNoSaleData = [];
  154. if (! empty($emailsWithNoSaleData)) {
  155. $subscribersWithNoSaleData = $emailContactModel
  156. ->getSubscribersToImportFromEmails($emailsWithNoSaleData);
  157. }
  158. if (! empty($subscribersWithNoSaleData)) {
  159. $updated += $this->subscriberExporter->exportSubscribers(
  160. $website,
  161. $subscribersWithNoSaleData
  162. );
  163. //add updated number for the website
  164. $this->countSubscribers += $updated;
  165. }
  166. //subscriber that are guest and also exist in sales order table.
  167. $subscribersWithSaleData = [];
  168. if (! empty($existInSales)) {
  169. $subscribersWithSaleData = $emailContactModel->getSubscribersToImportFromEmails($existInSales);
  170. }
  171. if (! empty($subscribersWithSaleData)) {
  172. $updated += $this->subscriberWithSalesExporter->exportSubscribersWithSales(
  173. $website,
  174. $subscribersWithSaleData
  175. );
  176. //add updated number for the website
  177. $this->countSubscribers += $updated;
  178. }
  179. return $updated;
  180. }
  181. /**
  182. * Check emails exist in sales order table.
  183. *
  184. * @param array $emails
  185. *
  186. * @return array
  187. */
  188. public function checkInSales($emails)
  189. {
  190. return $this->orderCollection->create()
  191. ->checkInSales($emails);
  192. }
  193. /**
  194. * Un-subscribe suppressed contacts.
  195. *
  196. * @return array
  197. */
  198. public function unsubscribe()
  199. {
  200. $result['customers'] = 0;
  201. $suppressedEmails = [];
  202. /**
  203. * Sync all suppressed for each store
  204. */
  205. $websites = $this->helper->getWebsites(true);
  206. foreach ($websites as $website) {
  207. //not enabled
  208. if (! $this->helper->isEnabled($website)) {
  209. continue;
  210. }
  211. $suppressedEmails = $this->getSuppressedContacts($website);
  212. }
  213. //Mark suppressed contacts
  214. if (! empty($suppressedEmails)) {
  215. $result['customers'] = $this->emailContactResource->unsubscribe($suppressedEmails);
  216. }
  217. return $result;
  218. }
  219. /**
  220. * @param \Magento\Store\Api\Data\WebsiteInterface $website
  221. * @return array
  222. */
  223. private function getSuppressedContacts($website)
  224. {
  225. $limit = 5;
  226. $maxToSelect = 1000;
  227. $skip = $i = 0;
  228. $contacts = [];
  229. $suppressedEmails = [];
  230. $date = $this->timezone->date()->sub($this->dateIntervalFactory->create(['interval_spec' => 'PT24H']));
  231. $dateString = $date->format(\DateTime::W3C);
  232. $client = $this->helper->getWebsiteApiClient($website);
  233. //there is a maximum of request we need to loop to get more suppressed contacts
  234. for ($i=0; $i<= $limit; $i++) {
  235. $apiContacts = $client->getContactsSuppressedSinceDate($dateString, $maxToSelect, $skip);
  236. // skip no more contacts or the api request failed
  237. if (empty($apiContacts) || isset($apiContacts->message)) {
  238. break;
  239. }
  240. $contacts = array_merge($contacts, $apiContacts);
  241. $skip += 1000;
  242. }
  243. // Contacts to un-subscribe
  244. foreach ($contacts as $apiContact) {
  245. if (isset($apiContact->suppressedContact)) {
  246. $suppressedContactEmail = $apiContact->suppressedContact->email;
  247. if (!in_array($suppressedContactEmail, $suppressedEmails, true)) {
  248. $suppressedEmails[] = $suppressedContactEmail;
  249. }
  250. }
  251. }
  252. return $suppressedEmails;
  253. }
  254. }