'tt-flow', 'posts_per_page' => -1, 'post_status' => 'any', ) ); return wp_list_pluck( $posts, 'post_title' ); } /** * Demote any published flow whose title matches a default seed name back * to draft, so the legacy Settings emails remain the sole sender. */ private static function draft_published_seeds() { $seed_names = wp_list_pluck( self::get_default_flows(), 'name' ); $posts = get_posts( array( 'post_type' => 'tt-flow', 'posts_per_page' => -1, 'post_status' => 'publish', 'fields' => 'ids', ) ); foreach ( $posts as $post_id ) { if ( in_array( get_the_title( $post_id ), $seed_names, true ) ) { wp_update_post( array( 'ID' => $post_id, 'post_status' => 'draft' ) ); } } } /** * Delete any tt-flow posts whose nodes are missing the 'type' field — * those were created by the v1.0 seeder with the wrong format. * User-created flows always have 'type' on every node (set by the UI). * Also resets the seeded flag so maybe_seed() re-creates correct flows. */ private static function migrate_bad_flows() { if ( get_option( self::SEEDED_OPTION ) === self::SEEDED_VERSION ) { return; } $posts = get_posts( array( 'post_type' => 'tt-flow', 'posts_per_page' => -1, 'post_status' => 'any', 'fields' => 'ids', ) ); if ( empty( $posts ) ) { return; } $meta_key = '_tt_notification_flow_flow_config'; foreach ( $posts as $post_id ) { $config = get_post_meta( $post_id, $meta_key, true ); if ( empty( $config['nodes'] ) || ! is_array( $config['nodes'] ) ) { continue; } foreach ( $config['nodes'] as $node ) { if ( ! isset( $node['type'] ) ) { wp_delete_post( $post_id, true ); break; } } } } /** * Force re-seed (e.g., for testing). Clears flag then re-runs. */ public static function reseed() { delete_option( self::SEEDED_OPTION ); self::maybe_seed(); } // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- private static function create_flow( array $def ) { $flow = new Flow( 'tt' ); $flow->set_props( array( 'name' => $def['name'], 'trigger' => $def['trigger'], 'flow_config' => $def['flow_config'], 'status' => 'draft', ) ); $flow->save(); } /** Edge with correct UI format. */ private static function edge( string $from, string $to ) { return array( 'id' => 'edge_' . $from . '-' . $to, 'type' => 'smoothstep', 'markerEnd' => array( 'type' => 'arrowclosed' ), 'source' => $from, 'target' => $to, 'data' => array( 'animated' => false ), ); } /** Trigger node. */ private static function trigger_node( string $trigger_value, string $trigger_label ) { return array( 'id' => 'node_1', 'type' => 'trigger', 'name' => 'trigger', 'position' => array( 'x' => 275, 'y' => 70 ), 'data' => array( 'label' => 'trigger: ' . $trigger_value, 'subtitle' => 'On "' . $trigger_label . '" event fires', 'triggerValue' => $trigger_value, ), ); } /** End node. */ private static function end_node( float $y = 600.0 ) { return array( 'id' => 'end_1', 'type' => 'end', 'name' => 'end', 'position' => array( 'x' => 275, 'y' => $y ), 'data' => array( 'label' => 'end_flow', 'subtitle' => 'Automation stops here', ), ); } /** Email action node. */ private static function email_node( string $node_id, string $receiver_type, string $receiver_label, string $subject, string $body, float $y = 420.0 ) { return array( 'id' => $node_id, 'type' => 'action', 'name' => 'email', 'position' => array( 'x' => 275, 'y' => $y ), 'data' => array( 'actionType' => 'send_email', 'label' => 'send_email', 'subtitle' => 'To: ' . $receiver_label, 'receiverType' => $receiver_type, 'from' => '', 'subject' => $subject, 'body' => $body, ), ); } /** Delay action node. */ private static function delay_node( string $node_id, int $delay, string $delay_unit, string $delay_condition, float $y = 245.0 ) { $subtitle = 'Wait for ' . $delay . ' ' . $delay_unit; if ( $delay_condition ) { $subtitle .= ' before ' . str_replace( '_', ' ', str_replace( 'before_', '', $delay_condition ) ); } return array( 'id' => $node_id, 'type' => 'action', 'name' => 'delay', 'position' => array( 'x' => 275, 'y' => $y ), 'data' => array( 'actionType' => 'add_delay', 'label' => 'add_delay', 'subtitle' => $subtitle, 'delay' => $delay, 'delayUnit' => $delay_unit, 'delayCondition' => $delay_condition, ), ); } /** * trigger → email → end */ private static function simple_flow( string $trigger_value, string $trigger_label, string $receiver_type, string $receiver_label, string $subject, string $body ) { return array( 'nodes' => array( self::trigger_node( $trigger_value, $trigger_label ), self::email_node( 'node_2', $receiver_type, $receiver_label, $subject, $body, 280.0 ), self::end_node( 490.0 ), ), 'edges' => array( self::edge( 'node_1', 'node_2' ), self::edge( 'node_2', 'end_1' ), ), ); } /** * trigger → delay → email → end */ private static function reminder_flow( string $trigger_value, string $trigger_label, string $receiver_type, string $receiver_label, string $subject, string $body ) { return array( 'nodes' => array( self::trigger_node( $trigger_value, $trigger_label ), self::delay_node( 'node_2', 24, 'hours', 'before_meeting_date', 245.0 ), self::email_node( 'node_3', $receiver_type, $receiver_label, $subject, $body, 420.0 ), self::end_node( 600.0 ), ), 'edges' => array( self::edge( 'node_1', 'node_2' ), self::edge( 'node_2', 'node_3' ), self::edge( 'node_3', 'end_1' ), ), ); } /** * Definitions for all 8 default flows. */ private static function get_default_flows() { $booking_tags = implode( '', array( '
| Meeting | {%meeting_title%} |
| Date | {%meeting_date%} |
| Time | {%meeting_time%} |
| Duration | {%meeting_duration%} |
| Location | {%meeting_location%} |
| Host | {%host_name%} |
Thank you!
' ), ), array( 'name' => 'Booking Confirmed – Host', 'trigger' => 'booking_created', 'flow_config' => self::simple_flow( 'booking_created', 'After Booking Confirmation', 'host_email', 'Host Email', 'New Booking: {%meeting_title%}', 'Hi {%host_name%},
A new booking has been made.
' . $booking_tags . 'Thank you!
' ), ), // ── Booking Canceled ─────────────────────────────────────────── array( 'name' => 'Booking Canceled – Customer', 'trigger' => 'booking_canceled', 'flow_config' => self::simple_flow( 'booking_canceled', 'After Booking Cancellation', 'customer_email', 'Customer Email', 'Booking Canceled: {%meeting_title%}', 'Hi {%customer_name%},
Your booking has been canceled.
' . '| Meeting | {%meeting_title%} |
| Date | {%meeting_date%} |
| Time | {%meeting_time%} |
If you have questions, please contact us.
Thank you!
' ), ), array( 'name' => 'Booking Canceled – Host', 'trigger' => 'booking_canceled', 'flow_config' => self::simple_flow( 'booking_canceled', 'After Booking Cancellation', 'host_email', 'Host Email', 'Booking Canceled: {%meeting_title%}', 'Hi {%host_name%},
A booking has been canceled.
' . '| Meeting | {%meeting_title%} |
| Date | {%meeting_date%} |
| Time | {%meeting_time%} |
| Customer | {%customer_name%} ({%customer_email%}) |
Thank you!
' ), ), // ── Booking Rescheduled ──────────────────────────────────────── array( 'name' => 'Booking Rescheduled – Customer', 'trigger' => 'booking_rescheduled', 'flow_config' => self::simple_flow( 'booking_rescheduled', 'After Booking Rescheduled', 'customer_email', 'Customer Email', 'Booking Rescheduled: {%meeting_title%}', 'Hi {%customer_name%},
Your booking has been rescheduled.
' . '| Meeting | {%meeting_title%} |
| New Date | {%meeting_date%} |
| New Time | {%meeting_time%} |
| Location | {%meeting_location%} |
| Host | {%host_name%} |
Thank you!
' ), ), array( 'name' => 'Booking Rescheduled – Host', 'trigger' => 'booking_rescheduled', 'flow_config' => self::simple_flow( 'booking_rescheduled', 'After Booking Rescheduled', 'host_email', 'Host Email', 'Booking Rescheduled: {%meeting_title%}', 'Hi {%host_name%},
A booking has been rescheduled.
' . '| Meeting | {%meeting_title%} |
| New Date | {%meeting_date%} |
| New Time | {%meeting_time%} |
| Customer | {%customer_name%} ({%customer_email%}) |
Thank you!
' ), ), // ── Booking Reminder (24 h before) ───────────────────────────── array( 'name' => 'Booking Reminder – Customer', 'trigger' => 'booking_created', 'flow_config' => self::reminder_flow( 'booking_created', 'After Booking Confirmation', 'customer_email', 'Customer Email', 'Reminder: {%meeting_title%} is coming up', 'Hi {%customer_name%},
This is a reminder that your meeting is coming up soon.
' . $booking_tags . 'We look forward to seeing you!
Thank you!
' ), ), array( 'name' => 'Booking Reminder – Host', 'trigger' => 'booking_created', 'flow_config' => self::reminder_flow( 'booking_created', 'After Booking Confirmation', 'host_email', 'Host Email', 'Reminder: {%meeting_title%} is coming up', 'Hi {%host_name%},
This is a reminder that a meeting is coming up soon.
' . $booking_tags . 'Thank you!
' ), ), ); } }