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 / Segments / RestApi / Endpoints / DynamicSegmentsBulkActionEndpoint.php
mailpoet / lib / Segments / RestApi / Endpoints Last commit date
AbstractSegmentsListingEndpoint.php 1 month ago DynamicSegmentsBulkActionEndpoint.php 2 months ago DynamicSegmentsListingEndpoint.php 1 month ago SegmentRequestValidationTrait.php 2 months ago SegmentsBulkActionEndpoint.php 2 months ago SegmentsEndpoint.php 2 months ago SegmentsListingEndpoint.php 1 month ago index.php 2 months ago
DynamicSegmentsBulkActionEndpoint.php
235 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Segments\RestApi\Endpoints;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\API\REST\ApiException;
9 use MailPoet\API\REST\Request;
10 use MailPoet\API\REST\Response;
11 use MailPoet\Entities\SegmentEntity;
12 use MailPoet\Listing\Handler as ListingHandler;
13 use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
14 use MailPoet\Segments\DynamicSegments\DynamicSegmentsListingRepository;
15 use MailPoet\Segments\SegmentsRepository;
16 use MailPoet\Validator\Builder;
17
18 class DynamicSegmentsBulkActionEndpoint extends SegmentsEndpoint {
19 private const ACTION_TRASH = 'trash';
20 private const ACTION_RESTORE = 'restore';
21 private const ACTION_DELETE = 'delete';
22
23 private const SUPPORTED_ACTIONS = [
24 self::ACTION_TRASH,
25 self::ACTION_RESTORE,
26 self::ACTION_DELETE,
27 ];
28
29 /** @var SegmentsRepository */
30 private $segmentsRepository;
31
32 /** @var ListingHandler */
33 private $listingHandler;
34
35 /** @var DynamicSegmentsListingRepository */
36 private $dynamicSegmentsListingRepository;
37
38 /** @var NewsletterSegmentRepository */
39 private $newsletterSegmentRepository;
40
41 public function __construct(
42 ListingHandler $listingHandler,
43 DynamicSegmentsListingRepository $dynamicSegmentsListingRepository,
44 SegmentsRepository $segmentsRepository,
45 NewsletterSegmentRepository $newsletterSegmentRepository
46 ) {
47 $this->listingHandler = $listingHandler;
48 $this->dynamicSegmentsListingRepository = $dynamicSegmentsListingRepository;
49 $this->segmentsRepository = $segmentsRepository;
50 $this->newsletterSegmentRepository = $newsletterSegmentRepository;
51 }
52
53 public function handle(Request $request): Response {
54 $action = $this->validateAction($request);
55 $this->validateGroup(is_string($request->getParam('group')) ? (string)$request->getParam('group') : null);
56 $orderParam = $request->getParam('order') ?? $request->getParam('sort_order');
57 $this->validateOrder(is_string($orderParam) ? (string)$orderParam : null, 'desc');
58 $this->validatePage($request->getParam('page'));
59 $this->validateOffset($request->getParam('offset'));
60 $this->validatePerPage($request->getParam('per_page') ?? $request->getParam('limit'), 25);
61 $orderbyParam = $request->getParam('orderby') ?? $request->getParam('sort_by');
62 $orderby = is_string($orderbyParam) && $orderbyParam !== ''
63 ? (string)$orderbyParam
64 : 'updated_at';
65 $allowedSortFields = ['name', 'created_at', 'updated_at'];
66 if (!in_array($orderby, $allowedSortFields, true)) {
67 throw new ApiException(
68 sprintf(
69 // translators: %s is the list of supported sort fields.
70 __('Unsupported sort field. Allowed values are: %s.', 'mailpoet'),
71 implode(', ', $allowedSortFields)
72 ),
73 400,
74 'mailpoet_segments_invalid_orderby'
75 );
76 }
77 $ids = $this->getSelectedIds($request, $orderby, is_string($orderParam) ? strtolower($orderParam) : 'desc');
78 $this->validateDynamicSelection($ids);
79 if ($action === self::ACTION_DELETE) {
80 $this->validateDeletionSelection($ids);
81 }
82
83 $result = [
84 'updated' => 0,
85 'deleted' => 0,
86 'skipped' => 0,
87 'errors' => [],
88 ];
89
90 if ($action === self::ACTION_TRASH) {
91 $this->trashSegments($ids, $result);
92 } elseif ($action === self::ACTION_RESTORE) {
93 $result['updated'] = $this->segmentsRepository->bulkRestore($ids, SegmentEntity::TYPE_DYNAMIC);
94 } else {
95 $result['deleted'] = $this->segmentsRepository->bulkDelete($ids, SegmentEntity::TYPE_DYNAMIC);
96 }
97
98 return new Response($result);
99 }
100
101 public static function getRequestSchema(): array {
102 return [
103 'action' => Builder::string()->required(),
104 'ids' => Builder::array(Builder::integer()),
105 'select_all' => Builder::boolean(),
106 'group' => Builder::string(),
107 'search' => Builder::string(),
108 'page' => Builder::integer(),
109 'per_page' => Builder::integer(),
110 'limit' => Builder::integer(),
111 'offset' => Builder::integer(),
112 'orderby' => Builder::string(),
113 'order' => Builder::string(),
114 'sort_by' => Builder::string(),
115 'sort_order' => Builder::string(),
116 ];
117 }
118
119 private function validateAction(Request $request): string {
120 $action = is_string($request->getParam('action')) ? (string)$request->getParam('action') : '';
121 if (!in_array($action, self::SUPPORTED_ACTIONS, true)) {
122 throw new ApiException(
123 sprintf(
124 // translators: %s is the list of supported bulk actions.
125 __('Unsupported bulk action. Allowed values are: %s.', 'mailpoet'),
126 implode(', ', self::SUPPORTED_ACTIONS)
127 ),
128 400,
129 'mailpoet_dynamic_segments_invalid_bulk_action'
130 );
131 }
132 return $action;
133 }
134
135 /**
136 * @return int[]
137 */
138 private function getSelectedIds(Request $request, string $sortBy, string $sortOrder): array {
139 if ($request->getParam('select_all') !== true) {
140 return $this->validateIds($request->getParam('ids'));
141 }
142
143 $definition = $this->listingHandler->getListingDefinition([
144 'group' => $this->validateGroup(is_string($request->getParam('group')) ? (string)$request->getParam('group') : null),
145 'search' => is_string($request->getParam('search')) ? (string)$request->getParam('search') : null,
146 'sort_by' => $sortBy,
147 'sort_order' => $sortOrder,
148 'params' => ['segments'],
149 ]);
150 $ids = $this->dynamicSegmentsListingRepository->getActionableIds($definition);
151 $ids = array_map('intval', $ids);
152 if ($ids === []) {
153 throw new ApiException(
154 __('At least one segment id is required.', 'mailpoet'),
155 400,
156 'mailpoet_segments_ids_required'
157 );
158 }
159 return $ids;
160 }
161
162 /**
163 * @param int[] $ids
164 * @param array{updated:int,deleted:int,skipped:int,errors:array<int, array{id:int|null,message:string}>} $result
165 */
166 private function trashSegments(array $ids, array &$result): void {
167 $newsletterBlockers = $this->newsletterSegmentRepository->getSubjectsOfActivelyUsedEmailsForSegments($ids);
168 $trashIds = [];
169 foreach ($ids as $id) {
170 if (isset($newsletterBlockers[$id])) {
171 $segment = $this->segmentsRepository->findOneById($id);
172 $this->skip($result, $id, sprintf(
173 // translators: %1$s is the name of the segment, %2$s is a comma-separated list of emails for which the segment is used.
174 _x('Segment \'%1$s\' cannot be deleted because it’s used for \'%2$s\' email', 'Alert shown when trying to delete segment, which is assigned to any automatic emails.', 'mailpoet'),
175 $segment instanceof SegmentEntity ? $segment->getName() : (string)$id,
176 join("', '", $newsletterBlockers[$id])
177 ));
178 continue;
179 }
180 $trashIds[] = $id;
181 }
182 $result['updated'] = $this->segmentsRepository->bulkTrash($trashIds, SegmentEntity::TYPE_DYNAMIC);
183 }
184
185 /**
186 * @param int[] $ids
187 */
188 private function validateDynamicSelection(array $ids): void {
189 foreach ($ids as $id) {
190 $segment = $this->segmentsRepository->findOneById($id);
191 if (!$segment instanceof SegmentEntity) {
192 throw new ApiException(
193 __('One or more selected dynamic segments were not found.', 'mailpoet'),
194 400,
195 'mailpoet_dynamic_segments_not_found'
196 );
197 }
198 if ($segment->getType() !== SegmentEntity::TYPE_DYNAMIC) {
199 throw new ApiException(
200 __('This endpoint only supports dynamic segments.', 'mailpoet'),
201 400,
202 'mailpoet_dynamic_segments_invalid_type'
203 );
204 }
205 }
206 }
207
208 /**
209 * @param int[] $ids
210 */
211 private function validateDeletionSelection(array $ids): void {
212 foreach ($ids as $id) {
213 $segment = $this->segmentsRepository->findOneById($id);
214 if ($segment instanceof SegmentEntity && $segment->getDeletedAt() === null) {
215 throw new ApiException(
216 __('Only dynamic segments in the trash can be permanently deleted.', 'mailpoet'),
217 400,
218 'mailpoet_dynamic_segments_delete_requires_trash'
219 );
220 }
221 }
222 }
223
224 /**
225 * @param array{updated:int,deleted:int,skipped:int,errors:array<int, array{id:int|null,message:string}>} $result
226 */
227 private function skip(array &$result, ?int $id, string $message): void {
228 $result['skipped']++;
229 $result['errors'][] = [
230 'id' => $id,
231 'message' => $message,
232 ];
233 }
234 }
235