PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.35.1
MailPoet – Newsletters, Email Marketing, and Automation v5.35.1
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 / EmailEditor / Integrations / MailPoet / Coupons / CouponBlockValidator.php
mailpoet / lib / EmailEditor / Integrations / MailPoet / Coupons Last commit date
CouponBlock.php 3 months ago CouponBlockAttributeParser.php 3 months ago CouponBlockDetector.php 3 months ago CouponBlockFailureTranslator.php 3 months ago CouponBlockGenerationFailureCollector.php 3 months ago CouponBlockGenerator.php 3 months ago CouponBlockValidationException.php 3 months ago CouponBlockValidator.php 3 months ago EmailContextBuilder.php 1 month ago index.php 3 months ago
CouponBlockValidator.php
198 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\EmailEditor\Integrations\MailPoet\Coupons;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\WooCommerce\Helper;
9 use MailPoet\WP\Functions as WPFunctions;
10
11 class CouponBlockValidator {
12 /** @var Helper */
13 private $wcHelper;
14
15 /** @var WPFunctions */
16 private $wp;
17
18 public function __construct(
19 Helper $wcHelper,
20 WPFunctions $wp
21 ) {
22 $this->wcHelper = $wcHelper;
23 $this->wp = $wp;
24 }
25
26 public function validate(array $attrs, string $recipientEmail): array {
27 $discountType = $this->validateDiscountType($attrs['discountType'] ?? null);
28 $amount = $this->validateRequiredNumber($attrs['amount'] ?? null, 'amount');
29 if ($discountType === 'percent' && $amount > 100) {
30 throw new CouponBlockValidationException('Percent coupon amount must be 100 or lower.');
31 }
32
33 $minimumAmount = $this->validateOptionalNumber($attrs['minimumAmount'] ?? null, 'minimumAmount');
34 $maximumAmount = $this->validateOptionalNumber($attrs['maximumAmount'] ?? null, 'maximumAmount');
35 if ($minimumAmount !== '' && $maximumAmount !== '' && $maximumAmount < $minimumAmount) {
36 throw new CouponBlockValidationException('Maximum amount must be greater than or equal to minimum amount.');
37 }
38
39 $emailRestrictions = $this->validateEmailRestrictions($attrs['emailRestrictions'] ?? []);
40 if (CouponBlockAttributeParser::toBoolean($attrs['restrictToSubscriber'] ?? false)) {
41 $recipientEmailRestrictions = $this->validateEmailRestrictions([$recipientEmail]);
42 if (empty($recipientEmailRestrictions)) {
43 throw new CouponBlockValidationException('Recipient email is required for recipient-restricted coupons.');
44 }
45 $emailRestrictions[] = $recipientEmailRestrictions[0];
46 }
47
48 return [
49 'discountType' => $discountType,
50 'amount' => $amount,
51 'expiryDay' => $this->validateOptionalInteger($attrs['expiryDay'] ?? null, 'expiryDay'),
52 'freeShipping' => CouponBlockAttributeParser::toBoolean($attrs['freeShipping'] ?? false),
53 'individualUse' => CouponBlockAttributeParser::toBoolean($attrs['individualUse'] ?? false),
54 'excludeSaleItems' => CouponBlockAttributeParser::toBoolean($attrs['excludeSaleItems'] ?? false),
55 'usageLimit' => $this->validateOptionalInteger($attrs['usageLimit'] ?? null, 'usageLimit'),
56 'usageLimitPerUser' => $this->validateOptionalInteger($attrs['usageLimitPerUser'] ?? null, 'usageLimitPerUser'),
57 'minimumAmount' => $minimumAmount,
58 'maximumAmount' => $maximumAmount,
59 'productIds' => $this->validateProductIds($attrs['productIds'] ?? []),
60 'excludedProductIds' => $this->validateProductIds($attrs['excludedProductIds'] ?? []),
61 'productCategoryIds' => $this->validateProductCategoryIds($attrs['productCategoryIds'] ?? []),
62 'excludedProductCategoryIds' => $this->validateProductCategoryIds($attrs['excludedProductCategoryIds'] ?? []),
63 'emailRestrictions' => array_values(array_unique($emailRestrictions)),
64 ];
65 }
66
67 private function validateDiscountType($discountType): string {
68 if (!is_string($discountType) || $discountType === '') {
69 throw new CouponBlockValidationException('Discount type is required.');
70 }
71
72 if (!in_array($discountType, array_keys($this->wcHelper->wcGetCouponTypes()), true)) {
73 throw new CouponBlockValidationException('Invalid discount type.');
74 }
75
76 return $discountType;
77 }
78
79 private function validateRequiredNumber($value, string $field): float {
80 if ($value === null || $value === '' || !is_numeric($value)) {
81 throw new CouponBlockValidationException(sprintf('%s must be numeric.', $field));
82 }
83
84 $value = (float)$value;
85 if ($value < 0) {
86 throw new CouponBlockValidationException(sprintf('%s must be greater than or equal to 0.', $field));
87 }
88
89 return $value;
90 }
91
92 /**
93 * @return float|''
94 */
95 private function validateOptionalNumber($value, string $field) {
96 if ($value === null || $value === '') {
97 return '';
98 }
99
100 return $this->validateRequiredNumber($value, $field);
101 }
102
103 private function validateOptionalInteger($value, string $field): int {
104 if ($value === null || $value === '') {
105 return 0;
106 }
107
108 if (filter_var($value, FILTER_VALIDATE_INT) === false) {
109 throw new CouponBlockValidationException(sprintf('%s must be an integer.', $field));
110 }
111
112 $value = (int)$value;
113 if ($value < 0) {
114 throw new CouponBlockValidationException(sprintf('%s must be greater than or equal to 0.', $field));
115 }
116
117 return $value;
118 }
119
120 private function validateProductIds($items): array {
121 $ids = $this->extractItemIds($items, 'product');
122 foreach ($ids as $id) {
123 if (!$this->wcHelper->wcGetProduct($id)) {
124 throw new CouponBlockValidationException('Invalid product ID.');
125 }
126 }
127 return $ids;
128 }
129
130 private function validateProductCategoryIds($items): array {
131 $ids = $this->extractItemIds($items, 'product category');
132 foreach ($ids as $id) {
133 $term = $this->wp->getTerm($id, 'product_cat');
134 if (!$term || $this->wp->isWpError($term)) {
135 throw new CouponBlockValidationException('Invalid product category ID.');
136 }
137 }
138 return $ids;
139 }
140
141 private function extractItemIds($items, string $label): array {
142 if ($items === null || $items === '') {
143 return [];
144 }
145 if (!is_array($items)) {
146 throw new CouponBlockValidationException(sprintf('Invalid %s IDs.', $label));
147 }
148
149 $ids = [];
150 foreach ($items as $item) {
151 $id = null;
152 if (is_array($item)) {
153 $id = $item['id'] ?? null;
154 } elseif (is_object($item) && isset($item->id)) {
155 $id = $item->id;
156 }
157
158 if (filter_var($id, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]) === false) {
159 throw new CouponBlockValidationException(sprintf('Invalid %s ID.', $label));
160 }
161 $ids[] = (int)$id;
162 }
163
164 return array_values(array_unique($ids));
165 }
166
167 private function validateEmailRestrictions($raw): array {
168 if ($raw === null || $raw === '') {
169 return [];
170 }
171
172 if (is_string($raw)) {
173 $raw = explode(',', $raw);
174 }
175
176 if (!is_array($raw)) {
177 throw new CouponBlockValidationException('Invalid email restrictions.');
178 }
179
180 $emails = [];
181 foreach ($raw as $email) {
182 if (!is_string($email)) {
183 throw new CouponBlockValidationException('Invalid email restriction.');
184 }
185 $email = strtolower(trim($this->wp->sanitizeEmail($email)));
186 if (!$email) {
187 continue;
188 }
189 if (!$this->wp->isEmail($email)) {
190 throw new CouponBlockValidationException('Invalid email restriction.');
191 }
192 $emails[] = $email;
193 }
194
195 return array_values(array_unique($emails));
196 }
197 }
198