1649 lines
55 KiB
PHP
1649 lines
55 KiB
PHP
<?php
|
||
namespace ElementorPro\Modules\Woocommerce;
|
||
|
||
use Elementor\Widget_Base;
|
||
use ElementorPro\Modules\Woocommerce\Skins\Skin_Loop_Product;
|
||
use ElementorPro\Modules\Woocommerce\Skins\Skin_Loop_Product_Taxonomy;
|
||
use ElementorPro\Core\Utils as ProUtils;
|
||
use ElementorPro\Plugin;
|
||
use ElementorPro\Base\Module_Base;
|
||
use ElementorPro\Modules\ThemeBuilder\Classes\Conditions_Manager;
|
||
use ElementorPro\Modules\Woocommerce\Conditions\Woocommerce;
|
||
use ElementorPro\Modules\Woocommerce\Documents\Product;
|
||
use ElementorPro\Modules\Woocommerce\Documents\Product_Post;
|
||
use ElementorPro\Modules\Woocommerce\Documents\Product_Archive;
|
||
use Elementor\Utils;
|
||
use Elementor\Core\Documents_Manager;
|
||
use Elementor\Settings;
|
||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||
use ElementorPro\Modules\Woocommerce\Classes\Products_Renderer;
|
||
use ElementorPro\Modules\Woocommerce\Widgets\Products as Products_Widget;
|
||
use ElementorPro\Modules\Woocommerce\Data\Controller as WoocommerceDataController;
|
||
use Elementor\Icons_Manager;
|
||
use ElementorPro\Modules\LoopBuilder\Module as LoopBuilderModule;
|
||
use ElementorPro\License\API;
|
||
|
||
if ( ! defined( 'ABSPATH' ) ) {
|
||
exit; // Exit if accessed directly.
|
||
}
|
||
|
||
class Module extends Module_Base {
|
||
|
||
const WOOCOMMERCE_GROUP = 'woocommerce';
|
||
const TEMPLATE_MINI_CART = 'cart/mini-cart.php';
|
||
const OPTION_NAME_USE_MINI_CART = 'use_mini_cart_template';
|
||
const MENU_CART_FRAGMENTS_ACTION = 'elementor-menu-cart-fragments';
|
||
const MENU_CART_LICENSE_FEATURE_NAME = 'woocommerce-menu-cart';
|
||
const SINGLE_PRODUCT_TEMPLATE_LICENSE_FEATURE_NAME = 'product-single';
|
||
const ARCHIVE_PRODUCT_TEMPLATE_LICENSE_FEATURE_NAME = 'product-archive';
|
||
const SITE_SETTINGS_PAGES_LICENSE_FEATURE_NAME = 'settings-woocommerce-pages';
|
||
const SITE_SETTINGS_NOTICES_LICENSE_FEATURE_NAME = 'settings-woocommerce-notices';
|
||
const DYNAMIC_TAGS_LICENSE_FEATURE_NAME = 'dynamic-tags-wc';
|
||
const LOOP_PRODUCT_SKIN_ID = 'product';
|
||
const LOOP_PRODUCT_TAXONOMY_SKIN_ID = 'product_taxonomy';
|
||
const WC_PERSISTENT_SITE_SETTINGS = [
|
||
'woocommerce_cart_page_id',
|
||
'woocommerce_checkout_page_id',
|
||
'woocommerce_myaccount_page_id',
|
||
'woocommerce_terms_page_id',
|
||
'woocommerce_purchase_summary_page_id',
|
||
'woocommerce_shop_page_id',
|
||
];
|
||
const WIDGET_NAME_CLASS_NAME_MAP = [
|
||
'woocommerce-products' => 'Products',
|
||
'wc-products' => 'Products_Deprecated',
|
||
'woocommerce-product-add-to-cart' => 'Product_Add_To_Cart',
|
||
'wc-elements' => 'Elements',
|
||
'wc-categories' => 'Categories',
|
||
'woocommerce-product-price' => 'Product_Price',
|
||
'woocommerce-product-title' => 'Product_Title',
|
||
'woocommerce-product-images' => 'Product_Images',
|
||
'woocommerce-product-upsell' => 'Product_Upsell',
|
||
'woocommerce-product-short-description' => 'Product_Short_Description',
|
||
'woocommerce-product-meta' => 'Product_Meta',
|
||
'woocommerce-product-stock' => 'Product_Stock',
|
||
'woocommerce-product-rating' => 'Product_Rating',
|
||
'woocommerce-product-data-tabs' => 'Product_Data_Tabs',
|
||
'woocommerce-product-related' => 'Product_Related',
|
||
'woocommerce-breadcrumb' => 'Breadcrumb',
|
||
'wc-add-to-cart' => 'Add_To_Cart',
|
||
'wc-archive-products' => 'Archive_Products',
|
||
'woocommerce-archive-products' => 'Archive_Products_Deprecated',
|
||
'woocommerce-product-additional-information' => 'Product_Additional_Information',
|
||
'woocommerce-menu-cart' => 'Menu_Cart',
|
||
'woocommerce-product-content' => 'Product_Content',
|
||
'woocommerce-archive-description' => 'Archive_Description',
|
||
'woocommerce-checkout-page' => 'Checkout',
|
||
'woocommerce-cart' => 'Cart',
|
||
'woocommerce-my-account' => 'My_Account',
|
||
'woocommerce-purchase-summary' => 'Purchase_Summary',
|
||
'woocommerce-notices' => 'Notices',
|
||
'wc-single-elements' => 'Single_Elements',
|
||
];
|
||
|
||
protected $docs_types = [];
|
||
protected $use_mini_cart_template;
|
||
protected $woocommerce_notices_elements = [];
|
||
|
||
public static function is_active() {
|
||
return class_exists( 'woocommerce' );
|
||
}
|
||
|
||
public static function is_product_search() {
|
||
return is_search() && 'product' === get_query_var( 'post_type' );
|
||
}
|
||
|
||
/**
|
||
* @param $settings
|
||
* @param string $icon
|
||
* @return void
|
||
*/
|
||
public static function render_menu_icon( $settings, string $icon ) {
|
||
if ( ! empty( $settings['icon'] ) && 'custom' === $settings['icon'] ) {
|
||
self::render_custom_menu_icon( $settings );
|
||
} else {
|
||
Icons_Manager::render_icon( [
|
||
'library' => 'eicons',
|
||
'value' => 'eicon-' . $icon,
|
||
] );
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param $settings
|
||
* @return void
|
||
*/
|
||
private static function render_custom_menu_icon( $settings ) {
|
||
if ( empty( $settings['menu_icon_svg'] ) ) {
|
||
echo '<i class="fas fa-shopping-cart"></i>'; // Default Custom icon.
|
||
} else {
|
||
Icons_Manager::render_icon( $settings['menu_icon_svg'], [
|
||
'class' => 'e-toggle-cart-custom-icon',
|
||
'aria-hidden' => 'true',
|
||
] );
|
||
}
|
||
}
|
||
|
||
public function get_name() {
|
||
return 'woocommerce';
|
||
}
|
||
|
||
public function get_widgets() {
|
||
return API::filter_active_features( static::WIDGET_NAME_CLASS_NAME_MAP );
|
||
}
|
||
|
||
const RECOMMENDED_POSTS_WIDGET_NAMES = [
|
||
'theme-post-featured-image',
|
||
'woocommerce-product-title',
|
||
'woocommerce-product-add-to-cart',
|
||
'woocommerce-product-price',
|
||
'woocommerce-product-rating',
|
||
'woocommerce-product-stock',
|
||
'woocommerce-product-meta',
|
||
'woocommerce-product-short-description',
|
||
'woocommerce-product-content',
|
||
'woocommerce-product-data-tabs',
|
||
'woocommerce-product-additional-information',
|
||
];
|
||
|
||
// 'WC page name' => 'Elementor widget name'
|
||
const WC_STATUS_PAGES_MAPPED_TO_WIDGETS = [
|
||
'Cart' => 'woocommerce-cart',
|
||
'Checkout' => 'woocommerce-checkout-page',
|
||
'My account' => 'woocommerce-my-account',
|
||
];
|
||
|
||
public function add_product_post_class( $classes ) {
|
||
$classes[] = 'product';
|
||
|
||
return $classes;
|
||
}
|
||
|
||
public function add_products_post_class_filter() {
|
||
add_filter( 'post_class', [ $this, 'add_product_post_class' ] );
|
||
}
|
||
|
||
public function remove_products_post_class_filter() {
|
||
remove_filter( 'post_class', [ $this, 'add_product_post_class' ] );
|
||
}
|
||
|
||
public function register_tags() {
|
||
if ( ! API::is_licence_has_feature( static::DYNAMIC_TAGS_LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) {
|
||
return;
|
||
}
|
||
|
||
$tags = [
|
||
'Product_Gallery',
|
||
'Product_Image',
|
||
'Product_Price',
|
||
'Product_Rating',
|
||
'Product_Sale',
|
||
'Product_Content',
|
||
'Product_Short_Description',
|
||
'Product_SKU',
|
||
'Product_Stock',
|
||
'Product_Terms',
|
||
'Product_Title',
|
||
'Category_Image',
|
||
'Woocommerce_Add_To_Cart',
|
||
];
|
||
|
||
/** @var \Elementor\Core\DynamicTags\Manager $module */
|
||
$module = Plugin::elementor()->dynamic_tags;
|
||
|
||
$module->register_group( self::WOOCOMMERCE_GROUP, [
|
||
'title' => esc_html__( 'WooCommerce', 'elementor-pro' ),
|
||
] );
|
||
|
||
foreach ( $tags as $tag ) {
|
||
$tag = 'ElementorPro\\Modules\\Woocommerce\\tags\\' . $tag;
|
||
|
||
$module->register( new $tag() );
|
||
}
|
||
}
|
||
|
||
public function register_wc_hooks() {
|
||
wc()->frontend_includes();
|
||
}
|
||
|
||
/**
|
||
* @param Conditions_Manager $conditions_manager
|
||
*/
|
||
public function register_conditions( $conditions_manager ) {
|
||
$woocommerce_condition = new Woocommerce();
|
||
|
||
$conditions_manager->get_condition( 'general' )->register_sub_condition( $woocommerce_condition );
|
||
}
|
||
|
||
/**
|
||
* @param Documents_Manager $documents_manager
|
||
*/
|
||
public function register_documents( $documents_manager ) {
|
||
if ( API::is_licence_has_feature( static::SINGLE_PRODUCT_TEMPLATE_LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) {
|
||
$this->docs_types = [
|
||
'product-post' => Product_Post::get_class_full_name(),
|
||
];
|
||
|
||
$this->docs_types['product'] = Product::get_class_full_name();
|
||
}
|
||
|
||
if ( API::is_licence_has_feature( static::ARCHIVE_PRODUCT_TEMPLATE_LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) {
|
||
$this->docs_types['product-archive'] = Product_Archive::get_class_full_name();
|
||
}
|
||
|
||
foreach ( $this->docs_types as $type => $class_name ) {
|
||
$documents_manager->register_document_type( $type, $class_name );
|
||
}
|
||
}
|
||
|
||
public static function render_menu_cart_toggle_button( $settings ) {
|
||
if ( null === WC()->cart ) {
|
||
return;
|
||
}
|
||
$product_count = WC()->cart->get_cart_contents_count();
|
||
$sub_total = WC()->cart->get_cart_subtotal();
|
||
$icon = ! empty( $settings['icon'] ) ? $settings['icon'] : 'cart-medium';
|
||
?>
|
||
<div class="elementor-menu-cart__toggle elementor-button-wrapper">
|
||
<a id="elementor-menu-cart__toggle_button" href="#" class="elementor-menu-cart__toggle_button elementor-button elementor-size-sm" aria-expanded="false">
|
||
<span class="elementor-button-text"><?php echo $sub_total; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
|
||
<span class="elementor-button-icon">
|
||
<span class="elementor-button-icon-qty" data-counter="<?php echo esc_attr( $product_count ); ?>"><?php echo $product_count; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
|
||
<?php
|
||
self::render_menu_icon( $settings, $icon );
|
||
?>
|
||
<span class="elementor-screen-only"><?php esc_html_e( 'Cart', 'elementor-pro' ); ?></span>
|
||
</span>
|
||
</a>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render Menu Cart
|
||
*
|
||
* The `widget_shopping_cart_content` div will be populated by woocommerce js.
|
||
*
|
||
* When in the editor we populate this on page load as we can't rely on the woocoommerce js to re-add the fragments
|
||
* each time a widget us re-rendered.
|
||
*/
|
||
public static function render_menu_cart( $settings ) {
|
||
if ( null === WC()->cart ) {
|
||
return;
|
||
}
|
||
|
||
$widget_cart_is_hidden = apply_filters( 'woocommerce_widget_cart_is_hidden', false );
|
||
$is_edit_mode = Plugin::elementor()->editor->is_edit_mode();
|
||
?>
|
||
<div class="elementor-menu-cart__wrapper">
|
||
<?php if ( ! $widget_cart_is_hidden ) : ?>
|
||
<div class="elementor-menu-cart__toggle_wrapper">
|
||
<div class="elementor-menu-cart__container elementor-lightbox" aria-hidden="true">
|
||
<div class="elementor-menu-cart__main" aria-hidden="true">
|
||
<?php self::render_menu_cart_close_button( $settings ); ?>
|
||
<div class="widget_shopping_cart_content">
|
||
<?php if ( $is_edit_mode ) {
|
||
woocommerce_mini_cart();
|
||
} ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php self::render_menu_cart_toggle_button( $settings ); ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div> <!-- close elementor-menu-cart__wrapper -->
|
||
<?php
|
||
}
|
||
|
||
public static function render_menu_cart_close_button( $settings ) {
|
||
$has_custom_icon = ! empty( $settings['close_cart_icon_svg']['value'] ) && 'yes' === $settings['close_cart_button_show'];
|
||
$toggle_button_class = 'elementor-menu-cart__close-button';
|
||
if ( $has_custom_icon ) {
|
||
$toggle_button_class .= '-custom';
|
||
}
|
||
?>
|
||
<div class="<?php echo sanitize_html_class( $toggle_button_class ); ?>">
|
||
<?php
|
||
if ( $has_custom_icon ) {
|
||
Icons_Manager::render_icon( $settings['close_cart_icon_svg'], [
|
||
'class' => 'e-close-cart-custom-icon',
|
||
'aria-hidden' => 'true',
|
||
] );
|
||
}
|
||
?>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Menu cart fragments.
|
||
*
|
||
* Ajax action to create fragments for the menu carts in a page.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function menu_cart_fragments() {
|
||
$all_fragments = [];
|
||
|
||
if ( ! isset( $_POST['_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['_nonce'] ), self::MENU_CART_FRAGMENTS_ACTION ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, it's used only for nonce verification
|
||
wp_send_json( [] );
|
||
}
|
||
|
||
$templates = ProUtils::_unstable_get_super_global_value( $_POST, 'templates' );
|
||
|
||
if ( ! is_array( $templates ) ) {
|
||
wp_send_json( [] );
|
||
}
|
||
|
||
if ( 'true' === ProUtils::_unstable_get_super_global_value( $_POST, 'is_editor' ) ) {
|
||
Plugin::elementor()->editor->set_edit_mode( true );
|
||
}
|
||
|
||
foreach ( $templates as $id ) {
|
||
$this->get_all_fragments( $id, $all_fragments );
|
||
}
|
||
|
||
wp_send_json( [ 'fragments' => $all_fragments ] );
|
||
}
|
||
|
||
/**
|
||
* Get All Fragments.
|
||
*
|
||
* @since 3.7.0
|
||
*
|
||
* @param $id
|
||
* @param $all_fragments
|
||
* @return void
|
||
*/
|
||
public function get_all_fragments( $id, &$all_fragments ) {
|
||
$fragments_in_document = $this->get_fragments_in_document( $id );
|
||
|
||
if ( $fragments_in_document ) {
|
||
$all_fragments += $fragments_in_document;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get Fragments In Document.
|
||
*
|
||
* A general function that will return any needed fragments for a Post.
|
||
*
|
||
* @since 3.7.0
|
||
* @access public
|
||
*
|
||
* @param int $id
|
||
*
|
||
* @return mixed $fragments
|
||
*/
|
||
public function get_fragments_in_document( $id ) {
|
||
$document = Plugin::elementor()->documents->get( $id );
|
||
|
||
if ( ! is_object( $document ) ) {
|
||
return false;
|
||
}
|
||
|
||
$fragments = [];
|
||
|
||
$data = $document->get_elements_data();
|
||
|
||
Plugin::elementor()->db->iterate_data(
|
||
$data,
|
||
$this->get_fragments_handler( $fragments )
|
||
);
|
||
|
||
return ! empty( $fragments ) ? $fragments : false;
|
||
}
|
||
|
||
/**
|
||
* Get Fragments Handler.
|
||
*
|
||
* @since 3.7.0
|
||
*
|
||
* @param array $fragments
|
||
* @return void
|
||
*/
|
||
public function get_fragments_handler( array &$fragments ) {
|
||
return function ( $element ) use ( &$fragments ) {
|
||
if ( ! isset( $element['widgetType'] ) ) {
|
||
return;
|
||
}
|
||
|
||
$fragment_data = $this->get_fragment_data( $element );
|
||
$total_fragments = count( $fragment_data ) / 2;
|
||
|
||
for ( $i = 0; $i < $total_fragments; $i++ ) {
|
||
$fragments[ $fragment_data['selector'][ $i ] ] = $fragment_data['html'][ $i ];
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Empty Cart Fragments
|
||
*
|
||
* When the Cart is emptied, the selected 'Empty Cart Template' needs to be added as an item
|
||
* in the WooCommerce `$fragments` array, so that WC will push the custom Template content into the DOM.
|
||
* This is done to prevent the need for a page refresh after the cart is cleared.
|
||
*
|
||
* @since 3.7.0
|
||
*
|
||
* @param array $fragments
|
||
* @return array
|
||
*/
|
||
public function empty_cart_fragments( $fragments ) {
|
||
// Only do this when the cart is empty.
|
||
if ( WC()->cart->get_cart_contents_count() !== 0 ) {
|
||
return $fragments;
|
||
}
|
||
|
||
$document = Plugin::elementor()->documents->get( url_to_postid( wp_get_referer() ) );
|
||
|
||
if ( is_object( $document ) ) {
|
||
$data = $document->get_elements_data();
|
||
|
||
Plugin::elementor()->db->iterate_data( $data, function( $element ) use ( &$fragments ) {
|
||
if (
|
||
isset( $element['widgetType'] )
|
||
&& 'woocommerce-cart' === $element['widgetType']
|
||
&& ( isset( $element['settings']['additional_template_switch'] ) && 'active' === $element['settings']['additional_template_switch'] )
|
||
&& ( isset( $element['settings']['additional_template_select'] ) && 0 < $element['settings']['additional_template_select'] )
|
||
) {
|
||
$fragments[ 'div.elementor-element-' . $element['id'] . ' .elementor-widget-container' ] = '<div class="elementor-widget-container">' . do_shortcode( '[elementor-template id="' . $element['settings']['additional_template_select'] . '"]' ) . '</div>';
|
||
}
|
||
} );
|
||
}
|
||
|
||
return $fragments;
|
||
}
|
||
|
||
public function maybe_init_cart() {
|
||
$has_cart = is_a( WC()->cart, 'WC_Cart' );
|
||
|
||
if ( ! $has_cart ) {
|
||
$session_class = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' );
|
||
WC()->session = new $session_class();
|
||
WC()->session->init();
|
||
WC()->cart = new \WC_Cart();
|
||
WC()->customer = new \WC_Customer( get_current_user_id(), true );
|
||
}
|
||
}
|
||
|
||
public function localized_settings_frontend( $settings ) {
|
||
$has_cart = is_a( WC()->cart, 'WC_Cart' );
|
||
|
||
if ( $has_cart ) {
|
||
$settings['woocommerce']['menu_cart'] = [
|
||
'cart_page_url' => wc_get_cart_url(),
|
||
'checkout_page_url' => wc_get_checkout_url(),
|
||
'fragments_nonce' => wp_create_nonce( self::MENU_CART_FRAGMENTS_ACTION ),
|
||
];
|
||
}
|
||
return $settings;
|
||
}
|
||
|
||
public function theme_template_include( $need_override_location, $location ) {
|
||
if ( is_product() && 'single' === $location ) {
|
||
$need_override_location = true;
|
||
}
|
||
|
||
return $need_override_location;
|
||
}
|
||
|
||
public function add_loop_recommended_widgets( $config, $post_id ) {
|
||
if ( ! $this->is_source_set_to_products( $post_id ) ) {
|
||
return $config;
|
||
}
|
||
|
||
$config = $this->add_woocommerce_widgets_to_recommended( $config );
|
||
return $this->hide_woocommerce_widgets_in_loop_document( $config );
|
||
}
|
||
|
||
/**
|
||
* Add plugin path to wc template search path.
|
||
* Based on: https://www.skyverge.com/blog/override-woocommerce-template-file-within-a-plugin/
|
||
* @param $template
|
||
* @param $template_name
|
||
* @param $template_path
|
||
*
|
||
* @return string
|
||
*/
|
||
public function woocommerce_locate_template( $template, $template_name, $template_path ) {
|
||
|
||
if ( self::TEMPLATE_MINI_CART !== $template_name ) {
|
||
return $template;
|
||
}
|
||
|
||
if ( ! $this->use_mini_cart_template ) {
|
||
return $template;
|
||
}
|
||
|
||
$plugin_path = plugin_dir_path( __DIR__ ) . 'woocommerce/wc-templates/';
|
||
|
||
if ( file_exists( $plugin_path . $template_name ) ) {
|
||
$template = $plugin_path . $template_name;
|
||
}
|
||
|
||
return $template;
|
||
}
|
||
|
||
/**
|
||
* WooCommerce/WordPress widget(s), some of the widgets have css classes that used by final selectors.
|
||
* before this filter, all those widgets were warped by `.elementor-widget-container` without chain original widget
|
||
* classes, now they will be warped by div with the original css classes.
|
||
*
|
||
* @param array $default_widget_args
|
||
* @param \Elementor\Widget_WordPress $widget
|
||
*
|
||
* @return array $default_widget_args
|
||
*/
|
||
public function woocommerce_wordpress_widget_css_class( $default_widget_args, $widget ) {
|
||
$widget_instance = $widget->get_widget_instance();
|
||
|
||
if ( ! empty( $widget_instance->widget_cssclass ) ) {
|
||
$default_widget_args['before_widget'] .= '<div class="' . $widget_instance->widget_cssclass . '">';
|
||
$default_widget_args['after_widget'] .= '</div>';
|
||
}
|
||
|
||
return $default_widget_args;
|
||
}
|
||
|
||
public function register_admin_fields( Settings $settings ) {
|
||
if ( ! API::is_licence_has_feature( static::MENU_CART_LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) {
|
||
return;
|
||
}
|
||
|
||
$settings->add_section( Settings::TAB_INTEGRATIONS, 'woocommerce', [
|
||
'callback' => function() {
|
||
echo '<hr><h2>' . esc_html__( 'WooCommerce', 'elementor-pro' ) . '</h2>';
|
||
},
|
||
'fields' => [
|
||
self::OPTION_NAME_USE_MINI_CART => [
|
||
'label' => esc_html__( 'Mini Cart Template', 'elementor-pro' ),
|
||
'field_args' => [
|
||
'type' => 'select',
|
||
'std' => 'initial',
|
||
'options' => [
|
||
'initial' => '', // Relevant until either menu-cart widget is used or option is explicitly set to 'no'.
|
||
'no' => esc_html__( 'Disable', 'elementor-pro' ),
|
||
'yes' => esc_html__( 'Enable', 'elementor-pro' ),
|
||
],
|
||
'desc' => esc_html__( 'Set to `Disable` in order to use your Theme\'s or WooCommerce\'s mini-cart template instead of Elementor\'s.', 'elementor-pro' ),
|
||
],
|
||
],
|
||
],
|
||
] );
|
||
}
|
||
|
||
/**
|
||
* Load Widget Before WooCommerce Ajax.
|
||
*
|
||
* When outputting the complex WooCommerce shortcodes (which we use in our widgets) e.g. Checkout, Cart, etc. WC
|
||
* immediately does more ajax calls and retrieves updated html fragments based on the data in the forms that may
|
||
* be autofilled by the current user's browser e.g. the Payment section holding the "Place order" button.
|
||
*
|
||
* This function runs before these ajax calls. Using the `elementorPageId` and `elementorWidgetId` querystring
|
||
* appended to the forms `_wp_http_referer` url field, or the referer page ID, it loads the relevant Elementor widget.
|
||
* The rendered Elementor widget replaces the default WooCommerce template used to refresh WooCommerce elements in the page.
|
||
*
|
||
* This is necessary for example in the Checkout Payment section where we modify the Terms & Conditions text
|
||
* using settings from the widget or when updating shipping methods on the Cart.
|
||
*
|
||
* @since 3.5.0
|
||
*/
|
||
public function load_widget_before_wc_ajax() {
|
||
// Make sure is a WooCommerce ajax call.
|
||
$wc_ajax = ProUtils::_unstable_get_super_global_value( $_GET, 'wc-ajax' );
|
||
if ( ! $wc_ajax ) {
|
||
return;
|
||
}
|
||
|
||
// Only handle relevant WC AJAX calls
|
||
if ( ! in_array( $wc_ajax, [ 'update_order_review', 'update_shipping_method' ], true ) ) {
|
||
return;
|
||
}
|
||
|
||
// Security checks.
|
||
switch ( $wc_ajax ) {
|
||
case 'update_order_review':
|
||
check_ajax_referer( 'update-order-review', 'security' );
|
||
break;
|
||
case 'update_shipping_method':
|
||
check_ajax_referer( 'update-shipping-method', 'security' );
|
||
break;
|
||
}
|
||
|
||
$page_id = false;
|
||
$widget_id = false;
|
||
|
||
// Try to get the `$page_id` and `$widget_id` we added as a query string to `_wp_http_referer` in `post_data`.
|
||
// This is only available when a form is submitted.
|
||
$raw_post_data = ProUtils::_unstable_get_super_global_value( $_POST, 'post_data' );
|
||
if ( $raw_post_data ) {
|
||
$raw_post_data = html_entity_decode( $raw_post_data );
|
||
|
||
parse_str( $raw_post_data, $post_data );
|
||
|
||
if ( isset( $post_data['_wp_http_referer'] ) ) {
|
||
$wp_http_referer = wp_unslash( $post_data['_wp_http_referer'] );
|
||
|
||
$wp_http_referer_query_string = wp_parse_url( $wp_http_referer, PHP_URL_QUERY );
|
||
if ( ! empty( $wp_http_referer_query_string ) ) {
|
||
parse_str( $wp_http_referer_query_string, $wp_http_referer_query_string );
|
||
|
||
if ( isset( $wp_http_referer_query_string['elementorPageId'] ) ) {
|
||
$page_id = $wp_http_referer_query_string['elementorPageId'];
|
||
}
|
||
|
||
if ( isset( $wp_http_referer_query_string['elementorWidgetId'] ) ) {
|
||
$widget_id = $wp_http_referer_query_string['elementorWidgetId'];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( ! $page_id ) {
|
||
$page_id = url_to_postid( wp_get_referer() );
|
||
}
|
||
|
||
// Bail if no `$page_id`.
|
||
if ( ! $page_id ) {
|
||
return;
|
||
}
|
||
|
||
// Get Elementor document from `$page_id`.
|
||
$document = Plugin::elementor()->documents->get_doc_for_frontend( $page_id );
|
||
|
||
// Bail if not Elementor page.
|
||
if ( ! $document ) {
|
||
return;
|
||
}
|
||
|
||
// Setup $page_id as the WP global $post, so is available to our widgets.
|
||
$post = get_post( $page_id, OBJECT );
|
||
setup_postdata( $post );
|
||
|
||
$widget_data = false;
|
||
if ( $widget_id ) {
|
||
// If we did manage to pass `$widget_id` to this ajax call we get the widget data by its ID.
|
||
$widget_data = Utils::find_element_recursive( $document->get_elements_data(), $widget_id );
|
||
} else {
|
||
// If we didn't manage to pass `$widget_id` to this ajax call we use this alternate method and get the first
|
||
// of the type of widget used on the WC endpoint pages responsible for these ajax calls - cart or checkout widget.
|
||
$woocommerce_widgets = [ 'woocommerce-cart', 'woocommerce-checkout-page' ];
|
||
|
||
$document_data = $document->get_elements_data();
|
||
Plugin::elementor()->db->iterate_data( $document_data, function( $element ) use ( $woocommerce_widgets, &$widget_data ) {
|
||
if ( $widget_data && ( ! isset( $element['widgetType'] ) || ! in_array( $element['widgetType'], $woocommerce_widgets, true ) ) ) {
|
||
return;
|
||
}
|
||
$widget_data = $element;
|
||
} );
|
||
}
|
||
|
||
// If we found a widget then run `add_render_hooks()` widget method.
|
||
if ( $widget_data ) {
|
||
$widget_instance = Plugin::elementor()->elements_manager->create_element_instance( $widget_data );
|
||
if ( method_exists( $widget_instance, 'add_render_hooks' ) ) {
|
||
$widget_instance->add_render_hooks();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Elementor Woocommerce Checkout Login User
|
||
*
|
||
* Handle the Ajax call for the custom login form on the Checkout Widget
|
||
*
|
||
* @since 3.5.0
|
||
*/
|
||
public function elementor_woocommerce_checkout_login_user() {
|
||
if ( is_user_logged_in() ) {
|
||
wp_logout();
|
||
}
|
||
|
||
$error = false;
|
||
$error_message = '';
|
||
|
||
if ( ! wp_verify_nonce( ProUtils::_unstable_get_super_global_value( $_POST, 'nonce' ), 'woocommerce-login' ) ) {
|
||
$error = true;
|
||
$error_message = sprintf(
|
||
/* translators: 1: Bold text opening tag, 2: Bold text closing tag. */
|
||
esc_html__( '%1$sError:%2$s The nonce security check didn’t pass. Please reload the page and try again. You may want to try clearing your browser cache as a last attempt.', 'elementor-pro' ),
|
||
'<strong>',
|
||
'</strong>'
|
||
);
|
||
} else {
|
||
$info = [
|
||
'user_login' => trim( ProUtils::_unstable_get_super_global_value( $_POST, 'username' ) ),
|
||
'user_password' => $_POST['password'] ?? '', // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, A password should not be sanitized.
|
||
'remember' => ProUtils::_unstable_get_super_global_value( $_POST, 'remember' ),
|
||
];
|
||
|
||
$user_signon = wp_signon( $info, false );
|
||
|
||
if ( is_wp_error( $user_signon ) ) {
|
||
$error = true;
|
||
$error_message = $user_signon->get_error_message();
|
||
}
|
||
}
|
||
|
||
if ( $error ) {
|
||
wc_add_notice(
|
||
$error_message,
|
||
'error'
|
||
);
|
||
$response = [
|
||
'logged_in' => false,
|
||
'message' => wc_print_notices( true ),
|
||
];
|
||
} else {
|
||
$response = [ 'logged_in' => true ];
|
||
}
|
||
|
||
echo wp_json_encode( $response );
|
||
wp_die();
|
||
}
|
||
|
||
/**
|
||
* Register Ajax Actions.
|
||
*
|
||
* Registers ajax action used by the Editor js.
|
||
*
|
||
* @since 3.5.0
|
||
*
|
||
* @param Ajax $ajax
|
||
*/
|
||
public function register_ajax_actions( Ajax $ajax ) {
|
||
// `woocommerce_update_page_option` is called in the editor save-show-modal.js.
|
||
$ajax->register_ajax_action( 'pro_woocommerce_update_page_option', [ $this, 'update_page_option' ] );
|
||
$ajax->register_ajax_action( 'pro_woocommerce_mock_notices', [ $this, 'woocommerce_mock_notices' ] );
|
||
}
|
||
|
||
/**
|
||
* @throws \Exception
|
||
*/
|
||
public function woocommerce_mock_notices( $data ) {
|
||
$document = ProUtils::_unstable_get_document_for_edit( $data['editor_post_id'] );
|
||
|
||
if ( in_array( 'wc_error', $data['notice_elements'], true ) ) {
|
||
$notice_message = sprintf(
|
||
'%1$s <a href="#" class="wc-backward">%2$s</a>',
|
||
esc_html__( 'This is how an error notice would look.', 'elementor-pro' ),
|
||
esc_html__( 'Here\'s a link', 'elementor-pro' )
|
||
);
|
||
wc_add_notice( $notice_message, 'error' );
|
||
}
|
||
|
||
if ( in_array( 'wc_message', $data['notice_elements'], true ) ) {
|
||
$notice_message = sprintf(
|
||
'<a href="#" tabindex="1" class="button wc-forward">%1$s</a> %2$s <a href="#" class="restore-item">%3$s</a>',
|
||
esc_html__( 'Button', 'elementor-pro' ),
|
||
esc_html__( 'This is what a WooCommerce message notice looks like.', 'elementor-pro' ),
|
||
esc_html__( 'Here\'s a link', 'elementor-pro' )
|
||
);
|
||
wc_add_notice( $notice_message, 'success' );
|
||
}
|
||
|
||
if ( in_array( 'wc_info', $data['notice_elements'], true ) ) {
|
||
$notice_message = sprintf(
|
||
'<a href="#" tabindex="1" class="button wc-forward">%1$s</a> %2$s',
|
||
esc_html__( 'Button', 'elementor-pro' ),
|
||
esc_html__( 'This is how WooCommerce provides an info notice.', 'elementor-pro' )
|
||
);
|
||
wc_add_notice( $notice_message, 'notice' );
|
||
}
|
||
|
||
return '<div class="woocommerce-notices-wrapper">' . wc_print_notices( true ) . '</div>';
|
||
}
|
||
|
||
/**
|
||
* Update Page Option.
|
||
*
|
||
* Ajax action can be used to update any WooCommerce option.
|
||
*
|
||
* @since 3.5.0
|
||
*
|
||
* @param array $data
|
||
*/
|
||
public function update_page_option( $data ) {
|
||
$is_admin = current_user_can( 'manage_options' );
|
||
$is_shop_manager = current_user_can( 'manage_woocommerce' );
|
||
$is_allowed = $is_admin || $is_shop_manager;
|
||
|
||
if ( ! $is_allowed ) {
|
||
return new \WP_Error( 401 );
|
||
}
|
||
|
||
$allowed_options = [
|
||
'woocommerce_checkout_page_id',
|
||
'woocommerce_cart_page_id',
|
||
'woocommerce_myaccount_page_id',
|
||
'elementor_woocommerce_purchase_summary_page_id',
|
||
];
|
||
|
||
$option_name = $data['option_name'];
|
||
$post_id = absint( $data['editor_post_id'] );
|
||
|
||
if ( ! in_array( $option_name, $allowed_options, true ) ) {
|
||
return new \WP_Error( 400 );
|
||
}
|
||
|
||
update_option( $option_name, $post_id );
|
||
}
|
||
|
||
public function init_site_settings( \Elementor\Core\Kits\Documents\Kit $kit ) {
|
||
$kit->register_tab( 'settings-woocommerce', \ElementorPro\Modules\Woocommerce\Settings\Settings_Woocommerce::class );
|
||
}
|
||
|
||
public function add_products_type_to_template_popup( $form ) {
|
||
$this->add_products_to_options( $form, '_elementor_source' );
|
||
}
|
||
|
||
public function add_products_type_to_loop_settings_query( $form ) {
|
||
$this->add_products_to_options( $form, 'source' );
|
||
}
|
||
|
||
public function add_products_taxonomy_type_to_template_popup( $form ) {
|
||
$this->add_taxonomies_to_options( $form, '_elementor_source' );
|
||
}
|
||
|
||
public function add_products_taxonomy_type_to_loop_settings_query( $form ) {
|
||
$this->add_taxonomies_to_options( $form, 'source' );
|
||
}
|
||
|
||
public function e_cart_count_fragments( $fragments ) {
|
||
$product_count = WC()->cart->get_cart_contents_count();
|
||
|
||
$fragments['.elementor-menu-cart__toggle_button span.elementor-button-text'] = '<span class="elementor-button-text">' . WC()->cart->get_cart_subtotal() . '</span>';
|
||
$fragments['.elementor-menu-cart__toggle_button span.elementor-button-icon-qty'] = '<span class="elementor-button-icon-qty" data-counter=' . $product_count . '>' . $product_count . '</span>';
|
||
|
||
return $fragments;
|
||
}
|
||
|
||
/**
|
||
* @param $form
|
||
* @param $control_name
|
||
* @return void
|
||
*/
|
||
protected function add_products_to_options( $form, $control_name ) {
|
||
if ( empty( $form ) ) {
|
||
return;
|
||
}
|
||
|
||
$controls = $form->get_controls( $control_name );
|
||
if ( ! $controls || ! isset( $controls['options'] ) ) {
|
||
return;
|
||
}
|
||
|
||
$options = $controls['options'];
|
||
$options[ self::LOOP_PRODUCT_SKIN_ID ] = esc_html__( 'Products', 'elementor-pro' );
|
||
|
||
$form->update_control( $control_name, [
|
||
'options' => $options,
|
||
] );
|
||
}
|
||
|
||
protected function add_taxonomies_to_options( $form, $control_name ) {
|
||
$controls = $form->get_controls( $control_name );
|
||
|
||
if ( ! $controls || ! isset( $controls['options'] ) ) {
|
||
return;
|
||
}
|
||
|
||
$options = $controls['options'];
|
||
$options[ self::LOOP_PRODUCT_TAXONOMY_SKIN_ID ] = esc_html__( 'Product Taxonomy', 'elementor-pro' );
|
||
|
||
$form->update_control($control_name, [
|
||
'options' => $options,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Add Update Kit Settings Hooks
|
||
*
|
||
* Add hooks that update the corresponding kit setting when the WooCommerce option is updated.
|
||
*/
|
||
public function add_update_kit_settings_hooks() {
|
||
add_action( 'update_option_woocommerce_cart_page_id', function( $old_value, $value ) {
|
||
Plugin::elementor()->kits_manager->update_kit_settings_based_on_option( 'woocommerce_cart_page_id', $value );
|
||
}, 10, 2 );
|
||
|
||
add_action( 'update_option_woocommerce_checkout_page_id', function( $old_value, $value ) {
|
||
Plugin::elementor()->kits_manager->update_kit_settings_based_on_option( 'woocommerce_checkout_page_id', $value );
|
||
}, 10, 2 );
|
||
|
||
add_action( 'update_option_woocommerce_myaccount_page_id', function( $old_value, $value ) {
|
||
Plugin::elementor()->kits_manager->update_kit_settings_based_on_option( 'woocommerce_myaccount_page_id', $value );
|
||
}, 10, 2 );
|
||
|
||
add_action( 'update_option_woocommerce_terms_page_id', function( $old_value, $value ) {
|
||
Plugin::elementor()->kits_manager->update_kit_settings_based_on_option( 'woocommerce_terms_page_id', $value );
|
||
}, 10, 2 );
|
||
}
|
||
|
||
/**
|
||
* Elementor WC My Account Logout
|
||
*
|
||
* Programatically log out if $_REQUEST['elementor_wc_logout'] is set.
|
||
* The $_REQUEST variables we have generated a custom logout URL for in the My Account menu.
|
||
*
|
||
* @since 3.5.0
|
||
*/
|
||
public function elementor_wc_my_account_logout() {
|
||
$elementor_wc_logout = ProUtils::_unstable_get_super_global_value( $_REQUEST, 'elementor_wc_logout' );
|
||
$nonce = ProUtils::_unstable_get_super_global_value( $_REQUEST, '_wpnonce' );
|
||
|
||
if ( $elementor_wc_logout && $nonce && wp_verify_nonce( $nonce, 'customer-logout' ) ) {
|
||
wp_logout(); // Log the user out Programatically.
|
||
wp_safe_redirect( esc_url( ProUtils::_unstable_get_super_global_value( $_REQUEST, 'elementor_my_account_redirect' ) ) ); // Redirect back to the widget page.
|
||
exit;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Add Localize Data
|
||
*
|
||
* Makes `woocommercePages` available with the page name and the associated post ID for use with the various
|
||
* widgets site settings modal.
|
||
*
|
||
* @param $settings
|
||
* @return array
|
||
*/
|
||
public function add_localize_data( $settings ) {
|
||
$settings['woocommerce']['woocommercePages'] = [
|
||
'checkout' => wc_get_page_id( 'checkout' ),
|
||
'cart' => wc_get_page_id( 'cart' ),
|
||
'myaccount' => wc_get_page_id( 'myaccount' ),
|
||
'purchase_summary' => get_option( 'elementor_woocommerce_purchase_summary_page_id' ),
|
||
];
|
||
|
||
return $settings;
|
||
}
|
||
|
||
/**
|
||
* Localize Added To Cart On Product Single
|
||
*
|
||
* WooCommerce doesn't trigger `added_to_cart` event on its products single page which is required for us to
|
||
* automatically open our Menu Cart if the settings is chosen. We make the `productAddedToCart` setting
|
||
* available that we can use in the Menu Cart js to check if a product has just been added.
|
||
*
|
||
* @since 3.5.0
|
||
*/
|
||
public function localize_added_to_cart_on_product_single() {
|
||
add_filter( 'elementor_pro/frontend/localize_settings', function ( $settings ) {
|
||
$settings['woocommerce']['productAddedToCart'] = true;
|
||
return $settings;
|
||
} );
|
||
}
|
||
|
||
public function e_notices_body_classes( $classes ) {
|
||
if ( $this->should_load_wc_notices_styles() ) {
|
||
foreach ( $this->get_styled_notice_elements() as $notice_element ) {
|
||
$classes[] = 'e-' . str_replace( '_', '-', $notice_element ) . '-notice';
|
||
}
|
||
}
|
||
|
||
return $classes;
|
||
}
|
||
|
||
public function get_styled_notice_elements() {
|
||
if ( empty( $this->woocommerce_notices_elements ) ) {
|
||
$kit = Plugin::elementor()->kits_manager->get_active_kit_for_frontend();
|
||
$this->woocommerce_notices_elements = $kit->get_settings_for_display( 'woocommerce_notices_elements' );
|
||
}
|
||
|
||
return ! empty( $this->woocommerce_notices_elements ) ? $this->woocommerce_notices_elements : [];
|
||
}
|
||
|
||
public function custom_gutenberg_woocommerce_notice() {
|
||
$min_suffix = Utils::is_script_debug() ? '' : '.min';
|
||
|
||
wp_enqueue_script(
|
||
'elementor-gutenberg-woocommerce-notice',
|
||
ELEMENTOR_PRO_URL . '/assets/js/gutenberg-woocommerce-notice' . $min_suffix . '.js',
|
||
[ 'wp-blocks' ],
|
||
ELEMENTOR_PRO_VERSION,
|
||
false
|
||
);
|
||
|
||
wp_set_script_translations( 'elementor-gutenberg-woocommerce-notice', 'elementor-pro' );
|
||
}
|
||
|
||
public function e_notices_css() {
|
||
if ( ! $this->should_load_wc_notices_styles() ) {
|
||
return false;
|
||
}
|
||
|
||
wp_enqueue_style(
|
||
'e-woocommerce-notices',
|
||
ELEMENTOR_PRO_URL . 'assets/css/woocommerce-notices.min.css',
|
||
[],
|
||
ELEMENTOR_PRO_VERSION
|
||
);
|
||
}
|
||
|
||
public function get_order_received_endpoint_url( $url, $endpoint, $value ) {
|
||
$order_received_endpoint = get_option( 'woocommerce_checkout_order_received_endpoint', 'order-received' );
|
||
|
||
if ( $order_received_endpoint === $endpoint ) {
|
||
$woocommerce_purchase_summary_page_id = get_option( 'elementor_woocommerce_purchase_summary_page_id' );
|
||
$order = wc_get_order( $value );
|
||
|
||
if ( $woocommerce_purchase_summary_page_id && $order ) {
|
||
$url = trailingslashit( trailingslashit( trailingslashit( get_permalink( $woocommerce_purchase_summary_page_id ) ) . $order_received_endpoint ) . $order->get_id() );
|
||
}
|
||
}
|
||
|
||
return $url;
|
||
}
|
||
|
||
public function maybe_define_woocommerce_checkout() {
|
||
$woocommerce_purchase_summary_page_id = get_option( 'elementor_woocommerce_purchase_summary_page_id' );
|
||
|
||
if ( $woocommerce_purchase_summary_page_id && intval( $woocommerce_purchase_summary_page_id ) === get_queried_object_id() ) {
|
||
if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
|
||
define( 'WOOCOMMERCE_CHECKOUT', true );
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Products Query Sources Fragments.
|
||
*
|
||
* Since we introduced additional query sources to the Products Widget,
|
||
* some of these query sources can now be used outside of the Single Product template.
|
||
*
|
||
* For example the Related Products and Cross-Sells.
|
||
*
|
||
* But now we'll need to make those sections also update when the Cart is updated. So
|
||
* we'll do this by creating fragments for each of these.
|
||
*
|
||
* @since 3.7.0
|
||
*
|
||
* @param array $fragments
|
||
*
|
||
* @return array
|
||
*/
|
||
public function products_query_sources_fragments( $fragments ) {
|
||
if ( WC()->cart->get_cart_contents_count() !== 0 ) {
|
||
$document = Plugin::elementor()->documents->get( url_to_postid( wp_get_referer() ) );
|
||
|
||
if ( is_object( $document ) ) {
|
||
$data = $document->get_elements_data();
|
||
|
||
Plugin::elementor()->db->iterate_data( $data, function( $element ) use ( &$fragments ) {
|
||
if (
|
||
isset( $element['widgetType'] )
|
||
&& 'woocommerce-products' === $element['widgetType']
|
||
) {
|
||
$settings = $element['settings'];
|
||
if ( isset( $settings[ Products_Renderer::QUERY_CONTROL_NAME . '_post_type' ] ) ) {
|
||
$query_type = $settings[ Products_Renderer::QUERY_CONTROL_NAME . '_post_type' ];
|
||
$query_types_to_check = [ 'related_products', 'upsells', 'cross_sells' ];
|
||
|
||
if ( in_array( $query_type, $query_types_to_check, true ) ) {
|
||
switch ( $query_type ) {
|
||
case 'related_products':
|
||
$content = self::get_products_related_content( $settings );
|
||
break;
|
||
case 'upsells':
|
||
$content = self::get_upsells_content( $settings );
|
||
break;
|
||
case 'cross_sells':
|
||
$content = self::get_cross_sells_content( $settings );
|
||
break;
|
||
default:
|
||
$content = null;
|
||
}
|
||
|
||
if ( $content ) {
|
||
$fragments[ 'div.elementor-element-' . $element['id'] . ' div.elementor-widget-container' ] = '<div class="elementor-widget-container">' . $content . '</div>';
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} );
|
||
}
|
||
} else {
|
||
$fragments['div.elementor-widget-container .woocommerce .cross-sells'] = '<div class="cross-sells"></div>';
|
||
|
||
$fragments['div.elementor-widget-container .woocommerce section.up-sells'] = '<section class="up-sells upsells products"></section>';
|
||
}
|
||
|
||
return $fragments;
|
||
}
|
||
|
||
/**
|
||
* Get Products Related Content.
|
||
*
|
||
* A function to return content for the 'related' products query type in the Products widget.
|
||
* This function is declared in the Module file so it can be accessed during a WC fragment refresh
|
||
* and also be used in the Product widget's render method.
|
||
*
|
||
* @since 3.7.0
|
||
* @access public
|
||
*
|
||
* @param array $settings
|
||
*
|
||
* @return mixed The content or false
|
||
*/
|
||
public static function get_products_related_content( $settings ) {
|
||
global $product;
|
||
|
||
$product = wc_get_product();
|
||
|
||
if ( ! $product ) {
|
||
return;
|
||
}
|
||
|
||
return self::get_product_widget_content(
|
||
$settings,
|
||
'related_products',
|
||
'woocommerce_product_related_products_heading',
|
||
'products_related_title_text'
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Get Upsells Content.
|
||
*
|
||
* A function to return content for the 'upsell' query type in the Products widget.
|
||
* This function is declared in the Module file so it can be accessed during a WC fragment refresh
|
||
* and also be used in the Product widget's render method.
|
||
*
|
||
* @since 3.7.0
|
||
* @access public
|
||
*
|
||
* @param array $settings
|
||
*
|
||
* @return mixed The content or false
|
||
*/
|
||
public static function get_upsells_content( $settings ) {
|
||
return self::get_product_widget_content(
|
||
$settings,
|
||
'upsells',
|
||
'woocommerce_product_upsells_products_heading',
|
||
'products_upsells_title_text'
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Get Cross Sells Content.
|
||
*
|
||
* A function to return content for the 'cross_sells' query type in the Products widget.
|
||
* This function is declared in the Module file so it can be accessed during a WC fragment refresh
|
||
* and also be used in the Product widget's render method.
|
||
*
|
||
* @since 3.7.0
|
||
* @access public
|
||
*
|
||
* @param array $settings
|
||
*
|
||
* @return mixed The content or false
|
||
*/
|
||
public static function get_cross_sells_content( $settings ) {
|
||
return self::get_product_widget_content(
|
||
$settings,
|
||
'cross_sells',
|
||
'woocommerce_product_cross_sells_products_heading',
|
||
'products_cross_sells_title_text'
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Print Woocommerce Shipping Message
|
||
*
|
||
* Format the shipping messages that will be displayed on the Cart and Checkout Widgets.
|
||
* This will add extra classes to those messages so that we can target certain messages
|
||
* with certain style controls.
|
||
*
|
||
* @since 3.5.0
|
||
*
|
||
* @param string $html the original HTML from WC
|
||
* @param string $classes the classes we will surround $html with
|
||
* @return string the final formatted HTML that will be rendered
|
||
*/
|
||
private function print_woocommerce_shipping_message( $html, $classes ) {
|
||
return '<span class="' . wp_sprintf( '%s', $classes ) . '">' . $html . '</span>';
|
||
}
|
||
|
||
/**
|
||
* Should load WC Notices Styles
|
||
*
|
||
* Determine if we should load the WooCommerce notices CSS.
|
||
* It should only load:
|
||
* - When we are in the Editor, regardless if any notices have been activated.
|
||
* - If WooCoomerce is active.
|
||
* - When we are on the front end, if at least one notice is activated.
|
||
*
|
||
* It should not load in WP Admin.
|
||
*
|
||
* @return boolean
|
||
*/
|
||
private function should_load_wc_notices_styles() {
|
||
if ( ! API::is_licence_has_feature( static::SITE_SETTINGS_NOTICES_LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) {
|
||
return false;
|
||
}
|
||
|
||
$woocommerce_active = in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) );
|
||
$is_editor = ProUtils::_unstable_get_super_global_value( $_GET, 'elementor-preview' );
|
||
|
||
// Editor checks.
|
||
if ( $woocommerce_active && $is_editor ) {
|
||
return true;
|
||
}
|
||
|
||
// Front end checks.
|
||
if (
|
||
0 < count( $this->get_styled_notice_elements() ) // At least one notice has been activated.
|
||
&& $woocommerce_active // WooCommerce is active.
|
||
&& ( ! is_admin() || $is_editor ) // We are not in WP Admin.
|
||
) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Get Product Widget Content.
|
||
*
|
||
* A general function to create markup for the new query types in the Products widget.
|
||
*
|
||
* @since 3.7.0
|
||
* @access private
|
||
*
|
||
* @param array $settings The widget settings.
|
||
* @param string $type The query type to create content for.
|
||
* @param string $title_hook The hook name to filter in the widget title.
|
||
* @param string $title_key The control ID for the section title.
|
||
*
|
||
* @return mixed The content or false
|
||
*/
|
||
private static function get_product_widget_content( $settings, $type, $title_hook, $title_key = '' ) {
|
||
add_filter( $title_hook, function ( $heading ) use ( $settings, $title_key ) {
|
||
$title_text = isset( $settings[ $title_key ] ) ? $settings[ $title_key ] : '';
|
||
|
||
if ( ! empty( $title_text ) ) {
|
||
return $title_text;
|
||
}
|
||
|
||
return $heading;
|
||
}, 10, 1 );
|
||
|
||
ob_start();
|
||
|
||
$args = self::parse_product_widget_args( $settings, $type );
|
||
|
||
if ( 'related_products' === $type ) {
|
||
woocommerce_related_products( $args );
|
||
} elseif ( 'upsells' === $type ) {
|
||
woocommerce_upsell_display( $args['limit'], $args['columns'], $args['orderby'], $args['order'] );
|
||
} else {
|
||
/**
|
||
* We need to wrap this content in the 'woocommerce' class for the layout to have the correct styling.
|
||
* Because this will only be used as a separate widget on the Cart page,
|
||
* the normal 'woocommerce' div from the cart widget will be closed before this content.
|
||
*/
|
||
echo '<div class="woocommerce">';
|
||
woocommerce_cross_sell_display( $args['limit'], $args['columns'], $args['orderby'], $args['order'] );
|
||
echo '</div>';
|
||
}
|
||
|
||
$products_html = ob_get_clean();
|
||
|
||
remove_filter( $title_hook, function() {}, 10 );
|
||
|
||
if ( $products_html ) {
|
||
$products_html = str_replace( '<ul class="products', '<ul class="products elementor-grid', $products_html );
|
||
|
||
return wp_kses_post( $products_html );
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Parse Product Widget Args.
|
||
*
|
||
* A general function to construct an arguments array for the new query types in the
|
||
* Products widget according to the widget's settings.
|
||
* These arguments will later be passed to the WooCommerce template functions.
|
||
*
|
||
* @since 3.7.0
|
||
* @access private
|
||
*
|
||
* @param array $settings The widget settings.
|
||
* @param string $type The query type to create arguments for.
|
||
*
|
||
* @return array $args
|
||
*/
|
||
private static function parse_product_widget_args( $settings, $type = 'related_products' ) {
|
||
$limit_key = 'related_products' === $type ? 'posts_per_page' : 'limit';
|
||
$query_name = Products_Renderer::QUERY_CONTROL_NAME;
|
||
|
||
$args = [
|
||
$limit_key => '-1',
|
||
'columns' => ! empty( $settings['columns'] ) ? $settings['columns'] : 4,
|
||
'orderby' => ! empty( $settings[ "{$query_name}_orderby" ] ) ? $settings[ "{$query_name}_orderby" ] : 'rand',
|
||
'order' => ! empty( $settings[ "{$query_name}_order" ] ) ? $settings[ "{$query_name}_order" ] : 'desc',
|
||
];
|
||
|
||
if ( ! empty( $settings['rows'] ) ) {
|
||
$args[ $limit_key ] = $args['columns'] * $settings['rows'];
|
||
}
|
||
|
||
return $args;
|
||
}
|
||
|
||
/**
|
||
* Get Fragment Data.
|
||
*
|
||
* A function that will return the selector and HTML for WC fragments.
|
||
*
|
||
* @since 3.7.0
|
||
* @access private
|
||
*
|
||
* @param array $element
|
||
*
|
||
* @return array $fragment_data
|
||
*/
|
||
private function get_fragment_data( $element ) {
|
||
$fragment_data = [];
|
||
|
||
if ( 'woocommerce-menu-cart' === $element['widgetType'] ) {
|
||
ob_start();
|
||
self::render_menu_cart_toggle_button( $element['settings'] );
|
||
$fragment_data['html'][] = ob_get_clean();
|
||
|
||
$fragment_data['selector'][] = 'div.elementor-element-' . $element['id'] . ' div.elementor-menu-cart__toggle';
|
||
}
|
||
|
||
return $fragment_data;
|
||
}
|
||
|
||
/**
|
||
* Is Preview
|
||
*
|
||
* Helper to check if we are doing either:
|
||
* - Viewing the WP Preview page - also used as the Elementor preview page when clicking "Preview Changes" in the editor
|
||
* - Viewing the page in the editor, but not the active page being edited e.g. if you click Edit Header while editing a page
|
||
*
|
||
* @since 3.7.0
|
||
*
|
||
* @return bool
|
||
*/
|
||
public static function is_preview() {
|
||
return Plugin::elementor()->preview->is_preview_mode() || is_preview();
|
||
}
|
||
|
||
public function __construct() {
|
||
parent::__construct();
|
||
|
||
new WoocommerceDataController();
|
||
|
||
add_action( 'elementor/kit/register_tabs', [ $this, 'init_site_settings' ], 1, 40 );
|
||
$this->add_update_kit_settings_hooks();
|
||
|
||
add_action( 'elementor/template-library/create_new_dialog_fields', [ $this, 'add_products_type_to_template_popup' ], 11 );
|
||
add_action( 'elementor-pro/modules/loop-builder/documents/loop/query_settings', [ $this, 'add_products_type_to_loop_settings_query' ], 11 );
|
||
|
||
add_action( 'elementor/template-library/create_new_dialog_fields', [ $this, 'add_products_taxonomy_type_to_template_popup' ], 13 );
|
||
add_action( 'elementor-pro/modules/loop-builder/documents/loop/query_settings', [ $this, 'add_products_taxonomy_type_to_loop_settings_query' ], 13 );
|
||
|
||
$this->use_mini_cart_template = 'yes' === get_option( 'elementor_' . self::OPTION_NAME_USE_MINI_CART, 'no' );
|
||
|
||
if ( is_admin() ) {
|
||
add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_fields' ], 15 );
|
||
}
|
||
|
||
add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'maybe_init_cart' ] );
|
||
add_action( 'elementor/dynamic_tags/register', [ $this, 'register_tags' ] );
|
||
add_action( 'elementor/documents/register', [ $this, 'register_documents' ] );
|
||
add_action( 'elementor/theme/register_conditions', [ $this, 'register_conditions' ] );
|
||
|
||
add_action( 'wp_ajax_elementor_woocommerce_checkout_login_user', [ $this, 'elementor_woocommerce_checkout_login_user' ] );
|
||
add_action( 'wp_ajax_nopriv_elementor_woocommerce_checkout_login_user', [ $this, 'elementor_woocommerce_checkout_login_user' ] );
|
||
|
||
add_action( 'wp_ajax_elementor_menu_cart_fragments', [ $this, 'menu_cart_fragments' ] );
|
||
add_action( 'wp_ajax_nopriv_elementor_menu_cart_fragments', [ $this, 'menu_cart_fragments' ] );
|
||
|
||
add_filter( 'woocommerce_add_to_cart_fragments', [ $this, 'e_cart_count_fragments' ] );
|
||
|
||
add_filter( 'elementor/theme/need_override_location', [ $this, 'theme_template_include' ], 10, 2 );
|
||
|
||
add_filter( 'elementor/document/config', [ $this, 'add_loop_recommended_widgets' ], 11, 2 );
|
||
|
||
add_filter( 'elementor_pro/frontend/localize_settings', [ $this, 'localized_settings_frontend' ] );
|
||
|
||
// Load our widget Before WooCommerce Ajax. See the variable's PHPDoc for details.
|
||
add_action( 'woocommerce_checkout_update_order_review', [ $this, 'load_widget_before_wc_ajax' ] );
|
||
add_action( 'woocommerce_before_calculate_totals', [ $this, 'load_widget_before_wc_ajax' ] );
|
||
|
||
// On Editor - Register WooCommerce frontend hooks before the Editor init.
|
||
// Priority = 5, in order to allow plugins remove/add their wc hooks on init.
|
||
$action = ProUtils::_unstable_get_super_global_value( $_REQUEST, 'action' );
|
||
if ( 'elementor' === $action && is_admin() ) {
|
||
add_action( 'init', [ $this, 'register_wc_hooks' ], 5 );
|
||
}
|
||
|
||
// Allow viewing of Checkout page in the Editor with an empty cart.
|
||
if (
|
||
( 'elementor' === $action && is_admin() ) // Elementor Editor
|
||
|| 'elementor_ajax' === $action // Elementor Editor Preview - Ajax Render Widget
|
||
|| ProUtils::_unstable_get_super_global_value( $_REQUEST, 'elementor-preview' ) // Elementor Editor Preview
|
||
) {
|
||
add_filter( 'woocommerce_checkout_redirect_empty_cart', '__return_false', 5 );
|
||
}
|
||
|
||
if ( $this->use_mini_cart_template ) {
|
||
add_filter( 'woocommerce_locate_template', [ $this, 'woocommerce_locate_template' ], 10, 3 );
|
||
}
|
||
|
||
$wc_ajax = ProUtils::_unstable_get_super_global_value( $_REQUEST, 'wc-ajax' );
|
||
if ( 'get_refreshed_fragments' === $wc_ajax ) {
|
||
add_action( 'woocommerce_add_to_cart_fragments', [ $this, 'products_query_sources_fragments' ] );
|
||
// Render the Empty Cart Template on WC fragment refresh
|
||
add_action( 'woocommerce_add_to_cart_fragments', [ $this, 'empty_cart_fragments' ] );
|
||
}
|
||
|
||
add_filter( 'elementor/widgets/wordpress/widget_args', [ $this, 'woocommerce_wordpress_widget_css_class' ], 10, 2 );
|
||
|
||
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
|
||
|
||
// Make the Logout redirect go to our my account widget page instead of the set My Account Page.
|
||
add_action( 'init', [ $this, 'elementor_wc_my_account_logout' ], 5 );
|
||
|
||
add_filter( 'elementor_pro/editor/localize_settings', [ $this, 'add_localize_data' ] );
|
||
|
||
add_action( 'wp', [ $this, 'maybe_define_woocommerce_checkout' ] );
|
||
|
||
add_filter( 'woocommerce_get_endpoint_url', [ $this, 'get_order_received_endpoint_url' ], 10, 3 );
|
||
|
||
// Filters for messages on the Shipping calculator
|
||
add_filter( 'woocommerce_shipping_may_be_available_html', function ( $html ) {
|
||
return $this->print_woocommerce_shipping_message( $html, 'woocommerce-shipping-may-be-available-html e-checkout-message e-cart-content' );
|
||
}, 10, 1 );
|
||
|
||
add_filter( 'woocommerce_shipping_not_enabled_on_cart_html', function ( $html ) {
|
||
return $this->print_woocommerce_shipping_message( $html, 'woocommerce-shipping-not_enabled-on-cart-html e-checkout-message e-cart-content' );
|
||
}, 10, 1 );
|
||
|
||
add_filter( 'woocommerce_shipping_estimate_html', function ( $html ) {
|
||
return $this->print_woocommerce_shipping_message( $html, 'woocommerce-shipping-estimate-html e-checkout-message e-cart-content' );
|
||
}, 10, 1 );
|
||
|
||
add_filter( 'woocommerce_cart_no_shipping_available_html', function ( $html ) {
|
||
return $this->print_woocommerce_shipping_message( $html, 'woocommerce-cart-no-shipping-available-html e-checkout-message e-cart-content' );
|
||
}, 10, 1 );
|
||
|
||
add_filter( 'woocommerce_no_available_payment_methods_message', function ( $html ) {
|
||
return $this->print_woocommerce_shipping_message( $html, 'woocommerce-no-available-payment-methods-message e-description' );
|
||
}, 10, 1 );
|
||
|
||
add_filter( 'woocommerce_no_shipping_available_html', function ( $html ) {
|
||
return $this->print_woocommerce_shipping_message( $html, 'woocommerce-no-shipping-available-html e-checkout-message' );
|
||
}, 10, 1 );
|
||
|
||
add_action( 'woocommerce_add_to_cart', [ $this, 'localize_added_to_cart_on_product_single' ] );
|
||
|
||
foreach ( LoopBuilderModule::LOOP_WIDGETS as $widget_type ) {
|
||
add_action( 'elementor/widget/' . $widget_type . '/skins_init', function( Widget_Base $widget ) {
|
||
$widget->add_skin( new Skin_Loop_Product( $widget ) );
|
||
} );
|
||
add_action( 'elementor/widget/' . $widget_type . '/skins_init', function ( Widget_Base $widget ) {
|
||
$widget->add_skin( new Skin_Loop_Product_Taxonomy( $widget ) );
|
||
}, 13 );
|
||
}
|
||
|
||
// WooCommerce Notice Site Settings
|
||
add_filter( 'body_class', [ $this, 'e_notices_body_classes' ] );
|
||
add_filter( 'wp_enqueue_scripts', [ $this, 'e_notices_css' ] );
|
||
add_action( 'enqueue_block_editor_assets', [ $this, 'custom_gutenberg_woocommerce_notice' ] );
|
||
|
||
add_filter( 'elementor/query/query_args', function( $query_args, $widget ) {
|
||
return $this->loop_query( $query_args, $widget );
|
||
}, 10, 2 );
|
||
|
||
add_filter( 'woocommerce_rest_prepare_system_status', function( $response, $system_status, $request ) {
|
||
return $this->add_system_status_data( $response, $system_status, $request );
|
||
}, 10, 3 );
|
||
|
||
add_filter( 'elementor/editor/localize_settings', function ( $config ) {
|
||
return $this->populate_persistent_settings( $config );
|
||
});
|
||
}
|
||
|
||
public function add_system_status_data( $response, $system_status, $request ) {
|
||
foreach ( $response->data['pages'] as $index => $wc_page ) {
|
||
$this->modify_response_if_widget_exists_in_page( $wc_page, $response, $index );
|
||
}
|
||
|
||
return $response;
|
||
}
|
||
|
||
private function modify_response_if_widget_exists_in_page( $wc_page, &$response, $index ) {
|
||
if ( empty( $wc_page['page_name'] ) || empty( $wc_page['page_id'] ) || ! array_key_exists( $wc_page['page_name'], static::WC_STATUS_PAGES_MAPPED_TO_WIDGETS ) ) {
|
||
return;
|
||
}
|
||
|
||
if ( isset( $wc_page['shortcode_present'] ) && false !== $wc_page['shortcode_present'] ) {
|
||
return;
|
||
}
|
||
|
||
$document = Plugin::elementor()->documents->get( $wc_page['page_id'] );
|
||
|
||
if ( ! $document || ! $document->is_built_with_elementor() ) {
|
||
return;
|
||
}
|
||
|
||
$elementor_data = get_post_meta( $wc_page['page_id'], '_elementor_data', true );
|
||
$widget_name = static::WC_STATUS_PAGES_MAPPED_TO_WIDGETS[ $wc_page['page_name'] ];
|
||
$widget_exists_in_page = false !== strpos( $elementor_data, $widget_name );
|
||
|
||
if ( $widget_exists_in_page ) {
|
||
$response->data['pages'][ $index ]['shortcode_present'] = true;
|
||
}
|
||
}
|
||
|
||
public function loop_query( $query_args, $widget ) {
|
||
if ( ! $this->is_product_query( $widget ) ) {
|
||
return $query_args;
|
||
}
|
||
|
||
return $this->parse_loop_query_args( $widget, $query_args );
|
||
}
|
||
|
||
private function is_product_query( $widget ) {
|
||
$widget_config = $widget->get_config();
|
||
|
||
return ( ! empty( $widget_config['is_loop'] ) && 'product' === $widget->get_current_skin_id() );
|
||
}
|
||
|
||
private function parse_loop_query_args( $widget, $query_args ) {
|
||
$settings = $this->adjust_setting_for_product_renderer( $widget );
|
||
|
||
// For Products_Renderer.
|
||
if ( ! isset( $GLOBALS['post'] ) ) {
|
||
$GLOBALS['post'] = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||
}
|
||
|
||
$shortcode = Products_Widget::get_shortcode_object( $settings );
|
||
|
||
$parsed_query_args = $shortcode->parse_query_args();
|
||
|
||
unset( $parsed_query_args['fields'] );
|
||
|
||
$override_various_query_args = array_filter( $query_args, function( $key ) {
|
||
return in_array( $key, [ 'posts_per_page', 'offset', 'paged' ], true );
|
||
}, ARRAY_FILTER_USE_KEY );
|
||
|
||
return wp_parse_args( $override_various_query_args, $parsed_query_args );
|
||
}
|
||
|
||
private function adjust_setting_for_product_renderer( $widget ) {
|
||
$settings = $widget->get_settings_for_display();
|
||
|
||
$query_name = $widget->get_query_name();
|
||
|
||
$unique_query_settings = array_filter( $settings, function( $key ) use ( $query_name ) {
|
||
return 0 === strpos( $key, $query_name );
|
||
}, ARRAY_FILTER_USE_KEY );
|
||
|
||
$query_settings = [];
|
||
|
||
foreach ( $unique_query_settings as $key => $value ) {
|
||
$query_settings[ 'query' . str_replace( $query_name, '', $key ) ] = $value;
|
||
}
|
||
|
||
$settings = array_merge( $settings, $query_settings );
|
||
|
||
if ( isset( $settings['posts_per_page'] ) && isset( $settings['columns'] ) ) {
|
||
$settings['rows'] = ceil( $settings['posts_per_page'] / $settings['columns'] );
|
||
}
|
||
|
||
$settings['paginate'] = 'yes';
|
||
$settings['allow_order'] = 'no';
|
||
$settings['show_result_count'] = 'no';
|
||
$settings['query_fields'] = false;
|
||
|
||
return $settings;
|
||
}
|
||
|
||
/**
|
||
* @param $post_id
|
||
* @return bool
|
||
*/
|
||
private function is_source_set_to_products( $post_id ) {
|
||
return 'product' === get_post_meta( $post_id, '_elementor_source', true );
|
||
}
|
||
|
||
/**
|
||
* @param array $config
|
||
* @return array
|
||
*/
|
||
private function add_woocommerce_widgets_to_recommended( array $config ) {
|
||
foreach ( static::RECOMMENDED_POSTS_WIDGET_NAMES as $recommended_posts_widget_name ) {
|
||
$config['panel']['widgets_settings'][ $recommended_posts_widget_name ] = [
|
||
'categories' => [ 'recommended' ],
|
||
'show_in_panel' => true,
|
||
];
|
||
}
|
||
return $config;
|
||
}
|
||
|
||
private function hide_woocommerce_widgets_in_loop_document( array $config ) {
|
||
$config['panel']['widgets_settings']['woocommerce-product-images'] = [
|
||
'show_in_panel' => false,
|
||
];
|
||
return $config;
|
||
}
|
||
|
||
private function populate_persistent_settings( array $config ) {
|
||
$config['persistent_keys'] = array_key_exists( 'persistent_keys', $config ) ?
|
||
array_merge( $config['persistent_keys'], self::WC_PERSISTENT_SITE_SETTINGS ) :
|
||
self::WC_PERSISTENT_SITE_SETTINGS;
|
||
|
||
return $config;
|
||
}
|
||
}
|