PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
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 / StatisticsExport.php
mailpoet / lib / API / JSON / v1 Last commit date
Analytics.php 3 years ago AutomatedLatestContent.php 2 months ago AutomaticEmails.php 3 years ago Captcha.php 1 year ago Coupons.php 2 years ago CustomFields.php 2 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 2 months ago Mailer.php 1 year ago NewsletterLinks.php 2 months ago NewsletterTemplates.php 2 months ago Newsletters.php 2 months ago Premium.php 10 months ago RedirectResponse.php 1 year ago Segments.php 2 months ago SendingQueue.php 2 months ago Services.php 6 months ago Settings.php 2 months ago Setup.php 1 year ago StatisticsExport.php 3 months ago SubscriberStats.php 1 month 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
StatisticsExport.php
239 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\API\JSON\v1;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\API\JSON\Endpoint as APIEndpoint;
9 use MailPoet\API\JSON\Error as APIError;
10 use MailPoet\API\JSON\Response;
11 use MailPoet\Config\AccessControl;
12 use MailPoet\Cron\Workers\StatisticsExport as StatisticsExportWorker;
13 use MailPoet\Entities\ScheduledTaskEntity;
14 use MailPoet\Logging\LoggerFactory;
15 use MailPoet\Newsletter\NewslettersRepository;
16 use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
17 use MailPoet\Newsletter\Statistics\Export\StatisticsExporter;
18 use MailPoet\Util\License\Features\CapabilitiesManager;
19 use MailPoet\WP\Functions as WPFunctions;
20 use MailPoetVendor\Carbon\Carbon;
21
22 class StatisticsExport extends APIEndpoint {
23 public $permissions = [
24 'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
25 ];
26
27 /** @var NewslettersRepository */
28 private $newslettersRepository;
29
30 /** @var StatisticsExporter */
31 private $exporter;
32
33 /** @var LoggerFactory */
34 private $loggerFactory;
35
36 /** @var ScheduledTasksRepository */
37 private $scheduledTasksRepository;
38
39 /** @var CapabilitiesManager */
40 private $capabilitiesManager;
41
42 /** @var WPFunctions */
43 private $wp;
44
45 public function __construct(
46 NewslettersRepository $newslettersRepository,
47 StatisticsExporter $exporter,
48 LoggerFactory $loggerFactory,
49 ScheduledTasksRepository $scheduledTasksRepository,
50 CapabilitiesManager $capabilitiesManager,
51 WPFunctions $wp
52 ) {
53 $this->newslettersRepository = $newslettersRepository;
54 $this->exporter = $exporter;
55 $this->loggerFactory = $loggerFactory;
56 $this->scheduledTasksRepository = $scheduledTasksRepository;
57 $this->capabilitiesManager = $capabilitiesManager;
58 $this->wp = $wp;
59 }
60
61 public function exportCampaign($data = []) {
62 $newsletterId = isset($data['id']) ? (int)$data['id'] : 0;
63 if ($newsletterId <= 0) {
64 return $this->badRequest([
65 APIError::BAD_REQUEST => __('Missing newsletter id.', 'mailpoet'),
66 ]);
67 }
68
69 $format = $this->resolveFormat($data);
70 if ($format === null) {
71 return $this->unsupportedFormatResponse();
72 }
73
74 $newsletter = $this->newslettersRepository->findOneById($newsletterId);
75 if (!$newsletter) {
76 return $this->newsletterNotFoundResponse();
77 }
78
79 try {
80 $result = $this->exporter->exportSingleAggregate($newsletter, $format);
81 } catch (\Throwable $e) {
82 if (function_exists('error_log')) {
83 // phpcs:disable QITStandard.PHP.DebugCode.DebugFunctionFound
84 error_log((string)$e); // phpcs:ignore Squiz.PHP.DiscouragedFunctions
85 // phpcs:enable QITStandard.PHP.DebugCode.DebugFunctionFound
86 }
87 $this->loggerFactory->getLogger(LoggerFactory::TOPIC_API)->warning('Campaign statistics export failed.', [
88 'exceptionMessage' => $e->getMessage(),
89 'exceptionTrace' => $e->getTraceAsString(),
90 ]);
91 return $this->errorResponse([
92 APIError::UNKNOWN => __('Could not generate the export. Please try again.', 'mailpoet'),
93 ], [], Response::STATUS_BAD_REQUEST);
94 }
95
96 return $this->successResponse($result);
97 }
98
99 public function exportRecipients($data = []) {
100 $newsletterId = isset($data['id']) ? (int)$data['id'] : 0;
101 if ($newsletterId <= 0) {
102 return $this->badRequest([
103 APIError::BAD_REQUEST => __('Missing newsletter id.', 'mailpoet'),
104 ]);
105 }
106
107 $format = $this->resolveFormat($data);
108 if ($format === null) {
109 return $this->unsupportedFormatResponse();
110 }
111
112 if ($this->isDetailedAnalyticsRestricted()) {
113 return $this->errorResponse([
114 APIError::FORBIDDEN => __('Per-recipient export requires a MailPoet plan with detailed analytics.', 'mailpoet'),
115 ], [], Response::STATUS_FORBIDDEN);
116 }
117
118 $newsletter = $this->newslettersRepository->findOneById($newsletterId);
119 if (!$newsletter) {
120 return $this->newsletterNotFoundResponse();
121 }
122
123 $task = new ScheduledTaskEntity();
124 $task->setType(StatisticsExportWorker::TASK_TYPE);
125 $task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
126 $task->setScheduledAt(Carbon::now()->millisecond(0));
127 $task->setPriority(ScheduledTaskEntity::PRIORITY_HIGH);
128 $task->setMeta([
129 'job_type' => StatisticsExportWorker::JOB_TYPE_RECIPIENTS,
130 'newsletter_id' => $newsletter->getId(),
131 'format' => $format,
132 'requested_by' => (int)$this->wp->getCurrentUserId(),
133 ]);
134 $this->scheduledTasksRepository->persist($task);
135 $this->scheduledTasksRepository->flush();
136
137 return $this->successResponse($this->buildStatusPayload($task));
138 }
139
140 public function getStatus($data = []) {
141 $taskId = isset($data['task_id']) ? (int)$data['task_id'] : 0;
142 if ($taskId <= 0) {
143 return $this->badRequest([
144 APIError::BAD_REQUEST => __('Missing task id.', 'mailpoet'),
145 ]);
146 }
147
148 if ($this->isDetailedAnalyticsRestricted()) {
149 return $this->errorResponse([
150 APIError::FORBIDDEN => __('Per-recipient export requires a MailPoet plan with detailed analytics.', 'mailpoet'),
151 ], [], Response::STATUS_FORBIDDEN);
152 }
153
154 $task = $this->scheduledTasksRepository->findOneById($taskId);
155 if (!$task instanceof ScheduledTaskEntity || $task->getType() !== StatisticsExportWorker::TASK_TYPE) {
156 return $this->errorResponse([
157 APIError::NOT_FOUND => __('Export task not found.', 'mailpoet'),
158 ], [], Response::STATUS_NOT_FOUND);
159 }
160
161 $meta = $task->getMeta() ?? [];
162 $jobType = isset($meta['job_type']) ? (string)$meta['job_type'] : '';
163 $allowedJobTypes = [
164 StatisticsExportWorker::JOB_TYPE_RECIPIENTS,
165 StatisticsExportWorker::JOB_TYPE_BULK,
166 ];
167 if (!in_array($jobType, $allowedJobTypes, true)) {
168 return $this->errorResponse([
169 APIError::NOT_FOUND => __('Export task not found.', 'mailpoet'),
170 ], [], Response::STATUS_NOT_FOUND);
171 }
172
173 $requestedBy = isset($meta['requested_by']) ? (int)$meta['requested_by'] : 0;
174 if ($requestedBy !== (int)$this->wp->getCurrentUserId()) {
175 return $this->errorResponse([
176 APIError::NOT_FOUND => __('Export task not found.', 'mailpoet'),
177 ], [], Response::STATUS_NOT_FOUND);
178 }
179
180 return $this->successResponse($this->buildStatusPayload($task));
181 }
182
183 private function isDetailedAnalyticsRestricted(): bool {
184 $capability = $this->capabilitiesManager->getCapability('detailedAnalytics');
185 return $capability === null || $capability->isRestricted;
186 }
187
188 private function resolveFormat(array $data): ?string {
189 $format = isset($data['format']) && is_string($data['format'])
190 ? strtolower($data['format'])
191 : StatisticsExporter::FORMAT_CSV;
192 if ($format !== StatisticsExporter::FORMAT_CSV && $format !== StatisticsExporter::FORMAT_XLSX) {
193 return null;
194 }
195 return $format;
196 }
197
198 private function unsupportedFormatResponse() {
199 return $this->badRequest([
200 APIError::BAD_REQUEST => __('Unsupported export format. Use csv or xlsx.', 'mailpoet'),
201 ]);
202 }
203
204 private function newsletterNotFoundResponse() {
205 return $this->errorResponse([
206 APIError::NOT_FOUND => __('This email does not exist.', 'mailpoet'),
207 ], [], Response::STATUS_NOT_FOUND);
208 }
209
210 /**
211 * @return array{
212 * taskId: int,
213 * status: string,
214 * exportFileURL?: string,
215 * totalExported?: int,
216 * error?: string,
217 * }
218 */
219 private function buildStatusPayload(ScheduledTaskEntity $task): array {
220 $meta = $task->getMeta() ?? [];
221 $status = $task->getStatus() ?? ScheduledTaskEntity::VIRTUAL_STATUS_RUNNING;
222
223 $payload = [
224 'taskId' => (int)$task->getId(),
225 'status' => $status,
226 ];
227 if (isset($meta['export_file_url'])) {
228 $payload['exportFileURL'] = (string)$meta['export_file_url'];
229 }
230 if (isset($meta['total_exported'])) {
231 $payload['totalExported'] = (int)$meta['total_exported'];
232 }
233 if (isset($meta['error'])) {
234 $payload['error'] = (string)$meta['error'];
235 }
236 return $payload;
237 }
238 }
239