| 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 . '/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 |
]); |
| 158 |
|
| 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 |
]); |
| 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 |
]); |
| 186 |
|
| 187 |
|
| 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( |
| 203 |
array( |
| 204 |
'methods' => \WP_REST_Server::READABLE, |
| 205 |
'callback' => [ |
| 206 |
$this, |
| 207 |
'get_all_funnels' |
| 208 |
], |
| 209 |
'permission_callback' => [ |
| 210 |
$this, |
| 211 |
'update_items_permissions_check' |
| 212 |
], |
| 213 |
), |
| 214 |
)); |
| 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 |
|
| 230 |
register_rest_route($this->namespace, '/' . $this->rest_base . '/steps/', array( |
| 231 |
'args' => array( |
| 232 |
'funnel_id' => array( |
| 233 |
'description' => __('Funnel ID.', 'wpfnl'), |
| 234 |
'type' => 'string', |
| 235 |
), |
| 236 |
'step_id' => array( |
| 237 |
'description' => __('Step ID.', 'wpfnl'), |
| 238 |
'type' => 'string', |
| 239 |
) |
| 240 |
), |
| 241 |
array( |
| 242 |
'methods' => \WP_REST_Server::EDITABLE, |
| 243 |
'callback' => [ |
| 244 |
$this, |
| 245 |
'update_step_meta' |
| 246 |
], |
| 247 |
'permission_callback' => [ |
| 248 |
$this, |
| 249 |
'update_items_permissions_check' |
| 250 |
], |
| 251 |
) |
| 252 |
)); |
| 253 |
|
| 254 |
|
| 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 |
]); |
| 268 |
|
| 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 |
]); |
| 282 |
|
| 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( |
| 312 |
$this->namespace, |
| 313 |
'/' . $this->rest_base . '/(?P<funnel_id>\d+)/funnel-name-change', |
| 314 |
array( |
| 315 |
array( |
| 316 |
'methods' => \WP_REST_Server::EDITABLE, |
| 317 |
'callback' => array( |
| 318 |
$this, |
| 319 |
'funnel_name_change', |
| 320 |
), |
| 321 |
'permission_callback' => array( |
| 322 |
$this, |
| 323 |
'update_items_permissions_check', |
| 324 |
), |
| 325 |
), |
| 326 |
) |
| 327 |
); |
| 328 |
|
| 329 |
register_rest_route( |
| 330 |
$this->namespace, |
| 331 |
'/' . $this->rest_base . '/wpfnl-get-funnel-settings', |
| 332 |
array( |
| 333 |
array( |
| 334 |
'methods' => \WP_REST_Server::EDITABLE, |
| 335 |
'callback' => array( |
| 336 |
$this, |
| 337 |
'wpfnl_get_funnel_settings', |
| 338 |
), |
| 339 |
'args' => array( |
| 340 |
'funnel_id' => array( |
| 341 |
'required' => true, |
| 342 |
'type' => array('integer', 'string'), |
| 343 |
'sanitize_callback' => 'absint', |
| 344 |
), |
| 345 |
), |
| 346 |
'permission_callback' => array( |
| 347 |
$this, |
| 348 |
'update_items_permissions_check', |
| 349 |
) |
| 350 |
), |
| 351 |
) |
| 352 |
); |
| 353 |
|
| 354 |
register_rest_route( |
| 355 |
$this->namespace, |
| 356 |
'/' . $this->rest_base . '/bulk-delete-funnel', |
| 357 |
array( |
| 358 |
array( |
| 359 |
'methods' => \WP_REST_Server::EDITABLE, |
| 360 |
'callback' => array( |
| 361 |
$this, |
| 362 |
'delete_marked_funnels', |
| 363 |
), |
| 364 |
'permission_callback' => array( |
| 365 |
$this, |
| 366 |
'update_items_permissions_check', |
| 367 |
), |
| 368 |
), |
| 369 |
) |
| 370 |
); |
| 371 |
|
| 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 |
); |
| 389 |
|
| 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 |
); |
| 407 |
|
| 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 |
); |
| 427 |
|
| 428 |
} |
| 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); |
| 440 |
|
| 441 |
if ($funnels) { |
| 442 |
if (count($funnels) > 0) { |
| 443 |
return false; |
| 444 |
} |
| 445 |
} |
| 446 |
|
| 447 |
return true; |
| 448 |
} |
| 449 |
|
| 450 |
|
| 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 |
} |
| 468 |
|
| 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 |
} |
| 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( |
| 517 |
'status' => true, |
| 518 |
); |
| 519 |
return $this->prepare_item_for_response($response, $request); |
| 520 |
} |
| 521 |
|
| 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 |
} |
| 536 |
|
| 537 |
|
| 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 |
/** |
| 700 |
* Get the funnel title and link |
| 701 |
* |
| 702 |
* @param $request |
| 703 |
* |
| 704 |
* @return \WP_REST_Response |
| 705 |
*/ |
| 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); |
| 711 |
$response['success'] = false; |
| 712 |
if ($steps) { |
| 713 |
if (isset($steps[0]) && $steps[0]['id']) { |
| 714 |
$response['link'] = get_post_permalink($steps[0]['id']); |
| 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 |
} |
| 721 |
$response['success'] = true; |
| 722 |
} |
| 723 |
} |
| 724 |
return $this->prepare_item_for_response($response, $request); |
| 725 |
} |
| 726 |
|
| 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 |
]; |
| 742 |
|
| 743 |
if (!$funnel_id) { |
| 744 |
return $this->prepare_item_for_response($response, $request); |
| 745 |
} |
| 746 |
|
| 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 |
} |
| 756 |
|
| 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 |
|
| 775 |
$response = [ |
| 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', |
| 861 |
'success' => false |
| 862 |
]; |
| 863 |
|
| 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); |
| 866 |
$funnel_identifier = get_post_meta($funnel_id, 'funnel_identifier', true); |
| 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); |
| 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 |
|
| 883 |
/** |
| 884 |
* Fires to modify funnel view link for A/B testing |
| 885 |
* |
| 886 |
* @param string $link |
| 887 |
* @param int $step_id |
| 888 |
* @param int $funnel_id |
| 889 |
* |
| 890 |
* @return string $link |
| 891 |
* @since 1.7.8 |
| 892 |
*/ |
| 893 |
$link = apply_filters('wpfunnels/modify_funnel_view_link', get_post_permalink($first_step_id), $first_step_id, $funnel_id); |
| 894 |
$response['title'] = $title; |
| 895 |
|
| 896 |
$utm_settings = Wpfnl_functions::get_funnel_utm_settings($funnel_id); |
| 897 |
$view_link = !empty($response['link']) ? $response['link'] : $link; |
| 898 |
|
| 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'; |
| 904 |
} |
| 905 |
|
| 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'] : []; |
| 911 |
|
| 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) { |
| 921 |
$response = [ |
| 922 |
'status' => 'success', |
| 923 |
'funnel_data' => $funnel_data, |
| 924 |
'funnel_identifier' => $funnel_identifier, |
| 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'] : [] |
| 932 |
]; |
| 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 |
|
| 943 |
/** |
| 944 |
* Fires to update funnel data response in case of A/B Testing |
| 945 |
* |
| 946 |
* @param array $response |
| 947 |
* |
| 948 |
* @return array $response |
| 949 |
**@since 1.7.8 |
| 950 |
*/ |
| 951 |
$response = apply_filters('wpfunnels/update_funnel_data_response', $response); |
| 952 |
if (!empty($response['ab_data'])) { |
| 953 |
$response['ab_data'] = $this->update_step_view_link_with_utm_params($response['ab_data'], $utm_settings); |
| 954 |
} |
| 955 |
} elseif ($title && $status) { |
| 956 |
|
| 957 |
$data = $this->get_default_add_step_node_data($init_position); |
| 958 |
$response = array( |
| 959 |
'status' => 'scratch-funnel', |
| 960 |
'title' => $title, |
| 961 |
'funnel_status' => $status, |
| 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 |
); |
| 967 |
} |
| 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(). |
| 976 |
return $response; |
| 977 |
} |
| 978 |
|
| 979 |
|
| 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 |
/** |
| 1059 |
* Updates A/B test variations' step view links with UTM parameters. |
| 1060 |
* |
| 1061 |
* This method takes A/B test data, specifically variations, and appends UTM parameters |
| 1062 |
* to the step view links of each variation. It does so based on the provided UTM settings |
| 1063 |
* if UTM tracking is enabled. |
| 1064 |
* |
| 1065 |
* @param array $ab_data An array containing A/B test data with variations. |
| 1066 |
* @param array $utm_settings An array of UTM parameters and their values. |
| 1067 |
* |
| 1068 |
* @return array The modified A/B test data with UTM parameters appended to step view links. |
| 1069 |
* @since 2.8.14 |
| 1070 |
*/ |
| 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']); |
| 1081 |
} |
| 1082 |
} |
| 1083 |
} |
| 1084 |
} |
| 1085 |
} |
| 1086 |
return $ab_data; |
| 1087 |
} |
| 1088 |
|
| 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 |
} |
| 1101 |
|
| 1102 |
|
| 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 |
} |
| 1145 |
|
| 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 = []; |
| 1161 |
|
| 1162 |
foreach ($steps_order as $step) { |
| 1163 |
$step_id = isset($step['id']) ? $step['id'] : null; |
| 1164 |
$_temp_step = $step; |
| 1165 |
|
| 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); |
| 1174 |
|
| 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 |
} |
| 1182 |
|
| 1183 |
$_temp_step['should_assign_product'] = $should_assign_product; |
| 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 |
|
| 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 |
/** |
| 1405 |
* Get formatted funnel data |
| 1406 |
* |
| 1407 |
* @param $drawflow |
| 1408 |
* |
| 1409 |
* @return mixed |
| 1410 |
* |
| 1411 |
* @since 2.0.5 |
| 1412 |
*/ |
| 1413 |
private function get_formatted_funnel_data($drawflow) |
| 1414 |
{ |
| 1415 |
if (isset($drawflow['drawflow']['Home']['data'])) { |
| 1416 |
$drawflow_data = $drawflow['drawflow']['Home']['data']; |
| 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); |
| 1431 |
$drawflow['drawflow']['Home']['data'][$key]['data'] = array( |
| 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, |
| 1437 |
); |
| 1438 |
} |
| 1439 |
} |
| 1440 |
} |
| 1441 |
} |
| 1442 |
return $drawflow; |
| 1443 |
} |
| 1444 |
|
| 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()]; |
| 1462 |
|
| 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 |
|
| 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); |
| 1496 |
|
| 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)) { |
| 1522 |
$steps_order[] = $_step; |
| 1523 |
} |
| 1524 |
} |
| 1525 |
|
| 1526 |
if (count($steps_order)) { |
| 1527 |
update_post_meta($funnel_id, '_steps_order', $steps_order); |
| 1528 |
|
| 1529 |
} else { |
| 1530 |
delete_post_meta($funnel_id, '_steps_order'); |
| 1531 |
} |
| 1532 |
} |
| 1533 |
|
| 1534 |
$response['step_id'] = Wpfnl_functions::get_first_step($funnel_id); |
| 1535 |
|
| 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 |
} |
| 1542 |
|
| 1543 |
$utm_settings = Wpfnl_functions::get_funnel_utm_settings($funnel_id); |
| 1544 |
$view_link = get_post_permalink($response['step_id']); |
| 1545 |
|
| 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 |
} |
| 1551 |
|
| 1552 |
$response['link'] = esc_url($view_link); |
| 1553 |
|
| 1554 |
$response['funnel_id'] = $funnel_id; |
| 1555 |
$response['success'] = true; |
| 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); |
| 1563 |
|
| 1564 |
do_action( 'wpfunnels_canvas_saved', absint( $funnel_id ) ); |
| 1565 |
|
| 1566 |
return rest_ensure_response($response); |
| 1567 |
} |
| 1568 |
|
| 1569 |
|
| 1570 |
/** |
| 1571 |
* Get steps |
| 1572 |
* |
| 1573 |
* @param $funnel_flow_data |
| 1574 |
* |
| 1575 |
* @return array |
| 1576 |
* |
| 1577 |
* @since 2.0.5 |
| 1578 |
*/ |
| 1579 |
private function get_steps($funnel_flow_data) |
| 1580 |
{ |
| 1581 |
$drawflow = $funnel_flow_data['drawflow']; |
| 1582 |
$steps = array(); |
| 1583 |
if (isset($drawflow['Home']['data'])) { |
| 1584 |
$drawflow_data = $drawflow['Home']['data']; |
| 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, |
| 1595 |
); |
| 1596 |
} |
| 1597 |
} |
| 1598 |
} |
| 1599 |
return $steps; |
| 1600 |
} |
| 1601 |
|
| 1602 |
|
| 1603 |
/** |
| 1604 |
* Get steps order |
| 1605 |
* |
| 1606 |
* @param $funnel_flow_data |
| 1607 |
* |
| 1608 |
* @return array |
| 1609 |
* |
| 1610 |
* @since 2.0.5 |
| 1611 |
*/ |
| 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(); |
| 1619 |
|
| 1620 |
|
| 1621 |
if (isset($drawflow['Home']['data'])) { |
| 1622 |
$drawflow_data = $drawflow['Home']['data']; |
| 1623 |
|
| 1624 |
/** |
| 1625 |
* If has only one step, that only step will be the first step, no conditions should be checked. |
| 1626 |
* just return the step order |
| 1627 |
*/ |
| 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)), |
| 1638 |
); |
| 1639 |
return $step_order; |
| 1640 |
|
| 1641 |
} |
| 1642 |
|
| 1643 |
/** |
| 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 |
| 1645 |
* step information includes output connection and input connection and it will be stored on $nodes |
| 1646 |
*/ |
| 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'])) |
| 1655 |
) { |
| 1656 |
|
| 1657 |
|
| 1658 |
if ('conditional' === $step_type || 'addstep' === $step_type) { |
| 1659 |
continue; |
| 1660 |
} |
| 1661 |
|
| 1662 |
/** |
| 1663 |
* A starting node is a node which has only output connection but not any input connection. |
| 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. |
| 1665 |
* for other step types (checkout, offer, thankyou), we will check if the step has any output connection and no input connection. |
| 1666 |
*/ |
| 1667 |
if ('landing' === $step_type) { |
| 1668 |
if ( |
| 1669 |
isset($data['outputs']['output_1']['connections']) && count($data['outputs']['output_1']['connections']) && |
| 1670 |
(isset($data['inputs']) && (count($data['inputs']) == 0 || (isset($data['inputs']['input_1']['connections']) && count($data['inputs']['input_1']['connections']) == 0))) |
| 1671 |
) { |
| 1672 |
$start_node = array( |
| 1673 |
'id' => $step_id, |
| 1674 |
'step_type' => $step_type, |
| 1675 |
'name' => sanitize_text_field(get_the_title($step_id)), |
| 1676 |
); |
| 1677 |
} |
| 1678 |
} else { |
| 1679 |
if ( |
| 1680 |
isset($data['outputs']['output_1']['connections']) && count($data['outputs']['output_1']['connections']) && |
| 1681 |
(isset($data['inputs']['input_1']['connections']) && count($data['inputs']['input_1']['connections']) === 0) |
| 1682 |
) { |
| 1683 |
$start_node = array( |
| 1684 |
'id' => $step_id, |
| 1685 |
'step_type' => $step_type, |
| 1686 |
'name' => sanitize_text_field(get_the_title($step_id)), |
| 1687 |
); |
| 1688 |
} else { |
| 1689 |
$step_order[] = array( |
| 1690 |
'id' => $step_id, |
| 1691 |
'step_type' => $step_type, |
| 1692 |
'name' => sanitize_text_field(get_the_title($step_id)), |
| 1693 |
); |
| 1694 |
} |
| 1695 |
} |
| 1696 |
} |
| 1697 |
} |
| 1698 |
|
| 1699 |
$step_order = $this->array_insert($step_order, $start_node, 0); |
| 1700 |
} |
| 1701 |
return $step_order; |
| 1702 |
} |
| 1703 |
|
| 1704 |
|
| 1705 |
/** |
| 1706 |
* Array insert element on position |
| 1707 |
* |
| 1708 |
* @param $original |
| 1709 |
* @param $inserted |
| 1710 |
* @param int $position |
| 1711 |
* |
| 1712 |
* @return mixed |
| 1713 |
*/ |
| 1714 |
private function array_insert(&$original, $inserted, $position) |
| 1715 |
{ |
| 1716 |
array_splice($original, $position, 0, array($inserted)); |
| 1717 |
return $original; |
| 1718 |
} |
| 1719 |
|
| 1720 |
|
| 1721 |
/** |
| 1722 |
* Export funnel data |
| 1723 |
* |
| 1724 |
* @param $request |
| 1725 |
* |
| 1726 |
* @return false|string |
| 1727 |
* |
| 1728 |
* @since 1.0.0 |
| 1729 |
*/ |
| 1730 |
public function export_funnel($request) |
| 1731 |
{ |
| 1732 |
$funnel_id = $request['funnel_id']; |
| 1733 |
|
| 1734 |
//===main array of data which will be downloaded as json file===// |
| 1735 |
$data = array(); |
| 1736 |
|
| 1737 |
$funnel_title = get_the_title($funnel_id); |
| 1738 |
$funnel_meta = get_post_meta($funnel_id); |
| 1739 |
|
| 1740 |
//=== Added title and meta of current funnel post===// |
| 1741 |
$data['title'] = $funnel_title; |
| 1742 |
$data['meta'] = $funnel_meta; |
| 1743 |
|
| 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 |
} |
| 1767 |
|
| 1768 |
return json_encode($data); |
| 1769 |
} |
| 1770 |
|
| 1771 |
/** |
| 1772 |
* Get UTM Params URL |
| 1773 |
*/ |
| 1774 |
|
| 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 |
{ |
| 1796 |
$default_settings = array( |
| 1797 |
'utm_enable' => 'off', |
| 1798 |
'utm_source' => '', |
| 1799 |
'utm_medium' => '', |
| 1800 |
'utm_campaign' => '', |
| 1801 |
'utm_content' => '', |
| 1802 |
); |
| 1803 |
$utm_settings = get_post_meta($funnel_id, '_wpfunnels_utm_params', true); |
| 1804 |
return wp_parse_args($utm_settings, $default_settings); |
| 1805 |
} |
| 1806 |
|
| 1807 |
|
| 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 |
} |
| 1822 |
|
| 1823 |
|
| 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 |
{ |
| 1833 |
|
| 1834 |
$funnel_id = $request['funnel_id']; |
| 1835 |
$steps = Wpfnl_functions::get_steps($funnel_id); |
| 1836 |
|
| 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 |
} |
| 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 |
} |
| 1873 |
|
| 1874 |
|
| 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 |
/** |
| 1902 |
* Change funnel name |
| 1903 |
* |
| 1904 |
* @param WP_REST_Request $payload Funnel name payload. |
| 1905 |
* |
| 1906 |
* @return array |
| 1907 |
* @since 2.7.5 |
| 1908 |
*/ |
| 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 |
} |
| 1918 |
|
| 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(); |
| 1925 |
|
| 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); |
| 1934 |
} |
| 1935 |
|
| 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 |
} |
| 1962 |
|
| 1963 |
/** |
| 1964 |
* Get funnel settings by funnel id |
| 1965 |
* |
| 1966 |
* @param array $payload Funnel Settings payload data. |
| 1967 |
* @return WP_Rest_Response |
| 1968 |
* |
| 1969 |
* @since 1.0.0 |
| 1970 |
*/ |
| 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 |
} |
| 1983 |
|
| 1984 |
// To get global settings. |
| 1985 |
$global_gtm = Wpfnl_functions::get_gtm_settings(); |
| 1986 |
$global_pixel = Wpfnl_functions::get_facebook_pixel_settings(); |
| 1987 |
$settings = Wpfnl_functions::get_funnel_settings($payload['funnel_id']); |
| 1988 |
|
| 1989 |
// Set default skip offer settings. |
| 1990 |
$skip_settings = array( |
| 1991 |
'skip_offer' => 'no', |
| 1992 |
'skip_if_quantity' => 'no', |
| 1993 |
); |
| 1994 |
|
| 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); |
| 1997 |
} |
| 1998 |
|
| 1999 |
// Preparing rest response. |
| 2000 |
$response = array( |
| 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)), |
| 2008 |
); |
| 2009 |
|
| 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 |
| 2022 |
} |
| 2023 |
|
| 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(); |
| 2035 |
|
| 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 |
} |
| 2202 |
|
| 2203 |
} |
| 2204 |
|