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 ); ?>

post_title ); ?>

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' ); ?> ·

post_status ) : ?>
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, '