PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.7.9
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.7.9
8.7.9 8.7.8 8.7.7 8.7.6 8.7.5 8.7.4 8.7.3 8.7.2 8.7.1 8.7.0 8.6.9 8.6.8 8.6.7 8.6.6 8.6.5 8.6.4 8.6.2 8.6.1 8.6.0 8.5.9 8.5.8 8.5.7 8.5.6 8.5.5 8.5.4 All 535 releases
chatbot / addons / automator / includes / actions / delay-actions.php

delay-actions.php in WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services 8.7.9, at addons/automator/includes/actions/delay-actions.php

241 lines 8.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Delay / Wait Action
4 *
5 * Pauses workflow execution for a configurable duration using WordPress Cron
6 * (wp_schedule_single_event). The remaining nodes and trigger data are
7 * persisted to the wpbot_automator_scheduled_tasks DB table so they can be
8 * resumed accurately after the delay fires.
9 *
10 * @package WPbot_Automator
11 */
12
13 namespace WPbot_Automator\Actions;
14
15 use WPbot_Automator\Core\Database;
16 use WPbot_Automator\Engine\Workflow_Runner;
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 /**
23 * Class Delay_Actions
24 */
25 class Delay_Actions extends Action {
26
27 /**
28 * WP Cron hook name used to resume deferred workflows.
29 */
30 const CRON_HOOK = 'wpbot_automator_resume_delayed_workflow';
31
32 /**
33 * Constructor
34 */
35 public function __construct() {
36 $this->id = 'delay';
37 $this->group = __( 'Flow Control', 'wpbot-automator' );
38
39 // Register the cron callback once per request.
40 add_action( self::CRON_HOOK, array( $this, 'resume_workflow' ), 10, 1 );
41 }
42
43 /**
44 * Execute the action.
45 *
46 * When the workflow runner hits this node it will call execute() with
47 * extra context that the runner passes via $action_data['__context'].
48 *
49 * Because the runner doesn't natively support "pausing", we intercept
50 * execution here by:
51 * 1. Resolving the delay in seconds.
52 * 2. Collecting the remaining nodes (everything downstream of this node).
53 * 3. Persisting the checkpoint to the DB.
54 * 4. Scheduling a single WP Cron event.
55 * 5. Returning a special '__halt' flag so the runner stops this branch.
56 *
57 * @param array $action_data Configuration for this action from the workflow.
58 * @param array $trigger_data Data passed from the trigger.
59 * @return array
60 */
61 public function execute( $action_data, $trigger_data ) {
62 $action_id = isset( $action_data['actionId'] ) ? $action_data['actionId'] : '';
63
64 if ( 'delay_execution' !== $action_id ) {
65 return array( 'success' => false, 'message' => 'Unknown delay action.' );
66 }
67
68 $cfg = isset( $action_data['config'] ) ? $action_data['config'] : array();
69 $delay_amount = isset( $cfg['delay_amount'] ) ? absint( $cfg['delay_amount'] ) : 0;
70 $delay_unit = isset( $cfg['delay_unit'] ) ? sanitize_key( $cfg['delay_unit'] ) : 'minutes';
71
72 // Convert to seconds.
73 $multiplier = array(
74 'minutes' => MINUTE_IN_SECONDS,
75 'hours' => HOUR_IN_SECONDS,
76 'days' => DAY_IN_SECONDS,
77 );
78 $delay_seconds = $delay_amount * ( isset( $multiplier[ $delay_unit ] ) ? $multiplier[ $delay_unit ] : MINUTE_IN_SECONDS );
79
80 if ( $delay_seconds <= 0 ) {
81 return array( 'success' => false, 'message' => 'Delay: invalid delay amount or unit.' );
82 }
83
84 // Pull the workflow context the runner embeds when it calls us.
85 // This is set via $action_data['__workflow_context'] by the modified runner.
86 $context = isset( $action_data['__workflow_context'] ) ? $action_data['__workflow_context'] : array();
87
88 $workflow_id = isset( $context['workflow_id'] ) ? absint( $context['workflow_id'] ) : 0;
89 $trigger_id = isset( $context['trigger_id'] ) ? $context['trigger_id'] : '';
90 $remaining_nodes = isset( $context['remaining_nodes'] ) ? $context['remaining_nodes'] : array();
91 $connections = isset( $context['connections'] ) ? $context['connections'] : array();
92
93 if ( empty( $remaining_nodes ) || ! $workflow_id ) {
94 // Nothing to schedule — just return success (no downstream nodes).
95 return array(
96 'success' => true,
97 'message' => sprintf(
98 'Delay: %d %s queued but no downstream nodes found; nothing to schedule.',
99 $delay_amount,
100 $delay_unit
101 ),
102 );
103 }
104
105 // Persist the checkpoint.
106 $scheduled_at = gmdate( 'Y-m-d H:i:s', time() + $delay_seconds );
107 $task_id = Database::add_scheduled_task(
108 array(
109 'workflow_id' => $workflow_id,
110 'trigger_id' => (string) $trigger_id,
111 'trigger_data' => $trigger_data,
112 'remaining_nodes' => $remaining_nodes,
113 'connections' => $connections,
114 'scheduled_at' => $scheduled_at,
115 )
116 );
117
118 if ( ! $task_id ) {
119 return array( 'success' => false, 'message' => 'Delay: failed to persist scheduled task to DB.' );
120 }
121
122 // Schedule the WP Cron event.
123 $scheduled = wp_schedule_single_event(
124 time() + $delay_seconds,
125 self::CRON_HOOK,
126 array( $task_id )
127 );
128
129 if ( false === $scheduled ) {
130 // Cron scheduling failed — clean up and bail.
131 Database::delete_scheduled_task( $task_id );
132 return array( 'success' => false, 'message' => 'Delay: wp_schedule_single_event() failed.' );
133 }
134
135 $human_delay = $delay_amount . ' ' . $delay_unit;
136
137 // error_log( sprintf(
138 // 'WPbot Automator - Delay: Workflow %d paused for %s. Task ID: %d. Resumes at %s.',
139 // $workflow_id,
140 // $human_delay,
141 // $task_id,
142 // $scheduled_at
143 // ) );
144
145 // Signal the workflow runner to halt further synchronous execution on
146 // this branch by returning the special __halt key.
147 return array(
148 'success' => true,
149 '__halt' => true,
150 'task_id' => $task_id,
151 'message' => sprintf( 'Delay: workflow paused for %s. Will resume at %s.', $human_delay, $scheduled_at ),
152 );
153 }
154
155 /**
156 * WP Cron callback: resume the deferred workflow.
157 *
158 * Called by WordPress Cron after the delay has elapsed.
159 *
160 * @param int $task_id Scheduled task row ID.
161 */
162 public function resume_workflow( $task_id ) {
163 $task = Database::get_scheduled_task( absint( $task_id ) );
164
165 if ( ! $task ) {
166 //error_log( sprintf( 'WPbot Automator - Delay: resume_workflow called with unknown task ID %d.', $task_id ) );
167 return;
168 }
169
170 if ( 'pending' !== $task['status'] ) {
171 // Already processed (guard against duplicate cron fires).
172 return;
173 }
174
175 // Mark as completed immediately to prevent double execution.
176 Database::complete_scheduled_task( $task_id );
177
178 $workflow_id = (int) $task['workflow_id'];
179 $trigger_id = $task['trigger_id'];
180 $trigger_data = is_array( $task['trigger_data'] ) ? $task['trigger_data'] : array();
181 $remaining_nodes = is_array( $task['remaining_nodes'] ) ? $task['remaining_nodes'] : array();
182 $connections = is_array( $task['connections'] ) ? $task['connections'] : array();
183
184 // error_log( sprintf(
185 // 'WPbot Automator - Delay: Resuming workflow %d from task %d. Remaining nodes: %d.',
186 // $workflow_id,
187 // $task_id,
188 // count( $remaining_nodes )
189 // ) );
190
191 if ( empty( $remaining_nodes ) ) {
192 return;
193 }
194
195 // Re-use the runner to execute remaining nodes directly.
196 // We grab the first remaining node id and pass the rest as the full set.
197 $first_node_id = $remaining_nodes[0];
198 $executed_nodes = array(); // Fresh slate — all remaining nodes are eligible.
199
200 // The runner's run_next_nodes is private, so we build a minimal fake
201 // workflow structure and call execute_workflow with only the remaining nodes.
202 $fake_workflow = array(
203 'id' => $workflow_id,
204 'workflow_data' => wp_json_encode(
205 array(
206 'nodes' => $remaining_nodes,
207 'connections' => $connections,
208 )
209 ),
210 );
211
212 // The first remaining_nodes entry is the node immediately after the
213 // delay node. We mark it as a "trigger" type with a wildcard so the
214 // runner's trigger-matching logic accepts it as the starting point.
215 $fake_workflow['workflow_data'] = wp_json_encode(
216 array(
217 'nodes' => array_map(
218 function( $node, $idx ) {
219 if ( 0 === $idx ) {
220 // Re-type as trigger so execute_workflow starts here.
221 $node['type'] = 'trigger';
222 }
223 return $node;
224 },
225 $remaining_nodes,
226 array_keys( $remaining_nodes )
227 ),
228 'connections' => $connections,
229 )
230 );
231
232 Workflow_Runner::execute_workflow( $fake_workflow, $trigger_data, $first_node_id );
233
234 // error_log( sprintf(
235 // 'WPbot Automator - Delay: Workflow %d resumed and completed from task %d.',
236 // $workflow_id,
237 // $task_id
238 // ) );
239 }
240 }
241