File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/BlockTypes.tar
AbstractBlock.php 0000644 00000034755 15155156421 0010016 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use WP_Block;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
/**
* AbstractBlock class.
*/
abstract class AbstractBlock {
/**
* Block namespace.
*
* @var string
*/
protected $namespace = 'woocommerce';
/**
* Block name within this namespace.
*
* @var string
*/
protected $block_name = '';
/**
* Tracks if assets have been enqueued.
*
* @var boolean
*/
protected $enqueued_assets = false;
/**
* Instance of the asset API.
*
* @var AssetApi
*/
protected $asset_api;
/**
* Instance of the asset data registry.
*
* @var AssetDataRegistry
*/
protected $asset_data_registry;
/**
* Instance of the integration registry.
*
* @var IntegrationRegistry
*/
protected $integration_registry;
/**
* Constructor.
*
* @param AssetApi $asset_api Instance of the asset API.
* @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
* @param IntegrationRegistry $integration_registry Instance of the integration registry.
* @param string $block_name Optionally set block name during construct.
*/
public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry, IntegrationRegistry $integration_registry, $block_name = '' ) {
$this->asset_api = $asset_api;
$this->asset_data_registry = $asset_data_registry;
$this->integration_registry = $integration_registry;
$this->block_name = $block_name ? $block_name : $this->block_name;
$this->initialize();
}
/**
* The default render_callback for all blocks. This will ensure assets are enqueued just in time, then render
* the block (if applicable).
*
* @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block|null $block Block instance.
* @return string Rendered block type output.
*/
public function render_callback( $attributes = [], $content = '', $block = null ) {
$render_callback_attributes = $this->parse_render_callback_attributes( $attributes );
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
$this->enqueue_assets( $render_callback_attributes, $content, $block );
}
return $this->render( $render_callback_attributes, $content, $block );
}
/**
* Enqueue assets used for rendering the block in editor context.
*
* This is needed if a block is not yet within the post content--`render` and `enqueue_assets` may not have ran.
*/
public function enqueue_editor_assets() {
if ( $this->enqueued_assets ) {
return;
}
$this->enqueue_data();
}
/**
* Initialize this block type.
*
* - Hook into WP lifecycle.
* - Register the block with WordPress.
*/
protected function initialize() {
if ( empty( $this->block_name ) ) {
_doing_it_wrong( __METHOD__, esc_html__( 'Block name is required.', 'woocommerce' ), '4.5.0' );
return false;
}
$this->integration_registry->initialize( $this->block_name . '_block' );
$this->register_block_type_assets();
$this->register_block_type();
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
}
/**
* Register script and style assets for the block type before it is registered.
*
* This registers the scripts; it does not enqueue them.
*/
protected function register_block_type_assets() {
if ( null !== $this->get_block_type_editor_script() ) {
$data = $this->asset_api->get_script_data( $this->get_block_type_editor_script( 'path' ) );
$has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );
$this->asset_api->register_script(
$this->get_block_type_editor_script( 'handle' ),
$this->get_block_type_editor_script( 'path' ),
array_merge(
$this->get_block_type_editor_script( 'dependencies' ),
$this->integration_registry->get_all_registered_editor_script_handles()
),
$has_i18n
);
}
if ( null !== $this->get_block_type_script() ) {
$data = $this->asset_api->get_script_data( $this->get_block_type_script( 'path' ) );
$has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );
$this->asset_api->register_script(
$this->get_block_type_script( 'handle' ),
$this->get_block_type_script( 'path' ),
array_merge(
$this->get_block_type_script( 'dependencies' ),
$this->integration_registry->get_all_registered_script_handles()
),
$has_i18n
);
}
}
/**
* Injects Chunk Translations into the page so translations work for lazy loaded components.
*
* The chunk names are defined when creating lazy loaded components using webpackChunkName.
*
* @param string[] $chunks Array of chunk names.
*/
protected function register_chunk_translations( $chunks ) {
foreach ( $chunks as $chunk ) {
$handle = 'wc-blocks-' . $chunk . '-chunk';
$this->asset_api->register_script( $handle, $this->asset_api->get_block_asset_build_path( $chunk ), [], true );
wp_add_inline_script(
$this->get_block_type_script( 'handle' ),
wp_scripts()->print_translations( $handle, false ),
'before'
);
wp_deregister_script( $handle );
}
}
/**
* Generate an array of chunks paths for loading translation.
*
* @param string $chunks_folder The folder to iterate over.
* @return string[] $chunks list of chunks to load.
*/
protected function get_chunks_paths( $chunks_folder ) {
$build_path = \Automattic\WooCommerce\Blocks\Package::get_path() . 'build/';
$blocks = [];
if ( ! is_dir( $build_path . $chunks_folder ) ) {
return [];
}
foreach ( new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $build_path . $chunks_folder ) ) as $block_name ) {
$blocks[] = str_replace( $build_path, '', $block_name );
}
$chunks = preg_filter( '/.js/', '', $blocks );
return $chunks;
}
/**
* Registers the block type with WordPress.
*
* @return string[] Chunks paths.
*/
protected function register_block_type() {
$block_settings = [
'render_callback' => $this->get_block_type_render_callback(),
'editor_script' => $this->get_block_type_editor_script( 'handle' ),
'editor_style' => $this->get_block_type_editor_style(),
'style' => $this->get_block_type_style(),
];
if ( isset( $this->api_version ) && '2' === $this->api_version ) {
$block_settings['api_version'] = 2;
}
$metadata_path = $this->asset_api->get_block_metadata_path( $this->block_name );
/**
* We always want to load block styles separately, for every theme.
* When the core assets are loaded separately, other blocks' styles get
* enqueued separately too. Thus we only need to handle the remaining
* case.
*/
if (
! is_admin() &&
! wc_current_theme_is_fse_theme() &&
$block_settings['style'] &&
(
! function_exists( 'wp_should_load_separate_core_block_assets' ) ||
! wp_should_load_separate_core_block_assets()
)
) {
$style_handles = $block_settings['style'];
$block_settings['style'] = null;
add_filter(
'render_block',
function( $html, $block ) use ( $style_handles ) {
if ( $block['blockName'] === $this->get_block_type() ) {
array_map( 'wp_enqueue_style', $style_handles );
}
return $html;
},
10,
2
);
}
// Prefer to register with metadata if the path is set in the block's class.
if ( ! empty( $metadata_path ) ) {
register_block_type_from_metadata(
$metadata_path,
$block_settings
);
return;
}
/*
* Insert attributes and supports if we're not registering the block using metadata.
* These are left unset until now and only added here because if they were set when registering with metadata,
* the attributes and supports from $block_settings would override the values from metadata.
*/
$block_settings['attributes'] = $this->get_block_type_attributes();
$block_settings['supports'] = $this->get_block_type_supports();
$block_settings['uses_context'] = $this->get_block_type_uses_context();
register_block_type(
$this->get_block_type(),
$block_settings
);
}
/**
* Get the block type.
*
* @return string
*/
protected function get_block_type() {
return $this->namespace . '/' . $this->block_name;
}
/**
* Get the render callback for this block type.
*
* Dynamic blocks should return a callback, for example, `return [ $this, 'render' ];`
*
* @see $this->register_block_type()
* @return callable|null;
*/
protected function get_block_type_render_callback() {
return [ $this, 'render_callback' ];
}
/**
* Get the editor script data for this block type.
*
* @see $this->register_block_type()
* @param string $key Data to get, or default to everything.
* @return array|string
*/
protected function get_block_type_editor_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-block',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
'dependencies' => [ 'wc-blocks' ],
];
return $key ? $script[ $key ] : $script;
}
/**
* Get the editor style handle for this block type.
*
* @see $this->register_block_type()
* @return string|null
*/
protected function get_block_type_editor_style() {
return 'wc-blocks-editor-style';
}
/**
* Get the frontend script handle for this block type.
*
* @see $this->register_block_type()
* @param string $key Data to get, or default to everything.
* @return array|string|null
*/
protected function get_block_type_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-block-frontend',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
'dependencies' => [],
];
return $key ? $script[ $key ] : $script;
}
/**
* Get the frontend style handle for this block type.
*
* @return string[]|null
*/
protected function get_block_type_style() {
$this->asset_api->register_style( 'wc-blocks-style-' . $this->block_name, $this->asset_api->get_block_asset_build_path( $this->block_name, 'css' ), [], 'all', true );
return [ 'wc-blocks-style', 'wc-blocks-style-' . $this->block_name ];
}
/**
* Get the supports array for this block type.
*
* @see $this->register_block_type()
* @return string;
*/
protected function get_block_type_supports() {
return [];
}
/**
* Get block attributes.
*
* @return array;
*/
protected function get_block_type_attributes() {
return [];
}
/**
* Get block usesContext.
*
* @return array;
*/
protected function get_block_type_uses_context() {
return [];
}
/**
* Parses block attributes from the render_callback.
*
* @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array.
* @return array
*/
protected function parse_render_callback_attributes( $attributes ) {
return is_a( $attributes, 'WP_Block' ) ? $attributes->attributes : $attributes;
}
/**
* Render the block. Extended by children.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
return $content;
}
/**
* Enqueue frontend assets for this block, just in time for rendering.
*
* @internal This prevents the block script being enqueued on all pages. It is only enqueued as needed. Note that
* we intentionally do not pass 'script' to register_block_type.
*
* @param array $attributes Any attributes that currently are available from the block.
* @param string $content The block content.
* @param WP_Block $block The block object.
*/
protected function enqueue_assets( array $attributes, $content, $block ) {
if ( $this->enqueued_assets ) {
return;
}
$this->enqueue_data( $attributes );
$this->enqueue_scripts( $attributes );
$this->enqueued_assets = true;
}
/**
* 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 = [] ) {
$registered_script_data = $this->integration_registry->get_all_registered_script_data();
foreach ( $registered_script_data as $asset_data_key => $asset_data_value ) {
if ( ! $this->asset_data_registry->exists( $asset_data_key ) ) {
$this->asset_data_registry->add( $asset_data_key, $asset_data_value );
}
}
if ( ! $this->asset_data_registry->exists( 'wcBlocksConfig' ) ) {
$this->asset_data_registry->add(
'wcBlocksConfig',
[
'buildPhase' => Package::feature()->get_flag(),
'pluginUrl' => plugins_url( '/', dirname( __DIR__ ) ),
'productCount' => array_sum( (array) wp_count_posts( 'product' ) ),
'restApiRoutes' => [
'/wc/store/v1' => array_keys( $this->get_routes_from_namespace( 'wc/store/v1' ) ),
],
'defaultAvatar' => get_avatar_url( 0, [ 'force_default' => true ] ),
/*
* translators: If your word count is based on single characters (e.g. East Asian characters),
* enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'.
* Do not translate into your own language.
*/
'wordCountType' => _x( 'words', 'Word count type. Do not translate!', 'woocommerce' ),
]
);
}
}
/**
* Get routes from a REST API namespace.
*
* @param string $namespace Namespace to retrieve.
* @return array
*/
protected function get_routes_from_namespace( $namespace ) {
$rest_server = rest_get_server();
$namespace_index = $rest_server->get_namespace_index(
[
'namespace' => $namespace,
'context' => 'view',
]
);
if ( is_wp_error( $namespace_index ) ) {
return [];
}
$response_data = $namespace_index->get_data();
return $response_data['routes'] ?? [];
}
/**
* Register/enqueue scripts used for this block on the frontend, during render.
*
* @param array $attributes Any attributes that currently are available from the block.
*/
protected function enqueue_scripts( array $attributes = [] ) {
if ( null !== $this->get_block_type_script() ) {
wp_enqueue_script( $this->get_block_type_script( 'handle' ) );
}
}
}
AbstractDynamicBlock.php 0000644 00000003544 15155156421 0011313 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* AbstractDynamicBlock class.
*/
abstract class AbstractDynamicBlock extends AbstractBlock {
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
* @return null
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Get block attributes.
*
* @return array
*/
protected function get_block_type_attributes() {
return array();
}
/**
* Get the schema for the alignment property.
*
* @return array Property definition for align.
*/
protected function get_schema_align() {
return array(
'type' => 'string',
'enum' => array( 'left', 'center', 'right', 'wide', 'full' ),
);
}
/**
* Get the schema for a list of IDs.
*
* @return array Property definition for a list of numeric ids.
*/
protected function get_schema_list_ids() {
return array(
'type' => 'array',
'items' => array(
'type' => 'number',
),
'default' => array(),
);
}
/**
* Get the schema for a boolean value.
*
* @param string $default The default value.
* @return array Property definition.
*/
protected function get_schema_boolean( $default = true ) {
return array(
'type' => 'boolean',
'default' => $default,
);
}
/**
* Get the schema for a numeric value.
*
* @param string $default The default value.
* @return array Property definition.
*/
protected function get_schema_number( $default ) {
return array(
'type' => 'number',
'default' => $default,
);
}
/**
* Get the schema for a string value.
*
* @param string $default The default value.
* @return array Property definition.
*/
protected function get_schema_string( $default = '' ) {
return array(
'type' => 'string',
'default' => $default,
);
}
}
AbstractInnerBlock.php 0000644 00000003374 15155156421 0011003 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* AbstractInnerBlock class.
*/
abstract class AbstractInnerBlock extends AbstractBlock {
/**
* Is this inner block lazy loaded? this helps us know if we should load its frontend script ot not.
*
* @var boolean
*/
protected $is_lazy_loaded = true;
/**
* Registers the block type with WordPress using the metadata file.
*
* The registration using metadata is now recommended. And it's required for "Inner Blocks" to
* fix the issue of missing translations in the inspector (in the Editor mode)
*/
protected function register_block_type() {
$block_settings = [
'render_callback' => $this->get_block_type_render_callback(),
'editor_style' => $this->get_block_type_editor_style(),
'style' => $this->get_block_type_style(),
];
if ( isset( $this->api_version ) && '2' === $this->api_version ) {
$block_settings['api_version'] = 2;
}
$metadata_path = $this->asset_api->get_block_metadata_path( $this->block_name, 'inner-blocks/' );
// Prefer to register with metadata if the path is set in the block's class.
register_block_type_from_metadata(
$metadata_path,
$block_settings
);
}
/**
* For lazy loaded inner blocks, we don't want to enqueue the script but rather leave it for webpack to do that.
*
* @see $this->register_block_type()
* @param string $key Data to get, or default to everything.
* @return array|string|null
*/
protected function get_block_type_script( $key = null ) {
if ( $this->is_lazy_loaded ) {
return null;
}
return parent::get_block_type_script( $key );
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
}
AbstractProductGrid.php 0000644 00000052351 15155156421 0011202 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\BlocksWpQuery;
use Automattic\WooCommerce\StoreApi\SchemaController;
use Automattic\WooCommerce\StoreApi\StoreApi;
/**
* AbstractProductGrid class.
*/
abstract class AbstractProductGrid extends AbstractDynamicBlock {
/**
* Attributes.
*
* @var array
*/
protected $attributes = array();
/**
* InnerBlocks content.
*
* @var string
*/
protected $content = '';
/**
* Query args.
*
* @var array
*/
protected $query_args = array();
/**
* Meta query args.
*
* @var array
*/
protected $meta_query = array();
/**
* Get a set of attributes shared across most of the grid blocks.
*
* @return array List of block attributes with type and defaults.
*/
protected function get_block_type_attributes() {
return array(
'className' => $this->get_schema_string(),
'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ),
'categories' => $this->get_schema_list_ids(),
'catOperator' => array(
'type' => 'string',
'default' => 'any',
),
'contentVisibility' => $this->get_schema_content_visibility(),
'align' => $this->get_schema_align(),
'alignButtons' => $this->get_schema_boolean( false ),
'isPreview' => $this->get_schema_boolean( false ),
'stockStatus' => array(
'type' => 'array',
'default' => array_keys( wc_get_product_stock_status_options() ),
),
);
}
/**
* Include and render the dynamic block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block|null $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes = array(), $content = '', $block = null ) {
$this->attributes = $this->parse_attributes( $attributes );
$this->content = $content;
$this->query_args = $this->parse_query_args();
$products = array_filter( array_map( 'wc_get_product', $this->get_products() ) );
if ( ! $products ) {
return '';
}
/**
* Override product description to prevent infinite loop.
*
* @see https://github.com/woocommerce/woocommerce-blocks/pull/6849
*/
foreach ( $products as $product ) {
$product->set_description( '' );
}
/**
* Product List Render event.
*
* Fires a WP Hook named `experimental__woocommerce_blocks-product-list-render` on render so that the client
* can add event handling when certain products are displayed. This can be used by tracking extensions such
* as Google Analytics to track impressions.
*
* Provides the list of product data (shaped like the Store API responses) and the block name.
*/
$this->asset_api->add_inline_script(
'wp-hooks',
'
window.addEventListener( "DOMContentLoaded", () => {
wp.hooks.doAction(
"experimental__woocommerce_blocks-product-list-render",
{
products: JSON.parse( decodeURIComponent( "' . esc_js(
rawurlencode(
wp_json_encode(
array_map(
[ StoreApi::container()->get( SchemaController::class )->get( 'product' ), 'get_item_response' ],
$products
)
)
)
) . '" ) ),
listName: "' . esc_js( $this->block_name ) . '"
}
);
} );
',
'after'
);
return sprintf(
'<div class="%s"><ul class="wc-block-grid__products">%s</ul></div>',
esc_attr( $this->get_container_classes() ),
implode( '', array_map( array( $this, 'render_product' ), $products ) )
);
}
/**
* Get the schema for the contentVisibility attribute
*
* @return array List of block attributes with type and defaults.
*/
protected function get_schema_content_visibility() {
return array(
'type' => 'object',
'properties' => array(
'image' => $this->get_schema_boolean( true ),
'title' => $this->get_schema_boolean( true ),
'price' => $this->get_schema_boolean( true ),
'rating' => $this->get_schema_boolean( true ),
'button' => $this->get_schema_boolean( true ),
),
);
}
/**
* Get the schema for the orderby attribute.
*
* @return array Property definition of `orderby` attribute.
*/
protected function get_schema_orderby() {
return array(
'type' => 'string',
'enum' => array( 'date', 'popularity', 'price_asc', 'price_desc', 'rating', 'title', 'menu_order' ),
'default' => 'date',
);
}
/**
* 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', 3 ),
'alignButtons' => false,
'categories' => array(),
'catOperator' => 'any',
'contentVisibility' => array(
'image' => true,
'title' => true,
'price' => true,
'rating' => true,
'button' => true,
),
'stockStatus' => array_keys( wc_get_product_stock_status_options() ),
);
return wp_parse_args( $attributes, $defaults );
}
/**
* Parse query args.
*
* @return array
*/
protected function parse_query_args() {
// Store the original meta query.
$this->meta_query = WC()->query->get_meta_query();
$query_args = array(
'post_type' => 'product',
'post_status' => 'publish',
'fields' => 'ids',
'ignore_sticky_posts' => true,
'no_found_rows' => false,
'orderby' => '',
'order' => '',
'meta_query' => $this->meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery
'tax_query' => array(), // phpcs:ignore WordPress.DB.SlowDBQuery
'posts_per_page' => $this->get_products_limit(),
);
$this->set_block_query_args( $query_args );
$this->set_ordering_query_args( $query_args );
$this->set_categories_query_args( $query_args );
$this->set_visibility_query_args( $query_args );
$this->set_stock_status_query_args( $query_args );
return $query_args;
}
/**
* Parse query args.
*
* @param array $query_args Query args.
*/
protected function set_ordering_query_args( &$query_args ) {
if ( isset( $this->attributes['orderby'] ) ) {
if ( 'price_desc' === $this->attributes['orderby'] ) {
$query_args['orderby'] = 'price';
$query_args['order'] = 'DESC';
} elseif ( 'price_asc' === $this->attributes['orderby'] ) {
$query_args['orderby'] = 'price';
$query_args['order'] = 'ASC';
} elseif ( 'date' === $this->attributes['orderby'] ) {
$query_args['orderby'] = 'date';
$query_args['order'] = 'DESC';
} else {
$query_args['orderby'] = $this->attributes['orderby'];
}
}
$query_args = array_merge(
$query_args,
WC()->query->get_catalog_ordering_args( $query_args['orderby'], $query_args['order'] )
);
}
/**
* Set args specific to this block
*
* @param array $query_args Query args.
*/
abstract protected function set_block_query_args( &$query_args );
/**
* 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,
);
}
}
/**
* Set visibility query args.
*
* @param array $query_args Query args.
*/
protected function set_visibility_query_args( &$query_args ) {
$product_visibility_terms = wc_get_product_visibility_term_ids();
$product_visibility_not_in = array( $product_visibility_terms['exclude-from-catalog'] );
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
$product_visibility_not_in[] = $product_visibility_terms['outofstock'];
}
$query_args['tax_query'][] = array(
'taxonomy' => 'product_visibility',
'field' => 'term_taxonomy_id',
'terms' => $product_visibility_not_in,
'operator' => 'NOT IN',
);
}
/**
* Set which stock status to use when displaying products.
*
* @param array $query_args Query args.
* @return void
*/
protected function set_stock_status_query_args( &$query_args ) {
$stock_statuses = array_keys( wc_get_product_stock_status_options() );
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
if ( isset( $this->attributes['stockStatus'] ) && $stock_statuses !== $this->attributes['stockStatus'] ) {
// Reset meta_query then update with our stock status.
$query_args['meta_query'] = $this->meta_query;
$query_args['meta_query'][] = array(
'key' => '_stock_status',
'value' => array_merge( [ '' ], $this->attributes['stockStatus'] ),
'compare' => 'IN',
);
} else {
$query_args['meta_query'] = $this->meta_query;
}
// phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
}
/**
* Works out the item limit based on rows and columns, or returns default.
*
* @return int
*/
protected function get_products_limit() {
if ( isset( $this->attributes['rows'], $this->attributes['columns'] ) && ! empty( $this->attributes['rows'] ) ) {
$this->attributes['limit'] = intval( $this->attributes['columns'] ) * intval( $this->attributes['rows'] );
}
return intval( $this->attributes['limit'] );
}
/**
* Run the query and return an array of product IDs
*
* @return array List of product IDs
*/
protected function get_products() {
/**
* Filters whether or not the product grid is cacheable.
*
* @param boolean $is_cacheable The list of script dependencies.
* @param array $query_args Query args for the products query passed to BlocksWpQuery.
* @return array True to enable cache, false to disable cache.
*
* @since 2.5.0
*/
$is_cacheable = (bool) apply_filters( 'woocommerce_blocks_product_grid_is_cacheable', true, $this->query_args );
$transient_version = \WC_Cache_Helper::get_transient_version( 'product_query' );
$query = new BlocksWpQuery( $this->query_args );
$results = wp_parse_id_list( $is_cacheable ? $query->get_cached_posts( $transient_version ) : $query->get_posts() );
// 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. Note _prime_post_caches is private--we could replace this with our own
// query if it becomes unavailable.
if ( is_callable( '_prime_post_caches' ) ) {
_prime_post_caches( $results );
}
$this->prime_product_variations( $results );
return $results;
}
/**
* Retrieve IDs that are not already present in the cache.
*
* Based on WordPress function: _get_non_cached_ids
*
* @param int[] $product_ids Array of IDs.
* @param string $cache_key The cache bucket to check against.
* @return int[] Array of IDs not present in the cache.
*/
protected function get_non_cached_ids( $product_ids, $cache_key ) {
$non_cached_ids = array();
$cache_values = wp_cache_get_multiple( $product_ids, $cache_key );
foreach ( $cache_values as $id => $value ) {
if ( ! $value ) {
$non_cached_ids[] = (int) $id;
}
}
return $non_cached_ids;
}
/**
* Prime query cache of product variation meta data.
*
* Prepares values in the product_ID_variation_meta_data cache for later use in the ProductSchema::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',
"<li class=\"wc-block-grid__product\">
<a href=\"{$data->permalink}\" class=\"wc-block-grid__product-link\">
{$data->badge}
{$data->image}
{$data->title}
</a>
{$data->price}
{$data->rating}
{$data->button}
</li>",
$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 '<div class="wc-block-grid__product-image">' . $product->get_image( 'woocommerce_thumbnail', $attr ) . '</div>'; // 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 '<div class="wc-block-grid__product-title">' . wp_kses_post( $product->get_title() ) . '</div>';
}
/**
* 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(
'<div class="wc-block-grid__product-rating">%s</div>',
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(
'<div class="wc-block-grid__product-price price">%s</div>',
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 ( ! $product->is_on_sale() ) {
return;
}
return '<div class="wc-block-grid__product-onsale">
<span aria-hidden="true">' . esc_html__( 'Sale', 'woocommerce' ) . '</span>
<span class="screen-reader-text">' . esc_html__( 'Product on sale', 'woocommerce' ) . '</span>
</div>';
}
/**
* 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 '<div class="wp-block-button wc-block-grid__product-add-to-cart">' . $this->get_add_to_cart( $product ) . '</div>';
}
/**
* 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(),
'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';
}
return sprintf(
'<a href="%s" %s>%s</a>',
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 ), true );
$this->asset_data_registry->add( 'maxColumns', wc_get_theme_support( 'product_blocks::max_columns', 6 ), true );
$this->asset_data_registry->add( 'defaultColumns', wc_get_theme_support( 'product_blocks::default_columns', 3 ), true );
$this->asset_data_registry->add( 'minRows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true );
$this->asset_data_registry->add( 'maxRows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true );
$this->asset_data_registry->add( 'defaultRows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true );
}
/**
* 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' ];
}
}
ActiveFilters.php 0000644 00000000342 15155156421 0010025 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ActiveFilters class.
*/
class ActiveFilters extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'active-filters';
}
AddToCartForm.php 0000644 00000013730 15155156421 0007717 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* CatalogSorting class.
*/
class AddToCartForm extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'add-to-cart-form';
/**
* Initializes the AddToCartForm block and hooks into the `wc_add_to_cart_message_html` filter
* to prevent displaying the Cart Notice when the block is inside the Single Product block
* and the Add to Cart button is clicked.
*
* It also hooks into the `woocommerce_add_to_cart_redirect` filter to prevent redirecting
* to another page when the block is inside the Single Product block and the Add to Cart button
* is clicked.
*
* @return void
*/
protected function initialize() {
parent::initialize();
add_filter( 'wc_add_to_cart_message_html', array( $this, 'add_to_cart_message_html_filter' ), 10, 2 );
add_filter( 'woocommerce_add_to_cart_redirect', array( $this, 'add_to_cart_redirect_filter' ), 10, 1 );
}
/**
* Get the block's attributes.
*
* @param array $attributes Block attributes. Default empty array.
* @return array Block attributes merged with defaults.
*/
private function parse_attributes( $attributes ) {
// These should match what's set in JS `registerBlockType`.
$defaults = array(
'isDescendentOfSingleProductBlock' => false,
);
return wp_parse_args( $attributes, $defaults );
}
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string | void Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
global $product;
$post_id = $block->context['postId'];
if ( ! isset( $post_id ) ) {
return '';
}
$previous_product = $product;
$product = wc_get_product( $post_id );
if ( ! $product instanceof \WC_Product ) {
$product = $previous_product;
return '';
}
ob_start();
/**
* Trigger the single product add to cart action for each product type.
*
* @since 9.7.0
*/
do_action( 'woocommerce_' . $product->get_type() . '_add_to_cart' );
$product = ob_get_clean();
if ( ! $product ) {
$product = $previous_product;
return '';
}
$parsed_attributes = $this->parse_attributes( $attributes );
$is_descendent_of_single_product_block = $parsed_attributes['isDescendentOfSingleProductBlock'];
$product = $this->add_is_descendent_of_single_product_block_hidden_input_to_product_form( $product, $is_descendent_of_single_product_block );
$classname = $attributes['className'] ?? '';
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$product_classname = $is_descendent_of_single_product_block ? 'product' : '';
$form = sprintf(
'<div class="wp-block-add-to-cart-form %1$s %2$s %3$s" style="%4$s">%5$s</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classname ),
esc_attr( $product_classname ),
esc_attr( $classes_and_styles['styles'] ),
$product
);
$product = $previous_product;
return $form;
}
/**
* Add a hidden input to the Add to Cart form to indicate that it is a descendent of a Single Product block.
*
* @param string $product The Add to Cart Form HTML.
* @param string $is_descendent_of_single_product_block Indicates if block is descendent of Single Product block.
*
* @return string The Add to Cart Form HTML with the hidden input.
*/
protected function add_is_descendent_of_single_product_block_hidden_input_to_product_form( $product, $is_descendent_of_single_product_block ) {
$hidden_is_descendent_of_single_product_block_input = sprintf(
'<input type="hidden" name="is-descendent-of-single-product-block" value="%1$s">',
$is_descendent_of_single_product_block ? 'true' : 'false'
);
$regex_pattern = '/<button\s+type="submit"[^>]*>.*?<\/button>/i';
preg_match( $regex_pattern, $product, $input_matches );
if ( ! empty( $input_matches ) ) {
$product = preg_replace( $regex_pattern, $hidden_is_descendent_of_single_product_block_input . $input_matches[0], $product );
}
return $product;
}
/**
* Filter the add to cart message to prevent the Notice from being displayed when the Add to Cart form is a descendent of a Single Product block
* and the Add to Cart button is clicked.
*
* @param string $message Message to be displayed when product is added to the cart.
*/
public function add_to_cart_message_html_filter( $message ) {
// phpcs:ignore
if ( isset( $_POST['is-descendent-of-single-product-block'] ) && 'true' === $_POST['is-descendent-of-single-product-block'] ) {
return false;
}
return $message;
}
/**
* Hooks into the `woocommerce_add_to_cart_redirect` filter to prevent redirecting
* to another page when the block is inside the Single Product block and the Add to Cart button
* is clicked.
*
* @param string $url The URL to redirect to after the product is added to the cart.
* @return string The filtered redirect URL.
*/
public function add_to_cart_redirect_filter( $url ) {
// phpcs:ignore
if ( isset( $_POST['is-descendent-of-single-product-block'] ) && 'true' == $_POST['is-descendent-of-single-product-block'] ) {
return wp_validate_redirect( wp_get_referer(), $url );
}
return $url;
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
}
/**
* It isn't necessary register block assets because it is a server side block.
*/
protected function register_block_type_assets() {
return null;
}
}
AllProducts.php 0000644 00000004042 15155156421 0007516 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* AllProducts class.
*/
class AllProducts extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'all-products';
/**
* 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 );
// Set this so filter blocks being used as widgets know when to render.
$this->asset_data_registry->add( 'hasFilterableProducts', true, true );
$this->asset_data_registry->add( 'minColumns', wc_get_theme_support( 'product_blocks::min_columns', 1 ), true );
$this->asset_data_registry->add( 'maxColumns', wc_get_theme_support( 'product_blocks::max_columns', 6 ), true );
$this->asset_data_registry->add( 'defaultColumns', wc_get_theme_support( 'product_blocks::default_columns', 3 ), true );
$this->asset_data_registry->add( 'minRows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true );
$this->asset_data_registry->add( 'maxRows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true );
$this->asset_data_registry->add( 'defaultRows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true );
// Hydrate the All Product block with data from the API. This is for the add to cart buttons which show current quantity in cart, and events.
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
}
}
/**
* It is necessary to register and enqueue assets during the render phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
}
}
AllReviews.php 0000644 00000002461 15155156421 0007342 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* AllReviews class.
*/
class AllReviews extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'all-reviews';
/**
* Get the frontend script handle for this block type.
*
* @see $this->register_block_type()
* @param string $key Data to get, or default to everything.
* @return array|string
*/
protected function get_block_type_script( $key = null ) {
$script = [
'handle' => 'wc-reviews-block-frontend',
'path' => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ),
'dependencies' => [],
];
return $key ? $script[ $key ] : $script;
}
/**
* 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( 'reviewRatingsEnabled', wc_review_ratings_enabled(), true );
$this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ), true );
}
}
AtomicBlock.php 0000644 00000001616 15155156421 0007455 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* AtomicBlock class.
*
* @internal
*/
class AtomicBlock extends AbstractBlock {
/**
* Get the editor script data for this block type.
*
* @param string $key Data to get, or default to everything.
* @return null
*/
protected function get_block_type_editor_script( $key = null ) {
return null;
}
/**
* Get the editor style handle for this block type.
*
* @return null
*/
protected function get_block_type_editor_style() {
return null;
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
* @return null
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
}
AttributeFilter.php 0000644 00000002127 15155156421 0010375 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* AttributeFilter class.
*/
class AttributeFilter extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'attribute-filter';
const FILTER_QUERY_VAR_PREFIX = 'filter_';
const QUERY_TYPE_QUERY_VAR_PREFIX = 'query_type_';
/**
* 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( 'attributes', array_values( wc_get_attribute_taxonomies() ), true );
}
/**
* Get the frontend style handle for this block type.
*
* @return string[]
*/
protected function get_block_type_style() {
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
}
}
Breadcrumbs.php 0000644 00000002431 15155156421 0007513 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* CatalogSorting class.
*/
class Breadcrumbs extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'breadcrumbs';
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string | void Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
ob_start();
woocommerce_breadcrumb();
$breadcrumb = ob_get_clean();
if ( ! $breadcrumb ) {
return;
}
$classname = $attributes['className'] ?? '';
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
return sprintf(
'<div class="woocommerce wc-block-breadcrumbs %1$s %2$s" style="%3$s">%4$s</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classname ),
esc_attr( $classes_and_styles['styles'] ),
$breadcrumb
);
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
}
Cart.php 0000644 00000030545 15155156421 0006162 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
/**
* Cart class.
*
* @internal
*/
class Cart extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart';
/**
* Chunks build folder.
*
* @var string
*/
protected $chunks_folder = 'cart-blocks';
/**
* Initialize this block type.
*
* - Hook into WP lifecycle.
* - Register the block with WordPress.
*/
protected function initialize() {
parent::initialize();
add_action( 'wp_loaded', array( $this, 'register_patterns' ) );
}
/**
* Dequeues the scripts added by WC Core to the Cart page.
*
* @return void
*/
public function dequeue_woocommerce_core_scripts() {
wp_dequeue_script( 'wc-cart' );
wp_dequeue_script( 'wc-password-strength-meter' );
wp_dequeue_script( 'selectWoo' );
wp_dequeue_style( 'select2' );
}
/**
* Register block pattern for Empty Cart Message to make it translatable.
*/
public function register_patterns() {
$shop_permalink = wc_get_page_id( 'shop' ) ? get_permalink( wc_get_page_id( 'shop' ) ) : '';
register_block_pattern(
'woocommerce/cart-heading',
array(
'title' => '',
'inserter' => false,
'content' => '<!-- wp:heading {"align":"wide", "level":1} --><h1 class="wp-block-heading alignwide">' . esc_html__( 'Cart', 'woocommerce' ) . '</h1><!-- /wp:heading -->',
)
);
register_block_pattern(
'woocommerce/cart-cross-sells-message',
array(
'title' => '',
'inserter' => false,
'content' => '<!-- wp:heading {"fontSize":"large"} --><h2 class="wp-block-heading has-large-font-size">' . esc_html__( 'You may be interested in…', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
)
);
register_block_pattern(
'woocommerce/cart-empty-message',
array(
'title' => '',
'inserter' => false,
'content' => '
<!-- wp:heading {"textAlign":"center","className":"with-empty-cart-icon wc-block-cart__empty-cart__title"} --><h2 class="wp-block-heading has-text-align-center with-empty-cart-icon wc-block-cart__empty-cart__title">' . esc_html__( 'Your cart is currently empty!', 'woocommerce' ) . '</h2><!-- /wp:heading -->
<!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center"><a href="' . esc_attr( esc_url( $shop_permalink ) ) . '">' . esc_html__( 'Browse store', 'woocommerce' ) . '</a></p><!-- /wp:paragraph -->
',
)
);
register_block_pattern(
'woocommerce/cart-new-in-store-message',
array(
'title' => '',
'inserter' => false,
'content' => '<!-- wp:heading {"textAlign":"center"} --><h2 class="wp-block-heading has-text-align-center">' . esc_html__( 'New in store', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
)
);
}
/**
* Get the editor script handle for this block type.
*
* @param string $key Data to get, or default to everything.
* @return array|string;
*/
protected function get_block_type_editor_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-block',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
'dependencies' => [ 'wc-blocks' ],
];
return $key ? $script[ $key ] : $script;
}
/**
* Get the frontend script handle for this block type.
*
* @see $this->register_block_type()
* @param string $key Data to get, or default to everything.
* @return array|string
*/
protected function get_block_type_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-block-frontend',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
'dependencies' => [],
];
return $key ? $script[ $key ] : $script;
}
/**
* Get the frontend style handle for this block type.
*
* @return string[]
*/
protected function get_block_type_style() {
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
}
/**
* Enqueue frontend assets for this block, just in time for rendering.
*
* @param array $attributes Any attributes that currently are available from the block.
* @param string $content The block content.
* @param WP_Block $block The block object.
*/
protected function enqueue_assets( array $attributes, $content, $block ) {
/**
* Fires before cart block scripts are enqueued.
*
* @since 2.6.0
*/
do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_before' );
parent::enqueue_assets( $attributes, $content, $block );
/**
* Fires after cart block scripts are enqueued.
*
* @since 2.6.0
*/
do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after' );
}
/**
* Append frontend scripts when rendering the Cart block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
// Dequeue the core scripts when rendering this block.
add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_woocommerce_core_scripts' ), 20 );
/**
* We need to check if $content has any templates from prior iterations of the block, in order to update to the latest iteration.
* We test the iteration version by searching for new blocks brought in by it.
* The blocks used for testing should be always available in the block (not removable by the user).
*/
$regex_for_filled_cart_block = '/<div[^<]*?data-block-name="woocommerce\/filled-cart-block"[^>]*?>/mi';
// Filled Cart block was added in i2, so we search for it to see if we have a Cart i1 template.
$has_i1_template = ! preg_match( $regex_for_filled_cart_block, $content );
if ( $has_i1_template ) {
/**
* This fallback structure needs to match the defaultTemplate variables defined in the block's edit.tsx files,
* starting from the parent block and going down each inner block, in the order the blocks were registered.
*/
$inner_blocks_html = '$0
<div data-block-name="woocommerce/filled-cart-block" class="wp-block-woocommerce-filled-cart-block">
<div data-block-name="woocommerce/cart-items-block" class="wp-block-woocommerce-cart-items-block">
<div data-block-name="woocommerce/cart-line-items-block" class="wp-block-woocommerce-cart-line-items-block"></div>
</div>
<div data-block-name="woocommerce/cart-totals-block" class="wp-block-woocommerce-cart-totals-block">
<div data-block-name="woocommerce/cart-order-summary-block" class="wp-block-woocommerce-cart-order-summary-block"></div>
<div data-block-name="woocommerce/cart-express-payment-block" class="wp-block-woocommerce-cart-express-payment-block"></div>
<div data-block-name="woocommerce/proceed-to-checkout-block" class="wp-block-woocommerce-proceed-to-checkout-block"></div>
<div data-block-name="woocommerce/cart-accepted-payment-methods-block" class="wp-block-woocommerce-cart-accepted-payment-methods-block"></div>
</div>
</div>
<div data-block-name="woocommerce/empty-cart-block" class="wp-block-woocommerce-empty-cart-block">
';
$content = preg_replace( '/<div class="[a-zA-Z0-9_\- ]*wp-block-woocommerce-cart[a-zA-Z0-9_\- ]*">/mi', $inner_blocks_html, $content );
$content = $content . '</div>';
}
/**
* Cart i3 added inner blocks for Order summary. We need to add them to Cart i2 templates.
* The order needs to match the order in which these blocks were registered.
*/
$order_summary_with_inner_blocks = '$0
<div data-block-name="woocommerce/cart-order-summary-heading-block" class="wp-block-woocommerce-cart-order-summary-heading-block"></div>
<div data-block-name="woocommerce/cart-order-summary-subtotal-block" class="wp-block-woocommerce-cart-order-summary-subtotal-block"></div>
<div data-block-name="woocommerce/cart-order-summary-fee-block" class="wp-block-woocommerce-cart-order-summary-fee-block"></div>
<div data-block-name="woocommerce/cart-order-summary-discount-block" class="wp-block-woocommerce-cart-order-summary-discount-block"></div>
<div data-block-name="woocommerce/cart-order-summary-coupon-form-block" class="wp-block-woocommerce-cart-order-summary-coupon-form-block"></div>
<div data-block-name="woocommerce/cart-order-summary-shipping-form-block" class="wp-block-woocommerce-cart-order-summary-shipping-block"></div>
<div data-block-name="woocommerce/cart-order-summary-taxes-block" class="wp-block-woocommerce-cart-order-summary-taxes-block"></div>
';
// Order summary subtotal block was added in i3, so we search for it to see if we have a Cart i2 template.
$regex_for_order_summary_subtotal = '/<div[^<]*?data-block-name="woocommerce\/cart-order-summary-subtotal-block"[^>]*?>/mi';
$regex_for_order_summary = '/<div[^<]*?data-block-name="woocommerce\/cart-order-summary-block"[^>]*?>/mi';
$has_i2_template = ! preg_match( $regex_for_order_summary_subtotal, $content );
if ( $has_i2_template ) {
$content = preg_replace( $regex_for_order_summary, $order_summary_with_inner_blocks, $content );
}
return $content;
}
/**
* 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( 'countryData', CartCheckoutUtils::get_country_data(), true );
$this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true );
$this->asset_data_registry->add( 'isShippingCalculatorEnabled', filter_var( get_option( 'woocommerce_enable_shipping_calc' ), FILTER_VALIDATE_BOOLEAN ), true );
$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true );
$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true );
$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled(), true );
$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled(), true );
$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled(), true );
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ), true );
$this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 );
$this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme(), true );
$pickup_location_settings = get_option( 'woocommerce_pickup_location_settings', [] );
$this->asset_data_registry->add( 'localPickupEnabled', wc_string_to_bool( $pickup_location_settings['enabled'] ?? 'no' ), true );
// Hydrate the following data depending on admin or frontend context.
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
}
/**
* Fires after cart block data is registered.
*
* @since 2.6.0
*/
do_action( 'woocommerce_blocks_cart_enqueue_data' );
}
/**
* Register script and style assets for the block type before it is registered.
*
* This registers the scripts; it does not enqueue them.
*/
protected function register_block_type_assets() {
parent::register_block_type_assets();
$chunks = $this->get_chunks_paths( $this->chunks_folder );
$vendor_chunks = $this->get_chunks_paths( 'vendors--cart-blocks' );
$shared_chunks = [];
$this->register_chunk_translations( array_merge( $chunks, $vendor_chunks, $shared_chunks ) );
}
/**
* Get list of Cart block & its inner-block types.
*
* @return array;
*/
public static function get_cart_block_types() {
return [
'Cart',
'CartOrderSummaryTaxesBlock',
'CartOrderSummarySubtotalBlock',
'FilledCartBlock',
'EmptyCartBlock',
'CartTotalsBlock',
'CartItemsBlock',
'CartLineItemsBlock',
'CartOrderSummaryBlock',
'CartExpressPaymentBlock',
'ProceedToCheckoutBlock',
'CartAcceptedPaymentMethodsBlock',
'CartOrderSummaryCouponFormBlock',
'CartOrderSummaryDiscountBlock',
'CartOrderSummaryFeeBlock',
'CartOrderSummaryHeadingBlock',
'CartOrderSummaryShippingBlock',
'CartCrossSellsBlock',
'CartCrossSellsProductsBlock',
];
}
}
CartAcceptedPaymentMethodsBlock.php 0000644 00000000440 15155156421 0013437 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartAcceptedPaymentMethodsBlock class.
*/
class CartAcceptedPaymentMethodsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-accepted-payment-methods-block';
}
CartCrossSellsBlock.php 0000644 00000000373 15155156421 0011146 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartCrossSellsBlock class.
*/
class CartCrossSellsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-cross-sells-block';
}
CartCrossSellsProductsBlock.php 0000644 00000000424 15155156421 0012667 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartCrossSellsProductsBlock class.
*/
class CartCrossSellsProductsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-cross-sells-products-block';
}
CartExpressPaymentBlock.php 0000644 00000000407 15155156421 0012037 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartExpressPaymentBlock class.
*/
class CartExpressPaymentBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-express-payment-block';
}
CartItemsBlock.php 0000644 00000000353 15155156421 0010131 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartItemsBlock class.
*/
class CartItemsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-items-block';
}
CartLineItemsBlock.php 0000644 00000000370 15155156421 0010740 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartLineItemsBlock class.
*/
class CartLineItemsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-line-items-block';
}
CartOrderSummaryBlock.php 0000644 00000000401 15155156421 0011473 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartOrderSummaryBlock class.
*/
class CartOrderSummaryBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-order-summary-block';
}
CartOrderSummaryCouponFormBlock.php 0000644 00000000441 15155156421 0013507 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartOrderSummaryCouponFormBlock class.
*/
class CartOrderSummaryCouponFormBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-order-summary-coupon-form-block';
}
CartOrderSummaryDiscountBlock.php 0000644 00000000432 15155156421 0013210 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartOrderSummaryDiscountBlock class.
*/
class CartOrderSummaryDiscountBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-order-summary-discount-block';
}
CartOrderSummaryFeeBlock.php 0000644 00000000413 15155156421 0012116 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartOrderSummaryFeeBlock class.
*/
class CartOrderSummaryFeeBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-order-summary-fee-block';
}
CartOrderSummaryHeadingBlock.php 0000644 00000000427 15155156421 0012763 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartOrderSummaryHeadingBlock class.
*/
class CartOrderSummaryHeadingBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-order-summary-heading-block';
}
CartOrderSummaryShippingBlock.php 0000644 00000000432 15155156421 0013201 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartOrderSummaryShippingBlock class.
*/
class CartOrderSummaryShippingBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-order-summary-shipping-block';
}
CartOrderSummarySubtotalBlock.php 0000644 00000000432 15155156421 0013215 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartOrderSummarySubtotalBlock class.
*/
class CartOrderSummarySubtotalBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-order-summary-subtotal-block';
}
CartOrderSummaryTaxesBlock.php 0000644 00000000421 15155156421 0012502 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartOrderSummaryTaxesBlock class.
*/
class CartOrderSummaryTaxesBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-order-summary-taxes-block';
}
CartTotalsBlock.php 0000644 00000000356 15155156421 0010321 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CartTotalsBlock class.
*/
class CartTotalsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'cart-totals-block';
}
CatalogSorting.php 0000644 00000002534 15155156421 0010206 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* CatalogSorting class.
*/
class CatalogSorting extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'catalog-sorting';
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string | void Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
ob_start();
woocommerce_catalog_ordering();
$catalog_sorting = ob_get_clean();
if ( ! $catalog_sorting ) {
return;
}
$classname = isset( $attributes['className'] ) ? $attributes['className'] : '';
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
return sprintf(
'<div class="woocommerce wc-block-catalog-sorting %1$s %2$s" style="%3$s">%4$s</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classname ),
esc_attr( $classes_and_styles['styles'] ),
$catalog_sorting
);
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
}
Checkout.php 0000644 00000046071 15155156421 0007037 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
/**
* Checkout class.
*
* @internal
*/
class Checkout extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout';
/**
* Chunks build folder.
*
* @var string
*/
protected $chunks_folder = 'checkout-blocks';
/**
* Initialize this block type.
*
* - Hook into WP lifecycle.
* - Register the block with WordPress.
*/
protected function initialize() {
parent::initialize();
add_action( 'wp_loaded', array( $this, 'register_patterns' ) );
}
/**
* Dequeues the scripts added by WC Core to the Checkout page.
*
* @return void
*/
public function dequeue_woocommerce_core_scripts() {
wp_dequeue_script( 'wc-checkout' );
wp_dequeue_script( 'wc-password-strength-meter' );
wp_dequeue_script( 'selectWoo' );
wp_dequeue_style( 'select2' );
}
/**
* Register block pattern for Empty Cart Message to make it translatable.
*/
public function register_patterns() {
register_block_pattern(
'woocommerce/checkout-heading',
array(
'title' => '',
'inserter' => false,
'content' => '<!-- wp:heading {"align":"wide", "level":1} --><h1 class="wp-block-heading alignwide">' . esc_html__( 'Checkout', 'woocommerce' ) . '</h1><!-- /wp:heading -->',
)
);
}
/**
* Get the editor script handle for this block type.
*
* @param string $key Data to get, or default to everything.
* @return array|string;
*/
protected function get_block_type_editor_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-block',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
'dependencies' => [ 'wc-blocks' ],
];
return $key ? $script[ $key ] : $script;
}
/**
* Get the frontend script handle for this block type.
*
* @see $this->register_block_type()
* @param string $key Data to get, or default to everything.
* @return array|string
*/
protected function get_block_type_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-block-frontend',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
'dependencies' => [],
];
return $key ? $script[ $key ] : $script;
}
/**
* Get the frontend style handle for this block type.
*
* @return string[]
*/
protected function get_block_type_style() {
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
}
/**
* Enqueue frontend assets for this block, just in time for rendering.
*
* @param array $attributes Any attributes that currently are available from the block.
* @param string $content The block content.
* @param WP_Block $block The block object.
*/
protected function enqueue_assets( array $attributes, $content, $block ) {
/**
* Fires before checkout block scripts are enqueued.
*
* @since 4.6.0
*/
do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_before' );
parent::enqueue_assets( $attributes, $content, $block );
/**
* Fires after checkout block scripts are enqueued.
*
* @since 4.6.0
*/
do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_after' );
}
/**
* Append frontend scripts when rendering the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( $this->is_checkout_endpoint() ) {
// Note: Currently the block only takes care of the main checkout form -- if an endpoint is set, refer to the
// legacy shortcode instead and do not render block.
return wc_current_theme_is_fse_theme() ? do_shortcode( '[woocommerce_checkout]' ) : '[woocommerce_checkout]';
}
// Dequeue the core scripts when rendering this block.
add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_woocommerce_core_scripts' ), 20 );
/**
* We need to check if $content has any templates from prior iterations of the block, in order to update to the latest iteration.
* We test the iteration version by searching for new blocks brought in by it.
* The blocks used for testing should be always available in the block (not removable by the user).
* Checkout i1's content was returning an empty div, with no data-block-name attribute
*/
$regex_for_empty_block = '/<div class="[a-zA-Z0-9_\- ]*wp-block-woocommerce-checkout[a-zA-Z0-9_\- ]*"><\/div>/mi';
$has_i1_template = preg_match( $regex_for_empty_block, $content );
if ( $has_i1_template ) {
// This fallback needs to match the default templates defined in our Blocks.
$inner_blocks_html = '
<div data-block-name="woocommerce/checkout-fields-block" class="wp-block-woocommerce-checkout-fields-block">
<div data-block-name="woocommerce/checkout-express-payment-block" class="wp-block-woocommerce-checkout-express-payment-block"></div>
<div data-block-name="woocommerce/checkout-contact-information-block" class="wp-block-woocommerce-checkout-contact-information-block"></div>
<div data-block-name="woocommerce/checkout-shipping-address-block" class="wp-block-woocommerce-checkout-shipping-address-block"></div>
<div data-block-name="woocommerce/checkout-billing-address-block" class="wp-block-woocommerce-checkout-billing-address-block"></div>
<div data-block-name="woocommerce/checkout-shipping-methods-block" class="wp-block-woocommerce-checkout-shipping-methods-block"></div>
<div data-block-name="woocommerce/checkout-payment-block" class="wp-block-woocommerce-checkout-payment-block"></div>' .
( isset( $attributes['showOrderNotes'] ) && false === $attributes['showOrderNotes'] ? '' : '<div data-block-name="woocommerce/checkout-order-note-block" class="wp-block-woocommerce-checkout-order-note-block"></div>' ) .
( isset( $attributes['showPolicyLinks'] ) && false === $attributes['showPolicyLinks'] ? '' : '<div data-block-name="woocommerce/checkout-terms-block" class="wp-block-woocommerce-checkout-terms-block"></div>' ) .
'<div data-block-name="woocommerce/checkout-actions-block" class="wp-block-woocommerce-checkout-actions-block"></div>
</div>
<div data-block-name="woocommerce/checkout-totals-block" class="wp-block-woocommerce-checkout-totals-block">
<div data-block-name="woocommerce/checkout-order-summary-block" class="wp-block-woocommerce-checkout-order-summary-block"></div>
</div>
';
$content = str_replace( '</div>', $inner_blocks_html . '</div>', $content );
}
/**
* Checkout i3 added inner blocks for Order summary.
* We need to add them to Checkout i2 templates.
* The order needs to match the order in which these blocks were registered.
*/
$order_summary_with_inner_blocks = '$0
<div data-block-name="woocommerce/checkout-order-summary-cart-items-block" class="wp-block-woocommerce-checkout-order-summary-cart-items-block"></div>
<div data-block-name="woocommerce/checkout-order-summary-subtotal-block" class="wp-block-woocommerce-checkout-order-summary-subtotal-block"></div>
<div data-block-name="woocommerce/checkout-order-summary-fee-block" class="wp-block-woocommerce-checkout-order-summary-fee-block"></div>
<div data-block-name="woocommerce/checkout-order-summary-discount-block" class="wp-block-woocommerce-checkout-order-summary-discount-block"></div>
<div data-block-name="woocommerce/checkout-order-summary-coupon-form-block" class="wp-block-woocommerce-checkout-order-summary-coupon-form-block"></div>
<div data-block-name="woocommerce/checkout-order-summary-shipping-block" class="wp-block-woocommerce-checkout-order-summary-shipping-block"></div>
<div data-block-name="woocommerce/checkout-order-summary-taxes-block" class="wp-block-woocommerce-checkout-order-summary-taxes-block"></div>
';
// Order summary subtotal block was added in i3, so we search for it to see if we have a Checkout i2 template.
$regex_for_order_summary_subtotal = '/<div[^<]*?data-block-name="woocommerce\/checkout-order-summary-subtotal-block"[^>]*?>/mi';
$regex_for_order_summary = '/<div[^<]*?data-block-name="woocommerce\/checkout-order-summary-block"[^>]*?>/mi';
$has_i2_template = ! preg_match( $regex_for_order_summary_subtotal, $content );
if ( $has_i2_template ) {
$content = preg_replace( $regex_for_order_summary, $order_summary_with_inner_blocks, $content );
}
/**
* Add the Local Pickup toggle to checkouts missing this forced template.
*/
$local_pickup_inner_blocks = '<div data-block-name="woocommerce/checkout-shipping-method-block" class="wp-block-woocommerce-checkout-shipping-method-block"></div>' . PHP_EOL . PHP_EOL . '<div data-block-name="woocommerce/checkout-pickup-options-block" class="wp-block-woocommerce-checkout-pickup-options-block"></div>' . PHP_EOL . PHP_EOL . '$0';
$has_local_pickup_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-shipping-method-block"[^>]*?>/mi';
$has_local_pickup = preg_match( $has_local_pickup_regex, $content );
if ( ! $has_local_pickup ) {
$shipping_address_block_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-shipping-address-block" class="wp-block-woocommerce-checkout-shipping-address-block"[^>]*?><\/div>/mi';
$content = preg_replace( $shipping_address_block_regex, $local_pickup_inner_blocks, $content );
}
return $content;
}
/**
* Check if we're viewing a checkout page endpoint, rather than the main checkout page itself.
*
* @return boolean
*/
protected function is_checkout_endpoint() {
return is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' );
}
/**
* 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( 'countryData', CartCheckoutUtils::get_country_data(), true );
$this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true );
$this->asset_data_registry->add(
'checkoutAllowsGuest',
false === filter_var(
wc()->checkout()->is_registration_required(),
FILTER_VALIDATE_BOOLEAN
),
true
);
$this->asset_data_registry->add(
'checkoutAllowsSignup',
filter_var(
wc()->checkout()->is_registration_enabled(),
FILTER_VALIDATE_BOOLEAN
),
true
);
$this->asset_data_registry->add( 'checkoutShowLoginReminder', filter_var( get_option( 'woocommerce_enable_checkout_login_reminder' ), FILTER_VALIDATE_BOOLEAN ), true );
$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true );
$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true );
$this->asset_data_registry->add( 'forcedBillingAddress', 'billing_only' === get_option( 'woocommerce_ship_to_destination' ), true );
$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled(), true );
$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled(), true );
$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled(), true );
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ), true );
$this->asset_data_registry->register_page_id( isset( $attributes['cartPageId'] ) ? $attributes['cartPageId'] : 0 );
$this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme(), true );
$pickup_location_settings = get_option( 'woocommerce_pickup_location_settings', [] );
$this->asset_data_registry->add( 'localPickupEnabled', wc_string_to_bool( $pickup_location_settings['enabled'] ?? 'no' ), true );
$is_block_editor = $this->is_block_editor();
// Hydrate the following data depending on admin or frontend context.
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'shippingMethodsExist' ) ) {
$methods_exist = wc_get_shipping_method_count( false, true ) > 0;
$this->asset_data_registry->add( 'shippingMethodsExist', $methods_exist );
}
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalShippingMethods' ) ) {
$shipping_methods = WC()->shipping()->get_shipping_methods();
$formatted_shipping_methods = array_reduce(
$shipping_methods,
function( $acc, $method ) {
if ( in_array( $method->id, LocalPickupUtils::get_local_pickup_method_ids(), true ) ) {
return $acc;
}
if ( $method->supports( 'settings' ) ) {
$acc[] = [
'id' => $method->id,
'title' => $method->method_title,
'description' => $method->method_description,
];
}
return $acc;
},
[]
);
$this->asset_data_registry->add( 'globalShippingMethods', $formatted_shipping_methods );
}
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'activeShippingZones' ) && class_exists( '\WC_Shipping_Zones' ) ) {
$shipping_zones = \WC_Shipping_Zones::get_zones();
$formatted_shipping_zones = array_reduce(
$shipping_zones,
function( $acc, $zone ) {
$acc[] = [
'id' => $zone['id'],
'title' => $zone['zone_name'],
'description' => $zone['formatted_zone_location'],
];
return $acc;
},
[]
);
$formatted_shipping_zones[] = [
'id' => 0,
'title' => __( 'International', 'woocommerce' ),
'description' => __( 'Locations outside all other zones', 'woocommerce' ),
];
$this->asset_data_registry->add( 'activeShippingZones', $formatted_shipping_zones );
}
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalPaymentMethods' ) ) {
// These are used to show options in the sidebar. We want to get the full list of enabled payment methods,
// not just the ones that are available for the current cart (which may not exist yet).
$payment_methods = $this->get_enabled_payment_gateways();
$formatted_payment_methods = array_reduce(
$payment_methods,
function( $acc, $method ) {
$acc[] = [
'id' => $method->id,
'title' => $method->method_title,
'description' => $method->method_description,
];
return $acc;
},
[]
);
$this->asset_data_registry->add( 'globalPaymentMethods', $formatted_payment_methods );
}
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
$this->asset_data_registry->hydrate_data_from_api_request( 'checkoutData', '/wc/store/v1/checkout' );
$this->hydrate_customer_payment_methods();
}
/**
* Fires after checkout block data is registered.
*
* @since 2.6.0
*/
do_action( 'woocommerce_blocks_checkout_enqueue_data' );
}
/**
* Get payment methods that are enabled in settings.
*
* @return array
*/
protected function get_enabled_payment_gateways() {
$payment_gateways = WC()->payment_gateways->payment_gateways();
return array_filter(
$payment_gateways,
function( $payment_gateway ) {
return 'yes' === $payment_gateway->enabled;
}
);
}
/**
* Are we currently on the admin block editor screen?
*/
protected function is_block_editor() {
if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
return false;
}
$screen = get_current_screen();
return $screen && $screen->is_block_editor();
}
/**
* Get saved customer payment methods for use in checkout.
*/
protected function hydrate_customer_payment_methods() {
if ( ! is_user_logged_in() || $this->asset_data_registry->exists( 'customerPaymentMethods' ) ) {
return;
}
add_filter( 'woocommerce_payment_methods_list_item', [ $this, 'include_token_id_with_payment_methods' ], 10, 2 );
$payment_gateways = $this->get_enabled_payment_gateways();
$payment_methods = wc_get_customer_saved_methods_list( get_current_user_id() );
// Filter out payment methods that are not enabled.
foreach ( $payment_methods as $payment_method_group => $saved_payment_methods ) {
$payment_methods[ $payment_method_group ] = array_filter(
$saved_payment_methods,
function( $saved_payment_method ) use ( $payment_gateways ) {
return in_array( $saved_payment_method['method']['gateway'], array_keys( $payment_gateways ), true );
}
);
}
$this->asset_data_registry->add(
'customerPaymentMethods',
$payment_methods
);
remove_filter( 'woocommerce_payment_methods_list_item', [ $this, 'include_token_id_with_payment_methods' ], 10, 2 );
}
/**
* Callback for woocommerce_payment_methods_list_item filter to add token id
* to the generated list.
*
* @param array $list_item The current list item for the saved payment method.
* @param \WC_Token $token The token for the current list item.
*
* @return array The list item with the token id added.
*/
public static function include_token_id_with_payment_methods( $list_item, $token ) {
$list_item['tokenId'] = $token->get_id();
$brand = ! empty( $list_item['method']['brand'] ) ?
strtolower( $list_item['method']['brand'] ) :
'';
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- need to match on translated value from core.
if ( ! empty( $brand ) && esc_html__( 'Credit card', 'woocommerce' ) !== $brand ) {
$list_item['method']['brand'] = wc_get_credit_card_type_label( $brand );
}
return $list_item;
}
/**
* Register script and style assets for the block type before it is registered.
*
* This registers the scripts; it does not enqueue them.
*/
protected function register_block_type_assets() {
parent::register_block_type_assets();
$chunks = $this->get_chunks_paths( $this->chunks_folder );
$vendor_chunks = $this->get_chunks_paths( 'vendors--checkout-blocks' );
$shared_chunks = [ 'cart-blocks/cart-express-payment--checkout-blocks/express-payment-frontend' ];
$this->register_chunk_translations( array_merge( $chunks, $vendor_chunks, $shared_chunks ) );
}
/**
* Get list of Checkout block & its inner-block types.
*
* @return array;
*/
public static function get_checkout_block_types() {
return [
'Checkout',
'CheckoutActionsBlock',
'CheckoutBillingAddressBlock',
'CheckoutContactInformationBlock',
'CheckoutExpressPaymentBlock',
'CheckoutFieldsBlock',
'CheckoutOrderNoteBlock',
'CheckoutOrderSummaryBlock',
'CheckoutOrderSummaryCartItemsBlock',
'CheckoutOrderSummaryCouponFormBlock',
'CheckoutOrderSummaryDiscountBlock',
'CheckoutOrderSummaryFeeBlock',
'CheckoutOrderSummaryShippingBlock',
'CheckoutOrderSummarySubtotalBlock',
'CheckoutOrderSummaryTaxesBlock',
'CheckoutPaymentBlock',
'CheckoutShippingAddressBlock',
'CheckoutShippingMethodsBlock',
'CheckoutShippingMethodBlock',
'CheckoutPickupOptionsBlock',
'CheckoutTermsBlock',
'CheckoutTotalsBlock',
];
}
}
CheckoutActionsBlock.php 0000644 00000000375 15155156421 0011330 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutActionsBlock class.
*/
class CheckoutActionsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-actions-block';
}
CheckoutBillingAddressBlock.php 0000644 00000000423 15155156421 0012610 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutBillingAddressBlock class.
*/
class CheckoutBillingAddressBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-billing-address-block';
}
CheckoutContactInformationBlock.php 0000644 00000000437 15155156421 0013530 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutContactInformationBlock class.
*/
class CheckoutContactInformationBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-contact-information-block';
}
CheckoutExpressPaymentBlock.php 0000644 00000000423 15155156421 0012711 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutExpressPaymentBlock class.
*/
class CheckoutExpressPaymentBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-express-payment-block';
}
CheckoutFieldsBlock.php 0000644 00000000372 15155156421 0011133 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutFieldsBlock class.
*/
class CheckoutFieldsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-fields-block';
}
CheckoutOrderNoteBlock.php 0000644 00000000404 15155156421 0011622 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutOrderNoteBlock class.
*/
class CheckoutOrderNoteBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-order-note-block';
}
CheckoutOrderSummaryBlock.php 0000644 00000000415 15155156421 0012354 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutOrderSummaryBlock class.
*/
class CheckoutOrderSummaryBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-order-summary-block';
}
CheckoutOrderSummaryCartItemsBlock.php 0000644 00000000452 15155156421 0014171 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutOrderSummaryCartItemsBlock class.
*/
class CheckoutOrderSummaryCartItemsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-order-summary-cart-items-block';
}
CheckoutOrderSummaryCouponFormBlock.php 0000644 00000000455 15155156421 0014370 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutOrderSummaryCouponFormBlock class.
*/
class CheckoutOrderSummaryCouponFormBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-order-summary-coupon-form-block';
}
CheckoutOrderSummaryDiscountBlock.php 0000644 00000000446 15155156421 0014071 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutOrderSummaryDiscountBlock class.
*/
class CheckoutOrderSummaryDiscountBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-order-summary-discount-block';
}
CheckoutOrderSummaryFeeBlock.php 0000644 00000000427 15155156421 0012777 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutOrderSummaryFeeBlock class.
*/
class CheckoutOrderSummaryFeeBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-order-summary-fee-block';
}
CheckoutOrderSummaryShippingBlock.php 0000644 00000000446 15155156421 0014062 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutOrderSummaryShippingBlock class.
*/
class CheckoutOrderSummaryShippingBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-order-summary-shipping-block';
}
CheckoutOrderSummarySubtotalBlock.php 0000644 00000000446 15155156421 0014076 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutOrderSummarySubtotalBlock class.
*/
class CheckoutOrderSummarySubtotalBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-order-summary-subtotal-block';
}
CheckoutOrderSummaryTaxesBlock.php 0000644 00000000435 15155156421 0013363 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutOrderSummaryTaxesBlock class.
*/
class CheckoutOrderSummaryTaxesBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-order-summary-taxes-block';
}
CheckoutPaymentBlock.php 0000644 00000000375 15155156421 0011345 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutPaymentBlock class.
*/
class CheckoutPaymentBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-payment-block';
}
CheckoutPickupOptionsBlock.php 0000644 00000000420 15155156421 0012526 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutPickupOptionsBlock class.
*/
class CheckoutPickupOptionsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-pickup-options-block';
}
CheckoutShippingAddressBlock.php 0000644 00000000426 15155156421 0013014 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutShippingAddressBlock class.
*/
class CheckoutShippingAddressBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-shipping-address-block';
}
CheckoutShippingMethodBlock.php 0000644 00000000423 15155156421 0012644 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutShippingMethodBlock class.
*/
class CheckoutShippingMethodBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-shipping-method-block';
}
CheckoutShippingMethodsBlock.php 0000644 00000000426 15155156421 0013032 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutShippingMethodsBlock class.
*/
class CheckoutShippingMethodsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-shipping-methods-block';
}
CheckoutTermsBlock.php 0000644 00000000367 15155156421 0011023 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutTermsBlock class.
*/
class CheckoutTermsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-terms-block';
}
CheckoutTotalsBlock.php 0000644 00000000372 15155156421 0011173 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutTotalsBlock class.
*/
class CheckoutTotalsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-totals-block';
}
ClassicTemplate.php 0000644 00000026052 15155156421 0010344 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use WC_Shortcode_Checkout;
use WC_Frontend_Scripts;
/**
* Classic Template class
*
* @internal
*/
class ClassicTemplate extends AbstractDynamicBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'legacy-template';
/**
* API version.
*
* @var string
*/
protected $api_version = '2';
const FILTER_PRODUCTS_BY_STOCK_QUERY_PARAM = 'filter_stock_status';
/**
* Initialize this block.
*/
protected function initialize() {
parent::initialize();
add_filter( 'render_block', array( $this, 'add_alignment_class_to_wrapper' ), 10, 2 );
add_filter( 'woocommerce_product_query_meta_query', array( $this, 'filter_products_by_stock' ) );
add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
}
/**
* Enqueue assets used for rendering the block in editor context.
*
* This is needed if a block is not yet within the post content--`render` and `enqueue_assets` may not have ran.
*/
public function enqueue_block_assets() {
// Ensures frontend styles for blocks exist in the site editor iframe.
if ( class_exists( 'WC_Frontend_Scripts' ) && is_admin() ) {
$frontend_scripts = new WC_Frontend_Scripts();
$styles = $frontend_scripts::get_styles();
foreach ( $styles as $handle => $style ) {
wp_enqueue_style(
$handle,
set_url_scheme( $style['src'] ),
$style['deps'],
$style['version'],
$style['media']
);
}
}
}
/**
* Render method for the Classic Template block. This method will determine which template to render.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
* @return string | void Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! isset( $attributes['template'] ) ) {
return;
}
/**
* We need to load the scripts here because when using block templates wp_head() gets run after the block
* template. As a result we are trying to enqueue required scripts before we have even registered them.
*
* @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5328#issuecomment-989013447
*/
if ( class_exists( 'WC_Frontend_Scripts' ) ) {
$frontend_scripts = new WC_Frontend_Scripts();
$frontend_scripts::load_scripts();
}
if ( OrderConfirmationTemplate::get_slug() === $attributes['template'] ) {
return $this->render_order_received();
}
if ( is_product() ) {
return $this->render_single_product();
}
$archive_templates = array(
'archive-product',
'taxonomy-product_cat',
'taxonomy-product_tag',
ProductAttributeTemplate::SLUG,
ProductSearchResultsTemplate::SLUG,
);
if ( in_array( $attributes['template'], $archive_templates, true ) ) {
// Set this so that our product filters can detect if it's a PHP template.
$this->asset_data_registry->add( 'isRenderingPhpTemplate', true, true );
// Set this so filter blocks being used as widgets know when to render.
$this->asset_data_registry->add( 'hasFilterableProducts', true, true );
$this->asset_data_registry->add(
'pageUrl',
html_entity_decode( get_pagenum_link() ),
''
);
return $this->render_archive_product();
}
ob_start();
echo "You're using the ClassicTemplate block";
wp_reset_postdata();
return ob_get_clean();
}
/**
* Render method for rendering the order confirmation template.
*
* @return string Rendered block type output.
*/
protected function render_order_received() {
ob_start();
echo '<div class="wp-block-group">';
echo sprintf(
'<%1$s %2$s>%3$s</%1$s>',
'h1',
get_block_wrapper_attributes(), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
esc_html__( 'Order confirmation', 'woocommerce' )
);
WC_Shortcode_Checkout::output( array() );
echo '</div>';
return ob_get_clean();
}
/**
* Render method for the single product template and parts.
*
* @return string Rendered block type output.
*/
protected function render_single_product() {
ob_start();
/**
* Hook: woocommerce_before_main_content
*
* Called before rendering the main content for a product.
*
* @see woocommerce_output_content_wrapper() Outputs opening DIV for the content (priority 10)
* @see woocommerce_breadcrumb() Outputs breadcrumb trail to the current product (priority 20)
* @see WC_Structured_Data::generate_website_data() Outputs schema markup (priority 30)
*
* @since 6.3.0
*/
do_action( 'woocommerce_before_main_content' );
while ( have_posts() ) :
the_post();
wc_get_template_part( 'content', 'single-product' );
endwhile;
/**
* Hook: woocommerce_after_main_content
*
* Called after rendering the main content for a product.
*
* @see woocommerce_output_content_wrapper_end() Outputs closing DIV for the content (priority 10)
*
* @since 6.3.0
*/
do_action( 'woocommerce_after_main_content' );
wp_reset_postdata();
return ob_get_clean();
}
/**
* Render method for the archive product template and parts.
*
* @return string Rendered block type output.
*/
protected function render_archive_product() {
ob_start();
/**
* Hook: woocommerce_before_main_content
*
* Called before rendering the main content for a product.
*
* @see woocommerce_output_content_wrapper() Outputs opening DIV for the content (priority 10)
* @see woocommerce_breadcrumb() Outputs breadcrumb trail to the current product (priority 20)
* @see WC_Structured_Data::generate_website_data() Outputs schema markup (priority 30)
*
* @since 6.3.0
*/
do_action( 'woocommerce_before_main_content' );
?>
<header class="woocommerce-products-header">
<?php
/**
* Hook: woocommerce_show_page_title
*
* Allows controlling the display of the page title.
*
* @since 6.3.0
*/
if ( apply_filters( 'woocommerce_show_page_title', true ) ) {
?>
<h1 class="woocommerce-products-header__title page-title">
<?php
woocommerce_page_title();
?>
</h1>
<?php
}
/**
* Hook: woocommerce_archive_description.
*
* @see woocommerce_taxonomy_archive_description() Renders the taxonomy archive description (priority 10)
* @see woocommerce_product_archive_description() Renders the product archive description (priority 10)
*
* @since 6.3.0
*/
do_action( 'woocommerce_archive_description' );
?>
</header>
<?php
if ( woocommerce_product_loop() ) {
/**
* Hook: woocommerce_before_shop_loop.
*
* @see woocommerce_output_all_notices() Render error notices (priority 10)
* @see woocommerce_result_count() Show number of results found (priority 20)
* @see woocommerce_catalog_ordering() Show form to control sort order (priority 30)
*
* @since 6.3.0
*/
do_action( 'woocommerce_before_shop_loop' );
woocommerce_product_loop_start();
if ( wc_get_loop_prop( 'total' ) ) {
while ( have_posts() ) {
the_post();
/**
* Hook: woocommerce_shop_loop.
*
* @since 6.3.0
*/
do_action( 'woocommerce_shop_loop' );
wc_get_template_part( 'content', 'product' );
}
}
woocommerce_product_loop_end();
/**
* Hook: woocommerce_after_shop_loop.
*
* @see woocommerce_pagination() Renders pagination (priority 10)
*
* @since 6.3.0
*/
do_action( 'woocommerce_after_shop_loop' );
} else {
/**
* Hook: woocommerce_no_products_found.
*
* @see wc_no_products_found() Default no products found content (priority 10)
*
* @since 6.3.0
*/
do_action( 'woocommerce_no_products_found' );
}
/**
* Hook: woocommerce_after_main_content
*
* Called after rendering the main content for a product.
*
* @see woocommerce_output_content_wrapper_end() Outputs closing DIV for the content (priority 10)
*
* @since 6.3.0
*/
do_action( 'woocommerce_after_main_content' );
wp_reset_postdata();
return ob_get_clean();
}
/**
* Get HTML markup with the right classes by attributes.
* This function appends the classname at the first element that have the class attribute.
* Based on the experience, all the wrapper elements have a class attribute.
*
* @param string $content Block content.
* @param array $block Parsed block data.
* @return string Rendered block type output.
*/
public function add_alignment_class_to_wrapper( string $content, array $block ) {
if ( ( 'woocommerce/' . $this->block_name ) !== $block['blockName'] ) {
return $content;
}
$attributes = (array) $block['attrs'];
// Set the default alignment to wide.
if ( ! isset( $attributes['align'] ) ) {
$attributes['align'] = 'wide';
}
$align_class_and_style = StyleAttributesUtils::get_align_class_and_style( $attributes );
if ( ! isset( $align_class_and_style['class'] ) ) {
return $content;
}
// Find the first tag.
$first_tag = '<[^<>]+>';
$matches = array();
preg_match( $first_tag, $content, $matches );
// If there is a tag, but it doesn't have a class attribute, add the class attribute.
if ( isset( $matches[0] ) && strpos( $matches[0], ' class=' ) === false ) {
$pattern_before_tag_closing = '/.+?(?=>)/';
return preg_replace( $pattern_before_tag_closing, '$0 class="' . $align_class_and_style['class'] . '"', $content, 1 );
}
// If there is a tag, and it has a class already, add the class attribute.
$pattern_get_class = '/(?<=class=\"|\')[^"|\']+(?=\"|\')/';
return preg_replace( $pattern_get_class, '$0 ' . $align_class_and_style['class'], $content, 1 );
}
/**
* Filter products by stock status when as query param there is "filter_stock_status"
*
* @param array $meta_query Meta query.
* @return array
*/
public function filter_products_by_stock( $meta_query ) {
global $wp_query;
if (
is_admin() ||
! $wp_query->is_main_query() ||
! isset( $_GET[ self::FILTER_PRODUCTS_BY_STOCK_QUERY_PARAM ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
) {
return $meta_query;
}
$stock_status = array_keys( wc_get_product_stock_status_options() );
$values = sanitize_text_field( wp_unslash( $_GET[ self::FILTER_PRODUCTS_BY_STOCK_QUERY_PARAM ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$values_to_array = explode( ',', $values );
$filtered_values = array_filter(
$values_to_array,
function( $value ) use ( $stock_status ) {
return in_array( $value, $stock_status, true );
}
);
if ( ! empty( $filtered_values ) ) {
$meta_query[] = array(
'key' => '_stock_status',
'value' => $filtered_values,
'compare' => 'IN',
);
}
return $meta_query;
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
}
CustomerAccount.php 0000644 00000007547 15155156421 0010415 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* CustomerAccount class.
*/
class CustomerAccount extends AbstractBlock {
const TEXT_ONLY = 'text_only';
const ICON_ONLY = 'icon_only';
const DISPLAY_ALT = 'alt';
/**
* Block name.
*
* @var string
*/
protected $block_name = 'customer-account';
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$account_link = get_option( 'woocommerce_myaccount_page_id' ) ? wc_get_account_endpoint_url( 'dashboard' ) : wp_login_url();
$allowed_svg = array(
'svg' => array(
'class' => true,
'xmlns' => true,
'width' => true,
'height' => true,
'viewbox' => true,
),
'path' => array(
'd' => true,
'fill' => true,
),
);
return "<div class='wp-block-woocommerce-customer-account " . esc_attr( $classes_and_styles['classes'] ) . "' style='" . esc_attr( $classes_and_styles['styles'] ) . "'>
<a href='" . esc_attr( $account_link ) . "'>
" . wp_kses( $this->render_icon( $attributes ), $allowed_svg ) . "<span class='label'>" . wp_kses( $this->render_label( $attributes ), array() ) . '</span>
</a>
</div>';
}
/**
* Gets the icon to render depending on the iconStyle and displayStyle.
*
* @param array $attributes Block attributes.
*
* @return string Label to render on the block
*/
private function render_icon( $attributes ) {
if ( self::TEXT_ONLY === $attributes['displayStyle'] ) {
return '';
}
if ( self::DISPLAY_ALT === $attributes['iconStyle'] ) {
return '<svg class="' . $attributes['iconClass'] . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" width="18" height="18">
<path
d="M9 0C4.03579 0 0 4.03579 0 9C0 13.9642 4.03579 18 9 18C13.9642 18 18 13.9642 18 9C18 4.03579 13.9642 0 9
0ZM9 4.32C10.5347 4.32 11.7664 5.57056 11.7664 7.08638C11.7664 8.62109 10.5158 9.85277 9 9.85277C7.4653
9.85277 6.23362 8.60221 6.23362 7.08638C6.23362 5.57056 7.46526 4.32 9 4.32ZM9 10.7242C11.1221 10.7242
12.96 12.2021 13.7937 14.4189C12.5242 15.5559 10.8379 16.238 9 16.238C7.16207 16.238 5.49474 15.5369
4.20632 14.4189C5.05891 12.2021 6.87793 10.7242 9 10.7242Z"
fill="currentColor"
/>
</svg>';
}
return '<svg class="' . $attributes['iconClass'] . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
<path
d="M8.00009 8.34785C10.3096 8.34785 12.1819 6.47909 12.1819 4.17393C12.1819 1.86876 10.3096 0 8.00009 0C5.69055
0 3.81824 1.86876 3.81824 4.17393C3.81824 6.47909 5.69055 8.34785 8.00009 8.34785ZM0.333496 15.6522C0.333496
15.8444 0.489412 16 0.681933 16H15.3184C15.5109 16 15.6668 15.8444 15.6668 15.6522V14.9565C15.6668 12.1428
13.7821 9.73911 10.0912 9.73911H5.90931C2.21828 9.73911 0.333645 12.1428 0.333645 14.9565L0.333496 15.6522Z"
fill="currentColor"
/>
</svg>';
}
/**
* Gets the label to render depending on the displayStyle.
*
* @param array $attributes Block attributes.
*
* @return string Label to render on the block.
*/
private function render_label( $attributes ) {
if ( self::ICON_ONLY === $attributes['displayStyle'] ) {
return '';
}
return get_current_user_id()
? __( 'My Account', 'woocommerce' )
: __( 'Login', 'woocommerce' );
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*
* @return null This block has no frontend script.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
}
EmptyCartBlock.php 0000644 00000000353 15155156421 0010146 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* EmptyCartBlock class.
*/
class EmptyCartBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'empty-cart-block';
}
EmptyMiniCartContentsBlock.php 0000644 00000000421 15155156421 0012475 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* EmptyMiniCartContentsBlock class.
*/
class EmptyMiniCartContentsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'empty-mini-cart-contents-block';
}
FeaturedCategory.php 0000644 00000004400 15155156421 0010515 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* FeaturedCategory class.
*/
class FeaturedCategory extends FeaturedItem {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'featured-category';
/**
* Get block attributes.
*
* @return array
*/
protected function get_block_type_attributes() {
return array_merge(
parent::get_block_type_attributes(),
array(
'textColor' => $this->get_schema_string(),
'fontSize' => $this->get_schema_string(),
'lineHeight' => $this->get_schema_string(),
'style' => array( 'type' => 'object' ),
)
);
}
/**
* Returns the featured category.
*
* @param array $attributes Block attributes. Default empty array.
* @return \WP_Term|null
*/
protected function get_item( $attributes ) {
$id = absint( $attributes['categoryId'] ?? 0 );
$category = get_term( $id, 'product_cat' );
if ( ! $category || is_wp_error( $category ) ) {
return null;
}
return $category;
}
/**
* Returns the name of the featured category.
*
* @param \WP_Term $category Featured category.
* @return string
*/
protected function get_item_title( $category ) {
return $category->name;
}
/**
* Returns the featured category image URL.
*
* @param \WP_Term $category Term object.
* @param string $size Image size, defaults to 'full'.
* @return string
*/
protected function get_item_image( $category, $size = 'full' ) {
$image = '';
$image_id = get_term_meta( $category->term_id, 'thumbnail_id', true );
if ( $image_id ) {
$image = wp_get_attachment_image_url( $image_id, $size );
}
return $image;
}
/**
* Renders the featured category attributes.
*
* @param \WP_Term $category Term object.
* @param array $attributes Block attributes. Default empty array.
* @return string
*/
protected function render_attributes( $category, $attributes ) {
$title = sprintf(
'<h2 class="wc-block-featured-category__title">%s</h2>',
wp_kses_post( $category->name )
);
$desc_str = sprintf(
'<div class="wc-block-featured-category__description">%s</div>',
wc_format_content( wp_kses_post( $category->description ) )
);
$output = $title;
if ( $attributes['showDesc'] ) {
$output .= $desc_str;
}
return $output;
}
}
FeaturedItem.php 0000644 00000022303 15155156421 0007640 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* FeaturedItem class.
*/
abstract class FeaturedItem extends AbstractDynamicBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name;
/**
* Default attribute values.
*
* @var array
*/
protected $defaults = array(
'align' => 'none',
);
/**
* Global style enabled for this block.
*
* @var array
*/
protected $global_style_wrapper = array(
'background_color',
'border_color',
'border_radius',
'border_width',
'font_size',
'padding',
'text_color',
);
/**
* Returns the featured item.
*
* @param array $attributes Block attributes. Default empty array.
* @return \WP_Term|\WC_Product|null
*/
abstract protected function get_item( $attributes );
/**
* Returns the name of the featured item.
*
* @param \WP_Term|\WC_Product $item Item object.
* @return string
*/
abstract protected function get_item_title( $item );
/**
* Returns the featured item image URL.
*
* @param \WP_Term|\WC_Product $item Item object.
* @param string $size Image size, defaults to 'full'.
* @return string
*/
abstract protected function get_item_image( $item, $size = 'full' );
/**
* Renders the featured item attributes.
*
* @param \WP_Term|\WC_Product $item Item object.
* @param array $attributes Block attributes. Default empty array.
* @return string
*/
abstract protected function render_attributes( $item, $attributes );
/**
* Render the featured item block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
$item = $this->get_item( $attributes );
if ( ! $item ) {
return '';
}
$attributes = wp_parse_args( $attributes, $this->defaults );
$attributes['height'] = $attributes['height'] ?? wc_get_theme_support( 'featured_block::default_height', 500 );
$image_url = esc_url( $this->get_image_url( $attributes, $item ) );
$styles = $this->get_styles( $attributes );
$classes = $this->get_classes( $attributes );
$output = sprintf( '<div class="%1$s wp-block-woocommerce-%2$s" style="%3$s">', esc_attr( trim( $classes ) ), $this->block_name, esc_attr( $styles ) );
$output .= sprintf( '<div class="wc-block-%s__wrapper">', $this->block_name );
$output .= $this->render_overlay( $attributes );
if ( ! $attributes['isRepeated'] && ! $attributes['hasParallax'] ) {
$output .= $this->render_image( $attributes, $item, $image_url );
} else {
$output .= $this->render_bg_image( $attributes, $image_url );
}
$output .= $this->render_attributes( $item, $attributes );
$output .= sprintf( '<div class="wc-block-%s__link">%s</div>', $this->block_name, $content );
$output .= '</div>';
$output .= '</div>';
return $output;
}
/**
* Returns the url the item's image
*
* @param array $attributes Block attributes. Default empty array.
* @param \WP_Term|\WC_Product $item Item object.
*
* @return string
*/
private function get_image_url( $attributes, $item ) {
$image_size = 'large';
if ( 'none' !== $attributes['align'] || $attributes['height'] > 800 ) {
$image_size = 'full';
}
if ( $attributes['mediaId'] ) {
return wp_get_attachment_image_url( $attributes['mediaId'], $image_size );
}
return $this->get_item_image( $item, $image_size );
}
/**
* Renders the featured image as a div background.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $image_url Item image url.
*
* @return string
*/
private function render_bg_image( $attributes, $image_url ) {
$styles = $this->get_bg_styles( $attributes, $image_url );
$classes = [ "wc-block-{$this->block_name}__background-image" ];
if ( $attributes['hasParallax'] ) {
$classes[] = ' has-parallax';
}
return sprintf( '<div class="%1$s" style="%2$s" /></div>', esc_attr( implode( ' ', $classes ) ), esc_attr( $styles ) );
}
/**
* Get the styles for the wrapper element (background image, color).
*
* @param array $attributes Block attributes. Default empty array.
* @param string $image_url Item image url.
*
* @return string
*/
public function get_bg_styles( $attributes, $image_url ) {
$style = '';
if ( $attributes['isRepeated'] || $attributes['hasParallax'] ) {
$style .= "background-image: url($image_url);";
}
if ( ! $attributes['isRepeated'] ) {
$style .= 'background-repeat: no-repeat;';
$bg_size = 'cover' === $attributes['imageFit'] ? $attributes['imageFit'] : 'auto';
$style .= 'background-size: ' . $bg_size . ';';
}
if ( $this->hasFocalPoint( $attributes ) ) {
$style .= sprintf(
'background-position: %s%% %s%%;',
$attributes['focalPoint']['x'] * 100,
$attributes['focalPoint']['y'] * 100
);
}
$global_style_style = StyleAttributesUtils::get_styles_by_attributes( $attributes, $this->global_style_wrapper );
$style .= $global_style_style;
return $style;
}
/**
* Renders the featured image
*
* @param array $attributes Block attributes. Default empty array.
* @param \WC_Product|\WP_Term $item Item object.
* @param string $image_url Item image url.
*
* @return string
*/
private function render_image( $attributes, $item, string $image_url ) {
$style = sprintf( 'object-fit: %s;', esc_attr( $attributes['imageFit'] ) );
$img_alt = $attributes['alt'] ?: $this->get_item_title( $item );
if ( $this->hasFocalPoint( $attributes ) ) {
$style .= sprintf(
'object-position: %s%% %s%%;',
$attributes['focalPoint']['x'] * 100,
$attributes['focalPoint']['y'] * 100
);
}
if ( ! empty( $image_url ) ) {
return sprintf(
'<img alt="%1$s" class="wc-block-%2$s__background-image" src="%3$s" style="%4$s" />',
esc_attr( $img_alt ),
$this->block_name,
esc_url( $image_url ),
esc_attr( $style )
);
}
return '';
}
/**
* Get the styles for the wrapper element (background image, color).
*
* @param array $attributes Block attributes. Default empty array.
* @return string
*/
public function get_styles( $attributes ) {
$style = '';
$min_height = $attributes['minHeight'] ?? wc_get_theme_support( 'featured_block::default_height', 500 );
if ( isset( $attributes['minHeight'] ) ) {
$style .= sprintf( 'min-height:%dpx;', intval( $min_height ) );
}
$global_style_style = StyleAttributesUtils::get_styles_by_attributes( $attributes, $this->global_style_wrapper );
$style .= $global_style_style;
return $style;
}
/**
* Get class names for the block container.
*
* @param array $attributes Block attributes. Default empty array.
* @return string
*/
public function get_classes( $attributes ) {
$classes = array( 'wc-block-' . $this->block_name );
if ( isset( $attributes['align'] ) ) {
$classes[] = "align{$attributes['align']}";
}
if ( isset( $attributes['dimRatio'] ) && ( 0 !== $attributes['dimRatio'] ) ) {
$classes[] = 'has-background-dim';
if ( 50 !== $attributes['dimRatio'] ) {
$classes[] = 'has-background-dim-' . 10 * round( $attributes['dimRatio'] / 10 );
}
}
if ( isset( $attributes['contentAlign'] ) && 'center' !== $attributes['contentAlign'] ) {
$classes[] = "has-{$attributes['contentAlign']}-content";
}
if ( isset( $attributes['className'] ) ) {
$classes[] = $attributes['className'];
}
$global_style_classes = StyleAttributesUtils::get_classes_by_attributes( $attributes, $this->global_style_wrapper );
$classes[] = $global_style_classes;
return implode( ' ', $classes );
}
/**
* Renders the block overlay
*
* @param array $attributes Block attributes. Default empty array.
*
* @return string
*/
private function render_overlay( $attributes ) {
if ( isset( $attributes['overlayGradient'] ) ) {
$overlay_styles = sprintf( 'background-image: %s', $attributes['overlayGradient'] );
} elseif ( isset( $attributes['overlayColor'] ) ) {
$overlay_styles = sprintf( 'background-color: %s', $attributes['overlayColor'] );
} else {
$overlay_styles = 'background-color: #000000';
}
return sprintf( '<div class="background-dim__overlay" style="%s"></div>', esc_attr( $overlay_styles ) );
}
/**
* Returns whether the focal point is defined for the block.
*
* @param array $attributes Block attributes. Default empty array.
*
* @return bool
*/
private function hasFocalPoint( $attributes ): bool {
return is_array( $attributes['focalPoint'] ) && 2 === count( $attributes['focalPoint'] );
}
/**
* 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( 'defaultHeight', wc_get_theme_support( 'featured_block::default_height', 500 ), true );
}
}
FeaturedProduct.php 0000644 00000005043 15155156421 0010364 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* FeaturedProduct class.
*/
class FeaturedProduct extends FeaturedItem {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'featured-product';
/**
* Returns the featured product.
*
* @param array $attributes Block attributes. Default empty array.
* @return \WP_Term|null
*/
protected function get_item( $attributes ) {
$id = absint( $attributes['productId'] ?? 0 );
$product = wc_get_product( $id );
if ( ! $product ) {
return null;
}
return $product;
}
/**
* Returns the name of the featured product.
*
* @param \WC_Product $product Product object.
* @return string
*/
protected function get_item_title( $product ) {
return $product->get_title();
}
/**
* Returns the featured product image URL.
*
* @param \WC_Product $product Product object.
* @param string $size Image size, defaults to 'full'.
* @return string
*/
protected function get_item_image( $product, $size = 'full' ) {
$image = '';
if ( $product->get_image_id() ) {
$image = wp_get_attachment_image_url( $product->get_image_id(), $size );
} elseif ( $product->get_parent_id() ) {
$parent_product = wc_get_product( $product->get_parent_id() );
if ( $parent_product ) {
$image = wp_get_attachment_image_url( $parent_product->get_image_id(), $size );
}
}
return $image;
}
/**
* Renders the featured product attributes.
*
* @param \WC_Product $product Product object.
* @param array $attributes Block attributes. Default empty array.
* @return string
*/
protected function render_attributes( $product, $attributes ) {
$title = sprintf(
'<h2 class="wc-block-featured-product__title">%s</h2>',
wp_kses_post( $product->get_title() )
);
if ( $product->is_type( 'variation' ) ) {
$title .= sprintf(
'<h3 class="wc-block-featured-product__variation">%s</h3>',
wp_kses_post( wc_get_formatted_variation( $product, true, true, false ) )
);
}
$desc_str = sprintf(
'<div class="wc-block-featured-product__description">%s</div>',
wc_format_content( wp_kses_post( $product->get_short_description() ? $product->get_short_description() : wc_trim_string( $product->get_description(), 400 ) ) )
);
$price_str = sprintf(
'<div class="wc-block-featured-product__price">%s</div>',
wp_kses_post( $product->get_price_html() )
);
$output = $title;
if ( $attributes['showDesc'] ) {
$output .= $desc_str;
}
if ( $attributes['showPrice'] ) {
$output .= $price_str;
}
return $output;
}
}
FilledCartBlock.php 0000644 00000000356 15155156421 0010252 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* FilledCartBlock class.
*/
class FilledCartBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'filled-cart-block';
}
FilledMiniCartContentsBlock.php 0000644 00000000424 15155156421 0012601 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* FilledMiniCartContentsBlock class.
*/
class FilledMiniCartContentsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'filled-mini-cart-contents-block';
}
FilterWrapper.php 0000644 00000000572 15155156421 0010054 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* FilledCartBlock class.
*/
class FilterWrapper extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'filter-wrapper';
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
}
HandpickedProducts.php 0000644 00000003504 15155156421 0011042 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* HandpickedProducts class.
*/
class HandpickedProducts extends AbstractProductGrid {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'handpicked-products';
/**
* Set args specific to this block
*
* @param array $query_args Query args.
*/
protected function set_block_query_args( &$query_args ) {
$ids = array_map( 'absint', $this->attributes['products'] );
$query_args['post__in'] = $ids;
$query_args['posts_per_page'] = count( $ids ); // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
}
/**
* Set visibility query args. Handpicked products will show hidden products if chosen.
*
* @param array $query_args Query args.
*/
protected function set_visibility_query_args( &$query_args ) {
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
$product_visibility_terms = wc_get_product_visibility_term_ids();
$query_args['tax_query'][] = array(
'taxonomy' => 'product_visibility',
'field' => 'term_taxonomy_id',
'terms' => array( $product_visibility_terms['outofstock'] ),
'operator' => 'NOT IN',
);
}
}
/**
* Get block attributes.
*
* @return array
*/
protected function get_block_type_attributes() {
return array(
'align' => $this->get_schema_align(),
'alignButtons' => $this->get_schema_boolean( false ),
'className' => $this->get_schema_string(),
'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
'orderby' => $this->get_schema_orderby(),
'products' => $this->get_schema_list_ids(),
'contentVisibility' => $this->get_schema_content_visibility(),
'isPreview' => $this->get_schema_boolean( false ),
);
}
}
MiniCart.php 0000644 00000054164 15155156421 0007002 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
use Automattic\WooCommerce\Blocks\Utils\Utils;
use Automattic\WooCommerce\Blocks\Utils\MiniCartUtils;
/**
* Mini-Cart class.
*
* @internal
*/
class MiniCart extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart';
/**
* Chunks build folder.
*
* @var string
*/
protected $chunks_folder = 'mini-cart-contents-block';
/**
* Array of scripts that will be lazy loaded when interacting with the block.
*
* @var string[]
*/
protected $scripts_to_lazy_load = array();
/**
* Inc Tax label.
*
* @var string
*/
protected $tax_label = '';
/**
* Visibility of price including tax.
*
* @var string
*/
protected $display_cart_prices_including_tax = false;
/**
* Constructor.
*
* @param AssetApi $asset_api Instance of the asset API.
* @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
* @param IntegrationRegistry $integration_registry Instance of the integration registry.
*/
public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry, IntegrationRegistry $integration_registry ) {
parent::__construct( $asset_api, $asset_data_registry, $integration_registry, $this->block_name );
}
/**
* Initialize this block type.
*
* - Hook into WP lifecycle.
* - Register the block with WordPress.
*/
protected function initialize() {
parent::initialize();
add_action( 'wp_loaded', array( $this, 'register_empty_cart_message_block_pattern' ) );
add_action( 'wp_print_footer_scripts', array( $this, 'print_lazy_load_scripts' ), 2 );
}
/**
* Get the editor script handle for this block type.
*
* @param string $key Data to get, or default to everything.
* @return array|string;
*/
protected function get_block_type_editor_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-block',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
'dependencies' => [ 'wc-blocks' ],
];
return $key ? $script[ $key ] : $script;
}
/**
* Get the frontend script handle for this block type.
*
* @see $this->register_block_type()
* @param string $key Data to get, or default to everything.
* @return array|string
*/
protected function get_block_type_script( $key = null ) {
if ( is_cart() || is_checkout() ) {
return;
}
$script = [
'handle' => 'wc-' . $this->block_name . '-block-frontend',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
'dependencies' => [],
];
return $key ? $script[ $key ] : $script;
}
/**
* Get the frontend style handle for this block type.
*
* @return string[]
*/
protected function get_block_type_style() {
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
}
/**
* 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 = [] ) {
if ( is_cart() || is_checkout() ) {
return;
}
parent::enqueue_data( $attributes );
// Hydrate the following data depending on admin or frontend context.
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
$label_info = $this->get_tax_label();
$this->tax_label = $label_info['tax_label'];
$this->display_cart_prices_including_tax = $label_info['display_cart_prices_including_tax'];
$this->asset_data_registry->add(
'taxLabel',
$this->tax_label,
''
);
}
$this->asset_data_registry->add(
'displayCartPricesIncludingTax',
$this->display_cart_prices_including_tax,
true
);
$template_part_edit_uri = '';
if (
current_user_can( 'edit_theme_options' ) &&
( wc_current_theme_is_fse_theme() || current_theme_supports( 'block-template-parts' ) )
) {
$theme_slug = BlockTemplateUtils::theme_has_template_part( 'mini-cart' ) ? wp_get_theme()->get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;
if ( version_compare( get_bloginfo( 'version' ), '5.9', '<' ) ) {
$site_editor_uri = add_query_arg(
array( 'page' => 'gutenberg-edit-site' ),
admin_url( 'themes.php' )
);
} else {
$site_editor_uri = add_query_arg(
array(
'canvas' => 'edit',
'path' => '/template-parts/single',
),
admin_url( 'site-editor.php' )
);
}
$template_part_edit_uri = esc_url_raw(
add_query_arg(
array(
'postId' => sprintf( '%s//%s', $theme_slug, 'mini-cart' ),
'postType' => 'wp_template_part',
),
$site_editor_uri
)
);
}
$this->asset_data_registry->add(
'templatePartEditUri',
$template_part_edit_uri,
''
);
/**
* Fires after cart block data is registered.
*
* @since 5.8.0
*/
do_action( 'woocommerce_blocks_cart_enqueue_data' );
}
/**
* Prints the variable containing information about the scripts to lazy load.
*/
public function print_lazy_load_scripts() {
$script_data = $this->asset_api->get_script_data( 'build/mini-cart-component-frontend.js' );
$num_dependencies = count( $script_data['dependencies'] );
$wp_scripts = wp_scripts();
for ( $i = 0; $i < $num_dependencies; $i++ ) {
$dependency = $script_data['dependencies'][ $i ];
foreach ( $wp_scripts->registered as $script ) {
if ( $script->handle === $dependency ) {
$this->append_script_and_deps_src( $script );
break;
}
}
}
$payment_method_registry = Package::container()->get( PaymentMethodRegistry::class );
$payment_methods = $payment_method_registry->get_all_active_payment_method_script_dependencies();
foreach ( $payment_methods as $payment_method ) {
$payment_method_script = $this->get_script_from_handle( $payment_method );
if ( ! is_null( $payment_method_script ) ) {
$this->append_script_and_deps_src( $payment_method_script );
}
}
$this->scripts_to_lazy_load['wc-block-mini-cart-component-frontend'] = array(
'src' => $script_data['src'],
'version' => $script_data['version'],
'translations' => $this->get_inner_blocks_translations(),
);
$inner_blocks_frontend_scripts = array();
$cart = $this->get_cart_instance();
if ( $cart ) {
// Preload inner blocks frontend scripts.
$inner_blocks_frontend_scripts = $cart->is_empty() ? array(
'empty-cart-frontend',
'filled-cart-frontend',
'shopping-button-frontend',
) : array(
'empty-cart-frontend',
'filled-cart-frontend',
'title-frontend',
'items-frontend',
'footer-frontend',
'products-table-frontend',
'cart-button-frontend',
'checkout-button-frontend',
'title-label-frontend',
'title-items-counter-frontend',
);
}
foreach ( $inner_blocks_frontend_scripts as $inner_block_frontend_script ) {
$script_data = $this->asset_api->get_script_data( 'build/mini-cart-contents-block/' . $inner_block_frontend_script . '.js' );
$this->scripts_to_lazy_load[ 'wc-block-' . $inner_block_frontend_script ] = array(
'src' => $script_data['src'],
'version' => $script_data['version'],
);
}
$data = rawurlencode( wp_json_encode( $this->scripts_to_lazy_load ) );
$mini_cart_dependencies_script = "var wcBlocksMiniCartFrontendDependencies = JSON.parse( decodeURIComponent( '" . esc_js( $data ) . "' ) );";
wp_add_inline_script(
'wc-mini-cart-block-frontend',
$mini_cart_dependencies_script,
'before'
);
}
/**
* Returns the script data given its handle.
*
* @param string $handle Handle of the script.
*
* @return \_WP_Dependency|null Object containing the script data if found, or null.
*/
protected function get_script_from_handle( $handle ) {
$wp_scripts = wp_scripts();
foreach ( $wp_scripts->registered as $script ) {
if ( $script->handle === $handle ) {
return $script;
}
}
return null;
}
/**
* Recursively appends a scripts and its dependencies into the scripts_to_lazy_load array.
*
* @param \_WP_Dependency $script Object containing script data.
*/
protected function append_script_and_deps_src( $script ) {
$wp_scripts = wp_scripts();
// This script and its dependencies have already been appended.
if ( ! $script || array_key_exists( $script->handle, $this->scripts_to_lazy_load ) || wp_script_is( $script->handle, 'enqueued' ) ) {
return;
}
if ( count( $script->deps ) ) {
foreach ( $script->deps as $dep ) {
if ( ! array_key_exists( $dep, $this->scripts_to_lazy_load ) ) {
$dep_script = $this->get_script_from_handle( $dep );
if ( ! is_null( $dep_script ) ) {
$this->append_script_and_deps_src( $dep_script );
}
}
}
}
if ( ! $script->src ) {
return;
}
$site_url = site_url() ?? wp_guess_url();
if ( Utils::wp_version_compare( '6.3', '>=' ) ) {
$script_before = $wp_scripts->get_inline_script_data( $script->handle, 'before' );
$script_after = $wp_scripts->get_inline_script_data( $script->handle, 'after' );
} else {
$script_before = $wp_scripts->print_inline_script( $script->handle, 'before', false );
$script_after = $wp_scripts->print_inline_script( $script->handle, 'after', false );
}
$this->scripts_to_lazy_load[ $script->handle ] = array(
'src' => preg_match( '|^(https?:)?//|', $script->src ) ? $script->src : $site_url . $script->src,
'version' => $script->ver,
'before' => $script_before,
'after' => $script_after,
'translations' => $wp_scripts->print_translations( $script->handle, false ),
);
}
/**
* Returns the markup for the cart price.
*
* @param array $attributes Block attributes.
*
* @return string
*/
protected function get_cart_price_markup( $attributes ) {
if ( isset( $attributes['hasHiddenPrice'] ) && false !== $attributes['hasHiddenPrice'] ) {
return;
}
$price_color = array_key_exists( 'priceColor', $attributes ) ? $attributes['priceColor']['color'] : '';
return '<span class="wc-block-mini-cart__amount" style="color:' . esc_attr( $price_color ) . ' "></span>' . $this->get_include_tax_label_markup( $attributes );
}
/**
* Returns the markup for render the tax label.
*
* @param array $attributes Block attributes.
*
* @return string
*/
protected function get_include_tax_label_markup( $attributes ) {
if ( empty( $this->tax_label ) ) {
return '';
}
$price_color = array_key_exists( 'priceColor', $attributes ) ? $attributes['priceColor']['color'] : '';
return '<small class="wc-block-mini-cart__tax-label" style="color:' . esc_attr( $price_color ) . ' " hidden>' . esc_html( $this->tax_label ) . '</small>';
}
/**
* Append frontend scripts when rendering the Mini-Cart block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
return $content . $this->get_markup( MiniCartUtils::migrate_attributes_to_color_panel( $attributes ) );
}
/**
* Render the markup for the Mini-Cart block.
*
* @param array $attributes Block attributes.
*
* @return string The HTML markup.
*/
protected function get_markup( $attributes ) {
if ( is_admin() || WC()->is_rest_api_request() ) {
// In the editor we will display the placeholder, so no need to load
// real cart data and to print the markup.
return '';
}
$classes_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array( 'text_color', 'background_color', 'font_size', 'font_weight', 'font_family' ) );
$wrapper_classes = sprintf( 'wc-block-mini-cart wp-block-woocommerce-mini-cart %s', $classes_styles['classes'] );
if ( ! empty( $attributes['className'] ) ) {
$wrapper_classes .= ' ' . $attributes['className'];
}
$wrapper_styles = $classes_styles['styles'];
$icon_color = array_key_exists( 'iconColor', $attributes ) ? esc_attr( $attributes['iconColor']['color'] ) : 'currentColor';
$product_count_color = array_key_exists( 'productCountColor', $attributes ) ? esc_attr( $attributes['productCountColor']['color'] ) : '';
// Default "Cart" icon.
$icon = '<svg class="wc-block-mini-cart__icon" width="32" height="32" viewBox="0 0 32 32" fill="' . $icon_color . '" xmlns="http://www.w3.org/2000/svg">
<circle cx="12.6667" cy="24.6667" r="2" fill="' . $icon_color . '"/>
<circle cx="23.3333" cy="24.6667" r="2" fill="' . $icon_color . '"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.28491 10.0356C9.47481 9.80216 9.75971 9.66667 10.0606 9.66667H25.3333C25.6232 9.66667 25.8989 9.79247 26.0888 10.0115C26.2787 10.2305 26.3643 10.5211 26.3233 10.8081L24.99 20.1414C24.9196 20.6341 24.4977 21 24 21H12C11.5261 21 11.1173 20.6674 11.0209 20.2034L9.08153 10.8701C9.02031 10.5755 9.09501 10.269 9.28491 10.0356ZM11.2898 11.6667L12.8136 19H23.1327L24.1803 11.6667H11.2898Z" fill="' . $icon_color . '"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.66669 6.66667C5.66669 6.11438 6.1144 5.66667 6.66669 5.66667H9.33335C9.81664 5.66667 10.2308 6.01229 10.3172 6.48778L11.0445 10.4878C11.1433 11.0312 10.7829 11.5517 10.2395 11.6505C9.69614 11.7493 9.17555 11.3889 9.07676 10.8456L8.49878 7.66667H6.66669C6.1144 7.66667 5.66669 7.21895 5.66669 6.66667Z" fill="' . $icon_color . '"/>
</svg>';
if ( isset( $attributes['miniCartIcon'] ) ) {
if ( 'bag' === $attributes['miniCartIcon'] ) {
$icon = '<svg class="wc-block-mini-cart__icon" width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.4444 14.2222C12.9354 14.2222 13.3333 14.6202 13.3333 15.1111C13.3333 15.8183 13.6143 16.4966 14.1144 16.9967C14.6145 17.4968 15.2927 17.7778 16 17.7778C16.7072 17.7778 17.3855 17.4968 17.8856 16.9967C18.3857 16.4966 18.6667 15.8183 18.6667 15.1111C18.6667 14.6202 19.0646 14.2222 19.5555 14.2222C20.0465 14.2222 20.4444 14.6202 20.4444 15.1111C20.4444 16.2898 19.9762 17.4203 19.1427 18.2538C18.3092 19.0873 17.1787 19.5555 16 19.5555C14.8212 19.5555 13.6908 19.0873 12.8573 18.2538C12.0238 17.4203 11.5555 16.2898 11.5555 15.1111C11.5555 14.6202 11.9535 14.2222 12.4444 14.2222Z" fill="' . $icon_color . '""/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2408 6.68254C11.4307 6.46089 11.7081 6.33333 12 6.33333H20C20.2919 6.33333 20.5693 6.46089 20.7593 6.68254L24.7593 11.3492C25.0134 11.6457 25.0717 12.0631 24.9085 12.4179C24.7453 12.7727 24.3905 13 24 13H8.00001C7.60948 13 7.25469 12.7727 7.0915 12.4179C6.92832 12.0631 6.9866 11.6457 7.24076 11.3492L11.2408 6.68254ZM12.4599 8.33333L10.1742 11H21.8258L19.5401 8.33333H12.4599Z" fill="' . $icon_color . '"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 12C7 11.4477 7.44772 11 8 11H24C24.5523 11 25 11.4477 25 12V25.3333C25 25.8856 24.5523 26.3333 24 26.3333H8C7.44772 26.3333 7 25.8856 7 25.3333V12ZM9 13V24.3333H23V13H9Z" fill="' . $icon_color . '"/>
</svg>';
} elseif ( 'bag-alt' === $attributes['miniCartIcon'] ) {
$icon = '<svg class="wc-block-mini-cart__icon" width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.5556 12.3333C19.0646 12.3333 18.6667 11.9354 18.6667 11.4444C18.6667 10.7372 18.3857 8.05893 17.8856 7.55883C17.3855 7.05873 16.7073 6.77778 16 6.77778C15.2928 6.77778 14.6145 7.05873 14.1144 7.55883C13.6143 8.05893 13.3333 10.7372 13.3333 11.4444C13.3333 11.9354 12.9354 12.3333 12.4445 12.3333C11.9535 12.3333 11.5556 11.9354 11.5556 11.4444C11.5556 10.2657 12.0238 7.13524 12.8573 6.30175C13.6908 5.46825 14.8213 5 16 5C17.1788 5 18.3092 5.46825 19.1427 6.30175C19.9762 7.13524 20.4445 10.2657 20.4445 11.4444C20.4445 11.9354 20.0465 12.3333 19.5556 12.3333Z" fill="' . $icon_color . '"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 12C7.5 11.4477 7.94772 11 8.5 11H23.5C24.0523 11 24.5 11.4477 24.5 12V25.3333C24.5 25.8856 24.0523 26.3333 23.5 26.3333H8.5C7.94772 26.3333 7.5 25.8856 7.5 25.3333V12ZM9.5 13V24.3333H22.5V13H9.5Z" fill="' . $icon_color . '" />
</svg>';
}
}
$button_html = $this->get_cart_price_markup( $attributes ) . '
<span class="wc-block-mini-cart__quantity-badge">
' . $icon . '
<span class="wc-block-mini-cart__badge" style="background:' . $product_count_color . '"></span>
</span>';
if ( is_cart() || is_checkout() ) {
if ( $this->should_not_render_mini_cart( $attributes ) ) {
return '';
}
// It is not necessary to load the Mini-Cart Block on Cart and Checkout page.
return '<div class="' . esc_attr( $wrapper_classes ) . '" style="visibility:hidden" aria-hidden="true">
<button class="wc-block-mini-cart__button" disabled>' . $button_html . '</button>
</div>';
}
$template_part_contents = '';
// Determine if we need to load the template part from the DB, the theme or WooCommerce in that order.
$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'mini-cart' ), 'wp_template_part' );
if ( count( $templates_from_db ) > 0 ) {
$template_slug_to_load = $templates_from_db[0]->theme;
} else {
$theme_has_mini_cart = BlockTemplateUtils::theme_has_template_part( 'mini-cart' );
$template_slug_to_load = $theme_has_mini_cart ? get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;
}
$template_part = BlockTemplateUtils::get_block_template( $template_slug_to_load . '//mini-cart', 'wp_template_part' );
if ( $template_part && ! empty( $template_part->content ) ) {
$template_part_contents = do_blocks( $template_part->content );
}
if ( '' === $template_part_contents ) {
$template_part_contents = do_blocks(
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
file_get_contents( Package::get_path() . 'templates/' . BlockTemplateUtils::DIRECTORY_NAMES['TEMPLATE_PARTS'] . '/mini-cart.html' )
);
}
return '<div class="' . esc_attr( $wrapper_classes ) . '" style="' . esc_attr( $wrapper_styles ) . '">
<button class="wc-block-mini-cart__button">' . $button_html . '</button>
<div class="is-loading wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true">
<div class="wc-block-mini-cart__drawer wc-block-components-drawer">
<div class="wc-block-components-drawer__content">
<div class="wc-block-mini-cart__template-part">'
. wp_kses_post( $template_part_contents ) .
'</div>
</div>
</div>
</div>
</div>';
}
/**
* Return the main instance of WC_Cart class.
*
* @return \WC_Cart CartController class instance.
*/
protected function get_cart_instance() {
$cart = WC()->cart;
if ( $cart && $cart instanceof \WC_Cart ) {
return $cart;
}
return null;
}
/**
* Get array with data for handle the tax label.
* the entire logic of this function is was taken from:
* https://github.com/woocommerce/woocommerce/blob/e730f7463c25b50258e97bf56e31e9d7d3bc7ae7/includes/class-wc-cart.php#L1582
*
* @return array;
*/
protected function get_tax_label() {
$cart = $this->get_cart_instance();
if ( $cart && $cart->display_prices_including_tax() ) {
if ( ! wc_prices_include_tax() ) {
$tax_label = WC()->countries->inc_tax_or_vat();
$display_cart_prices_including_tax = true;
return array(
'tax_label' => $tax_label,
'display_cart_prices_including_tax' => $display_cart_prices_including_tax,
);
}
return array(
'tax_label' => '',
'display_cart_prices_including_tax' => true,
);
}
if ( wc_prices_include_tax() ) {
$tax_label = WC()->countries->ex_tax_or_vat();
return array(
'tax_label' => $tax_label,
'display_cart_prices_including_tax' => false,
);
};
return array(
'tax_label' => '',
'display_cart_prices_including_tax' => false,
);
}
/**
* Prepare translations for inner blocks and dependencies.
*/
protected function get_inner_blocks_translations() {
$wp_scripts = wp_scripts();
$translations = array();
$chunks = $this->get_chunks_paths( $this->chunks_folder );
$vendor_chunks = $this->get_chunks_paths( 'vendors--mini-cart-contents-block' );
$shared_chunks = [ 'cart-blocks/cart-line-items--mini-cart-contents-block/products-table-frontend' ];
foreach ( array_merge( $chunks, $vendor_chunks, $shared_chunks ) as $chunk ) {
$handle = 'wc-blocks-' . $chunk . '-chunk';
$this->asset_api->register_script( $handle, $this->asset_api->get_block_asset_build_path( $chunk ), [], true );
$translations[] = $wp_scripts->print_translations( $handle, false );
wp_deregister_script( $handle );
}
$translations = array_filter( $translations );
return implode( '', $translations );
}
/**
* Register block pattern for Empty Cart Message to make it translatable.
*/
public function register_empty_cart_message_block_pattern() {
register_block_pattern(
'woocommerce/mini-cart-empty-cart-message',
array(
'title' => __( 'Empty Mini-Cart Message', 'woocommerce' ),
'inserter' => false,
'content' => '<!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center"><strong>' . __( 'Your cart is currently empty!', 'woocommerce' ) . '</strong></p><!-- /wp:paragraph -->',
)
);
}
/**
* Returns whether the Mini-Cart should be rendered or not.
*
* @param array $attributes Block attributes.
*
* @return bool
*/
public function should_not_render_mini_cart( array $attributes ) {
return isset( $attributes['cartAndCheckoutRenderStyle'] ) && 'hidden' !== $attributes['cartAndCheckoutRenderStyle'];
}
}
MiniCartCartButtonBlock.php 0000644 00000000410 15155156421 0011744 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* MiniCartCartButtonBlock class.
*/
class MiniCartCartButtonBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-cart-button-block';
}
MiniCartCheckoutButtonBlock.php 0000644 00000000424 15155156421 0012625 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* MiniCartCheckoutButtonBlock class.
*/
class MiniCartCheckoutButtonBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-checkout-button-block';
}
MiniCartContents.php 0000644 00000012034 15155156421 0010506 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* Mini-Cart Contents class.
*
* @internal
*/
class MiniCartContents extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-contents';
/**
* Get the editor script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*
* @return array|string;
*/
protected function get_block_type_editor_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-block',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
'dependencies' => [ 'wc-blocks' ],
];
return $key ? $script[ $key ] : $script;
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*
* @return null
*/
protected function get_block_type_script( $key = null ) {
// The frontend script is a dependency of the Mini-Cart block so it's
// already lazy-loaded.
return null;
}
/**
* Get the frontend style handle for this block type.
*
* @return string[]
*/
protected function get_block_type_style() {
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
}
/**
* Render the markup for the Mini-Cart Contents block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( is_admin() || WC()->is_rest_api_request() ) {
// In the editor we will display the placeholder, so no need to
// print the markup.
return '';
}
return $content;
}
/**
* Enqueue frontend assets for this block, just in time for rendering.
*
* @param array $attributes Any attributes that currently are available from the block.
* @param string $content The block content.
* @param WP_Block $block The block object.
*/
protected function enqueue_assets( array $attributes, $content, $block ) {
parent::enqueue_assets( $attributes, $content, $block );
$text_color = StyleAttributesUtils::get_text_color_class_and_style( $attributes );
$bg_color = StyleAttributesUtils::get_background_color_class_and_style( $attributes );
$styles = array(
array(
'selector' => array(
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout',
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout:hover',
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout:focus',
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-cart.wc-block-components-button:hover',
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-cart.wc-block-components-button:focus',
'.wc-block-mini-cart__shopping-button a:hover',
'.wc-block-mini-cart__shopping-button a:focus',
),
'properties' => array(
array(
'property' => 'color',
'value' => $bg_color ? $bg_color['value'] : false,
),
array(
'property' => 'border-color',
'value' => $text_color ? $text_color['value'] : false,
),
array(
'property' => 'background-color',
'value' => $text_color ? $text_color['value'] : false,
),
),
),
);
$parsed_style = '';
if ( array_key_exists( 'width', $attributes ) ) {
$parsed_style .= ':root{--drawer-width: ' . esc_html( $attributes['width'] ) . '}';
}
foreach ( $styles as $style ) {
$selector = is_array( $style['selector'] ) ? implode( ',', $style['selector'] ) : $style['selector'];
$properties = array_filter(
$style['properties'],
function( $property ) {
return $property['value'];
}
);
if ( ! empty( $properties ) ) {
$parsed_style .= $selector . '{';
foreach ( $properties as $property ) {
$parsed_style .= sprintf( '%1$s:%2$s;', $property['property'], $property['value'] );
}
$parsed_style .= '}';
}
}
wp_add_inline_style(
'wc-blocks-style',
$parsed_style
);
}
/**
* Get list of Mini-Cart Contents block & its inner-block types.
*
* @return array;
*/
public static function get_mini_cart_block_types() {
$block_types = [];
$block_types[] = 'MiniCartContents';
$block_types[] = 'EmptyMiniCartContentsBlock';
$block_types[] = 'FilledMiniCartContentsBlock';
$block_types[] = 'MiniCartFooterBlock';
$block_types[] = 'MiniCartItemsBlock';
$block_types[] = 'MiniCartProductsTableBlock';
$block_types[] = 'MiniCartShoppingButtonBlock';
$block_types[] = 'MiniCartCartButtonBlock';
$block_types[] = 'MiniCartCheckoutButtonBlock';
$block_types[] = 'MiniCartTitleBlock';
$block_types[] = 'MiniCartTitleItemsCounterBlock';
$block_types[] = 'MiniCartTitleLabelBlock';
return $block_types;
}
}
MiniCartFooterBlock.php 0000644 00000000373 15155156421 0011125 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* MiniCartFooterBlock class.
*/
class MiniCartFooterBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-footer-block';
}
MiniCartItemsBlock.php 0000644 00000000370 15155156421 0010745 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* MiniCartItemsBlock class.
*/
class MiniCartItemsBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-items-block';
}
MiniCartProductsTableBlock.php 0000644 00000000421 15155156421 0012434 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* MiniCartProductsTableBlock class.
*/
class MiniCartProductsTableBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-products-table-block';
}
MiniCartShoppingButtonBlock.php 0000644 00000000424 15155156421 0012647 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* MiniCartShoppingButtonBlock class.
*/
class MiniCartShoppingButtonBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-shopping-button-block';
}
MiniCartTitleBlock.php 0000644 00000000370 15155156421 0010745 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* MiniCartTitleBlock class.
*/
class MiniCartTitleBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-title-block';
}
MiniCartTitleItemsCounterBlock.php 0000644 00000000436 15155156421 0013312 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* MiniCartTitleItemsCounterBlock class.
*/
class MiniCartTitleItemsCounterBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-title-items-counter-block';
}
MiniCartTitleLabelBlock.php 0000644 00000000410 15155156421 0011700 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* MiniCartTitleLabelBlock class.
*/
class MiniCartTitleLabelBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-title-label-block';
}
PriceFilter.php 0000644 00000000465 15155156421 0007477 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* PriceFilter class.
*/
class PriceFilter extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'price-filter';
const MIN_PRICE_QUERY_VAR = 'min_price';
const MAX_PRICE_QUERY_VAR = 'max_price';
}
ProceedToCheckoutBlock.php 0000644 00000001417 15155156421 0011612 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProceedToCheckoutBlock class.
*/
class ProceedToCheckoutBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'proceed-to-checkout-block';
/**
* 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 = [] ) {
$this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 );
}
}
ProductAddToCart.php 0000644 00000001171 15155156421 0010430 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductAddToCart class.
*/
class ProductAddToCart extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-add-to-cart';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Register script and style assets for the block type before it is registered.
*
* This registers the scripts; it does not enqueue them.
*/
protected function register_block_type_assets() {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
}
}
ProductAverageRating.php 0000644 00000005326 15155156421 0011350 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductAverageRating class.
*/
class ProductAverageRating extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-average-rating';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Get block supports. Shared with the frontend.
* IMPORTANT: If you change anything here, make sure to update the JS file too.
*
* @return array
*/
protected function get_block_type_supports() {
return array(
'color' =>
array(
'text' => true,
'background' => true,
'__experimentalSkipSerialization' => true,
),
'spacing' =>
array(
'margin' => true,
'padding' => true,
'__experimentalSkipSerialization' => true,
),
'typography' =>
array(
'fontSize' => true,
'__experimentalFontWeight' => true,
'__experimentalSkipSerialization' => true,
),
'__experimentalSelector' => '.wc-block-components-product-average-rating',
);
}
/**
* Overwrite parent method to prevent script registration.
*
* It is necessary to register and enqueues assets during the render
* phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
$post_id = $block->context['postId'];
$product = wc_get_product( $post_id );
if ( ! $product || ! $product->get_review_count() ) {
return '';
}
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );
return sprintf(
'<div class="wc-block-components-product-average-rating-counter %1$s %2$s" style="%3$s">%4$s</div>',
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
esc_attr( $styles_and_classes['classes'] ),
esc_attr( $styles_and_classes['styles'] ?? '' ),
$product->get_average_rating()
);
}
}
ProductBestSellers.php 0000644 00000000674 15155156421 0011061 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductBestSellers class.
*/
class ProductBestSellers extends AbstractProductGrid {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-best-sellers';
/**
* Set args specific to this block
*
* @param array $query_args Query args.
*/
protected function set_block_query_args( &$query_args ) {
$query_args['orderby'] = 'popularity';
}
}
ProductButton.php 0000644 00000021523 15155156421 0010101 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductButton class.
*/
class ProductButton extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-button';
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-interactivity-frontend',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-interactivity-frontend' ),
'dependencies' => [ 'wc-interactivity' ],
];
return $key ? $script[ $key ] : $script;
}
/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Enqueue frontend assets for this block, just in time for rendering.
*
* @param array $attributes Any attributes that currently are available from the block.
* @param string $content The block content.
* @param WP_Block $block The block object.
*/
protected function enqueue_assets( array $attributes, $content, $block ) {
parent::enqueue_assets( $attributes, $content, $block );
if ( wc_current_theme_is_fse_theme() ) {
add_action(
'wp_enqueue_scripts',
array( $this, 'dequeue_add_to_cart_scripts' )
);
} else {
$this->dequeue_add_to_cart_scripts();
}
}
/**
* Dequeue the add-to-cart script.
* The block uses Interactivity API, it isn't necessary enqueue the add-to-cart script.
*/
public function dequeue_add_to_cart_scripts() {
wp_dequeue_script( 'wc-add-to-cart' );
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
$product = wc_get_product( $post_id );
if ( $product ) {
$number_of_items_in_cart = $this->get_cart_item_quantities_by_product_id( $product->get_id() );
$more_than_one_item = $number_of_items_in_cart > 0;
$initial_product_text = $more_than_one_item ? sprintf(
/* translators: %s: product number. */
__( '%s in cart', 'woocommerce' ),
$number_of_items_in_cart
) : $product->add_to_cart_text();
$cart_redirect_after_add = get_option( 'woocommerce_cart_redirect_after_add' ) === 'yes';
$ajax_add_to_cart_enabled = get_option( 'woocommerce_enable_ajax_add_to_cart' ) === 'yes';
$is_ajax_button = $ajax_add_to_cart_enabled && ! $cart_redirect_after_add && $product->supports( 'ajax_add_to_cart' ) && $product->is_purchasable() && $product->is_in_stock();
$html_element = $is_ajax_button ? 'button' : 'a';
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$classname = $attributes['className'] ?? '';
$custom_width_classes = isset( $attributes['width'] ) ? 'has-custom-width wp-block-button__width-' . $attributes['width'] : '';
$custom_align_classes = isset( $attributes['textAlign'] ) ? 'align-' . $attributes['textAlign'] : '';
$html_classes = implode(
' ',
array_filter(
array(
'wp-block-button__link',
'wp-element-button',
'wc-block-components-product-button__button',
$product->is_purchasable() && $product->is_in_stock() ? 'add_to_cart_button' : '',
$is_ajax_button ? 'ajax_add_to_cart' : '',
'product_type_' . $product->get_type(),
esc_attr( $styles_and_classes['classes'] ),
)
)
);
wc_store(
array(
'state' => array(
'woocommerce' => array(
'inTheCartText' => sprintf(
/* translators: %s: product number. */
__( '%s in cart', 'woocommerce' ),
'###'
),
),
),
)
);
$default_quantity = 1;
$context = array(
'woocommerce' => array(
/**
* Filters the change the quantity to add to cart.
*
* @since 10.9.0
* @param number $default_quantity The default quantity.
* @param number $product_id The product id.
*/
'quantityToAdd' => apply_filters( 'woocommerce_add_to_cart_quantity', $default_quantity, $product->get_id() ),
'productId' => $product->get_id(),
'addToCartText' => null !== $product->add_to_cart_text() ? $product->add_to_cart_text() : __( 'Add to cart', 'woocommerce' ),
'temporaryNumberOfItems' => $number_of_items_in_cart,
'animationStatus' => 'IDLE',
),
);
/**
* Allow filtering of the add to cart button arguments.
*
* @since 9.7.0
*/
$args = apply_filters(
'woocommerce_loop_add_to_cart_args',
array(
'class' => $html_classes,
'attributes' => array(
'data-product_id' => $product->get_id(),
'data-product_sku' => $product->get_sku(),
'aria-label' => $product->add_to_cart_description(),
'rel' => 'nofollow',
),
),
$product
);
if ( isset( $args['attributes']['aria-label'] ) ) {
$args['attributes']['aria-label'] = wp_strip_all_tags( $args['attributes']['aria-label'] );
}
if ( isset( WC()->cart ) && ! WC()->cart->is_empty() ) {
$this->prevent_cache();
}
$div_directives = 'data-wc-context=\'' . wp_json_encode( $context, JSON_NUMERIC_CHECK ) . '\'';
$button_directives = '
data-wc-on--click="actions.woocommerce.addToCart"
data-wc-class--loading="context.woocommerce.isLoading"
';
$span_button_directives = '
data-wc-text="selectors.woocommerce.addToCartText"
data-wc-class--wc-block-slide-in="selectors.woocommerce.slideInAnimation"
data-wc-class--wc-block-slide-out="selectors.woocommerce.slideOutAnimation"
data-wc-layout-init="init.woocommerce.syncTemporaryNumberOfItemsOnLoad"
data-wc-effect="effects.woocommerce.startAnimation"
data-wc-on--animationend="actions.woocommerce.handleAnimationEnd"
';
/**
* Filters the add to cart button class.
*
* @since 8.7.0
*
* @param string $class The class.
*/
return apply_filters(
'woocommerce_loop_add_to_cart_link',
strtr(
'<div data-wc-interactive class="wp-block-button wc-block-components-product-button {classes} {custom_classes}"
{div_directives}
>
<{html_element}
href="{add_to_cart_url}"
class="{button_classes}"
style="{button_styles}"
{attributes}
{button_directives}
>
<span {span_button_directives}> {add_to_cart_text} </span>
</{html_element}>
{view_cart_html}
</div>',
array(
'{classes}' => esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
'{custom_classes}' => esc_attr( $classname . ' ' . $custom_width_classes . ' ' . $custom_align_classes ),
'{html_element}' => $html_element,
'{add_to_cart_url}' => esc_url( $product->add_to_cart_url() ),
'{button_classes}' => isset( $args['class'] ) ? esc_attr( $args['class'] ) : '',
'{button_styles}' => esc_attr( $styles_and_classes['styles'] ),
'{attributes}' => isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '',
'{add_to_cart_text}' => esc_html( $initial_product_text ),
'{div_directives}' => $is_ajax_button ? $div_directives : '',
'{button_directives}' => $is_ajax_button ? $button_directives : '',
'{span_button_directives}' => $is_ajax_button ? $span_button_directives : '',
'{view_cart_html}' => $is_ajax_button ? $this->get_view_cart_html() : '',
)
),
$product,
$args
);
}
}
/**
* Get the number of items in the cart for a given product id.
*
* @param number $product_id The product id.
* @return number The number of items in the cart.
*/
private function get_cart_item_quantities_by_product_id( $product_id ) {
if ( ! isset( WC()->cart ) ) {
return 0;
}
$cart = WC()->cart->get_cart_item_quantities();
return isset( $cart[ $product_id ] ) ? $cart[ $product_id ] : 0;
}
/**
* Prevent caching on certain pages
*/
private function prevent_cache() {
\WC_Cache_Helper::set_nocache_constants();
nocache_headers();
}
/**
* Get the view cart link html.
*
* @return string The view cart html.
*/
private function get_view_cart_html() {
return sprintf(
'<span hidden data-wc-bind--hidden="!selectors.woocommerce.displayViewCart">
<a
href="%1$s"
class="added_to_cart wc_forward"
title="%2$s"
>
%2$s
</a>
</span>',
wc_get_cart_url(),
__( 'View cart', 'woocommerce' )
);
}
}
ProductCategories.php 0000644 00000031520 15155156421 0010711 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductCategories class.
*/
class ProductCategories extends AbstractDynamicBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-categories';
/**
* Default attribute values, should match what's set in JS `registerBlockType`.
*
* @var array
*/
protected $defaults = array(
'hasCount' => true,
'hasImage' => false,
'hasEmpty' => false,
'isDropdown' => false,
'isHierarchical' => true,
'showChildrenOnly' => false,
);
/**
* Get block attributes.
*
* @return array
*/
protected function get_block_type_attributes() {
return array_merge(
parent::get_block_type_attributes(),
array(
'align' => $this->get_schema_align(),
'className' => $this->get_schema_string(),
'hasCount' => $this->get_schema_boolean( true ),
'hasImage' => $this->get_schema_boolean( false ),
'hasEmpty' => $this->get_schema_boolean( false ),
'isDropdown' => $this->get_schema_boolean( false ),
'isHierarchical' => $this->get_schema_boolean( true ),
'showChildrenOnly' => $this->get_schema_boolean( false ),
'textColor' => $this->get_schema_string(),
'fontSize' => $this->get_schema_string(),
'lineHeight' => $this->get_schema_string(),
'style' => array( 'type' => 'object' ),
)
);
}
/**
* Render the Product Categories List block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
$uid = uniqid( 'product-categories-' );
$categories = $this->get_categories( $attributes );
if ( empty( $categories ) ) {
return '';
}
if ( ! empty( $content ) ) {
// Deal with legacy attributes (before this was an SSR block) that differ from defaults.
if ( strstr( $content, 'data-has-count="false"' ) ) {
$attributes['hasCount'] = false;
}
if ( strstr( $content, 'data-is-dropdown="true"' ) ) {
$attributes['isDropdown'] = true;
}
if ( strstr( $content, 'data-is-hierarchical="false"' ) ) {
$attributes['isHierarchical'] = false;
}
if ( strstr( $content, 'data-has-empty="true"' ) ) {
$attributes['hasEmpty'] = true;
}
}
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes(
$attributes,
array( 'line_height', 'text_color', 'font_size' )
);
$classes = $this->get_container_classes( $attributes ) . ' ' . $classes_and_styles['classes'];
$styles = $classes_and_styles['styles'];
$output = '<div class="wp-block-woocommerce-product-categories ' . esc_attr( $classes ) . '" style="' . esc_attr( $styles ) . '">';
$output .= ! empty( $attributes['isDropdown'] ) ? $this->renderDropdown( $categories, $attributes, $uid ) : $this->renderList( $categories, $attributes, $uid );
$output .= '</div>';
return $output;
}
/**
* Get the list of classes to apply to this block.
*
* @param array $attributes Block attributes. Default empty array.
* @return string space-separated list of classes.
*/
protected function get_container_classes( $attributes = array() ) {
$classes = array( 'wc-block-product-categories' );
if ( isset( $attributes['align'] ) ) {
$classes[] = "align{$attributes['align']}";
}
if ( ! empty( $attributes['className'] ) ) {
$classes[] = $attributes['className'];
}
if ( $attributes['isDropdown'] ) {
$classes[] = 'is-dropdown';
} else {
$classes[] = 'is-list';
}
return implode( ' ', $classes );
}
/**
* Get categories (terms) from the db.
*
* @param array $attributes Block attributes. Default empty array.
* @return array
*/
protected function get_categories( $attributes ) {
$hierarchical = wc_string_to_bool( $attributes['isHierarchical'] );
$children_only = wc_string_to_bool( $attributes['showChildrenOnly'] ) && is_product_category();
if ( $children_only ) {
$term_id = get_queried_object_id();
$categories = get_terms(
'product_cat',
[
'hide_empty' => ! $attributes['hasEmpty'],
'pad_counts' => true,
'hierarchical' => true,
'child_of' => $term_id,
]
);
} else {
$categories = get_terms(
'product_cat',
[
'hide_empty' => ! $attributes['hasEmpty'],
'pad_counts' => true,
'hierarchical' => true,
]
);
}
if ( ! is_array( $categories ) || empty( $categories ) ) {
return [];
}
// This ensures that no categories with a product count of 0 is rendered.
if ( ! $attributes['hasEmpty'] ) {
$categories = array_filter(
$categories,
function( $category ) {
return 0 !== $category->count;
}
);
}
return $hierarchical ? $this->build_category_tree( $categories, $children_only ) : $categories;
}
/**
* Build hierarchical tree of categories.
*
* @param array $categories List of terms.
* @param bool $children_only Is the block rendering only the children of the current category.
* @return array
*/
protected function build_category_tree( $categories, $children_only ) {
$categories_by_parent = [];
foreach ( $categories as $category ) {
if ( ! isset( $categories_by_parent[ 'cat-' . $category->parent ] ) ) {
$categories_by_parent[ 'cat-' . $category->parent ] = [];
}
$categories_by_parent[ 'cat-' . $category->parent ][] = $category;
}
$parent_id = $children_only ? get_queried_object_id() : 0;
$tree = $categories_by_parent[ 'cat-' . $parent_id ]; // these are top level categories. So all parents.
unset( $categories_by_parent[ 'cat-' . $parent_id ] );
foreach ( $tree as $category ) {
if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) {
$category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent );
}
}
return $tree;
}
/**
* Build hierarchical tree of categories by appending children in the tree.
*
* @param array $categories List of terms.
* @param array $categories_by_parent List of terms grouped by parent.
* @return array
*/
protected function fill_category_children( $categories, $categories_by_parent ) {
foreach ( $categories as $category ) {
if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) {
$category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent );
}
}
return $categories;
}
/**
* Render the category list as a dropdown.
*
* @param array $categories List of terms.
* @param array $attributes Block attributes. Default empty array.
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
* @return string Rendered output.
*/
protected function renderDropdown( $categories, $attributes, $uid ) {
$aria_label = empty( $attributes['hasCount'] ) ?
__( 'List of categories', 'woocommerce' ) :
__( 'List of categories with their product counts', 'woocommerce' );
$output = '
<div class="wc-block-product-categories__dropdown">
<label
class="screen-reader-text"
for="' . esc_attr( $uid ) . '-select"
>
' . esc_html__( 'Select a category', 'woocommerce' ) . '
</label>
<select aria-label="' . esc_attr( $aria_label ) . '" id="' . esc_attr( $uid ) . '-select">
<option value="false" hidden>
' . esc_html__( 'Select a category', 'woocommerce' ) . '
</option>
' . $this->renderDropdownOptions( $categories, $attributes, $uid ) . '
</select>
</div>
<button
type="button"
class="wc-block-product-categories__button"
aria-label="' . esc_html__( 'Go to category', 'woocommerce' ) . '"
onclick="const url = document.getElementById( \'' . esc_attr( $uid ) . '-select\' ).value; if ( \'false\' !== url ) document.location.href = url;"
>
<svg
aria-hidden="true"
role="img"
focusable="false"
class="dashicon dashicons-arrow-right-alt2"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
>
<path d="M6 15l5-5-5-5 1-2 7 7-7 7z" />
</svg>
</button>
';
return $output;
}
/**
* Render dropdown options list.
*
* @param array $categories List of terms.
* @param array $attributes Block attributes. Default empty array.
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
* @param int $depth Current depth.
* @return string Rendered output.
*/
protected function renderDropdownOptions( $categories, $attributes, $uid, $depth = 0 ) {
$output = '';
foreach ( $categories as $category ) {
$output .= '
<option value="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '">
' . str_repeat( '−', $depth ) . '
' . esc_html( $category->name ) . '
' . $this->getCount( $category, $attributes ) . '
</option>
' . ( ! empty( $category->children ) ? $this->renderDropdownOptions( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . '
';
}
return $output;
}
/**
* Render the category list as a list.
*
* @param array $categories List of terms.
* @param array $attributes Block attributes. Default empty array.
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
* @param int $depth Current depth.
* @return string Rendered output.
*/
protected function renderList( $categories, $attributes, $uid, $depth = 0 ) {
$classes = [
'wc-block-product-categories-list',
'wc-block-product-categories-list--depth-' . absint( $depth ),
];
if ( ! empty( $attributes['hasImage'] ) ) {
$classes[] = 'wc-block-product-categories-list--has-images';
}
$output = '<ul class="' . esc_attr( implode( ' ', $classes ) ) . '">' . $this->renderListItems( $categories, $attributes, $uid, $depth ) . '</ul>';
return $output;
}
/**
* Render a list of terms.
*
* @param array $categories List of terms.
* @param array $attributes Block attributes. Default empty array.
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
* @param int $depth Current depth.
* @return string Rendered output.
*/
protected function renderListItems( $categories, $attributes, $uid, $depth = 0 ) {
$output = '';
$link_color_class_and_style = StyleAttributesUtils::get_link_color_class_and_style( $attributes );
$link_color_style = isset( $link_color_class_and_style['style'] ) ? $link_color_class_and_style['style'] : '';
foreach ( $categories as $category ) {
$output .= '
<li class="wc-block-product-categories-list-item">
<a style="' . esc_attr( $link_color_style ) . '" href="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '">'
. $this->get_image_html( $category, $attributes )
. '<span class="wc-block-product-categories-list-item__name">' . esc_html( $category->name ) . '</span>'
. '</a>'
. $this->getCount( $category, $attributes )
. ( ! empty( $category->children ) ? $this->renderList( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . '
</li>
';
}
return preg_replace( '/\r|\n/', '', $output );
}
/**
* Returns the category image html
*
* @param \WP_Term $category Term object.
* @param array $attributes Block attributes. Default empty array.
* @param string $size Image size, defaults to 'woocommerce_thumbnail'.
* @return string
*/
public function get_image_html( $category, $attributes, $size = 'woocommerce_thumbnail' ) {
if ( empty( $attributes['hasImage'] ) ) {
return '';
}
$image_id = get_term_meta( $category->term_id, 'thumbnail_id', true );
if ( ! $image_id ) {
return '<span class="wc-block-product-categories-list-item__image wc-block-product-categories-list-item__image--placeholder">' . wc_placeholder_img( 'woocommerce_thumbnail' ) . '</span>';
}
return '<span class="wc-block-product-categories-list-item__image">' . wp_get_attachment_image( $image_id, 'woocommerce_thumbnail' ) . '</span>';
}
/**
* Get the count, if displaying.
*
* @param object $category Term object.
* @param array $attributes Block attributes. Default empty array.
* @return string
*/
protected function getCount( $category, $attributes ) {
if ( empty( $attributes['hasCount'] ) ) {
return '';
}
if ( $attributes['isDropdown'] ) {
return '(' . absint( $category->count ) . ')';
}
$screen_reader_text = sprintf(
/* translators: %s number of products in cart. */
_n( '%d product', '%d products', absint( $category->count ), 'woocommerce' ),
absint( $category->count )
);
return '<span class="wc-block-product-categories-list-item-count">'
. '<span aria-hidden="true">' . absint( $category->count ) . '</span>'
. '<span class="screen-reader-text">' . esc_html( $screen_reader_text ) . '</span>'
. '</span>';
}
}
ProductCategory.php 0000644 00000001335 15155156421 0010402 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductCategory class.
*/
class ProductCategory extends AbstractProductGrid {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-category';
/**
* Set args specific to this block
*
* @param array $query_args Query args.
*/
protected function set_block_query_args( &$query_args ) {}
/**
* Get block attributes.
*
* @return array
*/
protected function get_block_type_attributes() {
return array_merge(
parent::get_block_type_attributes(),
array(
'className' => $this->get_schema_string(),
'orderby' => $this->get_schema_orderby(),
'editMode' => $this->get_schema_boolean( true ),
)
);
}
}
ProductCollection.php 0000644 00000067603 15155156421 0010732 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use WP_Query;
/**
* ProductCollection class.
*/
class ProductCollection extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-collection';
/**
* The Block with its attributes before it gets rendered
*
* @var array
*/
protected $parsed_block;
/**
* All query args from WP_Query.
*
* @var array
*/
protected $valid_query_vars;
/**
* All the query args related to the filter by attributes block.
*
* @var array
*/
protected $attributes_filter_query_args = array();
/**
* Orderby options not natively supported by WordPress REST API
*
* @var array
*/
protected $custom_order_opts = array( 'popularity', 'rating' );
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
/**
* Initialize this block type.
*
* - Hook into WP lifecycle.
* - Register the block with WordPress.
* - Hook into pre_render_block to update the query.
*/
protected function initialize() {
parent::initialize();
// Update query for frontend rendering.
add_filter(
'query_loop_block_query_vars',
array( $this, 'build_frontend_query' ),
10,
3
);
add_filter(
'pre_render_block',
array( $this, 'add_support_for_filter_blocks' ),
10,
2
);
// Update the query for Editor.
add_filter( 'rest_product_query', array( $this, 'update_rest_query_in_editor' ), 10, 2 );
// Extend allowed `collection_params` for the REST API.
add_filter( 'rest_product_collection_params', array( $this, 'extend_rest_query_allowed_params' ), 10, 1 );
// Interactivity API: Add navigation directives to the product collection block.
add_filter( 'render_block_woocommerce/product-collection', array( $this, 'add_navigation_id_directive' ), 10, 3 );
add_filter( 'render_block_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 );
}
/**
* Mark the Product Collection as an interactive region so it can be updated
* during client-side navigation.
*
* @param string $block_content The block content.
* @param array $block The full block, including name and attributes.
* @param \WP_Block $instance The block instance.
*/
public function add_navigation_id_directive( $block_content, $block, $instance ) {
$is_product_collection_block = $block['attrs']['query']['isProductCollectionBlock'] ?? false;
if ( $is_product_collection_block ) {
// Enqueue the Interactivity API runtime.
wp_enqueue_script( 'wc-interactivity' );
$p = new \WP_HTML_Tag_Processor( $block_content );
// Add `data-wc-navigation-id to the query block.
if ( $p->next_tag( array( 'class_name' => 'wp-block-woocommerce-product-collection' ) ) ) {
$p->set_attribute(
'data-wc-navigation-id',
'wc-product-collection-' . $this->parsed_block['attrs']['queryId']
);
$p->set_attribute( 'data-wc-interactive', true );
$block_content = $p->get_updated_html();
}
}
return $block_content;
}
/**
* Add interactive links to all anchors inside the Query Pagination block.
*
* @param string $block_content The block content.
* @param array $block The full block, including name and attributes.
* @param \WP_Block $instance The block instance.
*/
public function add_navigation_link_directives( $block_content, $block, $instance ) {
$is_product_collection_block = $instance->context['query']['isProductCollectionBlock'] ?? false;
if (
$is_product_collection_block &&
$instance->context['queryId'] === $this->parsed_block['attrs']['queryId']
) {
$p = new \WP_HTML_Tag_Processor( $block_content );
$p->next_tag( array( 'class_name' => 'wp-block-query-pagination' ) );
while ( $p->next_tag( 'a' ) ) {
$class_attr = $p->get_attribute( 'class' );
$class_list = preg_split( '/\s+/', $class_attr );
$is_previous = in_array( 'wp-block-query-pagination-previous', $class_list, true );
$is_next = in_array( 'wp-block-query-pagination-next', $class_list, true );
$is_previous_or_next = $is_previous || $is_next;
$navigation_link_payload = array(
'prefetch' => $is_previous_or_next,
'scroll' => false,
);
$p->set_attribute(
'data-wc-navigation-link',
wp_json_encode( $navigation_link_payload )
);
if ( $is_previous ) {
$p->set_attribute( 'key', 'pagination-previous' );
} elseif ( $is_next ) {
$p->set_attribute( 'key', 'pagination-next' );
}
}
$block_content = $p->get_updated_html();
}
return $block_content;
}
/**
* 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 );
// The `loop_shop_per_page` filter can be found in WC_Query::product_query().
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
$this->asset_data_registry->add( 'loopShopPerPage', apply_filters( 'loop_shop_per_page', wc_get_default_products_per_row() * wc_get_default_product_rows_per_page() ), true );
}
/**
* Update the query for the product query block in Editor.
*
* @param array $args Query args.
* @param WP_REST_Request $request Request.
*/
public function update_rest_query_in_editor( $args, $request ): array {
// Only update the query if this is a product collection block.
$is_product_collection_block = $request->get_param( 'isProductCollectionBlock' );
if ( ! $is_product_collection_block ) {
return $args;
}
$orderby = $request->get_param( 'orderBy' );
$on_sale = $request->get_param( 'woocommerceOnSale' ) === 'true';
$stock_status = $request->get_param( 'woocommerceStockStatus' );
$product_attributes = $request->get_param( 'woocommerceAttributes' );
$handpicked_products = $request->get_param( 'woocommerceHandPickedProducts' );
$args['author'] = $request->get_param( 'author' ) ?? '';
return $this->get_final_query_args(
$args,
array(
'orderby' => $orderby,
'on_sale' => $on_sale,
'stock_status' => $stock_status,
'product_attributes' => $product_attributes,
'handpicked_products' => $handpicked_products,
)
);
}
/**
* Add support for filter blocks:
* - Price filter block
* - Attributes filter block
* - Rating filter block
* - In stock filter block etc.
*
* @param array $pre_render The pre-rendered block.
* @param array $parsed_block The parsed block.
*/
public function add_support_for_filter_blocks( $pre_render, $parsed_block ) {
$is_product_collection_block = $parsed_block['attrs']['query']['isProductCollectionBlock'] ?? false;
if ( ! $is_product_collection_block ) {
return;
}
$this->parsed_block = $parsed_block;
$this->asset_data_registry->add( 'hasFilterableProducts', true, true );
/**
* It enables the page to refresh when a filter is applied, ensuring that the product collection block,
* which is a server-side rendered (SSR) block, retrieves the products that match the filters.
*/
$this->asset_data_registry->add( 'isRenderingPhpTemplate', true, true );
}
/**
* Return a custom query based on attributes, filters and global WP_Query.
*
* @param WP_Query $query The WordPress Query.
* @param WP_Block $block The block being rendered.
* @param int $page The page number.
*
* @return array
*/
public function build_frontend_query( $query, $block, $page ) {
// If not in context of product collection block, return the query as is.
$is_product_collection_block = $block->context['query']['isProductCollectionBlock'] ?? false;
if ( ! $is_product_collection_block ) {
return $query;
}
$block_context_query = $block->context['query'];
// phpcs:ignore WordPress.DB.SlowDBQuery
$block_context_query['tax_query'] = ! empty( $query['tax_query'] ) ? $query['tax_query'] : array();
return $this->get_final_frontend_query( $block_context_query, $page );
}
/**
* Get the final query arguments for the frontend.
*
* @param array $query The query arguments.
* @param int $page The page number.
* @param bool $is_exclude_applied_filters Whether to exclude the applied filters or not.
*/
private function get_final_frontend_query( $query, $page = 1, $is_exclude_applied_filters = false ) {
$offset = $query['offset'] ?? 0;
$per_page = $query['perPage'] ?? 9;
$common_query_values = array(
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(),
'posts_per_page' => $query['perPage'],
'order' => $query['order'],
'offset' => ( $per_page * ( $page - 1 ) ) + $offset,
'post__in' => array(),
'post_status' => 'publish',
'post_type' => 'product',
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
'tax_query' => array(),
'paged' => $page,
's' => $query['search'],
'author' => $query['author'] ?? '',
);
$is_on_sale = $query['woocommerceOnSale'] ?? false;
$taxonomies_query = $this->get_filter_by_taxonomies_query( $query['tax_query'] ?? [] );
$handpicked_products = $query['woocommerceHandPickedProducts'] ?? [];
$final_query = $this->get_final_query_args(
$common_query_values,
array(
'on_sale' => $is_on_sale,
'stock_status' => $query['woocommerceStockStatus'],
'orderby' => $query['orderBy'],
'product_attributes' => $query['woocommerceAttributes'],
'taxonomies_query' => $taxonomies_query,
'handpicked_products' => $handpicked_products,
),
$is_exclude_applied_filters
);
return $final_query;
}
/**
* Get final query args based on provided values
*
* @param array $common_query_values Common query values.
* @param array $query Query from block context.
* @param bool $is_exclude_applied_filters Whether to exclude the applied filters or not.
*/
private function get_final_query_args( $common_query_values, $query, $is_exclude_applied_filters = false ) {
$handpicked_products = $query['handpicked_products'] ?? [];
$orderby_query = $query['orderby'] ? $this->get_custom_orderby_query( $query['orderby'] ) : [];
$on_sale_query = $this->get_on_sale_products_query( $query['on_sale'] );
$stock_query = $this->get_stock_status_query( $query['stock_status'] );
$visibility_query = is_array( $query['stock_status'] ) ? $this->get_product_visibility_query( $stock_query ) : [];
$attributes_query = $this->get_product_attributes_query( $query['product_attributes'] );
$taxonomies_query = $query['taxonomies_query'] ?? [];
$tax_query = $this->merge_tax_queries( $visibility_query, $attributes_query, $taxonomies_query );
// We exclude applied filters to generate product ids for the filter blocks.
$applied_filters_query = $is_exclude_applied_filters ? [] : $this->get_queries_by_applied_filters();
$merged_query = $this->merge_queries( $common_query_values, $orderby_query, $on_sale_query, $stock_query, $tax_query, $applied_filters_query );
return $this->filter_query_to_only_include_ids( $merged_query, $handpicked_products );
}
/**
* Extends allowed `collection_params` for the REST API
*
* By itself, the REST API doesn't accept custom `orderby` values,
* even if they are supported by a custom post type.
*
* @param array $params A list of allowed `orderby` values.
*
* @return array
*/
public function extend_rest_query_allowed_params( $params ) {
$original_enum = isset( $params['orderby']['enum'] ) ? $params['orderby']['enum'] : array();
$params['orderby']['enum'] = array_unique( array_merge( $original_enum, $this->custom_order_opts ) );
return $params;
}
/**
* Merge in the first parameter the keys "post_in", "meta_query" and "tax_query" of the second parameter.
*
* @param array[] ...$queries Query arrays to be merged.
* @return array
*/
private function merge_queries( ...$queries ) {
$merged_query = array_reduce(
$queries,
function( $acc, $query ) {
if ( ! is_array( $query ) ) {
return $acc;
}
// If the $query doesn't contain any valid query keys, we unpack/spread it then merge.
if ( empty( array_intersect( $this->get_valid_query_vars(), array_keys( $query ) ) ) ) {
return $this->merge_queries( $acc, ...array_values( $query ) );
}
return $this->array_merge_recursive_replace_non_array_properties( $acc, $query );
},
array()
);
/**
* If there are duplicated items in post__in, it means that we need to
* use the intersection of the results, which in this case, are the
* duplicated items.
*/
if (
! empty( $merged_query['post__in'] ) &&
count( $merged_query['post__in'] ) > count( array_unique( $merged_query['post__in'] ) )
) {
$merged_query['post__in'] = array_unique(
array_diff(
$merged_query['post__in'],
array_unique( $merged_query['post__in'] )
)
);
}
return $merged_query;
}
/**
* Return query params to support custom sort values
*
* @param string $orderby Sort order option.
*
* @return array
*/
private function get_custom_orderby_query( $orderby ) {
if ( ! in_array( $orderby, $this->custom_order_opts, true ) ) {
return array( 'orderby' => $orderby );
}
$meta_keys = array(
'popularity' => 'total_sales',
'rating' => '_wc_average_rating',
);
return array(
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'meta_key' => $meta_keys[ $orderby ],
'orderby' => 'meta_value_num',
);
}
/**
* Return a query for on sale products.
*
* @param bool $is_on_sale Whether to query for on sale products.
*
* @return array
*/
private function get_on_sale_products_query( $is_on_sale ) {
if ( ! $is_on_sale ) {
return array();
}
return array(
'post__in' => wc_get_product_ids_on_sale(),
);
}
/**
* Return or initialize $valid_query_vars.
*
* @return array
*/
private function get_valid_query_vars() {
if ( ! empty( $this->valid_query_vars ) ) {
return $this->valid_query_vars;
}
$valid_query_vars = array_keys( ( new WP_Query() )->fill_query_vars( array() ) );
$this->valid_query_vars = array_merge(
$valid_query_vars,
// fill_query_vars doesn't include these vars so we need to add them manually.
array(
'date_query',
'exact',
'ignore_sticky_posts',
'lazy_load_term_meta',
'meta_compare_key',
'meta_compare',
'meta_query',
'meta_type_key',
'meta_type',
'nopaging',
'offset',
'order',
'orderby',
'page',
'post_type',
'posts_per_page',
'suppress_filters',
'tax_query',
)
);
return $this->valid_query_vars;
}
/**
* Merge two array recursively but replace the non-array values instead of
* merging them. The merging strategy:
*
* - If keys from merge array doesn't exist in the base array, create them.
* - For array items with numeric keys, we merge them as normal.
* - For array items with string keys:
*
* - If the value isn't array, we'll use the value comming from the merge array.
* $base = ['orderby' => 'date']
* $new = ['orderby' => 'meta_value_num']
* Result: ['orderby' => 'meta_value_num']
*
* - If the value is array, we'll use recursion to merge each key.
* $base = ['meta_query' => [
* [
* 'key' => '_stock_status',
* 'compare' => 'IN'
* 'value' => ['instock', 'onbackorder']
* ]
* ]]
* $new = ['meta_query' => [
* [
* 'relation' => 'AND',
* [...<max_price_query>],
* [...<min_price_query>],
* ]
* ]]
* Result: ['meta_query' => [
* [
* 'key' => '_stock_status',
* 'compare' => 'IN'
* 'value' => ['instock', 'onbackorder']
* ],
* [
* 'relation' => 'AND',
* [...<max_price_query>],
* [...<min_price_query>],
* ]
* ]]
*
* $base = ['post__in' => [1, 2, 3, 4, 5]]
* $new = ['post__in' => [3, 4, 5, 6, 7]]
* Result: ['post__in' => [1, 2, 3, 4, 5, 3, 4, 5, 6, 7]]
*
* @param array $base First array.
* @param array $new Second array.
*/
private function array_merge_recursive_replace_non_array_properties( $base, $new ) {
foreach ( $new as $key => $value ) {
if ( is_numeric( $key ) ) {
$base[] = $value;
} else {
if ( is_array( $value ) ) {
if ( ! isset( $base[ $key ] ) ) {
$base[ $key ] = array();
}
$base[ $key ] = $this->array_merge_recursive_replace_non_array_properties( $base[ $key ], $value );
} else {
$base[ $key ] = $value;
}
}
}
return $base;
}
/**
* Return a query for products depending on their stock status.
*
* @param array $stock_statuses An array of acceptable stock statuses.
* @return array
*/
private function get_stock_status_query( $stock_statuses ) {
if ( ! is_array( $stock_statuses ) ) {
return array();
}
$stock_status_options = array_keys( wc_get_product_stock_status_options() );
/**
* If all available stock status are selected, we don't need to add the
* meta query for stock status.
*/
if (
count( $stock_statuses ) === count( $stock_status_options ) &&
array_diff( $stock_statuses, $stock_status_options ) === array_diff( $stock_status_options, $stock_statuses )
) {
return array();
}
/**
* If all stock statuses are selected except 'outofstock', we use the
* product visibility query to filter out out of stock products.
*
* @see get_product_visibility_query()
*/
$diff = array_diff( $stock_status_options, $stock_statuses );
if ( count( $diff ) === 1 && in_array( 'outofstock', $diff, true ) ) {
return array();
}
return array(
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
array(
'key' => '_stock_status',
'value' => (array) $stock_statuses,
'compare' => 'IN',
),
),
);
}
/**
* Return a query for product visibility depending on their stock status.
*
* @param array $stock_query Stock status query.
*
* @return array Tax query for product visibility.
*/
private function get_product_visibility_query( $stock_query ) {
$product_visibility_terms = wc_get_product_visibility_term_ids();
$product_visibility_not_in = array( is_search() ? $product_visibility_terms['exclude-from-search'] : $product_visibility_terms['exclude-from-catalog'] );
// Hide out of stock products.
if ( empty( $stock_query ) && 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
$product_visibility_not_in[] = $product_visibility_terms['outofstock'];
}
return array(
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
'tax_query' => array(
array(
'taxonomy' => 'product_visibility',
'field' => 'term_taxonomy_id',
'terms' => $product_visibility_not_in,
'operator' => 'NOT IN',
),
),
);
}
/**
* Merge tax_queries from various queries.
*
* @param array ...$queries Query arrays to be merged.
* @return array
*/
private function merge_tax_queries( ...$queries ) {
$tax_query = [];
foreach ( $queries as $query ) {
if ( ! empty( $query['tax_query'] ) ) {
$tax_query = array_merge( $tax_query, $query['tax_query'] );
}
}
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
return [ 'tax_query' => $tax_query ];
}
/**
* Return the `tax_query` for the requested attributes
*
* @param array $attributes Attributes and their terms.
*
* @return array
*/
private function get_product_attributes_query( $attributes = array() ) {
if ( empty( $attributes ) ) {
return array();
}
$grouped_attributes = array_reduce(
$attributes,
function ( $carry, $item ) {
$taxonomy = sanitize_title( $item['taxonomy'] );
if ( ! key_exists( $taxonomy, $carry ) ) {
$carry[ $taxonomy ] = array(
'field' => 'term_id',
'operator' => 'IN',
'taxonomy' => $taxonomy,
'terms' => array( $item['termId'] ),
);
} else {
$carry[ $taxonomy ]['terms'][] = $item['termId'];
}
return $carry;
},
array()
);
return array(
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
'tax_query' => array_values( $grouped_attributes ),
);
}
/**
* Return a query to filter products by taxonomies (product categories, product tags, etc.)
*
* For example:
* User could provide "Product Categories" using "Filters" ToolsPanel available in Inspector Controls.
* We use this function to extract its query from $tax_query.
*
* For example, this is how the query for product categories will look like in $tax_query array:
* Array
* (
* [taxonomy] => product_cat
* [terms] => Array
* (
* [0] => 36
* )
* )
*
* For product tags, taxonomy would be "product_tag"
*
* @param array $tax_query Query to filter products by taxonomies.
* @return array Query to filter products by taxonomies.
*/
private function get_filter_by_taxonomies_query( $tax_query ): array {
if ( ! is_array( $tax_query ) ) {
return [];
}
/**
* Get an array of taxonomy names associated with the "product" post type because
* we also want to include custom taxonomies associated with the "product" post type.
*/
$product_taxonomies = get_taxonomies( [ 'object_type' => [ 'product' ] ], 'names' );
$result = array_filter(
$tax_query,
function( $item ) use ( $product_taxonomies ) {
return isset( $item['taxonomy'] ) && in_array( $item['taxonomy'], $product_taxonomies, true );
}
);
// phpcs:ignore WordPress.DB.SlowDBQuery
return ! empty( $result ) ? [ 'tax_query' => $result ] : [];
}
/**
* Apply the query only to a subset of products
*
* @param array $query The query.
* @param array $ids Array of selected product ids.
*
* @return array
*/
private function filter_query_to_only_include_ids( $query, $ids ) {
if ( ! empty( $ids ) ) {
$query['post__in'] = empty( $query['post__in'] ) ?
$ids : array_intersect( $ids, $query['post__in'] );
}
return $query;
}
/**
* Return queries that are generated by query args.
*
* @return array
*/
private function get_queries_by_applied_filters() {
return array(
'price_filter' => $this->get_filter_by_price_query(),
'attributes_filter' => $this->get_filter_by_attributes_query(),
'stock_status_filter' => $this->get_filter_by_stock_status_query(),
'rating_filter' => $this->get_filter_by_rating_query(),
);
}
/**
* Return a query that filters products by price.
*
* @return array
*/
private function get_filter_by_price_query() {
$min_price = get_query_var( PriceFilter::MIN_PRICE_QUERY_VAR );
$max_price = get_query_var( PriceFilter::MAX_PRICE_QUERY_VAR );
$max_price_query = empty( $max_price ) ? array() : [
'key' => '_price',
'value' => $max_price,
'compare' => '<',
'type' => 'numeric',
];
$min_price_query = empty( $min_price ) ? array() : [
'key' => '_price',
'value' => $min_price,
'compare' => '>=',
'type' => 'numeric',
];
if ( empty( $min_price_query ) && empty( $max_price_query ) ) {
return array();
}
return array(
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
array(
'relation' => 'AND',
$max_price_query,
$min_price_query,
),
),
);
}
/**
* Return a query that filters products by attributes.
*
* @return array
*/
private function get_filter_by_attributes_query() {
$attributes_filter_query_args = $this->get_filter_by_attributes_query_vars();
$queries = array_reduce(
$attributes_filter_query_args,
function( $acc, $query_args ) {
$attribute_name = $query_args['filter'];
$attribute_query_type = $query_args['query_type'];
$attribute_value = get_query_var( $attribute_name );
$attribute_query = get_query_var( $attribute_query_type );
if ( empty( $attribute_value ) ) {
return $acc;
}
// It is necessary explode the value because $attribute_value can be a string with multiple values (e.g. "red,blue").
$attribute_value = explode( ',', $attribute_value );
$acc[] = array(
'taxonomy' => str_replace( AttributeFilter::FILTER_QUERY_VAR_PREFIX, 'pa_', $attribute_name ),
'field' => 'slug',
'terms' => $attribute_value,
'operator' => 'and' === $attribute_query ? 'AND' : 'IN',
);
return $acc;
},
array()
);
if ( empty( $queries ) ) {
return array();
}
return array(
// phpcs:ignore WordPress.DB.SlowDBQuery
'tax_query' => array(
array(
'relation' => 'AND',
$queries,
),
),
);
}
/**
* Get all the query args related to the filter by attributes block.
*
* @return array
* [color] => Array
* (
* [filter] => filter_color
* [query_type] => query_type_color
* )
*
* [size] => Array
* (
* [filter] => filter_size
* [query_type] => query_type_size
* )
* )
*/
private function get_filter_by_attributes_query_vars() {
if ( ! empty( $this->attributes_filter_query_args ) ) {
return $this->attributes_filter_query_args;
}
$this->attributes_filter_query_args = array_reduce(
wc_get_attribute_taxonomies(),
function( $acc, $attribute ) {
$acc[ $attribute->attribute_name ] = array(
'filter' => AttributeFilter::FILTER_QUERY_VAR_PREFIX . $attribute->attribute_name,
'query_type' => AttributeFilter::QUERY_TYPE_QUERY_VAR_PREFIX . $attribute->attribute_name,
);
return $acc;
},
array()
);
return $this->attributes_filter_query_args;
}
/**
* Return a query that filters products by stock status.
*
* @return array
*/
private function get_filter_by_stock_status_query() {
$filter_stock_status_values = get_query_var( StockFilter::STOCK_STATUS_QUERY_VAR );
if ( empty( $filter_stock_status_values ) ) {
return array();
}
$filtered_stock_status_values = array_filter(
explode( ',', $filter_stock_status_values ),
function( $stock_status ) {
return in_array( $stock_status, StockFilter::get_stock_status_query_var_values(), true );
}
);
if ( empty( $filtered_stock_status_values ) ) {
return array();
}
return array(
// Ignoring the warning of not using meta queries.
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
array(
'key' => '_stock_status',
'value' => $filtered_stock_status_values,
'operator' => 'IN',
),
),
);
}
/**
* Return a query that filters products by rating.
*
* @return array
*/
private function get_filter_by_rating_query() {
$filter_rating_values = get_query_var( RatingFilter::RATING_QUERY_VAR );
if ( empty( $filter_rating_values ) ) {
return array();
}
$parsed_filter_rating_values = explode( ',', $filter_rating_values );
$product_visibility_terms = wc_get_product_visibility_term_ids();
if ( empty( $parsed_filter_rating_values ) || empty( $product_visibility_terms ) ) {
return array();
}
$rating_terms = array_map(
function( $rating ) use ( $product_visibility_terms ) {
return $product_visibility_terms[ 'rated-' . $rating ];
},
$parsed_filter_rating_values
);
return array(
// phpcs:ignore WordPress.DB.SlowDBQuery
'tax_query' => array(
array(
'field' => 'term_taxonomy_id',
'taxonomy' => 'product_visibility',
'terms' => $rating_terms,
'operator' => 'IN',
'rating_filter' => true,
),
),
);
}
}
ProductDetails.php 0000644 00000003001 15155156421 0010202 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductDetails class.
*/
class ProductDetails extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-details';
/**
* It isn't necessary register block assets because it is a server side block.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
$tabs = $this->render_tabs();
$classname = $attributes['className'] ?? '';
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
return sprintf(
'<div class="wp-block-woocommerce-product-details %1$s %2$s">
<div style="%3$s">
%4$s
</div>
</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classname ),
esc_attr( $classes_and_styles['styles'] ),
$tabs
);
}
/**
* Gets the tabs with their content to be rendered by the block.
*
* @return string The tabs html to be rendered by the block
*/
protected function render_tabs() {
ob_start();
rewind_posts();
while ( have_posts() ) {
the_post();
woocommerce_output_product_data_tabs();
}
$tabs = ob_get_clean();
return $tabs;
}
}
ProductGallery.php 0000644 00000003537 15155156421 0010232 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductGallery class.
*/
class ProductGallery extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-gallery';
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
$classname = $attributes['className'] ?? '';
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( sprintf( 'woocommerce %1$s', $classname ) ) ) );
$html = sprintf(
'<div data-wc-interactive %1$s>
%2$s
</div>',
$wrapper_attributes,
$content
);
$p = new \WP_HTML_Tag_Processor( $content );
if ( $p->next_tag() ) {
$p->set_attribute( 'data-wc-interactive', true );
$p->set_attribute(
'data-wc-context',
wp_json_encode( array( 'woocommerce' => array( 'productGallery' => array( 'numberOfThumbnails' => 0 ) ) ) )
);
$html = $p->get_updated_html();
}
return $html;
}
/**
* Get the Interactivity API's view script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-frontend',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
'dependencies' => [ 'wc-interactivity' ],
];
return $key ? $script[ $key ] : $script;
}
}
ProductGalleryLargeImage.php 0000644 00000007346 15155156421 0012152 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductGalleryLargeImage class.
*/
class ProductGalleryLargeImage extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-gallery-large-image';
/**
* Register the context
*
* @return string[]
*/
protected function get_block_type_uses_context() {
return [ 'postId', 'hoverZoom' ];
}
/**
* Enqueue frontend assets for this block, just in time for rendering.
*
* @param array $attributes Any attributes that currently are available from the block.
* @param string $content The block content.
* @param WP_Block $block The block object.
*/
protected function enqueue_assets( array $attributes, $content, $block ) {
if ( $block->context['hoverZoom'] ) {
parent::enqueue_assets( $attributes, $content, $block );
}
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
$post_id = $block->context['postId'];
if ( ! isset( $post_id ) ) {
return '';
}
global $product;
$previous_product = $product;
$product = wc_get_product( $post_id );
if ( ! $product instanceof \WC_Product ) {
$product = $previous_product;
return '';
}
if ( class_exists( 'WC_Frontend_Scripts' ) ) {
$frontend_scripts = new \WC_Frontend_Scripts();
$frontend_scripts::load_scripts();
}
$processor = new \WP_HTML_Tag_Processor( $content );
$processor->next_tag();
$processor->remove_class( 'wp-block-woocommerce-product-gallery-large-image' );
$content = $processor->get_updated_html();
$image_html = wp_get_attachment_image(
$product->get_image_id(),
'full',
false,
array(
'class' => 'wc-block-woocommerce-product-gallery-large-image__image',
)
);
[$directives, $image_html] = $block->context['hoverZoom'] ? $this->get_html_with_interactivity( $image_html ) : array( array(), $image_html );
return strtr(
'<div class="wp-block-woocommerce-product-gallery-large-image" {directives}>
{image}
<div class="wc-block-woocommerce-product-gallery-large-image__content">
{content}
</div>
</div>',
array(
'{image}' => $image_html,
'{content}' => $content,
'{directives}' => array_reduce(
array_keys( $directives ),
function( $carry, $key ) use ( $directives ) {
return $carry . ' ' . $key . '="' . esc_attr( $directives[ $key ] ) . '"';
},
''
),
)
);
}
/**
* Get the HTML that adds interactivity to the image. This is used for the hover zoom effect.
*
* @param string $image_html The image HTML.
* @return array
*/
private function get_html_with_interactivity( $image_html ) {
$context = array(
'woocommerce' => array(
'styles' => array(
'transform' => 'scale(1.0)',
'transform-origin' => '',
),
),
);
$directives = array(
'data-wc-on--mousemove' => 'actions.woocommerce.handleMouseMove',
'data-wc-on--mouseleave' => 'actions.woocommerce.handleMouseLeave',
'data-wc-context' => wp_json_encode( $context, JSON_NUMERIC_CHECK ),
);
$image_html_processor = new \WP_HTML_Tag_Processor( $image_html );
$image_html_processor->next_tag( 'img' );
$image_html_processor->add_class( 'wc-block-woocommerce-product-gallery-large-image__image--hoverZoom' );
$image_html_processor->set_attribute( 'data-wc-bind--style', 'selectors.woocommerce.styles' );
$image_html = $image_html_processor->get_updated_html();
return array(
$directives,
$image_html,
);
}
}
ProductGalleryLargeImageNextPrevious.php 0000644 00000010450 15155156421 0014534 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductGalleryLargeImage class.
*/
class ProductGalleryLargeImageNextPrevious extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-gallery-large-image-next-previous';
/**
* It isn't necessary register block assets because it is a server side block.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context
*
* @return string[]
*/
protected function get_block_type_uses_context() {
return [ 'nextPreviousButtonsPosition', 'productGalleryClientId' ];
}
/**
* Return class suffix
*
* @param array $context Block context.
* @return string
*/
private function get_class_suffix( $context ) {
switch ( $context['nextPreviousButtonsPosition'] ) {
case 'insideTheImage':
return 'inside-image';
case 'outsideTheImage':
return 'outside-image';
case 'off':
return 'off';
default:
return 'off';
} }
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
$post_id = $block->context['postId'];
if ( ! isset( $post_id ) ) {
return '';
}
$product = wc_get_product( $post_id );
$product_gallery = $product->get_gallery_image_ids();
if ( empty( $product_gallery ) ) {
return null;
}
$context = $block->context;
$prev_button = sprintf(
'
<svg class="wc-block-product-gallery-large-image-next-previous-left--%1$s" xmlns="http://www.w3.org/2000/svg" width="49" height="48" viewBox="0 0 49 48" fill="none">
<g filter="url(#filter0_b_397_11356)">
<rect x="0.5" width="48" height="48" rx="5" fill="black" fill-opacity="0.5"/>
<path d="M28.1 12L30.5 14L21.3 24L30.5 34L28.1 36L17.3 24L28.1 12Z" fill="white"/>
</g>
<defs>
<filter id="filter0_b_397_11356" x="-9.5" y="-10" width="68" height="68" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="5"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_397_11356"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_397_11356" result="shape"/>
</filter>
</defs>
</svg>',
$this->get_class_suffix( $context )
);
$next_button = sprintf(
'
<svg class="wc-block-product-gallery-large-image-next-previous-right--%1$s" xmlns="http://www.w3.org/2000/svg" width="49" height="48" viewBox="0 0 49 48" fill="none">
<g filter="url(#filter0_b_397_11354)">
<rect x="0.5" width="48" height="48" rx="5" fill="black" fill-opacity="0.5"/>
<path d="M21.7001 12L19.3 14L28.5 24L19.3 34L21.7001 36L32.5 24L21.7001 12Z" fill="white"/>
</g>
<defs>
<filter id="filter0_b_397_11354" x="-9.5" y="-10" width="68" height="68" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="5"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_397_11354"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_397_11354" result="shape"/>
</filter>
</defs>
</svg>
',
$this->get_class_suffix( $context )
);
$alignment_class = isset( $attributes['layout']['verticalAlignment'] ) ? 'is-vertically-aligned-' . esc_attr( $attributes['layout']['verticalAlignment'] ) : '';
$position_class = 'wc-block-product-gallery-large-image-next-previous--' . $this->get_class_suffix( $context );
return strtr(
'<div class="wp-block-woocommerce-product-gallery-large-image-next-previous {alignment_class}">
<div class="wc-block-product-gallery-large-image-next-previous-container {position_class}">
{prev_button}
{next_button}
</div>
</div>',
array(
'{prev_button}' => $prev_button,
'{next_button}' => $next_button,
'{alignment_class}' => $alignment_class,
'{position_class}' => $position_class,
)
);
}
}
ProductGalleryPager.php 0000644 00000007114 15155156421 0011204 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductGalleryPager class.
*/
class ProductGalleryPager extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-gallery-pager';
/**
* It isn't necessary register block assets because it is a server side block.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context
*
* @return string[]
*/
protected function get_block_type_uses_context() {
return [ 'productGalleryClientId', 'pagerDisplayMode' ];
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
$pager_display_mode = $block->context['pagerDisplayMode'] ?? '';
$classname = $attributes['className'] ?? '';
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( sprintf( 'woocommerce %1$s', $classname ) ) ) );
$html = $this->render_pager( $pager_display_mode );
return sprintf(
'<div %1$s>
%2$s
</div>',
$wrapper_attributes,
$html
);
}
/**
* Renders the pager based on the given display mode.
*
* @param string $pager_display_mode The display mode for the pager. Possible values are 'dots', 'digits', and 'off'.
*
* @return string|null The rendered pager HTML, or null if the pager is disabled.
*/
private function render_pager( $pager_display_mode ) {
switch ( $pager_display_mode ) {
case 'dots':
return $this->render_dots_pager();
case 'digits':
return $this->render_digits_pager();
case 'off':
return null;
default:
return $this->render_dots_pager();
}
}
/**
* Renders the digits pager HTML.
*
* @return string The rendered digits pager HTML.
*/
private function render_digits_pager() {
return sprintf(
'<ul class="wp-block-woocommerce-product-gallery-pager__pager">
<li class="wp-block-woocommerce-product-gallery__pager-item is-active">1</li>
<li class="wp-block-woocommerce-product-gallery__pager-item">2</li>
<li class="wp-block-woocommerce-product-gallery__pager-item">3</li>
<li class="wp-block-woocommerce-product-gallery__pager-item">4</li>
</ul>'
);
}
/**
* Renders the dots pager HTML.
*
* @return string The rendered dots pager HTML.
*/
private function render_dots_pager() {
return sprintf(
'<ul class="wp-block-woocommerce-product-gallery-pager__pager">
<li class="wp-block-woocommerce-product-gallery__pager-item is-active">%1$s</li>
<li class="wp-block-woocommerce-product-gallery__pager-item">%2$s</li>
<li class="wp-block-woocommerce-product-gallery__pager-item">%2$s</li>
</ul>',
$this->get_selected_dot_icon(),
$this->get_dot_icon()
);
}
/**
* Returns the dot icon SVG code.
*
* @return string The dot icon SVG code.
*/
private function get_dot_icon() {
return sprintf(
'<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="6" cy="6" r="6" fill="black" fill-opacity="0.2"/>
</svg>'
);
}
/**
* Returns the selected dot icon SVG code.
*
* @return string The selected dot icon SVG code.
*/
private function get_selected_dot_icon() {
return sprintf(
'<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="6" cy="6" r="6" fill="black"/>
</svg>'
);
}
}
ProductGalleryThumbnails.php 0000644 00000005710 15155156421 0012254 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductGalleryLargeImage class.
*/
class ProductGalleryThumbnails extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-gallery-thumbnails';
/**
* It isn't necessary register block assets because it is a server side block.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context
*
* @return string[]
*/
protected function get_block_type_uses_context() {
return [ 'productGalleryClientId', 'postId', 'thumbnailsNumberOfThumbnails', 'thumbnailsPosition' ];
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( isset( $block->context['thumbnailsPosition'] ) && '' !== $block->context['thumbnailsPosition'] && 'off' !== $block->context['thumbnailsPosition'] ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
$product = wc_get_product( $post_id );
$post_thumbnail_id = $product->get_image_id();
$html = '';
if ( $product ) {
$attachment_ids = $product->get_gallery_image_ids();
if ( $attachment_ids && $post_thumbnail_id ) {
$html .= wc_get_gallery_image_html( $post_thumbnail_id, true );
$number_of_thumbnails = isset( $block->context['thumbnailsNumberOfThumbnails'] ) ? $block->context['thumbnailsNumberOfThumbnails'] : 3;
$thumbnails_count = 1;
foreach ( $attachment_ids as $attachment_id ) {
if ( $thumbnails_count >= $number_of_thumbnails ) {
break;
}
/**
* Filter the HTML markup for a single product image thumbnail in the gallery.
*
* @param string $thumbnail_html The HTML markup for the thumbnail.
* @param int $attachment_id The attachment ID of the thumbnail.
*
* @since 7.9.0
*/
$html .= apply_filters( 'woocommerce_single_product_image_thumbnail_html', wc_get_gallery_image_html( $attachment_id ), $attachment_id ); // phpcs:disable WordPress.XSS.EscapeOutput.OutputNotEscaped
$thumbnails_count++;
}
}
return sprintf(
'<div class="wc-block-components-product-gallery-thumbnails %1$s" style="%2$s">
%3$s
</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classes_and_styles['styles'] ),
$html
);
}
}
}
}
ProductImage.php 0000644 00000015310 15155156421 0007645 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductImage class.
*/
class ProductImage extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-image';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Get block supports. Shared with the frontend.
* IMPORTANT: If you change anything here, make sure to update the JS file too.
*
* @return array
*/
protected function get_block_type_supports() {
return array(
'__experimentalBorder' =>
array(
'radius' => true,
'__experimentalSkipSerialization' => true,
),
'typography' =>
array(
'fontSize' => true,
'__experimentalSkipSerialization' => true,
),
'spacing' =>
array(
'margin' => true,
'__experimentalSkipSerialization' => true,
),
'__experimentalSelector' => '.wc-block-components-product-image',
);
}
/**
* It is necessary to register and enqueues assets during the render phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Get the block's attributes.
*
* @param array $attributes Block attributes. Default empty array.
* @return array Block attributes merged with defaults.
*/
private function parse_attributes( $attributes ) {
// These should match what's set in JS `registerBlockType`.
$defaults = array(
'showProductLink' => true,
'showSaleBadge' => true,
'saleBadgeAlign' => 'right',
'imageSizing' => 'single',
'productId' => 'number',
'isDescendentOfQueryLoop' => 'false',
'scale' => 'cover',
);
return wp_parse_args( $attributes, $defaults );
}
/**
* Render on Sale Badge.
*
* @param \WC_Product $product Product object.
* @param array $attributes Attributes.
* @return string
*/
private function render_on_sale_badge( $product, $attributes ) {
if ( ! $product->is_on_sale() || false === $attributes['showSaleBadge'] ) {
return '';
}
$font_size = StyleAttributesUtils::get_font_size_class_and_style( $attributes );
$on_sale_badge = sprintf(
'
<div class="wc-block-components-product-sale-badge wc-block-components-product-sale-badge--align-%s wc-block-grid__product-onsale %s" style="%s">
<span aria-hidden="true">%s</span>
<span class="screen-reader-text">Product on sale</span>
</div>
',
esc_attr( $attributes['saleBadgeAlign'] ),
isset( $font_size['class'] ) ? esc_attr( $font_size['class'] ) : '',
isset( $font_size['style'] ) ? esc_attr( $font_size['style'] ) : '',
esc_html__( 'Sale', 'woocommerce' )
);
return $on_sale_badge;
}
/**
* Render anchor.
*
* @param \WC_Product $product Product object.
* @param string $on_sale_badge Return value from $render_image.
* @param string $product_image Return value from $render_on_sale_badge.
* @param array $attributes Attributes.
* @return string
*/
private function render_anchor( $product, $on_sale_badge, $product_image, $attributes ) {
$product_permalink = $product->get_permalink();
$pointer_events = false === $attributes['showProductLink'] ? 'pointer-events: none;' : '';
return sprintf(
'<a href="%1$s" style="%2$s">%3$s %4$s</a>',
$product_permalink,
$pointer_events,
$on_sale_badge,
$product_image
);
}
/**
* Render Image.
*
* @param \WC_Product $product Product object.
* @param array $attributes Parsed attributes.
* @return string
*/
private function render_image( $product, $attributes ) {
$image_size = 'single' === $attributes['imageSizing'] ? 'woocommerce_single' : 'woocommerce_thumbnail';
$image_style = 'max-width:none;';
if ( ! empty( $attributes['height'] ) ) {
$image_style .= sprintf( 'height:%s;', $attributes['height'] );
}
if ( ! empty( $attributes['width'] ) ) {
$image_style .= sprintf( 'width:%s;', $attributes['width'] );
}
if ( ! empty( $attributes['scale'] ) ) {
$image_style .= sprintf( 'object-fit:%s;', $attributes['scale'] );
}
if ( ! $product->get_image_id() ) {
// The alt text is left empty on purpose, as it's considered a decorative image.
// More can be found here: https://www.w3.org/WAI/tutorials/images/decorative/.
// Github discussion for a context: https://github.com/woocommerce/woocommerce-blocks/pull/7651#discussion_r1019560494.
return wc_placeholder_img(
$image_size,
array(
'alt' => '',
'style' => $image_style,
)
);
}
return $product->get_image(
$image_size,
array(
'alt' => $product->get_title(),
'data-testid' => 'product-image',
'style' => $image_style,
)
);
}
/**
* 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 = [] ) {
$this->asset_data_registry->add( 'isBlockThemeEnabled', wc_current_theme_is_fse_theme(), false );
}
/**
* Include and render the block
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$parsed_attributes = $this->parse_attributes( $attributes );
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
$product = wc_get_product( $post_id );
if ( $product ) {
return sprintf(
'<div class="wc-block-components-product-image wc-block-grid__product-image %1$s" style="%2$s">
%3$s
</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classes_and_styles['styles'] ),
$this->render_anchor(
$product,
$this->render_on_sale_badge( $product, $parsed_attributes ),
$this->render_image( $product, $parsed_attributes ),
$parsed_attributes
)
);
}
}
}
ProductImageGallery.php 0000644 00000003417 15155156421 0011172 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductImageGallery class.
*/
class ProductImageGallery extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-image-gallery';
/**
* It isn't necessary register block assets because it is a server side block.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context
*
* @return string[]
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
$post_id = $block->context['postId'];
if ( ! isset( $post_id ) ) {
return '';
}
global $product;
$previous_product = $product;
$product = wc_get_product( $post_id );
if ( ! $product instanceof \WC_Product ) {
$product = $previous_product;
return '';
}
if ( class_exists( 'WC_Frontend_Scripts' ) ) {
$frontend_scripts = new \WC_Frontend_Scripts();
$frontend_scripts::load_scripts();
}
ob_start();
woocommerce_show_product_sale_flash();
$sale_badge_html = ob_get_clean();
ob_start();
woocommerce_show_product_images();
$product_image_gallery_html = ob_get_clean();
$product = $previous_product;
$classname = $attributes['className'] ?? '';
return sprintf(
'<div class="wp-block-woocommerce-product-image-gallery %1$s">%2$s %3$s</div>',
esc_attr( $classname ),
$sale_badge_html,
$product_image_gallery_html
);
}
}
ProductNew.php 0000644 00000000700 15155156421 0007351 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductNew class.
*/
class ProductNew extends AbstractProductGrid {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-new';
/**
* Set args specific to this block
*
* @param array $query_args Query args.
*/
protected function set_block_query_args( &$query_args ) {
$query_args['orderby'] = 'date';
$query_args['order'] = 'DESC';
}
}
ProductOnSale.php 0000644 00000001371 15155156421 0010006 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductOnSale class.
*/
class ProductOnSale extends AbstractProductGrid {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-on-sale';
/**
* Set args specific to this block
*
* @param array $query_args Query args.
*/
protected function set_block_query_args( &$query_args ) {
$query_args['post__in'] = array_merge( array( 0 ), wc_get_product_ids_on_sale() );
}
/**
* Get block attributes.
*
* @return array
*/
protected function get_block_type_attributes() {
return array_merge(
parent::get_block_type_attributes(),
array(
'className' => $this->get_schema_string(),
'orderby' => $this->get_schema_orderby(),
)
);
}
}
ProductPrice.php 0000644 00000005451 15155156421 0007672 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductPrice class.
*/
class ProductPrice extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-price';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Get block supports. Shared with the frontend.
* IMPORTANT: If you change anything here, make sure to update the JS file too.
*
* @return array
*/
protected function get_block_type_supports() {
return array(
'color' =>
array(
'text' => true,
'background' => true,
'link' => false,
),
'typography' =>
array(
'fontSize' => true,
'__experimentalFontWeight' => true,
'__experimentalFontStyle' => true,
),
'__experimentalSelector' => '.wp-block-woocommerce-product-price .wc-block-components-product-price',
);
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
/**
* Overwrite parent method to prevent script registration.
*
* It is necessary to register and enqueues assets during the render
* phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
$product = wc_get_product( $post_id );
if ( $product ) {
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );
return sprintf(
'<div class="wp-block-woocommerce-product-price"><div class="wc-block-components-product-price wc-block-grid__product-price %1$s %2$s" style="%3$s">
%4$s
</div></div>',
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
esc_attr( $styles_and_classes['classes'] ),
esc_attr( $styles_and_classes['styles'] ?? '' ),
$product->get_price_html()
);
}
}
}
ProductQuery.php 0000644 00000066167 15155156421 0007750 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use WP_Query;
use Automattic\WooCommerce\Blocks\Utils\Utils;
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_tax_query
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
/**
* ProductQuery class.
*/
class ProductQuery extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-query';
/**
* The Block with its attributes before it gets rendered
*
* @var array
*/
protected $parsed_block;
/**
* Orderby options not natively supported by WordPress REST API
*
* @var array
*/
protected $custom_order_opts = array( 'popularity', 'rating' );
/**
* All the query args related to the filter by attributes block.
*
* @var array
*/
protected $attributes_filter_query_args = array();
/** This is a feature flag to enable the custom inherit Global Query implementation.
* This is not intended to be a permanent feature flag, but rather a temporary.
* It is also necessary to enable this feature flag on the PHP side: `assets/js/blocks/product-query/utils.tsx:83`.
* https://github.com/woocommerce/woocommerce-blocks/pull/7382
*
* @var boolean
*/
protected $is_custom_inherit_global_query_implementation_enabled = false;
/**
* All query args from WP_Query.
*
* @var array
*/
protected $valid_query_vars;
/**
* Initialize this block type.
*
* - Hook into WP lifecycle.
* - Register the block with WordPress.
* - Hook into pre_render_block to update the query.
*/
protected function initialize() {
add_filter( 'query_vars', array( $this, 'set_query_vars' ) );
parent::initialize();
add_filter(
'pre_render_block',
array( $this, 'update_query' ),
10,
2
);
add_filter(
'render_block',
array( $this, 'enqueue_styles' ),
10,
2
);
add_filter( 'rest_product_query', array( $this, 'update_rest_query' ), 10, 2 );
add_filter( 'rest_product_collection_params', array( $this, 'extend_rest_query_allowed_params' ), 10, 1 );
}
/**
* Post Template support for grid view was introduced in Gutenberg 16 / WordPress 6.3
* Fixed in:
* - https://github.com/woocommerce/woocommerce-blocks/pull/9916
* - https://github.com/woocommerce/woocommerce-blocks/pull/10360
*/
private function check_if_post_template_has_support_for_grid_view() {
if ( Utils::wp_version_compare( '6.3', '>=' ) ) {
return true;
}
if ( is_plugin_active( 'gutenberg/gutenberg.php' ) ) {
$gutenberg_version = '';
if ( defined( 'GUTENBERG_VERSION' ) ) {
$gutenberg_version = GUTENBERG_VERSION;
}
if ( ! $gutenberg_version ) {
$gutenberg_data = get_file_data(
WP_PLUGIN_DIR . '/gutenberg/gutenberg.php',
array( 'Version' => 'Version' )
);
$gutenberg_version = $gutenberg_data['Version'];
}
return version_compare( $gutenberg_version, '16.0', '>=' );
}
return false;
}
/**
* 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 );
$post_template_has_support_for_grid_view = $this->check_if_post_template_has_support_for_grid_view();
$this->asset_data_registry->add(
'postTemplateHasSupportForGridView',
$post_template_has_support_for_grid_view
);
// The `loop_shop_per_page` filter can be found in WC_Query::product_query().
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
$this->asset_data_registry->add( 'loopShopPerPage', apply_filters( 'loop_shop_per_page', wc_get_default_products_per_row() * wc_get_default_product_rows_per_page() ), true );
}
/**
* Check if a given block
*
* @param array $parsed_block The block being rendered.
* @return boolean
*/
public static function is_woocommerce_variation( $parsed_block ) {
return isset( $parsed_block['attrs']['namespace'] )
&& substr( $parsed_block['attrs']['namespace'], 0, 11 ) === 'woocommerce';
}
/**
* Enqueues the variation styles when rendering the Product Query variation.
*
* @param string $block_content The block content.
* @param array $block The full block, including name and attributes.
*
* @return string The block content.
*/
public function enqueue_styles( string $block_content, array $block ) {
if ( 'core/query' === $block['blockName'] && self::is_woocommerce_variation( $block ) ) {
wp_enqueue_style( 'wc-blocks-style-product-query' );
}
return $block_content;
}
/**
* Update the query for the product query block.
*
* @param string|null $pre_render The pre-rendered content. Default null.
* @param array $parsed_block The block being rendered.
*/
public function update_query( $pre_render, $parsed_block ) {
if ( 'core/query' !== $parsed_block['blockName'] ) {
return;
}
$this->parsed_block = $parsed_block;
if ( self::is_woocommerce_variation( $parsed_block ) ) {
// Set this so that our product filters can detect if it's a PHP template.
$this->asset_data_registry->add( 'hasFilterableProducts', true, true );
$this->asset_data_registry->add( 'isRenderingPhpTemplate', true, true );
add_filter(
'query_loop_block_query_vars',
array( $this, 'build_query' ),
10,
1
);
}
}
/**
* Merge tax_queries from various queries.
*
* @param array ...$queries Query arrays to be merged.
* @return array
*/
private function merge_tax_queries( ...$queries ) {
$tax_query = [];
foreach ( $queries as $query ) {
if ( ! empty( $query['tax_query'] ) ) {
$tax_query = array_merge( $tax_query, $query['tax_query'] );
}
}
return [ 'tax_query' => $tax_query ];
}
/**
* Update the query for the product query block in Editor.
*
* @param array $args Query args.
* @param WP_REST_Request $request Request.
*/
public function update_rest_query( $args, $request ): array {
$woo_attributes = $request->get_param( '__woocommerceAttributes' );
$is_valid_attributes = is_array( $woo_attributes );
$orderby = $request->get_param( 'orderby' );
$woo_stock_status = $request->get_param( '__woocommerceStockStatus' );
$on_sale = $request->get_param( '__woocommerceOnSale' ) === 'true';
$on_sale_query = $on_sale ? $this->get_on_sale_products_query() : [];
$orderby_query = $orderby ? $this->get_custom_orderby_query( $orderby ) : [];
$attributes_query = $is_valid_attributes ? $this->get_product_attributes_query( $woo_attributes ) : [];
$stock_query = is_array( $woo_stock_status ) ? $this->get_stock_status_query( $woo_stock_status ) : [];
$visibility_query = is_array( $woo_stock_status ) ? $this->get_product_visibility_query( $stock_query ) : [];
$tax_query = $is_valid_attributes ? $this->merge_tax_queries( $attributes_query, $visibility_query ) : [];
return array_merge( $args, $on_sale_query, $orderby_query, $stock_query, $tax_query );
}
/**
* Return a custom query based on attributes, filters and global WP_Query.
*
* @param WP_Query $query The WordPress Query.
* @return array
*/
public function build_query( $query ) {
$parsed_block = $this->parsed_block;
if ( ! $this->is_woocommerce_variation( $parsed_block ) ) {
return $query;
}
$common_query_values = array(
'meta_query' => array(),
'posts_per_page' => $query['posts_per_page'],
'orderby' => $query['orderby'],
'order' => $query['order'],
'offset' => $query['offset'],
'post__in' => array(),
'post_status' => 'publish',
'post_type' => 'product',
'tax_query' => array(),
);
$handpicked_products = isset( $parsed_block['attrs']['query']['include'] ) ?
$parsed_block['attrs']['query']['include'] : $common_query_values['post__in'];
$merged_query = $this->merge_queries(
$common_query_values,
$this->get_global_query( $parsed_block ),
$this->get_custom_orderby_query( $query['orderby'] ),
$this->get_queries_by_custom_attributes( $parsed_block ),
$this->get_queries_by_applied_filters(),
$this->get_filter_by_taxonomies_query( $query ),
$this->get_filter_by_keyword_query( $query )
);
return $this->filter_query_to_only_include_ids( $merged_query, $handpicked_products );
}
/**
* Merge in the first parameter the keys "post_in", "meta_query" and "tax_query" of the second parameter.
*
* @param array[] ...$queries Query arrays to be merged.
* @return array
*/
private function merge_queries( ...$queries ) {
$merged_query = array_reduce(
$queries,
function( $acc, $query ) {
if ( ! is_array( $query ) ) {
return $acc;
}
// If the $query doesn't contain any valid query keys, we unpack/spread it then merge.
if ( empty( array_intersect( $this->get_valid_query_vars(), array_keys( $query ) ) ) ) {
return $this->merge_queries( $acc, ...array_values( $query ) );
}
return $this->array_merge_recursive_replace_non_array_properties( $acc, $query );
},
array()
);
/**
* If there are duplicated items in post__in, it means that we need to
* use the intersection of the results, which in this case, are the
* duplicated items.
*/
if (
! empty( $merged_query['post__in'] ) &&
count( $merged_query['post__in'] ) > count( array_unique( $merged_query['post__in'] ) )
) {
$merged_query['post__in'] = array_unique(
array_diff(
$merged_query['post__in'],
array_unique( $merged_query['post__in'] )
)
);
}
return $merged_query;
}
/**
* Extends allowed `collection_params` for the REST API
*
* By itself, the REST API doesn't accept custom `orderby` values,
* even if they are supported by a custom post type.
*
* @param array $params A list of allowed `orderby` values.
*
* @return array
*/
public function extend_rest_query_allowed_params( $params ) {
$original_enum = isset( $params['orderby']['enum'] ) ? $params['orderby']['enum'] : array();
$params['orderby']['enum'] = array_merge( $original_enum, $this->custom_order_opts );
return $params;
}
/**
* Return a query for on sale products.
*
* @return array
*/
private function get_on_sale_products_query() {
return array(
'post__in' => wc_get_product_ids_on_sale(),
);
}
/**
* Return query params to support custom sort values
*
* @param string $orderby Sort order option.
*
* @return array
*/
private function get_custom_orderby_query( $orderby ) {
if ( ! in_array( $orderby, $this->custom_order_opts, true ) ) {
return array( 'orderby' => $orderby );
}
$meta_keys = array(
'popularity' => 'total_sales',
'rating' => '_wc_average_rating',
);
return array(
'meta_key' => $meta_keys[ $orderby ],
'orderby' => 'meta_value_num',
);
}
/**
* Apply the query only to a subset of products
*
* @param array $query The query.
* @param array $ids Array of selected product ids.
*
* @return array
*/
private function filter_query_to_only_include_ids( $query, $ids ) {
if ( ! empty( $ids ) ) {
$query['post__in'] = empty( $query['post__in'] ) ?
$ids : array_intersect( $ids, $query['post__in'] );
}
return $query;
}
/**
* Return the `tax_query` for the requested attributes
*
* @param array $attributes Attributes and their terms.
*
* @return array
*/
private function get_product_attributes_query( $attributes = array() ) {
$grouped_attributes = array_reduce(
$attributes,
function ( $carry, $item ) {
$taxonomy = sanitize_title( $item['taxonomy'] );
if ( ! key_exists( $taxonomy, $carry ) ) {
$carry[ $taxonomy ] = array(
'field' => 'term_id',
'operator' => 'IN',
'taxonomy' => $taxonomy,
'terms' => array( $item['termId'] ),
);
} else {
$carry[ $taxonomy ]['terms'][] = $item['termId'];
}
return $carry;
},
array()
);
return array(
'tax_query' => array_values( $grouped_attributes ),
);
}
/**
* Return a query for products depending on their stock status.
*
* @param array $stock_statii An array of acceptable stock statii.
* @return array
*/
private function get_stock_status_query( $stock_statii ) {
if ( ! is_array( $stock_statii ) ) {
return array();
}
$stock_status_options = array_keys( wc_get_product_stock_status_options() );
/**
* If all available stock status are selected, we don't need to add the
* meta query for stock status.
*/
if (
count( $stock_statii ) === count( $stock_status_options ) &&
array_diff( $stock_statii, $stock_status_options ) === array_diff( $stock_status_options, $stock_statii )
) {
return array();
}
/**
* If all stock statuses are selected except 'outofstock', we use the
* product visibility query to filter out out of stock products.
*
* @see get_product_visibility_query()
*/
$diff = array_diff( $stock_status_options, $stock_statii );
if ( count( $diff ) === 1 && in_array( 'outofstock', $diff, true ) ) {
return array();
}
return array(
'meta_query' => array(
array(
'key' => '_stock_status',
'value' => (array) $stock_statii,
'compare' => 'IN',
),
),
);
}
/**
* Return a query for product visibility depending on their stock status.
*
* @param array $stock_query Stock status query.
*
* @return array Tax query for product visibility.
*/
private function get_product_visibility_query( $stock_query ) {
$product_visibility_terms = wc_get_product_visibility_term_ids();
$product_visibility_not_in = array( is_search() ? $product_visibility_terms['exclude-from-search'] : $product_visibility_terms['exclude-from-catalog'] );
// Hide out of stock products.
if ( empty( $stock_query ) && 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
$product_visibility_not_in[] = $product_visibility_terms['outofstock'];
}
return array(
'tax_query' => array(
array(
'taxonomy' => 'product_visibility',
'field' => 'term_taxonomy_id',
'terms' => $product_visibility_not_in,
'operator' => 'NOT IN',
),
),
);
}
/**
* Set the query vars that are used by filter blocks.
*
* @return array
*/
private function get_query_vars_from_filter_blocks() {
$attributes_filter_query_args = array_reduce(
array_values( $this->get_filter_by_attributes_query_vars() ),
function( $acc, $array ) {
return array_merge( array_values( $array ), $acc );
},
array()
);
return array(
'price_filter_query_args' => array( PriceFilter::MIN_PRICE_QUERY_VAR, PriceFilter::MAX_PRICE_QUERY_VAR ),
'stock_filter_query_args' => array( StockFilter::STOCK_STATUS_QUERY_VAR ),
'attributes_filter_query_args' => $attributes_filter_query_args,
'rating_filter_query_args' => array( RatingFilter::RATING_QUERY_VAR ),
);
}
/**
* Set the query vars that are used by filter blocks.
*
* @param array $public_query_vars Public query vars.
* @return array
*/
public function set_query_vars( $public_query_vars ) {
$query_vars = $this->get_query_vars_from_filter_blocks();
return array_reduce(
array_values( $query_vars ),
function( $acc, $query_vars_filter_block ) {
return array_merge( $query_vars_filter_block, $acc );
},
$public_query_vars
);
}
/**
* Get all the query args related to the filter by attributes block.
*
* @return array
* [color] => Array
* (
* [filter] => filter_color
* [query_type] => query_type_color
* )
*
* [size] => Array
* (
* [filter] => filter_size
* [query_type] => query_type_size
* )
* )
*/
private function get_filter_by_attributes_query_vars() {
if ( ! empty( $this->attributes_filter_query_args ) ) {
return $this->attributes_filter_query_args;
}
$this->attributes_filter_query_args = array_reduce(
wc_get_attribute_taxonomies(),
function( $acc, $attribute ) {
$acc[ $attribute->attribute_name ] = array(
'filter' => AttributeFilter::FILTER_QUERY_VAR_PREFIX . $attribute->attribute_name,
'query_type' => AttributeFilter::QUERY_TYPE_QUERY_VAR_PREFIX . $attribute->attribute_name,
);
return $acc;
},
array()
);
return $this->attributes_filter_query_args;
}
/**
* Return queries that are generated by query args.
*
* @return array
*/
private function get_queries_by_applied_filters() {
return array(
'price_filter' => $this->get_filter_by_price_query(),
'attributes_filter' => $this->get_filter_by_attributes_query(),
'stock_status_filter' => $this->get_filter_by_stock_status_query(),
'rating_filter' => $this->get_filter_by_rating_query(),
);
}
/**
* Return queries that are generated by attributes
*
* @param array $parsed_block The Product Query that being rendered.
* @return array
*/
private function get_queries_by_custom_attributes( $parsed_block ) {
$query = $parsed_block['attrs']['query'];
$on_sale_enabled = isset( $query['__woocommerceOnSale'] ) && true === $query['__woocommerceOnSale'];
$attributes_query = isset( $query['__woocommerceAttributes'] ) ? $this->get_product_attributes_query( $query['__woocommerceAttributes'] ) : array();
$stock_query = isset( $query['__woocommerceStockStatus'] ) ? $this->get_stock_status_query( $query['__woocommerceStockStatus'] ) : array();
$visibility_query = $this->get_product_visibility_query( $stock_query );
return array(
'on_sale' => ( $on_sale_enabled ? $this->get_on_sale_products_query() : array() ),
'attributes' => $attributes_query,
'stock_status' => $stock_query,
'visibility' => $visibility_query,
);
}
/**
* Return a query that filters products by price.
*
* @return array
*/
private function get_filter_by_price_query() {
$min_price = get_query_var( PriceFilter::MIN_PRICE_QUERY_VAR );
$max_price = get_query_var( PriceFilter::MAX_PRICE_QUERY_VAR );
$max_price_query = empty( $max_price ) ? array() : [
'key' => '_price',
'value' => $max_price,
'compare' => '<',
'type' => 'numeric',
];
$min_price_query = empty( $min_price ) ? array() : [
'key' => '_price',
'value' => $min_price,
'compare' => '>=',
'type' => 'numeric',
];
if ( empty( $min_price_query ) && empty( $max_price_query ) ) {
return array();
}
return array(
'meta_query' => array(
array(
'relation' => 'AND',
$max_price_query,
$min_price_query,
),
),
);
}
/**
* Return a query that filters products by attributes.
*
* @return array
*/
private function get_filter_by_attributes_query() {
$attributes_filter_query_args = $this->get_filter_by_attributes_query_vars();
$queries = array_reduce(
$attributes_filter_query_args,
function( $acc, $query_args ) {
$attribute_name = $query_args['filter'];
$attribute_query_type = $query_args['query_type'];
$attribute_value = get_query_var( $attribute_name );
$attribute_query = get_query_var( $attribute_query_type );
if ( empty( $attribute_value ) ) {
return $acc;
}
// It is necessary explode the value because $attribute_value can be a string with multiple values (e.g. "red,blue").
$attribute_value = explode( ',', $attribute_value );
$acc[] = array(
'taxonomy' => str_replace( AttributeFilter::FILTER_QUERY_VAR_PREFIX, 'pa_', $attribute_name ),
'field' => 'slug',
'terms' => $attribute_value,
'operator' => 'and' === $attribute_query ? 'AND' : 'IN',
);
return $acc;
},
array()
);
if ( empty( $queries ) ) {
return array();
}
return array(
'tax_query' => array(
array(
'relation' => 'AND',
$queries,
),
),
);
}
/**
* Return a query that filters products by stock status.
*
* @return array
*/
private function get_filter_by_stock_status_query() {
$filter_stock_status_values = get_query_var( StockFilter::STOCK_STATUS_QUERY_VAR );
if ( empty( $filter_stock_status_values ) ) {
return array();
}
$filtered_stock_status_values = array_filter(
explode( ',', $filter_stock_status_values ),
function( $stock_status ) {
return in_array( $stock_status, StockFilter::get_stock_status_query_var_values(), true );
}
);
if ( empty( $filtered_stock_status_values ) ) {
return array();
}
return array(
// Ignoring the warning of not using meta queries.
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
array(
'key' => '_stock_status',
'value' => $filtered_stock_status_values,
'operator' => 'IN',
),
),
);
}
/**
* Return or initialize $valid_query_vars.
*
* @return array
*/
private function get_valid_query_vars() {
if ( ! empty( $this->valid_query_vars ) ) {
return $this->valid_query_vars;
}
$valid_query_vars = array_keys( ( new WP_Query() )->fill_query_vars( array() ) );
$this->valid_query_vars = array_merge(
$valid_query_vars,
// fill_query_vars doesn't include these vars so we need to add them manually.
array(
'date_query',
'exact',
'ignore_sticky_posts',
'lazy_load_term_meta',
'meta_compare_key',
'meta_compare',
'meta_query',
'meta_type_key',
'meta_type',
'nopaging',
'offset',
'order',
'orderby',
'page',
'post_type',
'posts_per_page',
'suppress_filters',
'tax_query',
)
);
return $this->valid_query_vars;
}
/**
* Merge two array recursively but replace the non-array values instead of
* merging them. The merging strategy:
*
* - If keys from merge array doesn't exist in the base array, create them.
* - For array items with numeric keys, we merge them as normal.
* - For array items with string keys:
*
* - If the value isn't array, we'll use the value comming from the merge array.
* $base = ['orderby' => 'date']
* $new = ['orderby' => 'meta_value_num']
* Result: ['orderby' => 'meta_value_num']
*
* - If the value is array, we'll use recursion to merge each key.
* $base = ['meta_query' => [
* [
* 'key' => '_stock_status',
* 'compare' => 'IN'
* 'value' => ['instock', 'onbackorder']
* ]
* ]]
* $new = ['meta_query' => [
* [
* 'relation' => 'AND',
* [...<max_price_query>],
* [...<min_price_query>],
* ]
* ]]
* Result: ['meta_query' => [
* [
* 'key' => '_stock_status',
* 'compare' => 'IN'
* 'value' => ['instock', 'onbackorder']
* ],
* [
* 'relation' => 'AND',
* [...<max_price_query>],
* [...<min_price_query>],
* ]
* ]]
*
* $base = ['post__in' => [1, 2, 3, 4, 5]]
* $new = ['post__in' => [3, 4, 5, 6, 7]]
* Result: ['post__in' => [1, 2, 3, 4, 5, 3, 4, 5, 6, 7]]
*
* @param array $base First array.
* @param array $new Second array.
*/
private function array_merge_recursive_replace_non_array_properties( $base, $new ) {
foreach ( $new as $key => $value ) {
if ( is_numeric( $key ) ) {
$base[] = $value;
} else {
if ( is_array( $value ) ) {
if ( ! isset( $base[ $key ] ) ) {
$base[ $key ] = array();
}
$base[ $key ] = $this->array_merge_recursive_replace_non_array_properties( $base[ $key ], $value );
} else {
$base[ $key ] = $value;
}
}
}
return $base;
}
/**
* Get product-related query variables from the global query.
*
* @param array $parsed_block The Product Query that being rendered.
*
* @return array
*/
private function get_global_query( $parsed_block ) {
if ( ! $this->is_custom_inherit_global_query_implementation_enabled ) {
return array();
}
global $wp_query;
$inherit_enabled = isset( $parsed_block['attrs']['query']['__woocommerceInherit'] ) && true === $parsed_block['attrs']['query']['__woocommerceInherit'];
if ( ! $inherit_enabled ) {
return array();
}
$query = array();
if ( isset( $wp_query->query_vars['taxonomy'] ) && isset( $wp_query->query_vars['term'] ) ) {
$query['tax_query'] = array(
array(
'taxonomy' => $wp_query->query_vars['taxonomy'],
'field' => 'slug',
'terms' => $wp_query->query_vars['term'],
),
);
}
if ( isset( $wp_query->query_vars['s'] ) ) {
$query['s'] = $wp_query->query_vars['s'];
}
return $query;
}
/**
* Return a query that filters products by rating.
*
* @return array
*/
private function get_filter_by_rating_query() {
$filter_rating_values = get_query_var( RatingFilter::RATING_QUERY_VAR );
if ( empty( $filter_rating_values ) ) {
return array();
}
$parsed_filter_rating_values = explode( ',', $filter_rating_values );
$product_visibility_terms = wc_get_product_visibility_term_ids();
if ( empty( $parsed_filter_rating_values ) || empty( $product_visibility_terms ) ) {
return array();
}
$rating_terms = array_map(
function( $rating ) use ( $product_visibility_terms ) {
return $product_visibility_terms[ 'rated-' . $rating ];
},
$parsed_filter_rating_values
);
return array(
'tax_query' => array(
array(
'field' => 'term_taxonomy_id',
'taxonomy' => 'product_visibility',
'terms' => $rating_terms,
'operator' => 'IN',
'rating_filter' => true,
),
),
);
}
/**
* Return a query to filter products by taxonomies (product categories, product tags, etc.)
*
* For example:
* User could provide "Product Categories" using "Filters" ToolsPanel available in Inspector Controls.
* We use this function to extract it's query from $tax_query.
*
* For example, this is how the query for product categories will look like in $tax_query array:
* Array
* (
* [taxonomy] => product_cat
* [terms] => Array
* (
* [0] => 36
* )
* )
*
* For product categories, taxonomy would be "product_tag"
*
* @param array $query WP_Query.
* @return array Query to filter products by taxonomies.
*/
private function get_filter_by_taxonomies_query( $query ): array {
if ( ! isset( $query['tax_query'] ) || ! is_array( $query['tax_query'] ) ) {
return [];
}
$tax_query = $query['tax_query'];
/**
* Get an array of taxonomy names associated with the "product" post type because
* we also want to include custom taxonomies associated with the "product" post type.
*/
$product_taxonomies = get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' );
$result = array_filter(
$tax_query,
function( $item ) use ( $product_taxonomies ) {
return isset( $item['taxonomy'] ) && in_array( $item['taxonomy'], $product_taxonomies, true );
}
);
return ! empty( $result ) ? [ 'tax_query' => $result ] : [];
}
/**
* Returns the keyword filter from the given query.
*
* @param WP_Query $query The query to extract the keyword filter from.
* @return array The keyword filter, or an empty array if none is found.
*/
private function get_filter_by_keyword_query( $query ): array {
if ( ! is_array( $query ) ) {
return [];
}
if ( isset( $query['s'] ) ) {
return [ 's' => $query['s'] ];
}
return [];
}
}
ProductRating.php 0000644 00000015212 15155156421 0010050 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductRating class.
*/
class ProductRating extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-rating';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Get block supports. Shared with the frontend.
* IMPORTANT: If you change anything here, make sure to update the JS file too.
*
* @return array
*/
protected function get_block_type_supports() {
return array(
'color' =>
array(
'text' => true,
'background' => false,
'link' => false,
'__experimentalSkipSerialization' => true,
),
'typography' =>
array(
'fontSize' => true,
'__experimentalSkipSerialization' => true,
),
'spacing' =>
array(
'margin' => true,
'padding' => true,
'__experimentalSkipSerialization' => true,
),
'__experimentalSelector' => '.wc-block-components-product-rating',
);
}
/**
* Get the block's attributes.
*
* @param array $attributes Block attributes. Default empty array.
* @return array Block attributes merged with defaults.
*/
private function parse_attributes( $attributes ) {
// These should match what's set in JS `registerBlockType`.
$defaults = array(
'productId' => 0,
'isDescendentOfQueryLoop' => false,
'textAlign' => '',
'isDescendentOfSingleProductBlock' => false,
'isDescendentOfSingleProductTemplate' => false,
);
return wp_parse_args( $attributes, $defaults );
}
/**
* Overwrite parent method to prevent script registration.
*
* It is necessary to register and enqueues assets during the render
* phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
}
/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$post_id = $block->context['postId'];
$product = wc_get_product( $post_id );
if ( $product && $product->get_review_count() > 0 ) {
$product_reviews_count = $product->get_review_count();
$product_rating = $product->get_average_rating();
$parsed_attributes = $this->parse_attributes( $attributes );
$is_descendent_of_single_product_block = $parsed_attributes['isDescendentOfSingleProductBlock'];
$is_descendent_of_single_product_template = $parsed_attributes['isDescendentOfSingleProductTemplate'];
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );
/**
* Filter the output from wc_get_rating_html.
*
* @param string $html Star rating markup. Default empty string.
* @param float $rating Rating being shown.
* @param int $count Total number of ratings.
* @return string
*/
$filter_rating_html = function( $html, $rating, $count ) use ( $post_id, $product_rating, $product_reviews_count, $is_descendent_of_single_product_block, $is_descendent_of_single_product_template ) {
$product_permalink = get_permalink( $post_id );
$reviews_count = $count;
$average_rating = $rating;
if ( $product_rating ) {
$average_rating = $product_rating;
}
if ( $product_reviews_count ) {
$reviews_count = $product_reviews_count;
}
if ( 0 < $average_rating || false === $product_permalink ) {
/* translators: %s: rating */
$label = sprintf( __( 'Rated %s out of 5', 'woocommerce' ), $average_rating );
$customer_reviews_count = sprintf(
/* translators: %s is referring to the total of reviews for a product */
_n(
'(%s customer review)',
'(%s customer reviews)',
$reviews_count,
'woocommerce'
),
esc_html( $reviews_count )
);
if ( $is_descendent_of_single_product_block ) {
$customer_reviews_count = '<a href="' . esc_url( $product_permalink ) . '#reviews">' . $customer_reviews_count . '</a>';
} elseif ( $is_descendent_of_single_product_template ) {
$customer_reviews_count = '<a class="woocommerce-review-link" rel="nofollow" href="#reviews">' . $customer_reviews_count . '</a>';
}
$reviews_count_html = sprintf( '<span class="wc-block-components-product-rating__reviews_count">%1$s</span>', $customer_reviews_count );
$html = sprintf(
'<div class="wc-block-components-product-rating__container">
<div class="wc-block-components-product-rating__stars wc-block-grid__product-rating__stars" role="img" aria-label="%1$s">
%2$s
</div>
%3$s
</div>
',
esc_attr( $label ),
wc_get_star_rating_html( $average_rating, $reviews_count ),
$is_descendent_of_single_product_block || $is_descendent_of_single_product_template ? $reviews_count_html : ''
);
} else {
$html = '';
}
return $html;
};
add_filter(
'woocommerce_product_get_rating_html',
$filter_rating_html,
10,
3
);
$rating_html = wc_get_rating_html( $product->get_average_rating() );
remove_filter(
'woocommerce_product_get_rating_html',
$filter_rating_html,
10
);
return sprintf(
'<div class="wc-block-components-product-rating wc-block-grid__product-rating %1$s %2$s" style="%3$s">
%4$s
</div>',
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
esc_attr( $styles_and_classes['classes'] ),
esc_attr( $styles_and_classes['styles'] ?? '' ),
$rating_html
);
}
return '';
}
}
ProductRatingCounter.php 0000644 00000014205 15155156421 0011411 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductRatingCounter class.
*/
class ProductRatingCounter extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-rating-counter';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Get block supports. Shared with the frontend.
* IMPORTANT: If you change anything here, make sure to update the JS file too.
*
* @return array
*/
protected function get_block_type_supports() {
return array(
'color' =>
array(
'text' => true,
'background' => false,
'link' => false,
'__experimentalSkipSerialization' => true,
),
'typography' =>
array(
'fontSize' => true,
'__experimentalSkipSerialization' => true,
),
'spacing' =>
array(
'margin' => true,
'padding' => true,
'__experimentalSkipSerialization' => true,
),
'__experimentalSelector' => '.wc-block-components-product-rating-counter',
);
}
/**
* Get the block's attributes.
*
* @param array $attributes Block attributes. Default empty array.
* @return array Block attributes merged with defaults.
*/
private function parse_attributes( $attributes ) {
// These should match what's set in JS `registerBlockType`.
$defaults = array(
'productId' => 0,
'isDescendentOfQueryLoop' => false,
'textAlign' => '',
'isDescendentOfSingleProductBlock' => false,
'isDescendentOfSingleProductTemplate' => false,
);
return wp_parse_args( $attributes, $defaults );
}
/**
* Overwrite parent method to prevent script registration.
*
* It is necessary to register and enqueues assets during the render
* phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$post_id = $block->context['postId'];
$product = wc_get_product( $post_id );
if ( $product && $product->get_review_count() > 0 ) {
$product_reviews_count = $product->get_review_count();
$product_rating = $product->get_average_rating();
$parsed_attributes = $this->parse_attributes( $attributes );
$is_descendent_of_single_product_block = $parsed_attributes['isDescendentOfSingleProductBlock'];
$is_descendent_of_single_product_template = $parsed_attributes['isDescendentOfSingleProductTemplate'];
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );
/**
* Filter the output from wc_get_rating_html.
*
* @param string $html Star rating markup. Default empty string.
* @param float $rating Rating being shown.
* @param int $count Total number of ratings.
* @return string
*/
$filter_rating_html = function( $html, $rating, $count ) use ( $post_id, $product_rating, $product_reviews_count, $is_descendent_of_single_product_block, $is_descendent_of_single_product_template ) {
$product_permalink = get_permalink( $post_id );
$reviews_count = $count;
$average_rating = $rating;
if ( $product_rating ) {
$average_rating = $product_rating;
}
if ( $product_reviews_count ) {
$reviews_count = $product_reviews_count;
}
if ( 0 < $average_rating || false === $product_permalink ) {
/* translators: %s: rating */
$customer_reviews_count = sprintf(
/* translators: %s is referring to the total of reviews for a product */
_n(
'(%s customer review)',
'(%s customer reviews)',
$reviews_count,
'woocommerce'
),
esc_html( $reviews_count )
);
if ( $is_descendent_of_single_product_block ) {
$customer_reviews_count = '<a href="' . esc_url( $product_permalink ) . '#reviews">' . $customer_reviews_count . '</a>';
} elseif ( $is_descendent_of_single_product_template ) {
$customer_reviews_count = '<a class="woocommerce-review-link" rel="nofollow" href="#reviews">' . $customer_reviews_count . '</a>';
}
$html = sprintf(
'<div class="wc-block-components-product-rating-counter__container">
<span class="wc-block-components-product-rating-counter__reviews_count">%1$s</span>
</div>
',
$customer_reviews_count
);
} else {
$html = '';
}
return $html;
};
add_filter(
'woocommerce_product_get_rating_html',
$filter_rating_html,
10,
3
);
$rating_html = wc_get_rating_html( $product->get_average_rating() );
remove_filter(
'woocommerce_product_get_rating_html',
$filter_rating_html,
10
);
return sprintf(
'<div class="wc-block-components-product-rating-counter wc-block-grid__product-rating-counter %1$s %2$s" style="%3$s">
%4$s
</div>',
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
esc_attr( $styles_and_classes['classes'] ),
esc_attr( $styles_and_classes['styles'] ?? '' ),
$rating_html
);
}
return '';
}
}
ProductRatingStars.php 0000644 00000010652 15155156421 0011070 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductRatingStars class.
*/
class ProductRatingStars extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-rating-stars';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Get block supports. Shared with the frontend.
* IMPORTANT: If you change anything here, make sure to update the JS file too.
*
* @return array
*/
protected function get_block_type_supports() {
return array(
'color' =>
array(
'text' => true,
'background' => false,
'link' => false,
'__experimentalSkipSerialization' => true,
),
'typography' =>
array(
'fontSize' => true,
'__experimentalSkipSerialization' => true,
),
'spacing' =>
array(
'margin' => true,
'padding' => true,
'__experimentalSkipSerialization' => true,
),
'__experimentalSelector' => '.wc-block-components-product-rating-stars',
);
}
/**
* Overwrite parent method to prevent script registration.
*
* It is necessary to register and enqueues assets during the render
* phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$post_id = $block->context['postId'];
$product = wc_get_product( $post_id );
if ( $product ) {
$product_reviews_count = $product->get_review_count();
$product_rating = $product->get_average_rating();
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );
/**
* Filter the output from wc_get_rating_html.
*
* @param string $html Star rating markup. Default empty string.
* @param float $rating Rating being shown.
* @param int $count Total number of ratings.
* @return string
*/
$filter_rating_html = function( $html, $rating, $count ) use ( $product_rating, $product_reviews_count ) {
$product_permalink = get_permalink();
$reviews_count = $count;
$average_rating = $rating;
if ( $product_rating ) {
$average_rating = $product_rating;
}
if ( $product_reviews_count ) {
$reviews_count = $product_reviews_count;
}
if ( 0 < $average_rating || false === $product_permalink ) {
/* translators: %s: rating */
$label = sprintf( __( 'Rated %s out of 5', 'woocommerce' ), $average_rating );
$html = sprintf(
'<div class="wc-block-components-product-rating-stars__container">
<div class="wc-block-components-product-rating__stars wc-block-grid__product-rating__stars" role="img" aria-label="%1$s">
%2$s
</div>
</div>
',
esc_attr( $label ),
wc_get_star_rating_html( $average_rating, $reviews_count )
);
} else {
$html = '';
}
return $html;
};
add_filter(
'woocommerce_product_get_rating_html',
$filter_rating_html,
10,
3
);
$rating_html = wc_get_rating_html( $product->get_average_rating() );
remove_filter(
'woocommerce_product_get_rating_html',
$filter_rating_html,
10
);
return sprintf(
'<div class="wc-block-components-product-rating wc-block-grid__product-rating %1$s %2$s" style="%3$s">
%4$s
</div>',
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
esc_attr( $styles_and_classes['classes'] ),
esc_attr( $styles_and_classes['styles'] ?? '' ),
$rating_html
);
}
}
}
ProductResultsCount.php 0000644 00000002560 15155156421 0011300 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductResultsCount class.
*/
class ProductResultsCount extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-results-count';
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
ob_start();
woocommerce_result_count();
$product_results_count = ob_get_clean();
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$classname = isset( $attributes['className'] ) ? $attributes['className'] : '';
return sprintf(
'<div class="woocommerce wc-block-product-results-count wp-block-woocommerce-product-results-count %1$s %2$s" style="%3$s">%4$s</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classname ),
esc_attr( $classes_and_styles['styles'] ),
$product_results_count
);
}
}
ProductReviews.php 0000644 00000001750 15155156421 0010252 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductReviews class.
*/
class ProductReviews extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-reviews';
/**
* It isn't necessary register block assets because it is a server side block.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
ob_start();
rewind_posts();
while ( have_posts() ) {
the_post();
comments_template();
}
$reviews = ob_get_clean();
$classname = $attributes['className'] ?? '';
return sprintf(
'<div class="wp-block-woocommerce-product-reviews %1$s">
%2$s
</div>',
esc_attr( $classname ),
$reviews
);
}
}
ProductSKU.php 0000644 00000003631 15155156421 0007270 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductSKU class.
*/
class ProductSKU extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-sku';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Overwrite parent method to prevent script registration.
*
* It is necessary to register and enqueues assets during the render
* phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$post_id = $block->context['postId'];
$product = wc_get_product( $post_id );
if ( ! $product ) {
return '';
}
$product_sku = $product->get_sku();
if ( ! $product_sku ) {
return '';
}
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
return sprintf(
'<div class="wc-block-components-product-sku wc-block-grid__product-sku wp-block-woocommerce-product-sku product_meta %1$s" style="%2$s">
SKU:
<strong class="sku">%3$s</strong>
</div>',
esc_attr( $styles_and_classes['classes'] ),
esc_attr( $styles_and_classes['styles'] ?? '' ),
$product_sku
);
}
}
ProductSaleBadge.php 0000644 00000006672 15155156421 0010445 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductSaleBadge class.
*/
class ProductSaleBadge extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-sale-badge';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Get block attributes.
*
* @return array
*/
protected function get_block_type_supports() {
return array(
'color' =>
array(
'gradients' => true,
'background' => true,
'link' => true,
),
'typography' =>
array(
'fontSize' => true,
'lineHeight' => true,
'__experimentalFontFamily' => true,
'__experimentalFontWeight' => true,
'__experimentalFontStyle' => true,
'__experimentalLetterSpacing' => true,
'__experimentalTextTransform' => true,
'__experimentalTextDecoration' => true,
'__experimentalSkipSerialization' => true,
),
'__experimentalBorder' =>
array(
'color' => true,
'radius' => true,
'width' => true,
),
'spacing' =>
array(
'margin' => true,
'padding' => true,
'__experimentalSkipSerialization' => true,
),
'__experimentalSelector' => '.wc-block-components-product-sale-badge',
);
}
/**
* Overwrite parent method to prevent script registration.
*
* It is necessary to register and enqueues assets during the render
* phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$post_id = $block->context['postId'];
$product = wc_get_product( $post_id );
$is_on_sale = $product->is_on_sale();
if ( ! $is_on_sale ) {
return null;
}
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$classname = isset( $attributes['className'] ) ? $attributes['className'] : '';
$align = isset( $attributes['align'] ) ? $attributes['align'] : '';
$output = '<div class="wp-block-woocommerce-product-sale-badge ' . esc_attr( $classname ) . '">';
$output .= sprintf( '<div class="wc-block-components-product-sale-badge %1$s wc-block-components-product-sale-badge--align-%2$s" style="%3$s">', esc_attr( $classes_and_styles['classes'] ), esc_attr( $align ), esc_attr( $classes_and_styles['styles'] ) );
$output .= '<span class="wc-block-components-product-sale-badge__text" aria-hidden="true">' . __( 'Sale', 'woocommerce' ) . '</span>';
$output .= '<span class="screen-reader-text">'
. __( 'Product on sale', 'woocommerce' )
. '</span>';
$output .= '</div></div>';
return $output;
}
}
ProductSearch.php 0000644 00000010771 15155156421 0010036 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductSearch class.
*/
class ProductSearch extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-search';
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
* @return null
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
static $instance_id = 0;
$attributes = wp_parse_args(
$attributes,
array(
'hasLabel' => true,
'align' => '',
'className' => '',
'label' => __( 'Search', 'woocommerce' ),
'placeholder' => __( 'Search products…', 'woocommerce' ),
)
);
/**
* Product Search event.
*
* Listens for product search form submission, and on submission fires a WP Hook named
* `experimental__woocommerce_blocks-product-search`. This can be used by tracking extensions such as Google
* Analytics to track searches.
*/
$this->asset_api->add_inline_script(
'wp-hooks',
"
window.addEventListener( 'DOMContentLoaded', () => {
const forms = document.querySelectorAll( '.wc-block-product-search form' );
for ( const form of forms ) {
form.addEventListener( 'submit', ( event ) => {
const field = form.querySelector( '.wc-block-product-search__field' );
if ( field && field.value ) {
wp.hooks.doAction( 'experimental__woocommerce_blocks-product-search', { event: event, searchTerm: field.value } );
}
} );
}
} );
",
'after'
);
$input_id = 'wc-block-search__input-' . ( ++$instance_id );
$wrapper_attributes = get_block_wrapper_attributes(
array(
'class' => implode(
' ',
array_filter(
[
'wc-block-product-search',
$attributes['align'] ? 'align' . $attributes['align'] : '',
]
)
),
)
);
$label_markup = $attributes['hasLabel'] ? sprintf(
'<label for="%s" class="wc-block-product-search__label">%s</label>',
esc_attr( $input_id ),
esc_html( $attributes['label'] )
) : sprintf(
'<label for="%s" class="wc-block-product-search__label screen-reader-text">%s</label>',
esc_attr( $input_id ),
esc_html( $attributes['label'] )
);
$input_markup = sprintf(
'<input type="search" id="%s" class="wc-block-product-search__field" placeholder="%s" name="s" />',
esc_attr( $input_id ),
esc_attr( $attributes['placeholder'] )
);
$button_markup = sprintf(
'<button type="submit" class="wc-block-product-search__button" aria-label="%s">
<svg aria-hidden="true" role="img" focusable="false" class="dashicon dashicons-arrow-right-alt2" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<path d="M6 15l5-5-5-5 1-2 7 7-7 7z" />
</svg>
</button>',
esc_attr__( 'Search', 'woocommerce' )
);
$field_markup = '
<div class="wc-block-product-search__fields">
' . $input_markup . $button_markup . '
<input type="hidden" name="post_type" value="product" />
</div>
';
return sprintf(
'<div %s><form role="search" method="get" action="%s">%s</form></div>',
$wrapper_attributes,
esc_url( home_url( '/' ) ),
$label_markup . $field_markup
);
}
/**
* 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 );
$gutenberg_version = '';
if ( is_plugin_active( 'gutenberg/gutenberg.php' ) ) {
if ( defined( 'GUTENBERG_VERSION' ) ) {
$gutenberg_version = GUTENBERG_VERSION;
}
if ( ! $gutenberg_version ) {
$gutenberg_data = get_file_data(
WP_PLUGIN_DIR . '/gutenberg/gutenberg.php',
array( 'Version' => 'Version' )
);
$gutenberg_version = $gutenberg_data['Version'];
}
}
$this->asset_data_registry->add(
'isBlockVariationAvailable',
version_compare( get_bloginfo( 'version' ), '6.1', '>=' ) || version_compare( $gutenberg_version, '13.4', '>=' )
);
}
}
ProductStockIndicator.php 0000644 00000007411 15155156421 0011546 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductStockIndicator class.
*/
class ProductStockIndicator extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-stock-indicator';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Register script and style assets for the block type before it is registered.
*
* This registers the scripts; it does not enqueue them.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Get stock text based on stock. For example:
* - In stock
* - Out of stock
* - Available on backorder
* - 2 left in stock
*
* @param [bool] $is_in_stock Whether the product is in stock.
* @param [bool] $is_low_stock Whether the product is low in stock.
* @param [int|null] $low_stock_amount The amount of stock that is considered low.
* @param [bool] $is_on_backorder Whether the product is on backorder.
* @return string Stock text.
*/
protected static function getTextBasedOnStock( $is_in_stock, $is_low_stock, $low_stock_amount, $is_on_backorder ) {
if ( $is_low_stock ) {
return sprintf(
/* translators: %d is number of items in stock for product */
__( '%d left in stock', 'woocommerce' ),
$low_stock_amount
);
} elseif ( $is_on_backorder ) {
return __( 'Available on backorder', 'woocommerce' );
} elseif ( $is_in_stock ) {
return __( 'In stock', 'woocommerce' );
} else {
return __( 'Out of stock', 'woocommerce' );
}
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$post_id = $block->context['postId'];
$product = wc_get_product( $post_id );
$is_in_stock = $product->is_in_stock();
$is_on_backorder = $product->is_on_backorder();
$low_stock_amount = $product->get_low_stock_amount();
$total_stock = $product->get_stock_quantity();
$is_low_stock = $low_stock_amount && $total_stock <= $low_stock_amount;
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$classnames = isset( $classes_and_styles['classes'] ) ? ' ' . $classes_and_styles['classes'] . ' ' : '';
$classnames .= isset( $attributes['className'] ) ? ' ' . $attributes['className'] . ' ' : '';
$classnames .= ! $is_in_stock ? ' wc-block-components-product-stock-indicator--out-of-stock ' : '';
$classnames .= $is_in_stock ? ' wc-block-components-product-stock-indicator--in-stock ' : '';
$classnames .= $is_low_stock ? ' wc-block-components-product-stock-indicator--low-stock ' : '';
$classnames .= $is_on_backorder ? ' wc-block-components-product-stock-indicator--available-on-backorder ' : '';
$output = '';
$output .= '<div class="wc-block-components-product-stock-indicator wp-block-woocommerce-product-stock-indicator ' . esc_attr( $classnames ) . '"';
$output .= isset( $classes_and_styles['styles'] ) ? ' style="' . esc_attr( $classes_and_styles['styles'] ) . '"' : '';
$output .= '>';
$output .= wp_kses_post( self::getTextBasedOnStock( $is_in_stock, $is_low_stock, $low_stock_amount, $is_on_backorder ) );
$output .= '</div>';
return $output;
}
}
ProductSummary.php 0000644 00000002102 15155156421 0010253 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductSummary class.
*/
class ProductSummary extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-summary';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Get block supports. Shared with the frontend.
* IMPORTANT: If you change anything here, make sure to update the JS file too.
*
* @return array
*/
protected function get_block_type_supports() {
return array(
'color' =>
array(
'link' => true,
'background' => false,
'text' => true,
),
'typography' =>
array(
'fontSize' => true,
),
'__experimentalSelector' => '.wc-block-components-product-summary',
);
}
/**
* Register script and style assets for the block type before it is registered.
*
* This registers the scripts; it does not enqueue them.
*/
protected function register_block_type_assets() {
$this->register_chunk_translations( [ $this->block_name ] );
}
}
ProductTag.php 0000644 00000004370 15155156421 0007342 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductTag class.
*/
class ProductTag extends AbstractProductGrid {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-tag';
/**
* Set args specific to this block.
*
* @param array $query_args Query args.
*/
protected function set_block_query_args( &$query_args ) {
if ( ! empty( $this->attributes['tags'] ) ) {
$query_args['tax_query'][] = array(
'taxonomy' => 'product_tag',
'terms' => array_map( 'absint', $this->attributes['tags'] ),
'field' => 'term_id',
'operator' => isset( $this->attributes['tagOperator'] ) && 'any' === $this->attributes['tagOperator'] ? 'IN' : 'AND',
);
}
}
/**
* Get block attributes.
*
* @return array
*/
protected function get_block_type_attributes() {
return array(
'className' => $this->get_schema_string(),
'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ),
'contentVisibility' => $this->get_schema_content_visibility(),
'align' => $this->get_schema_align(),
'alignButtons' => $this->get_schema_boolean( false ),
'orderby' => $this->get_schema_orderby(),
'tags' => $this->get_schema_list_ids(),
'tagOperator' => array(
'type' => 'string',
'default' => 'any',
),
'isPreview' => $this->get_schema_boolean( false ),
'stockStatus' => array_keys( wc_get_product_stock_status_options() ),
);
}
/**
* 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 );
$tag_count = wp_count_terms( 'product_tag' );
$this->asset_data_registry->add( 'hasTags', $tag_count > 0, true );
$this->asset_data_registry->add( 'limitTags', $tag_count > 100, true );
}
}
ProductTemplate.php 0000644 00000010310 15155156421 0010371 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use WP_Block;
use WP_Query;
/**
* ProductTemplate class.
*/
class ProductTemplate extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-template';
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string | void Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
$page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
// phpcs:ignore WordPress.Security.NonceVerification
$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];
// Use global query if needed.
$use_global_query = ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] );
if ( $use_global_query ) {
global $wp_query;
$query = clone $wp_query;
} else {
$query_args = build_query_vars_from_query_block( $block, $page );
$query = new WP_Query( $query_args );
}
if ( ! $query->have_posts() ) {
return '';
}
if ( $this->block_core_post_template_uses_featured_image( $block->inner_blocks ) ) {
update_post_thumbnail_cache( $query );
}
$classnames = '';
if ( isset( $block->context['displayLayout'] ) && isset( $block->context['query'] ) ) {
if ( isset( $block->context['displayLayout']['type'] ) && 'flex' === $block->context['displayLayout']['type'] ) {
$classnames = "is-flex-container columns-{$block->context['displayLayout']['columns']}";
}
}
if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
$classnames .= ' has-link-color';
}
$classnames .= ' wc-block-product-template';
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( $classnames ) ) );
$content = '';
while ( $query->have_posts() ) {
$query->the_post();
// Get an instance of the current Post Template block.
$block_instance = $block->parsed_block;
// Set the block name to one that does not correspond to an existing registered block.
// This ensures that for the inner instances of the Post Template block, we do not render any block supports.
$block_instance['blockName'] = 'core/null';
// Render the inner blocks of the Post Template block with `dynamic` set to `false` to prevent calling
// `render_callback` and ensure that no wrapper markup is included.
$block_content = (
new WP_Block(
$block_instance,
array(
'postType' => get_post_type(),
'postId' => get_the_ID(),
)
)
)->render( array( 'dynamic' => false ) );
// Wrap the render inner blocks in a `li` element with the appropriate post classes.
$post_classes = implode( ' ', get_post_class( 'wc-block-product' ) );
$content .= '<li data-wc-key="product-item-' . get_the_ID() . '" class="' . esc_attr( $post_classes ) . '">' . $block_content . '</li>';
}
/*
* Use this function to restore the context of the template tags
* from a secondary query loop back to the main query loop.
* Since we use two custom loops, it's safest to always restore.
*/
wp_reset_postdata();
return sprintf(
'<ul %1$s>%2$s</ul>',
$wrapper_attributes,
$content
);
}
/**
* Determines whether a block list contains a block that uses the featured image.
*
* @param WP_Block_List $inner_blocks Inner block instance.
*
* @return bool Whether the block list contains a block that uses the featured image.
*/
protected function block_core_post_template_uses_featured_image( $inner_blocks ) {
foreach ( $inner_blocks as $block ) {
if ( 'core/post-featured-image' === $block->name ) {
return true;
}
if (
'core/cover' === $block->name &&
! empty( $block->attributes['useFeaturedImage'] )
) {
return true;
}
if ( $block->inner_blocks && block_core_post_template_uses_featured_image( $block->inner_blocks ) ) {
return true;
}
}
return false;
}
}
ProductTitle.php 0000644 00000003112 15155156421 0007701 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductTitle class.
*/
class ProductTitle extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-title';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Get block supports. Shared with the frontend.
* IMPORTANT: If you change anything here, make sure to update the JS file too.
*
* @return array
*/
protected function get_block_type_supports() {
return array(
'color' =>
array(
'gradients' => true,
'background' => true,
'link' => false,
'text' => true,
'__experimentalSkipSerialization' => true,
),
'typography' =>
array(
'fontSize' => true,
'lineHeight' => true,
'__experimentalFontWeight' => true,
'__experimentalTextTransform' => true,
'__experimentalFontFamily' => true,
),
'spacing' =>
array(
'margin' => true,
'__experimentalSkipSerialization' => true,
),
'__experimentalSelector' => '.wc-block-components-product-title',
);
}
/**
* Register script and style assets for the block type before it is registered.
*
* This registers the scripts; it does not enqueue them.
*/
protected function register_block_type_assets() {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
}
}
ProductTopRated.php 0000644 00000000650 15155156421 0010346 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductTopRated class.
*/
class ProductTopRated extends AbstractProductGrid {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-top-rated';
/**
* Force orderby to rating.
*
* @param array $query_args Query args.
*/
protected function set_block_query_args( &$query_args ) {
$query_args['orderby'] = 'rating';
}
}
ProductsByAttribute.php 0000644 00000004070 15155156421 0011245 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ProductsByAttribute class.
*/
class ProductsByAttribute extends AbstractProductGrid {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'products-by-attribute';
/**
* Set args specific to this block
*
* @param array $query_args Query args.
*/
protected function set_block_query_args( &$query_args ) {
if ( ! empty( $this->attributes['attributes'] ) ) {
$taxonomy = sanitize_title( $this->attributes['attributes'][0]['attr_slug'] );
$terms = wp_list_pluck( $this->attributes['attributes'], 'id' );
$query_args['tax_query'][] = array(
'taxonomy' => $taxonomy,
'terms' => array_map( 'absint', $terms ),
'field' => 'term_id',
'operator' => 'all' === $this->attributes['attrOperator'] ? 'AND' : 'IN',
);
}
}
/**
* Get block attributes.
*
* @return array
*/
protected function get_block_type_attributes() {
return array(
'align' => $this->get_schema_align(),
'alignButtons' => $this->get_schema_boolean( false ),
'attributes' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'id' => array(
'type' => 'number',
),
'attr_slug' => array(
'type' => 'string',
),
),
),
'default' => array(),
),
'attrOperator' => array(
'type' => 'string',
'default' => 'any',
),
'className' => $this->get_schema_string(),
'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
'contentVisibility' => $this->get_schema_content_visibility(),
'orderby' => $this->get_schema_orderby(),
'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ),
'isPreview' => $this->get_schema_boolean( false ),
'stockStatus' => array(
'type' => 'array',
'default' => array_keys( wc_get_product_stock_status_options() ),
),
);
}
}
RatingFilter.php 0000644 00000001300 15155156421 0007646 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* PriceFilter class.
*/
class RatingFilter extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'rating-filter';
const RATING_QUERY_VAR = 'rating_filter';
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Get the frontend style handle for this block type.
*
* @return string[]
*/
protected function get_block_type_style() {
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
}
}
RelatedProducts.php 0000644 00000010433 15155156421 0010367 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* RelatedProducts class.
*/
class RelatedProducts extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'related-products';
/**
* The Block with its attributes before it gets rendered
*
* @var array
*/
protected $parsed_block;
/**
* Initialize this block type.
*
* - Hook into WP lifecycle.
* - Register the block with WordPress.
* - Hook into pre_render_block to update the query.
*/
protected function initialize() {
parent::initialize();
add_filter(
'pre_render_block',
array( $this, 'update_query' ),
10,
2
);
add_filter(
'render_block',
array( $this, 'render_block' ),
10,
2
);
}
/**
* It isn't necessary register block assets because it is a server side block.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
/**
* Update the query for the product query block.
*
* @param string|null $pre_render The pre-rendered content. Default null.
* @param array $parsed_block The block being rendered.
*/
public function update_query( $pre_render, $parsed_block ) {
if ( 'core/query' !== $parsed_block['blockName'] ) {
return;
}
$this->parsed_block = $parsed_block;
if ( ProductQuery::is_woocommerce_variation( $parsed_block ) && 'woocommerce/related-products' === $parsed_block['attrs']['namespace'] ) {
// Set this so that our product filters can detect if it's a PHP template.
add_filter(
'query_loop_block_query_vars',
array( $this, 'build_query' ),
10,
1
);
}
}
/**
* Return a custom query based on attributes, filters and global WP_Query.
*
* @param WP_Query $query The WordPress Query.
* @return array
*/
public function build_query( $query ) {
$parsed_block = $this->parsed_block;
if ( ! $this->is_related_products_block( $parsed_block ) ) {
return $query;
}
$related_products_ids = $this->get_related_products_ids( $query['posts_per_page'] );
if ( count( $related_products_ids ) < 1 ) {
return array();
}
return array(
'post_type' => 'product',
'post__in' => $related_products_ids,
'post_status' => 'publish',
'posts_per_page' => $query['posts_per_page'],
);
}
/**
* If there are no related products, return an empty string.
*
* @param string $content The block content.
* @param array $block The block.
*
* @return string The block content.
*/
public function render_block( string $content, array $block ) {
if ( ! $this->is_related_products_block( $block ) ) {
return $content;
}
// If there are no related products, render nothing.
$related_products_ids = $this->get_related_products_ids();
if ( count( $related_products_ids ) < 1 ) {
return '';
}
return $content;
}
/**
* Determines whether the block is a related products block.
*
* @param array $block The block.
*
* @return bool Whether the block is a related products block.
*/
private function is_related_products_block( $block ) {
if ( ProductQuery::is_woocommerce_variation( $block ) && isset( $block['attrs']['namespace'] ) && 'woocommerce/related-products' === $block['attrs']['namespace'] ) {
return true;
}
return false;
}
/**
* Get related products ids.
* The logic is copied from the core function woocommerce_related_products. https://github.com/woocommerce/woocommerce/blob/ca49caabcba84ce9f60a03c6d3534ec14b350b80/plugins/woocommerce/includes/wc-template-functions.php/#L2039-L2074
*
* @param number $product_per_page Products per page.
* @return array Products ids.
*/
private function get_related_products_ids( $product_per_page = 5 ) {
global $post;
$product = wc_get_product( $post->ID );
$related_products = array_filter( array_map( 'wc_get_product', wc_get_related_products( $product->get_id(), $product_per_page, $product->get_upsell_ids() ) ), 'wc_products_array_filter_visible' );
$related_products = wc_products_array_orderby( $related_products, 'rand', 'desc' );
$related_product_ids = array_map(
function( $product ) {
return $product->get_id();
},
$related_products
);
return $related_product_ids;
}
}
ReviewsByCategory.php 0000644 00000002507 15155156421 0010703 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ReviewsByCategory class.
*/
class ReviewsByCategory extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'reviews-by-category';
/**
* Get the frontend script handle for this block type.
*
* @see $this->register_block_type()
* @param string $key Data to get, or default to everything.
* @return array|string
*/
protected function get_block_type_script( $key = null ) {
$script = [
'handle' => 'wc-reviews-block-frontend',
'path' => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ),
'dependencies' => [],
];
return $key ? $script[ $key ] : $script;
}
/**
* 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( 'reviewRatingsEnabled', wc_review_ratings_enabled(), true );
$this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ), true );
}
}
ReviewsByProduct.php 0000644 00000002504 15155156421 0010543 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* ReviewsByProduct class.
*/
class ReviewsByProduct extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'reviews-by-product';
/**
* Get the frontend script handle for this block type.
*
* @see $this->register_block_type()
* @param string $key Data to get, or default to everything.
* @return array|string
*/
protected function get_block_type_script( $key = null ) {
$script = [
'handle' => 'wc-reviews-block-frontend',
'path' => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ),
'dependencies' => [],
];
return $key ? $script[ $key ] : $script;
}
/**
* 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( 'reviewRatingsEnabled', wc_review_ratings_enabled(), true );
$this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ), true );
}
}
SingleProduct.php 0000644 00000013322 15155156421 0010045 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* SingleProduct class.
*/
class SingleProduct extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'single-product';
/**
* Product ID of the current product to be displayed in the Single Product block.
* This is used to replace the global post for the Single Product inner blocks.
*
* @var int
*/
protected $product_id = 0;
/**
* Single Product inner blocks names.
* This is used to map all the inner blocks for a Single Product block.
*
* @var array
*/
protected $single_product_inner_blocks_names = [];
/**
* Initialize the block and Hook into the `render_block_context` filter
* to update the context with the correct data.
*
* @var string
*/
protected function initialize() {
parent::initialize();
add_filter( 'render_block_context', [ $this, 'update_context' ], 10, 3 );
add_filter( 'render_block_core/post-excerpt', [ $this, 'restore_global_post' ], 10, 3 );
add_filter( 'render_block_core/post-title', [ $this, 'restore_global_post' ], 10, 3 );
}
/**
* Restore the global post variable right before generating the render output for the post title and/or post excerpt blocks.
*
* This is required due to the changes made via the replace_post_for_single_product_inner_block method.
* It is a temporary fix to ensure these blocks work as expected until Gutenberg versions 15.2 and 15.6 are part of the core of WordPress.
*
* @see https://github.com/WordPress/gutenberg/pull/48001
* @see https://github.com/WordPress/gutenberg/pull/49495
*
* @param string $block_content The block content.
* @param array $parsed_block The full block, including name and attributes.
* @param \WP_Block $block_instance The block instance.
*
* @return mixed
*/
public function restore_global_post( $block_content, $parsed_block, $block_instance ) {
if ( isset( $block_instance->context['singleProduct'] ) && $block_instance->context['singleProduct'] ) {
wp_reset_postdata();
}
return $block_content;
}
/**
* Render the Single Product block
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
$html = sprintf(
'<div class="woocommerce">
%1$s
</div>',
$content
);
return $html;
}
/**
* Update the context by injecting the correct post data
* for each one of the Single Product inner blocks.
*
* @param array $context Block context.
* @param array $block Block attributes.
* @param WP_Block $parent_block Block instance.
*
* @return array Updated block context.
*/
public function update_context( $context, $block, $parent_block ) {
if ( 'woocommerce/single-product' === $block['blockName']
&& isset( $block['attrs']['productId'] ) ) {
$this->product_id = $block['attrs']['productId'];
$this->single_product_inner_blocks_names = array_reverse(
$this->extract_single_product_inner_block_names( $block )
);
}
$this->replace_post_for_single_product_inner_block( $block, $context );
return $context;
}
/**
* Extract the inner block names for the Single Product block. This way it's possible
* to map all the inner blocks for a Single Product block and manipulate the data as needed.
*
* @param array $block The Single Product block or its inner blocks.
* @param array $result Array of inner block names.
*
* @return array Array containing all the inner block names of a Single Product block.
*/
protected function extract_single_product_inner_block_names( $block, &$result = [] ) {
if ( isset( $block['blockName'] ) ) {
$result[] = $block['blockName'];
}
if ( isset( $block['innerBlocks'] ) ) {
foreach ( $block['innerBlocks'] as $inner_block ) {
$this->extract_single_product_inner_block_names( $inner_block, $result );
}
}
return $result;
}
/**
* Replace the global post for the Single Product inner blocks and reset it after.
*
* This is needed because some of the inner blocks may use the global post
* instead of fetching the product through the `productId` attribute, so even if the
* `productId` is passed to the inner block, it will still use the global post.
*
* @param array $block Block attributes.
* @param array $context Block context.
*/
protected function replace_post_for_single_product_inner_block( $block, &$context ) {
if ( $this->single_product_inner_blocks_names ) {
$block_name = array_pop( $this->single_product_inner_blocks_names );
if ( $block_name === $block['blockName'] ) {
/**
* This is a temporary fix to ensure the Post Title and Excerpt blocks work as expected
* until Gutenberg versions 15.2 and 15.6 are included in the core of WordPress.
*
* Important: the original post data is restored in the restore_global_post method.
*
* @see https://github.com/WordPress/gutenberg/pull/48001
* @see https://github.com/WordPress/gutenberg/pull/49495
*/
if ( 'core/post-excerpt' === $block_name || 'core/post-title' === $block_name ) {
global $post;
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$post = get_post( $this->product_id );
if ( $post instanceof \WP_Post ) {
setup_postdata( $post );
}
}
$context['postId'] = $this->product_id;
$context['singleProduct'] = true;
}
}
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*
* @return null This block has no frontend script.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
}
StockFilter.php 0000644 00000002546 15155156421 0007522 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* AttributeFilter class.
*/
class StockFilter extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'stock-filter';
const STOCK_STATUS_QUERY_VAR = 'filter_stock_status';
/**
* Extra data passed through from server to client for block.
*
* @param array $stock_statuses Any stock statuses 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 $stock_statuses = [] ) {
parent::enqueue_data( $stock_statuses );
$this->asset_data_registry->add( 'stockStatusOptions', wc_get_product_stock_status_options(), true );
$this->asset_data_registry->add( 'hideOutOfStockItems', 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ), true );
}
/**
* Get Stock status query variables values.
*/
public static function get_stock_status_query_var_values() {
return array_keys( wc_get_product_stock_status_options() );
}
/**
* Get the frontend style handle for this block type.
*
* @return string[]
*/
protected function get_block_type_style() {
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
}
}
StoreNotices.php 0000644 00000003012 15155156421 0007677 0 ustar 00 <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* StoreNotices class.
*/
class StoreNotices extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'store-notices';
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string | void Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
ob_start();
woocommerce_output_all_notices();
$notices = ob_get_clean();
if ( ! $notices ) {
return;
}
$classname = isset( $attributes['className'] ) ? $attributes['className'] : '';
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
if ( isset( $attributes['align'] ) ) {
$classname .= " align{$attributes['align']}";
}
return sprintf(
'<div class="woocommerce wc-block-store-notices %1$s %2$s">%3$s</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classname ),
wc_kses_notice( $notices )
);
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
}