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' ) ) );
?>
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 number:', '5d-powers-for-divi' ) . ' ' . esc_html( $order->get_order_number() ) . '
';
if ( $date_created ) {
$html_safe .= '- ' . esc_html__( 'Date:', '5d-powers-for-divi' ) . ' ' . esc_html( wc_format_datetime( $date_created ) ) . '
';
}
$html_safe .= '- ' . esc_html__( 'Status:', '5d-powers-for-divi' ) . ' ' . esc_html( $status ) . '
';
$html_safe .= '- ' . esc_html__( 'Total:', '5d-powers-for-divi' ) . ' ' . wp_kses_post( $order->get_formatted_order_total() ) . '
';
if ( $order->get_payment_method_title() ) {
$html_safe .= '- ' . esc_html__( 'Payment method:', '5d-powers-for-divi' ) . ' ' . esc_html( $order->get_payment_method_title() ) . '
';
}
$html_safe .= '
';
$html_safe .= '
' . esc_html__( 'Order summary', '5d-powers-for-divi' ) . '
';
foreach ( $order->get_items() as $item ) {
$html_safe .= '- ' . esc_html( $item->get_name() ) . ' × ' . esc_html( number_format_i18n( absint( $item->get_quantity() ) ) ) . '' . wp_kses_post( $order->get_formatted_line_subtotal( $item ) ) . '
';
}
$html_safe .= '
';
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 ); }
}