PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.1
MailPoet – Newsletters, Email Marketing, and Automation v5.34.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 1 month 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 1 month 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 TrackingConsentController.php 1 month ago index.php 3 years ago
BulkActionController.php
262 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, trigger_automations?: bool} $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 $skipHooks = !($data['trigger_automations'] ?? false);
110
111 $ids = $this->subscriberListingRepository->getActionableIds($definition);
112 $count = $this->dispatch($action, $ids, $segment, $tag, $skipHooks);
113
114 $result = ['count' => $count];
115 if ($segment instanceof SegmentEntity) {
116 $result['segment'] = ['id' => (int)$segment->getId(), 'name' => (string)$segment->getName()];
117 }
118 if ($tag instanceof TagEntity) {
119 $result['tag'] = ['id' => (int)$tag->getId(), 'name' => (string)$tag->getName()];
120 }
121 return $result;
122 }
123
124 /**
125 * @param int[] $ids
126 */
127 private function dispatch(
128 string $action,
129 array $ids,
130 ?SegmentEntity $segment,
131 ?TagEntity $tag,
132 bool $skipHooks
133 ): int {
134 switch ($action) {
135 case self::ACTION_TRASH:
136 return $this->subscribersRepository->bulkTrash($ids);
137 case self::ACTION_RESTORE:
138 return $this->subscribersRepository->bulkRestore($ids);
139 case self::ACTION_DELETE:
140 return $this->subscribersRepository->bulkDelete($ids);
141 case self::ACTION_REMOVE_FROM_ALL_LISTS:
142 return $this->subscribersRepository->bulkRemoveFromAllSegments($ids);
143 case self::ACTION_UNSUBSCRIBE:
144 $this->trackBulkUnsubscribe($ids);
145 return $this->subscribersRepository->bulkUnsubscribe($ids);
146 case self::ACTION_MOVE_TO_LIST:
147 return $this->subscribersRepository->bulkMoveToSegment(
148 $this->requireSegment($segment, $action),
149 $ids,
150 $skipHooks
151 );
152 case self::ACTION_ADD_TO_LIST:
153 return $this->subscribersRepository->bulkAddToSegment(
154 $this->requireSegment($segment, $action),
155 $ids,
156 $skipHooks
157 );
158 case self::ACTION_REMOVE_FROM_LIST:
159 return $this->subscribersRepository->bulkRemoveFromSegment($this->requireSegment($segment, $action), $ids);
160 case self::ACTION_ADD_TAG:
161 return $this->subscribersRepository->bulkAddTag($this->requireTag($tag, $action), $ids, $skipHooks);
162 case self::ACTION_REMOVE_TAG:
163 return $this->subscribersRepository->bulkRemoveTag($this->requireTag($tag, $action), $ids, $skipHooks);
164 default:
165 // Reachable only if SUPPORTED_ACTIONS and this switch drift out of sync.
166 throw new BulkActionException(
167 // translators: %s is the offending bulk-action name.
168 sprintf(__("Invalid bulk action '%s' provided.", 'mailpoet'), $action),
169 'mailpoet_subscribers_invalid_bulk_action',
170 400
171 );
172 }
173 }
174
175 /**
176 * @param array{segment_id?: int|string} $data
177 */
178 private function resolveSegment(array $data): SegmentEntity {
179 if (!isset($data['segment_id'])) {
180 throw new BulkActionException(
181 __('A list is required for this bulk action.', 'mailpoet'),
182 'mailpoet_subscribers_missing_segment',
183 400
184 );
185 }
186 $segment = $this->segmentsRepository->findOneById((int)$data['segment_id']);
187 if (!$segment instanceof SegmentEntity) {
188 throw new BulkActionException(
189 __('This list does not exist.', 'mailpoet'),
190 'mailpoet_subscribers_segment_not_found',
191 404
192 );
193 }
194 return $segment;
195 }
196
197 /**
198 * @param array{tag_id?: int|string} $data
199 */
200 private function resolveTag(array $data): TagEntity {
201 if (!isset($data['tag_id'])) {
202 throw new BulkActionException(
203 __('A tag is required for this bulk action.', 'mailpoet'),
204 'mailpoet_subscribers_missing_tag',
205 400
206 );
207 }
208 $tag = $this->tagRepository->findOneById((int)$data['tag_id']);
209 if (!$tag instanceof TagEntity) {
210 throw new BulkActionException(
211 __('This tag does not exist.', 'mailpoet'),
212 'mailpoet_subscribers_tag_not_found',
213 404
214 );
215 }
216 return $tag;
217 }
218
219 private function requireSegment(?SegmentEntity $segment, string $action): SegmentEntity {
220 if (!$segment instanceof SegmentEntity) {
221 throw new BulkActionException(
222 // translators: %s is the action name.
223 sprintf(__("The '%s' bulk action requires a list.", 'mailpoet'), $action),
224 'mailpoet_subscribers_missing_segment',
225 400
226 );
227 }
228 return $segment;
229 }
230
231 private function requireTag(?TagEntity $tag, string $action): TagEntity {
232 if (!$tag instanceof TagEntity) {
233 throw new BulkActionException(
234 // translators: %s is the action name.
235 sprintf(__("The '%s' bulk action requires a tag.", 'mailpoet'), $action),
236 'mailpoet_subscribers_missing_tag',
237 400
238 );
239 }
240 return $tag;
241 }
242
243 /**
244 * @param int[] $ids
245 */
246 private function trackBulkUnsubscribe(array $ids): void {
247 if ($ids === []) return;
248 $subscribers = $this->subscribersRepository->findBy(['id' => $ids]);
249 foreach ($subscribers as $subscriber) {
250 if (
251 $subscriber instanceof SubscriberEntity
252 && $subscriber->getStatus() !== SubscriberEntity::STATUS_UNSUBSCRIBED
253 ) {
254 $this->unsubscribesTracker->track(
255 (int)$subscriber->getId(),
256 StatisticsUnsubscribeEntity::SOURCE_ADMINISTRATOR
257 );
258 }
259 }
260 }
261 }
262