Remove all plugins / install base theme
This commit is contained in:
@@ -1,117 +0,0 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
<?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';
|
||||
const MODEL = 'gpt-3.5-turbo-1106';
|
||||
|
||||
/**
|
||||
* 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,
|
||||
'model' => self::MODEL,
|
||||
);
|
||||
|
||||
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,
|
||||
'model' => self::MODEL,
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\AIContent;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use Automattic\WooCommerce\Blocks\Images\Pexels;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* ContentProcessor class.
|
||||
*
|
||||
* Process images for content
|
||||
*/
|
||||
class ContentProcessor {
|
||||
|
||||
/**
|
||||
* Summarize the business description to ensure better results are returned by AI.
|
||||
*
|
||||
* @param string $business_description The business description.
|
||||
* @param Connection $ai_connection The AI connection.
|
||||
* @param string $token The JWT token.
|
||||
* @param integer $character_limit The character limit for the business description.
|
||||
*
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
public static function summarize_business_description( $business_description, $ai_connection, $token, $character_limit = 150 ) {
|
||||
if ( empty( $business_description ) ) {
|
||||
return new WP_Error( 'business_description_not_found', __( 'No business description provided for generating AI content.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( strlen( $business_description ) > $character_limit ) {
|
||||
$prompt = sprintf( 'You are a professional writer. Read the following business description and write a text with less than %s characters to summarize the products the business is selling: "%s". Make sure you do not add double quotes in your response. Do not add any explanations in the response', $character_limit, $business_description );
|
||||
|
||||
$response = $ai_connection->fetch_ai_response( $token, $prompt, 30 );
|
||||
|
||||
$business_description = $response['completion'] ?? $business_description;
|
||||
}
|
||||
|
||||
return $business_description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that images are provided for assignment to products and patterns.
|
||||
*
|
||||
* @param array|WP_Error $images The array of images.
|
||||
* @param Connection $ai_connection The AI connection.
|
||||
* @param string $token The JWT token.
|
||||
* @param string $business_description The business description.
|
||||
*
|
||||
* @return array|int|mixed|string|WP_Error
|
||||
*/
|
||||
public static function verify_images( $images, $ai_connection, $token, $business_description ) {
|
||||
if ( ! is_wp_error( $images ) && ! empty( $images['images'] ) && ! empty( $images['search_term'] ) ) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
$images = ( new Pexels() )->get_images( $ai_connection, $token, $business_description );
|
||||
|
||||
if ( is_wp_error( $images ) ) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
if ( empty( $images['images'] ) || empty( $images['search_term'] ) ) {
|
||||
return new WP_Error( 'images_not_found', __( 'No images provided for generating AI content.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the size of images for optimal performance on products and patterns.
|
||||
*
|
||||
* @param string $image_url The image URL.
|
||||
* @param string $usage_type The usage type of the image. Either 'products' or 'patterns'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function adjust_image_size( $image_url, $usage_type ) {
|
||||
$parsed_url = wp_parse_url( $image_url );
|
||||
|
||||
if ( ! isset( $parsed_url['query'] ) ) {
|
||||
return $image_url;
|
||||
}
|
||||
|
||||
$width = 'products' === $usage_type ? 400 : 500;
|
||||
|
||||
parse_str( $parsed_url['query'], $query_params );
|
||||
|
||||
unset( $query_params['h'], $query_params['w'] );
|
||||
$query_params['w'] = $width;
|
||||
$url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . $parsed_url['path'];
|
||||
|
||||
return add_query_arg( $query_params, $url );
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\AIContent;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Patterns Helper class.
|
||||
*/
|
||||
class PatternsHelper {
|
||||
/**
|
||||
* Fetches the AI-selected image for the pattern or returns the default image.
|
||||
*
|
||||
* @param array $images The array of images.
|
||||
* @param int $index The index of the image to return.
|
||||
* @param string $default_image The default image to return.
|
||||
*
|
||||
* @return string The image.
|
||||
*/
|
||||
public static function get_image_url( $images, $index, $default_image ) {
|
||||
$image = filter_var( $default_image, FILTER_VALIDATE_URL )
|
||||
? $default_image
|
||||
: plugins_url( $default_image, dirname( __DIR__, 2 ) );
|
||||
|
||||
if ( isset( $images[ $index ] ) ) {
|
||||
$image = $images[ $index ];
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the post that has the generated data by the AI for the patterns.
|
||||
*
|
||||
* @return \WP_Post|null
|
||||
*/
|
||||
public static function get_patterns_ai_data_post() {
|
||||
$arg = array(
|
||||
'post_type' => 'patterns_ai_data',
|
||||
'posts_per_page' => 1,
|
||||
'no_found_rows' => true,
|
||||
'cache_results' => true,
|
||||
);
|
||||
|
||||
$query = new \WP_Query( $arg );
|
||||
|
||||
$posts = $query->get_posts();
|
||||
return $posts[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the post that has the generated data by the AI for the patterns.
|
||||
*
|
||||
* @return \WP_Post|null
|
||||
*/
|
||||
public static function delete_patterns_ai_data_post() {
|
||||
$patterns_ai_data_post = self::get_patterns_ai_data_post();
|
||||
|
||||
if ( isset( $patterns_ai_data_post ) ) {
|
||||
return wp_delete_post( $patterns_ai_data_post->ID, true );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upsert the patterns AI data.
|
||||
*
|
||||
* @param array $patterns_dictionary The patterns dictionary.
|
||||
*
|
||||
* @return WP_Error|null
|
||||
*/
|
||||
public static function upsert_patterns_ai_data_post( $patterns_dictionary ) {
|
||||
$patterns_ai_data_post = self::get_patterns_ai_data_post();
|
||||
|
||||
if ( isset( $patterns_ai_data_post ) ) {
|
||||
$patterns_ai_data_post->post_content = wp_json_encode( $patterns_dictionary );
|
||||
return wp_update_post( $patterns_ai_data_post, true );
|
||||
} else {
|
||||
$patterns_ai_data_post = array(
|
||||
'post_title' => 'Patterns AI Data',
|
||||
'post_content' => wp_json_encode( $patterns_dictionary ),
|
||||
'post_status' => 'publish',
|
||||
'post_type' => 'patterns_ai_data',
|
||||
);
|
||||
return wp_insert_post( $patterns_ai_data_post, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Patterns Dictionary.
|
||||
*
|
||||
* @param string|null $pattern_slug The pattern slug.
|
||||
*
|
||||
* @return array|WP_Error Returns pattern dictionary or WP_Error on failure.
|
||||
*/
|
||||
public static function get_patterns_dictionary( $pattern_slug = null ) {
|
||||
$patterns_dictionary_file = plugin_dir_path( __FILE__ ) . 'dictionary.json';
|
||||
|
||||
if ( ! file_exists( $patterns_dictionary_file ) ) {
|
||||
return new WP_Error( 'missing_patterns_dictionary', __( 'The patterns dictionary is missing.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$default_patterns_dictionary = wp_json_file_decode( $patterns_dictionary_file, array( 'associative' => true ) );
|
||||
|
||||
if ( json_last_error() !== JSON_ERROR_NONE ) {
|
||||
return new WP_Error( 'json_decode_error', __( 'Error decoding JSON.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$patterns_ai_data_post = self::get_patterns_ai_data_post();
|
||||
$patterns_dictionary = '';
|
||||
if ( ! empty( $patterns_ai_data_post->post_content ) ) {
|
||||
$patterns_dictionary = json_decode( $patterns_ai_data_post->post_content, true );
|
||||
|
||||
if ( json_last_error() !== JSON_ERROR_NONE ) {
|
||||
return new WP_Error( 'json_decode_error', __( 'Error decoding JSON.', 'woocommerce' ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ( $patterns_dictionary === $default_patterns_dictionary || empty( $patterns_dictionary ) ) && $pattern_slug ) {
|
||||
return self::find_pattern_by_slug( $default_patterns_dictionary, $pattern_slug );
|
||||
} elseif ( $pattern_slug && is_array( $patterns_dictionary ) ) {
|
||||
return self::find_pattern_by_slug( $patterns_dictionary, $pattern_slug );
|
||||
} elseif ( is_array( $patterns_dictionary ) ) {
|
||||
return $patterns_dictionary;
|
||||
}
|
||||
|
||||
return $default_patterns_dictionary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a pattern by slug in a given dictionary.
|
||||
*
|
||||
* @param array $patterns_dictionary The patterns' dictionary.
|
||||
* @param string $slug The slug to search for.
|
||||
*
|
||||
* @return array|null Returns the pattern if found, otherwise null.
|
||||
*/
|
||||
private static function find_pattern_by_slug( $patterns_dictionary, $slug ) {
|
||||
foreach ( $patterns_dictionary as $pattern ) {
|
||||
if ( ! is_array( $pattern ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $pattern['slug'] === $slug ) {
|
||||
return $pattern;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,479 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\AIContent;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Pattern Images class.
|
||||
*/
|
||||
class UpdatePatterns {
|
||||
|
||||
/**
|
||||
* All patterns that are actively in use in the Assembler.
|
||||
*/
|
||||
const WC_PATTERNS_IN_THE_ASSEMBLER = [
|
||||
'woocommerce-blocks/featured-category-triple',
|
||||
'woocommerce-blocks/hero-product-3-split',
|
||||
'woocommerce-blocks/hero-product-chessboard',
|
||||
'woocommerce-blocks/hero-product-split',
|
||||
'woocommerce-blocks/product-collection-4-columns',
|
||||
'woocommerce-blocks/product-collection-5-columns',
|
||||
'woocommerce-blocks/social-follow-us-in-social-media',
|
||||
'woocommerce-blocks/testimonials-3-columns',
|
||||
'woocommerce-blocks/product-collection-featured-products-5-columns',
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate AI content and assign AI-managed images to Patterns.
|
||||
*
|
||||
* @param Connection $ai_connection The AI connection.
|
||||
* @param string|WP_Error $token The JWT token.
|
||||
* @param array|WP_Error $images The array of images.
|
||||
* @param string $business_description The business description.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function generate_content( $ai_connection, $token, $images, $business_description ) {
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$images = ContentProcessor::verify_images( $images, $ai_connection, $token, $business_description );
|
||||
$patterns_dictionary = PatternsHelper::get_patterns_dictionary();
|
||||
|
||||
if ( is_wp_error( $patterns_dictionary ) ) {
|
||||
return $patterns_dictionary;
|
||||
}
|
||||
|
||||
$patterns = $this->assign_selected_images_to_patterns( $patterns_dictionary, $images['images'] );
|
||||
|
||||
if ( is_wp_error( $patterns ) ) {
|
||||
return new WP_Error( 'failed_to_set_pattern_images', __( 'Failed to set the pattern images.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$ai_generated_patterns_content = $this->generate_ai_content_for_patterns( $ai_connection, $token, $patterns, $business_description );
|
||||
|
||||
if ( is_wp_error( $ai_generated_patterns_content ) ) {
|
||||
return new WP_Error( 'failed_to_set_pattern_content', __( 'Failed to set the pattern content.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$patterns_ai_data_post = PatternsHelper::get_patterns_ai_data_post();
|
||||
|
||||
if ( isset( $patterns_ai_data_post->post_content ) && json_decode( $patterns_ai_data_post->post_content ) === $ai_generated_patterns_content ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$updated_content = PatternsHelper::upsert_patterns_ai_data_post( $ai_generated_patterns_content );
|
||||
|
||||
if ( is_wp_error( $updated_content ) ) {
|
||||
return new WP_Error( 'failed_to_update_patterns_content', __( 'Failed to update patterns content.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the patterns with AI generated content.
|
||||
*
|
||||
* @param Connection $ai_connection The AI connection.
|
||||
* @param string|WP_Error $token The JWT token.
|
||||
* @param array $patterns The array of patterns.
|
||||
* @param string $business_description The business description.
|
||||
*
|
||||
* @return array|WP_Error The patterns with AI generated content.
|
||||
*/
|
||||
public function generate_ai_content_for_patterns( $ai_connection, $token, $patterns, $business_description ) {
|
||||
$prompts = $this->prepare_prompts( $patterns );
|
||||
$expected_results_format = $this->prepare_expected_results_format( $prompts );
|
||||
$formatted_prompts = $this->format_prompts_for_ai( $prompts, $business_description, $expected_results_format );
|
||||
$ai_responses = $this->fetch_and_validate_ai_responses( $ai_connection, $token, $formatted_prompts, $expected_results_format );
|
||||
|
||||
if ( is_wp_error( $ai_responses ) ) {
|
||||
return $ai_responses;
|
||||
}
|
||||
|
||||
return $this->apply_ai_responses_to_patterns( $patterns, $ai_responses );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the prompts for the AI.
|
||||
*
|
||||
* @param array $patterns The array of patterns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepare_prompts( array $patterns ) {
|
||||
$prompts = [];
|
||||
$result = [];
|
||||
$group_size = count( self::WC_PATTERNS_IN_THE_ASSEMBLER );
|
||||
$i = 1;
|
||||
foreach ( $patterns as $pattern ) {
|
||||
$slug = $pattern['slug'] ?? '';
|
||||
|
||||
if ( ! in_array( $slug, self::WC_PATTERNS_IN_THE_ASSEMBLER, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $pattern['content'] ?? '';
|
||||
$counter = 1;
|
||||
$result[ $slug ] = [];
|
||||
|
||||
if ( isset( $content['titles'] ) ) {
|
||||
foreach ( $content['titles'] as $title ) {
|
||||
$result[ $slug ][ $counter ++ ] = $title['ai_prompt'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $content['descriptions'] ) ) {
|
||||
foreach ( $content['descriptions'] as $description ) {
|
||||
$result[ $slug ][ $counter ++ ] = $description['ai_prompt'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $content['buttons'] ) ) {
|
||||
foreach ( $content['buttons'] as $button ) {
|
||||
$result[ $slug ][ $counter ++ ] = $button['ai_prompt'];
|
||||
}
|
||||
}
|
||||
|
||||
$i ++;
|
||||
|
||||
if ( $i === $group_size ) {
|
||||
$prompts[] = $result;
|
||||
$result = [];
|
||||
$i = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $prompts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the expected results format for the AI.
|
||||
*
|
||||
* @param array $prompts The array of prompts.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepare_expected_results_format( array $prompts ) {
|
||||
$expected_results_format = [];
|
||||
foreach ( $prompts as $prompt ) {
|
||||
$expected_result_format = [];
|
||||
|
||||
foreach ( $prompt as $key => $values ) {
|
||||
$expected_result_format[ $key ] = [];
|
||||
|
||||
foreach ( $values as $sub_key => $sub_value ) {
|
||||
$expected_result_format[ $key ][ $sub_key ] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$expected_results_format[] = $expected_result_format;
|
||||
}
|
||||
|
||||
return $expected_results_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the prompts for the AI.
|
||||
*
|
||||
* @param array $prompts The array of prompts.
|
||||
* @param string $business_description The business description.
|
||||
* @param array $expected_results_format The expected results format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function format_prompts_for_ai( array $prompts, string $business_description, array $expected_results_format ) {
|
||||
$i = 0;
|
||||
$formatted_prompts = [];
|
||||
foreach ( $prompts as $prompt ) {
|
||||
$formatted_prompts[] = sprintf(
|
||||
"You are an experienced writer. Given a business described as: '%s', generate content for the sections using the following prompts for each one of them: `'%s'`, always making sure that the expected number of characters is respected. The response should be an array of data in JSON format. Each element should be an object with the pattern name as the key, and the generated content as values. Here's an example format: `'%s'`",
|
||||
$business_description,
|
||||
wp_json_encode( $prompt ),
|
||||
wp_json_encode( $expected_results_format[ $i ] )
|
||||
);
|
||||
$i ++;
|
||||
}
|
||||
|
||||
return $formatted_prompts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and validates the AI responses.
|
||||
*
|
||||
* @param Connection $ai_connection The AI connection.
|
||||
* @param string|WP_Error $token The JWT token.
|
||||
* @param array $formatted_prompts The array of formatted prompts.
|
||||
* @param array $expected_results_format The array of expected results format.
|
||||
*
|
||||
* @return array|mixed
|
||||
*/
|
||||
private function fetch_and_validate_ai_responses( $ai_connection, $token, $formatted_prompts, $expected_results_format ) {
|
||||
$ai_request_retries = 0;
|
||||
$ai_responses = [];
|
||||
$success = false;
|
||||
while ( $ai_request_retries < 5 && ! $success ) {
|
||||
$ai_request_retries ++;
|
||||
$ai_responses = $ai_connection->fetch_ai_responses( $token, $formatted_prompts, 60, 'json_object' );
|
||||
|
||||
if ( is_wp_error( $ai_responses ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $ai_responses ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$loops_success = [];
|
||||
$i = 0;
|
||||
foreach ( $ai_responses as $ai_response ) {
|
||||
if ( ! isset( $ai_response['completion'] ) ) {
|
||||
$loops_success[] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$completion = json_decode( $ai_response['completion'], true );
|
||||
|
||||
if ( ! is_array( $completion ) ) {
|
||||
$loops_success[] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = array_diff_key( $expected_results_format[ $i ], $completion );
|
||||
$i ++;
|
||||
|
||||
if ( ! empty( $diff ) ) {
|
||||
$loops_success[] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$empty_results = false;
|
||||
foreach ( $completion as $completion_item ) {
|
||||
foreach ( $completion_item as $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
$empty_results = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $empty_results ) {
|
||||
$loops_success[] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$loops_success[] = true;
|
||||
}
|
||||
|
||||
if ( ! in_array( false, $loops_success, true ) ) {
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $success ) {
|
||||
return new WP_Error( 'failed_to_fetch_ai_responses', __( 'Failed to fetch AI responses.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
return $ai_responses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the AI responses to the patterns.
|
||||
*
|
||||
* @param array $patterns The array of patterns.
|
||||
* @param array $ai_responses The array of AI responses.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function apply_ai_responses_to_patterns( array $patterns, array $ai_responses ) {
|
||||
foreach ( $patterns as $i => $pattern ) {
|
||||
$pattern_slug = $pattern['slug'];
|
||||
|
||||
if ( ! in_array( $pattern_slug, self::WC_PATTERNS_IN_THE_ASSEMBLER, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $ai_responses as $ai_response ) {
|
||||
$ai_response = json_decode( $ai_response['completion'], true );
|
||||
|
||||
if ( isset( $ai_response[ $pattern_slug ] ) ) {
|
||||
$ai_response_content = $ai_response[ $pattern_slug ];
|
||||
|
||||
$counter = 1;
|
||||
if ( isset( $patterns[ $i ]['content']['titles'] ) ) {
|
||||
foreach ( $patterns[ $i ]['content']['titles'] as $j => $title ) {
|
||||
if ( ! isset( $ai_response_content[ $counter ] ) ) {
|
||||
$ai_response_content[ $counter ] = $ai_response_content[ $counter - 1 ] ?? '';
|
||||
}
|
||||
|
||||
$patterns[ $i ]['content']['titles'][ $j ]['default'] = $this->sanitize_string( $ai_response_content[ $counter ] );
|
||||
|
||||
$counter ++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $patterns[ $i ]['content']['descriptions'] ) ) {
|
||||
foreach ( $patterns[ $i ]['content']['descriptions'] as $k => $description ) {
|
||||
if ( ! isset( $ai_response_content[ $counter ] ) ) {
|
||||
$ai_response_content[ $counter ] = $ai_response_content[ $counter - 1 ] ?? '';
|
||||
}
|
||||
|
||||
$patterns[ $i ]['content']['descriptions'][ $k ]['default'] = $this->sanitize_string( $ai_response_content[ $counter ] );
|
||||
|
||||
$counter ++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $patterns[ $i ]['content']['buttons'] ) ) {
|
||||
foreach ( $patterns[ $i ]['content']['buttons'] as $l => $button ) {
|
||||
if ( ! isset( $ai_response_content[ $counter ] ) ) {
|
||||
$ai_response_content[ $counter ] = $ai_response_content[ $counter - 1 ] ?? '';
|
||||
}
|
||||
|
||||
$patterns[ $i ]['content']['buttons'][ $l ]['default'] = $this->sanitize_string( $ai_response_content[ $counter ] );
|
||||
|
||||
$counter ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $patterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the string from the AI generated content. It removes double quotes that can cause issues when
|
||||
* decoding the patterns JSON.
|
||||
*
|
||||
* @param string $string The string to be sanitized.
|
||||
*
|
||||
* @return string The sanitized string.
|
||||
*/
|
||||
private function sanitize_string( $string ) {
|
||||
return str_replace( '"', '', $string );
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign selected images to patterns.
|
||||
*
|
||||
* @param array $patterns_dictionary The array of patterns.
|
||||
* @param array $selected_images The array of images.
|
||||
*
|
||||
* @return array|WP_Error The patterns with images.
|
||||
*/
|
||||
private function assign_selected_images_to_patterns( $patterns_dictionary, $selected_images ) {
|
||||
$patterns_with_images = array();
|
||||
|
||||
foreach ( $patterns_dictionary as $pattern ) {
|
||||
if ( ! $this->pattern_has_images( $pattern ) ) {
|
||||
$patterns_with_images[] = $pattern;
|
||||
continue;
|
||||
}
|
||||
|
||||
list( $images, $alts ) = $this->get_images_for_pattern( $pattern, $selected_images );
|
||||
if ( empty( $images ) ) {
|
||||
$patterns_with_images[] = $pattern;
|
||||
continue;
|
||||
}
|
||||
|
||||
$pattern['images'] = $images;
|
||||
|
||||
$string = wp_json_encode( $pattern );
|
||||
|
||||
foreach ( $alts as $i => $alt ) {
|
||||
$alt = empty( $alt ) ? 'the text should be related to the store description but generic enough to adapt to any image' : $alt;
|
||||
$string = str_replace( "{image.$i}", $alt, $string );
|
||||
}
|
||||
|
||||
$pattern = json_decode( $string, true );
|
||||
|
||||
$patterns_with_images[] = $pattern;
|
||||
}
|
||||
|
||||
return $patterns_with_images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Patterns Dictionary.
|
||||
*
|
||||
* @return mixed|WP_Error|null
|
||||
*/
|
||||
public static function get_patterns_dictionary() {
|
||||
$patterns_dictionary = plugin_dir_path( __FILE__ ) . 'dictionary.json';
|
||||
|
||||
if ( ! file_exists( $patterns_dictionary ) ) {
|
||||
return new WP_Error( 'missing_patterns_dictionary', __( 'The patterns dictionary is missing.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
return wp_json_file_decode( $patterns_dictionary, array( 'associative' => true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the pattern has images.
|
||||
*
|
||||
* @param array $pattern The array representing the pattern.
|
||||
*
|
||||
* @return bool True if the pattern has images, false otherwise.
|
||||
*/
|
||||
private function pattern_has_images( array $pattern ): bool {
|
||||
return isset( $pattern['images_total'] ) && $pattern['images_total'] > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the images for the given pattern.
|
||||
*
|
||||
* @param array $pattern The array representing the pattern.
|
||||
* @param array $selected_images The array of images.
|
||||
*
|
||||
* @return array An array containing an array of the images in the first position and their alts in the second.
|
||||
*/
|
||||
private function get_images_for_pattern( array $pattern, array $selected_images ): array {
|
||||
$images = array();
|
||||
$alts = array();
|
||||
foreach ( $selected_images as $selected_image ) {
|
||||
if ( ! isset( $selected_image['title'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset( $selected_image['URL'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( str_contains( '.jpeg', $selected_image['title'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expected_image_format = $pattern['images_format'] ?? 'portrait';
|
||||
$selected_image_format = $this->get_selected_image_format( $selected_image );
|
||||
|
||||
if ( $selected_image_format !== $expected_image_format ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$selected_image_url = ContentProcessor::adjust_image_size( $selected_image['URL'], 'patterns' );
|
||||
|
||||
$images[] = $selected_image_url;
|
||||
$alts[] = $selected_image['title'];
|
||||
}
|
||||
|
||||
return array( $images, $alts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected image format. Defaults to portrait.
|
||||
*
|
||||
* @param array $selected_image The selected image to be assigned to the pattern.
|
||||
*
|
||||
* @return string The selected image format.
|
||||
*/
|
||||
private function get_selected_image_format( $selected_image ) {
|
||||
if ( ! isset( $selected_image['width'], $selected_image['height'] ) ) {
|
||||
return 'portrait';
|
||||
}
|
||||
|
||||
return $selected_image['width'] === $selected_image['height'] ? 'square' : ( $selected_image['width'] > $selected_image['height'] ? 'landscape' : 'portrait' );
|
||||
}
|
||||
}
|
||||
@@ -1,509 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\AIContent;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use WP_Error;
|
||||
/**
|
||||
* Pattern Images class.
|
||||
*/
|
||||
class UpdateProducts {
|
||||
|
||||
/**
|
||||
* The dummy products.
|
||||
*/
|
||||
const DUMMY_PRODUCTS = [
|
||||
[
|
||||
'title' => 'Vintage Typewriter',
|
||||
'image' => 'assets/images/pattern-placeholders/writing-typing-keyboard-technology-white-vintage.jpg',
|
||||
'description' => 'A hit spy novel or a love letter? Anything you type using this vintage typewriter from the 20s is bound to make a mark.',
|
||||
'price' => 90,
|
||||
],
|
||||
[
|
||||
'title' => 'Leather-Clad Leisure Chair',
|
||||
'image' => 'assets/images/pattern-placeholders/table-wood-house-chair-floor-window.jpg',
|
||||
'description' => 'Sit back and relax in this comfy designer chair. High-grain leather and steel frame add luxury to your your leisure.',
|
||||
'price' => 249,
|
||||
],
|
||||
[
|
||||
'title' => 'Black and White Summer Portrait',
|
||||
'image' => 'assets/images/pattern-placeholders/white-black-black-and-white-photograph-monochrome-photography.jpg',
|
||||
'description' => 'This 24" x 30" high-quality print just exudes summer. Hang it on the wall and forget about the world outside.',
|
||||
'price' => 115,
|
||||
],
|
||||
[
|
||||
'title' => '3-Speed Bike',
|
||||
'image' => 'assets/images/pattern-placeholders/road-sport-vintage-wheel-retro-old.jpg',
|
||||
'description' => 'Zoom through the streets on this premium 3-speed bike. Manufactured and assembled in Germany in the 80s.',
|
||||
'price' => 115,
|
||||
],
|
||||
[
|
||||
'title' => 'Hi-Fi Headphones',
|
||||
'image' => 'assets/images/pattern-placeholders/man-person-music-black-and-white-white-photography.jpg',
|
||||
'description' => 'Experience your favorite songs in a new way with these premium hi-fi headphones.',
|
||||
'price' => 125,
|
||||
],
|
||||
[
|
||||
'title' => 'Retro Glass Jug (330 ml)',
|
||||
'image' => 'assets/images/pattern-placeholders/drinkware-liquid-tableware-dishware-bottle-fluid.jpg',
|
||||
'description' => 'Thick glass and a classic silhouette make this jug a must-have for any retro-inspired kitchen.',
|
||||
'price' => 115,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate AI content and assign AI-managed images to Products.
|
||||
*
|
||||
* @param Connection $ai_connection The AI connection.
|
||||
* @param string|WP_Error $token The JWT token.
|
||||
* @param array|WP_Error $images The array of images.
|
||||
* @param string $business_description The business description.
|
||||
*
|
||||
* @return array|WP_Error The generated content for the products. An error if the content could not be generated.
|
||||
*/
|
||||
public function generate_content( $ai_connection, $token, $images, $business_description ) {
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
$images = ContentProcessor::verify_images( $images, $ai_connection, $token, $business_description );
|
||||
|
||||
if ( is_wp_error( $images ) ) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
if ( empty( $business_description ) ) {
|
||||
return new \WP_Error( 'missing_business_description', __( 'No business description provided for generating AI content.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$dummy_products_to_update = $this->fetch_dummy_products_to_update();
|
||||
|
||||
if ( is_wp_error( $dummy_products_to_update ) ) {
|
||||
return $dummy_products_to_update;
|
||||
}
|
||||
|
||||
if ( empty( $dummy_products_to_update ) ) {
|
||||
return array(
|
||||
'product_content' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
$products_information_list = $this->assign_ai_selected_images_to_dummy_products( $dummy_products_to_update, $images['images'] );
|
||||
|
||||
return $this->assign_ai_generated_content_to_dummy_products( $ai_connection, $token, $products_information_list, $business_description, $images['search_term'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all dummy products that were not modified by the store owner.
|
||||
*
|
||||
* @return array|WP_Error An array with the dummy products that need to have their content updated by AI.
|
||||
*/
|
||||
public function fetch_dummy_products_to_update() {
|
||||
$real_products = $this->fetch_product_ids();
|
||||
$real_products_count = count( $real_products );
|
||||
|
||||
if ( is_array( $real_products ) && $real_products_count > 6 ) {
|
||||
return array(
|
||||
'product_content' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
$dummy_products = $this->fetch_product_ids( 'dummy' );
|
||||
$dummy_products_count = count( $dummy_products );
|
||||
$products_to_create = max( 0, 6 - $real_products_count - $dummy_products_count );
|
||||
while ( $products_to_create > 0 ) {
|
||||
$this->create_new_product( self::DUMMY_PRODUCTS[ $products_to_create - 1 ] );
|
||||
$products_to_create--;
|
||||
}
|
||||
|
||||
// Identify dummy products that need to have their content updated.
|
||||
$dummy_products_ids = $this->fetch_product_ids( 'dummy' );
|
||||
if ( ! is_array( $dummy_products_ids ) ) {
|
||||
return new \WP_Error( 'failed_to_fetch_dummy_products', __( 'Failed to fetch dummy products.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$dummy_products = array_map(
|
||||
function ( $product ) {
|
||||
return wc_get_product( $product->ID );
|
||||
},
|
||||
$dummy_products_ids
|
||||
);
|
||||
|
||||
$dummy_products_to_update = [];
|
||||
foreach ( $dummy_products as $dummy_product ) {
|
||||
if ( ! $dummy_product instanceof \WC_Product ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$should_update_dummy_product = $this->should_update_dummy_product( $dummy_product );
|
||||
|
||||
if ( $should_update_dummy_product ) {
|
||||
$dummy_products_to_update[] = $dummy_product;
|
||||
}
|
||||
}
|
||||
|
||||
return $dummy_products_to_update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the dummy product should have its content generated and managed by AI.
|
||||
*
|
||||
* @param \WC_Product $dummy_product The dummy product.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_update_dummy_product( $dummy_product ): bool {
|
||||
$current_product_hash = $this->get_hash_for_product( $dummy_product );
|
||||
$ai_modified_product_hash = $this->get_hash_for_ai_modified_product( $dummy_product );
|
||||
|
||||
$date_created = $dummy_product->get_date_created();
|
||||
$date_modified = $dummy_product->get_date_modified();
|
||||
|
||||
if ( ! $date_created instanceof \WC_DateTime || ! $date_modified instanceof \WC_DateTime ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$formatted_date_created = $dummy_product->get_date_created()->date( 'Y-m-d H:i:s' );
|
||||
$formatted_date_modified = $dummy_product->get_date_modified()->date( 'Y-m-d H:i:s' );
|
||||
|
||||
$timestamp_created = strtotime( $formatted_date_created );
|
||||
$timestamp_modified = strtotime( $formatted_date_modified );
|
||||
$timestamp_current = time();
|
||||
|
||||
$dummy_product_recently_modified = abs( $timestamp_current - $timestamp_modified ) < 10;
|
||||
$dummy_product_not_modified = abs( $timestamp_modified - $timestamp_created ) < 60;
|
||||
|
||||
if ( $current_product_hash === $ai_modified_product_hash || $dummy_product_not_modified || $dummy_product_recently_modified ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new product and assigns the _headstart_post meta to it.
|
||||
*
|
||||
* @param array $product_data The product data.
|
||||
*
|
||||
* @return bool|int|\WP_Error
|
||||
*/
|
||||
public function create_new_product( $product_data ) {
|
||||
$product = new \WC_Product();
|
||||
$image_src = plugins_url( $product_data['image'], dirname( __DIR__, 2 ) );
|
||||
$image_alt = $product_data['title'];
|
||||
$product_image_id = $this->product_image_upload( $product->get_id(), $image_src, $image_alt );
|
||||
|
||||
$saved_product = $this->product_update( $product, $product_image_id, $product_data['title'], $product_data['description'], $product_data['price'] );
|
||||
|
||||
if ( is_wp_error( $saved_product ) ) {
|
||||
return $saved_product;
|
||||
}
|
||||
|
||||
return update_post_meta( $saved_product, '_headstart_post', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all existing products that have the _headstart_post meta assigned to them.
|
||||
*
|
||||
* @param string $type The type of products to fetch.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function fetch_product_ids( string $type = 'user_created' ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( 'user_created' === $type ) {
|
||||
return $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE ID NOT IN ( SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE pm.meta_key = %s AND p.post_type = 'product' AND p.post_status = 'publish' ) AND post_type = 'product' AND post_status = 'publish' LIMIT 6", '_headstart_post' ) );
|
||||
}
|
||||
|
||||
return $wpdb->get_results( $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE pm.meta_key = %s AND p.post_type = 'product' AND p.post_status = 'publish'", '_headstart_post' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash for a product based on its name, description and image_id.
|
||||
*
|
||||
* @param \WC_Product $product The product.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function get_hash_for_product( $product ) {
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return md5( $product->get_name() . $product->get_description() . $product->get_image_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash for a product that had its content AI-generated.
|
||||
*
|
||||
* @param \WC_Product $product The product.
|
||||
*
|
||||
* @return false|mixed
|
||||
*/
|
||||
public function get_hash_for_ai_modified_product( $product ) {
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_post_meta( $product->get_id(), '_ai_generated_content', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a hash with the AI-generated content and save it as a meta for the product.
|
||||
*
|
||||
* @param \WC_Product $product The product.
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
public function create_hash_for_ai_modified_product( $product ) {
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = $this->get_hash_for_product( $product );
|
||||
|
||||
return update_post_meta( $product->get_id(), '_ai_generated_content', $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the product content with the AI-generated content.
|
||||
*
|
||||
* @param array $ai_generated_product_content The AI-generated product content.
|
||||
*
|
||||
* @return void|WP_Error
|
||||
*/
|
||||
public function update_product_content( $ai_generated_product_content ) {
|
||||
if ( ! isset( $ai_generated_product_content['product_id'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product = wc_get_product( $ai_generated_product_content['product_id'] );
|
||||
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $ai_generated_product_content['image']['src'] ) || ! isset( $ai_generated_product_content['image']['alt'] ) || ! isset( $ai_generated_product_content['title'] ) || ! isset( $ai_generated_product_content['description'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product_image_id = $this->product_image_upload( $product->get_id(), $ai_generated_product_content['image']['src'], $ai_generated_product_content['image']['alt'] );
|
||||
|
||||
$this->product_update( $product, $product_image_id, $ai_generated_product_content['title'], $ai_generated_product_content['description'], $ai_generated_product_content['price'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload the image for the product.
|
||||
*
|
||||
* @param int $product_id The product ID.
|
||||
* @param string $image_src The image source.
|
||||
* @param string $image_alt The image alt.
|
||||
*
|
||||
* @return int|string|WP_Error
|
||||
*/
|
||||
private function product_image_upload( $product_id, $image_src, $image_alt ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
|
||||
// Since the media_sideload_image function is expensive and can take longer to complete
|
||||
// the process of downloading the external image and uploading it to the media library,
|
||||
// here we are increasing the time limit to avoid any issues.
|
||||
set_time_limit( 150 );
|
||||
wp_raise_memory_limit( 'image' );
|
||||
|
||||
return media_sideload_image( $image_src, $product_id, $image_alt, 'id' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the default content for the products.
|
||||
*
|
||||
* @param array $dummy_products_to_update The dummy products to update.
|
||||
* @param array $ai_selected_images The images' information.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function assign_ai_selected_images_to_dummy_products( $dummy_products_to_update, $ai_selected_images ) {
|
||||
$products_information_list = [];
|
||||
$dummy_products_count = count( $dummy_products_to_update );
|
||||
for ( $i = 0; $i < $dummy_products_count; $i ++ ) {
|
||||
$image_src = $ai_selected_images[ $i ]['URL'] ?? '';
|
||||
|
||||
if ( wc_is_valid_url( $image_src ) ) {
|
||||
$image_src = ContentProcessor::adjust_image_size( $image_src, 'products' );
|
||||
}
|
||||
|
||||
$image_alt = $ai_selected_images[ $i ]['title'] ?? '';
|
||||
|
||||
$products_information_list[] = [
|
||||
'title' => 'A product title',
|
||||
'description' => 'A product description',
|
||||
'price' => 'The product price',
|
||||
'image' => [
|
||||
'src' => $image_src,
|
||||
'alt' => $image_alt,
|
||||
],
|
||||
'product_id' => $dummy_products_to_update[ $i ]->get_id(),
|
||||
];
|
||||
}
|
||||
|
||||
return $products_information_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the product content.
|
||||
*
|
||||
* @param Connection $ai_connection The AI connection.
|
||||
* @param string $token The JWT token.
|
||||
* @param array $products_information_list The products information list.
|
||||
* @param string $business_description The business description.
|
||||
* @param string $search_term The search term.
|
||||
*
|
||||
* @return array|int|string|\WP_Error
|
||||
*/
|
||||
public function assign_ai_generated_content_to_dummy_products( $ai_connection, $token, $products_information_list, $business_description, $search_term ) {
|
||||
$business_description = ContentProcessor::summarize_business_description( $business_description, $ai_connection, $token, 100 );
|
||||
|
||||
if ( is_wp_error( $business_description ) ) {
|
||||
return $business_description;
|
||||
}
|
||||
|
||||
$prompts = [];
|
||||
foreach ( $products_information_list as $product_information ) {
|
||||
if ( ! empty( $product_information['image']['alt'] ) ) {
|
||||
$prompts[] = sprintf( 'Considering that you are the owner of a store with the following description "%s", create the title for a product that is related to "%s" and to an image described as "%s". Do not include any adjectives or descriptions of the qualities of the product and always refer to objects or services, not humans.', $business_description, $search_term, $product_information['image']['alt'] );
|
||||
} else {
|
||||
$prompts[] = sprintf( 'You are the owner of a business described as: "%s". Create the title for a product that could be sold on your store. Do not include any adjectives or descriptions of the qualities of the product and always refer to objects or services, not humans.', $business_description );
|
||||
}
|
||||
}
|
||||
|
||||
$expected_results_format = [];
|
||||
foreach ( $products_information_list as $index => $product ) {
|
||||
$expected_results_format[ $index ] = [
|
||||
'title' => '',
|
||||
'price' => '',
|
||||
];
|
||||
}
|
||||
|
||||
$formatted_prompt = sprintf(
|
||||
"Generate two-words titles and price for products using the following prompts for each one of them: '%s'. Ensure each entry is unique and does not repeat the given examples. It should be a number and it's not too low or too high for the corresponding product title being advertised. Convert the price to this currency: '%s'. Do not include backticks or the word json in the response. Here's an example of the expected output format in JSON: '%s'.",
|
||||
wp_json_encode( $prompts ),
|
||||
get_woocommerce_currency(),
|
||||
wp_json_encode( $expected_results_format )
|
||||
);
|
||||
|
||||
$ai_request_retries = 0;
|
||||
$success = false;
|
||||
while ( $ai_request_retries < 5 && ! $success ) {
|
||||
$ai_request_retries ++;
|
||||
$ai_response = $ai_connection->fetch_ai_response( $token, $formatted_prompt, 30 );
|
||||
if ( is_wp_error( $ai_response ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $ai_response ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset( $ai_response['completion'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$completion = json_decode( $ai_response['completion'], true );
|
||||
|
||||
if ( ! is_array( $completion ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = array_diff_key( $expected_results_format, $completion );
|
||||
|
||||
if ( ! empty( $diff ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$empty_results = false;
|
||||
foreach ( $completion as $completion_item ) {
|
||||
if ( empty( $completion_item ) ) {
|
||||
$empty_results = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $empty_results ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $products_information_list as $index => $product_information ) {
|
||||
$products_information_list[ $index ]['title'] = str_replace( '"', '', $completion[ $index ]['title'] );
|
||||
$products_information_list[ $index ]['price'] = $completion[ $index ]['price'];
|
||||
}
|
||||
|
||||
$success = true;
|
||||
}
|
||||
|
||||
if ( ! $success ) {
|
||||
return new WP_Error( 'failed_to_fetch_ai_responses', __( 'Failed to fetch AI responses for products.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
return array(
|
||||
'product_content' => $products_information_list,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the products content.
|
||||
*/
|
||||
public function reset_products_content() {
|
||||
$dummy_products_to_update = $this->fetch_dummy_products_to_update();
|
||||
$i = 0;
|
||||
foreach ( $dummy_products_to_update as $product ) {
|
||||
$image_src = plugins_url( self::DUMMY_PRODUCTS[ $i ]['image'], dirname( __DIR__, 2 ) );
|
||||
$image_alt = self::DUMMY_PRODUCTS[ $i ]['title'];
|
||||
$product_image_id = $this->product_image_upload( $product->get_id(), $image_src, $image_alt );
|
||||
|
||||
$this->product_update( $product, $product_image_id, self::DUMMY_PRODUCTS[ $i ]['title'], self::DUMMY_PRODUCTS[ $i ]['description'], self::DUMMY_PRODUCTS[ $i ]['price'] );
|
||||
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the product with the new content.
|
||||
*
|
||||
* @param \WC_Product $product The product.
|
||||
* @param int $product_image_id The product image ID.
|
||||
* @param string $product_title The product title.
|
||||
* @param string $product_description The product description.
|
||||
* @param int $product_price The product price.
|
||||
*
|
||||
* @return int|\WP_Error
|
||||
*/
|
||||
private function product_update( $product, $product_image_id, $product_title, $product_description, $product_price ) {
|
||||
if ( ! $product instanceof \WC_Product ) {
|
||||
return new WP_Error( 'invalid_product', __( 'Invalid product.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( ! is_wp_error( $product_image_id ) ) {
|
||||
$product->set_image_id( $product_image_id );
|
||||
} else {
|
||||
wc_get_logger()->warning(
|
||||
sprintf(
|
||||
// translators: %s is a generated error message.
|
||||
__( 'The image upload failed: "%s", creating the product without image', 'woocommerce' ),
|
||||
$product_image_id->get_error_message()
|
||||
),
|
||||
);
|
||||
}
|
||||
$product->set_name( $product_title );
|
||||
$product->set_description( $product_description );
|
||||
$product->set_price( $product_price );
|
||||
$product->set_regular_price( $product_price );
|
||||
$product->set_slug( sanitize_title( $product_title ) );
|
||||
$product->save();
|
||||
|
||||
$this->create_hash_for_ai_modified_product( $product );
|
||||
|
||||
return $product->get_id();
|
||||
}
|
||||
}
|
||||
@@ -1,656 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Banner",
|
||||
"slug": "woocommerce-blocks/banner",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Up to 60% off",
|
||||
"ai_prompt": "A four words title advertising the sale"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Holiday Sale",
|
||||
"ai_prompt": "A two words label with the sale name"
|
||||
},
|
||||
{
|
||||
"default": "Get your favorite vinyl at record-breaking prices.",
|
||||
"ai_prompt": "The main description of the sale with at least 65 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop vinyl records",
|
||||
"ai_prompt": "A 3 words button text to go to the sale page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Discount Banner",
|
||||
"slug": "woocommerce-blocks/discount-banner",
|
||||
"content": {
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Select products",
|
||||
"ai_prompt": "A two words description of the products on sale"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Discount Banner with Image",
|
||||
"slug": "woocommerce-blocks/discount-banner-with-image",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Select products",
|
||||
"ai_prompt": "A two words description of the products on sale"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Featured Category Focus",
|
||||
"slug": "woocommerce-blocks/featured-category-focus",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Black and white high-quality prints",
|
||||
"ai_prompt": "The four words title of the featured category related to the following image description: {image.0}"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop prints",
|
||||
"ai_prompt": "A two words button text to go to the featured category"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Featured Category Triple",
|
||||
"slug": "woocommerce-blocks/featured-category-triple",
|
||||
"images_total": 3,
|
||||
"images_format": "portrait",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Home decor",
|
||||
"ai_prompt": "A one-word graphic title that encapsulates the essence of the business, inspired by the following image description: {image.0} and the nature of the business. The title should reflect the key elements and characteristics of the business, as portrayed in the image"
|
||||
},
|
||||
{
|
||||
"default": "Retro photography",
|
||||
"ai_prompt": "A two-words graphic title that encapsulates the essence of the business, inspired by the following image description: {image.1} and the nature of the business. The title should reflect the key elements and characteristics of the business, as portrayed in the image"
|
||||
},
|
||||
{
|
||||
"default": "Handmade gifts",
|
||||
"ai_prompt": "A two-words graphic title that encapsulates the essence of the business, inspired by the following image description: {image.2} and the nature of the business. The title should reflect the key elements and characteristics of the business, as portrayed in the image"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Featured Products: Fresh & Tasty",
|
||||
"slug": "woocommerce-blocks/featured-products-fresh-and-tasty",
|
||||
"images_total": 4,
|
||||
"images_format": "portrait",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Fresh & tasty goods",
|
||||
"ai_prompt": "The title of the featured products with at least 20 characters"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Sweet Organic Lemons",
|
||||
"ai_prompt": "The three words description of the featured product related to the following image description: {image.0}"
|
||||
},
|
||||
{
|
||||
"default": "Fresh Organic Tomatoes",
|
||||
"ai_prompt": "The three words description of the featured product related to the following image description: {image.1}"
|
||||
},
|
||||
{
|
||||
"default": "Fresh Lettuce (Washed)",
|
||||
"ai_prompt": "The three words description of the featured product related to the following image description: {image.2}"
|
||||
},
|
||||
{
|
||||
"default": "Russet Organic Potatoes",
|
||||
"ai_prompt": "The three words description of the featured product related to the following image description: {image.3}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hero Product 3 Split",
|
||||
"slug": "woocommerce-blocks/hero-product-3-split",
|
||||
"images_total": 1,
|
||||
"images_format": "portrait",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Timeless elegance",
|
||||
"ai_prompt": "Write a two words title for advertising the store"
|
||||
},
|
||||
{
|
||||
"default": "Durable glass",
|
||||
"ai_prompt": "Write a two words title for advertising the store"
|
||||
},
|
||||
{
|
||||
"default": "Versatile charm",
|
||||
"ai_prompt": "Write a two words title for advertising the store"
|
||||
},
|
||||
{
|
||||
"default": "New: Retro Glass Jug",
|
||||
"ai_prompt": "Write a title with less than 20 characters for advertising the store"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Elevate your table with a 330ml Retro Glass Jug, blending classic design and durable hardened glass.",
|
||||
"ai_prompt": "Write a text with approximately 130 characters, to describe a product the business is selling"
|
||||
},
|
||||
{
|
||||
"default": "Crafted from resilient thick glass, this jug ensures lasting quality, making it perfect for everyday use with a touch of vintage charm.",
|
||||
"ai_prompt": "Write a text with approximately 130 characters, to describe a product the business is selling"
|
||||
},
|
||||
{
|
||||
"default": "The Retro Glass Jug's classic silhouette effortlessly complements any setting, making it the ideal choice for serving beverages with style and flair.",
|
||||
"ai_prompt": "Write a long text, with at least 130 characters, to describe a product the business is selling"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hero Product Chessboard",
|
||||
"slug": "woocommerce-blocks/hero-product-chessboard",
|
||||
"images_total": 2,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Quality Materials",
|
||||
"ai_prompt": "A two words title describing the first displayed product feature"
|
||||
},
|
||||
{
|
||||
"default": "Unique design",
|
||||
"ai_prompt": "A two words title describing the second displayed product feature"
|
||||
},
|
||||
{
|
||||
"default": "Make your house feel like home",
|
||||
"ai_prompt": "A two words title describing the fourth displayed product feature"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "We use only the highest-quality materials in our products, ensuring that they look great and last for years to come.",
|
||||
"ai_prompt": "A description of the product feature with at least 115 characters"
|
||||
},
|
||||
{
|
||||
"default": "From bold prints to intricate details, our products are a perfect combination of style and function.",
|
||||
"ai_prompt": "A description of the product feature with at least 115 characters"
|
||||
},
|
||||
{
|
||||
"default": "Add a touch of charm and coziness this holiday season with a wide selection of hand-picked decorations — from minimalist vases to designer furniture.",
|
||||
"ai_prompt": "A description of the product feature with at least 115 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop home decor",
|
||||
"ai_prompt": "A two words button text to go to the product page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hero Product Split",
|
||||
"slug": "woocommerce-blocks/hero-product-split",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Keep dry with 50% off rain jackets",
|
||||
"ai_prompt": "An impact phrase that advertises the product the store is selling with at least 35 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Just Arrived Full Hero",
|
||||
"slug": "woocommerce-blocks/just-arrived-full-hero",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Sound like no other",
|
||||
"ai_prompt": "An impact phrase that advertises the displayed product collection with at least 10 characters"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Experience your music like never before with our latest generation of hi-fidelity headphones.",
|
||||
"ai_prompt": "A description of the product collection with at least 35 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop now",
|
||||
"ai_prompt": "A two words button text to go to the product collection page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collection Banner",
|
||||
"slug": "woocommerce-blocks/product-collection-banner",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Brand New for the Holidays",
|
||||
"ai_prompt": "An impact phrase that advertises the displayed product collection with at least 25 characters related to the following image description: {image.0}"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Check out our brand new collection of holiday products and find the right gift for anyone.",
|
||||
"ai_prompt": "A description of the product collection with at least 90 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collections Featured Collection",
|
||||
"slug": "woocommerce-blocks/product-collections-featured-collection",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "This week's popular products",
|
||||
"ai_prompt": "An impact phrase that advertises the displayed product collection with at least 30 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collections Featured Collections",
|
||||
"slug": "woocommerce-blocks/product-collections-featured-collections",
|
||||
"images_total": 4,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Tech gifts under $100",
|
||||
"ai_prompt": "An impact phrase that advertises the product collection with at least 20 characters related to the following image descriptions: {image.0}, {image.1}"
|
||||
},
|
||||
{
|
||||
"default": "For the gamers",
|
||||
"ai_prompt": "An impact phrase that advertises the product collection with at least 15 characters related to the following image descriptions: {image.2}, {image.3}"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop tech",
|
||||
"ai_prompt": "A two words button text to go to the product collection page"
|
||||
},
|
||||
{
|
||||
"default": "Shop games",
|
||||
"ai_prompt": "A two words button text to go to the product collection page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collections Newest Arrivals",
|
||||
"slug": "woocommerce-blocks/product-collections-newest-arrivals",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Our newest arrivals",
|
||||
"ai_prompt": "An impact phrase that advertises the displayed product collection with at least 20 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "More new products",
|
||||
"ai_prompt": "The button text to go to the product collection page with at least 15 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collection 4 Columns",
|
||||
"slug": "woocommerce-blocks/product-collection-4-columns",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Staff picks",
|
||||
"ai_prompt": "An impact phrase that advertises the displayed product collection with at least 20 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collection 5 Columns",
|
||||
"slug": "woocommerce-blocks/product-collection-5-columns",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Our latest and greatest",
|
||||
"ai_prompt": "An impact phrase with that advertises the product collection with at least 20 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Gallery",
|
||||
"slug": "woocommerce-blocks/product-query-product-gallery",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Bestsellers",
|
||||
"ai_prompt": "An impact phrase that advertises the featured products with at least 10 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Featured Products 2 Columns",
|
||||
"slug": "woocommerce-blocks/featured-products-2-cols",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Fan favorites",
|
||||
"ai_prompt": "An impact phrase that advertises the featured products with at least 10 characters"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Get ready to start the season right. All the fan favorites in one place at the best price.",
|
||||
"ai_prompt": "A description of the featured products with at least 90 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop All",
|
||||
"ai_prompt": "A two words button text to go to the featured products page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Hero 2 Column 2 Row",
|
||||
"slug": "woocommerce-blocks/product-hero-2-col-2-row",
|
||||
"images_total": 2,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "The Eden Jacket",
|
||||
"ai_prompt": "A three words title that advertises a product related to the following image description: {image.0}"
|
||||
},
|
||||
{
|
||||
"default": "100% Woolen",
|
||||
"ai_prompt": "A two words title that advertises a product feature"
|
||||
},
|
||||
{
|
||||
"default": "Fits your wardrobe",
|
||||
"ai_prompt": "A three words title that advertises a product feature"
|
||||
},
|
||||
{
|
||||
"default": "Versatile",
|
||||
"ai_prompt": "An one word title that advertises a product feature"
|
||||
},
|
||||
{
|
||||
"default": "Normal Fit",
|
||||
"ai_prompt": "A two words title that advertises a product feature"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Perfect for any look featuring a mid-rise, relax fitting silhouette.",
|
||||
"ai_prompt": "The description of a product with at least 65 characters related to the following image: {image.0}"
|
||||
},
|
||||
{
|
||||
"default": "Reflect your fashionable style.",
|
||||
"ai_prompt": "The description of a product feature with at least 30 characters"
|
||||
},
|
||||
{
|
||||
"default": "Half tuck into your pants or layer over.",
|
||||
"ai_prompt": "The description of a product feature with at least 30 characters"
|
||||
},
|
||||
{
|
||||
"default": "Button-down front for any type of mood or look.",
|
||||
"ai_prompt": "The description of a product feature with at least 30 characters"
|
||||
},
|
||||
{
|
||||
"default": "42% Cupro 34% Linen 24% Viscose",
|
||||
"ai_prompt": "The description of a product feature with at least 30 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "View product",
|
||||
"ai_prompt": "A two words button text to go to the product page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Shop by Price",
|
||||
"slug": "woocommerce-blocks/shop-by-price",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Outdoor Furniture & Accessories",
|
||||
"ai_prompt": "An impact phrase that advertises the first product collection with at least 30 characters"
|
||||
},
|
||||
{
|
||||
"default": "Summer Dinning",
|
||||
"ai_prompt": "An impact phrase that advertises the second product collection with at least 20 characters"
|
||||
},
|
||||
{
|
||||
"default": "Women's Styles",
|
||||
"ai_prompt": "An impact phrase that advertises the third product collection with at least 20 characters"
|
||||
},
|
||||
{
|
||||
"default": "Kids' Styles",
|
||||
"ai_prompt": "An impact phrase that advertises the fourth product collection with at least 20 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Small Discount Banner with Image",
|
||||
"slug": "woocommerce-blocks/small-discount-banner-with-image",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Chairs",
|
||||
"ai_prompt": "A single word that advertises the product and is related to the following image description: {image.0}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Social: Follow us on social media",
|
||||
"slug": "woocommerce-blocks/social-follow-us-in-social-media",
|
||||
"images_total": 4,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Stay in the loop",
|
||||
"ai_prompt": "A phrase that advertises the social media accounts of the store with at least 25 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Alternating Image and Text",
|
||||
"slug": "woocommerce-blocks/alt-image-and-text",
|
||||
"images_total": 2,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Our products",
|
||||
"ai_prompt": "A two words impact phrase that advertises the products"
|
||||
},
|
||||
{
|
||||
"default": "Sustainable blends, stylish accessories",
|
||||
"ai_prompt": "An impact phrase that advertises the products with at least 40 characters and related to the following image description: {image.0}"
|
||||
},
|
||||
{
|
||||
"default": "About us",
|
||||
"ai_prompt": "A two words impact phrase that advertises the brand"
|
||||
},
|
||||
{
|
||||
"default": "Committed to a greener lifestyle",
|
||||
"ai_prompt": "An impact phrase that advertises the brand with at least 50 characters related to the following image description: {image.1}"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Indulge in the finest organic coffee beans, teas, and hand-picked accessories, all locally sourced and sustainable for a mindful lifestyle.",
|
||||
"ai_prompt": "A description of the products with at least 180 characters"
|
||||
},
|
||||
{
|
||||
"default": "Our passion is crafting mindful moments with locally sourced, organic, and sustainable products. We're more than a store; we're your path to a community-driven, eco-friendly lifestyle that embraces premium quality.",
|
||||
"ai_prompt": "A description of the products with at least 180 characters"
|
||||
},
|
||||
{
|
||||
"default": "Locally sourced ingredients",
|
||||
"ai_prompt": "A three word description of the products"
|
||||
},
|
||||
{
|
||||
"default": "Premium organic blends",
|
||||
"ai_prompt": "A three word description of the products"
|
||||
},
|
||||
{
|
||||
"default": "Hand-picked accessories",
|
||||
"ai_prompt": "A three word description of the products"
|
||||
},
|
||||
{
|
||||
"default": "Sustainable business practices",
|
||||
"ai_prompt": "A three word description of the products"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Meet us",
|
||||
"ai_prompt": "A two words button text to go to the product page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Testimonials 3 Columns",
|
||||
"slug": "woocommerce-blocks/testimonials-3-columns",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Eclectic finds, ethical delights",
|
||||
"ai_prompt": "Write a short title advertising a testimonial from a customer"
|
||||
},
|
||||
{
|
||||
"default": "Sip, Shop, Savor",
|
||||
"ai_prompt": "Write a short title advertising a testimonial from a customer"
|
||||
},
|
||||
{
|
||||
"default": "LOCAL LOVE",
|
||||
"ai_prompt": "Write a short title advertising a testimonial from a customer"
|
||||
},
|
||||
{
|
||||
"default": "What our customers say",
|
||||
"ai_prompt": "Write just 4 words to advertise testimonials from customers"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Transformed my daily routine with unique, eco-friendly treasures. Exceptional quality and service. Proud to support a store that aligns with my values.",
|
||||
"ai_prompt": "Write the testimonial from a customer with approximately 150 characters"
|
||||
},
|
||||
{
|
||||
"default": "The organic coffee beans are a revelation. Each sip feels like a journey. Beautifully crafted accessories add a touch of elegance to my home.",
|
||||
"ai_prompt": "Write the testimonial from a customer with approximately 150 characters"
|
||||
},
|
||||
{
|
||||
"default": "From sustainably sourced teas to chic vases, this store is a treasure trove. Love knowing my purchases contribute to a greener planet.",
|
||||
"ai_prompt": "Write the testimonial from a customer with approximately 150 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Testimonials Single",
|
||||
"slug": "woocommerce-blocks/testimonials-single",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "A ‘brewtiful’ experience :-)",
|
||||
"ai_prompt": "A two words title that advertises the testimonial"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Exceptional flavors, sustainable choices. The carefully curated collection of coffee pots and accessories turned my kitchen into a haven of style and taste.",
|
||||
"ai_prompt": "A description of the testimonial with at least 225 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Featured Category Cover Image",
|
||||
"slug": "woocommerce-blocks/featured-category-cover-image",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Sit back and relax",
|
||||
"ai_prompt": "A description for a product with at least 20 characters"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "With a wide range of designer chairs to elevate your living space.",
|
||||
"ai_prompt": "An impact phrase that advertises the products with at least 55 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop chairs",
|
||||
"ai_prompt": "A two words button text to go to the shop page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collection: Featured Products 5 Columns",
|
||||
"slug": "woocommerce-blocks/product-collection-featured-products-5-columns",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Shop new arrivals",
|
||||
"ai_prompt": "An impact phrase that advertises the newest additions to the store with at least 20 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,95 +0,0 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Assets;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Exception;
|
||||
use Automattic\Jetpack\Constants;
|
||||
/**
|
||||
* The Api class provides an interface to various asset registration helpers.
|
||||
*
|
||||
* Contains asset api methods
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Api {
|
||||
|
||||
/**
|
||||
* Stores the prefixed WC version. Used because the WC Blocks version has not been updated since the monorepo merge.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $wc_version;
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
// Use wc- prefix here to prevent collisions when WC Core version catches up to a version previously used by the WC Blocks feature plugin.
|
||||
$this->wc_version = 'wc-' . Constants::get_constant( 'WC_VERSION' );
|
||||
$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->wc_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->wc_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->wc_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->wc_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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,439 +0,0 @@
|
||||
<?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' ),
|
||||
'currentThemeIsFSETheme' => wc_current_theme_is_fse_theme(),
|
||||
'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 Deprecated. If set to true, duplicate data will be ignored if the key exists.
|
||||
* If false, duplicate data will cause an exception.
|
||||
*/
|
||||
public function add( $key, $data, $check_key_exists = false ) {
|
||||
if ( $check_key_exists ) {
|
||||
wc_deprecated_argument( 'Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry::add()', '8.9', 'The $check_key_exists parameter is no longer used: all duplicate data will be ignored if the key exists by default' );
|
||||
}
|
||||
|
||||
$this->add_data( $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.
|
||||
*/
|
||||
protected function add_data( $key, $data ) {
|
||||
if ( ! is_string( $key ) ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
|
||||
trigger_error( esc_html__( 'Key for the data being registered must be a string', 'woocommerce' ), E_USER_WARNING );
|
||||
return;
|
||||
}
|
||||
if ( $this->exists( $key ) ) {
|
||||
return;
|
||||
}
|
||||
if ( isset( $this->data[ $key ] ) ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
|
||||
trigger_error( esc_html__( 'Overriding existing data with an already registered key is not allowed', 'woocommerce' ), E_USER_WARNING );
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
<?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-types', $this->api->get_block_asset_build_path( 'wc-types' ), array(), false );
|
||||
$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-vendors-frontend', $this->api->get_block_asset_build_path( 'wc-blocks-vendors-frontend' ), array(), false );
|
||||
$this->api->register_script( 'wc-blocks-checkout', 'assets/client/blocks/blocks-checkout.js', array( 'wc-blocks-vendors-frontend' ) );
|
||||
$this->api->register_script( 'wc-blocks-components', 'assets/client/blocks/blocks-components.js', array( 'wc-blocks-vendors-frontend' ) );
|
||||
|
||||
// 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' ), dirname( __DIR__ ) ), array(), 'all', true );
|
||||
$this->register_style( 'wc-interactivity-dropdown', plugins_url( $this->api->get_block_asset_build_path( 'wc-interactivity-dropdown', 'css' ), dirname( __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 $this->api->wc_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 )
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,410 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use Automattic\WooCommerce\Blocks\Images\Pexels;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\AIContent\PatternsHelper;
|
||||
use Automattic\WooCommerce\Blocks\AIContent\UpdatePatterns;
|
||||
use Automattic\WooCommerce\Blocks\AIContent\UpdateProducts;
|
||||
|
||||
/**
|
||||
* 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',
|
||||
'featureFlag' => 'Feature Flag',
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if ( $pattern_data['featureFlag'] && ! Features::is_enabled( $pattern_data['featureFlag'] ) ) {
|
||||
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
|
||||
*/
|
||||
|
||||
$content = array();
|
||||
$images = array();
|
||||
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.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 UpdatePatterns() )->generate_content( $ai_connection, $token, $images, $business_description );
|
||||
|
||||
if ( is_wp_error( $populate_patterns ) ) {
|
||||
return $populate_patterns->get_error_message();
|
||||
}
|
||||
|
||||
$populate_products = ( new UpdateProducts() )->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 ( isset( $pattern_dictionary['slug'] ) && $pattern_dictionary['slug'] === $slug ) {
|
||||
return $pattern_dictionary;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,558 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ComingSoonTemplate;
|
||||
|
||||
/**
|
||||
* BlockTypesController class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockTemplatesController {
|
||||
|
||||
/**
|
||||
* Directory which contains all templates
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TEMPLATES_ROOT_DIR = 'templates';
|
||||
|
||||
/**
|
||||
* Initialization method.
|
||||
*/
|
||||
public function init() {
|
||||
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_template', array( $this, 'add_block_template_details' ), 10, 3 );
|
||||
add_filter( 'get_block_templates', array( $this, 'add_block_templates' ), 10, 3 );
|
||||
add_filter( 'taxonomy_template_hierarchy', array( $this, 'add_archive_product_to_eligible_for_fallback_templates' ), 10, 1 );
|
||||
add_action( 'after_switch_theme', array( $this, 'check_should_use_blockified_product_grid_templates' ), 10, 2 );
|
||||
add_filter( 'post_type_archive_title', array( $this, 'update_product_archive_title' ), 10, 2 );
|
||||
|
||||
if ( wc_current_theme_is_fse_theme() ) {
|
||||
// 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'], array( 'render_block_core_template_part', 'gutenberg_render_block_core_template_part' ), true )
|
||||
) {
|
||||
$settings['render_callback'] = array( $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'] ), array( 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = 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( ProductCatalogTemplate::SLUG, $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 && ProductCatalogTemplate::SLUG === $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,
|
||||
function ( $template_slug ) {
|
||||
return BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $template_slug );
|
||||
}
|
||||
);
|
||||
|
||||
if ( count( $templates_eligible_for_fallback ) > 0 ) {
|
||||
$template_hierarchy[] = ProductCatalogTemplate::SLUG;
|
||||
}
|
||||
|
||||
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( ProductCatalogTemplate::SLUG );
|
||||
$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 = 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 template title and description to WooCommerce templates.
|
||||
*
|
||||
* @param WP_Block_Template|null $block_template The found block template, or null if there isn't one.
|
||||
* @param string $id Template unique identifier (example: 'theme_slug//template_slug').
|
||||
* @param array $template_type Template type: 'wp_template' or 'wp_template_part'.
|
||||
* @return WP_Block_Template|null
|
||||
*/
|
||||
public function add_block_template_details( $block_template, $id, $template_type ) {
|
||||
return BlockTemplateUtils::update_template_data( $block_template, $template_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
$slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : array();
|
||||
|
||||
if ( ! BlockTemplateUtils::supports_block_templates( $template_type ) && ! in_array( ComingSoonTemplate::SLUG, $slugs, true ) ) {
|
||||
return $query_result;
|
||||
}
|
||||
|
||||
$post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';
|
||||
$template_files = $this->get_block_templates( $slugs, $template_type );
|
||||
$theme_slug = wp_get_theme()->get_stylesheet();
|
||||
|
||||
// @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 ) {
|
||||
$query_result[] = $template_file;
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_not_custom = false === array_search(
|
||||
$theme_slug . '//' . $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 ) {
|
||||
$template = BlockTemplateUtils::build_template_result_from_file( $template_file, $template_type );
|
||||
$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 );
|
||||
|
||||
// There is the chance that the user customized the default template, installed a theme with a custom template
|
||||
// and customized that one as well. When that happens, duplicates might appear in the list.
|
||||
// See: https://github.com/woocommerce/woocommerce/issues/42220.
|
||||
$query_result = BlockTemplateUtils::remove_duplicate_customized_templates( $query_result, $theme_slug );
|
||||
|
||||
/**
|
||||
* 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 ) use ( $template_type ) {
|
||||
return BlockTemplateUtils::update_template_data( $template, $template_type );
|
||||
},
|
||||
$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' ) {
|
||||
$template_files = BlockTemplateUtils::get_template_paths( $template_type );
|
||||
$templates = array();
|
||||
|
||||
foreach ( $template_files as $template_file ) {
|
||||
// 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( ProductCatalogTemplate::SLUG );
|
||||
$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( ProductCatalogTemplate::SLUG );
|
||||
$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 );
|
||||
|
||||
return array_merge( $templates_from_db, $templates_from_woo );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the product archive title to "Shop".
|
||||
*
|
||||
* Attention: this method is run in classic themes as well, so it
|
||||
* can't be moved to the ProductCatalogTemplate class. See:
|
||||
* https://github.com/woocommerce/woocommerce/pull/46429
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
use Automattic\WooCommerce\Blocks\Templates\AbstractTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\AbstractTemplatePart;
|
||||
use Automattic\WooCommerce\Blocks\Templates\MiniCartTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\CartTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\CheckoutTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\CheckoutHeaderTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ComingSoonTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ComingSoonEntireSiteTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ComingSoonStoreOnlyTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductCategoryTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductTagTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplate;
|
||||
|
||||
/**
|
||||
* BlockTemplatesRegistry class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockTemplatesRegistry {
|
||||
|
||||
/**
|
||||
* The array of registered templates.
|
||||
*
|
||||
* @var AbstractTemplate[]|AbstractTemplatePart[]
|
||||
*/
|
||||
private $templates = array();
|
||||
|
||||
/**
|
||||
* Initialization method.
|
||||
*/
|
||||
public function init() {
|
||||
if ( BlockTemplateUtils::supports_block_templates( 'wp_template' ) ) {
|
||||
$templates = array(
|
||||
ProductCatalogTemplate::SLUG => new ProductCatalogTemplate(),
|
||||
ProductCategoryTemplate::SLUG => new ProductCategoryTemplate(),
|
||||
ProductTagTemplate::SLUG => new ProductTagTemplate(),
|
||||
ProductAttributeTemplate::SLUG => new ProductAttributeTemplate(),
|
||||
ProductSearchResultsTemplate::SLUG => new ProductSearchResultsTemplate(),
|
||||
CartTemplate::SLUG => new CartTemplate(),
|
||||
CheckoutTemplate::SLUG => new CheckoutTemplate(),
|
||||
OrderConfirmationTemplate::SLUG => new OrderConfirmationTemplate(),
|
||||
SingleProductTemplate::SLUG => new SingleProductTemplate(),
|
||||
);
|
||||
} else {
|
||||
$templates = array();
|
||||
}
|
||||
if ( Features::is_enabled( 'launch-your-store' ) ) {
|
||||
$templates[ ComingSoonTemplate::SLUG ] = new ComingSoonTemplate();
|
||||
}
|
||||
if ( BlockTemplateUtils::supports_block_templates( 'wp_template_part' ) ) {
|
||||
$template_parts = array(
|
||||
MiniCartTemplate::SLUG => new MiniCartTemplate(),
|
||||
CheckoutHeaderTemplate::SLUG => new CheckoutHeaderTemplate(),
|
||||
);
|
||||
} else {
|
||||
$template_parts = array();
|
||||
}
|
||||
$this->templates = array_merge( $templates, $template_parts );
|
||||
|
||||
// Init all templates.
|
||||
foreach ( $this->templates as $template ) {
|
||||
$template->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the template matching the slug
|
||||
*
|
||||
* @param string $template_slug Slug of the template to retrieve.
|
||||
*
|
||||
* @return AbstractTemplate|AbstractTemplatePart|null
|
||||
*/
|
||||
public function get_template( $template_slug ) {
|
||||
if ( array_key_exists( $template_slug, $this->templates ) ) {
|
||||
$registered_template = $this->templates[ $template_slug ];
|
||||
return $registered_template;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,512 +0,0 @@
|
||||
<?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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interactivity namespace. Only used when utilizing the interactivity API.
|
||||
|
||||
* @return string The interactivity namespace, used to namespace interactivity API actions and state.
|
||||
*/
|
||||
protected function get_full_block_name() {
|
||||
return $this->namespace . '/' . $this->block_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
/**
|
||||
* Gives opportunity to return routes without invoking the compute intensive REST API.
|
||||
*
|
||||
* @since 8.7.0
|
||||
* @param array $routes Array of routes.
|
||||
* @param string $namespace Namespace for routes.
|
||||
* @param string $context Context, can be edit or view.
|
||||
*/
|
||||
$routes = apply_filters(
|
||||
'woocommerce_blocks_pre_get_routes_from_namespace',
|
||||
array(),
|
||||
$namespace,
|
||||
'view'
|
||||
);
|
||||
|
||||
if ( ! empty( $routes ) ) {
|
||||
return $routes;
|
||||
}
|
||||
|
||||
$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' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
<?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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,715 +0,0 @@
|
||||
<?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 ( empty( $this->attributes['contentVisibility']['image'] ) ) {
|
||||
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';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to manipulate (add/modify/remove) attributes in the HTML code of the generated add to cart button.
|
||||
*
|
||||
* @since 8.6.0
|
||||
*
|
||||
* @param array $attributes An associative array containing default HTML attributes of the add to cart button.
|
||||
* @param WC_Product $product The WC_Product instance of the product that will be added to the cart once the button is pressed.
|
||||
*
|
||||
* @return array Returns an associative array derived from the default array passed as an argument and added the extra HTML attributes.
|
||||
*/
|
||||
$attributes = apply_filters( 'woocommerce_blocks_product_grid_add_to_cart_attributes', $attributes, $product );
|
||||
|
||||
return sprintf(
|
||||
'<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 ) );
|
||||
$this->asset_data_registry->add( 'maxColumns', wc_get_theme_support( 'product_blocks::max_columns', 6 ) );
|
||||
$this->asset_data_registry->add( 'defaultColumns', wc_get_theme_support( 'product_blocks::default_columns', 3 ) );
|
||||
$this->asset_data_registry->add( 'minRows', wc_get_theme_support( 'product_blocks::min_rows', 1 ) );
|
||||
$this->asset_data_registry->add( 'maxRows', wc_get_theme_support( 'product_blocks::max_rows', 6 ) );
|
||||
$this->asset_data_registry->add( 'defaultRows', wc_get_theme_support( 'product_blocks::default_rows', 3 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
// Currently these blocks rely on the styles from the All Products block.
|
||||
return [ 'wc-blocks-style', 'wc-blocks-style-all-products' ];
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ActiveFilters class.
|
||||
*/
|
||||
class ActiveFilters extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'active-filters';
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?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 );
|
||||
$this->asset_data_registry->add( 'minColumns', wc_get_theme_support( 'product_blocks::min_columns', 1 ) );
|
||||
$this->asset_data_registry->add( 'maxColumns', wc_get_theme_support( 'product_blocks::max_columns', 6 ) );
|
||||
$this->asset_data_registry->add( 'defaultColumns', wc_get_theme_support( 'product_blocks::default_columns', 3 ) );
|
||||
$this->asset_data_registry->add( 'minRows', wc_get_theme_support( 'product_blocks::min_rows', 1 ) );
|
||||
$this->asset_data_registry->add( 'maxRows', wc_get_theme_support( 'product_blocks::max_rows', 6 ) );
|
||||
$this->asset_data_registry->add( 'defaultRows', wc_get_theme_support( 'product_blocks::default_rows', 3 ) );
|
||||
|
||||
// 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 ] );
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?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() );
|
||||
$this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ) );
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?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() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' ] );
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
|
||||
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
|
||||
|
||||
/**
|
||||
* 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() );
|
||||
$this->asset_data_registry->add( 'baseLocation', wc_get_base_location() );
|
||||
$this->asset_data_registry->add( 'isShippingCalculatorEnabled', filter_var( get_option( 'woocommerce_enable_shipping_calc' ), FILTER_VALIDATE_BOOLEAN ) );
|
||||
$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ) );
|
||||
$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ) );
|
||||
$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled() );
|
||||
$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled() );
|
||||
$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled() );
|
||||
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ) );
|
||||
$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() );
|
||||
$this->asset_data_registry->add( 'activeShippingZones', CartCheckoutUtils::get_shipping_zones() );
|
||||
$pickup_location_settings = LocalPickupUtils::get_local_pickup_settings();
|
||||
$this->asset_data_registry->add( 'localPickupEnabled', $pickup_location_settings['enabled'] );
|
||||
|
||||
// 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',
|
||||
'CartOrderSummaryTotalsBlock',
|
||||
'FilledCartBlock',
|
||||
'EmptyCartBlock',
|
||||
'CartTotalsBlock',
|
||||
'CartItemsBlock',
|
||||
'CartLineItemsBlock',
|
||||
'CartOrderSummaryBlock',
|
||||
'CartExpressPaymentBlock',
|
||||
'ProceedToCheckoutBlock',
|
||||
'CartAcceptedPaymentMethodsBlock',
|
||||
'CartOrderSummaryCouponFormBlock',
|
||||
'CartOrderSummaryDiscountBlock',
|
||||
'CartOrderSummaryFeeBlock',
|
||||
'CartOrderSummaryHeadingBlock',
|
||||
'CartOrderSummaryShippingBlock',
|
||||
'CartCrossSellsBlock',
|
||||
'CartCrossSellsProductsBlock',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartAcceptedPaymentMethodsBlock class.
|
||||
*/
|
||||
class CartAcceptedPaymentMethodsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-accepted-payment-methods-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartCrossSellsBlock class.
|
||||
*/
|
||||
class CartCrossSellsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-cross-sells-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartCrossSellsProductsBlock class.
|
||||
*/
|
||||
class CartCrossSellsProductsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-cross-sells-products-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartExpressPaymentBlock class.
|
||||
*/
|
||||
class CartExpressPaymentBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-express-payment-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartItemsBlock class.
|
||||
*/
|
||||
class CartItemsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-items-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartLineItemsBlock class.
|
||||
*/
|
||||
class CartLineItemsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-line-items-block';
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryBlock class.
|
||||
*/
|
||||
class CartOrderSummaryBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-block';
|
||||
|
||||
/**
|
||||
* Get the contents of the given inner block.
|
||||
*
|
||||
* @param string $block_name Name of the order summary inner block.
|
||||
* @param string $content The content to search.
|
||||
* @return array|bool
|
||||
*/
|
||||
private function get_inner_block_content( $block_name, $content ) {
|
||||
if ( preg_match( $this->inner_block_regex( $block_name ), $content, $matches ) ) {
|
||||
return $matches[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the regex that will return an inner block.
|
||||
*
|
||||
* @param string $block_name Name of the order summary inner block.
|
||||
* @return string Regex pattern.
|
||||
*/
|
||||
private function inner_block_regex( $block_name ) {
|
||||
return '/<div data-block-name="woocommerce\/cart-order-summary-' . $block_name . '-block"(.+?)>(.*?)<\/div>/si';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the Cart Order Summary block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param object $block Block object.
|
||||
* @return string Rendered block.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
// The order-summary-totals block was introduced as a new parent block for the totals
|
||||
// (subtotal, discount, fees, shipping and taxes) blocks.
|
||||
$regex_for_cart_order_summary_totals = '/<div data-block-name="woocommerce\/cart-order-summary-totals-block"(.+?)>/';
|
||||
$order_summary_totals_content = '<div data-block-name="woocommerce/cart-order-summary-totals-block" class="wp-block-woocommerce-cart-order-summary-totals-block">';
|
||||
|
||||
$totals_inner_blocks = array( 'subtotal', 'discount', 'fee', 'shipping', 'taxes' ); // We want to move these blocks inside a parent 'totals' block.
|
||||
|
||||
if ( preg_match( $regex_for_cart_order_summary_totals, $content ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
foreach ( $totals_inner_blocks as $key => $block_name ) {
|
||||
$inner_block_content = $this->get_inner_block_content( $block_name, $content );
|
||||
|
||||
if ( $inner_block_content ) {
|
||||
$order_summary_totals_content .= "\n" . $inner_block_content;
|
||||
|
||||
// The last block is replaced with the totals block.
|
||||
if ( count( $totals_inner_blocks ) - 1 === $key ) {
|
||||
$order_summary_totals_content .= '</div>';
|
||||
$content = preg_replace( $this->inner_block_regex( $block_name ), $order_summary_totals_content, $content );
|
||||
} else {
|
||||
// Otherwise, remove the block.
|
||||
$content = preg_replace( $this->inner_block_regex( $block_name ), '', $content );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return preg_replace( '/\n\n( *?)/i', '', $content );
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?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';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryDiscountBlock class.
|
||||
*/
|
||||
class CartOrderSummaryDiscountBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-discount-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryFeeBlock class.
|
||||
*/
|
||||
class CartOrderSummaryFeeBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-fee-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryHeadingBlock class.
|
||||
*/
|
||||
class CartOrderSummaryHeadingBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-heading-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryShippingBlock class.
|
||||
*/
|
||||
class CartOrderSummaryShippingBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-shipping-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummarySubtotalBlock class.
|
||||
*/
|
||||
class CartOrderSummarySubtotalBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-subtotal-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryTaxesBlock class.
|
||||
*/
|
||||
class CartOrderSummaryTaxesBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-taxes-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartOrderSummaryTotalsBlock class.
|
||||
*/
|
||||
class CartOrderSummaryTotalsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-order-summary-totals-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CartTotalsBlock class.
|
||||
*/
|
||||
class CartTotalsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-totals-block';
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,583 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
);
|
||||
|
||||
add_action( 'save_post', array( $this, 'update_local_pickup_title' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
<div data-block-name="woocommerce/checkout-additional-information-block" class="wp-block-woocommerce-checkout-additional-information-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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Additional Information block to checkouts missing it.
|
||||
*/
|
||||
$additional_information_inner_blocks = '$0' . PHP_EOL . PHP_EOL . '<div data-block-name="woocommerce/checkout-additional-information-block" class="wp-block-woocommerce-checkout-additional-information-block"></div>' . PHP_EOL . PHP_EOL;
|
||||
$has_additional_information_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-additional-information-block"[^>]*?>/mi';
|
||||
$has_additional_information_block = preg_match( $has_additional_information_regex, $content );
|
||||
|
||||
if ( ! $has_additional_information_block ) {
|
||||
$payment_block_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-payment-block" class="wp-block-woocommerce-checkout-payment-block"[^>]*?><\/div>/mi';
|
||||
$content = preg_replace( $payment_block_regex, $additional_information_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' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the local pickup title in WooCommerce Settings when the checkout page containing a Checkout block is saved.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
* @param \WP_Post $post The post object.
|
||||
* @return void
|
||||
*/
|
||||
public function update_local_pickup_title( $post_id, $post ) {
|
||||
|
||||
// This is not a proper save action, maybe an autosave, so don't continue.
|
||||
if ( empty( $post->post_status ) || 'inherit' === $post->post_status ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are editing the checkout page and that it contains a Checkout block.
|
||||
// Cast to string for Checkout page ID comparison because get_option can return it as a string, so better to compare both values as strings.
|
||||
if ( ! empty( $post->post_type ) && 'wp_template' !== $post->post_type && ( false === has_block( 'woocommerce/checkout', $post ) || (string) get_option( 'woocommerce_checkout_page_id' ) !== (string) $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ( ! empty( $post->post_type ) && ! empty( $post->post_name ) && 'page-checkout' !== $post->post_name && 'wp_template' === $post->post_type ) || false === has_block( 'woocommerce/checkout', $post ) ) {
|
||||
return;
|
||||
}
|
||||
$pickup_location_settings = LocalPickupUtils::get_local_pickup_settings( 'edit' );
|
||||
|
||||
if ( ! isset( $pickup_location_settings['title'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $post->post_content ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_blocks = parse_blocks( $post->post_content );
|
||||
$title = $this->find_local_pickup_text_in_checkout_block( $post_blocks );
|
||||
|
||||
if ( $title ) {
|
||||
$pickup_location_settings['title'] = $title;
|
||||
update_option( 'woocommerce_pickup_location_settings', $pickup_location_settings );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurse through the blocks to find the shipping methods block, then get the value of the localPickupText attribute from it.
|
||||
*
|
||||
* @param array $blocks The block(s) to search for the local pickup text.
|
||||
* @return null|string The local pickup text if found, otherwise void.
|
||||
*/
|
||||
private function find_local_pickup_text_in_checkout_block( $blocks ) {
|
||||
if ( ! is_array( $blocks ) ) {
|
||||
return null;
|
||||
}
|
||||
foreach ( $blocks as $block ) {
|
||||
if ( ! empty( $block['blockName'] ) && 'woocommerce/checkout-shipping-method-block' === $block['blockName'] ) {
|
||||
if ( ! empty( $block['attrs']['localPickupText'] ) ) {
|
||||
return $block['attrs']['localPickupText'];
|
||||
}
|
||||
}
|
||||
if ( ! empty( $block['innerBlocks'] ) ) {
|
||||
$answer = $this->find_local_pickup_text_in_checkout_block( $block['innerBlocks'] );
|
||||
if ( $answer ) {
|
||||
return $answer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
|
||||
$country_data = CartCheckoutUtils::get_country_data();
|
||||
$address_formats = WC()->countries->get_address_formats();
|
||||
|
||||
// Move the address format into the 'countryData' setting.
|
||||
// We need to skip 'default' because that's not a valid country.
|
||||
foreach ( $address_formats as $country_code => $format ) {
|
||||
if ( 'default' === $country_code ) {
|
||||
continue;
|
||||
}
|
||||
$country_data[ $country_code ]['format'] = $format;
|
||||
}
|
||||
|
||||
$this->asset_data_registry->add( 'countryData', $country_data );
|
||||
$this->asset_data_registry->add( 'defaultAddressFormat', $address_formats['default'] );
|
||||
$this->asset_data_registry->add( 'baseLocation', wc_get_base_location() );
|
||||
$this->asset_data_registry->add(
|
||||
'checkoutAllowsGuest',
|
||||
false === filter_var(
|
||||
wc()->checkout()->is_registration_required(),
|
||||
FILTER_VALIDATE_BOOLEAN
|
||||
)
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'checkoutAllowsSignup',
|
||||
filter_var(
|
||||
wc()->checkout()->is_registration_enabled(),
|
||||
FILTER_VALIDATE_BOOLEAN
|
||||
)
|
||||
);
|
||||
$this->asset_data_registry->add( 'checkoutShowLoginReminder', filter_var( get_option( 'woocommerce_enable_checkout_login_reminder' ), FILTER_VALIDATE_BOOLEAN ) );
|
||||
$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ) );
|
||||
$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ) );
|
||||
$this->asset_data_registry->add( 'forcedBillingAddress', 'billing_only' === get_option( 'woocommerce_ship_to_destination' ) );
|
||||
$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled() );
|
||||
$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled() );
|
||||
$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled() );
|
||||
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ) );
|
||||
$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() );
|
||||
|
||||
$pickup_location_settings = LocalPickupUtils::get_local_pickup_settings();
|
||||
$this->asset_data_registry->add( 'localPickupEnabled', $pickup_location_settings['enabled'] );
|
||||
$this->asset_data_registry->add( 'localPickupText', $pickup_location_settings['title'] );
|
||||
|
||||
$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' ) ) {
|
||||
$this->asset_data_registry->add( 'activeShippingZones', CartCheckoutUtils::get_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',
|
||||
'CheckoutAdditionalInformationBlock',
|
||||
'CheckoutBillingAddressBlock',
|
||||
'CheckoutContactInformationBlock',
|
||||
'CheckoutExpressPaymentBlock',
|
||||
'CheckoutFieldsBlock',
|
||||
'CheckoutOrderNoteBlock',
|
||||
'CheckoutOrderSummaryBlock',
|
||||
'CheckoutOrderSummaryCartItemsBlock',
|
||||
'CheckoutOrderSummaryCouponFormBlock',
|
||||
'CheckoutOrderSummaryDiscountBlock',
|
||||
'CheckoutOrderSummaryFeeBlock',
|
||||
'CheckoutOrderSummaryShippingBlock',
|
||||
'CheckoutOrderSummarySubtotalBlock',
|
||||
'CheckoutOrderSummaryTaxesBlock',
|
||||
'CheckoutOrderSummaryTotalsBlock',
|
||||
'CheckoutPaymentBlock',
|
||||
'CheckoutShippingAddressBlock',
|
||||
'CheckoutShippingMethodsBlock',
|
||||
'CheckoutShippingMethodBlock',
|
||||
'CheckoutPickupOptionsBlock',
|
||||
'CheckoutTermsBlock',
|
||||
'CheckoutTotalsBlock',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutActionsBlock class.
|
||||
*/
|
||||
class CheckoutActionsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-actions-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutAdditionalInformationBlock class.
|
||||
*/
|
||||
class CheckoutAdditionalInformationBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-additional-information-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutBillingAddressBlock class.
|
||||
*/
|
||||
class CheckoutBillingAddressBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-billing-address-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutContactInformationBlock class.
|
||||
*/
|
||||
class CheckoutContactInformationBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-contact-information-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutExpressPaymentBlock class.
|
||||
*/
|
||||
class CheckoutExpressPaymentBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-express-payment-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutFieldsBlock class.
|
||||
*/
|
||||
class CheckoutFieldsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-fields-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderNoteBlock class.
|
||||
*/
|
||||
class CheckoutOrderNoteBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-note-block';
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-block';
|
||||
|
||||
/**
|
||||
* Get the contents of the given inner block.
|
||||
*
|
||||
* @param string $block_name Name of the order summary inner block.
|
||||
* @param string $content The content to search.
|
||||
* @return array|bool
|
||||
*/
|
||||
private function get_inner_block_content( $block_name, $content ) {
|
||||
if ( preg_match( $this->inner_block_regex( $block_name ), $content, $matches ) ) {
|
||||
return $matches[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the regex that will return an inner block.
|
||||
*
|
||||
* @param string $block_name Name of the order summary inner block.
|
||||
* @return string Regex pattern.
|
||||
*/
|
||||
private function inner_block_regex( $block_name ) {
|
||||
return '/<div data-block-name="woocommerce\/checkout-order-summary-' . $block_name . '-block"(.+?)>(.*?)<\/div>/si';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the Checkout Order Summary block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @param object $block Block object.
|
||||
* @return string Rendered block.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
// The order-summary-totals block was introduced as a new parent block for the totals
|
||||
// (subtotal, discount, fees, shipping and taxes) blocks.
|
||||
$regex_for_checkout_order_summary_totals = '/<div data-block-name="woocommerce\/checkout-order-summary-totals-block"(.+?)>/';
|
||||
$order_summary_totals_content = '<div data-block-name="woocommerce/checkout-order-summary-totals-block" class="wp-block-woocommerce-checkout-order-summary-totals-block">';
|
||||
|
||||
// We want to move these blocks inside a parent 'totals' block.
|
||||
$totals_inner_blocks = array( 'subtotal', 'discount', 'fee', 'shipping', 'taxes' );
|
||||
|
||||
if ( preg_match( $regex_for_checkout_order_summary_totals, $content ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
foreach ( $totals_inner_blocks as $key => $block_name ) {
|
||||
$inner_block_content = $this->get_inner_block_content( $block_name, $content );
|
||||
|
||||
if ( $inner_block_content ) {
|
||||
$order_summary_totals_content .= "\n" . $inner_block_content;
|
||||
|
||||
// The last block is replaced with the totals block.
|
||||
if ( count( $totals_inner_blocks ) - 1 === $key ) {
|
||||
$order_summary_totals_content .= '</div>';
|
||||
$content = preg_replace( $this->inner_block_regex( $block_name ), $order_summary_totals_content, $content );
|
||||
} else {
|
||||
// Otherwise, remove the block.
|
||||
$content = preg_replace( $this->inner_block_regex( $block_name ), '', $content );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty lines.
|
||||
return preg_replace( '/\n\n( *?)/i', '', $content );
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?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';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?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';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryDiscountBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryDiscountBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-discount-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryFeeBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryFeeBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-fee-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryShippingBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryShippingBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-shipping-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummarySubtotalBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummarySubtotalBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-subtotal-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryTaxesBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryTaxesBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-taxes-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutOrderSummaryTotalsBlock class.
|
||||
*/
|
||||
class CheckoutOrderSummaryTotalsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-order-summary-totals-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutPaymentBlock class.
|
||||
*/
|
||||
class CheckoutPaymentBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-payment-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutPickupOptionsBlock class.
|
||||
*/
|
||||
class CheckoutPickupOptionsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-pickup-options-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutShippingAddressBlock class.
|
||||
*/
|
||||
class CheckoutShippingAddressBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-shipping-address-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutShippingMethodBlock class.
|
||||
*/
|
||||
class CheckoutShippingMethodBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-shipping-method-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutShippingMethodsBlock class.
|
||||
*/
|
||||
class CheckoutShippingMethodsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-shipping-methods-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutTermsBlock class.
|
||||
*/
|
||||
class CheckoutTermsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-terms-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* CheckoutTotalsBlock class.
|
||||
*/
|
||||
class CheckoutTotalsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-totals-block';
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductCategoryTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductTagTemplate;
|
||||
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';
|
||||
|
||||
/**
|
||||
* Initialize this block.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
add_filter( 'render_block', array( $this, 'add_alignment_class_to_wrapper' ), 10, 2 );
|
||||
add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
|
||||
// Indicate to interactivity powered components that this block is on the page,
|
||||
// and needs refresh to update data.
|
||||
$this->asset_data_registry->add( 'needsRefreshForInteractivityAPI', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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::SLUG === $attributes['template'] ) {
|
||||
return $this->render_order_received();
|
||||
}
|
||||
|
||||
if ( is_product() ) {
|
||||
return $this->render_single_product();
|
||||
}
|
||||
|
||||
$valid = false;
|
||||
$archive_templates = array(
|
||||
ProductCatalogTemplate::SLUG,
|
||||
ProductCategoryTemplate::SLUG,
|
||||
ProductTagTemplate::SLUG,
|
||||
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 );
|
||||
|
||||
// Set this so filter blocks being used as widgets know when to render.
|
||||
$this->asset_data_registry->add( 'hasFilterableProducts', 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ComingSoon class.
|
||||
*/
|
||||
class ComingSoon extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'coming-soon';
|
||||
|
||||
/**
|
||||
* 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 ] );
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockHooksTrait;
|
||||
|
||||
/**
|
||||
* CustomerAccount class.
|
||||
*/
|
||||
class CustomerAccount extends AbstractBlock {
|
||||
use BlockHooksTrait;
|
||||
|
||||
const TEXT_ONLY = 'text_only';
|
||||
const ICON_ONLY = 'icon_only';
|
||||
const DISPLAY_ALT = 'alt';
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'customer-account';
|
||||
|
||||
/**
|
||||
* Block Hook API placements.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hooked_block_placements = array(
|
||||
array(
|
||||
'position' => 'after',
|
||||
'anchor' => 'core/navigation',
|
||||
'area' => 'header',
|
||||
'callback' => 'should_unhook_block',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
/**
|
||||
* The hooked_block_{$hooked_block_type} filter was added in WordPress 6.5.
|
||||
* We are the only code adding the filter 'hooked_block_woocommerce/customer-account'.
|
||||
* Using has_filter() for a compatibility check won't work because add_filter() is used in the same file.
|
||||
*/
|
||||
if ( version_compare( get_bloginfo( 'version' ), '6.5', '>=' ) ) {
|
||||
add_filter( 'hooked_block_woocommerce/customer-account', array( $this, 'modify_hooked_block_attributes' ), 10, 5 );
|
||||
add_filter( 'hooked_block_types', array( $this, 'register_hooked_block' ), 9, 4 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the Block Hooks API to modify the attributes of the hooked block.
|
||||
*
|
||||
* @param array|null $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block.
|
||||
* @param string $hooked_block_type The hooked block type name.
|
||||
* @param string $relative_position The relative position of the hooked block.
|
||||
* @param array $parsed_anchor_block The anchor block, in parsed block array format.
|
||||
* @param WP_Block_Template|WP_Post|array $context The block template, template part, `wp_navigation` post type,
|
||||
* or pattern that the anchor block belongs to.
|
||||
* @return array|null
|
||||
*/
|
||||
public function modify_hooked_block_attributes( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context ) {
|
||||
$parsed_hooked_block['attrs']['displayStyle'] = 'icon_only';
|
||||
|
||||
/*
|
||||
* The Mini Cart block (which is hooked into the header) has a margin of 0.5em on the left side.
|
||||
* We want to match that margin for the Customer Account block so it looks consistent.
|
||||
*/
|
||||
$parsed_hooked_block['attrs']['style']['spacing']['margin']['left'] = '0.5em';
|
||||
return $parsed_hooked_block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the Block Hooks API to determine if the block should be auto-inserted.
|
||||
*
|
||||
* @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 array|\WP_Post|\WP_Block_Template $context Where the block is embedded.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function should_unhook_block( $hooked_blocks, $position, $anchor_block, $context ) {
|
||||
$block_name = $this->namespace . '/' . $this->block_name;
|
||||
$block_is_hooked = in_array( $block_name, $hooked_blocks, true );
|
||||
|
||||
if ( $block_is_hooked ) {
|
||||
$active_theme = wp_get_theme()->get( 'Name' );
|
||||
$exclude_themes = array( 'Twenty Twenty-Two', 'Twenty Twenty-Three' );
|
||||
|
||||
if ( in_array( $active_theme, $exclude_themes, true ) ) {
|
||||
$key = array_search( $block_name, $hooked_blocks, true );
|
||||
unset( $hooked_blocks[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $hooked_blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
),
|
||||
);
|
||||
|
||||
$label_markup = self::ICON_ONLY === $attributes['displayStyle'] ? '' : '<span class="label">' . wp_kses( $this->render_label(), array() ) . '</span>';
|
||||
|
||||
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 ) . $label_markup . '
|
||||
</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.
|
||||
*
|
||||
* @return string Label to render on the block.
|
||||
*/
|
||||
private function render_label() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* EmptyCartBlock class.
|
||||
*/
|
||||
class EmptyCartBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'empty-cart-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* EmptyMiniCartContentsBlock class.
|
||||
*/
|
||||
class EmptyMiniCartContentsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'empty-mini-cart-contents-block';
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
<?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 ) );
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
<?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 || ( 'publish' !== $product->get_status() && ! current_user_can( 'read_product', $id ) ) ) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* FilledCartBlock class.
|
||||
*/
|
||||
class FilledCartBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'filled-cart-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* FilledMiniCartContentsBlock class.
|
||||
*/
|
||||
class FilledMiniCartContentsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'filled-mini-cart-contents-block';
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* FilterWrapper 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;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?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 ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,604 +0,0 @@
|
||||
<?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;
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockHooksTrait;
|
||||
|
||||
/**
|
||||
* Mini-Cart class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MiniCart extends AbstractBlock {
|
||||
use BlockHooksTrait;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Block Hook API placements.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hooked_block_placements = array(
|
||||
array(
|
||||
'position' => 'after',
|
||||
'anchor' => 'core/navigation',
|
||||
'area' => 'header',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetApi $asset_api Instance of the asset API.
|
||||
* @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
|
||||
* @param IntegrationRegistry $integration_registry Instance of the integration registry.
|
||||
*/
|
||||
public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry, IntegrationRegistry $integration_registry ) {
|
||||
parent::__construct( $asset_api, $asset_data_registry, $integration_registry, $this->block_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
add_action( 'wp_loaded', array( $this, 'register_empty_cart_message_block_pattern' ) );
|
||||
add_action( 'wp_print_footer_scripts', array( $this, 'print_lazy_load_scripts' ), 2 );
|
||||
add_filter( 'hooked_block_types', array( $this, 'register_hooked_block' ), 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
|
||||
$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 = get_block_template( $template_slug_to_load . '//mini-cart', 'wp_template_part' );
|
||||
|
||||
if ( $template_part && ! empty( $template_part->content ) ) {
|
||||
$template_part_contents = do_blocks( $template_part->content );
|
||||
}
|
||||
|
||||
if ( '' === $template_part_contents ) {
|
||||
$template_part_contents = do_blocks(
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
file_get_contents( Package::get_path() . 'templates/' . BlockTemplateUtils::DIRECTORY_NAMES['TEMPLATE_PARTS'] . '/mini-cart.html' )
|
||||
);
|
||||
}
|
||||
|
||||
return '<div class="' . esc_attr( $wrapper_classes ) . '" style="' . esc_attr( $wrapper_styles ) . '">
|
||||
<button class="wc-block-mini-cart__button">' . $button_html . '</button>
|
||||
<div class="is-loading wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true">
|
||||
<div class="wc-block-mini-cart__drawer wc-block-components-drawer">
|
||||
<div class="wc-block-components-drawer__content">
|
||||
<div class="wc-block-mini-cart__template-part">'
|
||||
. wp_kses_post( $template_part_contents ) .
|
||||
'</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the main instance of WC_Cart class.
|
||||
*
|
||||
* @return \WC_Cart CartController class instance.
|
||||
*/
|
||||
protected function get_cart_instance() {
|
||||
$cart = WC()->cart;
|
||||
|
||||
if ( $cart && $cart instanceof \WC_Cart ) {
|
||||
return $cart;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array with data for handle the tax label.
|
||||
* the entire logic of this function is was taken from:
|
||||
* https://github.com/woocommerce/woocommerce/blob/e730f7463c25b50258e97bf56e31e9d7d3bc7ae7/includes/class-wc-cart.php#L1582
|
||||
*
|
||||
* @return array;
|
||||
*/
|
||||
protected function get_tax_label() {
|
||||
$cart = $this->get_cart_instance();
|
||||
|
||||
if ( $cart && $cart->display_prices_including_tax() ) {
|
||||
if ( ! wc_prices_include_tax() ) {
|
||||
$tax_label = WC()->countries->inc_tax_or_vat();
|
||||
$display_cart_prices_including_tax = true;
|
||||
return array(
|
||||
'tax_label' => $tax_label,
|
||||
'display_cart_prices_including_tax' => $display_cart_prices_including_tax,
|
||||
);
|
||||
}
|
||||
return array(
|
||||
'tax_label' => '',
|
||||
'display_cart_prices_including_tax' => true,
|
||||
);
|
||||
}
|
||||
|
||||
if ( wc_prices_include_tax() ) {
|
||||
$tax_label = WC()->countries->ex_tax_or_vat();
|
||||
return array(
|
||||
'tax_label' => $tax_label,
|
||||
'display_cart_prices_including_tax' => false,
|
||||
);
|
||||
};
|
||||
|
||||
return array(
|
||||
'tax_label' => '',
|
||||
'display_cart_prices_including_tax' => false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare translations for inner blocks and dependencies.
|
||||
*/
|
||||
protected function get_inner_blocks_translations() {
|
||||
$wp_scripts = wp_scripts();
|
||||
$translations = array();
|
||||
|
||||
$chunks = $this->get_chunks_paths( $this->chunks_folder );
|
||||
$vendor_chunks = $this->get_chunks_paths( 'vendors--mini-cart-contents-block' );
|
||||
$shared_chunks = [ 'cart-blocks/cart-line-items--mini-cart-contents-block/products-table-frontend' ];
|
||||
|
||||
foreach ( array_merge( $chunks, $vendor_chunks, $shared_chunks ) as $chunk ) {
|
||||
$handle = 'wc-blocks-' . $chunk . '-chunk';
|
||||
$this->asset_api->register_script( $handle, $this->asset_api->get_block_asset_build_path( $chunk ), [], true );
|
||||
$translations[] = $wp_scripts->print_translations( $handle, false );
|
||||
wp_deregister_script( $handle );
|
||||
}
|
||||
|
||||
$translations = array_filter( $translations );
|
||||
|
||||
return implode( '', $translations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block pattern for Empty Cart Message to make it translatable.
|
||||
*/
|
||||
public function register_empty_cart_message_block_pattern() {
|
||||
register_block_pattern(
|
||||
'woocommerce/mini-cart-empty-cart-message',
|
||||
array(
|
||||
'title' => __( 'Empty Mini-Cart Message', 'woocommerce' ),
|
||||
'inserter' => false,
|
||||
'content' => '<!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center"><strong>' . __( 'Your cart is currently empty!', 'woocommerce' ) . '</strong></p><!-- /wp:paragraph -->',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the Mini-Cart should be rendered or not.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_not_render_mini_cart( array $attributes ) {
|
||||
return isset( $attributes['cartAndCheckoutRenderStyle'] ) && 'hidden' !== $attributes['cartAndCheckoutRenderStyle'];
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartCartButtonBlock class.
|
||||
*/
|
||||
class MiniCartCartButtonBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-cart-button-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartCheckoutButtonBlock class.
|
||||
*/
|
||||
class MiniCartCheckoutButtonBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-checkout-button-block';
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartFooterBlock class.
|
||||
*/
|
||||
class MiniCartFooterBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-footer-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartItemsBlock class.
|
||||
*/
|
||||
class MiniCartItemsBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-items-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartProductsTableBlock class.
|
||||
*/
|
||||
class MiniCartProductsTableBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-products-table-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartShoppingButtonBlock class.
|
||||
*/
|
||||
class MiniCartShoppingButtonBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-shopping-button-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartTitleBlock class.
|
||||
*/
|
||||
class MiniCartTitleBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-title-block';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?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';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* MiniCartTitleLabelBlock class.
|
||||
*/
|
||||
class MiniCartTitleLabelBlock extends AbstractInnerBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart-title-label-block';
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
<?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 -->',
|
||||
)
|
||||
);
|
||||
|
||||
register_block_pattern(
|
||||
'woocommerce/order-confirmation-additional-fields-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__( 'Additional information', 'woocommerce' ) . '</h3><!-- /wp:heading -->',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render custom fields for the order.
|
||||
*
|
||||
* @param array $fields List of additional fields with values.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_additional_fields( $fields ) {
|
||||
if ( empty( $fields ) ) {
|
||||
return '';
|
||||
}
|
||||
return '<dl class="wc-block-components-additional-fields-list">' . implode( '', array_map( array( $this, 'render_additional_field' ), $fields ) ) . '</dl>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render custom field row.
|
||||
*
|
||||
* @param array $field An additional field and value.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_additional_field( $field ) {
|
||||
return sprintf(
|
||||
'<dt>%1$s</dt><dd>%2$s</dd>',
|
||||
esc_html( $field['label'] ),
|
||||
esc_html( $field['value'] )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
|
||||
|
||||
/**
|
||||
* AdditionalFields class.
|
||||
*/
|
||||
class AdditionalFields extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-additional-fields';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
$controller = Package::container()->get( CheckoutFields::class );
|
||||
$content .= $this->render_additional_fields(
|
||||
$controller->filter_fields_for_order_confirmation(
|
||||
array_merge(
|
||||
$controller->get_order_additional_fields_with_values( $order, 'contact', 'other', 'view' ),
|
||||
$controller->get_order_additional_fields_with_values( $order, 'order', 'other', 'view' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
|
||||
|
||||
/**
|
||||
* AdditionalFieldsWrapper class.
|
||||
*/
|
||||
class AdditionalFieldsWrapper extends AbstractOrderConfirmationBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'order-confirmation-additional-fields-wrapper';
|
||||
|
||||
/**
|
||||
* 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 = '' ) {
|
||||
if ( ! $permission ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Contact and additional fields are currently grouped in this section.
|
||||
$additional_fields = array_merge(
|
||||
Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'contact' ),
|
||||
Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'order' )
|
||||
);
|
||||
|
||||
return empty( $additional_fields ) ? '' : $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( 'additionalFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'order' ) );
|
||||
$this->asset_data_registry->add( 'additionalContactFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'contact' ) );
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
|
||||
|
||||
/**
|
||||
* 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>' : '';
|
||||
|
||||
$controller = Package::container()->get( CheckoutFields::class );
|
||||
$custom = $this->render_additional_fields(
|
||||
$controller->get_order_additional_fields_with_values( $order, 'address', 'billing', 'view' )
|
||||
);
|
||||
|
||||
return $address . $phone . $custom;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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( 'additionalAddressFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'address' ) );
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
|
||||
|
||||
/**
|
||||
* 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>' : '';
|
||||
|
||||
$controller = Package::container()->get( CheckoutFields::class );
|
||||
$custom = $this->render_additional_fields(
|
||||
$controller->get_order_additional_fields_with_values( $order, 'address', 'shipping', 'view' )
|
||||
);
|
||||
|
||||
return $address . $phone . $custom;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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( 'additionalAddressFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'address' ) );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user