:get_variations()
* method. Doing so here reduces the total number of queries needed.
*
* @param int[] $product_ids Product ids to prime variation cache for.
*/
protected function prime_product_variations( $product_ids ) {
$cache_group = 'product_variation_meta_data';
$prime_product_ids = $this->get_non_cached_ids( wp_parse_id_list( $product_ids ), $cache_group );
if ( ! $prime_product_ids ) {
return;
}
global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$product_variations = $wpdb->get_results( "SELECT ID as variation_id, post_parent as product_id from {$wpdb->posts} WHERE post_parent IN ( " . implode( ',', $prime_product_ids ) . ' )', ARRAY_A );
$prime_variation_ids = array_column( $product_variations, 'variation_id' );
$variation_ids_by_parent = array_column( $product_variations, 'product_id', 'variation_id' );
if ( empty( $prime_variation_ids ) ) {
return;
}
$all_variation_meta_data = $wpdb->get_results(
$wpdb->prepare(
"SELECT post_id as variation_id, meta_key as attribute_key, meta_value as attribute_value FROM {$wpdb->postmeta} WHERE post_id IN (" . implode( ',', array_map( 'esc_sql', $prime_variation_ids ) ) . ') AND meta_key LIKE %s',
$wpdb->esc_like( 'attribute_' ) . '%'
)
);
// phpcs:enable
// Prepare the data to cache by indexing by the parent product.
$primed_data = array_reduce(
$all_variation_meta_data,
function( $values, $data ) use ( $variation_ids_by_parent ) {
$values[ $variation_ids_by_parent[ $data->variation_id ] ?? 0 ][] = $data;
return $values;
},
array_fill_keys( $prime_product_ids, [] )
);
// Cache everything.
foreach ( $primed_data as $product_id => $variation_meta_data ) {
wp_cache_set(
$product_id,
[
'last_modified' => get_the_modified_date( 'U', $product_id ),
'data' => $variation_meta_data,
],
$cache_group
);
}
}
/**
* Get the list of classes to apply to this block.
*
* @return string space-separated list of classes.
*/
protected function get_container_classes() {
$classes = array(
'wc-block-grid',
"wp-block-{$this->block_name}",
"wc-block-{$this->block_name}",
"has-{$this->attributes['columns']}-columns",
);
if ( $this->attributes['rows'] > 1 ) {
$classes[] = 'has-multiple-rows';
}
if ( isset( $this->attributes['align'] ) ) {
$classes[] = "align{$this->attributes['align']}";
}
if ( ! empty( $this->attributes['alignButtons'] ) ) {
$classes[] = 'has-aligned-buttons';
}
if ( ! empty( $this->attributes['className'] ) ) {
$classes[] = $this->attributes['className'];
}
return implode( ' ', $classes );
}
/**
* Render a single products.
*
* @param \WC_Product $product Product object.
* @return string Rendered product output.
*/
protected function render_product( $product ) {
$data = (object) array(
'permalink' => esc_url( $product->get_permalink() ),
'image' => $this->get_image_html( $product ),
'title' => $this->get_title_html( $product ),
'rating' => $this->get_rating_html( $product ),
'price' => $this->get_price_html( $product ),
'badge' => $this->get_sale_badge_html( $product ),
'button' => $this->get_button_html( $product ),
);
/**
* Filters the HTML for products in the grid.
*
* @param string $html Product grid item HTML.
* @param array $data Product data passed to the template.
* @param \WC_Product $product Product object.
* @return string Updated product grid item HTML.
*
* @since 2.2.0
*/
return apply_filters(
'woocommerce_blocks_product_grid_item_html',
"
permalink}\" class=\"wc-block-grid__product-link\">
{$data->badge}
{$data->image}
{$data->title}
{$data->price}
{$data->rating}
{$data->button}
",
$data,
$product
);
}
/**
* Get the product image.
*
* @param \WC_Product $product Product.
* @return string
*/
protected function get_image_html( $product ) {
if ( array_key_exists( 'image', $this->attributes['contentVisibility'] ) && false === $this->attributes['contentVisibility']['image'] ) {
return '';
}
$attr = array(
'alt' => '',
);
if ( $product->get_image_id() ) {
$image_alt = get_post_meta( $product->get_image_id(), '_wp_attachment_image_alt', true );
$attr = array(
'alt' => ( $image_alt ? $image_alt : $product->get_name() ),
);
}
return '' . $product->get_image( 'woocommerce_thumbnail', $attr ) . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Get the product title.
*
* @param \WC_Product $product Product.
* @return string
*/
protected function get_title_html( $product ) {
if ( empty( $this->attributes['contentVisibility']['title'] ) ) {
return '';
}
return '' . wp_kses_post( $product->get_title() ) . '
';
}
/**
* Render the rating icons.
*
* @param WC_Product $product Product.
* @return string Rendered product output.
*/
protected function get_rating_html( $product ) {
if ( empty( $this->attributes['contentVisibility']['rating'] ) ) {
return '';
}
$rating_count = $product->get_rating_count();
$average = $product->get_average_rating();
if ( $rating_count > 0 ) {
return sprintf(
'%s
',
wc_get_rating_html( $average, $rating_count ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
}
return '';
}
/**
* Get the price.
*
* @param \WC_Product $product Product.
* @return string Rendered product output.
*/
protected function get_price_html( $product ) {
if ( empty( $this->attributes['contentVisibility']['price'] ) ) {
return '';
}
return sprintf(
'%s
',
wp_kses_post( $product->get_price_html() )
);
}
/**
* Get the sale badge.
*
* @param \WC_Product $product Product.
* @return string Rendered product output.
*/
protected function get_sale_badge_html( $product ) {
if ( empty( $this->attributes['contentVisibility']['price'] ) ) {
return '';
}
if ( empty( $this->attributes['contentVisibility']['image'] ) ) {
return '';
}
if ( ! $product->is_on_sale() ) {
return;
}
return '
' . esc_html__( 'Sale', 'woocommerce' ) . '
' . esc_html__( 'Product on sale', 'woocommerce' ) . '
';
}
/**
* Get the button.
*
* @param \WC_Product $product Product.
* @return string Rendered product output.
*/
protected function get_button_html( $product ) {
if ( empty( $this->attributes['contentVisibility']['button'] ) ) {
return '';
}
return '' . $this->get_add_to_cart( $product ) . '
';
}
/**
* Get the "add to cart" button.
*
* @param \WC_Product $product Product.
* @return string Rendered product output.
*/
protected function get_add_to_cart( $product ) {
$attributes = array(
'aria-label' => $product->add_to_cart_description(),
'data-quantity' => '1',
'data-product_id' => $product->get_id(),
'data-product_sku' => $product->get_sku(),
'data-price' => wc_get_price_to_display( $product ),
'rel' => 'nofollow',
'class' => 'wp-block-button__link ' . ( function_exists( 'wc_wp_theme_get_element_class_name' ) ? wc_wp_theme_get_element_class_name( 'button' ) : '' ) . ' add_to_cart_button',
);
if (
$product->supports( 'ajax_add_to_cart' ) &&
$product->is_purchasable() &&
( $product->is_in_stock() || $product->backorders_allowed() )
) {
$attributes['class'] .= ' ajax_add_to_cart';
}
/**
* Filter to manipulate (add/modify/remove) attributes in the HTML code of the generated add to cart button.
*
* @since 8.6.0
*
* @param array $attributes An associative array containing default HTML attributes of the add to cart button.
* @param WC_Product $product The WC_Product instance of the product that will be added to the cart once the button is pressed.
*
* @return array Returns an associative array derived from the default array passed as an argument and added the extra HTML attributes.
*/
$attributes = apply_filters( 'woocommerce_blocks_product_grid_add_to_cart_attributes', $attributes, $product );
return sprintf(
'%s',
esc_url( $product->add_to_cart_url() ),
wc_implode_html_attributes( $attributes ),
esc_html( $product->add_to_cart_text() )
);
}
/**
* Extra data passed through from server to client for block.
*
* @param array $attributes Any attributes that currently are available from the block.
* Note, this will be empty in the editor context when the block is
* not in the post content on editor load.
*/
protected function enqueue_data( array $attributes = [] ) {
parent::enqueue_data( $attributes );
$this->asset_data_registry->add( 'minColumns', wc_get_theme_support( 'product_blocks::min_columns', 1 ) );
$this->asset_data_registry->add( 'maxColumns', wc_get_theme_support( 'product_blocks::max_columns', 6 ) );
$this->asset_data_registry->add( 'defaultColumns', wc_get_theme_support( 'product_blocks::default_columns', 3 ) );
$this->asset_data_registry->add( 'minRows', wc_get_theme_support( 'product_blocks::min_rows', 1 ) );
$this->asset_data_registry->add( 'maxRows', wc_get_theme_support( 'product_blocks::max_rows', 6 ) );
$this->asset_data_registry->add( 'defaultRows', wc_get_theme_support( 'product_blocks::default_rows', 3 ) );
}
/**
* Get the frontend style handle for this block type.
*
* @return string[]
*/
protected function get_block_type_style() {
// Currently these blocks rely on the styles from the All Products block.
return [ 'wc-blocks-style', 'wc-blocks-style-all-products' ];
}
}