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 / Cron / CliCommands / CronCommand.php
mailpoet / lib / Cron / CliCommands Last commit date
ClaimedTaskRunner.php 1 month ago Cli.php 1 month ago CronCommand.php 1 month ago DaemonRunner.php 1 month ago ExecutionLimitOverride.php 1 month ago ScheduledTaskResolver.php 1 month ago ScheduledTasksLister.php 1 month ago TaskAdder.php 1 month ago TaskCanceller.php 1 month ago TaskRunner.php 1 month ago TaskTrigger.php 1 month ago WorkerTypesCatalog.php 1 month ago index.php 1 month ago
CronCommand.php
429 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Cron\CliCommands;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use Throwable;
9 use WP_CLI;
10 use WP_CLI\Formatter;
11
12 class CronCommand {
13 private ScheduledTasksLister $scheduledTasksLister;
14
15 private WorkerTypesCatalog $workerTypesCatalog;
16
17 private TaskTrigger $taskTrigger;
18
19 private TaskRunner $taskRunner;
20
21 private TaskCanceller $taskCanceller;
22
23 private DaemonRunner $daemonRunner;
24
25 private TaskAdder $taskAdder;
26
27 public function __construct(
28 ScheduledTasksLister $scheduledTasksLister,
29 WorkerTypesCatalog $workerTypesCatalog,
30 TaskTrigger $taskTrigger,
31 TaskRunner $taskRunner,
32 TaskCanceller $taskCanceller,
33 DaemonRunner $daemonRunner,
34 TaskAdder $taskAdder
35 ) {
36 $this->scheduledTasksLister = $scheduledTasksLister;
37 $this->workerTypesCatalog = $workerTypesCatalog;
38 $this->taskTrigger = $taskTrigger;
39 $this->taskRunner = $taskRunner;
40 $this->taskCanceller = $taskCanceller;
41 $this->daemonRunner = $daemonRunner;
42 $this->taskAdder = $taskAdder;
43 }
44
45 /**
46 * Lists MailPoet scheduled tasks.
47 *
48 * ## OPTIONS
49 *
50 * [--status=<status>]
51 * : Filter tasks by status. Defaults to actionable tasks (scheduled, running, and cli).
52 * ---
53 * options:
54 * - scheduled
55 * - running
56 * - cli
57 * - completed
58 * - cancelled
59 * - paused
60 * - invalid
61 * - all
62 * ---
63 *
64 * [--type=<type>]
65 * : Filter tasks by type.
66 *
67 * [--limit=<n>]
68 * : Maximum number of tasks to return. Default 50.
69 *
70 * [--field=<field>]
71 * : Print the value of a single field for each task.
72 *
73 * [--fields=<fields>]
74 * : Limit the output to specific fields. Comma-separated.
75 *
76 * [--format=<format>]
77 * : Render output in a particular format.
78 * ---
79 * default: table
80 * options:
81 * - table
82 * - csv
83 * - json
84 * - ids
85 * - count
86 * ---
87 *
88 * ## EXAMPLES
89 *
90 * wp mailpoet cron list
91 * wp mailpoet cron list --status=running
92 * wp mailpoet cron list --status=all --format=json
93 * wp mailpoet cron list --type=sending --limit=10
94 *
95 * @subcommand list
96 *
97 * @param array $args
98 * @param array $assocArgs
99 */
100 public function list(array $args, array $assocArgs): void {
101 $status = isset($assocArgs['status']) ? (string)$assocArgs['status'] : null;
102 $type = isset($assocArgs['type']) ? (string)$assocArgs['type'] : null;
103 $limit = array_key_exists('limit', $assocArgs) ? (int)$assocArgs['limit'] : ScheduledTasksLister::DEFAULT_LIMIT;
104 $format = $assocArgs['format'] ?? 'table';
105
106 try {
107 $rows = $this->scheduledTasksLister->getRows($status, $type, $limit);
108 } catch (Throwable $e) {
109 WP_CLI::error($e->getMessage());
110 return;
111 }
112
113 // The Formatter constructor consumes the format/fields keys from $assocArgs (by reference),
114 // so read the format before constructing it.
115 $formatter = new Formatter($assocArgs, ScheduledTasksLister::FIELDS);
116 if ($format === 'ids') {
117 $formatter->display_items(array_column($rows, 'id'));
118 return;
119 }
120 $formatter->display_items($rows);
121 }
122
123 /**
124 * Lists all known MailPoet cron worker task types and their attributes.
125 *
126 * ## OPTIONS
127 *
128 * [--fields=<fields>]
129 * : Limit the output to specific fields. Comma-separated.
130 *
131 * [--field=<field>]
132 * : Print the value of a single field for each type.
133 *
134 * [--format=<format>]
135 * : Render output in a particular format.
136 * ---
137 * default: table
138 * options:
139 * - table
140 * - csv
141 * - json
142 * - yaml
143 * - count
144 * ---
145 *
146 * ## EXAMPLES
147 *
148 * wp mailpoet cron types
149 * wp mailpoet cron types --format=json
150 * wp mailpoet cron types --fields=type,addable
151 *
152 * @subcommand types
153 *
154 * @param array $args
155 * @param array $assocArgs
156 */
157 public function types(array $args, array $assocArgs): void {
158 $rows = $this->workerTypesCatalog->getRows();
159
160 $formatter = new Formatter($assocArgs, WorkerTypesCatalog::FIELDS);
161 $formatter->display_items($rows);
162 }
163
164 /**
165 * Marks a MailPoet cron task as due now so the site's own cron processor picks it up.
166 *
167 * This does not kick the cron pipeline; the MailPoet cron runner runs the task on its next tick.
168 * By type it targets the next scheduled task of that type; with --task-id it targets an exact row
169 * and also re-schedules a paused one.
170 *
171 * ## OPTIONS
172 *
173 * <type>
174 * : The task type to trigger. See `wp mailpoet cron types` for valid values.
175 *
176 * [--task-id=<id>]
177 * : Trigger an exact task by ID instead of the next scheduled task of the type.
178 *
179 * ## EXAMPLES
180 *
181 * wp mailpoet cron trigger sending
182 * wp mailpoet cron trigger bounce --task-id=42
183 *
184 * @subcommand trigger
185 *
186 * @param array $args
187 * @param array $assocArgs
188 */
189 public function trigger(array $args, array $assocArgs): void {
190 $type = (string)$args[0];
191 $taskId = array_key_exists('task-id', $assocArgs) ? (int)$assocArgs['task-id'] : null;
192
193 try {
194 $triggered = $this->taskTrigger->trigger($type, $taskId);
195 } catch (Throwable $e) {
196 WP_CLI::error($e->getMessage());
197 return;
198 }
199
200 WP_CLI::success(sprintf(
201 "Task %d (%s) is now due. The MailPoet cron runner will pick it up on its next tick.",
202 $triggered['id'],
203 $triggered['type']
204 ));
205 }
206
207 /**
208 * Runs a MailPoet cron worker inside this WP-CLI process.
209 *
210 * Without --task-id, it snapshots the currently-due tasks of the given type and runs each once,
211 * claiming it as 'cli' (invisible to the site daemon) so the daemon never double-processes it.
212 * Self-rescheduling batched workers (e.g. subscribers_engagement_score) process one batch per due
213 * task and leave a continuation for the site cron, so run again (or `trigger`) to process the rest.
214 * The 20-second execution limit is lifted by default; pass --timeout to cap it.
215 *
216 * ## OPTIONS
217 *
218 * <type>
219 * : The task type to run. See `wp mailpoet cron types` for valid values.
220 *
221 * [--task-id=<id>]
222 * : Run this exact task in-process: it is claimed (status 'cli', hidden from the web daemon) and run
223 * here, so the daemon never double-processes it. Only scheduled or paused tasks can be run by ID.
224 *
225 * [--timeout=<seconds>]
226 * : Cap the run at this many seconds (restores an execution limit). Omit to run to completion.
227 *
228 * ## EXAMPLES
229 *
230 * wp mailpoet cron run log_cleanup
231 * wp mailpoet cron run sending
232 * wp mailpoet cron run bounce --task-id=42
233 * wp mailpoet cron run sending --timeout=30
234 *
235 * @subcommand run
236 *
237 * @param array $args
238 * @param array $assocArgs
239 */
240 public function run(array $args, array $assocArgs): void {
241 $type = (string)$args[0];
242 $taskId = array_key_exists('task-id', $assocArgs) ? (int)$assocArgs['task-id'] : null;
243 $timeout = array_key_exists('timeout', $assocArgs) ? (int)$assocArgs['timeout'] : null;
244
245 try {
246 $result = $this->taskRunner->run($type, $taskId, $timeout);
247 } catch (Throwable $e) {
248 WP_CLI::error($e->getMessage());
249 return;
250 }
251
252 // limit_reached and an un-drained backlog are both non-fatal partial outcomes (still exit 0):
253 // warn so the operator sees that not everything ran. A fully drained backlog is a success.
254 if ($result['limit_reached'] || !$result['backlog_drained']) {
255 WP_CLI::warning($result['message']);
256 return;
257 }
258
259 WP_CLI::success($result['message']);
260 }
261
262 /**
263 * Runs one full MailPoet daemon pass over all cron workers inside this WP-CLI process.
264 *
265 * Each worker runs once, processing the tasks that are due. This is a single daemon pass, not a
266 * backlog drain: a worker with many due tasks may need several passes, or use
267 * `wp mailpoet cron run <type>` to drain one type. The 20-second execution limit is lifted by
268 * default; pass --timeout to cap it. Per-worker errors collected during the pass are printed and
269 * make the command exit non-zero.
270 *
271 * ## OPTIONS
272 *
273 * [--timeout=<seconds>]
274 * : Cap the pass at this many seconds (restores an execution limit). Omit to run to completion.
275 * Unlike `run`, hitting --timeout mid-pass surfaces as a worker error and exits non-zero.
276 *
277 * ## EXAMPLES
278 *
279 * wp mailpoet cron run-daemon
280 * wp mailpoet cron run-daemon --timeout=30
281 *
282 * @subcommand run-daemon
283 *
284 * @param array $args
285 * @param array $assocArgs
286 */
287 // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps -- WP-CLI maps the run_daemon method to the run-daemon subcommand; the underscore is required.
288 public function run_daemon(array $args, array $assocArgs): void {
289 $timeout = array_key_exists('timeout', $assocArgs) ? (int)$assocArgs['timeout'] : null;
290
291 try {
292 $result = $this->daemonRunner->run($timeout);
293 } catch (Throwable $e) {
294 WP_CLI::error($e->getMessage());
295 return;
296 }
297
298 $errors = $result['errors'];
299 if (empty($errors)) {
300 WP_CLI::success('Daemon pass finished. Note: large backlogs may need multiple passes or `wp mailpoet cron run <type>`.');
301 return;
302 }
303
304 foreach ($errors as $error) {
305 WP_CLI::warning(sprintf('%s: %s', $error['worker'], $error['message']));
306 }
307 WP_CLI::error(sprintf('Daemon pass finished with %d worker error(s).', count($errors)));
308 }
309
310 /**
311 * Adds a new MailPoet cron task to the schedule, optionally running it immediately in this process.
312 *
313 * Only standard worker types are addable; see `wp mailpoet cron types` (the mailing 'sending' and
314 * 'stats_notification' rows are created by app flows and are rejected). By default the task is due
315 * now at low priority. If a task of the type is already scheduled the command reports it and does
316 * nothing unless --force is given. With --run the task is created already claimed (status 'cli',
317 * hidden from the web daemon) and processed in-CLI, bypassing the web daemon and the duplicate check.
318 *
319 * ## OPTIONS
320 *
321 * <type>
322 * : The task type to add. See `wp mailpoet cron types` for addable values.
323 *
324 * [--at=<datetime>]
325 * : Schedule for this date/time (e.g. '2026-01-01 09:00', 'tomorrow 8am'). Defaults to now.
326 *
327 * [--in=<seconds>]
328 * : Schedule this many seconds from now. Cannot be combined with --at.
329 *
330 * [--priority=<priority>]
331 * : Task priority. Lower runs sooner.
332 * ---
333 * default: low
334 * options:
335 * - high
336 * - medium
337 * - low
338 * ---
339 *
340 * [--force]
341 * : Add the task even if one of the type is already scheduled.
342 *
343 * [--run]
344 * : Claim the task (status 'cli', hidden from the web daemon) and run it in this process now. Cannot
345 * be combined with --at/--in.
346 *
347 * ## EXAMPLES
348 *
349 * wp mailpoet cron add log_cleanup
350 * wp mailpoet cron add bounce --in=3600 --priority=high
351 * wp mailpoet cron add bounce --at='tomorrow 8am'
352 * wp mailpoet cron add log_cleanup --force
353 * wp mailpoet cron add subscribers_count_cache_recalculation --run
354 *
355 * @subcommand add
356 *
357 * @param array $args
358 * @param array $assocArgs
359 */
360 public function add(array $args, array $assocArgs): void {
361 $type = (string)$args[0];
362 $at = array_key_exists('at', $assocArgs) ? (string)$assocArgs['at'] : null;
363 $in = array_key_exists('in', $assocArgs) ? (int)$assocArgs['in'] : null;
364 $priority = array_key_exists('priority', $assocArgs) ? (string)$assocArgs['priority'] : 'low';
365 $force = (bool)($assocArgs['force'] ?? false);
366 $run = (bool)($assocArgs['run'] ?? false);
367
368 try {
369 $result = $this->taskAdder->add($type, $at, $in, $priority, $force, $run);
370 } catch (Throwable $e) {
371 WP_CLI::error($e->getMessage());
372 return;
373 }
374
375 if ($result['action'] === 'duplicate') {
376 WP_CLI::warning($result['message']);
377 return;
378 }
379
380 WP_CLI::success($result['message']);
381
382 if ($result['run'] !== null) {
383 if ($result['run']['completed']) {
384 WP_CLI::success($result['run']['message']);
385 } else {
386 WP_CLI::warning($result['run']['message']);
387 }
388 }
389 }
390
391 /**
392 * Cancels a MailPoet cron task by setting its status to cancelled.
393 *
394 * Scheduled, paused, and cli tasks can be cancelled; cancelling a cli task recovers a stuck CLI
395 * claim. Running tasks are owned by their executor and completed ones are history, so both are
396 * rejected.
397 *
398 * ## OPTIONS
399 *
400 * <task-id>
401 * : The ID of the task to cancel. See `wp mailpoet cron list` for task IDs.
402 *
403 * ## EXAMPLES
404 *
405 * wp mailpoet cron cancel 42
406 *
407 * @subcommand cancel
408 *
409 * @param array $args
410 * @param array $assocArgs
411 */
412 public function cancel(array $args, array $assocArgs): void {
413 $taskId = (int)$args[0];
414
415 try {
416 $cancelled = $this->taskCanceller->cancel($taskId);
417 } catch (Throwable $e) {
418 WP_CLI::error($e->getMessage());
419 return;
420 }
421
422 WP_CLI::success(sprintf(
423 "Task %d (%s) cancelled.",
424 $cancelled['id'],
425 $cancelled['type']
426 ));
427 }
428 }
429