PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
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 / PurchasedInCategory.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
PurchasedInCategory.php
224 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 PurchasedInCategory {
22 const SLUG = 'woocommerce_product_purchased_in_category';
23
24 /** @var WCHelper */
25 private $woocommerceHelper;
26
27 /** @var AutomaticEmailScheduler */
28 private $scheduler;
29
30 /** @var LoggerFactory */
31 private $loggerFactory;
32
33 /** @var AutomaticEmailsRepository */
34 private $repository;
35
36 /** @var SubscribersRepository */
37 private $subscribersRepository;
38
39 public function __construct(
40 ?WCHelper $woocommerceHelper = null
41 ) {
42 if ($woocommerceHelper === null) {
43 $woocommerceHelper = ContainerWrapper::getInstance()->get(WCHelper::class);
44 }
45 $this->woocommerceHelper = $woocommerceHelper;
46 $this->scheduler = ContainerWrapper::getInstance()->get(AutomaticEmailScheduler::class);
47 $this->loggerFactory = LoggerFactory::getInstance();
48 $this->repository = ContainerWrapper::getInstance()->get(AutomaticEmailsRepository::class);
49 $this->subscribersRepository = ContainerWrapper::getInstance()->get(SubscribersRepository::class);
50 }
51
52 public function getEventDetails() {
53 return [
54 'slug' => self::SLUG,
55 'title' => _x('Purchased In This Category', 'This is the name of a type for automatic email for ecommerce. Those emails are sent automatically every time a customer buys for the first time a product in a given category', 'mailpoet'),
56 'description' => __('Let MailPoet send an email to customers who purchase a product for the first time in a specific category.', 'mailpoet'),
57 // translators: %s is the name of the category.
58 'listingScheduleDisplayText' => __('Email sent when a customer buys a product in category: %s', 'mailpoet'),
59 // translators: %s is the name of the category.
60 'listingScheduleDisplayTextPlural' => __('Email sent when a customer buys a product in categories: %s', 'mailpoet'),
61 'afterDelayText' => __('after a purchase', 'mailpoet'),
62 'timeDelayValues' => [
63 'immediate' => [
64 'text' => __('immediately', 'mailpoet'),
65 'displayAfterTimeNumberField' => false,
66 ],
67 'minutes' => [
68 'text' => __('minute(s)', 'mailpoet'),
69 'displayAfterTimeNumberField' => true,
70 ],
71 'hours' => [
72 'text' => __('hour(s)', 'mailpoet'),
73 'displayAfterTimeNumberField' => true,
74 ],
75 'days' => [
76 'text' => __('day(s)', 'mailpoet'),
77 'displayAfterTimeNumberField' => true,
78 ],
79 'weeks' => [
80 'text' => __('week(s)', 'mailpoet'),
81 'displayAfterTimeNumberField' => true,
82 ],
83 ],
84 'options' => [
85 'multiple' => true,
86 'endpoint' => 'product_categories',
87 'placeholder' => _x('Search category', 'Search input for product category (ecommerce)', 'mailpoet'),
88 ],
89 ];
90 }
91
92 public function init() {
93 WPFunctions::get()->removeAllFilters('woocommerce_product_purchased_get_categories');
94 WPFunctions::get()->addFilter(
95 'woocommerce_product_purchased_get_categories',
96 [$this, 'getCategories']
97 );
98
99 $acceptedOrderStates = WPFunctions::get()->applyFilters('mailpoet_first_purchase_order_states', ['completed', 'processing']);
100 if (!is_array($acceptedOrderStates)) {
101 $acceptedOrderStates = ['completed', 'processing'];
102 }
103 foreach ($acceptedOrderStates as $state) {
104 if (!is_string($state)) {
105 continue;
106 }
107 WPFunctions::get()->addAction(
108 'woocommerce_order_status_' . $state,
109 [$this, 'scheduleEmail'],
110 10,
111 1
112 );
113 }
114 }
115
116 public function getCategories($searchQuery) {
117 $args = [
118 'taxonomy' => 'product_cat',
119 'search' => $searchQuery,
120 'orderby' => 'name',
121 'hierarchical' => 0,
122 'hide_empty' => 1,
123 'order' => 'ASC',
124 ];
125 $allCategories = get_categories($args);
126
127 return array_map(function($category) {
128 return [
129 'id' => $category->term_id, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
130 'name' => $category->name,
131 ];
132 }, $allCategories);
133 }
134
135 public function scheduleEmail($orderId) {
136 $orderDetails = $this->woocommerceHelper->wcGetOrder($orderId);
137 if (!$orderDetails || !$orderDetails->get_billing_email()) {
138 $this->loggerFactory->getLogger(self::SLUG)->info(
139 'Email not scheduled because the order customer was not found',
140 ['order_id' => $orderId]
141 );
142 return;
143 }
144 $customerEmail = $orderDetails->get_billing_email();
145
146 $subscriber = $this->subscribersRepository->getWooCommerceSegmentSubscriber($customerEmail);
147
148 if (!$subscriber instanceof SubscriberEntity) {
149 $this->loggerFactory->getLogger(self::SLUG)->info(
150 'Email not scheduled because the customer was not found as WooCommerce list subscriber',
151 ['order_id' => $orderId, 'customer_email' => $customerEmail]
152 );
153 return;
154 }
155
156 $orderedProductCategories = [];
157 foreach ($orderDetails->get_items() as $orderItemProduct) {
158 $product = $orderItemProduct->get_product();
159 if (!$product instanceof \WC_Product) {
160 continue;
161 }
162 if ($product->get_type() === 'variation') {
163 // WooCommerce returns a empty list when get_category_ids() is called for a product variation,
164 // so we need to get the parent product
165 $product = $this->woocommerceHelper->wcGetProduct($product->get_parent_id());
166 }
167 $orderedProductCategories = array_merge($orderedProductCategories, $product->get_category_ids());
168 }
169
170 $schedulingCondition = function(NewsletterEntity $automaticEmail) use ($orderedProductCategories, $subscriber) {
171 $matchedCategories = $this->getProductCategoryIdsMatchingNewsletterTrigger($automaticEmail, $orderedProductCategories);
172 if (empty($matchedCategories)) {
173 return false;
174 }
175
176 if ($this->repository->wasScheduledForSubscriber((int)$automaticEmail->getId(), (int)$subscriber->getId())) {
177 $sentAllProducts = $this->repository->alreadySentAllProducts((int)$automaticEmail->getId(), (int)$subscriber->getId(), 'orderedProductCategories', $matchedCategories);
178 if ($sentAllProducts) return false;
179 }
180
181 return true;
182 };
183
184 $this->loggerFactory->getLogger(self::SLUG)->info(
185 'Email scheduled',
186 [
187 'order_id' => $orderId,
188 'customer_email' => $customerEmail,
189 'subscriber_id' => $subscriber->getId(),
190 ]
191 );
192 $this->scheduler->scheduleAutomaticEmail(
193 WooCommerce::SLUG,
194 self::SLUG,
195 $schedulingCondition,
196 $subscriber,
197 ['orderedProductCategories' => $orderedProductCategories],
198 [$this, 'metaModifier']
199 );
200 }
201
202 public function metaModifier(NewsletterEntity $automaticEmail, array $meta): array {
203 $orderedProductCategoryIds = $meta['orderedProductCategories'] ?? null;
204 if (empty($orderedProductCategoryIds)) {
205 return $meta;
206 }
207 $meta['orderedProductCategories'] = $this->getProductCategoryIdsMatchingNewsletterTrigger($automaticEmail, $orderedProductCategoryIds);
208
209 return $meta;
210 }
211
212 private function getProductCategoryIdsMatchingNewsletterTrigger(NewsletterEntity $automaticEmail, array $orderedCategoryIds): array {
213 $automaticEmailMetaValue = $automaticEmail->getOptionValue(NewsletterOptionFieldEntity::NAME_META);
214 $optionValue = Helpers::isJson($automaticEmailMetaValue) ? json_decode($automaticEmailMetaValue, true) : $automaticEmailMetaValue;
215
216 if (!is_array($optionValue) || empty($optionValue['option']) || !is_array($optionValue['option'])) {
217 return [];
218 }
219 $emailTriggeringCategoryIds = array_column($optionValue['option'], 'id');
220
221 return array_intersect($emailTriggeringCategoryIds, $orderedCategoryIds);
222 }
223 }
224