File "woocommerce.php"
Full path: /home/dora/public_html/wp-content/themes/bricks/includes/woocommerce.php
File size: 42.47 KB
MIME-type: --
Charset: utf-8
<?php
namespace Bricks;
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
class Woocommerce {
public static $product_categories = [];
public static $product_tags = [];
public static $is_active = false;
public function __construct() {
self::$is_active = self::is_woocommerce_active();
if ( ! self::$is_active ) {
// Make sure the WooCommerce templates are not loaded, in case CPT "product" is used
add_filter( 'template_include', [ $this, 'no_woo_template_include' ], 1001 );
return;
}
add_action( 'after_setup_theme', [ $this, 'add_theme_support' ] );
add_action( 'init', [ $this, 'set_products_terms' ] );
add_action( 'init', [ $this, 'init_elements' ] );
add_action( 'init', [ $this, 'init_theme_styles' ], 9 );
add_action( 'wp', [ $this, 'maybe_set_template_preview_content' ], 9 );
// Disable default bricks title for WooCommerce pages if template is active (@since 1.8)
add_filter( 'bricks/default_page_title', [ $this, 'default_page_title' ], 10, 2 );
add_filter( 'bricks/element/maybe_set_aria_current_page', [ $this, 'maybe_set_aria_current_page' ], 10, 2 );
add_filter( 'bricks/builder/supported_post_types', [ $this, 'bypass_builder_post_type_check' ], 10, 2 );
// On the builder hook to set the panel elements first element category
add_filter( 'bricks/builder/first_element_category', [ $this, 'set_first_element_category' ], 10, 3 );
// Builder/Database: set the post id used to localize the builder data -> is_shop() page
add_filter( 'bricks/builder/data_post_id', [ $this, 'maybe_set_post_id' ], 10, 1 );
// Add Template Types to control options
add_filter( 'bricks/setup/control_options', [ $this, 'add_template_types' ] );
// Remove the template conditions for the Cart & Checkout template parts
add_filter( 'builder/settings/template/controls_data', [ $this, 'remove_template_conditions' ], 9 );
// During the active_templates search set proper content_type
add_filter( 'bricks/database/content_type', [ $this, 'set_content_type' ], 10, 2 );
// Remove default WooCommerce styles
add_filter( 'woocommerce_enqueue_styles', '__return_false' );
// Add WooCommerce specific link selectors to allow Theme Styles link styles to apply to WooCommerce elements (@since 1.5.7)
add_filter( 'bricks/link_css_selectors', [ $this, 'link_css_selectors' ], 10, 1 );
// Enqueue Bricks WooCommerce custom styles
add_action( 'wp_enqueue_scripts', [ $this, 'wp_enqueue_scripts' ], 10 );
// add_action( 'wp_enqueue_scripts', [ $this, 'unload_photoswipe5_lightbox_assets' ] );
// Product archive hooks
add_action( 'bricks/archive_product/before', [ $this, 'setup_query' ], 10, 2 );
add_action( 'bricks/archive_product/after', [ $this, 'reset_query' ], 10, 2 );
// Mini cart fragments
add_filter( 'woocommerce_add_to_cart_fragments', [ $this, 'update_mini_cart' ], 10, 1 );
// Breadcrumb separator
add_filter( 'woocommerce_breadcrumb_defaults', [ $this, 'breadcrumb_separator' ] );
add_filter( 'woocommerce_get_breadcrumb', [ $this, 'add_breadcrumbs_from_filters' ], 10, 2 );
/**
* Quantity input field: Add plus/minus buttons
*
* @since 1.7 - Render button after the input in order to hide it if input[type="hidden"]
*/
add_action( 'woocommerce_after_quantity_input_field', [ $this, 'quantity_input_field_add_minus_button' ] );
add_action( 'woocommerce_after_quantity_input_field', [ $this, 'quantity_input_field_add_plus_button' ] );
// Product tabs: Remove panel titles
add_filter( 'woocommerce_product_description_heading', '__return_false' );
add_filter( 'woocommerce_product_additional_information_heading', '__return_false' );
add_filter( 'woocommerce_reviews_title', '__return_false' );
// On Sale HTML
add_filter( 'woocommerce_sale_flash', [ $this, 'badge_sale' ], 10, 3 );
// Single product
add_action( 'woocommerce_before_shop_loop_item_title', [ $this, 'badge_new' ], 9 );
add_filter( 'woocommerce_product_review_comment_form_args', [ $this, 'product_review_comment_form_args' ] );
// Query loop: using the query loop builder for products
add_filter( 'bricks/posts/merge_query', [ $this, 'maybe_merge_query' ], 10, 2 );
add_filter( 'bricks/posts/query_vars', [ $this, 'set_products_query_vars' ], 10, 3 );
// Query: Add Woo Cart contents
add_filter( 'bricks/setup/control_options', [ $this, 'add_control_options' ], 10, 1 );
add_filter( 'bricks/query/run', [ $this, 'run_cart_query' ], 10, 2 );
add_filter( 'bricks/query/loop_object', [ $this, 'set_loop_object' ], 10, 3 );
// TODO: Needed?
add_filter( 'bricks/query/loop_object_id', [ $this, 'set_loop_object_id' ], 10, 3 );
add_filter( 'bricks/query/loop_object_type', [ $this, 'set_loop_object_type' ], 10, 3 );
add_filter( 'post_class', [ $this, 'post_class' ], 10, 3 );
// Checkout: Make sure the fields removed by the user inside the builder are not required during the checkout process (@since 1.5.7)
add_filter( 'woocommerce_checkout_fields', [ $this, 'woocommerce_checkout_fields' ], 99, 1 );
// @since 1.6.1 - AJAX Add to cart
if ( self::enabled_ajax_add_to_cart() ) {
add_action( 'wc_ajax_bricks_add_to_cart', [ $this, 'add_to_cart' ] );
add_action( 'wc_ajax_nopriv_bricks_add_to_cart', [ $this, 'add_to_cart' ] );
}
// @since 1.7 - Remove / Restore Woo native hook actions when using {do_action}
add_action( 'bricks/dynamic_data/before_do_action', [ $this, 'maybe_remove_woo_hook_actions' ], 10, 4 );
add_action( 'bricks/dynamic_data/after_do_action', [ $this, 'maybe_restore_woo_hook_actions' ], 10, 5 );
// @since 1.8.1 - Bricks WooCommerce Notice
self::maybe_remove_native_woocommerce_notices_hooks();
}
/**
* Checkout: Make sure the removed billing/shipping fields in the WooCommerce checkout customer details element are set to be not required
*
* @since 1.5.7
*/
public function woocommerce_checkout_fields( $fields ) {
if ( ! is_checkout() ) {
return $fields;
}
$templates = Templates::get_templates_by_type( 'wc_form_checkout' );
if ( empty( $templates[0] ) ) {
return $fields;
}
$elements = get_post_meta( $templates[0], BRICKS_DB_PAGE_CONTENT, true );
if ( empty( $elements ) ) {
return $fields;
}
$customer_details_settings = false;
// Get settings of "Checkout customer details" element
foreach ( $elements as $element ) {
if ( $element['name'] === 'woocommerce-checkout-customer-details' && ! empty( $element['settings'] ) ) {
$customer_details_settings = $element['settings'];
}
}
// Mark removed billing fields to not-required
if ( ! empty( $customer_details_settings['removeBillingFields'] ) && ! empty( $fields['billing'] ) ) {
foreach ( $customer_details_settings['removeBillingFields'] as $field_id ) {
if ( isset( $fields['billing'][ $field_id ] ) ) {
$fields['billing'][ $field_id ]['required'] = 0;
}
}
}
// Mark removed shipping fields to not-required
if ( ! empty( $customer_details_settings['removeShippingFields'] ) && ! empty( $fields['shipping'] ) ) {
foreach ( $customer_details_settings['removeShippingFields'] as $field_id ) {
if ( isset( $fields['shipping'][ $field_id ] ) ) {
$fields['shipping'][ $field_id ]['required'] = 0;
}
}
}
return $fields;
}
/**
* Cart or checkout build with Bricks: Remove 'wordpress' post class to avoid auto-containing Bricks content
*
* @since 1.5.5
*/
public function post_class( $classes, $class, $post_id ) {
$remove_wordpress_class = false;
// Cart page
if ( is_cart() ) {
$count = is_object( WC()->cart ) ? WC()->cart->get_cart_contents_count() : 0;
if (
( $count && self::get_template_data_by_type( 'wc_cart', false ) ) || // Cart has items & Bricks template
( ! $count && self::get_template_data_by_type( 'wc_cart_empty', false ) ) // Empty cart & Bricks template
) {
$remove_wordpress_class = true;
}
}
// Checkout page
if ( is_checkout() ) {
// Order pay
if ( get_query_var( 'order-pay' ) ) {
if ( self::get_template_data_by_type( 'wc_form_pay', false ) ) {
$remove_wordpress_class = true;
}
}
// Order receipt (= thank you page)
if ( get_query_var( 'order-received' ) ) {
if ( self::get_template_data_by_type( 'wc_thankyou', false ) ) {
$remove_wordpress_class = true;
}
}
// Checkout page
elseif ( self::get_template_data_by_type( 'wc_form_checkout', false ) ) {
$remove_wordpress_class = true;
}
}
// STEP: Remove 'wordpress' post class to avoid auto-containing Bricks content
if ( $remove_wordpress_class ) {
$index = array_search( 'wordpress', $classes );
if ( isset( $classes[ $index ] ) ) {
unset( $classes[ $index ] );
}
}
return $classes;
}
/**
* If WooCommerce is not used, make sure the single and archive Woo templates are not used
*
* @since 1.5.1
*
* @param string $template
* @return string
*/
public function no_woo_template_include( $template ) {
if ( empty( $template ) ) {
return $template;
}
if ( strpos( $template, '/bricks/archive-product.php' ) ) {
return get_query_template( 'archive', [ 'archive.php' ] );
}
if ( strpos( $template, '/bricks/single-product.php' ) ) {
return get_query_template( 'single', [ 'single.php' ] );
}
return $template;
}
/**
* Sale badge HTML
*
* Show text or percentage.
*/
public function badge_sale( $html, $post, $product ) {
$badge_type = Database::get_setting( 'woocommerceBadgeSale', false );
// Type: ''
if ( ! $badge_type ) {
return;
}
// Type: text
elseif ( $badge_type === 'text' ) {
return '<span class="badge onsale">' . esc_html__( 'Sale', 'bricks' ) . '</span>';
}
// Type: percentage
if ( $product->is_type( 'variable' ) ) {
$percentages = [];
// Get all variation prices
$prices = $product->get_variation_prices();
foreach ( $prices['price'] as $key => $price ) {
if ( $prices['regular_price'][ $key ] !== $price ) {
$percentages[] = round( 100 - ( floatval( $prices['sale_price'][ $key ] ) / floatval( $prices['regular_price'][ $key ] ) * 100 ) );
}
}
// Use highest discountvalue
$percentage = max( $percentages ) . '%';
} elseif ( $product->is_type( 'grouped' ) ) {
$percentages = [];
$children = $product->get_children();
foreach ( $children as $child ) {
$child_product = wc_get_product( $child );
$regular_price = (float) $child_product->get_regular_price();
$sale_price = (float) $child_product->get_sale_price();
if ( $sale_price != 0 || ! empty( $sale_price ) ) {
$percentages[] = round( 100 - ( $sale_price / $regular_price * 100 ) );
}
}
// Use highest value
$percentage = max( $percentages ) . '%';
} else {
$regular_price = (float) $product->get_regular_price();
$sale_price = (float) $product->get_sale_price();
if ( $sale_price != 0 || ! empty( $sale_price ) ) {
$percentage = round( 100 - ( $sale_price / $regular_price * 100 ) ) . '%';
} else {
return $html;
}
}
return '<span class="badge onsale">-' . $percentage . '</span>';
}
public static function badge_new() {
global $product;
$newness_in_days = Database::get_setting( 'woocommerceBadgeNew', false );
if ( ! $newness_in_days ) {
return;
}
$newness_timestamp = time() - ( 60 * 60 * 24 * $newness_in_days );
$created = strtotime( $product->get_date_created() );
$is_new = $newness_timestamp < $created; // Created less than {$newness_in_days} days ago
if ( $is_new ) {
echo '<span class="badge new">' . esc_html__( 'New', 'bricks' ) . '</span>';
}
}
/**
* Product review submit button: Add 'button' class to apply Woo button styles
*/
public function product_review_comment_form_args( $comment_form ) {
$comment_form['class_submit'] = 'button';
return $comment_form;
}
/**
* WooCommerce support sets WC_Template_Loader::$theme_support = true
*/
public function add_theme_support() {
add_theme_support( 'woocommerce', [
'product_grid' => [
'default_columns' => 4,
'default_rows' => 3,
'min_columns' => 1,
'max_columns' => 6,
'min_rows' => 1,
],
] );
add_theme_support( 'wc-product-gallery-slider' );
// Disable/enable product gallery zoom
if ( Database::get_setting( 'woocommerceDisableProductGalleryZoom', false ) ) {
remove_theme_support( 'wc-product-gallery-zoom' );
} else {
add_theme_support( 'wc-product-gallery-zoom' );
}
// Disable/enable product gallery lightbox (always disabled in builder)
$disable_product_gallery_lightbox = Database::get_setting( 'woocommerceDisableProductGalleryLightbox', false );
if ( $disable_product_gallery_lightbox || bricks_is_builder() ) {
remove_theme_support( 'wc-product-gallery-lightbox' );
} else {
add_theme_support( 'wc-product-gallery-lightbox' );
}
}
/**
* Get products terms (categories, tags) for in-builder product query controls
*/
public function set_products_terms() {
if ( bricks_is_builder() ) {
self::$product_categories = self::get_products_terms( 'product_cat' );
self::$product_tags = self::get_products_terms( 'product_tag' );
}
}
/**
* Get terms for a given product taxonomy
*/
public static function get_products_terms( $taxonomy = null ) {
if ( empty( $taxonomy ) ) {
return [];
}
$terms = get_terms(
[
'taxonomy' => $taxonomy,
'hide_empty' => false,
]
);
$tags = [];
foreach ( $terms as $term ) {
$tags[ $term->term_id ] = $term->name;
}
return $tags;
}
/**
* Check if WooCommerce plugin is active
*
* @return boolean
*/
public static function is_woocommerce_active() {
return class_exists( 'woocommerce' ) && ! Database::get_setting( 'woocommerceDisableBuilder', false );
}
/**
* Init WooCommerce theme styles
*/
public function init_theme_styles() {
$file = BRICKS_PATH . 'includes/woocommerce/theme-styles.php';
if ( is_readable( $file ) ) {
require_once $file;
new Woocommerce_Theme_Styles();
}
}
/**
* Init WooCommerce elements
*/
public function init_elements() {
// Load WooCommerce helpers
$helpers_file = BRICKS_PATH . 'includes/woocommerce/helpers.php';
if ( is_readable( $helpers_file ) ) {
require_once $helpers_file;
}
$woo_elements = [
'woocommerce-breadcrumbs',
'woocommerce-mini-cart',
'product-title',
'product-gallery',
'product-short-description',
'product-price',
'product-stock',
'product-meta',
'product-rating',
'product-content',
'product-add-to-cart',
'product-related',
'product-additional-information',
'product-tabs',
'product-upsells',
'woocommerce-cart-collaterals',
'woocommerce-cart-coupon',
'woocommerce-cart-items',
'woocommerce-checkout-customer-details',
'woocommerce-checkout-order-review',
'woocommerce-checkout-thankyou',
'woocommerce-checkout-order-table',
'woocommerce-checkout-order-payment',
'woocommerce-products',
'woocommerce-products-pagination',
'woocommerce-products-orderby',
'woocommerce-products-total-results',
'woocommerce-products-filter',
'woocommerce-products-archive-description',
'woocommerce-notice',
// 'woocommerce-template-hook', // NOTE: Not in use as action hooks can be added via the 'do_action' DD tag (@since 1.7)
];
foreach ( $woo_elements as $element_name ) {
// @since 1.8.1 - Only register woocommerce-notice if user activated it
if ( $element_name === 'woocommerce-notice' && ! self::use_bricks_woo_notice_element() ) {
continue;
}
$woo_element_file = BRICKS_PATH . "includes/woocommerce/elements/$element_name.php";
// Get the class name from the element name
$class_name = str_replace( '-', '_', $element_name );
$class_name = ucwords( $class_name, '_' );
$class_name = "Bricks\\$class_name";
if ( is_readable( $woo_element_file ) ) {
Elements::register_element( $woo_element_file, $element_name, $class_name );
}
}
}
public function quantity_input_field_add_minus_button() {
$html = '<span class="action minus">';
$html .= '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="6" y1="12" x2="18" y2="12"></line></svg>';
$html .= '</span>';
echo $html;
}
public function quantity_input_field_add_plus_button() {
$html = '<span class="action plus">';
$html .= '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="6" x2="12" y2="18"></line><line x1="6" y1="12" x2="18" y2="12"></line></svg>';
$html .= '</span>';
echo $html;
}
public function breadcrumb_separator( $defaults ) {
$defaults['delimiter'] = '<span>/</span>';
return $defaults;
}
/**
* Add search breadbrumb in the product archive if using Bricks search filter
*
* @param array $crumbs
* @param WC_Breadcrumb $crumbs_obj
* @return array
*/
public function add_breadcrumbs_from_filters( $crumbs, $crumbs_obj ) {
if ( ! empty( $_GET['b_search'] ) && Woocommerce_Helpers::is_archive_product() ) {
$crumbs[] = [
sprintf( __( 'Search results for “%s”', 'woocommerce' ), wp_strip_all_tags( $_GET['b_search'] ) ),
remove_query_arg( 'paged' )
];
}
return $crumbs;
}
/**
* Bypass Builder post type check because page set to WooCommerce Shop fails
*
* @return boolean
*/
public function bypass_builder_post_type_check( $supported_post_types, $current_post_type ) {
if ( in_array( 'page', $supported_post_types ) && ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) ) {
$supported_post_types[] = 'product';
}
return $supported_post_types;
}
/**
* Builder: Set single product template & populate content (if needed)
*
* @return void
*/
public function maybe_set_template_preview_content() {
$post_id = get_the_ID();
$template_type = get_post_meta( $post_id, BRICKS_DB_TEMPLATE_TYPE, true );
$template_preview_id = Helpers::get_template_setting( 'templatePreviewPostId', $post_id );
if (
strpos( $template_type, 'wc_' ) !== false ||
wc_get_page_id( 'shop' ) == $template_preview_id ) {
// Necessary to add 'woocommerce' to body_class for styling
add_filter(
'is_woocommerce',
function() {
return true;
}
);
}
// Remove 'woocommerce body class in builder panel
if ( bricks_is_builder_main() ) {
add_filter(
'is_woocommerce',
function() {
return false;
}
);
}
// Form checkout template
if (
$template_type === 'wc_form_checkout' ||
$template_type === 'wc_form_pay' ||
$template_type === 'wc_cart'
) {
add_filter( 'body_class', [ $this, 'add_body_class' ], 9, 1 );
}
// Return: Not in builder nor template
if ( ! bricks_is_builder() || ! Helpers::is_bricks_template( $post_id ) ) {
return;
}
// Get the last product and save it as preview ID
if ( $template_type === 'wc_product' ) {
// Template has already a preview post ID: Leave
$template_preview_post_id = Helpers::get_template_setting( 'templatePreviewPostId', $post_id );
if ( $template_preview_post_id ) {
return;
}
$products = wc_get_products(
[
'limit' => 1,
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
]
);
if ( isset( $products[0] ) ) {
Helpers::set_template_setting( $post_id, 'templatePreviewPostId', $products[0] );
Helpers::set_template_setting( $post_id, 'templatePreviewType', 'single' );
Helpers::set_template_setting( $post_id, 'templatePreviewAutoContent', 1 ); // This setting will be used to trigger a notification
}
}
// TODO: Replace this logic by a generic template preview CPT Archive > CPT = products
// elseif ( $template_type === 'wc_archive' ) {
// $template_preview_type = Helpers::get_template_setting( 'templatePreviewType', $post_id );
// if ( $template_preview_type ) {
// return;
// }
// Helpers::set_template_setting( $post_id, 'templatePreviewType', 'archive-product' );
// }
}
/**
* Cart page / Checkout page: Return no title if rendered via Bricks template
*
* @since 1.8
*/
public function default_page_title( $post_title, $post_id ) {
if ( is_cart() ) {
if ( WC()->cart->is_empty() ) {
// Empty cart template
$cart_template_ids = \Bricks\Templates::get_templates_by_type( 'wc_cart_empty' );
} else {
// Normal cart template
$cart_template_ids = \Bricks\Templates::get_templates_by_type( 'wc_cart' );
}
return empty( $cart_template_ids ) ? $post_title : '';
}
if ( is_checkout() ) {
$checkout_template_ids = \Bricks\Templates::get_templates_by_type( 'wc_form_checkout' );
return empty( $checkout_template_ids ) ? $post_title : '';
}
return $post_title;
}
/**
* Set aria-current="page" for WooCommerce Shop page
*
* @since 1.8
*/
public function maybe_set_aria_current_page( $set, $url ) {
// Return: Not the WooCommerce shop page
if ( ! is_shop() ) {
return $set;
}
return $url === get_permalink( wc_get_page_id( 'shop' ) );
}
/**
* Builder: Add body classes to Woo templates
*
* @param array $classes
* @return void
*/
public function add_body_class( $classes ) {
if ( get_post_type() !== BRICKS_DB_TEMPLATE_SLUG ) {
return $classes;
}
if ( Templates::get_template_type() === 'wc_form_checkout' ) {
$classes[] = 'woocommerce-checkout';
$classes[] = 'woocommerce-page';
} elseif ( Templates::get_template_type() === 'wc_form_pay' ) {
$classes[] = 'woocommerce-checkout';
} elseif ( Templates::get_template_type() === 'wc_cart' ) {
$classes[] = 'woocommerce-cart';
$classes[] = 'woocommerce-page';
}
return $classes;
}
/**
* On the builder, move up WooCommerce specific elements
*
* @since 1.2.1
*
* @param string $category
* @param integer $post_id
* @param string $post_type
* @return string
*/
public function set_first_element_category( $category, $post_id, $post_type ) {
if ( BRICKS_DB_TEMPLATE_SLUG === $post_type ) {
$template_type = get_post_meta( $post_id, BRICKS_DB_TEMPLATE_TYPE, true );
if ( $template_type == 'wc_product' ) {
return 'woocommerce_product';
} elseif ( strpos( $template_type, 'wc_' ) !== false ) {
return 'woocommerce';
}
} elseif ( is_post_type_archive( 'product' ) || $post_id == wc_get_page_id( 'shop' ) ) {
return 'woocommerce';
} elseif ( 'product' == $post_type ) {
return 'woocommerce_product';
}
return $category;
}
/**
* Page marked as shop - is_shop() - has a global $post_id set to the first product (like is_home)
*
* In builder or when setting the active templates we need to replace the active post id by the page id
*
* @param integer $post_id
* @return void
*/
public function maybe_set_post_id( $post_id ) {
// If launching bricks builder on page defined as shop
if ( is_shop() && ! Helpers::is_bricks_template( $post_id ) ) {
$page_id = wc_get_page_id( 'shop' );
$post_id = ! empty( $page_id ) ? $page_id : $post_id;
}
return $post_id;
}
/**
* Add WooCommerce element link selectors to allow Theme Styles for the links
*
* @since 1.5.7
*/
public function link_css_selectors( $selectors ) {
$selectors[] = '.brxe-product-short-description a';
$selectors[] = '.brxe-product-tabs .woocommerce-Tabs-panel a';
return $selectors;
}
/**
* NOTE: Not in use as we renamed the 'PhotoSwipe' class to 'Photoswipe5' to avoid conflicts with WooCommerce Photoswipe 4
*/
public function unload_photoswipe5_lightbox_assets() {
// Remove Bricks lightbox (as Photoswipe 5 conflicts with Photoswipe 4, the latter which is used by WooCommerce)
if ( is_product() && current_theme_supports( 'wc-product-gallery-lightbox' ) ) {
wp_deregister_script( 'bricks-photoswipe' );
wp_deregister_script( 'bricks-photoswipe-lightbox' );
wp_deregister_style( 'bricks-photoswipe' );
}
}
/**
* Remove WooCommerce scripts on non-WooCommerce pages
*
* @since 1.2.1
*/
public function wp_enqueue_scripts() {
if ( bricks_is_builder_iframe() ) {
// Required for product gallery & tabs
wp_enqueue_script( 'wc-single-product' );
}
if ( ! bricks_is_builder_main() ) {
wp_enqueue_script( 'bricks-woocommerce', BRICKS_URL_ASSETS . 'js/integrations/woocommerce.min.js', [ 'bricks-scripts' ], filemtime( BRICKS_PATH_ASSETS . 'js/integrations/woocommerce.min.js' ), true );
wp_enqueue_style( 'bricks-woocommerce', BRICKS_URL_ASSETS . 'css/integrations/woocommerce.min.css', [ 'bricks-frontend' ], filemtime( BRICKS_PATH_ASSETS . 'css/integrations/woocommerce.min.css' ) );
}
if ( is_rtl() ) {
wp_enqueue_style( 'bricks-woocommerce-rtl', BRICKS_URL_ASSETS . 'css/integrations/woocommerce-rtl.min.css', [ 'bricks-frontend' ], filemtime( BRICKS_PATH_ASSETS . 'css/integrations/woocommerce-rtl.min.css' ) );
}
// @since 1.6.1 - Bricks WooCommerce settings for frontend
wp_localize_script(
'bricks-scripts',
'bricksWooCommerce',
[
'ajaxAddToCartEnabled' => self::enabled_ajax_add_to_cart(),
'ajaxAddingText' => esc_html__( 'Adding', 'bricks' ),
'ajaxAddedText' => esc_html__( 'Added', 'bricks' ),
]
);
}
/**
* Before Bricks searchs for the right template, set the content_type if needed
*
* @param string $content_type
* @param integer $post_id
* @return void
*/
public static function set_content_type( $content_type, $post_id ) {
// These will only kick in if user has defaultTemplatesDisabled = false
if ( is_product() ) {
$content_type = 'wc_product';
} elseif ( is_shop() ) {
$content_type = 'content';
} elseif ( Woocommerce_Helpers::is_archive_product() ) {
$content_type = 'wc_archive';
}
return $content_type;
}
/**
* Add template types to control options
*
* @param array $control_options
* @return array
*
* @since 1.4
*/
public function add_template_types( $control_options ) {
$template_types = $control_options['templateTypes'];
// @since 1.3: Product archive & single product templates
$template_types['wc_archive'] = 'WooCommerce - ' . esc_html__( 'Product archive', 'bricks' );
$template_types['wc_product'] = 'WooCommerce - ' . esc_html__( 'Single product', 'bricks' );
// @since 1.4: Cart & checkout templates
$template_types['wc_cart'] = 'WooCommerce - ' . esc_html__( 'Cart', 'bricks' );
$template_types['wc_cart_empty'] = 'WooCommerce - ' . esc_html__( 'Empty cart', 'bricks' );
$template_types['wc_form_checkout'] = 'WooCommerce - ' . esc_html__( 'Checkout', 'bricks' );
$template_types['wc_form_pay'] = 'WooCommerce - ' . esc_html__( 'Pay', 'bricks' );
$template_types['wc_thankyou'] = 'WooCommerce - ' . esc_html__( 'Thank you', 'bricks' );
$template_types['wc_order_receipt'] = 'WooCommerce - ' . esc_html__( 'Order receipt', 'bricks' );
$control_options['templateTypes'] = $template_types;
return $control_options;
}
/**
* Remove "Template Conditions" & "Populate Content" panel controls for WooCommerce Cart & Checkout template parts
*
* @param array $settings
* @return array
*
* @since 1.4
*/
public function remove_template_conditions( $settings ) {
$excluded_templates = [
'wc_cart',
'wc_cart_empty',
'wc_form_checkout',
'wc_form_pay',
'wc_thankyou',
'wc_order_receipt',
];
if ( isset( $settings['controlGroups']['template-preview'] ) ) {
$settings['controlGroups']['template-preview']['required'] = [ 'templateType', '!=', $excluded_templates, 'templateType' ];
}
if ( isset( $settings['controls']['templateConditionsInfo'] ) ) {
$settings['controls']['templateConditionsInfo']['required'] = [ 'templateType', '!=', $excluded_templates, 'templateType' ];
}
if ( isset( $settings['controls']['templateConditions'] ) ) {
$settings['controls']['templateConditions']['required'] = [ 'templateType', '!=', $excluded_templates, 'templateType' ];
}
$settings['controls'][] = [
'group' => 'template-conditions',
'type' => 'info',
'content' => esc_html__( 'This template type is automatically rendered on the correct page.', 'bricks' ),
'required' => [ 'templateType', '=', $excluded_templates, 'templateType' ],
];
return $settings;
}
/**
* Get template data by template type
*
* For woocommerce templates inside Bricks theme.
*
* Return template data rendered via Bricks template shortcode.
*
* (@since 1.8) Return template id if render is false, this is because we do not trigger any hooks when we are not rendering the template.
* Example: do_shortcode will be execute in post_class filter, which will trigger the do_shortcode action, and causing wc_print_notices to be executed in post_class filter before the actual template is rendered. Resulted actual template rendering empty notices. (wc_print_notices() will erase the notices after it is executed)
*
* @see /includes/woocommerce/cart/cart.php (wc_cart), etc.
*
* @since 1.4
*/
public static function get_template_data_by_type( $type = '', $render = true ) {
// Do not check for Database::get_setting( 'defaultTemplatesDisabled' )
$template_ids = Templates::get_templates_by_type( $type );
// No template found
if ( empty( $template_ids[0] ) ) {
return false;
}
// Return template id if render is false
if ( ! $render ) {
return $template_ids[0];
}
return do_shortcode( '[bricks_template id="' . $template_ids[0] . '"]' );
}
/**
* Add Archive Product content type
*
* Note: Not in use
*
* @param array $types
* @return void
*/
public function add_content_types( $types ) {
$types['archive-product'] = esc_html__( 'Archive (products)', 'bricks' );
return $types;
}
/**
* Setup the products query loop in the products archive, including is_shop page (frontend only)
*
* @param array $data Elements list
* @param string $post_id
* @return void
*/
public function setup_query( $data, $post_id ) {
$query_element = Woocommerce_Helpers::get_products_element( $post_id, $data );
// No query element to merge, proceed with regular WooCommerce loop
if ( ! $query_element ) {
wc_setup_loop();
return;
}
// Force the post type to feed the Bricks Query class
if ( empty( $query_element['settings']['query'] ) ) {
$query_element['settings']['query'] = [
'post_type' => [ 'product' ],
'ignore_sticky_posts' => 1
];
}
// Query
$query_object = new Query( $query_element );
$query = $query_object->query_result;
// Remove ordering query arguments which may have been added by 'get_catalog_ordering_args'
WC()->query->remove_ordering_args();
$columns = isset( $query_element['settings']['columns'] ) ? $query_element['settings']['columns'] : 4;
wc_setup_loop(
[
'columns' => $columns,
'name' => 'bricks-products',
'is_shortcode' => true,
'is_search' => false,
'is_paginated' => true,
'total' => (int) $query->found_posts,
'total_pages' => (int) $query->max_num_pages,
'per_page' => (int) $query->get( 'posts_per_page' ),
'current_page' => (int) max( 1, $query->get( 'paged', 1 ) ),
]
);
}
public function reset_query( $sections, $post_id ) {
wc_reset_loop();
}
/**
* Update the mini-cart fragments
*
* @param array $fragments
* @return void
*/
public function update_mini_cart( $fragments ) {
if ( ! is_object( WC()->cart ) ) {
return;
}
// Cart Count
$count = WC()->cart->get_cart_contents_count();
$fragments['span.cart-count'] = '<span class="cart-count ' . ( $count == 0 ? 'hide' : 'show' ) . '">' . $count . '</span>';
// Cart Subtotal
$subtotal = WC()->cart->get_cart_subtotal();
if ( $subtotal ) {
$fragments['span.cart-subtotal'] = '<span class="cart-subtotal">' . $subtotal . '</span>';
}
return $fragments;
}
/**
* Check if the query loop is on Woo products, and if yes, check if we should merge the main query
*
* @since 1.5
*
* @param boolean $merge
* @param string $element_id
* @return boolean
*/
public function maybe_merge_query( $merge, $element_id ) {
$query = Query::get_query_for_element_id( $element_id );
if ( ! isset( $query->query_vars['post_type'] ) ) {
return $merge;
}
if ( is_array( $query->query_vars['post_type'] ) && ! in_array( 'product', $query->query_vars['post_type'] ) ) {
return $merge;
}
return Woocommerce_Helpers::is_archive_product();
}
/**
* Add products query vars to the query loop
*
* @since 1.5
*
* @param array $query_vars
* @param array $settings
* @param string $element_id
* @return boolean
*/
public function set_products_query_vars( $query_vars, $settings, $element_id ) {
if ( ! isset( $query_vars['post_type'] ) ) {
return $query_vars;
}
if ( is_array( $query_vars['post_type'] ) && ! in_array( 'product', $query_vars['post_type'] ) ) {
return $query_vars;
}
$new_query_vars = $query_vars;
$filter_args = Woocommerce_Helpers::filters_query_args( $settings );
// Override the query settings by the filters (orderby, filters)
foreach ( $filter_args as $key => $filter_value ) {
if ( in_array( $key, [ 'meta_query', 'tax_query' ] ) && ! empty( $query_vars[ $key ] ) ) {
continue;
}
$new_query_vars[ $key ] = $filter_value;
}
// STEP: Merge meta or/and tax query (if has conflicts)
foreach ( [ 'meta_query', 'tax_query' ] as $type ) {
if ( empty( $query_vars[ $type ] ) || empty( $filter_args[ $type ] ) ) {
continue;
}
$relation_query_vars = isset( $query_vars[ $type ]['relation'] ) ? $query_vars[ $type ]['relation'] : false;
$relation_filter_args = isset( $filter_args[ $type ]['relation'] ) ? $filter_args[ $type ]['relation'] : false;
// Both meta query sources have the relation key set and they are different
if ( $relation_query_vars && $relation_filter_args && $relation_query_vars != $relation_filter_args ) {
$new_query_vars[ $type ] = [
'relation' => 'AND',
0 => $query_vars[ $type ],
1 => $filter_args[ $type ]
];
}
// Relations are equal or not set
else {
$relation = $relation_query_vars ? $relation_query_vars : ( $relation_filter_args ? $relation_filter_args : false );
unset( $query_vars[ $type ]['relation'] );
unset( $filter_args[ $type ]['relation'] );
$new_query_vars[ $type ] = array_merge( $query_vars[ $type ], $filter_args[ $type ] );
if ( $relation ) {
$new_query_vars[ $type ]['relation'] = $relation;
}
}
}
return $new_query_vars;
}
/**
* Adds the cart contents query to the Query Loop builder
*
* @param array $control_options
* @return array
*/
public function add_control_options( $control_options ) {
$control_options['queryTypes']['wooCart'] = esc_html__( 'Cart contents', 'bricks' );
return $control_options;
}
/**
* Returns the cart contents query
*
* @param array $results
* @param Query $query
* @return array
*/
public function run_cart_query( $results, $query ) {
if ( $query->object_type !== 'wooCart' ) {
return $results;
}
// Avoid Uncaught Error: Call to a member function get_cart() on null (@since 1.8.1)
if ( is_null( WC()->cart ) ) {
return [];
}
return WC()->cart->get_cart();
}
/**
* Sets the loop object (to WP_Post) in each query loop iteration
*
* @param array $loop_object
* @param string $loop_key
* @param Query $query
* @return array
*/
public function set_loop_object( $loop_object, $loop_key, $query ) {
if ( $query->object_type !== 'wooCart' ) {
return $loop_object;
}
// @see woocommerce/templates/cart/cart.php
$_product = apply_filters( 'woocommerce_cart_item_product', $loop_object['data'], $loop_object, $loop_key );
$product_id = apply_filters( 'woocommerce_cart_item_product_id', $loop_object['product_id'], $loop_object, $loop_key );
global $post;
$post = get_post( $product_id );
setup_postdata( $post );
return $loop_object;
}
/**
* Returns the loop object id (for the cart query)
*
* @since 1.5.3
*/
public function set_loop_object_id( $object_id, $object, $query_id ) {
$query_object_type = Query::get_query_object_type( $query_id );
if ( $query_object_type !== 'wooCart' ) {
return $object_id;
}
return get_the_ID();
}
/**
* Returns the loop object type (for the cart query)
*
* @since 1.5.3
*/
public function set_loop_object_type( $object_type, $object, $query_id ) {
$query_object_type = Query::get_query_object_type( $query_id );
if ( $query_object_type !== 'wooCart' ) {
return $object_type;
}
return 'post';
}
/**
* Check if user enabled single ajax add to cart
*
* @return bool
* @since 1.6.1
*/
public static function enabled_ajax_add_to_cart() {
return Database::get_setting( 'woocommerceEnableAjaxAddToCart', false );
}
/**
* AJAX Add to cart
* Support product types: simple, variable, grouped
*
* @since 1.6.1
*
* @see woocommerce/includes/class-wc-ajax.php add_to_cart()
*/
public function add_to_cart() {
ob_start();
if ( ! isset( $_POST['product_id'] ) ) {
return;
}
$product_type = isset( $_POST['product_type'] ) ? sanitize_title( wp_unslash( $_POST['product_type'] ) ) : 'simple';
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $_POST['product_id'] ) );
$quantity = isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : 1;
$product_status = get_post_status( $product_id );
$variation_id = isset( $_POST['variation_id'] ) ? absint( $_POST['variation_id'] ) : 0;
$variation = isset( $_POST['variation'] ) ? (array) $_POST['variation'] : [];
$products = isset( $_POST['products'] ) ? (array) $_POST['products'] : [];
switch ( $product_type ) {
case 'grouped':
// No products added
if ( count( $products ) < 1 ) {
return;
}
$passed = [];
foreach ( $products as $id => $quantity ) {
if ( $quantity > 0 ) {
$each_passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $id, $quantity );
if ( $each_passed_validation && false !== WC()->cart->add_to_cart( $id, $quantity ) && 'publish' === $product_status ) {
do_action( 'woocommerce_ajax_added_to_cart', $id );
$passed[ $id ] = $quantity;
}
}
}
// Overall passed validation for grouped products
$passed_validation = count( $passed ) === count( $products );
if ( $passed_validation && 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
foreach ( $passed as $id => $quantity ) {
wc_add_to_cart_message( [ $id => $quantity ], true );
}
}
break;
default:
case 'variable':
case 'simple':
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id, $variation );
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation ) && 'publish' === $product_status ) {
do_action( 'woocommerce_ajax_added_to_cart', $product_id );
if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
wc_add_to_cart_message( [ $product_id => $quantity ], true );
}
} else {
$passed_validation = false;
}
break;
}
// Return error
if ( ! $passed_validation ) {
// If there was an error adding to the cart, redirect to the product page to show any errors
$data = [
'error' => true,
'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id ),
];
// Send error json
wp_send_json( $data );
}
// All good, return fragments
\WC_AJAX::get_refreshed_fragments();
}
/**
* Check if use bricks woo notice element
*
* @since 1.8.1
* @return bool
*/
public static function use_bricks_woo_notice_element() {
return Database::get_setting( 'woocommerceUseBricksWooNotice', false );
}
/**
* Remove all native woocommerce notices hooks if use Bricks woo notice element
* So user can control the location of notices via the Bricks woo notice element
*
* @since 1.8.1
* @see woocommerce/includes/wc-template-hooks.php Notices
*/
public static function maybe_remove_native_woocommerce_notices_hooks() {
if ( ! self::use_bricks_woo_notice_element() ) {
return;
}
// cart-empty.php
remove_action( 'woocommerce_cart_is_empty', 'woocommerce_output_all_notices', 5 );
remove_action( 'woocommerce_shortcode_before_product_cat_loop', 'woocommerce_output_all_notices', 10 );
// archive-product.php
remove_action( 'woocommerce_before_shop_loop', 'woocommerce_output_all_notices', 10 );
remove_action( 'woocommerce_before_single_product', 'woocommerce_output_all_notices', 10 );
// cart.php
remove_action( 'woocommerce_before_cart', 'woocommerce_output_all_notices', 10 );
// This hook is fired when using the [woocommerce_checkout] shortcode
remove_action( 'woocommerce_before_checkout_form_cart_notices', 'woocommerce_output_all_notices', 10 );
// form-checkout.php
remove_action( 'woocommerce_before_checkout_form', 'woocommerce_output_all_notices', 10 );
// inside shortcode [woocommerce_checkout] order_pay
remove_action( 'before_woocommerce_pay', 'woocommerce_output_all_notices', 10 );
// my-account.php
remove_action( 'woocommerce_account_content', 'woocommerce_output_all_notices', 5 );
// myaccount/form-login.php
remove_action( 'woocommerce_before_customer_login_form', 'woocommerce_output_all_notices', 10 );
// myaccount/form-lost-password.php
remove_action( 'woocommerce_before_lost_password_form', 'woocommerce_output_all_notices', 10 );
// myaccount/form-reset-password.php
remove_action( 'woocommerce_before_reset_password_form', 'woocommerce_output_all_notices', 10 );
}
/**
* Remove WooCommerce hook actions to avoid duplicate content
*
* @since 1.7
*
* @param string $action
* @param array $filters
* @param string $context
* @param \WP_Post $post
*
* @return void
*/
public function maybe_remove_woo_hook_actions( $action, $filters, $context, $post ) {
$template = Woocommerce_Helpers::get_repeated_wc_template_hooks_by_action( $action );
// STEP: Exit if not supported template
if ( empty( $template ) ) {
return;
}
$template_name = array_keys( $template )[0];
// STEP: Remove native woo hook actions
Woocommerce_Helpers::execute_actions_in_wc_template( $template_name, 'remove', $action );
}
/**
* Restore WooCommerce hooks
*
* @since 1.7
*
* @param string $action
* @param array $filters
* @param string $context
* @param \WP_Post $post
* @param mixed $value
*
* @return void
*/
public function maybe_restore_woo_hook_actions( $action, $filters, $context, $post, $value ) {
$template = Woocommerce_Helpers::get_repeated_wc_template_hooks_by_action( $action );
// STEP: Exit if not supported template
if ( empty( $template ) ) {
return;
}
$template = array_keys( $template )[0];
// STEP: Restore native woo hook actions
Woocommerce_Helpers::execute_actions_in_wc_template( $template, 'add', $action );
}
}