plugin = $plugin;
}
/** Return the action used to secure Free funnel administration forms. */
public static function nonce_action() {
return self::NONCE_ACTION;
}
public function register() {
add_action( 'init', array( $this, 'register_post_types' ) );
add_action( 'admin_menu', array( $this, 'add_menu' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'assets' ) );
add_action( 'admin_post_divi_powers_plus_funnel_install', array( $this, 'install_action' ) );
add_action( 'admin_post_divi_powers_plus_funnel_rebuild', array( $this, 'rebuild_action' ) );
add_action( 'admin_post_divi_powers_plus_funnel_general', array( $this, 'save_general_action' ) );
add_action( 'admin_post_divi_powers_plus_funnel_step', array( $this, 'save_step_action' ) );
add_action( 'admin_post_divi_powers_plus_funnel_add_step', array( $this, 'add_step_action' ) );
add_action( 'admin_post_divi_powers_plus_funnel_delete_step', array( $this, 'delete_step_action' ) );
add_action( 'admin_post_divi_powers_plus_funnel_duplicate_step', array( $this, 'duplicate_step_action' ) );
add_action( 'admin_post_divi_powers_plus_funnel_reorder', array( $this, 'reorder_action' ) );
add_filter( 'display_post_states', array( $this, 'post_states' ), 20, 2 );
}
public function register_post_types() {
register_post_type( 'fivedp_funnel', array( 'labels' => array( 'name' => __( 'Funnels', '5d-powers-for-divi' ) ), 'public' => false, 'show_ui' => false, 'show_in_menu' => false, 'supports' => array( 'title' ) ) );
register_post_type( 'fivedp_woo_profile', array( 'labels' => array( 'name' => __( 'Woo Journey Profiles', '5d-powers-for-divi' ) ), 'public' => false, 'show_ui' => false, 'show_in_menu' => false, 'supports' => array( 'title' ) ) );
if ( is_admin() && current_user_can( 'manage_options' ) ) {
$this->state();
}
}
public function add_menu() {
add_submenu_page( 'divi-powers', __( 'Funnels', '5d-powers-for-divi' ), __( 'Funnels', '5d-powers-for-divi' ), 'manage_options', 'divi-powers-plus-funnel', array( $this, 'render_page' ) );
}
public function assets() {
$page = sanitize_key( wp_unslash( $_GET['page'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( 'divi-powers-plus-funnel' !== $page ) {
return;
}
wp_enqueue_style( 'divi-powers-plus-funnel-workspace', DIVI_POWERS_URL . 'assets/css/funnel-workspace.css', array( 'divi-powers-admin' ), DIVI_POWERS_VERSION );
wp_enqueue_script(
'divi-powers-plus-funnel-workspace',
DIVI_POWERS_URL . 'assets/js/funnel-workspace.js',
array( 'wp-i18n' ),
DIVI_POWERS_VERSION,
true
);
wp_set_script_translations( 'divi-powers-plus-funnel-workspace', '5d-powers-for-divi' );
}
public function install_action() {
$this->handle_install( false );
}
public function rebuild_action() {
$this->handle_install( true );
}
private function handle_install( $force ) {
$this->authorize();
$name = sanitize_text_field( wp_unslash( $_POST['starter_name'] ?? __( 'My First Funnel', '5d-powers-for-divi' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- authorize() verifies this admin-post request before input is read.
$funnel_id = $force ? absint( wp_unslash( $_POST['funnel_id'] ?? 0 ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- authorize() verifies this admin-post request before input is read.
if ( $force && ! $funnel_id ) { $this->redirect( 'error', __( 'Choose a funnel to rebuild.', '5d-powers-for-divi' ) ); }
$result = $this->install( $name, $force, $funnel_id, ! $force );
if ( is_wp_error( $result ) ) {
$this->redirect( 'error', $result->get_error_message() );
}
$this->redirect( $force ? 'rebuilt' : 'installed', '', absint( $result['funnel_id'] ?? 0 ) );
}
public function render_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$funnel_id = absint( wp_unslash( $_GET['funnel_id'] ?? 0 ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin navigation parameter.
$state = $this->state( $funnel_id );
$starter = $state['starter']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$funnel = $funnel_id ? $this->funnel( $funnel_id, $starter ) : null;
if ( $funnel ) {
$this->render_workspace( $funnel, $starter );
return;
}
$this->render_library();
}
private function render_library() {
// Migrate the original starter lazily without changing its pages or settings.
$this->state();
$page = max( 1, absint( $_GET['paged'] ?? 1 ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$query = new WP_Query( array( 'post_type' => 'fivedp_funnel', 'post_status' => array( 'publish', 'draft', 'private' ), 'posts_per_page' => 20, 'paged' => $page, 'orderby' => 'ID', 'order' => 'DESC' ) );
?>
notice(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
posts as $post ) :
$state = $this->state( $post->ID );
$funnel = $this->funnel( $post->ID, $state['starter'] );
$checkout = Divi_Powers_Free_Checkout::metrics( $post->ID ); ?>
add_query_arg( 'paged', '%#%' ), 'current' => $page, 'total' => $query->max_num_pages ) ) ); ?>
notice();
$entry_page = $this->entry_page( $steps );
?>
render_settings_tab( $funnel, $starter );
} elseif ( 'analytics' === $tab ) {
$this->render_analytics_tab( $funnel );
} else {
$this->render_build_tab( $funnel, $starter );
}
?>
checklist( $funnel, $starter );
$complete = count( array_filter( wp_list_pluck( $checklist, 'complete' ) ) );
?>
$step ) : $this->render_step_card( $funnel, $step, $index, $steps ); endforeach; ?>
$step ) {
$this->render_step_drawer( $funnel, $step, $index, $steps );
}
$this->render_checklist_drawer( $checklist );
$this->render_add_step_modal( $funnel, $steps );
}
private function render_step_card( $funnel, $step, $index, $steps ) {
$page_id = absint( $step['page_id'] );
$page = $page_id ? get_post( $page_id ) : null;
$type = $step['step_type'];
$next = $steps[ $index + 1 ] ?? null;
$seo = $page_id ? Divi_Powers_Funnel_Meta::search_settings( $page_id, $type ) : array();
?>
⋮⋮
step_icon( $type ) ); ?>
step_type_label( $type ) ) ); ?>
post_title ) : esc_html__( 'Page missing', '5d-powers-for-divi' ); ?> ·
↓
pages();
$products = $this->products();
$seo = $page_id ? Divi_Powers_Funnel_Meta::search_settings( $page_id, $step['step_type'] ) : Divi_Powers_Funnel_Meta::default_search_settings( $step['step_type'] );
$next = $steps[ $index + 1 ] ?? null;
$next_url = $next ? Divi_Powers_Funnel_Meta::next_url( $funnel['id'], $step['id'] ) : '';
?>
pages();
?>
entry_page( $funnel['steps'] );
?>
authorize();
// phpcs:disable WordPress.Security.NonceVerification.Missing -- authorize() verifies this admin-post request before input is read.
$funnel_id = $this->request_funnel_id();
$funnel = get_post( $funnel_id );
if ( ! $funnel || 'fivedp_funnel' !== $funnel->post_type ) {
$this->redirect( 'error', __( 'The funnel could not be found.', '5d-powers-for-divi' ) );
}
$name = substr( sanitize_text_field( wp_unslash( $_POST['funnel_name'] ?? $funnel->post_title ) ), 0, 80 );
$status = sanitize_key( wp_unslash( $_POST['funnel_status'] ?? 'draft' ) );
if ( ! in_array( $status, array( 'draft', 'active', 'paused' ), true ) ) {
$status = 'draft';
}
wp_update_post( array( 'ID' => $funnel_id, 'post_title' => $name ) );
update_post_meta( $funnel_id, '_divi_powers_funnel_status', $status );
$state = $this->state( $funnel_id );
$state['starter']['name'] = $name;
$this->save_state( $state['starter'] );
delete_transient( 'divi_powers_active_entry_map' );
$this->sync_contexts( $funnel_id, $this->steps( $funnel_id, $state['starter'] ), $name );
$this->redirect( 'saved', '', $funnel_id, 'settings' );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
public function save_step_action() {
$this->authorize();
// phpcs:disable WordPress.Security.NonceVerification.Missing -- authorize() verifies this admin-post request before input is read.
$funnel_id = $this->request_funnel_id();
$step_id = sanitize_key( wp_unslash( $_POST['step_id'] ?? '' ) );
$state = $this->state( $funnel_id );
$steps = $this->steps( $funnel_id, $state['starter'] );
$found = false;
foreach ( $steps as &$step ) {
if ( $step['id'] !== $step_id ) {
continue;
}
$found = true;
$page_id = absint( wp_unslash( $_POST['page_id'] ?? 0 ) );
if ( ! $page_id || 'page' !== get_post_type( $page_id ) || 'trash' === get_post_status( $page_id ) ) {
$this->redirect( 'error', __( 'Choose an available WordPress page.', '5d-powers-for-divi' ), $funnel_id );
}
$this->assert_page_available( $page_id, $funnel_id, $step_id );
$old_page = absint( $step['page_id'] );
$step['page_id'] = $page_id;
$step['label'] = substr( sanitize_text_field( wp_unslash( $_POST['step_label'] ?? $step['label'] ) ), 0, 80 );
if ( 'checkout' === $step['step_type'] ) {
$step['product_id'] = absint( wp_unslash( $_POST['product_id'] ?? 0 ) );
$layout = sanitize_key( wp_unslash( $_POST['checkout_layout'] ?? 'two_column' ) );
$step['checkout_layout'] = in_array( $layout, array( 'classic', 'two_column', 'focused' ), true ) ? $layout : 'two_column';
}
$search_input = isset( $_POST['search'] ) && is_array( $_POST['search'] ) ? wp_unslash( $_POST['search'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- save_search_settings() allowlists and sanitizes every supported field before persistence.
Divi_Powers_Funnel_Meta::save_search_settings( $page_id, $search_input, $step['step_type'] );
if ( $old_page && $old_page !== $page_id ) {
Divi_Powers_Funnel_Meta::detach_page( $old_page, $funnel_id );
}
break;
}
unset( $step );
if ( ! $found ) {
$this->redirect( 'error', __( 'The selected funnel step no longer exists.', '5d-powers-for-divi' ), $funnel_id );
}
$this->save_steps( $funnel_id, $steps, $state['starter'] );
$this->redirect( 'step-saved', '', $funnel_id );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
public function add_step_action() {
$this->authorize();
// phpcs:disable WordPress.Security.NonceVerification.Missing -- authorize() verifies this admin-post request before input is read.
$funnel_id = $this->request_funnel_id();
$state = $this->state( $funnel_id );
$steps = $this->steps( $funnel_id, $state['starter'] );
$type = sanitize_key( wp_unslash( $_POST['step_type'] ?? 'content' ) );
if ( ! in_array( $type, array( 'landing', 'offer', 'content', 'checkout', 'thank_you' ), true ) ) {
$type = 'content';
}
$method = sanitize_key( wp_unslash( $_POST['creation_method'] ?? 'blank' ) );
$page_id = 0;
if ( 'existing' === $method ) {
$page_id = absint( wp_unslash( $_POST['existing_page_id'] ?? 0 ) );
if ( ! $page_id || 'page' !== get_post_type( $page_id ) ) {
$this->redirect( 'error', __( 'Choose an existing WordPress page.', '5d-powers-for-divi' ), $funnel_id );
}
} elseif ( 'duplicate' === $method ) {
$source_id = sanitize_key( wp_unslash( $_POST['source_step_id'] ?? '' ) );
$source = $this->find_step( $steps, $source_id );
if ( ! $source ) {
$this->redirect( 'error', __( 'Choose a funnel step to duplicate.', '5d-powers-for-divi' ), $funnel_id );
}
$page_id = $this->duplicate_page( absint( $source['page_id'] ), get_the_title( $source['page_id'] ) . ' Copy' );
} else {
$title = substr( sanitize_text_field( wp_unslash( $_POST['new_page_title'] ?? __( 'New Funnel Page', '5d-powers-for-divi' ) ) ), 0, 100 );
$page_id = $this->create_blank_page( $title, $type );
}
if ( is_wp_error( $page_id ) ) {
$this->redirect( 'error', $page_id->get_error_message(), $funnel_id );
}
$this->assert_page_available( $page_id, $funnel_id );
$step_id = 'step-' . wp_generate_password( 10, false, false );
$profile_id = 'checkout' === $type ? $this->ensure_profile( array( 'name' => get_the_title( $funnel_id ) ), $funnel_id ) : 0;
if ( is_wp_error( $profile_id ) ) { $this->redirect( 'error', $profile_id->get_error_message(), $funnel_id ); }
$steps[] = array(
'id' => sanitize_key( $step_id ),
'type' => 'checkout' === $type ? 'checkout' : 'page',
'step_type' => $type,
'page_id' => absint( $page_id ),
'profile_id' => absint( $profile_id ),
'product_id' => 'checkout' === $type ? absint( $state['starter']['product_id'] ?? 0 ) : 0,
'checkout_layout' => 'two_column',
'label' => $this->step_type_label( $type ),
);
$this->save_steps( $funnel_id, $steps, $state['starter'] );
$this->redirect( 'step-added', '', $funnel_id );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
public function delete_step_action() {
$this->authorize();
// phpcs:disable WordPress.Security.NonceVerification.Missing -- authorize() verifies this admin-post request before input is read.
$funnel_id = $this->request_funnel_id();
$step_id = sanitize_key( wp_unslash( $_POST['step_id'] ?? '' ) );
$state = $this->state( $funnel_id );
$steps = $this->steps( $funnel_id, $state['starter'] );
if ( count( $steps ) <= 1 ) {
$this->redirect( 'error', __( 'A funnel must retain at least one step.', '5d-powers-for-divi' ), $funnel_id );
}
$remaining = array();
foreach ( $steps as $step ) {
if ( $step['id'] === $step_id ) {
Divi_Powers_Funnel_Meta::detach_page( $step['page_id'], $funnel_id );
continue;
}
$remaining[] = $step;
}
$this->save_steps( $funnel_id, $remaining, $state['starter'] );
$this->redirect( 'step-removed', '', $funnel_id );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
public function duplicate_step_action() {
$this->authorize();
// phpcs:disable WordPress.Security.NonceVerification.Missing -- authorize() verifies this admin-post request before input is read.
$funnel_id = $this->request_funnel_id();
$step_id = sanitize_key( wp_unslash( $_POST['step_id'] ?? '' ) );
$state = $this->state( $funnel_id );
$steps = $this->steps( $funnel_id, $state['starter'] );
$source = $this->find_step( $steps, $step_id );
if ( ! $source ) {
$this->redirect( 'error', __( 'The selected funnel step no longer exists.', '5d-powers-for-divi' ), $funnel_id );
}
$page_id = $this->duplicate_page( $source['page_id'], get_the_title( $source['page_id'] ) . ' Copy' );
if ( is_wp_error( $page_id ) ) {
$this->redirect( 'error', $page_id->get_error_message(), $funnel_id );
}
$copy = $source;
$copy['id'] = 'step-' . wp_generate_password( 10, false, false );
$copy['page_id'] = absint( $page_id );
if ( 'checkout' === $copy['step_type'] ) {
$profile_id = $this->ensure_profile( array( 'name' => get_the_title( $funnel_id ) ), $funnel_id );
if ( is_wp_error( $profile_id ) ) { $this->redirect( 'error', $profile_id->get_error_message(), $funnel_id ); }
$copy['profile_id'] = $profile_id;
}
$copy['label'] .= ' Copy';
$inserted = array();
foreach ( $steps as $step ) {
$inserted[] = $step;
if ( $step['id'] === $step_id ) {
$inserted[] = $copy;
}
}
$this->save_steps( $funnel_id, $inserted, $state['starter'] );
$this->redirect( 'step-duplicated', '', $funnel_id );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
public function reorder_action() {
$this->authorize();
// phpcs:disable WordPress.Security.NonceVerification.Missing -- authorize() verifies this admin-post request before input is read.
$funnel_id = $this->request_funnel_id();
$order_input = sanitize_text_field( wp_unslash( $_POST['step_order'] ?? '' ) );
$order = array_filter( array_map( 'sanitize_key', explode( ',', $order_input ) ) );
$state = $this->state( $funnel_id );
$steps = $this->steps( $funnel_id, $state['starter'] );
$map = array();
foreach ( $steps as $step ) {
$map[ $step['id'] ] = $step;
}
$sorted = array();
foreach ( $order as $id ) {
if ( isset( $map[ $id ] ) ) {
$sorted[] = $map[ $id ];
unset( $map[ $id ] );
}
}
$sorted = array_merge( $sorted, array_values( $map ) );
$this->save_steps( $funnel_id, $sorted, $state['starter'] );
wp_safe_redirect( $this->workspace_url( $funnel_id ) );
exit;
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
public function install( $name, $force = false, $funnel_id = 0, $create_new = false ) {
if ( ! class_exists( 'Divi_Powers_Funnel_Meta' ) ) {
return new WP_Error(
'divi_powers_plus_lite_update_required',
__( 'Update 5D Powers for Divi before creating the free funnel.', '5d-powers-for-divi' )
);
}
$name = $name ? substr( sanitize_text_field( $name ), 0, 80 ) : __( 'My First Funnel', '5d-powers-for-divi' );
$state = $this->state( $funnel_id );
$starter = $create_new ? array() : $state['starter'];
if ( $funnel_id && ! $this->is_basic_funnel( $funnel_id ) ) {
return new WP_Error( 'invalid_funnel', __( 'This funnel is not available in the basic workspace.', '5d-powers-for-divi' ) );
}
if ( ! $force && ! $create_new && ! empty( $starter['funnel_id'] ) ) { return $starter; }
$starter['name'] = $name;
foreach ( array( 'landing_page_id', 'offer_page_id', 'checkout_page_id', 'thank_you_page_id' ) as $key ) {
$context = get_post_meta( absint( $starter[$key] ?? 0 ), Divi_Powers_Funnel_Meta::CONTEXT_META, true );
if ( ! empty( $context['funnel_id'] ) && absint( $context['funnel_id'] ) !== absint( $starter['funnel_id'] ?? 0 ) ) { return new WP_Error( 'page_in_use', __( 'An original page now belongs to another funnel. Restore its assignment before rebuilding.', '5d-powers-for-divi' ) ); }
}
$specs = array(
'landing_page_id' => array( 'asset' => 'landing', 'slug' => 'divi-powers-starter-funnel-landing', 'title' => $name . ' — Landing' ),
'offer_page_id' => array( 'asset' => 'offer', 'slug' => 'divi-powers-starter-funnel-offer', 'title' => $name . ' — Offer' ),
'checkout_page_id' => array( 'asset' => 'checkout', 'slug' => 'divi-powers-starter-funnel-checkout', 'title' => $name . ' — Checkout' ),
'thank_you_page_id' => array( 'asset' => 'thank-you', 'slug' => 'divi-powers-starter-funnel-thank-you', 'title' => $name . ' — Thank You' ),
);
$created = array();
foreach ( $specs as $key => $spec ) {
$id = absint( $starter[ $key ] ?? 0 );
if ( ! $this->valid_asset( $id, 'page', $spec['asset'] ) ) {
$id = 0;
}
$is_new = false;
if ( ! $id ) {
$id = wp_insert_post( array( 'post_type' => 'page', 'post_status' => 'publish', 'post_title' => $spec['title'], 'post_name' => sanitize_title( $name . '-' . $spec['asset'] ), 'post_content' => '' ), true );
if ( is_wp_error( $id ) ) {
return $id;
}
update_post_meta( $id, self::STARTER_META, $spec['asset'] );
$is_new = true;
}
$starter[ $key ] = absint( $id );
$created[ $key ] = $is_new;
$this->configure_canvas( $id, $is_new );
}
$product_id = absint( $starter['product_id'] ?? 0 );
if ( ! $this->valid_asset( $product_id, 'product', 'product' ) ) {
$product_id = 0;
}
if ( ! $product_id ) {
$product_id = $this->create_product( $name );
if ( is_wp_error( $product_id ) ) {
return $product_id;
}
}
$starter['product_id'] = absint( $product_id );
$profile_id = $this->ensure_profile( $starter, absint( $starter['funnel_id'] ?? 0 ) );
if ( is_wp_error( $profile_id ) ) {
return $profile_id;
}
$starter['profile_id'] = absint( $profile_id );
$funnel_id = absint( $starter['funnel_id'] ?? 0 );
if ( ! $this->valid_asset( $funnel_id, 'fivedp_funnel', 'funnel' ) ) {
$funnel_id = 0;
}
if ( ! $funnel_id ) {
$funnel_id = wp_insert_post( array( 'post_type' => 'fivedp_funnel', 'post_status' => 'publish', 'post_title' => $name ), true );
if ( is_wp_error( $funnel_id ) ) {
return $funnel_id;
}
update_post_meta( $funnel_id, self::STARTER_META, 'funnel' );
}
$starter['funnel_id'] = absint( $funnel_id );
$this->save_state( $starter );
$default_steps = array(
array( 'id' => 'starter-landing', 'type' => 'page', 'step_type' => 'landing', 'page_id' => absint( $starter['landing_page_id'] ), 'profile_id' => 0, 'product_id' => 0, 'checkout_layout' => '', 'label' => __( 'Landing Page', '5d-powers-for-divi' ) ),
array( 'id' => 'starter-offer', 'type' => 'page', 'step_type' => 'offer', 'page_id' => absint( $starter['offer_page_id'] ), 'profile_id' => 0, 'product_id' => 0, 'checkout_layout' => '', 'label' => __( 'Offer Page', '5d-powers-for-divi' ) ),
array( 'id' => 'starter-checkout', 'type' => 'checkout', 'step_type' => 'checkout', 'page_id' => absint( $starter['checkout_page_id'] ), 'profile_id' => absint( $profile_id ), 'product_id' => absint( $product_id ), 'checkout_layout' => 'two_column', 'label' => __( 'Checkout', '5d-powers-for-divi' ) ),
array( 'id' => 'starter-thank-you', 'type' => 'page', 'step_type' => 'thank_you', 'page_id' => absint( $starter['thank_you_page_id'] ), 'profile_id' => 0, 'product_id' => 0, 'checkout_layout' => '', 'label' => __( 'Thank You', '5d-powers-for-divi' ) ),
);
$existing_steps = $this->steps( $funnel_id, $starter );
$steps = $force && $existing_steps ? $existing_steps : $default_steps;
foreach ( $steps as &$step ) {
if ( 'checkout' === $step['step_type'] && 'fivedp_woo_profile' !== get_post_type( absint( $step['profile_id'] ?? 0 ) ) ) { $step['profile_id'] = $profile_id; }
}
unset( $step );
$this->save_steps( $funnel_id, $steps, $starter );
$context = array(
'landing_url' => get_permalink( $starter['landing_page_id'] ),
'offer_url' => Divi_Powers_Funnel_Meta::next_url( $funnel_id, 'starter-landing' ),
'checkout_url' => Divi_Powers_Funnel_Meta::next_url( $funnel_id, 'starter-offer' ),
'thank_you_url' => get_permalink( $starter['thank_you_page_id'] ),
'starter_page_url' => $this->lite_starter_url(),
);
foreach ( $specs as $key => $spec ) {
$content = $this->layout_content( $spec['asset'], $context );
if ( ! $content ) {
return new WP_Error( 'plus_layout', __( 'A bundled starter layout could not be read.', '5d-powers-for-divi' ) );
}
$this->apply_content( $starter[ $key ], $spec['asset'], $content, $force || $created[ $key ] );
}
$starter['installed_at'] = time();
$starter['version'] = self::STARTER_VERSION;
$this->save_state( $starter );
return $starter;
}
private function save_steps( $funnel_id, $steps, $starter ) {
$funnel = get_post( $funnel_id );
if ( ! $funnel || ! $this->is_basic_funnel( $funnel_id ) ) {
return false;
}
$clean = array();
foreach ( array_values( $steps ) as $index => $step ) {
$type = sanitize_key( $step['step_type'] ?? 'content' );
if ( ! in_array( $type, array( 'landing', 'offer', 'content', 'checkout', 'thank_you' ), true ) ) {
$type = 'content';
}
$clean[] = array(
'id' => sanitize_key( $step['id'] ?? 'step-' . ( $index + 1 ) ),
'type' => 'checkout' === $type ? 'checkout' : 'page',
'step_type' => $type,
'page_id' => absint( $step['page_id'] ?? 0 ),
'profile_id' => 'checkout' === $type ? absint( $step['profile_id'] ?? $starter['profile_id'] ?? 0 ) : 0,
'product_id' => 'checkout' === $type ? absint( $step['product_id'] ?? $starter['product_id'] ?? 0 ) : 0,
'checkout_layout' => 'checkout' === $type && in_array( sanitize_key( $step['checkout_layout'] ?? '' ), array( 'classic', 'two_column', 'focused' ), true ) ? sanitize_key( $step['checkout_layout'] ) : ( 'checkout' === $type ? 'two_column' : '' ),
'label' => substr( sanitize_text_field( $step['label'] ?? $this->step_type_label( $type ) ), 0, 80 ),
'is_entry' => 0 === $index ? 1 : 0,
'is_goal' => 'thank_you' === $type ? 1 : 0,
'x' => 80 + ( 310 * $index ),
'y' => 160,
);
}
$edges = array();
for ( $index = 0; $index < count( $clean ) - 1; $index++ ) {
$edges[] = array( 'id' => 'edge-' . $clean[ $index ]['id'] . '-' . $clean[ $index + 1 ]['id'], 'source' => $clean[ $index ]['id'], 'target' => $clean[ $index + 1 ]['id'], 'label' => __( 'Continue', '5d-powers-for-divi' ) );
}
$definition = array( 'schema_version' => 3, 'nodes' => $clean, 'edges' => $edges, 'viewport' => array( 'x' => 0, 'y' => 0, 'zoom' => 1 ) );
update_post_meta( $funnel_id, '_divi_powers_funnel_definition', $definition );
update_post_meta( $funnel_id, '_divi_powers_funnel_description', __( 'A basic funnel managed with 5D Powers for Divi.', '5d-powers-for-divi' ) );
if ( ! get_post_meta( $funnel_id, '_divi_powers_funnel_status', true ) ) {
update_post_meta( $funnel_id, '_divi_powers_funnel_status', 'draft' );
}
update_post_meta( $funnel_id, self::STARTER_VERSION_META, self::STARTER_VERSION );
$this->sync_checkout( $funnel_id, $clean, $starter );
$this->sync_contexts( $funnel_id, $clean, $funnel->post_title );
delete_transient( 'divi_powers_active_entry_map' );
return true;
}
private function sync_checkout( $funnel_id, $steps, $starter ) {
$checkout = null;
$thank_you = 0;
foreach ( $steps as $step ) {
if ( 'checkout' === $step['step_type'] && ! $checkout ) {
$checkout = $step;
}
if ( 'thank_you' === $step['step_type'] && ! $thank_you ) {
$thank_you = absint( $step['page_id'] );
}
}
if ( ! $checkout ) {
delete_post_meta( $funnel_id, '_divi_powers_basic_checkout' );
delete_post_meta( $funnel_id, '_divi_powers_basic_checkouts' );
return;
}
$checkout_settings = array();
foreach ( $steps as $checkout ) {
if ( 'checkout' !== $checkout['step_type'] ) { continue; }
$profile_id = absint( $checkout['profile_id'] );
$definition = get_post_meta( $profile_id, '_divi_powers_woo_profile_definition', true );
$definition = is_array( $definition ) ? $definition : array();
$definition = array_merge(
array(
'schema_version' => 5, 'route_slug' => sanitize_title( get_the_title( $funnel_id ) ), 'category_ids' => array(), 'priority' => 90, 'cart_behavior' => 'replace', 'skip_cart' => 1, 'distraction_free' => 1, 'hide_coupon' => 0, 'hide_order_notes' => 0, 'checkout_grid' => array(), 'field_profile_id' => 0, 'payment_ruleset_id' => 0, 'shipping_ruleset_id' => 0,
'commerce_auto_complete_virtual' => 'inherit', 'commerce_auto_complete_downloadable' => 'inherit', 'commerce_auto_complete_zero' => 'inherit', 'commerce_force_quantity_one' => 'inherit', 'commerce_cart_isolation' => 'inherit', 'commerce_coupon_links' => 'inherit', 'commerce_hide_coupon_after_apply' => 'inherit', 'commerce_account_behavior' => 'inherit', 'commerce_order_button_text' => '',
),
$definition
);
$definition['checkout_page_id'] = absint( $checkout['page_id'] );
$definition['thank_you_page_id'] = $thank_you;
$definition['product_ids'] = array_filter( array( absint( $checkout['product_id'] ) ) );
$definition['funnel_ids'] = array( absint( $funnel_id ) );
$definition['checkout_layout'] = $checkout['checkout_layout'];
update_post_meta( $profile_id, '_divi_powers_woo_profile_definition', $definition );
update_post_meta( $profile_id, '_divi_powers_woo_profile_status', 'active' );
$checkout_settings[ absint( $checkout['page_id'] ) ] = array( 'layout' => $checkout['checkout_layout'], 'checkout_page_id' => absint( $checkout['page_id'] ), 'thank_you_page_id' => $thank_you, 'distraction_free' => 1, 'sticky_summary' => 1 );
}
update_post_meta( $funnel_id, '_divi_powers_basic_checkouts', $checkout_settings );
update_post_meta( $funnel_id, '_divi_powers_basic_checkout', reset( $checkout_settings ) );
}
private function sync_contexts( $funnel_id, $steps, $name ) {
foreach ( array_values( $steps ) as $index => $step ) {
if ( empty( $step['page_id'] ) ) {
continue;
}
Divi_Powers_Funnel_Meta::assign_page(
$step['page_id'],
array(
'funnel_id' => $funnel_id,
'funnel_name' => $name,
'step_id' => $step['id'],
'step_type' => $step['step_type'],
'step_label' => $step['label'],
'step_position' => $index + 1,
'is_entry' => 0 === $index,
'is_goal' => 'thank_you' === $step['step_type'],
'analytics_enabled' => 1,
)
);
}
}
private function steps( $funnel_id, $starter ) {
$definition = get_post_meta( absint( $funnel_id ), '_divi_powers_funnel_definition', true );
$nodes = is_array( $definition ) && isset( $definition['nodes'] ) && is_array( $definition['nodes'] ) ? array_values( $definition['nodes'] ) : array();
if ( ! $nodes && ! empty( $starter['landing_page_id'] ) ) {
$nodes = array(
array( 'id' => 'starter-landing', 'type' => 'page', 'step_type' => 'landing', 'page_id' => $starter['landing_page_id'], 'label' => 'Landing Page' ),
array( 'id' => 'starter-offer', 'type' => 'page', 'step_type' => 'offer', 'page_id' => $starter['offer_page_id'], 'label' => 'Offer Page' ),
array( 'id' => 'starter-checkout', 'type' => 'checkout', 'step_type' => 'checkout', 'page_id' => $starter['checkout_page_id'], 'profile_id' => $starter['profile_id'], 'product_id' => $starter['product_id'], 'checkout_layout' => 'two_column', 'label' => 'Checkout' ),
array( 'id' => 'starter-thank-you', 'type' => 'page', 'step_type' => 'thank_you', 'page_id' => $starter['thank_you_page_id'], 'label' => 'Thank You' ),
);
}
$out = array();
foreach ( $nodes as $index => $node ) {
$type = sanitize_key( $node['step_type'] ?? '' );
if ( ! $type ) {
$legacy_label = strtolower( (string) ( $node['label'] ?? '' ) );
if ( 'checkout' === sanitize_key( $node['type'] ?? '' ) ) {
$type = 'checkout';
} elseif ( ! empty( $node['is_goal'] ) || false !== strpos( $legacy_label, 'thank' ) ) {
$type = 'thank_you';
} elseif ( false !== strpos( $legacy_label, 'offer' ) ) {
$type = 'offer';
} elseif ( false !== strpos( $legacy_label, 'landing' ) || 0 === $index ) {
$type = 'landing';
} else {
$type = 'content';
}
}
$page_id = absint( $node['page_id'] ?? 0 );
if ( 'checkout' === $type && ! $page_id && ! empty( $node['profile_id'] ) ) {
$profile = get_post_meta( absint( $node['profile_id'] ), '_divi_powers_woo_profile_definition', true );
$page_id = is_array( $profile ) ? absint( $profile['checkout_page_id'] ?? 0 ) : 0;
}
$out[] = array(
'id' => sanitize_key( $node['id'] ?? 'step-' . ( $index + 1 ) ),
'type' => 'checkout' === $type ? 'checkout' : 'page',
'step_type' => $type,
'page_id' => $page_id,
'profile_id' => absint( $node['profile_id'] ?? $starter['profile_id'] ?? 0 ),
'product_id' => absint( $node['product_id'] ?? ( 'checkout' === $type ? $starter['product_id'] ?? 0 : 0 ) ),
'checkout_layout' => sanitize_key( $node['checkout_layout'] ?? ( 'checkout' === $type ? 'two_column' : '' ) ),
'label' => sanitize_text_field( $node['label'] ?? $this->step_type_label( $type ) ),
);
}
return $out;
}
private function funnel( $funnel_id, $starter ) {
$post = get_post( $funnel_id );
if ( ! $post || ! $this->is_basic_funnel( $funnel_id ) ) {
return null;
}
return array( 'id' => $post->ID, 'title' => $post->post_title, 'status' => sanitize_key( get_post_meta( $post->ID, '_divi_powers_funnel_status', true ) ?: 'draft' ), 'steps' => $this->steps( $post->ID, $starter ) );
}
private function checklist( $funnel, $starter ) {
$steps = $funnel['steps'];
$has_landing = false;
$has_checkout = false;
$has_thank_you = false;
$product = 0;
foreach ( $steps as $step ) {
$available = $step['page_id'] && 'publish' === get_post_status( $step['page_id'] );
if ( 'landing' === $step['step_type'] && $available ) { $has_landing = true; }
if ( 'checkout' === $step['step_type'] && $available ) { $has_checkout = true; $product = absint( $step['product_id'] ); }
if ( 'thank_you' === $step['step_type'] && $available ) { $has_thank_you = true; }
}
$gateway = false;
if ( function_exists( 'WC' ) && WC() && WC()->payment_gateways() ) {
foreach ( WC()->payment_gateways()->payment_gateways() as $payment_gateway ) {
if ( isset( $payment_gateway->enabled ) && 'yes' === $payment_gateway->enabled ) { $gateway = true; break; }
}
}
$orders = Divi_Powers_Free_Checkout::metrics( $funnel['id'] )['orders'];
return array(
array( 'label' => __( 'Landing page connected', '5d-powers-for-divi' ), 'detail' => __( 'A published entry page is available.', '5d-powers-for-divi' ), 'complete' => $has_landing ),
array( 'label' => __( 'Checkout page connected', '5d-powers-for-divi' ), 'detail' => __( 'A published checkout page is available.', '5d-powers-for-divi' ), 'complete' => $has_checkout ),
array( 'label' => __( 'Product selected', '5d-powers-for-divi' ), 'detail' => __( 'The checkout step has a WooCommerce product.', '5d-powers-for-divi' ), 'complete' => $product && 'trash' !== get_post_status( $product ) ),
array( 'label' => __( 'Thank-you page connected', '5d-powers-for-divi' ), 'detail' => __( 'A published completion page is available.', '5d-powers-for-divi' ), 'complete' => $has_thank_you ),
array( 'label' => __( 'Payment gateway available', '5d-powers-for-divi' ), 'detail' => __( 'At least one WooCommerce payment gateway is enabled.', '5d-powers-for-divi' ), 'complete' => $gateway || ( $product && '0' === (string) get_post_meta( $product, '_price', true ) ) ),
array( 'label' => __( 'Test order completed', '5d-powers-for-divi' ), 'detail' => __( 'Run the entire journey once before launch.', '5d-powers-for-divi' ), 'complete' => absint( $orders ) > 0 ),
array( 'label' => __( 'Funnel published', '5d-powers-for-divi' ), 'detail' => __( 'Set the funnel status to Live.', '5d-powers-for-divi' ), 'complete' => 'active' === $funnel['status'] ),
);
}
private function ensure_profile( $starter, $funnel_id ) {
$profile_id = absint( $starter['profile_id'] ?? 0 );
if ( ! $this->valid_asset( $profile_id, 'fivedp_woo_profile', 'profile' ) ) {
$profile_id = 0;
}
if ( ! $profile_id ) {
$profile_id = wp_insert_post( array( 'post_type' => 'fivedp_woo_profile', 'post_status' => 'publish', 'post_title' => ( $starter['name'] ?? __( 'My First Funnel', '5d-powers-for-divi' ) ) . ' — Checkout Journey' ), true );
if ( is_wp_error( $profile_id ) ) {
return $profile_id;
}
update_post_meta( $profile_id, self::STARTER_META, 'profile' );
}
update_post_meta( $profile_id, '_divi_powers_woo_profile_description', __( 'One-site free checkout journey managed inside the 5D Powers for Divi funnel workspace.', '5d-powers-for-divi' ) );
update_post_meta( $profile_id, '_divi_powers_woo_profile_status', 'active' );
update_post_meta( $profile_id, self::STARTER_VERSION_META, self::STARTER_VERSION );
return absint( $profile_id );
}
private function create_blank_page( $title, $type ) {
$content = '';
if ( 'checkout' === $type ) { $content = '[woocommerce_checkout]'; }
if ( 'thank_you' === $type ) { $content = '[divi_powers_order_confirmation]'; }
$id = wp_insert_post( array( 'post_type' => 'page', 'post_status' => 'draft', 'post_title' => $title, 'post_content' => $content ), true );
if ( ! is_wp_error( $id ) ) { $this->configure_canvas( $id, true ); }
return $id;
}
private function duplicate_page( $page_id, $title ) {
$source = get_post( $page_id );
if ( ! $source || 'page' !== $source->post_type ) {
return new WP_Error(
'divi_powers_plus_page_duplicate',
__( 'The source page could not be duplicated.', '5d-powers-for-divi' )
);
}
/*
* wp_insert_post() expects slashed post data. Divi 5 block documents can
* contain JSON Unicode escape sequences such as \u003c. Passing the raw
* database value causes WordPress to strip those backslashes, leaving
* visible text such as u003c instead of valid Divi block comments.
*/
$new_id = wp_insert_post(
array(
'post_type' => 'page',
'post_status' => 'draft',
'post_title' => $title,
'post_content' => wp_slash( $source->post_content ),
'post_excerpt' => wp_slash( $source->post_excerpt ),
'post_content_filtered' => wp_slash( $source->post_content_filtered ),
'post_author' => get_current_user_id() ? get_current_user_id() : absint( $source->post_author ),
'post_parent' => absint( $source->post_parent ),
'menu_order' => intval( $source->menu_order ),
'comment_status' => $source->comment_status,
'ping_status' => $source->ping_status,
),
true
);
if ( is_wp_error( $new_id ) ) {
return $new_id;
}
$this->copy_page_meta( $page_id, $new_id );
$this->copy_page_terms( $page_id, $new_id );
clean_post_cache( $new_id );
do_action( 'divi_powers_page_duplicated', $new_id, $page_id, 'plus' );
return absint( $new_id );
}
/**
* Copy the complete page-level record used by Divi while excluding volatile
* editor locks and funnel ownership that must be regenerated for the copy.
*/
private function copy_page_meta( $source_id, $destination_id ) {
$excluded = array(
'_edit_lock',
'_edit_last',
'_wp_old_slug',
'_wp_trash_meta_status',
'_wp_trash_meta_time',
'_encloseme',
'_pingme',
self::STARTER_META,
self::STARTER_VERSION_META,
self::NATIVE_LAYOUT_META,
self::GENERATED_HASH_META,
Divi_Powers_Funnel_Meta::CONTEXT_META,
'_divi_powers_funnel_step_type',
);
$metadata = get_post_meta( $source_id );
foreach ( $metadata as $key => $values ) {
if ( in_array( $key, $excluded, true ) ) {
continue;
}
foreach ( $values as $value ) {
$value = maybe_unserialize( $value );
/*
* Metadata APIs also strip slashes from string values. Preserve
* Divi JSON, custom CSS, block data, and encoded attributes.
*/
if ( is_string( $value ) ) {
$value = wp_slash( $value );
}
add_post_meta( $destination_id, $key, $value );
}
}
}
/**
* Preserve page taxonomies used by third-party plugins and custom workflows.
*/
private function copy_page_terms( $source_id, $destination_id ) {
foreach ( get_object_taxonomies( 'page' ) as $taxonomy ) {
$term_ids = wp_get_object_terms(
$source_id,
$taxonomy,
array( 'fields' => 'ids' )
);
if ( is_wp_error( $term_ids ) || ! $term_ids ) {
continue;
}
wp_set_object_terms(
$destination_id,
array_map( 'intval', $term_ids ),
$taxonomy,
false
);
}
}
private function find_step( $steps, $id ) {
foreach ( $steps as $step ) {
if ( $step['id'] === $id ) { return $step; }
}
return null;
}
private function state( $funnel_id = 0 ) {
$state = get_option( self::ONBOARDING_OPTION, array() );
$state = is_array( $state ) ? $state : array();
$legacy = isset( $state['starter'] ) && is_array( $state['starter'] ) ? $state['starter'] : array();
$legacy_id = absint( $legacy['funnel_id'] ?? 0 );
if ( $legacy_id && 'fivedp_funnel' === get_post_type( $legacy_id ) && ! metadata_exists( 'post', $legacy_id, self::STATE_META ) ) {
update_post_meta( $legacy_id, self::STATE_META, $legacy );
if ( ! metadata_exists( 'post', $legacy_id, '_divi_powers_basic_checkout' ) ) {
$checkout = get_option( 'divi_powers_plus_checkout', array() );
if ( is_array( $checkout ) ) { update_post_meta( $legacy_id, '_divi_powers_basic_checkout', $checkout ); }
}
if ( ! metadata_exists( 'post', $legacy_id, '_divi_powers_basic_metrics' ) ) {
update_post_meta( $legacy_id, '_divi_powers_basic_metrics', get_option( 'divi_powers_plus_metrics', array() ) );
}
}
$saved = $funnel_id ? get_post_meta( $funnel_id, self::STATE_META, true ) : $legacy;
$state['starter'] = is_array( $saved ) ? $saved : array();
return $state;
}
private function save_state( $starter ) {
$id = absint( $starter['funnel_id'] ?? 0 );
if ( ! $id ) { return; }
update_post_meta( $id, self::STATE_META, $starter );
$state = get_option( self::ONBOARDING_OPTION, array() );
$state = is_array( $state ) ? $state : array();
// Preserve the first funnel reference used by existing onboarding and integrations.
if ( empty( $state['starter']['funnel_id'] ) || $id === absint( $state['starter']['funnel_id'] ) ) {
$state['starter'] = $starter;
$state['starter_version'] = self::STARTER_VERSION;
update_option( self::ONBOARDING_OPTION, $state, false );
}
}
public function is_basic_funnel( $id ) {
$this->state( $id );
if ( ! $id || 'fivedp_funnel' !== get_post_type( $id ) || 'trash' === get_post_status( $id ) || ! metadata_exists( 'post', $id, self::STATE_META ) || 'pro' === get_post_meta( $id, '_divi_powers_funnel_editor', true ) ) { return false; }
$definition = get_post_meta( $id, '_divi_powers_funnel_definition', true );
if ( ! is_array( $definition ) ) { return true; }
$nodes = array_values( $definition['nodes'] ?? array() );
if ( array_filter( array_diff_key( $definition, array_flip( array( 'schema_version', 'nodes', 'edges', 'viewport' ) ) ) ) ) { return false; }
foreach ( $nodes as $node ) {
$allowed = array( 'id', 'type', 'step_type', 'page_id', 'profile_id', 'product_id', 'checkout_layout', 'label', 'is_entry', 'is_goal', 'x', 'y' );
if ( array_filter( array_diff_key( $node, array_flip( $allowed ) ) ) ) { return false; }
if ( ! in_array( $node['type'] ?? '', array( 'page', 'checkout' ), true ) || ! empty( $node['conditions'] ) || ! empty( $node['variants'] ) ) { return false; }
}
$edges = array_values( $definition['edges'] ?? array() );
if ( count( $edges ) !== max( 0, count( $nodes ) - 1 ) ) { return false; }
foreach ( $edges as $i => $edge ) {
if ( array_filter( array_diff_key( $edge, array_flip( array( 'id', 'source', 'target', 'label' ) ) ) ) ) { return false; }
if ( ( $edge['source'] ?? '' ) !== ( $nodes[$i]['id'] ?? '' ) || ( $edge['target'] ?? '' ) !== ( $nodes[$i+1]['id'] ?? '' ) || ! empty( $edge['conditions'] ) ) { return false; }
}
return true;
}
private function assert_page_available( $page_id, $funnel_id, $step_id = '' ) {
$context = get_post_meta( $page_id, Divi_Powers_Funnel_Meta::CONTEXT_META, true );
if ( is_array( $context ) && ! empty( $context['funnel_id'] ) && ( absint( $context['funnel_id'] ) !== absint( $funnel_id ) || (string) ( $context['step_id'] ?? '' ) !== (string) $step_id ) ) {
wp_die( esc_html__( 'This page belongs to another funnel step. Duplicate the page or choose an unassigned page.', '5d-powers-for-divi' ) );
}
}
private function request_funnel_id() {
$id = absint( wp_unslash( $_POST['funnel_id'] ?? 0 ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Every caller invokes authorize() before this helper.
if ( ! $this->is_basic_funnel( $id ) ) { wp_die( esc_html__( 'Choose a basic funnel managed by 5D Powers for Divi.', '5d-powers-for-divi' ) ); }
return $id;
}
private function pages() {
return get_posts( array( 'post_type' => 'page', 'post_status' => array( 'publish', 'draft', 'private' ), 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC' ) );
}
private function products() {
return get_posts( array( 'post_type' => 'product', 'post_status' => array( 'publish', 'draft', 'private' ), 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC' ) );
}
private function entry_page( $steps ) {
return ! empty( $steps[0]['page_id'] ) ? absint( $steps[0]['page_id'] ) : 0;
}
private function step_type_label( $type ) {
$labels = array( 'landing' => __( 'Landing', '5d-powers-for-divi' ), 'offer' => __( 'Offer', '5d-powers-for-divi' ), 'content' => __( 'Content', '5d-powers-for-divi' ), 'checkout' => __( 'Checkout', '5d-powers-for-divi' ), 'thank_you' => __( 'Thank You', '5d-powers-for-divi' ) );
return $labels[ $type ] ?? __( 'Page', '5d-powers-for-divi' );
}
private function step_icon( $type ) {
$icons = array( 'landing' => '⌂', 'offer' => '✦', 'content' => '▤', 'checkout' => '▣', 'thank_you' => '✓' );
return $icons[ $type ] ?? '○';
}
private function workspace_url( $funnel_id ) {
return admin_url( 'admin.php?page=divi-powers-plus-funnel&funnel_id=' . absint( $funnel_id ) );
}
private function tab_url( $funnel_id, $tab ) {
return add_query_arg( 'tab', sanitize_key( $tab ), $this->workspace_url( $funnel_id ) );
}
private function redirect( $result, $message = '', $funnel_id = 0, $tab = 'build' ) {
$url = $funnel_id ? $this->tab_url( $funnel_id, $tab ) : admin_url( 'admin.php?page=divi-powers-plus-funnel' );
$args = array( 'dp_plus_result' => sanitize_key( $result ) );
if ( $message ) { $args['message'] = rawurlencode( $message ); }
wp_safe_redirect( add_query_arg( $args, $url ) );
exit;
}
private function notice() {
$result = sanitize_key( wp_unslash( $_GET['dp_plus_result'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! $result ) { return ''; }
$messages = array(
'installed' => __( 'Your funnel is ready.', '5d-powers-for-divi' ),
'rebuilt' => __( 'The four original starter pages were rebuilt.', '5d-powers-for-divi' ),
'saved' => __( 'Funnel settings saved.', '5d-powers-for-divi' ),
'step-saved' => __( 'Step settings saved.', '5d-powers-for-divi' ),
'step-added' => __( 'The new step was added.', '5d-powers-for-divi' ),
'step-removed' => __( 'The step was removed from the funnel. Its WordPress page was kept.', '5d-powers-for-divi' ),
'step-duplicated' => __( 'The step and its page were duplicated as drafts.', '5d-powers-for-divi' ),
);
$message = 'error' === $result ? sanitize_text_field( rawurldecode( wp_unslash( $_GET['message'] ?? '' ) ) ) : ( $messages[ $result ] ?? __( 'Funnel updated.', '5d-powers-for-divi' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Read-only admin notice parameter is sanitized here and escaped below.
return '' . esc_html( $message ) . '
';
}
private function authorize() {
if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You are not allowed to manage funnels.', '5d-powers-for-divi' ) ); }
check_admin_referer( self::NONCE_ACTION, 'nonce' );
}
private function create_product( $name ) {
if ( class_exists( 'WC_Product_Simple' ) ) {
$product = new WC_Product_Simple();
$product->set_name( $name . ' — Free Test Product' ); $product->set_status( 'publish' ); $product->set_catalog_visibility( 'hidden' ); $product->set_regular_price( '0' ); $product->set_price( '0' ); $product->set_virtual( true ); $product->set_sold_individually( true );
$id = $product->save();
} else {
$id = wp_insert_post( array( 'post_type' => 'product', 'post_status' => 'publish', 'post_title' => $name . ' — Free Test Product' ), true );
if ( ! is_wp_error( $id ) ) { update_post_meta( $id, '_regular_price', '0' ); update_post_meta( $id, '_price', '0' ); update_post_meta( $id, '_virtual', 'yes' ); update_post_meta( $id, '_sold_individually', 'yes' ); update_post_meta( $id, '_visibility', 'hidden' ); }
}
if ( is_wp_error( $id ) ) { return $id; }
update_post_meta( $id, self::STARTER_META, 'product' ); update_post_meta( $id, self::STARTER_VERSION_META, self::STARTER_VERSION );
return absint( $id );
}
private function layout_content( $type, $context ) {
$files = array( 'landing' => 'starter-funnel-landing.json', 'offer' => 'starter-funnel-offer.json', 'checkout' => 'starter-funnel-checkout.json', 'thank-you' => 'starter-funnel-thank-you.json' );
$path = DIVI_POWERS_PATH . 'assets/layouts/' . ( $files[ $type ] ?? '' );
if ( ! is_readable( $path ) ) { return ''; }
$layout = json_decode( file_get_contents( $path ), true ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$content = is_array( $layout ) && ! empty( $layout['data'] ) ? reset( $layout['data'] ) : '';
if ( ! is_string( $content ) || false === strpos( $content, '