Merged in feature/MAW-855-import-code-into-aws (pull request #2)

code import from pantheon

* code import from pantheon
This commit is contained in:
Tony Volpe
2023-12-04 23:08:14 +00:00
parent 8c9b1312bc
commit 8f4b5efda6
4766 changed files with 185592 additions and 239967 deletions

View File

@@ -21,6 +21,13 @@ class Favorites {
*/
const META_NAME = 'navigation_favorites';
/**
* Favorites instance.
*
* @var Favorites|null
*/
protected static $instance = null;
/**
* Get class instance.
*/

View File

@@ -16,6 +16,27 @@ class DeprecatedExtendedTask extends Task {
*/
public $id = '';
/**
* Additional info.
*
* @var string|null
*/
public $additional_info = '';
/**
* Content.
*
* @var string
*/
public $content = '';
/**
* Whether the task is complete or not.
*
* @var boolean
*/
public $is_complete = false;
/**
* Snoozeable.
*
@@ -30,6 +51,35 @@ class DeprecatedExtendedTask extends Task {
*/
public $is_dismissable = false;
/**
* Whether the store is capable of viewing the task.
*
* @var bool
*/
public $can_view = true;
/**
* Level.
*
* @var int
*/
public $level = 3;
/**
* Time.
*
* @var string|null
*/
public $time;
/**
* Title.
*
* @var string
*/
public $title = '';
/**
* Constructor.
*

View File

@@ -177,6 +177,15 @@ abstract class Task {
return null;
}
/**
* Badge.
*
* @return string
*/
public function get_badge() {
return '';
}
/**
* Level.
*
@@ -486,6 +495,7 @@ abstract class Task {
'id' => $this->get_id(),
'parentId' => $this->get_parent_id(),
'title' => $this->get_title(),
'badge' => $this->get_badge(),
'canView' => $this->can_view(),
'content' => $this->get_content(),
'additionalInfo' => $this->get_additional_info(),

View File

@@ -9,7 +9,6 @@ use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\DeprecatedExtendedTask;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\ReviewShippingOptions;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\TourInAppMarketplace;
/**
* Task Lists class.
*/
@@ -36,13 +35,15 @@ class TaskLists {
protected static $default_tasks_loaded = false;
/**
* Array of default tasks.
* The contents of this array is used in init_tasks() to run their init() methods.
* If the classes do not have an init() method then nothing is executed.
* Beyond that, adding tasks to this list has no effect, see init_default_lists() for the list of tasks.
* that are added for each task list.
*
* @var array
*/
const DEFAULT_TASKS = array(
'StoreDetails',
'Purchase',
'Products',
'WooCommercePayments',
'Payments',
@@ -53,7 +54,6 @@ class TaskLists {
'AdditionalPayments',
'ReviewShippingOptions',
'GetMobileApp',
'TourInAppMarketplace',
);
/**
@@ -109,19 +109,30 @@ class TaskLists {
*/
public static function init_default_lists() {
$tasks = array(
'CustomizeStore',
'StoreDetails',
'Purchase',
'Products',
'Appearance',
'WooCommercePayments',
'Payments',
'Tax',
'Shipping',
'Marketing',
'Appearance',
);
if ( Features::is_enabled( 'core-profiler' ) ) {
array_shift( $tasks );
$key = array_search( 'StoreDetails', $tasks, true );
if ( false !== $key ) {
unset( $tasks[ $key ] );
}
}
// Remove the old Personalize your store task if the new CustomizeStore is enabled.
$task_to_remove = Features::is_enabled( 'customize-store' ) ? 'Appearance' : 'CustomizeStore';
$store_customisation_task_index = array_search( $task_to_remove, $tasks, true );
if ( false !== $store_customisation_task_index ) {
unset( $tasks[ $store_customisation_task_index ] );
}
self::add_list(
@@ -182,11 +193,6 @@ class TaskLists {
);
}
if ( ! wp_is_mobile() ) { // Permit In-App Marketplace Tour on desktops only.
$tour_task = new TourInAppMarketplace();
self::add_task( 'extended', $tour_task );
}
if ( has_filter( 'woocommerce_admin_experimental_onboarding_tasklists' ) ) {
/**
* Filter to override default task lists.
@@ -431,7 +437,7 @@ class TaskLists {
foreach ( $submenu['woocommerce'] as $key => $menu_item ) {
if ( 0 === strpos( $menu_item[0], _x( 'Home', 'Admin menu name', 'woocommerce' ) ) ) {
$submenu['woocommerce'][ $key ][0] .= ' <span class="awaiting-mod update-plugins remaining-tasks-badge count-' . esc_attr( $tasks_count ) . '">' . number_format_i18n( $tasks_count ) . '</span>'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$submenu['woocommerce'][ $key ][0] .= ' <span class="awaiting-mod update-plugins remaining-tasks-badge woocommerce-task-list-remaining-tasks-badge"><span class="count-' . esc_attr( $tasks_count ) . '">' . absint( $tasks_count ) . '</span></span>'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
break;
}
}

View File

@@ -14,14 +14,12 @@ use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
class Appearance extends Task {
/**
* Constructor
*
* @param TaskList $task_list Parent task list.
* Constructor.
*/
public function __construct( $task_list ) {
parent::__construct( $task_list );
add_action( 'admin_enqueue_scripts', array( $this, 'add_media_scripts' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_return_notice_script' ) );
public function __construct() {
if ( ! $this->is_complete() ) {
add_action( 'load-theme-install.php', array( $this, 'mark_actioned' ) );
}
}
/**
@@ -39,13 +37,7 @@ class Appearance extends Task {
* @return string
*/
public function get_title() {
if ( $this->get_parent_option( 'use_completed_title' ) === true ) {
if ( $this->is_complete() ) {
return __( 'You personalized your store', 'woocommerce' );
}
return __( 'Personalize your store', 'woocommerce' );
}
return __( 'Personalize my store', 'woocommerce' );
return __( 'Choose your theme', 'woocommerce' );
}
/**
@@ -55,7 +47,7 @@ class Appearance extends Task {
*/
public function get_content() {
return __(
'Add your logo, create a homepage, and start designing your store.',
"Choose a theme that best fits your brand's look and feel, then make it your own. Change the colors, add your logo, and create pages.",
'woocommerce'
);
}
@@ -70,68 +62,11 @@ class Appearance extends Task {
}
/**
* Addtional data.
* Action label.
*
* @return array
* @return string
*/
public function get_additional_data() {
return array(
'has_homepage' => self::has_homepage(),
'has_products' => Products::has_products(),
'stylesheet' => get_option( 'stylesheet' ),
'theme_mods' => get_theme_mods(),
'support_custom_logo' => false !== get_theme_support( 'custom-logo' ),
);
}
/**
* Add media scripts for image uploader.
*/
public function add_media_scripts() {
if ( ! PageController::is_admin_page() || ! $this->can_view() ) {
return;
}
wp_enqueue_media();
}
/**
* Adds a return to task list notice when completing the task.
*
* @param string $hook Page hook.
*/
public function possibly_add_return_notice_script( $hook ) {
global $post;
if ( $hook !== 'post.php' || $post->post_type !== 'page' ) {
return;
}
if ( $this->is_complete() || ! $this->is_active() ) {
return;
}
WCAdminAssets::register_script( 'wp-admin-scripts', 'onboarding-homepage-notice', true );
}
/**
* Check if the site has a homepage set up.
*/
public static function has_homepage() {
if ( get_option( 'classic-editor-replace' ) === 'classic' ) {
return true;
}
$homepage_id = get_option( 'woocommerce_onboarding_homepage_post_id', false );
if ( ! $homepage_id ) {
return false;
}
$post = get_post( $homepage_id );
$completed = $post && $post->post_status === 'publish';
return $completed;
public function get_action_label() {
return __( 'Choose theme', 'woocommerce' );
}
}

View File

@@ -0,0 +1,207 @@
<?php
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
use Jetpack_Gutenberg;
/**
* Customize Your Store Task
*/
class CustomizeStore extends Task {
/**
* Constructor
*
* @param TaskList $task_list Parent task list.
*/
public function __construct( $task_list ) {
parent::__construct( $task_list );
add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_site_editor_scripts' ) );
// Use "switch_theme" instead of "after_switch_theme" because the latter is fired after the next WP load and we don't want to trigger action when switching theme to TT3 via onboarding theme API.
global $_GET;
$theme_switch_via_cys_ai_loader = isset( $_GET['theme_switch_via_cys_ai_loader'] ) ? 1 === absint( $_GET['theme_switch_via_cys_ai_loader'] ) : false;
if ( ! $theme_switch_via_cys_ai_loader ) {
add_action( 'switch_theme', array( $this, 'mark_task_as_complete' ) );
}
}
/**
* ID.
*
* @return string
*/
public function get_id() {
return 'customize-store';
}
/**
* Title.
*
* @return string
*/
public function get_title() {
return __( 'Customize your store ', 'woocommerce' );
}
/**
* Content.
*
* @return string
*/
public function get_content() {
return '';
}
/**
* Time.
*
* @return string
*/
public function get_time() {
return '';
}
/**
* Task completion.
*
* @return bool
*/
public function is_complete() {
return get_option( 'woocommerce_admin_customize_store_completed' ) === 'yes';
}
/**
* Task visibility.
*
* @return bool
*/
public function can_view() {
return true;
}
/**
* Possibly add site editor scripts.
*/
public function possibly_add_site_editor_scripts() {
$is_customize_store_pages = (
isset( $_GET['page'] ) &&
'wc-admin' === $_GET['page'] &&
isset( $_GET['path'] ) &&
str_starts_with( wc_clean( wp_unslash( $_GET['path'] ) ), '/customize-store' )
);
if ( ! $is_customize_store_pages ) {
return;
}
// See: https://github.com/WordPress/WordPress/blob/master/wp-admin/site-editor.php.
if ( ! wp_is_block_theme() ) {
wp_die( esc_html__( 'The theme you are currently using is not compatible.', 'woocommerce' ) );
}
global $editor_styles;
// Flag that we're loading the block editor.
$current_screen = get_current_screen();
$current_screen->is_block_editor( true );
// Default to is-fullscreen-mode to avoid jumps in the UI.
add_filter(
'admin_body_class',
static function( $classes ) {
return "$classes is-fullscreen-mode";
}
);
$block_editor_context = new \WP_Block_Editor_Context( array( 'name' => 'core/edit-site' ) );
$indexed_template_types = array();
foreach ( get_default_block_template_types() as $slug => $template_type ) {
$template_type['slug'] = (string) $slug;
$indexed_template_types[] = $template_type;
}
$custom_settings = array(
'siteUrl' => site_url(),
'postsPerPage' => get_option( 'posts_per_page' ),
'styles' => get_block_editor_theme_styles(),
'defaultTemplateTypes' => $indexed_template_types,
'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(),
'supportsLayout' => wp_theme_has_theme_json(),
'supportsTemplatePartsMode' => ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ),
);
// Add additional back-compat patterns registered by `current_screen` et al.
$custom_settings['__experimentalAdditionalBlockPatterns'] = \WP_Block_Patterns_Registry::get_instance()->get_all_registered( true );
$custom_settings['__experimentalAdditionalBlockPatternCategories'] = \WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered( true );
$editor_settings = get_block_editor_settings( $custom_settings, $block_editor_context );
$active_global_styles_id = \WP_Theme_JSON_Resolver::get_user_global_styles_post_id();
$active_theme = get_stylesheet();
$preload_paths = array(
array( '/wp/v2/media', 'OPTIONS' ),
'/wp/v2/types?context=view',
'/wp/v2/types/wp_template?context=edit',
'/wp/v2/types/wp_template-part?context=edit',
'/wp/v2/templates?context=edit&per_page=-1',
'/wp/v2/template-parts?context=edit&per_page=-1',
'/wp/v2/themes?context=edit&status=active',
'/wp/v2/global-styles/' . $active_global_styles_id . '?context=edit',
'/wp/v2/global-styles/' . $active_global_styles_id,
'/wp/v2/global-styles/themes/' . $active_theme,
);
block_editor_rest_api_preload( $preload_paths, $block_editor_context );
wp_add_inline_script(
'wp-blocks',
sprintf(
'window.wcBlockSettings = %s;',
wp_json_encode( $editor_settings )
)
);
// Preload server-registered block schemas.
wp_add_inline_script(
'wp-blocks',
'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');'
);
wp_add_inline_script(
'wp-blocks',
sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( isset( $editor_settings['blockCategories'] ) ? $editor_settings['blockCategories'] : array() ) ),
'after'
);
wp_enqueue_script( 'wp-editor' );
wp_enqueue_script( 'wp-format-library' ); // Not sure if this is needed.
wp_enqueue_script( 'wp-router' );
wp_enqueue_style( 'wp-editor' );
wp_enqueue_style( 'wp-edit-site' );
wp_enqueue_style( 'wp-format-library' );
wp_enqueue_media();
if (
current_theme_supports( 'wp-block-styles' ) &&
( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 )
) {
wp_enqueue_style( 'wp-block-library-theme' );
}
/** This action is documented in wp-admin/edit-form-blocks.php
*
* @since 8.0.3
*/
do_action( 'enqueue_block_editor_assets' );
// Load Jetpack's block editor assets because they are not enqueued by default.
if ( class_exists( 'Jetpack_Gutenberg' ) ) {
Jetpack_Gutenberg::enqueue_block_editor_assets();
}
}
/**
* Mark task as complete.
*/
public function mark_task_as_complete() {
update_option( 'woocommerce_admin_customize_store_completed', 'yes' );
}
}

View File

@@ -2,6 +2,7 @@
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
use Automattic\Jetpack\Connection\Manager;
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
use Automattic\WooCommerce\Admin\PluginsHelper;
@@ -79,8 +80,7 @@ class ExperimentalShippingRecommendation extends Task {
* @return bool
*/
public static function has_plugins_active() {
return PluginsHelper::is_plugin_active( 'woocommerce-services' ) &&
PluginsHelper::is_plugin_active( 'jetpack' );
return PluginsHelper::is_plugin_active( 'woocommerce-services' );
}
/**
@@ -89,9 +89,8 @@ class ExperimentalShippingRecommendation extends Task {
* @return bool
*/
public static function has_jetpack_connected() {
if ( class_exists( '\Jetpack' ) && is_callable( '\Jetpack::is_connection_ready' ) ) {
return \Jetpack::is_connection_ready();
}
return false;
$jetpack_connection_manager = new Manager( 'woocommerce' );
return $jetpack_connection_manager->is_connected() && $jetpack_connection_manager->has_connected_owner();
}
}

View File

@@ -79,7 +79,7 @@ class Tax extends Task {
public function get_content() {
return self::can_use_automated_taxes()
? __(
'Good news! WooCommerce Services and Jetpack can automate your sales tax calculations for you.',
'Good news! WooCommerce Tax can automate your sales tax calculations for you.',
'woocommerce'
)
: __(
@@ -115,7 +115,10 @@ class Tax extends Task {
*/
public function is_complete() {
if ( $this->is_complete_result === null ) {
$this->is_complete_result = get_option( 'wc_connect_taxes_enabled' ) ||
$wc_connect_taxes_enabled = get_option( 'wc_connect_taxes_enabled' );
$is_wc_connect_taxes_enabled = ( $wc_connect_taxes_enabled === 'yes' ) || ( $wc_connect_taxes_enabled === true ); // seems that in some places boolean is used, and other places 'yes' | 'no' is used
$this->is_complete_result = $is_wc_connect_taxes_enabled ||
count( TaxDataStore::get_taxes( array() ) ) > 0 ||
get_option( 'woocommerce_no_sales_tax' ) !== false;
}

View File

@@ -24,7 +24,7 @@ class TourInAppMarketplace extends Task {
*/
public function get_title() {
return __(
'Discover where to find powerful store add-ons and integrations, with a WooCommerce Marketplace tour',
'Discover ways of extending your store with a tour of the Woo Marketplace',
'woocommerce'
);
}
@@ -62,7 +62,7 @@ class TourInAppMarketplace extends Task {
* @return string
*/
public function get_action_url() {
return admin_url( 'admin.php?page=wc-addons&tutorial=true' );
return admin_url( 'admin.php?page=wc-admin&path=%2Fextensions&tutorial=true' );
}
/**

View File

@@ -6,7 +6,7 @@ use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
use Automattic\WooCommerce\Admin\PluginsHelper;
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\Init as Suggestions;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskList;
use Automattic\WooCommerce\Internal\Admin\WCPayPromotion\Init as WCPayPromotionInit;
/**
* WooCommercePayments Task
@@ -37,6 +37,21 @@ class WooCommercePayments extends Task {
return __( 'Set up WooPayments', 'woocommerce' );
}
/**
* Badge.
*
* @return string
*/
public function get_badge() {
/**
* Filter WooPayments onboarding task badge.
*
* @param string $badge Badge content.
* @since 8.2.0
*/
return apply_filters( 'woocommerce_admin_woopayments_onboarding_task_badge', '' );
}
/**
* Content.
*
@@ -73,6 +88,13 @@ class WooCommercePayments extends Task {
* @return string
*/
public function get_additional_info() {
if ( WCPayPromotionInit::is_woopay_eligible() ) {
return __(
'By using WooPayments you agree to be bound by our <a href="https://wordpress.com/tos/" target="_blank">Terms of Service</a> (including WooPay <a href="https://wordpress.com/tos/#more-woopay-specifically" target="_blank">merchant terms</a>) and acknowledge that you have read our <a href="https://automattic.com/privacy/" target="_blank">Privacy Policy</a>',
'woocommerce'
);
}
return __(
'By using WooPayments you agree to be bound by our <a href="https://wordpress.com/tos/" target="_blank">Terms of Service</a> and acknowledge that you have read our <a href="https://automattic.com/privacy/" target="_blank">Privacy Policy</a>',
'woocommerce'
@@ -86,7 +108,7 @@ class WooCommercePayments extends Task {
*/
public function is_complete() {
if ( null === $this->is_complete_result ) {
$this->is_complete_result = self::is_connected();
$this->is_complete_result = self::is_connected() && ! self::is_account_partially_onboarded();
}
return $this->is_complete_result;
@@ -102,8 +124,7 @@ class WooCommercePayments extends Task {
return ! $payments->is_complete() && // Do not re-display the task if the "add payments" task has already been completed.
self::is_installed() &&
self::is_supported() &&
! self::is_connected();
self::is_supported();
}
/**
@@ -146,6 +167,23 @@ class WooCommercePayments extends Task {
return false;
}
/**
* Check if WooCommerce Payments needs setup.
* Errored data or payments not enabled.
*
* @return bool
*/
public static function is_account_partially_onboarded() {
if ( class_exists( '\WC_Payments' ) ) {
$wc_payments_gateway = \WC_Payments::get_gateway();
return method_exists( $wc_payments_gateway, 'is_account_partially_onboarded' )
? $wc_payments_gateway->is_account_partially_onboarded()
: false;
}
return false;
}
/**
* Check if the store is in a supported country.
*

View File

@@ -904,7 +904,7 @@ class DefaultPaymentGateways {
* @return array Array of countries.
*/
public static function get_wcpay_countries() {
return array( 'US', 'PR', 'AU', 'CA', 'CY', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'IE', 'IT', 'LU', 'LT', 'LV', 'NO', 'NZ', 'MT', 'AT', 'BE', 'NL', 'PL', 'PT', 'CH', 'HK', 'SI', 'SK', 'SG', 'BG', 'CZ', 'HR', 'HU', 'RO', 'SE' );
return array( 'US', 'PR', 'AU', 'CA', 'CY', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'IE', 'IT', 'LU', 'LT', 'LV', 'NO', 'NZ', 'MT', 'AT', 'BE', 'NL', 'PL', 'PT', 'CH', 'HK', 'SI', 'SK', 'SG', 'BG', 'CZ', 'HR', 'HU', 'RO', 'SE', 'JP', 'AE' );
}
/**

View File

@@ -25,6 +25,7 @@ class Init {
*/
public function __construct() {
PaymentGatewaysController::init();
add_action( 'update_option_woocommerce_default_country', array( $this, 'delete_specs_transient' ) );
}
/**

View File

@@ -11,46 +11,66 @@ use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
* Product block registration and style registration functionality.
*/
class BlockRegistry {
/**
* The directory where blocks are stored after build.
*/
const BLOCKS_DIR = 'product-editor/blocks';
/**
* Array of all available product blocks.
* Generic blocks directory.
*/
const PRODUCT_BLOCKS = [
const GENERIC_BLOCKS_DIR = 'product-editor/blocks/generic';
/**
* Product fields blocks directory.
*/
const PRODUCT_FIELDS_BLOCKS_DIR = 'product-editor/blocks/product-fields';
/**
* Array of all available generic blocks.
*/
const GENERIC_BLOCKS = [
'woocommerce/conditional',
'woocommerce/product-category-field',
'woocommerce/product-checkbox-field',
'woocommerce/product-collapsible',
'woocommerce/product-radio-field',
'woocommerce/product-pricing-field',
'woocommerce/product-section',
'woocommerce/product-tab',
'woocommerce/product-toggle-field',
'woocommerce/product-taxonomy-field',
'woocommerce/product-text-field',
'woocommerce/product-number-field',
];
/**
* Array of all available product fields blocks.
*/
const PRODUCT_FIELDS_BLOCKS = [
'woocommerce/product-catalog-visibility-field',
'woocommerce/product-description-field',
'woocommerce/product-downloads-field',
'woocommerce/product-images-field',
'woocommerce/product-inventory-email-field',
'woocommerce/product-sku-field',
'woocommerce/product-name-field',
'woocommerce/product-pricing-field',
'woocommerce/product-radio-field',
'woocommerce/product-regular-price-field',
'woocommerce/product-sale-price-field',
'woocommerce/product-schedule-sale-fields',
'woocommerce/product-section',
'woocommerce/product-shipping-class-field',
'woocommerce/product-shipping-dimensions-fields',
'woocommerce/product-summary-field',
'woocommerce/product-tab',
'woocommerce/product-tag-field',
'woocommerce/product-inventory-quantity-field',
'woocommerce/product-toggle-field',
'woocommerce/product-variation-items-field',
'woocommerce/product-variations-fields',
'woocommerce/product-password-field',
'woocommerce/product-has-variations-notice',
'woocommerce/product-single-variation-notice',
];
/**
* Get a file path for a given block file.
*
* @param string $path File path.
* @param string $dir File directory.
*/
private function get_file_path( $path ) {
return WC_ABSPATH . WCAdminAssets::get_path( 'js' ) . trailingslashit( self::BLOCKS_DIR ) . $path;
private function get_file_path( $path, $dir ) {
return WC_ABSPATH . WCAdminAssets::get_path( 'js' ) . trailingslashit( $dir ) . $path;
}
/**
@@ -65,8 +85,11 @@ class BlockRegistry {
* Register all the product blocks.
*/
private function register_product_blocks() {
foreach ( self::PRODUCT_BLOCKS as $block_name ) {
$this->register_block( $block_name );
foreach ( self::PRODUCT_FIELDS_BLOCKS as $block_name ) {
$this->register_block( $block_name, self::PRODUCT_FIELDS_BLOCKS_DIR );
}
foreach ( self::GENERIC_BLOCKS as $block_name ) {
$this->register_block( $block_name, self::GENERIC_BLOCKS_DIR );
}
}
@@ -103,16 +126,60 @@ class BlockRegistry {
return $block_name;
}
/**
* Augment the attributes of a block by adding attributes that are used by the product editor.
*
* @param array $attributes Block attributes.
*/
private function augment_attributes( $attributes ) {
// Note: If you modify this function, also update the client-side
// registerWooBlockType function in @woocommerce/block-templates.
return array_merge(
$attributes,
[
'_templateBlockId' => [
'type' => 'string',
'__experimentalRole' => 'content',
],
'_templateBlockOrder' => [
'type' => 'integer',
'__experimentalRole' => 'content',
],
'_templateBlockHideConditions' => [
'type' => 'array',
'__experimentalRole' => 'content',
],
]
);
}
/**
* Augment the uses_context of a block by adding attributes that are used by the product editor.
*
* @param array $uses_context Block uses_context.
*/
private function augment_uses_context( $uses_context ) {
// Note: If you modify this function, also update the client-side
// registerProductEditorBlockType function in @woocommerce/product-editor.
return array_merge(
isset( $uses_context ) ? $uses_context : [],
[
'postType',
]
);
}
/**
* Register a single block.
*
* @param string $block_name Block name.
* @param string $block_dir Block directory.
*
* @return WP_Block_Type|false The registered block type on success, or false on failure.
*/
private function register_block( $block_name ) {
private function register_block( $block_name, $block_dir ) {
$block_name = $this->remove_block_prefix( $block_name );
$block_json_file = $this->get_file_path( $block_name . '/block.json' );
$block_json_file = $this->get_file_path( $block_name . '/block.json', $block_dir );
if ( ! file_exists( $block_json_file ) ) {
return false;
@@ -130,7 +197,13 @@ class BlockRegistry {
$registry->unregister( $metadata['name'] );
}
return register_block_type_from_metadata( $block_json_file );
return register_block_type_from_metadata(
$block_json_file,
[
'attributes' => $this->augment_attributes( isset( $metadata['attributes'] ) ? $metadata['attributes'] : [] ),
'uses_context' => $this->augment_uses_context( isset( $metadata['usesContext'] ) ? $metadata['usesContext'] : [] ),
]
);
}
}

View File

@@ -6,9 +6,10 @@
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor;
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Admin\Features\TransientNotices;
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\SimpleProductTemplate;
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\ProductVariationTemplate;
use Automattic\WooCommerce\Admin\PageController;
use Automattic\WooCommerce\Internal\Admin\Loader;
use Automattic\WooCommerce\Internal\Admin\BlockTemplateRegistry\BlockTemplateRegistry;
use WP_Block_Editor_Context;
/**
@@ -38,16 +39,27 @@ class Init {
* Constructor
*/
public function __construct() {
if ( Features::is_enabled( 'product-variation-management' ) ) {
array_push( $this->supported_post_types, 'variable' );
}
$this->redirection_controller = new RedirectionController( $this->supported_post_types );
if ( \Automattic\WooCommerce\Utilities\FeaturesUtil::feature_is_enabled( 'product_block_editor' ) ) {
// Register the product block template.
$template_registry = wc_get_container()->get( BlockTemplateRegistry::class );
$template_registry->register( new SimpleProductTemplate() );
$template_registry->register( new ProductVariationTemplate() );
if ( ! Features::is_enabled( 'new-product-management-experience' ) ) {
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'dequeue_conflicting_styles' ), 100 );
add_action( 'get_edit_post_link', array( $this, 'update_edit_product_link' ), 10, 2 );
}
add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_filter( 'woocommerce_register_post_type_product', array( $this, 'add_product_template' ) );
add_filter( 'woocommerce_register_post_type_product_variation', array( $this, 'enable_rest_api_for_product_variation' ) );
add_action( 'current_screen', array( $this, 'set_current_screen_to_block_editor_if_wc_admin' ) );
@@ -68,12 +80,16 @@ class Init {
}
$post_type_object = get_post_type_object( 'product' );
$block_editor_context = new WP_Block_Editor_Context( array( 'name' => self::EDITOR_CONTEXT_NAME ) );
$template_registry = wc_get_container()->get( BlockTemplateRegistry::class );
$editor_settings = array();
if ( ! empty( $post_type_object->template ) ) {
$editor_settings['template'] = $post_type_object->template;
$editor_settings['templateLock'] = ! empty( $post_type_object->template_lock ) ? $post_type_object->template_lock : false;
$editor_settings['__unstableResolvedAssets'] = $this->get_resolved_assets();
$editor_settings['template'] = $post_type_object->template;
$editor_settings['templateLock'] = ! empty( $post_type_object->template_lock ) ? $post_type_object->template_lock : false;
$editor_settings['templates'] = array(
'product' => $post_type_object->template,
'product_variation' => $template_registry->get_registered( 'product-variation' )->get_formatted_template(),
);
}
$editor_settings = get_block_editor_settings( $editor_settings, $block_editor_context );
@@ -92,6 +108,7 @@ class Init {
'before'
);
wp_tinymce_inline_scripts();
wp_enqueue_media();
}
/**
@@ -144,105 +161,6 @@ class Init {
return $link;
}
/**
* Get the resolved assets needed for the iframe editor.
*
* @return array Styles and scripts.
*/
private function get_resolved_assets() {
if ( function_exists( 'gutenberg_resolve_assets_override' ) ) {
return gutenberg_resolve_assets_override();
}
global $pagenow;
$script_handles = array(
'wp-polyfill',
);
// Note for core merge: only 'wp-edit-blocks' should be in this array.
$style_handles = array(
'wp-edit-blocks',
);
if ( current_theme_supports( 'wp-block-styles' ) ) {
$style_handles[] = 'wp-block-library-theme';
}
if ( 'widgets.php' === $pagenow || 'customize.php' === $pagenow ) {
$style_handles[] = 'wp-widgets';
$style_handles[] = 'wp-edit-widgets';
}
$block_registry = \WP_Block_Type_Registry::get_instance();
foreach ( $block_registry->get_all_registered() as $block_type ) {
// In older WordPress versions, like 6.0, these properties are not defined.
if ( isset( $block_type->style_handles ) && is_array( $block_type->style_handles ) ) {
$style_handles = array_merge( $style_handles, $block_type->style_handles );
}
if ( isset( $block_type->editor_style_handles ) && is_array( $block_type->editor_style_handles ) ) {
$style_handles = array_merge( $style_handles, $block_type->editor_style_handles );
}
if ( isset( $block_type->script_handles ) && is_array( $block_type->script_handles ) ) {
$script_handles = array_merge( $script_handles, $block_type->script_handles );
}
}
$style_handles = array_unique( $style_handles );
$done = wp_styles()->done;
ob_start();
// We do not need reset styles for the iframed editor.
wp_styles()->done = array( 'wp-reset-editor-styles' );
wp_styles()->do_items( $style_handles );
wp_styles()->done = $done;
$styles = ob_get_clean();
$script_handles = array_unique( $script_handles );
$done = wp_scripts()->done;
ob_start();
wp_scripts()->done = array();
wp_scripts()->do_items( $script_handles );
wp_scripts()->done = $done;
$scripts = ob_get_clean();
/*
* Generate font @font-face styles for the site editor iframe.
* Use the registered font families for printing.
*/
if ( class_exists( '\WP_Fonts' ) ) {
$wp_fonts = wp_fonts();
$registered = $wp_fonts->get_registered_font_families();
if ( ! empty( $registered ) ) {
$queue = $wp_fonts->queue;
$done = $wp_fonts->done;
$wp_fonts->done = array();
$wp_fonts->queue = $registered;
ob_start();
$wp_fonts->do_items();
$styles .= ob_get_clean();
// Reset the Web Fonts API.
$wp_fonts->done = $done;
$wp_fonts->queue = $queue;
}
}
return array(
'styles' => $styles,
'scripts' => $scripts,
);
}
/**
* Enqueue styles needed for the rich text editor.
*
@@ -251,488 +169,47 @@ class Init {
*/
public function add_product_template( $args ) {
if ( ! isset( $args['template'] ) ) {
$args['template_lock'] = 'all';
$args['template'] = array(
array(
'woocommerce/product-tab',
array(
'id' => 'general',
'title' => __( 'General', 'woocommerce' ),
'order' => 10,
),
array(
array(
'woocommerce/product-section',
array(
'title' => __( 'Basic details', 'woocommerce' ),
'description' => __( 'This info will be displayed on the product page, category pages, social media, and search results.', 'woocommerce' ),
),
array(
array(
'woocommerce/product-name-field',
array(
'name' => 'Product name',
'autoFocus' => true,
),
),
array(
'woocommerce/product-summary-field',
),
array(
'core/columns',
array(),
array(
array(
'core/column',
array(
'templateLock' => 'all',
),
array(
array(
'woocommerce/product-regular-price-field',
array(
'name' => 'regular_price',
'label' => __( 'List price', 'woocommerce' ),
'help' => __( 'Manage more settings in <PricingTab>Pricing.</PricingTab>', 'woocommerce' ),
),
),
),
),
array(
'core/column',
array(
'templateLock' => 'all',
),
array(
array(
'woocommerce/product-sale-price-field',
array(
'label' => __( 'Sale price', 'woocommerce' ),
),
),
),
),
),
),
),
),
array(
'woocommerce/product-section',
array(
'title' => __( 'Description', 'woocommerce' ),
'description' => __( 'What makes this product unique? What are its most important features? Enrich the product page by adding rich content using blocks.', 'woocommerce' ),
),
array(
array(
'woocommerce/product-description-field',
),
),
),
array(
'woocommerce/product-section',
array(
'title' => __( 'Images', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Images guide link opening tag. %2$s: Images guide link closing tag.*/
__( 'Drag images, upload new ones or select files from your library. For best results, use JPEG files that are 1000 by 1000 pixels or larger. %1$sHow to prepare images?%2$s', 'woocommerce' ),
'<a href="http://woocommerce.com/#" target="_blank" rel="noreferrer">',
'</a>'
),
),
array(
array(
'woocommerce/product-images-field',
array(
'images' => array(),
),
),
),
),
array(
'woocommerce/product-section',
array(
'title' => __( 'Organization & visibility', 'woocommerce' ),
'description' => __( 'Help customers find this product by assigning it to categories or featuring it across your sales channels.', 'woocommerce' ),
),
array(
array(
'woocommerce/product-category-field',
array(
'name' => 'categories',
),
),
),
),
array(
'woocommerce/product-section',
array(
'title' => __( 'Attributes', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Attributes guide link opening tag. %2$s: Attributes guide link closing tag.*/
__( 'Add descriptive pieces of information that customers can use to filter and search for this product. %1$sLearn more%2$s', 'woocommerce' ),
'<a href="https://woocommerce.com/document/managing-product-taxonomies/#product-attributes" target="_blank" rel="noreferrer">',
'</a>'
),
),
array(
array(
'woocommerce/product-attributes-field',
),
),
),
),
),
array(
'woocommerce/product-tab',
array(
'id' => 'pricing',
'title' => __( 'Pricing', 'woocommerce' ),
'order' => 20,
),
array(
array(
'woocommerce/product-section',
array(
'title' => __( 'Pricing', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Images guide link opening tag. %2$s: Images guide link closing tag.*/
__( 'Set a competitive price, put the product on sale, and manage tax calculations. %1$sHow to price your product?%2$s', 'woocommerce' ),
'<a href="https://woocommerce.com/posts/how-to-price-products-strategies-expert-tips/" target="_blank" rel="noreferrer">',
'</a>'
),
'blockGap' => 'unit-40',
),
array(
array(
'woocommerce/product-section',
array(),
array(
array(
'core/columns',
array(),
array(
array(
'core/column',
array(
'templateLock' => 'all',
),
array(
array(
'woocommerce/product-regular-price-field',
array(
'name' => 'regular_price',
'label' => __( 'List price', 'woocommerce' ),
),
),
),
),
array(
'core/column',
array(
'templateLock' => 'all',
),
array(
array(
'woocommerce/product-sale-price-field',
array(
'label' => __( 'Sale price', 'woocommerce' ),
),
),
),
),
),
),
array(
'woocommerce/product-schedule-sale-fields',
),
),
),
array(
'woocommerce/product-radio-field',
array(
'title' => __( 'Charge sales tax on', 'woocommerce' ),
'property' => 'tax_status',
'options' => array(
array(
'label' => __( 'Product and shipping', 'woocommerce' ),
'value' => 'taxable',
),
array(
'label' => __( 'Only shipping', 'woocommerce' ),
'value' => 'shipping',
),
array(
'label' => __( "Don't charge tax", 'woocommerce' ),
'value' => 'none',
),
),
),
),
array(
'woocommerce/product-collapsible',
array(
'toggleText' => __( 'Advanced', 'woocommerce' ),
'initialCollapsed' => true,
'persistRender' => true,
),
array(
array(
'woocommerce/product-radio-field',
array(
'title' => __( 'Tax class', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
__( 'Apply a tax rate if this product qualifies for tax reduction or exemption. %1$sLearn more%2$s.', 'woocommerce' ),
'<a href="https://woocommerce.com/document/setting-up-taxes-in-woocommerce/#shipping-tax-class" target="_blank" rel="noreferrer">',
'</a>'
),
'property' => 'tax_class',
'options' => array(
array(
'label' => __( 'Standard', 'woocommerce' ),
'value' => '',
),
array(
'label' => __( 'Reduced rate', 'woocommerce' ),
'value' => 'reduced-rate',
),
array(
'label' => __( 'Zero rate', 'woocommerce' ),
'value' => 'zero-rate',
),
),
),
),
),
),
),
),
),
),
array(
'woocommerce/product-tab',
array(
'id' => 'inventory',
'title' => __( 'Inventory', 'woocommerce' ),
'order' => 30,
),
array(
array(
'woocommerce/product-section',
array(
'title' => __( 'Inventory', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Inventory settings link opening tag. %2$s: Inventory settings link closing tag.*/
__( 'Set up and manage inventory for this product, including status and available quantity. %1$sManage store inventory settings%2$s', 'woocommerce' ),
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=products&section=inventory' ) . '" target="_blank" rel="noreferrer">',
'</a>'
),
'blockGap' => 'unit-40',
),
array(
array(
'woocommerce/product-section',
array(),
array(
array(
'woocommerce/product-sku-field',
),
array(
'woocommerce/product-toggle-field',
array(
'label' => __( 'Track stock quantity for this product', 'woocommerce' ),
'property' => 'manage_stock',
'disabled' => 'yes' !== get_option( 'woocommerce_manage_stock' ),
),
),
array(
'woocommerce/conditional',
array(
'mustMatch' => array(
'manage_stock' => array( true ),
),
),
array(
array(
'woocommerce/product-inventory-quantity-field',
),
),
),
),
),
array(
'woocommerce/conditional',
array(
'mustMatch' => array(
'manage_stock' => array( false ),
),
),
array(
array(
'woocommerce/product-radio-field',
array(
'title' => __( 'Stock status', 'woocommerce' ),
'property' => 'stock_status',
'options' => array(
array(
'label' => __( 'In stock', 'woocommerce' ),
'value' => 'instock',
),
array(
'label' => __( 'Out of stock', 'woocommerce' ),
'value' => 'outofstock',
),
array(
'label' => __( 'On backorder', 'woocommerce' ),
'value' => 'onbackorder',
),
),
),
),
),
),
array(
'woocommerce/product-collapsible',
array(
'toggleText' => __( 'Advanced', 'woocommerce' ),
'initialCollapsed' => true,
'persistRender' => true,
),
array(
array(
'woocommerce/product-section',
array(
'blockGap' => 'unit-40',
),
array(
array(
'woocommerce/conditional',
array(
'mustMatch' => array(
'manage_stock' => array( true ),
),
),
array(
array(
'woocommerce/product-radio-field',
array(
'title' => __( 'When out of stock', 'woocommerce' ),
'property' => 'backorders',
'options' => array(
array(
'label' => __( 'Allow purchases', 'woocommerce' ),
'value' => 'yes',
),
array(
'label' => __(
'Allow purchases, but notify customers',
'woocommerce'
),
'value' => 'notify',
),
array(
'label' => __( "Don't allow purchases", 'woocommerce' ),
'value' => 'no',
),
),
),
),
array(
'woocommerce/product-inventory-email-field',
),
),
),
array(
'woocommerce/product-checkbox-field',
array(
'title' => __(
'Restrictions',
'woocommerce'
),
'label' => __(
'Limit purchases to 1 item per order',
'woocommerce'
),
'property' => 'sold_individually',
'tooltip' => __(
'When checked, customers will be able to purchase only 1 item in a single order. This is particularly useful for items that have limited quantity, like art or handmade goods.',
'woocommerce'
),
),
),
// Get the template from the registry.
$template_registry = wc_get_container()->get( BlockTemplateRegistry::class );
$template = $template_registry->get_registered( 'simple-product' );
),
),
),
),
),
),
),
),
array(
'woocommerce/product-tab',
array(
'id' => 'shipping',
'title' => __( 'Shipping', 'woocommerce' ),
'order' => 40,
),
array(
array(
'woocommerce/product-section',
array(
'title' => __( 'Fees & dimensions', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: How to get started? link opening tag. %2$s: How to get started? link closing tag.*/
__( 'Set up shipping costs and enter dimensions used for accurate rate calculations. %1$sHow to get started?%2$s.', 'woocommerce' ),
'<a href="https://woocommerce.com/posts/how-to-calculate-shipping-costs-for-your-woocommerce-store/" target="_blank" rel="noreferrer">',
'</a>'
),
),
array(
array(
'woocommerce/product-shipping-class-field',
),
array(
'woocommerce/product-shipping-dimensions-fields',
),
),
),
),
),
);
if ( Features::is_enabled( 'product-variation-management' ) ) {
array_push(
$args['template'],
array(
'woocommerce/product-tab',
array(
'id' => 'variations',
'title' => __( 'Variations', 'woocommerce' ),
'order' => 40,
),
array(
array(
'woocommerce/product-variations-fields',
array(
'description' => sprintf(
/* translators: %1$s: Sell your product in multiple variations like size or color. strong opening tag. %2$s: Sell your product in multiple variations like size or color. strong closing tag.*/
__( '%1$sSell your product in multiple variations like size or color.%2$s Get started by adding options for the buyers to choose on the product page.', 'woocommerce' ),
'<strong>',
'</strong>'
),
),
array(),
),
),
)
);
if ( isset( $template ) ) {
$args['template_lock'] = 'all';
$args['template'] = $template->get_formatted_template();
}
}
return $args;
}
/**
* Enables variation post type in REST API.
*
* @param array $args Array of post type arguments.
* @return array Array of post type arguments.
*/
public function enable_rest_api_for_product_variation( $args ) {
$args['show_in_rest'] = true;
return $args;
}
/**
* Adds fields so that we can store user preferences for the variations block.
*
* @param array $user_data_fields User data fields.
* @return array
*/
public function add_user_data_fields( $user_data_fields ) {
return array_merge(
$user_data_fields,
array(
'variable_product_block_tour_shown',
'product_block_variable_options_notice_dismissed',
'variable_items_without_price_notice_dismissed',
)
);
}
/**
* Sets the current screen to the block editor if a wc-admin page.
*/

View File

@@ -0,0 +1,65 @@
<?php
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\AbstractBlockTemplate;
/**
* Block template class.
*/
abstract class AbstractProductFormTemplate extends AbstractBlockTemplate implements ProductFormTemplateInterface {
/**
* Get the template area.
*/
public function get_area(): string {
return 'product-form';
}
/**
* Get a group block by ID.
*
* @param string $group_id The group block ID.
* @throws \UnexpectedValueException If block is not of type GroupInterface.
*/
public function get_group_by_id( string $group_id ): ?GroupInterface {
$group = $this->get_block( $group_id );
if ( $group && ! $group instanceof GroupInterface ) {
throw new \UnexpectedValueException( 'Block with specified ID is not a group.' );
}
return $group;
}
/**
* Get a section block by ID.
*
* @param string $section_id The section block ID.
* @throws \UnexpectedValueException If block is not of type SectionInterface.
*/
public function get_section_by_id( string $section_id ): ?SectionInterface {
$section = $this->get_block( $section_id );
if ( $section && ! $section instanceof SectionInterface ) {
throw new \UnexpectedValueException( 'Block with specified ID is not a section.' );
}
return $section;
}
/**
* Get a block by ID.
*
* @param string $block_id The block block ID.
*/
public function get_block_by_id( string $block_id ): ?BlockInterface {
return $this->get_block( $block_id );
}
/**
* Add a custom block type to this template.
*
* @param array $block_config The block data.
*/
public function add_group( array $block_config ): GroupInterface {
$block = new Group( $block_config, $this->get_root_template(), $this );
return $this->add_inner_block( $block );
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* WooCommerce Product Group Block class.
*/
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockContainerTrait;
/**
* Class for Group block.
*/
class Group extends ProductBlock implements GroupInterface {
use BlockContainerTrait;
/**
* Group Block constructor.
*
* @param array $config The block configuration.
* @param BlockTemplateInterface $root_template The block template that this block belongs to.
* @param ContainerInterface|null $parent The parent block container.
*
* @throws \ValueError If the block configuration is invalid.
* @throws \ValueError If the parent block container does not belong to the same template as the block.
* @throws \InvalidArgumentException If blockName key and value are passed into block configuration.
*/
public function __construct( array $config, BlockTemplateInterface &$root_template, ContainerInterface &$parent = null ) {
if ( ! empty( $config['blockName'] ) ) {
throw new \InvalidArgumentException( 'Unexpected key "blockName", this defaults to "woocommerce/product-tab".' );
}
if ( $config['id'] && ( empty( $config['attributes'] ) || empty( $config['attributes']['id'] ) ) ) {
$config['attributes'] = empty( $config['attributes'] ) ? [] : $config['attributes'];
$config['attributes']['id'] = $config['id'];
}
parent::__construct( array_merge( array( 'blockName' => 'woocommerce/product-tab' ), $config ), $root_template, $parent );
}
/**
* Add a section block type to this template.
*
* @param array $block_config The block data.
*/
public function add_section( array $block_config ): SectionInterface {
$block = new Section( $block_config, $this->get_root_template(), $this );
return $this->add_inner_block( $block );
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
/**
* Interface for group containers, which contain sections and blocks.
*/
interface GroupInterface extends BlockContainerInterface {
/**
* Adds a new section to the group
*
* @param array $block_config block config.
* @return SectionInterface new block section.
*/
public function add_section( array $block_config ): SectionInterface;
/**
* Adds a new block to the group.
*
* @param array $block_config block config.
*/
public function add_block( array $block_config ): BlockInterface;
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* WooCommerce Product Block class.
*/
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\AbstractBlock;
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockContainerTrait;
/**
* Class for Product block.
*/
class ProductBlock extends AbstractBlock implements ContainerInterface {
use BlockContainerTrait;
/**
* Adds block to the section block.
*
* @param array $block_config The block data.
*/
public function &add_block( array $block_config ): BlockInterface {
$block = new ProductBlock( $block_config, $this->get_root_template(), $this );
return $this->add_inner_block( $block );
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
/**
* Interface for block containers.
*/
interface ProductFormTemplateInterface extends BlockTemplateInterface {
/**
* Adds a new group block.
*
* @param array $block_config block config.
* @return BlockInterface new block section.
*/
public function add_group( array $block_config ): GroupInterface;
/**
* Gets Group block by id.
*
* @param string $group_id group id.
* @return GroupInterface|null
*/
public function get_group_by_id( string $group_id ): ?GroupInterface;
/**
* Gets Section block by id.
*
* @param string $section_id section id.
* @return SectionInterface|null
*/
public function get_section_by_id( string $section_id ): ?SectionInterface;
/**
* Gets Block by id.
*
* @param string $block_id block id.
* @return BlockInterface|null
*/
public function get_block_by_id( string $block_id ): ?BlockInterface;
}

View File

@@ -0,0 +1,528 @@
<?php
/**
* ProductVariationTemplate
*/
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
use Automattic\WooCommerce\Admin\Features\Features;
/**
* Simple Product Template.
*/
class ProductVariationTemplate extends AbstractProductFormTemplate implements ProductFormTemplateInterface {
/**
* The context name used to identify the editor.
*/
const GROUP_IDS = array(
'GENERAL' => 'general',
'PRICING' => 'pricing',
'INVENTORY' => 'inventory',
'SHIPPING' => 'shipping',
);
/**
* The option name used check whether the single variation notice has been dismissed.
*/
const SINGLE_VARIATION_NOTICE_DISMISSED_OPTION = 'woocommerce_single_variation_notice_dismissed';
/**
* SimpleProductTemplate constructor.
*/
public function __construct() {
$this->add_group_blocks();
$this->add_general_group_blocks();
$this->add_pricing_group_blocks();
$this->add_inventory_group_blocks();
$this->add_shipping_group_blocks();
}
/**
* Get the template ID.
*/
public function get_id(): string {
return 'product-variation';
}
/**
* Get the template title.
*/
public function get_title(): string {
return __( 'Product Variation Template', 'woocommerce' );
}
/**
* Get the template description.
*/
public function get_description(): string {
return __( 'Template for the product variation form', 'woocommerce' );
}
/**
* Adds the group blocks to the template.
*/
private function add_group_blocks() {
$this->add_group(
[
'id' => $this::GROUP_IDS['GENERAL'],
'order' => 10,
'attributes' => [
'title' => __( 'General', 'woocommerce' ),
],
]
);
$this->add_group(
[
'id' => $this::GROUP_IDS['PRICING'],
'order' => 20,
'attributes' => [
'title' => __( 'Pricing', 'woocommerce' ),
],
]
);
$this->add_group(
[
'id' => $this::GROUP_IDS['INVENTORY'],
'order' => 30,
'attributes' => [
'title' => __( 'Inventory', 'woocommerce' ),
],
]
);
$this->add_group(
[
'id' => $this::GROUP_IDS['SHIPPING'],
'order' => 40,
'attributes' => [
'title' => __( 'Shipping', 'woocommerce' ),
],
]
);
}
/**
* Adds the general group blocks to the template.
*/
private function add_general_group_blocks() {
$general_group = $this->get_group_by_id( $this::GROUP_IDS['GENERAL'] );
$general_group->add_block(
[
'id' => 'general-single-variation-notice',
'blockName' => 'woocommerce/product-single-variation-notice',
'order' => 10,
'attributes' => [
'content' => __( '<strong>Youre editing details specific to this variation.</strong> Some information, like description and images, will be inherited from the main product, <noticeLink><parentProductName/></noticeLink>.', 'woocommerce' ),
'type' => 'info',
'isDismissible' => true,
'name' => $this::SINGLE_VARIATION_NOTICE_DISMISSED_OPTION,
],
]
);
// Basic Details Section.
$basic_details = $general_group->add_section(
[
'id' => 'product-variation-details-section',
'order' => 10,
'attributes' => [
'title' => __( 'Variation details', 'woocommerce' ),
'description' => __( 'This info will be displayed on the product page, category pages, social media, and search results.', 'woocommerce' ),
],
]
);
$basic_details->add_block(
[
'id' => 'product-variation-note',
'blockName' => 'woocommerce/product-summary-field',
'order' => 20,
'attributes' => [
'property' => 'description',
'label' => __( 'Note <optional />', 'woocommerce' ),
'helpText' => 'Enter an optional note displayed on the product page when customers select this variation.',
],
]
);
$basic_details->add_block(
[
'id' => 'product-variation-visibility',
'blockName' => 'woocommerce/product-checkbox-field',
'order' => 30,
'attributes' => [
'property' => 'status',
'label' => __( 'Hide in product catalog', 'woocommerce' ),
'checkedValue' => 'private',
'uncheckedValue' => 'publish',
],
]
);
// Images section.
$images_section = $general_group->add_section(
[
'id' => 'product-variation-images-section',
'order' => 30,
'attributes' => [
'title' => __( 'Image', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Images guide link opening tag. %2$s: Images guide link closing tag. */
__( 'Drag images, upload new ones or select files from your library. For best results, use JPEG files that are 1000 by 1000 pixels or larger. %1$sHow to prepare images?%2$s', 'woocommerce' ),
'<a href="https://woocommerce.com/posts/how-to-take-professional-product-photos-top-tips" target="_blank" rel="noreferrer">',
'</a>'
),
],
]
);
$images_section->add_block(
[
'id' => 'product-variation-image',
'blockName' => 'woocommerce/product-images-field',
'order' => 10,
'attributes' => [
'property' => 'image',
'multiple' => false,
],
]
);
// Downloads section.
if ( Features::is_enabled( 'product-virtual-downloadable' ) ) {
$general_group->add_section(
[
'id' => 'product-variation-downloads-section',
'order' => 40,
'attributes' => [
'title' => __( 'Downloads', 'woocommerce' ),
'description' => __( "Add any files you'd like to make available for the customer to download after purchasing, such as instructions or warranty info.", 'woocommerce' ),
],
]
)->add_block(
[
'id' => 'product-variation-downloads',
'blockName' => 'woocommerce/product-downloads-field',
'order' => 10,
]
);
}
}
/**
* Adds the pricing group blocks to the template.
*/
private function add_pricing_group_blocks() {
$pricing_group = $this->get_group_by_id( $this::GROUP_IDS['PRICING'] );
$pricing_group->add_block(
[
'id' => 'pricing-single-variation-notice',
'blockName' => 'woocommerce/product-single-variation-notice',
'order' => 10,
'attributes' => [
'content' => __( '<strong>Youre editing details specific to this variation.</strong> Some information, like description and images, will be inherited from the main product, <noticeLink><parentProductName/></noticeLink>.', 'woocommerce' ),
'type' => 'info',
'isDismissible' => true,
'name' => $this::SINGLE_VARIATION_NOTICE_DISMISSED_OPTION,
],
]
);
// Product Pricing Section.
$product_pricing_section = $pricing_group->add_section(
[
'id' => 'product-pricing-section',
'order' => 20,
'attributes' => [
'title' => __( 'Pricing', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Images guide link opening tag. %2$s: Images guide link closing tag.*/
__( 'Set a competitive price, put the product on sale, and manage tax calculations. %1$sHow to price your product?%2$s', 'woocommerce' ),
'<a href="https://woocommerce.com/posts/how-to-price-products-strategies-expert-tips/" target="_blank" rel="noreferrer">',
'</a>'
),
'blockGap' => 'unit-40',
],
]
);
$pricing_columns = $product_pricing_section->add_block(
[
'id' => 'product-pricing-group-pricing-columns',
'blockName' => 'core/columns',
'order' => 10,
]
);
$pricing_column_1 = $pricing_columns->add_block(
[
'id' => 'product-pricing-group-pricing-column-1',
'blockName' => 'core/column',
'order' => 10,
'attributes' => [
'templateLock' => 'all',
],
]
);
$pricing_column_1->add_block(
[
'id' => 'product-pricing-regular-price',
'blockName' => 'woocommerce/product-regular-price-field',
'order' => 10,
'attributes' => [
'name' => 'regular_price',
'label' => __( 'Regular price', 'woocommerce' ),
'isRequired' => true,
],
]
);
$pricing_column_2 = $pricing_columns->add_block(
[
'id' => 'product-pricing-group-pricing-column-2',
'blockName' => 'core/column',
'order' => 20,
'attributes' => [
'templateLock' => 'all',
],
]
);
$pricing_column_2->add_block(
[
'id' => 'product-pricing-sale-price',
'blockName' => 'woocommerce/product-sale-price-field',
'order' => 10,
'attributes' => [
'label' => __( 'Sale price', 'woocommerce' ),
],
]
);
$product_pricing_section->add_block(
[
'id' => 'product-pricing-schedule-sale-fields',
'blockName' => 'woocommerce/product-schedule-sale-fields',
'order' => 20,
]
);
$product_pricing_section->add_block(
[
'id' => 'product-tax-class',
'blockName' => 'woocommerce/product-radio-field',
'order' => 40,
'attributes' => [
'title' => __( 'Tax class', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
__( 'Apply a tax rate if this product qualifies for tax reduction or exemption. %1$sLearn more%2$s.', 'woocommerce' ),
'<a href="https://woocommerce.com/document/setting-up-taxes-in-woocommerce/#shipping-tax-class" target="_blank" rel="noreferrer">',
'</a>'
),
'property' => 'tax_class',
'options' => [
[
'label' => __( 'Same as main product', 'woocommerce' ),
'value' => 'parent',
],
[
'label' => __( 'Standard', 'woocommerce' ),
'value' => '',
],
[
'label' => __( 'Reduced rate', 'woocommerce' ),
'value' => 'reduced-rate',
],
[
'label' => __( 'Zero rate', 'woocommerce' ),
'value' => 'zero-rate',
],
],
],
]
);
}
/**
* Adds the inventory group blocks to the template.
*/
private function add_inventory_group_blocks() {
$inventory_group = $this->get_group_by_id( $this::GROUP_IDS['INVENTORY'] );
$inventory_group->add_block(
[
'id' => 'inventory-single-variation-notice',
'blockName' => 'woocommerce/product-single-variation-notice',
'order' => 10,
'attributes' => [
'content' => __( '<strong>Youre editing details specific to this variation.</strong> Some information, like description and images, will be inherited from the main product, <noticeLink><parentProductName/></noticeLink>.', 'woocommerce' ),
'type' => 'info',
'isDismissible' => true,
'name' => $this::SINGLE_VARIATION_NOTICE_DISMISSED_OPTION,
],
]
);
// Product Inventory Section.
$product_inventory_section = $inventory_group->add_section(
[
'id' => 'product-variation-inventory-section',
'order' => 20,
'attributes' => [
'title' => __( 'Inventory', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Inventory settings link opening tag. %2$s: Inventory settings link closing tag.*/
__( 'Set up and manage inventory for this product, including status and available quantity. %1$sManage store inventory settings%2$s', 'woocommerce' ),
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=products&section=inventory' ) . '" target="_blank" rel="noreferrer">',
'</a>'
),
'blockGap' => 'unit-40',
],
]
);
$product_inventory_inner_section = $product_inventory_section->add_section(
[
'id' => 'product-variation-inventory-inner-section',
'order' => 10,
]
);
$product_inventory_inner_section->add_block(
[
'id' => 'product-variation-sku-field',
'blockName' => 'woocommerce/product-sku-field',
'order' => 10,
]
);
$product_inventory_inner_section->add_block(
[
'id' => 'product-variation-track-stock',
'blockName' => 'woocommerce/product-toggle-field',
'order' => 20,
'attributes' => [
'label' => __( 'Track stock quantity for this product', 'woocommerce' ),
'property' => 'manage_stock',
'disabled' => 'yes' !== get_option( 'woocommerce_manage_stock' ),
'disabledCopy' => sprintf(
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
__( 'Per your %1$sstore settings%2$s, inventory management is <strong>disabled</strong>.', 'woocommerce' ),
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=products&section=inventory' ) . '" target="_blank" rel="noreferrer">',
'</a>'
),
],
]
);
$product_inventory_quantity_conditional = $product_inventory_inner_section->add_block(
[
'id' => 'product-variation-inventory-quantity-conditional-wrapper',
'blockName' => 'woocommerce/conditional',
'order' => 30,
'attributes' => [
'mustMatch' => [
'manage_stock' => [ true ],
],
],
]
);
$product_inventory_quantity_conditional->add_block(
[
'id' => 'product-variation-inventory-quantity',
'blockName' => 'woocommerce/product-inventory-quantity-field',
'order' => 10,
]
);
$product_stock_status_conditional = $product_inventory_section->add_block(
[
'id' => 'product-variation-stock-status-conditional-wrapper',
'blockName' => 'woocommerce/conditional',
'order' => 20,
'attributes' => [
'mustMatch' => [
'manage_stock' => [ false ],
],
],
]
);
$product_stock_status_conditional->add_block(
[
'id' => 'product-variation-stock-status',
'blockName' => 'woocommerce/product-radio-field',
'order' => 10,
'attributes' => [
'title' => __( 'Stock status', 'woocommerce' ),
'property' => 'stock_status',
'options' => [
[
'label' => __( 'In stock', 'woocommerce' ),
'value' => 'instock',
],
[
'label' => __( 'Out of stock', 'woocommerce' ),
'value' => 'outofstock',
],
[
'label' => __( 'On backorder', 'woocommerce' ),
'value' => 'onbackorder',
],
],
],
]
);
}
/**
* Adds the shipping group blocks to the template.
*/
private function add_shipping_group_blocks() {
$shipping_group = $this->get_group_by_id( $this::GROUP_IDS['SHIPPING'] );
$shipping_group->add_block(
[
'id' => 'shipping-single-variation-notice',
'blockName' => 'woocommerce/product-single-variation-notice',
'order' => 10,
'attributes' => [
'content' => __( '<strong>Youre editing details specific to this variation.</strong> Some information, like description and images, will be inherited from the main product, <noticeLink><parentProductName/></noticeLink>.', 'woocommerce' ),
'type' => 'info',
'isDismissible' => true,
'name' => $this::SINGLE_VARIATION_NOTICE_DISMISSED_OPTION,
],
]
);
// Virtual section.
$shipping_group->add_section(
[
'id' => 'product-variation-virtual-section',
'order' => 20,
]
)->add_block(
[
'id' => 'product-variation-virtual',
'blockName' => 'woocommerce/product-toggle-field',
'order' => 10,
'attributes' => [
'property' => 'virtual',
'checkedValue' => false,
'uncheckedValue' => true,
'label' => __( 'This variation requires shipping or pickup', 'woocommerce' ),
],
]
);
// Product Shipping Section.
$product_fee_and_dimensions_section = $shipping_group->add_section(
[
'id' => 'product-variation-fee-and-dimensions-section',
'order' => 30,
'attributes' => [
'title' => __( 'Fees & dimensions', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: How to get started? link opening tag. %2$s: How to get started? link closing tag.*/
__( 'Set up shipping costs and enter dimensions used for accurate rate calculations. %1$sHow to get started?%2$s.', 'woocommerce' ),
'<a href="https://woocommerce.com/posts/how-to-calculate-shipping-costs-for-your-woocommerce-store/" target="_blank" rel="noreferrer">',
'</a>'
),
],
]
);
$product_fee_and_dimensions_section->add_block(
[
'id' => 'product-variation-shipping-class',
'blockName' => 'woocommerce/product-shipping-class-field',
'order' => 10,
]
);
$product_fee_and_dimensions_section->add_block(
[
'id' => 'product-variation-shipping-dimensions',
'blockName' => 'woocommerce/product-shipping-dimensions-fields',
'order' => 20,
]
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* WooCommerce Section Block class.
*/
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
/**
* Class for Section block.
*/
class Section extends ProductBlock implements SectionInterface {
/**
* Section Block constructor.
*
* @param array $config The block configuration.
* @param BlockTemplateInterface $root_template The block template that this block belongs to.
* @param ContainerInterface|null $parent The parent block container.
*
* @throws \ValueError If the block configuration is invalid.
* @throws \ValueError If the parent block container does not belong to the same template as the block.
* @throws \InvalidArgumentException If blockName key and value are passed into block configuration.
*/
public function __construct( array $config, BlockTemplateInterface &$root_template, ContainerInterface &$parent = null ) {
if ( ! empty( $config['blockName'] ) ) {
throw new \InvalidArgumentException( 'Unexpected key "blockName", this defaults to "woocommerce/product-section".' );
}
parent::__construct( array_merge( array( 'blockName' => 'woocommerce/product-section' ), $config ), $root_template, $parent );
}
/**
* Add a section block type to this template.
*
* @param array $block_config The block data.
*/
public function add_section( array $block_config ): SectionInterface {
$block = new Section( $block_config, $this->get_root_template(), $this );
return $this->add_inner_block( $block );
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
/**
* Interface for section containers, which contain sub-sections and blocks.
*/
interface SectionInterface extends BlockContainerInterface {
/**
* Adds a new sub-section to the section.
*
* @param array $block_config block config.
* @return SectionInterface new block section.
*/
public function add_section( array $block_config ): SectionInterface;
/**
* Adds a new block to the section.
*
* @param array $block_config block config.
*/
public function add_block( array $block_config ): BlockInterface;
}

View File

@@ -0,0 +1,878 @@
<?php
/**
* SimpleProductTemplate
*/
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates;
use Automattic\WooCommerce\Admin\Features\Features;
/**
* Simple Product Template.
*/
class SimpleProductTemplate extends AbstractProductFormTemplate implements ProductFormTemplateInterface {
/**
* The context name used to identify the editor.
*/
const GROUP_IDS = array(
'GENERAL' => 'general',
'ORGANIZATION' => 'organization',
'PRICING' => 'pricing',
'INVENTORY' => 'inventory',
'SHIPPING' => 'shipping',
'VARIATIONS' => 'variations',
);
/**
* SimpleProductTemplate constructor.
*/
public function __construct() {
$this->add_group_blocks();
$this->add_general_group_blocks();
$this->add_organization_group_blocks();
$this->add_pricing_group_blocks();
$this->add_inventory_group_blocks();
$this->add_shipping_group_blocks();
$this->add_variation_group_blocks();
}
/**
* Get the template ID.
*/
public function get_id(): string {
return 'simple-product';
}
/**
* Get the template title.
*/
public function get_title(): string {
return __( 'Simple Product Template', 'woocommerce' );
}
/**
* Get the template description.
*/
public function get_description(): string {
return __( 'Template for the simple product form', 'woocommerce' );
}
/**
* Adds the group blocks to the template.
*/
private function add_group_blocks() {
$this->add_group(
[
'id' => $this::GROUP_IDS['GENERAL'],
'order' => 10,
'attributes' => [
'title' => __( 'General', 'woocommerce' ),
],
]
);
$this->add_group(
[
'id' => $this::GROUP_IDS['ORGANIZATION'],
'order' => 15,
'attributes' => [
'title' => __( 'Organization', 'woocommerce' ),
],
]
);
$this->add_group(
[
'id' => $this::GROUP_IDS['PRICING'],
'order' => 20,
'attributes' => [
'title' => __( 'Pricing', 'woocommerce' ),
],
]
);
$this->add_group(
[
'id' => $this::GROUP_IDS['INVENTORY'],
'order' => 30,
'attributes' => [
'title' => __( 'Inventory', 'woocommerce' ),
],
]
);
$this->add_group(
[
'id' => $this::GROUP_IDS['SHIPPING'],
'order' => 40,
'attributes' => [
'title' => __( 'Shipping', 'woocommerce' ),
],
]
);
if ( Features::is_enabled( 'product-variation-management' ) ) {
$this->add_group(
[
'id' => $this::GROUP_IDS['VARIATIONS'],
'order' => 50,
'attributes' => [
'title' => __( 'Variations', 'woocommerce' ),
],
]
);
}
}
/**
* Adds the general group blocks to the template.
*/
private function add_general_group_blocks() {
$general_group = $this->get_group_by_id( $this::GROUP_IDS['GENERAL'] );
// Basic Details Section.
$basic_details = $general_group->add_section(
[
'id' => 'basic-details',
'order' => 10,
'attributes' => [
'title' => __( 'Basic details', 'woocommerce' ),
'description' => __( 'This info will be displayed on the product page, category pages, social media, and search results.', 'woocommerce' ),
],
]
);
$basic_details->add_block(
[
'id' => 'product-name',
'blockName' => 'woocommerce/product-name-field',
'order' => 10,
'attributes' => [
'name' => 'Product name',
'autoFocus' => true,
],
]
);
$basic_details->add_block(
[
'id' => 'product-summary',
'blockName' => 'woocommerce/product-summary-field',
'order' => 20,
'attributes' => [
'property' => 'short_description',
],
]
);
$pricing_columns = $basic_details->add_block(
[
'id' => 'product-pricing-columns',
'blockName' => 'core/columns',
'order' => 30,
]
);
$pricing_column_1 = $pricing_columns->add_block(
[
'id' => 'product-pricing-column-1',
'blockName' => 'core/column',
'order' => 10,
'attributes' => [
'templateLock' => 'all',
],
]
);
$pricing_column_1->add_block(
[
'id' => 'product-regular-price',
'blockName' => 'woocommerce/product-regular-price-field',
'order' => 10,
'attributes' => [
'name' => 'regular_price',
'label' => __( 'List price', 'woocommerce' ),
/* translators: PricingTab: This is a link tag to the pricing tab. */
'help' => __( 'Manage more settings in <PricingTab>Pricing.</PricingTab>', 'woocommerce' ),
],
]
);
$pricing_column_2 = $pricing_columns->add_block(
[
'id' => 'product-pricing-column-2',
'blockName' => 'core/column',
'order' => 20,
'attributes' => [
'templateLock' => 'all',
],
]
);
$pricing_column_2->add_block(
[
'id' => 'product-sale-price',
'blockName' => 'woocommerce/product-sale-price-field',
'order' => 10,
'attributes' => [
'label' => __( 'Sale price', 'woocommerce' ),
],
]
);
// Description section.
$description_section = $general_group->add_section(
[
'id' => 'product-description-section',
'order' => 20,
'attributes' => [
'title' => __( 'Description', 'woocommerce' ),
'description' => __( 'What makes this product unique? What are its most important features? Enrich the product page by adding rich content using blocks.', 'woocommerce' ),
],
]
);
$description_section->add_block(
[
'id' => 'product-description',
'blockName' => 'woocommerce/product-description-field',
'order' => 10,
]
);
// Images section.
$images_section = $general_group->add_section(
[
'id' => 'product-images-section',
'order' => 30,
'attributes' => [
'title' => __( 'Images', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Images guide link opening tag. %2$s: Images guide link closing tag. */
__( 'Drag images, upload new ones or select files from your library. For best results, use JPEG files that are 1000 by 1000 pixels or larger. %1$sHow to prepare images?%2$s', 'woocommerce' ),
'<a href="https://woocommerce.com/posts/how-to-take-professional-product-photos-top-tips" target="_blank" rel="noreferrer">',
'</a>'
),
],
]
);
$images_section->add_block(
[
'id' => 'product-images',
'blockName' => 'woocommerce/product-images-field',
'order' => 10,
'attributes' => [
'images' => [],
'property' => 'images',
],
]
);
// Downloads section.
if ( Features::is_enabled( 'product-virtual-downloadable' ) ) {
$general_group->add_section(
[
'id' => 'product-downloads-section',
'order' => 40,
'attributes' => [
'title' => __( 'Downloads', 'woocommerce' ),
'description' => __( "Add any files you'd like to make available for the customer to download after purchasing, such as instructions or warranty info.", 'woocommerce' ),
],
]
)->add_block(
[
'id' => 'product-downloads',
'blockName' => 'woocommerce/product-downloads-field',
'order' => 10,
]
);
}
}
/**
* Adds the organization group blocks to the template.
*/
private function add_organization_group_blocks() {
$organization_group = $this->get_group_by_id( $this::GROUP_IDS['ORGANIZATION'] );
// Product Catalog Section.
$product_catalog_section = $organization_group->add_section(
[
'id' => 'product-catalog-section',
'order' => 10,
'attributes' => [
'title' => __( 'Product catalog', 'woocommerce' ),
],
]
);
$product_catalog_section->add_block(
[
'id' => 'product-categories',
'blockName' => 'woocommerce/product-taxonomy-field',
'order' => 10,
'attributes' => [
'slug' => 'product_cat',
'property' => 'categories',
'label' => __( 'Categories', 'woocommerce' ),
'createTitle' => __( 'Create new category', 'woocommerce' ),
'dialogNameHelpText' => __( 'Shown to customers on the product page.', 'woocommerce' ),
'parentTaxonomyText' => __( 'Parent category', 'woocommerce' ),
],
]
);
$product_catalog_section->add_block(
[
'id' => 'product-tags',
'blockName' => 'woocommerce/product-tag-field',
'attributes' => [
'name' => 'tags',
],
]
);
$product_catalog_section->add_block(
[
'id' => 'product-catalog-search-visibility',
'blockName' => 'woocommerce/product-catalog-visibility-field',
'order' => 20,
'attributes' => [
'label' => __( 'Hide in product catalog', 'woocommerce' ),
'visibility' => 'search',
],
]
);
$product_catalog_section->add_block(
[
'id' => 'product-catalog-catalog-visibility',
'blockName' => 'woocommerce/product-catalog-visibility-field',
'order' => 30,
'attributes' => [
'label' => __( 'Hide from search results', 'woocommerce' ),
'visibility' => 'catalog',
],
]
);
$product_catalog_section->add_block(
[
'id' => 'product-enable-product-reviews',
'blockName' => 'woocommerce/product-checkbox-field',
'order' => 40,
'attributes' => [
'label' => __( 'Enable product reviews', 'woocommerce' ),
'property' => 'reviews_allowed',
],
]
);
$product_catalog_section->add_block(
[
'id' => 'product-post-password',
'blockName' => 'woocommerce/product-password-field',
'order' => 50,
'attributes' => [
'label' => __( 'Require a password', 'woocommerce' ),
],
]
);
// Attributes section.
$product_catalog_section = $organization_group->add_section(
[
'id' => 'product-attributes-section',
'order' => 20,
'attributes' => [
'title' => __( 'Attributes', 'woocommerce' ),
],
]
);
$product_catalog_section->add_block(
[
'id' => 'product-attributes',
'blockName' => 'woocommerce/product-attributes-field',
'order' => 10,
]
);
}
/**
* Adds the pricing group blocks to the template.
*/
private function add_pricing_group_blocks() {
$pricing_group = $this->get_group_by_id( $this::GROUP_IDS['PRICING'] );
$pricing_group->add_block(
[
'id' => 'pricing-has-variations-notice',
'blockName' => 'woocommerce/product-has-variations-notice',
'order' => 10,
'attributes' => [
'content' => __( 'This product has options, such as size or color. You can now manage each variation\'s price and other details individually.', 'woocommerce' ),
'buttonText' => __( 'Go to Variations', 'woocommerce' ),
'type' => 'info',
],
]
);
// Product Pricing Section.
$product_pricing_section = $pricing_group->add_section(
[
'id' => 'product-pricing-section',
'order' => 20,
'attributes' => [
'title' => __( 'Pricing', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Images guide link opening tag. %2$s: Images guide link closing tag.*/
__( 'Set a competitive price, put the product on sale, and manage tax calculations. %1$sHow to price your product?%2$s', 'woocommerce' ),
'<a href="https://woocommerce.com/posts/how-to-price-products-strategies-expert-tips/" target="_blank" rel="noreferrer">',
'</a>'
),
'blockGap' => 'unit-40',
],
]
);
$pricing_columns = $product_pricing_section->add_block(
[
'id' => 'product-pricing-group-pricing-columns',
'blockName' => 'core/columns',
'order' => 10,
]
);
$pricing_column_1 = $pricing_columns->add_block(
[
'id' => 'product-pricing-group-pricing-column-1',
'blockName' => 'core/column',
'order' => 10,
'attributes' => [
'templateLock' => 'all',
],
]
);
$pricing_column_1->add_block(
[
'id' => 'product-pricing-regular-price',
'blockName' => 'woocommerce/product-regular-price-field',
'order' => 10,
'attributes' => [
'name' => 'regular_price',
'label' => __( 'List price', 'woocommerce' ),
],
]
);
$pricing_column_2 = $pricing_columns->add_block(
[
'id' => 'product-pricing-group-pricing-column-2',
'blockName' => 'core/column',
'order' => 20,
'attributes' => [
'templateLock' => 'all',
],
]
);
$pricing_column_2->add_block(
[
'id' => 'product-pricing-sale-price',
'blockName' => 'woocommerce/product-sale-price-field',
'order' => 10,
'attributes' => [
'label' => __( 'Sale price', 'woocommerce' ),
],
]
);
$product_pricing_section->add_block(
[
'id' => 'product-pricing-schedule-sale-fields',
'blockName' => 'woocommerce/product-schedule-sale-fields',
'order' => 20,
]
);
$product_pricing_section->add_block(
[
'id' => 'product-sale-tax',
'blockName' => 'woocommerce/product-radio-field',
'order' => 30,
'attributes' => [
'title' => __( 'Charge sales tax on', 'woocommerce' ),
'property' => 'tax_status',
'options' => [
[
'label' => __( 'Product and shipping', 'woocommerce' ),
'value' => 'taxable',
],
[
'label' => __( 'Only shipping', 'woocommerce' ),
'value' => 'shipping',
],
[
'label' => __( "Don't charge tax", 'woocommerce' ),
'value' => 'none',
],
],
],
]
);
$pricing_advanced_block = $product_pricing_section->add_block(
[
'id' => 'product-pricing-advanced',
'blockName' => 'woocommerce/product-collapsible',
'order' => 40,
'attributes' => [
'toggleText' => __( 'Advanced', 'woocommerce' ),
'initialCollapsed' => true,
'persistRender' => true,
],
]
);
$pricing_advanced_block->add_block(
[
'id' => 'product-tax-class',
'blockName' => 'woocommerce/product-radio-field',
'order' => 10,
'attributes' => [
'title' => __( 'Tax class', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
__( 'Apply a tax rate if this product qualifies for tax reduction or exemption. %1$sLearn more%2$s.', 'woocommerce' ),
'<a href="https://woocommerce.com/document/setting-up-taxes-in-woocommerce/#shipping-tax-class" target="_blank" rel="noreferrer">',
'</a>'
),
'property' => 'tax_class',
'options' => [
[
'label' => __( 'Standard', 'woocommerce' ),
'value' => '',
],
[
'label' => __( 'Reduced rate', 'woocommerce' ),
'value' => 'reduced-rate',
],
[
'label' => __( 'Zero rate', 'woocommerce' ),
'value' => 'zero-rate',
],
],
],
]
);
}
/**
* Adds the inventory group blocks to the template.
*/
private function add_inventory_group_blocks() {
$inventory_group = $this->get_group_by_id( $this::GROUP_IDS['INVENTORY'] );
$inventory_group->add_block(
[
'id' => 'product_variation_notice_inventory_tab',
'blockName' => 'woocommerce/product-has-variations-notice',
'order' => 10,
'attributes' => [
'content' => __( 'This product has options, such as size or color. You can now manage each variation\'s price and other details individually.', 'woocommerce' ),
'buttonText' => __( 'Go to Variations', 'woocommerce' ),
'type' => 'info',
],
]
);
// Product Pricing Section.
$product_inventory_section = $inventory_group->add_section(
[
'id' => 'product-inventory-section',
'order' => 20,
'attributes' => [
'title' => __( 'Inventory', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Inventory settings link opening tag. %2$s: Inventory settings link closing tag.*/
__( 'Set up and manage inventory for this product, including status and available quantity. %1$sManage store inventory settings%2$s', 'woocommerce' ),
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=products&section=inventory' ) . '" target="_blank" rel="noreferrer">',
'</a>'
),
'blockGap' => 'unit-40',
],
]
);
$product_inventory_inner_section = $product_inventory_section->add_section(
[
'id' => 'product-inventory-inner-section',
'order' => 10,
]
);
$product_inventory_inner_section->add_block(
[
'id' => 'product-sku-field',
'blockName' => 'woocommerce/product-sku-field',
'order' => 10,
]
);
$product_inventory_inner_section->add_block(
[
'id' => 'product-track-stock',
'blockName' => 'woocommerce/product-toggle-field',
'order' => 20,
'attributes' => [
'label' => __( 'Track stock quantity for this product', 'woocommerce' ),
'property' => 'manage_stock',
'disabled' => 'yes' !== get_option( 'woocommerce_manage_stock' ),
'disabledCopy' => sprintf(
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
__( 'Per your %1$sstore settings%2$s, inventory management is <strong>disabled</strong>.', 'woocommerce' ),
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=products&section=inventory' ) . '" target="_blank" rel="noreferrer">',
'</a>'
),
],
]
);
$product_inventory_quantity_conditional = $product_inventory_inner_section->add_block(
[
'id' => 'product-inventory-quantity-conditional-wrapper',
'blockName' => 'woocommerce/conditional',
'order' => 30,
'attributes' => [
'mustMatch' => [
'manage_stock' => [ true ],
],
],
]
);
$product_inventory_quantity_conditional->add_block(
[
'id' => 'product-inventory-quantity',
'blockName' => 'woocommerce/product-inventory-quantity-field',
'order' => 10,
]
);
$product_stock_status_conditional = $product_inventory_section->add_block(
[
'id' => 'product-stock-status-conditional-wrapper',
'blockName' => 'woocommerce/conditional',
'order' => 20,
'attributes' => [
'mustMatch' => [
'manage_stock' => [ false ],
],
],
]
);
$product_stock_status_conditional->add_block(
[
'id' => 'product-stock-status',
'blockName' => 'woocommerce/product-radio-field',
'order' => 10,
'attributes' => [
'title' => __( 'Stock status', 'woocommerce' ),
'property' => 'stock_status',
'options' => [
[
'label' => __( 'In stock', 'woocommerce' ),
'value' => 'instock',
],
[
'label' => __( 'Out of stock', 'woocommerce' ),
'value' => 'outofstock',
],
[
'label' => __( 'On backorder', 'woocommerce' ),
'value' => 'onbackorder',
],
],
],
]
);
$product_inventory_advanced = $product_inventory_section->add_block(
[
'id' => 'product-inventory-advanced',
'blockName' => 'woocommerce/product-collapsible',
'order' => 30,
'attributes' => [
'toggleText' => __( 'Advanced', 'woocommerce' ),
'initialCollapsed' => true,
'persistRender' => true,
],
]
);
$product_inventory_advanced_wrapper = $product_inventory_advanced->add_block(
[
'blockName' => 'woocommerce/product-section',
'order' => 10,
'attributes' => [
'blockGap' => 'unit-40',
],
]
);
$product_out_of_stock_conditional = $product_inventory_advanced_wrapper->add_block(
[
'id' => 'product-out-of-stock-conditional-wrapper',
'blockName' => 'woocommerce/conditional',
'order' => 10,
'attributes' => [
'mustMatch' => [
'manage_stock' => [ true ],
],
],
]
);
$product_out_of_stock_conditional->add_block(
[
'id' => 'product-out-of-stock',
'blockName' => 'woocommerce/product-radio-field',
'order' => 10,
'attributes' => [
'title' => __( 'When out of stock', 'woocommerce' ),
'property' => 'backorders',
'options' => [
[
'label' => __( 'Allow purchases', 'woocommerce' ),
'value' => 'yes',
],
[
'label' => __(
'Allow purchases, but notify customers',
'woocommerce'
),
'value' => 'notify',
],
[
'label' => __( "Don't allow purchases", 'woocommerce' ),
'value' => 'no',
],
],
],
]
);
$product_out_of_stock_conditional->add_block(
[
'id' => 'product-inventory-email',
'blockName' => 'woocommerce/product-inventory-email-field',
'order' => 20,
]
);
$product_inventory_advanced_wrapper->add_block(
[
'id' => 'product-limit-purchase',
'blockName' => 'woocommerce/product-checkbox-field',
'order' => 20,
'attributes' => [
'title' => __(
'Restrictions',
'woocommerce'
),
'label' => __(
'Limit purchases to 1 item per order',
'woocommerce'
),
'property' => 'sold_individually',
'tooltip' => __(
'When checked, customers will be able to purchase only 1 item in a single order. This is particularly useful for items that have limited quantity, like art or handmade goods.',
'woocommerce'
),
],
]
);
}
/**
* Adds the shipping group blocks to the template.
*/
private function add_shipping_group_blocks() {
$shipping_group = $this->get_group_by_id( $this::GROUP_IDS['SHIPPING'] );
$shipping_group->add_block(
[
'id' => 'product_variation_notice_shipping_tab',
'blockName' => 'woocommerce/product-has-variations-notice',
'order' => 10,
'attributes' => [
'content' => __( 'This product has options, such as size or color. You can now manage each variation\'s price and other details individually.', 'woocommerce' ),
'buttonText' => __( 'Go to Variations', 'woocommerce' ),
'type' => 'info',
],
]
);
// Virtual section.
$shipping_group->add_section(
[
'id' => 'product-virtual-section',
'order' => 10,
]
)->add_block(
[
'id' => 'product-virtual',
'blockName' => 'woocommerce/product-toggle-field',
'order' => 10,
'attributes' => [
'property' => 'virtual',
'checkedValue' => false,
'uncheckedValue' => true,
'label' => __( 'This product requires shipping or pickup', 'woocommerce' ),
],
]
);
// Product Shipping Section.
$product_fee_and_dimensions_section = $shipping_group->add_section(
[
'id' => 'product-fee-and-dimensions-section',
'order' => 20,
'attributes' => [
'title' => __( 'Fees & dimensions', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: How to get started? link opening tag. %2$s: How to get started? link closing tag.*/
__( 'Set up shipping costs and enter dimensions used for accurate rate calculations. %1$sHow to get started?%2$s.', 'woocommerce' ),
'<a href="https://woocommerce.com/posts/how-to-calculate-shipping-costs-for-your-woocommerce-store/" target="_blank" rel="noreferrer">',
'</a>'
),
],
]
);
$product_fee_and_dimensions_section->add_block(
[
'id' => 'product-shipping-class',
'blockName' => 'woocommerce/product-shipping-class-field',
'order' => 10,
]
);
$product_fee_and_dimensions_section->add_block(
[
'id' => 'product-shipping-dimensions',
'blockName' => 'woocommerce/product-shipping-dimensions-fields',
'order' => 20,
]
);
}
/**
* Adds the variation group blocks to the template.
*/
private function add_variation_group_blocks() {
$variation_group = $this->get_group_by_id( $this::GROUP_IDS['VARIATIONS'] );
if ( ! $variation_group ) {
return;
}
$variation_fields = $variation_group->add_block(
[
'id' => 'product_variation-field-group',
'blockName' => 'woocommerce/product-variations-fields',
'order' => 10,
'attributes' => [
'description' => sprintf(
/* translators: %1$s: Sell your product in multiple variations like size or color. strong opening tag. %2$s: Sell your product in multiple variations like size or color. strong closing tag.*/
__( '%1$sSell your product in multiple variations like size or color.%2$s Get started by adding options for the buyers to choose on the product page.', 'woocommerce' ),
'<strong>',
'</strong>'
),
],
]
);
$variation_options_section = $variation_fields->add_block(
[
'id' => 'product-variation-options-section',
'blockName' => 'woocommerce/product-section',
'order' => 10,
'attributes' => [
'title' => __( 'Variation options', 'woocommerce' ),
'description' => __( 'Add and manage attributes used for product options, such as size and color.', 'woocommerce' ),
],
]
);
$variation_options_section->add_block(
[
'id' => 'product-variation-options',
'blockName' => 'woocommerce/product-variations-options-field',
]
);
$variation_section = $variation_fields->add_block(
[
'id' => 'product-variation-section',
'blockName' => 'woocommerce/product-section',
'order' => 20,
'attributes' => [
'title' => __( 'Variations', 'woocommerce' ),
'description' => __( 'Manage individual product combinations created from options.', 'woocommerce' ),
],
]
);
$variation_section->add_block(
[
'id' => 'product-variation-items',
'blockName' => 'woocommerce/product-variation-items-field',
'order' => 10,
]
);
}
}

View File

@@ -5,6 +5,7 @@
namespace Automattic\WooCommerce\Admin\Features\ProductBlockEditor;
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
/**
@@ -62,7 +63,16 @@ class RedirectionController {
*/
protected function is_product_supported( $product_id ): bool {
$product = $product_id ? wc_get_product( $product_id ) : null;
return $product && in_array( $product->get_type(), $this->supported_post_types, true );
$digital_product = $product->is_downloadable() || $product->is_virtual();
if ( $product && in_array( $product->get_type(), $this->supported_post_types, true ) ) {
if ( Features::is_enabled( 'product-virtual-downloadable' ) ) {
return true;
}
return ! $digital_product;
}
return false;
}
/**
@@ -123,6 +133,7 @@ class RedirectionController {
);
}
/**
* Redirect non supported product types to legacy editor.
*/