12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022 |
- <?php
- namespace Dotdigitalgroup\Email\Model\Sales;
- use Dotdigitalgroup\Email\Model\AbandonedCart\PendingContactUpdater;
- use Dotdigitalgroup\Email\Model\ResourceModel\Campaign;
- /**
- * Customer and guest Abandoned Carts.
- *
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- */
- class Quote
- {
- //customer
- const XML_PATH_LOSTBASKET_CUSTOMER_ENABLED_1 = 'abandoned_carts/customers/enabled_1';
- const XML_PATH_LOSTBASKET_CUSTOMER_ENABLED_2 = 'abandoned_carts/customers/enabled_2';
- const XML_PATH_LOSTBASKET_CUSTOMER_ENABLED_3 = 'abandoned_carts/customers/enabled_3';
- const XML_PATH_LOSTBASKET_CUSTOMER_INTERVAL_1 = 'abandoned_carts/customers/send_after_1';
- const XML_PATH_LOSTBASKET_CUSTOMER_INTERVAL_2 = 'abandoned_carts/customers/send_after_2';
- const XML_PATH_LOSTBASKET_CUSTOMER_INTERVAL_3 = 'abandoned_carts/customers/send_after_3';
- const XML_PATH_LOSTBASKET_CUSTOMER_CAMPAIGN_1 = 'abandoned_carts/customers/campaign_1';
- const XML_PATH_LOSTBASKET_CUSTOMER_CAMPAIGN_2 = 'abandoned_carts/customers/campaign_2';
- const XML_PATH_LOSTBASKET_CUSTOMER_CAMPAIGN_3 = 'abandoned_carts/customers/campaign_3';
- //guest
- const XML_PATH_LOSTBASKET_GUEST_ENABLED_1 = 'abandoned_carts/guests/enabled_1';
- const XML_PATH_LOSTBASKET_GUEST_ENABLED_2 = 'abandoned_carts/guests/enabled_2';
- const XML_PATH_LOSTBASKET_GUEST_ENABLED_3 = 'abandoned_carts/guests/enabled_3';
- const XML_PATH_LOSTBASKET_GUEST_INTERVAL_1 = 'abandoned_carts/guests/send_after_1';
- const XML_PATH_LOSTBASKET_GUEST_INTERVAL_2 = 'abandoned_carts/guests/send_after_2';
- const XML_PATH_LOSTBASKET_GUEST_INTERVAL_3 = 'abandoned_carts/guests/send_after_3';
- const XML_PATH_LOSTBASKET_GUEST_CAMPAIGN_1 = 'abandoned_carts/guests/campaign_1';
- const XML_PATH_LOSTBASKET_GUEST_CAMPAIGN_2 = 'abandoned_carts/guests/campaign_2';
- const XML_PATH_LOSTBASKET_GUEST_CAMPAIGN_3 = 'abandoned_carts/guests/campaign_3';
- const CUSTOMER_LOST_BASKET_ONE = 1;
- const CUSTOMER_LOST_BASKET_TWO = 2;
- const CUSTOMER_LOST_BASKET_THREE = 3;
- const GUEST_LOST_BASKET_ONE = 1;
- const GUEST_LOST_BASKET_TWO = 2;
- const GUEST_LOST_BASKET_THREE = 3;
- const STATUS_PENDING = 'PendingOptIn';
- const STATUS_CONFIRMED = 'Confirmed';
- const STATUS_SENT = 'Sent';
- const STATUS_EXPIRED = 'Expired';
- /**
- * @var \Dotdigitalgroup\Email\Model\AbandonedFactory
- */
- public $abandonedFactory;
- /**
- * @var \Dotdigitalgroup\Email\Model\ResourceModel\Abandoned\CollectionFactory
- */
- public $abandonedCollectionFactory;
- /**
- * @var \Magento\Quote\Model\ResourceModel\Quote\CollectionFactory
- */
- public $quoteCollectionFactory;
- /**
- * @var Campaign
- */
- private $campaignResource;
- /**
- * @var \Dotdigitalgroup\Email\Helper\Data
- */
- private $helper;
- /**
- * @var \Magento\Framework\App\Config\ScopeConfigInterface
- */
- private $scopeConfig;
- /**
- * @var \Dotdigitalgroup\Email\Model\ResourceModel\Order\CollectionFactory
- */
- private $orderCollection;
- /**
- * @var \Dotdigitalgroup\Email\Model\CampaignFactory
- */
- private $campaignFactory;
- /**
- * @var \Dotdigitalgroup\Email\Model\ResourceModel\Campaign\CollectionFactory
- */
- private $campaignCollection;
- /**
- * @var \Dotdigitalgroup\Email\Model\RulesFactory
- */
- private $rulesFactory;
- /**
- * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
- */
- private $timeZone;
- /**
- * Total number of customers found.
- * @var int
- */
- public $totalCustomers = 0;
- /**
- * Total number of guest found.
- * @var int
- */
- public $totalGuests = 0;
- /**
- * @var \Dotdigitalgroup\Email\Model\ResourceModel\Abandoned
- */
- private $abandonedResource;
- /**
- * @var \Dotdigitalgroup\Email\Model\DateIntervalFactory
- */
- private $dateIntervalFactory;
- /**
- * @var PendingContactUpdater
- */
- private $acPendingContactUpdater;
- /**
- * Quote constructor.
- *
- * @param \Dotdigitalgroup\Email\Model\AbandonedFactory $abandonedFactory
- * @param \Dotdigitalgroup\Email\Model\RulesFactory $rulesFactory
- * @param Campaign $campaignResource
- * @param \Dotdigitalgroup\Email\Model\CampaignFactory $campaignFactory
- * @param \Dotdigitalgroup\Email\Model\ResourceModel\Abandoned $abandonedResource
- * @param \Magento\Quote\Model\ResourceModel\Quote\CollectionFactory $quoteCollectionFactory
- * @param \Dotdigitalgroup\Email\Model\ResourceModel\Order\CollectionFactory $collectionFactory
- * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone
- * @param \Dotdigitalgroup\Email\Model\DateIntervalFactory $dateIntervalFactory
- * @param PendingContactUpdater $pendingContactUpdater
- */
- public function __construct(
- \Dotdigitalgroup\Email\Model\AbandonedFactory $abandonedFactory,
- \Dotdigitalgroup\Email\Model\RulesFactory $rulesFactory,
- Campaign $campaignResource,
- \Dotdigitalgroup\Email\Model\CampaignFactory $campaignFactory,
- \Dotdigitalgroup\Email\Model\ResourceModel\Abandoned $abandonedResource,
- \Magento\Quote\Model\ResourceModel\Quote\CollectionFactory $quoteCollectionFactory,
- \Dotdigitalgroup\Email\Model\ResourceModel\Order\CollectionFactory $collectionFactory,
- \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone,
- \Dotdigitalgroup\Email\Model\DateIntervalFactory $dateIntervalFactory,
- PendingContactUpdater $pendingContactUpdater
- ) {
- $this->timeZone = $timezone;
- $this->rulesFactory = $rulesFactory;
- $this->campaignFactory = $campaignFactory;
- $this->helper = $abandonedResource->helper;
- $this->abandonedFactory = $abandonedFactory;
- $this->campaignResource = $campaignResource;
- $this->orderCollection = $collectionFactory;
- $this->abandonedResource = $abandonedResource;
- $this->dateIntervalFactory = $dateIntervalFactory;
- $this->scopeConfig = $this->helper->getScopeConfig();
- $this->quoteCollectionFactory = $quoteCollectionFactory;
- $this->campaignCollection = $campaignFactory->create()->campaignCollection;
- $this->abandonedCollectionFactory = $abandonedFactory->create()->abandonedCollectionFactory;
- $this->acPendingContactUpdater = $pendingContactUpdater;
- }
- /**
- * Process abandoned carts.
- *
- * @return array
- */
- public function processAbandonedCarts()
- {
- $result = [];
- $stores = $this->helper->getStores();
- $this->acPendingContactUpdater->update();
- foreach ($stores as $store) {
- $storeId = $store->getId();
- $websiteId = $store->getWebsiteId();
- $result = $this->processAbandonedCartsForCustomers($storeId, $websiteId, $result);
- $result = $this->processAbandonedCartsForGuests($storeId, $websiteId, $result);
- }
- return $result;
- }
- /**
- * Process abandoned carts for customer
- *
- * @param int $storeId
- * @param int $websiteId
- * @param array $result
- *
- * @return array
- */
- private function processAbandonedCartsForCustomers($storeId, $websiteId, $result)
- {
- $secondCustomerEnabled = $this->isLostBasketCustomerEnabled(self::CUSTOMER_LOST_BASKET_TWO, $storeId);
- $thirdCustomerEnabled = $this->isLostBasketCustomerEnabled(self::CUSTOMER_LOST_BASKET_THREE, $storeId);
- //first customer
- if ($this->isLostBasketCustomerEnabled(self::CUSTOMER_LOST_BASKET_ONE, $storeId) ||
- $secondCustomerEnabled ||
- $thirdCustomerEnabled
- ) {
- $result[$storeId]['firstCustomer'] = $this->processCustomerFirstAbandonedCart($storeId);
- }
- //second customer
- if ($secondCustomerEnabled) {
- $result[$storeId]['secondCustomer'] = $this->processExistingAbandonedCart(
- $this->getLostBasketCustomerCampaignId(self::CUSTOMER_LOST_BASKET_TWO, $storeId),
- $storeId,
- $websiteId,
- self::CUSTOMER_LOST_BASKET_TWO
- );
- }
- //third customer
- if ($thirdCustomerEnabled) {
- $result[$storeId]['thirdCustomer'] = $this->processExistingAbandonedCart(
- $this->getLostBasketCustomerCampaignId(self::CUSTOMER_LOST_BASKET_THREE, $storeId),
- $storeId,
- $websiteId,
- self::CUSTOMER_LOST_BASKET_THREE
- );
- }
- return $result;
- }
- /**
- * Process abandoned carts for guests
- *
- * @param int $storeId
- * @param int $websiteId
- * @param array $result
- *
- * @return array
- */
- private function processAbandonedCartsForGuests($storeId, $websiteId, $result)
- {
- $secondGuestEnabled = $this->isLostBasketGuestEnabled(self::GUEST_LOST_BASKET_TWO, $storeId);
- $thirdGuestEnabled = $this->isLostBasketGuestEnabled(self::GUEST_LOST_BASKET_THREE, $storeId);
- //first guest
- if ($this->isLostBasketGuestEnabled(self::GUEST_LOST_BASKET_ONE, $storeId) ||
- $secondGuestEnabled ||
- $thirdGuestEnabled
- ) {
- $result[$storeId]['firstGuest'] = $this->processGuestFirstAbandonedCart($storeId);
- }
- //second guest
- if ($secondGuestEnabled) {
- $result[$storeId]['secondGuest'] = $this->processExistingAbandonedCart(
- $this->getLostBasketGuestCampaignId(self::GUEST_LOST_BASKET_TWO, $storeId),
- $storeId,
- $websiteId,
- self::GUEST_LOST_BASKET_TWO,
- true
- );
- }
- //third guest
- if ($thirdGuestEnabled) {
- $result[$storeId]['thirdGuest'] = $this->processExistingAbandonedCart(
- $this->getLostBasketGuestCampaignId(self::GUEST_LOST_BASKET_THREE, $storeId),
- $storeId,
- $websiteId,
- self::GUEST_LOST_BASKET_THREE,
- true
- );
- }
- return $result;
- }
- /**
- * @param int $num
- * @param int $storeId
- *
- * @return bool
- */
- public function isLostBasketCustomerEnabled($num, $storeId)
- {
- return $this->scopeConfig->isSetFlag(
- constant('self::XML_PATH_LOSTBASKET_CUSTOMER_ENABLED_' . $num),
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $storeId
- );
- }
- /**
- * @param int $num
- * @param int $storeId
- *
- * @return mixed
- */
- public function getLostBasketCustomerInterval($num, $storeId)
- {
- return $this->scopeConfig->getValue(
- constant('self::XML_PATH_LOSTBASKET_CUSTOMER_INTERVAL_' . $num),
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $storeId
- );
- }
- /**
- * @param string|null $from
- * @param string|null $to
- * @param bool $guest
- * @param int $storeId
- * @return \Magento\Quote\Model\ResourceModel\Quote\Collection|\Magento\Sales\Model\ResourceModel\Order\Collection
- * @throws \Magento\Framework\Exception\NoSuchEntityException
- */
- public function getStoreQuotes($from = null, $to = null, $guest = false, $storeId = 0)
- {
- $updated = [
- 'from' => $from,
- 'to' => $to,
- 'date' => true,
- ];
- $salesCollection = $this->orderCollection->create()
- ->getStoreQuotes($storeId, $updated, $guest);
- //process rules on collection
- $ruleModel = $this->rulesFactory->create();
- $websiteId = $this->helper->storeManager->getStore($storeId)
- ->getWebsiteId();
- $salesCollection = $ruleModel->process(
- $salesCollection,
- \Dotdigitalgroup\Email\Model\Rules::ABANDONED,
- $websiteId
- );
- return $salesCollection;
- }
- /**
- * @param int $num
- * @param int $storeId
- *
- * @return mixed
- */
- public function getLostBasketCustomerCampaignId($num, $storeId)
- {
- return $this->scopeConfig->getValue(
- constant('self::XML_PATH_LOSTBASKET_CUSTOMER_CAMPAIGN_' . $num),
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $storeId
- );
- }
- /**
- * Send email only if the interval limit passed, no emails during this interval.
- * Return false for any found for this period.
- *
- * @param string $email
- * @param int $storeId
- *
- * @return bool
- */
- public function isIntervalCampaignFound($email, $storeId)
- {
- $cartLimit = $this->scopeConfig->getValue(
- \Dotdigitalgroup\Email\Helper\Config::XML_PATH_CONNECTOR_ABANDONED_CART_LIMIT,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $storeId
- );
- //no limit is set skip
- if (! $cartLimit) {
- return false;
- }
- $fromTime = $this->timeZone->scopeDate($storeId, 'now', true);
- $toTime = clone $fromTime;
- $interval = $this->dateIntervalFactory->create(
- ['interval_spec' => sprintf('PT%sH', $cartLimit)]
- );
- $fromTime->sub($interval);
- $fromDate = $fromTime->getTimestamp();
- $toDate = $toTime->getTimestamp();
- $updated = [
- 'from' => $fromDate,
- 'to' => $toDate,
- 'date' => true,
- ];
- //total campaigns sent for this interval of time
- $campaignLimit = $this->campaignCollection->create()
- ->getNumberOfCampaignsForContactByInterval($email, $updated);
- //found campaign
- if ($campaignLimit) {
- return true;
- }
- return false;
- }
- /**
- * @param int $num
- * @param int $storeId
- *
- * @return bool
- */
- public function isLostBasketGuestEnabled($num, $storeId)
- {
- return $this->scopeConfig->isSetFlag(
- constant('self::XML_PATH_LOSTBASKET_GUEST_ENABLED_' . $num),
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $storeId
- );
- }
- /**
- * @param int $num
- * @param int $storeId
- *
- * @return mixed
- */
- public function getLostBasketSendAfterForGuest($num, $storeId)
- {
- return $this->scopeConfig->getValue(
- constant('self::XML_PATH_LOSTBASKET_GUEST_INTERVAL_' . $num),
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $storeId
- );
- }
- /**
- * @param int $num
- * @param int $storeId
- *
- * @return mixed
- */
- public function getLostBasketGuestCampaignId($num, $storeId)
- {
- return $this->scopeConfig->getValue(
- constant('self::XML_PATH_LOSTBASKET_GUEST_CAMPAIGN_' . $num),
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $storeId
- );
- }
- /**
- * @param int $storeId
- * @param int $num
- *
- * @return \DateInterval
- */
- private function getInterval($storeId, $num)
- {
- if ($num == 1) {
- $minutes = $this->getLostBasketCustomerInterval($num, $storeId);
- $interval = $this->dateIntervalFactory->create(
- ['interval_spec' => sprintf('PT%sM', $minutes)]
- );
- } else {
- $hours = (int)$this->getLostBasketCustomerInterval($num, $storeId);
- $interval = $this->dateIntervalFactory->create(
- ['interval_spec' => sprintf('PT%sH', $hours)]
- );
- }
- return $interval;
- }
- /**
- * @param int $storeId
- * @param int $num
- *
- * @return \DateInterval
- */
- protected function getSendAfterIntervalForGuest($storeId, $num)
- {
- $timeInterval = $this->getLostBasketSendAfterForGuest($num, $storeId);
- //for the first cart which use the minutes
- if ($num == 1) {
- $interval = $this->dateIntervalFactory->create(
- ['interval_spec' => sprintf('PT%sM', $timeInterval)]
- );
- } else {
- $interval = $this->dateIntervalFactory->create(
- ['interval_spec' => sprintf('PT%sH', $timeInterval)]
- );
- }
- return $interval;
- }
- /**
- * @param int $storeId
- * @return int|string
- */
- private function processCustomerFirstAbandonedCart($storeId)
- {
- $abandonedNum = 1;
- $interval = $this->getInterval($storeId, $abandonedNum);
- $fromTime = new \DateTime('now', new \DateTimezone('UTC'));
- $fromTime->sub($interval);
- $toTime = clone $fromTime;
- $fromTime->sub($this->dateIntervalFactory->create(['interval_spec' => 'PT5M']));
- $fromDate = $fromTime->format('Y-m-d H:i:s');
- $toDate = $toTime->format('Y-m-d H:i:s');
- //active quotes
- $quoteCollection = $this->getStoreQuotes($fromDate, $toDate, false, $storeId);
- //found abandoned carts
- if ($quoteCollection->getSize()) {
- $this->helper->log('Customer AC 1 ' . $fromDate . ' - ' . $toDate);
- }
- //campaign id for customers
- $campaignId = $this->getLostBasketCustomerCampaignId($abandonedNum, $storeId);
- $result = $this->createCustomerFirstAbandonedCart($quoteCollection, $storeId, $campaignId);
- $result += $this->processConfirmedCustomerAbandonedCart($storeId, $campaignId);
- return $result;
- }
- /**
- * @param $quoteCollection
- * @param $storeId
- * @param $campaignId
- * @param $result
- *
- * @return int
- * @throws \Magento\Framework\Exception\NoSuchEntityException
- */
- private function createCustomerFirstAbandonedCart($quoteCollection, $storeId, $campaignId)
- {
- $result = 0;
- foreach ($quoteCollection as $quote) {
- $websiteId = $this->helper->storeManager->getStore($storeId)->getWebsiteId();
- if (! $this->updateDataFieldAndCreateAc($quote, $websiteId)) {
- continue;
- }
- //send campaign; check if valid to be sent
- if ($this->isLostBasketCustomerEnabled(self::CUSTOMER_LOST_BASKET_ONE, $storeId)) {
- $this->sendEmailCampaign(
- $quote->getCustomerEmail(),
- $quote,
- $campaignId,
- self::CUSTOMER_LOST_BASKET_ONE,
- $websiteId
- );
- }
- $this->totalCustomers++;
- $result = $this->totalCustomers;
- }
- return $result;
- }
- /**
- * @param \Magento\Quote\Model\Quote $quote
- * @param int $websiteId
- *
- * @return bool
- */
- private function updateDataFieldAndCreateAc($quote, $websiteId)
- {
- $quoteId = $quote->getId();
- $items = $quote->getAllItems();
- $email = $quote->getCustomerEmail();
- $itemIds = $this->getQuoteItemIds($items);
- $abandonedModel = $this->abandonedFactory->create()
- ->loadByQuoteId($quoteId);
- $contact = $this->helper->getContact($email, $websiteId);
- if (!$contact) {
- return false;
- }
- if ($contact->status === self::STATUS_PENDING) {
- $this->createAbandonedCart($abandonedModel, $quote, $itemIds, self::STATUS_PENDING);
- return false;
- }
- if ($this->abandonedCartAlreadyExists($abandonedModel) &&
- $this->shouldNotSendACAgain($abandonedModel, $quote) &&
- $this->isNotAConfirmedContact($abandonedModel)
- ) {
- if ($this->shouldDeleteAbandonedCart($quote)) {
- $this->deleteAbandonedCart($abandonedModel);
- }
- return false;
- } else {
- if ($mostExpensiveItem = $this->getMostExpensiveItems($items)) {
- $this->helper->updateAbandonedProductName(
- $mostExpensiveItem->getName(),
- $email,
- $websiteId
- );
- }
- $this->createAbandonedCart($abandonedModel, $quote, $itemIds, self::STATUS_SENT);
- return true;
- }
- }
- /**
- * @param array $allItemsIds
- * @return array
- */
- private function getQuoteItemIds($allItemsIds)
- {
- $itemIds = [];
- foreach ($allItemsIds as $item) {
- $itemIds[] = $item->getProductId();
- }
- return $itemIds;
- }
- /**
- * @param array $items
- * @return bool|\Magento\Quote\Model\Quote\Item
- */
- private function getMostExpensiveItems($items)
- {
- $mostExpensiveItem = false;
- foreach ($items as $item) {
- /** @var $item \Magento\Quote\Model\Quote\Item */
- if ($mostExpensiveItem == false) {
- $mostExpensiveItem = $item;
- } elseif ($item->getPrice() > $mostExpensiveItem->getPrice()) {
- $mostExpensiveItem = $item;
- }
- }
- return $mostExpensiveItem;
- }
- /**
- * @param \Magento\Quote\Model\Quote $quote
- * @param \Dotdigitalgroup\Email\Model\Abandoned $abandonedModel
- * @return bool
- */
- private function isItemsChanged($quote, $abandonedModel)
- {
- if ($quote->getItemsCount() != $abandonedModel->getItemsCount()) {
- return true;
- } else {
- //number of items matches
- $quoteItemIds = $this->getQuoteItemIds($quote->getAllItems());
- $abandonedItemIds = explode(',', $abandonedModel->getItemsIds());
- //quote items not same
- if (! $this->isItemsIdsSame($quoteItemIds, $abandonedItemIds)) {
- return true;
- }
- return false;
- }
- }
- /**
- * @param \Dotdigitalgroup\Email\Model\Abandoned $abandonedModel
- * @param \Magento\Quote\Model\Quote $quote
- * @param array $itemIds
- * @param string $status
- *
- * @throws \Magento\Framework\Exception\LocalizedException
- */
- private function createAbandonedCart($abandonedModel, $quote, $itemIds, $status)
- {
- $abandonedModel->setStoreId($quote->getStoreId())
- ->setCustomerId($quote->getCustomerId())
- ->setEmail($quote->getCustomerEmail())
- ->setQuoteId($quote->getId())
- ->setQuoteUpdatedAt($quote->getUpdatedAt())
- ->setAbandonedCartNumber(1)
- ->setItemsCount($quote->getItemsCount())
- ->setItemsIds(implode(',', $itemIds))
- ->setStatus($status)
- ->save();
- }
- /**
- * @param string $email
- * @param \Magento\Quote\Model\Quote $quote
- * @param string $campaignId
- * @param int $number
- * @param int $websiteId
- */
- private function sendEmailCampaign($email, $quote, $campaignId, $number, $websiteId)
- {
- $storeId = $quote->getStoreId();
- //interval campaign found
- if ($this->isIntervalCampaignFound($email, $storeId) || ! $campaignId) {
- return;
- }
- $customerId = $quote->getCustomerId();
- $message = ($customerId)? 'Abandoned Cart ' . $number : 'Guest Abandoned Cart ' . $number;
- $campaign = $this->campaignFactory->create()
- ->setEmail($email)
- ->setCustomerId($customerId)
- ->setEventName(\Dotdigitalgroup\Email\Model\Campaign::CAMPAIGN_EVENT_LOST_BASKET)
- ->setQuoteId($quote->getId())
- ->setMessage($message)
- ->setCampaignId($campaignId)
- ->setStoreId($storeId)
- ->setWebsiteId($websiteId)
- ->setSendStatus(\Dotdigitalgroup\Email\Model\Campaign::PENDING);
- $this->campaignResource->save($campaign);
- }
- /**
- * @param int $storeId
- * @return int
- */
- private function processGuestFirstAbandonedCart($storeId)
- {
- $abandonedNum = 1;
- $sendAfter = $this->getSendAfterIntervalForGuest($storeId, $abandonedNum);
- $fromTime = new \DateTime('now', new \DateTimezone('UTC'));
- $fromTime->sub($sendAfter);
- $toTime = clone $fromTime;
- $fromTime->sub($this->dateIntervalFactory->create(['interval_spec' => 'PT5M']));
- //format time
- $fromDate = $fromTime->format('Y-m-d H:i:s');
- $toDate = $toTime->format('Y-m-d H:i:s');
- $quoteCollection = $this->getStoreQuotes($fromDate, $toDate, true, $storeId);
- if ($quoteCollection->getSize()) {
- $this->helper->log('Guest AC 1 ' . $fromDate . ' - ' . $toDate);
- }
- $guestCampaignId = $this->getLostBasketGuestCampaignId($abandonedNum, $storeId);
- $result = $this->createGuestFirstAbandonedCart($quoteCollection, $storeId, $guestCampaignId);
- $result += $this->processConfirmedGuestAbandonedCart($storeId, $guestCampaignId);
- return $result;
- }
- /**
- * @param $quoteCollection
- * @param $storeId
- * @param $guestCampaignId
- * @param $result
- *
- * @return int
- */
- private function createGuestFirstAbandonedCart($quoteCollection, $storeId, $guestCampaignId)
- {
- $result = 0;
- foreach ($quoteCollection as $quote) {
- $websiteId = $this->helper->storeManager->getStore($storeId)->getWebsiteId();
- if (! $this->updateDataFieldAndCreateAc($quote, $websiteId)) {
- continue;
- }
- //send campaign; check if still valid to be sent
- if ($this->isLostBasketGuestEnabled(self::GUEST_LOST_BASKET_ONE, $storeId)) {
- $this->sendEmailCampaign(
- $quote->getCustomerEmail(),
- $quote,
- $guestCampaignId,
- self::GUEST_LOST_BASKET_ONE,
- $websiteId
- );
- }
- $this->totalGuests++;
- $result = $this->totalGuests;
- }
- return $result;
- }
- /**
- * @param \Dotdigitalgroup\Email\Model\Abandoned $abandonedModel
- *
- * @return mixed
- */
- private function abandonedCartAlreadyExists($abandonedModel)
- {
- return $abandonedModel->getId();
- }
- /**
- * @param \Dotdigitalgroup\Email\Model\Abandoned $abandonedModel
- * @param \Magento\Quote\Model\Quote $quote
- * @return bool
- */
- private function shouldNotSendACAgain($abandonedModel, $quote)
- {
- return
- !$quote->getIsActive() ||
- $quote->getItemsCount() == 0 ||
- !$this->isItemsChanged($quote, $abandonedModel);
- }
- /**
- * @param \Magento\Quote\Model\Quote $quote
- * @return bool
- */
- private function shouldDeleteAbandonedCart($quote)
- {
- return !$quote->getIsActive() || $quote->getItemsCount() == 0;
- }
- /**
- * @param \Dotdigitalgroup\Email\Model\Abandoned $abandonedModel
- * @throws \Exception
- */
- private function deleteAbandonedCart($abandonedModel)
- {
- $this->abandonedResource->delete($abandonedModel);
- }
- /**
- * @param int $campaignId
- * @param int $storeId
- * @param int $websiteId
- * @param int $number
- * @param bool $guest
- *
- * @return int
- */
- private function processExistingAbandonedCart($campaignId, $storeId, $websiteId, $number, $guest = false)
- {
- $result = 0;
- $fromTime = new \DateTime('now', new \DateTimezone('UTC'));
- if ($guest) {
- $interval = $this->getSendAfterIntervalForGuest($storeId, $number);
- $message = 'Guest';
- } else {
- $interval = $this->getInterval($storeId, $number);
- $message = 'Customer';
- }
- $fromTime->sub($interval);
- $toTime = clone $fromTime;
- $fromTime->sub($this->dateIntervalFactory->create(['interval_spec' => 'PT5M']));
- $fromDate = $fromTime->format('Y-m-d H:i:s');
- $toDate = $toTime->format('Y-m-d H:i:s');
- //get abandoned carts already sent
- $abandonedCollection = $this->getAbandonedCartsForStore(
- $number,
- $fromDate,
- $toDate,
- $storeId,
- $guest
- );
- //quote collection based on the updated date from abandoned cart table
- $quoteIds = $abandonedCollection->getColumnValues('quote_id');
- if (empty($quoteIds)) {
- return $result;
- }
- $quoteCollection = $this->getProcessedQuoteByIds($quoteIds, $storeId);
- //found abandoned carts
- if ($quoteCollection->getSize()) {
- $this->helper->log(
- $message . ' Abandoned Cart ' . $number . ',from ' . $fromDate . ' : ' . $toDate . ', storeId '
- . $storeId
- );
- }
- foreach ($quoteCollection as $quote) {
- $quoteId = $quote->getId();
- $email = $quote->getCustomerEmail();
- if ($mostExpensiveItem = $this->getMostExpensiveItems($quote->getAllItems())) {
- $this->helper->updateAbandonedProductName(
- $mostExpensiveItem->getName(),
- $email,
- $websiteId
- );
- }
- $abandonedModel = $this->abandonedFactory->create()
- ->loadByQuoteId($quoteId);
- //number of items changed or not active anymore
- if ($this->isItemsChanged($quote, $abandonedModel)) {
- if ($this->shouldDeleteAbandonedCart($quote)) {
- $this->deleteAbandonedCart($abandonedModel);
- }
- continue;
- }
- $abandonedModel->setAbandonedCartNumber($number)
- ->setQuoteUpdatedAt($quote->getUpdatedAt())
- ->save();
- $this->sendEmailCampaign($email, $quote, $campaignId, $number, $websiteId);
- $result++;
- }
- return $result;
- }
- /**
- * @param int $number
- * @param string $from
- * @param string $to
- * @param int $storeId
- * @param bool $guest
- *
- * @return mixed
- */
- private function getAbandonedCartsForStore($number, $from, $to, $storeId, $guest = false)
- {
- $updated = [
- 'from' => $from,
- 'to' => $to,
- 'date' => true
- ];
- return $this->abandonedCollectionFactory->create()->getAbandonedCartsForStore(
- --$number,
- $storeId,
- $updated,
- self::STATUS_SENT,
- $this->helper->isOnlySubscribersForAC($storeId),
- $guest
- );
- }
- /**
- * @param array $quoteIds
- * @param int $storeId
- * @return mixed
- */
- private function getProcessedQuoteByIds($quoteIds, $storeId)
- {
- $quoteCollection = $this->quoteCollectionFactory->create()
- ->addFieldToFilter('entity_id', ['in' => $quoteIds]);
- //process rules on collection
- $ruleModel = $this->rulesFactory->create();
- $quoteCollection = $ruleModel->process(
- $quoteCollection,
- \Dotdigitalgroup\Email\Model\Rules::ABANDONED,
- $this->helper->storeManager->getStore($storeId)->getWebsiteId()
- );
- return $quoteCollection;
- }
- /**
- * Compare items ids.
- *
- * @param array $quoteItemIds
- * @param array $abandonedItemIds
- * @return bool
- */
- private function isItemsIdsSame($quoteItemIds, $abandonedItemIds)
- {
- return $quoteItemIds == $abandonedItemIds;
- }
- /**
- * @param $storeId
- * @param $guestCampaignId
- *
- * @return int
- */
- private function processConfirmedGuestAbandonedCart($storeId, $guestCampaignId)
- {
- $ac1QuoteIdsWithConfirmedContacts = $this->abandonedCollectionFactory->create()
- ->getCollectionByConfirmedStatus($storeId, true)
- ->getColumnValues('quote_id');
- $quoteCollectionFromIds = $this->orderCollection->create()
- ->getStoreQuotesFromQuoteIds($ac1QuoteIdsWithConfirmedContacts, $storeId);
- return $this->createGuestFirstAbandonedCart(
- $quoteCollectionFromIds,
- $storeId,
- $guestCampaignId
- );
- }
- /**
- * @param $storeId
- * @param $campaignId
- * @param $result
- *
- * @return int
- * @throws \Magento\Framework\Exception\NoSuchEntityException
- */
- private function processConfirmedCustomerAbandonedCart($storeId, $campaignId)
- {
- $ac1QuoteIdsWithConfirmedContacts = $this->abandonedCollectionFactory->create()
- ->getCollectionByConfirmedStatus($storeId)
- ->getColumnValues('quote_id');
- $quoteCollectionFromIds = $this->orderCollection->create()
- ->getStoreQuotesFromQuoteIds($ac1QuoteIdsWithConfirmedContacts, $storeId);
- return $this->createCustomerFirstAbandonedCart(
- $quoteCollectionFromIds,
- $storeId,
- $campaignId
- );
- }
- private function isNotAConfirmedContact($abandonedModel)
- {
- return $abandonedModel->getStatus() !== self::STATUS_CONFIRMED;
- }
- }
|