PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.3
MailPoet – Newsletters, Email Marketing, and Automation v5.34.3
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 / API / JSON / v1 / Segments.php
mailpoet / lib / API / JSON / v1 Last commit date
Analytics.php 1 week ago AutomatedLatestContent.php 2 months ago AutomaticEmails.php 3 years ago Captcha.php 1 year ago Coupons.php 2 years ago CustomFields.php 3 months ago DynamicProducts.php 1 year ago DynamicSegments.php 2 months ago FeatureFlags.php 3 years ago Forms.php 2 months ago Help.php 1 year ago ImportExport.php 3 months ago Mailer.php 1 year ago NewsletterLinks.php 2 months ago NewsletterTemplates.php 3 months ago Newsletters.php 2 months ago Premium.php 11 months ago RedirectResponse.php 1 year ago Segments.php 1 week ago SendingQueue.php 3 months ago Services.php 6 months ago Settings.php 3 months ago Setup.php 1 year ago StatisticsExport.php 3 months ago SubscriberStats.php 2 months ago Subscribers.php 2 months ago Tags.php 3 years ago UserFlags.php 2 years ago WoocommerceProductVariations.php 2 months ago WoocommerceSettings.php 3 years ago index.php 3 years ago
Segments.php
307 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\API\JSON\v1;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use Exception;
9 use MailPoet\API\JSON\Endpoint as APIEndpoint;
10 use MailPoet\API\JSON\Error as APIError;
11 use MailPoet\API\JSON\Response;
12 use MailPoet\API\JSON\ResponseBuilders\SegmentsResponseBuilder;
13 use MailPoet\Config\AccessControl;
14 use MailPoet\ConflictException;
15 use MailPoet\Cron\CronWorkerScheduler;
16 use MailPoet\Cron\Workers\WooCommerceSync;
17 use MailPoet\Doctrine\Validator\ValidationException;
18 use MailPoet\Entities\SegmentEntity;
19 use MailPoet\Entities\SubscriberEntity;
20 use MailPoet\Form\FormsRepository;
21 use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
22 use MailPoet\Segments\SegmentSaveController;
23 use MailPoet\Segments\SegmentsRepository;
24 use MailPoet\Segments\SegmentSubscribersRepository;
25 use MailPoet\Segments\WP;
26 use MailPoet\Subscribers\SubscribersRepository;
27 use Throwable;
28
29 class Segments extends APIEndpoint {
30 public $permissions = [
31 'global' => AccessControl::PERMISSION_MANAGE_SEGMENTS,
32 ];
33
34 /** @var SegmentsRepository */
35 private $segmentsRepository;
36
37 /** @var SegmentsResponseBuilder */
38 private $segmentsResponseBuilder;
39
40 /** @var SegmentSaveController */
41 private $segmentSavecontroller;
42
43 /** @var SubscribersRepository */
44 private $subscribersRepository;
45
46 /** @var WP */
47 private $wpSegment;
48
49 /** @var NewsletterSegmentRepository */
50 private $newsletterSegmentRepository;
51
52 /** @var CronWorkerScheduler */
53 private $cronWorkerScheduler;
54
55 /** @var FormsRepository */
56 private $formsRepository;
57
58 /** @var SegmentSubscribersRepository */
59 private $segmentSubscribersRepository;
60
61 public function __construct(
62 SegmentsRepository $segmentsRepository,
63 SegmentsResponseBuilder $segmentsResponseBuilder,
64 SegmentSaveController $segmentSavecontroller,
65 SegmentSubscribersRepository $segmentSubscribersRepository,
66 SubscribersRepository $subscribersRepository,
67 WP $wpSegment,
68 NewsletterSegmentRepository $newsletterSegmentRepository,
69 CronWorkerScheduler $cronWorkerScheduler,
70 FormsRepository $formsRepository
71 ) {
72 $this->segmentsRepository = $segmentsRepository;
73 $this->segmentsResponseBuilder = $segmentsResponseBuilder;
74 $this->segmentSavecontroller = $segmentSavecontroller;
75 $this->subscribersRepository = $subscribersRepository;
76 $this->wpSegment = $wpSegment;
77 $this->newsletterSegmentRepository = $newsletterSegmentRepository;
78 $this->cronWorkerScheduler = $cronWorkerScheduler;
79 $this->formsRepository = $formsRepository;
80 $this->segmentSubscribersRepository = $segmentSubscribersRepository;
81 }
82
83 public function get($data = []) {
84 $id = (isset($data['id']) ? (int)$data['id'] : false);
85 $segment = $this->segmentsRepository->findOneById($id);
86 if ($segment instanceof SegmentEntity) {
87 return $this->successResponse($this->segmentsResponseBuilder->build($segment));
88 } else {
89 return $this->errorResponse([
90 APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'),
91 ]);
92 }
93 }
94
95 public function save($data = []) {
96 try {
97 $data['name'] = isset($data['name']) ? sanitize_text_field($data['name']) : '';
98 $data['description'] = isset($data['description']) ? sanitize_textarea_field($data['description']) : '';
99 if (array_key_exists('public_description', $data)) {
100 $data['public_description'] = sanitize_textarea_field((string)$data['public_description']);
101 }
102 $segment = $this->segmentSavecontroller->save($data);
103 } catch (ValidationException $exception) {
104 return $this->badRequest([
105 APIError::BAD_REQUEST => __('Please specify a name.', 'mailpoet'),
106 ]);
107 } catch (ConflictException $exception) {
108 return $this->badRequest([
109 APIError::BAD_REQUEST => __('Another record already exists. Please specify a different "name".', 'mailpoet'),
110 ]);
111 }
112 $response = $this->segmentsResponseBuilder->build($segment);
113 return $this->successResponse($response);
114 }
115
116 public function restore($data = []) {
117 $segment = $this->getSegment($data);
118 if ($segment instanceof SegmentEntity) {
119 if (!$this->isTrashOrRestoreAllowed($segment)) {
120 return $this->errorResponse([
121 APIError::FORBIDDEN => __('This list cannot be moved to trash.', 'mailpoet'),
122 ]);
123 }
124 // When the segment is of type WP_USERS we want to restore all its subscribers
125 if ($segment->getType() === SegmentEntity::TYPE_WP_USERS) {
126 $subscribers = $this->subscribersRepository->findBySegment((int)$segment->getId());
127 $subscriberIds = array_map(function (SubscriberEntity $subscriberEntity): int {
128 return (int)$subscriberEntity->getId();
129 }, $subscribers);
130 $this->subscribersRepository->bulkRestore($subscriberIds);
131 }
132
133 $this->segmentsRepository->bulkRestore([$segment->getId()], $segment->getType());
134 $this->segmentsRepository->refresh($segment);
135 return $this->successResponse(
136 $this->segmentsResponseBuilder->build($segment),
137 ['count' => 1]
138 );
139 } else {
140 return $this->errorResponse([
141 APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'),
142 ]);
143 }
144 }
145
146 public function trash($data = []) {
147 $segment = $this->getSegment($data);
148 if (!$segment instanceof SegmentEntity) {
149
150 return $this->errorResponse([
151 APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'),
152 ]);
153 }
154
155 if (!$this->isTrashOrRestoreAllowed($segment)) {
156 return $this->errorResponse([
157 APIError::FORBIDDEN => __('This list cannot be moved to trash.', 'mailpoet'),
158 ]);
159 }
160
161 $activelyUsedNewslettersSubjects = $this->newsletterSegmentRepository->getSubjectsOfActivelyUsedEmailsForSegments([$segment->getId()]);
162 if (isset($activelyUsedNewslettersSubjects[$segment->getId()])) {
163 return $this->badRequest([
164 APIError::BAD_REQUEST => str_replace(
165 '%1$s',
166 "'" . join("', '", $activelyUsedNewslettersSubjects[$segment->getId()]) . "'",
167 // translators: %1$s is a comma-separated list of emails for which the segment is used.
168 _x('List cannot be deleted because it’s used for %1$s email', 'Alert shown when trying to delete segment, which is assigned to any automatic emails.', 'mailpoet')
169 ),
170 ]);
171 }
172
173 $activelyUsedFormNames = $this->formsRepository->getNamesOfFormsForSegments();
174 if (isset($activelyUsedFormNames[$segment->getId()])) {
175 return $this->badRequest([
176 APIError::BAD_REQUEST => str_replace(
177 '%1$s',
178 "'" . join("', '", $activelyUsedFormNames[$segment->getId()]) . "'",
179 // translators: %1$s is a comma-separated list of forms for which the segment is used.
180 _nx(
181 'List cannot be deleted because it’s used for %1$s form',
182 'List cannot be deleted because it’s used for %1$s forms',
183 count($activelyUsedFormNames[$segment->getId()]),
184 'Alert shown when trying to delete segment, when it is assigned to a form.',
185 'mailpoet'
186 )
187 ),
188 ]);
189 }
190
191 // When the segment is of type WP_USERS we want to trash all subscribers who aren't subscribed in another list
192 if ($segment->getType() === SegmentEntity::TYPE_WP_USERS) {
193 $subscribers = $this->subscribersRepository->findExclusiveSubscribersBySegment((int)$segment->getId());
194 $subscriberIds = array_map(function (SubscriberEntity $subscriberEntity): int {
195 return (int)$subscriberEntity->getId();
196 }, $subscribers);
197 $this->subscribersRepository->bulkTrash($subscriberIds);
198 }
199
200 $this->segmentsRepository->doTrash([$segment->getId()], $segment->getType());
201 $this->segmentsRepository->refresh($segment);
202 return $this->successResponse(
203 $this->segmentsResponseBuilder->build($segment),
204 ['count' => 1]
205 );
206 }
207
208 public function delete($data = []) {
209 $segment = $this->getSegment($data);
210 if ($segment instanceof SegmentEntity) {
211 $this->segmentsRepository->bulkDelete([$segment->getId()]);
212 return $this->successResponse(null, ['count' => 1]);
213 } else {
214 return $this->errorResponse([
215 APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'),
216 ]);
217 }
218 }
219
220 public function duplicate($data = []) {
221 $segment = $this->getSegment($data);
222
223 if ($segment instanceof SegmentEntity) {
224 try {
225 $duplicate = $this->segmentSavecontroller->duplicate($segment);
226 } catch (Exception $e) {
227 return $this->errorResponse([
228 APIError::UNKNOWN => __('Duplicating of segment failed.', 'mailpoet'),
229 ], [], Response::STATUS_UNKNOWN);
230 }
231 return $this->successResponse(
232 $this->segmentsResponseBuilder->build($duplicate),
233 ['count' => 1]
234 );
235 } else {
236 return $this->errorResponse([
237 APIError::NOT_FOUND => __('This list does not exist.', 'mailpoet'),
238 ]);
239 }
240 }
241
242 public function synchronize($data) {
243 try {
244 if ($data['type'] === SegmentEntity::TYPE_WC_USERS) {
245 $this->cronWorkerScheduler->scheduleImmediatelyIfNotRunning(WooCommerceSync::TASK_TYPE);
246 } else {
247 $this->wpSegment->synchronizeUsers();
248 }
249 } catch (\Exception $e) {
250 return $this->errorResponse([
251 $e->getCode() => $e->getMessage(),
252 ]);
253 }
254
255 return $this->successResponse(null);
256 }
257
258 public function subscriberCount($data = []) {
259 $segmentIds = $data['segmentIds'] ?? [];
260 if (empty($segmentIds)) {
261 return $this->errorResponse([
262 APIError::BAD_REQUEST => __('No segment IDs provided.', 'mailpoet'),
263 ]);
264 }
265 // filterSegmentId arrives as a string (it can come straight from a URL query
266 // param). Empty and 0 mean "no filter segment"; anything else must be a valid
267 // positive segment id, otherwise the count would be silently wrong.
268 $filterSegmentId = null;
269 if (!empty($data['filterSegmentId'])) {
270 $filterSegmentId = filter_var($data['filterSegmentId'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
271 if ($filterSegmentId === false) {
272 return $this->badRequest([
273 APIError::BAD_REQUEST => __('Invalid filter segment.', 'mailpoet'),
274 ]);
275 }
276 }
277 $status = $data['status'] ?? SubscriberEntity::STATUS_SUBSCRIBED;
278 try {
279 $response['count'] = $this->segmentSubscribersRepository->getSubscribersCountBySegmentIds($segmentIds, $status, $filterSegmentId);
280 } catch (Throwable $e) {
281 return $this->errorResponse([
282 APIError::UNKNOWN => __('The recipient count could not be calculated.', 'mailpoet'),
283 ], [], Response::STATUS_UNKNOWN);
284 }
285
286 return $this->successResponse($response);
287 }
288
289 private function isTrashOrRestoreAllowed(SegmentEntity $segment): bool {
290 $allowedSegmentTypes = [
291 SegmentEntity::TYPE_DEFAULT,
292 SegmentEntity::TYPE_WP_USERS,
293 ];
294 if (in_array($segment->getType(), $allowedSegmentTypes, true)) {
295 return true;
296 }
297
298 return false;
299 }
300
301 private function getSegment(array $data): ?SegmentEntity {
302 return isset($data['id'])
303 ? $this->segmentsRepository->findOneById((int)$data['id'])
304 : null;
305 }
306 }
307