PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.10.8
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.10.8
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
wpfunnels / includes / core / rest-api / Controllers / class-funnel-controller.php

class-funnel-controller.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.10.8, at includes/core/rest-api/Controllers/class-funnel-controller.php

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