PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.36.0
MailPoet – Newsletters, Email Marketing, and Automation v5.36.0
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 / Newsletter / RestApi / Endpoints / NewslettersBulkActionEndpoint.php
mailpoet / lib / Newsletter / RestApi / Endpoints Last commit date
NewsletterDuplicateEndpoint.php 2 months ago NewsletterStatusEndpoint.php 2 months ago NewslettersBulkActionEndpoint.php 2 months ago NewslettersListingEndpoint.php 2 months ago SendingStatusListingEndpoint.php 1 month ago SendingStatusResendEndpoint.php 2 months ago index.php 2 months ago
NewslettersBulkActionEndpoint.php
227 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Newsletter\RestApi\Endpoints;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\API\REST\ApiException;
9 use MailPoet\API\REST\Endpoint;
10 use MailPoet\API\REST\Request;
11 use MailPoet\API\REST\Response;
12 use MailPoet\Config\AccessControl;
13 use MailPoet\Cron\Workers\StatisticsExport;
14 use MailPoet\Entities\ScheduledTaskEntity;
15 use MailPoet\Listing\Handler as ListingHandler;
16 use MailPoet\Listing\ListingDefinition;
17 use MailPoet\Newsletter\BulkActionController;
18 use MailPoet\Newsletter\BulkActionException;
19 use MailPoet\Newsletter\Listing\NewsletterListingRepository;
20 use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
21 use MailPoet\Newsletter\Statistics\Export\StatisticsExporter;
22 use MailPoet\Util\License\Features\CapabilitiesManager;
23 use MailPoet\Validator\Builder;
24 use MailPoet\WP\Functions as WPFunctions;
25 use MailPoetVendor\Carbon\Carbon;
26
27 /**
28 * `POST /mailpoet/v1/newsletters/bulk-action`
29 *
30 * Mirrors the legacy JSON endpoint: `trash`, `restore`, and `delete` flow
31 * through {@see BulkActionController}; `export_stats` is handled here because
32 * it is async (it queues a scheduled task) and gated behind a premium
33 * capability instead of the bulk dispatcher's plain list of supported actions.
34 */
35 class NewslettersBulkActionEndpoint extends Endpoint {
36 public const ACTION_EXPORT_STATS = 'export_stats';
37
38 /** @var ListingHandler */
39 private $listingHandler;
40
41 /** @var NewsletterListingRepository */
42 private $newsletterListingRepository;
43
44 /** @var BulkActionController */
45 private $bulkActionController;
46
47 /** @var ScheduledTasksRepository */
48 private $scheduledTasksRepository;
49
50 /** @var CapabilitiesManager */
51 private $capabilitiesManager;
52
53 /** @var WPFunctions */
54 private $wp;
55
56 public function __construct(
57 ListingHandler $listingHandler,
58 NewsletterListingRepository $newsletterListingRepository,
59 BulkActionController $bulkActionController,
60 ScheduledTasksRepository $scheduledTasksRepository,
61 CapabilitiesManager $capabilitiesManager,
62 WPFunctions $wp
63 ) {
64 $this->listingHandler = $listingHandler;
65 $this->newsletterListingRepository = $newsletterListingRepository;
66 $this->bulkActionController = $bulkActionController;
67 $this->scheduledTasksRepository = $scheduledTasksRepository;
68 $this->capabilitiesManager = $capabilitiesManager;
69 $this->wp = $wp;
70 }
71
72 public function checkPermissions(): bool {
73 return $this->wp->currentUserCan(AccessControl::PERMISSION_MANAGE_EMAILS);
74 }
75
76 public function handle(Request $request): Response {
77 $actionParam = $request->getParam('action');
78 $action = is_string($actionParam) ? $actionParam : '';
79 $selectAll = $request->getParam('select_all') === true;
80 $selection = $this->getSelection($request);
81 if ($selection === [] && !$selectAll) {
82 throw new ApiException(
83 __('No newsletters selected.', 'mailpoet'),
84 400,
85 'mailpoet_newsletters_missing_selection'
86 );
87 }
88 $definition = $this->buildDefinition($request, $selection);
89
90 if ($action === self::ACTION_EXPORT_STATS) {
91 return $this->handleExportStats($request, $definition);
92 }
93
94 try {
95 $result = $this->bulkActionController->execute($action, $definition);
96 } catch (BulkActionException $exception) {
97 throw new ApiException(
98 $exception->getMessage(),
99 $exception->getStatusCode(),
100 $exception->getErrorCode()
101 );
102 }
103
104 return new Response([
105 'action' => $result['action'],
106 'count' => $result['count'],
107 ]);
108 }
109
110 public static function getRequestSchema(): array {
111 return [
112 'action' => Builder::string()->required(),
113 'selection' => Builder::array(Builder::integer()),
114 'type' => Builder::string(),
115 'group' => Builder::string(),
116 'search' => Builder::string(),
117 'filter' => Builder::object(),
118 'parent_id' => Builder::integer(),
119 'select_all' => Builder::boolean(),
120 'format' => Builder::string(),
121 ];
122 }
123
124 private function handleExportStats(Request $request, ListingDefinition $definition): Response {
125 $capability = $this->capabilitiesManager->getCapability('detailedAnalytics');
126 if ($capability === null || $capability->isRestricted) {
127 throw new ApiException(
128 __('Bulk statistics export requires a MailPoet plan with detailed analytics.', 'mailpoet'),
129 403,
130 'mailpoet_newsletters_export_forbidden'
131 );
132 }
133
134 $ids = array_values(array_map('intval', $this->newsletterListingRepository->getActionableIds($definition)));
135 if ($ids === []) {
136 throw new ApiException(
137 __('No newsletters selected for export.', 'mailpoet'),
138 400,
139 'mailpoet_newsletters_export_empty'
140 );
141 }
142
143 $formatParam = $request->getParam('format');
144 $format = is_string($formatParam) ? strtolower($formatParam) : StatisticsExporter::FORMAT_CSV;
145 if ($format !== StatisticsExporter::FORMAT_CSV && $format !== StatisticsExporter::FORMAT_XLSX) {
146 throw new ApiException(
147 __('Unsupported export format. Use csv or xlsx.', 'mailpoet'),
148 400,
149 'mailpoet_newsletters_export_invalid_format'
150 );
151 }
152
153 $task = new ScheduledTaskEntity();
154 $task->setType(StatisticsExport::TASK_TYPE);
155 $task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
156 $task->setScheduledAt(Carbon::now()->millisecond(0));
157 $task->setPriority(ScheduledTaskEntity::PRIORITY_HIGH);
158 $task->setMeta([
159 'job_type' => StatisticsExport::JOB_TYPE_BULK,
160 'newsletter_ids' => $ids,
161 'format' => $format,
162 'requested_by' => (int)$this->wp->getCurrentUserId(),
163 ]);
164 $this->scheduledTasksRepository->persist($task);
165 $this->scheduledTasksRepository->flush();
166
167 return new Response([
168 'action' => self::ACTION_EXPORT_STATS,
169 'task_id' => (int)$task->getId(),
170 'count' => count($ids),
171 ]);
172 }
173
174 /**
175 * @return int[]
176 */
177 private function getSelection(Request $request): array {
178 $selection = $request->getParam('selection');
179 return is_array($selection) ? $this->toIntList($selection) : [];
180 }
181
182 /**
183 * @param int[] $selection
184 */
185 private function buildDefinition(Request $request, array $selection): ListingDefinition {
186 $filter = $request->getParam('filter');
187 $searchParam = $request->getParam('search');
188 $groupParam = $request->getParam('group');
189 $typeParam = $request->getParam('type');
190 $parentIdParam = $request->getParam('parent_id');
191
192 $params = [];
193 if (is_string($typeParam) && in_array($typeParam, NewslettersListingEndpoint::SUPPORTED_TYPES, true)) {
194 $params['type'] = $typeParam;
195 }
196 if (is_numeric($parentIdParam)) {
197 $params['parentId'] = (int)$parentIdParam;
198 }
199
200 return $this->listingHandler->getListingDefinition([
201 'offset' => 0,
202 'limit' => 0,
203 'sort_by' => 'id',
204 'sort_order' => 'desc',
205 'search' => is_string($searchParam) ? $searchParam : null,
206 'group' => is_string($groupParam) ? $groupParam : null,
207 'filter' => is_array($filter) ? $filter : [],
208 'selection' => $selection,
209 'params' => $params,
210 ]);
211 }
212
213 /**
214 * @param array<mixed> $values
215 * @return int[]
216 */
217 private function toIntList(array $values): array {
218 $ints = [];
219 foreach ($values as $value) {
220 if (is_scalar($value) && (int)$value > 0) {
221 $ints[] = (int)$value;
222 }
223 }
224 return array_values(array_unique($ints));
225 }
226 }
227