attributes = $this->parse_attributes( $attributes ); $this->content = $content; } /** * Get the block's attributes. * * @param array $attributes Block attributes. Default empty array. * @return array Block attributes merged with defaults. */ protected function parse_attributes( $attributes ) { // These should match what's set in JS `registerBlockType`. $defaults = array( 'columns' => wc_get_theme_support( 'product_blocks::default_columns', 3 ), 'rows' => wc_get_theme_support( 'product_blocks::default_rows', 1 ), 'categories' => array(), 'catOperator' => 'any', 'contentVisibility' => array( 'title' => true, 'price' => true, 'rating' => true, 'button' => true, ), ); $attributes = wp_parse_args( $attributes, $defaults ); if ( ! empty( $attributes['rows'] ) && ! empty( $attributes['columns'] ) ) { $attributes['limit'] = intval( $attributes['columns'] ) * intval( $attributes['rows'] ); } else { $attributes['limit'] = -1; } return $attributes; } /** * Set args specific to this block * * @param array $query_args Query args. */ abstract protected function set_block_query_args( &$query_args ); /** * Set orderby/order query args. * * @param array $query_args Query args. */ protected function set_ordering_query_args( &$query_args ) { $orderby = ''; $order = ''; if ( isset( $this->attributes['orderby'] ) ) { if ( 'price_desc' === $this->attributes['orderby'] ) { $orderby = 'price'; $order = 'DESC'; } elseif ( 'price_asc' === $this->attributes['orderby'] ) { $orderby = 'price'; $order = 'ASC'; } else { $orderby = $this->attributes['orderby']; } } // This handles orderby queries and hooks in custom orderby functions. $query_args = array_merge( $query_args, WC()->query->get_catalog_ordering_args( $orderby, $order ) ); } /** * Set categories query args. * * @param array $query_args Query args. */ protected function set_categories_query_args( &$query_args ) { if ( ! empty( $this->attributes['categories'] ) ) { $categories = array_map( 'absint', $this->attributes['categories'] ); $query_args['tax_query'][] = array( 'taxonomy' => 'product_cat', 'terms' => $categories, 'field' => 'term_id', 'operator' => 'all' === $this->attributes['catOperator'] ? 'AND' : 'IN', /* * When cat_operator is AND, the children categories should be excluded, * as only products belonging to all the children categories would be selected. */ 'include_children' => 'all' === $this->attributes['catOperator'] ? false : true, ); } } /** * Get all product query args for WP_Query. * * @return array */ protected function get_products_query_args() { $query_args = array( 'post_type' => 'product', 'post_status' => 'publish', 'fields' => 'ids', 'ignore_sticky_posts' => true, 'no_found_rows' => false, 'posts_per_page' => intval( $this->attributes['limit'] ), 'tax_query' => array(), // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_query' => array(), // phpcs:ignore WordPress.DB.SlowDBQuery ); $this->set_ordering_query_args( $query_args ); $this->set_categories_query_args( $query_args ); $this->set_block_query_args( $query_args ); return $query_args; } /** * Run the query and return an array of product IDs * * @return array List of product IDs */ protected function get_products() { $query_hash = md5( wp_json_encode( $this->attributes ) . __CLASS__ ); $transient_name = 'wc_block_' . $query_hash; $transient_value = get_transient( $transient_name ); $transient_version = WC_Cache_Helper::get_transient_version( 'product_query' ); if ( isset( $transient_value['value'], $transient_value['version'] ) && $transient_value['version'] === $transient_version ) { $results = $transient_value['value']; } else { $query = new WP_Query( $this->get_products_query_args() ); $results = wp_parse_id_list( $query->posts ); $transient_value = array( 'version' => $transient_version, 'value' => $results, ); set_transient( $transient_name, $transient_value, DAY_IN_SECONDS * 30 ); // Remove ordering query arguments which may have been added by get_catalog_ordering_args. WC()->query->remove_ordering_args(); } // Prime caches to reduce future queries. if ( is_callable( '_prime_post_caches' ) ) { _prime_post_caches( $results ); } return $results; } /** * Render the Products block. * * @return string Rendered block type output. */ public function render() { $products = $this->get_products(); $classes = $this->get_container_classes(); $output = implode( '', array_map( array( $this, 'render_product' ), $products ) ); return sprintf( '