PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.36.0
MailPoet – Newsletters, Email Marketing, and Automation v5.36.0
5.36.0 5.35.1 5.35.0 5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / AutomaticEmails / WooCommerce / Events / AbandonedCart.php
mailpoet / lib / AutomaticEmails / WooCommerce / Events Last commit date
AbandonedCart.php 1 year ago FirstPurchase.php 3 months ago PurchasedInCategory.php 3 months ago PurchasedProduct.php 3 months ago index.php 3 years ago
AbandonedCart.php
243 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\AutomaticEmails\WooCommerce\Events;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\AutomaticEmails\WooCommerce\WooCommerce as WooCommerceEmail;
9 use MailPoet\Entities\SubscriberEntity;
10 use MailPoet\Newsletter\Scheduler\AutomaticEmailScheduler;
11 use MailPoet\Statistics\Track\SubscriberActivityTracker;
12 use MailPoet\Statistics\Track\SubscriberCookie;
13 use MailPoet\Subscribers\SubscribersRepository;
14 use MailPoet\WooCommerce\Helper as WooCommerceHelper;
15 use MailPoet\WP\Functions as WPFunctions;
16
17 class AbandonedCart {
18 const SLUG = 'woocommerce_abandoned_shopping_cart';
19 const TASK_META_NAME = 'cart_product_ids';
20
21
22 const HOOK_SCHEDULE = 'mailpoet_abandoned_cart_schedule';
23 const HOOK_RE_SCHEDULE = 'mailpoet_abandoned_cart_reschedule';
24 const HOOK_CANCEL = 'mailpoet_abandoned_cart_cancel';
25
26 /** @var WPFunctions */
27 private $wp;
28
29 /** @var WooCommerceHelper */
30 private $wooCommerceHelper;
31
32 /** @var SubscriberCookie */
33 private $subscriberCookie;
34
35 /** @var AutomaticEmailScheduler */
36 private $scheduler;
37
38 /** @var SubscriberActivityTracker */
39 private $subscriberActivityTracker;
40
41 /** @var SubscribersRepository */
42 private $subscribersRepository;
43
44 /** @var bool */
45 private $loadSavedCartAfterLogin;
46
47 public function __construct(
48 WPFunctions $wp,
49 WooCommerceHelper $wooCommerceHelper,
50 SubscriberCookie $subscriberCookie,
51 SubscriberActivityTracker $subscriberActivityTracker,
52 AutomaticEmailScheduler $scheduler,
53 SubscribersRepository $subscribersRepository
54 ) {
55 $this->wp = $wp;
56 $this->wooCommerceHelper = $wooCommerceHelper;
57 $this->subscriberCookie = $subscriberCookie;
58 $this->subscriberActivityTracker = $subscriberActivityTracker;
59 $this->scheduler = $scheduler;
60 $this->subscribersRepository = $subscribersRepository;
61 }
62
63 public function getEventDetails() {
64 return [
65 'slug' => self::SLUG,
66 'title' => _x('Abandoned Shopping Cart', 'This is the name of a type of automatic email for ecommerce. Those emails are sent automatically when a customer adds product to his shopping cart but never complete the checkout process.', 'mailpoet'),
67 'description' => __('Send an email to logged-in visitors who have items in their shopping carts but left your website without checking out. Can convert up to 5% of abandoned carts.', 'mailpoet'),
68 'listingScheduleDisplayText' => _x('Send the email when a customer abandons their cart.', 'Description of Abandoned Shopping Cart email', 'mailpoet'),
69 'afterDelayText' => __('after abandoning the cart', 'mailpoet'),
70 'badge' => [
71 'text' => __('Must-have', 'mailpoet'),
72 'style' => 'red',
73 ],
74 'timeDelayValues' => [
75 'minutes' => [
76 'text' => __('minute(s)', 'mailpoet'),
77 'displayAfterTimeNumberField' => true,
78 ],
79 'hours' => [
80 'text' => __('hour(s)', 'mailpoet'),
81 'displayAfterTimeNumberField' => true,
82 ],
83 'days' => [
84 'text' => __('day(s)', 'mailpoet'),
85 'displayAfterTimeNumberField' => true,
86 ],
87 'weeks' => [
88 'text' => __('week(s)', 'mailpoet'),
89 'displayAfterTimeNumberField' => true,
90 ],
91 ],
92 'defaultAfterTimeType' => 'minutes',
93 'schedulingReadMoreLink' => [
94 'link' => 'https://www.mailpoet.com/blog/abandoned-cart-woocommerce',
95 'text' => __('We recommend setting up 3 abandoned cart emails. Here’s why.', 'mailpoet'),
96 ],
97 ];
98 }
99
100 public function init() {
101 if (!$this->wooCommerceHelper->isWooCommerceActive()) {
102 return;
103 }
104
105 // item added to cart (not fired on quantity changes)
106 $this->wp->addAction(
107 'woocommerce_add_to_cart',
108 [$this, 'handleCartChange'],
109 10
110 );
111
112 // item removed from cart (not fired on quantity changes, not even change to zero)
113 $this->wp->addAction(
114 'woocommerce_cart_item_removed',
115 [$this, 'handleCartChange'],
116 10
117 );
118
119 // item quantity updated (not fired when quantity updated to zero)
120 $this->wp->addAction(
121 'woocommerce_after_cart_item_quantity_update',
122 [$this, 'handleCartChange'],
123 10
124 );
125
126 // item quantity set to zero
127 $this->wp->addAction(
128 'woocommerce_remove_cart_item',
129 [$this, 'handleCartChange'],
130 10
131 );
132
133 // cart emptied (not called when all items removed)
134 $this->wp->addAction(
135 'woocommerce_cart_emptied',
136 [$this, 'handleCartChange'],
137 10
138 );
139
140 // undo removal of item from cart or cart emptying (does not fire any other cart change hook)
141 $this->wp->addAction(
142 'woocommerce_cart_item_restored',
143 [$this, 'handleCartChange'],
144 10
145 );
146
147 // we should handle loading cart from session if user logs in
148 $this->wp->addAction(
149 'woocommerce_load_cart_from_session',
150 [$this, 'handleUserLogin'],
151 10
152 );
153
154 // cart loaded from session (only processed after login, not on every page load)
155 $this->wp->addAction(
156 'woocommerce_cart_loaded_from_session',
157 [$this, 'handleCartChangeOnLogin'],
158 10
159 );
160
161 $this->subscriberActivityTracker->registerCallback(
162 'mailpoet_abandoned_cart',
163 [$this, 'handleSubscriberActivity']
164 );
165 }
166
167 public function handleCartChange() {
168 $cart = $this->wooCommerceHelper->WC()->cart;
169
170 $currentAction = current_action();
171 if ($currentAction !== 'woocommerce_cart_emptied' && $cart && !$cart->is_empty()) {
172 $this->scheduleAbandonedCartEmail($this->getCartProductIds($cart));
173 } else {
174 $this->cancelAbandonedCartEmail();
175 }
176 }
177
178 public function handleUserLogin() {
179 $wpUserId = $this->wp->getCurrentUserId();
180 if (!$wpUserId) {
181 return false;
182 }
183 $this->loadSavedCartAfterLogin = (bool)$this->wp->getUserMeta($wpUserId, '_woocommerce_load_saved_cart_after_login', true);
184 }
185
186 public function handleCartChangeOnLogin() {
187 if (!$this->loadSavedCartAfterLogin) {
188 return false;
189 }
190 $this->handleCartChange();
191 }
192
193 public function handleSubscriberActivity(SubscriberEntity $subscriber) {
194 // on subscriber activity on site reschedule all currently scheduled (not yet sent) emails for given subscriber
195 // (it tracks at most once per minute to avoid processing many calls at the same time, i.e. AJAX)
196 $this->rescheduleAbandonedCartEmail($subscriber);
197 }
198
199 private function getCartProductIds($cart) {
200 $cartItems = $cart->get_cart() ?: [];
201 return array_column($cartItems, 'product_id');
202 }
203
204 private function scheduleAbandonedCartEmail(array $cartProductIds = []) {
205 $subscriber = $this->getSubscriber();
206 if (!$subscriber) {
207 return;
208 }
209
210 $this->wp->doAction(self::HOOK_SCHEDULE, $subscriber, $cartProductIds);
211 $meta = [self::TASK_META_NAME => $cartProductIds];
212 $this->scheduler->scheduleOrRescheduleAutomaticEmail(WooCommerceEmail::SLUG, self::SLUG, $subscriber, $meta);
213 }
214
215 private function rescheduleAbandonedCartEmail(SubscriberEntity $subscriber) {
216 $this->wp->doAction(self::HOOK_RE_SCHEDULE, $subscriber);
217 $this->scheduler->rescheduleAutomaticEmail(WooCommerceEmail::SLUG, self::SLUG, $subscriber);
218 }
219
220 private function cancelAbandonedCartEmail() {
221 $subscriber = $this->getSubscriber();
222 if (!$subscriber) {
223 return;
224 }
225 $this->wp->doAction(self::HOOK_CANCEL, $subscriber);
226 $this->scheduler->cancelAutomaticEmail(WooCommerceEmail::SLUG, self::SLUG, $subscriber);
227 }
228
229 private function getSubscriber(): ?SubscriberEntity {
230 $wpUser = $this->wp->wpGetCurrentUser();
231 if ($wpUser->exists()) {
232 return $this->subscribersRepository->findOneBy(['wpUserId' => $wpUser->ID]);
233 }
234
235 // if user not logged in, try to find subscriber by cookie
236 $subscriberId = $this->subscriberCookie->getSubscriberId();
237 if ($subscriberId) {
238 return $this->subscribersRepository->findOneById($subscriberId);
239 }
240 return null;
241 }
242 }
243