PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
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 / Automation / Integrations / WooCommerce / Fields / CustomerOrderFieldsFactory.php
mailpoet / lib / Automation / Integrations / WooCommerce / Fields Last commit date
CustomerFieldsFactory.php 11 months ago CustomerOrderFieldsFactory.php 1 year ago CustomerReviewFieldsFactory.php 1 year ago CustomerSubscriptionFieldsFactory.php 11 months ago OrderFieldsFactory.php 2 months ago TermOptionsBuilder.php 2 years ago TermParentsLoader.php 1 year ago index.php 3 years ago
CustomerOrderFieldsFactory.php
415 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Automation\Integrations\WooCommerce\Fields;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use DateTimeImmutable;
9 use DateTimeZone;
10 use MailPoet\Automation\Engine\Data\Field;
11 use MailPoet\Automation\Integrations\WooCommerce\Payloads\CustomerPayload;
12 use MailPoet\Automation\Integrations\WooCommerce\WooCommerce;
13 use WC_Customer;
14 use WC_Order;
15 use WC_Order_Item_Product;
16 use WC_Product;
17
18 class CustomerOrderFieldsFactory {
19 /** @var WooCommerce */
20 private $wooCommerce;
21
22 /** @var TermOptionsBuilder */
23 private $termOptionsBuilder;
24
25 /** @var TermParentsLoader */
26 private $termParentsLoader;
27
28 public function __construct(
29 WooCommerce $wooCommerce,
30 TermOptionsBuilder $termOptionsBuilder,
31 TermParentsLoader $termParentsLoader
32 ) {
33 $this->wooCommerce = $wooCommerce;
34 $this->termOptionsBuilder = $termOptionsBuilder;
35 $this->termParentsLoader = $termParentsLoader;
36 }
37
38 /** @return Field[] */
39 public function getFields(): array {
40 return [
41 new Field(
42 'woocommerce:customer:spent-total',
43 Field::TYPE_NUMBER,
44 __('Total spent', 'mailpoet'),
45 function (CustomerPayload $payload, array $params = []) {
46 $customer = $payload->getCustomer();
47 $inTheLastSeconds = isset($params['in_the_last']) ? (int)$params['in_the_last'] : null;
48 if (!$customer) {
49 $order = $payload->getOrder();
50 return $order && $this->isInTheLastSeconds($order, $inTheLastSeconds) ? $payload->getTotalSpent() : 0.0;
51 }
52 return $inTheLastSeconds === null
53 ? $payload->getTotalSpent()
54 : $this->getRecentSpentTotal($customer, $inTheLastSeconds);
55 },
56 [
57 'params' => ['in_the_last'],
58 ]
59 ),
60 new Field(
61 'woocommerce:customer:spent-average',
62 Field::TYPE_NUMBER,
63 __('Average spent', 'mailpoet'),
64 function (CustomerPayload $payload, array $params = []) {
65 $customer = $payload->getCustomer();
66 $inTheLastSeconds = isset($params['in_the_last']) ? (int)$params['in_the_last'] : null;
67
68 if (!$customer) {
69 $order = $payload->getOrder();
70 return $order && $this->isInTheLastSeconds($order, $inTheLastSeconds) ? $payload->getAverageSpent() : 0.0;
71 }
72
73 if ($inTheLastSeconds === null) {
74 return $payload->getAverageSpent();
75 } else {
76 $totalSpent = $this->getRecentSpentTotal($customer, $inTheLastSeconds);
77 $orderCount = $this->getRecentOrderCount($customer, $inTheLastSeconds);
78 return $orderCount > 0 ? ($totalSpent / $orderCount) : 0.0;
79 }
80 },
81 [
82 'params' => ['in_the_last'],
83 ]
84 ),
85 new Field(
86 'woocommerce:customer:order-count',
87 Field::TYPE_INTEGER,
88 __('Order count', 'mailpoet'),
89 function (CustomerPayload $payload, array $params = []) {
90 $customer = $payload->getCustomer();
91 $inTheLastSeconds = isset($params['in_the_last']) ? (int)$params['in_the_last'] : null;
92 if (!$customer) {
93 $order = $payload->getOrder();
94 return $order && $this->isInTheLastSeconds($order, $inTheLastSeconds) ? $payload->getOrderCount() : 0;
95 }
96 return $inTheLastSeconds === null
97 ? $payload->getOrderCount()
98 : $this->getRecentOrderCount($customer, $inTheLastSeconds);
99 },
100 [
101 'params' => ['in_the_last'],
102 ]
103 ),
104 new Field(
105 'woocommerce:customer:first-paid-order-date',
106 Field::TYPE_DATETIME,
107 __('First paid order date', 'mailpoet'),
108 function (CustomerPayload $payload) {
109 $customer = $payload->getCustomer();
110 if (!$customer) {
111 $order = $payload->getOrder();
112 return $order && $order->is_paid() ? $order->get_date_created() : null;
113 }
114 return $this->getPaidOrderDate($customer, true);
115 }
116 ),
117 new Field(
118 'woocommerce:customer:last-paid-order-date',
119 Field::TYPE_DATETIME,
120 __('Last paid order date', 'mailpoet'),
121 function (CustomerPayload $payload) {
122 $customer = $payload->getCustomer();
123 if (!$customer) {
124 $order = $payload->getOrder();
125 return $order && $order->is_paid() ? $order->get_date_created() : null;
126 }
127 return $this->getPaidOrderDate($customer, false);
128 }
129 ),
130 new Field(
131 'woocommerce:customer:purchased-categories',
132 Field::TYPE_ENUM_ARRAY,
133 __('Purchased categories', 'mailpoet'),
134 function (CustomerPayload $payload, array $params = []) {
135 $customer = $payload->getCustomer();
136 $inTheLastSeconds = isset($params['in_the_last']) ? (int)$params['in_the_last'] : null;
137 if (!$customer) {
138 $order = $payload->getOrder();
139 $items = $order && $order->is_paid() && $this->isInTheLastSeconds($order, $inTheLastSeconds) ? $order->get_items() : [];
140 $ids = [];
141 foreach ($items as $item) {
142 $product = $item instanceof WC_Order_Item_Product ? $item->get_product() : null;
143 $ids = array_merge($ids, $product instanceof WC_Product ? $product->get_category_ids() : []);
144 }
145 $ids = array_unique($ids);
146 } else {
147 $ids = $this->getOrderProductTermIds($customer, 'product_cat', $inTheLastSeconds);
148 }
149 $ids = array_merge($ids, $this->termParentsLoader->getParentIds($ids));
150 sort($ids);
151 return $ids;
152 },
153 [
154 'options' => $this->termOptionsBuilder->getTermOptions('product_cat'),
155 'params' => ['in_the_last'],
156 ]
157 ),
158 new Field(
159 'woocommerce:customer:purchased-tags',
160 Field::TYPE_ENUM_ARRAY,
161 __('Purchased tags', 'mailpoet'),
162 function (CustomerPayload $payload, array $params = []) {
163 $customer = $payload->getCustomer();
164 $inTheLastSeconds = isset($params['in_the_last']) ? (int)$params['in_the_last'] : null;
165 if (!$customer) {
166 $order = $payload->getOrder();
167 $items = $order && $order->is_paid() && $this->isInTheLastSeconds($order, $inTheLastSeconds) ? $order->get_items() : [];
168 $ids = [];
169 foreach ($items as $item) {
170 $product = $item instanceof WC_Order_Item_Product ? $item->get_product() : null;
171 $ids = array_merge($ids, $product instanceof WC_Product ? $product->get_tag_ids() : []);
172 }
173 $ids = array_unique($ids);
174 } else {
175 $ids = $this->getOrderProductTermIds($customer, 'product_tag', $inTheLastSeconds);
176 }
177 sort($ids);
178 return $ids;
179 },
180 [
181 'options' => $this->termOptionsBuilder->getTermOptions('product_tag'),
182 'params' => ['in_the_last'],
183 ]
184 ),
185 ];
186 }
187
188 private function getRecentSpentTotal(WC_Customer $customer, int $inTheLastSeconds): float {
189 global $wpdb;
190 $statuses = array_map(function (string $status) {
191 return "wc-$status";
192 }, $this->wooCommerce->wcGetIsPaidStatuses());
193
194 if ($this->wooCommerce->isWooCommerceCustomOrdersTableEnabled()) {
195 return (float)$wpdb->get_var(
196 $wpdb->prepare(
197 '
198 SELECT SUM(o.total_amount)
199 FROM %i o
200 WHERE o.customer_id = %d
201 AND o.status IN (' . implode(',', array_fill(0, count($statuses), '%s')) . ')
202 AND o.date_created_gmt >= DATE_SUB(current_timestamp, INTERVAL %d SECOND)
203 ',
204 array_merge(
205 [
206 $wpdb->prefix . 'wc_orders',
207 $customer->get_id(),
208 ],
209 $statuses,
210 [$inTheLastSeconds]
211 )
212 )
213 );
214 }
215
216 return (float)$wpdb->get_var(
217 $wpdb->prepare(
218 "
219 SELECT SUM(pm_total.meta_value)
220 FROM {$wpdb->posts} p
221 LEFT JOIN {$wpdb->postmeta} pm_user ON p.ID = pm_user.post_id AND pm_user.meta_key = '_customer_user'
222 LEFT JOIN {$wpdb->postmeta} pm_total ON p.ID = pm_total.post_id AND pm_total.meta_key = '_order_total'
223 WHERE p.post_type = 'shop_order'
224 AND p.post_status IN (" . implode(',', array_fill(0, count($statuses), '%s')) . ")
225 AND pm_user.meta_value = %d
226 AND p.post_date_gmt >= DATE_SUB(current_timestamp, INTERVAL %d SECOND)
227 ",
228 array_merge(
229 $statuses,
230 [$customer->get_id(), $inTheLastSeconds]
231 )
232 )
233 );
234 }
235
236 private function getRecentOrderCount(WC_Customer $customer, int $inTheLastSeconds): int {
237 global $wpdb;
238 $statuses = array_keys($this->wooCommerce->wcGetOrderStatuses());
239
240 if ($this->wooCommerce->isWooCommerceCustomOrdersTableEnabled()) {
241 return (int)$wpdb->get_var(
242 $wpdb->prepare(
243 '
244 SELECT COUNT(o.id)
245 FROM %i o
246 WHERE o.customer_id = %d
247 AND o.status IN (' . implode(',', array_fill(0, count($statuses), '%s')) . ')
248 AND o.date_created_gmt >= DATE_SUB(current_timestamp, INTERVAL %d SECOND)
249 ',
250 array_merge(
251 [
252 $wpdb->prefix . 'wc_orders',
253 $customer->get_id(),
254 ],
255 $statuses,
256 [$inTheLastSeconds]
257 )
258 )
259 );
260 }
261
262 return (int)$wpdb->get_var(
263 $wpdb->prepare(
264 "
265 SELECT COUNT(p.ID)
266 FROM {$wpdb->posts} p
267 LEFT JOIN {$wpdb->postmeta} pm_user ON p.ID = pm_user.post_id AND pm_user.meta_key = '_customer_user'
268 WHERE p.post_type = 'shop_order'
269 AND p.post_status IN (" . implode(',', array_fill(0, count($statuses), '%s')) . ")
270 AND pm_user.meta_value = %d
271 AND p.post_date_gmt >= DATE_SUB(current_timestamp, INTERVAL %d SECOND)
272 ",
273 array_merge(
274 $statuses,
275 [
276 $customer->get_id(),
277 $inTheLastSeconds,
278 ]
279 )
280 )
281 );
282 }
283
284 private function getPaidOrderDate(WC_Customer $customer, bool $fetchFirst): ?DateTimeImmutable {
285 global $wpdb;
286 $sorting = $fetchFirst ? 'ASC' : 'DESC';
287 $statuses = array_map(function (string $status) {
288 return "wc-$status";
289 }, $this->wooCommerce->wcGetIsPaidStatuses());
290
291 if ($this->wooCommerce->isWooCommerceCustomOrdersTableEnabled()) {
292 $date = $wpdb->get_var(
293 $wpdb->prepare(
294 '
295 SELECT o.date_created_gmt
296 FROM %i o
297 WHERE o.customer_id = %d
298 AND o.status IN (' . implode(',', array_fill(0, count($statuses), '%s')) . ')
299 AND o.total_amount > 0
300 ORDER BY o.date_created_gmt ' . $sorting /* phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The argument is safe. */ . '
301 LIMIT 1
302 ',
303 array_merge(
304 [
305 $wpdb->prefix . 'wc_orders',
306 $customer->get_id(),
307 ],
308 $statuses
309 )
310 )
311 );
312 } else {
313 $date = $wpdb->get_var(
314 $wpdb->prepare(
315 "
316 SELECT p.post_date_gmt
317 FROM {$wpdb->prefix}posts p
318 LEFT JOIN {$wpdb->prefix}postmeta pm_total ON p.ID = pm_total.post_id AND pm_total.meta_key = '_order_total'
319 LEFT JOIN {$wpdb->prefix}postmeta pm_user ON p.ID = pm_user.post_id AND pm_user.meta_key = '_customer_user'
320 WHERE p.post_type = 'shop_order'
321 AND p.post_status IN (" . implode(',', array_fill(0, count($statuses), '%s')) . ")
322 AND pm_user.meta_value = %d
323 AND pm_total.meta_value > 0
324 ORDER BY p.post_date_gmt " . $sorting /* phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The argument is safe. */ . "
325 LIMIT 1
326 ",
327 array_merge(
328 $statuses,
329 [$customer->get_id()]
330 )
331 )
332 );
333 }
334
335 return $date ? new DateTimeImmutable($date, new DateTimeZone('GMT')) : null;
336 }
337
338 private function getOrderProductTermIds(WC_Customer $customer, string $taxonomy, ?int $inTheLastSeconds = null): array {
339 global $wpdb;
340
341 $statuses = array_map(function (string $status) {
342 return "wc-$status";
343 }, $this->wooCommerce->wcGetIsPaidStatuses());
344 $statusesPlaceholder = implode(',', array_fill(0, count($statuses), '%s'));
345
346 // get all product categories that the customer has purchased
347 if ($this->wooCommerce->isWooCommerceCustomOrdersTableEnabled()) {
348 $inTheLastFilter = isset($inTheLastSeconds) ? 'AND o.date_created_gmt >= DATE_SUB(current_timestamp, INTERVAL %d SECOND)' : '';
349
350 $orderIdsSubquery = "
351 SELECT o.id
352 FROM %i o
353 WHERE o.status IN ($statusesPlaceholder)
354 AND o.customer_id = %d
355 $inTheLastFilter
356 ";
357 $orderIdsSubqueryArgs = array_merge(
358 [$wpdb->prefix . 'wc_orders'],
359 $statuses,
360 [$customer->get_id()],
361 $inTheLastSeconds ? [$inTheLastSeconds] : []
362 );
363 } else {
364 $inTheLastFilter = isset($inTheLastSeconds) ? 'AND p.post_date_gmt >= DATE_SUB(current_timestamp, INTERVAL %d SECOND)' : '';
365
366 $orderIdsSubquery = "
367 SELECT p.ID
368 FROM {$wpdb->posts} p
369 LEFT JOIN {$wpdb->postmeta} pm_user ON p.ID = pm_user.post_id AND pm_user.meta_key = '_customer_user'
370 WHERE p.post_type = 'shop_order'
371 AND p.post_status IN ($statusesPlaceholder)
372 AND pm_user.meta_value = %d
373 $inTheLastFilter
374 ";
375 $orderIdsSubqueryArgs = array_merge(
376 $statuses,
377 [$customer->get_id()],
378 $inTheLastSeconds ? [$inTheLastSeconds] : []
379 );
380 }
381
382 $result = $wpdb->get_col(
383 // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- The number of replacements is dynamic.
384 $wpdb->prepare(
385 "
386 SELECT DISTINCT tt.term_id
387 FROM {$wpdb->term_taxonomy} tt
388 JOIN %i AS oi ON oi.order_id IN (" . $orderIdsSubquery . /* phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The subquery uses placeholders. */ ") AND oi.order_item_type = 'line_item'
389 JOIN %i AS pid ON oi.order_item_id = pid.order_item_id AND pid.meta_key = '_product_id'
390 JOIN {$wpdb->posts} p ON pid.meta_value = p.ID
391 JOIN {$wpdb->term_relationships} tr ON IF(p.post_type = 'product_variation', p.post_parent, p.ID) = tr.object_id AND tr.term_taxonomy_id = tt.term_taxonomy_id
392 WHERE tt.taxonomy = %s
393 ORDER BY tt.term_id ASC
394 ",
395 array_merge(
396 [$wpdb->prefix . 'woocommerce_order_items'],
397 $orderIdsSubqueryArgs,
398 [
399 $wpdb->prefix . 'woocommerce_order_itemmeta',
400 (string)($taxonomy),
401 ]
402 )
403 )
404 );
405 return array_map('intval', $result);
406 }
407
408 private function isInTheLastSeconds(WC_Order $order, ?int $inTheLastSeconds): bool {
409 if ($inTheLastSeconds === null) {
410 return true;
411 }
412 return $order->get_date_created() >= new DateTimeImmutable("-$inTheLastSeconds seconds");
413 }
414 }
415