newInstanceWithoutConstructor();
}
/**
* Class construct.
* required actions that affects woocommerce global checkout shortcode / page.
*
* @since 1.1.0
*/
public function __construct() {
// Create empty shortcode to simulate our widget page as checkout page.
add_shortcode( 'sellkit_checkout_widget_simulated', function() {
return '';
} );
add_action( 'wp_ajax_sellkit_checkout_ajax_handler', [ $this, 'ajax_handler' ] );
add_action( 'wp_ajax_nopriv_sellkit_checkout_ajax_handler', [ $this, 'ajax_handler' ] );
// Add shipping total to review order by custom hook.
add_action( 'sellkit-checkout-widget-display-shipping-price', [ $this, 'add_shipping_total_to_review_order' ] );
// Modify checkout order-review item.
add_action( 'sellkit-one-page-checkout-custom-order-item', [ $this, 'modify_checkout_order_item' ], 1, 4 );
// Modify sent data before processing & validate order.
add_filter( 'woocommerce_checkout_posted_data', [ $this, 'modify_order_data_before_validate' ], 10, 1 );
// Validate user defined fields.
add_action( 'woocommerce_checkout_process', [ $this, 'validate_user_defined_fields' ] );
// Save user defined fields.
add_action( 'woocommerce_checkout_update_order_meta', [ $this, 'save_user_defined_fields' ] );
// Display user defined fields in order edit page.
add_action( 'woocommerce_admin_order_data_after_billing_address', [ $this, 'add_item_to_order_details' ], 10, 1 );
add_action( 'woocommerce_admin_order_data_after_shipping_address', [ $this, 'add_item_to_order_details' ], 10, 1 );
// Add custom shipping field to email.
add_filter( 'woocommerce_order_get_formatted_shipping_address', [ $this, 'attach_user_shipping_fields_to_order_email' ], 10, 3 );
// Add custom billing field to email.
add_filter( 'woocommerce_order_get_formatted_billing_address', [ $this, 'attach_user_billing_fields_to_order_email' ], 10, 3 );
// Filter checkout ajax fragment.
add_filter( 'woocommerce_update_order_review_fragments', [ $this, 'checkout_fragment' ] );
// Simulate checkout page for our widget.
add_action( 'wp', [ $this, 'simulate_our_widget_page_as_checkout' ] );
// Fix shipping method price change on ajax.
// Manage bump order discounts.
// Template replacement.
add_action( 'woocommerce_checkout_update_order_review', [ $this, 'sellkit_checkout_during_ajax' ], 10, 1 );
add_filter( 'sellkit-shipping-methods-choosen-method', [ $this, 'sellkit_default_shipping_method' ], 10, 1 );
// Modify discounted products.
add_action( 'woocommerce_before_calculate_totals', [ $this, 'before_cart_calculate' ], 999 );
// Allow viewing of Checkout page in the Editor with an empty cart.
$action = isset( $_REQUEST['action'] ) ? filter_var( $_REQUEST['action'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : ''; // phpcs:ignore
$elementor_preview = isset( $_REQUEST['elementor-preview'] ) ? filter_var( $_REQUEST['elementor-preview'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : ''; // phpcs:ignore
if (
( 'elementor' === $action && is_admin() ) // Elementor Editor.
|| 'elementor_ajax' === $action // Elementor Editor Preview - Ajax Render Widget.
|| ! empty( $elementor_preview ) // Elementor Editor Preview.
) {
add_filter( 'woocommerce_checkout_redirect_empty_cart', '__return_false', 5 );
}
}
/**
* Handle ajax calls.
*
* @since 1.1.0
* @return void
*/
public function ajax_handler() {
check_ajax_referer( 'sellkit_elementor', 'nonce' );
$sub_action = filter_input( INPUT_POST, 'sub_action', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
if ( method_exists( $this, $sub_action ) ) {
call_user_func( [ $this, $sub_action ] );
return;
}
wp_send_json_error();
}
/**
* Modify checkout ajax response HTML.
*
* @param array $response woocommerce checkout page ajax response.
* @return array
* @since 1.1.0
*/
public function checkout_fragment( $response ) {
// Get posted data and identify if it's been sent by jupiter widget or not.
$checkout_form_data = filter_input( INPUT_POST, 'post_data', FILTER_DEFAULT );
$checkout_form_data = explode( '&', $checkout_form_data );
$posted_data = [];
foreach ( $checkout_form_data as $input ) {
$item = explode( '=', urldecode( $input ) );
$posted_data[ trim( $item[0] ) ] = $item[1];
}
// Identify if it's been sent by jupiter checkout widget or not.
if ( ! array_key_exists( 'form_id', $posted_data ) && ! array_key_exists( 'post_id', $posted_data ) ) {
// Default woocommerce checkout.
return $response;
}
// Get widget settings.
$widget_settings = Helper::instance()->retrieve_checkout_widget_settings( $posted_data['post_id'], $posted_data['form_id'] );
// Order review fragment.
ob_start();
woocommerce_order_review();
$order_review = ob_get_clean();
// Payment fragment.
ob_start();
woocommerce_checkout_payment();
$payment = ob_get_clean();
// Shipping method fragment.
ob_start();
echo '';
wc_cart_totals_shipping_html();
echo '';
$shipping_method = ob_get_clean();
$response['.woocommerce-checkout-review-order-table'] = $order_review;
$response['.woocommerce-checkout-payment'] = $payment;
$response['.sellkit-one-page-shipping-methods'] = '';
if ( ! array_key_exists( 'show_shipping_method', $widget_settings ) ) {
$response['.sellkit-one-page-shipping-methods'] = $shipping_method;
}
return $response;
}
/**
* Apply coupon code using ajax.
*
* @return void
* @since 1.1.0
*/
public function apply_coupon() {
$code = filter_input( INPUT_POST, 'code', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
if ( empty( $code ) ) {
wp_send_json_error();
}
$result = WC()->cart->add_discount( $code );
wp_send_json_success( $result );
}
/**
* Update cart item quantity in checkout page by ajax.
*
* @return void
* @since 1.1.0
*/
public function change_cart_item_qty() {
$qty = filter_input( INPUT_POST, 'qty', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$action = filter_input( INPUT_POST, 'mode', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$funnel_id = filter_input( INPUT_POST, 'related_checkout', FILTER_SANITIZE_NUMBER_INT );
self::make_changes_after_cart_item_edit( $funnel_id );
if ( 'add' === $action ) {
WC()->cart->add_to_cart( $id, $qty );
self::make_changes_after_cart_item_edit( $funnel_id );
wp_send_json_success();
}
if ( 'remove' === $action ) {
$post_type = get_post_type( $id );
if ( 'product_variation' === $post_type ) {
foreach ( WC()->cart->get_cart() as $item_key => $item ) {
if ( $item['variation_id'] === (int) $id ) {
WC()->cart->remove_cart_item( $item_key ); // we remove it.
break; // stop the loop.
}
}
self::make_changes_after_cart_item_edit( $funnel_id );
wp_send_json_success();
}
$product_cart_id = WC()->cart->generate_cart_id( $id );
$cart_item_key = WC()->cart->find_product_in_cart( $product_cart_id );
if ( $cart_item_key ) {
WC()->cart->remove_cart_item( $cart_item_key );
}
self::make_changes_after_cart_item_edit( $funnel_id );
wp_send_json_success();
}
( $qty > 0 ) ? WC()->cart->set_quantity( $id, $qty ) : WC()->cart->remove_cart_item( $id );
self::make_changes_after_cart_item_edit( $funnel_id );
wp_send_json_success();
}
/**
* Apply changes after editing cart items.
*
* @since 1.2.5
* @param int $funnel_id funnel step id.
*/
public static function make_changes_after_cart_item_edit( $funnel_id ) {
if ( empty( $funnel_id ) ) {
return;
}
$funnel_data = get_post_meta( $funnel_id, 'step_data', true );
$optimization_data = ! empty( $funnel_data['data']['optimization'] ) ? $funnel_data['data']['optimization'] : '';
if ( empty( $optimization_data ) ) {
return;
}
$auto_apply_coupon_cookie_value = null;
if ( isset( $_COOKIE['sellkit_rejected_coupon'] ) ) {
// Get the value of the cookie.
$auto_apply_coupon_cookie_value = sanitize_text_field( wp_unslash( $_COOKIE['sellkit_rejected_coupon'] ) );
$auto_apply_coupon_cookie_value = explode( ',', $auto_apply_coupon_cookie_value );
}
// Help to apply funnel discount prices and coupons when changing product quantity.
self::apply_discounted_prices( wc()->cart, $funnel_id );
self::apply_discounted_prices( wc()->cart, $funnel_id, 'bumps' );
if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
WC()->cart->remove_coupons();
foreach ( $optimization_data['auto_apply_coupons'] as $auto_apply_coupon ) {
if ( ! empty( $auto_apply_coupon_cookie_value ) && in_array( $auto_apply_coupon['label'], $auto_apply_coupon_cookie_value, true ) ) {
continue;
}
wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
}
wc_clear_notices();
}
}
/**
* Place shipping price based on design to checkout order-review.
*
* @return void
* @since 1.1.0
*/
public function add_shipping_total_to_review_order() {
?>
|
cart->get_shipping_total();
if ( WC()->cart->display_prices_including_tax() ) {
$price = WC()->cart->get_shipping_total() + WC()->cart->shipping_tax_total;
}
echo wp_kses_post( wc_price( $price ) );
?>
|
cart->get_cart();
//phpcs:disable
ob_start();
?>
get_image(); ?>
get_type();
if ( $cart_items[ $cart_item_key ] === end( $cart_items ) ) {
$extra_class .= ' last-cart-item';
}
if ( 'variation' === $product->get_type() ) {
add_filter( 'woocommerce_cart_item_name', [ $this, 'modify_variation_title' ], 50, 2 );
add_filter( 'woocommerce_get_item_data', [ $this, 'modify_variation_items' ], 10, 2 );
}
?>
get_type() ) {
remove_filter( 'woocommerce_cart_item_name', [ $this, 'modify_variation_title' ], 50 );
remove_filter( 'woocommerce_get_item_data', [ $this, 'modify_variation_items' ], 10 );
}
}
/**
* Remove variation attributes from variation product title.
*
* @param string $default default value.
* @param array $cart_item cart item data.
* @since 1.6.8
*/
public function modify_variation_title( $default, $cart_item ) {
$product = $cart_item['product_id'];
return get_the_title( $product );
}
/**
* Display variation attributes separated from title even if those are less than 3.
*
* @param array $item_data attributes array.
* @param array $cart_item cart item data.
* @since 1.6.8
*/
public function modify_variation_items( $item_data, $cart_item ) {
$product = new \WC_Product_Variation( $cart_item['variation_id'] );
$attributes = $product->get_attributes();
if ( count( $attributes ) > 2 ) {
return $item_data;
}
foreach ( $attributes as $key => $value ) {
$key = str_replace( 'pa_', '', $key );
$key = str_replace( '-', '', $key );
$key = str_replace( '_', '', $key );
$item_data[] = [
'key' => $key,
'value' => $value,
];
}
return $item_data;
}
/**
* Modify fields after pressing place order button.
* we hooked here because some removed fields still get checked and have not removed by previous hooks.
*
* @SuppressWarnings(PHPMD.NPathComplexity)
* @param array $data checkout form data.
* @return array $data data of woocommerce checkout form.
* @since 1.1.0
*/
public function modify_order_data_before_validate( $data ) {
$post_id = filter_input( INPUT_POST, 'post_id', FILTER_SANITIZE_NUMBER_INT );
$form_id = filter_input( INPUT_POST, 'form_id', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
if ( empty( $post_id ) && empty( $form_id ) ) {
return $data;
}
$settings = Helper::instance()->retrieve_checkout_widget_settings( $post_id, $form_id );
if ( empty( $settings ) ) {
return $data;
}
$widget_shipping_field = Helper::instance()->get_user_defined_fields_slug( $settings['shipping_list'], 'shipping_list_field' );
$widget_billing_field = Helper::instance()->get_user_defined_fields_slug( $settings['billing_list'], 'billing_list_field' );
$default_shipping_fields = Helper::instance()->shipping_fields();
$default_billing_fields = Helper::instance()->billing_fields();
// Unset removed shipping fields.
foreach ( $default_shipping_fields as $field => $details ) {
if ( ! in_array( $field, $widget_shipping_field, true ) ) {
unset( $data[ $field ] );
}
}
// Unset removed billing fields.
foreach ( $default_billing_fields as $field => $details ) {
if ( ! in_array( $field, $widget_billing_field, true ) && 'billing_email' !== $field ) {
unset( $data[ $field ] );
}
}
$mail = filter_input( INPUT_POST, 'billing_email', FILTER_SANITIZE_EMAIL );
if ( ! array_key_exists( 'billing_email', $data ) && ! empty( $mail ) ) {
$data['billing_email'] = $mail;
}
return $data;
}
/**
* Login user by ajax.
* sign user in by using form included in widget.
*
* @return void
* @since 1.1.0
*/
public function auth_user() {
$email = filter_input( INPUT_POST, 'email', FILTER_SANITIZE_EMAIL );
$pass = filter_input( INPUT_POST, 'pass', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$verify = filter_var( $email, FILTER_VALIDATE_EMAIL );
if ( ! $verify || empty( $email ) || empty( $pass ) ) {
wp_send_json_error( __( 'Email or password field is not valid.', 'sellkit' ) );
}
$result = wp_authenticate( $email, $pass );
if ( is_wp_error( $result ) ) {
wp_send_json_error( $result->get_error_message() );
}
wp_clear_auth_cookie();
wp_set_current_user( $result->ID );
wp_set_auth_cookie( $result->ID );
wp_send_json_success( $result );
}
/**
* Get widget settings in ajax calls using form and post id.
*
* @return void
* @since 1.1.0
*/
private function widget_settings() {
$form_id = filter_input( INPUT_POST, 'form_id', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$post_id = filter_input( INPUT_POST, 'post_id', FILTER_SANITIZE_NUMBER_INT );
if ( empty( $form_id ) && empty( $post_id ) ) {
return;
}
$widget_settings = Helper::instance()->retrieve_checkout_widget_settings( $post_id, $form_id );
return $widget_settings;
}
/**
* Validate user defined custom value.
*
* @return void
* @since 1.1.0
*/
public function validate_user_defined_fields() {
$widget_settings = $this->widget_settings();
if ( empty( $widget_settings ) ) {
return;
}
$widget_shipping_fields = $widget_settings['shipping_list'];
$widget_billing_fields = $widget_settings['billing_list'];
Helper::instance()->validate_user_defined_fields( $widget_shipping_fields, $widget_billing_fields );
Helper::instance()->make_sure_to_convert_required_field_to_optional( $widget_shipping_fields, $widget_billing_fields );
}
/**
* Save user defined custom fields value to database.
*
* @param int $order_id id of woocommerce order.
* @return void
* @since 1.1.0
*/
public function save_user_defined_fields( $order_id ) {
$widget_settings = $this->widget_settings();
if ( empty( $widget_settings ) ) {
return;
}
$widget_shipping_fields = $widget_settings['shipping_list'];
$widget_billing_fields = $widget_settings['billing_list'];
Helper::instance()->save_user_defined_fields( $widget_shipping_fields, $widget_billing_fields, $order_id );
}
/**
* Attach user defined shipping field values to order emails.
*
* @param string $address address.
* @param string $raw_address raw address.
* @param object $order woocommerce order object.
* @return string
* @since 1.1.0
*/
public function attach_user_shipping_fields_to_order_email( $address, $raw_address, $order ) {
$order_id = $order->get_id();
$custom_fields = get_post_meta( $order_id, 'sellkit_checkout_widget_custom_field_of_order', true );
if ( empty( $custom_fields ) || ! is_array( $custom_fields ) ) {
return $address;
}
foreach ( $custom_fields as $field ) {
if ( 'yes' !== $field['show_in_email'] ) {
continue;
}
$key = $field['key_name'];
$value = get_post_meta( $order_id, $key, true );
if ( strpos( $key, 'shipping' ) !== false ) {
$address .= '
' . $value;
}
}
return $address;
}
/**
* Attach user defined billing field values to order emails.
*
* @param string $address address.
* @param string $raw_address raw address.
* @param object $order woocommerce order object.
* @return string
* @since 1.1.0
*/
public function attach_user_billing_fields_to_order_email( $address, $raw_address, $order ) {
$order_id = $order->get_id();
$custom_fields = get_post_meta( $order_id, 'sellkit_checkout_widget_custom_field_of_order', true );
if ( empty( $custom_fields ) || ! is_array( $custom_fields ) ) {
return $address;
}
foreach ( $custom_fields as $field ) {
if ( 'yes' !== $field['show_in_email'] ) {
continue;
}
$key = $field['key_name'];
$value = get_post_meta( $order_id, $key, true );
if ( strpos( $key, 'billing' ) !== false ) {
$address .= '
' . $value;
}
}
return $address;
}
/**
* Place custom coupon form in checkout order-review based design.
* We have used same form in Local_Hooks. but this one is for ajax response.
*
* @see sellkit_Core\Raven\Modules\Checkout\Classes\Local_Hooks::coupon_form.
* @param array $settings widget settings.
* @return void
* @since 1.1.0
*/
public function coupon_form( $settings ) {
if ( array_key_exists( 'show_coupon_field', $settings ) ) {
return;
}
echo '| ';
Local_Hooks::form( $settings );
echo ' |
';
do_action( 'sellkit-checkout-after-coupon-form-ajax' );
}
/**
* Look for an email if exists or not.
* Is used for login process.
*
* @return void
* @since 1.1.0
*/
public function search_for_email() {
$email = filter_input( INPUT_POST, 'email', FILTER_SANITIZE_EMAIL );
$valid = filter_var( $email, FILTER_VALIDATE_EMAIL );
if ( ! $valid || empty( $email ) ) {
wp_send_json_error();
}
$check = email_exists( $email );
if ( false === $check ) {
wp_send_json_error();
}
wp_send_json_success();
}
/**
* Look for an username if exists or not.
* Is used for register process.
*
* @return void
* @since 1.1.0
*/
public function search_for_username() {
$username = filter_input( INPUT_POST, 'user', FILTER_SANITIZE_EMAIL );
if ( empty( $username ) ) {
wp_send_json_error();
}
$username = sanitize_user( $username );
$check = username_exists( $username );
if ( false !== $check ) {
wp_send_json_error();
}
wp_send_json_success();
}
/**
* Validate postcode via ajax.
*
* @since 1.1.0
* @return void
*/
public function validate_postcode() {
$country = filter_input( INPUT_POST, 'country_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$postcode = filter_input( INPUT_POST, 'post_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$parent = filter_input( INPUT_POST, 'parent', FILTER_DEFAULT );
$postcode = wc_format_postcode( $postcode, $country );
$valid = \WC_Validation::is_postcode( $postcode, $country );
if ( ! $valid ) {
wp_send_json_error( $parent );
}
wp_send_json_success( $parent );
}
/**
* Validate phone number via ajax.
*
* @since 1.1.0
* @return void
*/
public function validate_phone_number() {
$phone = filter_input( INPUT_POST, 'phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$parent = filter_input( INPUT_POST, 'parent', FILTER_DEFAULT );
$valid = \WC_Validation::is_phone( $phone );
if ( ! $valid ) {
wp_send_json_error( $parent );
}
wp_send_json_success( $parent );
}
/**
* Simulate checkout page where our widget is present. using empty shortcode [sellkit_checkout_widget_simulated].
*
* @since 1.1.0
* @return void
*/
public function simulate_our_widget_page_as_checkout() {
$page_id = get_the_ID();
$content = get_post_field( 'post_content', $page_id );
if ( false === strpos( $content, 'woocommerce_checkout' ) ) {
return;
}
add_filter( 'woocommerce_is_checkout', function() {
return true;
} );
if ( false === strpos( $content, 'sellkit_checkout_widget_simulated' ) ) {
return;
}
add_filter( 'theme_mod_jupiterx_jupiterx_checkout_cart_elements', function() {
return [];
}, 10 );
}
/**
* Integrate user country with our widget.
*
* @since 1.1.0
* @return void
*/
public function set_customer_details_ajax() {
$country = filter_input( INPUT_POST, 'country', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$state = filter_input( INPUT_POST, 'state', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$shipping_country = filter_input( INPUT_POST, 'shipping_country', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$shipping_state = filter_input( INPUT_POST, 'shipping_state', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
if ( ! empty( $country ) ) {
setcookie( 'sellkit-checkout-billing-cc', $country, time() + ( 86400 * 3 ), '/' );
setcookie( 'sellkit-checkout-billing-state', $state, time() + ( 86400 * 3 ), '/' );
}
if ( ! empty( $shipping_country ) ) {
setcookie( 'sellkit-checkout-shipping-cc', $shipping_country, time() + ( 86400 * 3 ), '/' );
setcookie( 'sellkit-checkout-shipping-state', $shipping_state, time() + ( 86400 * 3 ), '/' );
}
wp_send_json_success();
}
/**
* State lookup using postcode and country.
*
* @since 1.1.0
* @return void
*/
public function sellkit_state_lookup_by_postcode() {
$country = filter_input( INPUT_POST, 'country_value', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$postcode = filter_input( INPUT_POST, 'postcode_value', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$api = 'https://api.zippopotam.us/' . $country . '/' . $postcode;
if ( empty( $country ) || empty( $postcode ) ) {
wp_send_json_error( esc_html__( 'Not valid inputs', 'sellkit' ) );
}
$response = wp_remote_get( $api );
if ( is_wp_error( $response ) || ! is_array( $response ) ) {
wp_send_json_error( esc_html__( 'Server error.', 'sellkit' ) );
}
if ( '{}' === $response['body'] ) {
// Country or postcode is not supported. it's kina an error but we show nothing in frontend.
wp_send_json_error( '' );
}
$body = json_decode( $response['body'], true );
wp_send_json_success( $body );
}
/**
* Modify cart contents using bundled products.
*
* @since 1.1.0
* @return void
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function sellkit_checkout_modify_cart_by_bundle_products() {
$qty = filter_input( INPUT_POST, 'qty', FILTER_SANITIZE_NUMBER_INT );
$id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT );
$type = filter_input( INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$checkout_id = filter_input( INPUT_POST, 'checkout_id', FILTER_SANITIZE_NUMBER_INT );
$modify = filter_input( INPUT_POST, 'modify', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$key = filter_input( INPUT_POST, 'key', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
if ( ! empty( $key ) ) {
foreach ( WC()->cart->get_cart() as $item_key => $item ) {
if ( $item_key === $key ) {
WC()->cart->set_quantity( $item_key, $qty, true );
break; // stop the loop.
}
}
wp_send_json_success();
}
if ( 'radio' === $type ) {
$step_data = get_post_meta( $checkout_id, 'step_data', true );
$products = $step_data['data']['products']['list'];
$reset_cart = isset( $step_data['data']['products']['reset_cart'] ) ? $step_data['data']['products']['reset_cart'] : 'true';
if ( 'true' === $reset_cart ) {
foreach ( WC()->cart->get_cart() as $item ) {
if ( empty( $item['product_id'] ) ) {
continue;
}
if ( array_key_exists( $item['product_id'], $products ) ) {
continue;
}
$products[ $item['product_id'] ] = [
'quantity' => $item['quantity'],
'discount' => '',
'discountType' => 'fixed',
];
}
}
$selected = ! empty( $products[ $id ] ) ? $products[ $id ] : [];
$real_quantity = ( empty( $selected['quantity'] ) ) ? 1 : $selected['quantity'];
if ( (int) $real_quantity !== (int) $qty ) {
wp_send_json_error( esc_html__( 'Oh do not be tricky.', 'sellkit' ) );
}
}
if ( 'radio' === $type ) {
WC()->cart->empty_cart();
WC()->cart->add_to_cart( $id, $qty );
}
if ( 'checkbox' === $type && 'add' === $modify ) {
WC()->cart->add_to_cart( $id, $qty );
}
if ( 'checkbox' === $type && 'remove' === $modify ) {
$post_type = get_post_type( $id );
// Maybe product is a variation.
if ( 'product_variation' === $post_type ) {
foreach ( WC()->cart->get_cart() as $item_key => $item ) {
if ( $item['variation_id'] === (int) $id ) {
WC()->cart->remove_cart_item( $item_key ); // we remove it.
break; // stop the loop.
}
}
self::make_changes_after_cart_item_edit( $checkout_id );
wp_send_json_success();
}
// Default product.
$product_cart_id = WC()->cart->generate_cart_id( $id );
$cart_item_key = WC()->cart->find_product_in_cart( $product_cart_id );
if ( $cart_item_key ) {
WC()->cart->remove_cart_item( $cart_item_key );
}
}
self::make_changes_after_cart_item_edit( $checkout_id );
wp_send_json_success();
}
/**
* Apply funnel discount.
* Will be called twice, first when page is loading , second during checkout ajax.
*
* @param object $cart cart object.
* @param int $checkout_id_ajax checkout id.
* @param string $source source of products, bump or default products to be checked.
* @param array $upsell_data upsell product data coming from popup.
* @since 1.2.8
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public static function apply_discounted_prices( $cart = [], $checkout_id_ajax = null, $source = 'default', $upsell_data = [] ) {
$checkout_id = get_queried_object_id();
if ( wp_doing_ajax() ) {
$checkout_id = $checkout_id_ajax;
}
if ( empty( $checkout_id ) ) {
return;
}
$step_data = get_post_meta( $checkout_id, 'step_data', true );
if ( empty( $step_data ) ) {
return;
}
$products = [];
if (
'default' === $source &&
array_key_exists( 'data', $step_data ) &&
( ! empty( $step_data['data']['products'] ) && array_key_exists( 'list', $step_data['data']['products'] ) )
) {
$products = $step_data['data']['products']['list'];
}
if ( 'bumps' === $source && ! empty( $step_data['bump'] ) ) {
$bumps = $step_data['bump'];
foreach ( $bumps as $bump ) {
if ( ! array_key_exists( 'products', $bump['data'] ) ) {
continue;
}
$key = ! empty( $bump['data']['products']['list'] ) ? array_key_first( $bump['data']['products']['list'] ) : null;
if ( empty( $key ) ) {
continue;
}
$products[ $key ] = $bump['data']['products']['list'][ $key ];
}
}
if ( 'upsell' === $source ) {
$products = $upsell_data['data']['products']['list'];
}
if ( empty( $products ) ) {
return;
}
$final_price = 0;
foreach ( $cart->get_cart() as $key => $details ) {
$item_id = $details['product_id'];
$price = false;
if ( ! empty( $details['variation_id'] ) ) {
$item_id = $details['variation_id'];
}
foreach ( $products as $product_id => $product_details ) {
if ( $item_id === $product_id ) {
$discount_type = isset( $product_details['discountType'] ) ? $product_details['discountType'] : '';
$discount_value = isset( $product_details['discount'] ) ? $product_details['discount'] : '';
$price = Helper::calculate_discount( $item_id, $discount_type, $discount_value );
if ( 'upsell' === $source ) {
self::$sellkit_upsell_products[ $product_id ] = $price;
}
}
}
if ( false !== $price ) {
$details['data']->set_price( $price );
}
$final_price += $details['data']->get_price() * $details['quantity'];
}
self::modify_products_price_before_woo_apply_discounts( $final_price );
}
/**
* Modify cart subtotal before validating discounts.
*
* @since 1.2.8
* @param int $final_price cart new subtotal.
*/
public static function modify_products_price_before_woo_apply_discounts( $final_price ) {
add_filter( 'woocommerce_coupon_validate_minimum_amount', function( $value, $coupon ) use ( $final_price ) {
return $coupon->get_minimum_amount() > $final_price;
}, 99, 2 );
add_filter( 'woocommerce_coupon_validate_maximum_amount', function( $value, $coupon ) use ( $final_price ) {
return $coupon->get_maximum_amount() < $final_price;
}, 99, 2 );
}
/**
* Fix shipping method price on checkout ajax state change.
*
* @param string $data checkout form data.
* @since 1.1.0
* @return void
*/
public function sellkit_checkout_during_ajax( $data ) {
$data = explode( '&', $data );
$clear = [];
foreach ( $data as $key => $input ) {
$input = explode( '=', $input );
$clear[ $input[0] ] = $input[1];
}
if ( ! array_key_exists( 'form_id', $clear ) ) {
return;
}
$this->apply_template_replacement_during_ajax( $clear );
$_POST = Helper::instance()->assigning_default_ajax_shipping_fields( $_POST, $clear ); // phpcs:ignore
Local_Hooks::order_bump( $clear );
if ( array_key_exists( 'sellkit-bundle-products', $clear ) ) {
$option = $clear['sellkit-bundle-products'];
Local_Hooks::bundle_product_action( $option );
}
// Apply discounts.
if ( ! array_key_exists( 'sellkit_current_page_id', $clear ) ) {
return;
}
self::apply_discounted_prices( wc()->cart, $clear['sellkit_current_page_id'] );
self::apply_discounted_prices( wc()->cart, $clear['sellkit_current_page_id'], 'bumps' );
}
/**
* Template replacement during ajax call.
*
* @param array $data checkout form data.
* @since 1.2.1
*/
private function apply_template_replacement_during_ajax( $data ) {
$widget_settings = Helper::instance()->retrieve_checkout_widget_settings( $data['post_id'], $data['form_id'] );
Local_Hooks::enable_disable_sections( $widget_settings, $data );
add_filter( 'wc_get_template', function( $located, $template_name ) {
$our_path = sellkit()->plugin_dir() . 'includes/elementor/modules/checkout/templates/';
$files = [
'review-order.php',
'payment-method.php',
'payment.php',
'form-checkout.php',
'cart-item-data.php',
'cart-shipping.php',
'terms.php',
];
$template = str_replace( 'checkout/', '', $template_name );
$template = str_replace( 'cart/', '', $template );
if ( in_array( $template, $files, true ) ) {
$located = $our_path . $template;
}
return $located;
}, 10, 2 );
}
/**
* Modify default selected shipping method.
*
* @since 1.2.8
* @param string $default default selected method.
* @return string
*/
public function sellkit_default_shipping_method( $default ) {
$applied_coupons = WC()->cart->get_applied_coupons();
if ( count( $applied_coupons ) < 1 ) {
return $default;
}
foreach ( $applied_coupons as $coupon_code ) {
$coupon = new \WC_Coupon( $coupon_code );
if ( $coupon->get_free_shipping() ) {
if ( count( WC()->session->get( 'shipping_for_package_0' )['rates'] ) > 0 ) {
// Loop through.
foreach ( WC()->session->get( 'shipping_for_package_0' )['rates'] as $rate_id => $rate ) {
// For free shipping.
if ( 'free_shipping' === $rate->method_id ) {
return $rate_id;
}
}
}
}
}
return $default;
}
/**
* Recalculate checkout and cart prices.
*
* @since 1.2.8
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function before_cart_calculate() {
// More than twice isn't necessary. this is called multiple times by WooCommerce.
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 && wp_doing_ajax() ) {
return;
}
// Gather checkout id.
$checkout_id = get_queried_object_id();
if ( wp_doing_ajax() ) {
// First try to catch id using our ajax call.
$checkout_id = filter_input( INPUT_POST, 'related_checkout', FILTER_SANITIZE_NUMBER_INT );
// Second try to catch id using woocommerce ajax.
if ( empty( $checkout_id ) ) {
$checkout_id = filter_input( INPUT_POST, 'sellkit_current_page_id', FILTER_SANITIZE_NUMBER_INT );
}
// Catch id after pressing place order button. none of above methods worked.
if ( empty( $checkout_id ) ) {
$data = filter_input( INPUT_POST, 'post_data', FILTER_DEFAULT );
if ( ! empty( $data ) ) {
parse_str( $data, $data );
}
if ( is_array( $data ) && array_key_exists( 'sellkit_current_page_id', $data ) ) {
$checkout_id = $data['sellkit_current_page_id'];
}
}
}
// Empty id ? no action.
if ( empty( $checkout_id ) ) {
return;
}
$checkout_id = (int) $checkout_id;
// Apply funnel prices.
self::apply_discounted_prices( wc()->cart, $checkout_id );
self::apply_discounted_prices( wc()->cart, $checkout_id, 'bumps' );
// Apply upsell discount.
$sellkit_upsell_prices = null;
if ( isset( $_POST['woocommerce-process-checkout-nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['woocommerce-process-checkout-nonce'] ) ), 'woocommerce-process_checkout' ) ) {
$sellkit_upsell_prices = isset( $_POST['sellkit_product_prices'] ) ? sanitize_textarea_field( wp_unslash( $_POST['sellkit_product_prices'] ) ) : null;
}
$upsell_product_ids;
if ( isset( $sellkit_upsell_prices ) && ! empty( $sellkit_upsell_prices ) ) {
$sellkit_upsell_prices = json_decode( $sellkit_upsell_prices, true );
$upsell_product_ids = array_keys( $sellkit_upsell_prices );
}
if ( isset( $upsell_product_ids ) ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
$product_name = $product->get_name();
$product_id = $product->get_id();
if ( in_array( $product_id, $upsell_product_ids, true ) ) {
$upsell_price = $sellkit_upsell_prices[ $product_id ];
$product->set_price( $upsell_price );
}
$price = $product->get_price();
}
}
$auto_apply_coupon_cookie_value = null;
if ( isset( $_COOKIE['sellkit_rejected_coupon'] ) ) {
// Get the value of the cookie.
$auto_apply_coupon_cookie_value = sanitize_text_field( wp_unslash( $_COOKIE['sellkit_rejected_coupon'] ) );
$auto_apply_coupon_cookie_value = explode( ',', $auto_apply_coupon_cookie_value );
}
// We should re apply coupons after each price changes. to make sure everything is correct.
$optimization_data = ! empty( $funnel_data['data']['optimization'] ) ? $funnel_data['data']['optimization'] : '';
if ( Checkout::apply_coupon_validation( $optimization_data ) ) {
WC()->cart->remove_coupons();
foreach ( $optimization_data['auto_apply_coupons'] as $auto_apply_coupon ) {
if ( ! empty( $auto_apply_coupon_cookie_value ) && in_array( $auto_apply_coupon['label'], $auto_apply_coupon_cookie_value, true ) ) {
continue;
}
wc()->cart->add_discount( get_the_title( $auto_apply_coupon['value'] ) );
}
wc_clear_notices();
}
}
/**
* Check sellkit step through ajax.
* And in order decide to show upsell or downsell steps through a popup.
*
* @since 1.6.2
*/
public function call_funnel_popups() {
$step = filter_input( INPUT_POST, 'step', FILTER_SANITIZE_NUMBER_INT );
$funnel = new Sellkit_Funnel( $step );
$next_step = $funnel->next_step_data;
$popups = [ 'upsell', 'downsell' ];
$funnel->next_step_data['type'] = (array) $funnel->next_step_data['type'];
if ( 'decision' === $funnel->next_step_data['type']['key'] ) {
$page_id = isset( $funnel->next_step_data['page_id'] ) ? apply_filters( 'wpml_object_id', $funnel->next_step_data['page_id'], 'sellkit_step', true ) : 0; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
$this->helper_id = $step;
$this->take_care_of_decision_step( $page_id, $funnel->funnel_id );
return;
}
if ( in_array( $funnel->next_step_data['type']['key'], $popups, true ) ) {
$next_step = $this->is_set_upsell_product( $next_step );
wp_send_json_success( [
'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
'next_type' => $next_step['type']['key'],
] );
}
}
/**
* Check the upsell or downsell have product or not, ignore upsell/downsell step if product doesn't set.
*
* @param array $next_step Next step data.
* @since 2.6.0
*/
private function is_set_upsell_product( $next_step ) {
if ( isset( $next_step['type']['key'] ) && ! in_array( $next_step['type']['key'], [ 'upsell', 'downsell' ], true ) ) {
return $next_step;
}
$products = isset( $next_step['data'] ) ? $next_step['data']['products'] : [];
$list = isset( $products['list'] ) ? $products['list'] : [];
if ( empty( $list ) ) {
$funnel = isset( $next_step['page_id'] ) ? new Sellkit_Funnel( $next_step['page_id'] ) : null;
$next_step = ! empty( $funnel ) ? $funnel->next_no_step_data : [];
if ( empty( $next_step ) && ! empty( $funnel ) ) {
return $funnel->end_node_step_data;
}
if ( isset( $next_step['type']['key'] ) && in_array( $next_step['type']['key'], [ 'upsell', 'downsell' ], true ) ) {
$next_step = $this->is_set_upsell_product( $next_step );
}
}
return $next_step;
}
/**
* Gets step id before the decision step and return result.
*
* @param int $step_id id of the step before decision step.
* @param int $funnel_id id of the funnel.
* @since 1.6.2
*/
private function take_care_of_decision_step( $step_id, $funnel_id = null ) {
// Failed decision step due to no page id should be checked a little different.
if ( empty( $step_id ) && ! empty( $funnel_id ) ) {
$this->check_decision_step_with_no_page_id();
}
$funnel = new Sellkit_Funnel( $step_id );
$conditions = ! empty( $funnel->current_step_data['data']['conditions'] ) ? $funnel->current_step_data['data']['conditions'] : [];
$is_valid = sellkit_conditions_validation( $conditions );
$next_step = $funnel->next_no_step_data;
if ( $is_valid ) {
$next_step = $funnel->next_step_data;
}
$next_step = $this->is_set_upsell_product( $next_step );
$next_step['type'] = (array) $next_step['type'];
if ( 'decision' === $next_step['type']['key'] ) {
$this->take_care_of_decision_step( apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ) ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
return;
}
wp_send_json_success( [
'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
'next_type' => $next_step['type']['key'],
] );
}
/**
* Decision next step directly using funnel data.
*
* @since 1.8.6
*/
private function check_decision_step_with_no_page_id() {
$funnel = new Sellkit_Funnel( $this->helper_id );
$decicion_data = $funnel->next_step_data;
$conditions = $decicion_data['data']['conditions'];
$funnel_data = get_post_meta( $funnel->funnel_id, 'nodes', true );
$next_no = 'none' !== $decicion_data['targets'][1]['nodeId'] ? $funnel_data[ $decicion_data['targets'][1]['nodeId'] ] : $funnel->end_node_step_data;
$next_yes = 'none' !== $decicion_data['targets'][0]['nodeId'] ? $funnel_data[ $decicion_data['targets'][0]['nodeId'] ] : $funnel->end_node_step_data;
$is_valid = sellkit_conditions_validation( $conditions );
$next_step = $next_no;
if ( $is_valid ) {
$next_step = $next_yes;
}
$next_step = $this->is_set_upsell_product( $next_step );
wp_send_json_success( [
'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
'next_type' => $next_step['type']['key'],
] );
}
/**
* Perform upsell popup accept button.
*
* @since 1.6.2
*/
public function perform_upsell_accept_button() {
// Gather and validate information.
$upsell_id = filter_input( INPUT_POST, 'upsell_id', FILTER_SANITIZE_NUMBER_INT );
$checkout_id = filter_input( INPUT_POST, 'checkout_id', FILTER_SANITIZE_NUMBER_INT );
$response = [];
if ( empty( $upsell_id ) ) {
wp_send_json_error( esc_html__( 'Empty Upsell ID.', 'sellkit' ) );
}
$upsell_data = get_post_meta( $upsell_id, 'step_data', true );
if ( empty( $upsell_data ) ) {
wp_send_json_error( esc_html__( 'Empty Step Data.', 'sellkit' ) );
}
// Add product to cart.
$product_id = '';
$qty = '';
if (
is_array( $upsell_data['data'] ) &&
array_key_exists( 'products', $upsell_data['data'] ) &&
! empty( $upsell_data['data']['products']['list'] )
) {
$product_id = array_key_first( $upsell_data['data']['products']['list'] );
$qty = ! empty( $upsell_data['data']['products']['list'][ $product_id ] ) ? $upsell_data['data']['products']['list'][ $product_id ]['quantity'] : 0;
}
if ( empty( $qty ) ) {
$qty = 1;
}
$added_product_hash = '';
if ( ! in_array( $product_id, array_column( WC()->cart->get_cart(), 'product_id' ), true ) ) {
$added_product_hash = WC()->cart->add_to_cart( $product_id, $qty );
// Take care of cart and applied discounts.
self::apply_discounted_prices( WC()->cart, $checkout_id, 'upsell', $upsell_data );
self::make_changes_after_cart_item_edit( $checkout_id );
}
// Response.
$funnel = new Sellkit_Funnel( $upsell_id );
$next_step = $funnel->next_step_data;
$next_step = $this->is_set_upsell_product( $next_step );
$next_step['type'] = ! empty( $next_step['type'] ) ? (array) $next_step['type'] : null;
if ( 'upsell' === $funnel->current_step_data['type']['key'] && isset( WC()->cart->cart_contents[ $added_product_hash ] ) ) {
$analytics_updater = new Data_Updater();
$total = empty( WC()->cart->cart_contents[ $added_product_hash ]['line_total'] ) ? 0 : WC()->cart->cart_contents[ $added_product_hash ]['line_total'];
$analytics_updater->set_funnel_id( $funnel->funnel_id );
$analytics_updater->add_new_upsell_revenue_log( $total );
}
$contact_data = [
'key' => $funnel->current_step_data['type']['key'],
'page_id' => $funnel->current_step_data['page_id'],
];
Base_Contacts::step_is_passed( $contact_data );
// Step with empty data, will be redirected to thankyou page.
if ( empty( $next_step ) || empty( $next_step['type'] ) ) {
wp_send_json_success(
[
'next_id' => $funnel->end_node_step_data['page_id'],
'next_type' => $funnel->end_node_step_data['type']['key'],
'upsell_prices' => wp_json_encode( self::$sellkit_upsell_products ),
]
);
}
if ( 'decision' === $next_step['type']['key'] ) {
$this->take_care_of_decision_step( $next_step['page_id'] );
return;
}
$response = [
'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
'next_type' => $next_step['type']['key'],
'upsell_prices' => wp_json_encode( self::$sellkit_upsell_products ),
];
wp_send_json_success( $response );
}
/**
* Perform upsell popup reject button.
*
* @since 1.6.2
*/
public function perform_upsell_reject_button() {
$upsell_id = filter_input( INPUT_POST, 'upsell_id', FILTER_SANITIZE_NUMBER_INT );
if ( empty( $upsell_id ) ) {
wp_send_json_error( esc_html__( 'Empty Upsell ID.', 'sellkit' ) );
}
$funnel = new Sellkit_Funnel( $upsell_id );
$next_step = $funnel->next_no_step_data;
$next_step = $this->is_set_upsell_product( $next_step );
if ( $next_step ) {
$next_step['type'] = (array) $next_step['type'];
}
// Step with empty data, will be redirected to thankyou page.
if ( empty( $next_step ) || empty( $next_step['type'] ) ) {
wp_send_json_success(
[
'next_id' => $funnel->end_node_step_data['page_id'],
'next_type' => $funnel->end_node_step_data['type']['key'],
]
);
}
if ( 'decision' === $next_step['type']['key'] ) {
$this->take_care_of_decision_step( $next_step['page_id'] );
return;
}
$response = [
'next_id' => apply_filters( 'wpml_object_id', $next_step['page_id'], 'sellkit_step', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML API.
'next_type' => $next_step['type']['key'],
];
wp_send_json_success( $response );
}
/**
* Add new item to the WooCommerce order details on the edit page.
*
* @param object $order woocommerce order object.
* @since 1.8.9
*/
public function add_item_to_order_details( $order ) {
$order_id = $order->get_id();
$custom_fields = get_post_meta( $order_id, 'sellkit_checkout_widget_custom_field_of_order', true );
$current_action = current_action();
if ( empty( $custom_fields ) || ! is_array( $custom_fields ) ) {
return;
}
foreach ( $custom_fields as $field ) {
$key_name = $field['key_name'];
$label = $field['label'];
// For old users data.
if ( empty( $label ) ) {
$label = $key_name;
$label = str_replace( '_', ' ', $label );
$label = str_replace( '-', ' ', $label );
$label = ucwords( $label );
}
if (
'woocommerce_admin_order_data_after_shipping_address' === $current_action &&
'shipping' === substr( $key_name, 0, 8 )
) {
?>