first commit
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
|
||||
/**
|
||||
* Assets class.
|
||||
*
|
||||
* @deprecated 5.0.0 This class will be removed in a future release. This has been replaced by AssetsController.
|
||||
* @internal
|
||||
*/
|
||||
class Assets {
|
||||
|
||||
/**
|
||||
* Initialize class features on init.
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function init() {
|
||||
_deprecated_function( 'Assets::init', '5.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block scripts & styles.
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function register_assets() {
|
||||
_deprecated_function( 'Assets::register_assets', '5.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the vendors style file. We need to do it after the other files
|
||||
* because we need to check if `wp-edit-post` has been enqueued.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function enqueue_scripts() {
|
||||
_deprecated_function( 'Assets::enqueue_scripts', '5.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add body classes.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
* @param array $classes Array of CSS classnames.
|
||||
* @return array Modified array of CSS classnames.
|
||||
*/
|
||||
public static function add_theme_body_class( $classes = [] ) {
|
||||
_deprecated_function( 'Assets::add_theme_body_class', '5.0.0' );
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add theme class to admin body.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
* @param array $classes String with the CSS classnames.
|
||||
* @return array Modified string of CSS classnames.
|
||||
*/
|
||||
public static function add_theme_admin_body_class( $classes = '' ) {
|
||||
_deprecated_function( 'Assets::add_theme_admin_body_class', '5.0.0' );
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a redirect field to the login form so blocks can redirect users after login.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function redirect_to_field() {
|
||||
_deprecated_function( 'Assets::redirect_to_field', '5.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a block script in the frontend.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @since 2.6.0 Changed $name to $script_name and added $handle argument.
|
||||
* @since 2.9.0 Made it so scripts are not loaded in admin pages.
|
||||
* @deprecated 4.5.0 Block types register the scripts themselves.
|
||||
*
|
||||
* @param string $script_name Name of the script used to identify the file inside build folder.
|
||||
* @param string $handle Optional. Provided if the handle should be different than the script name. `wc-` prefix automatically added.
|
||||
* @param array $dependencies Optional. An array of registered script handles this script depends on. Default empty array.
|
||||
*/
|
||||
public static function register_block_script( $script_name, $handle = '', $dependencies = [] ) {
|
||||
_deprecated_function( 'register_block_script', '4.5.0' );
|
||||
$asset_api = Package::container()->get( AssetApi::class );
|
||||
$asset_api->register_block_script( $script_name, $handle, $dependencies );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Assets;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Exception;
|
||||
/**
|
||||
* The Api class provides an interface to various asset registration helpers.
|
||||
*
|
||||
* Contains asset api methods
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Api {
|
||||
/**
|
||||
* Stores inline scripts already enqueued.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $inline_scripts = [];
|
||||
|
||||
/**
|
||||
* Reference to the Package instance
|
||||
*
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
/**
|
||||
* Constructor for class
|
||||
*
|
||||
* @param Package $package An instance of Package.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file modified time as a cache buster if we're in dev mode.
|
||||
*
|
||||
* @param string $file Local path to the file (relative to the plugin
|
||||
* directory).
|
||||
* @return string The cache buster value to use for the given file.
|
||||
*/
|
||||
protected function get_file_version( $file ) {
|
||||
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $this->package->get_path() . $file ) ) {
|
||||
return filemtime( $this->package->get_path( trim( $file, '/' ) ) );
|
||||
}
|
||||
return $this->package->get_version();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the url to an asset for this plugin.
|
||||
*
|
||||
* @param string $relative_path An optional relative path appended to the
|
||||
* returned url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_asset_url( $relative_path = '' ) {
|
||||
return $this->package->get_url( $relative_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to a block's metadata
|
||||
*
|
||||
* @param string $block_name The block to get metadata for.
|
||||
* @param string $path Optional. The path to the metadata file inside the 'build' folder.
|
||||
*
|
||||
* @return string|boolean False if metadata file is not found for the block.
|
||||
*/
|
||||
public function get_block_metadata_path( $block_name, $path = '' ) {
|
||||
$path_to_metadata_from_plugin_root = $this->package->get_path( 'build/' . $path . $block_name . '/block.json' );
|
||||
if ( ! file_exists( $path_to_metadata_from_plugin_root ) ) {
|
||||
return false;
|
||||
}
|
||||
return $path_to_metadata_from_plugin_root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get src, version and dependencies given a script relative src.
|
||||
*
|
||||
* @param string $relative_src Relative src to the script.
|
||||
* @param array $dependencies Optional. An array of registered script handles this script depends on. Default empty array.
|
||||
*
|
||||
* @return array src, version and dependencies of the script.
|
||||
*/
|
||||
public function get_script_data( $relative_src, $dependencies = [] ) {
|
||||
$src = '';
|
||||
$version = '1';
|
||||
|
||||
if ( $relative_src ) {
|
||||
$src = $this->get_asset_url( $relative_src );
|
||||
$asset_path = $this->package->get_path(
|
||||
str_replace( '.js', '.asset.php', $relative_src )
|
||||
);
|
||||
|
||||
if ( file_exists( $asset_path ) ) {
|
||||
$asset = require $asset_path;
|
||||
$dependencies = isset( $asset['dependencies'] ) ? array_merge( $asset['dependencies'], $dependencies ) : $dependencies;
|
||||
$version = ! empty( $asset['version'] ) ? $asset['version'] : $this->get_file_version( $relative_src );
|
||||
} else {
|
||||
$version = $this->get_file_version( $relative_src );
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'src' => $src,
|
||||
'version' => $version,
|
||||
'dependencies' => $dependencies,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a script according to `wp_register_script`, adding the correct prefix, and additionally loading translations.
|
||||
*
|
||||
* When creating script assets, the following rules should be followed:
|
||||
* 1. All asset handles should have a `wc-` prefix.
|
||||
* 2. If the asset handle is for a Block (in editor context) use the `-block` suffix.
|
||||
* 3. If the asset handle is for a Block (in frontend context) use the `-block-frontend` suffix.
|
||||
* 4. If the asset is for any other script being consumed or enqueued by the blocks plugin, use the `wc-blocks-` prefix.
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @throws Exception If the registered script has a dependency on itself.
|
||||
*
|
||||
* @param string $handle Unique name of the script.
|
||||
* @param string $relative_src Relative url for the script to the path from plugin root.
|
||||
* @param array $dependencies Optional. An array of registered script handles this script depends on. Default empty array.
|
||||
* @param bool $has_i18n Optional. Whether to add a script translation call to this file. Default: true.
|
||||
*/
|
||||
public function register_script( $handle, $relative_src, $dependencies = [], $has_i18n = true ) {
|
||||
$script_data = $this->get_script_data( $relative_src, $dependencies );
|
||||
|
||||
if ( in_array( $handle, $script_data['dependencies'], true ) ) {
|
||||
if ( $this->package->feature()->is_development_environment() ) {
|
||||
$dependencies = array_diff( $script_data['dependencies'], [ $handle ] );
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function() use ( $handle ) {
|
||||
echo '<div class="error"><p>';
|
||||
/* translators: %s file handle name. */
|
||||
printf( esc_html__( 'Script with handle %s had a dependency on itself which has been removed. This is an indicator that your JS code has a circular dependency that can cause bugs.', 'woocommerce' ), esc_html( $handle ) );
|
||||
echo '</p></div>';
|
||||
}
|
||||
);
|
||||
} else {
|
||||
throw new Exception( sprintf( 'Script with handle %s had a dependency on itself. This is an indicator that your JS code has a circular dependency that can cause bugs.', $handle ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the list of script dependencies.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param array $dependencies The list of script dependencies.
|
||||
* @param string $handle The script's handle.
|
||||
* @return array
|
||||
*/
|
||||
$script_dependencies = apply_filters( 'woocommerce_blocks_register_script_dependencies', $script_data['dependencies'], $handle );
|
||||
|
||||
wp_register_script( $handle, $script_data['src'], $script_dependencies, $script_data['version'], true );
|
||||
|
||||
if ( $has_i18n && function_exists( 'wp_set_script_translations' ) ) {
|
||||
wp_set_script_translations( $handle, 'woocommerce', $this->package->get_path( 'languages' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a style according to `wp_register_style`.
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @since 2.6.0 Change src to be relative source.
|
||||
*
|
||||
* @param string $handle Name of the stylesheet. Should be unique.
|
||||
* @param string $relative_src Relative source of the stylesheet to the plugin path.
|
||||
* @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
|
||||
* @param string $media Optional. The media for which this stylesheet has been defined. Default 'all'. Accepts media types like
|
||||
* 'all', 'print' and 'screen', or media queries like '(orientation: portrait)' and '(max-width: 640px)'.
|
||||
*/
|
||||
public function register_style( $handle, $relative_src, $deps = [], $media = 'all' ) {
|
||||
$filename = str_replace( plugins_url( '/', __DIR__ ), '', $relative_src );
|
||||
$src = $this->get_asset_url( $relative_src );
|
||||
$ver = $this->get_file_version( $filename );
|
||||
wp_register_style( $handle, $src, $deps, $ver, $media );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate asset path for current builds.
|
||||
*
|
||||
* @param string $filename Filename for asset path (without extension).
|
||||
* @param string $type File type (.css or .js).
|
||||
* @return string The generated path.
|
||||
*/
|
||||
public function get_block_asset_build_path( $filename, $type = 'js' ) {
|
||||
return "build/$filename.$type";
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an inline script, once.
|
||||
*
|
||||
* @param string $handle Script handle.
|
||||
* @param string $script Script contents.
|
||||
*/
|
||||
public function add_inline_script( $handle, $script ) {
|
||||
if ( ! empty( $this->inline_scripts[ $handle ] ) && in_array( $script, $this->inline_scripts[ $handle ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_add_inline_script( $handle, $script );
|
||||
|
||||
if ( isset( $this->inline_scripts[ $handle ] ) ) {
|
||||
$this->inline_scripts[ $handle ][] = $script;
|
||||
} else {
|
||||
$this->inline_scripts[ $handle ] = array( $script );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Assets;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class instance for registering data used on the current view session by
|
||||
* assets.
|
||||
*
|
||||
* Holds data registered for output on the current view session when
|
||||
* `wc-settings` is enqueued( directly or via dependency )
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class AssetDataRegistry {
|
||||
/**
|
||||
* Contains registered data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data = [];
|
||||
|
||||
/**
|
||||
* Contains preloaded API data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $preloaded_api_requests = [];
|
||||
|
||||
/**
|
||||
* Lazy data is an array of closures that will be invoked just before
|
||||
* asset data is generated for the enqueued script.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $lazy_data = [];
|
||||
|
||||
/**
|
||||
* Asset handle for registered data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $handle = 'wc-settings';
|
||||
|
||||
/**
|
||||
* Asset API interface for various asset registration.
|
||||
*
|
||||
* @var API
|
||||
*/
|
||||
private $api;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Api $asset_api Asset API interface for various asset registration.
|
||||
*/
|
||||
public function __construct( Api $asset_api ) {
|
||||
$this->api = $asset_api;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into WP asset registration for enqueueing asset data.
|
||||
*/
|
||||
protected function init() {
|
||||
if ( $this->is_site_editor() ) {
|
||||
add_action( 'enqueue_block_editor_assets', array( $this, 'register_data_script' ) );
|
||||
} else {
|
||||
add_action( 'init', array( $this, 'register_data_script' ) );
|
||||
}
|
||||
add_action( 'wp_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 2 );
|
||||
add_action( 'admin_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current URL is the Site Editor.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_site_editor() {
|
||||
$url_path = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
|
||||
return str_contains( $url_path, 'site-editor.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes core data via the wcSettings global. This data is shared throughout the client.
|
||||
*
|
||||
* Settings that are used by various components or multiple blocks should be added here. Note, that settings here are
|
||||
* global so be sure not to add anything heavy if possible.
|
||||
*
|
||||
* @return array An array containing core data.
|
||||
*/
|
||||
protected function get_core_data() {
|
||||
return [
|
||||
'adminUrl' => admin_url(),
|
||||
'countries' => WC()->countries->get_countries(),
|
||||
'currency' => $this->get_currency_data(),
|
||||
'currentUserId' => get_current_user_id(),
|
||||
'currentUserIsAdmin' => current_user_can( 'manage_woocommerce' ),
|
||||
'homeUrl' => esc_url( home_url( '/' ) ),
|
||||
'locale' => $this->get_locale_data(),
|
||||
'dashboardUrl' => wc_get_account_endpoint_url( 'dashboard' ),
|
||||
'orderStatuses' => $this->get_order_statuses(),
|
||||
'placeholderImgSrc' => wc_placeholder_img_src(),
|
||||
'productsSettings' => $this->get_products_settings(),
|
||||
'siteTitle' => get_bloginfo( 'name' ),
|
||||
'storePages' => $this->get_store_pages(),
|
||||
'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ),
|
||||
'wcVersion' => defined( 'WC_VERSION' ) ? WC_VERSION : '',
|
||||
'wpLoginUrl' => wp_login_url(),
|
||||
'wpVersion' => get_bloginfo( 'version' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get currency data to include in settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_currency_data() {
|
||||
$currency = get_woocommerce_currency();
|
||||
|
||||
return [
|
||||
'code' => $currency,
|
||||
'precision' => wc_get_price_decimals(),
|
||||
'symbol' => html_entity_decode( get_woocommerce_currency_symbol( $currency ) ),
|
||||
'symbolPosition' => get_option( 'woocommerce_currency_pos' ),
|
||||
'decimalSeparator' => wc_get_price_decimal_separator(),
|
||||
'thousandSeparator' => wc_get_price_thousand_separator(),
|
||||
'priceFormat' => html_entity_decode( get_woocommerce_price_format() ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get locale data to include in settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_locale_data() {
|
||||
global $wp_locale;
|
||||
|
||||
return [
|
||||
'siteLocale' => get_locale(),
|
||||
'userLocale' => get_user_locale(),
|
||||
'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get store pages to include in settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_store_pages() {
|
||||
$store_pages = [
|
||||
'myaccount' => wc_get_page_id( 'myaccount' ),
|
||||
'shop' => wc_get_page_id( 'shop' ),
|
||||
'cart' => wc_get_page_id( 'cart' ),
|
||||
'checkout' => wc_get_page_id( 'checkout' ),
|
||||
'privacy' => wc_privacy_policy_page_id(),
|
||||
'terms' => wc_terms_and_conditions_page_id(),
|
||||
];
|
||||
|
||||
if ( is_callable( '_prime_post_caches' ) ) {
|
||||
_prime_post_caches( array_values( $store_pages ), false, false );
|
||||
}
|
||||
|
||||
return array_map(
|
||||
[ $this, 'format_page_resource' ],
|
||||
$store_pages
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product related settings.
|
||||
*
|
||||
* Note: For the time being we are exposing only the settings that are used by blocks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_products_settings() {
|
||||
return [
|
||||
'cartRedirectAfterAdd' => get_option( 'woocommerce_cart_redirect_after_add' ) === 'yes',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a page object into a standard array of data.
|
||||
*
|
||||
* @param WP_Post|int $page Page object or ID.
|
||||
* @return array
|
||||
*/
|
||||
protected function format_page_resource( $page ) {
|
||||
if ( is_numeric( $page ) && $page > 0 ) {
|
||||
$page = get_post( $page );
|
||||
}
|
||||
if ( ! is_a( $page, '\WP_Post' ) || 'publish' !== $page->post_status ) {
|
||||
return [
|
||||
'id' => 0,
|
||||
'title' => '',
|
||||
'permalink' => false,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'id' => $page->ID,
|
||||
'title' => $page->post_title,
|
||||
'permalink' => get_permalink( $page->ID ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns block-related data for enqueued wc-settings script.
|
||||
* Format order statuses by removing a leading 'wc-' if present.
|
||||
*
|
||||
* @return array formatted statuses.
|
||||
*/
|
||||
protected function get_order_statuses() {
|
||||
$formatted_statuses = array();
|
||||
foreach ( wc_get_order_statuses() as $key => $value ) {
|
||||
$formatted_key = preg_replace( '/^wc-/', '', $key );
|
||||
$formatted_statuses[ $formatted_key ] = $value;
|
||||
}
|
||||
return $formatted_statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for on demand initialization of asset data and registering it with
|
||||
* the internal data registry.
|
||||
*
|
||||
* Note: core data will overwrite any externally registered data via the api.
|
||||
*/
|
||||
protected function initialize_core_data() {
|
||||
/**
|
||||
* Filters the array of shared settings.
|
||||
*
|
||||
* Low level hook for registration of new data late in the cycle. This is deprecated.
|
||||
* Instead, use the data api:
|
||||
*
|
||||
* ```php
|
||||
* Automattic\WooCommerce\Blocks\Package::container()->get( Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry::class )->add( $key, $value )
|
||||
* ```
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @deprecated
|
||||
* @param array $data Settings data.
|
||||
* @return array
|
||||
*/
|
||||
$settings = apply_filters( 'woocommerce_shared_settings', $this->data );
|
||||
|
||||
// Surface a deprecation warning in the error console.
|
||||
if ( has_filter( 'woocommerce_shared_settings' ) ) {
|
||||
$error_handle = 'deprecated-shared-settings-error';
|
||||
$error_message = '`woocommerce_shared_settings` filter in Blocks is deprecated. See https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/docs/contributors/block-assets.md';
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter,WordPress.WP.EnqueuedResourceParameters.MissingVersion
|
||||
wp_register_script( $error_handle, '' );
|
||||
wp_enqueue_script( $error_handle );
|
||||
wp_add_inline_script(
|
||||
$error_handle,
|
||||
sprintf( 'console.warn( "%s" );', $error_message )
|
||||
);
|
||||
}
|
||||
|
||||
// note this WILL wipe any data already registered to these keys because they are protected.
|
||||
$this->data = array_replace_recursive( $settings, $this->get_core_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through each registered lazy data callback and adds the returned
|
||||
* value to the data array.
|
||||
*
|
||||
* This method is executed right before preparing the data for printing to
|
||||
* the rendered screen.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function execute_lazy_data() {
|
||||
foreach ( $this->lazy_data as $key => $callback ) {
|
||||
$this->data[ $key ] = $callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes private registered data to child classes.
|
||||
*
|
||||
* @return array The registered data on the private data property
|
||||
*/
|
||||
protected function get() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows checking whether a key exists.
|
||||
*
|
||||
* @param string $key The key to check if exists.
|
||||
* @return bool Whether the key exists in the current data registry.
|
||||
*/
|
||||
public function exists( $key ) {
|
||||
return array_key_exists( $key, $this->data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for adding data to the registry.
|
||||
*
|
||||
* You can only register data that is not already in the registry identified by the given key. If there is a
|
||||
* duplicate found, unless $ignore_duplicates is true, an exception will be thrown.
|
||||
*
|
||||
* @param string $key The key used to reference the data being registered.
|
||||
* @param mixed $data If not a function, registered to the registry as is. If a function, then the
|
||||
* callback is invoked right before output to the screen.
|
||||
* @param boolean $check_key_exists If set to true, duplicate data will be ignored if the key exists.
|
||||
* If false, duplicate data will cause an exception.
|
||||
*
|
||||
* @throws InvalidArgumentException Only throws when site is in debug mode. Always logs the error.
|
||||
*/
|
||||
public function add( $key, $data, $check_key_exists = false ) {
|
||||
if ( $check_key_exists && $this->exists( $key ) ) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->add_data( $key, $data );
|
||||
} catch ( Exception $e ) {
|
||||
if ( $this->debug() ) {
|
||||
// bubble up.
|
||||
throw $e;
|
||||
}
|
||||
wc_caught_exception( $e, __METHOD__, [ $key, $data ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate from API.
|
||||
*
|
||||
* @param string $path REST API path to preload.
|
||||
*/
|
||||
public function hydrate_api_request( $path ) {
|
||||
if ( ! isset( $this->preloaded_api_requests[ $path ] ) ) {
|
||||
$this->preloaded_api_requests = rest_preload_api_request( $this->preloaded_api_requests, $path );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a page permalink to the data registry.
|
||||
*
|
||||
* @param integer $page_id Page ID to add to the registry.
|
||||
*/
|
||||
public function register_page_id( $page_id ) {
|
||||
$permalink = $page_id ? get_permalink( $page_id ) : false;
|
||||
|
||||
if ( $permalink ) {
|
||||
$this->data[ 'page-' . $page_id ] = $permalink;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for registering the data script via WordPress API.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_data_script() {
|
||||
$this->api->register_script(
|
||||
$this->handle,
|
||||
'build/wc-settings.js',
|
||||
[ 'wp-api-fetch' ],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for enqueuing asset data via the WP api.
|
||||
*
|
||||
* Note: while this is hooked into print/admin_print_scripts, it still only
|
||||
* happens if the script attached to `wc-settings` handle is enqueued. This
|
||||
* is done to allow for any potentially expensive data generation to only
|
||||
* happen for routes that need it.
|
||||
*/
|
||||
public function enqueue_asset_data() {
|
||||
if ( wp_script_is( $this->handle, 'enqueued' ) ) {
|
||||
$this->initialize_core_data();
|
||||
$this->execute_lazy_data();
|
||||
|
||||
$data = rawurlencode( wp_json_encode( $this->data ) );
|
||||
$wc_settings_script = "var wcSettings = wcSettings || JSON.parse( decodeURIComponent( '" . esc_js( $data ) . "' ) );";
|
||||
$preloaded_api_requests_script = '';
|
||||
|
||||
if ( count( $this->preloaded_api_requests ) > 0 ) {
|
||||
$preloaded_api_requests = rawurlencode( wp_json_encode( $this->preloaded_api_requests ) );
|
||||
$preloaded_api_requests_script = "wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( JSON.parse( decodeURIComponent( '" . esc_js( $preloaded_api_requests ) . "' ) ) ) );";
|
||||
}
|
||||
|
||||
wp_add_inline_script(
|
||||
$this->handle,
|
||||
$wc_settings_script . $preloaded_api_requests_script,
|
||||
'before'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See self::add() for docs.
|
||||
*
|
||||
* @param string $key Key for the data.
|
||||
* @param mixed $data Value for the data.
|
||||
*
|
||||
* @throws InvalidArgumentException If key is not a string or already
|
||||
* exists in internal data cache.
|
||||
*/
|
||||
protected function add_data( $key, $data ) {
|
||||
if ( ! is_string( $key ) ) {
|
||||
if ( $this->debug() ) {
|
||||
throw new InvalidArgumentException(
|
||||
'Key for the data being registered must be a string'
|
||||
);
|
||||
}
|
||||
}
|
||||
if ( isset( $this->data[ $key ] ) ) {
|
||||
if ( $this->debug() ) {
|
||||
throw new InvalidArgumentException(
|
||||
'Overriding existing data with an already registered key is not allowed'
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( \is_callable( $data ) ) {
|
||||
$this->lazy_data[ $key ] = $data;
|
||||
return;
|
||||
}
|
||||
$this->data[ $key ] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes whether the current site is in debug mode or not.
|
||||
*
|
||||
* @return boolean True means the site is in debug mode.
|
||||
*/
|
||||
protected function debug() {
|
||||
return defined( 'WP_DEBUG' ) && WP_DEBUG;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry as AssetDataRegistry;
|
||||
|
||||
/**
|
||||
* AssetsController class.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @internal
|
||||
*/
|
||||
final class AssetsController {
|
||||
|
||||
/**
|
||||
* Asset API interface for various asset registration.
|
||||
*
|
||||
* @var AssetApi
|
||||
*/
|
||||
private $api;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetApi $asset_api Asset API interface for various asset registration.
|
||||
*/
|
||||
public function __construct( AssetApi $asset_api ) {
|
||||
$this->api = $asset_api;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class features.
|
||||
*/
|
||||
protected function init() {
|
||||
add_action( 'init', array( $this, 'register_assets' ) );
|
||||
add_filter( 'wp_resource_hints', array( $this, 'add_resource_hints' ), 10, 2 );
|
||||
add_action( 'body_class', array( $this, 'add_theme_body_class' ), 1 );
|
||||
add_action( 'admin_body_class', array( $this, 'add_theme_body_class' ), 1 );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'update_block_style_dependencies' ), 20 );
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'update_block_settings_dependencies' ), 100 );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'update_block_settings_dependencies' ), 100 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block scripts & styles.
|
||||
*/
|
||||
public function register_assets() {
|
||||
$this->register_style( 'wc-blocks-vendors-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-vendors-style', 'css' ), __DIR__ ) );
|
||||
$this->register_style( 'wc-blocks-editor-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-editor-style', 'css' ), __DIR__ ), [ 'wp-edit-blocks' ], 'all', true );
|
||||
$this->register_style( 'wc-blocks-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-style', 'css' ), __DIR__ ), [ 'wc-blocks-vendors-style' ], 'all', true );
|
||||
|
||||
$this->api->register_script( 'wc-blocks-middleware', 'build/wc-blocks-middleware.js', [], false );
|
||||
$this->api->register_script( 'wc-blocks-data-store', 'build/wc-blocks-data.js', [ 'wc-blocks-middleware' ] );
|
||||
$this->api->register_script( 'wc-blocks-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-vendors' ), [], false );
|
||||
$this->api->register_script( 'wc-blocks-registry', 'build/wc-blocks-registry.js', [], false );
|
||||
$this->api->register_script( 'wc-blocks', $this->api->get_block_asset_build_path( 'wc-blocks' ), [ 'wc-blocks-vendors' ], false );
|
||||
$this->api->register_script( 'wc-blocks-shared-context', 'build/wc-blocks-shared-context.js', [] );
|
||||
$this->api->register_script( 'wc-blocks-shared-hocs', 'build/wc-blocks-shared-hocs.js', [], false );
|
||||
|
||||
// The price package is shared externally so has no blocks prefix.
|
||||
$this->api->register_script( 'wc-price-format', 'build/price-format.js', [], false );
|
||||
|
||||
$this->api->register_script( 'wc-blocks-checkout', 'build/blocks-checkout.js', [] );
|
||||
|
||||
wp_add_inline_script(
|
||||
'wc-blocks-middleware',
|
||||
"
|
||||
var wcBlocksMiddlewareConfig = {
|
||||
storeApiNonce: '" . esc_js( wp_create_nonce( 'wc_store_api' ) ) . "',
|
||||
wcStoreApiNonceTimestamp: '" . esc_js( time() ) . "'
|
||||
};
|
||||
",
|
||||
'before'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines resource hints to help speed up the loading of some critical blocks.
|
||||
*
|
||||
* These will not impact page loading times negatively because they are loaded once the current page is idle.
|
||||
*
|
||||
* @param array $urls URLs to print for resource hints. Each URL is an array of resource attributes, or a URL string.
|
||||
* @param string $relation_type The relation type the URLs are printed. Possible values: preconnect, dns-prefetch, prefetch, prerender.
|
||||
* @return array URLs to print for resource hints.
|
||||
*/
|
||||
public function add_resource_hints( $urls, $relation_type ) {
|
||||
if ( ! in_array( $relation_type, [ 'prefetch', 'prerender' ], true ) || is_admin() ) {
|
||||
return $urls;
|
||||
}
|
||||
|
||||
// We only need to prefetch when the cart has contents.
|
||||
$cart = wc()->cart;
|
||||
|
||||
if ( ! $cart || ! $cart instanceof \WC_Cart || 0 === $cart->get_cart_contents_count() ) {
|
||||
return $urls;
|
||||
}
|
||||
|
||||
if ( 'prefetch' === $relation_type ) {
|
||||
$urls = array_merge(
|
||||
$urls,
|
||||
$this->get_prefetch_resource_hints()
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'prerender' === $relation_type ) {
|
||||
$urls = array_merge(
|
||||
$urls,
|
||||
$this->get_prerender_resource_hints()
|
||||
);
|
||||
}
|
||||
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resource hints during prefetch requests.
|
||||
*
|
||||
* @return array Array of URLs.
|
||||
*/
|
||||
private function get_prefetch_resource_hints() {
|
||||
$urls = [];
|
||||
|
||||
// Core page IDs.
|
||||
$cart_page_id = wc_get_page_id( 'cart' );
|
||||
$checkout_page_id = wc_get_page_id( 'checkout' );
|
||||
|
||||
// Checks a specific page (by ID) to see if it contains the named block.
|
||||
$has_block_cart = $cart_page_id && has_block( 'woocommerce/cart', $cart_page_id );
|
||||
$has_block_checkout = $checkout_page_id && has_block( 'woocommerce/checkout', $checkout_page_id );
|
||||
|
||||
// Checks the current page to see if it contains the named block.
|
||||
$is_block_cart = has_block( 'woocommerce/cart' );
|
||||
$is_block_checkout = has_block( 'woocommerce/checkout' );
|
||||
|
||||
if ( $has_block_cart && ! $is_block_cart ) {
|
||||
$urls = array_merge( $urls, $this->get_block_asset_resource_hints( 'cart-frontend' ) );
|
||||
}
|
||||
|
||||
if ( $has_block_checkout && ! $is_block_checkout ) {
|
||||
$urls = array_merge( $urls, $this->get_block_asset_resource_hints( 'checkout-frontend' ) );
|
||||
}
|
||||
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resource hints during prerender requests.
|
||||
*
|
||||
* @return array Array of URLs.
|
||||
*/
|
||||
private function get_prerender_resource_hints() {
|
||||
$urls = [];
|
||||
$is_block_cart = has_block( 'woocommerce/cart' );
|
||||
|
||||
if ( ! $is_block_cart ) {
|
||||
return $urls;
|
||||
}
|
||||
|
||||
$checkout_page_id = wc_get_page_id( 'checkout' );
|
||||
$checkout_page_url = $checkout_page_id ? get_permalink( $checkout_page_id ) : '';
|
||||
|
||||
if ( $checkout_page_url ) {
|
||||
$urls[] = $checkout_page_url;
|
||||
}
|
||||
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resource hint for a block by name.
|
||||
*
|
||||
* @param string $filename Block filename.
|
||||
* @return array
|
||||
*/
|
||||
private function get_block_asset_resource_hints( $filename = '' ) {
|
||||
if ( ! $filename ) {
|
||||
return [];
|
||||
}
|
||||
$script_data = $this->api->get_script_data(
|
||||
$this->api->get_block_asset_build_path( $filename )
|
||||
);
|
||||
$resources = array_merge(
|
||||
[ add_query_arg( 'ver', $script_data['version'], $script_data['src'] ) ],
|
||||
$this->get_script_dependency_src_array( $script_data['dependencies'] )
|
||||
);
|
||||
return array_map(
|
||||
function( $src ) {
|
||||
return array(
|
||||
'href' => $src,
|
||||
'as' => 'script',
|
||||
);
|
||||
},
|
||||
array_unique( array_filter( $resources ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the src of all script dependencies (handles).
|
||||
*
|
||||
* @param array $dependencies Array of dependency handles.
|
||||
* @return string[] Array of src strings.
|
||||
*/
|
||||
private function get_script_dependency_src_array( array $dependencies ) {
|
||||
$wp_scripts = wp_scripts();
|
||||
return array_reduce(
|
||||
$dependencies,
|
||||
function( $src, $handle ) use ( $wp_scripts ) {
|
||||
if ( isset( $wp_scripts->registered[ $handle ] ) ) {
|
||||
$src[] = add_query_arg( 'ver', $wp_scripts->registered[ $handle ]->ver, $this->get_absolute_url( $wp_scripts->registered[ $handle ]->src ) );
|
||||
$src = array_merge( $src, $this->get_script_dependency_src_array( $wp_scripts->registered[ $handle ]->deps ) );
|
||||
}
|
||||
return $src;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an absolute url to relative links for WordPress core scripts.
|
||||
*
|
||||
* @param string $src Original src that can be relative.
|
||||
* @return string Correct full path string.
|
||||
*/
|
||||
private function get_absolute_url( $src ) {
|
||||
$wp_scripts = wp_scripts();
|
||||
if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $wp_scripts->content_url && 0 === strpos( $src, $wp_scripts->content_url ) ) ) {
|
||||
$src = $wp_scripts->base_url . $src;
|
||||
}
|
||||
return $src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add body classes to the frontend and within admin.
|
||||
*
|
||||
* @param string|array $classes Array or string of CSS classnames.
|
||||
* @return string|array Modified classnames.
|
||||
*/
|
||||
public function add_theme_body_class( $classes ) {
|
||||
$class = 'theme-' . get_template();
|
||||
|
||||
if ( is_array( $classes ) ) {
|
||||
$classes[] = $class;
|
||||
} else {
|
||||
$classes .= ' ' . $class . ' ';
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file modified time as a cache buster if we're in dev mode.
|
||||
*
|
||||
* @param string $file Local path to the file.
|
||||
* @return string The cache buster value to use for the given file.
|
||||
*/
|
||||
protected function get_file_version( $file ) {
|
||||
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( \Automattic\WooCommerce\Blocks\Package::get_path() . $file ) ) {
|
||||
return filemtime( \Automattic\WooCommerce\Blocks\Package::get_path() . $file );
|
||||
}
|
||||
return \Automattic\WooCommerce\Blocks\Package::get_version();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a style according to `wp_register_style`.
|
||||
*
|
||||
* @param string $handle Name of the stylesheet. Should be unique.
|
||||
* @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
|
||||
* @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
|
||||
* @param string $media Optional. The media for which this stylesheet has been defined. Default 'all'. Accepts media types like
|
||||
* 'all', 'print' and 'screen', or media queries like '(orientation: portrait)' and '(max-width: 640px)'.
|
||||
* @param boolean $rtl Optional. Whether or not to register RTL styles.
|
||||
*/
|
||||
protected function register_style( $handle, $src, $deps = [], $media = 'all', $rtl = false ) {
|
||||
$filename = str_replace( plugins_url( '/', __DIR__ ), '', $src );
|
||||
$ver = self::get_file_version( $filename );
|
||||
|
||||
wp_register_style( $handle, $src, $deps, $ver, $media );
|
||||
|
||||
if ( $rtl ) {
|
||||
wp_style_add_data( $handle, 'rtl', 'replace' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update block style dependencies after they have been registered.
|
||||
*/
|
||||
public function update_block_style_dependencies() {
|
||||
$wp_styles = wp_styles();
|
||||
$style = $wp_styles->query( 'wc-blocks-style', 'registered' );
|
||||
|
||||
if ( ! $style ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In WC < 5.5, `woocommerce-general` is not registered in block editor
|
||||
// screens, so we don't add it as a dependency if it's not registered.
|
||||
// In WC >= 5.5, `woocommerce-general` is registered on `admin_enqueue_scripts`,
|
||||
// so we need to check if it's registered here instead of on `init`.
|
||||
if (
|
||||
wp_style_is( 'woocommerce-general', 'registered' ) &&
|
||||
! in_array( 'woocommerce-general', $style->deps, true )
|
||||
) {
|
||||
$style->deps[] = 'woocommerce-general';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix scripts with wc-settings dependency.
|
||||
*
|
||||
* The wc-settings script only works correctly when enqueued in the footer. This is to give blocks etc time to
|
||||
* register their settings data before it's printed.
|
||||
*
|
||||
* This code will look at registered scripts, and if they have a wc-settings dependency, force them to print in the
|
||||
* footer instead of the header.
|
||||
*
|
||||
* This only supports packages known to require wc-settings!
|
||||
*
|
||||
* @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5052
|
||||
*/
|
||||
public function update_block_settings_dependencies() {
|
||||
$wp_scripts = wp_scripts();
|
||||
$known_packages = [ 'wc-settings', 'wc-blocks-checkout', 'wc-price-format' ];
|
||||
|
||||
foreach ( $wp_scripts->registered as $handle => $script ) {
|
||||
// scripts that are loaded in the footer has extra->group = 1.
|
||||
if ( array_intersect( $known_packages, $script->deps ) && ! isset( $script->extra['group'] ) ) {
|
||||
// Append the script to footer.
|
||||
$wp_scripts->add_data( $handle, 'group', 1 );
|
||||
// Show a warning.
|
||||
$error_handle = 'wc-settings-dep-in-header';
|
||||
$used_deps = implode( ', ', array_intersect( $known_packages, $script->deps ) );
|
||||
$error_message = "Scripts that have a dependency on [$used_deps] must be loaded in the footer, {$handle} was registered to load in the header, but has been switched to load in the footer instead. See https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5059";
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter,WordPress.WP.EnqueuedResourceParameters.MissingVersion
|
||||
wp_register_script( $error_handle, '' );
|
||||
wp_enqueue_script( $error_handle );
|
||||
wp_add_inline_script(
|
||||
$error_handle,
|
||||
sprintf( 'console.warn( "%s" );', $error_message )
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
|
||||
/**
|
||||
* Registers patterns under the `./patterns/` directory.
|
||||
* Each pattern is defined as a PHP file and defines its metadata using plugin-style headers.
|
||||
* The minimum required definition is:
|
||||
*
|
||||
* /**
|
||||
* * Title: My Pattern
|
||||
* * Slug: my-theme/my-pattern
|
||||
* *
|
||||
*
|
||||
* The output of the PHP source corresponds to the content of the pattern, e.g.:
|
||||
*
|
||||
* <main><p><?php echo "Hello"; ?></p></main>
|
||||
*
|
||||
* Other settable fields include:
|
||||
*
|
||||
* - Description
|
||||
* - Viewport Width
|
||||
* - Categories (comma-separated values)
|
||||
* - Keywords (comma-separated values)
|
||||
* - Block Types (comma-separated values)
|
||||
* - Inserter (yes/no)
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockPatterns {
|
||||
const SLUG_REGEX = '/^[A-z0-9\/_-]+$/';
|
||||
const COMMA_SEPARATED_REGEX = '/[\s,]+/';
|
||||
|
||||
/**
|
||||
* Path to the patterns directory.
|
||||
*
|
||||
* @var string $patterns_path
|
||||
*/
|
||||
private $patterns_path;
|
||||
|
||||
/**
|
||||
* Constructor for class
|
||||
*
|
||||
* @param Package $package An instance of Package.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
$this->patterns_path = $package->get_path( 'patterns' );
|
||||
|
||||
add_action( 'init', array( $this, 'register_block_patterns' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the block patterns and categories under `./patterns/`.
|
||||
*/
|
||||
public function register_block_patterns() {
|
||||
if ( ! class_exists( 'WP_Block_Patterns_Registry' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$default_headers = array(
|
||||
'title' => 'Title',
|
||||
'slug' => 'Slug',
|
||||
'description' => 'Description',
|
||||
'viewportWidth' => 'Viewport Width',
|
||||
'categories' => 'Categories',
|
||||
'keywords' => 'Keywords',
|
||||
'blockTypes' => 'Block Types',
|
||||
'inserter' => 'Inserter',
|
||||
);
|
||||
|
||||
if ( ! file_exists( $this->patterns_path ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = glob( $this->patterns_path . '/*.php' );
|
||||
if ( ! $files ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $files as $file ) {
|
||||
$pattern_data = get_file_data( $file, $default_headers );
|
||||
|
||||
if ( empty( $pattern_data['slug'] ) ) {
|
||||
_doing_it_wrong(
|
||||
'register_block_patterns',
|
||||
esc_html(
|
||||
sprintf(
|
||||
/* translators: %s: file name. */
|
||||
__( 'Could not register file "%s" as a block pattern ("Slug" field missing)', 'woocommerce' ),
|
||||
$file
|
||||
)
|
||||
),
|
||||
'6.0.0'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! preg_match( self::SLUG_REGEX, $pattern_data['slug'] ) ) {
|
||||
_doing_it_wrong(
|
||||
'register_block_patterns',
|
||||
esc_html(
|
||||
sprintf(
|
||||
/* translators: %1s: file name; %2s: slug value found. */
|
||||
__( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")', 'woocommerce' ),
|
||||
$file,
|
||||
$pattern_data['slug']
|
||||
)
|
||||
),
|
||||
'6.0.0'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( \WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Title is a required property.
|
||||
if ( ! $pattern_data['title'] ) {
|
||||
_doing_it_wrong(
|
||||
'register_block_patterns',
|
||||
esc_html(
|
||||
sprintf(
|
||||
/* translators: %1s: file name; %2s: slug value found. */
|
||||
__( 'Could not register file "%s" as a block pattern ("Title" field missing)', 'woocommerce' ),
|
||||
$file
|
||||
)
|
||||
),
|
||||
'6.0.0'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// For properties of type array, parse data as comma-separated.
|
||||
foreach ( array( 'categories', 'keywords', 'blockTypes' ) as $property ) {
|
||||
if ( ! empty( $pattern_data[ $property ] ) ) {
|
||||
$pattern_data[ $property ] = array_filter(
|
||||
preg_split(
|
||||
self::COMMA_SEPARATED_REGEX,
|
||||
(string) $pattern_data[ $property ]
|
||||
)
|
||||
);
|
||||
} else {
|
||||
unset( $pattern_data[ $property ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Parse properties of type int.
|
||||
foreach ( array( 'viewportWidth' ) as $property ) {
|
||||
if ( ! empty( $pattern_data[ $property ] ) ) {
|
||||
$pattern_data[ $property ] = (int) $pattern_data[ $property ];
|
||||
} else {
|
||||
unset( $pattern_data[ $property ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Parse properties of type bool.
|
||||
foreach ( array( 'inserter' ) as $property ) {
|
||||
if ( ! empty( $pattern_data[ $property ] ) ) {
|
||||
$pattern_data[ $property ] = in_array(
|
||||
strtolower( $pattern_data[ $property ] ),
|
||||
array( 'yes', 'true' ),
|
||||
true
|
||||
);
|
||||
} else {
|
||||
unset( $pattern_data[ $property ] );
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction
|
||||
$pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', 'woo-gutenberg-products-block' );
|
||||
if ( ! empty( $pattern_data['description'] ) ) {
|
||||
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction
|
||||
$pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', 'woo-gutenberg-products-block' );
|
||||
}
|
||||
|
||||
// The actual pattern content is the output of the file.
|
||||
ob_start();
|
||||
include $file;
|
||||
$pattern_data['content'] = ob_get_clean();
|
||||
if ( ! $pattern_data['content'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $pattern_data['categories'] as $key => $category ) {
|
||||
$category_slug = _wp_to_kebab_case( $category );
|
||||
|
||||
$pattern_data['categories'][ $key ] = $category_slug;
|
||||
|
||||
register_block_pattern_category(
|
||||
$category_slug,
|
||||
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
|
||||
array( 'label' => __( $category, 'woocommerce' ) )
|
||||
);
|
||||
}
|
||||
|
||||
register_block_pattern( $pattern_data['slug'], $pattern_data );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplateCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
/**
|
||||
* BlockTypesController class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockTemplatesController {
|
||||
|
||||
/**
|
||||
* Holds the Package instance
|
||||
*
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
/**
|
||||
* Holds the path for the directory where the block templates will be kept.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $templates_directory;
|
||||
|
||||
/**
|
||||
* Holds the path for the directory where the block template parts will be kept.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $template_parts_directory;
|
||||
|
||||
/**
|
||||
* Directory which contains all templates
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TEMPLATES_ROOT_DIR = 'templates';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Package $package An instance of Package.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
$this->package = $package;
|
||||
|
||||
// This feature is gated for WooCommerce versions 6.0.0 and above.
|
||||
if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '6.0.0', '>=' ) ) {
|
||||
$root_path = plugin_dir_path( __DIR__ ) . self::TEMPLATES_ROOT_DIR . DIRECTORY_SEPARATOR;
|
||||
$this->templates_directory = $root_path . BlockTemplateUtils::DIRECTORY_NAMES['TEMPLATES'];
|
||||
$this->template_parts_directory = $root_path . BlockTemplateUtils::DIRECTORY_NAMES['TEMPLATE_PARTS'];
|
||||
$this->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization method.
|
||||
*/
|
||||
protected function init() {
|
||||
add_action( 'template_redirect', array( $this, 'render_block_template' ) );
|
||||
add_filter( 'pre_get_block_template', array( $this, 'get_block_template_fallback' ), 10, 3 );
|
||||
add_filter( 'pre_get_block_file_template', array( $this, 'get_block_file_template' ), 10, 3 );
|
||||
add_filter( 'get_block_templates', array( $this, 'add_block_templates' ), 10, 3 );
|
||||
add_filter( 'current_theme_supports-block-templates', array( $this, 'remove_block_template_support_for_shop_page' ) );
|
||||
add_filter( 'taxonomy_template_hierarchy', array( $this, 'add_archive_product_to_eligible_for_fallback_templates' ), 10, 1 );
|
||||
add_filter( 'post_type_archive_title', array( $this, 'update_product_archive_title' ), 10, 2 );
|
||||
|
||||
if ( $this->package->is_experimental_build() ) {
|
||||
add_action( 'after_switch_theme', array( $this, 'check_should_use_blockified_product_grid_templates' ), 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used on the `pre_get_block_template` hook to return the fallback template from the db in case
|
||||
* the template is eligible for it.
|
||||
*
|
||||
* @param \WP_Block_Template|null $template Block template object to short-circuit the default query,
|
||||
* or null to allow WP to run its normal queries.
|
||||
* @param string $id Template unique identifier (example: theme_slug//template_slug).
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
public function get_block_template_fallback( $template, $id, $template_type ) {
|
||||
$template_name_parts = explode( '//', $id );
|
||||
list( $theme, $slug ) = $template_name_parts;
|
||||
|
||||
if ( ! BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $slug ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$wp_query_args = array(
|
||||
'post_name__in' => array( 'archive-product', $slug ),
|
||||
'post_type' => $template_type,
|
||||
'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash' ),
|
||||
'no_found_rows' => true,
|
||||
'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
|
||||
array(
|
||||
'taxonomy' => 'wp_theme',
|
||||
'field' => 'name',
|
||||
'terms' => $theme,
|
||||
),
|
||||
),
|
||||
);
|
||||
$template_query = new \WP_Query( $wp_query_args );
|
||||
$posts = $template_query->posts;
|
||||
|
||||
// If we have more than one result from the query, it means that the current template is present in the db (has
|
||||
// been customized by the user) and we should not return the `archive-product` template.
|
||||
if ( count( $posts ) > 1 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( count( $posts ) > 0 && 'archive-product' === $posts[0]->post_name ) {
|
||||
$template = _build_block_template_result_from_post( $posts[0] );
|
||||
|
||||
if ( ! is_wp_error( $template ) ) {
|
||||
$template->id = $theme . '//' . $slug;
|
||||
$template->slug = $slug;
|
||||
$template->title = BlockTemplateUtils::get_block_template_title( $slug );
|
||||
$template->description = BlockTemplateUtils::get_block_template_description( $slug );
|
||||
unset( $template->source );
|
||||
|
||||
return $template;
|
||||
}
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the `archive-product` template to the `taxonomy-product_cat`, `taxonomy-product_tag`, `taxonomy-attribute`
|
||||
* templates to be able to fall back to it.
|
||||
*
|
||||
* @param array $template_hierarchy A list of template candidates, in descending order of priority.
|
||||
*/
|
||||
public function add_archive_product_to_eligible_for_fallback_templates( $template_hierarchy ) {
|
||||
$template_slugs = array_map(
|
||||
'_strip_template_file_suffix',
|
||||
$template_hierarchy
|
||||
);
|
||||
|
||||
$templates_eligible_for_fallback = array_filter(
|
||||
$template_slugs,
|
||||
array( BlockTemplateUtils::class, 'template_is_eligible_for_product_archive_fallback' )
|
||||
);
|
||||
|
||||
if ( count( $templates_eligible_for_fallback ) > 0 ) {
|
||||
$template_hierarchy[] = 'archive-product';
|
||||
}
|
||||
|
||||
return $template_hierarchy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the old and current themes and determines if the "wc_blocks_use_blockified_product_grid_block_as_template"
|
||||
* option need to be updated accordingly.
|
||||
*
|
||||
* @param string $old_name Old theme name.
|
||||
* @param \WP_Theme $old_theme Instance of the old theme.
|
||||
* @return void
|
||||
*/
|
||||
public function check_should_use_blockified_product_grid_templates( $old_name, $old_theme ) {
|
||||
if ( ! wc_current_theme_is_fse_theme() ) {
|
||||
update_option( Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE, wc_bool_to_string( false ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $old_theme->is_block_theme() && wc_current_theme_is_fse_theme() ) {
|
||||
update_option( Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE, wc_bool_to_string( true ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if there's a block template file in `woo-gutenberg-products-block/templates/templates/`
|
||||
* to return to pre_get_posts short-circuiting the query in Gutenberg.
|
||||
*
|
||||
* @param \WP_Block_Template|null $template Return a block template object to short-circuit the default query,
|
||||
* or null to allow WP to run its normal queries.
|
||||
* @param string $id Template unique identifier (example: theme_slug//template_slug).
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return mixed|\WP_Block_Template|\WP_Error
|
||||
*/
|
||||
public function get_block_file_template( $template, $id, $template_type ) {
|
||||
$template_name_parts = explode( '//', $id );
|
||||
|
||||
if ( count( $template_name_parts ) < 2 ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
list( $template_id, $template_slug ) = $template_name_parts;
|
||||
|
||||
// If the theme has an archive-product.html template, but not a taxonomy-product_cat/tag/attribute.html template let's use the themes archive-product.html template.
|
||||
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback_from_theme( $template_slug ) ) {
|
||||
$template_path = BlockTemplateUtils::get_theme_template_path( 'archive-product' );
|
||||
$template_object = BlockTemplateUtils::create_new_block_template_object( $template_path, $template_type, $template_slug, true );
|
||||
return BlockTemplateUtils::build_template_result_from_file( $template_object, $template_type );
|
||||
}
|
||||
|
||||
// This is a real edge-case, we are supporting users who have saved templates under the deprecated slug. See its definition for more information.
|
||||
// You can likely ignore this code unless you're supporting/debugging early customised templates.
|
||||
if ( BlockTemplateUtils::DEPRECATED_PLUGIN_SLUG === strtolower( $template_id ) ) {
|
||||
// Because we are using get_block_templates we have to unhook this method to prevent a recursive loop where this filter is applied.
|
||||
remove_filter( 'pre_get_block_file_template', array( $this, 'get_block_file_template' ), 10, 3 );
|
||||
$template_with_deprecated_id = BlockTemplateUtils::get_block_template( $id, $template_type );
|
||||
// Let's hook this method back now that we have used the function.
|
||||
add_filter( 'pre_get_block_file_template', array( $this, 'get_block_file_template' ), 10, 3 );
|
||||
|
||||
if ( null !== $template_with_deprecated_id ) {
|
||||
return $template_with_deprecated_id;
|
||||
}
|
||||
}
|
||||
|
||||
// If we are not dealing with a WooCommerce template let's return early and let it continue through the process.
|
||||
if ( BlockTemplateUtils::PLUGIN_SLUG !== $template_id ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
// If we don't have a template let Gutenberg do its thing.
|
||||
if ( ! $this->block_template_is_available( $template_slug, $template_type ) ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$directory = $this->get_templates_directory( $template_type );
|
||||
$template_file_path = $directory . '/' . $template_slug . '.html';
|
||||
$template_object = BlockTemplateUtils::create_new_block_template_object( $template_file_path, $template_type, $template_slug );
|
||||
$template_built = BlockTemplateUtils::build_template_result_from_file( $template_object, $template_type );
|
||||
|
||||
if ( null !== $template_built ) {
|
||||
return $template_built;
|
||||
}
|
||||
|
||||
// Hand back over to Gutenberg if we can't find a template.
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the block template objects to be used.
|
||||
*
|
||||
* @param array $query_result Array of template objects.
|
||||
* @param array $query Optional. Arguments to retrieve templates.
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
* @return array
|
||||
*/
|
||||
public function add_block_templates( $query_result, $query, $template_type ) {
|
||||
if ( ! BlockTemplateUtils::supports_block_templates() ) {
|
||||
return $query_result;
|
||||
}
|
||||
|
||||
$post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';
|
||||
$slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : array();
|
||||
$template_files = $this->get_block_templates( $slugs, $template_type );
|
||||
|
||||
// @todo: Add apply_filters to _gutenberg_get_template_files() in Gutenberg to prevent duplication of logic.
|
||||
foreach ( $template_files as $template_file ) {
|
||||
|
||||
// If we have a template which is eligible for a fallback, we need to explicitly tell Gutenberg that
|
||||
// it has a theme file (because it is using the fallback template file). And then `continue` to avoid
|
||||
// adding duplicates.
|
||||
if ( BlockTemplateUtils::set_has_theme_file_if_fallback_is_available( $query_result, $template_file ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the current $post_type is set (e.g. on an Edit Post screen), and isn't included in the available post_types
|
||||
// on the template file, then lets skip it so that it doesn't get added. This is typically used to hide templates
|
||||
// in the template dropdown on the Edit Post page.
|
||||
if ( $post_type &&
|
||||
isset( $template_file->post_types ) &&
|
||||
! in_array( $post_type, $template_file->post_types, true )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// It would be custom if the template was modified in the editor, so if it's not custom we can load it from
|
||||
// the filesystem.
|
||||
if ( 'custom' !== $template_file->source ) {
|
||||
$template = BlockTemplateUtils::build_template_result_from_file( $template_file, $template_type );
|
||||
} else {
|
||||
$template_file->title = BlockTemplateUtils::get_block_template_title( $template_file->slug );
|
||||
$template_file->description = BlockTemplateUtils::get_block_template_description( $template_file->slug );
|
||||
$query_result[] = $template_file;
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_not_custom = false === array_search(
|
||||
wp_get_theme()->get_stylesheet() . '//' . $template_file->slug,
|
||||
array_column( $query_result, 'id' ),
|
||||
true
|
||||
);
|
||||
$fits_slug_query =
|
||||
! isset( $query['slug__in'] ) || in_array( $template_file->slug, $query['slug__in'], true );
|
||||
$fits_area_query =
|
||||
! isset( $query['area'] ) || ( property_exists( $template_file, 'area' ) && $template_file->area === $query['area'] );
|
||||
$should_include = $is_not_custom && $fits_slug_query && $fits_area_query;
|
||||
if ( $should_include ) {
|
||||
$query_result[] = $template;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to remove theme (i.e. filesystem) templates that have the same slug as a customised one.
|
||||
// This only affects saved templates that were saved BEFORE a theme template with the same slug was added.
|
||||
$query_result = BlockTemplateUtils::remove_theme_templates_with_custom_alternative( $query_result );
|
||||
|
||||
/**
|
||||
* WC templates from theme aren't included in `$this->get_block_templates()` but are handled by Gutenberg.
|
||||
* We need to do additional search through all templates file to update title and description for WC
|
||||
* templates that aren't listed in theme.json.
|
||||
*/
|
||||
$query_result = array_map(
|
||||
function( $template ) {
|
||||
if ( 'theme' === $template->origin && BlockTemplateUtils::template_has_title( $template ) ) {
|
||||
return $template;
|
||||
}
|
||||
if ( $template->title === $template->slug ) {
|
||||
$template->title = BlockTemplateUtils::get_block_template_title( $template->slug );
|
||||
}
|
||||
if ( ! $template->description ) {
|
||||
$template->description = BlockTemplateUtils::get_block_template_description( $template->slug );
|
||||
}
|
||||
|
||||
if ( str_contains( $template->slug, 'single-product' ) ) {
|
||||
if ( ! is_admin() && ! BlockTemplateUtils::template_has_legacy_template_block( $template ) ) {
|
||||
|
||||
$new_content = SingleProductTemplateCompatibility::add_compatibility_layer( $template->content );
|
||||
$template->content = $new_content;
|
||||
}
|
||||
return $template;
|
||||
}
|
||||
|
||||
return $template;
|
||||
},
|
||||
$query_result
|
||||
);
|
||||
|
||||
return $query_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the templates saved in the database.
|
||||
*
|
||||
* @param array $slugs An array of slugs to retrieve templates for.
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return int[]|\WP_Post[] An array of found templates.
|
||||
*/
|
||||
public function get_block_templates_from_db( $slugs = array(), $template_type = 'wp_template' ) {
|
||||
$check_query_args = array(
|
||||
'post_type' => $template_type,
|
||||
'posts_per_page' => -1,
|
||||
'no_found_rows' => true,
|
||||
'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
|
||||
array(
|
||||
'taxonomy' => 'wp_theme',
|
||||
'field' => 'name',
|
||||
'terms' => array( BlockTemplateUtils::DEPRECATED_PLUGIN_SLUG, BlockTemplateUtils::PLUGIN_SLUG, get_stylesheet() ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if ( is_array( $slugs ) && count( $slugs ) > 0 ) {
|
||||
$check_query_args['post_name__in'] = $slugs;
|
||||
}
|
||||
|
||||
$check_query = new \WP_Query( $check_query_args );
|
||||
$saved_woo_templates = $check_query->posts;
|
||||
|
||||
return array_map(
|
||||
function( $saved_woo_template ) {
|
||||
return BlockTemplateUtils::build_template_result_from_post( $saved_woo_template );
|
||||
},
|
||||
$saved_woo_templates
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the templates from the WooCommerce blocks directory, skipping those for which a template already exists
|
||||
* in the theme directory.
|
||||
*
|
||||
* @param string[] $slugs An array of slugs to filter templates by. Templates whose slug does not match will not be returned.
|
||||
* @param array $already_found_templates Templates that have already been found, these are customised templates that are loaded from the database.
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return array Templates from the WooCommerce blocks plugin directory.
|
||||
*/
|
||||
public function get_block_templates_from_woocommerce( $slugs, $already_found_templates, $template_type = 'wp_template' ) {
|
||||
$directory = $this->get_templates_directory( $template_type );
|
||||
$template_files = BlockTemplateUtils::get_template_paths( $directory );
|
||||
$templates = array();
|
||||
|
||||
foreach ( $template_files as $template_file ) {
|
||||
// Skip the template if it's blockified, and we should only use classic ones.
|
||||
// Until the blockified Product Grid Block is implemented, we need to always skip the blockified templates.
|
||||
// phpcs:ignore Squiz.PHP.CommentedOutCode
|
||||
if ( // $this->package->is_experimental_build() &&
|
||||
// ! BlockTemplateUtils::should_use_blockified_product_grid_templates() &&
|
||||
strpos( $template_file, 'blockified' ) !== false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$template_slug = BlockTemplateUtils::generate_template_slug_from_path( $template_file );
|
||||
|
||||
// This template does not have a slug we're looking for. Skip it.
|
||||
if ( is_array( $slugs ) && count( $slugs ) > 0 && ! in_array( $template_slug, $slugs, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the theme already has a template, or the template is already in the list (i.e. it came from the
|
||||
// database) then we should not overwrite it with the one from the filesystem.
|
||||
if (
|
||||
BlockTemplateUtils::theme_has_template( $template_slug ) ||
|
||||
count(
|
||||
array_filter(
|
||||
$already_found_templates,
|
||||
function ( $template ) use ( $template_slug ) {
|
||||
$template_obj = (object) $template; //phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
|
||||
return $template_obj->slug === $template_slug;
|
||||
}
|
||||
)
|
||||
) > 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback_from_db( $template_slug, $already_found_templates ) ) {
|
||||
$template = clone BlockTemplateUtils::get_fallback_template_from_db( $template_slug, $already_found_templates );
|
||||
$template_id = explode( '//', $template->id );
|
||||
$template->id = $template_id[0] . '//' . $template_slug;
|
||||
$template->slug = $template_slug;
|
||||
$template->title = BlockTemplateUtils::get_block_template_title( $template_slug );
|
||||
$template->description = BlockTemplateUtils::get_block_template_description( $template_slug );
|
||||
$templates[] = $template;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the theme has an archive-product.html template, but not a taxonomy-product_cat/tag/attribute.html template let's use the themes archive-product.html template.
|
||||
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback_from_theme( $template_slug ) ) {
|
||||
$template_file = BlockTemplateUtils::get_theme_template_path( 'archive-product' );
|
||||
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug, true );
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point the template only exists in the Blocks filesystem, if is a taxonomy-product_cat/tag/attribute.html template
|
||||
// let's use the archive-product.html template from Blocks.
|
||||
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $template_slug ) ) {
|
||||
$template_file = $this->get_template_path_from_woocommerce( 'archive-product' );
|
||||
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug, false );
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point the template only exists in the Blocks filesystem and has not been saved in the DB,
|
||||
// or superseded by the theme.
|
||||
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug );
|
||||
}
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and build the block template objects from the block template files.
|
||||
*
|
||||
* @param array $slugs An array of slugs to retrieve templates for.
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return array WP_Block_Template[] An array of block template objects.
|
||||
*/
|
||||
public function get_block_templates( $slugs = array(), $template_type = 'wp_template' ) {
|
||||
$templates_from_db = $this->get_block_templates_from_db( $slugs, $template_type );
|
||||
$templates_from_woo = $this->get_block_templates_from_woocommerce( $slugs, $templates_from_db, $template_type );
|
||||
$templates = array_merge( $templates_from_db, $templates_from_woo );
|
||||
|
||||
return BlockTemplateUtils::filter_block_templates_by_feature_flag( $templates );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the directory where templates of a specific template type can be found.
|
||||
*
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_templates_directory( $template_type = 'wp_template' ) {
|
||||
if ( 'wp_template_part' === $template_type ) {
|
||||
return $this->template_parts_directory;
|
||||
}
|
||||
|
||||
// When the blockified Product Grid Block will be implemented, we need to use the blockified templates.
|
||||
// if ( $this->package->is_experimental_build() && BlockTemplateUtils::should_use_blockified_product_grid_templates() ) {
|
||||
// return $this->templates_directory . '/blockified';
|
||||
// }.
|
||||
|
||||
return $this->templates_directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of a template on the Blocks template folder.
|
||||
*
|
||||
* @param string $template_slug Block template slug e.g. single-product.
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_template_path_from_woocommerce( $template_slug, $template_type = 'wp_template' ) {
|
||||
return $this->get_templates_directory( $template_type ) . '/' . $template_slug . '.html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a block template with that name exists in Woo Blocks
|
||||
*
|
||||
* @param string $template_name Template to check.
|
||||
* @param array $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function block_template_is_available( $template_name, $template_type = 'wp_template' ) {
|
||||
if ( ! $template_name ) {
|
||||
return false;
|
||||
}
|
||||
$directory = $this->get_templates_directory( $template_type ) . '/' . $template_name . '.html';
|
||||
|
||||
return is_readable(
|
||||
$directory
|
||||
) || $this->get_block_templates( array( $template_name ), $template_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the default block template from Woo Blocks if no theme templates exist.
|
||||
*/
|
||||
public function render_block_template() {
|
||||
if ( is_embed() || ! BlockTemplateUtils::supports_block_templates() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
is_singular( 'product' ) && $this->block_template_is_available( 'single-product' )
|
||||
) {
|
||||
$templates = get_block_templates( array( 'slug__in' => array( 'single-product' ) ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
|
||||
}
|
||||
|
||||
if ( ! BlockTemplateUtils::theme_has_template( 'single-product' ) ) {
|
||||
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
|
||||
}
|
||||
} elseif (
|
||||
( is_product_taxonomy() && is_tax( 'product_cat' ) ) && $this->block_template_is_available( 'taxonomy-product_cat' )
|
||||
) {
|
||||
$templates = get_block_templates( array( 'slug__in' => array( 'taxonomy-product_cat' ) ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
|
||||
}
|
||||
|
||||
if ( ! BlockTemplateUtils::theme_has_template( 'taxonomy-product_cat' ) ) {
|
||||
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
|
||||
}
|
||||
} elseif (
|
||||
( is_product_taxonomy() && is_tax( 'product_tag' ) ) && $this->block_template_is_available( 'taxonomy-product_tag' )
|
||||
) {
|
||||
$templates = get_block_templates( array( 'slug__in' => array( 'taxonomy-product_tag' ) ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
|
||||
}
|
||||
|
||||
if ( ! BlockTemplateUtils::theme_has_template( 'taxonomy-product_tag' ) ) {
|
||||
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
|
||||
}
|
||||
} elseif (
|
||||
( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) && $this->block_template_is_available( 'archive-product' )
|
||||
) {
|
||||
$templates = get_block_templates( array( 'slug__in' => array( 'archive-product' ) ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
|
||||
}
|
||||
|
||||
if ( ! BlockTemplateUtils::theme_has_template( 'archive-product' ) ) {
|
||||
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
|
||||
}
|
||||
} else {
|
||||
$queried_object = get_queried_object();
|
||||
if ( is_null( $queried_object ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $queried_object->taxonomy ) && taxonomy_is_product_attribute( $queried_object->taxonomy ) && $this->block_template_is_available( ProductAttributeTemplate::SLUG )
|
||||
) {
|
||||
$templates = get_block_templates( array( 'slug__in' => array( ProductAttributeTemplate::SLUG ) ) );
|
||||
|
||||
if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
|
||||
add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
|
||||
}
|
||||
|
||||
if ( ! BlockTemplateUtils::theme_has_template( ProductAttributeTemplate::SLUG ) ) {
|
||||
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the template panel from the Sidebar of the Shop page because
|
||||
* the Site Editor handles it.
|
||||
*
|
||||
* @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/6278
|
||||
*
|
||||
* @param bool $is_support Whether the active theme supports block templates.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function remove_block_template_support_for_shop_page( $is_support ) {
|
||||
global $pagenow, $post;
|
||||
|
||||
if (
|
||||
is_admin() &&
|
||||
'post.php' === $pagenow &&
|
||||
function_exists( 'wc_get_page_id' ) &&
|
||||
is_a( $post, 'WP_Post' ) &&
|
||||
wc_get_page_id( 'shop' ) === $post->ID
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $is_support;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the product archive title to "Shop".
|
||||
*
|
||||
* @param string $post_type_name Post type 'name' label.
|
||||
* @param string $post_type Post type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function update_product_archive_title( $post_type_name, $post_type ) {
|
||||
if (
|
||||
function_exists( 'is_shop' ) &&
|
||||
is_shop() &&
|
||||
'product' === $post_type
|
||||
) {
|
||||
return __( 'Shop', 'woocommerce' );
|
||||
}
|
||||
|
||||
return $post_type_name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
<?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 );
|
||||
}
|
||||
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 );
|
||||
// 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.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return 'wc-blocks-style';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes ) {
|
||||
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' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,689 @@
|
||||
<?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->image}
|
||||
{$data->title}
|
||||
</a>
|
||||
{$data->badge}
|
||||
{$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( 'min_columns', wc_get_theme_support( 'product_blocks::min_columns', 1 ), true );
|
||||
$this->asset_data_registry->add( 'max_columns', wc_get_theme_support( 'product_blocks::max_columns', 6 ), true );
|
||||
$this->asset_data_registry->add( 'default_columns', wc_get_theme_support( 'product_blocks::default_columns', 3 ), true );
|
||||
$this->asset_data_registry->add( 'min_rows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true );
|
||||
$this->asset_data_registry->add( 'max_rows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true );
|
||||
$this->asset_data_registry->add( 'default_rows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true );
|
||||
$this->asset_data_registry->add( 'stock_status_options', wc_get_product_stock_status_options(), true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ActiveFilters class.
|
||||
*/
|
||||
class ActiveFilters extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'active-filters';
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?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';
|
||||
|
||||
/**
|
||||
* 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 '';
|
||||
}
|
||||
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
$product = wc_get_product( $post_id );
|
||||
if ( ! $product instanceof \WC_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 ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
|
||||
return sprintf(
|
||||
'<div class="wp-block-add-to-cart-form %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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* It isn't necessary register block assets because it is a server side block.
|
||||
*/
|
||||
protected function register_block_type_assets() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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( 'has_filterable_products', true, true );
|
||||
|
||||
$this->asset_data_registry->add( 'min_columns', wc_get_theme_support( 'product_blocks::min_columns', 1 ), true );
|
||||
$this->asset_data_registry->add( 'max_columns', wc_get_theme_support( 'product_blocks::max_columns', 6 ), true );
|
||||
$this->asset_data_registry->add( 'default_columns', wc_get_theme_support( 'product_blocks::default_columns', 3 ), true );
|
||||
$this->asset_data_registry->add( 'min_rows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true );
|
||||
$this->asset_data_registry->add( 'max_rows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true );
|
||||
$this->asset_data_registry->add( 'default_rows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true );
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$this->hydrate_from_api();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected function hydrate_from_api() {
|
||||
$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 ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes ) {
|
||||
/**
|
||||
* Fires before cart block scripts are enqueued.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_before' );
|
||||
parent::enqueue_assets( $attributes );
|
||||
/**
|
||||
* 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 ) {
|
||||
// Deregister core cart scripts and styles.
|
||||
wp_dequeue_script( 'wc-cart' );
|
||||
wp_dequeue_script( 'wc-password-strength-meter' );
|
||||
wp_dequeue_script( 'selectWoo' );
|
||||
wp_dequeue_style( 'select2' );
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
if ( wc_shipping_enabled() ) {
|
||||
$this->asset_data_registry->add(
|
||||
'shippingCountries',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_shipping_countries() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'shippingStates',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_shipping_country_states() );
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'countryLocale',
|
||||
function() {
|
||||
// Merge country and state data to work around https://github.com/woocommerce/woocommerce/issues/28944.
|
||||
$country_locale = wc()->countries->get_country_locale();
|
||||
$states = wc()->countries->get_states();
|
||||
|
||||
foreach ( $states as $country => $states ) {
|
||||
if ( empty( $states ) ) {
|
||||
$country_locale[ $country ]['state']['required'] = false;
|
||||
$country_locale[ $country ]['state']['hidden'] = true;
|
||||
}
|
||||
}
|
||||
return $country_locale;
|
||||
},
|
||||
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 );
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$this->hydrate_from_api();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after cart block data is registered.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_cart_enqueue_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes accents from an array of values, sorts by the values, then returns the original array values sorted.
|
||||
*
|
||||
* @param array $array Array of values to sort.
|
||||
* @return array Sorted array.
|
||||
*/
|
||||
protected function deep_sort_with_accents( $array ) {
|
||||
if ( ! is_array( $array ) || empty( $array ) ) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
$array_without_accents = array_map(
|
||||
function( $value ) {
|
||||
return is_array( $value )
|
||||
? $this->deep_sort_with_accents( $value )
|
||||
: remove_accents( wc_strtolower( html_entity_decode( $value ) ) );
|
||||
},
|
||||
$array
|
||||
);
|
||||
|
||||
asort( $array_without_accents );
|
||||
return array_replace( $array_without_accents, $array );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate the cart block with data from the API.
|
||||
*/
|
||||
protected function hydrate_from_api() {
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
|
||||
}
|
||||
/**
|
||||
* 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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartAcceptedPaymentMethodsBlock class.
|
||||
*/
|
||||
class CartAcceptedPaymentMethodsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-accepted-payment-methods-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartCrossSellsBlock class.
|
||||
*/
|
||||
class CartCrossSellsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-cross-sells-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartCrossSellsProductsBlock class.
|
||||
*/
|
||||
class CartCrossSellsProductsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-cross-sells-products-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartExpressPaymentBlock class.
|
||||
*/
|
||||
class CartExpressPaymentBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-express-payment-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartItemsBlock class.
|
||||
*/
|
||||
class CartItemsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-items-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartLineItemsBlock class.
|
||||
*/
|
||||
class CartLineItemsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-line-items-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryBlock class.
|
||||
*/
|
||||
class CartOrderSummaryBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryDiscountBlock class.
|
||||
*/
|
||||
class CartOrderSummaryDiscountBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-discount-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryFeeBlock class.
|
||||
*/
|
||||
class CartOrderSummaryFeeBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-fee-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryHeadingBlock class.
|
||||
*/
|
||||
class CartOrderSummaryHeadingBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-heading-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryShippingBlock class.
|
||||
*/
|
||||
class CartOrderSummaryShippingBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-shipping-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummarySubtotalBlock class.
|
||||
*/
|
||||
class CartOrderSummarySubtotalBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-subtotal-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryTaxesBlock class.
|
||||
*/
|
||||
class CartOrderSummaryTaxesBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-taxes-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartTotalsBlock class.
|
||||
*/
|
||||
class CartTotalsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-totals-block';
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes ) {
|
||||
/**
|
||||
* Fires before checkout block scripts are enqueued.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_before' );
|
||||
parent::enqueue_assets( $attributes );
|
||||
/**
|
||||
* 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 '[woocommerce_checkout]';
|
||||
}
|
||||
|
||||
// Deregister core checkout scripts and styles.
|
||||
wp_dequeue_script( 'wc-checkout' );
|
||||
wp_dequeue_script( 'wc-password-strength-meter' );
|
||||
wp_dequeue_script( 'selectWoo' );
|
||||
wp_dequeue_style( 'select2' );
|
||||
|
||||
/**
|
||||
* 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(
|
||||
'allowedCountries',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_allowed_countries() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'allowedStates',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_allowed_country_states() );
|
||||
},
|
||||
true
|
||||
);
|
||||
if ( wc_shipping_enabled() ) {
|
||||
$this->asset_data_registry->add(
|
||||
'shippingCountries',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_shipping_countries() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'shippingStates',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_shipping_country_states() );
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'countryLocale',
|
||||
function() {
|
||||
// Merge country and state data to work around https://github.com/woocommerce/woocommerce/issues/28944.
|
||||
$country_locale = wc()->countries->get_country_locale();
|
||||
$states = wc()->countries->get_states();
|
||||
|
||||
foreach ( $states as $country => $states ) {
|
||||
if ( empty( $states ) ) {
|
||||
$country_locale[ $country ]['state']['required'] = false;
|
||||
$country_locale[ $country ]['state']['hidden'] = true;
|
||||
}
|
||||
}
|
||||
return $country_locale;
|
||||
},
|
||||
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 );
|
||||
|
||||
$pickup_location_settings = get_option( 'woocommerce_pickup_location_settings', [] );
|
||||
$local_pickup_enabled = wc_string_to_bool( $pickup_location_settings['enabled'] ?? 'no' );
|
||||
|
||||
$this->asset_data_registry->add( 'localPickupEnabled', $local_pickup_enabled, 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->hydrate_from_api();
|
||||
$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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes accents from an array of values, sorts by the values, then returns the original array values sorted.
|
||||
*
|
||||
* @param array $array Array of values to sort.
|
||||
* @return array Sorted array.
|
||||
*/
|
||||
protected function deep_sort_with_accents( $array ) {
|
||||
if ( ! is_array( $array ) || empty( $array ) ) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
if ( is_array( reset( $array ) ) ) {
|
||||
return array_map( [ $this, 'deep_sort_with_accents' ], $array );
|
||||
}
|
||||
|
||||
$array_without_accents = array_map( 'remove_accents', array_map( 'wc_strtolower', array_map( 'html_entity_decode', $array ) ) );
|
||||
asort( $array_without_accents );
|
||||
return array_replace( $array_without_accents, $array );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate the checkout block with data from the API.
|
||||
*/
|
||||
protected function hydrate_from_api() {
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
|
||||
|
||||
// Print existing notices now, otherwise they are caught by the Cart
|
||||
// Controller and converted to exceptions.
|
||||
wc_print_notices();
|
||||
add_filter( 'woocommerce_store_api_disable_nonce_check', '__return_true' );
|
||||
|
||||
$rest_preload_api_requests = rest_preload_api_request( [], '/wc/store/v1/checkout' );
|
||||
$this->asset_data_registry->add( 'checkoutData', $rest_preload_api_requests['/wc/store/v1/checkout']['body'] ?? [] );
|
||||
|
||||
remove_filter( 'woocommerce_store_api_disable_nonce_check', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutActionsBlock class.
|
||||
*/
|
||||
class CheckoutActionsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-actions-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutBillingAddressBlock class.
|
||||
*/
|
||||
class CheckoutBillingAddressBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-billing-address-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutContactInformationBlock class.
|
||||
*/
|
||||
class CheckoutContactInformationBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-contact-information-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutExpressPaymentBlock class.
|
||||
*/
|
||||
class CheckoutExpressPaymentBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-express-payment-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutFieldsBlock class.
|
||||
*/
|
||||
class CheckoutFieldsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-fields-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderNoteBlock class.
|
||||
*/
|
||||
class CheckoutOrderNoteBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-note-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryDiscountBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryDiscountBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-discount-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryFeeBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryFeeBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-fee-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryShippingBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryShippingBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-shipping-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummarySubtotalBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummarySubtotalBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-subtotal-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryTaxesBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryTaxesBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-taxes-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutPaymentBlock class.
|
||||
*/
|
||||
class CheckoutPaymentBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-payment-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutPickupOptionsBlock class.
|
||||
*/
|
||||
class CheckoutPickupOptionsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-pickup-options-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutShippingAddressBlock class.
|
||||
*/
|
||||
class CheckoutShippingAddressBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-shipping-address-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutShippingMethodBlock class.
|
||||
*/
|
||||
class CheckoutShippingMethodBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-shipping-method-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutShippingMethodsBlock class.
|
||||
*/
|
||||
class CheckoutShippingMethodsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-shipping-methods-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutTermsBlock class.
|
||||
*/
|
||||
class CheckoutTermsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-terms-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutTotalsBlock class.
|
||||
*/
|
||||
class CheckoutTotalsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-totals-block';
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
use WC_Query;
|
||||
|
||||
/**
|
||||
* Classic Single Product 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' ) );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
$archive_templates = array( 'archive-product', 'taxonomy-product_cat', 'taxonomy-product_tag', ProductAttributeTemplate::SLUG, ProductSearchResultsTemplate::SLUG );
|
||||
|
||||
if ( 'single-product' === $attributes['template'] ) {
|
||||
return $this->render_single_product();
|
||||
} elseif ( 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( 'is_rendering_php_template', true, true );
|
||||
|
||||
// Set this so filter blocks being used as widgets know when to render.
|
||||
$this->asset_data_registry->add( 'has_filterable_products', true, true );
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'page_url',
|
||||
html_entity_decode( get_pagenum_link() ),
|
||||
''
|
||||
);
|
||||
|
||||
return $this->render_archive_product();
|
||||
} else {
|
||||
ob_start();
|
||||
|
||||
echo "You're using the ClassicTemplate block";
|
||||
|
||||
wp_reset_postdata();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* EmptyCartBlock class.
|
||||
*/
|
||||
class EmptyCartBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'empty-cart-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* EmptyMiniCartContentsBlock class.
|
||||
*/
|
||||
class EmptyMiniCartContentsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'empty-mini-cart-contents-block';
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
<?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>', implode( ' ', $classes ), $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;', $attributes['imageFit'] );
|
||||
|
||||
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" />',
|
||||
wp_kses_post( $attributes['alt'] ?: $this->get_item_title( $item ) ),
|
||||
$this->block_name,
|
||||
$image_url,
|
||||
$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( 'min_height', wc_get_theme_support( 'featured_block::min_height', 500 ), true );
|
||||
$this->asset_data_registry->add( 'default_height', wc_get_theme_support( 'featured_block::default_height', 500 ), true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* FilledCartBlock class.
|
||||
*/
|
||||
class FilledCartBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'filled-cart-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* FilledMiniCartContentsBlock class.
|
||||
*/
|
||||
class FilledMiniCartContentsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'filled-mini-cart-contents-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* FilledCartBlock class.
|
||||
*/
|
||||
class FilterWrapper extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'filter-wrapper';
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,622 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 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, 'enqueue_wc_settings' ), 1 );
|
||||
// We need this action to run after the equivalent in AssetDataRegistry.
|
||||
add_action( 'wp_print_footer_scripts', array( $this, 'print_lazy_load_scripts' ), 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
''
|
||||
);
|
||||
|
||||
$cart_payload = $this->get_cart_payload();
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'cartTotals',
|
||||
isset( $cart_payload['totals'] ) ? $cart_payload['totals'] : null,
|
||||
null
|
||||
);
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'cartItemsCount',
|
||||
isset( $cart_payload['items_count'] ) ? $cart_payload['items_count'] : null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
$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()
|
||||
) {
|
||||
$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 = 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' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to enqueue `wc-settings` script and dequeue it later on so when
|
||||
* AssetDataRegistry runs, it appears enqueued- This allows the necessary
|
||||
* data to be printed to the page.
|
||||
*/
|
||||
public function enqueue_wc_settings() {
|
||||
// Return early if another block has already enqueued `wc-settings`.
|
||||
if ( wp_script_is( 'wc-settings', 'enqueued' ) ) {
|
||||
return;
|
||||
}
|
||||
// We are lazy-loading `wc-settings`, but we need to enqueue it here so
|
||||
// AssetDataRegistry knows it's going to load.
|
||||
wp_enqueue_script( 'wc-settings' );
|
||||
// After AssetDataRegistry function runs, we dequeue `wc-settings`.
|
||||
add_action( 'wp_print_footer_scripts', array( $this, 'dequeue_wc_settings' ), 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to dequeue `wc-settings` script.
|
||||
*/
|
||||
public function dequeue_wc_settings() {
|
||||
wp_dequeue_script( 'wc-settings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
);
|
||||
}
|
||||
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 ) || isset( $wp_scripts->queue[ $script->handle ] ) ) {
|
||||
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();
|
||||
|
||||
$this->scripts_to_lazy_load[ $script->handle ] = array(
|
||||
'src' => preg_match( '|^(https?:)?//|', $script->src ) ? $script->src : $site_url . $script->src,
|
||||
'version' => $script->ver,
|
||||
'before' => $wp_scripts->print_inline_script( $script->handle, 'before', false ),
|
||||
'after' => $wp_scripts->print_inline_script( $script->handle, 'after', false ),
|
||||
'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;
|
||||
}
|
||||
|
||||
$cart = $this->get_cart_instance();
|
||||
$cart_contents_total = $cart->get_subtotal();
|
||||
|
||||
if ( $cart->display_prices_including_tax() ) {
|
||||
$cart_contents_total += $cart->get_subtotal_tax();
|
||||
}
|
||||
|
||||
return '<span class="wc-block-mini-cart__amount">' . esc_html( wp_strip_all_tags( wc_price( $cart_contents_total ) ) ) . '</span>
|
||||
' . $this->get_include_tax_label_markup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the markup for render the tax label.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_include_tax_label_markup() {
|
||||
$cart = $this->get_cart_instance();
|
||||
$cart_contents_total = $cart->get_subtotal();
|
||||
|
||||
return ( ! empty( $this->tax_label ) && 0 !== $cart_contents_total ) ? ( "<small class='wc-block-mini-cart__tax-label'>" . 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( $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 '';
|
||||
}
|
||||
|
||||
$cart = $this->get_cart_instance();
|
||||
$cart_contents_count = $cart->get_cart_contents_count();
|
||||
$cart_contents_total = $cart->get_subtotal();
|
||||
|
||||
if ( $cart->display_prices_including_tax() ) {
|
||||
$cart_contents_total += $cart->get_subtotal_tax();
|
||||
}
|
||||
|
||||
$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'];
|
||||
|
||||
$aria_label = sprintf(
|
||||
/* translators: %1$d is the number of products in the cart. %2$s is the cart total */
|
||||
_n(
|
||||
'%1$d item in cart, total price of %2$s',
|
||||
'%1$d items in cart, total price of %2$s',
|
||||
$cart_contents_count,
|
||||
'woocommerce'
|
||||
),
|
||||
$cart_contents_count,
|
||||
wp_strip_all_tags( wc_price( $cart_contents_total ) )
|
||||
);
|
||||
$icon = '<svg class="wc-block-mini-cart__icon" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.84614 18.2769C7.89712 18.2769 7.93845 18.2356 7.93845 18.1846C7.93845 18.1336 7.89712 18.0923 7.84614 18.0923C7.79516 18.0923 7.75384 18.1336 7.75384 18.1846C7.75384 18.2356 7.79516 18.2769 7.84614 18.2769ZM6.03076 18.1846C6.03076 17.182 6.84353 16.3692 7.84614 16.3692C8.84875 16.3692 9.66152 17.182 9.66152 18.1846C9.66152 19.1872 8.84875 20 7.84614 20C6.84353 20 6.03076 19.1872 6.03076 18.1846Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.3231 18.2769C17.3741 18.2769 17.4154 18.2356 17.4154 18.1846C17.4154 18.1336 17.3741 18.0923 17.3231 18.0923C17.2721 18.0923 17.2308 18.1336 17.2308 18.1846C17.2308 18.2356 17.2721 18.2769 17.3231 18.2769ZM15.5077 18.1846C15.5077 17.182 16.3205 16.3692 17.3231 16.3692C18.3257 16.3692 19.1385 17.182 19.1385 18.1846C19.1385 19.1872 18.3257 20 17.3231 20C16.3205 20 15.5077 19.1872 15.5077 18.1846Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0631 9.53835L19.4662 12.6685L19.4648 12.6757L19.4648 12.6757C19.3424 13.2919 19.0072 13.8454 18.5178 14.2394C18.031 14.6312 17.4226 14.8404 16.798 14.8308H8.44017C7.81556 14.8404 7.20714 14.6312 6.72038 14.2394C6.2312 13.8456 5.89605 13.2924 5.77352 12.6765L5.77335 12.6757L4.33477 5.48814C4.3286 5.46282 4.32345 5.43711 4.31934 5.41104L3.61815 1.90768H0.953842C0.42705 1.90768 0 1.48063 0 0.953842C0 0.42705 0.42705 0 0.953842 0H4.4C4.85462 0 5.24607 0.320858 5.33529 0.766644L6.04403 4.30769H12.785C13.0114 4.99157 13.3319 5.63258 13.7312 6.21538H6.42585L7.64421 12.3026L7.64449 12.304C7.67966 12.4811 7.77599 12.6402 7.91662 12.7534C8.05725 12.8666 8.23322 12.9267 8.41372 12.9233L8.432 12.9231H16.8062L16.8244 12.9233C17.0049 12.9267 17.1809 12.8666 17.3215 12.7534C17.4614 12.6408 17.5575 12.4828 17.5931 12.3068L17.5937 12.304L18.1649 9.30867C18.762 9.45873 19.387 9.53842 20.0307 9.53842C20.0415 9.53842 20.0523 9.5384 20.0631 9.53835Z" fill="currentColor"/>
|
||||
</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">' . $cart_contents_count . '</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="' . $wrapper_classes . '" style="visibility:hidden" aria-hidden="true">
|
||||
<button class="wc-block-mini-cart__button" aria-label="' . esc_attr( $aria_label ) . '" disabled>' . $button_html . '</button>
|
||||
</div>';
|
||||
}
|
||||
|
||||
$template_part_contents = '';
|
||||
|
||||
// Determine if we need to load the template part from the theme, or WooCommerce in that order.
|
||||
$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" aria-label="' . esc_attr( $aria_label ) . '">' . $button_html . '</button>
|
||||
<div class="wc-block-mini-cart__drawer is-loading is-mobile wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true">
|
||||
<div class="components-modal__frame wc-block-components-drawer">
|
||||
<div class="components-modal__content">
|
||||
<div class="components-modal__header">
|
||||
<div class="components-modal__header-heading-container"></div>
|
||||
</div>
|
||||
<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,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cart Payload.
|
||||
*
|
||||
* @return object;
|
||||
*/
|
||||
protected function get_cart_payload() {
|
||||
$notices = wc_get_notices(); // Backup the notices because StoreAPI will remove them.
|
||||
$payload = WC()->api->get_endpoint_data( '/wc/store/cart' );
|
||||
|
||||
if ( ! empty( $notices ) ) {
|
||||
wc_set_notices( $notices ); // Restore the notices.
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartCartButtonBlock class.
|
||||
*/
|
||||
class MiniCartCartButtonBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-cart-button-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartCheckoutButtonBlock class.
|
||||
*/
|
||||
class MiniCartCheckoutButtonBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-checkout-button-block';
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* Mini Cart 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes ) {
|
||||
parent::enqueue_assets( $attributes );
|
||||
$text_color = StyleAttributesUtils::get_text_color_class_and_style( $attributes );
|
||||
$bg_color = StyleAttributesUtils::get_background_color_class_and_style( $attributes );
|
||||
|
||||
$styles = array(
|
||||
array(
|
||||
'selector' => '.wc-block-mini-cart__drawer .components-modal__header',
|
||||
'properties' => array(
|
||||
array(
|
||||
'property' => 'color',
|
||||
'value' => $text_color ? $text_color['value'] : false,
|
||||
),
|
||||
),
|
||||
),
|
||||
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 = '';
|
||||
|
||||
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 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';
|
||||
|
||||
return $block_types;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartFooterBlock class.
|
||||
*/
|
||||
class MiniCartFooterBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-footer-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartItemsBlock class.
|
||||
*/
|
||||
class MiniCartItemsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-items-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartProductsTableBlock class.
|
||||
*/
|
||||
class MiniCartProductsTableBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-products-table-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartShoppingButtonBlock class.
|
||||
*/
|
||||
class MiniCartShoppingButtonBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-shopping-button-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartTitleBlock class.
|
||||
*/
|
||||
class MiniCartTitleBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-title-block';
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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';
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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 ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?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';
|
||||
|
||||
/**
|
||||
* 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(
|
||||
'background' => true,
|
||||
'link' => false,
|
||||
'text' => true,
|
||||
'__experimentalSkipSerialization' => true,
|
||||
),
|
||||
'__experimentalBorder' =>
|
||||
array(
|
||||
'radius' => true,
|
||||
'__experimentalSkipSerialization' => true,
|
||||
),
|
||||
'typography' =>
|
||||
array(
|
||||
'fontSize' => true,
|
||||
'__experimentalFontWeight' => true,
|
||||
'__experimentalSkipSerialization' => true,
|
||||
),
|
||||
'__experimentalSelector' => '.wp-block-button.wc-block-components-product-button .wc-block-components-product-button__button',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
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 ) {
|
||||
$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 );
|
||||
$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );
|
||||
$classname = isset( $attributes['className'] ) ? $attributes['className'] : '';
|
||||
$custom_width_classes = isset( $attributes['width'] ) ? 'has-custom-width wp-block-button__width-' . $attributes['width'] : '';
|
||||
$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(),
|
||||
$styles_and_classes['classes'],
|
||||
)
|
||||
)
|
||||
);
|
||||
/**
|
||||
* 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'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
sprintf(
|
||||
'<div class="wp-block-button wc-block-components-product-button %1$s %2$s">
|
||||
<%3$s href="%4$s" class="%5$s" style="%6$s" %7$s>%8$s</%3$s>
|
||||
</div>',
|
||||
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
|
||||
esc_attr( $classname . ' ' . $custom_width_classes ),
|
||||
$html_element,
|
||||
esc_url( $product->add_to_cart_url() ),
|
||||
isset( $args['class'] ) ? esc_attr( $args['class'] ) : '',
|
||||
esc_attr( $styles_and_classes['styles'] ),
|
||||
isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '',
|
||||
esc_html( $product->add_to_cart_text() )
|
||||
),
|
||||
$product,
|
||||
$args
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
<?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,
|
||||
);
|
||||
|
||||
/**
|
||||
* 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 ),
|
||||
'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'] );
|
||||
$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 ) : $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build hierarchical tree of categories.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @return array
|
||||
*/
|
||||
protected function build_category_tree( $categories ) {
|
||||
$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;
|
||||
}
|
||||
|
||||
$tree = $categories_by_parent['cat-0'];
|
||||
unset( $categories_by_parent['cat-0'] );
|
||||
|
||||
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>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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 ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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">
|
||||
%3$s
|
||||
</div>',
|
||||
esc_attr( $classes_and_styles['classes'] ),
|
||||
esc_attr( $classname ),
|
||||
$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();
|
||||
|
||||
while ( have_posts() ) {
|
||||
the_post();
|
||||
woocommerce_output_product_data_tabs();
|
||||
}
|
||||
|
||||
$tabs = ob_get_clean();
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?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' => 'full-size',
|
||||
'productId' => 'number',
|
||||
'isDescendentOfQueryLoop' => 'false',
|
||||
);
|
||||
|
||||
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>
|
||||
',
|
||||
$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.
|
||||
* @return string
|
||||
*/
|
||||
private function render_image( $product ) {
|
||||
$image_info = wp_get_attachment_image_src( get_post_thumbnail_id( $product->get_id() ), 'woocommerce_thumbnail' );
|
||||
|
||||
if ( ! isset( $image_info[0] ) ) {
|
||||
// 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 sprintf( '<img src="%s" alt="" />', wc_placeholder_img_src( 'woocommerce_thumbnail' ) );
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<img data-testid="product-image" alt="%s" src="%s">',
|
||||
$product->get_title(),
|
||||
$image_info[0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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( 'is_block_theme_enabled', 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 );
|
||||
|
||||
$border_radius = StyleAttributesUtils::get_border_radius_class_and_style( $attributes );
|
||||
$margin = StyleAttributesUtils::get_margin_class_and_style( $attributes );
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
|
||||
$post_id = $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
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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
|
||||
*
|
||||
* @var 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'];
|
||||
global $product;
|
||||
$product = wc_get_product( $post_id );
|
||||
|
||||
if ( class_exists( 'WC_Frontend_Scripts' ) ) {
|
||||
$frontend_scripts = new \WC_Frontend_Scripts();
|
||||
$frontend_scripts::load_scripts();
|
||||
}
|
||||
|
||||
$classname = $attributes['className'] ?? '';
|
||||
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();
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?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' => '.wc-block-components-product-price',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
$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-price wc-block-grid__product-price %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_price_html()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,899 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use WP_Query;
|
||||
|
||||
// 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( '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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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( 'has_filterable_products', true, true );
|
||||
$this->asset_data_registry->add( 'is_rendering_php_template', true, true );
|
||||
$this->asset_data_registry->add( 'product_ids', $this->get_products_ids_by_attributes( $parsed_block ), 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(
|
||||
'post_type' => 'product',
|
||||
'post__in' => array(),
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $query['posts_per_page'],
|
||||
'orderby' => $query['orderby'],
|
||||
'order' => $query['order'],
|
||||
'offset' => $query['offset'],
|
||||
'meta_query' => array(),
|
||||
'tax_query' => array(),
|
||||
);
|
||||
|
||||
return $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 the product ids based on the attributes and global query.
|
||||
* This is used to allow the filter blocks to render data that matches with variations. More details here: https://github.com/woocommerce/woocommerce-blocks/issues/7245
|
||||
*
|
||||
* @param array $parsed_block The block being rendered.
|
||||
* @return array
|
||||
*/
|
||||
private function get_products_ids_by_attributes( $parsed_block ) {
|
||||
$query = $this->merge_queries(
|
||||
array(
|
||||
'post_type' => 'product',
|
||||
'post__in' => array(),
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => -1,
|
||||
'meta_query' => array(),
|
||||
'tax_query' => array(),
|
||||
),
|
||||
$this->get_queries_by_custom_attributes( $parsed_block ),
|
||||
$this->get_global_query( $parsed_block )
|
||||
);
|
||||
|
||||
$products = new \WP_Query( $query );
|
||||
$post_ids = wp_list_pluck( $products->posts, 'ID' );
|
||||
|
||||
return $post_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?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',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function filter_rating_html( $html, $rating, $count ) {
|
||||
$product_permalink = get_permalink();
|
||||
if ( 0 < $rating || false === $product_permalink ) {
|
||||
/* translators: %s: rating */
|
||||
$label = sprintf( __( 'Rated %s out of 5', 'woocommerce' ), $rating );
|
||||
$html = '<div class="wc-block-components-product-rating__stars wc-block-grid__product-rating__stars" role="img" aria-label="' . esc_attr( $label ) . '">' . wc_get_star_rating_html( $rating, $count ) . '</div>';
|
||||
} else {
|
||||
$product_review_url = esc_url( $product_permalink . '#reviews' );
|
||||
$html = '<a class="wc-block-components-product-rating__link" href="' . $product_review_url . '">' . __( 'Add review', 'woocommerce' ) . '</a>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
|
||||
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );
|
||||
|
||||
add_filter(
|
||||
'woocommerce_product_get_rating_html',
|
||||
[ $this, 'filter_rating_html' ],
|
||||
10,
|
||||
3
|
||||
);
|
||||
|
||||
$rating_html = wc_get_rating_html( $product->get_average_rating() );
|
||||
|
||||
remove_filter(
|
||||
'woocommerce_product_get_rating_html',
|
||||
[ $this, '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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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'] ),
|
||||
$classname,
|
||||
esc_attr( $classes_and_styles['styles'] ),
|
||||
$product_results_count
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?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 %1$s" style="%2$s">
|
||||
SKU:
|
||||
<strong>%3$s</strong>
|
||||
</div>',
|
||||
esc_attr( $styles_and_classes['classes'] ),
|
||||
esc_attr( $styles_and_classes['styles'] ?? '' ),
|
||||
$product_sku
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?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'] : '';
|
||||
|
||||
$output = '<div class="wc-block-components-product-sale-badge '
|
||||
. esc_attr( $classes_and_styles['classes'] ) . ' '
|
||||
. esc_attr( $classname ) . '" '
|
||||
. 'style="' . 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>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?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', '>=' )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?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';
|
||||
|
||||
/**
|
||||
* 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' => false,
|
||||
'background' => false,
|
||||
'text' => true,
|
||||
),
|
||||
'typography' =>
|
||||
array(
|
||||
'fontSize' => true,
|
||||
),
|
||||
'__experimentalSelector' => '.wc-block-components-product-stock-indicator',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ' . 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;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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 ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?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 ] );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user