Merged in feature/280-dev-dev01 (pull request #21)
auto-patch 280-dev-dev01-2024-01-19T16_41_58 * auto-patch 280-dev-dev01-2024-01-19T16_41_58
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\AI;
|
||||
|
||||
use Automattic\Jetpack\Config;
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\Jetpack\Connection\Utils;
|
||||
|
||||
/**
|
||||
* Class Configuration
|
||||
*/
|
||||
class Configuration {
|
||||
|
||||
/**
|
||||
* The name of the option that stores the site owner's consent to connect to the AI API.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $consent_option_name = 'woocommerce_blocks_allow_ai_connection';
|
||||
/**
|
||||
* The Jetpack connection manager.
|
||||
*
|
||||
* @var Manager
|
||||
*/
|
||||
private $manager;
|
||||
/**
|
||||
* The Jetpack configuration.
|
||||
*
|
||||
* @var Config
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Configuration constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! class_exists( 'Automattic\Jetpack\Connection\Manager' ) || ! class_exists( 'Automattic\Jetpack\Config' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->manager = new Manager( 'woocommerce_blocks' );
|
||||
$this->config = new Config();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the site and user connection and registration.
|
||||
*
|
||||
* @return bool|\WP_Error
|
||||
*/
|
||||
public function init() {
|
||||
if ( ! $this->should_connect() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->enable_connection_feature();
|
||||
|
||||
return $this->register_and_connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the site should connect to Jetpack.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_connect() {
|
||||
$site_owner_consent = get_option( $this->consent_option_name );
|
||||
|
||||
return $site_owner_consent && class_exists( 'Automattic\Jetpack\Connection\Utils' ) && class_exists( 'Automattic\Jetpack\Connection\Manager' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Jetpack's connection feature within the WooCommerce Blocks plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function enable_connection_feature() {
|
||||
$this->config->ensure(
|
||||
'connection',
|
||||
array(
|
||||
'slug' => 'woocommerce/woocommerce-blocks',
|
||||
'name' => 'WooCommerce Blocks',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the site with Jetpack.
|
||||
*
|
||||
* @return bool|\WP_Error
|
||||
*/
|
||||
private function register_and_connect() {
|
||||
Utils::init_default_constants();
|
||||
|
||||
$jetpack_id = \Jetpack_Options::get_option( 'id' );
|
||||
$jetpack_public = \Jetpack_Options::get_option( 'public' );
|
||||
|
||||
$register = $jetpack_id && $jetpack_public ? true : $this->manager->register();
|
||||
|
||||
if ( true === $register && ! $this->manager->is_user_connected() ) {
|
||||
$this->manager->connect_user();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the site with Jetpack.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function unregister_site() {
|
||||
if ( $this->manager->is_connected() ) {
|
||||
$this->manager->remove_connection();
|
||||
}
|
||||
}
|
||||
}
|
||||
161
wp/wp-content/plugins/woocommerce/src/Blocks/AI/Connection.php
Normal file
161
wp/wp-content/plugins/woocommerce/src/Blocks/AI/Connection.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\AI;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
use WpOrg\Requests\Exception;
|
||||
use WpOrg\Requests\Requests;
|
||||
|
||||
/**
|
||||
* Class Connection
|
||||
*/
|
||||
class Connection {
|
||||
const TEXT_COMPLETION_API_URL = 'https://public-api.wordpress.com/wpcom/v2/text-completion';
|
||||
|
||||
/**
|
||||
* The post request.
|
||||
*
|
||||
* @param string $token The JWT token.
|
||||
* @param string $prompt The prompt to send to the API.
|
||||
* @param int $timeout The timeout for the request.
|
||||
* @param string $response_format The response format.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetch_ai_response( $token, $prompt, $timeout = 15, $response_format = null ) {
|
||||
if ( $token instanceof \WP_Error ) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$body = array(
|
||||
'feature' => 'woocommerce_blocks_patterns',
|
||||
'prompt' => $prompt,
|
||||
'token' => $token,
|
||||
);
|
||||
|
||||
if ( $response_format ) {
|
||||
$body['response_format'] = $response_format;
|
||||
}
|
||||
|
||||
$response = wp_remote_post(
|
||||
self::TEXT_COMPLETION_API_URL,
|
||||
array(
|
||||
'body' => $body,
|
||||
'timeout' => $timeout,
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new \WP_Error( $response->get_error_code(), esc_html__( 'Failed to connect with the AI endpoint: try again later.', 'woocommerce' ), $response->get_error_message() );
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
|
||||
return json_decode( $body, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the AI responses in parallel using the given token and prompts.
|
||||
*
|
||||
* @param string $token The JWT token.
|
||||
* @param array $prompts The prompts to send to the API.
|
||||
* @param int $timeout The timeout for the request.
|
||||
* @param string $response_format The response format.
|
||||
*
|
||||
* @return array|WP_Error The responses or a WP_Error object.
|
||||
*/
|
||||
public function fetch_ai_responses( $token, array $prompts, $timeout = 15, $response_format = null ) {
|
||||
if ( $token instanceof \WP_Error ) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$requests = array();
|
||||
foreach ( $prompts as $prompt ) {
|
||||
$data = array(
|
||||
'feature' => 'woocommerce_blocks_patterns',
|
||||
'prompt' => $prompt,
|
||||
'token' => $token,
|
||||
);
|
||||
|
||||
if ( $response_format ) {
|
||||
$data['response_format'] = $response_format;
|
||||
}
|
||||
|
||||
$requests[] = array(
|
||||
'url' => self::TEXT_COMPLETION_API_URL,
|
||||
'type' => 'POST',
|
||||
'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
|
||||
'data' => wp_json_encode( $data ),
|
||||
);
|
||||
}
|
||||
|
||||
$responses = Requests::request_multiple( $requests, array( 'timeout' => $timeout ) );
|
||||
|
||||
$processed_responses = array();
|
||||
|
||||
foreach ( $responses as $key => $response ) {
|
||||
if ( is_wp_error( $response ) || is_a( $response, Exception::class ) ) {
|
||||
return new WP_Error( 'failed-to-connect-with-the-ai-endpoint', esc_html__( 'Failed to connect with the AI endpoint: try again later.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$processed_responses[ $key ] = json_decode( $response->body, true );
|
||||
}
|
||||
|
||||
return $processed_responses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the site ID.
|
||||
*
|
||||
* @return integer|\WP_Error The site ID or a WP_Error object.
|
||||
*/
|
||||
public function get_site_id() {
|
||||
if ( ! class_exists( Jetpack_Options::class ) ) {
|
||||
return new \WP_Error( 'site-id-error', esc_html__( 'Failed to fetch the site ID: try again later.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$site_id = Jetpack_Options::get_option( 'id' );
|
||||
|
||||
if ( ! $site_id ) {
|
||||
return new \WP_Error( 'site-id-error', esc_html__( 'Failed to fetch the site ID: The site is not registered.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
return $site_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the JWT token.
|
||||
*
|
||||
* @param integer $site_id The site ID.
|
||||
*
|
||||
* @return string|\WP_Error The JWT token or a WP_Error object.
|
||||
*/
|
||||
public function get_jwt_token( $site_id ) {
|
||||
if ( is_wp_error( $site_id ) ) {
|
||||
return $site_id;
|
||||
}
|
||||
|
||||
$request = Client::wpcom_json_api_request_as_user(
|
||||
sprintf( '/sites/%d/jetpack-openai-query/jwt', $site_id ),
|
||||
'2',
|
||||
array(
|
||||
'method' => 'POST',
|
||||
'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
|
||||
)
|
||||
);
|
||||
|
||||
$response = json_decode( wp_remote_retrieve_body( $request ) );
|
||||
|
||||
if ( $response instanceof \WP_Error ) {
|
||||
return new \WP_Error( $response->get_error_code(), esc_html__( 'Failed to generate the JWT token', 'woocommerce' ), $response->get_error_message() );
|
||||
}
|
||||
|
||||
if ( ! isset( $response->token ) ) {
|
||||
return new \WP_Error( 'failed-to-retrieve-jwt-token', esc_html__( 'Failed to retrieve the JWT token: Try again later.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
return $response->token;
|
||||
}
|
||||
}
|
||||
95
wp/wp-content/plugins/woocommerce/src/Blocks/Assets.php
Normal file
95
wp/wp-content/plugins/woocommerce/src/Blocks/Assets.php
Normal file
@@ -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 );
|
||||
}
|
||||
}
|
||||
327
wp/wp-content/plugins/woocommerce/src/Blocks/Assets/Api.php
Normal file
327
wp/wp-content/plugins/woocommerce/src/Blocks/Assets/Api.php
Normal file
@@ -0,0 +1,327 @@
|
||||
<?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 = [];
|
||||
|
||||
/**
|
||||
* Determines if caching is enabled for script data.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $disable_cache = false;
|
||||
|
||||
/**
|
||||
* Stores loaded script data for the current request
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $script_data = null;
|
||||
|
||||
/**
|
||||
* Stores the hash for the script data, made up of the site url, plugin version and package path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $script_data_hash;
|
||||
|
||||
/**
|
||||
* Stores the transient key used to cache the script data. This will change if the site is accessed via HTTPS or HTTP.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $script_data_transient_key = 'woocommerce_blocks_asset_api_script_data';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
$this->disable_cache = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) || ! $this->package->feature()->is_production_environment();
|
||||
|
||||
// If the site is accessed via HTTPS, change the transient key. This is to prevent the script URLs being cached
|
||||
// with the first scheme they are accessed on after cache expiry.
|
||||
if ( is_ssl() ) {
|
||||
$this->script_data_transient_key .= '_ssl';
|
||||
}
|
||||
if ( ! $this->disable_cache ) {
|
||||
$this->script_data_hash = $this->get_script_data_hash();
|
||||
}
|
||||
add_action( 'shutdown', array( $this, 'update_script_data_cache' ), 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'assets/client/blocks' 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( 'assets/client/blocks/' . $path . $block_name . '/block.json' );
|
||||
if ( ! file_exists( $path_to_metadata_from_plugin_root ) ) {
|
||||
return false;
|
||||
}
|
||||
return $path_to_metadata_from_plugin_root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a hash containing the site url, plugin version and package path.
|
||||
*
|
||||
* Moving the plugin, changing the version, or changing the site url will result in a new hash and the cache will be invalidated.
|
||||
*
|
||||
* @return string The generated hash.
|
||||
*/
|
||||
private function get_script_data_hash() {
|
||||
return md5( get_option( 'siteurl', '' ) . $this->package->get_version() . $this->package->get_path() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and load cached script data from the transient cache.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_cached_script_data() {
|
||||
if ( $this->disable_cache ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$transient_value = json_decode( (string) get_transient( $this->script_data_transient_key ), true );
|
||||
|
||||
if (
|
||||
json_last_error() !== JSON_ERROR_NONE ||
|
||||
empty( $transient_value ) ||
|
||||
empty( $transient_value['script_data'] ) ||
|
||||
empty( $transient_value['version'] ) ||
|
||||
$transient_value['version'] !== $this->package->get_version() ||
|
||||
empty( $transient_value['hash'] ) ||
|
||||
$transient_value['hash'] !== $this->script_data_hash
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (array) ( $transient_value['script_data'] ?? [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Store all cached script data in the transient cache.
|
||||
*/
|
||||
public function update_script_data_cache() {
|
||||
if ( is_null( $this->script_data ) || $this->disable_cache ) {
|
||||
return;
|
||||
}
|
||||
set_transient(
|
||||
$this->script_data_transient_key,
|
||||
wp_json_encode(
|
||||
array(
|
||||
'script_data' => $this->script_data,
|
||||
'version' => $this->package->get_version(),
|
||||
'hash' => $this->script_data_hash,
|
||||
)
|
||||
),
|
||||
DAY_IN_SECONDS * 30
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [] ) {
|
||||
if ( ! $relative_src ) {
|
||||
return array(
|
||||
'src' => '',
|
||||
'version' => '1',
|
||||
'dependencies' => $dependencies,
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_null( $this->script_data ) ) {
|
||||
$this->script_data = $this->get_cached_script_data();
|
||||
}
|
||||
|
||||
if ( empty( $this->script_data[ $relative_src ] ) ) {
|
||||
$asset_path = $this->package->get_path( str_replace( '.js', '.asset.php', $relative_src ) );
|
||||
// The following require is safe because we are checking if the file exists and it is not a user input.
|
||||
// nosemgrep audit.php.lang.security.file.inclusion-arg.
|
||||
$asset = file_exists( $asset_path ) ? require $asset_path : [];
|
||||
|
||||
$this->script_data[ $relative_src ] = array(
|
||||
'src' => $this->get_asset_url( $relative_src ),
|
||||
'version' => ! empty( $asset['version'] ) ? $asset['version'] : $this->get_file_version( $relative_src ),
|
||||
'dependencies' => ! empty( $asset['dependencies'] ) ? $asset['dependencies'] : [],
|
||||
);
|
||||
}
|
||||
|
||||
// Return asset details as well as the requested dependencies array.
|
||||
return [
|
||||
'src' => $this->script_data[ $relative_src ]['src'],
|
||||
'version' => $this->script_data[ $relative_src ]['version'],
|
||||
'dependencies' => array_merge( $this->script_data[ $relative_src ]['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)'.
|
||||
* @param boolean $rtl Optional. Whether or not to register RTL styles.
|
||||
*/
|
||||
public function register_style( $handle, $relative_src, $deps = [], $media = 'all', $rtl = false ) {
|
||||
$filename = str_replace( plugins_url( '/', dirname( __DIR__ ) ), '', $relative_src );
|
||||
$src = $this->get_asset_url( $relative_src );
|
||||
$ver = $this->get_file_version( $filename );
|
||||
wp_register_style( $handle, $src, $deps, $ver, $media );
|
||||
|
||||
if ( $rtl ) {
|
||||
wp_style_add_data( $handle, 'rtl', 'replace' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 "assets/client/blocks/$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,452 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Assets;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Hydration;
|
||||
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() {
|
||||
add_action( 'init', array( $this, 'register_data_script' ) );
|
||||
add_action( is_admin() ? 'admin_print_footer_scripts' : 'wp_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' ),
|
||||
'dateFormat' => wc_date_format(),
|
||||
'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' => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ),
|
||||
'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. This should use camelCase.
|
||||
* @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 the 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[ $path ] = Package::container()->get( Hydration::class )->get_rest_api_response_data( $path );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate some data from the API.
|
||||
*
|
||||
* @param string $key The key used to reference the data being registered.
|
||||
* @param string $path REST API path to preload.
|
||||
* @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 hydrate_data_from_api_request( $key, $path, $check_key_exists = false ) {
|
||||
$this->add(
|
||||
$key,
|
||||
function() use ( $path ) {
|
||||
if ( isset( $this->preloaded_api_requests[ $path ], $this->preloaded_api_requests[ $path ]['body'] ) ) {
|
||||
return $this->preloaded_api_requests[ $path ]['body'];
|
||||
}
|
||||
$response = Package::container()->get( Hydration::class )->get_rest_api_response_data( $path );
|
||||
return $response['body'] ?? '';
|
||||
},
|
||||
$check_key_exists
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
'assets/client/blocks/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,366 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
|
||||
/**
|
||||
* 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_action( 'enqueue_block_editor_assets', array( $this, 'register_and_enqueue_site_editor_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-packages-style', plugins_url( $this->api->get_block_asset_build_path( 'packages-style', 'css' ), dirname( __DIR__ ) ), array(), 'all', true );
|
||||
$this->register_style( 'wc-blocks-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks', 'css' ), dirname( __DIR__ ) ), array(), 'all', true );
|
||||
$this->register_style( 'wc-blocks-editor-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-editor-style', 'css' ), dirname( __DIR__ ) ), array( 'wp-edit-blocks' ), 'all', true );
|
||||
|
||||
$this->api->register_script( 'wc-blocks-middleware', 'assets/client/blocks/wc-blocks-middleware.js', array(), false );
|
||||
$this->api->register_script( 'wc-blocks-data-store', 'assets/client/blocks/wc-blocks-data.js', array( 'wc-blocks-middleware' ) );
|
||||
$this->api->register_script( 'wc-blocks-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-vendors' ), array(), false );
|
||||
$this->api->register_script( 'wc-blocks-registry', 'assets/client/blocks/wc-blocks-registry.js', array(), false );
|
||||
$this->api->register_script( 'wc-blocks', $this->api->get_block_asset_build_path( 'wc-blocks' ), array( 'wc-blocks-vendors' ), false );
|
||||
$this->api->register_script( 'wc-blocks-shared-context', 'assets/client/blocks/wc-blocks-shared-context.js' );
|
||||
$this->api->register_script( 'wc-blocks-shared-hocs', 'assets/client/blocks/wc-blocks-shared-hocs.js', array(), false );
|
||||
|
||||
// The price package is shared externally so has no blocks prefix.
|
||||
$this->api->register_script( 'wc-price-format', 'assets/client/blocks/price-format.js', array(), false );
|
||||
|
||||
$this->api->register_script( 'wc-blocks-checkout', 'assets/client/blocks/blocks-checkout.js', array() );
|
||||
$this->api->register_script( 'wc-blocks-components', 'assets/client/blocks/blocks-components.js', array() );
|
||||
|
||||
// Register the interactivity components here for now.
|
||||
$this->api->register_script( 'wc-interactivity-dropdown', 'assets/client/blocks/wc-interactivity-dropdown.js', array() );
|
||||
$this->api->register_script( 'wc-interactivity-checkbox-list', 'assets/client/blocks/wc-interactivity-checkbox-list.js', array() );
|
||||
$this->register_style( 'wc-interactivity-checkbox-list', plugins_url( $this->api->get_block_asset_build_path( 'wc-interactivity-checkbox-list', 'css' ), __DIR__ ), array(), 'all', true );
|
||||
$this->register_style( 'wc-interactivity-dropdown', plugins_url( $this->api->get_block_asset_build_path( 'wc-interactivity-dropdown', 'css' ), __DIR__ ), array(), 'all', true );
|
||||
|
||||
wp_add_inline_script(
|
||||
'wc-blocks-middleware',
|
||||
"
|
||||
var wcBlocksMiddlewareConfig = {
|
||||
storeApiNonce: '" . esc_js( wp_create_nonce( 'wc_store_api' ) ) . "',
|
||||
wcStoreApiNonceTimestamp: '" . esc_js( time() ) . "'
|
||||
};
|
||||
",
|
||||
'before'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register and enqueue assets for exclusive usage within the Site Editor.
|
||||
*/
|
||||
public function register_and_enqueue_site_editor_assets() {
|
||||
$this->api->register_script( 'wc-blocks-classic-template-revert-button', 'assets/client/blocks/wc-blocks-classic-template-revert-button.js' );
|
||||
$this->api->register_style( 'wc-blocks-classic-template-revert-button-style', 'assets/client/blocks/wc-blocks-classic-template-revert-button-style.css' );
|
||||
|
||||
$current_screen = get_current_screen();
|
||||
if ( $current_screen instanceof \WP_Screen && 'site-editor' === $current_screen->base ) {
|
||||
wp_enqueue_script( 'wc-blocks-classic-template-revert-button' );
|
||||
wp_enqueue_style( 'wc-blocks-classic-template-revert-button-style' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, array( 'prefetch', 'prerender' ), true ) || is_admin() ) {
|
||||
return $urls;
|
||||
}
|
||||
|
||||
// We only need to prefetch when the cart has contents.
|
||||
$cart = wc()->cart;
|
||||
|
||||
if ( ! $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 = array();
|
||||
|
||||
// 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 = array();
|
||||
$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 array();
|
||||
}
|
||||
$script_data = $this->api->get_script_data(
|
||||
$this->api->get_block_asset_build_path( $filename )
|
||||
);
|
||||
$resources = array_merge(
|
||||
array( esc_url( 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[] = esc_url( 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;
|
||||
},
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = array(), $media = 'all', $rtl = false ) {
|
||||
$filename = str_replace( plugins_url( '/', dirname( __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 = array( '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 )
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
401
wp/wp-content/plugins/woocommerce/src/Blocks/BlockPatterns.php
Normal file
401
wp/wp-content/plugins/woocommerce/src/Blocks/BlockPatterns.php
Normal file
@@ -0,0 +1,401 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use Automattic\WooCommerce\Blocks\Images\Pexels;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\Patterns\PatternsHelper;
|
||||
use Automattic\WooCommerce\Blocks\Patterns\PatternUpdater;
|
||||
use Automattic\WooCommerce\Blocks\Patterns\ProductUpdater;
|
||||
|
||||
/**
|
||||
* Registers patterns under the `./patterns/` directory and updates their content.
|
||||
* 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,]+/';
|
||||
const PATTERNS_AI_DATA_POST_TYPE = 'patterns_ai_data';
|
||||
|
||||
/**
|
||||
* 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' ) );
|
||||
add_action( 'update_option_woo_ai_describe_store_description', array( $this, 'schedule_on_option_update' ), 10, 2 );
|
||||
add_action( 'update_option_woo_ai_describe_store_description', array( $this, 'update_ai_connection_allowed_option' ), 10, 2 );
|
||||
add_action( 'upgrader_process_complete', array( $this, 'schedule_on_plugin_update' ), 10, 2 );
|
||||
add_action( 'woocommerce_update_patterns_content', array( $this, 'update_patterns_content' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the 'woocommerce_blocks_allow_ai_connection' option is set to true if the site is connected to AI.
|
||||
*
|
||||
* @param string $option The option name.
|
||||
* @param string $value The option value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update_ai_connection_allowed_option( $option, $value ): bool {
|
||||
$ai_connection = new Connection();
|
||||
|
||||
$site_id = $ai_connection->get_site_id();
|
||||
|
||||
if ( is_wp_error( $site_id ) ) {
|
||||
return update_option( 'woocommerce_blocks_allow_ai_connection', false );
|
||||
}
|
||||
|
||||
$token = $ai_connection->get_jwt_token( $site_id );
|
||||
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return update_option( 'woocommerce_blocks_allow_ai_connection', false );
|
||||
}
|
||||
|
||||
return update_option( 'woocommerce_blocks_allow_ai_connection', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the block patterns and categories under `./patterns/`.
|
||||
*/
|
||||
public function register_block_patterns() {
|
||||
if ( ! class_exists( 'WP_Block_Patterns_Registry' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_post_type(
|
||||
self::PATTERNS_AI_DATA_POST_TYPE,
|
||||
array(
|
||||
'labels' => array(
|
||||
'name' => __( 'Patterns AI Data', 'woocommerce' ),
|
||||
'singular_name' => __( 'Patterns AI Data', 'woocommerce' ),
|
||||
),
|
||||
'public' => false,
|
||||
'hierarchical' => false,
|
||||
'rewrite' => false,
|
||||
'query_var' => false,
|
||||
'delete_with_user' => false,
|
||||
'can_export' => true,
|
||||
)
|
||||
);
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
$dictionary = PatternsHelper::get_patterns_dictionary();
|
||||
|
||||
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', 'woocommerce' );
|
||||
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', 'woocommerce' );
|
||||
}
|
||||
|
||||
$pattern_data_from_dictionary = $this->get_pattern_from_dictionary( $dictionary, $pattern_data['slug'] );
|
||||
|
||||
// The actual pattern content is the output of the file.
|
||||
ob_start();
|
||||
|
||||
/*
|
||||
For patterns that can have AI-generated content, we need to get its content from the dictionary and pass
|
||||
it to the pattern file through the "$content" and "$images" variables.
|
||||
This is to avoid having to access the dictionary for each pattern when it's registered or inserted.
|
||||
Before the "$content" and "$images" variables were populated in each pattern. Since the pattern
|
||||
registration happens in the init hook, the dictionary was being access one for each pattern and
|
||||
for each page load. This way we only do it once on registration.
|
||||
For more context: https://github.com/woocommerce/woocommerce-blocks/pull/11733
|
||||
*/
|
||||
if ( ! is_null( $pattern_data_from_dictionary ) ) {
|
||||
$content = $pattern_data_from_dictionary['content'];
|
||||
$images = $pattern_data_from_dictionary['images'] ?? array();
|
||||
}
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the patterns content when the store description is changed.
|
||||
*
|
||||
* @param string $option The option name.
|
||||
* @param string $value The option value.
|
||||
*/
|
||||
public function schedule_on_option_update( $option, $value ) {
|
||||
$last_business_description = get_option( 'last_business_description_with_ai_content_generated' );
|
||||
|
||||
if ( $last_business_description === $value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->schedule_patterns_content_update( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the patterns content when the WooCommerce Blocks plugin is updated.
|
||||
*
|
||||
* @param \WP_Upgrader $upgrader_object WP_Upgrader instance.
|
||||
* @param array $options Array of bulk item update data.
|
||||
*/
|
||||
public function schedule_on_plugin_update( $upgrader_object, $options ) {
|
||||
if ( 'update' === $options['action'] && 'plugin' === $options['type'] && isset( $options['plugins'] ) ) {
|
||||
foreach ( $options['plugins'] as $plugin ) {
|
||||
if ( str_contains( $plugin, 'woocommerce-gutenberg-products-block.php' ) || str_contains( $plugin, 'woocommerce.php' ) ) {
|
||||
$business_description = get_option( 'woo_ai_describe_store_description' );
|
||||
|
||||
if ( $business_description ) {
|
||||
$this->schedule_patterns_content_update( $business_description );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the patterns content when the store description is changed.
|
||||
*
|
||||
* @param string $business_description The business description.
|
||||
*/
|
||||
public function schedule_patterns_content_update( $business_description ) {
|
||||
if ( ! class_exists( 'WooCommerce' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$action_scheduler = WP_PLUGIN_DIR . '/woocommerce/packages/action-scheduler/action-scheduler.php';
|
||||
|
||||
if ( ! file_exists( $action_scheduler ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once $action_scheduler;
|
||||
|
||||
as_schedule_single_action( time(), 'woocommerce_update_patterns_content', array( $business_description ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the patterns content.
|
||||
*
|
||||
* @param string $value The new value saved for the add_option_woo_ai_describe_store_description option.
|
||||
*
|
||||
* @return bool|string|\WP_Error
|
||||
*/
|
||||
public function update_patterns_content( $value ) {
|
||||
$allow_ai_connection = get_option( 'woocommerce_blocks_allow_ai_connection' );
|
||||
|
||||
if ( ! $allow_ai_connection ) {
|
||||
return new \WP_Error(
|
||||
'ai_connection_not_allowed',
|
||||
__( 'AI content generation is not allowed on this store. Update your store settings if you wish to enable this feature.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$ai_connection = new Connection();
|
||||
|
||||
$site_id = $ai_connection->get_site_id();
|
||||
|
||||
if ( is_wp_error( $site_id ) ) {
|
||||
return $site_id->get_error_message();
|
||||
}
|
||||
|
||||
$token = $ai_connection->get_jwt_token( $site_id );
|
||||
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return $token->get_error_message();
|
||||
}
|
||||
|
||||
$business_description = get_option( 'woo_ai_describe_store_description' );
|
||||
|
||||
$images = ( new Pexels() )->get_images( $ai_connection, $token, $business_description );
|
||||
|
||||
if ( is_wp_error( $images ) ) {
|
||||
return $images->get_error_message();
|
||||
}
|
||||
|
||||
$populate_patterns = ( new PatternUpdater() )->generate_content( $ai_connection, $token, $images, $business_description );
|
||||
|
||||
if ( is_wp_error( $populate_patterns ) ) {
|
||||
return $populate_patterns->get_error_message();
|
||||
}
|
||||
|
||||
$populate_products = ( new ProductUpdater() )->generate_content( $ai_connection, $token, $images, $business_description );
|
||||
|
||||
if ( is_wp_error( $populate_products ) ) {
|
||||
return $populate_products->get_error_message();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the patterns dictionary to get the pattern data corresponding to the pattern slug.
|
||||
*
|
||||
* @param array $dictionary The patterns dictionary.
|
||||
* @param string $slug The pattern slug.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function get_pattern_from_dictionary( $dictionary, $slug ) {
|
||||
foreach ( $dictionary as $pattern_dictionary ) {
|
||||
if ( $pattern_dictionary['slug'] === $slug ) {
|
||||
return $pattern_dictionary;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,782 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\Templates\CartTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\CheckoutTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplateCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateMigrationUtils;
|
||||
|
||||
/**
|
||||
* BlockTypesController class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockTemplatesController {
|
||||
|
||||
/**
|
||||
* Directory which contains all templates
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TEMPLATES_ROOT_DIR = 'templates';
|
||||
|
||||
/**
|
||||
* Package instance.
|
||||
*
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Package $package An instance of Package.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
$this->package = $package;
|
||||
|
||||
$feature_gating = $package->feature();
|
||||
$is_block_templates_controller_refactor_enabled = $feature_gating->is_block_templates_controller_refactor_enabled();
|
||||
|
||||
// This feature is gated for WooCommerce versions 6.0.0 and above.
|
||||
if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '6.0.0', '>=' ) && ! $is_block_templates_controller_refactor_enabled ) {
|
||||
$this->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization method.
|
||||
*/
|
||||
protected function init() {
|
||||
add_filter( 'default_wp_template_part_areas', array( $this, 'register_mini_cart_template_part_area' ), 10, 1 );
|
||||
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 );
|
||||
add_action( 'after_switch_theme', array( $this, 'check_should_use_blockified_product_grid_templates' ), 10, 2 );
|
||||
|
||||
if ( wc_current_theme_is_fse_theme() ) {
|
||||
add_action( 'init', array( $this, 'maybe_migrate_content' ) );
|
||||
|
||||
// By default, the Template Part Block only supports template parts that are in the current theme directory.
|
||||
// This render_callback wrapper allows us to add support for plugin-housed template parts.
|
||||
add_filter(
|
||||
'block_type_metadata_settings',
|
||||
function( $settings, $metadata ) {
|
||||
if (
|
||||
isset( $metadata['name'], $settings['render_callback'] ) &&
|
||||
'core/template-part' === $metadata['name'] &&
|
||||
in_array( $settings['render_callback'], [ 'render_block_core_template_part', 'gutenberg_render_block_core_template_part' ], true )
|
||||
) {
|
||||
$settings['render_callback'] = [ $this, 'render_woocommerce_template_part' ];
|
||||
}
|
||||
return $settings;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
// Prevents shortcodes in templates having their HTML content broken by wpautop.
|
||||
// @see https://core.trac.wordpress.org/ticket/58366 for more info.
|
||||
add_filter(
|
||||
'block_type_metadata_settings',
|
||||
function( $settings, $metadata ) {
|
||||
if (
|
||||
isset( $metadata['name'], $settings['render_callback'] ) &&
|
||||
'core/shortcode' === $metadata['name']
|
||||
) {
|
||||
$settings['original_render_callback'] = $settings['render_callback'];
|
||||
$settings['render_callback'] = function( $attributes, $content ) use ( $settings ) {
|
||||
// The shortcode has already been rendered, so look for the cart/checkout HTML.
|
||||
if ( strstr( $content, 'woocommerce-cart-form' ) || strstr( $content, 'wc-empty-cart-message' ) || strstr( $content, 'woocommerce-checkout-form' ) ) {
|
||||
// Return early before wpautop runs again.
|
||||
return $content;
|
||||
}
|
||||
|
||||
$render_callback = $settings['original_render_callback'];
|
||||
|
||||
return $render_callback( $attributes, $content );
|
||||
};
|
||||
}
|
||||
return $settings;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
/**
|
||||
* Prevents the pages that are assigned as cart/checkout from showing the "template" selector in the page-editor.
|
||||
* We want to avoid this flow and point users towards the site editor instead.
|
||||
*/
|
||||
add_action(
|
||||
'current_screen',
|
||||
function () {
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_screen = get_current_screen();
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( $current_screen && 'page' === $current_screen->id && ! empty( $_GET['post'] ) && in_array( absint( $_GET['post'] ), [ wc_get_page_id( 'cart' ), wc_get_page_id( 'checkout' ) ], true ) ) {
|
||||
wp_add_inline_style( 'wc-blocks-editor-style', '.edit-post-post-template { display: none; }' );
|
||||
}
|
||||
},
|
||||
10
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Mini-Cart to the default template part areas.
|
||||
*
|
||||
* @param array $default_area_definitions An array of supported area objects.
|
||||
* @return array The supported template part areas including the Mini-Cart one.
|
||||
*/
|
||||
public function register_mini_cart_template_part_area( $default_area_definitions ) {
|
||||
$mini_cart_template_part_area = [
|
||||
'area' => 'mini-cart',
|
||||
'label' => __( 'Mini-Cart', 'woocommerce' ),
|
||||
'description' => __( 'The Mini-Cart template allows shoppers to see their cart items and provides access to the Cart and Checkout pages.', 'woocommerce' ),
|
||||
'icon' => 'mini-cart',
|
||||
'area_tag' => 'mini-cart',
|
||||
];
|
||||
return array_merge( $default_area_definitions, [ $mini_cart_template_part_area ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the `core/template-part` block on the server.
|
||||
*
|
||||
* @param array $attributes The block attributes.
|
||||
* @return string The render.
|
||||
*/
|
||||
public function render_woocommerce_template_part( $attributes ) {
|
||||
if ( isset( $attributes['theme'] ) && 'woocommerce/woocommerce' === $attributes['theme'] ) {
|
||||
$template_part = BlockTemplateUtils::get_block_template( $attributes['theme'] . '//' . $attributes['slug'], 'wp_template_part' );
|
||||
|
||||
if ( $template_part && ! empty( $template_part->content ) ) {
|
||||
return do_blocks( $template_part->content );
|
||||
}
|
||||
}
|
||||
return function_exists( '\gutenberg_render_block_core_template_part' ) ? \gutenberg_render_block_core_template_part( $attributes ) : \render_block_core_template_part( $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
// Add protection against invalid ids.
|
||||
if ( ! is_string( $id ) || ! strstr( $id, '//' ) ) {
|
||||
return null;
|
||||
}
|
||||
// Add protection against invalid template types.
|
||||
if (
|
||||
'wp_template' !== $template_type &&
|
||||
'wp_template_part' !== $template_type
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
$template_name_parts = explode( '//', $id );
|
||||
$theme = $template_name_parts[0] ?? '';
|
||||
$slug = $template_name_parts[1] ?? '';
|
||||
|
||||
if ( empty( $theme ) || empty( $slug ) || ! 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 `woocommerce/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 = BlockTemplateUtils::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( $template_type ) ) {
|
||||
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 ( str_contains( $template->slug, 'single-product' ) ) {
|
||||
// We don't want to add the compatibility layer on the Editor Side.
|
||||
// The second condition is necessary to not apply the compatibility layer on the REST API. Gutenberg uses the REST API to clone the template.
|
||||
// More details: https://github.com/woocommerce/woocommerce-blocks/issues/9662.
|
||||
if ( ( ! is_admin() && ! ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) && ! BlockTemplateUtils::template_has_legacy_template_block( $template ) ) {
|
||||
// Add the product class to the body. We should move this to a more appropriate place.
|
||||
add_filter(
|
||||
'body_class',
|
||||
function( $classes ) {
|
||||
return array_merge( $classes, wc_get_product_class() );
|
||||
}
|
||||
);
|
||||
|
||||
global $product;
|
||||
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
$product_id = get_the_ID();
|
||||
if ( $product_id ) {
|
||||
wc_setup_product_data( $product_id );
|
||||
}
|
||||
}
|
||||
|
||||
if ( post_password_required() ) {
|
||||
$template->content = SingleProductTemplate::add_password_form( $template->content );
|
||||
} else {
|
||||
$template->content = SingleProductTemplateCompatibility::add_compatibility_layer( $template->content );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
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' ) {
|
||||
wc_deprecated_function( 'BlockTemplatesController::get_block_templates_from_db()', '7.8', '\Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils::get_block_templates_from_db()' );
|
||||
return BlockTemplateUtils::get_block_templates_from_db( $slugs, $template_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = BlockTemplateUtils::get_templates_directory( $template_type );
|
||||
$template_files = BlockTemplateUtils::get_template_paths( $directory );
|
||||
$templates = array();
|
||||
|
||||
foreach ( $template_files as $template_file ) {
|
||||
// Skip the Product Gallery template part, as it is not supposed to be exposed at this point.
|
||||
if ( str_contains( $template_file, 'templates/parts/product-gallery.html' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip the template if it's blockified, and we should only use classic ones.
|
||||
if ( ! 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 = BlockTemplateUtils::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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 BlockTemplateUtils::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 = BlockTemplateUtils::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' )
|
||||
) {
|
||||
global $post;
|
||||
|
||||
$valid_slugs = [ 'single-product' ];
|
||||
if ( 'product' === $post->post_type && $post->post_name ) {
|
||||
$valid_slugs[] = 'single-product-' . $post->post_name;
|
||||
}
|
||||
$templates = get_block_templates( array( 'slug__in' => $valid_slugs ) );
|
||||
|
||||
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_search() ) {
|
||||
$templates = get_block_templates( array( 'slug__in' => array( ProductSearchResultsTemplate::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( ProductSearchResultsTemplate::SLUG ) ) {
|
||||
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 );
|
||||
}
|
||||
} elseif (
|
||||
is_cart() &&
|
||||
! BlockTemplateUtils::theme_has_template( CartTemplate::get_slug() ) && $this->block_template_is_available( CartTemplate::get_slug() )
|
||||
) {
|
||||
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
|
||||
} elseif (
|
||||
is_checkout() &&
|
||||
! BlockTemplateUtils::theme_has_template( CheckoutTemplate::get_slug() ) && $this->block_template_is_available( CheckoutTemplate::get_slug() )
|
||||
) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates page content to templates if needed.
|
||||
*/
|
||||
public function maybe_migrate_content() {
|
||||
// Migration should occur on a normal request to ensure every requirement is met.
|
||||
// We are postponing it if WP is in maintenance mode, installing, WC installing or if the request is part of a WP-CLI command.
|
||||
if ( wp_is_maintenance_mode() || ! get_option( 'woocommerce_db_version', false ) || Constants::is_defined( 'WP_SETUP_CONFIG' ) || Constants::is_defined( 'WC_INSTALLING' ) || Constants::is_defined( 'WP_CLI' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! BlockTemplateMigrationUtils::has_migrated_page( 'cart' ) ) {
|
||||
BlockTemplateMigrationUtils::migrate_page( 'cart' );
|
||||
}
|
||||
if ( ! BlockTemplateMigrationUtils::has_migrated_page( 'checkout' ) ) {
|
||||
BlockTemplateMigrationUtils::migrate_page( 'checkout' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use WP_Block;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
|
||||
|
||||
/**
|
||||
* AbstractBlock class.
|
||||
*/
|
||||
abstract class AbstractBlock {
|
||||
|
||||
/**
|
||||
* Block namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'woocommerce';
|
||||
|
||||
/**
|
||||
* Block name within this namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = '';
|
||||
|
||||
/**
|
||||
* Tracks if assets have been enqueued.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $enqueued_assets = false;
|
||||
|
||||
/**
|
||||
* Instance of the asset API.
|
||||
*
|
||||
* @var AssetApi
|
||||
*/
|
||||
protected $asset_api;
|
||||
|
||||
/**
|
||||
* Instance of the asset data registry.
|
||||
*
|
||||
* @var AssetDataRegistry
|
||||
*/
|
||||
protected $asset_data_registry;
|
||||
|
||||
/**
|
||||
* Instance of the integration registry.
|
||||
*
|
||||
* @var IntegrationRegistry
|
||||
*/
|
||||
protected $integration_registry;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetApi $asset_api Instance of the asset API.
|
||||
* @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
|
||||
* @param IntegrationRegistry $integration_registry Instance of the integration registry.
|
||||
* @param string $block_name Optionally set block name during construct.
|
||||
*/
|
||||
public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry, IntegrationRegistry $integration_registry, $block_name = '' ) {
|
||||
$this->asset_api = $asset_api;
|
||||
$this->asset_data_registry = $asset_data_registry;
|
||||
$this->integration_registry = $integration_registry;
|
||||
$this->block_name = $block_name ? $block_name : $this->block_name;
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default render_callback for all blocks. This will ensure assets are enqueued just in time, then render
|
||||
* the block (if applicable).
|
||||
*
|
||||
* @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array.
|
||||
* @param string $content Block content. Default empty string.
|
||||
* @param WP_Block|null $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
public function render_callback( $attributes = [], $content = '', $block = null ) {
|
||||
|
||||
$render_callback_attributes = $this->parse_render_callback_attributes( $attributes );
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$this->enqueue_assets( $render_callback_attributes, $content, $block );
|
||||
}
|
||||
return $this->render( $render_callback_attributes, $content, $block );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets used for rendering the block in editor context.
|
||||
*
|
||||
* This is needed if a block is not yet within the post content--`render` and `enqueue_assets` may not have ran.
|
||||
*/
|
||||
public function enqueue_editor_assets() {
|
||||
if ( $this->enqueued_assets ) {
|
||||
return;
|
||||
}
|
||||
$this->enqueue_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
if ( empty( $this->block_name ) ) {
|
||||
_doing_it_wrong( __METHOD__, esc_html__( 'Block name is required.', 'woocommerce' ), '4.5.0' );
|
||||
return false;
|
||||
}
|
||||
$this->integration_registry->initialize( $this->block_name . '_block' );
|
||||
$this->register_block_type_assets();
|
||||
$this->register_block_type();
|
||||
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register script and style assets for the block type before it is registered.
|
||||
*
|
||||
* This registers the scripts; it does not enqueue them.
|
||||
*/
|
||||
protected function register_block_type_assets() {
|
||||
if ( null !== $this->get_block_type_editor_script() ) {
|
||||
$data = $this->asset_api->get_script_data( $this->get_block_type_editor_script( 'path' ) );
|
||||
$has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );
|
||||
|
||||
$this->asset_api->register_script(
|
||||
$this->get_block_type_editor_script( 'handle' ),
|
||||
$this->get_block_type_editor_script( 'path' ),
|
||||
array_merge(
|
||||
$this->get_block_type_editor_script( 'dependencies' ),
|
||||
$this->integration_registry->get_all_registered_editor_script_handles()
|
||||
),
|
||||
$has_i18n
|
||||
);
|
||||
}
|
||||
if ( null !== $this->get_block_type_script() ) {
|
||||
$data = $this->asset_api->get_script_data( $this->get_block_type_script( 'path' ) );
|
||||
$has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );
|
||||
|
||||
$this->asset_api->register_script(
|
||||
$this->get_block_type_script( 'handle' ),
|
||||
$this->get_block_type_script( 'path' ),
|
||||
array_merge(
|
||||
$this->get_block_type_script( 'dependencies' ),
|
||||
$this->integration_registry->get_all_registered_script_handles()
|
||||
),
|
||||
$has_i18n
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects Chunk Translations into the page so translations work for lazy loaded components.
|
||||
*
|
||||
* The chunk names are defined when creating lazy loaded components using webpackChunkName.
|
||||
*
|
||||
* @param string[] $chunks Array of chunk names.
|
||||
*/
|
||||
protected function register_chunk_translations( $chunks ) {
|
||||
foreach ( $chunks as $chunk ) {
|
||||
$handle = 'wc-blocks-' . $chunk . '-chunk';
|
||||
$this->asset_api->register_script( $handle, $this->asset_api->get_block_asset_build_path( $chunk ), [], true );
|
||||
wp_add_inline_script(
|
||||
$this->get_block_type_script( 'handle' ),
|
||||
wp_scripts()->print_translations( $handle, false ),
|
||||
'before'
|
||||
);
|
||||
wp_deregister_script( $handle );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an array of chunks paths for loading translation.
|
||||
*
|
||||
* @param string $chunks_folder The folder to iterate over.
|
||||
* @return string[] $chunks list of chunks to load.
|
||||
*/
|
||||
protected function get_chunks_paths( $chunks_folder ) {
|
||||
$build_path = \Automattic\WooCommerce\Blocks\Package::get_path() . 'assets/client/blocks/';
|
||||
$blocks = [];
|
||||
if ( ! is_dir( $build_path . $chunks_folder ) ) {
|
||||
return [];
|
||||
}
|
||||
foreach ( new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $build_path . $chunks_folder ) ) as $block_name ) {
|
||||
$blocks[] = str_replace( $build_path, '', $block_name );
|
||||
}
|
||||
|
||||
$chunks = preg_filter( '/.js/', '', $blocks );
|
||||
return $chunks;
|
||||
}
|
||||
/**
|
||||
* Registers the block type with WordPress.
|
||||
*
|
||||
* @return string[] Chunks paths.
|
||||
*/
|
||||
protected function register_block_type() {
|
||||
$block_settings = [
|
||||
'render_callback' => $this->get_block_type_render_callback(),
|
||||
'editor_script' => $this->get_block_type_editor_script( 'handle' ),
|
||||
'editor_style' => $this->get_block_type_editor_style(),
|
||||
'style' => $this->get_block_type_style(),
|
||||
];
|
||||
|
||||
if ( isset( $this->api_version ) && '2' === $this->api_version ) {
|
||||
$block_settings['api_version'] = 2;
|
||||
}
|
||||
|
||||
$metadata_path = $this->asset_api->get_block_metadata_path( $this->block_name );
|
||||
|
||||
/**
|
||||
* We always want to load block styles separately, for every theme.
|
||||
* When the core assets are loaded separately, other blocks' styles get
|
||||
* enqueued separately too. Thus we only need to handle the remaining
|
||||
* case.
|
||||
*/
|
||||
if (
|
||||
! is_admin() &&
|
||||
! wc_current_theme_is_fse_theme() &&
|
||||
$block_settings['style'] &&
|
||||
(
|
||||
! function_exists( 'wp_should_load_separate_core_block_assets' ) ||
|
||||
! wp_should_load_separate_core_block_assets()
|
||||
)
|
||||
) {
|
||||
$style_handles = $block_settings['style'];
|
||||
$block_settings['style'] = null;
|
||||
add_filter(
|
||||
'render_block',
|
||||
function( $html, $block ) use ( $style_handles ) {
|
||||
if ( $block['blockName'] === $this->get_block_type() ) {
|
||||
array_map( 'wp_enqueue_style', $style_handles );
|
||||
}
|
||||
return $html;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
// Prefer to register with metadata if the path is set in the block's class.
|
||||
if ( ! empty( $metadata_path ) ) {
|
||||
register_block_type_from_metadata(
|
||||
$metadata_path,
|
||||
$block_settings
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert attributes and supports if we're not registering the block using metadata.
|
||||
* These are left unset until now and only added here because if they were set when registering with metadata,
|
||||
* the attributes and supports from $block_settings would override the values from metadata.
|
||||
*/
|
||||
$block_settings['attributes'] = $this->get_block_type_attributes();
|
||||
$block_settings['supports'] = $this->get_block_type_supports();
|
||||
$block_settings['uses_context'] = $this->get_block_type_uses_context();
|
||||
|
||||
register_block_type(
|
||||
$this->get_block_type(),
|
||||
$block_settings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_block_type() {
|
||||
return $this->namespace . '/' . $this->block_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the render callback for this block type.
|
||||
*
|
||||
* Dynamic blocks should return a callback, for example, `return [ $this, 'render' ];`
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @return callable|null;
|
||||
*/
|
||||
protected function get_block_type_render_callback() {
|
||||
return [ $this, 'render_callback' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor script data for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor style handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get_block_type_editor_style() {
|
||||
return 'wc-blocks-editor-style';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string|null
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return string[]|null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
$this->asset_api->register_style( 'wc-blocks-style-' . $this->block_name, $this->asset_api->get_block_asset_build_path( $this->block_name, 'css' ), [], 'all', true );
|
||||
|
||||
return [ 'wc-blocks-style', 'wc-blocks-style-' . $this->block_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the supports array for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @return string;
|
||||
*/
|
||||
protected function get_block_type_supports() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array;
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block usesContext.
|
||||
*
|
||||
* @return array;
|
||||
*/
|
||||
protected function get_block_type_uses_context() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses block attributes from the render_callback.
|
||||
*
|
||||
* @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array.
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_render_callback_attributes( $attributes ) {
|
||||
return is_a( $attributes, 'WP_Block' ) ? $attributes->attributes : $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the block. Extended by children.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @internal This prevents the block script being enqueued on all pages. It is only enqueued as needed. Note that
|
||||
* we intentionally do not pass 'script' to register_block_type.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* @param string $content The block content.
|
||||
* @param WP_Block $block The block object.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes, $content, $block ) {
|
||||
if ( $this->enqueued_assets ) {
|
||||
return;
|
||||
}
|
||||
$this->enqueue_data( $attributes );
|
||||
$this->enqueue_scripts( $attributes );
|
||||
$this->enqueued_assets = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
$registered_script_data = $this->integration_registry->get_all_registered_script_data();
|
||||
|
||||
foreach ( $registered_script_data as $asset_data_key => $asset_data_value ) {
|
||||
if ( ! $this->asset_data_registry->exists( $asset_data_key ) ) {
|
||||
$this->asset_data_registry->add( $asset_data_key, $asset_data_value );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $this->asset_data_registry->exists( 'wcBlocksConfig' ) ) {
|
||||
$this->asset_data_registry->add(
|
||||
'wcBlocksConfig',
|
||||
[
|
||||
'buildPhase' => Package::feature()->get_flag(),
|
||||
'pluginUrl' => plugins_url( '/', dirname( __DIR__, 2 ) ),
|
||||
'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,65 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* AbstractInnerBlock class.
|
||||
*/
|
||||
abstract class AbstractInnerBlock extends AbstractBlock {
|
||||
|
||||
/**
|
||||
* Is this inner block lazy loaded? this helps us know if we should load its frontend script ot not.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $is_lazy_loaded = true;
|
||||
|
||||
/**
|
||||
* Registers the block type with WordPress using the metadata file.
|
||||
*
|
||||
* The registration using metadata is now recommended. And it's required for "Inner Blocks" to
|
||||
* fix the issue of missing translations in the inspector (in the Editor mode)
|
||||
*/
|
||||
protected function register_block_type() {
|
||||
$block_settings = [
|
||||
'render_callback' => $this->get_block_type_render_callback(),
|
||||
'editor_style' => $this->get_block_type_editor_style(),
|
||||
'style' => $this->get_block_type_style(),
|
||||
];
|
||||
|
||||
if ( isset( $this->api_version ) && '2' === $this->api_version ) {
|
||||
$block_settings['api_version'] = 2;
|
||||
}
|
||||
|
||||
$metadata_path = $this->asset_api->get_block_metadata_path( $this->block_name, 'inner-blocks/' );
|
||||
// Prefer to register with metadata if the path is set in the block's class.
|
||||
register_block_type_from_metadata(
|
||||
$metadata_path,
|
||||
$block_settings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* For lazy loaded inner blocks, we don't want to enqueue the script but rather leave it for webpack to do that.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string|null
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
|
||||
if ( $this->is_lazy_loaded ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parent::get_block_type_script( $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,699 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlocksWpQuery;
|
||||
use Automattic\WooCommerce\StoreApi\SchemaController;
|
||||
use Automattic\WooCommerce\StoreApi\StoreApi;
|
||||
|
||||
/**
|
||||
* AbstractProductGrid class.
|
||||
*/
|
||||
abstract class AbstractProductGrid extends AbstractDynamicBlock {
|
||||
|
||||
/**
|
||||
* Attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = array();
|
||||
|
||||
/**
|
||||
* InnerBlocks content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content = '';
|
||||
|
||||
/**
|
||||
* Query args.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $query_args = array();
|
||||
|
||||
/**
|
||||
* Meta query args.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $meta_query = array();
|
||||
|
||||
/**
|
||||
* Get a set of attributes shared across most of the grid blocks.
|
||||
*
|
||||
* @return array List of block attributes with type and defaults.
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return array(
|
||||
'className' => $this->get_schema_string(),
|
||||
'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
|
||||
'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ),
|
||||
'categories' => $this->get_schema_list_ids(),
|
||||
'catOperator' => array(
|
||||
'type' => 'string',
|
||||
'default' => 'any',
|
||||
),
|
||||
'contentVisibility' => $this->get_schema_content_visibility(),
|
||||
'align' => $this->get_schema_align(),
|
||||
'alignButtons' => $this->get_schema_boolean( false ),
|
||||
'isPreview' => $this->get_schema_boolean( false ),
|
||||
'stockStatus' => array(
|
||||
'type' => 'array',
|
||||
'default' => array_keys( wc_get_product_stock_status_options() ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Include and render the dynamic block.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param string $content Block content. Default empty string.
|
||||
* @param WP_Block|null $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes = array(), $content = '', $block = null ) {
|
||||
$this->attributes = $this->parse_attributes( $attributes );
|
||||
$this->content = $content;
|
||||
$this->query_args = $this->parse_query_args();
|
||||
$products = array_filter( array_map( 'wc_get_product', $this->get_products() ) );
|
||||
|
||||
if ( ! $products ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Override product description to prevent infinite loop.
|
||||
*
|
||||
* @see https://github.com/woocommerce/woocommerce-blocks/pull/6849
|
||||
*/
|
||||
foreach ( $products as $product ) {
|
||||
$product->set_description( '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Product List Render event.
|
||||
*
|
||||
* Fires a WP Hook named `experimental__woocommerce_blocks-product-list-render` on render so that the client
|
||||
* can add event handling when certain products are displayed. This can be used by tracking extensions such
|
||||
* as Google Analytics to track impressions.
|
||||
*
|
||||
* Provides the list of product data (shaped like the Store API responses) and the block name.
|
||||
*/
|
||||
$this->asset_api->add_inline_script(
|
||||
'wp-hooks',
|
||||
'
|
||||
window.addEventListener( "DOMContentLoaded", () => {
|
||||
wp.hooks.doAction(
|
||||
"experimental__woocommerce_blocks-product-list-render",
|
||||
{
|
||||
products: JSON.parse( decodeURIComponent( "' . esc_js(
|
||||
rawurlencode(
|
||||
wp_json_encode(
|
||||
array_map(
|
||||
[ StoreApi::container()->get( SchemaController::class )->get( 'product' ), 'get_item_response' ],
|
||||
$products
|
||||
)
|
||||
)
|
||||
)
|
||||
) . '" ) ),
|
||||
listName: "' . esc_js( $this->block_name ) . '"
|
||||
}
|
||||
);
|
||||
} );
|
||||
',
|
||||
'after'
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div class="%s"><ul class="wc-block-grid__products">%s</ul></div>',
|
||||
esc_attr( $this->get_container_classes() ),
|
||||
implode( '', array_map( array( $this, 'render_product' ), $products ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for the contentVisibility attribute
|
||||
*
|
||||
* @return array List of block attributes with type and defaults.
|
||||
*/
|
||||
protected function get_schema_content_visibility() {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'image' => $this->get_schema_boolean( true ),
|
||||
'title' => $this->get_schema_boolean( true ),
|
||||
'price' => $this->get_schema_boolean( true ),
|
||||
'rating' => $this->get_schema_boolean( true ),
|
||||
'button' => $this->get_schema_boolean( true ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for the orderby attribute.
|
||||
*
|
||||
* @return array Property definition of `orderby` attribute.
|
||||
*/
|
||||
protected function get_schema_orderby() {
|
||||
return array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'date', 'popularity', 'price_asc', 'price_desc', 'rating', 'title', 'menu_order' ),
|
||||
'default' => 'date',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block's attributes.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return array Block attributes merged with defaults.
|
||||
*/
|
||||
protected function parse_attributes( $attributes ) {
|
||||
// These should match what's set in JS `registerBlockType`.
|
||||
$defaults = array(
|
||||
'columns' => wc_get_theme_support( 'product_blocks::default_columns', 3 ),
|
||||
'rows' => wc_get_theme_support( 'product_blocks::default_rows', 3 ),
|
||||
'alignButtons' => false,
|
||||
'categories' => array(),
|
||||
'catOperator' => 'any',
|
||||
'contentVisibility' => array(
|
||||
'image' => true,
|
||||
'title' => true,
|
||||
'price' => true,
|
||||
'rating' => true,
|
||||
'button' => true,
|
||||
),
|
||||
'stockStatus' => array_keys( wc_get_product_stock_status_options() ),
|
||||
);
|
||||
|
||||
return wp_parse_args( $attributes, $defaults );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse query args.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_query_args() {
|
||||
// Store the original meta query.
|
||||
$this->meta_query = WC()->query->get_meta_query();
|
||||
|
||||
$query_args = array(
|
||||
'post_type' => 'product',
|
||||
'post_status' => 'publish',
|
||||
'fields' => 'ids',
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => false,
|
||||
'orderby' => '',
|
||||
'order' => '',
|
||||
'meta_query' => $this->meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery
|
||||
'tax_query' => array(), // phpcs:ignore WordPress.DB.SlowDBQuery
|
||||
'posts_per_page' => $this->get_products_limit(),
|
||||
);
|
||||
|
||||
$this->set_block_query_args( $query_args );
|
||||
$this->set_ordering_query_args( $query_args );
|
||||
$this->set_categories_query_args( $query_args );
|
||||
$this->set_visibility_query_args( $query_args );
|
||||
$this->set_stock_status_query_args( $query_args );
|
||||
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse query args.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_ordering_query_args( &$query_args ) {
|
||||
if ( isset( $this->attributes['orderby'] ) ) {
|
||||
if ( 'price_desc' === $this->attributes['orderby'] ) {
|
||||
$query_args['orderby'] = 'price';
|
||||
$query_args['order'] = 'DESC';
|
||||
} elseif ( 'price_asc' === $this->attributes['orderby'] ) {
|
||||
$query_args['orderby'] = 'price';
|
||||
$query_args['order'] = 'ASC';
|
||||
} elseif ( 'date' === $this->attributes['orderby'] ) {
|
||||
$query_args['orderby'] = 'date';
|
||||
$query_args['order'] = 'DESC';
|
||||
} else {
|
||||
$query_args['orderby'] = $this->attributes['orderby'];
|
||||
}
|
||||
}
|
||||
|
||||
$query_args = array_merge(
|
||||
$query_args,
|
||||
WC()->query->get_catalog_ordering_args( $query_args['orderby'], $query_args['order'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set args specific to this block
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
abstract protected function set_block_query_args( &$query_args );
|
||||
|
||||
/**
|
||||
* Set categories query args.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_categories_query_args( &$query_args ) {
|
||||
if ( ! empty( $this->attributes['categories'] ) ) {
|
||||
$categories = array_map( 'absint', $this->attributes['categories'] );
|
||||
|
||||
$query_args['tax_query'][] = array(
|
||||
'taxonomy' => 'product_cat',
|
||||
'terms' => $categories,
|
||||
'field' => 'term_id',
|
||||
'operator' => 'all' === $this->attributes['catOperator'] ? 'AND' : 'IN',
|
||||
|
||||
/*
|
||||
* When cat_operator is AND, the children categories should be excluded,
|
||||
* as only products belonging to all the children categories would be selected.
|
||||
*/
|
||||
'include_children' => 'all' === $this->attributes['catOperator'] ? false : true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set visibility query args.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_visibility_query_args( &$query_args ) {
|
||||
$product_visibility_terms = wc_get_product_visibility_term_ids();
|
||||
$product_visibility_not_in = array( $product_visibility_terms['exclude-from-catalog'] );
|
||||
|
||||
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
|
||||
$product_visibility_not_in[] = $product_visibility_terms['outofstock'];
|
||||
}
|
||||
|
||||
$query_args['tax_query'][] = array(
|
||||
'taxonomy' => 'product_visibility',
|
||||
'field' => 'term_taxonomy_id',
|
||||
'terms' => $product_visibility_not_in,
|
||||
'operator' => 'NOT IN',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set which stock status to use when displaying products.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
* @return void
|
||||
*/
|
||||
protected function set_stock_status_query_args( &$query_args ) {
|
||||
$stock_statuses = array_keys( wc_get_product_stock_status_options() );
|
||||
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
if ( isset( $this->attributes['stockStatus'] ) && $stock_statuses !== $this->attributes['stockStatus'] ) {
|
||||
// Reset meta_query then update with our stock status.
|
||||
$query_args['meta_query'] = $this->meta_query;
|
||||
$query_args['meta_query'][] = array(
|
||||
'key' => '_stock_status',
|
||||
'value' => array_merge( [ '' ], $this->attributes['stockStatus'] ),
|
||||
'compare' => 'IN',
|
||||
);
|
||||
} else {
|
||||
$query_args['meta_query'] = $this->meta_query;
|
||||
}
|
||||
// phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
}
|
||||
|
||||
/**
|
||||
* Works out the item limit based on rows and columns, or returns default.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_products_limit() {
|
||||
if ( isset( $this->attributes['rows'], $this->attributes['columns'] ) && ! empty( $this->attributes['rows'] ) ) {
|
||||
$this->attributes['limit'] = intval( $this->attributes['columns'] ) * intval( $this->attributes['rows'] );
|
||||
}
|
||||
return intval( $this->attributes['limit'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the query and return an array of product IDs
|
||||
*
|
||||
* @return array List of product IDs
|
||||
*/
|
||||
protected function get_products() {
|
||||
/**
|
||||
* Filters whether or not the product grid is cacheable.
|
||||
*
|
||||
* @param boolean $is_cacheable The list of script dependencies.
|
||||
* @param array $query_args Query args for the products query passed to BlocksWpQuery.
|
||||
* @return array True to enable cache, false to disable cache.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
$is_cacheable = (bool) apply_filters( 'woocommerce_blocks_product_grid_is_cacheable', true, $this->query_args );
|
||||
$transient_version = \WC_Cache_Helper::get_transient_version( 'product_query' );
|
||||
|
||||
$query = new BlocksWpQuery( $this->query_args );
|
||||
$results = wp_parse_id_list( $is_cacheable ? $query->get_cached_posts( $transient_version ) : $query->get_posts() );
|
||||
|
||||
// Remove ordering query arguments which may have been added by get_catalog_ordering_args.
|
||||
WC()->query->remove_ordering_args();
|
||||
|
||||
// Prime caches to reduce future queries. Note _prime_post_caches is private--we could replace this with our own
|
||||
// query if it becomes unavailable.
|
||||
if ( is_callable( '_prime_post_caches' ) ) {
|
||||
_prime_post_caches( $results );
|
||||
}
|
||||
|
||||
$this->prime_product_variations( $results );
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve IDs that are not already present in the cache.
|
||||
*
|
||||
* Based on WordPress function: _get_non_cached_ids
|
||||
*
|
||||
* @param int[] $product_ids Array of IDs.
|
||||
* @param string $cache_key The cache bucket to check against.
|
||||
* @return int[] Array of IDs not present in the cache.
|
||||
*/
|
||||
protected function get_non_cached_ids( $product_ids, $cache_key ) {
|
||||
$non_cached_ids = array();
|
||||
$cache_values = wp_cache_get_multiple( $product_ids, $cache_key );
|
||||
|
||||
foreach ( $cache_values as $id => $value ) {
|
||||
if ( ! $value ) {
|
||||
$non_cached_ids[] = (int) $id;
|
||||
}
|
||||
}
|
||||
|
||||
return $non_cached_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prime query cache of product variation meta data.
|
||||
*
|
||||
* Prepares values in the product_ID_variation_meta_data cache for later use in the ProductSchema::get_variations()
|
||||
* method. Doing so here reduces the total number of queries needed.
|
||||
*
|
||||
* @param int[] $product_ids Product ids to prime variation cache for.
|
||||
*/
|
||||
protected function prime_product_variations( $product_ids ) {
|
||||
$cache_group = 'product_variation_meta_data';
|
||||
$prime_product_ids = $this->get_non_cached_ids( wp_parse_id_list( $product_ids ), $cache_group );
|
||||
|
||||
if ( ! $prime_product_ids ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
|
||||
$product_variations = $wpdb->get_results( "SELECT ID as variation_id, post_parent as product_id from {$wpdb->posts} WHERE post_parent IN ( " . implode( ',', $prime_product_ids ) . ' )', ARRAY_A );
|
||||
$prime_variation_ids = array_column( $product_variations, 'variation_id' );
|
||||
$variation_ids_by_parent = array_column( $product_variations, 'product_id', 'variation_id' );
|
||||
|
||||
if ( empty( $prime_variation_ids ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$all_variation_meta_data = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT post_id as variation_id, meta_key as attribute_key, meta_value as attribute_value FROM {$wpdb->postmeta} WHERE post_id IN (" . implode( ',', array_map( 'esc_sql', $prime_variation_ids ) ) . ') AND meta_key LIKE %s',
|
||||
$wpdb->esc_like( 'attribute_' ) . '%'
|
||||
)
|
||||
);
|
||||
// phpcs:enable
|
||||
// Prepare the data to cache by indexing by the parent product.
|
||||
$primed_data = array_reduce(
|
||||
$all_variation_meta_data,
|
||||
function( $values, $data ) use ( $variation_ids_by_parent ) {
|
||||
$values[ $variation_ids_by_parent[ $data->variation_id ] ?? 0 ][] = $data;
|
||||
return $values;
|
||||
},
|
||||
array_fill_keys( $prime_product_ids, [] )
|
||||
);
|
||||
|
||||
// Cache everything.
|
||||
foreach ( $primed_data as $product_id => $variation_meta_data ) {
|
||||
wp_cache_set(
|
||||
$product_id,
|
||||
[
|
||||
'last_modified' => get_the_modified_date( 'U', $product_id ),
|
||||
'data' => $variation_meta_data,
|
||||
],
|
||||
$cache_group
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of classes to apply to this block.
|
||||
*
|
||||
* @return string space-separated list of classes.
|
||||
*/
|
||||
protected function get_container_classes() {
|
||||
$classes = array(
|
||||
'wc-block-grid',
|
||||
"wp-block-{$this->block_name}",
|
||||
"wc-block-{$this->block_name}",
|
||||
"has-{$this->attributes['columns']}-columns",
|
||||
);
|
||||
|
||||
if ( $this->attributes['rows'] > 1 ) {
|
||||
$classes[] = 'has-multiple-rows';
|
||||
}
|
||||
|
||||
if ( isset( $this->attributes['align'] ) ) {
|
||||
$classes[] = "align{$this->attributes['align']}";
|
||||
}
|
||||
|
||||
if ( ! empty( $this->attributes['alignButtons'] ) ) {
|
||||
$classes[] = 'has-aligned-buttons';
|
||||
}
|
||||
|
||||
if ( ! empty( $this->attributes['className'] ) ) {
|
||||
$classes[] = $this->attributes['className'];
|
||||
}
|
||||
|
||||
return implode( ' ', $classes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single products.
|
||||
*
|
||||
* @param \WC_Product $product Product object.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function render_product( $product ) {
|
||||
$data = (object) array(
|
||||
'permalink' => esc_url( $product->get_permalink() ),
|
||||
'image' => $this->get_image_html( $product ),
|
||||
'title' => $this->get_title_html( $product ),
|
||||
'rating' => $this->get_rating_html( $product ),
|
||||
'price' => $this->get_price_html( $product ),
|
||||
'badge' => $this->get_sale_badge_html( $product ),
|
||||
'button' => $this->get_button_html( $product ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the HTML for products in the grid.
|
||||
*
|
||||
* @param string $html Product grid item HTML.
|
||||
* @param array $data Product data passed to the template.
|
||||
* @param \WC_Product $product Product object.
|
||||
* @return string Updated product grid item HTML.
|
||||
*
|
||||
* @since 2.2.0
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_blocks_product_grid_item_html',
|
||||
"<li class=\"wc-block-grid__product\">
|
||||
<a href=\"{$data->permalink}\" class=\"wc-block-grid__product-link\">
|
||||
{$data->badge}
|
||||
{$data->image}
|
||||
{$data->title}
|
||||
</a>
|
||||
{$data->price}
|
||||
{$data->rating}
|
||||
{$data->button}
|
||||
</li>",
|
||||
$data,
|
||||
$product
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product image.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_image_html( $product ) {
|
||||
if ( array_key_exists( 'image', $this->attributes['contentVisibility'] ) && false === $this->attributes['contentVisibility']['image'] ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$attr = array(
|
||||
'alt' => '',
|
||||
);
|
||||
|
||||
if ( $product->get_image_id() ) {
|
||||
$image_alt = get_post_meta( $product->get_image_id(), '_wp_attachment_image_alt', true );
|
||||
$attr = array(
|
||||
'alt' => ( $image_alt ? $image_alt : $product->get_name() ),
|
||||
);
|
||||
}
|
||||
|
||||
return '<div class="wc-block-grid__product-image">' . $product->get_image( 'woocommerce_thumbnail', $attr ) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product title.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_title_html( $product ) {
|
||||
if ( empty( $this->attributes['contentVisibility']['title'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<div class="wc-block-grid__product-title">' . wp_kses_post( $product->get_title() ) . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the rating icons.
|
||||
*
|
||||
* @param WC_Product $product Product.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function get_rating_html( $product ) {
|
||||
if ( empty( $this->attributes['contentVisibility']['rating'] ) ) {
|
||||
return '';
|
||||
}
|
||||
$rating_count = $product->get_rating_count();
|
||||
$average = $product->get_average_rating();
|
||||
|
||||
if ( $rating_count > 0 ) {
|
||||
return sprintf(
|
||||
'<div class="wc-block-grid__product-rating">%s</div>',
|
||||
wc_get_rating_html( $average, $rating_count ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function get_price_html( $product ) {
|
||||
if ( empty( $this->attributes['contentVisibility']['price'] ) ) {
|
||||
return '';
|
||||
}
|
||||
return sprintf(
|
||||
'<div class="wc-block-grid__product-price price">%s</div>',
|
||||
wp_kses_post( $product->get_price_html() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sale badge.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function get_sale_badge_html( $product ) {
|
||||
if ( empty( $this->attributes['contentVisibility']['price'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( ! $product->is_on_sale() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return '<div class="wc-block-grid__product-onsale">
|
||||
<span aria-hidden="true">' . esc_html__( 'Sale', 'woocommerce' ) . '</span>
|
||||
<span class="screen-reader-text">' . esc_html__( 'Product on sale', 'woocommerce' ) . '</span>
|
||||
</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the button.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function get_button_html( $product ) {
|
||||
if ( empty( $this->attributes['contentVisibility']['button'] ) ) {
|
||||
return '';
|
||||
}
|
||||
return '<div class="wp-block-button wc-block-grid__product-add-to-cart">' . $this->get_add_to_cart( $product ) . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "add to cart" button.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function get_add_to_cart( $product ) {
|
||||
$attributes = array(
|
||||
'aria-label' => $product->add_to_cart_description(),
|
||||
'data-quantity' => '1',
|
||||
'data-product_id' => $product->get_id(),
|
||||
'data-product_sku' => $product->get_sku(),
|
||||
'data-price' => wc_get_price_to_display( $product ),
|
||||
'rel' => 'nofollow',
|
||||
'class' => 'wp-block-button__link ' . ( function_exists( 'wc_wp_theme_get_element_class_name' ) ? wc_wp_theme_get_element_class_name( 'button' ) : '' ) . ' add_to_cart_button',
|
||||
);
|
||||
|
||||
if (
|
||||
$product->supports( 'ajax_add_to_cart' ) &&
|
||||
$product->is_purchasable() &&
|
||||
( $product->is_in_stock() || $product->backorders_allowed() )
|
||||
) {
|
||||
$attributes['class'] .= ' ajax_add_to_cart';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<a href="%s" %s>%s</a>',
|
||||
esc_url( $product->add_to_cart_url() ),
|
||||
wc_implode_html_attributes( $attributes ),
|
||||
esc_html( $product->add_to_cart_text() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'minColumns', wc_get_theme_support( 'product_blocks::min_columns', 1 ), true );
|
||||
$this->asset_data_registry->add( 'maxColumns', wc_get_theme_support( 'product_blocks::max_columns', 6 ), true );
|
||||
$this->asset_data_registry->add( 'defaultColumns', wc_get_theme_support( 'product_blocks::default_columns', 3 ), true );
|
||||
$this->asset_data_registry->add( 'minRows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true );
|
||||
$this->asset_data_registry->add( 'maxRows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true );
|
||||
$this->asset_data_registry->add( 'defaultRows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
// Currently these blocks rely on the styles from the All Products block.
|
||||
return [ 'wc-blocks-style', 'wc-blocks-style-all-products' ];
|
||||
}
|
||||
}
|
||||
@@ -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,200 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* CatalogSorting class.
|
||||
*/
|
||||
class AddToCartForm extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'add-to-cart-form';
|
||||
|
||||
/**
|
||||
* Initializes the AddToCartForm block and hooks into the `wc_add_to_cart_message_html` filter
|
||||
* to prevent displaying the Cart Notice when the block is inside the Single Product block
|
||||
* and the Add to Cart button is clicked.
|
||||
*
|
||||
* It also hooks into the `woocommerce_add_to_cart_redirect` filter to prevent redirecting
|
||||
* to another page when the block is inside the Single Product block and the Add to Cart button
|
||||
* is clicked.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
add_filter( 'wc_add_to_cart_message_html', array( $this, 'add_to_cart_message_html_filter' ), 10, 2 );
|
||||
add_filter( 'woocommerce_add_to_cart_redirect', array( $this, 'add_to_cart_redirect_filter' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block's attributes.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return array Block attributes merged with defaults.
|
||||
*/
|
||||
private function parse_attributes( $attributes ) {
|
||||
// These should match what's set in JS `registerBlockType`.
|
||||
$defaults = array(
|
||||
'isDescendentOfSingleProductBlock' => false,
|
||||
);
|
||||
|
||||
return wp_parse_args( $attributes, $defaults );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
*
|
||||
* @return string | void Rendered block output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
global $product;
|
||||
|
||||
$post_id = $block->context['postId'];
|
||||
|
||||
if ( ! isset( $post_id ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$previous_product = $product;
|
||||
$product = wc_get_product( $post_id );
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
$product = $previous_product;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
/**
|
||||
* Trigger the single product add to cart action for each product type.
|
||||
*
|
||||
* @since 9.7.0
|
||||
*/
|
||||
do_action( 'woocommerce_' . $product->get_type() . '_add_to_cart' );
|
||||
|
||||
$product = ob_get_clean();
|
||||
|
||||
if ( ! $product ) {
|
||||
$product = $previous_product;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$parsed_attributes = $this->parse_attributes( $attributes );
|
||||
$is_descendent_of_single_product_block = $parsed_attributes['isDescendentOfSingleProductBlock'];
|
||||
$product = $this->add_is_descendent_of_single_product_block_hidden_input_to_product_form( $product, $is_descendent_of_single_product_block );
|
||||
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
$product_classname = $is_descendent_of_single_product_block ? 'product' : '';
|
||||
|
||||
$form = sprintf(
|
||||
'<div class="wp-block-add-to-cart-form wc-block-add-to-cart-form %1$s %2$s %3$s" style="%4$s">%5$s</div>',
|
||||
esc_attr( $classes_and_styles['classes'] ),
|
||||
esc_attr( $classname ),
|
||||
esc_attr( $product_classname ),
|
||||
esc_attr( $classes_and_styles['styles'] ),
|
||||
$product
|
||||
);
|
||||
|
||||
$product = $previous_product;
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a hidden input to the Add to Cart form to indicate that it is a descendent of a Single Product block.
|
||||
*
|
||||
* @param string $product The Add to Cart Form HTML.
|
||||
* @param string $is_descendent_of_single_product_block Indicates if block is descendent of Single Product block.
|
||||
*
|
||||
* @return string The Add to Cart Form HTML with the hidden input.
|
||||
*/
|
||||
protected function add_is_descendent_of_single_product_block_hidden_input_to_product_form( $product, $is_descendent_of_single_product_block ) {
|
||||
|
||||
$hidden_is_descendent_of_single_product_block_input = sprintf(
|
||||
'<input type="hidden" name="is-descendent-of-single-product-block" value="%1$s">',
|
||||
$is_descendent_of_single_product_block ? 'true' : 'false'
|
||||
);
|
||||
$regex_pattern = '/<button\s+type="submit"[^>]*>.*?<\/button>/i';
|
||||
|
||||
preg_match( $regex_pattern, $product, $input_matches );
|
||||
|
||||
if ( ! empty( $input_matches ) ) {
|
||||
$product = preg_replace( $regex_pattern, $hidden_is_descendent_of_single_product_block_input . $input_matches[0], $product );
|
||||
}
|
||||
|
||||
return $product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the add to cart message to prevent the Notice from being displayed when the Add to Cart form is a descendent of a Single Product block
|
||||
* and the Add to Cart button is clicked.
|
||||
*
|
||||
* @param string $message Message to be displayed when product is added to the cart.
|
||||
*/
|
||||
public function add_to_cart_message_html_filter( $message ) {
|
||||
// phpcs:ignore
|
||||
if ( isset( $_POST['is-descendent-of-single-product-block'] ) && 'true' === $_POST['is-descendent-of-single-product-block'] ) {
|
||||
return false;
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into the `woocommerce_add_to_cart_redirect` filter to prevent redirecting
|
||||
* to another page when the block is inside the Single Product block and the Add to Cart button
|
||||
* is clicked.
|
||||
*
|
||||
* @param string $url The URL to redirect to after the product is added to the cart.
|
||||
* @return string The filtered redirect URL.
|
||||
*/
|
||||
public function add_to_cart_redirect_filter( $url ) {
|
||||
// phpcs:ignore
|
||||
if ( isset( $_POST['is-descendent-of-single-product-block'] ) && 'true' == $_POST['is-descendent-of-single-product-block'] ) {
|
||||
|
||||
if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
|
||||
return wc_get_cart_url();
|
||||
}
|
||||
|
||||
return wp_validate_redirect( wp_get_referer(), $url );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* It isn't necessary register block assets because it is a server side block.
|
||||
*/
|
||||
protected function register_block_type_assets() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* AllProducts class.
|
||||
*/
|
||||
class AllProducts extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'all-products';
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
// Set this so filter blocks being used as widgets know when to render.
|
||||
$this->asset_data_registry->add( 'hasFilterableProducts', true, true );
|
||||
$this->asset_data_registry->add( 'minColumns', wc_get_theme_support( 'product_blocks::min_columns', 1 ), true );
|
||||
$this->asset_data_registry->add( 'maxColumns', wc_get_theme_support( 'product_blocks::max_columns', 6 ), true );
|
||||
$this->asset_data_registry->add( 'defaultColumns', wc_get_theme_support( 'product_blocks::default_columns', 3 ), true );
|
||||
$this->asset_data_registry->add( 'minRows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true );
|
||||
$this->asset_data_registry->add( 'maxRows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true );
|
||||
$this->asset_data_registry->add( 'defaultRows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true );
|
||||
|
||||
// Hydrate the All Product block with data from the API. This is for the add to cart buttons which show current quantity in cart, and events.
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It is necessary to register and enqueue assets during the render phase because we want to load assets only if the block has the content.
|
||||
*/
|
||||
protected function register_block_type_assets() {
|
||||
parent::register_block_type_assets();
|
||||
$this->register_chunk_translations( [ $this->block_name ] );
|
||||
}
|
||||
}
|
||||
@@ -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,37 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* AttributeFilter class.
|
||||
*/
|
||||
class AttributeFilter extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'attribute-filter';
|
||||
const FILTER_QUERY_VAR_PREFIX = 'filter_';
|
||||
const QUERY_TYPE_QUERY_VAR_PREFIX = 'query_type_';
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'attributes', array_values( wc_get_attribute_taxonomies() ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
305
wp/wp-content/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php
Normal file
305
wp/wp-content/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php
Normal file
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
|
||||
|
||||
/**
|
||||
* Cart class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Cart extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart';
|
||||
|
||||
/**
|
||||
* Chunks build folder.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $chunks_folder = 'cart-blocks';
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
add_action( 'wp_loaded', array( $this, 'register_patterns' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dequeues the scripts added by WC Core to the Cart page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dequeue_woocommerce_core_scripts() {
|
||||
wp_dequeue_script( 'wc-cart' );
|
||||
wp_dequeue_script( 'wc-password-strength-meter' );
|
||||
wp_dequeue_script( 'selectWoo' );
|
||||
wp_dequeue_style( 'select2' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block pattern for Empty Cart Message to make it translatable.
|
||||
*/
|
||||
public function register_patterns() {
|
||||
$shop_permalink = wc_get_page_id( 'shop' ) ? get_permalink( wc_get_page_id( 'shop' ) ) : '';
|
||||
|
||||
register_block_pattern(
|
||||
'woocommerce/cart-heading',
|
||||
array(
|
||||
'title' => '',
|
||||
'inserter' => false,
|
||||
'content' => '<!-- wp:heading {"align":"wide", "level":1} --><h1 class="wp-block-heading alignwide">' . esc_html__( 'Cart', 'woocommerce' ) . '</h1><!-- /wp:heading -->',
|
||||
)
|
||||
);
|
||||
register_block_pattern(
|
||||
'woocommerce/cart-cross-sells-message',
|
||||
array(
|
||||
'title' => '',
|
||||
'inserter' => false,
|
||||
'content' => '<!-- wp:heading {"fontSize":"large"} --><h2 class="wp-block-heading has-large-font-size">' . esc_html__( 'You may be interested in…', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
|
||||
)
|
||||
);
|
||||
register_block_pattern(
|
||||
'woocommerce/cart-empty-message',
|
||||
array(
|
||||
'title' => '',
|
||||
'inserter' => false,
|
||||
'content' => '
|
||||
<!-- wp:heading {"textAlign":"center","className":"with-empty-cart-icon wc-block-cart__empty-cart__title"} --><h2 class="wp-block-heading has-text-align-center with-empty-cart-icon wc-block-cart__empty-cart__title">' . esc_html__( 'Your cart is currently empty!', 'woocommerce' ) . '</h2><!-- /wp:heading -->
|
||||
<!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center"><a href="' . esc_attr( esc_url( $shop_permalink ) ) . '">' . esc_html__( 'Browse store', 'woocommerce' ) . '</a></p><!-- /wp:paragraph -->
|
||||
',
|
||||
)
|
||||
);
|
||||
register_block_pattern(
|
||||
'woocommerce/cart-new-in-store-message',
|
||||
array(
|
||||
'title' => '',
|
||||
'inserter' => false,
|
||||
'content' => '<!-- wp:heading {"textAlign":"center"} --><h2 class="wp-block-heading has-text-align-center">' . esc_html__( 'New in store', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string;
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* @param string $content The block content.
|
||||
* @param WP_Block $block The block object.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes, $content, $block ) {
|
||||
/**
|
||||
* Fires before cart block scripts are enqueued.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_before' );
|
||||
parent::enqueue_assets( $attributes, $content, $block );
|
||||
/**
|
||||
* Fires after cart block scripts are enqueued.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Append frontend scripts when rendering the Cart block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
// Dequeue the core scripts when rendering this block.
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_woocommerce_core_scripts' ), 20 );
|
||||
|
||||
/**
|
||||
* We need to check if $content has any templates from prior iterations of the block, in order to update to the latest iteration.
|
||||
* We test the iteration version by searching for new blocks brought in by it.
|
||||
* The blocks used for testing should be always available in the block (not removable by the user).
|
||||
*/
|
||||
|
||||
$regex_for_filled_cart_block = '/<div[^<]*?data-block-name="woocommerce\/filled-cart-block"[^>]*?>/mi';
|
||||
// Filled Cart block was added in i2, so we search for it to see if we have a Cart i1 template.
|
||||
$has_i1_template = ! preg_match( $regex_for_filled_cart_block, $content );
|
||||
|
||||
if ( $has_i1_template ) {
|
||||
/**
|
||||
* This fallback structure needs to match the defaultTemplate variables defined in the block's edit.tsx files,
|
||||
* starting from the parent block and going down each inner block, in the order the blocks were registered.
|
||||
*/
|
||||
$inner_blocks_html = '$0
|
||||
<div data-block-name="woocommerce/filled-cart-block" class="wp-block-woocommerce-filled-cart-block">
|
||||
<div data-block-name="woocommerce/cart-items-block" class="wp-block-woocommerce-cart-items-block">
|
||||
<div data-block-name="woocommerce/cart-line-items-block" class="wp-block-woocommerce-cart-line-items-block"></div>
|
||||
</div>
|
||||
<div data-block-name="woocommerce/cart-totals-block" class="wp-block-woocommerce-cart-totals-block">
|
||||
<div data-block-name="woocommerce/cart-order-summary-block" class="wp-block-woocommerce-cart-order-summary-block"></div>
|
||||
<div data-block-name="woocommerce/cart-express-payment-block" class="wp-block-woocommerce-cart-express-payment-block"></div>
|
||||
<div data-block-name="woocommerce/proceed-to-checkout-block" class="wp-block-woocommerce-proceed-to-checkout-block"></div>
|
||||
<div data-block-name="woocommerce/cart-accepted-payment-methods-block" class="wp-block-woocommerce-cart-accepted-payment-methods-block"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-block-name="woocommerce/empty-cart-block" class="wp-block-woocommerce-empty-cart-block">
|
||||
';
|
||||
|
||||
$content = preg_replace( '/<div class="[a-zA-Z0-9_\- ]*wp-block-woocommerce-cart[a-zA-Z0-9_\- ]*">/mi', $inner_blocks_html, $content );
|
||||
$content = $content . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Cart i3 added inner blocks for Order summary. We need to add them to Cart i2 templates.
|
||||
* The order needs to match the order in which these blocks were registered.
|
||||
*/
|
||||
$order_summary_with_inner_blocks = '$0
|
||||
<div data-block-name="woocommerce/cart-order-summary-heading-block" class="wp-block-woocommerce-cart-order-summary-heading-block"></div>
|
||||
<div data-block-name="woocommerce/cart-order-summary-subtotal-block" class="wp-block-woocommerce-cart-order-summary-subtotal-block"></div>
|
||||
<div data-block-name="woocommerce/cart-order-summary-fee-block" class="wp-block-woocommerce-cart-order-summary-fee-block"></div>
|
||||
<div data-block-name="woocommerce/cart-order-summary-discount-block" class="wp-block-woocommerce-cart-order-summary-discount-block"></div>
|
||||
<div data-block-name="woocommerce/cart-order-summary-coupon-form-block" class="wp-block-woocommerce-cart-order-summary-coupon-form-block"></div>
|
||||
<div data-block-name="woocommerce/cart-order-summary-shipping-form-block" class="wp-block-woocommerce-cart-order-summary-shipping-block"></div>
|
||||
<div data-block-name="woocommerce/cart-order-summary-taxes-block" class="wp-block-woocommerce-cart-order-summary-taxes-block"></div>
|
||||
';
|
||||
// Order summary subtotal block was added in i3, so we search for it to see if we have a Cart i2 template.
|
||||
$regex_for_order_summary_subtotal = '/<div[^<]*?data-block-name="woocommerce\/cart-order-summary-subtotal-block"[^>]*?>/mi';
|
||||
$regex_for_order_summary = '/<div[^<]*?data-block-name="woocommerce\/cart-order-summary-block"[^>]*?>/mi';
|
||||
$has_i2_template = ! preg_match( $regex_for_order_summary_subtotal, $content );
|
||||
|
||||
if ( $has_i2_template ) {
|
||||
$content = preg_replace( $regex_for_order_summary, $order_summary_with_inner_blocks, $content );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
$this->asset_data_registry->add( 'countryData', CartCheckoutUtils::get_country_data(), true );
|
||||
$this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true );
|
||||
$this->asset_data_registry->add( 'isShippingCalculatorEnabled', filter_var( get_option( 'woocommerce_enable_shipping_calc' ), FILTER_VALIDATE_BOOLEAN ), true );
|
||||
$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true );
|
||||
$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true );
|
||||
$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled(), true );
|
||||
$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled(), true );
|
||||
$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled(), true );
|
||||
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ), true );
|
||||
$this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 );
|
||||
$this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme(), true );
|
||||
|
||||
$pickup_location_settings = get_option( 'woocommerce_pickup_location_settings', [] );
|
||||
$this->asset_data_registry->add( 'localPickupEnabled', wc_string_to_bool( $pickup_location_settings['enabled'] ?? 'no' ), true );
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after cart block data is registered.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_cart_enqueue_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register script and style assets for the block type before it is registered.
|
||||
*
|
||||
* This registers the scripts; it does not enqueue them.
|
||||
*/
|
||||
protected function register_block_type_assets() {
|
||||
parent::register_block_type_assets();
|
||||
$chunks = $this->get_chunks_paths( $this->chunks_folder );
|
||||
$vendor_chunks = $this->get_chunks_paths( 'vendors--cart-blocks' );
|
||||
$shared_chunks = [];
|
||||
$this->register_chunk_translations( array_merge( $chunks, $vendor_chunks, $shared_chunks ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of Cart block & its inner-block types.
|
||||
*
|
||||
* @return array;
|
||||
*/
|
||||
public static function get_cart_block_types() {
|
||||
return [
|
||||
'Cart',
|
||||
'CartOrderSummaryTaxesBlock',
|
||||
'CartOrderSummarySubtotalBlock',
|
||||
'FilledCartBlock',
|
||||
'EmptyCartBlock',
|
||||
'CartTotalsBlock',
|
||||
'CartItemsBlock',
|
||||
'CartLineItemsBlock',
|
||||
'CartOrderSummaryBlock',
|
||||
'CartExpressPaymentBlock',
|
||||
'ProceedToCheckoutBlock',
|
||||
'CartAcceptedPaymentMethodsBlock',
|
||||
'CartOrderSummaryCouponFormBlock',
|
||||
'CartOrderSummaryDiscountBlock',
|
||||
'CartOrderSummaryFeeBlock',
|
||||
'CartOrderSummaryHeadingBlock',
|
||||
'CartOrderSummaryShippingBlock',
|
||||
'CartCrossSellsBlock',
|
||||
'CartCrossSellsProductsBlock',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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,505 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
|
||||
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
|
||||
|
||||
/**
|
||||
* Checkout class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Checkout extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout';
|
||||
|
||||
/**
|
||||
* Chunks build folder.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $chunks_folder = 'checkout-blocks';
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
add_action( 'wp_loaded', array( $this, 'register_patterns' ) );
|
||||
// This prevents the page redirecting when the cart is empty. This is so the editor still loads the page preview.
|
||||
add_filter(
|
||||
'woocommerce_checkout_redirect_empty_cart',
|
||||
function( $return ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return isset( $_GET['_wp-find-template'] ) ? false : $return;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dequeues the scripts added by WC Core to the Checkout page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dequeue_woocommerce_core_scripts() {
|
||||
wp_dequeue_script( 'wc-checkout' );
|
||||
wp_dequeue_script( 'wc-password-strength-meter' );
|
||||
wp_dequeue_script( 'selectWoo' );
|
||||
wp_dequeue_style( 'select2' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block pattern for Empty Cart Message to make it translatable.
|
||||
*/
|
||||
public function register_patterns() {
|
||||
register_block_pattern(
|
||||
'woocommerce/checkout-heading',
|
||||
array(
|
||||
'title' => '',
|
||||
'inserter' => false,
|
||||
'content' => '<!-- wp:heading {"align":"wide", "level":1} --><h1 class="wp-block-heading alignwide">' . esc_html__( 'Checkout', 'woocommerce' ) . '</h1><!-- /wp:heading -->',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string;
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* @param string $content The block content.
|
||||
* @param WP_Block $block The block object.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes, $content, $block ) {
|
||||
/**
|
||||
* Fires before checkout block scripts are enqueued.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_before' );
|
||||
parent::enqueue_assets( $attributes, $content, $block );
|
||||
/**
|
||||
* Fires after checkout block scripts are enqueued.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_after' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Append frontend scripts when rendering the block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
|
||||
if ( $this->is_checkout_endpoint() ) {
|
||||
// Note: Currently the block only takes care of the main checkout form -- if an endpoint is set, refer to the
|
||||
// legacy shortcode instead and do not render block.
|
||||
return wc_current_theme_is_fse_theme() ? do_shortcode( '[woocommerce_checkout]' ) : '[woocommerce_checkout]';
|
||||
}
|
||||
|
||||
// Dequeue the core scripts when rendering this block.
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_woocommerce_core_scripts' ), 20 );
|
||||
|
||||
/**
|
||||
* We need to check if $content has any templates from prior iterations of the block, in order to update to the latest iteration.
|
||||
* We test the iteration version by searching for new blocks brought in by it.
|
||||
* The blocks used for testing should be always available in the block (not removable by the user).
|
||||
* Checkout i1's content was returning an empty div, with no data-block-name attribute
|
||||
*/
|
||||
$regex_for_empty_block = '/<div class="[a-zA-Z0-9_\- ]*wp-block-woocommerce-checkout[a-zA-Z0-9_\- ]*"><\/div>/mi';
|
||||
$has_i1_template = preg_match( $regex_for_empty_block, $content );
|
||||
|
||||
if ( $has_i1_template ) {
|
||||
// This fallback needs to match the default templates defined in our Blocks.
|
||||
$inner_blocks_html = '
|
||||
<div data-block-name="woocommerce/checkout-fields-block" class="wp-block-woocommerce-checkout-fields-block">
|
||||
<div data-block-name="woocommerce/checkout-express-payment-block" class="wp-block-woocommerce-checkout-express-payment-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-contact-information-block" class="wp-block-woocommerce-checkout-contact-information-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-shipping-address-block" class="wp-block-woocommerce-checkout-shipping-address-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-billing-address-block" class="wp-block-woocommerce-checkout-billing-address-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-shipping-methods-block" class="wp-block-woocommerce-checkout-shipping-methods-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-payment-block" class="wp-block-woocommerce-checkout-payment-block"></div>' .
|
||||
( isset( $attributes['showOrderNotes'] ) && false === $attributes['showOrderNotes'] ? '' : '<div data-block-name="woocommerce/checkout-order-note-block" class="wp-block-woocommerce-checkout-order-note-block"></div>' ) .
|
||||
( isset( $attributes['showPolicyLinks'] ) && false === $attributes['showPolicyLinks'] ? '' : '<div data-block-name="woocommerce/checkout-terms-block" class="wp-block-woocommerce-checkout-terms-block"></div>' ) .
|
||||
'<div data-block-name="woocommerce/checkout-actions-block" class="wp-block-woocommerce-checkout-actions-block"></div>
|
||||
</div>
|
||||
<div data-block-name="woocommerce/checkout-totals-block" class="wp-block-woocommerce-checkout-totals-block">
|
||||
<div data-block-name="woocommerce/checkout-order-summary-block" class="wp-block-woocommerce-checkout-order-summary-block"></div>
|
||||
</div>
|
||||
';
|
||||
|
||||
$content = str_replace( '</div>', $inner_blocks_html . '</div>', $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkout i3 added inner blocks for Order summary.
|
||||
* We need to add them to Checkout i2 templates.
|
||||
* The order needs to match the order in which these blocks were registered.
|
||||
*/
|
||||
$order_summary_with_inner_blocks = '$0
|
||||
<div data-block-name="woocommerce/checkout-order-summary-cart-items-block" class="wp-block-woocommerce-checkout-order-summary-cart-items-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-order-summary-subtotal-block" class="wp-block-woocommerce-checkout-order-summary-subtotal-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-order-summary-fee-block" class="wp-block-woocommerce-checkout-order-summary-fee-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-order-summary-discount-block" class="wp-block-woocommerce-checkout-order-summary-discount-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-order-summary-coupon-form-block" class="wp-block-woocommerce-checkout-order-summary-coupon-form-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-order-summary-shipping-block" class="wp-block-woocommerce-checkout-order-summary-shipping-block"></div>
|
||||
<div data-block-name="woocommerce/checkout-order-summary-taxes-block" class="wp-block-woocommerce-checkout-order-summary-taxes-block"></div>
|
||||
';
|
||||
// Order summary subtotal block was added in i3, so we search for it to see if we have a Checkout i2 template.
|
||||
$regex_for_order_summary_subtotal = '/<div[^<]*?data-block-name="woocommerce\/checkout-order-summary-subtotal-block"[^>]*?>/mi';
|
||||
$regex_for_order_summary = '/<div[^<]*?data-block-name="woocommerce\/checkout-order-summary-block"[^>]*?>/mi';
|
||||
$has_i2_template = ! preg_match( $regex_for_order_summary_subtotal, $content );
|
||||
|
||||
if ( $has_i2_template ) {
|
||||
$content = preg_replace( $regex_for_order_summary, $order_summary_with_inner_blocks, $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Local Pickup toggle to checkouts missing this forced template.
|
||||
*/
|
||||
$local_pickup_inner_blocks = '<div data-block-name="woocommerce/checkout-shipping-method-block" class="wp-block-woocommerce-checkout-shipping-method-block"></div>' . PHP_EOL . PHP_EOL . '<div data-block-name="woocommerce/checkout-pickup-options-block" class="wp-block-woocommerce-checkout-pickup-options-block"></div>' . PHP_EOL . PHP_EOL . '$0';
|
||||
$has_local_pickup_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-shipping-method-block"[^>]*?>/mi';
|
||||
$has_local_pickup = preg_match( $has_local_pickup_regex, $content );
|
||||
|
||||
if ( ! $has_local_pickup ) {
|
||||
$shipping_address_block_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-shipping-address-block" class="wp-block-woocommerce-checkout-shipping-address-block"[^>]*?><\/div>/mi';
|
||||
$content = preg_replace( $shipping_address_block_regex, $local_pickup_inner_blocks, $content );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're viewing a checkout page endpoint, rather than the main checkout page itself.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_checkout_endpoint() {
|
||||
return is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
$this->asset_data_registry->add( 'countryData', CartCheckoutUtils::get_country_data(), true );
|
||||
$this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true );
|
||||
$this->asset_data_registry->add(
|
||||
'checkoutAllowsGuest',
|
||||
false === filter_var(
|
||||
wc()->checkout()->is_registration_required(),
|
||||
FILTER_VALIDATE_BOOLEAN
|
||||
),
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'checkoutAllowsSignup',
|
||||
filter_var(
|
||||
wc()->checkout()->is_registration_enabled(),
|
||||
FILTER_VALIDATE_BOOLEAN
|
||||
),
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add( 'checkoutShowLoginReminder', filter_var( get_option( 'woocommerce_enable_checkout_login_reminder' ), FILTER_VALIDATE_BOOLEAN ), true );
|
||||
$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true );
|
||||
$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true );
|
||||
$this->asset_data_registry->add( 'forcedBillingAddress', 'billing_only' === get_option( 'woocommerce_ship_to_destination' ), true );
|
||||
$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled(), true );
|
||||
$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled(), true );
|
||||
$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled(), true );
|
||||
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ), true );
|
||||
$this->asset_data_registry->register_page_id( isset( $attributes['cartPageId'] ) ? $attributes['cartPageId'] : 0 );
|
||||
$this->asset_data_registry->add( 'isBlockTheme', wc_current_theme_is_fse_theme(), true );
|
||||
|
||||
$pickup_location_settings = get_option( 'woocommerce_pickup_location_settings', [] );
|
||||
$this->asset_data_registry->add( 'localPickupEnabled', wc_string_to_bool( $pickup_location_settings['enabled'] ?? 'no' ), true );
|
||||
|
||||
$is_block_editor = $this->is_block_editor();
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'shippingMethodsExist' ) ) {
|
||||
$methods_exist = wc_get_shipping_method_count( false, true ) > 0;
|
||||
$this->asset_data_registry->add( 'shippingMethodsExist', $methods_exist );
|
||||
}
|
||||
|
||||
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalShippingMethods' ) ) {
|
||||
$shipping_methods = WC()->shipping()->get_shipping_methods();
|
||||
$formatted_shipping_methods = array_reduce(
|
||||
$shipping_methods,
|
||||
function( $acc, $method ) {
|
||||
if ( in_array( $method->id, LocalPickupUtils::get_local_pickup_method_ids(), true ) ) {
|
||||
return $acc;
|
||||
}
|
||||
if ( $method->supports( 'settings' ) ) {
|
||||
$acc[] = [
|
||||
'id' => $method->id,
|
||||
'title' => $method->method_title,
|
||||
'description' => $method->method_description,
|
||||
];
|
||||
}
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
$this->asset_data_registry->add( 'globalShippingMethods', $formatted_shipping_methods );
|
||||
}
|
||||
|
||||
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'activeShippingZones' ) && class_exists( '\WC_Shipping_Zones' ) ) {
|
||||
$shipping_zones = \WC_Shipping_Zones::get_zones();
|
||||
$formatted_shipping_zones = array_reduce(
|
||||
$shipping_zones,
|
||||
function( $acc, $zone ) {
|
||||
$acc[] = [
|
||||
'id' => $zone['id'],
|
||||
'title' => $zone['zone_name'],
|
||||
'description' => $zone['formatted_zone_location'],
|
||||
];
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
$formatted_shipping_zones[] = [
|
||||
'id' => 0,
|
||||
'title' => __( 'International', 'woocommerce' ),
|
||||
'description' => __( 'Locations outside all other zones', 'woocommerce' ),
|
||||
];
|
||||
$this->asset_data_registry->add( 'activeShippingZones', $formatted_shipping_zones );
|
||||
}
|
||||
|
||||
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalPaymentMethods' ) ) {
|
||||
// These are used to show options in the sidebar. We want to get the full list of enabled payment methods,
|
||||
// not just the ones that are available for the current cart (which may not exist yet).
|
||||
$payment_methods = $this->get_enabled_payment_gateways();
|
||||
$formatted_payment_methods = array_reduce(
|
||||
$payment_methods,
|
||||
function( $acc, $method ) {
|
||||
$acc[] = [
|
||||
'id' => $method->id,
|
||||
'title' => $method->method_title,
|
||||
'description' => $method->method_description,
|
||||
];
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
$this->asset_data_registry->add( 'globalPaymentMethods', $formatted_payment_methods );
|
||||
}
|
||||
|
||||
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'incompatibleExtensions' ) ) {
|
||||
if ( ! class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) || ! function_exists( 'get_plugins' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$declared_extensions = \Automattic\WooCommerce\Utilities\FeaturesUtil::get_compatible_plugins_for_feature( 'cart_checkout_blocks' );
|
||||
$all_plugins = \get_plugins(); // Note that `get_compatible_plugins_for_feature` calls `get_plugins` internally, so this is already in cache.
|
||||
$incompatible_extensions = array_reduce(
|
||||
$declared_extensions['incompatible'],
|
||||
function( $acc, $item ) use ( $all_plugins ) {
|
||||
$plugin = $all_plugins[ $item ] ?? null;
|
||||
$plugin_id = $plugin['TextDomain'] ?? dirname( $item, 2 );
|
||||
$plugin_name = $plugin['Name'] ?? $plugin_id;
|
||||
$acc[] = [
|
||||
'id' => $plugin_id,
|
||||
'title' => $plugin_name,
|
||||
];
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
$this->asset_data_registry->add( 'incompatibleExtensions', $incompatible_extensions );
|
||||
}
|
||||
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
|
||||
$this->asset_data_registry->hydrate_data_from_api_request( 'checkoutData', '/wc/store/v1/checkout' );
|
||||
$this->hydrate_customer_payment_methods();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after checkout block data is registered.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_checkout_enqueue_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get payment methods that are enabled in settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_enabled_payment_gateways() {
|
||||
$payment_gateways = WC()->payment_gateways->payment_gateways();
|
||||
return array_filter(
|
||||
$payment_gateways,
|
||||
function( $payment_gateway ) {
|
||||
return 'yes' === $payment_gateway->enabled;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we currently on the admin block editor screen?
|
||||
*/
|
||||
protected function is_block_editor() {
|
||||
if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
|
||||
return false;
|
||||
}
|
||||
$screen = get_current_screen();
|
||||
|
||||
return $screen && $screen->is_block_editor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saved customer payment methods for use in checkout.
|
||||
*/
|
||||
protected function hydrate_customer_payment_methods() {
|
||||
if ( ! is_user_logged_in() || $this->asset_data_registry->exists( 'customerPaymentMethods' ) ) {
|
||||
return;
|
||||
}
|
||||
add_filter( 'woocommerce_payment_methods_list_item', [ $this, 'include_token_id_with_payment_methods' ], 10, 2 );
|
||||
|
||||
$payment_gateways = $this->get_enabled_payment_gateways();
|
||||
$payment_methods = wc_get_customer_saved_methods_list( get_current_user_id() );
|
||||
|
||||
// Filter out payment methods that are not enabled.
|
||||
foreach ( $payment_methods as $payment_method_group => $saved_payment_methods ) {
|
||||
$payment_methods[ $payment_method_group ] = array_values(
|
||||
array_filter(
|
||||
$saved_payment_methods,
|
||||
function( $saved_payment_method ) use ( $payment_gateways ) {
|
||||
return in_array( $saved_payment_method['method']['gateway'], array_keys( $payment_gateways ), true );
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'customerPaymentMethods',
|
||||
$payment_methods
|
||||
);
|
||||
remove_filter( 'woocommerce_payment_methods_list_item', [ $this, 'include_token_id_with_payment_methods' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for woocommerce_payment_methods_list_item filter to add token id
|
||||
* to the generated list.
|
||||
*
|
||||
* @param array $list_item The current list item for the saved payment method.
|
||||
* @param \WC_Token $token The token for the current list item.
|
||||
*
|
||||
* @return array The list item with the token id added.
|
||||
*/
|
||||
public static function include_token_id_with_payment_methods( $list_item, $token ) {
|
||||
$list_item['tokenId'] = $token->get_id();
|
||||
$brand = ! empty( $list_item['method']['brand'] ) ?
|
||||
strtolower( $list_item['method']['brand'] ) :
|
||||
'';
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- need to match on translated value from core.
|
||||
if ( ! empty( $brand ) && esc_html__( 'Credit card', 'woocommerce' ) !== $brand ) {
|
||||
$list_item['method']['brand'] = wc_get_credit_card_type_label( $brand );
|
||||
}
|
||||
return $list_item;
|
||||
}
|
||||
/**
|
||||
* Register script and style assets for the block type before it is registered.
|
||||
*
|
||||
* This registers the scripts; it does not enqueue them.
|
||||
*/
|
||||
protected function register_block_type_assets() {
|
||||
parent::register_block_type_assets();
|
||||
$chunks = $this->get_chunks_paths( $this->chunks_folder );
|
||||
$vendor_chunks = $this->get_chunks_paths( 'vendors--checkout-blocks' );
|
||||
$shared_chunks = [ 'cart-blocks/cart-express-payment--checkout-blocks/express-payment-frontend' ];
|
||||
$this->register_chunk_translations( array_merge( $chunks, $vendor_chunks, $shared_chunks ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of Checkout block & its inner-block types.
|
||||
*
|
||||
* @return array;
|
||||
*/
|
||||
public static function get_checkout_block_types() {
|
||||
return [
|
||||
'Checkout',
|
||||
'CheckoutActionsBlock',
|
||||
'CheckoutBillingAddressBlock',
|
||||
'CheckoutContactInformationBlock',
|
||||
'CheckoutExpressPaymentBlock',
|
||||
'CheckoutFieldsBlock',
|
||||
'CheckoutOrderNoteBlock',
|
||||
'CheckoutOrderSummaryBlock',
|
||||
'CheckoutOrderSummaryCartItemsBlock',
|
||||
'CheckoutOrderSummaryCouponFormBlock',
|
||||
'CheckoutOrderSummaryDiscountBlock',
|
||||
'CheckoutOrderSummaryFeeBlock',
|
||||
'CheckoutOrderSummaryShippingBlock',
|
||||
'CheckoutOrderSummarySubtotalBlock',
|
||||
'CheckoutOrderSummaryTaxesBlock',
|
||||
'CheckoutPaymentBlock',
|
||||
'CheckoutShippingAddressBlock',
|
||||
'CheckoutShippingMethodsBlock',
|
||||
'CheckoutShippingMethodBlock',
|
||||
'CheckoutPickupOptionsBlock',
|
||||
'CheckoutTermsBlock',
|
||||
'CheckoutTotalsBlock',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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,127 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use WC_Shortcode_Cart;
|
||||
use WC_Shortcode_Checkout;
|
||||
use WC_Frontend_Scripts;
|
||||
|
||||
/**
|
||||
* Classic Shortcode class
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ClassicShortcode extends AbstractDynamicBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'classic-shortcode';
|
||||
|
||||
/**
|
||||
* API version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $api_version = '2';
|
||||
|
||||
/**
|
||||
* 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['shortcode'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to load the scripts here because when using block templates wp_head() gets run after the block
|
||||
* template. As a result we are trying to enqueue required scripts before we have even registered them.
|
||||
*
|
||||
* @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5328#issuecomment-989013447
|
||||
*/
|
||||
if ( class_exists( 'WC_Frontend_Scripts' ) ) {
|
||||
$frontend_scripts = new WC_Frontend_Scripts();
|
||||
$frontend_scripts::load_scripts();
|
||||
}
|
||||
|
||||
if ( 'cart' === $attributes['shortcode'] ) {
|
||||
return $this->render_cart( $attributes );
|
||||
}
|
||||
|
||||
if ( 'checkout' === $attributes['shortcode'] ) {
|
||||
return $this->render_checkout( $attributes );
|
||||
}
|
||||
|
||||
return "You're using the ClassicShortcode block";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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( 'woocommerce', 'wp-block-group' );
|
||||
|
||||
if ( isset( $attributes['align'] ) ) {
|
||||
$classes[] = "align{$attributes['align']}";
|
||||
}
|
||||
|
||||
return implode( ' ', $classes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method for rendering the cart shortcode.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render_cart( $attributes ) {
|
||||
if ( ! isset( WC()->cart ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
echo '<div class="' . esc_attr( $this->get_container_classes( $attributes ) ) . '">';
|
||||
WC_Shortcode_Cart::output( array() );
|
||||
echo '</div>';
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method for rendering the checkout shortcode.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render_checkout( $attributes ) {
|
||||
if ( ! isset( WC()->cart ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
echo '<div class="' . esc_attr( $this->get_container_classes( $attributes ) ) . '">';
|
||||
WC_Shortcode_Checkout::output( array() );
|
||||
echo '</div>';
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
use WC_Shortcode_Checkout;
|
||||
use WC_Frontend_Scripts;
|
||||
|
||||
/**
|
||||
* Classic Template class
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ClassicTemplate extends AbstractDynamicBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'legacy-template';
|
||||
|
||||
/**
|
||||
* API version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $api_version = '2';
|
||||
|
||||
const FILTER_PRODUCTS_BY_STOCK_QUERY_PARAM = 'filter_stock_status';
|
||||
|
||||
/**
|
||||
* Initialize this block.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
add_filter( 'render_block', array( $this, 'add_alignment_class_to_wrapper' ), 10, 2 );
|
||||
add_filter( 'woocommerce_product_query_meta_query', array( $this, 'filter_products_by_stock' ) );
|
||||
add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets used for rendering the block in editor context.
|
||||
*
|
||||
* This is needed if a block is not yet within the post content--`render` and `enqueue_assets` may not have ran.
|
||||
*/
|
||||
public function enqueue_block_assets() {
|
||||
// Ensures frontend styles for blocks exist in the site editor iframe.
|
||||
if ( class_exists( 'WC_Frontend_Scripts' ) && is_admin() ) {
|
||||
$frontend_scripts = new WC_Frontend_Scripts();
|
||||
$styles = $frontend_scripts::get_styles();
|
||||
|
||||
foreach ( $styles as $handle => $style ) {
|
||||
wp_enqueue_style(
|
||||
$handle,
|
||||
set_url_scheme( $style['src'] ),
|
||||
$style['deps'],
|
||||
$style['version'],
|
||||
$style['media']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method for the Classic Template block. This method will determine which template to render.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string | void Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
if ( ! isset( $attributes['template'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to load the scripts here because when using block templates wp_head() gets run after the block
|
||||
* template. As a result we are trying to enqueue required scripts before we have even registered them.
|
||||
*
|
||||
* @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5328#issuecomment-989013447
|
||||
*/
|
||||
if ( class_exists( 'WC_Frontend_Scripts' ) ) {
|
||||
$frontend_scripts = new WC_Frontend_Scripts();
|
||||
$frontend_scripts::load_scripts();
|
||||
}
|
||||
|
||||
if ( OrderConfirmationTemplate::get_slug() === $attributes['template'] ) {
|
||||
return $this->render_order_received();
|
||||
}
|
||||
|
||||
if ( is_product() ) {
|
||||
return $this->render_single_product();
|
||||
}
|
||||
|
||||
$valid = false;
|
||||
$archive_templates = array(
|
||||
'archive-product',
|
||||
'taxonomy-product_cat',
|
||||
'taxonomy-product_tag',
|
||||
ProductAttributeTemplate::SLUG,
|
||||
ProductSearchResultsTemplate::SLUG,
|
||||
);
|
||||
|
||||
// Set selected template when we directly find template base slug.
|
||||
if ( in_array( $attributes['template'], $archive_templates, true ) ) {
|
||||
$valid = true;
|
||||
}
|
||||
|
||||
// Set selected template when we find template base slug as prefix for a specific term.
|
||||
foreach ( $archive_templates as $template ) {
|
||||
if ( 0 === strpos( $attributes['template'], $template ) ) {
|
||||
$valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $valid ) {
|
||||
// Set this so that our product filters can detect if it's a PHP template.
|
||||
$this->asset_data_registry->add( 'isRenderingPhpTemplate', true, true );
|
||||
|
||||
// Set this so filter blocks being used as widgets know when to render.
|
||||
$this->asset_data_registry->add( 'hasFilterableProducts', true, true );
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'pageUrl',
|
||||
html_entity_decode( get_pagenum_link() ),
|
||||
''
|
||||
);
|
||||
|
||||
return $this->render_archive_product();
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
echo "You're using the ClassicTemplate block";
|
||||
|
||||
wp_reset_postdata();
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method for rendering the order confirmation template.
|
||||
*
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render_order_received() {
|
||||
ob_start();
|
||||
|
||||
echo '<div class="wp-block-group">';
|
||||
|
||||
echo sprintf(
|
||||
'<%1$s %2$s>%3$s</%1$s>',
|
||||
'h1',
|
||||
get_block_wrapper_attributes(), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
esc_html__( 'Order confirmation', 'woocommerce' )
|
||||
);
|
||||
|
||||
WC_Shortcode_Checkout::output( array() );
|
||||
|
||||
echo '</div>';
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method for the single product template and parts.
|
||||
*
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render_single_product() {
|
||||
ob_start();
|
||||
|
||||
/**
|
||||
* Hook: woocommerce_before_main_content
|
||||
*
|
||||
* Called before rendering the main content for a product.
|
||||
*
|
||||
* @see woocommerce_output_content_wrapper() Outputs opening DIV for the content (priority 10)
|
||||
* @see woocommerce_breadcrumb() Outputs breadcrumb trail to the current product (priority 20)
|
||||
* @see WC_Structured_Data::generate_website_data() Outputs schema markup (priority 30)
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
do_action( 'woocommerce_before_main_content' );
|
||||
|
||||
$product_query = new \WP_Query(
|
||||
array(
|
||||
'post_type' => 'product',
|
||||
'p' => get_the_ID(),
|
||||
)
|
||||
);
|
||||
|
||||
while ( $product_query->have_posts() ) :
|
||||
|
||||
$product_query->the_post();
|
||||
wc_get_template_part( 'content', 'single-product' );
|
||||
|
||||
endwhile;
|
||||
|
||||
/**
|
||||
* Hook: woocommerce_after_main_content
|
||||
*
|
||||
* Called after rendering the main content for a product.
|
||||
*
|
||||
* @see woocommerce_output_content_wrapper_end() Outputs closing DIV for the content (priority 10)
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
do_action( 'woocommerce_after_main_content' );
|
||||
|
||||
wp_reset_postdata();
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method for the archive product template and parts.
|
||||
*
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render_archive_product() {
|
||||
ob_start();
|
||||
|
||||
/**
|
||||
* Hook: woocommerce_before_main_content
|
||||
*
|
||||
* Called before rendering the main content for a product.
|
||||
*
|
||||
* @see woocommerce_output_content_wrapper() Outputs opening DIV for the content (priority 10)
|
||||
* @see woocommerce_breadcrumb() Outputs breadcrumb trail to the current product (priority 20)
|
||||
* @see WC_Structured_Data::generate_website_data() Outputs schema markup (priority 30)
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
do_action( 'woocommerce_before_main_content' );
|
||||
|
||||
?>
|
||||
<header class="woocommerce-products-header">
|
||||
<?php
|
||||
/**
|
||||
* Hook: woocommerce_show_page_title
|
||||
*
|
||||
* Allows controlling the display of the page title.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
if ( apply_filters( 'woocommerce_show_page_title', true ) ) {
|
||||
?>
|
||||
<h1 class="woocommerce-products-header__title page-title">
|
||||
<?php
|
||||
woocommerce_page_title();
|
||||
?>
|
||||
</h1>
|
||||
<?php
|
||||
}
|
||||
/**
|
||||
* Hook: woocommerce_archive_description.
|
||||
*
|
||||
* @see woocommerce_taxonomy_archive_description() Renders the taxonomy archive description (priority 10)
|
||||
* @see woocommerce_product_archive_description() Renders the product archive description (priority 10)
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
do_action( 'woocommerce_archive_description' );
|
||||
?>
|
||||
</header>
|
||||
<?php
|
||||
if ( woocommerce_product_loop() ) {
|
||||
|
||||
/**
|
||||
* Hook: woocommerce_before_shop_loop.
|
||||
*
|
||||
* @see woocommerce_output_all_notices() Render error notices (priority 10)
|
||||
* @see woocommerce_result_count() Show number of results found (priority 20)
|
||||
* @see woocommerce_catalog_ordering() Show form to control sort order (priority 30)
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
do_action( 'woocommerce_before_shop_loop' );
|
||||
|
||||
woocommerce_product_loop_start();
|
||||
|
||||
if ( wc_get_loop_prop( 'total' ) ) {
|
||||
while ( have_posts() ) {
|
||||
the_post();
|
||||
|
||||
/**
|
||||
* Hook: woocommerce_shop_loop.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
do_action( 'woocommerce_shop_loop' );
|
||||
|
||||
wc_get_template_part( 'content', 'product' );
|
||||
}
|
||||
}
|
||||
|
||||
woocommerce_product_loop_end();
|
||||
|
||||
/**
|
||||
* Hook: woocommerce_after_shop_loop.
|
||||
*
|
||||
* @see woocommerce_pagination() Renders pagination (priority 10)
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
do_action( 'woocommerce_after_shop_loop' );
|
||||
} else {
|
||||
/**
|
||||
* Hook: woocommerce_no_products_found.
|
||||
*
|
||||
* @see wc_no_products_found() Default no products found content (priority 10)
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
do_action( 'woocommerce_no_products_found' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook: woocommerce_after_main_content
|
||||
*
|
||||
* Called after rendering the main content for a product.
|
||||
*
|
||||
* @see woocommerce_output_content_wrapper_end() Outputs closing DIV for the content (priority 10)
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
do_action( 'woocommerce_after_main_content' );
|
||||
|
||||
wp_reset_postdata();
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML markup with the right classes by attributes.
|
||||
* This function appends the classname at the first element that have the class attribute.
|
||||
* Based on the experience, all the wrapper elements have a class attribute.
|
||||
*
|
||||
* @param string $content Block content.
|
||||
* @param array $block Parsed block data.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
public function add_alignment_class_to_wrapper( string $content, array $block ) {
|
||||
if ( ( 'woocommerce/' . $this->block_name ) !== $block['blockName'] ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$attributes = (array) $block['attrs'];
|
||||
|
||||
// Set the default alignment to wide.
|
||||
if ( ! isset( $attributes['align'] ) ) {
|
||||
$attributes['align'] = 'wide';
|
||||
}
|
||||
|
||||
$align_class_and_style = StyleAttributesUtils::get_align_class_and_style( $attributes );
|
||||
|
||||
if ( ! isset( $align_class_and_style['class'] ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Find the first tag.
|
||||
$first_tag = '<[^<>]+>';
|
||||
$matches = array();
|
||||
preg_match( $first_tag, $content, $matches );
|
||||
|
||||
// If there is a tag, but it doesn't have a class attribute, add the class attribute.
|
||||
if ( isset( $matches[0] ) && strpos( $matches[0], ' class=' ) === false ) {
|
||||
$pattern_before_tag_closing = '/.+?(?=>)/';
|
||||
return preg_replace( $pattern_before_tag_closing, '$0 class="' . $align_class_and_style['class'] . '"', $content, 1 );
|
||||
}
|
||||
|
||||
// If there is a tag, and it has a class already, add the class attribute.
|
||||
$pattern_get_class = '/(?<=class=\"|\')[^"|\']+(?=\"|\')/';
|
||||
return preg_replace( $pattern_get_class, '$0 ' . $align_class_and_style['class'], $content, 1 );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter products by stock status when as query param there is "filter_stock_status"
|
||||
*
|
||||
* @param array $meta_query Meta query.
|
||||
* @return array
|
||||
*/
|
||||
public function filter_products_by_stock( $meta_query ) {
|
||||
global $wp_query;
|
||||
|
||||
if (
|
||||
is_admin() ||
|
||||
! $wp_query->is_main_query() ||
|
||||
! isset( $_GET[ self::FILTER_PRODUCTS_BY_STOCK_QUERY_PARAM ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
) {
|
||||
return $meta_query;
|
||||
}
|
||||
|
||||
$stock_status = array_keys( wc_get_product_stock_status_options() );
|
||||
$values = sanitize_text_field( wp_unslash( $_GET[ self::FILTER_PRODUCTS_BY_STOCK_QUERY_PARAM ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
$values_to_array = explode( ',', $values );
|
||||
|
||||
$filtered_values = array_filter(
|
||||
$values_to_array,
|
||||
function( $value ) use ( $stock_status ) {
|
||||
return in_array( $value, $stock_status, true );
|
||||
}
|
||||
);
|
||||
|
||||
if ( ! empty( $filtered_values ) ) {
|
||||
|
||||
$meta_query[] = array(
|
||||
'key' => '_stock_status',
|
||||
'value' => $filtered_values,
|
||||
'compare' => 'IN',
|
||||
);
|
||||
}
|
||||
return $meta_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CollectionAttributeFilter class.
|
||||
*/
|
||||
final class CollectionActiveFilters extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'collection-active-filters';
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
$query_id = $block->context['queryId'] ?? 0;
|
||||
|
||||
/**
|
||||
* Filters the active filter data provided by filter blocks.
|
||||
*
|
||||
* $data = array(
|
||||
* <id> => array(
|
||||
* 'type' => string,
|
||||
* 'items' => array(
|
||||
* array(
|
||||
* 'title' => string,
|
||||
* 'attributes' => array(
|
||||
* <key> => string
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* );
|
||||
*
|
||||
* @since 11.7.0
|
||||
*
|
||||
* @param array $data The active filters data
|
||||
* @param array $params The query param parsed from the URL.
|
||||
* @return array Active filters data.
|
||||
*/
|
||||
$active_filters = apply_filters( 'collection_active_filters_data', array(), $this->get_filter_query_params( $query_id ) );
|
||||
|
||||
if ( empty( $active_filters ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$context = array(
|
||||
'queryId' => $query_id,
|
||||
'params' => array_keys( $this->get_filter_query_params( $query_id ) ),
|
||||
);
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => 'wc-block-active-filters',
|
||||
'data-wc-interactive' => wp_json_encode( array( 'namespace' => 'woocommerce/collection-active-filters' ) ),
|
||||
'data-wc-context' => wp_json_encode( $context ),
|
||||
)
|
||||
);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<div <?php echo $wrapper_attributes; ?>>
|
||||
<ul class="wc-block-active-filters__list %3$s">
|
||||
<?php foreach ( $active_filters as $filter ) : ?>
|
||||
<li>
|
||||
<span class="wc-block-active-filters__list-item-type"><?php echo esc_html( $filter['type'] ); ?>: </span>
|
||||
<ul>
|
||||
<?php $this->render_items( $filter['items'], $attributes['displayStyle'] ); ?>
|
||||
</ul>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<button class="wc-block-active-filters__clear-all" data-wc-on--click="actions.clearAll">
|
||||
<span aria-hidden="true"><?php echo esc_html__( 'Clear All', 'woocommerce' ); ?></span>
|
||||
<span class="screen-reader-text"><?php echo esc_html__( 'Clear All Filters', 'woocommerce' ); ?></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list items.
|
||||
*
|
||||
* @param array $items Items data.
|
||||
* @param string $style Display style: list | chips.
|
||||
*/
|
||||
private function render_items( $items, $style ) {
|
||||
foreach ( $items as $item ) {
|
||||
if ( 'chips' === $style ) {
|
||||
$this->render_chip_item( $item );
|
||||
} else {
|
||||
$this->render_list_item( $item );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list item of an active filter.
|
||||
*
|
||||
* @param array $args Item data.
|
||||
* @return string Item HTML.
|
||||
*/
|
||||
private function render_list_item( $args ) {
|
||||
list ( 'title' => $title, 'attributes' => $attributes ) = wp_parse_args(
|
||||
$args,
|
||||
array(
|
||||
'title' => '',
|
||||
'attributes' => array(),
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $title || empty( $attributes ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remove_label = sprintf( 'Remove %s filter', wp_strip_all_tags( $title ) );
|
||||
?>
|
||||
<li class="wc-block-active-filters__list-item">
|
||||
<span class="wc-block-active-filters__list-item-name">
|
||||
<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<button class="wc-block-active-filters__list-item-remove" <?php echo $this->get_html_attributes( $attributes ); ?>>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" class="wc-block-components-chip__remove-icon" aria-hidden="true" focusable="false"><path d="M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z"></path></svg>
|
||||
<span class="screen-reader-text"><?php echo esc_html( $remove_label ); ?></span>
|
||||
</button>
|
||||
<?php echo wp_kses_post( $title ); ?>
|
||||
</span>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the chip item of an active filter.
|
||||
*
|
||||
* @param array $args Item data.
|
||||
* @return string Item HTML.
|
||||
*/
|
||||
private function render_chip_item( $args ) {
|
||||
list ( 'title' => $title, 'attributes' => $attributes ) = wp_parse_args(
|
||||
$args,
|
||||
array(
|
||||
'title' => '',
|
||||
'attributes' => array(),
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $title || empty( $attributes ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remove_label = sprintf( 'Remove %s filter', wp_strip_all_tags( $title ) );
|
||||
?>
|
||||
<li class="wc-block-active-filters__list-item">
|
||||
<span class="is-removable wc-block-components-chip wc-block-components-chip--radius-large">
|
||||
<span aria-hidden="false" class="wc-block-components-chip__text"><?php echo wp_kses_post( $title ); ?></span>
|
||||
<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<button class="wc-block-components-chip__remove" aria-label="<?php echo esc_attr( $remove_label ); ?>" <?php echo $this->get_html_attributes( $attributes ); ?>>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" role="img" class="wc-block-components-chip__remove-icon" aria-hidden="true" focusable="false"><path d="M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z"></path></svg>
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Build HTML attributes string from assoc array.
|
||||
*
|
||||
* @param array $attributes Attributes data as an assoc array.
|
||||
* @return string Escaped HTML attributes string.
|
||||
*/
|
||||
private function get_html_attributes( $attributes ) {
|
||||
return array_reduce(
|
||||
array_keys( $attributes ),
|
||||
function( $acc, $key ) use ( $attributes ) {
|
||||
$acc .= sprintf( ' %1$s="%2$s"', esc_attr( $key ), esc_attr( $attributes[ $key ] ) );
|
||||
return $acc;
|
||||
},
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the filter parameters from the URL.
|
||||
* For now we only get the global query params from the URL. In the future,
|
||||
* we should get the query params based on $query_id.
|
||||
*
|
||||
* @param int $query_id Query ID.
|
||||
* @return array Parsed filter params.
|
||||
*/
|
||||
private function get_filter_query_params( $query_id ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
|
||||
|
||||
$parsed_url = wp_parse_url( esc_url_raw( $request_uri ) );
|
||||
|
||||
if ( empty( $parsed_url['query'] ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
parse_str( $parsed_url['query'], $url_query_params );
|
||||
|
||||
/**
|
||||
* Filters the active filter data provided by filter blocks.
|
||||
*
|
||||
* @since 11.7.0
|
||||
*
|
||||
* @param array $filter_param_keys The active filters data
|
||||
* @param array $url_param_keys The query param parsed from the URL.
|
||||
*
|
||||
* @return array Active filters params.
|
||||
*/
|
||||
$filter_param_keys = array_unique( apply_filters( 'collection_filter_query_param_keys', array(), array_keys( $url_query_params ) ) );
|
||||
|
||||
return array_filter(
|
||||
$url_query_params,
|
||||
function( $key ) use ( $filter_param_keys ) {
|
||||
return in_array( $key, $filter_param_keys, true );
|
||||
},
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\InteractivityComponents\Dropdown;
|
||||
|
||||
/**
|
||||
* CollectionAttributeFilter class.
|
||||
*/
|
||||
final class CollectionAttributeFilter extends AbstractBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'collection-attribute-filter';
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
|
||||
add_filter( 'collection_filter_query_param_keys', array( $this, 'get_filter_query_param_keys' ), 10, 2 );
|
||||
add_filter( 'collection_active_filters_data', array( $this, 'register_active_filters_data' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the query param keys.
|
||||
*
|
||||
* @param array $filter_param_keys The active filters data.
|
||||
* @param array $url_param_keys The query param parsed from the URL.
|
||||
*
|
||||
* @return array Active filters param keys.
|
||||
*/
|
||||
public function get_filter_query_param_keys( $filter_param_keys, $url_param_keys ) {
|
||||
$attribute_param_keys = array_filter(
|
||||
$url_param_keys,
|
||||
function( $param ) {
|
||||
return strpos( $param, 'filter_' ) === 0 || strpos( $param, 'query_type_' ) === 0;
|
||||
}
|
||||
);
|
||||
|
||||
return array_merge(
|
||||
$filter_param_keys,
|
||||
$attribute_param_keys
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the active filters data.
|
||||
*
|
||||
* @param array $data The active filters data.
|
||||
* @param array $params The query param parsed from the URL.
|
||||
* @return array Active filters data.
|
||||
*/
|
||||
public function register_active_filters_data( $data, $params ) {
|
||||
$product_attributes_map = array_reduce(
|
||||
wc_get_attribute_taxonomies(),
|
||||
function( $acc, $attribute_object ) {
|
||||
$acc[ $attribute_object->attribute_name ] = $attribute_object->attribute_label;
|
||||
return $acc;
|
||||
},
|
||||
array()
|
||||
);
|
||||
|
||||
$active_product_attributes = array_reduce(
|
||||
array_keys( $params ),
|
||||
function( $acc, $attribute ) {
|
||||
if ( strpos( $attribute, 'filter_' ) === 0 ) {
|
||||
$acc[] = str_replace( 'filter_', '', $attribute );
|
||||
}
|
||||
return $acc;
|
||||
},
|
||||
array()
|
||||
);
|
||||
|
||||
$active_product_attributes = array_filter(
|
||||
$active_product_attributes,
|
||||
function( $item ) use ( $product_attributes_map ) {
|
||||
return in_array( $item, array_keys( $product_attributes_map ), true );
|
||||
}
|
||||
);
|
||||
|
||||
foreach ( $active_product_attributes as $product_attribute ) {
|
||||
$terms = explode( ',', get_query_var( "filter_{$product_attribute}" ) );
|
||||
|
||||
// Get attribute term by slug.
|
||||
$terms = array_map(
|
||||
function( $term ) use ( $product_attribute ) {
|
||||
$term_object = get_term_by( 'slug', $term, "pa_{$product_attribute}" );
|
||||
return array(
|
||||
'title' => $term_object->name,
|
||||
'attributes' => array(
|
||||
'data-wc-on--click' => 'woocommerce/collection-attribute-filter::actions.removeFilter',
|
||||
'data-wc-context' => 'woocommerce/collection-attribute-filter::' . wp_json_encode(
|
||||
array(
|
||||
'value' => $term,
|
||||
'attributeSlug' => $product_attribute,
|
||||
'queryType' => get_query_var( "query_type_{$product_attribute}" ),
|
||||
)
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
$terms
|
||||
);
|
||||
|
||||
$data[ $product_attribute ] = array(
|
||||
'type' => $product_attributes_map[ $product_attribute ],
|
||||
'items' => $terms,
|
||||
);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
if (
|
||||
is_admin() ||
|
||||
empty( $block->context['collectionData']['attribute_counts'] ) ||
|
||||
empty( $attributes['attributeId'] )
|
||||
) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$product_attribute = wc_get_attribute( $attributes['attributeId'] );
|
||||
|
||||
$attribute_counts = array_reduce(
|
||||
$block->context['collectionData']['attribute_counts'],
|
||||
function( $acc, $count ) {
|
||||
$acc[ $count['term'] ] = $count['count'];
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
$attribute_terms = get_terms(
|
||||
array(
|
||||
'taxonomy' => $product_attribute->slug,
|
||||
'include' => array_keys( $attribute_counts ),
|
||||
)
|
||||
);
|
||||
|
||||
$selected_terms = array_filter(
|
||||
explode(
|
||||
',',
|
||||
get_query_var( 'filter_' . str_replace( 'pa_', '', $product_attribute->slug ) )
|
||||
)
|
||||
);
|
||||
|
||||
$attribute_options = array_map(
|
||||
function( $term ) use ( $attribute_counts, $selected_terms ) {
|
||||
$term = (array) $term;
|
||||
$term['count'] = $attribute_counts[ $term['term_id'] ];
|
||||
$term['selected'] = in_array( $term['slug'], $selected_terms, true );
|
||||
return $term;
|
||||
},
|
||||
$attribute_terms
|
||||
);
|
||||
|
||||
$filter_content = 'dropdown' === $attributes['displayStyle'] ? $this->render_attribute_dropdown( $attribute_options, $attributes ) : $this->render_attribute_list( $attribute_options, $attributes );
|
||||
|
||||
$context = array(
|
||||
'attributeSlug' => str_replace( 'pa_', '', $product_attribute->slug ),
|
||||
'queryType' => $attributes['queryType'],
|
||||
'selectType' => $attributes['selectType'],
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div %1$s>%2$s</div>',
|
||||
get_block_wrapper_attributes(
|
||||
array(
|
||||
'data-wc-context' => wp_json_encode( $context ),
|
||||
'data-wc-interactive' => wp_json_encode( array( 'namespace' => 'woocommerce/collection-attribute-filter' ) ),
|
||||
)
|
||||
),
|
||||
$filter_content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the dropdown.
|
||||
*
|
||||
* @param array $options Data to render the dropdown.
|
||||
* @param bool $attributes Block attributes.
|
||||
*/
|
||||
private function render_attribute_dropdown( $options, $attributes ) {
|
||||
$list_items = array();
|
||||
$selected_item = array();
|
||||
|
||||
foreach ( $options as $option ) {
|
||||
$item = array(
|
||||
'label' => $attributes['showCounts'] ? sprintf( '%1$s (%2$d)', $option['name'], $option['count'] ) : $option['name'],
|
||||
'value' => $option['slug'],
|
||||
);
|
||||
|
||||
$list_items[] = $item;
|
||||
|
||||
if ( $option['selected'] ) {
|
||||
$selected_item = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return Dropdown::render(
|
||||
array(
|
||||
'items' => $list_items,
|
||||
'action' => 'woocommerce/collection-attribute-filter::actions.navigate',
|
||||
'selected_item' => $selected_item,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list.
|
||||
*
|
||||
* @param array $options Data to render the list.
|
||||
* @param bool $attributes Block attributes.
|
||||
*/
|
||||
private function render_attribute_list( $options, $attributes ) {
|
||||
$output = '<ul class="wc-block-checkbox-list wc-block-components-checkbox-list wc-block-stock-filter-list">';
|
||||
foreach ( $options as $option ) {
|
||||
$output .= $this->render_list_item_template( $option, $attributes['showCounts'] );
|
||||
}
|
||||
$output .= '</ul>';
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list item.
|
||||
*
|
||||
* @param array $option Data to render the list item.
|
||||
* @param bool $show_counts Whether to display the count.
|
||||
*/
|
||||
private function render_list_item_template( $option, $show_counts ) {
|
||||
$count_html = $show_counts ?
|
||||
sprintf(
|
||||
'<span class="wc-filter-element-label-list-count">
|
||||
<span aria-hidden="true">%1$s</span>
|
||||
<span class="screen-reader-text">%2$s</span>
|
||||
</span>',
|
||||
$option['count'],
|
||||
// translators: %d is the number of products.
|
||||
sprintf( _n( '%d product', '%d products', $option['count'], 'woocommerce' ), $option['count'] )
|
||||
) :
|
||||
'';
|
||||
|
||||
$template = '<li>
|
||||
<div class="wc-block-components-checkbox wc-block-checkbox-list__checkbox">
|
||||
<label for="%1$s">
|
||||
<input
|
||||
id="%1$s"
|
||||
class="wc-block-components-checkbox__input"
|
||||
type="checkbox"
|
||||
aria-invalid="false"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
data-wc-context=\'{ "attributeTermSlug": "%5$s" }\'
|
||||
value="%5$s"
|
||||
%4$s
|
||||
/>
|
||||
<svg class="wc-block-components-checkbox__mark" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20"><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"></path></svg>
|
||||
<span class="wc-block-components-checkbox__label">%2$s %3$s</span>
|
||||
</label>
|
||||
</div>
|
||||
</li>';
|
||||
|
||||
return sprintf(
|
||||
$template,
|
||||
esc_attr( $option['slug'] ) . '-' . $option['term_id'],
|
||||
esc_html( $option['name'] ),
|
||||
$count_html,
|
||||
checked( $option['selected'], true, false ),
|
||||
esc_attr( $option['slug'] )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Hydration;
|
||||
|
||||
/**
|
||||
* CollectionFilters class.
|
||||
*/
|
||||
final class CollectionFilters extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'collection-filters';
|
||||
|
||||
/**
|
||||
* Cache the current response from the API.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $current_response = null;
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
add_action( 'render_block_context', array( $this, 'modify_inner_blocks_context' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ( ! is_admin() ) {
|
||||
/**
|
||||
* At this point, WP starts rendering the Collection Filters block,
|
||||
* we can safely unset the current response.
|
||||
*/
|
||||
$this->current_response = 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 ) {
|
||||
$attributes_data = array(
|
||||
'data-wc-interactive' => wp_json_encode( array( 'namespace' => 'woocommerce/collection-filters' ) ),
|
||||
'class' => 'wc-block-collection-filters',
|
||||
);
|
||||
|
||||
if ( ! isset( $block->context['queryId'] ) ) {
|
||||
$attributes_data['data-wc-navigation-id'] = sprintf(
|
||||
'wc-collection-filters-%s',
|
||||
md5( wp_json_encode( $block->parsed_block['innerBlocks'] ) )
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<nav %1$s>%2$s</nav>',
|
||||
get_block_wrapper_attributes( $attributes_data ),
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the context of inner blocks.
|
||||
*
|
||||
* @param array $context The block context.
|
||||
* @param array $parsed_block The parsed block.
|
||||
* @param WP_Block $parent_block The parent block.
|
||||
* @return array
|
||||
*/
|
||||
public function modify_inner_blocks_context( $context, $parsed_block, $parent_block ) {
|
||||
if ( is_admin() || ! is_a( $parent_block, 'WP_Block' ) ) {
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the first direct child of Collection Filters is rendering, we
|
||||
* hydrate and cache the collection data response.
|
||||
*/
|
||||
if (
|
||||
"woocommerce/{$this->block_name}" === $parent_block->name &&
|
||||
! isset( $this->current_response )
|
||||
) {
|
||||
$this->current_response = $this->get_aggregated_collection_data( $parent_block );
|
||||
}
|
||||
|
||||
if ( empty( $this->current_response ) ) {
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter blocks use the collectionData context, so we only update that
|
||||
* specific context with fetched data.
|
||||
*/
|
||||
if ( isset( $context['collectionData'] ) ) {
|
||||
$context['collectionData'] = $this->current_response;
|
||||
}
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the aggregated collection data from the API.
|
||||
* Loop through inner blocks and build a query string to pass to the API.
|
||||
*
|
||||
* @param WP_Block $block The block instance.
|
||||
* @return array
|
||||
*/
|
||||
private function get_aggregated_collection_data( $block ) {
|
||||
$collection_data_params = $this->get_inner_collection_data_params( $block->inner_blocks );
|
||||
|
||||
if ( empty( array_filter( $collection_data_params ) ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$response = Package::container()->get( Hydration::class )->get_rest_api_response_data(
|
||||
add_query_arg(
|
||||
array_merge(
|
||||
$this->get_formatted_products_params( $block->context['query'] ?? array() ),
|
||||
$collection_data_params,
|
||||
),
|
||||
'/wc/store/v1/products/collection-data'
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! empty( $response['body'] ) ) {
|
||||
return json_decode( wp_json_encode( $response['body'] ), true );
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all inner blocks recursively.
|
||||
*
|
||||
* @param WP_Block_List $inner_blocks The block to get inner blocks from.
|
||||
* @param array $results The results array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_inner_collection_data_params( $inner_blocks, &$results = array() ) {
|
||||
if ( is_a( $inner_blocks, 'WP_Block_List' ) ) {
|
||||
foreach ( $inner_blocks as $inner_block ) {
|
||||
if ( ! empty( $inner_block->attributes['queryParam'] ) ) {
|
||||
$query_param = $inner_block->attributes['queryParam'];
|
||||
/**
|
||||
* There can be multiple attribute filters so we transform
|
||||
* the query param of each filter into an array to merge
|
||||
* them together.
|
||||
*/
|
||||
if ( ! empty( $query_param['calculate_attribute_counts'] ) ) {
|
||||
$query_param['calculate_attribute_counts'] = array( $query_param['calculate_attribute_counts'] );
|
||||
}
|
||||
$results = array_merge_recursive( $results, $query_param );
|
||||
}
|
||||
$this->get_inner_collection_data_params(
|
||||
$inner_block->inner_blocks,
|
||||
$results
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted products params for ProductCollectionData route from the
|
||||
* query context.
|
||||
*
|
||||
* @param array $query The query context.
|
||||
* @return array
|
||||
*/
|
||||
private function get_formatted_products_params( $query ) {
|
||||
$params = array();
|
||||
|
||||
if ( empty( $query['isProductCollectionBlock'] ) ) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following params can be passed directly to Store API endpoints.
|
||||
*/
|
||||
$shared_params = array( 'exclude', 'offset', 'search' );
|
||||
|
||||
/**
|
||||
* The following params just need to transform the key, their value can
|
||||
* be passed as it is to the Store API.
|
||||
*/
|
||||
$mapped_params = array(
|
||||
'woocommerceStockStatus' => 'stock_status',
|
||||
'woocommerceOnSale' => 'on_sale',
|
||||
'woocommerceHandPickedProducts' => 'include',
|
||||
);
|
||||
|
||||
$taxonomy_mapper = function( $key ) {
|
||||
$mapping = array(
|
||||
'product_tag' => 'tag',
|
||||
'product_cat' => 'category',
|
||||
);
|
||||
|
||||
return $mapping[ $key ] ?? '_unstable_tax_' . $key;
|
||||
};
|
||||
|
||||
array_walk(
|
||||
$query,
|
||||
function( $value, $key ) use ( $shared_params, $mapped_params, $taxonomy_mapper, &$params ) {
|
||||
if ( in_array( $key, $shared_params, true ) ) {
|
||||
$params[ $key ] = $value;
|
||||
}
|
||||
|
||||
if ( in_array( $key, array_keys( $mapped_params ), true ) ) {
|
||||
$params[ $mapped_params[ $key ] ] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of taxQuery and woocommerceAttributes need additional
|
||||
* transformation to the shape that Store API accepts.
|
||||
*/
|
||||
if ( 'taxQuery' === $key && is_array( $value ) ) {
|
||||
array_walk(
|
||||
$value,
|
||||
function( $terms, $taxonomy ) use ( $taxonomy_mapper, &$params ) {
|
||||
$params[ $taxonomy_mapper( $taxonomy ) ] = implode( ',', $terms );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'woocommerceAttributes' === $key && is_array( $value ) ) {
|
||||
array_walk(
|
||||
$value,
|
||||
function( $attribute ) use ( &$params ) {
|
||||
$params['attributes'][] = array(
|
||||
'attribute' => $attribute['taxonomy'],
|
||||
'term_id' => $attribute['termId'],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Product Collection determines the product visibility based on stock
|
||||
* statuses. We need to pass the catalog_visibility param to the Store
|
||||
* API to make sure the product visibility is correct.
|
||||
*/
|
||||
$params['catalog_visibility'] = is_search() ? 'search' : 'visible';
|
||||
|
||||
/**
|
||||
* `false` values got removed from `add_query_arg`, so we need to convert
|
||||
* them to numeric.
|
||||
*/
|
||||
return array_map(
|
||||
function( $param ) {
|
||||
return is_bool( $param ) ? +$param : $param;
|
||||
},
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CollectionPriceFilter class.
|
||||
*/
|
||||
final class CollectionPriceFilter extends AbstractBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'collection-price-filter';
|
||||
|
||||
const MIN_PRICE_QUERY_VAR = 'min_price';
|
||||
const MAX_PRICE_QUERY_VAR = 'max_price';
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
|
||||
add_filter( 'collection_filter_query_param_keys', array( $this, 'get_filter_query_param_keys' ), 10, 2 );
|
||||
add_filter( 'collection_active_filters_data', array( $this, 'register_active_filters_data' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the query param keys.
|
||||
*
|
||||
* @param array $filter_param_keys The active filters data.
|
||||
* @param array $url_param_keys The query param parsed from the URL.
|
||||
*
|
||||
* @return array Active filters param keys.
|
||||
*/
|
||||
public function get_filter_query_param_keys( $filter_param_keys, $url_param_keys ) {
|
||||
$price_param_keys = array_filter(
|
||||
$url_param_keys,
|
||||
function( $param ) {
|
||||
return self::MIN_PRICE_QUERY_VAR === $param || self::MAX_PRICE_QUERY_VAR === $param;
|
||||
}
|
||||
);
|
||||
|
||||
return array_merge(
|
||||
$filter_param_keys,
|
||||
$price_param_keys
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the active filters data.
|
||||
*
|
||||
* @param array $data The active filters data.
|
||||
* @param array $params The query param parsed from the URL.
|
||||
* @return array Active filters data.
|
||||
*/
|
||||
public function register_active_filters_data( $data, $params ) {
|
||||
$min_price = intval( $params[ self::MIN_PRICE_QUERY_VAR ] ?? 0 );
|
||||
$max_price = intval( $params[ self::MAX_PRICE_QUERY_VAR ] ?? 0 );
|
||||
$formatted_min_price = $min_price ? wp_strip_all_tags( wc_price( $min_price, array( 'decimals' => 0 ) ) ) : null;
|
||||
$formatted_max_price = $max_price ? wp_strip_all_tags( wc_price( $max_price, array( 'decimals' => 0 ) ) ) : null;
|
||||
|
||||
if ( ! $formatted_min_price && ! $formatted_max_price ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( $formatted_min_price && $formatted_max_price ) {
|
||||
$title = sprintf(
|
||||
/* translators: %1$s and %2$s are the formatted minimum and maximum prices respectively. */
|
||||
__( 'Between %1$s and %2$s', 'woocommerce' ),
|
||||
$formatted_min_price,
|
||||
$formatted_max_price
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $formatted_min_price ) {
|
||||
/* translators: %s is the formatted maximum price. */
|
||||
$title = sprintf( __( 'Up to %s', 'woocommerce' ), $formatted_max_price );
|
||||
}
|
||||
|
||||
if ( ! $formatted_max_price ) {
|
||||
/* translators: %s is the formatted minimum price. */
|
||||
$title = sprintf( __( 'From %s', 'woocommerce' ), $formatted_min_price );
|
||||
}
|
||||
|
||||
$data['price'] = array(
|
||||
'type' => __( 'Price', 'woocommerce' ),
|
||||
'items' => array(
|
||||
array(
|
||||
'title' => $title,
|
||||
'attributes' => array(
|
||||
'data-wc-on--click' => 'woocommerce/collection-price-filter::actions.reset',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
if (
|
||||
is_admin() ||
|
||||
empty( $block->context['collectionData'] ) ||
|
||||
empty( $block->context['collectionData']['price_range'] )
|
||||
) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$price_range = $block->context['collectionData']['price_range'];
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes();
|
||||
$min_range = $price_range['min_price'] / 10 ** $price_range['currency_minor_unit'];
|
||||
$max_range = $price_range['max_price'] / 10 ** $price_range['currency_minor_unit'];
|
||||
$min_price = intval( get_query_var( self::MIN_PRICE_QUERY_VAR, $min_range ) );
|
||||
$max_price = intval( get_query_var( self::MAX_PRICE_QUERY_VAR, $max_range ) );
|
||||
$formatted_min_price = wc_price( $min_price, array( 'decimals' => 0 ) );
|
||||
$formatted_max_price = wc_price( $max_price, array( 'decimals' => 0 ) );
|
||||
|
||||
$data = array(
|
||||
'minPrice' => $min_price,
|
||||
'maxPrice' => $max_price,
|
||||
'minRange' => $min_range,
|
||||
'maxRange' => $max_range,
|
||||
);
|
||||
|
||||
wc_initial_state(
|
||||
'woocommerce/collection-price-filter',
|
||||
$data
|
||||
);
|
||||
|
||||
list (
|
||||
'showInputFields' => $show_input_fields,
|
||||
'inlineInput' => $inline_input
|
||||
) = $attributes;
|
||||
|
||||
// Max range shouldn't be 0.
|
||||
if ( ! $max_range ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// CSS variables for the range bar style.
|
||||
$__low = 100 * ( $min_price - $min_range ) / ( $max_range - $min_range );
|
||||
$__high = 100 * ( $max_price - $min_range ) / ( $max_range - $min_range );
|
||||
$range_style = "--low: $__low%; --high: $__high%";
|
||||
|
||||
$data_directive = wp_json_encode( array( 'namespace' => 'woocommerce/collection-price-filter' ) );
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => $show_input_fields && $inline_input ? 'inline-input' : '',
|
||||
'data-wc-interactive' => $data_directive,
|
||||
)
|
||||
);
|
||||
|
||||
$price_min = $show_input_fields ?
|
||||
sprintf(
|
||||
'<input
|
||||
class="min"
|
||||
type="text"
|
||||
value="%d"
|
||||
data-wc-bind--value="state.minPrice"
|
||||
data-wc-on--input="actions.setMinPrice"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
/>',
|
||||
esc_attr( $min_price )
|
||||
) : sprintf(
|
||||
'<span data-wc-text="state.formattedMinPrice">%s</span>',
|
||||
// Not escaped, as this is HTML.
|
||||
$formatted_min_price
|
||||
);
|
||||
|
||||
$price_max = $show_input_fields ?
|
||||
sprintf(
|
||||
'<input
|
||||
class="max"
|
||||
type="text"
|
||||
value="%d"
|
||||
data-wc-bind--value="state.maxPrice"
|
||||
data-wc-on--input="actions.setMaxPrice"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
/>',
|
||||
esc_attr( $max_price )
|
||||
) : sprintf(
|
||||
'<span data-wc-text="state.formattedMaxPrice">%s</span>',
|
||||
// Not escaped, as this is HTML.
|
||||
$formatted_max_price
|
||||
);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
|
||||
<div
|
||||
class="range"
|
||||
style="<?php echo esc_attr( $range_style ); ?>"
|
||||
data-wc-bind--style="state.rangeStyle"
|
||||
>
|
||||
<div class="range-bar"></div>
|
||||
<input
|
||||
type="range"
|
||||
class="min"
|
||||
min="<?php echo esc_attr( $min_range ); ?>"
|
||||
max="<?php echo esc_attr( $max_range ); ?>"
|
||||
value="<?php echo esc_attr( $min_price ); ?>"
|
||||
data-wc-bind--max="state.maxRange"
|
||||
data-wc-bind--value="state.minPrice"
|
||||
data-wc-class--active="state.isMinActive"
|
||||
data-wc-on--input="actions.setMinPrice"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
>
|
||||
<input
|
||||
type="range"
|
||||
class="max"
|
||||
min="<?php echo esc_attr( $min_range ); ?>"
|
||||
max="<?php echo esc_attr( $max_range ); ?>"
|
||||
value="<?php echo esc_attr( $max_price ); ?>"
|
||||
data-wc-bind--max="state.maxRange"
|
||||
data-wc-bind--value="state.maxPrice"
|
||||
data-wc-class--active="state.isMaxActive"
|
||||
data-wc-on--input="actions.setMaxPrice"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
>
|
||||
</div>
|
||||
<div class="text">
|
||||
<?php // $price_min and $price_max are escaped in the sprintf() calls above. ?>
|
||||
<?php echo $price_min; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php echo $price_max; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\InteractivityComponents\CheckboxList;
|
||||
use Automattic\WooCommerce\Blocks\InteractivityComponents\Dropdown;
|
||||
|
||||
/**
|
||||
* Collection Rating Filter Block
|
||||
*
|
||||
* @package Automattic\WooCommerce\Blocks\BlockTypes
|
||||
*/
|
||||
final class CollectionRatingFilter extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'collection-rating-filter';
|
||||
|
||||
const RATING_FILTER_QUERY_VAR = 'rating_filter';
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
|
||||
add_filter( 'collection_filter_query_param_keys', array( $this, 'get_filter_query_param_keys' ), 10, 2 );
|
||||
add_filter( 'collection_active_filters_data', array( $this, 'register_active_filters_data' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the query param keys.
|
||||
*
|
||||
* @param array $filter_param_keys The active filters data.
|
||||
* @param array $url_param_keys The query param parsed from the URL.
|
||||
*
|
||||
* @return array Active filters param keys.
|
||||
*/
|
||||
public function get_filter_query_param_keys( $filter_param_keys, $url_param_keys ) {
|
||||
$price_param_keys = array_filter(
|
||||
$url_param_keys,
|
||||
function( $param ) {
|
||||
return self::RATING_FILTER_QUERY_VAR === $param;
|
||||
}
|
||||
);
|
||||
|
||||
return array_merge(
|
||||
$filter_param_keys,
|
||||
$price_param_keys
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the active filters data.
|
||||
*
|
||||
* @param array $data The active filters data.
|
||||
* @param array $params The query param parsed from the URL.
|
||||
* @return array Active filters data.
|
||||
*/
|
||||
public function register_active_filters_data( $data, $params ) {
|
||||
if ( empty( $params[ self::RATING_FILTER_QUERY_VAR ] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$active_ratings = array_filter(
|
||||
explode( ',', $params[ self::RATING_FILTER_QUERY_VAR ] )
|
||||
);
|
||||
|
||||
if ( empty( $active_ratings ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$active_ratings = array_map(
|
||||
function( $rating ) {
|
||||
return array(
|
||||
/* translators: %d is the rating value. */
|
||||
'title' => sprintf( __( 'Rated %d out of 5', 'woocommerce' ), $rating ),
|
||||
'attributes' => array(
|
||||
'data-wc-on--click' => 'woocommerce/collection-rating-filter::actions.removeFilter',
|
||||
'data-wc-context' => 'woocommerce/collection-rating-filter::' . wp_json_encode( array( 'value' => $rating ) ),
|
||||
),
|
||||
);
|
||||
},
|
||||
$active_ratings
|
||||
);
|
||||
|
||||
$data['rating'] = array(
|
||||
'type' => __( 'Rating', 'woocommerce' ),
|
||||
'items' => $active_ratings,
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
// don't render if its admin, or ajax in progress.
|
||||
if ( is_admin() || wp_doing_ajax() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rating_counts = $block->context['collectionData']['rating_counts'] ?? array();
|
||||
$display_style = $attributes['displayStyle'] ?? 'list';
|
||||
$show_counts = $attributes['showCounts'] ?? false;
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
|
||||
$selected_ratings_query_param = isset( $_GET[ self::RATING_FILTER_QUERY_VAR ] ) ? sanitize_text_field( wp_unslash( $_GET[ self::RATING_FILTER_QUERY_VAR ] ) ) : '';
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'data-wc-interactive' => 'woocommerce/collection-rating-filter',
|
||||
'class' => 'wc-block-rating-filter',
|
||||
)
|
||||
);
|
||||
|
||||
$input = 'list' === $display_style ? CheckboxList::render(
|
||||
array(
|
||||
'items' => $this->get_checkbox_list_items( $rating_counts, $selected_ratings_query_param, $show_counts ),
|
||||
'on_change' => 'woocommerce/collection-rating-filter::actions.onCheckboxChange',
|
||||
)
|
||||
) : Dropdown::render(
|
||||
$this->get_dropdown_props( $rating_counts, $selected_ratings_query_param, $show_counts )
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div %1$s>
|
||||
<div class="wc-block-rating-filter__controls">%2$s</div>
|
||||
<div class="wc-block-rating-filter__actions"></div>
|
||||
</div>',
|
||||
$wrapper_attributes,
|
||||
$input
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the rating label.
|
||||
*
|
||||
* @param int $rating The rating to render.
|
||||
* @param string $count_label The count label to render.
|
||||
* @return string|false
|
||||
*/
|
||||
private function render_rating_label( $rating, $count_label ) {
|
||||
$width = $rating * 20;
|
||||
|
||||
$rating_label = sprintf(
|
||||
/* translators: %1$d is referring to rating value. Example: Rated 4 out of 5. */
|
||||
__( 'Rated %1$d out of 5', 'woocommerce' ),
|
||||
$rating,
|
||||
);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="wc-block-components-product-rating">
|
||||
<div class="wc-block-components-product-rating__stars" role="img" aria-label="<?php echo esc_attr( $rating_label ); ?>">
|
||||
<span style="width: <?php echo esc_attr( $width ); ?>%" aria-hidden="true">
|
||||
</span>
|
||||
</div>
|
||||
<span class="wc-block-components-product-rating-count">
|
||||
<?php echo esc_html( $count_label ); ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the checkbox list items.
|
||||
*
|
||||
* @param array $rating_counts The rating counts.
|
||||
* @param string $selected_ratings_query The url query param for selected ratings.
|
||||
* @param bool $show_counts Whether to show the counts.
|
||||
* @return array
|
||||
*/
|
||||
private function get_checkbox_list_items( $rating_counts, $selected_ratings_query, $show_counts ) {
|
||||
$ratings_array = explode( ',', $selected_ratings_query );
|
||||
|
||||
return array_map(
|
||||
function( $rating ) use ( $ratings_array, $show_counts ) {
|
||||
$rating_str = (string) $rating['rating'];
|
||||
$count = $rating['count'];
|
||||
$count_label = $show_counts ? "($count)" : '';
|
||||
|
||||
return array(
|
||||
'id' => 'rating-' . $rating_str,
|
||||
'checked' => in_array( $rating_str, $ratings_array, true ),
|
||||
'label' => $this->render_rating_label( (int) $rating_str, $count_label ),
|
||||
'value' => $rating_str,
|
||||
);
|
||||
},
|
||||
$rating_counts
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dropdown props.
|
||||
*
|
||||
* @param mixed $rating_counts The rating counts.
|
||||
* @param mixed $selected_ratings_query The url query param for selected ratings.
|
||||
* @param mixed $show_counts Whether to show the counts.
|
||||
* @return array<array-key, array>
|
||||
*/
|
||||
private function get_dropdown_props( $rating_counts, $selected_ratings_query, $show_counts ) {
|
||||
$ratings_array = explode( ',', $selected_ratings_query );
|
||||
|
||||
$selected_item = array_reduce(
|
||||
$rating_counts,
|
||||
function( $carry, $rating ) use ( $ratings_array, $show_counts ) {
|
||||
if ( in_array( (string) $rating['rating'], $ratings_array, true ) ) {
|
||||
$count = $rating['count'];
|
||||
$count_label = $show_counts ? "($count)" : '';
|
||||
$rating_str = (string) $rating['rating'];
|
||||
return array(
|
||||
/* translators: %d is referring to the average rating value. Example: Rated 4 out of 5. */
|
||||
'label' => sprintf( __( 'Rated %d out of 5', 'woocommerce' ), $rating_str ) . ' ' . $count_label,
|
||||
'value' => $rating['rating'],
|
||||
);
|
||||
}
|
||||
return $carry;
|
||||
},
|
||||
array()
|
||||
);
|
||||
|
||||
return array(
|
||||
'items' => array_map(
|
||||
function ( $rating ) use ( $show_counts ) {
|
||||
$count = $rating['count'];
|
||||
$count_label = $show_counts ? "($count)" : '';
|
||||
$rating_str = (string) $rating['rating'];
|
||||
return array(
|
||||
/* translators: %d is referring to the average rating value. Example: Rated 4 out of 5. */
|
||||
'label' => sprintf( __( 'Rated %d out of 5', 'woocommerce' ), $rating_str ) . ' ' . $count_label,
|
||||
'value' => $rating['rating'],
|
||||
);
|
||||
},
|
||||
$rating_counts
|
||||
),
|
||||
'selected_item' => $selected_item,
|
||||
'action' => 'woocommerce/collection-rating-filter::actions.onDropdownChange',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\InteractivityComponents\Dropdown;
|
||||
|
||||
/**
|
||||
* CollectionStockFilter class.
|
||||
*/
|
||||
final class CollectionStockFilter extends AbstractBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'collection-stock-filter';
|
||||
|
||||
const STOCK_STATUS_QUERY_VAR = 'filter_stock_status';
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
|
||||
add_filter( 'collection_filter_query_param_keys', array( $this, 'get_filter_query_param_keys' ), 10, 2 );
|
||||
add_filter( 'collection_active_filters_data', array( $this, 'register_active_filters_data' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the query param keys.
|
||||
*
|
||||
* @param array $filter_param_keys The active filters data.
|
||||
* @param array $url_param_keys The query param parsed from the URL.
|
||||
*
|
||||
* @return array Active filters param keys.
|
||||
*/
|
||||
public function get_filter_query_param_keys( $filter_param_keys, $url_param_keys ) {
|
||||
$stock_param_keys = array_filter(
|
||||
$url_param_keys,
|
||||
function( $param ) {
|
||||
return self::STOCK_STATUS_QUERY_VAR === $param;
|
||||
}
|
||||
);
|
||||
|
||||
return array_merge(
|
||||
$filter_param_keys,
|
||||
$stock_param_keys
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the active filters data.
|
||||
*
|
||||
* @param array $data The active filters data.
|
||||
* @param array $params The query param parsed from the URL.
|
||||
* @return array Active filters data.
|
||||
*/
|
||||
public function register_active_filters_data( $data, $params ) {
|
||||
$stock_status_options = wc_get_product_stock_status_options();
|
||||
|
||||
if ( empty( $params[ self::STOCK_STATUS_QUERY_VAR ] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$active_stock_statuses = array_filter(
|
||||
explode( ',', $params[ self::STOCK_STATUS_QUERY_VAR ] )
|
||||
);
|
||||
|
||||
if ( empty( $active_stock_statuses ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$active_stock_statuses = array_map(
|
||||
function( $status ) use ( $stock_status_options ) {
|
||||
return array(
|
||||
'title' => $stock_status_options[ $status ],
|
||||
'attributes' => array(
|
||||
'data-wc-on--click' => 'woocommerce/collection-stock-filter::actions.removeFilter',
|
||||
'data-wc-context' => 'woocommerce/collection-stock-filter::' . wp_json_encode( array( 'value' => $status ) ),
|
||||
),
|
||||
);
|
||||
},
|
||||
$active_stock_statuses
|
||||
);
|
||||
|
||||
$data['stock'] = array(
|
||||
'type' => __( 'Stock Status', 'woocommerce' ),
|
||||
'items' => $active_stock_statuses,
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $stock_statuses Any stock statuses that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $stock_statuses = [] ) {
|
||||
parent::enqueue_data( $stock_statuses );
|
||||
$this->asset_data_registry->add( 'stockStatusOptions', wc_get_product_stock_status_options(), true );
|
||||
$this->asset_data_registry->add( 'hideOutOfStockItems', 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
// don't render if its admin, or ajax in progress.
|
||||
if ( is_admin() || wp_doing_ajax() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$stock_status_counts = $block->context['collectionData']['stock_status_counts'] ?? [];
|
||||
$wrapper_attributes = get_block_wrapper_attributes();
|
||||
|
||||
return sprintf(
|
||||
'<div %1$s>
|
||||
<div class="wc-block-stock-filter__controls">%2$s</div>
|
||||
<div class="wc-block-stock-filter__actions"></div>
|
||||
</div>',
|
||||
$wrapper_attributes,
|
||||
$this->get_stock_filter_html( $stock_status_counts, $attributes ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stock filter HTML
|
||||
*
|
||||
* @param array $stock_counts An array of stock counts.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
private function get_stock_filter_html( $stock_counts, $attributes ) {
|
||||
$display_style = $attributes['displayStyle'] ?? 'list';
|
||||
$show_counts = $attributes['showCounts'] ?? false;
|
||||
$stock_statuses = wc_get_product_stock_status_options();
|
||||
|
||||
// check the url params to select initial item on page load.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here.
|
||||
$selected_stock_status = isset( $_GET[ self::STOCK_STATUS_QUERY_VAR ] ) ? sanitize_text_field( wp_unslash( $_GET[ self::STOCK_STATUS_QUERY_VAR ] ) ) : '';
|
||||
|
||||
$list_items = array_map(
|
||||
function( $item ) use ( $stock_statuses, $show_counts ) {
|
||||
$label = $show_counts ? $stock_statuses[ $item['status'] ] . ' (' . $item['count'] . ')' : $stock_statuses[ $item['status'] ];
|
||||
return array(
|
||||
'label' => $label,
|
||||
'value' => $item['status'],
|
||||
);
|
||||
},
|
||||
$stock_counts
|
||||
);
|
||||
|
||||
$selected_items = array_values(
|
||||
array_filter(
|
||||
$list_items,
|
||||
function( $item ) use ( $selected_stock_status ) {
|
||||
return $item['value'] === $selected_stock_status;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Just for the dropdown, we can only select 1 item.
|
||||
$selected_item = $selected_items[0] ?? array(
|
||||
'label' => null,
|
||||
'value' => null,
|
||||
);
|
||||
|
||||
$data_directive = wp_json_encode( array( 'namespace' => 'woocommerce/collection-stock-filter' ) );
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div data-wc-interactive='<?php echo esc_attr( $data_directive ); ?>'>
|
||||
<?php if ( 'list' === $display_style ) : ?>
|
||||
<div class="wc-block-stock-filter style-list">
|
||||
<ul class="wc-block-checkbox-list wc-block-components-checkbox-list wc-block-stock-filter-list">
|
||||
<?php foreach ( $stock_counts as $stock_count ) { ?>
|
||||
<li>
|
||||
<div class="wc-block-components-checkbox wc-block-checkbox-list__checkbox">
|
||||
<label for="<?php echo esc_attr( $stock_count['status'] ); ?>">
|
||||
<input
|
||||
id="<?php echo esc_attr( $stock_count['status'] ); ?>"
|
||||
class="wc-block-components-checkbox__input"
|
||||
type="checkbox"
|
||||
aria-invalid="false"
|
||||
data-wc-on--change="actions.updateProducts"
|
||||
value="<?php echo esc_attr( $stock_count['status'] ); ?>"
|
||||
<?php checked( strpos( $selected_stock_status, $stock_count['status'] ) !== false, 1 ); ?>
|
||||
>
|
||||
<svg class="wc-block-components-checkbox__mark" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20">
|
||||
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"></path>
|
||||
</svg>
|
||||
<span class="wc-block-components-checkbox__label">
|
||||
<?php echo esc_html( $stock_statuses[ $stock_count['status'] ] ); ?>
|
||||
|
||||
<?php if ( $show_counts ) : ?>
|
||||
<?php
|
||||
// translators: %s: number of products.
|
||||
$screen_reader_text = sprintf( _n( '%s product', '%s products', $stock_count['count'], 'woocommerce' ), number_format_i18n( $stock_count['count'] ) );
|
||||
?>
|
||||
<span>
|
||||
<span aria-hidden="true">
|
||||
<?php $show_counts ? print( esc_html( '(' . $stock_count['count'] . ')' ) ) : null; ?>
|
||||
</span>
|
||||
<span class="screen-reader-text">
|
||||
<?php esc_html( $screen_reader_text ); ?>
|
||||
</span>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( 'dropdown' === $display_style ) : ?>
|
||||
<?php
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Dropdown::render() escapes output.
|
||||
echo Dropdown::render(
|
||||
array(
|
||||
'items' => $list_items,
|
||||
'action' => 'woocommerce/collection-stock-filter::actions.navigate',
|
||||
'selected_item' => $selected_item,
|
||||
)
|
||||
);
|
||||
?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
@@ -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>', esc_attr( implode( ' ', $classes ) ), esc_attr( $styles ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the styles for the wrapper element (background image, color).
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param string $image_url Item image url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_bg_styles( $attributes, $image_url ) {
|
||||
$style = '';
|
||||
|
||||
if ( $attributes['isRepeated'] || $attributes['hasParallax'] ) {
|
||||
$style .= "background-image: url($image_url);";
|
||||
}
|
||||
|
||||
if ( ! $attributes['isRepeated'] ) {
|
||||
$style .= 'background-repeat: no-repeat;';
|
||||
|
||||
$bg_size = 'cover' === $attributes['imageFit'] ? $attributes['imageFit'] : 'auto';
|
||||
$style .= 'background-size: ' . $bg_size . ';';
|
||||
}
|
||||
|
||||
if ( $this->hasFocalPoint( $attributes ) ) {
|
||||
$style .= sprintf(
|
||||
'background-position: %s%% %s%%;',
|
||||
$attributes['focalPoint']['x'] * 100,
|
||||
$attributes['focalPoint']['y'] * 100
|
||||
);
|
||||
}
|
||||
|
||||
$global_style_style = StyleAttributesUtils::get_styles_by_attributes( $attributes, $this->global_style_wrapper );
|
||||
$style .= $global_style_style;
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the featured image
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param \WC_Product|\WP_Term $item Item object.
|
||||
* @param string $image_url Item image url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function render_image( $attributes, $item, string $image_url ) {
|
||||
$style = sprintf( 'object-fit: %s;', esc_attr( $attributes['imageFit'] ) );
|
||||
$img_alt = $attributes['alt'] ?: $this->get_item_title( $item );
|
||||
|
||||
if ( $this->hasFocalPoint( $attributes ) ) {
|
||||
$style .= sprintf(
|
||||
'object-position: %s%% %s%%;',
|
||||
$attributes['focalPoint']['x'] * 100,
|
||||
$attributes['focalPoint']['y'] * 100
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! empty( $image_url ) ) {
|
||||
return sprintf(
|
||||
'<img alt="%1$s" class="wc-block-%2$s__background-image" src="%3$s" style="%4$s" />',
|
||||
esc_attr( $img_alt ),
|
||||
$this->block_name,
|
||||
esc_url( $image_url ),
|
||||
esc_attr( $style )
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the styles for the wrapper element (background image, color).
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return string
|
||||
*/
|
||||
public function get_styles( $attributes ) {
|
||||
$style = '';
|
||||
|
||||
$min_height = $attributes['minHeight'] ?? wc_get_theme_support( 'featured_block::default_height', 500 );
|
||||
|
||||
if ( isset( $attributes['minHeight'] ) ) {
|
||||
$style .= sprintf( 'min-height:%dpx;', intval( $min_height ) );
|
||||
}
|
||||
|
||||
$global_style_style = StyleAttributesUtils::get_styles_by_attributes( $attributes, $this->global_style_wrapper );
|
||||
$style .= $global_style_style;
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get class names for the block container.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return string
|
||||
*/
|
||||
public function get_classes( $attributes ) {
|
||||
$classes = array( 'wc-block-' . $this->block_name );
|
||||
|
||||
if ( isset( $attributes['align'] ) ) {
|
||||
$classes[] = "align{$attributes['align']}";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['dimRatio'] ) && ( 0 !== $attributes['dimRatio'] ) ) {
|
||||
$classes[] = 'has-background-dim';
|
||||
|
||||
if ( 50 !== $attributes['dimRatio'] ) {
|
||||
$classes[] = 'has-background-dim-' . 10 * round( $attributes['dimRatio'] / 10 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $attributes['contentAlign'] ) && 'center' !== $attributes['contentAlign'] ) {
|
||||
$classes[] = "has-{$attributes['contentAlign']}-content";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['className'] ) ) {
|
||||
$classes[] = $attributes['className'];
|
||||
}
|
||||
|
||||
$global_style_classes = StyleAttributesUtils::get_classes_by_attributes( $attributes, $this->global_style_wrapper );
|
||||
|
||||
$classes[] = $global_style_classes;
|
||||
|
||||
return implode( ' ', $classes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the block overlay
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function render_overlay( $attributes ) {
|
||||
if ( isset( $attributes['overlayGradient'] ) ) {
|
||||
$overlay_styles = sprintf( 'background-image: %s', $attributes['overlayGradient'] );
|
||||
} elseif ( isset( $attributes['overlayColor'] ) ) {
|
||||
$overlay_styles = sprintf( 'background-color: %s', $attributes['overlayColor'] );
|
||||
} else {
|
||||
$overlay_styles = 'background-color: #000000';
|
||||
}
|
||||
|
||||
return sprintf( '<div class="background-dim__overlay" style="%s"></div>', esc_attr( $overlay_styles ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the focal point is defined for the block.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function hasFocalPoint( $attributes ): bool {
|
||||
return is_array( $attributes['focalPoint'] ) && 2 === count( $attributes['focalPoint'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'defaultHeight', wc_get_theme_support( 'featured_block::default_height', 500 ), true );
|
||||
}
|
||||
}
|
||||
@@ -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,23 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* FilledCartBlock class.
|
||||
*/
|
||||
class FilterWrapper extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'filter-wrapper';
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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 ); // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
|
||||
}
|
||||
|
||||
/**
|
||||
* Set visibility query args. Handpicked products will show hidden products if chosen.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_visibility_query_args( &$query_args ) {
|
||||
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
|
||||
$product_visibility_terms = wc_get_product_visibility_term_ids();
|
||||
$query_args['tax_query'][] = array(
|
||||
'taxonomy' => 'product_visibility',
|
||||
'field' => 'term_taxonomy_id',
|
||||
'terms' => array( $product_visibility_terms['outofstock'] ),
|
||||
'operator' => 'NOT IN',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return array(
|
||||
'align' => $this->get_schema_align(),
|
||||
'alignButtons' => $this->get_schema_boolean( false ),
|
||||
'className' => $this->get_schema_string(),
|
||||
'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
|
||||
'orderby' => $this->get_schema_orderby(),
|
||||
'products' => $this->get_schema_list_ids(),
|
||||
'contentVisibility' => $this->get_schema_content_visibility(),
|
||||
'isPreview' => $this->get_schema_boolean( false ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,696 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
use Automattic\WooCommerce\Blocks\Utils\Utils;
|
||||
use Automattic\WooCommerce\Blocks\Utils\MiniCartUtils;
|
||||
|
||||
/**
|
||||
* Mini-Cart class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MiniCart extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart';
|
||||
|
||||
/**
|
||||
* Chunks build folder.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $chunks_folder = 'mini-cart-contents-block';
|
||||
|
||||
/**
|
||||
* Array of scripts that will be lazy loaded when interacting with the block.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $scripts_to_lazy_load = array();
|
||||
|
||||
/**
|
||||
* Inc Tax label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tax_label = '';
|
||||
|
||||
/**
|
||||
* Visibility of price including tax.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $display_cart_prices_including_tax = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetApi $asset_api Instance of the asset API.
|
||||
* @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
|
||||
* @param IntegrationRegistry $integration_registry Instance of the integration registry.
|
||||
*/
|
||||
public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry, IntegrationRegistry $integration_registry ) {
|
||||
parent::__construct( $asset_api, $asset_data_registry, $integration_registry, $this->block_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
add_action( 'wp_loaded', array( $this, 'register_empty_cart_message_block_pattern' ) );
|
||||
add_action( 'hooked_block_types', array( $this, 'register_auto_insert' ), 10, 4 );
|
||||
add_action( 'wp_print_footer_scripts', array( $this, 'print_lazy_load_scripts' ), 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string;
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
if ( is_cart() || is_checkout() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
if ( is_cart() || is_checkout() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$label_info = $this->get_tax_label();
|
||||
|
||||
$this->tax_label = $label_info['tax_label'];
|
||||
$this->display_cart_prices_including_tax = $label_info['display_cart_prices_including_tax'];
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'taxLabel',
|
||||
$this->tax_label,
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'displayCartPricesIncludingTax',
|
||||
$this->display_cart_prices_including_tax,
|
||||
true
|
||||
);
|
||||
|
||||
$template_part_edit_uri = '';
|
||||
|
||||
if (
|
||||
current_user_can( 'edit_theme_options' ) &&
|
||||
( wc_current_theme_is_fse_theme() || current_theme_supports( 'block-template-parts' ) )
|
||||
) {
|
||||
$theme_slug = BlockTemplateUtils::theme_has_template_part( 'mini-cart' ) ? wp_get_theme()->get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;
|
||||
|
||||
if ( version_compare( get_bloginfo( 'version' ), '5.9', '<' ) ) {
|
||||
$site_editor_uri = add_query_arg(
|
||||
array( 'page' => 'gutenberg-edit-site' ),
|
||||
admin_url( 'themes.php' )
|
||||
);
|
||||
} else {
|
||||
$site_editor_uri = add_query_arg(
|
||||
array(
|
||||
'canvas' => 'edit',
|
||||
'path' => '/template-parts/single',
|
||||
),
|
||||
admin_url( 'site-editor.php' )
|
||||
);
|
||||
}
|
||||
|
||||
$template_part_edit_uri = esc_url_raw(
|
||||
add_query_arg(
|
||||
array(
|
||||
'postId' => sprintf( '%s//%s', $theme_slug, 'mini-cart' ),
|
||||
'postType' => 'wp_template_part',
|
||||
),
|
||||
$site_editor_uri
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'templatePartEditUri',
|
||||
$template_part_edit_uri,
|
||||
''
|
||||
);
|
||||
|
||||
/**
|
||||
* Fires after cart block data is registered.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_cart_enqueue_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the variable containing information about the scripts to lazy load.
|
||||
*/
|
||||
public function print_lazy_load_scripts() {
|
||||
$script_data = $this->asset_api->get_script_data( 'assets/client/blocks/mini-cart-component-frontend.js' );
|
||||
|
||||
$num_dependencies = is_countable( $script_data['dependencies'] ) ? count( $script_data['dependencies'] ) : 0;
|
||||
$wp_scripts = wp_scripts();
|
||||
|
||||
for ( $i = 0; $i < $num_dependencies; $i++ ) {
|
||||
$dependency = $script_data['dependencies'][ $i ];
|
||||
|
||||
foreach ( $wp_scripts->registered as $script ) {
|
||||
if ( $script->handle === $dependency ) {
|
||||
$this->append_script_and_deps_src( $script );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$payment_method_registry = Package::container()->get( PaymentMethodRegistry::class );
|
||||
$payment_methods = $payment_method_registry->get_all_active_payment_method_script_dependencies();
|
||||
|
||||
foreach ( $payment_methods as $payment_method ) {
|
||||
$payment_method_script = $this->get_script_from_handle( $payment_method );
|
||||
|
||||
if ( ! is_null( $payment_method_script ) ) {
|
||||
$this->append_script_and_deps_src( $payment_method_script );
|
||||
}
|
||||
}
|
||||
|
||||
$this->scripts_to_lazy_load['wc-block-mini-cart-component-frontend'] = array(
|
||||
'src' => $script_data['src'],
|
||||
'version' => $script_data['version'],
|
||||
'translations' => $this->get_inner_blocks_translations(),
|
||||
);
|
||||
|
||||
$inner_blocks_frontend_scripts = array();
|
||||
$cart = $this->get_cart_instance();
|
||||
if ( $cart ) {
|
||||
// Preload inner blocks frontend scripts.
|
||||
$inner_blocks_frontend_scripts = $cart->is_empty() ? array(
|
||||
'empty-cart-frontend',
|
||||
'filled-cart-frontend',
|
||||
'shopping-button-frontend',
|
||||
) : array(
|
||||
'empty-cart-frontend',
|
||||
'filled-cart-frontend',
|
||||
'title-frontend',
|
||||
'items-frontend',
|
||||
'footer-frontend',
|
||||
'products-table-frontend',
|
||||
'cart-button-frontend',
|
||||
'checkout-button-frontend',
|
||||
'title-label-frontend',
|
||||
'title-items-counter-frontend',
|
||||
);
|
||||
}
|
||||
foreach ( $inner_blocks_frontend_scripts as $inner_block_frontend_script ) {
|
||||
$script_data = $this->asset_api->get_script_data( 'assets/client/blocks/mini-cart-contents-block/' . $inner_block_frontend_script . '.js' );
|
||||
$this->scripts_to_lazy_load[ 'wc-block-' . $inner_block_frontend_script ] = array(
|
||||
'src' => $script_data['src'],
|
||||
'version' => $script_data['version'],
|
||||
);
|
||||
}
|
||||
|
||||
$data = rawurlencode( wp_json_encode( $this->scripts_to_lazy_load ) );
|
||||
$mini_cart_dependencies_script = "var wcBlocksMiniCartFrontendDependencies = JSON.parse( decodeURIComponent( '" . esc_js( $data ) . "' ) );";
|
||||
|
||||
wp_add_inline_script(
|
||||
'wc-mini-cart-block-frontend',
|
||||
$mini_cart_dependencies_script,
|
||||
'before'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the script data given its handle.
|
||||
*
|
||||
* @param string $handle Handle of the script.
|
||||
*
|
||||
* @return \_WP_Dependency|null Object containing the script data if found, or null.
|
||||
*/
|
||||
protected function get_script_from_handle( $handle ) {
|
||||
$wp_scripts = wp_scripts();
|
||||
foreach ( $wp_scripts->registered as $script ) {
|
||||
if ( $script->handle === $handle ) {
|
||||
return $script;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively appends a scripts and its dependencies into the scripts_to_lazy_load array.
|
||||
*
|
||||
* @param \_WP_Dependency $script Object containing script data.
|
||||
*/
|
||||
protected function append_script_and_deps_src( $script ) {
|
||||
$wp_scripts = wp_scripts();
|
||||
|
||||
// This script and its dependencies have already been appended.
|
||||
if ( ! $script || array_key_exists( $script->handle, $this->scripts_to_lazy_load ) || wp_script_is( $script->handle, 'enqueued' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_countable( $script->deps ) && count( $script->deps ) ) {
|
||||
foreach ( $script->deps as $dep ) {
|
||||
if ( ! array_key_exists( $dep, $this->scripts_to_lazy_load ) ) {
|
||||
$dep_script = $this->get_script_from_handle( $dep );
|
||||
|
||||
if ( ! is_null( $dep_script ) ) {
|
||||
$this->append_script_and_deps_src( $dep_script );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( ! $script->src ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$site_url = site_url() ?? wp_guess_url();
|
||||
|
||||
if ( Utils::wp_version_compare( '6.3', '>=' ) ) {
|
||||
$script_before = $wp_scripts->get_inline_script_data( $script->handle, 'before' );
|
||||
$script_after = $wp_scripts->get_inline_script_data( $script->handle, 'after' );
|
||||
} else {
|
||||
$script_before = $wp_scripts->print_inline_script( $script->handle, 'before', false );
|
||||
$script_after = $wp_scripts->print_inline_script( $script->handle, 'after', false );
|
||||
}
|
||||
|
||||
$this->scripts_to_lazy_load[ $script->handle ] = array(
|
||||
'src' => preg_match( '|^(https?:)?//|', $script->src ) ? $script->src : $site_url . $script->src,
|
||||
'version' => $script->ver,
|
||||
'before' => $script_before,
|
||||
'after' => $script_after,
|
||||
'translations' => $wp_scripts->print_translations( $script->handle, false ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the markup for the cart price.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_cart_price_markup( $attributes ) {
|
||||
if ( isset( $attributes['hasHiddenPrice'] ) && false !== $attributes['hasHiddenPrice'] ) {
|
||||
return;
|
||||
}
|
||||
$price_color = array_key_exists( 'priceColor', $attributes ) ? $attributes['priceColor']['color'] : '';
|
||||
|
||||
return '<span class="wc-block-mini-cart__amount" style="color:' . esc_attr( $price_color ) . ' "></span>' . $this->get_include_tax_label_markup( $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the markup for render the tax label.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_include_tax_label_markup( $attributes ) {
|
||||
if ( empty( $this->tax_label ) ) {
|
||||
return '';
|
||||
}
|
||||
$price_color = array_key_exists( 'priceColor', $attributes ) ? $attributes['priceColor']['color'] : '';
|
||||
|
||||
return '<small class="wc-block-mini-cart__tax-label" style="color:' . esc_attr( $price_color ) . ' " hidden>' . esc_html( $this->tax_label ) . '</small>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Append frontend scripts when rendering the Mini-Cart block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
return $content . $this->get_markup( MiniCartUtils::migrate_attributes_to_color_panel( $attributes ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the markup for the Mini-Cart block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
*
|
||||
* @return string The HTML markup.
|
||||
*/
|
||||
protected function get_markup( $attributes ) {
|
||||
if ( is_admin() || WC()->is_rest_api_request() ) {
|
||||
// In the editor we will display the placeholder, so no need to load
|
||||
// real cart data and to print the markup.
|
||||
return '';
|
||||
}
|
||||
|
||||
$classes_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array( 'text_color', 'background_color', 'font_size', 'font_weight', 'font_family' ) );
|
||||
$wrapper_classes = sprintf( 'wc-block-mini-cart wp-block-woocommerce-mini-cart %s', $classes_styles['classes'] );
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$wrapper_classes .= ' ' . $attributes['className'];
|
||||
}
|
||||
$wrapper_styles = $classes_styles['styles'];
|
||||
|
||||
$icon_color = array_key_exists( 'iconColor', $attributes ) ? esc_attr( $attributes['iconColor']['color'] ) : 'currentColor';
|
||||
$product_count_color = array_key_exists( 'productCountColor', $attributes ) ? esc_attr( $attributes['productCountColor']['color'] ) : '';
|
||||
|
||||
// Default "Cart" icon.
|
||||
$icon = '<svg class="wc-block-mini-cart__icon" width="32" height="32" viewBox="0 0 32 32" fill="' . $icon_color . '" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12.6667" cy="24.6667" r="2" fill="' . $icon_color . '"/>
|
||||
<circle cx="23.3333" cy="24.6667" r="2" fill="' . $icon_color . '"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.28491 10.0356C9.47481 9.80216 9.75971 9.66667 10.0606 9.66667H25.3333C25.6232 9.66667 25.8989 9.79247 26.0888 10.0115C26.2787 10.2305 26.3643 10.5211 26.3233 10.8081L24.99 20.1414C24.9196 20.6341 24.4977 21 24 21H12C11.5261 21 11.1173 20.6674 11.0209 20.2034L9.08153 10.8701C9.02031 10.5755 9.09501 10.269 9.28491 10.0356ZM11.2898 11.6667L12.8136 19H23.1327L24.1803 11.6667H11.2898Z" fill="' . $icon_color . '"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.66669 6.66667C5.66669 6.11438 6.1144 5.66667 6.66669 5.66667H9.33335C9.81664 5.66667 10.2308 6.01229 10.3172 6.48778L11.0445 10.4878C11.1433 11.0312 10.7829 11.5517 10.2395 11.6505C9.69614 11.7493 9.17555 11.3889 9.07676 10.8456L8.49878 7.66667H6.66669C6.1144 7.66667 5.66669 7.21895 5.66669 6.66667Z" fill="' . $icon_color . '"/>
|
||||
</svg>';
|
||||
|
||||
if ( isset( $attributes['miniCartIcon'] ) ) {
|
||||
if ( 'bag' === $attributes['miniCartIcon'] ) {
|
||||
$icon = '<svg class="wc-block-mini-cart__icon" width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.4444 14.2222C12.9354 14.2222 13.3333 14.6202 13.3333 15.1111C13.3333 15.8183 13.6143 16.4966 14.1144 16.9967C14.6145 17.4968 15.2927 17.7778 16 17.7778C16.7072 17.7778 17.3855 17.4968 17.8856 16.9967C18.3857 16.4966 18.6667 15.8183 18.6667 15.1111C18.6667 14.6202 19.0646 14.2222 19.5555 14.2222C20.0465 14.2222 20.4444 14.6202 20.4444 15.1111C20.4444 16.2898 19.9762 17.4203 19.1427 18.2538C18.3092 19.0873 17.1787 19.5555 16 19.5555C14.8212 19.5555 13.6908 19.0873 12.8573 18.2538C12.0238 17.4203 11.5555 16.2898 11.5555 15.1111C11.5555 14.6202 11.9535 14.2222 12.4444 14.2222Z" fill="' . $icon_color . '""/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2408 6.68254C11.4307 6.46089 11.7081 6.33333 12 6.33333H20C20.2919 6.33333 20.5693 6.46089 20.7593 6.68254L24.7593 11.3492C25.0134 11.6457 25.0717 12.0631 24.9085 12.4179C24.7453 12.7727 24.3905 13 24 13H8.00001C7.60948 13 7.25469 12.7727 7.0915 12.4179C6.92832 12.0631 6.9866 11.6457 7.24076 11.3492L11.2408 6.68254ZM12.4599 8.33333L10.1742 11H21.8258L19.5401 8.33333H12.4599Z" fill="' . $icon_color . '"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 12C7 11.4477 7.44772 11 8 11H24C24.5523 11 25 11.4477 25 12V25.3333C25 25.8856 24.5523 26.3333 24 26.3333H8C7.44772 26.3333 7 25.8856 7 25.3333V12ZM9 13V24.3333H23V13H9Z" fill="' . $icon_color . '"/>
|
||||
</svg>';
|
||||
} elseif ( 'bag-alt' === $attributes['miniCartIcon'] ) {
|
||||
$icon = '<svg class="wc-block-mini-cart__icon" width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.5556 12.3333C19.0646 12.3333 18.6667 11.9354 18.6667 11.4444C18.6667 10.7372 18.3857 8.05893 17.8856 7.55883C17.3855 7.05873 16.7073 6.77778 16 6.77778C15.2928 6.77778 14.6145 7.05873 14.1144 7.55883C13.6143 8.05893 13.3333 10.7372 13.3333 11.4444C13.3333 11.9354 12.9354 12.3333 12.4445 12.3333C11.9535 12.3333 11.5556 11.9354 11.5556 11.4444C11.5556 10.2657 12.0238 7.13524 12.8573 6.30175C13.6908 5.46825 14.8213 5 16 5C17.1788 5 18.3092 5.46825 19.1427 6.30175C19.9762 7.13524 20.4445 10.2657 20.4445 11.4444C20.4445 11.9354 20.0465 12.3333 19.5556 12.3333Z" fill="' . $icon_color . '"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 12C7.5 11.4477 7.94772 11 8.5 11H23.5C24.0523 11 24.5 11.4477 24.5 12V25.3333C24.5 25.8856 24.0523 26.3333 23.5 26.3333H8.5C7.94772 26.3333 7.5 25.8856 7.5 25.3333V12ZM9.5 13V24.3333H22.5V13H9.5Z" fill="' . $icon_color . '" />
|
||||
</svg>';
|
||||
}
|
||||
}
|
||||
|
||||
$button_html = $this->get_cart_price_markup( $attributes ) . '
|
||||
<span class="wc-block-mini-cart__quantity-badge">
|
||||
' . $icon . '
|
||||
<span class="wc-block-mini-cart__badge" style="background:' . $product_count_color . '"></span>
|
||||
</span>';
|
||||
|
||||
if ( is_cart() || is_checkout() ) {
|
||||
if ( $this->should_not_render_mini_cart( $attributes ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// It is not necessary to load the Mini-Cart Block on Cart and Checkout page.
|
||||
return '<div class="' . esc_attr( $wrapper_classes ) . '" style="visibility:hidden" aria-hidden="true">
|
||||
<button class="wc-block-mini-cart__button" disabled>' . $button_html . '</button>
|
||||
</div>';
|
||||
}
|
||||
|
||||
$template_part_contents = '';
|
||||
|
||||
// Determine if we need to load the template part from the DB, the theme or WooCommerce in that order.
|
||||
$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'mini-cart' ), 'wp_template_part' );
|
||||
if ( is_countable( $templates_from_db ) && count( $templates_from_db ) > 0 ) {
|
||||
$template_slug_to_load = $templates_from_db[0]->theme;
|
||||
} else {
|
||||
$theme_has_mini_cart = BlockTemplateUtils::theme_has_template_part( 'mini-cart' );
|
||||
$template_slug_to_load = $theme_has_mini_cart ? get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;
|
||||
}
|
||||
$template_part = BlockTemplateUtils::get_block_template( $template_slug_to_load . '//mini-cart', 'wp_template_part' );
|
||||
|
||||
if ( $template_part && ! empty( $template_part->content ) ) {
|
||||
$template_part_contents = do_blocks( $template_part->content );
|
||||
}
|
||||
|
||||
if ( '' === $template_part_contents ) {
|
||||
$template_part_contents = do_blocks(
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
file_get_contents( Package::get_path() . 'templates/' . BlockTemplateUtils::DIRECTORY_NAMES['TEMPLATE_PARTS'] . '/mini-cart.html' )
|
||||
);
|
||||
}
|
||||
|
||||
return '<div class="' . esc_attr( $wrapper_classes ) . '" style="' . esc_attr( $wrapper_styles ) . '">
|
||||
<button class="wc-block-mini-cart__button">' . $button_html . '</button>
|
||||
<div class="is-loading wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true">
|
||||
<div class="wc-block-mini-cart__drawer wc-block-components-drawer">
|
||||
<div class="wc-block-components-drawer__content">
|
||||
<div class="wc-block-mini-cart__template-part">'
|
||||
. wp_kses_post( $template_part_contents ) .
|
||||
'</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the main instance of WC_Cart class.
|
||||
*
|
||||
* @return \WC_Cart CartController class instance.
|
||||
*/
|
||||
protected function get_cart_instance() {
|
||||
$cart = WC()->cart;
|
||||
|
||||
if ( $cart && $cart instanceof \WC_Cart ) {
|
||||
return $cart;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array with data for handle the tax label.
|
||||
* the entire logic of this function is was taken from:
|
||||
* https://github.com/woocommerce/woocommerce/blob/e730f7463c25b50258e97bf56e31e9d7d3bc7ae7/includes/class-wc-cart.php#L1582
|
||||
*
|
||||
* @return array;
|
||||
*/
|
||||
protected function get_tax_label() {
|
||||
$cart = $this->get_cart_instance();
|
||||
|
||||
if ( $cart && $cart->display_prices_including_tax() ) {
|
||||
if ( ! wc_prices_include_tax() ) {
|
||||
$tax_label = WC()->countries->inc_tax_or_vat();
|
||||
$display_cart_prices_including_tax = true;
|
||||
return array(
|
||||
'tax_label' => $tax_label,
|
||||
'display_cart_prices_including_tax' => $display_cart_prices_including_tax,
|
||||
);
|
||||
}
|
||||
return array(
|
||||
'tax_label' => '',
|
||||
'display_cart_prices_including_tax' => true,
|
||||
);
|
||||
}
|
||||
|
||||
if ( wc_prices_include_tax() ) {
|
||||
$tax_label = WC()->countries->ex_tax_or_vat();
|
||||
return array(
|
||||
'tax_label' => $tax_label,
|
||||
'display_cart_prices_including_tax' => false,
|
||||
);
|
||||
};
|
||||
|
||||
return array(
|
||||
'tax_label' => '',
|
||||
'display_cart_prices_including_tax' => false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare translations for inner blocks and dependencies.
|
||||
*/
|
||||
protected function get_inner_blocks_translations() {
|
||||
$wp_scripts = wp_scripts();
|
||||
$translations = array();
|
||||
|
||||
$chunks = $this->get_chunks_paths( $this->chunks_folder );
|
||||
$vendor_chunks = $this->get_chunks_paths( 'vendors--mini-cart-contents-block' );
|
||||
$shared_chunks = [ 'cart-blocks/cart-line-items--mini-cart-contents-block/products-table-frontend' ];
|
||||
|
||||
foreach ( array_merge( $chunks, $vendor_chunks, $shared_chunks ) as $chunk ) {
|
||||
$handle = 'wc-blocks-' . $chunk . '-chunk';
|
||||
$this->asset_api->register_script( $handle, $this->asset_api->get_block_asset_build_path( $chunk ), [], true );
|
||||
$translations[] = $wp_scripts->print_translations( $handle, false );
|
||||
wp_deregister_script( $handle );
|
||||
}
|
||||
|
||||
$translations = array_filter( $translations );
|
||||
|
||||
return implode( '', $translations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block pattern for Empty Cart Message to make it translatable.
|
||||
*/
|
||||
public function register_empty_cart_message_block_pattern() {
|
||||
register_block_pattern(
|
||||
'woocommerce/mini-cart-empty-cart-message',
|
||||
array(
|
||||
'title' => __( 'Empty Mini-Cart Message', 'woocommerce' ),
|
||||
'inserter' => false,
|
||||
'content' => '<!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center"><strong>' . __( 'Your cart is currently empty!', 'woocommerce' ) . '</strong></p><!-- /wp:paragraph -->',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for `hooked_block_types` to auto-inject the mini-cart block into headers after navigation.
|
||||
*
|
||||
* @param array $hooked_blocks An array of block slugs hooked into a given context.
|
||||
* @param string $position Position of the block insertion point.
|
||||
* @param string $anchor_block The block acting as the anchor for the inserted block.
|
||||
* @param \WP_Block_Template|array $context Where the block is embedded.
|
||||
* @since $VID:$
|
||||
* @return array An array of block slugs hooked into a given context.
|
||||
*/
|
||||
public function register_auto_insert( $hooked_blocks, $position, $anchor_block, $context ) {
|
||||
// Cache for active theme.
|
||||
static $active_theme_name = null;
|
||||
if ( is_null( $active_theme_name ) ) {
|
||||
$active_theme_name = wp_get_theme()->get( 'Name' );
|
||||
}
|
||||
/**
|
||||
* A list of pattern slugs to exclude from auto-insert (useful when
|
||||
* there are patterns that have a very specific location for the block)
|
||||
*
|
||||
* @since $VID:$
|
||||
*/
|
||||
$pattern_exclude_list = apply_filters( 'woocommerce_blocks_mini_cart_auto_insert_pattern_exclude_list', array( 'twentytwentytwo/header-centered-logo', 'twentytwentytwo/header-stacked' ) );
|
||||
|
||||
/**
|
||||
* A list of theme slugs to execute this with. This is a temporary
|
||||
* measure until improvements to the Block Hooks API allow for exposing
|
||||
* to all block themes.
|
||||
*
|
||||
* @since $VID:$
|
||||
*/
|
||||
$theme_include_list = apply_filters( 'woocommerce_blocks_mini_cart_auto_insert_theme_include_list', array( 'Twenty Twenty-Four', 'Twenty Twenty-Three', 'Twenty Twenty-Two', 'Tsubaki', 'Zaino', 'Thriving Artist', 'Amulet' ) );
|
||||
|
||||
if ( $context && in_array( $active_theme_name, $theme_include_list, true ) ) {
|
||||
if (
|
||||
'after' === $position &&
|
||||
'core/navigation' === $anchor_block &&
|
||||
$this->is_header_part_or_pattern( $context ) &&
|
||||
! $this->pattern_is_excluded( $context, $pattern_exclude_list ) &&
|
||||
! $this->has_mini_cart_block( $context )
|
||||
) {
|
||||
$hooked_blocks[] = 'woocommerce/' . $this->block_name;
|
||||
}
|
||||
}
|
||||
|
||||
return $hooked_blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the pattern is excluded or not
|
||||
*
|
||||
* @param array|\WP_Block_Template $context Where the block is embedded.
|
||||
* @param array $pattern_exclude_list List of pattern slugs to exclude.
|
||||
* @since $VID:$
|
||||
* @return boolean
|
||||
*/
|
||||
private function pattern_is_excluded( $context, $pattern_exclude_list ) {
|
||||
$pattern_slug = is_array( $context ) && isset( $context['slug'] ) ? $context['slug'] : '';
|
||||
if ( ! $pattern_slug ) {
|
||||
/**
|
||||
* Woo patterns have a slug property in $context, but core/theme patterns dont.
|
||||
* In that case, we fallback to the name property, as they're the same.
|
||||
*/
|
||||
$pattern_slug = is_array( $context ) && isset( $context['name'] ) ? $context['name'] : '';
|
||||
}
|
||||
return in_array( $pattern_slug, $pattern_exclude_list, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided context contains a mini-cart block.
|
||||
*
|
||||
* @param array|\WP_Block_Template $context Where the block is embedded.
|
||||
* @since $VID:$
|
||||
* @return boolean
|
||||
*/
|
||||
private function has_mini_cart_block( $context ) {
|
||||
/**
|
||||
* Note: this won't work for parsing WP_Block_Template instance until it's fixed in core
|
||||
* because $context->content is set as the result of `traverse_and_serialize_blocks` so
|
||||
* the filter callback doesn't get the original content.
|
||||
*
|
||||
* @see https://core.trac.wordpress.org/ticket/59882
|
||||
*/
|
||||
$content = is_array( $context ) && isset( $context['content'] ) ? $context['content'] : '';
|
||||
$content = '' === $content && $context instanceof \WP_Block_Template ? $context->content : $content;
|
||||
return strpos( $content, 'wp:woocommerce/mini-cart' ) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a provided context, returns whether the context refers to header content.
|
||||
*
|
||||
* @param array|\WP_Block_Template $context Where the block is embedded.
|
||||
* @since $VID:$
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_header_part_or_pattern( $context ) {
|
||||
$is_header_pattern = is_array( $context ) &&
|
||||
(
|
||||
( isset( $context['blockTypes'] ) && in_array( 'core/template-part/header', $context['blockTypes'], true ) ) ||
|
||||
( isset( $context['categories'] ) && in_array( 'header', $context['categories'], true ) )
|
||||
);
|
||||
$is_header_part = $context instanceof \WP_Block_Template && 'header' === $context->area;
|
||||
return ( $is_header_pattern || $is_header_part );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,169 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* Mini-Cart Contents class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MiniCartContents extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-contents';
|
||||
|
||||
/**
|
||||
* Get the editor script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
*
|
||||
* @return array|string;
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
// The frontend script is a dependency of the Mini-Cart block so it's
|
||||
// already lazy-loaded.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the markup for the Mini-Cart Contents block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
if ( is_admin() || WC()->is_rest_api_request() ) {
|
||||
// In the editor we will display the placeholder, so no need to
|
||||
// print the markup.
|
||||
return '';
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* @param string $content The block content.
|
||||
* @param WP_Block $block The block object.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes, $content, $block ) {
|
||||
parent::enqueue_assets( $attributes, $content, $block );
|
||||
$text_color = StyleAttributesUtils::get_text_color_class_and_style( $attributes );
|
||||
$bg_color = StyleAttributesUtils::get_background_color_class_and_style( $attributes );
|
||||
|
||||
$styles = array(
|
||||
array(
|
||||
'selector' => array(
|
||||
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout',
|
||||
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout:hover',
|
||||
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout:focus',
|
||||
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-cart.wc-block-components-button:hover',
|
||||
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-cart.wc-block-components-button:focus',
|
||||
'.wc-block-mini-cart__shopping-button a:hover',
|
||||
'.wc-block-mini-cart__shopping-button a:focus',
|
||||
),
|
||||
'properties' => array(
|
||||
array(
|
||||
'property' => 'color',
|
||||
'value' => $bg_color ? $bg_color['value'] : false,
|
||||
),
|
||||
array(
|
||||
'property' => 'border-color',
|
||||
'value' => $text_color ? $text_color['value'] : false,
|
||||
),
|
||||
array(
|
||||
'property' => 'background-color',
|
||||
'value' => $text_color ? $text_color['value'] : false,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$parsed_style = '';
|
||||
if ( array_key_exists( 'width', $attributes ) ) {
|
||||
$parsed_style .= ':root{--drawer-width: ' . esc_html( $attributes['width'] ) . '}';
|
||||
}
|
||||
|
||||
foreach ( $styles as $style ) {
|
||||
$selector = is_array( $style['selector'] ) ? implode( ',', $style['selector'] ) : $style['selector'];
|
||||
|
||||
$properties = array_filter(
|
||||
$style['properties'],
|
||||
function( $property ) {
|
||||
return $property['value'];
|
||||
}
|
||||
);
|
||||
|
||||
if ( ! empty( $properties ) ) {
|
||||
$parsed_style .= $selector . '{';
|
||||
foreach ( $properties as $property ) {
|
||||
$parsed_style .= sprintf( '%1$s:%2$s;', $property['property'], $property['value'] );
|
||||
}
|
||||
$parsed_style .= '}';
|
||||
}
|
||||
}
|
||||
|
||||
wp_add_inline_style(
|
||||
'wc-blocks-style',
|
||||
$parsed_style
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of Mini-Cart Contents block & its inner-block types.
|
||||
*
|
||||
* @return array;
|
||||
*/
|
||||
public static function get_mini_cart_block_types() {
|
||||
$block_types = [];
|
||||
|
||||
$block_types[] = 'MiniCartContents';
|
||||
$block_types[] = 'EmptyMiniCartContentsBlock';
|
||||
$block_types[] = 'FilledMiniCartContentsBlock';
|
||||
$block_types[] = 'MiniCartFooterBlock';
|
||||
$block_types[] = 'MiniCartItemsBlock';
|
||||
$block_types[] = 'MiniCartProductsTableBlock';
|
||||
$block_types[] = 'MiniCartShoppingButtonBlock';
|
||||
$block_types[] = 'MiniCartCartButtonBlock';
|
||||
$block_types[] = 'MiniCartCheckoutButtonBlock';
|
||||
$block_types[] = 'MiniCartTitleBlock';
|
||||
$block_types[] = 'MiniCartTitleItemsCounterBlock';
|
||||
$block_types[] = 'MiniCartTitleLabelBlock';
|
||||
|
||||
return $block_types;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartTitleItemsCounterBlock class.
|
||||
*/
|
||||
class MiniCartTitleItemsCounterBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-title-items-counter-block';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartTitleLabelBlock class.
|
||||
*/
|
||||
class MiniCartTitleLabelBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-title-label-block';
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* AbstractOrderConfirmationBlock class.
|
||||
*/
|
||||
abstract class AbstractOrderConfirmationBlock extends AbstractBlock {
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
add_action( 'wp_loaded', array( $this, 'register_patterns' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content from a hook and return it.
|
||||
*
|
||||
* @param string $hook Hook name.
|
||||
* @param array $args Array of args to pass to the hook.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_hook_content( $hook, $args ) {
|
||||
ob_start();
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
do_action_ref_array( $hook, $args );
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
$order = $this->get_order();
|
||||
$permission = $this->get_view_order_permissions( $order );
|
||||
$block_content = $order ? $this->render_content( $order, $permission, $attributes, $content ) : $this->render_content_fallback();
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
|
||||
if ( ! empty( $classes_and_styles['classes'] ) ) {
|
||||
$classname .= ' ' . $classes_and_styles['classes'];
|
||||
}
|
||||
|
||||
return $block_content ? sprintf(
|
||||
'<div class="wc-block-%4$s %1$s" style="%2$s">%3$s</div>',
|
||||
esc_attr( trim( $classname ) ),
|
||||
esc_attr( $classes_and_styles['styles'] ),
|
||||
$block_content,
|
||||
esc_attr( $this->block_name )
|
||||
) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* This renders the content of the block within the wrapper. The permission determines what data can be shown under
|
||||
* the given context.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function render_content( $order, $permission = false, $attributes = [], $content = '' );
|
||||
|
||||
/**
|
||||
* This is what gets rendered when the order does not exist. Renders nothing by default, but can be overridden by
|
||||
* child classes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function render_content_fallback() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current order.
|
||||
*
|
||||
* @return \WC_Order|null
|
||||
*/
|
||||
protected function get_order() {
|
||||
$order_id = absint( get_query_var( 'order-received' ) );
|
||||
|
||||
if ( $order_id ) {
|
||||
return wc_get_order( $order_id );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* View mode for order details based on the order, current user, and settings.
|
||||
*
|
||||
* @param \WC_Order|null $order Order object.
|
||||
* @return string|false Returns "full" if the user can view all order details. False if they can view no details.
|
||||
*/
|
||||
protected function get_view_order_permissions( $order ) {
|
||||
if ( ! $order || ! $this->has_valid_order_key( $order ) ) {
|
||||
return false; // Always disallow access to invalid orders and those without a valid key.
|
||||
}
|
||||
|
||||
// For customers with accounts, verify the order belongs to the current user or disallow access.
|
||||
if ( $this->is_customer_order( $order ) ) {
|
||||
return $this->is_current_customer_order( $order ) ? 'full' : false;
|
||||
}
|
||||
|
||||
// Guest orders are displayed only within the grace period or after verification. If email verification is required, return false.
|
||||
return $this->email_verification_required( $order ) ? false : 'full';
|
||||
}
|
||||
|
||||
/**
|
||||
* See if guest checkout is enabled.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function allow_guest_checkout() {
|
||||
return 'yes' === get_option( 'woocommerce_enable_guest_checkout' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Guest users without an active session can provide their email address to view order details. This however can only
|
||||
* be permitted if the user also provided the correct order key, and guest checkout is actually enabled.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function email_verification_permitted( $order ) {
|
||||
return $this->allow_guest_checkout() && $this->has_valid_order_key( $order ) && ! $this->is_customer_order( $order );
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the order was created within the grace period for viewing details.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_within_grace_period( $order ) {
|
||||
/**
|
||||
* Controls the grace period within which we do not require any sort of email verification step before rendering
|
||||
* the 'order received' or 'order pay' pages.
|
||||
*
|
||||
* @see \WC_Shortcode_Checkout::order_received()
|
||||
* @since 11.4.0
|
||||
* @param int $grace_period Time in seconds after an order is placed before email verification may be required.
|
||||
* @param \WC_Order $order The order for which this grace period is being assessed.
|
||||
* @param string $context Indicates the context in which we might verify the email address. Typically 'order-pay' or 'order-received'.
|
||||
*/
|
||||
$verification_grace_period = (int) apply_filters( 'woocommerce_order_email_verification_grace_period', 10 * MINUTE_IN_SECONDS, $order, 'order-received' );
|
||||
$date_created = $order->get_date_created();
|
||||
|
||||
return is_a( $date_created, \WC_DateTime::class ) && time() - $date_created->getTimestamp() <= $verification_grace_period;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the email has been verified (posted email matches given order email).
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_email_verified( $order ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
if ( empty( $_POST ) || ! isset( $_POST['email'] ) || ! wp_verify_nonce( $_POST['check_submission'] ?? '', 'wc_verify_email' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $order->get_billing_email() && sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ) === $order->get_billing_email();
|
||||
}
|
||||
|
||||
/**
|
||||
* See if we need to verify the email address before showing the order details.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function email_verification_required( $order ) {
|
||||
$session = wc()->session;
|
||||
|
||||
// Skip verification if the current user still has the order in their session.
|
||||
if ( is_a( $session, \WC_Session::class ) && $order->get_id() === (int) $session->get( 'store_api_draft_order' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip verification if the order was created within the grace period.
|
||||
if ( $this->is_within_grace_period( $order ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the user verified their email address, we can skip further verification.
|
||||
if ( $this->is_email_verified( $order ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an opportunity to override the (potential) requirement for shoppers to verify their email address
|
||||
* before we show information such as the order summary, or order payment page.
|
||||
*
|
||||
* @see \WC_Shortcode_Checkout::order_received()
|
||||
* @since 11.4.0
|
||||
* @param bool $email_verification_required If email verification is required.
|
||||
* @param WC_Order $order The relevant order.
|
||||
* @param string $context The context under which we are performing this check.
|
||||
*/
|
||||
return (bool) apply_filters( 'woocommerce_order_email_verification_required', true, $order, 'order-received' );
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the order key is valid.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function has_valid_order_key( $order ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return ! empty( $_GET['key'] ) && $order->key_is_valid( wc_clean( wp_unslash( $_GET['key'] ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the current order came from a guest or a logged in customer.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_customer_order( $order ) {
|
||||
return 0 < $order->get_user_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the current logged in user ID matches the given order customer ID.
|
||||
*
|
||||
* Returns false for logged-out customers.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_current_customer_order( $order ) {
|
||||
return $this->is_customer_order( $order ) && $order->get_user_id() === get_current_user_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block pattern for Order Confirmation to make it translatable.
|
||||
*/
|
||||
public function register_patterns() {
|
||||
|
||||
register_block_pattern(
|
||||
'woocommerce/order-confirmation-totals-heading',
|
||||
array(
|
||||
'title' => '',
|
||||
'inserter' => false,
|
||||
'content' => '<!-- wp:heading {"level":3,"style":{"typography":{"fontSize":"24px"}}} --><h3 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Order details', 'woocommerce' ) . '</h3><!-- /wp:heading -->',
|
||||
)
|
||||
);
|
||||
|
||||
register_block_pattern(
|
||||
'woocommerce/order-confirmation-downloads-heading',
|
||||
array(
|
||||
'title' => '',
|
||||
'inserter' => false,
|
||||
'content' => '<!-- wp:heading {"level":3,"style":{"typography":{"fontSize":"24px"}}} --><h3 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Downloads', 'woocommerce' ) . '</h3><!-- /wp:heading -->',
|
||||
)
|
||||
);
|
||||
|
||||
register_block_pattern(
|
||||
'woocommerce/order-confirmation-shipping-heading',
|
||||
array(
|
||||
'title' => '',
|
||||
'inserter' => false,
|
||||
'content' => '<!-- wp:heading {"level":3,"style":{"typography":{"fontSize":"24px"}}} --><h3 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Shipping address', 'woocommerce' ) . '</h3><!-- /wp:heading -->',
|
||||
)
|
||||
);
|
||||
|
||||
register_block_pattern(
|
||||
'woocommerce/order-confirmation-billing-heading',
|
||||
array(
|
||||
'title' => '',
|
||||
'inserter' => false,
|
||||
'content' => '<!-- wp:heading {"level":3,"style":{"typography":{"fontSize":"24px"}}} --><h3 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Billing address', 'woocommerce' ) . '</h3><!-- /wp:heading -->',
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
/**
|
||||
* AdditionalInformation class.
|
||||
*/
|
||||
class AdditionalInformation extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-additional-information';
|
||||
|
||||
/**
|
||||
* This renders the content of the block within the wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
if ( ! $permission ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$this->remove_core_hooks();
|
||||
$content .= $this->get_hook_content( 'woocommerce_thankyou_' . $order->get_payment_method(), [ $order->get_id() ] );
|
||||
$content .= $this->get_hook_content( 'woocommerce_thankyou', [ $order->get_id() ] );
|
||||
$this->restore_core_hooks();
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove core hooks from the thankyou page.
|
||||
*/
|
||||
protected function remove_core_hooks() {
|
||||
remove_action( 'woocommerce_thankyou', 'woocommerce_order_details_table', 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore core hooks from the thankyou page.
|
||||
*/
|
||||
protected function restore_core_hooks() {
|
||||
add_action( 'woocommerce_thankyou', 'woocommerce_order_details_table', 10 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
/**
|
||||
* BillingAddress class.
|
||||
*/
|
||||
class BillingAddress extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-billing-address';
|
||||
|
||||
/**
|
||||
* This renders the content of the block within the wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
if ( ! $permission || ! $order->has_billing_address() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$address = '<address>' . wp_kses_post( $order->get_formatted_billing_address() ) . '</address>';
|
||||
$phone = $order->get_billing_phone() ? '<p class="woocommerce-customer-details--phone">' . esc_html( $order->get_billing_phone() ) . '</p>' : '';
|
||||
|
||||
return $address . $phone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
/**
|
||||
* BillingWrapper class.
|
||||
*/
|
||||
class BillingWrapper extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-billing-wrapper';
|
||||
|
||||
/**
|
||||
* This renders the content of the billing wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
if ( ! $order || ! $order->has_billing_address() || ! $permission ) {
|
||||
return '';
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* Downloads class.
|
||||
*/
|
||||
class Downloads extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-downloads';
|
||||
|
||||
/**
|
||||
* This renders the content of the block within the wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
$show_downloads = $order && $order->has_downloadable_item() && $order->is_download_permitted();
|
||||
$downloads = $order ? $order->get_downloadable_items() : [];
|
||||
|
||||
if ( ! $permission || ! $show_downloads ) {
|
||||
return $this->render_content_fallback();
|
||||
}
|
||||
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, [ 'border_color', 'border_radius', 'border_width', 'border_style', 'background_color', 'text_color' ] );
|
||||
|
||||
return '
|
||||
<table cellspacing="0" class="wc-block-order-confirmation-downloads__table ' . esc_attr( $classes_and_styles['classes'] ) . '" style="' . esc_attr( $classes_and_styles['styles'] ) . '">
|
||||
<thead>
|
||||
<tr>
|
||||
' . $this->render_order_downloads_column_headers( $order ) . '
|
||||
</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
' . $this->render_order_downloads( $order, $downloads ) . '
|
||||
</tbody>
|
||||
</table>
|
||||
';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_inline_styles( array $attributes ) {
|
||||
$link_classes_and_styles = StyleAttributesUtils::get_link_color_class_and_style( $attributes );
|
||||
$link_hover_classes_and_styles = StyleAttributesUtils::get_link_hover_color_class_and_style( $attributes );
|
||||
$border_classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, [ 'border_color', 'border_radius', 'border_width', 'border_style' ] );
|
||||
|
||||
return '
|
||||
.wc-block-order-confirmation-downloads__table a {' . $link_classes_and_styles['style'] . '}
|
||||
.wc-block-order-confirmation-downloads__table a:hover, .wc-block-order-confirmation-downloads__table a:focus {' . $link_hover_classes_and_styles['style'] . '}
|
||||
.wc-block-order-confirmation-downloads__table {' . $border_classes_and_styles['styles'] . '}
|
||||
.wc-block-order-confirmation-downloads__table th, .wc-block-order-confirmation-downloads__table td {' . $border_classes_and_styles['styles'] . '}
|
||||
';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* @param string $content The block content.
|
||||
* @param \WP_Block $block The block object.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes, $content, $block ) {
|
||||
parent::enqueue_assets( $attributes, $content, $block );
|
||||
|
||||
$styles = $this->get_inline_styles( $attributes );
|
||||
|
||||
wp_add_inline_style( 'wc-blocks-style', $styles );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column headers for downloads table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function render_order_downloads_column_headers() {
|
||||
$columns = wc_get_account_downloads_columns();
|
||||
$return = '';
|
||||
|
||||
foreach ( $columns as $column_id => $column_name ) {
|
||||
$return .= '<th class="' . esc_attr( $column_id ) . '"><span class="nobr">' . esc_html( $column_name ) . '</span></th>';
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render downloads.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param array $downloads Array of downloads.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_order_downloads( $order, $downloads ) {
|
||||
$return = '';
|
||||
foreach ( $downloads as $download ) {
|
||||
$return .= '<tr>' . $this->render_order_download_row( $download ) . '</tr>';
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a download row in the table.
|
||||
*
|
||||
* @param array $download Download data.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_order_download_row( $download ) {
|
||||
$return = '';
|
||||
|
||||
foreach ( wc_get_account_downloads_columns() as $column_id => $column_name ) {
|
||||
$return .= '<td class="' . esc_attr( $column_id ) . '" data-title="' . esc_attr( $column_name ) . '">';
|
||||
|
||||
if ( has_action( 'woocommerce_account_downloads_column_' . $column_id ) ) {
|
||||
$return .= $this->get_hook_content( 'woocommerce_account_downloads_column_' . $column_id, [ $download ] );
|
||||
} else {
|
||||
switch ( $column_id ) {
|
||||
case 'download-product':
|
||||
if ( $download['product_url'] ) {
|
||||
$return .= '<a href="' . esc_url( $download['product_url'] ) . '">' . esc_html( $download['product_name'] ) . '</a>';
|
||||
} else {
|
||||
$return .= esc_html( $download['product_name'] );
|
||||
}
|
||||
break;
|
||||
case 'download-file':
|
||||
$return .= '<a href="' . esc_url( $download['download_url'] ) . '" class="woocommerce-MyAccount-downloads-file button alt">' . esc_html( $download['download_name'] ) . '</a>';
|
||||
break;
|
||||
case 'download-remaining':
|
||||
$return .= is_numeric( $download['downloads_remaining'] ) ? esc_html( $download['downloads_remaining'] ) : esc_html__( '∞', 'woocommerce' );
|
||||
break;
|
||||
case 'download-expires':
|
||||
if ( ! empty( $download['access_expires'] ) ) {
|
||||
$return .= '<time datetime="' . esc_attr( gmdate( 'Y-m-d', strtotime( $download['access_expires'] ) ) ) . '" title="' . esc_attr( strtotime( $download['access_expires'] ) ) . '">' . esc_html( date_i18n( get_option( 'date_format' ), strtotime( $download['access_expires'] ) ) ) . '</time>';
|
||||
} else {
|
||||
$return .= esc_html__( 'Never', 'woocommerce' );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$return .= '</td>';
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
/**
|
||||
* DownloadsWrapper class.
|
||||
*/
|
||||
class DownloadsWrapper extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-downloads-wrapper';
|
||||
|
||||
/**
|
||||
* See if the store has a downloadable product. This controls if we bother to show a preview in the editor.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function store_has_downloadable_products() {
|
||||
$has_downloadable_product = get_transient( 'wc_blocks_has_downloadable_product', false );
|
||||
|
||||
if ( false === $has_downloadable_product ) {
|
||||
$product_ids = get_posts(
|
||||
array(
|
||||
'post_type' => 'product',
|
||||
'numberposts' => 1,
|
||||
'post_status' => 'publish',
|
||||
'fields' => 'ids',
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_downloadable',
|
||||
'value' => 'yes',
|
||||
'compare' => '=',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$has_downloadable_product = ! empty( $product_ids );
|
||||
set_transient( 'wc_blocks_has_downloadable_product', $has_downloadable_product ? '1' : '0', MONTH_IN_SECONDS );
|
||||
}
|
||||
|
||||
return (bool) $has_downloadable_product;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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( 'storeHasDownloadableProducts', $this->store_has_downloadable_products() );
|
||||
}
|
||||
|
||||
/**
|
||||
* This renders the content of the downloads wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
$show_downloads = $order && $order->has_downloadable_item() && $order->is_download_permitted();
|
||||
|
||||
if ( ! $show_downloads || ! $permission ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
/**
|
||||
* ShippingAddress class.
|
||||
*/
|
||||
class ShippingAddress extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-shipping-address';
|
||||
|
||||
/**
|
||||
* This renders the content of the block within the wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
if ( ! $permission || ! $order->needs_shipping_address() || ! $order->has_shipping_address() ) {
|
||||
return $this->render_content_fallback();
|
||||
}
|
||||
|
||||
$address = '<address>' . wp_kses_post( $order->get_formatted_shipping_address() ) . '</address>';
|
||||
$phone = $order->get_shipping_phone() ? '<p class="woocommerce-customer-details--phone">' . esc_html( $order->get_shipping_phone() ) . '</p>' : '';
|
||||
|
||||
return $address . $phone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
/**
|
||||
* ShippingWrapper class.
|
||||
*/
|
||||
class ShippingWrapper extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-shipping-wrapper';
|
||||
|
||||
/**
|
||||
* This renders the content of the shipping wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
if ( ! $order || ! $order->has_shipping_address() || ! $order->needs_shipping_address() || ! $permission ) {
|
||||
return '';
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* Status class.
|
||||
*/
|
||||
class Status extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-status';
|
||||
|
||||
/**
|
||||
* This block uses a custom render method so that the email verification form can be appended to the block. This does
|
||||
* not inherit styles from the parent 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 ) {
|
||||
$order = $this->get_order();
|
||||
$classname = $attributes['className'] ?? '';
|
||||
|
||||
if ( isset( $attributes['align'] ) ) {
|
||||
$classname .= " align{$attributes['align']}";
|
||||
}
|
||||
|
||||
$block = parent::render( $attributes, $content, $block );
|
||||
|
||||
if ( ! $block ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$additional_content = $this->render_confirmation_notice( $order );
|
||||
|
||||
return $additional_content ? $block . sprintf(
|
||||
'<div class="wc-block-order-confirmation-status-description %1$s">%2$s</div>',
|
||||
esc_attr( trim( $classname ) ),
|
||||
$additional_content
|
||||
) : $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* This renders the content of the block within the wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
if ( ! $permission ) {
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
return '<p>' . wp_kses_post( apply_filters( 'woocommerce_thankyou_order_received_text', esc_html__( 'Thank you. Your order has been received.', 'woocommerce' ), null ) ) . '</p>';
|
||||
}
|
||||
|
||||
$content = $this->get_hook_content( 'woocommerce_before_thankyou', [ $order->get_id() ] );
|
||||
$status = $order->get_status();
|
||||
|
||||
// Unlike the core handling, this includes some extra messaging for completed orders so the text is appropriate for other order statuses.
|
||||
switch ( $status ) {
|
||||
case 'cancelled':
|
||||
$content .= '<p>' . wp_kses_post(
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
apply_filters(
|
||||
'woocommerce_thankyou_order_received_text',
|
||||
esc_html__( 'Your order has been cancelled.', 'woocommerce' ),
|
||||
$order
|
||||
)
|
||||
) . '</p>';
|
||||
break;
|
||||
case 'refunded':
|
||||
$content .= '<p>' . wp_kses_post(
|
||||
sprintf(
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
apply_filters(
|
||||
'woocommerce_thankyou_order_received_text',
|
||||
// translators: %s: date and time of the order refund.
|
||||
esc_html__( 'Your order was refunded %s.', 'woocommerce' ),
|
||||
$order
|
||||
),
|
||||
wc_format_datetime( $order->get_date_modified() )
|
||||
)
|
||||
) . '</p>';
|
||||
break;
|
||||
case 'completed':
|
||||
$content .= '<p>' . wp_kses_post(
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
apply_filters(
|
||||
'woocommerce_thankyou_order_received_text',
|
||||
esc_html__( 'Thank you. Your order has been fulfilled.', 'woocommerce' ),
|
||||
$order
|
||||
)
|
||||
) . '</p>';
|
||||
break;
|
||||
case 'failed':
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
$order_received_text = apply_filters( 'woocommerce_thankyou_order_received_text', esc_html__( 'Your order cannot be processed as the originating bank/merchant has declined your transaction. Please attempt your purchase again.', 'woocommerce' ), null );
|
||||
$actions = '<a href="' . esc_url( $order->get_checkout_payment_url() ) . '" class="button">' . esc_html__( 'Try again', 'woocommerce' ) . '</a> ';
|
||||
|
||||
if ( wc_get_page_permalink( 'myaccount' ) ) {
|
||||
$actions .= '<a href="' . esc_url( wc_get_page_permalink( 'myaccount' ) ) . '" class="button">' . esc_html__( 'My account', 'woocommerce' ) . '</a> ';
|
||||
}
|
||||
|
||||
$content .= '
|
||||
<p>' . $order_received_text . '</p>
|
||||
<p class="wc-block-order-confirmation-status__actions">' . $actions . '</p>
|
||||
';
|
||||
break;
|
||||
default:
|
||||
$content .= '<p>' . wp_kses_post(
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
apply_filters(
|
||||
'woocommerce_thankyou_order_received_text',
|
||||
esc_html__( 'Thank you. Your order has been received.', 'woocommerce' ),
|
||||
$order
|
||||
)
|
||||
) . '</p>';
|
||||
break;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is what gets rendered when the order does not exist.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function render_content_fallback() {
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
return '<p>' . esc_html__( 'Please check your email for the order confirmation.', 'woocommerce' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* If the order is invalid or there is no permission to view the details, tell the user to check email or log-in.
|
||||
*
|
||||
* @param \WC_Order|null $order Order object.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_confirmation_notice( $order = null ) {
|
||||
if ( ! $order ) {
|
||||
$content = '<p>' . esc_html__( 'If you\'ve just placed an order, give your email a quick check for the confirmation.', 'woocommerce' );
|
||||
|
||||
if ( wc_get_page_permalink( 'myaccount' ) ) {
|
||||
$content .= ' ' . sprintf(
|
||||
/* translators: 1: opening a link tag 2: closing a link tag */
|
||||
esc_html__( 'Have an account with us? %1$sLog in here to view your order details%2$s.', 'woocommerce' ),
|
||||
'<a href="' . esc_url( wc_get_page_permalink( 'myaccount' ) ) . '" class="button">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
$content .= '</p>';
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
$permission = $this->get_view_order_permissions( $order );
|
||||
|
||||
if ( $permission ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$verification_required = $this->email_verification_required( $order );
|
||||
$verification_permitted = $this->email_verification_permitted( $order );
|
||||
$my_account_page = wc_get_page_permalink( 'myaccount' );
|
||||
|
||||
$content = '<p>';
|
||||
$content .= esc_html__( 'Great news! Your order has been received, and a confirmation will be sent to your email address.', 'woocommerce' );
|
||||
$content .= $my_account_page ? ' ' . sprintf(
|
||||
/* translators: 1: opening a link tag 2: closing a link tag */
|
||||
esc_html__( 'Have an account with us? %1$sLog in here%2$s to view your order.', 'woocommerce' ),
|
||||
'<a href="' . esc_url( $my_account_page ) . '" class="button">',
|
||||
'</a>'
|
||||
) : '';
|
||||
|
||||
if ( $verification_required && $verification_permitted ) {
|
||||
$content .= ' ' . esc_html__( 'Alternatively, confirm the email address linked to the order below.', 'woocommerce' );
|
||||
}
|
||||
|
||||
$content .= '</p>';
|
||||
|
||||
if ( $verification_required && $verification_permitted ) {
|
||||
$content .= $this->render_verification_form();
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Email verification for guest users.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function render_verification_form() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$check_submission_notice = ! empty( $_POST ) ? wc_print_notice( esc_html__( 'We were unable to verify the email address you provided. Please try again.', 'woocommerce' ), 'error', [], true ) : '';
|
||||
|
||||
return '<form method="post" class="woocommerce-form woocommerce-verify-email">' .
|
||||
$check_submission_notice .
|
||||
sprintf(
|
||||
'<p class="form-row verify-email">
|
||||
<label for="%1$s">%2$s</label>
|
||||
<input type="email" name="email" id="%1$s" autocomplete="email" class="input-text" required />
|
||||
</p>',
|
||||
esc_attr( 'verify-email' ),
|
||||
esc_html__( 'Email address', 'woocommerce' ) . ' <span class="required">*</span>'
|
||||
) .
|
||||
sprintf(
|
||||
'<p class="form-row login-submit">
|
||||
<input type="submit" name="wp-submit" id="%1$s" class="button button-primary %4$s" value="%2$s" />
|
||||
%3$s
|
||||
</p>',
|
||||
esc_attr( 'verify-email-submit' ),
|
||||
esc_html__( 'Confirm email and view order', 'woocommerce' ),
|
||||
wp_nonce_field( 'wc_verify_email', 'check_submission', true, false ),
|
||||
esc_attr( wc_wp_theme_get_element_class_name( 'button' ) )
|
||||
) .
|
||||
'</form>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
/**
|
||||
* Summary class.
|
||||
*/
|
||||
class Summary extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-summary';
|
||||
|
||||
/**
|
||||
* This renders the content of the block within the wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
if ( ! $permission ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = '<ul class="wc-block-order-confirmation-summary-list">';
|
||||
$content .= $this->render_summary_row( __( 'Order number:', 'woocommerce' ), $order->get_order_number() );
|
||||
$content .= $this->render_summary_row( __( 'Date:', 'woocommerce' ), wc_format_datetime( $order->get_date_created() ) );
|
||||
$content .= $this->render_summary_row( __( 'Total:', 'woocommerce' ), $order->get_formatted_order_total() );
|
||||
$content .= $this->render_summary_row( __( 'Email:', 'woocommerce' ), $order->get_billing_email() );
|
||||
$content .= $this->render_summary_row( __( 'Payment method:', 'woocommerce' ), $order->get_payment_method_title() );
|
||||
$content .= '</ul>';
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render row in the order summary.
|
||||
*
|
||||
* @param string $name name of row.
|
||||
* @param string $value value of row.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_summary_row( $name, $value ) {
|
||||
return $value ? '<li class="wc-block-order-confirmation-summary-list-item"><span class="wc-block-order-confirmation-summary-list-item__key">' . esc_html( $name ) . '</span> <span class="wc-block-order-confirmation-summary-list-item__value">' . wp_kses_post( $value ) . '</span></li>' : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* Totals class.
|
||||
*/
|
||||
class Totals extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-totals';
|
||||
|
||||
/**
|
||||
* This renders the content of the block within the wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
if ( ! $permission ) {
|
||||
return $this->render_content_fallback();
|
||||
}
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, [ 'border_color', 'border_radius', 'border_width', 'border_style', 'background_color', 'text_color' ] );
|
||||
|
||||
return $this->get_hook_content( 'woocommerce_order_details_before_order_table', [ $order ] ) . '
|
||||
<table cellspacing="0" class="wc-block-order-confirmation-totals__table ' . esc_attr( $classes_and_styles['classes'] ) . '" style="' . esc_attr( $classes_and_styles['styles'] ) . '">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="wc-block-order-confirmation-totals__product">' . esc_html__( 'Product', 'woocommerce' ) . '</th>
|
||||
<th class="wc-block-order-confirmation-totals__total">' . esc_html__( 'Total', 'woocommerce' ) . '</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
' . $this->get_hook_content( 'woocommerce_order_details_before_order_table_items', [ $order ] ) . '
|
||||
' . $this->render_order_details_table_items( $order ) . '
|
||||
' . $this->get_hook_content( 'woocommerce_order_details_after_order_table_items', [ $order ] ) . '
|
||||
</tbody>
|
||||
<tfoot>
|
||||
' . $this->render_order_details_table_totals( $order ) . '
|
||||
</tfoot>
|
||||
</table>
|
||||
' . $this->render_order_details_customer_note( $order ) . '
|
||||
' . $this->get_hook_content( 'woocommerce_order_details_after_order_table', [ $order ] ) . '
|
||||
' . $this->get_hook_content( 'woocommerce_after_order_details', [ $order ] ) . '
|
||||
';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_inline_styles( array $attributes ) {
|
||||
$link_classes_and_styles = StyleAttributesUtils::get_link_color_class_and_style( $attributes );
|
||||
$link_hover_classes_and_styles = StyleAttributesUtils::get_link_hover_color_class_and_style( $attributes );
|
||||
$border_classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, [ 'border_color', 'border_radius', 'border_width', 'border_style' ] );
|
||||
|
||||
return '
|
||||
.wc-block-order-confirmation-totals__table a {' . $link_classes_and_styles['style'] . '}
|
||||
.wc-block-order-confirmation-totals__table a:hover, .wc-block-order-confirmation-totals__table a:focus {' . $link_hover_classes_and_styles['style'] . '}
|
||||
.wc-block-order-confirmation-totals__table {' . $border_classes_and_styles['styles'] . '}
|
||||
.wc-block-order-confirmation-totals__table th, .wc-block-order-confirmation-totals__table td {' . $border_classes_and_styles['styles'] . '}
|
||||
';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* @param string $content The block content.
|
||||
* @param \WP_Block $block The block object.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes, $content, $block ) {
|
||||
parent::enqueue_assets( $attributes, $content, $block );
|
||||
|
||||
$styles = $this->get_inline_styles( $attributes );
|
||||
|
||||
wp_add_inline_style( 'wc-blocks-style', $styles );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render order details table items.
|
||||
*
|
||||
* Loosely based on the templates order-details.php and order-details-item.php from core.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_order_details_table_items( $order ) {
|
||||
$return = '';
|
||||
$order_items = array_filter(
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
$order->get_items( apply_filters( 'woocommerce_purchase_order_item_types', 'line_item' ) ),
|
||||
function( $item ) {
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
return apply_filters( 'woocommerce_order_item_visible', true, $item );
|
||||
}
|
||||
);
|
||||
|
||||
foreach ( $order_items as $item_id => $item ) {
|
||||
$product = $item->get_product();
|
||||
$return .= $this->render_order_details_table_item( $order, $item_id, $item, $product );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an item in the order details table.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param integer $item_id Item ID.
|
||||
* @param \WC_Order_Item $item Item object.
|
||||
* @param \WC_Product|false $product Product object if it exists.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_order_details_table_item( $order, $item_id, $item, $product ) {
|
||||
$is_visible = $product && $product->is_visible();
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
$row_class = apply_filters( 'woocommerce_order_item_class', 'woocommerce-table__line-item order_item', $item, $order );
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
$product_permalink = apply_filters( 'woocommerce_order_item_permalink', $is_visible ? $product->get_permalink( $item ) : '', $item, $order );
|
||||
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
$item_name = apply_filters(
|
||||
'woocommerce_order_item_name',
|
||||
$product_permalink ? sprintf( '<a href="%s">%s</a>', $product_permalink, $item->get_name() ) : $item->get_name(),
|
||||
$item,
|
||||
$is_visible
|
||||
);
|
||||
$qty = $item->get_quantity();
|
||||
$refunded_qty = $order->get_qty_refunded_for_item( $item_id );
|
||||
$qty_display = $refunded_qty ? '<del>' . esc_html( $qty ) . '</del> <ins>' . esc_html( $qty - ( $refunded_qty * -1 ) ) . '</ins>' : esc_html( $qty );
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
$item_qty = apply_filters(
|
||||
'woocommerce_order_item_quantity_html',
|
||||
'<strong class="product-quantity">' . sprintf( '× %s', $qty_display ) . '</strong>',
|
||||
$item
|
||||
);
|
||||
|
||||
return '
|
||||
<tr class="' . esc_attr( $row_class ) . '">
|
||||
<td class="wc-block-order-confirmation-totals__product">
|
||||
' . wp_kses_post( $item_name ) . '
|
||||
' . wp_kses_post( $item_qty ) . '
|
||||
' . $this->get_hook_content( 'woocommerce_order_item_meta_start', [ $item_id, $item, $order, false ] ) . '
|
||||
' . wc_display_item_meta( $item, [ 'echo' => false ] ) . '
|
||||
' . $this->get_hook_content( 'woocommerce_order_item_meta_end', [ $item_id, $item, $order, false ] ) . '
|
||||
' . $this->render_order_details_table_item_purchase_note( $order, $product ) . '
|
||||
</td>
|
||||
<td class="wc-block-order-confirmation-totals__total">
|
||||
' . wp_kses_post( $order->get_formatted_line_subtotal( $item ) ) . '
|
||||
</td>
|
||||
</tr>
|
||||
';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an item purchase note.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param \WC_Product|false $product Product object if it exists.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_order_details_table_item_purchase_note( $order, $product ) {
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
$show_purchase_note = $order->has_status( apply_filters( 'woocommerce_purchase_note_order_statuses', array( 'completed', 'processing' ) ) );
|
||||
$purchase_note = $product ? $product->get_purchase_note() : '';
|
||||
|
||||
return $show_purchase_note && $purchase_note ? '<div class="product-purchase-note">' . wp_kses_post( $purchase_note ) . '</div>' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render order details table totals.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_order_details_table_totals( $order ) {
|
||||
add_filter( 'woocommerce_order_shipping_to_display_shipped_via', '__return_empty_string' );
|
||||
|
||||
$return = '';
|
||||
$total_rows = array_diff_key(
|
||||
$order->get_order_item_totals(),
|
||||
array(
|
||||
'cart_subtotal' => '',
|
||||
'payment_method' => '',
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $total_rows as $total ) {
|
||||
$return .= '
|
||||
<tr>
|
||||
<th class="wc-block-order-confirmation-totals__label" scope="row">' . esc_html( $total['label'] ) . '</th>
|
||||
<td class="wc-block-order-confirmation-totals__total">' . wp_kses_post( $total['value'] ) . '</td>
|
||||
</tr>
|
||||
';
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render customer note.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_order_details_customer_note( $order ) {
|
||||
if ( ! $order->get_customer_note() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<div class="wc-block-order-confirmation-order-note">' .
|
||||
'<p class="wc-block-order-confirmation-order-note__label">' .
|
||||
esc_html__( 'Note:', 'woocommerce' ) .
|
||||
'</p>' .
|
||||
'<p>' . wp_kses_post( nl2br( wptexturize( $order->get_customer_note() ) ) ) . '</p>' .
|
||||
'</div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
/**
|
||||
* TotalsWrapper class.
|
||||
*/
|
||||
class TotalsWrapper extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-totals-wrapper';
|
||||
|
||||
/**
|
||||
* This renders the content of the totals wrapper.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string|false $permission If the current user can view the order details or not.
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Original block content.
|
||||
*/
|
||||
protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
|
||||
if ( ! $permission ) {
|
||||
return '';
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* Used in templates to wrap page content. Allows content to be populated at template level.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PageContentWrapper extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'page-content-wrapper';
|
||||
|
||||
/**
|
||||
* It isn't necessary to register block assets.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string|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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user