PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.3.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.3.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
← All changes | admin-legacy-core/ajax/steps.php +145 -0 3.0.1 → 3.3.0 View file →
@@ -329,8 +329,12 @@
329 329 if ( ! is_array( $flow_steps ) ) {
330 330 wp_send_json( $result );
331 331 }
332 332
333 + // Handle redirect updates for source steps (offer steps pointing to the deleted step).
334 + // This includes both control steps and A/B testing variations.
335 + $flow_steps = $this->process_redirect_updates_on_step_deletion( $flow_steps, $flow_id );
336 +
333 337 $is_ab_test = get_post_meta( $step_id, 'wcf-ab-test', true );
334 338
335 339 if ( ! $is_ab_test ) {
336 340
@@ -451,9 +455,150 @@
451 455 'text' => sprintf( __( 'Data saved successfully for step id %s', 'cartflows' ), $step_id ),
452 456 );
453 457
454 458 wp_send_json( $result );
459 + }
455 460
461 + /**
462 + * Process redirect updates when a step is deleted.
463 + *
464 + * Handles updating redirects for source steps (offer steps pointing to the deleted step).
465 + * This includes both control steps and A/B testing variations.
466 + *
467 + * @since 3.1.0
468 + * @param array<int, array<string, mixed>> $flow_steps The current flow steps array.
469 + * @param int $flow_id The flow post ID — used to enforce per-flow ownership on each ID in the payload.
470 + * @return array<int, array<string, mixed>> The updated flow steps array.
471 + */
472 + private function process_redirect_updates_on_step_deletion( $flow_steps, $flow_id ) {
473 +
474 + // Per-call IDOR guard — re-verify the current user can edit this flow.
475 + if ( ! $this->user_can_edit_flow( $flow_id ) ) {
476 + return $flow_steps;
477 + }
478 +
479 + $redirect_updates_json = isset( $_POST['redirect_updates'] ) ? sanitize_text_field( wp_unslash( $_POST['redirect_updates'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
480 +
481 + if ( empty( $redirect_updates_json ) ) {
482 + return $flow_steps;
483 + }
484 +
485 + $redirect_updates = json_decode( $redirect_updates_json, true );
486 +
487 + if ( ! is_array( $redirect_updates ) ) {
488 + return $flow_steps;
489 + }
490 +
491 + foreach ( $redirect_updates as $source_step_id => $redirects ) {
492 + $source_step_id = absint( $source_step_id );
493 +
494 + // IDOR guard — source step must belong to this flow.
495 + if ( ! $this->is_step_in_flow( $source_step_id, $flow_id ) ) {
496 + continue;
497 + }
498 +
499 + // IDOR guard — accept/reject targets must belong to this flow if provided.
500 + if ( ! empty( $redirects['accept'] ) && ! $this->is_step_in_flow( absint( $redirects['accept'] ), $flow_id ) ) {
501 + unset( $redirects['accept'] );
502 + }
503 + if ( ! empty( $redirects['reject'] ) && ! $this->is_step_in_flow( absint( $redirects['reject'] ), $flow_id ) ) {
504 + unset( $redirects['reject'] );
505 + }
506 +
507 + $flow_steps = $this->update_single_step_redirects( $flow_steps, $source_step_id, $redirects );
508 + }
509 +
510 + return $flow_steps;
456 511 }
457 512
513 + /**
514 + * Update redirects for a single source step.
515 + *
516 + * Determines if the source is a control step or a variation and delegates
517 + * to the appropriate handler.
518 + *
519 + * @since 3.1.0
520 + * @param array $flow_steps The current flow steps array.
521 + * @param int $source_step_id The source step ID.
522 + * @param array $redirects The redirect targets (accept/reject).
523 + * @return array The updated flow steps array.
524 + */
525 + private function update_single_step_redirects( $flow_steps, $source_step_id, $redirects ) {
526 + if ( $source_step_id <= 0 ) {
527 + return $flow_steps;
528 + }
458 529
530 + $accept_target = isset( $redirects['accept'] ) && ! empty( $redirects['accept'] ) ? absint( $redirects['accept'] ) : 0;
531 + $reject_target = isset( $redirects['reject'] ) && ! empty( $redirects['reject'] ) ? absint( $redirects['reject'] ) : 0;
532 +
533 + // First, check if it's a control step.
534 + $flow_steps = $this->update_control_step_redirects( $flow_steps, $source_step_id, $accept_target, $reject_target );
535 +
536 + // If not found as control step, check if it's a variation.
537 + $flow_steps = $this->update_variation_step_redirects( $flow_steps, $source_step_id, $accept_target, $reject_target );
538 +
539 + return $flow_steps;
540 + }
541 +
542 + /**
543 + * Update redirects for a control step (direct match in flow_steps).
544 + *
545 + * @since 3.1.0
546 + * @param array $flow_steps The current flow steps array.
547 + * @param int $source_step_id The source step ID.
548 + * @param int $accept_target The accept redirect target step ID.
549 + * @param int $reject_target The reject redirect target step ID.
550 + * @return array The updated flow steps array.
551 + */
552 + private function update_control_step_redirects( $flow_steps, $source_step_id, $accept_target, $reject_target ) {
553 + foreach ( $flow_steps as $idx => $step_data ) {
554 + if ( isset( $step_data['id'] ) && (int) $step_data['id'] === $source_step_id ) {
555 + // Update post meta for the control step.
556 + if ( $accept_target > 0 ) {
557 + update_post_meta( $source_step_id, 'wcf-yes-next-step', $accept_target );
558 + $flow_steps[ $idx ]['offer_yes_step_id'] = $accept_target;
559 + }
560 + if ( $reject_target > 0 ) {
561 + update_post_meta( $source_step_id, 'wcf-no-next-step', $reject_target );
562 + $flow_steps[ $idx ]['offer_no_step_id'] = $reject_target;
563 + }
564 + break;
565 + }
566 + }
567 +
568 + return $flow_steps;
569 + }
570 +
571 + /**
572 + * Update redirects for an A/B test variation step.
573 + *
574 + * Variations don't have separate post meta, they're stored in the flow_steps array.
575 + *
576 + * @since 3.1.0
577 + * @param array $flow_steps The current flow steps array.
578 + * @param int $source_step_id The source step ID (variation ID).
579 + * @param int $accept_target The accept redirect target step ID.
580 + * @param int $reject_target The reject redirect target step ID.
581 + * @return array The updated flow steps array.
582 + */
583 + private function update_variation_step_redirects( $flow_steps, $source_step_id, $accept_target, $reject_target ) {
584 + foreach ( $flow_steps as $idx => $step_data ) {
585 + // Check if this step has A/B test variations.
586 + if ( isset( $step_data['ab-test-variations'] ) && is_array( $step_data['ab-test-variations'] ) ) {
587 + foreach ( $step_data['ab-test-variations'] as $var_idx => $variation ) {
588 + if ( isset( $variation['id'] ) && (int) $variation['id'] === $source_step_id ) {
589 + // Update the variation's redirects in the flow_steps array.
590 + if ( $accept_target > 0 ) {
591 + $flow_steps[ $idx ]['ab-test-variations'][ $var_idx ]['offer_yes_step_id'] = $accept_target;
592 + }
593 + if ( $reject_target > 0 ) {
594 + $flow_steps[ $idx ]['ab-test-variations'][ $var_idx ]['offer_no_step_id'] = $reject_target;
595 + }
596 + return $flow_steps; // Found and updated, exit early.
597 + }
598 + }
599 + }
600 + }
601 +
602 + return $flow_steps;
603 + }
459 604 }