PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.33.1
MailPoet – Newsletters, Email Marketing, and Automation v5.33.1
5.37.0 5.36.1 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 / Subscribers / BulkActionController.php
mailpoet / lib / Subscribers Last commit date
ConfirmationEmailTemplate 2 months ago ImportExport 1 month ago RestApi 1 month ago Statistics 2 months ago BulkActionController.php 3 months ago BulkActionException.php 3 months ago BulkConfirmationEmailResender.php 3 months ago ConfirmationEmailCustomizer.php 3 months ago ConfirmationEmailMailer.php 3 months ago ConfirmationEmailResolver.php 3 months ago EngagementDataBackfiller.php 3 months ago InactiveSubscribersController.php 1 month ago LinkTokens.php 3 months ago NewSubscriberNotificationMailer.php 3 months ago RequiredCustomFieldValidator.php 3 months ago SegmentsCountRecalculator.php 1 month ago Source.php 3 months ago SubscriberActions.php 3 months ago SubscriberCustomFieldRepository.php 3 years ago SubscriberIPsRepository.php 2 years ago SubscriberLimitNotificationEvaluator.php 3 months ago SubscriberLimitNotificationMailer.php 3 months ago SubscriberLimitNotificationScheduler.php 3 months ago SubscriberListingRepository.php 1 month ago SubscriberPersonalDataEraser.php 3 months ago SubscriberSaveController.php 3 months ago SubscriberSegmentRepository.php 1 month ago SubscriberSubscribeController.php 1 month ago SubscriberTagRepository.php 4 years ago SubscribersCountsController.php 1 month ago SubscribersEmailCountsController.php 1 month ago SubscribersRepository.php 1 month ago index.php 3 years ago
BulkActionController.php
247 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Subscribers;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Entities\SegmentEntity;
9 use MailPoet\Entities\StatisticsUnsubscribeEntity;
10 use MailPoet\Entities\SubscriberEntity;
11 use MailPoet\Entities\TagEntity;
12 use MailPoet\Listing\ListingDefinition;
13 use MailPoet\Segments\SegmentsRepository;
14 use MailPoet\Statistics\Track\Unsubscribes;
15 use MailPoet\Tags\TagRepository;
16
17 /**
18 * Centralized bulk-action dispatcher used by the REST endpoint at
19 * `mailpoet/v1/subscribers/bulk-action`. Keeping the per-action branch +
20 * reference-data resolution in one place keeps the REST endpoint a thin
21 * HTTP adapter.
22 */
23 class BulkActionController {
24 public const ACTION_TRASH = 'trash';
25 public const ACTION_RESTORE = 'restore';
26 public const ACTION_DELETE = 'delete';
27 public const ACTION_UNSUBSCRIBE = 'unsubscribe';
28 public const ACTION_MOVE_TO_LIST = 'moveToList';
29 public const ACTION_ADD_TO_LIST = 'addToList';
30 public const ACTION_REMOVE_FROM_LIST = 'removeFromList';
31 public const ACTION_REMOVE_FROM_ALL_LISTS = 'removeFromAllLists';
32 public const ACTION_ADD_TAG = 'addTag';
33 public const ACTION_REMOVE_TAG = 'removeTag';
34
35 public const SUPPORTED_ACTIONS = [
36 self::ACTION_TRASH,
37 self::ACTION_RESTORE,
38 self::ACTION_DELETE,
39 self::ACTION_UNSUBSCRIBE,
40 self::ACTION_MOVE_TO_LIST,
41 self::ACTION_ADD_TO_LIST,
42 self::ACTION_REMOVE_FROM_LIST,
43 self::ACTION_REMOVE_FROM_ALL_LISTS,
44 self::ACTION_ADD_TAG,
45 self::ACTION_REMOVE_TAG,
46 ];
47
48 private const ACTIONS_REQUIRING_SEGMENT = [
49 self::ACTION_MOVE_TO_LIST,
50 self::ACTION_ADD_TO_LIST,
51 self::ACTION_REMOVE_FROM_LIST,
52 ];
53
54 private const ACTIONS_REQUIRING_TAG = [
55 self::ACTION_ADD_TAG,
56 self::ACTION_REMOVE_TAG,
57 ];
58
59 /** @var SubscriberListingRepository */
60 private $subscriberListingRepository;
61
62 /** @var SubscribersRepository */
63 private $subscribersRepository;
64
65 /** @var SegmentsRepository */
66 private $segmentsRepository;
67
68 /** @var TagRepository */
69 private $tagRepository;
70
71 /** @var Unsubscribes */
72 private $unsubscribesTracker;
73
74 public function __construct(
75 SubscriberListingRepository $subscriberListingRepository,
76 SubscribersRepository $subscribersRepository,
77 SegmentsRepository $segmentsRepository,
78 TagRepository $tagRepository,
79 Unsubscribes $unsubscribesTracker
80 ) {
81 $this->subscriberListingRepository = $subscriberListingRepository;
82 $this->subscribersRepository = $subscribersRepository;
83 $this->segmentsRepository = $segmentsRepository;
84 $this->tagRepository = $tagRepository;
85 $this->unsubscribesTracker = $unsubscribesTracker;
86 }
87
88 /**
89 * @param array{segment_id?: int|string, tag_id?: int|string} $data
90 * @return array{count: int, segment?: array{id: int, name: string}, tag?: array{id: int, name: string}}
91 * @throws BulkActionException
92 */
93 public function execute(string $action, ListingDefinition $definition, array $data = []): array {
94 if (!in_array($action, self::SUPPORTED_ACTIONS, true)) {
95 throw new BulkActionException(
96 // translators: %s is the offending bulk-action name.
97 sprintf(__("Invalid bulk action '%s' provided.", 'mailpoet'), $action),
98 'mailpoet_subscribers_invalid_bulk_action',
99 400
100 );
101 }
102
103 $segment = in_array($action, self::ACTIONS_REQUIRING_SEGMENT, true) || isset($data['segment_id'])
104 ? $this->resolveSegment($data)
105 : null;
106 $tag = in_array($action, self::ACTIONS_REQUIRING_TAG, true) || isset($data['tag_id'])
107 ? $this->resolveTag($data)
108 : null;
109
110 $ids = $this->subscriberListingRepository->getActionableIds($definition);
111 $count = $this->dispatch($action, $ids, $segment, $tag);
112
113 $result = ['count' => $count];
114 if ($segment instanceof SegmentEntity) {
115 $result['segment'] = ['id' => (int)$segment->getId(), 'name' => (string)$segment->getName()];
116 }
117 if ($tag instanceof TagEntity) {
118 $result['tag'] = ['id' => (int)$tag->getId(), 'name' => (string)$tag->getName()];
119 }
120 return $result;
121 }
122
123 /**
124 * @param int[] $ids
125 */
126 private function dispatch(string $action, array $ids, ?SegmentEntity $segment, ?TagEntity $tag): int {
127 switch ($action) {
128 case self::ACTION_TRASH:
129 return $this->subscribersRepository->bulkTrash($ids);
130 case self::ACTION_RESTORE:
131 return $this->subscribersRepository->bulkRestore($ids);
132 case self::ACTION_DELETE:
133 return $this->subscribersRepository->bulkDelete($ids);
134 case self::ACTION_REMOVE_FROM_ALL_LISTS:
135 return $this->subscribersRepository->bulkRemoveFromAllSegments($ids);
136 case self::ACTION_UNSUBSCRIBE:
137 $this->trackBulkUnsubscribe($ids);
138 return $this->subscribersRepository->bulkUnsubscribe($ids);
139 case self::ACTION_MOVE_TO_LIST:
140 return $this->subscribersRepository->bulkMoveToSegment($this->requireSegment($segment, $action), $ids);
141 case self::ACTION_ADD_TO_LIST:
142 return $this->subscribersRepository->bulkAddToSegment($this->requireSegment($segment, $action), $ids);
143 case self::ACTION_REMOVE_FROM_LIST:
144 return $this->subscribersRepository->bulkRemoveFromSegment($this->requireSegment($segment, $action), $ids);
145 case self::ACTION_ADD_TAG:
146 return $this->subscribersRepository->bulkAddTag($this->requireTag($tag, $action), $ids);
147 case self::ACTION_REMOVE_TAG:
148 return $this->subscribersRepository->bulkRemoveTag($this->requireTag($tag, $action), $ids);
149 default:
150 // Reachable only if SUPPORTED_ACTIONS and this switch drift out of sync.
151 throw new BulkActionException(
152 // translators: %s is the offending bulk-action name.
153 sprintf(__("Invalid bulk action '%s' provided.", 'mailpoet'), $action),
154 'mailpoet_subscribers_invalid_bulk_action',
155 400
156 );
157 }
158 }
159
160 /**
161 * @param array{segment_id?: int|string} $data
162 */
163 private function resolveSegment(array $data): SegmentEntity {
164 if (!isset($data['segment_id'])) {
165 throw new BulkActionException(
166 __('A list is required for this bulk action.', 'mailpoet'),
167 'mailpoet_subscribers_missing_segment',
168 400
169 );
170 }
171 $segment = $this->segmentsRepository->findOneById((int)$data['segment_id']);
172 if (!$segment instanceof SegmentEntity) {
173 throw new BulkActionException(
174 __('This list does not exist.', 'mailpoet'),
175 'mailpoet_subscribers_segment_not_found',
176 404
177 );
178 }
179 return $segment;
180 }
181
182 /**
183 * @param array{tag_id?: int|string} $data
184 */
185 private function resolveTag(array $data): TagEntity {
186 if (!isset($data['tag_id'])) {
187 throw new BulkActionException(
188 __('A tag is required for this bulk action.', 'mailpoet'),
189 'mailpoet_subscribers_missing_tag',
190 400
191 );
192 }
193 $tag = $this->tagRepository->findOneById((int)$data['tag_id']);
194 if (!$tag instanceof TagEntity) {
195 throw new BulkActionException(
196 __('This tag does not exist.', 'mailpoet'),
197 'mailpoet_subscribers_tag_not_found',
198 404
199 );
200 }
201 return $tag;
202 }
203
204 private function requireSegment(?SegmentEntity $segment, string $action): SegmentEntity {
205 if (!$segment instanceof SegmentEntity) {
206 throw new BulkActionException(
207 // translators: %s is the action name.
208 sprintf(__("The '%s' bulk action requires a list.", 'mailpoet'), $action),
209 'mailpoet_subscribers_missing_segment',
210 400
211 );
212 }
213 return $segment;
214 }
215
216 private function requireTag(?TagEntity $tag, string $action): TagEntity {
217 if (!$tag instanceof TagEntity) {
218 throw new BulkActionException(
219 // translators: %s is the action name.
220 sprintf(__("The '%s' bulk action requires a tag.", 'mailpoet'), $action),
221 'mailpoet_subscribers_missing_tag',
222 400
223 );
224 }
225 return $tag;
226 }
227
228 /**
229 * @param int[] $ids
230 */
231 private function trackBulkUnsubscribe(array $ids): void {
232 if ($ids === []) return;
233 $subscribers = $this->subscribersRepository->findBy(['id' => $ids]);
234 foreach ($subscribers as $subscriber) {
235 if (
236 $subscriber instanceof SubscriberEntity
237 && $subscriber->getStatus() !== SubscriberEntity::STATUS_UNSUBSCRIBED
238 ) {
239 $this->unsubscribesTracker->track(
240 (int)$subscriber->getId(),
241 StatisticsUnsubscribeEntity::SOURCE_ADMINISTRATOR
242 );
243 }
244 }
245 }
246 }
247