PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.59
Timetics – Appointment Booking Calendar & Scheduling v1.0.59
1.0.61 1.0.60 1.0.59 1.0.58 1.0.57 1.0.56 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.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 All 62 releases
timetics / core / admin / notification-seeder.php

notification-seeder.php in Timetics – Appointment Booking Calendar & Scheduling 1.0.59, at core/admin/notification-seeder.php

430 lines 19.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Seeds the 8 default notification flows on first activation.
4 *
5 * @package Timetics
6 */
7
8 namespace Timetics\Core\Admin;
9
10 use Ens\Flow\Flow;
11
12 defined( 'ABSPATH' ) || exit;
13
14 class Notification_Seeder {
15
16 const SEEDED_OPTION = 'timetics_default_flows_seeded';
17 const SEEDED_VERSION = '2.0';
18
19 /**
20 * Seed the 8 default flows as drafts.
21 *
22 * The legacy email system in Settings remains the active sender; these
23 * flows ship as drafts (examples) so they never fire until a user
24 * explicitly publishes one.
25 */
26 public static function maybe_seed() {
27 self::migrate_bad_flows();
28
29 if ( get_option( self::SEEDED_OPTION ) === self::SEEDED_VERSION ) {
30 return;
31 }
32
33 $existing = self::existing_flow_titles();
34
35 // Demote previously published default seeds so they stop firing.
36 self::draft_published_seeds();
37
38 foreach ( self::get_default_flows() as $flow_def ) {
39 // Skip if a flow with this name already exists (avoids duplicates
40 // on upgrade); user-renamed flows are left untouched.
41 if ( in_array( $flow_def['name'], $existing, true ) ) {
42 continue;
43 }
44 self::create_flow( $flow_def );
45 }
46
47 update_option( self::SEEDED_OPTION, self::SEEDED_VERSION, false );
48 }
49
50 /**
51 * Titles of all tt-flow posts currently in the DB (any status).
52 *
53 * @return string[]
54 */
55 private static function existing_flow_titles() {
56 $posts = get_posts( array(
57 'post_type' => 'tt-flow',
58 'posts_per_page' => -1,
59 'post_status' => 'any',
60 ) );
61
62 return wp_list_pluck( $posts, 'post_title' );
63 }
64
65 /**
66 * Demote any published flow whose title matches a default seed name back
67 * to draft, so the legacy Settings emails remain the sole sender.
68 */
69 private static function draft_published_seeds() {
70 $seed_names = wp_list_pluck( self::get_default_flows(), 'name' );
71
72 $posts = get_posts( array(
73 'post_type' => 'tt-flow',
74 'posts_per_page' => -1,
75 'post_status' => 'publish',
76 'fields' => 'ids',
77 ) );
78
79 foreach ( $posts as $post_id ) {
80 if ( in_array( get_the_title( $post_id ), $seed_names, true ) ) {
81 wp_update_post( array( 'ID' => $post_id, 'post_status' => 'draft' ) );
82 }
83 }
84 }
85
86 /**
87 * Delete any tt-flow posts whose nodes are missing the 'type' field —
88 * those were created by the v1.0 seeder with the wrong format.
89 * User-created flows always have 'type' on every node (set by the UI).
90 * Also resets the seeded flag so maybe_seed() re-creates correct flows.
91 */
92 private static function migrate_bad_flows() {
93 if ( get_option( self::SEEDED_OPTION ) === self::SEEDED_VERSION ) {
94 return;
95 }
96
97 $posts = get_posts( array(
98 'post_type' => 'tt-flow',
99 'posts_per_page' => -1,
100 'post_status' => 'any',
101 'fields' => 'ids',
102 ) );
103
104 if ( empty( $posts ) ) {
105 return;
106 }
107
108 $meta_key = '_tt_notification_flow_flow_config';
109
110 foreach ( $posts as $post_id ) {
111 $config = get_post_meta( $post_id, $meta_key, true );
112
113 if ( empty( $config['nodes'] ) || ! is_array( $config['nodes'] ) ) {
114 continue;
115 }
116
117 foreach ( $config['nodes'] as $node ) {
118 if ( ! isset( $node['type'] ) ) {
119 wp_delete_post( $post_id, true );
120 break;
121 }
122 }
123 }
124 }
125
126 /**
127 * Force re-seed (e.g., for testing). Clears flag then re-runs.
128 */
129 public static function reseed() {
130 delete_option( self::SEEDED_OPTION );
131 self::maybe_seed();
132 }
133
134 // -------------------------------------------------------------------------
135 // Private helpers
136 // -------------------------------------------------------------------------
137
138 private static function create_flow( array $def ) {
139 $flow = new Flow( 'tt' );
140 $flow->set_props( array(
141 'name' => $def['name'],
142 'trigger' => $def['trigger'],
143 'flow_config' => $def['flow_config'],
144 'status' => 'draft',
145 ) );
146 $flow->save();
147 }
148
149 /** Edge with correct UI format. */
150 private static function edge( string $from, string $to ) {
151 return array(
152 'id' => 'edge_' . $from . '-' . $to,
153 'type' => 'smoothstep',
154 'markerEnd' => array( 'type' => 'arrowclosed' ),
155 'source' => $from,
156 'target' => $to,
157 'data' => array( 'animated' => false ),
158 );
159 }
160
161 /** Trigger node. */
162 private static function trigger_node( string $trigger_value, string $trigger_label ) {
163 return array(
164 'id' => 'node_1',
165 'type' => 'trigger',
166 'name' => 'trigger',
167 'position' => array( 'x' => 275, 'y' => 70 ),
168 'data' => array(
169 'label' => 'trigger: ' . $trigger_value,
170 'subtitle' => 'On "' . $trigger_label . '" event fires',
171 'triggerValue' => $trigger_value,
172 ),
173 );
174 }
175
176 /** End node. */
177 private static function end_node( float $y = 600.0 ) {
178 return array(
179 'id' => 'end_1',
180 'type' => 'end',
181 'name' => 'end',
182 'position' => array( 'x' => 275, 'y' => $y ),
183 'data' => array(
184 'label' => 'end_flow',
185 'subtitle' => 'Automation stops here',
186 ),
187 );
188 }
189
190 /** Email action node. */
191 private static function email_node( string $node_id, string $receiver_type, string $receiver_label, string $subject, string $body, float $y = 420.0 ) {
192 return array(
193 'id' => $node_id,
194 'type' => 'action',
195 'name' => 'email',
196 'position' => array( 'x' => 275, 'y' => $y ),
197 'data' => array(
198 'actionType' => 'send_email',
199 'label' => 'send_email',
200 'subtitle' => 'To: ' . $receiver_label,
201 'receiverType' => $receiver_type,
202 'from' => '',
203 'subject' => $subject,
204 'body' => $body,
205 ),
206 );
207 }
208
209 /** Delay action node. */
210 private static function delay_node( string $node_id, int $delay, string $delay_unit, string $delay_condition, float $y = 245.0 ) {
211 $subtitle = 'Wait for ' . $delay . ' ' . $delay_unit;
212 if ( $delay_condition ) {
213 $subtitle .= ' before ' . str_replace( '_', ' ', str_replace( 'before_', '', $delay_condition ) );
214 }
215
216 return array(
217 'id' => $node_id,
218 'type' => 'action',
219 'name' => 'delay',
220 'position' => array( 'x' => 275, 'y' => $y ),
221 'data' => array(
222 'actionType' => 'add_delay',
223 'label' => 'add_delay',
224 'subtitle' => $subtitle,
225 'delay' => $delay,
226 'delayUnit' => $delay_unit,
227 'delayCondition' => $delay_condition,
228 ),
229 );
230 }
231
232 /**
233 * trigger → email → end
234 */
235 private static function simple_flow( string $trigger_value, string $trigger_label, string $receiver_type, string $receiver_label, string $subject, string $body ) {
236 return array(
237 'nodes' => array(
238 self::trigger_node( $trigger_value, $trigger_label ),
239 self::email_node( 'node_2', $receiver_type, $receiver_label, $subject, $body, 280.0 ),
240 self::end_node( 490.0 ),
241 ),
242 'edges' => array(
243 self::edge( 'node_1', 'node_2' ),
244 self::edge( 'node_2', 'end_1' ),
245 ),
246 );
247 }
248
249 /**
250 * trigger → delay → email → end
251 */
252 private static function reminder_flow( string $trigger_value, string $trigger_label, string $receiver_type, string $receiver_label, string $subject, string $body ) {
253 return array(
254 'nodes' => array(
255 self::trigger_node( $trigger_value, $trigger_label ),
256 self::delay_node( 'node_2', 24, 'hours', 'before_meeting_date', 245.0 ),
257 self::email_node( 'node_3', $receiver_type, $receiver_label, $subject, $body, 420.0 ),
258 self::end_node( 600.0 ),
259 ),
260 'edges' => array(
261 self::edge( 'node_1', 'node_2' ),
262 self::edge( 'node_2', 'node_3' ),
263 self::edge( 'node_3', 'end_1' ),
264 ),
265 );
266 }
267
268 /**
269 * Definitions for all 8 default flows.
270 */
271 private static function get_default_flows() {
272 $booking_tags = implode( '', array(
273 '<table style="border-collapse:collapse;width:100%;margin:20px 0;">',
274 '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;width:40%;">Meeting</td><td style="padding:8px 0;color:#556880;">{%meeting_title%}</td></tr>',
275 '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Date</td><td style="padding:8px 0;color:#556880;">{%meeting_date%}</td></tr>',
276 '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Time</td><td style="padding:8px 0;color:#556880;">{%meeting_time%}</td></tr>',
277 '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Duration</td><td style="padding:8px 0;color:#556880;">{%meeting_duration%}</td></tr>',
278 '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Location</td><td style="padding:8px 0;color:#556880;">{%meeting_location%}</td></tr>',
279 ) );
280
281 return array(
282
283 // ── Booking Confirmed ──────────────────────────────────────────
284 array(
285 'name' => 'Booking Confirmed – Customer',
286 'trigger' => 'booking_created',
287 'flow_config' => self::simple_flow(
288 'booking_created',
289 'After Booking Confirmation',
290 'customer_email',
291 'Customer Email',
292 'Booking Confirmed: {%meeting_title%}',
293 '<p>Hi {%customer_name%},</p><p>Your booking has been confirmed.</p>'
294 . $booking_tags
295 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Host</td><td style="padding:8px 0;color:#556880;">{%host_name%}</td></tr>'
296 . '</table><p>Thank you!</p>'
297 ),
298 ),
299
300 array(
301 'name' => 'Booking Confirmed – Host',
302 'trigger' => 'booking_created',
303 'flow_config' => self::simple_flow(
304 'booking_created',
305 'After Booking Confirmation',
306 'host_email',
307 'Host Email',
308 'New Booking: {%meeting_title%}',
309 '<p>Hi {%host_name%},</p><p>A new booking has been made.</p>'
310 . $booking_tags
311 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Customer</td><td style="padding:8px 0;color:#556880;">{%customer_name%} ({%customer_email%})</td></tr>'
312 . '</table><p>Thank you!</p>'
313 ),
314 ),
315
316 // ── Booking Canceled ───────────────────────────────────────────
317 array(
318 'name' => 'Booking Canceled – Customer',
319 'trigger' => 'booking_canceled',
320 'flow_config' => self::simple_flow(
321 'booking_canceled',
322 'After Booking Cancellation',
323 'customer_email',
324 'Customer Email',
325 'Booking Canceled: {%meeting_title%}',
326 '<p>Hi {%customer_name%},</p><p>Your booking has been canceled.</p>'
327 . '<table style="border-collapse:collapse;width:100%;margin:20px 0;">'
328 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;width:40%;">Meeting</td><td style="padding:8px 0;color:#556880;">{%meeting_title%}</td></tr>'
329 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Date</td><td style="padding:8px 0;color:#556880;">{%meeting_date%}</td></tr>'
330 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Time</td><td style="padding:8px 0;color:#556880;">{%meeting_time%}</td></tr>'
331 . '</table><p>If you have questions, please contact us.</p><p>Thank you!</p>'
332 ),
333 ),
334
335 array(
336 'name' => 'Booking Canceled – Host',
337 'trigger' => 'booking_canceled',
338 'flow_config' => self::simple_flow(
339 'booking_canceled',
340 'After Booking Cancellation',
341 'host_email',
342 'Host Email',
343 'Booking Canceled: {%meeting_title%}',
344 '<p>Hi {%host_name%},</p><p>A booking has been canceled.</p>'
345 . '<table style="border-collapse:collapse;width:100%;margin:20px 0;">'
346 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;width:40%;">Meeting</td><td style="padding:8px 0;color:#556880;">{%meeting_title%}</td></tr>'
347 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Date</td><td style="padding:8px 0;color:#556880;">{%meeting_date%}</td></tr>'
348 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Time</td><td style="padding:8px 0;color:#556880;">{%meeting_time%}</td></tr>'
349 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Customer</td><td style="padding:8px 0;color:#556880;">{%customer_name%} ({%customer_email%})</td></tr>'
350 . '</table><p>Thank you!</p>'
351 ),
352 ),
353
354 // ── Booking Rescheduled ────────────────────────────────────────
355 array(
356 'name' => 'Booking Rescheduled – Customer',
357 'trigger' => 'booking_rescheduled',
358 'flow_config' => self::simple_flow(
359 'booking_rescheduled',
360 'After Booking Rescheduled',
361 'customer_email',
362 'Customer Email',
363 'Booking Rescheduled: {%meeting_title%}',
364 '<p>Hi {%customer_name%},</p><p>Your booking has been rescheduled.</p>'
365 . '<table style="border-collapse:collapse;width:100%;margin:20px 0;">'
366 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;width:40%;">Meeting</td><td style="padding:8px 0;color:#556880;">{%meeting_title%}</td></tr>'
367 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">New Date</td><td style="padding:8px 0;color:#556880;">{%meeting_date%}</td></tr>'
368 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">New Time</td><td style="padding:8px 0;color:#556880;">{%meeting_time%}</td></tr>'
369 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Location</td><td style="padding:8px 0;color:#556880;">{%meeting_location%}</td></tr>'
370 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Host</td><td style="padding:8px 0;color:#556880;">{%host_name%}</td></tr>'
371 . '</table><p>Thank you!</p>'
372 ),
373 ),
374
375 array(
376 'name' => 'Booking Rescheduled – Host',
377 'trigger' => 'booking_rescheduled',
378 'flow_config' => self::simple_flow(
379 'booking_rescheduled',
380 'After Booking Rescheduled',
381 'host_email',
382 'Host Email',
383 'Booking Rescheduled: {%meeting_title%}',
384 '<p>Hi {%host_name%},</p><p>A booking has been rescheduled.</p>'
385 . '<table style="border-collapse:collapse;width:100%;margin:20px 0;">'
386 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;width:40%;">Meeting</td><td style="padding:8px 0;color:#556880;">{%meeting_title%}</td></tr>'
387 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">New Date</td><td style="padding:8px 0;color:#556880;">{%meeting_date%}</td></tr>'
388 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">New Time</td><td style="padding:8px 0;color:#556880;">{%meeting_time%}</td></tr>'
389 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Customer</td><td style="padding:8px 0;color:#556880;">{%customer_name%} ({%customer_email%})</td></tr>'
390 . '</table><p>Thank you!</p>'
391 ),
392 ),
393
394 // ── Booking Reminder (24 h before) ─────────────────────────────
395 array(
396 'name' => 'Booking Reminder – Customer',
397 'trigger' => 'booking_created',
398 'flow_config' => self::reminder_flow(
399 'booking_created',
400 'After Booking Confirmation',
401 'customer_email',
402 'Customer Email',
403 'Reminder: {%meeting_title%} is coming up',
404 '<p>Hi {%customer_name%},</p><p>This is a reminder that your meeting is coming up soon.</p>'
405 . $booking_tags
406 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Host</td><td style="padding:8px 0;color:#556880;">{%host_name%}</td></tr>'
407 . '</table><p>We look forward to seeing you!</p><p>Thank you!</p>'
408 ),
409 ),
410
411 array(
412 'name' => 'Booking Reminder – Host',
413 'trigger' => 'booking_created',
414 'flow_config' => self::reminder_flow(
415 'booking_created',
416 'After Booking Confirmation',
417 'host_email',
418 'Host Email',
419 'Reminder: {%meeting_title%} is coming up',
420 '<p>Hi {%host_name%},</p><p>This is a reminder that a meeting is coming up soon.</p>'
421 . $booking_tags
422 . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Customer</td><td style="padding:8px 0;color:#556880;">{%customer_name%} ({%customer_email%})</td></tr>'
423 . '</table><p>Thank you!</p>'
424 ),
425 ),
426
427 );
428 }
429 }
430