plugin = $plugin; } public function register() { add_action( 'admin_menu', array( $this, 'add_menu' ) ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_init', array( $this, 'migrate_legacy_checkout_shortcode' ), 30 ); add_action( 'admin_enqueue_scripts', array( $this, 'admin_assets' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'frontend_assets' ), 40 ); add_shortcode( 'divi_powers_order_confirmation', array( $this, 'confirmation_shortcode' ) ); add_filter( 'woocommerce_is_checkout', array( $this, 'is_checkout' ) ); add_filter( 'woocommerce_get_checkout_url', array( $this, 'checkout_url' ), 15 ); add_filter( 'woocommerce_get_checkout_order_received_url', array( $this, 'received_url' ), 15, 2 ); add_filter( 'woocommerce_get_return_url', array( $this, 'gateway_return_url' ), 15, 2 ); add_filter( 'body_class', array( $this, 'body_classes' ) ); add_action( 'template_redirect', array( $this, 'normalize_legacy_received_url' ), 0 ); add_action( 'template_redirect', array( $this, 'capture_checkout_visit' ), 5 ); add_action( 'woocommerce_checkout_create_order', array( $this, 'attribute_order' ), 15, 2 ); add_action( 'woocommerce_checkout_order_processed', array( $this, 'count_order' ), 40, 3 ); } public static function defaults() { return array( 'layout' => 'two_column', 'checkout_page_id' => 0, 'thank_you_page_id' => 0, 'distraction_free' => 1, 'sticky_summary' => 1 ); } public function settings( $funnel_id = 0 ) { $saved = get_option( self::OPTION_KEY, array() ); if ( ! is_admin() || $funnel_id ) { $id = $funnel_id ?: $this->current_funnel_id(); $local = $id ? get_post_meta( $id, '_divi_powers_basic_checkout', true ) : null; $checkouts = $id ? get_post_meta( $id, '_divi_powers_basic_checkouts', true ) : array(); $page_id = get_queried_object_id(); if ( is_array( $checkouts ) && isset( $checkouts[ $page_id ] ) ) { $local = $checkouts[ $page_id ]; } if ( is_array( $local ) && $local ) { $saved = $local; } elseif ( $id ) { $saved = array(); } } return wp_parse_args( is_array( $saved ) ? $this->sanitize( $saved ) : array(), self::defaults() ); } public function register_settings() { register_setting( 'divi_powers_plus_checkout_group', self::OPTION_KEY, array( 'type' => 'array', 'sanitize_callback' => array( $this, 'sanitize' ), 'default' => self::defaults() ) ); } public function sanitize( $input ) { $input = is_array( $input ) ? $input : array(); $layout = sanitize_key( $input['layout'] ?? 'two_column' ); if ( ! in_array( $layout, array( 'classic', 'two_column', 'focused' ), true ) ) { $layout = 'two_column'; } return array( 'layout' => $layout, 'checkout_page_id' => absint( $input['checkout_page_id'] ?? 0 ), 'thank_you_page_id' => absint( $input['thank_you_page_id'] ?? 0 ), 'distraction_free' => empty( $input['distraction_free'] ) ? 0 : 1, 'sticky_summary' => empty( $input['sticky_summary'] ) ? 0 : 1, ); } public function add_menu() { add_submenu_page( 'divi-powers', __( 'Checkout Layouts', '5d-powers-for-divi' ), __( 'Checkout Layouts', '5d-powers-for-divi' ), 'manage_options', 'divi-powers-plus-checkout', array( $this, 'render_page' ) ); } public function admin_assets() { $page = sanitize_key( wp_unslash( $_GET['page'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( 'divi-powers-plus-checkout' !== $page ) { return; } wp_enqueue_style( 'divi-powers-free-commerce-admin', DIVI_POWERS_URL . 'assets/css/free-commerce-admin.css', array( 'divi-powers-admin' ), DIVI_POWERS_VERSION ); } public function frontend_assets() { if ( ! $this->is_configured_checkout_page() && ! $this->is_configured_thank_you_page() ) { return; } wp_enqueue_style( 'divi-powers-free-checkout', DIVI_POWERS_URL . 'assets/css/free-checkout.css', array(), DIVI_POWERS_VERSION ); } public function render_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $settings = $this->settings(); $pages = get_pages( array( 'sort_column' => 'post_title', 'post_status' => array( 'publish', 'draft', 'private' ) ) ); ?>

layout_card( 'classic', __( 'Classic Enhanced', '5d-powers-for-divi' ), __( 'A cleaned-up traditional checkout with stronger spacing, field hierarchy, and order-summary styling.', '5d-powers-for-divi' ), $settings['layout'] ); ?> layout_card( 'two_column', __( 'Modern Two-Column', '5d-powers-for-divi' ), __( 'Customer and payment details on the left with a clear, optionally sticky order summary on the right.', '5d-powers-for-divi' ), $settings['layout'] ); ?> layout_card( 'focused', __( 'Focused Single-Column', '5d-powers-for-divi' ), __( 'A narrow distraction-free checkout for virtual products, services, courses, tickets, and lead magnets.', '5d-powers-for-divi' ), $settings['layout'] ); ?>

esc_like( $needle ) . '%'; $cursor = 0 === strpos( $migration_state, 'cursor:' ) ? absint( substr( $migration_state, 7 ) ) : 0; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- A one-time migration must inspect persisted content and should not cache the result. $ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE ID > %d AND post_content LIKE %s AND post_type <> %s ORDER BY ID ASC LIMIT 100", $cursor, $like, 'revision' ) ); foreach ( $ids as $post_id ) { $cursor = max( $cursor, absint( $post_id ) ); $post = get_post( absint( $post_id ) ); if ( ! $post || false === strpos( (string) $post->post_content, $needle ) ) { continue; } $current = (string) $post->post_content; $stored_hash = (string) get_post_meta( $post->ID, '_divi_powers_generated_content_hash', true ); $updated = str_replace( $needle, '[woocommerce_checkout]', $current ); $result = wp_update_post( array( 'ID' => $post->ID, 'post_content' => wp_slash( $updated ), ), true ); if ( ! is_wp_error( $result ) && $stored_hash && hash_equals( $stored_hash, hash( 'sha256', $current ) ) ) { update_post_meta( $post->ID, '_divi_powers_generated_content_hash', hash( 'sha256', $updated ) ); } } update_option( self::SHORTCODE_MIGRATION_OPTION, count( $ids ) < 100 ? '1' : 'cursor:' . $cursor, false ); } public function confirmation_shortcode() { if ( ! function_exists( 'wc_get_order' ) ) { return wp_kses_post( '

' . esc_html__( 'WooCommerce order confirmation is unavailable.', '5d-powers-for-divi' ) . '

' ); } $order_id = absint( $_GET['dp_order'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! $order_id ) { $order_id = absint( get_query_var( 'order-received' ) ); } if ( ! $order_id ) { $order_id = absint( $_GET['order-received'] ?? $_GET['order_id'] ?? 0 ); } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $key = wc_clean( sanitize_text_field( wp_unslash( $_GET['key'] ?? '' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $order = $order_id ? wc_get_order( $order_id ) : null; if ( ! $order || ( $key && ! hash_equals( $order->get_order_key(), $key ) ) ) { return wp_kses_post( '

' . esc_html__( 'Your verified order details will appear here after checkout.', '5d-powers-for-divi' ) . '

' ); } if ( ! $key && ! current_user_can( 'manage_woocommerce' ) && ( ! get_current_user_id() || absint( $order->get_user_id() ) !== get_current_user_id() ) ) { return wp_kses_post( '

' . esc_html__( 'Order verification is required.', '5d-powers-for-divi' ) . '

' ); } $date_created = $order->get_date_created(); $status = function_exists( 'wc_get_order_status_name' ) ? wc_get_order_status_name( $order->get_status() ) : $order->get_status(); $html_safe = '
'; $html_safe .= '

' . esc_html__( 'Thank you. Your order has been received.', '5d-powers-for-divi' ) . '

'; $html_safe .= ''; $html_safe .= '

' . esc_html__( 'Order summary', '5d-powers-for-divi' ) . '

'; return wp_kses_post( $html_safe ); } public function is_checkout( $is_checkout ) { return $is_checkout || $this->is_configured_checkout_page(); } public function checkout_url( $url ) { if ( ! $this->runtime_enabled() ) { return $url; } $page_id = absint( $this->settings()['checkout_page_id'] ); return $page_id && 'publish' === get_post_status( $page_id ) ? get_permalink( $page_id ) : $url; } public function received_url( $url, $order ) { return $this->custom_received_url( $url, $order ); } public function gateway_return_url( $url, $order ) { return $this->custom_received_url( $url, $order ); } private function custom_received_url( $url, $order ) { if ( ! $this->runtime_enabled() || ! $order ) { return $url; } // The order owns its destination; another browser visit must never change it. $funnel_id = absint( $order->get_meta( self::ORDER_META, true ) ); $snapshot = absint( $order->get_meta( '_divi_powers_basic_thank_you_page', true ) ); $settings = $funnel_id ? $this->settings( $funnel_id ) : $this->sanitize( get_option( self::OPTION_KEY, array() ) ); $page_id = $snapshot ?: absint( $settings['thank_you_page_id'] ); if ( ! $page_id || ! $order || 'publish' !== get_post_status( $page_id ) ) { return $url; } return add_query_arg( array( 'dp_order' => $order->get_id(), 'key' => $order->get_order_key() ), get_permalink( $page_id ) ); } /** * Convert older custom thank-you links that used WooCommerce's reserved * order-received endpoint variable. On a normal WordPress page that query * variable can make WooCommerce reinterpret the request as an endpoint and * mark the page as a 404 before Divi renders it. */ public function normalize_legacy_received_url() { if ( is_admin() || wp_doing_ajax() || empty( $_GET['order-received'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $page_id = absint( $this->settings()['thank_you_page_id'] ); if ( ! $page_id || 'publish' !== get_post_status( $page_id ) ) { return; } $page_url = get_permalink( $page_id ); if ( ! $page_url ) { return; } $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_url( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $request_path = wp_parse_url( $request_uri, PHP_URL_PATH ); $page_path = wp_parse_url( $page_url, PHP_URL_PATH ); if ( untrailingslashit( rawurldecode( (string) $request_path ) ) !== untrailingslashit( rawurldecode( (string) $page_path ) ) ) { return; } $order_id = absint( $_GET['order-received'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $key = wc_clean( sanitize_text_field( wp_unslash( $_GET['key'] ?? '' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! $order_id || ! $key || ! function_exists( 'wc_get_order' ) ) { return; } $order = wc_get_order( $order_id ); if ( ! $order || ! hash_equals( (string) $order->get_order_key(), (string) $key ) ) { return; } wp_safe_redirect( add_query_arg( array( 'dp_order' => $order_id, 'key' => $key, ), $page_url ) ); exit; } public function body_classes( $classes ) { if ( $this->is_configured_checkout_page() ) { $settings = $this->settings(); $classes[] = 'dp-plus-checkout-page'; $classes[] = 'dp-plus-checkout-layout-' . sanitize_html_class( $settings['layout'] ); if ( $settings['distraction_free'] ) { $classes[] = 'dp-plus-checkout-distraction-free'; } if ( $settings['sticky_summary'] ) { $classes[] = 'dp-plus-checkout-sticky-summary'; } } if ( $this->is_configured_thank_you_page() ) { $classes[] = 'dp-plus-thank-you-page'; } return $classes; } public function capture_checkout_visit() { if ( function_exists( 'wc_get_page_id' ) && is_page( wc_get_page_id( 'checkout' ) ) && ! $this->page_funnel_id( get_queried_object_id() ) && function_exists( 'WC' ) && WC() && WC()->session ) { WC()->session->set( self::SESSION_CONTEXT, 0 ); } if ( ! $this->is_configured_checkout_page() || is_admin() || wp_doing_ajax() ) { return; } $funnel_id = $this->page_funnel_id( get_queried_object_id() ); if ( function_exists( 'WC' ) && WC() && WC()->session ) { $marker = 'dp_plus_view_' . $funnel_id; WC()->session->set( self::SESSION_CONTEXT, $funnel_id ); if ( WC()->session->get( $marker ) ) { return; } WC()->session->set( $marker, 1 ); } $this->increment_metric( 'checkout_visits', $funnel_id ); } public function attribute_order( $order, $data ) { unset( $data ); $funnel_id = function_exists( 'WC' ) && WC() && WC()->session ? absint( WC()->session->get( self::SESSION_CONTEXT ) ) : 0; if ( $this->runtime_enabled() && $this->valid_funnel( $funnel_id ) ) { $order->update_meta_data( self::ORDER_META, $funnel_id ); $order->update_meta_data( '_divi_powers_basic_thank_you_page', absint( $this->settings( $funnel_id )['thank_you_page_id'] ) ); } } public function count_order( $order_id, $posted_data, $order ) { unset( $posted_data ); $order = $order instanceof WC_Order ? $order : wc_get_order( $order_id ); if ( ! $order || ! $order->get_meta( self::ORDER_META, true ) || $order->get_meta( self::ORDER_COUNTED_META, true ) ) { return; } $order->update_meta_data( self::ORDER_COUNTED_META, 1 ); $order->save(); $this->increment_metric( 'orders', absint( $order->get_meta( self::ORDER_META, true ) ) ); } public static function metrics( $funnel_id = 0 ) { $metrics = $funnel_id ? get_post_meta( $funnel_id, '_divi_powers_basic_metrics', true ) : get_option( self::METRICS_OPTION, array() ); if ( $funnel_id && ! metadata_exists( 'post', $funnel_id, '_divi_powers_basic_metrics' ) ) { $legacy = get_option( 'divi_powers_onboarding', array() ); if ( $funnel_id === absint( $legacy['starter']['funnel_id'] ?? 0 ) ) { $metrics = get_option( self::METRICS_OPTION, array() ); } } return wp_parse_args( is_array( $metrics ) ? $metrics : array(), array( 'checkout_visits' => 0, 'orders' => 0 ) ); } private function increment_metric( $key, $funnel_id = 0 ) { $metrics = self::metrics( $funnel_id ); $metrics[$key] = absint( $metrics[$key] ?? 0 ) + 1; if ( $funnel_id ) { update_post_meta( $funnel_id, '_divi_powers_basic_metrics', $metrics ); } else { update_option( self::METRICS_OPTION, $metrics, false ); } } private function valid_funnel( $id ) { return $id && 'fivedp_funnel' === get_post_type( $id ) && 'trash' !== get_post_status( $id ) && 'pro' !== get_post_meta( $id, '_divi_powers_funnel_editor', true ) && metadata_exists( 'post', $id, '_divi_powers_basic_funnel_state' ) && 'active' === get_post_meta( $id, '_divi_powers_funnel_status', true ); } private function page_funnel_id( $page_id ) { $context = get_post_meta( $page_id, Divi_Powers_Funnel_Meta::CONTEXT_META, true ); $id = is_array( $context ) ? absint( $context['funnel_id'] ?? 0 ) : 0; return $this->valid_funnel( $id ) ? $id : 0; } private function current_funnel_id() { $page_id = get_queried_object_id(); $id = $this->page_funnel_id( $page_id ); if ( $id ) { return $id; } // A configured global checkout starts a new store journey, clearing a prior funnel. $global = get_option( self::OPTION_KEY, array() ); if ( $page_id && $page_id === absint( $global['checkout_page_id'] ?? 0 ) ) { return 0; } if ( function_exists( 'WC' ) && WC() && WC()->session ) { $id = absint( WC()->session->get( self::SESSION_CONTEXT ) ); if ( $this->valid_funnel( $id ) ) { return $id; } } return 0; } private function runtime_enabled() { return $this->plugin->is_power_enabled( 'plus_checkout' ); } private function is_configured_checkout_page() { $id = absint( $this->settings()['checkout_page_id'] ); return $this->runtime_enabled() && $id && is_page( $id ); } private function is_configured_thank_you_page() { $id = absint( $this->settings()['thank_you_page_id'] ); return $this->runtime_enabled() && $id && is_page( $id ); } }