PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.3
Booktics – Appointment Booking Calendar for Service Businesses v1.0.3
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 All 31 releases
booktics / vendor / themewinter / email-notification-sdk / src / Flow / FlowManager.php

FlowManager.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.3, at vendor/themewinter/email-notification-sdk/src/Flow/FlowManager.php

406 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ENS\Flow;
3
4 use ENS\Config;
5 use ENS\Email\EmailSender;
6
7 /**
8 * Class FlowManager
9 *
10 * @package ENS\Flow
11 *
12 * @since 1.0.0
13 */
14 class FlowManager {
15
16 /**
17 * Handle the action.
18 *
19 * @since 1.0.0
20 *
21 * @param string $action The name of the action.
22 * @param array $data The data associated with the action.
23 */
24 public function handle( $action, $data ) {
25 $prefix_for_cpt = Config::get( 'general_prefix' ) ?: '';
26 $post_type = $prefix_for_cpt . '-flow';
27
28 $meta_key = '_' . $prefix_for_cpt . '_notification_flow_trigger';
29
30 $flows = get_posts( [
31 'post_type' => $post_type,
32 'meta_key' => $meta_key,
33 'meta_value' => $action,
34 'post_status' => 'publish',
35 ] );
36
37 if ( empty( $flows ) || !is_array( $flows ) ) {
38 return;
39 }
40
41 foreach ( $flows as $flow ) {
42 if ( !isset( $flow->ID ) ) {
43 continue;
44 }
45
46 $flow_config = get_post_meta( $flow->ID, '_' . $prefix_for_cpt . '_notification_flow_flow_config', true );
47
48 if ( empty( $flow_config ) || !is_array( $flow_config ) ) {
49 continue;
50 }
51
52 $this->execute_flow( $flow_config, $data );
53 }
54 }
55
56 /**
57 * Resume flow callback.
58 *
59 * @since 1.0.0
60 *
61 * @param int $flow_id The ID of the flow.
62 * @param string $resume_time The resume time.
63 */
64 public function resume_flow_callback( $flow_id, $resume_time ) {
65 if ( !$flow_id || !$resume_time ) {
66 return;
67 }
68
69 $checkpoint = $this->get_user_flow_checkpoint( $resume_time, $flow_id );
70 if ( empty( $checkpoint ) || !is_array( $checkpoint ) ) {
71 return;
72 }
73
74 if ( !isset( $checkpoint['resume_after'] ) || time() < $checkpoint['resume_after'] ) {
75 return;
76 }
77
78 if ( !isset( $checkpoint['flow_snapshot'] ) || !isset( $checkpoint['hook_data'] ) ) {
79 return;
80 }
81
82 $flow = $checkpoint['flow_snapshot'];
83 $hook_data = $checkpoint['hook_data'];
84 $resume_node = $checkpoint['resume_node'] ?? null;
85
86 $this->execute_flow( $flow, $hook_data, $resume_node );
87 }
88
89 /**
90 * Execute a flow.
91 *
92 * @since 1.0.0
93 *
94 * @param array $flow The flow data.
95 * @param array $hook_data The hook data.
96 * @param string|null $resume_from_id The node ID to resume from.
97 */
98 public function execute_flow( $flow, $hook_data, $resume_from_id = null ) {
99 if ( !isset( $flow['nodes'] ) || !is_array( $flow['nodes'] ) ||
100 !isset( $flow['edges'] ) || !is_array( $flow['edges'] ) ) {
101 return;
102 }
103
104 $nodes = $flow['nodes'];
105 $edges = $flow['edges'];
106
107 // Build node map with validation
108 $node_map = [];
109 foreach ( $nodes as $node ) {
110 if ( !isset( $node['id'], $node['name'] ) ) {
111 continue;
112 }
113 $node_map[$node['id']] = $node;
114 }
115
116 $start_node_id = $resume_from_id ?? $this->find_trigger_node( $nodes );
117 if ( !$start_node_id || !isset( $node_map[$start_node_id] ) ) {
118 return;
119 }
120
121 $current_node_id = $start_node_id;
122
123 while ( $current_node_id && isset( $node_map[$current_node_id] ) ) {
124 $node = $node_map[$current_node_id];
125
126 switch ( $node['name'] ) {
127 case 'trigger':
128 $current_node_id = $this->get_next_node_id( $edges, $node['id'] );
129 break;
130
131 case 'condition':
132 if ( !isset( $node['data'] ) || !is_array( $node['data'] ) ) {
133 $current_node_id = null;
134 break;
135 }
136
137 $field = $node['data']['field'] ?? null;
138 $operator = $node['data']['operator'] ?? null;
139 $value = $node['data']['value'] ?? null;
140
141 $field_value = $hook_data[$field] ?? null;
142
143 if ( $field === null || $operator === null ) {
144 $current_node_id = null;
145 break;
146 }
147
148 $is_true = $this->compare_values( $field_value, $operator, $value );
149 $current_node_id = $this->get_next_node_id(
150 $edges,
151 $node['id'],
152 $is_true ? 'true' : 'false'
153 );
154 break;
155
156 case 'delay':
157 if ( !isset( $node['data'] ) || !is_array( $node['data'] ) ) {
158 $current_node_id = null;
159 break;
160 }
161
162 $delayUnit = $node['data']['delayUnit'] ?? 'seconds';
163 $delay = isset( $node['data']['delay'] ) ? (int) $node['data']['delay'] : 60;
164
165 $delay_depeneds_on = $node['data']['delayCondition'] ?? null;
166 $delay_condition = 'after';
167 $dependent_key = $delay_depeneds_on;
168
169 if(str_contains( $delay_depeneds_on, 'before' )) {
170 $delay_condition = 'before';
171 $dependent_key = str_replace( 'before_', '', $delay_depeneds_on );
172 }
173 if(str_contains( $delay_depeneds_on, 'after' )) {
174 $delay_condition = 'after';
175 $dependent_key = str_replace( 'after_', '', $delay_depeneds_on );
176 }
177
178 // $dependency_field = $node['data']['delayDependency'] ?? null;
179 $dependency_value = isset( $delay_depeneds_on ) ? ( $hook_data[$dependent_key] ?? current_time( 'timestamp' ) ) : current_time( 'timestamp' );
180 // $dependency_condition = !empty( $node['data']['delayDependency'] ) ?
181 // ( $node['data']['delayCondition'] ?? 'after' ) : '';
182
183 $general_prefix = Config::get( 'general_prefix' ) ?: '';
184
185 // Calculate seconds based on unit
186 $seconds = $delay;
187 switch ( $delayUnit ) {
188 case 'minutes':
189 $seconds *= 60;
190 break;
191 case 'hours':
192 $seconds *= 60 * 60;
193 break;
194 case 'days':
195 $seconds *= 60 * 60 * 24;
196 break;
197 }
198
199 // Calculate resume time
200 $resume_time = ( $delay_condition == 'before' ) ?
201 $dependency_value - $seconds :
202 $dependency_value + $seconds;
203
204 $next_node_id = $this->get_next_node_id( $edges, $node['id'] );
205 if ( !$next_node_id ) {
206 $current_node_id = null;
207 break;
208 }
209
210 $flow_id = uniqid( 'flow_', true );
211
212 // Save checkpoint
213 $this->save_user_flow_checkpoint( $resume_time, $flow_id, [
214 'resume_node' => $next_node_id,
215 'resume_after' => $resume_time,
216 'flow_snapshot' => $flow,
217 'hook_data' => $hook_data,
218 ] );
219
220 // Schedule resume
221 wp_schedule_single_event(
222 $resume_time,
223 $general_prefix . '_resume_flow_after_delay',
224 [
225 'flow_id' => $flow_id,
226 'resume_time' => $resume_time,
227 ]
228 );
229
230 return; // Pause execution
231
232 case 'email':
233 if ( !isset( $node['data'] ) || !is_array( $node['data'] ) ) {
234 $current_node_id = null;
235 break;
236 }
237
238 $receiverType = $node['data']['receiverType'] ?? null;
239 if ( $receiverType && isset( $hook_data[$receiverType] ) ) {
240 $user_email = $hook_data[$receiverType];
241 $this->send_email_to_user( $user_email, $node['data'], $hook_data );
242 }
243 $current_node_id = $this->get_next_node_id( $edges, $node['id'] );
244 break;
245
246 case 'end':
247 return;
248
249 default:
250 $current_node_id = $this->get_next_node_id( $edges, $node['id'] );
251 break;
252 }
253 }
254 }
255
256 /**
257 * Compare values with type safety.
258 *
259 * @since 1.0.0
260 *
261 * @param mixed $left The left value.
262 * @param string $operator The operator.
263 * @param mixed $right The right value.
264 *
265 * @return bool
266 */
267 public function compare_values( $left, $operator, $right ) {
268 if ( $operator === null ) {
269 return false;
270 }
271
272 switch ( $operator ) {
273 case 'equal':return $left == $right;
274 case 'not_equal':return $left != $right;
275 case 'greater_than':
276 if ( !is_numeric( $left ) || !is_numeric( $right ) ) {
277 return false;
278 }
279
280 return $left > $right;
281 case 'less_than':
282 if ( !is_numeric( $left ) || !is_numeric( $right ) ) {
283 return false;
284 }
285
286 return $left < $right;
287 case 'greater_equal':
288 if ( !is_numeric( $left ) || !is_numeric( $right ) ) {
289 return false;
290 }
291
292 return $left >= $right;
293 case 'less_equal':
294 if ( !is_numeric( $left ) || !is_numeric( $right ) ) {
295 return false;
296 }
297
298 return $left <= $right;
299 default:return false;
300 }
301 }
302
303 /**
304 * Find trigger node in nodes array.
305 *
306 * @since 1.0.0
307 *
308 * @param array $nodes The nodes array.
309 *
310 * @return string|null
311 */
312 public function find_trigger_node( $nodes ) {
313 foreach ( $nodes as $node ) {
314 if ( isset( $node['name'] ) && $node['name'] === 'trigger' && isset( $node['id'] ) ) {
315 return $node['id'];
316 }
317 }
318 return null;
319 }
320
321 /**
322 * Get next node ID from edges.
323 *
324 * @since 1.0.0
325 *
326 * @param array $edges The edges array.
327 * @param string $from_id The source node ID.
328 * @param string|null $label The edge label (optional).
329 *
330 * @return string|null
331 */
332 public function get_next_node_id( $edges, $from_id, $label = null ) {
333 foreach ( $edges as $edge ) {
334 if ( !isset( $edge['source'], $edge['target'] ) ) {
335 continue;
336 }
337
338 if ( $edge['source'] === $from_id ) {
339 if ( $label === null || ( isset( $edge['label'] ) && $edge['label'] === $label ) ) {
340 return $edge['target'];
341 }
342 }
343 }
344 return null;
345 }
346
347 /**
348 * Save user flow checkpoint.
349 *
350 * @since 1.0.0
351 *
352 * @param int $resume_time The timestamp to resume at.
353 * @param string $flow_id The flow ID.
354 * @param array $data The checkpoint data.
355 */
356 public function save_user_flow_checkpoint( $resume_time, $flow_id, $data ) {
357 if ( empty( $data ) || !isset( $data['resume_after'] ) ) {
358 return;
359 }
360
361 $expiration = max( 300, $data['resume_after'] - current_time( 'timestamp' ) + 300 );
362 $key = sprintf( 'flow_checkpoint_%s_%s', $flow_id, $resume_time );
363 set_transient( $key, $data, $expiration );
364 }
365
366 /**
367 * Get user flow checkpoint.
368 *
369 * @since 1.0.0
370 *
371 * @param int $resume_time The resume time.
372 * @param string $flow_id The flow ID.
373 *
374 * @return array|null
375 */
376 public function get_user_flow_checkpoint( $resume_time, $flow_id ) {
377 $key = sprintf( 'flow_checkpoint_%s_%s', $flow_id, $resume_time );
378 $checkpoint = get_transient( $key );
379 return is_array( $checkpoint ) ? $checkpoint : null;
380 }
381
382 /**
383 * Send email to user with validation.
384 *
385 * @since 1.0.0
386 *
387 * @param string $email The recipient email.
388 * @param array $data The email data.
389 * @param array $action_data The action data.
390 */
391 public function send_email_to_user( $email, $data, $action_data ) {
392 if ( !filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
393 return;
394 }
395
396 $subject = $data['subject'] ?? '';
397 $body = $data['body'] ?? '';
398
399 if ( empty( $subject ) || empty( $body ) ) {
400 return;
401 }
402
403 $email_sender = new EmailSender( $email, $subject, $body, $action_data );
404 $email_sender->send();
405 }
406 }