plugin updates
This commit is contained in:
@@ -2,16 +2,15 @@
|
||||
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;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\Patterns\PatternRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Patterns\PTKPatternsStore;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Registers patterns under the `./patterns/` directory and from the PTK API and updates their content.
|
||||
* Each pattern from core is defined as a PHP file and defines its metadata using plugin-style headers.
|
||||
* The minimum required definition is:
|
||||
*
|
||||
* /**
|
||||
@@ -35,82 +34,69 @@ use Automattic\WooCommerce\Blocks\AIContent\UpdateProducts;
|
||||
* @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.
|
||||
* Path to the patterns' directory.
|
||||
*
|
||||
* @var string $patterns_path
|
||||
*/
|
||||
private $patterns_path;
|
||||
private string $patterns_path;
|
||||
|
||||
/**
|
||||
* PatternRegistry instance.
|
||||
*
|
||||
* @var PatternRegistry $pattern_registry
|
||||
*/
|
||||
private PatternRegistry $pattern_registry;
|
||||
|
||||
/**
|
||||
* Patterns dictionary
|
||||
*
|
||||
* @var array|WP_Error
|
||||
*/
|
||||
private $dictionary;
|
||||
|
||||
/**
|
||||
* PTKPatternsStore instance.
|
||||
*
|
||||
* @var PTKPatternsStore $ptk_patterns_store
|
||||
*/
|
||||
private PTKPatternsStore $ptk_patterns_store;
|
||||
|
||||
/**
|
||||
* Constructor for class
|
||||
*
|
||||
* @param Package $package An instance of Package.
|
||||
* @param Package $package An instance of Package.
|
||||
* @param PatternRegistry $pattern_registry An instance of PatternRegistry.
|
||||
* @param PTKPatternsStore $ptk_patterns_store An instance of PTKPatternsStore.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
$this->patterns_path = $package->get_path( 'patterns' );
|
||||
public function __construct(
|
||||
Package $package,
|
||||
PatternRegistry $pattern_registry,
|
||||
PTKPatternsStore $ptk_patterns_store
|
||||
) {
|
||||
$this->patterns_path = $package->get_path( 'patterns' );
|
||||
$this->pattern_registry = $pattern_registry;
|
||||
$this->ptk_patterns_store = $ptk_patterns_store;
|
||||
|
||||
$this->dictionary = PatternsHelper::get_patterns_dictionary();
|
||||
|
||||
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' ) );
|
||||
|
||||
if ( Features::is_enabled( 'pattern-toolkit-full-composability' ) ) {
|
||||
add_action( 'init', array( $this, 'register_ptk_patterns' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the 'woocommerce_blocks_allow_ai_connection' option is set to true if the site is connected to AI.
|
||||
* Register block patterns from core.
|
||||
*
|
||||
* @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, true );
|
||||
}
|
||||
|
||||
$token = $ai_connection->get_jwt_token( $site_id );
|
||||
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return update_option( 'woocommerce_blocks_allow_ai_connection', false, true );
|
||||
}
|
||||
|
||||
return update_option( 'woocommerce_blocks_allow_ai_connection', true, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the block patterns and categories under `./patterns/`.
|
||||
* @return void
|
||||
*/
|
||||
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',
|
||||
@@ -132,279 +118,38 @@ class BlockPatterns {
|
||||
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 );
|
||||
$this->pattern_registry->register_block_pattern( $file, $pattern_data, $this->dictionary );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the patterns content when the store description is changed.
|
||||
* Register patterns from the Patterns Toolkit.
|
||||
*
|
||||
* @param string $option The option name.
|
||||
* @param string $value The option value.
|
||||
* @return void
|
||||
*/
|
||||
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 ) {
|
||||
public function register_ptk_patterns() {
|
||||
// Only if the user has allowed tracking, we register the patterns from the PTK.
|
||||
$allow_tracking = 'yes' === get_option( 'woocommerce_allow_tracking' );
|
||||
if ( ! $allow_tracking ) {
|
||||
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' )
|
||||
$patterns = $this->ptk_patterns_store->get_patterns();
|
||||
if ( empty( $patterns ) ) {
|
||||
wc_get_logger()->warning(
|
||||
__( 'Empty patterns received from the PTK Pattern Store', 'woocommerce' ),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$ai_connection = new Connection();
|
||||
foreach ( $patterns as $pattern ) {
|
||||
$pattern['slug'] = $pattern['name'];
|
||||
$pattern['content'] = $pattern['html'];
|
||||
|
||||
$site_id = $ai_connection->get_site_id();
|
||||
|
||||
if ( is_wp_error( $site_id ) ) {
|
||||
return $site_id->get_error_message();
|
||||
$this->pattern_registry->register_block_pattern( $pattern['ID'], $pattern, $this->dictionary );
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ComingSoonTemplate;
|
||||
|
||||
/**
|
||||
* BlockTypesController class.
|
||||
* BlockTemplatesController class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@@ -29,7 +29,6 @@ class BlockTemplatesController {
|
||||
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.
|
||||
@@ -531,28 +530,4 @@ class BlockTemplatesController {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use Automattic\WooCommerce\Blocks\Templates\ProductCategoryTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductTagTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductFiltersOverlayTemplate;
|
||||
|
||||
/**
|
||||
* BlockTemplatesRegistry class.
|
||||
@@ -58,8 +59,9 @@ class BlockTemplatesRegistry {
|
||||
}
|
||||
if ( BlockTemplateUtils::supports_block_templates( 'wp_template_part' ) ) {
|
||||
$template_parts = array(
|
||||
MiniCartTemplate::SLUG => new MiniCartTemplate(),
|
||||
CheckoutHeaderTemplate::SLUG => new CheckoutHeaderTemplate(),
|
||||
MiniCartTemplate::SLUG => new MiniCartTemplate(),
|
||||
CheckoutHeaderTemplate::SLUG => new CheckoutHeaderTemplate(),
|
||||
ProductFiltersOverlayTemplate::SLUG => new ProductFiltersOverlayTemplate(),
|
||||
);
|
||||
} else {
|
||||
$template_parts = array();
|
||||
|
||||
@@ -6,6 +6,7 @@ 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;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
|
||||
/**
|
||||
* AbstractBlock class.
|
||||
@@ -239,7 +240,7 @@ abstract class AbstractBlock {
|
||||
$block_settings['style'] = null;
|
||||
add_filter(
|
||||
'render_block',
|
||||
function( $html, $block ) use ( $style_handles ) {
|
||||
function ( $html, $block ) use ( $style_handles ) {
|
||||
if ( $block['blockName'] === $this->get_block_type() ) {
|
||||
array_map( 'wp_enqueue_style', $style_handles );
|
||||
}
|
||||
@@ -434,24 +435,35 @@ abstract class AbstractBlock {
|
||||
}
|
||||
|
||||
if ( ! $this->asset_data_registry->exists( 'wcBlocksConfig' ) ) {
|
||||
$wc_blocks_config = [
|
||||
'pluginUrl' => plugins_url( '/', dirname( __DIR__, 2 ) ),
|
||||
'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' ),
|
||||
];
|
||||
if ( is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$wc_blocks_config = array_merge(
|
||||
$wc_blocks_config,
|
||||
[
|
||||
// Note that while we don't have a consolidated way of doing feature-flagging
|
||||
// we are borrowing from the WC Admin Features implementation. Also note we cannot
|
||||
// use the wcAdminFeatures global because it's not always enqueued in the context of blocks.
|
||||
'experimentalBlocksEnabled' => Features::is_enabled( 'experimental-blocks' ),
|
||||
'productCount' => array_sum( (array) wp_count_posts( 'product' ) ),
|
||||
]
|
||||
);
|
||||
}
|
||||
$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' ),
|
||||
]
|
||||
$wc_blocks_config
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ class Totals extends AbstractOrderConfirmationBlock {
|
||||
'<p class="wc-block-order-confirmation-order-note__label">' .
|
||||
esc_html__( 'Note:', 'woocommerce' ) .
|
||||
'</p>' .
|
||||
'<p>' . wp_kses_post( nl2br( wptexturize( $order->get_customer_note() ) ) ) . '</p>' .
|
||||
'<p>' . wp_kses( nl2br( wptexturize( $order->get_customer_note() ) ), [] ) . '</p>' .
|
||||
'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,12 +276,14 @@ class ProductCollection extends AbstractBlock {
|
||||
static $dirty_enhanced_queries = array();
|
||||
static $render_product_collection_callback = null;
|
||||
|
||||
$block_name = $parsed_block['blockName'];
|
||||
$force_page_reload_global =
|
||||
$block_name = $parsed_block['blockName'];
|
||||
$is_product_collection_block = $parsed_block['attrs']['query']['isProductCollectionBlock'] ?? false;
|
||||
$force_page_reload_global =
|
||||
$parsed_block['attrs']['forcePageReload'] ?? false &&
|
||||
isset( $block['attrs']['queryId'] );
|
||||
|
||||
if (
|
||||
$is_product_collection_block &&
|
||||
'woocommerce/product-collection' === $block_name &&
|
||||
! $force_page_reload_global
|
||||
) {
|
||||
|
||||
@@ -13,7 +13,7 @@ class ProductFilters extends AbstractBlock {
|
||||
protected $block_name = 'product-filters';
|
||||
|
||||
/**
|
||||
* Register the context
|
||||
* Register the context.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductFiltersOverlay class.
|
||||
*/
|
||||
class ProductFiltersOverlay extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-filters-overlay';
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include and render the block.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param string $content Block content. Default empty string.
|
||||
* @param WP_Block $block Block instance.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
ob_start();
|
||||
printf( '<div>%s</div>', esc_html__( 'Filters Overlay', 'woocommerce' ) );
|
||||
$html = ob_get_clean();
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
|
||||
@@ -48,6 +48,7 @@ final class BlockTypesController {
|
||||
*/
|
||||
protected function init() {
|
||||
add_action( 'init', array( $this, 'register_blocks' ) );
|
||||
add_filter( 'block_categories_all', array( $this, 'register_block_categories' ), 10, 2 );
|
||||
add_filter( 'render_block', array( $this, 'add_data_attributes' ), 10, 2 );
|
||||
add_action( 'woocommerce_login_form_end', array( $this, 'redirect_to_field' ) );
|
||||
add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_legacy_widgets_with_block_equivalent' ) );
|
||||
@@ -107,6 +108,29 @@ final class BlockTypesController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block categories
|
||||
*
|
||||
* Used in combination with the `block_categories_all` filter, to append
|
||||
* WooCommerce Blocks related categories to the Gutenberg editor.
|
||||
*
|
||||
* @param array $categories The array of already registered categories.
|
||||
*/
|
||||
public function register_block_categories( $categories ) {
|
||||
$woocommerce_block_categories = array(
|
||||
array(
|
||||
'slug' => 'woocommerce',
|
||||
'title' => __( 'WooCommerce', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'slug' => 'woocommerce-product-elements',
|
||||
'title' => __( 'WooCommerce Product Elements', 'woocommerce' ),
|
||||
),
|
||||
);
|
||||
|
||||
return array_merge( $categories, $woocommerce_block_categories );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data- attributes to blocks when rendered if the block is under the woocommerce/ namespace.
|
||||
*
|
||||
@@ -297,9 +321,12 @@ final class BlockTypesController {
|
||||
MiniCartContents::get_mini_cart_block_types()
|
||||
);
|
||||
|
||||
if ( Package::feature()->is_experimental_build() ) {
|
||||
// Update plugins/woocommerce-blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md
|
||||
// when modifying this list.
|
||||
if ( Features::is_enabled( 'experimental-blocks' ) ) {
|
||||
$block_types[] = 'ProductFilter';
|
||||
$block_types[] = 'ProductFilters';
|
||||
$block_types[] = 'ProductFiltersOverlay';
|
||||
$block_types[] = 'ProductFilterStockStatus';
|
||||
$block_types[] = 'ProductFilterPrice';
|
||||
$block_types[] = 'ProductFilterAttribute';
|
||||
@@ -318,6 +345,7 @@ final class BlockTypesController {
|
||||
'AllProducts',
|
||||
'Cart',
|
||||
'Checkout',
|
||||
'ProductGallery',
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -348,6 +376,7 @@ final class BlockTypesController {
|
||||
'OrderConfirmation\AdditionalInformation',
|
||||
'OrderConfirmation\AdditionalFieldsWrapper',
|
||||
'OrderConfirmation\AdditionalFields',
|
||||
'ProductGallery',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ use Automattic\WooCommerce\Blocks\BlockPatterns;
|
||||
use Automattic\WooCommerce\Blocks\BlockTemplatesRegistry;
|
||||
use Automattic\WooCommerce\Blocks\BlockTemplatesController;
|
||||
use Automattic\WooCommerce\Blocks\BlockTypesController;
|
||||
use Automattic\WooCommerce\Blocks\Patterns\AIPatterns;
|
||||
use Automattic\WooCommerce\Blocks\Patterns\PatternRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Patterns\PTKClient;
|
||||
use Automattic\WooCommerce\Blocks\Patterns\PTKPatternsStore;
|
||||
use Automattic\WooCommerce\Blocks\QueryFilters;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Notices;
|
||||
@@ -109,7 +113,7 @@ class Bootstrap {
|
||||
|
||||
add_action(
|
||||
'admin_init',
|
||||
function() {
|
||||
function () {
|
||||
// Delete this notification because the blocks are included in WC Core now. This will handle any sites
|
||||
// with lingering notices.
|
||||
InboxNotifications::delete_surface_cart_checkout_blocks_notification();
|
||||
@@ -145,6 +149,7 @@ class Bootstrap {
|
||||
if ( ! $is_store_api_request ) {
|
||||
// Template related functionality. These won't be loaded for store API requests, but may be loaded for
|
||||
// regular rest requests to maintain compatibility with the store editor.
|
||||
$this->container->get( AIPatterns::class );
|
||||
$this->container->get( BlockPatterns::class );
|
||||
$this->container->get( BlockTypesController::class );
|
||||
$this->container->get( BlockTemplatesRegistry::class )->init();
|
||||
@@ -153,6 +158,7 @@ class Bootstrap {
|
||||
$this->container->get( ArchiveProductTemplatesCompatibility::class )->init();
|
||||
$this->container->get( SingleProductTemplateCompatibility::class )->init();
|
||||
$this->container->get( Notices::class )->init();
|
||||
$this->container->get( PTKPatternsStore::class );
|
||||
}
|
||||
|
||||
$this->container->get( QueryFilters::class )->init();
|
||||
@@ -178,7 +184,7 @@ class Bootstrap {
|
||||
}
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function() {
|
||||
function () {
|
||||
echo '<div class="error"><p>';
|
||||
printf(
|
||||
/* translators: %1$s is the node install command, %2$s is the install command, %3$s is the build command, %4$s is the watch command. */
|
||||
@@ -218,19 +224,19 @@ class Bootstrap {
|
||||
);
|
||||
$this->container->register(
|
||||
AssetDataRegistry::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
return new AssetDataRegistry( $container->get( AssetApi::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
AssetsController::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
return new AssetsController( $container->get( AssetApi::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
PaymentMethodRegistry::class,
|
||||
function() {
|
||||
function () {
|
||||
return new PaymentMethodRegistry();
|
||||
}
|
||||
);
|
||||
@@ -250,13 +256,13 @@ class Bootstrap {
|
||||
);
|
||||
$this->container->register(
|
||||
BlockTemplatesRegistry::class,
|
||||
function ( Container $container ) {
|
||||
function () {
|
||||
return new BlockTemplatesRegistry();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BlockTemplatesController::class,
|
||||
function ( Container $container ) {
|
||||
function () {
|
||||
return new BlockTemplatesController();
|
||||
}
|
||||
);
|
||||
@@ -281,51 +287,51 @@ class Bootstrap {
|
||||
);
|
||||
$this->container->register(
|
||||
DraftOrders::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
return new DraftOrders( $container->get( Package::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CreateAccount::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
return new CreateAccount( $container->get( Package::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
GoogleAnalytics::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new GoogleAnalytics( $asset_api );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
Notices::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
return new Notices( $container->get( Package::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
Hydration::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
return new Hydration( $container->get( AssetDataRegistry::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CheckoutFields::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
return new CheckoutFields( $container->get( AssetDataRegistry::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CheckoutFieldsAdmin::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$checkout_fields_controller = $container->get( CheckoutFields::class );
|
||||
return new CheckoutFieldsAdmin( $checkout_fields_controller );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CheckoutFieldsFrontend::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$checkout_fields_controller = $container->get( CheckoutFields::class );
|
||||
return new CheckoutFieldsFrontend( $checkout_fields_controller );
|
||||
}
|
||||
@@ -347,36 +353,58 @@ class Bootstrap {
|
||||
// Maintains backwards compatibility with previous Store API namespace.
|
||||
$this->container->register(
|
||||
'Automattic\WooCommerce\Blocks\StoreApi\Formatters',
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\StoreApi\Formatters', '6.4.0', 'Automattic\WooCommerce\StoreApi\Formatters', '6.5.0' );
|
||||
return $container->get( StoreApi::class )->container()->get( \Automattic\WooCommerce\StoreApi\Formatters::class );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
'Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi',
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi', '6.4.0', 'Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema', '6.5.0' );
|
||||
return $container->get( StoreApi::class )->container()->get( \Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema::class );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
'Automattic\WooCommerce\Blocks\StoreApi\SchemaController',
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\StoreApi\SchemaController', '6.4.0', 'Automattic\WooCommerce\StoreApi\SchemaController', '6.5.0' );
|
||||
return $container->get( StoreApi::class )->container()->get( SchemaController::class );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
'Automattic\WooCommerce\Blocks\StoreApi\RoutesController',
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\StoreApi\RoutesController', '6.4.0', 'Automattic\WooCommerce\StoreApi\RoutesController', '6.5.0' );
|
||||
return $container->get( StoreApi::class )->container()->get( RoutesController::class );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
PTKClient::class,
|
||||
function () {
|
||||
return new PTKClient();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
PTKPatternsStore::class,
|
||||
function () {
|
||||
return new PTKPatternsStore( $this->container->get( PTKClient::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BlockPatterns::class,
|
||||
function () {
|
||||
return new BlockPatterns( $this->package );
|
||||
return new BlockPatterns(
|
||||
$this->package,
|
||||
new PatternRegistry(),
|
||||
$this->container->get( PTKPatternsStore::class )
|
||||
);
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
AIPatterns::class,
|
||||
function () {
|
||||
return new AIPatterns();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
@@ -389,13 +417,13 @@ class Bootstrap {
|
||||
);
|
||||
$this->container->register(
|
||||
TasksController::class,
|
||||
function() {
|
||||
function () {
|
||||
return new TasksController();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
QueryFilters::class,
|
||||
function() {
|
||||
function () {
|
||||
return new QueryFilters();
|
||||
}
|
||||
);
|
||||
@@ -470,28 +498,28 @@ class Bootstrap {
|
||||
protected function register_payment_methods() {
|
||||
$this->container->register(
|
||||
Cheque::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new Cheque( $asset_api );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
PayPal::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new PayPal( $asset_api );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BankTransfer::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new BankTransfer( $asset_api );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CashOnDelivery::class,
|
||||
function( Container $container ) {
|
||||
function ( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new CashOnDelivery( $asset_api );
|
||||
}
|
||||
|
||||
@@ -123,22 +123,4 @@ class Package {
|
||||
public function feature() {
|
||||
return $this->feature_gating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_experimental_build() {
|
||||
return $this->feature()->is_experimental_build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an feature plugin or experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_feature_plugin_build() {
|
||||
return $this->feature()->is_feature_plugin_build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1187,7 +1187,7 @@ class CheckoutFields {
|
||||
*/
|
||||
$value = apply_filters( "woocommerce_get_default_value_for_{$missing_field}", null, $group, $wc_object );
|
||||
|
||||
if ( $value ) {
|
||||
if ( isset( $value ) ) {
|
||||
$meta_data[ $missing_field ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,12 @@
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
/**
|
||||
* Service class that handles the feature flags.
|
||||
* Service class that used to handle feature flags. That functionality
|
||||
* is removed now and it is only used to determine "environment".
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FeatureGating {
|
||||
|
||||
/**
|
||||
* Current flag value.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $flag;
|
||||
|
||||
const EXPERIMENTAL_FLAG = 3;
|
||||
const FEATURE_PLUGIN_FLAG = 2;
|
||||
const CORE_FLAG = 1;
|
||||
|
||||
/**
|
||||
* Current environment
|
||||
*
|
||||
@@ -33,35 +22,16 @@ class FeatureGating {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $flag Hardcoded flag value. Useful for tests.
|
||||
* @param string $environment Hardcoded environment value. Useful for tests.
|
||||
*/
|
||||
public function __construct( $flag = 0, $environment = 'unset' ) {
|
||||
$this->flag = $flag;
|
||||
public function __construct( $environment = 'unset' ) {
|
||||
$this->environment = $environment;
|
||||
$this->load_flag();
|
||||
$this->load_environment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set correct flag.
|
||||
* Set correct environment.
|
||||
*/
|
||||
public function load_flag() {
|
||||
if ( 0 === $this->flag ) {
|
||||
$default_flag = defined( 'WC_BLOCKS_IS_FEATURE_PLUGIN' ) ? self::FEATURE_PLUGIN_FLAG : self::CORE_FLAG;
|
||||
if ( file_exists( __DIR__ . '/../../../../blocks.ini' ) ) {
|
||||
$allowed_flags = [ self::EXPERIMENTAL_FLAG, self::FEATURE_PLUGIN_FLAG, self::CORE_FLAG ];
|
||||
$woo_options = parse_ini_file( __DIR__ . '/../../../../blocks.ini' );
|
||||
$this->flag = is_array( $woo_options ) && in_array( intval( $woo_options['woocommerce_blocks_phase'] ), $allowed_flags, true ) ? $woo_options['woocommerce_blocks_phase'] : $default_flag;
|
||||
} else {
|
||||
$this->flag = $default_flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set correct environment.
|
||||
*/
|
||||
public function load_environment() {
|
||||
if ( 'unset' === $this->environment ) {
|
||||
if ( file_exists( __DIR__ . '/../../../../blocks.ini' ) ) {
|
||||
@@ -74,33 +44,6 @@ class FeatureGating {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current flag value.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_flag() {
|
||||
return $this->flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_experimental_build() {
|
||||
return $this->flag >= self::EXPERIMENTAL_FLAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an feature plugin or experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_feature_plugin_build() {
|
||||
return $this->flag >= self::FEATURE_PLUGIN_FLAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current environment value.
|
||||
*
|
||||
@@ -136,46 +79,4 @@ class FeatureGating {
|
||||
public function is_test_environment() {
|
||||
return self::TEST_ENVIRONMENT === $this->environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns core flag value.
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
public static function get_core_flag() {
|
||||
return self::CORE_FLAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature plugin flag value.
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
public static function get_feature_plugin_flag() {
|
||||
return self::FEATURE_PLUGIN_FLAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns experimental flag value.
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
public static function get_experimental_flag() {
|
||||
return self::EXPERIMENTAL_FLAG;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the block templates controller refactor should be used to display blocks.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_block_templates_controller_refactor_enabled() {
|
||||
if ( file_exists( __DIR__ . '/../../../../blocks.ini' ) ) {
|
||||
$conf = parse_ini_file( __DIR__ . '/../../../../blocks.ini' );
|
||||
return $this->is_development_environment() && isset( $conf['use_block_templates_controller_refactor'] ) && true === (bool) $conf['use_block_templates_controller_refactor'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ class Notices {
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all notices with the new block based notices.
|
||||
* Replaces all notices with the new block-based notices.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
@@ -25,7 +25,7 @@ class Installer {
|
||||
|
||||
/**
|
||||
* Modifies default page content replacing it with classic shortcode block.
|
||||
* We check for shortcode as default because after WooCommerce 8.3, block based checkout is used by default.
|
||||
* We check for shortcode as default because after WooCommerce 8.3, block-based checkout is used by default.
|
||||
* This only runs on Tools > Create Pages as the filter is not applied on WooCommerce plugin activation.
|
||||
*
|
||||
* @param array $pages Default pages.
|
||||
|
||||
@@ -71,25 +71,6 @@ class Package {
|
||||
return self::get_package()->feature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_experimental_build() {
|
||||
return self::get_package()->is_experimental_build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in a feature plugin or experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_feature_plugin_build() {
|
||||
return self::get_package()->is_feature_plugin_build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the dependency injection container for woocommerce blocks.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Patterns;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AI\Connection;
|
||||
use Automattic\WooCommerce\Blocks\AIContent\UpdatePatterns;
|
||||
use Automattic\WooCommerce\Blocks\AIContent\UpdateProducts;
|
||||
use Automattic\WooCommerce\Blocks\Images\Pexels;
|
||||
|
||||
/**
|
||||
* AIPatterns class.
|
||||
*/
|
||||
class AIPatterns {
|
||||
const PATTERNS_AI_DATA_POST_TYPE = 'patterns_ai_data';
|
||||
|
||||
/**
|
||||
* Constructor for the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'init', array( $this, 'register_patterns_ai_data_post_type' ) );
|
||||
|
||||
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' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Patterns AI Data post type to store patterns with the AI-generated content.
|
||||
*/
|
||||
public function register_patterns_ai_data_post_type() {
|
||||
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,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the 'woocommerce_blocks_allow_ai_connection' option is set to true if the site is connected to AI.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update_ai_connection_allowed_option(): 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, true );
|
||||
}
|
||||
|
||||
$token = $ai_connection->get_jwt_token( $site_id );
|
||||
|
||||
if ( is_wp_error( $token ) ) {
|
||||
return update_option( 'woocommerce_blocks_allow_ai_connection', false, true );
|
||||
}
|
||||
|
||||
return update_option( 'woocommerce_blocks_allow_ai_connection', true, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return bool|string|\WP_Error
|
||||
*/
|
||||
public function update_patterns_content() {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Patterns;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* PatternsToolkit class.
|
||||
*/
|
||||
class PTKClient {
|
||||
/**
|
||||
* The Patterns Toolkit API URL
|
||||
*/
|
||||
const PATTERNS_TOOLKIT_URL = 'https://public-api.wordpress.com/rest/v1/ptk/patterns/';
|
||||
|
||||
/**
|
||||
* Fetch the WooCommerce patterns from the Patterns Toolkit (PTK) API.
|
||||
*
|
||||
* @param array $options Options for fetching patterns.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function fetch_patterns( array $options = array() ) {
|
||||
$locale = get_user_locale();
|
||||
$lang = preg_replace( '/(_.*)$/', '', $locale );
|
||||
|
||||
$ptk_url = self::PATTERNS_TOOLKIT_URL . $lang;
|
||||
|
||||
if ( isset( $options['site'] ) ) {
|
||||
$ptk_url = add_query_arg( 'site', $options['site'], $ptk_url );
|
||||
}
|
||||
|
||||
if ( isset( $options['categories'] ) ) {
|
||||
$ptk_url = add_query_arg( 'categories', implode( ',', $options['categories'] ), $ptk_url );
|
||||
}
|
||||
|
||||
if ( isset( $options['per_page'] ) ) {
|
||||
$ptk_url = add_query_arg( 'per_page', $options['per_page'], $ptk_url );
|
||||
}
|
||||
|
||||
$patterns = wp_safe_remote_get( $ptk_url );
|
||||
if ( is_wp_error( $patterns ) || 200 !== wp_remote_retrieve_response_code( $patterns ) ) {
|
||||
return new WP_Error(
|
||||
'patterns_toolkit_api_error',
|
||||
__( 'Failed to connect with the Patterns Toolkit API: try again later.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $patterns );
|
||||
|
||||
if ( empty( $body ) ) {
|
||||
return new WP_Error(
|
||||
'patterns_toolkit_api_error',
|
||||
__( 'Empty response received from the Patterns Toolkit API.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$decoded_body = json_decode( $body, true );
|
||||
|
||||
if ( ! is_array( $decoded_body ) ) {
|
||||
return new WP_Error(
|
||||
'patterns_toolkit_api_error',
|
||||
__( 'Wrong response received from the Patterns Toolkit API: try again later.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
return $decoded_body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Patterns;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use WP_Upgrader;
|
||||
|
||||
/**
|
||||
* PTKPatterns class.
|
||||
*/
|
||||
class PTKPatternsStore {
|
||||
const TRANSIENT_NAME = 'ptk_patterns';
|
||||
|
||||
// Some patterns need to be excluded because they have dependencies which
|
||||
// are not installed by default (like Jetpack). Otherwise, the user
|
||||
// would see an error when trying to insert them in the editor.
|
||||
const EXCLUDED_PATTERNS = array( '13923', '14781', '14779', '13666', '13664', '13660', '13588', '14922', '14880', '13596', '13967', '13958', '15050', '15027' );
|
||||
|
||||
/**
|
||||
* PatternsToolkit instance.
|
||||
*
|
||||
* @var PTKClient $ptk_client
|
||||
*/
|
||||
private PTKClient $ptk_client;
|
||||
|
||||
/**
|
||||
* Constructor for the class.
|
||||
*
|
||||
* @param PTKClient $ptk_client An instance of PatternsToolkit.
|
||||
*/
|
||||
public function __construct( PTKClient $ptk_client ) {
|
||||
$this->ptk_client = $ptk_client;
|
||||
|
||||
if ( Features::is_enabled( 'pattern-toolkit-full-composability' ) ) {
|
||||
// We want to flush the cached patterns when:
|
||||
// - The WooCommerce plugin is deactivated.
|
||||
// - The `woocommerce_allow_tracking` option is disabled.
|
||||
//
|
||||
// We also want to re-fetch the patterns and update the cache when:
|
||||
// - The `woocommerce_allow_tracking` option changes to enabled.
|
||||
// - The WooCommerce plugin is activated (if `woocommerce_allow_tracking` is enabled).
|
||||
// - The WooCommerce plugin is updated.
|
||||
|
||||
add_action( 'woocommerce_activated_plugin', array( $this, 'flush_or_fetch_patterns' ), 10, 2 );
|
||||
add_action( 'update_option_woocommerce_allow_tracking', array( $this, 'flush_or_fetch_patterns' ), 10, 2 );
|
||||
add_action( 'deactivated_plugin', array( $this, 'flush_cached_patterns' ), 10, 2 );
|
||||
add_action( 'upgrader_process_complete', array( $this, 'fetch_patterns_on_plugin_update' ), 10, 2 );
|
||||
|
||||
// This is the scheduled action that takes care of flushing and re-fetching the patterns from the PTK API.
|
||||
add_action( 'fetch_patterns', array( $this, 'fetch_patterns' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the cached patterns when the `woocommerce_allow_tracking` option is disabled.
|
||||
* Resets and fetch the patterns from the PTK when it is enabled (if the scheduler
|
||||
* is initialized, it's done asynchronously via a scheduled action).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function flush_or_fetch_patterns() {
|
||||
if ( $this->allowed_tracking_is_enabled() ) {
|
||||
$this->schedule_fetch_patterns();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->flush_cached_patterns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an async action to fetch the PTK patterns when the scheduler is initialized.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function schedule_fetch_patterns() {
|
||||
if ( did_action( 'action_scheduler_init' ) ) {
|
||||
$this->schedule_action_if_not_pending( 'fetch_patterns' );
|
||||
} else {
|
||||
add_action(
|
||||
'action_scheduler_init',
|
||||
function () {
|
||||
$this->schedule_action_if_not_pending( 'fetch_patterns' );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an action if it's not already pending.
|
||||
*
|
||||
* @param string $action The action name to schedule.
|
||||
* @return void
|
||||
*/
|
||||
private function schedule_action_if_not_pending( $action ) {
|
||||
if ( as_has_scheduled_action( $action ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
as_schedule_single_action( time(), $action );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the patterns from the Patterns Toolkit cache.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_patterns() {
|
||||
$patterns = get_transient( self::TRANSIENT_NAME );
|
||||
|
||||
// Only if the transient is not set, we schedule fetching the patterns from the PTK.
|
||||
if ( false === $patterns ) {
|
||||
$this->schedule_fetch_patterns();
|
||||
return array();
|
||||
}
|
||||
|
||||
return $patterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter patterns to exclude those with the given IDs.
|
||||
*
|
||||
* @param array $patterns The patterns to filter.
|
||||
* @param array $pattern_ids The pattern IDs to exclude.
|
||||
* @return array
|
||||
*/
|
||||
private function filter_patterns( array $patterns, array $pattern_ids ) {
|
||||
return array_filter(
|
||||
$patterns,
|
||||
function ( $pattern ) use ( $pattern_ids ) {
|
||||
if ( ! isset( $pattern['ID'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( isset( $pattern['post_type'] ) && 'wp_block' !== $pattern['post_type'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! in_array( (string) $pattern['ID'], $pattern_ids, true );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-fetch the patterns when the WooCommerce plugin is updated.
|
||||
*
|
||||
* @param WP_Upgrader $upgrader_object WP_Upgrader instance.
|
||||
* @param array $options Array of bulk item update data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fetch_patterns_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' ) ) {
|
||||
$this->schedule_fetch_patterns();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cached patterns to fetch them again from the PTK.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function flush_cached_patterns() {
|
||||
delete_transient( self::TRANSIENT_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cached patterns and fetch them again from the PTK API.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fetch_patterns() {
|
||||
if ( ! $this->allowed_tracking_is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->flush_cached_patterns();
|
||||
|
||||
$patterns = $this->ptk_client->fetch_patterns(
|
||||
array(
|
||||
'categories' => array( 'intro', 'about', 'services', 'testimonials' ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $patterns ) ) {
|
||||
wc_get_logger()->warning(
|
||||
sprintf(
|
||||
// translators: %s is a generated error message.
|
||||
__( 'Failed to get the patterns from the PTK: "%s"', 'woocommerce' ),
|
||||
$patterns->get_error_message()
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$patterns = $this->filter_patterns( $patterns, self::EXCLUDED_PATTERNS );
|
||||
|
||||
set_transient( self::TRANSIENT_NAME, $patterns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user allowed tracking.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function allowed_tracking_is_enabled(): bool {
|
||||
return 'yes' === get_option( 'woocommerce_allow_tracking' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Patterns;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
|
||||
/**
|
||||
* PatternRegistry class.
|
||||
*/
|
||||
class PatternRegistry {
|
||||
const SLUG_REGEX = '/^[A-z0-9\/_-]+$/';
|
||||
const COMMA_SEPARATED_REGEX = '/[\s,]+/';
|
||||
|
||||
/**
|
||||
* Register a block pattern.
|
||||
*
|
||||
* @param string $source The pattern source.
|
||||
* @param array $pattern_data The pattern data.
|
||||
* @param array $dictionary The patterns' dictionary.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_block_pattern( $source, $pattern_data, $dictionary ) {
|
||||
if ( empty( $pattern_data['slug'] ) ) {
|
||||
_doing_it_wrong(
|
||||
'register_block_patterns',
|
||||
esc_html(
|
||||
sprintf(
|
||||
/* translators: %s: file name. */
|
||||
__( 'Could not register pattern "%s" as a block pattern ("Slug" field missing)', 'woocommerce' ),
|
||||
$source
|
||||
)
|
||||
),
|
||||
'6.0.0'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
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 pattern "%1$s" as a block pattern (invalid slug "%2$s")', 'woocommerce' ),
|
||||
$source,
|
||||
$pattern_data['slug']
|
||||
)
|
||||
),
|
||||
'6.0.0'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $pattern_data['featureFlag'] ) && '' !== $pattern_data['featureFlag'] && ! Features::is_enabled( $pattern_data['featureFlag'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Title is a required property.
|
||||
if ( ! isset( $pattern_data['title'] ) || ! $pattern_data['title'] ) {
|
||||
_doing_it_wrong(
|
||||
'register_block_patterns',
|
||||
esc_html(
|
||||
sprintf(
|
||||
/* translators: %1s: file name; %2s: slug value found. */
|
||||
__( 'Could not register pattern "%s" as a block pattern ("Title" field missing)', 'woocommerce' ),
|
||||
$source
|
||||
)
|
||||
),
|
||||
'6.0.0'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For properties of type array, parse data as comma-separated.
|
||||
foreach ( array( 'categories', 'keywords', 'blockTypes' ) as $property ) {
|
||||
if ( ! empty( $pattern_data[ $property ] ) ) {
|
||||
if ( is_array( $pattern_data[ $property ] ) ) {
|
||||
$pattern_data[ $property ] = array_values(
|
||||
array_map(
|
||||
function ( $property ) {
|
||||
return $property['title'];
|
||||
},
|
||||
$pattern_data[ $property ]
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$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'] );
|
||||
|
||||
if ( file_exists( $source ) ) {
|
||||
// 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 $source;
|
||||
$pattern_data['content'] = ob_get_clean();
|
||||
|
||||
if ( ! $pattern_data['content'] ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $pattern_data['categories'] ) ) {
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Templates;
|
||||
|
||||
/**
|
||||
* ProductFiltersOverlayTemplate class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ProductFiltersOverlayTemplate extends AbstractTemplatePart {
|
||||
|
||||
/**
|
||||
* The slug of the template.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SLUG = 'product-filters-overlay';
|
||||
|
||||
/**
|
||||
* The template part area where the template part belongs.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $template_area = 'product-filters-overlay';
|
||||
|
||||
/**
|
||||
* Initialization method.
|
||||
*/
|
||||
public function init() {
|
||||
add_filter( 'default_wp_template_part_areas', array( $this, 'register_product_filters_overlay_template_part_area' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title of the template.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_template_title() {
|
||||
return _x( 'Filters Overlay', 'Template name', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the template.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_template_description() {
|
||||
return __( 'Template used to display the Product Filters Overlay.', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Filters Overlay to the default template part areas.
|
||||
*
|
||||
* @param array $default_area_definitions An array of supported area objects.
|
||||
* @return array The supported template part areas including the Filters Overlay one.
|
||||
*/
|
||||
public function register_product_filters_overlay_template_part_area( $default_area_definitions ) {
|
||||
$product_filters_overlay_template_part_area = array(
|
||||
'area' => 'product-filters-overlay',
|
||||
'label' => $this->get_template_title(),
|
||||
'description' => $this->get_template_description(),
|
||||
'icon' => 'filter',
|
||||
'area_tag' => 'product-filters-overlay',
|
||||
);
|
||||
return array_merge( $default_area_definitions, array( $product_filters_overlay_template_part_area ) );
|
||||
}
|
||||
}
|
||||
@@ -316,6 +316,7 @@ class BlockTemplateUtils {
|
||||
$wp_template_part_filenames = array(
|
||||
'checkout-header.html',
|
||||
'mini-cart.html',
|
||||
'product-filters-overlay.html',
|
||||
);
|
||||
|
||||
/*
|
||||
|
||||
@@ -44,6 +44,32 @@ class CartCheckoutUtils {
|
||||
return $checkout_page_id && has_block( 'woocommerce/checkout', $checkout_page_id );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the template overriding the page loads the page content or not.
|
||||
* Templates by default load the page content, but if that block is deleted the content can get out of sync with the one presented in the page editor.
|
||||
*
|
||||
* @param string $block The block to check.
|
||||
*
|
||||
* @return bool true if the template has out of sync content.
|
||||
*/
|
||||
public static function is_overriden_by_custom_template_content( string $block ): bool {
|
||||
|
||||
$block = str_replace( 'woocommerce/', '', $block );
|
||||
|
||||
if ( wc_current_theme_is_fse_theme() ) {
|
||||
$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'page-' . $block ) );
|
||||
foreach ( $templates_from_db as $template ) {
|
||||
if ( ! has_block( 'woocommerce/page-content-wrapper', $template->content ) ) {
|
||||
// Return true if the template does not load the page content via the woocommerce/page-content-wrapper block.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets country codes, names, states, and locale information.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user