PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.3
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.3
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
← All changes | includes/core/rest-api/Controllers/class-funnel-controller.php +1802 -881 2.8.16 → 3.13.3 View file →
@@ -1,10 +1,11 @@
1 1 <?php
2 2 /**
3 3 * Funnel controller
4 - *
4 + *
5 5 * @package WPFunnels\Rest\Controllers
6 6 */
7 +
7 8 namespace WPFunnels\Rest\Controllers;
8 9
9 10 use Error;
10 11 use WP_Error;
@@ -13,178 +14,193 @@
13 14 use WPFunnels\Wpfnl_functions;
14 15 use Wpfnl_Controller_Type_Factory;
15 16 use WPFunnels\Modules\Admin\Funnel\Module;
16 17 use WPFunnels\Wpfnl;
18 +use WPFunnels\Migration\Migration;
17 19
18 20 class FunnelController extends Wpfnl_REST_Controller
19 21 {
20 22
21 - /**
22 - * Endpoint namespace.
23 - *
24 - * @var string
25 - */
26 - protected $namespace = 'wpfunnels/v1';
23 + /**
24 + * Endpoint namespace.
25 + *
26 + * @var string
27 + */
28 + protected $namespace = 'wpfunnels/v1';
27 29
28 - /**
29 - * Route base.
30 - *
31 - * @var string
32 - */
33 - protected $rest_base = 'funnel-control';
30 + /**
31 + * Route base.
32 + *
33 + * @var string
34 + */
35 + protected $rest_base = 'funnel-control';
34 36
35 - /**
36 - * Check if user has valid permission
37 - *
38 - * @param $request
39 - *
40 - * @return bool|WP_Error
41 - * @since 1.0.0
42 - */
43 - public function update_items_permissions_check($request)
44 - {
45 - if (!Wpfnl_functions::wpfnl_rest_check_manager_permissions( 'steps', 'edit' )) {
46 - return new WP_Error('wpfunnels_rest_cannot_edit', __('Sorry, you cannot edit this resource.', 'wpfnl'), ['status' => rest_authorization_required_code()]);
47 - }
48 - return true;
49 - }
37 + /**
38 + * Check if user has valid permission
39 + *
40 + * @param $request
41 + *
42 + * @return bool|WP_Error
43 + * @since 1.0.0
44 + */
45 + public function update_items_permissions_check($request)
46 + {
47 + if (!Wpfnl_functions::wpfnl_rest_check_manager_permissions('steps', 'edit')) {
48 + return new WP_Error('wpfunnels_rest_cannot_edit', __('Sorry, you cannot edit this resource.', 'wpfnl'), ['status' => rest_authorization_required_code()]);
49 + }
50 + return true;
51 + }
50 52
51 - /**
52 - * Makes sure the current user has access to READ the settings APIs.
53 - *
54 - * @param WP_REST_Request $request Full data about the request.
55 - *
56 - * @return WP_Error|boolean
57 - * @since 3.0.0
58 - */
59 - public function get_items_permissions_check($request)
60 - {
61 - if (!Wpfnl_functions::wpfnl_rest_check_manager_permissions('settings')) {
62 - return new WP_Error('wpfunnels_rest_cannot_view', __('Sorry, you cannot list resources.', 'wpfnl'), ['status' => rest_authorization_required_code()]);
63 - }
64 - return true;
65 - }
53 + /**
54 + * Makes sure the current user has access to READ the settings APIs.
55 + *
56 + * @param WP_REST_Request $request Full data about the request.
57 + *
58 + * @return WP_Error|boolean
59 + * @since 3.0.0
60 + */
61 + public function get_items_permissions_check($request)
62 + {
63 + if (!Wpfnl_functions::wpfnl_rest_check_manager_permissions('settings')) {
64 + return new WP_Error('wpfunnels_rest_cannot_view', __('Sorry, you cannot list resources.', 'wpfnl'), ['status' => rest_authorization_required_code()]);
65 + }
66 + return true;
67 + }
66 68
67 69
68 - /**
69 - * Register rest routes
70 - *
71 - * @since 1.0.0
72 - */
73 - public function register_routes()
74 - {
75 - register_rest_route($this->namespace, '/' . $this->rest_base . '/saveFunnel/', [
76 - [
77 - 'methods' => \WP_REST_Server::EDITABLE,
78 - 'callback' => [
79 - $this,
80 - 'save_funnel_data'
81 - ],
82 - 'permission_callback' => [
83 - $this,
84 - 'update_items_permissions_check'
85 - ] ,
86 - ],
87 - ]);
70 + /**
71 + * Register rest routes
72 + *
73 + * @since 1.0.0
74 + */
75 + public function register_routes()
76 + {
77 + register_rest_route($this->namespace, '/' . $this->rest_base . '/saveFunnel/', [
78 + [
79 + 'methods' => \WP_REST_Server::EDITABLE,
80 + 'callback' => [
81 + $this,
82 + 'save_funnel_data'
83 + ],
84 + 'permission_callback' => [
85 + $this,
86 + 'update_items_permissions_check'
87 + ],
88 + ],
89 + ]);
88 90
89 - register_rest_route($this->namespace, '/' . $this->rest_base . '/getThankyouData/', [
90 - [
91 - 'methods' => \WP_REST_Server::READABLE,
92 - 'callback' => [
93 - $this,
94 - 'get_thankyou_data'
95 - ],
96 - 'permission_callback' => [
97 - $this,
98 - 'update_items_permissions_check'
99 - ] ,
100 - ],
101 - ]);
91 + register_rest_route($this->namespace, '/' . $this->rest_base . '/getThankyouData/', [
92 + [
93 + 'methods' => \WP_REST_Server::READABLE,
94 + 'callback' => [
95 + $this,
96 + 'get_thankyou_data'
97 + ],
98 + 'permission_callback' => [
99 + $this,
100 + 'update_items_permissions_check'
101 + ],
102 + ],
103 + ]);
102 104
103 - register_rest_route($this->namespace, '/' . $this->rest_base . '/saveConditionalNode/', [
104 - [
105 - 'methods' => \WP_REST_Server::EDITABLE,
106 - 'callback' => [
107 - $this,
108 - 'save_conditional_node'
109 - ],
110 - 'permission_callback' => [
111 - $this,
112 - 'update_items_permissions_check'
113 - ] ,
114 - ],
115 - ]);
116 - register_rest_route($this->namespace, '/' . $this->rest_base . '/getConditionalNode/', [
117 - [
118 - 'methods' => \WP_REST_Server::READABLE,
119 - 'callback' => [
120 - $this,
121 - 'get_conditional_node'
122 - ],
123 - 'permission_callback' => [
124 - $this,
125 - 'get_items_permissions_check'
126 - ] ,
127 - ],
128 - ]);
129 - register_rest_route($this->namespace, '/' . $this->rest_base . '/getFunnel/', [
130 - [
131 - 'methods' => \WP_REST_Server::READABLE,
132 - 'callback' => [
133 - $this,
134 - 'get_funnel_data'
135 - ],
136 - 'permission_callback' => [
137 - $this,
138 - 'update_items_permissions_check'
139 - ],
140 - ],
141 - ]);
105 + register_rest_route($this->namespace, '/' . $this->rest_base . '/saveConditionalNode/', [
106 + [
107 + 'methods' => \WP_REST_Server::EDITABLE,
108 + 'callback' => [
109 + $this,
110 + 'save_conditional_node'
111 + ],
112 + 'permission_callback' => [
113 + $this,
114 + 'update_items_permissions_check'
115 + ],
116 + ],
117 + ]);
118 + register_rest_route($this->namespace, '/' . $this->rest_base . '/getConditionalNode/', [
119 + [
120 + 'methods' => \WP_REST_Server::READABLE,
121 + 'callback' => [
122 + $this,
123 + 'get_conditional_node'
124 + ],
125 + 'permission_callback' => [
126 + $this,
127 + 'get_items_permissions_check'
128 + ],
129 + ],
130 + ]);
131 + register_rest_route($this->namespace, '/' . $this->rest_base . '/getFunnel/', [
132 + [
133 + 'methods' => \WP_REST_Server::READABLE,
134 + 'callback' => [
135 + $this,
136 + 'get_funnel_data'
137 + ],
138 + 'permission_callback' => [
139 + $this,
140 + 'update_items_permissions_check'
141 + ],
142 + ],
143 + ]);
142 144
143 - register_rest_route($this->namespace, '/' . $this->rest_base . '/getStepType/', [
144 - [
145 - 'methods' => \WP_REST_Server::READABLE,
146 - 'callback' => [
147 - $this,
148 - 'get_step_type'
149 - ],
150 - 'permission_callback' => [
151 - $this,
152 - 'update_items_permissions_check'
153 - ] ,
154 - ],
155 - ]);
145 + register_rest_route($this->namespace, '/' . $this->rest_base . '/getFunnelStats/', [
146 + [
147 + 'methods' => \WP_REST_Server::READABLE,
148 + 'callback' => [
149 + $this,
150 + 'get_funnel_stats'
151 + ],
152 + 'permission_callback' => [
153 + $this,
154 + 'update_items_permissions_check'
155 + ],
156 + ],
157 + ]);
156 158
157 - register_rest_route($this->namespace, '/' . $this->rest_base . '/getFunnelInfo/', [
158 - [
159 - 'methods' => \WP_REST_Server::READABLE,
160 - 'callback' => [
161 - $this,
162 - 'get_funnel_info'
163 - ],
164 - 'permission_callback' => [
165 - $this,
166 - 'update_items_permissions_check'
167 - ],
168 - ],
169 - ]);
159 + register_rest_route($this->namespace, '/' . $this->rest_base . '/getStepType/', [
160 + [
161 + 'methods' => \WP_REST_Server::READABLE,
162 + 'callback' => [
163 + $this,
164 + 'get_step_type'
165 + ],
166 + 'permission_callback' => [
167 + $this,
168 + 'update_items_permissions_check'
169 + ],
170 + ],
171 + ]);
170 172
173 + register_rest_route($this->namespace, '/' . $this->rest_base . '/getFunnelInfo/', [
174 + [
175 + 'methods' => \WP_REST_Server::READABLE,
176 + 'callback' => [
177 + $this,
178 + 'get_funnel_info'
179 + ],
180 + 'permission_callback' => [
181 + $this,
182 + 'update_items_permissions_check'
183 + ],
184 + ],
185 + ]);
171 186
172 - register_rest_route($this->namespace, '/' . $this->rest_base . '/exportFunnel/', [
173 - [
174 - 'methods' => \WP_REST_Server::READABLE,
175 - 'callback' => [
176 - $this,
177 - 'export_funnel'
178 - ],
179 - 'permission_callback' => [
180 - $this,
181 - 'update_items_permissions_check'
182 - ] ,
183 - ],
184 - ]);
185 187
186 - register_rest_route($this->namespace, '/' . $this->rest_base . '/getallfunnels/', array(
188 + register_rest_route($this->namespace, '/' . $this->rest_base . '/exportFunnel/', [
189 + [
190 + 'methods' => \WP_REST_Server::READABLE,
191 + 'callback' => [
192 + $this,
193 + 'export_funnel'
194 + ],
195 + 'permission_callback' => [
196 + $this,
197 + 'update_items_permissions_check'
198 + ],
199 + ],
200 + ]);
201 +
202 + register_rest_route($this->namespace, '/' . $this->rest_base . '/getallfunnels/', array(
187 203 array(
188 204 'methods' => \WP_REST_Server::READABLE,
189 205 'callback' => [
190 206 $this,
@@ -192,12 +208,26 @@
192 208 ],
193 209 'permission_callback' => [
194 210 $this,
195 211 'update_items_permissions_check'
196 - ] ,
212 + ],
197 213 ),
198 214 ));
199 215
216 + register_rest_route($this->namespace, '/' . $this->rest_base . '/getStepGeneralInfo/', [
217 + [
218 + 'methods' => \WP_REST_Server::READABLE,
219 + 'callback' => [
220 + $this,
221 + 'get_step_general_info'
222 + ],
223 + 'permission_callback' => [
224 + $this,
225 + 'update_items_permissions_check'
226 + ],
227 + ],
228 + ]);
229 +
200 230 register_rest_route($this->namespace, '/' . $this->rest_base . '/steps/', array(
201 231 'args' => array(
202 232 'funnel_id' => array(
203 233 'description' => __('Funnel ID.', 'wpfnl'),
@@ -216,48 +246,76 @@
216 246 ],
217 247 'permission_callback' => [
218 248 $this,
219 249 'update_items_permissions_check'
220 - ] ,
250 + ],
221 251 )
222 252 ));
223 253
224 254
225 - register_rest_route($this->namespace, '/' . $this->rest_base . '/get_gbf_data/(?P<funnel_id>\d+)', [
226 - [
227 - 'methods' => \WP_REST_Server::READABLE,
228 - 'callback' => [
229 - $this,
230 - 'get_GBF_data'
231 - ],
232 - 'permission_callback' => [
233 - $this,
234 - 'update_items_permissions_check'
235 - ] ,
236 - ],
237 - ]);
255 + register_rest_route($this->namespace, '/' . $this->rest_base . '/get_gbf_data/(?P<funnel_id>\d+)', [
256 + [
257 + 'methods' => \WP_REST_Server::READABLE,
258 + 'callback' => [
259 + $this,
260 + 'get_GBF_data'
261 + ],
262 + 'permission_callback' => [
263 + $this,
264 + 'update_items_permissions_check'
265 + ],
266 + ],
267 + ]);
238 268
239 - register_rest_route($this->namespace, '/' . $this->rest_base . '/get-settings/(?P<step_id>[\d]+)', [
240 - [
241 - 'methods' => \WP_REST_Server::READABLE,
242 - 'callback' => [
243 - $this,
244 - 'get_ab_settings'
245 - ],
246 - 'permission_callback' => [
247 - $this,
248 - 'update_items_permissions_check'
249 - ] ,
250 - ],
251 - ]);
269 + register_rest_route($this->namespace, '/' . $this->rest_base . '/get-optimizations/(?P<step_id>[\d]+)', [
270 + [
271 + 'methods' => \WP_REST_Server::READABLE,
272 + 'callback' => [
273 + $this,
274 + 'get_optimizations'
275 + ],
276 + 'permission_callback' => [
277 + $this,
278 + 'update_items_permissions_check'
279 + ],
280 + ],
281 + ]);
252 282
253 - register_rest_route(
283 + register_rest_route($this->namespace, '/' . $this->rest_base . '/save-optimizations/', [
284 + [
285 + 'methods' => \WP_REST_Server::EDITABLE,
286 + 'callback' => [
287 + $this,
288 + 'save_optimizations'
289 + ],
290 + 'permission_callback' => [
291 + $this,
292 + 'update_items_permissions_check'
293 + ],
294 + ],
295 + ]);
296 +
297 + register_rest_route($this->namespace, '/' . $this->rest_base . '/get-settings/(?P<step_id>[\d]+)', [
298 + [
299 + 'methods' => \WP_REST_Server::READABLE,
300 + 'callback' => [
301 + $this,
302 + 'get_ab_settings'
303 + ],
304 + 'permission_callback' => [
305 + $this,
306 + 'update_items_permissions_check'
307 + ],
308 + ],
309 + ]);
310 +
311 + register_rest_route(
254 312 $this->namespace,
255 313 '/' . $this->rest_base . '/(?P<funnel_id>\d+)/funnel-name-change',
256 314 array(
257 315 array(
258 - 'methods' => \WP_REST_Server::EDITABLE,
259 - 'callback' => array(
316 + 'methods' => \WP_REST_Server::EDITABLE,
317 + 'callback' => array(
260 318 $this,
261 319 'funnel_name_change',
262 320 ),
263 321 'permission_callback' => array(
@@ -267,25 +325,25 @@
267 325 ),
268 326 )
269 327 );
270 328
271 - register_rest_route(
329 + register_rest_route(
272 330 $this->namespace,
273 331 '/' . $this->rest_base . '/wpfnl-get-funnel-settings',
274 332 array(
275 333 array(
276 - 'methods' => \WP_REST_Server::EDITABLE,
277 - 'callback' => array(
334 + 'methods' => \WP_REST_Server::EDITABLE,
335 + 'callback' => array(
278 336 $this,
279 337 'wpfnl_get_funnel_settings',
280 338 ),
281 - 'args' => array(
282 - 'funnel_id' => array(
283 - 'required' => true,
284 - 'type' => array('integer', 'string'),
285 - 'sanitize_callback' => 'absint',
286 - ),
287 - ),
339 + 'args' => array(
340 + 'funnel_id' => array(
341 + 'required' => true,
342 + 'type' => array('integer', 'string'),
343 + 'sanitize_callback' => 'absint',
344 + ),
345 + ),
288 346 'permission_callback' => array(
289 347 $this,
290 348 'update_items_permissions_check',
291 349 )
@@ -292,15 +350,15 @@
292 350 ),
293 351 )
294 352 );
295 353
296 - register_rest_route(
354 + register_rest_route(
297 355 $this->namespace,
298 356 '/' . $this->rest_base . '/bulk-delete-funnel',
299 357 array(
300 358 array(
301 - 'methods' => \WP_REST_Server::EDITABLE,
302 - 'callback' => array(
359 + 'methods' => \WP_REST_Server::EDITABLE,
360 + 'callback' => array(
303 361 $this,
304 362 'delete_marked_funnels',
305 363 ),
306 364 'permission_callback' => array(
@@ -310,271 +368,695 @@
310 368 ),
311 369 )
312 370 );
313 371
314 - }
372 + register_rest_route(
373 + $this->namespace,
374 + '/' . $this->rest_base . '/bulk-restore-funnel',
375 + array(
376 + array(
377 + 'methods' => \WP_REST_Server::EDITABLE,
378 + 'callback' => array(
379 + $this,
380 + 'restore_marked_funnels',
381 + ),
382 + 'permission_callback' => array(
383 + $this,
384 + 'update_items_permissions_check',
385 + ),
386 + ),
387 + )
388 + );
315 389
316 - /**
317 - * Check if funnel data exists or not
318 - **/
319 - public function get_all_funnels()
320 - {
321 - $args = array(
322 - 'post_type' => 'wpfunnel_steps',
323 - 'numberposts' => -1
324 - );
325 - $funnels = get_posts($args);
390 + register_rest_route(
391 + $this->namespace,
392 + '/' . $this->rest_base . '/bulk-trash-funnel',
393 + array(
394 + array(
395 + 'methods' => \WP_REST_Server::EDITABLE,
396 + 'callback' => array(
397 + $this,
398 + 'trash_marked_funnels',
399 + ),
400 + 'permission_callback' => array(
401 + $this,
402 + 'update_items_permissions_check',
403 + ),
404 + ),
405 + )
406 + );
326 407
327 - if ($funnels) {
328 - if (count($funnels) > 0) {
329 - return false;
330 - }
331 - }
408 + register_rest_route(
409 + $this->namespace,
410 + '/' . $this->rest_base . '/(?P<funnel_id>\d+)/convert-to-store-checkout',
411 + array(
412 + array(
413 + 'methods' => \WP_REST_Server::EDITABLE,
414 + 'callback' => array( $this, 'convert_to_store_checkout' ),
415 + 'permission_callback' => array( $this, 'update_items_permissions_check' ),
416 + 'args' => array(
417 + 'funnel_id' => array(
418 + 'required' => true,
419 + 'validate_callback' => function ( $param ) {
420 + return is_numeric( $param );
421 + },
422 + ),
423 + ),
424 + ),
425 + )
426 + );
332 427
333 - return true;
334 - }
428 + }
335 429
430 + /**
431 + * Check if funnel data exists or not
432 + **/
433 + public function get_all_funnels()
434 + {
435 + $args = array(
436 + 'post_type' => 'wpfunnel_steps',
437 + 'numberposts' => -1
438 + );
439 + $funnels = get_posts($args);
336 440
337 - /**
338 - * Save thankyou data
339 - */
340 - public function get_thankyou_data($request)
341 - {
342 - $step_id = $request['step_id'];
343 - $data = array();
344 - $data['_wpfnl_thankyou_order_overview'] = get_post_meta( $step_id, '_wpfnl_thankyou_order_overview', true ) ? get_post_meta( $step_id, '_wpfnl_thankyou_order_overview', true ) : 'on';
345 - $data['_wpfnl_thankyou_order_details'] = get_post_meta( $step_id, '_wpfnl_thankyou_order_details', true ) ? get_post_meta( $step_id, '_wpfnl_thankyou_order_details', true ) : 'on';
346 - $data['_wpfnl_thankyou_billing_details'] = get_post_meta( $step_id, '_wpfnl_thankyou_billing_details', true ) ? get_post_meta( $step_id, '_wpfnl_thankyou_billing_details', true ): 'on';
347 - $data['_wpfnl_thankyou_shipping_details'] = get_post_meta( $step_id, '_wpfnl_thankyou_shipping_details', true ) ? get_post_meta( $step_id, '_wpfnl_thankyou_shipping_details', true ) : 'on';
348 - $data['_wpfnl_thankyou_is_custom_redirect'] = get_post_meta( $step_id, '_wpfnl_thankyou_is_custom_redirect', true ) ? get_post_meta( $step_id, '_wpfnl_thankyou_is_custom_redirect', true ) : 'off';
349 - $data['_wpfnl_thankyou_is_direct_redirect'] = get_post_meta( $step_id, '_wpfnl_thankyou_is_direct_redirect', true ) ? get_post_meta( $step_id, '_wpfnl_thankyou_is_direct_redirect', true ) : 'off';
350 - $data['_wpfnl_thankyou_set_time'] = get_post_meta( $step_id, '_wpfnl_thankyou_set_time', true ) ? get_post_meta( $step_id, '_wpfnl_thankyou_set_time', true ) : '';
351 - $data['_wpfnl_thankyou_custom_redirect_url']= get_post_meta( $step_id, '_wpfnl_thankyou_custom_redirect_url', true ) ? get_post_meta( $step_id, '_wpfnl_thankyou_custom_redirect_url', true ) : '';
352 - return $data;
353 - }
441 + if ($funnels) {
442 + if (count($funnels) > 0) {
443 + return false;
444 + }
445 + }
354 446
355 - /**
356 - * Get conditional node
357 - *
358 - * @param WP_REST_Request $request request.
359 - *
360 - * @return array|WP_Error
361 - */
362 - public function get_conditional_node($request)
363 - {
364 - $funnel_id = $request['funnel_id'];
365 - $node_identifier = $request['node_identifier'];
447 + return true;
448 + }
366 449
367 - $data = get_post_meta($funnel_id, $node_identifier, true);
368 - $steps = Wpfnl_functions::get_steps( $funnel_id );
369 - $optin_step = [];
370 - if( is_array($steps) ){
371 - foreach( $steps as $step ){
372 - if( 'landing' === $step['step_type'] || 'custom' === $step['step_type'] ){
373 - $optin_step[] = [
374 - 'name' => __('Optin form (', 'wpfnl').$step['name'].')',
375 - 'value' => __('optin_', 'wpfnl').$step['id'],
376 - ];
377 - }
378 - }
379 - }
380 -
381 - if ($data) {
382 - $response = array(
383 - 'status' => 'success',
384 - 'data' => $data,
385 - 'optinStep' => []
386 - );
387 -
388 - } else {
389 - $response = array(
390 - 'status' => 'error',
391 - 'data' => '',
392 - 'optinStep' => []
393 - );
394 - }
395 450
396 - if( !empty($optin_step) ){
397 - $response['status'] = 'success';
398 - $response['optinStep'] = $optin_step;
399 - }
400 -
401 - return $this->prepare_item_for_response($response, $request);
402 - }
451 + /**
452 + * Save thankyou data
453 + */
454 + public function get_thankyou_data($request)
455 + {
456 + $step_id = $request['step_id'];
457 + $data = array();
458 + $data['_wpfnl_thankyou_order_overview'] = get_post_meta($step_id, '_wpfnl_thankyou_order_overview', true) ? get_post_meta($step_id, '_wpfnl_thankyou_order_overview', true) : 'on';
459 + $data['_wpfnl_thankyou_order_details'] = get_post_meta($step_id, '_wpfnl_thankyou_order_details', true) ? get_post_meta($step_id, '_wpfnl_thankyou_order_details', true) : 'on';
460 + $data['_wpfnl_thankyou_billing_details'] = get_post_meta($step_id, '_wpfnl_thankyou_billing_details', true) ? get_post_meta($step_id, '_wpfnl_thankyou_billing_details', true) : 'on';
461 + $data['_wpfnl_thankyou_shipping_details'] = get_post_meta($step_id, '_wpfnl_thankyou_shipping_details', true) ? get_post_meta($step_id, '_wpfnl_thankyou_shipping_details', true) : 'on';
462 + $data['_wpfnl_thankyou_is_custom_redirect'] = get_post_meta($step_id, '_wpfnl_thankyou_is_custom_redirect', true) ? get_post_meta($step_id, '_wpfnl_thankyou_is_custom_redirect', true) : 'off';
463 + $data['_wpfnl_thankyou_is_direct_redirect'] = get_post_meta($step_id, '_wpfnl_thankyou_is_direct_redirect', true) ? get_post_meta($step_id, '_wpfnl_thankyou_is_direct_redirect', true) : 'off';
464 + $data['_wpfnl_thankyou_set_time'] = get_post_meta($step_id, '_wpfnl_thankyou_set_time', true) ? get_post_meta($step_id, '_wpfnl_thankyou_set_time', true) : '';
465 + $data['_wpfnl_thankyou_custom_redirect_url'] = get_post_meta($step_id, '_wpfnl_thankyou_custom_redirect_url', true) ? get_post_meta($step_id, '_wpfnl_thankyou_custom_redirect_url', true) : '';
466 + return $data;
467 + }
403 468
404 - /**
405 - * Save conditional node
406 - *
407 - * @param string $request request.
408 - *
409 - * @return array|WP_Error
410 - */
411 - public function save_conditional_node($request)
412 - {
413 - $funnel_id = $request['funnel_id'];
414 - $condition_data = $request['condition_data'];
415 - $node_identifier = $request['node_identifier'];
416 - update_post_meta($funnel_id, $node_identifier, $condition_data);
469 + /**
470 + * Get conditional node
471 + *
472 + * @param WP_REST_Request $request request.
473 + *
474 + * @return array|WP_Error
475 + */
476 + public function get_conditional_node($request)
477 + {
478 + $funnel_id = $request['funnel_id'];
479 +
480 + $steps = Wpfnl_functions::get_steps($funnel_id);
481 + $optin_step = [];
482 + if (is_array($steps)) {
483 + foreach ($steps as $step) {
484 + if ('landing' === $step['step_type'] || 'custom' === $step['step_type']) {
485 + $optin_step[] = [
486 + 'name' => __('Optin form (', 'wpfnl') . $step['name'] . ')',
487 + 'value' => __('optin_', 'wpfnl') . $step['id'],
488 + ];
489 + }
490 + }
491 + }
417 492 $response = array(
493 + 'status' => 'error',
494 + );
495 + if (!empty($optin_step)) {
496 + $response['status'] = 'success';
497 + $response['optinStep'] = $optin_step;
498 + }
499 +
500 + return $this->prepare_item_for_response($response, $request);
501 + }
502 +
503 + /**
504 + * Save conditional node
505 + *
506 + * @param string $request request.
507 + *
508 + * @return array|WP_Error
509 + */
510 + public function save_conditional_node($request)
511 + {
512 + $funnel_id = $request['funnel_id'];
513 + $condition_data = $request['condition_data'];
514 + $node_identifier = $request['node_identifier'];
515 + update_post_meta($funnel_id, $node_identifier, $condition_data);
516 + $response = array(
418 517 'status' => true,
419 518 );
420 - return $this->prepare_item_for_response( $response, $request );
421 - }
519 + return $this->prepare_item_for_response($response, $request);
520 + }
422 521
423 - /**
424 - * Get step_type.
425 - *
426 - * @param string $request request.
427 - *
428 - * @return array|WP_Error
429 - */
430 - public function get_step_type($request)
431 - {
432 - $step_type = '';
433 - $step_id = $request['step_id'];
434 - $step_type = get_post_meta($step_id, '_step_type', true);
435 - return $step_type;
436 - }
522 + /**
523 + * Get step_type.
524 + *
525 + * @param string $request request.
526 + *
527 + * @return array|WP_Error
528 + */
529 + public function get_step_type($request)
530 + {
531 + $step_type = '';
532 + $step_id = $request['step_id'];
533 + $step_type = get_post_meta($step_id, '_step_type', true);
534 + return $step_type;
535 + }
437 536
537 +
438 538 /**
539 + * Retrieve checkout optimization settings for a funnel step.
540 + *
541 + * Reads post meta for the given step and returns optimization settings
542 + * including express checkout, field validation, product images display,
543 + * collapsible order summary, and enhanced phone field configuration.
544 + * Falls back to default values when no meta is stored.
545 + *
546 + * @param WP_REST_Request $request Request object. Expects 'step_id' parameter.
547 + * @return WP_REST_Response REST response containing the optimization settings.
548 + */
549 + public function get_optimizations($request) {
550 + $step_id = $request['step_id'];
551 + $express_checkout_enabled = get_post_meta($step_id, '_wpfnl_express_checkout_enabled', true);
552 + $express_checkout_position = get_post_meta($step_id, '_wpfnl_express_checkout_position', true);
553 + $field_validation_enabled = get_post_meta($step_id, '_wpfnl_field_validation_enabled', true);
554 + $field_validation_message = get_post_meta($step_id, '_wpfnl_field_validation_message', true);
555 + $display_product_images = get_post_meta($step_id, '_wpfnl_display_product_images', true);
556 + $collapsible_order_summary_enabled = get_post_meta($step_id, '_wpfnl_collapsible_order_summary_enabled', true);
557 + $enhanced_phone_field_enabled = get_post_meta($step_id, '_wpfnl_enhanced_phone_field_enabled', true);
558 + $validate_phone_number = get_post_meta($step_id, '_wpfnl_validate_phone_number', true);
559 + $save_phone_number_format = get_post_meta($step_id, '_wpfnl_save_phone_number_format', true);
560 + $phone_help_text = get_post_meta($step_id, '_wpfnl_phone_help_text', true);
561 +
562 + $testimonial_raw = get_post_meta($step_id, 'wpf_checkout_testimonial', true);
563 + $testimonial_default = array(
564 + 'enabled' => false,
565 + 'layout' => 'layout-1',
566 + 'position' => 'after_bump',
567 + 'testimonial' => array( 'text' => '', 'author' => '', 'rating' => 5 ),
568 + 'guarantee' => array( 'headline' => '', 'text' => '', 'days' => 30, 'image' => array( 'id' => 0, 'url' => '' ) ),
569 + 'benefits' => array( 'title' => "Here's what you get", 'items' => array() ),
570 + );
571 + if ( is_array( $testimonial_raw ) ) {
572 + $testimonial_config = wp_parse_args( $testimonial_raw, $testimonial_default );
573 + // Deep-merge guarantee sub-array so new fields always exist
574 + if ( ! isset( $testimonial_config['guarantee'] ) || ! is_array( $testimonial_config['guarantee'] ) ) {
575 + $testimonial_config['guarantee'] = $testimonial_default['guarantee'];
576 + } else {
577 + $testimonial_config['guarantee'] = wp_parse_args( $testimonial_config['guarantee'], $testimonial_default['guarantee'] );
578 + }
579 + } else {
580 + $testimonial_config = $testimonial_default;
581 + }
582 +
583 + $response = array(
584 + 'express_checkout_enabled' => $express_checkout_enabled ? $express_checkout_enabled : 'yes',
585 + 'express_checkout_position' => $express_checkout_position ? $express_checkout_position : 'top',
586 + 'field_validation_enabled' => $field_validation_enabled ? $field_validation_enabled : 'no',
587 + 'field_validation_message' => $field_validation_message ? $field_validation_message : '{field} is required',
588 + 'display_product_images' => $display_product_images ? $display_product_images : 'no',
589 + 'collapsible_order_summary_enabled' => $collapsible_order_summary_enabled ? $collapsible_order_summary_enabled : 'yes',
590 + 'enhanced_phone_field_enabled' => $enhanced_phone_field_enabled ? $enhanced_phone_field_enabled : 'no',
591 + 'validate_phone_number' => $validate_phone_number ? $validate_phone_number : 'no',
592 + 'save_phone_number_format' => $save_phone_number_format ? $save_phone_number_format : 'without_country_code',
593 + 'phone_help_text' => $phone_help_text ? $phone_help_text : '',
594 + 'testimonial' => $testimonial_config,
595 + );
596 + return $this->prepare_item_for_response($response, $request);
597 + }
598 +
599 + public function save_optimizations($request) {
600 + $step_id = $request['step_id'];
601 + $express_checkout_enabled = sanitize_text_field($request['express_checkout_enabled']);
602 + $express_checkout_position = sanitize_text_field($request['express_checkout_position']);
603 + update_post_meta($step_id, '_wpfnl_express_checkout_enabled', $express_checkout_enabled);
604 + if ( $express_checkout_position ) {
605 + update_post_meta($step_id, '_wpfnl_express_checkout_position', $express_checkout_position);
606 + }
607 +
608 + $field_validation_enabled = isset($request['field_validation_enabled']) ? sanitize_text_field($request['field_validation_enabled']) : 'no';
609 + $field_validation_message = isset($request['field_validation_message']) ? sanitize_text_field($request['field_validation_message']) : '{field} is required';
610 + update_post_meta($step_id, '_wpfnl_field_validation_enabled', $field_validation_enabled);
611 + update_post_meta($step_id, '_wpfnl_field_validation_message', $field_validation_message);
612 +
613 + $display_product_images = isset($request['display_product_images']) ? sanitize_text_field($request['display_product_images']) : 'no';
614 + update_post_meta($step_id, '_wpfnl_display_product_images', $display_product_images);
615 +
616 + $collapsible_order_summary_enabled = isset($request['collapsible_order_summary_enabled']) ? sanitize_text_field($request['collapsible_order_summary_enabled']) : 'yes';
617 + update_post_meta($step_id, '_wpfnl_collapsible_order_summary_enabled', $collapsible_order_summary_enabled);
618 +
619 + $enhanced_phone_field_enabled = isset($request['enhanced_phone_field_enabled']) ? sanitize_text_field($request['enhanced_phone_field_enabled']) : 'no';
620 + $validate_phone_number = isset($request['validate_phone_number']) ? sanitize_text_field($request['validate_phone_number']) : 'no';
621 + $save_phone_number_format = isset($request['save_phone_number_format']) ? sanitize_text_field($request['save_phone_number_format']) : 'without_country_code';
622 + $phone_help_text = isset($request['phone_help_text']) ? sanitize_text_field($request['phone_help_text']) : '';
623 +
624 + update_post_meta($step_id, '_wpfnl_enhanced_phone_field_enabled', $enhanced_phone_field_enabled);
625 + update_post_meta($step_id, '_wpfnl_validate_phone_number', $validate_phone_number);
626 + update_post_meta($step_id, '_wpfnl_save_phone_number_format', $save_phone_number_format);
627 + update_post_meta($step_id, '_wpfnl_phone_help_text', $phone_help_text);
628 +
629 + // Testimonial section
630 + if ( isset( $request['testimonial'] ) && is_array( $request['testimonial'] ) ) {
631 + $raw = $request['testimonial'];
632 +
633 + $testimonial_data = array(
634 + 'enabled' => ! empty( $raw['enabled'] ),
635 + 'layout' => sanitize_key( isset( $raw['layout'] ) ? $raw['layout'] : 'layout-1' ),
636 + 'position' => sanitize_key( isset( $raw['position'] ) ? $raw['position'] : 'after_bump' ),
637 + 'testimonial' => array(
638 + 'text' => isset( $raw['testimonial']['text'] ) ? sanitize_textarea_field( $raw['testimonial']['text'] ) : '',
639 + 'author' => isset( $raw['testimonial']['author'] ) ? sanitize_text_field( $raw['testimonial']['author'] ) : '',
640 + 'rating' => isset( $raw['testimonial']['rating'] ) ? max( 1, min( 5, absint( $raw['testimonial']['rating'] ) ) ) : 5,
641 + ),
642 + 'guarantee' => array(
643 + 'headline' => isset( $raw['guarantee']['headline'] ) ? sanitize_text_field( $raw['guarantee']['headline'] ) : '',
644 + 'text' => isset( $raw['guarantee']['text'] ) ? sanitize_textarea_field( $raw['guarantee']['text'] ) : '',
645 + 'days' => isset( $raw['guarantee']['days'] ) ? max( 1, absint( $raw['guarantee']['days'] ) ) : 30,
646 + 'image' => array(
647 + 'id' => isset( $raw['guarantee']['image']['id'] ) ? absint( $raw['guarantee']['image']['id'] ) : 0,
648 + 'url' => isset( $raw['guarantee']['image']['url'] ) ? esc_url_raw( $raw['guarantee']['image']['url'] ) : '',
649 + ),
650 + ),
651 + 'benefits' => array(
652 + 'title' => isset( $raw['benefits']['title'] ) ? sanitize_text_field( $raw['benefits']['title'] ) : '',
653 + 'items' => array(),
654 + ),
655 + );
656 +
657 + if ( isset( $raw['benefits']['items'] ) && is_array( $raw['benefits']['items'] ) ) {
658 + foreach ( $raw['benefits']['items'] as $item ) {
659 + $testimonial_data['benefits']['items'][] = sanitize_text_field( $item );
660 + }
661 + }
662 +
663 + update_post_meta( $step_id, 'wpf_checkout_testimonial', $testimonial_data );
664 + }
665 +
666 + $response = array(
667 + 'success' => true,
668 + 'message' => __('Saved Successfully', 'wpfnl')
669 + );
670 + return $this->prepare_item_for_response($response, $request);
671 + }
672 +
673 + /**
674 + * Get step_type.
675 + *
676 + * @param string $request request.
677 + *
678 + * @return array|WP_Error
679 + */
680 + public function get_step_general_info($request)
681 + {
682 + $step_type = '';
683 + $step_id = isset($request['step_id']) ? $request['step_id'] : null;
684 + $step_type = get_post_meta($step_id, '_step_type', true);
685 + $custom_script = get_post_meta($step_id, '_wpfnl_custom_script', true);
686 + $custom_css = get_post_meta($step_id, '_wpfnl_custom_css', true);
687 +
688 + $response = array(
689 + 'step_type' => $step_type,
690 + 'step_title' => get_the_title($step_id),
691 + 'step_view_link' => get_post_permalink($step_id),
692 + 'custom_script' => html_entity_decode($custom_script),
693 + 'custom_css' => html_entity_decode($custom_css)
694 + );
695 + return $this->prepare_item_for_response($response, $request);
696 + }
697 +
698 +
699 + /**
439 700 * Get the funnel title and link
440 701 *
441 702 * @param $request
442 - *
703 + *
443 704 * @return \WP_REST_Response
444 705 */
445 - public function get_funnel_info( $request )
446 - {
447 - $funnel_id = $request['funnel_id'];
448 - $title = html_entity_decode(get_the_title($funnel_id));
449 - $steps = get_post_meta( $funnel_id, '_steps_order', true );
706 + public function get_funnel_info($request)
707 + {
708 + $funnel_id = $request['funnel_id'];
709 + $title = html_entity_decode(get_the_title($funnel_id));
710 + $steps = get_post_meta($funnel_id, '_steps_order', true);
450 711 $response['success'] = false;
451 - if ($steps) {
452 - if ( isset($steps[0]) && $steps[0]['id'] ) {
712 + if ($steps) {
713 + if (isset($steps[0]) && $steps[0]['id']) {
453 714 $response['link'] = get_post_permalink($steps[0]['id']);
454 - $response['title']= $title;
455 - $utm_params = $this->get_utm_params( $funnel_id );
456 - if($utm_params != '') {
457 - $response['link'] = $response['link'].$utm_params;
458 - $response['title']= $title;
459 - }
715 + $response['title'] = $title;
716 + $utm_params = $this->get_utm_params($funnel_id);
717 + if ($utm_params != '') {
718 + $response['link'] = $response['link'] . $utm_params;
719 + $response['title'] = $title;
720 + }
460 721 $response['success'] = true;
461 - }
462 - }
722 + }
723 + }
463 724 return $this->prepare_item_for_response($response, $request);
464 - }
725 + }
465 726
466 - /**
467 - * Retrieves the funnel data based on the provided request.
468 - *
469 - * @param mixed $request The request data.
470 - * @return WP_Rest_Response The prepared funnel data response.
471 - *
472 - * @since 2.7.17
473 - */
474 - public function get_funnel_data( $request ) {
475 - $funnel_id = isset( $request['funnel_id'] ) ? $request['funnel_id'] : null;
476 - $response = [
477 - 'status' => 'error',
478 - 'success' => false
479 - ];
480 - if (!$funnel_id) {
481 - return $this->prepare_item_for_response( $response, $request );
482 - }
727 + /**
728 + * Retrieves the funnel data based on the provided request.
729 + *
730 + * @param mixed $request The request data.
731 + * @return WP_Rest_Response The prepared funnel data response.
732 + *
733 + * @since 2.7.17
734 + */
735 + public function get_funnel_data($request)
736 + {
737 + $funnel_id = isset($request['funnel_id']) ? $request['funnel_id'] : null;
738 + $response = [
739 + 'status' => 'error',
740 + 'success' => false
741 + ];
483 742
484 - $response = $this->prepare_funnel_data_response( $funnel_id );
743 + if (!$funnel_id) {
744 + return $this->prepare_item_for_response($response, $request);
745 + }
485 746
486 - return $this->prepare_item_for_response( $response, $request );
487 - }
747 + if( version_compare(WPFNL_VERSION, '3.0.0', '>=') && 'yes' !== get_post_meta( $funnel_id, 'wpfnls_is_newui_migrated', true ) ){
748 + $migration = new Migration();
749 + $funnel_data = get_post_meta($funnel_id, 'funnel_data', true) ? get_post_meta($funnel_id, 'funnel_data', true) : get_post_meta($funnel_id, '_funnel_data', true);
750 + $migration->update_funnel_data($funnel_id, $funnel_data);
751 + update_post_meta($funnel_id, 'wpfnls_is_newui_migrated', 'yes');
752 + }
753 + $response = $this->prepare_funnel_data_response($funnel_id);
754 + return $this->prepare_item_for_response($response, $request);
755 + }
488 756
489 - /**
490 - * Prepares the response data for the funnel data based on the provided parameters.
491 - *
492 - * @param int $funnel_id The ID of the funnel.
493 - * @param mixed $request The request data.
494 - * @return array The prepared response data.
495 - *
496 - * @since 2.7.17
497 - */
498 - private function prepare_funnel_data_response( $funnel_id ) {
757 +
758 + /**
759 + * Retrieves visit/conversion figures for a funnel's steps plus funnel totals.
760 + *
761 + * Deliberately kept out of get_funnel_data() so that opening a funnel on the
762 + * canvas never blocks on analytics. On sites with large amounts of tracked
763 + * traffic these aggregates can be slow, so the canvas renders its structure
764 + * first and fills the numbers in once this responds.
765 + *
766 + * @param mixed $request The request data.
767 + * @return \WP_REST_Response
768 + *
769 + * @since 3.12.12
770 + */
771 + public function get_funnel_stats($request)
772 + {
773 + $funnel_id = isset($request['funnel_id']) ? absint($request['funnel_id']) : 0;
774 +
499 775 $response = [
500 - 'status' => 'error',
776 + 'success' => false,
777 + 'steps' => (object) [],
778 + 'funnel_analytics' => [
779 + 'total_orders' => 0,
780 + 'total_revenue' => 0,
781 + 'aov' => 0,
782 + ],
783 + ];
784 +
785 + if (!$funnel_id) {
786 + return $this->prepare_item_for_response($response, $request);
787 + }
788 +
789 + $cache_key = 'wpfnl_funnel_stats_' . $funnel_id;
790 + $cached = get_transient($cache_key);
791 +
792 + if (false !== $cached) {
793 + return $this->prepare_item_for_response($cached, $request);
794 + }
795 +
796 + $steps = get_post_meta($funnel_id, '_steps', true);
797 + $steps = is_array($steps) ? $steps : [];
798 + $step_ids = array_map(
799 + function ($step) {
800 + return isset($step['id']) ? $step['id'] : null;
801 + },
802 + $steps
803 + );
804 +
805 + $response['success'] = true;
806 + $response['steps'] = self::get_steps_analytics_map($step_ids);
807 + $response['funnel_analytics'] = self::get_funnel_analytics($funnel_id);
808 +
809 + // Cached so repeated canvas opens don't recompute the aggregates. Kept
810 + // short enough that the numbers still track recent activity.
811 + set_transient($cache_key, $response, 10 * MINUTE_IN_SECONDS);
812 +
813 + return $this->prepare_item_for_response($response, $request);
814 + }
815 +
816 +
817 + /**
818 + * Prepares the response data for the funnel data based on the provided parameters.
819 + *
820 + * @param int $funnel_id The ID of the funnel.
821 + * @param mixed $request The request data.
822 + * @return array The prepared response data.
823 + *
824 + * @since 2.7.17
825 + */
826 +
827 + /**
828 + * Restore node_identifier precision from the html field.
829 + *
830 + * PHP json_encode truncates floats, so node_identifier stored as a number loses digits.
831 + * The html field is a string (e.g. "addstep311.7949003047232") and preserves full precision.
832 + * This method extracts node_identifier from html so the API response always matches what JS stored.
833 + *
834 + * @param array $funnel_data
835 + * @return array
836 + */
837 + private function fix_node_identifier_precision($funnel_data)
838 + {
839 + if (!isset($funnel_data['drawflow']['Home']['data']) || !is_array($funnel_data['drawflow']['Home']['data'])) {
840 + return $funnel_data;
841 + }
842 + foreach ($funnel_data['drawflow']['Home']['data'] as &$node) {
843 + if (!isset($node['html'], $node['data']['node_identifier'])) {
844 + continue;
845 + }
846 + $html = $node['html'];
847 + if (strpos($html, 'addstep') === 0) {
848 + $node['data']['node_identifier'] = substr($html, 7);
849 + } elseif (strpos($html, 'conditional') === 0) {
850 + $node['data']['node_identifier'] = substr($html, 11);
851 + }
852 + }
853 + unset($node);
854 + return $funnel_data;
855 + }
856 +
857 + private function prepare_funnel_data_response($funnel_id)
858 + {
859 + $response = [
860 + 'status' => 'error',
501 861 'success' => false
502 862 ];
503 863
504 - $funnel_data = get_post_meta($funnel_id, 'funnel_data', true);
864 + $is_new_ui_data = get_post_meta($funnel_id, 'wpfnls_is_newui_migrated', true);
865 + $funnel_data = 'yes' === $is_new_ui_data ? get_post_meta($funnel_id, '_funnel_data', true) : get_post_meta($funnel_id, 'funnel_data', true);
505 866 $funnel_identifier = get_post_meta($funnel_id, 'funnel_identifier', true);
506 - $_steps_order = get_post_meta( $funnel_id, '_steps', true );
507 - $_steps_order = is_array( $_steps_order ) ? $_steps_order : [] ;
508 - $status = get_post_status($funnel_id);
509 - $title = html_entity_decode(get_the_title($funnel_id));
510 - $first_step_id = Wpfnl_functions::get_first_step($funnel_id);
867 + $_steps_order = get_post_meta($funnel_id, '_steps', true);
868 + $_steps_order = is_array($_steps_order) ? $_steps_order : [];
869 + $status = get_post_status($funnel_id);
870 + $title = html_entity_decode(get_the_title($funnel_id));
871 + $first_step_id = Wpfnl_functions::get_first_step($funnel_id);
511 872
873 + // Notice: We recently updated the architecture of our funnel template showcase site to version 3.1.3.
874 + // If user's site's template data isn't synchronized with the latest data from our showcase site,
875 + // user may encounter issues importing templates. Unfortunately, this situation is unavoidable.
876 + // To ensure a smooth experience, we'll display a notice if user's site's template data needs updating.
877 + $is_imported_funnel = get_post_meta($funnel_id, '_is_imported', true);
878 + $show_template_sync_notice = false;
879 + if ('yes' === $is_new_ui_data && 'yes' === $is_imported_funnel && !$funnel_data) {
880 + $show_template_sync_notice = true;
881 + }
882 +
512 883 /**
513 884 * Fires to modify funnel view link for A/B testing
514 885 *
515 886 * @param string $link
516 - * @param int $step_id
517 - * @param int $funnel_id
887 + * @param int $step_id
888 + * @param int $funnel_id
518 889 *
519 890 * @return string $link
520 891 * @since 1.7.8
521 892 */
522 - $link = apply_filters('wpfunnels/modify_funnel_view_link', get_post_permalink($first_step_id), $first_step_id, $funnel_id);
893 + $link = apply_filters('wpfunnels/modify_funnel_view_link', get_post_permalink($first_step_id), $first_step_id, $funnel_id);
523 894 $response['title'] = $title;
524 895
525 - $utm_settings = Wpfnl_functions::get_funnel_utm_settings( $funnel_id );
526 - $view_link = !empty( $response[ 'link' ] ) ? $response[ 'link' ] : $link;
896 + $utm_settings = Wpfnl_functions::get_funnel_utm_settings($funnel_id);
897 + $view_link = !empty($response['link']) ? $response['link'] : $link;
527 898
528 - if ( $utm_settings[ 'utm_enable' ] == 'on' ) {
529 - unset( $utm_settings[ 'utm_enable' ] );
530 - $view_link = add_query_arg( $utm_settings, $view_link );
531 - $view_link = strtolower( $view_link );
532 - $utm_settings[ 'utm_enable' ] = 'on';
899 + if ($utm_settings['utm_enable'] == 'on') {
900 + unset($utm_settings['utm_enable']);
901 + $view_link = add_query_arg($utm_settings, $view_link);
902 + $view_link = strtolower($view_link);
903 + $utm_settings['utm_enable'] = 'on';
533 904 }
534 905
535 - $response['success'] = true;
536 - $step_order_data = self::get_steps_order_data($_steps_order);
537 - $response['steps_order'] = $step_order_data['steps_order'];
538 - $response['is_ob'] = $step_order_data['is_ob'];
906 + $response['success'] = true;
907 + $step_order_data = self::get_steps_order_data($_steps_order);
908 + $response['steps_order'] = isset( $step_order_data['steps_order'] ) ? $step_order_data['steps_order'] : [];
909 + $response['is_ob'] = isset( $step_order_data['is_ob'] ) ? $step_order_data['is_ob'] : false;
910 + $response['ob_steps'] = isset( $step_order_data['ob_steps'] ) ? $step_order_data['ob_steps'] : [];
539 911
540 - if ( $funnel_data ) {
912 + $init_position = [
913 + 'pos_x' => 383,
914 + 'pos_y' => 143
915 + ];
916 +
917 + $funnel_data = Wpfnl_functions::remove_disconnected_addstep_node($funnel_data, $funnel_id);
918 + $funnel_data = $this->fix_node_identifier_precision($funnel_data);
919 +
920 + if ($funnel_data) {
541 921 $response = [
542 - 'status' => 'success',
543 - 'funnel_data' => $funnel_data,
922 + 'status' => 'success',
923 + 'funnel_data' => $funnel_data,
544 924 'funnel_identifier' => $funnel_identifier,
545 - 'steps_order' => isset ( $response['steps_order'] ) ? $response['steps_order'] : [],
546 - 'funnel_status' => $status,
547 - 'title' => $title,
548 - 'link' => $view_link,
549 - 'is_ob' => isset ( $response['is_ob'] ) ? $response['is_ob'] : false
925 + 'steps_order' => isset ($response['steps_order']) ? $response['steps_order'] : [],
926 + 'funnel_status' => $status,
927 + 'title' => $title,
928 + 'link' => $view_link,
929 + 'reset_funnel' => get_post_meta($funnel_id, '_wpfnl_is_reset', true),
930 + 'is_ob' => isset ($response['is_ob']) ? $response['is_ob'] : false,
931 + 'ob_steps' => isset ($response['ob_steps']) ? $response['ob_steps'] : []
550 932 ];
551 933
934 + if (empty($funnel_data['drawflow']['Home']['data'])) {
935 + $data = $this->get_default_add_step_node_data($init_position);
936 + $response['status'] = 'scratch-funnel';
937 + $response['funnel_data'] = $data['data'];
938 + $response['node_identifier'] = $data['node_identifier'];
939 + }
940 +
941 + $response['funnel_data'] = $this->update_step_id_in_funnel_data_and_identifier($response['funnel_data'], $utm_settings);
942 +
552 943 /**
553 944 * Fires to update funnel data response in case of A/B Testing
554 945 *
555 946 * @param array $response
556 947 *
557 - * @since 1.7.8
558 948 * @return array $response
559 - **/
949 + **@since 1.7.8
950 + */
560 951 $response = apply_filters('wpfunnels/update_funnel_data_response', $response);
561 -
562 - if ( !empty( $response[ 'ab_data' ] ) ) {
563 - $response[ 'ab_data' ] = $this->update_step_view_link_with_utm_params( $response[ 'ab_data' ], $utm_settings );
952 + if (!empty($response['ab_data'])) {
953 + $response['ab_data'] = $this->update_step_view_link_with_utm_params($response['ab_data'], $utm_settings);
564 954 }
565 955 } elseif ($title && $status) {
566 - $response = [
567 - 'status' => 'scratch-funnel',
568 - 'title' => $title,
956 +
957 + $data = $this->get_default_add_step_node_data($init_position);
958 + $response = array(
959 + 'status' => 'scratch-funnel',
960 + 'title' => $title,
569 961 'funnel_status' => $status,
570 - ];
962 + 'funnel_data' => $data['data'],
963 + 'reset_funnel' => get_post_meta($funnel_id, '_wpfnl_is_reset', true),
964 + 'node_identifier' => $data['node_identifier'],
965 + 'show_template_sync_notice' => $show_template_sync_notice,
966 + );
571 967 }
572 968
969 +
970 + $response['first_node_id'] = $this->get_first_step_node($first_step_id, $response['funnel_data']);
971 + $response['conditional_data'] = $this->get_conditional_step($response['funnel_data']);
972 +
973 + // Visit/conversion figures and funnel totals are intentionally absent
974 + // here — the canvas fetches them separately via getFunnelStats so that
975 + // opening a funnel never waits on analytics. See get_funnel_stats().
573 976 return $response;
574 977 }
575 978
979 +
576 980 /**
981 + * Update funnel identifier
982 + *
983 + * @param $remote_step
984 + * @param $new_step
985 + * @param $args
986 + *
987 + * @return void
988 + */
989 + public function update_step_id_in_funnel_data_and_identifier($funnel_flow_array, $utm_settings)
990 + {
991 +
992 + if (!isset($funnel_flow_array['drawflow']['Home']['data']) || !is_array($funnel_flow_array['drawflow']['Home']['data'])) {
993 + return $funnel_flow_array;
994 + }
995 + $isUtm = false;
996 + if (isset($utm_settings['utm_enable']) && $utm_settings['utm_enable'] == 'on') {
997 + $isUtm = true;
998 + unset($utm_settings['utm_enable']);
999 + }
1000 +
1001 + // Loop through each step in funnel
1002 + foreach ($funnel_flow_array['drawflow']['Home']['data'] as &$step) {
1003 +
1004 + // Find corresponding step in step id data
1005 + if ('conditional' !== $step['data']['step_type'] && 'addstep' !== $step['data']['step_type'] ) {
1006 +
1007 + $step_view_link = get_post_permalink($step['data']['step_id']);
1008 + $step_view_link = $isUtm ? add_query_arg($utm_settings, $step_view_link) : $step_view_link;
1009 + // Check if step id is not in used_step_types array
1010 + $post_edit_link = base64_encode(get_edit_post_link($step['data']['step_id']));
1011 + $post_view_link = base64_encode($step_view_link);
1012 +
1013 + $step['data']['step_edit_link'] = $post_edit_link;
1014 + $step['data']['step_view_link'] = $post_view_link;
1015 + }
1016 + }
1017 + return $funnel_flow_array;
1018 + }
1019 +
1020 +
1021 + /**
1022 + * get conditional steps with validity checking
1023 + *
1024 + * @param $funnel_flow_array
1025 + *
1026 + * @return array
1027 + *
1028 + * @since 3.4.13
1029 + */
1030 + public function get_conditional_step($funnel_flow_array){
1031 + if (!isset($funnel_flow_array['drawflow']['Home']['data']) || !is_array($funnel_flow_array['drawflow']['Home']['data'])) {
1032 + return [];
1033 + }
1034 +
1035 + $condition_data = [];
1036 + foreach ($funnel_flow_array['drawflow']['Home']['data'] as &$step) {
1037 +
1038 + // Find corresponding step in step id data
1039 + if ('conditional' !== $step['data']['step_type'] && 'addstep' !== $step['data']['step_type'] ) {
1040 + $step_id = $step['data']['step_id'];
1041 + $is_condition_enabled = get_post_meta($step_id, '_wpfnl_maybe_enable_condition', true);
1042 + if( 'yes' === $is_condition_enabled ){
1043 + $data = [
1044 + 'step_id' => $step_id,
1045 + 'data' => 'no'
1046 + ];
1047 + $conditions = get_post_meta($step_id, '_wpfnl_step_conditions', true);
1048 + if( is_array($conditions) && !empty($conditions) ){
1049 + $data['data'] = 'yes';
1050 + }
1051 + array_push($condition_data, $data);
1052 + }
1053 + }
1054 + }
1055 + return $condition_data;
1056 + }
1057 +
1058 + /**
577 1059 * Updates A/B test variations' step view links with UTM parameters.
578 1060 *
579 1061 * This method takes A/B test data, specifically variations, and appends UTM parameters
580 1062 * to the step view links of each variation. It does so based on the provided UTM settings
@@ -585,17 +1067,18 @@
585 1067 *
586 1068 * @return array The modified A/B test data with UTM parameters appended to step view links.
587 1069 * @since 2.8.14
588 1070 */
589 - private function update_step_view_link_with_utm_params( $ab_data, $utm_settings ) {
590 - if ( !empty( $ab_data ) && !empty( $utm_settings[ 'utm_enable' ] ) && 'on' === $utm_settings[ 'utm_enable' ] ) {
591 - unset( $utm_settings[ 'utm_enable' ] );
592 - foreach( $ab_data as $ab_key => $data ) {
593 - $variations = $data[ 'data' ][ 'start_settings' ][ 'variations' ] ?? [];
594 - if ( !empty( $variations ) ) {
595 - foreach( $variations as $var_key => $variation ) {
596 - if ( !empty( $variation[ 'step_view_link' ] ) ) {
597 - $ab_data[ $ab_key ][ 'data' ][ 'start_settings' ][ 'variations' ][ $var_key ][ 'step_view_link' ] = add_query_arg( $utm_settings, $variation[ 'step_view_link' ] );
1071 + private function update_step_view_link_with_utm_params($ab_data, $utm_settings)
1072 + {
1073 + if (!empty($ab_data) && !empty($utm_settings['utm_enable']) && 'on' === $utm_settings['utm_enable']) {
1074 + unset($utm_settings['utm_enable']);
1075 + foreach ($ab_data as $ab_key => $data) {
1076 + $variations = $data['data']['start_settings']['variations'] ?? [];
1077 + if (!empty($variations)) {
1078 + foreach ($variations as $var_key => $variation) {
1079 + if (!empty($variation['step_view_link'])) {
1080 + $ab_data[$ab_key]['data']['start_settings']['variations'][$var_key]['step_view_link'] = add_query_arg($utm_settings, $variation['step_view_link']);
598 1081 }
599 1082 }
600 1083 }
601 1084 }
@@ -602,119 +1085,356 @@
602 1085 }
603 1086 return $ab_data;
604 1087 }
605 1088
606 - /**
607 - * Get formatted steps order data.
608 - *
609 - * Formats the steps order data by populating additional fields and determining if any step is an order bump.
610 - *
611 - * @param array $steps_order The array of funnel steps order.
612 - * @return array Formatted steps order data with additional fields and order bump flag.
613 - *
614 - * @since 2.7.17
615 - */
616 - private static function get_steps_order_data( $steps_order ) {
617 - $is_order_bump = false;
618 - $formatted_steps_order = [];
619 - foreach ($steps_order as $step) {
620 - $step_id = isset( $step['id'] ) ? $step['id'] : null;
621 - $_temp_step = $step;
622 - $_temp_step['visit'] = 0;
623 - $_temp_step['conversion'] = 0;
624 - $_temp_step['name'] = get_the_title( $step_id );
625 - $_step_type = get_post_meta( $step_id, '_step_type', true );
626 - $should_assign_product = in_array($_step_type, ['checkout', 'upsell', 'downsell']) && !get_post_meta( $step_id, '_wpfnl_checkout_products', true);
1089 + /**
1090 + * Get the first step Node
1091 + */
1092 + public function get_first_step_node($first_step_id, $funnel_data)
1093 + {
1094 + $data = isset($funnel_data['drawflow']['Home']['data']) ? $funnel_data['drawflow']['Home']['data'] : [];
1095 + foreach ($data as $step_data) {
1096 + if (isset($step_data['data']['step_id']) && $step_data['data']['step_id'] == $first_step_id) {
1097 + return $step_data['id'];
1098 + }
1099 + }
1100 + }
627 1101
628 - if ( 'checkout' === $_step_type ) {
629 - $is_order_bump = self::is_order_bump( $step_id );
630 - }
631 1102
632 - $_temp_step['should_assign_product'] = $should_assign_product;
633 - /**
634 - * Fires to add Mail Mint automation data
635 - *
636 - * @param array $step
637 - * @param int $step_id
638 - * @since 2.7.0
639 - */
640 - $formatted_steps_order[] = apply_filters('wpfunnels/step_data', $_temp_step, $step_id);
641 - }
1103 + /**
1104 + * Get default add step node data and identifire for canvas
1105 + *
1106 + * @return Array Returns an array containing the default add step node data and identifier
1107 + * @since 2.8.0
1108 + *
1109 + */
1110 + public function get_default_add_step_node_data($init_position)
1111 + {
1112 + $node_identifier = rand() * (500 - 100) + 100;
1113 + $data = [
1114 + 'drawflow' => [
1115 + 'Home' => [
1116 + 'data' => [
1117 + 1 => [
1118 + 'id' => 1,
1119 + 'name' => 'addstep',
1120 + 'data' => [
1121 + 'step_type' => 'addstep',
1122 + 'node_identifier' => $node_identifier
1123 + ],
1124 + 'class' => 'addstep',
1125 + 'html' => 'addstep' . $node_identifier,
1126 + 'typenode' => 'vue',
1127 + 'inputs' => [
1128 + 'input_1' => [
1129 + 'connections' => []
1130 + ]
1131 + ],
1132 + 'outputs' => [],
1133 + 'pos_x' => isset($init_position['pos_x']) ? $init_position['pos_x'] : 383,
1134 + 'pos_y' => isset($init_position['pos_y']) ? $init_position['pos_y'] : 143,
1135 + ]
1136 + ]
1137 + ]
1138 + ]
1139 + ];
1140 + return [
1141 + 'data' => $data,
1142 + 'node_identifier' => $node_identifier,
1143 + ];
1144 + }
642 1145
643 - return [
644 - 'steps_order' => $formatted_steps_order,
645 - 'is_ob' => $is_order_bump
646 - ];
647 - }
1146 + /**
1147 + * Get formatted steps order data.
1148 + *
1149 + * Formats the steps order data by populating additional fields and determining if any step is an order bump.
1150 + *
1151 + * @param array $steps_order The array of funnel steps order.
1152 + * @return array Formatted steps order data with additional fields and order bump flag.
1153 + *
1154 + * @since 2.7.17
1155 + */
1156 + private static function get_steps_order_data($steps_order)
1157 + {
1158 + $is_order_bump = false;
1159 + $ob_steps = [];
1160 + $formatted_steps_order = [];
648 1161
649 - /**
650 - * Checks if a step in the funnel is an order bump.
651 - * Retrieves the order bump settings for the specified step and determines if it is an order bump.
652 - * @param array $steps_order The array of funnel steps.
653 - * @param int $step_id The ID of the step to check.
654 - *
655 - *
656 - * @return bool True if the step is an order bump, false otherwise.
657 - * @since 2.7.17
658 - *
659 - */
660 - public static function is_order_bump( $step_id) {
661 - $all_settings = get_post_meta($step_id, 'order-bump-settings', true) ?: [];
662 - $is_multiple = Wpfnl_functions::check_array_is_multidimentional($all_settings);
1162 + foreach ($steps_order as $step) {
1163 + $step_id = isset($step['id']) ? $step['id'] : null;
1164 + $_temp_step = $step;
663 1165
664 - if (!$is_multiple && $all_settings) {
665 - $all_settings['name'] = 'Order bump';
666 - $all_settings = Wpfnl_functions::migrate_order_bump($all_settings, $step_id);
667 - }
1166 + // Null rather than 0 so the canvas can tell "not loaded yet" apart
1167 + // from a genuine zero and show a placeholder until getFunnelStats
1168 + // responds.
1169 + $_temp_step['visit'] = null;
1170 + $_temp_step['conversion'] = null;
1171 + $_temp_step['name'] = get_the_title($step_id);
1172 + $_step_type = get_post_meta($step_id, '_step_type', true);
1173 + $should_assign_product = in_array($_step_type, ['checkout', 'upsell', 'downsell']) && !get_post_meta($step_id, '_wpfnl_' . $_step_type . '_products', true);
668 1174
669 - if (is_array($all_settings) && count($all_settings) > 0) {
670 - $funnel_id = get_post_meta($step_id, '_funnel_id', true);
671 - $type = get_post_meta($funnel_id, '_wpfnl_funnel_type', true) ?: 'wc';
672 - $class_object = Wpfnl_Controller_Type_Factory::build($type);
673 - if ($class_object) {
674 - $all_settings = $class_object->get_ob_settings($all_settings);
675 - }
676 - }
677 -
1175 + if ('checkout' === $_step_type) {
1176 + $step_has_ob = self::is_order_bump($step_id);
1177 + if ($step_has_ob) {
1178 + $is_order_bump = true;
1179 + $ob_steps[] = (int) $step_id;
1180 + }
1181 + }
678 1182
679 - if (count($all_settings)) {
680 - return true;
681 - }
682 - return false;
683 - }
1183 + $_temp_step['should_assign_product'] = $should_assign_product;
684 1184
1185 + /**
1186 + * Fires to add Mail Mint automation data
1187 + *
1188 + * @param array $step
1189 + * @param int $step_id
1190 + * @since 2.7.0
1191 + */
1192 + $formatted_steps_order[] = apply_filters('wpfunnels/step_data', $_temp_step, $step_id);
1193 + }
1194 +
1195 + return [
1196 + 'steps_order' => $formatted_steps_order,
1197 + 'is_ob' => $is_order_bump,
1198 + 'ob_steps' => $ob_steps,
1199 + ];
1200 + }
1201 +
685 1202 /**
1203 + * Get visit and conversion analytics data for a set of steps in one batch.
1204 + *
1205 + * Replaces N per-step queries (previously run once per step in
1206 + * get_steps_order_data()) with two GROUP BY queries covering the whole
1207 + * funnel, avoiding an N+1 query pattern that caused the funnel canvas to
1208 + * time out on funnels with several steps. Count semantics are unchanged
1209 + * from the previous per-step queries — grouping by step_id and running
1210 + * the same WHERE/JOIN conditions together is equivalent to running them
1211 + * individually per step.
1212 + *
1213 + * @param array $step_ids The step IDs to fetch analytics for.
1214 + * @return array Map of step_id => ['visit' => int, 'conversion' => int].
1215 + *
1216 + * @since 3.12.12
1217 + */
1218 + private static function get_steps_analytics_map($step_ids)
1219 + {
1220 + $step_ids = array_values(array_unique(array_filter($step_ids)));
1221 +
1222 + $map = [];
1223 + foreach ($step_ids as $step_id) {
1224 + $map[$step_id] = array(
1225 + 'visit' => 0,
1226 + 'conversion' => 0,
1227 + );
1228 + }
1229 +
1230 + if (empty($step_ids)) {
1231 + return $map;
1232 + }
1233 +
1234 + global $wpdb;
1235 + $analytics_table = $wpdb->prefix . 'wpfnl_analytics';
1236 + $analytics_meta_table = $wpdb->prefix . 'wpfnl_analytics_meta';
1237 +
1238 + // Check if the analytics table exists before querying.
1239 + $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $analytics_table));
1240 + if (!$table_exists) {
1241 + return $map;
1242 + }
1243 +
1244 + $cache_key = 'wpfnl_pro_steps_analytics_' . md5(implode(',', $step_ids));
1245 + $cached_map = wp_cache_get($cache_key, 'wpfnl_pro');
1246 +
1247 + if (false !== $cached_map) {
1248 + return $cached_map;
1249 + }
1250 +
1251 + $placeholders = implode(', ', array_fill(0, count($step_ids), '%s'));
1252 +
1253 + // Get total visits, grouped per step.
1254 + $visits_query = $wpdb->prepare(
1255 + "SELECT wpft1.step_id, COUNT( DISTINCT( wpft1.id ) ) AS total_visits
1256 + FROM {$analytics_table} as wpft1
1257 + WHERE wpft1.step_id IN ({$placeholders})
1258 + GROUP BY wpft1.step_id
1259 + ORDER BY NULL",
1260 + $step_ids
1261 + ); // phpcs:ignore
1262 + $visits_rows = $wpdb->get_results($visits_query); // phpcs:ignore
1263 +
1264 + foreach ($visits_rows as $row) {
1265 + if (isset($map[$row->step_id])) {
1266 + $map[$row->step_id]['visit'] = $row->total_visits;
1267 + }
1268 + }
1269 +
1270 + // Get conversions, grouped per step.
1271 + //
1272 + // Read the meta table directly on its denormalised step_id rather than
1273 + // joining back through the analytics table. The join form had to walk
1274 + // every visit for the funnel (tens of thousands of rows) and look up
1275 + // each one's meta rows, which dominated the whole request on sites
1276 + // with real traffic volume. Filtering on step_id touches only the
1277 + // conversion rows themselves — a few thousand instead of ~150k.
1278 + //
1279 + // Counting meta rows keeps the original numbers intact, including the
1280 + // pre-existing behaviour where a visit carrying two 'conversion'='yes'
1281 + // meta rows counts twice.
1282 + $conversion_query = $wpdb->prepare(
1283 + "SELECT step_id, COUNT(*) AS conversions
1284 + FROM {$analytics_meta_table}
1285 + WHERE step_id IN ({$placeholders})
1286 + AND meta_key = 'conversion'
1287 + AND meta_value = 'yes'
1288 + GROUP BY step_id
1289 + ORDER BY NULL",
1290 + $step_ids
1291 + ); // phpcs:ignore
1292 + $conversion_rows = $wpdb->get_results($conversion_query); // phpcs:ignore
1293 +
1294 + foreach ($conversion_rows as $row) {
1295 + if (isset($map[$row->step_id])) {
1296 + $map[$row->step_id]['conversion'] = $row->conversions;
1297 + }
1298 + }
1299 +
1300 + wp_cache_set($cache_key, $map, 'wpfnl_pro', 3600);
1301 +
1302 + return $map;
1303 + }
1304 +
1305 + /**
1306 + * Get funnel-level analytics data (total orders, revenue, AOV, orderbump revenue).
1307 + *
1308 + * Queries the wpfnl_stats table for completed orders belonging to a specific funnel.
1309 + *
1310 + * @param int $funnel_id The funnel ID.
1311 + * @return array Analytics data with total_orders, total_revenue, aov, and orderbump_revenue.
1312 + *
1313 + * @since 3.5.0
1314 + */
1315 + private static function get_funnel_analytics($funnel_id)
1316 + {
1317 + $analytics = array(
1318 + 'total_orders' => 0,
1319 + 'total_revenue' => 0,
1320 + 'aov' => 0,
1321 + );
1322 +
1323 + if (!$funnel_id) {
1324 + return $analytics;
1325 + }
1326 +
1327 + global $wpdb;
1328 + $stats_table = $wpdb->prefix . 'wpfnl_stats';
1329 +
1330 + // Check if the stats table exists before querying.
1331 + $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $stats_table));
1332 + if (!$table_exists) {
1333 + return $analytics;
1334 + }
1335 +
1336 + $cache_key = 'wpfnl_funnel_analytics_' . $funnel_id;
1337 + $cached_data = wp_cache_get($cache_key, 'wpfnl_pro');
1338 +
1339 + if (false !== $cached_data) {
1340 + return $cached_data;
1341 + }
1342 +
1343 + $row = $wpdb->get_row($wpdb->prepare(
1344 + "SELECT
1345 + COUNT(DISTINCT id) AS total_orders,
1346 + COALESCE(SUM(total_sales), 0) AS total_revenue
1347 + FROM {$stats_table}
1348 + WHERE funnel_id = %d AND status IN (%s, %s)",
1349 + $funnel_id,
1350 + 'completed',
1351 + 'processing'
1352 + )); // phpcs:ignore
1353 +
1354 + if ($row) {
1355 + $analytics['total_orders'] = (int) $row->total_orders;
1356 + $analytics['total_revenue'] = round((float) $row->total_revenue, 2);
1357 + $analytics['aov'] = $analytics['total_orders'] > 0
1358 + ? round($analytics['total_revenue'] / $analytics['total_orders'], 2)
1359 + : 0;
1360 + }
1361 +
1362 + wp_cache_set($cache_key, $analytics, 'wpfnl_pro', 3600);
1363 +
1364 + return $analytics;
1365 + }
1366 +
1367 + /**
1368 + * Checks if a step in the funnel is an order bump.
1369 + * Retrieves the order bump settings for the specified step and determines if it is an order bump.
1370 + * @param array $steps_order The array of funnel steps.
1371 + * @param int $step_id The ID of the step to check.
1372 + *
1373 + *
1374 + * @return bool True if the step is an order bump, false otherwise.
1375 + * @since 2.7.17
1376 + *
1377 + */
1378 + public static function is_order_bump($step_id)
1379 + {
1380 + $all_settings = get_post_meta($step_id, 'order-bump-settings', true) ?: [];
1381 + $is_multiple = Wpfnl_functions::check_array_is_multidimentional($all_settings);
1382 +
1383 + if (!$is_multiple && $all_settings) {
1384 + $all_settings['name'] = 'Order bump';
1385 + $all_settings = Wpfnl_functions::migrate_order_bump($all_settings, $step_id);
1386 + }
1387 +
1388 + if (is_array($all_settings) && count($all_settings) > 0) {
1389 + $funnel_id = get_post_meta($step_id, '_funnel_id', true);
1390 + $type = get_post_meta($funnel_id, '_wpfnl_funnel_type', true) ?: 'wc';
1391 + $class_object = Wpfnl_Controller_Type_Factory::build($type);
1392 + if ($class_object) {
1393 + $all_settings = $class_object->get_ob_settings($all_settings);
1394 + }
1395 + }
1396 +
1397 +
1398 + if (count($all_settings)) {
1399 + return true;
1400 + }
1401 + return false;
1402 + }
1403 +
1404 + /**
686 1405 * Get formatted funnel data
687 1406 *
688 1407 * @param $drawflow
689 - *
1408 + *
690 1409 * @return mixed
691 1410 *
692 1411 * @since 2.0.5
693 1412 */
694 - private function get_formatted_funnel_data( $drawflow ) {
695 - if( isset( $drawflow['drawflow']['Home']['data'] ) ) {
1413 + private function get_formatted_funnel_data($drawflow)
1414 + {
1415 + if (isset($drawflow['drawflow']['Home']['data'])) {
696 1416 $drawflow_data = $drawflow['drawflow']['Home']['data'];
697 - foreach ( $drawflow_data as $key => $data ) {
698 - $step_data = $data['data'];
699 - $step_type = $step_data['step_type'];
700 - if('conditional' !== $step_type) {
701 - $step_id = $step_data['step_id'];
702 - $funnel_id = Wpfnl_functions::get_funnel_id_from_step($step_id);
703 - if( 'conditional' !== $step_type ) {
704 - $edit_post_link = get_edit_post_link( $step_id );
705 - $view_link = get_the_permalink( $step_id );
706 - $utm_params = $this->get_utm_params( $funnel_id );
707 - if($utm_params != '') {
708 - $view_link = $view_link.$utm_params;
709 - }
710 - $title = get_the_title( $step_id );
1417 + foreach ($drawflow_data as $key => $data) {
1418 + $step_data = $data['data'];
1419 + $step_type = $step_data['step_type'];
1420 + if ('conditional' !== $step_type) {
1421 + $step_id = $step_data['step_id'];
1422 + $funnel_id = Wpfnl_functions::get_funnel_id_from_step($step_id);
1423 + if ('conditional' !== $step_type) {
1424 + $edit_post_link = get_edit_post_link($step_id);
1425 + $view_link = get_the_permalink($step_id);
1426 + $utm_params = $this->get_utm_params($funnel_id);
1427 + if ($utm_params != '') {
1428 + $view_link = $view_link . $utm_params;
1429 + }
1430 + $title = get_the_title($step_id);
711 1431 $drawflow['drawflow']['Home']['data'][$key]['data'] = array(
712 - 'step_edit_link' => base64_encode( $edit_post_link ),
713 - 'step_id' => $step_id,
714 - 'step_type' => $step_data['step_type'],
715 - 'step_view_link' => base64_encode( rtrim( $view_link, '/' ) ),
716 - 'step_name' => $title,
1432 + 'step_edit_link' => base64_encode($edit_post_link),
1433 + 'step_id' => $step_id,
1434 + 'step_type' => $step_data['step_type'],
1435 + 'step_view_link' => base64_encode(rtrim($view_link, '/')),
1436 + 'step_name' => $title,
717 1437 );
718 1438 }
719 1439 }
720 1440 }
@@ -721,134 +1441,158 @@
721 1441 }
722 1442 return $drawflow;
723 1443 }
724 1444
725 - /**
726 - * Save funnel data.
727 - *
728 - * @param string $request request.
729 - *
730 - * @return array|WP_Error
731 - */
732 - public function save_funnel_data($request)
733 - {
734 - $funnel_id = $request[ 'funnel_id' ] ?? null;
735 - $funnel_json = $request[ 'funnel_data' ] ?? [];
736 - $funnel_identifier = $request[ 'funnel_identifier' ] ?? [];
737 - $should_update_steps_order = $request[ 'should_update_steps_order' ] ?? false;
738 - $should_update_steps = $request[ 'should_update_steps' ] ?? false;
739 - $funnel_data = [];
740 - $_steps = [];
741 - $response = [ 'success' => true, 'link' => home_url() ];
742 -
743 - if ($funnel_json) {
744 - $funnel_data = $funnel_json;
745 - $steps = $funnel_data['drawflow']['Home']['data'];
746 - foreach ($steps as $key => $step) {
747 - if ($step) {
748 - $node_data = $step['data'];
749 - if (isset($node_data["step_name"])) unset($node_data["step_name"]);
750 - $step['data'] = $node_data;
751 - $_steps[$key] = $step;
752 - }
753 - }
754 - }
755 - $funnel_data['drawflow']['Home']['data'] = apply_filters('wpfunnels/modify_funnel_data', $_steps);
756 - update_post_meta($funnel_id, 'funnel_data', $funnel_data);
757 - update_post_meta($funnel_id, 'funnel_identifier', $funnel_identifier);
1445 + /**
1446 + * Save funnel data.
1447 + *
1448 + * @param string $request request.
1449 + *
1450 + * @return array|WP_Error
1451 + */
1452 + public function save_funnel_data($request)
1453 + {
1454 + $funnel_id = $request['funnel_id'] ?? null;
1455 + $funnel_json = $request['funnel_data'] ?? [];
1456 + $funnel_identifier = $request['funnel_identifier'] ?? [];
1457 + $should_update_steps_order = $request['should_update_steps_order'] ?? false;
1458 + $should_update_steps = $request['should_update_steps'] ?? false;
1459 + $funnel_data = [];
1460 + $_steps = [];
1461 + $response = ['success' => true, 'link' => home_url()];
758 1462
759 - if( $should_update_steps ) {
760 - $steps = $this->get_steps( $funnel_data );
761 - update_post_meta( $funnel_id, '_steps', $steps );
762 - Wpfnl_functions::generate_first_step( $funnel_id, $steps );
1463 + if ($funnel_json) {
1464 + $funnel_data = $funnel_json;
1465 + $steps = $funnel_data['drawflow']['Home']['data'];
1466 +
1467 + if (is_array($steps)) {
1468 + foreach ($steps as $key => $step) {
1469 + if ($step) {
1470 + $node_data = $step['data'];
1471 + if (isset($node_data["step_name"])) unset($node_data["step_name"]);
1472 +
1473 + // Preserve node_identifier at full precision by extracting it from the html
1474 + // field (a string) rather than using the float value decoded by json_decode,
1475 + // which PHP truncates before we can read it.
1476 + if (isset($step['html'], $node_data['node_identifier'])) {
1477 + $html = $step['html'];
1478 + if (strpos($html, 'addstep') === 0) {
1479 + $node_data['node_identifier'] = substr($html, 7);
1480 + } elseif (strpos($html, 'conditional') === 0) {
1481 + $node_data['node_identifier'] = substr($html, 11);
1482 + }
1483 + }
1484 +
1485 + $step['data'] = $node_data;
1486 + $_steps[$key] = $step;
1487 +
1488 + }
1489 + }
1490 + }
1491 +
763 1492 }
1493 + $funnel_data['drawflow']['Home']['data'] = apply_filters('wpfunnels/modify_funnel_data', $_steps);
1494 + update_post_meta($funnel_id, '_funnel_data', $funnel_data);
1495 + update_post_meta($funnel_id, 'funnel_identifier', $funnel_identifier);
764 1496
765 - $_steps_order = $this->get_steps_order( $funnel_data );
766 - $key = array_search('checkout', array_column($_steps_order, 'step_type'));
767 - if( false !== $key ){
768 - $funnel_type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true );
769 - if( 'lead' === $funnel_type ){
770 - if( Wpfnl_functions::is_wc_active() ){
771 - update_post_meta( $funnel_id, '_wpfnl_funnel_type', 'wc' );
772 - }else{
773 - if( Wpfnl_functions::is_lms_addon_active() ){
774 - update_post_meta( $funnel_id, '_wpfnl_funnel_type', 'lms' );
775 - }
776 - }
777 - }
778 - }
779 - if( $should_update_steps_order ) {
780 - $steps_order = array();
781 - foreach ($_steps_order as $_step){
782 - if(count($_step)) {
1497 + if ($should_update_steps) {
1498 + $steps = $this->get_steps($funnel_data);
1499 + update_post_meta($funnel_id, '_steps', $steps);
1500 + Wpfnl_functions::generate_first_step($funnel_id, $steps);
1501 + }
1502 +
1503 + $_steps_order = $this->get_steps_order($funnel_data);
1504 + $key = array_search('checkout', array_column($_steps_order, 'step_type'));
1505 + if (false !== $key) {
1506 + $funnel_type = get_post_meta($funnel_id, '_wpfnl_funnel_type', true);
1507 + if ('lead' === $funnel_type) {
1508 + if (Wpfnl_functions::is_wc_active()) {
1509 + update_post_meta($funnel_id, '_wpfnl_funnel_type', 'wc');
1510 + } else {
1511 + // Check if LMS add-on is active AND at least one LMS plugin (LearnDash or CreatorLMS) is active
1512 + if (Wpfnl_functions::is_lms_addon_active() && Wpfnl_functions::is_any_lms_plugin_active()) {
1513 + update_post_meta($funnel_id, '_wpfnl_funnel_type', 'lms');
1514 + }
1515 + }
1516 + }
1517 + }
1518 + if ($should_update_steps_order) {
1519 + $steps_order = array();
1520 + foreach ($_steps_order as $_step) {
1521 + if (count($_step)) {
783 1522 $steps_order[] = $_step;
784 1523 }
785 1524 }
786 1525
787 - if(count($steps_order)) {
788 - update_post_meta( $funnel_id, '_steps_order', $steps_order );
789 -
1526 + if (count($steps_order)) {
1527 + update_post_meta($funnel_id, '_steps_order', $steps_order);
1528 +
790 1529 } else {
791 - delete_post_meta( $funnel_id, '_steps_order' );
1530 + delete_post_meta($funnel_id, '_steps_order');
792 1531 }
793 1532 }
794 1533
795 - $response['step_id'] = Wpfnl_functions::get_first_step( $funnel_id );
1534 + $response['step_id'] = Wpfnl_functions::get_first_step($funnel_id);
796 1535
797 - // Fallback for existing users' existing funnel
798 - // For new funnel, this condition should not trigger
799 - if( !$response['step_id'] ) {
800 - Wpfnl_functions::generate_first_step( $funnel_id );
801 - $response['step_id'] = Wpfnl_functions::get_first_step( $funnel_id );
802 - }
803 - $utm_settings = Wpfnl_functions::get_funnel_utm_settings( $funnel_id );
804 - $view_link = get_post_permalink( $response['step_id'] );
1536 + // Fallback for existing users' existing funnel
1537 + // For new funnel, this condition should not trigger
1538 + if (!$response['step_id']) {
1539 + Wpfnl_functions::generate_first_step($funnel_id);
1540 + $response['step_id'] = Wpfnl_functions::get_first_step($funnel_id);
1541 + }
805 1542
806 - if( !empty( $utm_settings['utm_enable'] ) && 'on' === $utm_settings['utm_enable'] ) {
807 - unset($utm_settings['utm_enable']);
808 - $view_link = add_query_arg($utm_settings,$view_link);
809 - $view_link = strtolower($view_link);
810 - }
1543 + $utm_settings = Wpfnl_functions::get_funnel_utm_settings($funnel_id);
1544 + $view_link = get_post_permalink($response['step_id']);
811 1545
812 - $response['link'] = esc_url( $view_link );
1546 + if (!empty($utm_settings['utm_enable']) && 'on' === $utm_settings['utm_enable']) {
1547 + unset($utm_settings['utm_enable']);
1548 + $view_link = add_query_arg($utm_settings, $view_link);
1549 + $view_link = strtolower($view_link);
1550 + }
813 1551
814 - $response['funnel_id'] = $funnel_id;
1552 + $response['link'] = esc_url($view_link);
1553 +
1554 + $response['funnel_id'] = $funnel_id;
815 1555 $response['success'] = true;
816 - $response['funnel_type'] = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true );
817 - $response = apply_filters( 'wpfunnels/update_funnel_link', $response );
818 - do_action( 'wpfunnels/after_save_funnel_data', $funnel_id );
819 - if( isset($request['mintSteps']) ){
820 - do_action( 'wpfunnels/save_mint_automation', $funnel_id, $request['mintSteps']);
821 - }
1556 + $response['funnel_type'] = get_post_meta($funnel_id, '_wpfnl_funnel_type', true);
1557 + $response = apply_filters('wpfunnels/update_funnel_link', $response);
1558 + do_action('wpfunnels/after_save_funnel_data', $funnel_id);
1559 + if (isset($request['mintSteps'])) {
1560 + do_action('wpfunnels/save_mint_automation', $funnel_id, $request['mintSteps']);
1561 + }
1562 + Wpfnl_functions::generate_first_step($funnel_id);
822 1563
823 - return rest_ensure_response($response);
824 - }
1564 + do_action( 'wpfunnels_canvas_saved', absint( $funnel_id ) );
825 1565
1566 + return rest_ensure_response($response);
1567 + }
826 1568
1569 +
827 1570 /**
828 1571 * Get steps
829 1572 *
830 1573 * @param $funnel_flow_data
831 - *
1574 + *
832 1575 * @return array
833 1576 *
834 1577 * @since 2.0.5
835 1578 */
836 - private function get_steps( $funnel_flow_data ) {
837 - $drawflow = $funnel_flow_data['drawflow'];
838 - $steps = array();
839 - if( isset( $drawflow['Home']['data'] ) ) {
1579 + private function get_steps($funnel_flow_data)
1580 + {
1581 + $drawflow = $funnel_flow_data['drawflow'];
1582 + $steps = array();
1583 + if (isset($drawflow['Home']['data'])) {
840 1584 $drawflow_data = $drawflow['Home']['data'];
841 - foreach ( $drawflow_data as $key => $data ) {
842 - $step_data = $data['data'];
843 - if( 'conditional' !== $step_data['step_type'] ) {
844 - $step_id = $step_data['step_id'];
845 - $step_type = $step_data['step_type'];
846 - $step_name = sanitize_text_field(get_the_title($step_data['step_id']));
847 - $steps[] = array(
848 - 'id' => $step_id,
849 - 'step_type' => $step_type,
850 - 'name' => $step_name,
1585 + foreach ($drawflow_data as $key => $data) {
1586 + $step_data = $data['data'];
1587 + if ('conditional' !== $step_data['step_type'] && 'addstep' !== $step_data['step_type']) {
1588 + $step_id = $step_data['step_id'];
1589 + $step_type = $step_data['step_type'];
1590 + $step_name = sanitize_text_field(get_the_title($step_data['step_id']));
1591 + $steps[] = array(
1592 + 'id' => $step_id,
1593 + 'step_type' => $step_type,
1594 + 'name' => $step_name,
851 1595 );
852 1596 }
853 1597 }
854 1598 }
@@ -859,22 +1603,23 @@
859 1603 /**
860 1604 * Get steps order
861 1605 *
862 1606 * @param $funnel_flow_data
863 - *
1607 + *
864 1608 * @return array
865 1609 *
866 1610 * @since 2.0.5
867 1611 */
868 - private function get_steps_order( $funnel_flow_data ) {
869 - $drawflow = $funnel_flow_data['drawflow'];
870 - $nodes = array();
871 - $step_order = array();
872 - $first_node_id = '';
873 - $start_node = array();
1612 + public function get_steps_order($funnel_flow_data)
1613 + {
1614 + $drawflow = $funnel_flow_data['drawflow'];
1615 + $nodes = array();
1616 + $step_order = array();
1617 + $first_node_id = '';
1618 + $start_node = array();
874 1619
875 1620
876 - if( isset( $drawflow['Home']['data'] ) ) {
1621 + if (isset($drawflow['Home']['data'])) {
877 1622 $drawflow_data = $drawflow['Home']['data'];
878 1623
879 1624 /**
880 1625 * If has only one step, that only step will be the first step, no conditions should be checked.
@@ -879,18 +1624,18 @@
879 1624 /**
880 1625 * If has only one step, that only step will be the first step, no conditions should be checked.
881 1626 * just return the step order
882 1627 */
883 - if( 1 === count( $drawflow_data ) ) {
884 - $node_id = array_keys($drawflow_data)[0];
885 - $data = $drawflow_data[$node_id];
886 - $step_data = isset( $data['data'] ) ? $data['data'] : array();
887 - $step_type = isset( $step_data['step_type'] ) ? $step_data['step_type'] : '';
888 - $step_id = isset( $step_data['step_id'] ) ? $step_data['step_id'] : 0;
889 - $step_order[] = array(
890 - 'id' => $step_id,
891 - 'step_type' => $step_type,
892 - 'name' => sanitize_text_field( get_the_title( $step_id ) ),
1628 + if (1 === count($drawflow_data)) {
1629 + $node_id = array_keys($drawflow_data)[0];
1630 + $data = $drawflow_data[$node_id];
1631 + $step_data = isset($data['data']) ? $data['data'] : array();
1632 + $step_type = isset($step_data['step_type']) ? $step_data['step_type'] : '';
1633 + $step_id = isset($step_data['step_id']) ? $step_data['step_id'] : 0;
1634 + $step_order[] = array(
1635 + 'id' => $step_id,
1636 + 'step_type' => $step_type,
1637 + 'name' => sanitize_text_field(get_the_title($step_id)),
893 1638 );
894 1639 return $step_order;
895 1640
896 1641 }
@@ -898,18 +1643,20 @@
898 1643 /**
899 1644 * First we will find the first node (the node which has only output connection but no input connection will be considered as first node) and the list of nodes array which has the
900 1645 * step information includes output connection and input connection and it will be stored on $nodes
901 1646 */
902 - foreach ( $drawflow_data as $key => $data ) {
903 - $step_data = $data['data'];
904 - $step_type = $step_data['step_type'];
905 - $step_id = $step_type !== 'conditional' ? $step_data['step_id'] : 0;
906 - if(
907 - (isset( $data['outputs']['output_1']['connections'] ) && count( $data['outputs']['output_1']['connections'] ) ) ||
908 - (isset( $data['inputs']['input_1']['connections'] ) && count($data['inputs']['input_1']['connections']) )
1647 + foreach ($drawflow_data as $key => $data) {
1648 + $step_data = $data['data'];
1649 +
1650 + $step_type = $step_data['step_type'];
1651 + $step_id = 'conditional' !== $step_type && 'addstep' !== $step_type ? $step_data['step_id'] : 0;
1652 + if (
1653 + (isset($data['outputs']['output_1']['connections']) && count($data['outputs']['output_1']['connections'])) ||
1654 + (isset($data['inputs']['input_1']['connections']) && count($data['inputs']['input_1']['connections']))
909 1655 ) {
910 1656
911 - if('conditional' === $step_type) {
1657 +
1658 + if ('conditional' === $step_type || 'addstep' === $step_type) {
912 1659 continue;
913 1660 }
914 1661
915 1662 /**
@@ -916,17 +1663,17 @@
916 1663 * A starting node is a node which has only output connection but not any input connection.
917 1664 * if the step is landing, then there should not be any input connection for this step. so we will only consider the output connection for landing only.
918 1665 * for other step types (checkout, offer, thankyou), we will check if the step has any output connection and no input connection.
919 1666 */
920 - if( 'landing' === $step_type ) {
1667 + if ('landing' === $step_type) {
921 1668 if (
922 1669 isset($data['outputs']['output_1']['connections']) && count($data['outputs']['output_1']['connections']) &&
923 - (isset( $data['inputs'] ) && count($data['inputs']) == 0 )
1670 + (isset($data['inputs']) && (count($data['inputs']) == 0 || (isset($data['inputs']['input_1']['connections']) && count($data['inputs']['input_1']['connections']) == 0)))
924 1671 ) {
925 - $start_node = array(
926 - 'id' => $step_id,
1672 + $start_node = array(
1673 + 'id' => $step_id,
927 1674 'step_type' => $step_type,
928 - 'name' => sanitize_text_field(get_the_title($step_id)),
1675 + 'name' => sanitize_text_field(get_the_title($step_id)),
929 1676 );
930 1677 }
931 1678 } else {
932 1679 if (
@@ -932,26 +1679,26 @@
932 1679 if (
933 1680 isset($data['outputs']['output_1']['connections']) && count($data['outputs']['output_1']['connections']) &&
934 1681 (isset($data['inputs']['input_1']['connections']) && count($data['inputs']['input_1']['connections']) === 0)
935 1682 ) {
936 - $start_node = array(
937 - 'id' => $step_id,
1683 + $start_node = array(
1684 + 'id' => $step_id,
938 1685 'step_type' => $step_type,
939 - 'name' => sanitize_text_field(get_the_title($step_id)),
1686 + 'name' => sanitize_text_field(get_the_title($step_id)),
940 1687 );
941 1688 } else {
942 1689 $step_order[] = array(
943 - 'id' => $step_id,
1690 + 'id' => $step_id,
944 1691 'step_type' => $step_type,
945 - 'name' => sanitize_text_field(get_the_title($step_id)),
1692 + 'name' => sanitize_text_field(get_the_title($step_id)),
946 1693 );
947 1694 }
948 1695 }
949 1696 }
950 1697 }
1698 +
951 1699 $step_order = $this->array_insert($step_order, $start_node, 0);
952 1700 }
953 -
954 1701 return $step_order;
955 1702 }
956 1703
957 1704
@@ -960,12 +1707,13 @@
960 1707 *
961 1708 * @param $original
962 1709 * @param $inserted
963 1710 * @param int $position
964 - *
1711 + *
965 1712 * @return mixed
966 1713 */
967 - private function array_insert(&$original, $inserted, $position) {
1714 + private function array_insert(&$original, $inserted, $position)
1715 + {
968 1716 array_splice($original, $position, 0, array($inserted));
969 1717 return $original;
970 1718 }
971 1719
@@ -973,180 +1721,185 @@
973 1721 /**
974 1722 * Export funnel data
975 1723 *
976 1724 * @param $request
977 - *
1725 + *
978 1726 * @return false|string
979 1727 *
980 1728 * @since 1.0.0
981 1729 */
982 - public function export_funnel($request)
983 - {
984 - $funnel_id = $request['funnel_id'];
1730 + public function export_funnel($request)
1731 + {
1732 + $funnel_id = $request['funnel_id'];
985 1733
986 - //===main array of data which will be downloaded as json file===//
987 - $data = array();
1734 + //===main array of data which will be downloaded as json file===//
1735 + $data = array();
988 1736
989 - $funnel_title = get_the_title($funnel_id);
990 - $funnel_meta = get_post_meta($funnel_id);
1737 + $funnel_title = get_the_title($funnel_id);
1738 + $funnel_meta = get_post_meta($funnel_id);
991 1739
992 - //=== Added title and meta of current funnel post===//
993 - $data['title'] = $funnel_title;
994 - $data['meta'] = $funnel_meta;
1740 + //=== Added title and meta of current funnel post===//
1741 + $data['title'] = $funnel_title;
1742 + $data['meta'] = $funnel_meta;
995 1743
996 - //=== Find list of steps and ther data===//
997 - //== Getting steps from identifier meta==//
998 - if (isset($data['meta']['funnel_identifier'])) {
999 - $identifier_meta = $data['meta']['funnel_identifier'];
1000 - $all_steps_array = array();
1001 - foreach ($identifier_meta as $identifier_meta_key => $identifier_meta_value) {
1002 - $node_step_pair = json_decode($identifier_meta_value, true);
1003 - foreach ($node_step_pair as $node_step_pair_key => $node_step_pair_value) {
1004 - $current_steps_array = array();
1005 - $step_id = $node_step_pair_value;
1006 - $step_title = get_the_title($step_id);
1007 - $step_meta = get_post_meta($step_id);
1008 - $content_post = get_post($step_id);
1009 - $content = json_encode($content_post->post_content);
1010 - $current_steps_array['step_id'] = $step_id;
1011 - $current_steps_array['title'] = $step_title;
1012 - $current_steps_array['meta'] = $step_meta;
1013 - $current_steps_array['content'] = $content;
1014 - $all_steps_array[] = $current_steps_array;
1015 - }
1016 - }
1017 - $data['steps'] = $all_steps_array;
1018 - }
1744 + //=== Find list of steps and ther data===//
1745 + //== Getting steps from identifier meta==//
1746 + if (isset($data['meta']['funnel_identifier'])) {
1747 + $identifier_meta = $data['meta']['funnel_identifier'];
1748 + $all_steps_array = array();
1749 + foreach ($identifier_meta as $identifier_meta_key => $identifier_meta_value) {
1750 + $node_step_pair = json_decode($identifier_meta_value, true);
1751 + foreach ($node_step_pair as $node_step_pair_key => $node_step_pair_value) {
1752 + $current_steps_array = array();
1753 + $step_id = $node_step_pair_value;
1754 + $step_title = get_the_title($step_id);
1755 + $step_meta = get_post_meta($step_id);
1756 + $content_post = get_post($step_id);
1757 + $content = json_encode($content_post->post_content);
1758 + $current_steps_array['step_id'] = $step_id;
1759 + $current_steps_array['title'] = $step_title;
1760 + $current_steps_array['meta'] = $step_meta;
1761 + $current_steps_array['content'] = $content;
1762 + $all_steps_array[] = $current_steps_array;
1763 + }
1764 + }
1765 + $data['steps'] = $all_steps_array;
1766 + }
1019 1767
1020 - return json_encode($data);
1021 - }
1022 - /**
1023 - * Get UTM Params URL
1024 - */
1768 + return json_encode($data);
1769 + }
1025 1770
1026 - public function get_utm_params( $funnel_id ) {
1027 - $utm_params = '';
1028 - $utm_settings = $this->get_utm_settings( $funnel_id );
1029 - $utm_params = '?utm_source='.'fgddffd'.'&utm_medium='.'ghfds'.'&utm_campaign='.'dsaghfds';
1030 - return $utm_params;
1031 - if($utm_settings['utm_enable'] == 'on') {
1032 - $utm_params = '?utm_source='.$utm_settings['utm_source'].'&utm_medium='.$utm_settings['utm_medium'].'&utm_campaign='.$utm_settings['utm_campaign'];
1033 - $utm_params .= ((!empty($utm_settings['utm_content'])) ? '&utm_content='.$utm_settings['utm_content'] : '');
1034 - $utm_params = strtolower($utm_params);
1035 - }
1036 - return $utm_params;
1037 - }
1771 + /**
1772 + * Get UTM Params URL
1773 + */
1038 1774
1039 - /**
1040 - * Get GTM Settings
1041 - *
1042 - * @return array
1043 - */
1044 - public function get_utm_settings( $funnel_id ) {
1775 + public function get_utm_params($funnel_id)
1776 + {
1777 + $utm_params = '';
1778 + $utm_settings = $this->get_utm_settings($funnel_id);
1779 + $utm_params = '?utm_source=' . 'fgddffd' . '&utm_medium=' . 'ghfds' . '&utm_campaign=' . 'dsaghfds';
1780 + return $utm_params;
1781 + if ($utm_settings['utm_enable'] == 'on') {
1782 + $utm_params = '?utm_source=' . $utm_settings['utm_source'] . '&utm_medium=' . $utm_settings['utm_medium'] . '&utm_campaign=' . $utm_settings['utm_campaign'];
1783 + $utm_params .= ((!empty($utm_settings['utm_content'])) ? '&utm_content=' . $utm_settings['utm_content'] : '');
1784 + $utm_params = strtolower($utm_params);
1785 + }
1786 + return $utm_params;
1787 + }
1788 +
1789 + /**
1790 + * Get GTM Settings
1791 + *
1792 + * @return array
1793 + */
1794 + public function get_utm_settings($funnel_id)
1795 + {
1045 1796 $default_settings = array(
1046 - 'utm_enable' => 'off',
1047 - 'utm_source' => '',
1048 - 'utm_medium' => '',
1049 - 'utm_campaign' => '',
1050 - 'utm_content' => '',
1797 + 'utm_enable' => 'off',
1798 + 'utm_source' => '',
1799 + 'utm_medium' => '',
1800 + 'utm_campaign' => '',
1801 + 'utm_content' => '',
1051 1802 );
1052 - $utm_settings = get_post_meta( $funnel_id, '_wpfunnels_utm_params', true );
1053 - return wp_parse_args($utm_settings, $default_settings);
1054 - }
1803 + $utm_settings = get_post_meta($funnel_id, '_wpfunnels_utm_params', true);
1804 + return wp_parse_args($utm_settings, $default_settings);
1805 + }
1055 1806
1056 1807
1057 - /**
1058 - * Prepare a single setting object for response.
1059 - *
1060 - * @param object|array $item Setting object.
1061 - * @param WP_REST_Request $request Request object.
1062 - *
1063 - * @return \WP_REST_Response $response Response data.
1064 - * @since 1.0.0
1065 - */
1066 - public function prepare_item_for_response($item, $request)
1067 - {
1068 - $data = $this->add_additional_fields_to_object($item, $request);
1069 - return rest_ensure_response($data);
1070 - }
1808 + /**
1809 + * Prepare a single setting object for response.
1810 + *
1811 + * @param object|array $item Setting object.
1812 + * @param WP_REST_Request $request Request object.
1813 + *
1814 + * @return \WP_REST_Response $response Response data.
1815 + * @since 1.0.0
1816 + */
1817 + public function prepare_item_for_response($item, $request)
1818 + {
1819 + $data = $this->add_additional_fields_to_object($item, $request);
1820 + return rest_ensure_response($data);
1821 + }
1071 1822
1072 1823
1073 - /**
1074 - * Get GBF data
1075 - *
1076 - * @param $request
1077 - *
1078 - * @return WP_Error|\WP_REST_Response
1079 - */
1080 - public function get_GBF_data( $request ){
1824 + /**
1825 + * Get GBF data
1826 + *
1827 + * @param $request
1828 + *
1829 + * @return WP_Error|\WP_REST_Response
1830 + */
1831 + public function get_GBF_data($request)
1832 + {
1081 1833
1082 - $funnel_id = $request['funnel_id'];
1083 - $steps = Wpfnl_functions::get_steps( $funnel_id );
1834 + $funnel_id = $request['funnel_id'];
1835 + $steps = Wpfnl_functions::get_steps($funnel_id);
1084 1836
1085 - if( !is_plugin_active( 'wpfunnels-pro-gbf/wpfnl-pro-gb.php' ) ){
1086 - $response = array(
1087 - 'success' => false,
1088 - 'data' => 'Global Funnel is not activated'
1089 - );
1090 - return rest_ensure_response($response);
1091 - }
1092 -
1093 - $is_gbf = get_post_meta( $funnel_id, 'is_global_funnel', true );
1094 - if( 'yes' === $is_gbf ){
1095 - $start_condition = get_post_meta( $funnel_id, 'global_funnel_start_condition', true );
1096 - $step_ids = array();
1097 - foreach( $steps as $step ){
1098 - if( $step['step_type'] == 'checkout' ){
1099 - if( !empty( $start_condition ) ){
1100 - array_push($step_ids,$step['id']);
1101 - }
1102 - }elseif( $step['step_type'] == 'upsell' ){
1103 - $upsell_rules = get_post_meta( $step['id'], 'global_funnel_upsell_rules', true );
1104 - if( !empty( $upsell_rules ) ){
1105 - array_push($step_ids,$step['id']);
1106 - }
1107 - }elseif( $step['step_type'] == 'downsell' ){
1108 - $downsell_rules = get_post_meta( $step['id'], 'global_funnel_downsell_rules', true );
1109 - if( !empty( $downsell_rules ) ){
1110 - array_push($step_ids,$step['id']);
1111 - }
1112 - }
1113 - }
1114 - }
1115 - $response = array(
1116 - 'success' => false,
1117 - 'data' => []
1118 - );
1119 - return rest_ensure_response($response);
1120 - }
1837 + if ( ! Wpfnl_functions::is_global_funnel_activated() ) {
1838 + $response = array(
1839 + 'success' => false,
1840 + 'data' => 'Global Funnel is not activated'
1841 + );
1842 + return rest_ensure_response($response);
1843 + }
1121 1844
1845 + $is_gbf = get_post_meta($funnel_id, 'is_global_funnel', true);
1846 + if ('yes' === $is_gbf) {
1847 + $start_condition = get_post_meta($funnel_id, 'global_funnel_start_condition', true);
1848 + $step_ids = array();
1849 + foreach ($steps as $step) {
1850 + if ($step['step_type'] == 'checkout') {
1851 + if (!empty($start_condition)) {
1852 + array_push($step_ids, $step['id']);
1853 + }
1854 + } elseif ($step['step_type'] == 'upsell') {
1855 + $upsell_rules = get_post_meta($step['id'], 'global_funnel_upsell_rules', true);
1856 + if (!empty($upsell_rules)) {
1857 + array_push($step_ids, $step['id']);
1858 + }
1859 + } elseif ($step['step_type'] == 'downsell') {
1860 + $downsell_rules = get_post_meta($step['id'], 'global_funnel_downsell_rules', true);
1861 + if (!empty($downsell_rules)) {
1862 + array_push($step_ids, $step['id']);
1863 + }
1864 + }
1865 + }
1866 + }
1867 + $response = array(
1868 + 'success' => false,
1869 + 'data' => []
1870 + );
1871 + return rest_ensure_response($response);
1872 + }
1122 1873
1123 - /**
1124 - * Get ab testing default setttings
1125 - *
1126 - * @param WP_REST_Request $request
1127 - *
1128 - * @return Array
1129 - *
1130 - * @since 1.6.21
1131 - */
1132 - public function get_ab_settings( WP_REST_Request $request ){
1133 -
1134 - $response = [];
1135 - if( isset( $request['step_id'] ) ){
1136 - $step_id = $request['step_id'];
1137 - $default_settings = $this->get_default_start_setting( $step_id );
1138 - $response['data'] = $default_settings;
1139 - $response['success'] = true;
1140 - }else{
1141 - $response['data'] = '';
1142 - $response['success'] = false;
1143 - }
1144 -
1145 - return rest_ensure_response( $response );
1146 - }
1147 1874
1148 - /**
1875 + /**
1876 + * Get ab testing default setttings
1877 + *
1878 + * @param WP_REST_Request $request
1879 + *
1880 + * @return Array
1881 + *
1882 + * @since 1.6.21
1883 + */
1884 + public function get_ab_settings(WP_REST_Request $request)
1885 + {
1886 +
1887 + $response = [];
1888 + if (isset($request['step_id'])) {
1889 + $step_id = $request['step_id'];
1890 + $default_settings = $this->get_default_start_setting($step_id);
1891 + $response['data'] = $default_settings;
1892 + $response['success'] = true;
1893 + } else {
1894 + $response['data'] = '';
1895 + $response['success'] = false;
1896 + }
1897 +
1898 + return rest_ensure_response($response);
1899 + }
1900 +
1901 + /**
1149 1902 * Change funnel name
1150 1903 *
1151 1904 * @param WP_REST_Request $payload Funnel name payload.
1152 1905 *
@@ -1152,61 +1905,63 @@
1152 1905 *
1153 1906 * @return array
1154 1907 * @since 2.7.5
1155 1908 */
1156 - public function funnel_name_change( $payload ) {
1157 - if ( !isset( $payload['funnel_id'], $payload['funnel_name'] ) ) {
1158 - return new WP_Error(
1159 - 'rest_invalid_request_params',
1160 - __( 'Invalid request params.' ),
1161 - array( 'status' => 404 )
1162 - );
1163 - }
1909 + public function funnel_name_change($payload)
1910 + {
1911 + if (!isset($payload['funnel_id'], $payload['funnel_name'])) {
1912 + return new WP_Error(
1913 + 'rest_invalid_request_params',
1914 + __('Invalid request params.'),
1915 + array('status' => 404)
1916 + );
1917 + }
1164 1918
1165 - $funnel_id = sanitize_text_field( $payload['funnel_id'] );
1166 - $updated_name = sanitize_text_field( $payload['funnel_name'] );
1167 - Wpfnl::$instance->funnel_store->set_id($funnel_id);
1168 - Wpfnl::$instance->funnel_store->update_funnel_name($updated_name);
1169 - self::update_steps_url_on_funnel_name_update( $funnel_id );
1170 - flush_rewrite_rules();
1171 -
1172 - $response = array(
1173 - 'success' => true,
1174 - 'message' => 'Funnel name changed successfully',
1175 - 'funnelID' => $funnel_id,
1176 - 'name' => $updated_name
1177 - );
1919 + $funnel_id = sanitize_text_field($payload['funnel_id']);
1920 + $updated_name = sanitize_text_field($payload['funnel_name']);
1921 + Wpfnl::$instance->funnel_store->set_id($funnel_id);
1922 + Wpfnl::$instance->funnel_store->update_funnel_name($updated_name);
1923 + self::update_steps_url_on_funnel_name_update($funnel_id);
1924 + flush_rewrite_rules();
1178 1925
1179 - return rest_ensure_response( $response );
1926 + $response = array(
1927 + 'success' => true,
1928 + 'message' => 'Funnel name changed successfully',
1929 + 'funnelID' => $funnel_id,
1930 + 'name' => $updated_name
1931 + );
1932 +
1933 + return rest_ensure_response($response);
1180 1934 }
1181 1935
1182 - /**
1183 - * Update the URL and funnel name for each step in a funnel.
1184 - *
1185 - * This function retrieves the step IDs associated with a specific funnel,
1186 - * and updates the URL and funnel name for each step based on the step ID.
1187 - *
1188 - * @param int $funnel_id The ID of the funnel.
1189 - * @return void
1190 - * @since 2.7.10
1191 - */
1192 - public function update_steps_url_on_funnel_name_update( $funnel_id ){
1193 - $step_controller = new StepController();
1194 - $step_ids = Wpfnl_functions::get_step_ids( $funnel_id );
1195 - foreach ( $step_ids as $step_id ) {
1196 - $step_title = get_post_meta( $step_id, '_wpf_step_title', true );
1197 - $step_slug = get_post_meta( $step_id, '_wpf_step_slug', true );
1198 - $settings = array(
1199 - 'funnel_id' => $funnel_id,
1200 - 'step_id' => $step_id,
1201 - 'title' => !empty($step_title) ? $step_title : get_the_title( $step_id ),
1202 - 'slug' => !empty($step_slug) ? $step_slug : get_post_field('post_name', $step_id)
1203 - );
1204 - $step_controller->update_step_meta_on_funnel_name_change( $funnel_id, $step_id, $settings );
1205 - }
1206 - }
1936 + /**
1937 + * Update the URL and funnel name for each step in a funnel.
1938 + *
1939 + * This function retrieves the step IDs associated with a specific funnel,
1940 + * and updates the URL and funnel name for each step based on the step ID.
1941 + *
1942 + * @param int $funnel_id The ID of the funnel.
1943 + * @return void
1944 + * @since 2.7.10
1945 + */
1946 + public function update_steps_url_on_funnel_name_update($funnel_id)
1947 + {
1948 + $step_controller = new StepController();
1949 + $step_ids = Wpfnl_functions::get_step_ids($funnel_id);
1950 + foreach ($step_ids as $step_id) {
1951 + $step_title = get_post_meta($step_id, '_wpf_step_title', true);
1952 + $step_slug = get_post_meta($step_id, '_wpf_step_slug', true);
1953 + $settings = array(
1954 + 'funnel_id' => $funnel_id,
1955 + 'step_id' => $step_id,
1956 + 'title' => !empty($step_title) ? $step_title : get_the_title($step_id),
1957 + 'slug' => !empty($step_slug) ? $step_slug : get_post_field('post_name', $step_id)
1958 + );
1959 + $step_controller->update_step_meta_on_funnel_name_change($funnel_id, $step_id, $settings);
1960 + }
1961 + }
1207 1962
1208 - /**
1963 + /**
1209 1964 * Get funnel settings by funnel id
1210 1965 *
1211 1966 * @param array $payload Funnel Settings payload data.
1212 1967 * @return WP_Rest_Response
@@ -1212,71 +1967,237 @@
1212 1967 * @return WP_Rest_Response
1213 1968 *
1214 1969 * @since 1.0.0
1215 1970 */
1216 - public function wpfnl_get_funnel_settings( $payload ) {
1217 - // Early return in case of missing funnel id.
1218 - if ( !isset( $payload['funnel_id'] )){
1219 - return $this->prepare_wp_error_response(
1220 - 'rest_missing_funnelID',
1221 - __('Funnel id is missing', 'wpfnl'),
1222 - array(
1223 - 'status' => 400
1224 - )
1225 - );
1226 - }
1971 + public function wpfnl_get_funnel_settings($payload)
1972 + {
1973 + // Early return in case of missing funnel id.
1974 + if (!isset($payload['funnel_id'])) {
1975 + return $this->prepare_wp_error_response(
1976 + 'rest_missing_funnelID',
1977 + __('Funnel id is missing', 'wpfnl'),
1978 + array(
1979 + 'status' => 400
1980 + )
1981 + );
1982 + }
1227 1983
1228 - // To get global settings.
1229 - $global_gtm = Wpfnl_functions::get_gtm_settings();
1984 + // To get global settings.
1985 + $global_gtm = Wpfnl_functions::get_gtm_settings();
1230 1986 $global_pixel = Wpfnl_functions::get_facebook_pixel_settings();
1231 - $settings = Wpfnl_functions::get_funnel_settings( $payload['funnel_id'] );
1987 + $settings = Wpfnl_functions::get_funnel_settings($payload['funnel_id']);
1232 1988
1233 - // Set default skip offer settings.
1989 + // Set default skip offer settings.
1234 1990 $skip_settings = array(
1235 - 'skip_offer' => 'no',
1991 + 'skip_offer' => 'no',
1236 1992 'skip_if_quantity' => 'no',
1237 1993 );
1238 1994
1239 - if ( get_post_meta( $payload['funnel_id'], '_wpfunnels_skip_offer', true ) ) {
1240 - $skip_settings = get_post_meta( $payload['funnel_id'], '_wpfunnels_skip_offer', true );
1995 + if (get_post_meta($payload['funnel_id'], '_wpfunnels_skip_offer', true)) {
1996 + $skip_settings = get_post_meta($payload['funnel_id'], '_wpfunnels_skip_offer', true);
1241 1997 }
1242 1998
1243 - // Preparing rest response.
1999 + // Preparing rest response.
1244 2000 $response = array(
1245 - 'success' => true,
1246 - 'data' => $settings,
1247 - 'globalGtm' => isset ( $global_gtm['gtm_enable'] ) ? $global_gtm['gtm_enable'] : 'no',
1248 - 'globalPixel' => isset ( $global_pixel['enable_fb_pixel'] ) ? $global_pixel['enable_fb_pixel'] : 'no',
1249 - 'skipSettings' => $skip_settings,
1250 - 'skipRecurringOffer' => get_post_meta( $payload['funnel_id'], '_wpfunnels_skip_recurring_offer', true ),
1251 - 'skipRecurringOfferWithinDays' => get_post_meta( $payload['funnel_id'], '_wpfunnels_skip_recurring_offer_within_days', true ),
2001 + 'success' => true,
2002 + 'data' => $settings,
2003 + 'globalGtm' => isset ($global_gtm['gtm_enable']) ? $global_gtm['gtm_enable'] : 'no',
2004 + 'globalPixel' => isset ($global_pixel['enable_fb_pixel']) ? $global_pixel['enable_fb_pixel'] : 'no',
2005 + 'skipSettings' => $skip_settings,
2006 + 'skipRecurringOffer' => get_post_meta($payload['funnel_id'], '_wpfunnels_skip_recurring_offer', true),
2007 + 'skipRecurringOfferWithinDays' => sanitize_text_field(get_post_meta($payload['funnel_id'], '_wpfunnels_skip_recurring_offer_within_days', true)),
1252 2008 );
1253 2009
1254 - /**
1255 - * Fires to get an individual funnel settings.
1256 - *
1257 - * @since 1.0.0
1258 - *
1259 - * @param string $response The settings data.
1260 - * @param string $payload['funnel_id'] The individual funnel id.
1261 - */
1262 - $response = apply_filters( 'wpfunnels/funnel_individual_settings', $response, $payload['funnel_id'] );
1263 - $response = rest_ensure_response( $response );
1264 -
1265 - return $response;//phpcs:ignore
2010 + /**
2011 + * Fires to get an individual funnel settings.
2012 + *
2013 + * @param string $response The settings data.
2014 + * @param string $payload ['funnel_id'] The individual funnel id.
2015 + * @since 1.0.0
2016 + *
2017 + */
2018 + $response = apply_filters('wpfunnels/funnel_individual_settings', $response, $payload['funnel_id']);
2019 + $response = rest_ensure_response($response);
2020 +
2021 + return $response;//phpcs:ignore
1266 2022 }
1267 2023
1268 - /**
1269 - * Delete marked funnel
1270 - *
1271 - * @param $payload
1272 - *
1273 - * @return array|bool
1274 - * @since 1.0.0
1275 - */
1276 - public function delete_marked_funnels( $payload ) {
1277 - $funnel_controller = Module::instance();
2024 + /**
2025 + * Delete marked funnel
2026 + *
2027 + * @param $payload
2028 + *
2029 + * @return array|bool
2030 + * @since 1.0.0
2031 + */
2032 + public function delete_marked_funnels($payload)
2033 + {
2034 + $funnel_controller = Module::instance();
1278 2035
1279 - return $funnel_controller->delete_marked_funnels ( $payload );
1280 - }
2036 + return $funnel_controller->delete_marked_funnels($payload);
2037 + }
2038 +
2039 +
2040 + /**
2041 + * Restore marked funnel
2042 + *
2043 + * @param $payload
2044 + *
2045 + * @return array|bool
2046 + * @since 3.1.8
2047 + */
2048 + public function restore_marked_funnels($payload)
2049 + {
2050 + $funnel_controller = Module::instance();
2051 +
2052 + return $funnel_controller->restore_marked_funnels($payload);
2053 + }
2054 +
2055 + /**
2056 + * Restore marked funnel
2057 + *
2058 + * @param $payload
2059 + *
2060 + * @return array|bool
2061 + * @since 3.1.8
2062 + */
2063 + public function trash_marked_funnels($payload)
2064 + {
2065 + $funnel_controller = Module::instance();
2066 +
2067 + return $funnel_controller->trash_marked_funnels($payload);
2068 + }
2069 +
2070 +
2071 + /**
2072 + * Convert a regular funnel to a Store Checkout funnel.
2073 + *
2074 + * Sets the funnel type to store_checkout, removes any landing steps
2075 + * permanently, and ensures the checkout step is first.
2076 + *
2077 + * @param WP_REST_Request $request
2078 + * @return WP_REST_Response|WP_Error
2079 + * @since 3.10.6
2080 + */
2081 + public function convert_to_store_checkout( $request ) {
2082 + $funnel_id = absint( $request['funnel_id'] );
2083 +
2084 + if ( ! $funnel_id ) {
2085 + return new WP_Error(
2086 + 'rest_invalid_funnel_id',
2087 + __( 'Invalid funnel ID.', 'wpfnl' ),
2088 + array( 'status' => 400 )
2089 + );
2090 + }
2091 +
2092 + // Set funnel type to store_checkout.
2093 + update_post_meta( $funnel_id, '_wpfnl_funnel_type', 'store_checkout' );
2094 +
2095 + // Get current steps order.
2096 + $steps_order = get_post_meta( $funnel_id, '_steps_order', true );
2097 + if ( ! is_array( $steps_order ) ) {
2098 + $steps_order = array();
2099 + }
2100 +
2101 + // Find and permanently delete all landing steps.
2102 + $new_steps_order = array();
2103 + foreach ( $steps_order as $step ) {
2104 + if ( isset( $step['step_type'] ) && 'landing' === $step['step_type'] ) {
2105 + wp_delete_post( absint( $step['id'] ), true );
2106 + } else {
2107 + $new_steps_order[] = $step;
2108 + }
2109 + }
2110 +
2111 + // Sort so checkout step comes first.
2112 + usort( $new_steps_order, function( $a, $b ) {
2113 + $priority = array( 'checkout' => 0, 'thankyou' => 1, 'upsell' => 2, 'downsell' => 3 );
2114 + $a_priority = isset( $priority[ $a['step_type'] ] ) ? $priority[ $a['step_type'] ] : 99;
2115 + $b_priority = isset( $priority[ $b['step_type'] ] ) ? $priority[ $b['step_type'] ] : 99;
2116 + return $a_priority - $b_priority;
2117 + } );
2118 +
2119 + // Re-index to avoid serialization issues.
2120 + $new_steps_order = array_values( $new_steps_order );
2121 +
2122 + // Persist both step meta keys that the codebase reads from.
2123 + update_post_meta( $funnel_id, '_steps_order', $new_steps_order );
2124 + update_post_meta( $funnel_id, '_steps', $new_steps_order );
2125 +
2126 + // Update _first_step to the checkout step.
2127 + foreach ( $new_steps_order as $step ) {
2128 + if ( isset( $step['step_type'] ) && 'checkout' === $step['step_type'] ) {
2129 + Wpfnl_functions::update_funnel_first_step( $funnel_id, $step['id'] );
2130 + break;
2131 + }
2132 + }
2133 +
2134 + // Remove landing step nodes from funnel canvas data and clean up dangling connections.
2135 + $funnel_data = get_post_meta( $funnel_id, '_funnel_data', true );
2136 + if ( is_array( $funnel_data ) && isset( $funnel_data['drawflow']['Home']['data'] ) ) {
2137 + // Collect landing node IDs first.
2138 + $landing_node_ids = array();
2139 + foreach ( $funnel_data['drawflow']['Home']['data'] as $node_id => $node ) {
2140 + if ( isset( $node['data']['step_type'] ) && 'landing' === $node['data']['step_type'] ) {
2141 + $landing_node_ids[] = (string) $node_id;
2142 + }
2143 + }
2144 +
2145 + // Remove landing nodes.
2146 + foreach ( $landing_node_ids as $landing_node_id ) {
2147 + unset( $funnel_data['drawflow']['Home']['data'][ $landing_node_id ] );
2148 + }
2149 +
2150 + // Clean up input/output connections in remaining nodes that referenced landing nodes.
2151 + foreach ( $funnel_data['drawflow']['Home']['data'] as $node_id => &$node ) {
2152 + // Clean inputs.
2153 + if ( isset( $node['inputs'] ) && is_array( $node['inputs'] ) ) {
2154 + foreach ( $node['inputs'] as $input_key => &$input ) {
2155 + if ( isset( $input['connections'] ) && is_array( $input['connections'] ) ) {
2156 + $input['connections'] = array_values( array_filter(
2157 + $input['connections'],
2158 + function( $conn ) use ( $landing_node_ids ) {
2159 + return ! in_array( (string) $conn['node'], $landing_node_ids, true );
2160 + }
2161 + ) );
2162 + }
2163 + }
2164 + unset( $input );
2165 + }
2166 + // Clean outputs.
2167 + if ( isset( $node['outputs'] ) && is_array( $node['outputs'] ) ) {
2168 + foreach ( $node['outputs'] as $output_key => &$output ) {
2169 + if ( isset( $output['connections'] ) && is_array( $output['connections'] ) ) {
2170 + $output['connections'] = array_values( array_filter(
2171 + $output['connections'],
2172 + function( $conn ) use ( $landing_node_ids ) {
2173 + return ! in_array( (string) $conn['node'], $landing_node_ids, true );
2174 + }
2175 + ) );
2176 + }
2177 + }
2178 + unset( $output );
2179 + }
2180 + }
2181 + unset( $node );
2182 +
2183 + update_post_meta( $funnel_id, '_funnel_data', $funnel_data );
2184 + }
2185 +
2186 + // Return the checkout step ID so the frontend can use it immediately.
2187 + $checkout_step_id = null;
2188 + foreach ( $new_steps_order as $step ) {
2189 + if ( isset( $step['step_type'] ) && 'checkout' === $step['step_type'] ) {
2190 + $checkout_step_id = $step['id'];
2191 + delete_post_meta( absint( $step['id'] ), '_wpfnl_checkout_products' );
2192 + break;
2193 + }
2194 + }
2195 +
2196 + return rest_ensure_response( array(
2197 + 'success' => true,
2198 + 'message' => __( 'Funnel converted to Store Checkout successfully.', 'wpfnl' ),
2199 + 'checkout_step_id' => $checkout_step_id,
2200 + ) );
2201 + }
1281 2202
1282 2203 }