PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / trunk
MailPoet – Newsletters, Email Marketing, and Automation vtrunk
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 / Cron / CliCommands / TaskAdder.php
mailpoet / lib / Cron / CliCommands Last commit date
ClaimedTaskRunner.php 2 months ago Cli.php 2 months ago CronCommand.php 2 months ago DaemonRunner.php 2 months ago ExecutionLimitOverride.php 2 months ago ScheduledTaskResolver.php 2 months ago ScheduledTasksLister.php 2 months ago TaskAdder.php 2 months ago TaskCanceller.php 2 months ago TaskRunner.php 2 months ago TaskTrigger.php 2 months ago WorkerTypesCatalog.php 2 months ago index.php 2 months ago
TaskAdder.php
155 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Cron\CliCommands;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use Exception;
9 use InvalidArgumentException;
10 use MailPoet\Entities\ScheduledTaskEntity;
11 use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
12 use MailPoetVendor\Carbon\Carbon;
13
14 /**
15 * Adds a new scheduled task for a standard cron worker, optionally claiming and running it in-process.
16 *
17 * Only standard CronWorkerInterface workers are addable (see WorkerTypesCatalog::getAddableTypes());
18 * mailing 'sending'/'stats_notification' rows are created by app flows and are rejected. --run drives a
19 * freshly claimed row through ClaimedTaskRunner, shared with `cron run --task-id`.
20 *
21 * See doc/wp-cli-cron-commands.md for command behaviour.
22 */
23 class TaskAdder {
24 const PRIORITY_MAP = [
25 'high' => ScheduledTaskEntity::PRIORITY_HIGH,
26 'medium' => ScheduledTaskEntity::PRIORITY_MEDIUM,
27 'low' => ScheduledTaskEntity::PRIORITY_LOW,
28 ];
29
30 private WorkerTypesCatalog $workerTypesCatalog;
31
32 private ScheduledTasksRepository $scheduledTasksRepository;
33
34 private ClaimedTaskRunner $claimedTaskRunner;
35
36 public function __construct(
37 WorkerTypesCatalog $workerTypesCatalog,
38 ScheduledTasksRepository $scheduledTasksRepository,
39 ClaimedTaskRunner $claimedTaskRunner
40 ) {
41 $this->workerTypesCatalog = $workerTypesCatalog;
42 $this->scheduledTasksRepository = $scheduledTasksRepository;
43 $this->claimedTaskRunner = $claimedTaskRunner;
44 }
45
46 /**
47 * @return array{id: int, type: string, action: string, message: string, run: array{completed: bool, limit_reached: bool, message: string}|null}
48 */
49 public function add(string $type, ?string $at, ?int $in, string $priority, bool $force, bool $run): array {
50 $this->workerTypesCatalog->assertAddableType($type);
51 $priorityValue = $this->resolvePriority($priority);
52
53 if ($run && ($at !== null || $in !== null)) {
54 throw new InvalidArgumentException('--run cannot be combined with --at or --in. A claimed task runs immediately.');
55 }
56
57 if ($run) {
58 return $this->claimAndRun($type, $priorityValue);
59 }
60
61 $scheduledAt = $this->resolveScheduledAt($at, $in);
62
63 if (!$force) {
64 $existing = $this->scheduledTasksRepository->findScheduledTask($type);
65 if ($existing instanceof ScheduledTaskEntity) {
66 $existingId = (int)$existing->getId();
67 return [
68 'id' => $existingId,
69 'type' => $type,
70 'action' => 'duplicate',
71 'message' => sprintf("A task of type '%s' is already scheduled as task %d. Use --force to add another.", $type, $existingId),
72 'run' => null,
73 ];
74 }
75 }
76
77 $task = $this->createTask($type, $priorityValue, $scheduledAt);
78
79 return [
80 'id' => (int)$task->getId(),
81 'type' => $type,
82 'action' => 'created',
83 'message' => sprintf("Added task %d (%s), scheduled for %s, priority %s.", $task->getId(), $type, $scheduledAt->format('Y-m-d H:i:s'), $priority),
84 'run' => null,
85 ];
86 }
87
88 private function createTask(string $type, int $priority, Carbon $scheduledAt): ScheduledTaskEntity {
89 $task = new ScheduledTaskEntity();
90 $task->setType($type);
91 $task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
92 $task->setPriority($priority);
93 $task->setScheduledAt($scheduledAt);
94 $this->scheduledTasksRepository->persist($task);
95 $this->scheduledTasksRepository->flush();
96 return $task;
97 }
98
99 /**
100 * Claims a fresh row (status STATUS_CLI) and processes exactly that one task. The duplicate check is
101 * intentionally skipped: the claim is a fresh, independently-owned row, never a "scheduled
102 * duplicate", so --run ignores --force entirely.
103 *
104 * @return array{id: int, type: string, action: string, message: string, run: array{completed: bool, limit_reached: bool, message: string}}
105 */
106 private function claimAndRun(string $type, int $priority): array {
107 $worker = $this->workerTypesCatalog->getWorkerByType($type);
108 if ($worker === null) {
109 // Unreachable: assertAddableType already restricts to types with a standard worker.
110 throw new InvalidArgumentException("Task type '{$type}' has no runnable worker.");
111 }
112
113 $task = $this->claimedTaskRunner->claimNew($type, $priority);
114 $taskId = (int)$task->getId();
115
116 $runResult = $this->claimedTaskRunner->run($worker, $task);
117
118 return [
119 'id' => $taskId,
120 'type' => $type,
121 'action' => 'claimed',
122 'message' => sprintf("Claimed task %d (%s) and ran it in this process.", $taskId, $type),
123 'run' => $runResult,
124 ];
125 }
126
127 private function resolveScheduledAt(?string $at, ?int $in): Carbon {
128 if ($at !== null && $in !== null) {
129 throw new InvalidArgumentException('--at and --in cannot be used together. Pick one.');
130 }
131
132 if ($at !== null) {
133 try {
134 return Carbon::parse($at)->millisecond(0);
135 } catch (Exception $e) {
136 throw new InvalidArgumentException("Could not parse --at value '{$at}'. Use a date/time like '2026-01-01 09:00' or 'tomorrow 8am'.");
137 }
138 }
139
140 if ($in !== null) {
141 return Carbon::now()->millisecond(0)->addSeconds($in);
142 }
143
144 return Carbon::now()->millisecond(0);
145 }
146
147 private function resolvePriority(string $priority): int {
148 if (!isset(self::PRIORITY_MAP[$priority])) {
149 $valid = implode(', ', array_keys(self::PRIORITY_MAP));
150 throw new InvalidArgumentException("Invalid priority '{$priority}'. Valid values: {$valid}.");
151 }
152 return self::PRIORITY_MAP[$priority];
153 }
154 }
155