PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.3.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.3.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
cartflows / modules / webhook / classes / class-cartflows-webhook-events.php

class-cartflows-webhook-events.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.3.0, at modules/webhook/classes/class-cartflows-webhook-events.php

263 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CartFlows Webhook Events.
4 *
5 * Registers WordPress action hooks that trigger webhook dispatches.
6 *
7 * @package CartFlows
8 */
9
10 // Exit if accessed directly.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 if ( ! class_exists( 'Cartflows_Webhook_Events' ) ) {
16
17 /**
18 * Class Cartflows_Webhook_Events.
19 */
20 class Cartflows_Webhook_Events {
21
22 /**
23 * Member Variable
24 *
25 * @var instance
26 */
27 private static $instance = null;
28
29 /**
30 * Initiator
31 *
32 * @return object initialized object of class.
33 */
34 public static function get_instance() {
35 if ( is_null( self::$instance ) ) {
36 self::$instance = new self();
37 }
38 return self::$instance;
39 }
40
41 /**
42 * Constructor
43 */
44 public function __construct() {
45 $this->register_hooks();
46 }
47
48 /**
49 * Register all event hooks.
50 */
51 private function register_hooks() {
52 // Step visited.
53 add_action( 'template_redirect', array( $this, 'handle_step_visited' ) );
54
55 // Checkout initiated — hook passes parsed checkout form data as first argument.
56 add_action( 'cartflows_woo_checkout_update_order_review_init', array( $this, 'handle_checkout_initiated' ), 10, 1 );
57
58 // Order created (free version uses woocommerce_checkout_order_processed, filtered by CartFlows flow).
59 add_action( 'woocommerce_checkout_order_processed', array( $this, 'handle_order_created' ), 10, 3 );
60
61 // Order completed and Order failed — uses woocommerce_order_status_changed for reliability across WC versions and HPOS.
62 add_action( 'woocommerce_order_status_changed', array( $this, 'handle_order_status_changed' ), 10, 4 );
63 }
64
65 /**
66 * Get available events for the free version.
67 *
68 * @return array
69 */
70 public static function get_available_events() {
71 $events = array(
72 'cartflows_step_visited' => __( 'Funnel Step Visited', 'cartflows' ),
73 'checkout_initiated' => __( 'Checkout Initiated', 'cartflows' ),
74 'order_created' => __( 'Order Created', 'cartflows' ),
75 'order_completed' => __( 'Order Completed', 'cartflows' ),
76 'order_failed' => __( 'Order Failed', 'cartflows' ),
77 );
78
79 return apply_filters( 'cartflows_webhook_available_events', $events );
80 }
81
82 /**
83 * Handle step visited event.
84 */
85 public function handle_step_visited() {
86 if ( ! function_exists( 'wcf_get_step_type' ) ) {
87 return;
88 }
89
90 global $post;
91
92 if ( ! $post ) {
93 return;
94 }
95
96 $step_id = $post->ID;
97 $step_type = wcf_get_step_type( $step_id );
98
99 if ( empty( $step_type ) ) {
100 return;
101 }
102
103 $flow_id = get_post_meta( $step_id, 'wcf-flow-id', true );
104
105 if ( empty( $flow_id ) ) {
106 return;
107 }
108
109 $context = array(
110 'funnel_id' => absint( $flow_id ),
111 'step_id' => absint( $step_id ),
112 'step_type' => $step_type,
113 'step_url' => get_permalink( $step_id ),
114 );
115
116 wcf()->logger->log( 'Webhook event: cartflows_step_visited - Step ' . $step_id . ' (Type: ' . $step_type . ')' );
117
118 Cartflows_Webhook_Dispatcher::get_instance()->dispatch( 'cartflows_step_visited', $context );
119 }
120
121 /**
122 * Handle checkout initiated event.
123 *
124 * Fired by cartflows_woo_checkout_update_order_review_init which passes
125 * the parsed checkout form data (from $_POST['post_data']).
126 *
127 * @param array $ajax_data Parsed checkout form data.
128 */
129 public function handle_checkout_initiated( $ajax_data = array() ) {
130 $step_id = 0;
131
132 // Get checkout ID from the hook's parsed form data.
133 if ( ! empty( $ajax_data ) && isset( $ajax_data['_wcf_checkout_id'] ) ) {
134 $step_id = absint( $ajax_data['_wcf_checkout_id'] );
135 }
136
137 // Fallback: try the utility method.
138 if ( empty( $step_id ) && is_callable( array( wcf()->utils, 'get_checkout_id_from_post_data' ) ) ) {
139 $step_id = absint( wcf()->utils->get_checkout_id_from_post_data() );
140 }
141
142 // Fallback: try the global function.
143 if ( empty( $step_id ) && function_exists( '_get_wcf_checkout_id' ) ) {
144 $step_id = absint( _get_wcf_checkout_id() );
145 }
146
147 if ( empty( $step_id ) ) {
148 return;
149 }
150
151 $flow_id = get_post_meta( $step_id, 'wcf-flow-id', true );
152
153 if ( empty( $flow_id ) ) {
154 return;
155 }
156
157 $context = array(
158 'funnel_id' => absint( $flow_id ),
159 'step_id' => absint( $step_id ),
160 );
161
162 wcf()->logger->log( 'Webhook event: checkout_initiated - Step ' . $step_id );
163
164 Cartflows_Webhook_Dispatcher::get_instance()->dispatch( 'checkout_initiated', $context );
165 }
166
167 /**
168 * Handle order created event (from woocommerce_checkout_order_processed).
169 *
170 * @param int $order_id Order ID.
171 * @param array $posted_data Posted checkout data.
172 * @param WC_Order $order Order object.
173 */
174 public function handle_order_created( $order_id, $posted_data, $order ) {
175 if ( ! $order ) {
176 return;
177 }
178
179 $flow_id = $order->get_meta( '_wcf_flow_id' );
180
181 if ( empty( $flow_id ) ) {
182 return;
183 }
184
185 $step_id = $order->get_meta( '_wcf_checkout_id' );
186
187 $context = array(
188 'funnel_id' => absint( $flow_id ),
189 'step_id' => absint( $step_id ),
190 'order_id' => absint( $order_id ),
191 'order' => $order,
192 );
193
194 wcf()->logger->log( 'Webhook event: order_created - Order ' . $order_id . ' Flow ' . $flow_id );
195
196 Cartflows_Webhook_Dispatcher::get_instance()->dispatch( 'order_created', $context );
197 }
198
199 /**
200 * Handle order status changed event.
201 *
202 * Uses woocommerce_order_status_changed which is more reliable across
203 * WooCommerce versions and HPOS configurations. Fires for all status
204 * transitions; we filter for completed and failed.
205 *
206 * @param int $order_id Order ID.
207 * @param string $old_status Old order status.
208 * @param string $new_status New order status.
209 * @param WC_Order $order Order object.
210 */
211 public function handle_order_status_changed( $order_id, $old_status, $new_status, $order ) {
212 $event_map = array(
213 'completed' => 'order_completed',
214 'processing' => 'order_completed',
215 'wcf-main-order' => 'order_completed',
216 'failed' => 'order_failed',
217 );
218
219 if ( ! isset( $event_map[ $new_status ] ) ) {
220 return;
221 }
222
223 $event = $event_map[ $new_status ];
224
225 if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
226 $order = wc_get_order( $order_id );
227 }
228
229 if ( ! $order ) {
230 wcf()->logger->log( 'Webhook event: ' . $event . ' - Order ' . $order_id . ' not found, skipping.' );
231 return;
232 }
233
234 $flow_id = $order->get_meta( '_wcf_flow_id' );
235
236 // Force fresh DB read if meta appears empty (guards against stale cache).
237 if ( empty( $flow_id ) ) {
238 $order = wc_get_order( $order_id );
239 $flow_id = $order ? $order->get_meta( '_wcf_flow_id' ) : '';
240 }
241
242 if ( empty( $flow_id ) ) {
243 return;
244 }
245
246 $step_id = $order->get_meta( '_wcf_checkout_id' );
247
248 $context = array(
249 'funnel_id' => absint( $flow_id ),
250 'step_id' => absint( $step_id ),
251 'order_id' => absint( $order_id ),
252 'order' => $order,
253 );
254
255 wcf()->logger->log( 'Webhook event: ' . $event . ' - Order ' . $order_id . ' Flow ' . $flow_id . ' (status: ' . $old_status . ' -> ' . $new_status . ')' );
256
257 Cartflows_Webhook_Dispatcher::get_instance()->dispatch( $event, $context );
258 }
259 }
260
261 Cartflows_Webhook_Events::get_instance();
262 }
263