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 / PurchasedProduct.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
PurchasedProduct.php
227 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\AutomaticEmails\WooCommerce\Events;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\AutomaticEmails\WooCommerce\WooCommerce;
9 use MailPoet\DI\ContainerWrapper;
10 use MailPoet\Entities\NewsletterEntity;
11 use MailPoet\Entities\NewsletterOptionFieldEntity;
12 use MailPoet\Entities\SubscriberEntity;
13 use MailPoet\Logging\LoggerFactory;
14 use MailPoet\Newsletter\AutomaticEmailsRepository;
15 use MailPoet\Newsletter\Scheduler\AutomaticEmailScheduler;
16 use MailPoet\Subscribers\SubscribersRepository;
17 use MailPoet\Util\Helpers;
18 use MailPoet\WooCommerce\Helper as WCHelper;
19 use MailPoet\WP\Functions as WPFunctions;
20
21 class PurchasedProduct {
22 const SLUG = 'woocommerce_product_purchased';
23 /**
24 * @var \MailPoet\WooCommerce\Helper
25 */
26 private $helper;
27
28 /** @var AutomaticEmailScheduler */
29 private $scheduler;
30
31 /** @var LoggerFactory */
32 private $loggerFactory;
33
34 /** @var AutomaticEmailsRepository */
35 private $repository;
36
37 /** @var SubscribersRepository */
38 private $subscribersRepository;
39
40 public function __construct(
41 ?WCHelper $helper = null
42 ) {
43 if ($helper === null) {
44 $helper = ContainerWrapper::getInstance()->get(WCHelper::class);
45 }
46 $this->helper = $helper;
47 $this->scheduler = ContainerWrapper::getInstance()->get(AutomaticEmailScheduler::class);
48 $this->loggerFactory = LoggerFactory::getInstance();
49 $this->repository = ContainerWrapper::getInstance()->get(AutomaticEmailsRepository::class);
50 $this->subscribersRepository = ContainerWrapper::getInstance()->get(SubscribersRepository::class);
51 }
52
53 public function init() {
54 WPFunctions::get()->removeAllFilters('woocommerce_product_purchased_get_products');
55 WPFunctions::get()->addFilter(
56 'woocommerce_product_purchased_get_products',
57 [
58 $this,
59 'getProducts',
60 ]
61 );
62
63
64 $acceptedOrderStates = WPFunctions::get()->applyFilters('mailpoet_first_purchase_order_states', ['completed', 'processing']);
65 if (!is_array($acceptedOrderStates)) {
66 $acceptedOrderStates = ['completed', 'processing'];
67 }
68 foreach ($acceptedOrderStates as $state) {
69 if (!is_string($state)) {
70 continue;
71 }
72 WPFunctions::get()->addAction('woocommerce_order_status_' . $state, [
73 $this,
74 'scheduleEmailWhenProductIsPurchased',
75 ], 10, 1);
76 }
77 }
78
79 public function getEventDetails() {
80 return [
81 'slug' => self::SLUG,
82 'title' => __('Purchased This Product', 'mailpoet'),
83 'description' => __('Let MailPoet send an email to customers who purchase a specific product for the first time.', 'mailpoet'),
84 // translators: %s is the name of the product.
85 'listingScheduleDisplayText' => __('Email sent when a customer buys product: %s', 'mailpoet'),
86 // translators: %s is the name of the products.
87 'listingScheduleDisplayTextPlural' => __('Email sent when a customer buys products: %s', 'mailpoet'),
88 'afterDelayText' => __('after a purchase', 'mailpoet'),
89 'timeDelayValues' => [
90 'immediate' => [
91 'text' => __('immediately', 'mailpoet'),
92 'displayAfterTimeNumberField' => false,
93 ],
94 'minutes' => [
95 'text' => __('minute(s)', 'mailpoet'),
96 'displayAfterTimeNumberField' => true,
97 ],
98 'hours' => [
99 'text' => __('hour(s)', 'mailpoet'),
100 'displayAfterTimeNumberField' => true,
101 ],
102 'days' => [
103 'text' => __('day(s)', 'mailpoet'),
104 'displayAfterTimeNumberField' => true,
105 ],
106 'weeks' => [
107 'text' => __('week(s)', 'mailpoet'),
108 'displayAfterTimeNumberField' => true,
109 ],
110 ],
111 'options' => [
112 'multiple' => true,
113 'endpoint' => 'products',
114 'placeholder' => __('Search products', 'mailpoet'),
115 ],
116 ];
117 }
118
119 public function getProducts($productSearchQuery) {
120 $args = [
121 'post_type' => 'product',
122 'post_status' => 'publish',
123 's' => $productSearchQuery,
124 'orderby' => 'title',
125 'order' => 'ASC',
126 ];
127 $woocommerceProducts = new \WP_Query($args);
128 $woocommerceProducts = $woocommerceProducts->get_posts();
129 /** @var \WP_Post[] $woocommerceProducts */
130 if (empty($woocommerceProducts)) {
131 $this->loggerFactory->getLogger(self::SLUG)->info(
132 'no products found',
133 ['search_query' => $productSearchQuery]
134 );
135 return;
136 }
137
138 $woocommerceProducts = array_map(function($product) {
139 return [
140 'id' => $product->ID,
141 'name' => $product->post_title, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
142 ];
143 }, $woocommerceProducts);
144 return $woocommerceProducts;
145 }
146
147 public function scheduleEmailWhenProductIsPurchased($orderId) {
148 $orderDetails = $this->helper->wcGetOrder($orderId);
149 if (!$orderDetails || !$orderDetails->get_billing_email()) {
150 $this->loggerFactory->getLogger(self::SLUG)->info(
151 'Email not scheduled because the order customer was not found',
152 ['order_id' => $orderId]
153 );
154 return;
155 }
156 $customerEmail = $orderDetails->get_billing_email();
157
158 $subscriber = $this->subscribersRepository->getWooCommerceSegmentSubscriber($customerEmail);
159
160 if (!$subscriber instanceof SubscriberEntity) {
161 $this->loggerFactory->getLogger(self::SLUG)->info(
162 'Email not scheduled because the customer was not found as WooCommerce list subscriber',
163 ['order_id' => $orderId, 'customer_email' => $customerEmail]
164 );
165 return;
166 }
167
168 $orderedProducts = array_map(function($product) {
169 return ($product instanceof \WC_Order_Item_Product) ? $product->get_product_id() : null;
170 }, $orderDetails->get_items());
171 $orderedProducts = array_values(array_filter($orderedProducts));
172
173 $schedulingCondition = function(NewsletterEntity $automaticEmail) use ($orderedProducts, $subscriber) {
174 $matchedProducts = $this->getProductIdsMatchingNewsletterTrigger($automaticEmail, $orderedProducts);
175 if (empty($matchedProducts)) {
176 return false;
177 }
178
179 if ($this->repository->wasScheduledForSubscriber((int)$automaticEmail->getId(), (int)$subscriber->getId())) {
180 $sentAllProducts = $this->repository->alreadySentAllProducts((int)$automaticEmail->getId(), (int)$subscriber->getId(), 'orderedProducts', $matchedProducts);
181 if ($sentAllProducts) return false;
182 }
183
184 return true;
185 };
186
187 $this->loggerFactory->getLogger(self::SLUG)->info(
188 'Email scheduled',
189 [
190 'order_id' => $orderId,
191 'customer_email' => $customerEmail,
192 'subscriber_id' => $subscriber->getId(),
193 ]
194 );
195 return $this->scheduler->scheduleAutomaticEmail(
196 WooCommerce::SLUG,
197 self::SLUG,
198 $schedulingCondition,
199 $subscriber,
200 ['orderedProducts' => $orderedProducts],
201 [$this, 'metaModifier']
202 );
203 }
204
205 public function metaModifier(NewsletterEntity $newsletter, array $meta): array {
206 $orderedProductIds = $meta['orderedProducts'] ?? null;
207 if (empty($orderedProductIds)) {
208 return $meta;
209 }
210 $meta['orderedProducts'] = $this->getProductIdsMatchingNewsletterTrigger($newsletter, $orderedProductIds);
211
212 return $meta;
213 }
214
215 private function getProductIdsMatchingNewsletterTrigger(NewsletterEntity $automaticEmail, array $orderedProductIds): array {
216 $automaticEmailMetaValue = $automaticEmail->getOptionValue(NewsletterOptionFieldEntity::NAME_META);
217 $optionValue = Helpers::isJson($automaticEmailMetaValue) ? json_decode($automaticEmailMetaValue, true) : $automaticEmailMetaValue;
218
219 if (!is_array($optionValue) || empty($optionValue['option']) || !is_array($optionValue['option'])) {
220 return [];
221 }
222 $emailTriggeringProductIds = array_column($optionValue['option'], 'id');
223
224 return array_intersect($emailTriggeringProductIds, $orderedProductIds);
225 }
226 }
227