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

@@ -97,7 +97,37 @@ class OnboardingFreeExtensions extends WC_REST_Data_Controller {
}
}
$extensions = $this->replace_jetpack_with_jetpack_boost_for_treatment( $extensions );
return new WP_REST_Response( $extensions );
}
private function replace_jetpack_with_jetpack_boost_for_treatment( array $extensions ) {
$is_treatment = \WooCommerce\Admin\Experimental_Abtest::in_treatment( 'woocommerce_jetpack_copy' );
if ( ! $is_treatment ) {
return $extensions;
}
$has_core_profiler = array_search( 'obw/core-profiler', array_column( $extensions, 'key' ) );
if ( $has_core_profiler === false ) {
return $extensions;
}
$has_jetpack = array_search( 'jetpack', array_column( $extensions[ $has_core_profiler ]['plugins'], 'key' ) );
if ( $has_jetpack === false ) {
return $extensions;
}
$jetpack = &$extensions[ $has_core_profiler ]['plugins'][ $has_jetpack ];
$jetpack->key = 'jetpack-boost';
$jetpack->name = 'Jetpack Boost';
$jetpack->label = __( 'Optimize store performance with Jetpack Boost', 'woocommerce' );
$jetpack->description = __( 'Speed up your store and improve your SEO with performance-boosting tools from Jetpack. Learn more', 'woocommerce' );
$jetpack->learn_more_link = 'https://jetpack.com/boost/';
return $extensions;
}
}

View File

@@ -148,6 +148,9 @@ class OnboardingPlugins extends WC_REST_Data_Controller {
true
);
}
add_action( 'woocommerce_plugins_install_error', array( $this, 'log_plugins_install_error' ), 10, 4 );
add_action( 'woocommerce_plugins_install_api_error', array( $this, 'log_plugins_install_api_error' ), 10, 2 );
}
/**
@@ -411,4 +414,41 @@ class OnboardingPlugins extends WC_REST_Data_Controller {
),
);
}
public function log_plugins_install_error( $slug, $api, $result, $upgrader ) {
$properties = array(
'error_message' => sprintf(
/* translators: %s: plugin slug (example: woocommerce-services) */
__(
'The requested plugin `%s` could not be installed.',
'woocommerce'
),
$slug
),
'type' => 'plugin_info_api_error',
'slug' => $slug,
'api_version' => $api->version,
'api_download_link' => $api->download_link,
'upgrader_skin_message' => implode( ',', $upgrader->skin->get_upgrade_messages() ),
'result' => is_wp_error( $result ) ? $result->get_error_message() : 'null',
);
wc_admin_record_tracks_event( 'coreprofiler_install_plugin_error', $properties );
}
public function log_plugins_install_api_error( $slug, $api ) {
$properties = array(
'error_message' => sprintf(
// translators: %s: plugin slug (example: woocommerce-services).
__(
'The requested plugin `%s` could not be installed. Plugin API call failed.',
'woocommerce'
),
$slug
),
'type' => 'plugin_install_error',
'api_error_message' => $api->get_error_message(),
'slug' => $slug,
);
wc_admin_record_tracks_event( 'coreprofiler_install_plugin_error', $properties );
}
}

View File

@@ -391,6 +391,7 @@ class OnboardingProfile extends \WC_REST_Data_Controller {
'items' => array(
'enum' => array(
'jetpack',
'jetpack-boost',
'woocommerce-services',
'woocommerce-payments',
'mailchimp-for-woocommerce',

View File

@@ -39,7 +39,7 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
/**
* Duration to milisecond mapping.
*
* @var string
* @var array
*/
protected $duration_to_ms = array(
'day' => DAY_IN_SECONDS * 1000,
@@ -762,6 +762,7 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
if ( ! $task && $id ) {
$task = new DeprecatedExtendedTask(
null,
array(
'id' => $id,
'is_dismissable' => true,
@@ -795,6 +796,7 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
if ( ! $task && $id ) {
$task = new DeprecatedExtendedTask(
null,
array(
'id' => $id,
'is_dismissable' => true,
@@ -837,6 +839,7 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
if ( ! $task && $task_id ) {
$task = new DeprecatedExtendedTask(
null,
array(
'id' => $task_id,
'is_snoozeable' => true,
@@ -874,6 +877,7 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
if ( ! $task && $id ) {
$task = new DeprecatedExtendedTask(
null,
array(
'id' => $id,
'is_snoozeable' => true,
@@ -961,6 +965,7 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
if ( ! $task && $id ) {
$task = new DeprecatedExtendedTask(
null,
array(
'id' => $id,
)

View File

@@ -61,6 +61,29 @@ class OnboardingThemes extends \WC_REST_Data_Controller {
'schema' => array( $this, 'get_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/recommended',
array(
'methods' => 'GET',
'callback' => array( $this, 'get_recommended_themes' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'industry' => array(
'type' => 'string',
'description' => 'Limits the results to themes relevant for this industry (optional)',
),
'currency' => array(
'type' => 'string',
'enum' => array( 'USD', 'AUD', 'CAD', 'EUR', 'GBP' ),
'default' => 'USD',
'description' => 'Returns pricing in this currency (optional, default: USD)',
),
),
'schema' => array( $this, 'get_recommended_item_schema' ),
)
);
}
/**
@@ -83,12 +106,7 @@ class OnboardingThemes extends \WC_REST_Data_Controller {
* @return WP_Error|array Theme installation status.
*/
public function install_theme( $request ) {
$allowed_themes = Themes::get_allowed_themes();
$theme = sanitize_text_field( $request['theme'] );
if ( ! in_array( $theme, $allowed_themes, true ) ) {
return new \WP_Error( 'woocommerce_rest_invalid_theme', __( 'Invalid theme.', 'woocommerce' ), 404 );
}
$theme = sanitize_text_field( $request['theme'] );
$installed_themes = wp_get_themes();
@@ -120,7 +138,7 @@ class OnboardingThemes extends \WC_REST_Data_Controller {
return new \WP_Error(
'woocommerce_rest_theme_install',
sprintf(
/* translators: %s: theme slug (example: woocommerce-services) */
/* translators: %s: theme slug (example: woocommerce-services) */
__( 'The requested theme `%s` could not be installed. Theme API call failed.', 'woocommerce' ),
$theme
),
@@ -135,7 +153,7 @@ class OnboardingThemes extends \WC_REST_Data_Controller {
return new \WP_Error(
'woocommerce_rest_theme_install',
sprintf(
/* translators: %s: theme slug (example: woocommerce-services) */
/* translators: %s: theme slug (example: woocommerce-services) */
__( 'The requested theme `%s` could not be installed.', 'woocommerce' ),
$theme
),
@@ -157,11 +175,7 @@ class OnboardingThemes extends \WC_REST_Data_Controller {
* @return WP_Error|array Theme activation status.
*/
public function activate_theme( $request ) {
$allowed_themes = Themes::get_allowed_themes();
$theme = sanitize_text_field( $request['theme'] );
if ( ! in_array( $theme, $allowed_themes, true ) ) {
return new \WP_Error( 'woocommerce_rest_invalid_theme', __( 'Invalid theme.', 'woocommerce' ), 404 );
}
$theme = sanitize_text_field( $request['theme'] );
require_once ABSPATH . 'wp-admin/includes/theme.php';
@@ -184,6 +198,190 @@ class OnboardingThemes extends \WC_REST_Data_Controller {
) );
}
/**
* Get recommended themes.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|array Theme activation status.
*/
public function get_recommended_themes( $request ) {
// Check if "industry" and "currency" parameters are provided in the request.
$industry = $request->get_param( 'industry' );
$currency = $request->get_param( 'currency' ) ?? 'USD';
// Return empty response if marketplace suggestions are disabled.
if (
/**
* Filter allow marketplace suggestions.
*
* User can disable all suggestions via filter.
*
* @since 8.3.0
*/
! apply_filters( 'woocommerce_allow_marketplace_suggestions', true ) ||
get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) === 'no'
) {
/**
* Filter the onboarding recommended themes response.
*
* @since 8.3.0
*
* @param array $response The recommended themes response.
* @param array $filtered_themes The filtered themes.
* @param string $industry The industry to filter by (if provided).
* @param string $currency The currency to convert prices to. (USD, AUD, CAD, EUR, GBP).
*
* @return array
*/
return apply_filters(
'__experimental_woocommerce_rest_get_recommended_themes',
array(
'themes' => array(),
'_links' => array(
'browse_all' => array(
'href' => home_url( '/wp-admin/themes.php' ),
),
),
),
$industry,
$currency
);
}
$current_theme_slug = wp_get_theme()->get_stylesheet();
// To be implemented: 1. Fetch themes from the marketplace API. 2. Convert prices to the requested currency.
// These are Dotcom themes.
$themes = array(
array(
'name' => 'Tsubaki',
'price' => 'Free',
'color_palettes' => array(),
'total_palettes' => 0,
'slug' => 'tsubaki',
'is_active' => 'tsubaki' === $current_theme_slug,
'thumbnail_url' => 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/tsubaki/screenshot.png',
'link_url' => 'https://wordpress.com/theme/tsubaki/',
),
array(
'name' => 'Tazza',
'price' => 'Free',
'color_palettes' => array(),
'total_palettes' => 0,
'slug' => 'tazza',
'is_active' => 'tazza' === $current_theme_slug,
'thumbnail_url' => 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/tazza/screenshot.png',
'link_url' => 'https://wordpress.com/theme/tazza/',
'total_palettes' => 0,
),
array(
'name' => 'Amulet',
'price' => 'Free',
'color_palettes' => array(
array(
'title' => 'Default',
'primary' => '#FEFBF3',
'secondary' => '#7F7E7A',
),
array(
'title' => 'Brown Sugar',
'primary' => '#EFEBE0',
'secondary' => '#AC6239',
),
array(
'title' => 'Midnight',
'primary' => '#161514',
'secondary' => '#AFADA7',
),
array(
'title' => 'Olive',
'primary' => '#FEFBF3',
'secondary' => '#7F7E7A',
),
),
'total_palettes' => 5,
'slug' => 'amulet',
'is_active' => 'amulet' === $current_theme_slug,
'thumbnail_url' => 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/amulet/screenshot.png',
'link_url' => 'https://wordpress.com/theme/amulet/',
),
array(
'name' => 'Zaino',
'price' => 'Free',
'color_palettes' => array(
array(
'title' => 'Default',
'primary' => '#202124',
'secondary' => '#E3CBC0',
),
array(
'title' => 'Aubergine',
'primary' => '#1B1031',
'secondary' => '#E1746D',
),
array(
'title' => 'Block out',
'primary' => '#FF5252',
'secondary' => '#252525',
),
array(
'title' => 'Canary',
'primary' => '#FDFF85',
'secondary' => '#353535',
),
),
'total_palettes' => 11,
'slug' => 'zaino',
'is_active' => 'zaino' === $current_theme_slug,
'thumbnail_url' => 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/zaino/screenshot.png',
'link_url' => 'https://wordpress.com/theme/zaino/',
),
);
// To be implemented: Filter themes based on industry.
if ( $industry ) {
$filtered_themes = array_filter(
$themes,
function ( $theme ) use ( $industry ) {
// Filter themes by industry.
// Example: return $theme['industry'] === $industry;.
return true;
}
);
} else {
$filtered_themes = $themes;
}
$response = array(
'themes' => $filtered_themes,
'_links' => array(
'browse_all' => array(
'href' => admin_url( 'themes.php' ),
),
),
);
/**
* Filter the onboarding recommended themes response.
*
* @since 8.3.0
*
* @param array $response The recommended themes response.
* @param array $filtered_themes The filtered themes.
* @param string $industry The industry to filter by (if provided).
* @param string $currency The currency to convert prices to. (USD, AUD, CAD, EUR, GBP).
*
* @return array
*/
return apply_filters(
'__experimental_woocommerce_rest_get_recommended_themes',
$response,
$industry,
$currency
);
}
/**
* Get the schema, conforming to JSON Schema.
*
@@ -218,4 +416,82 @@ class OnboardingThemes extends \WC_REST_Data_Controller {
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the recommended themes schema, conforming to JSON Schema.
*
* @return array
*/
public function get_recommended_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'onboarding_theme',
'type' => 'object',
'properties' => array(
'themes' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'name' => array(
'type' => 'string',
'description' => 'Theme Name',
),
'price' => array(
'type' => 'string',
'description' => 'Price',
),
'is_active' => array(
'type' => 'boolean',
'description' => 'Whether theme is active',
),
'thumbnail_url' => array(
'type' => 'string',
'description' => 'Thumbnail URL',
),
'link_url' => array(
'type' => 'string',
'description' => 'Link URL for the theme',
),
'color_palettes' => array(
'type' => 'array',
'description' => 'Array of color palette objects',
'items' => array(
'type' => 'object',
'properties' => array(
'primary' => array(
'type' => 'string',
'description' => 'Primary color',
),
'secondary' => array(
'type' => 'string',
'description' => 'Secondary color',
),
),
),
),
),
),
),
'_links' => array(
'type' => 'object',
'description' => 'Links related to this response',
'properties' => array(
'browse_all' => array(
'type' => 'object',
'description' => 'Link to browse all themes',
'properties' => array(
'href' => array(
'type' => 'string',
'description' => 'URL for browsing all themes',
),
),
),
),
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}

View File

@@ -201,6 +201,7 @@ class Options extends \WC_REST_Data_Controller {
'woocommerce_dimension_unit',
'woocommerce_weight_unit',
'woocommerce_product_editor_show_feedback_bar',
'woocommerce_single_variation_notice_dismissed',
'woocommerce_product_tour_modal_hidden',
'woocommerce_block_product_tour_shown',
'woocommerce_revenue_report_date_tour_shown',
@@ -213,6 +214,10 @@ class Options extends \WC_REST_Data_Controller {
'wcpay_welcome_page_incentives_dismissed',
'wcpay_welcome_page_viewed_timestamp',
'wcpay_welcome_page_exit_survey_more_info_needed_timestamp',
'woocommerce_customize_store_onboarding_tour_hidden',
'woocommerce_customize_store_ai_suggestions',
'woocommerce_admin_customize_store_completed',
'woocommerce_admin_customize_store_completed_theme_id',
// WC Test helper options.
'wc-admin-test-helper-rest-api-filters',
'wc_admin_helper_feature_values',

View File

@@ -8,6 +8,7 @@
namespace Automattic\WooCommerce\Admin\API;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\WooCommercePayments;
use Automattic\WooCommerce\Admin\PaymentMethodSuggestionsDataSourcePoller;
use Automattic\WooCommerce\Admin\PluginsHelper;
use Automattic\WooCommerce\Internal\Admin\Notes\InstallJPAndWCSPlugins;
@@ -600,16 +601,16 @@ class Plugins extends \WC_REST_Data_Controller {
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error communicating with the WooPayments plugin.', 'woocommerce' ), 500 );
}
$connect_url = add_query_arg(
array(
'wcpay-connect' => 'WCADMIN_PAYMENT_TASK',
'_wpnonce' => wp_create_nonce( 'wcpay-connect' ),
),
admin_url()
);
$args = WooCommercePayments::is_account_partially_onboarded() ? [
'wcpay-login' => '1',
'_wpnonce' => wp_create_nonce( 'wcpay-login' ),
] : [
'wcpay-connect' => 'WCADMIN_PAYMENT_TASK',
'_wpnonce' => wp_create_nonce( 'wcpay-connect' ),
];
return( array(
'connectUrl' => $connect_url,
'connectUrl' => add_query_arg( $args, admin_url() ),
) );
}

View File

@@ -30,6 +30,13 @@ class DataStore extends SqlQuery {
*/
protected $cache_timeout = 3600;
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = '';
/**
* Table used as a data store for this report.
*

View File

@@ -542,6 +542,87 @@ class Segmenter {
}
}
/**
* Calculate segments for totals where the segmenting property is bound to product (e.g. category, product_id, variation_id).
*
* @param array $segmenting_selections SELECT part of segmenting SQL query--one for 'product_level' and one for 'order_level'.
* @param string $segmenting_from FROM part of segmenting SQL query.
* @param string $segmenting_where WHERE part of segmenting SQL query.
* @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
* @param string $segmenting_dimension_name Name of the segmenting dimension.
* @param string $table_name Name of SQL table which is the stats table for orders.
* @param array $totals_query Array of SQL clauses for totals query.
* @param string $unique_orders_table Name of temporary SQL table that holds unique orders.
*
* @return array
*/
protected function get_product_related_totals_segments( $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $totals_query, $unique_orders_table ) {
return array();
}
/**
* Calculate segments for intervals where the segmenting property is bound to product (e.g. category, product_id, variation_id).
*
* @param array $segmenting_selections SELECT part of segmenting SQL query--one for 'product_level' and one for 'order_level'.
* @param string $segmenting_from FROM part of segmenting SQL query.
* @param string $segmenting_where WHERE part of segmenting SQL query.
* @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
* @param string $segmenting_dimension_name Name of the segmenting dimension.
* @param string $table_name Name of SQL table which is the stats table for orders.
* @param array $intervals_query Array of SQL clauses for intervals query.
* @param string $unique_orders_table Name of temporary SQL table that holds unique orders.
*
* @return array
*/
protected function get_product_related_intervals_segments( $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $intervals_query, $unique_orders_table ) {
return array();
}
/**
* Calculate segments for totals query where the segmenting property is bound to order (e.g. coupon or customer type).
*
* @param string $segmenting_select SELECT part of segmenting SQL query.
* @param string $segmenting_from FROM part of segmenting SQL query.
* @param string $segmenting_where WHERE part of segmenting SQL query.
* @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
* @param string $table_name Name of SQL table which is the stats table for orders.
* @param array $totals_query Array of SQL clauses for intervals query.
*
* @return array
*/
protected function get_order_related_totals_segments( $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $totals_query ) {
return array();
}
/**
* Calculate segments for intervals query where the segmenting property is bound to order (e.g. coupon or customer type).
*
* @param string $segmenting_select SELECT part of segmenting SQL query.
* @param string $segmenting_from FROM part of segmenting SQL query.
* @param string $segmenting_where WHERE part of segmenting SQL query.
* @param string $segmenting_groupby GROUP BY part of segmenting SQL query.
* @param string $table_name Name of SQL table which is the stats table for orders.
* @param array $intervals_query Array of SQL clauses for intervals query.
*
* @return array
*/
protected function get_order_related_intervals_segments( $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $intervals_query ) {
return array();
}
/**
* Return array of segments formatted for REST response.
*
* @param string $type Type of segments to return--'totals' or 'intervals'.
* @param array $query_params SQL query parameter array.
* @param string $table_name Name of main SQL table for the data store (used as basis for JOINS).
*
* @return array
*/
protected function get_segments( $type, $query_params, $table_name ) {
return array();
}
/**
* Calculate segments for segmenting property bound to product (e.g. category, product_id, variation_id).
*

View File

@@ -0,0 +1,8 @@
<?php
namespace Automattic\WooCommerce\Admin\BlockTemplates;
/**
* Interface for block containers.
*/
interface BlockContainerInterface extends BlockInterface, ContainerInterface {}

View File

@@ -0,0 +1,119 @@
<?php
namespace Automattic\WooCommerce\Admin\BlockTemplates;
/**
* Interface for block configuration used to specify blocks in BlockTemplate.
*/
interface BlockInterface {
/**
* Key for the block name in the block configuration.
*/
public const NAME_KEY = 'blockName';
/**
* Key for the block ID in the block configuration.
*/
public const ID_KEY = 'id';
/**
* Key for the internal order in the block configuration.
*/
public const ORDER_KEY = 'order';
/**
* Key for the block attributes in the block configuration.
*/
public const ATTRIBUTES_KEY = 'attributes';
/**
* Key for the block hide conditions in the block configuration.
*/
public const HIDE_CONDITIONS_KEY = 'hideConditions';
/**
* Get the block name.
*/
public function get_name(): string;
/**
* Get the block ID.
*/
public function get_id(): string;
/**
* Get the block order.
*/
public function get_order(): int;
/**
* Set the block order.
*
* @param int $order The block order.
*/
public function set_order( int $order );
/**
* Get the block attributes.
*/
public function get_attributes(): array;
/**
* Set the block attributes.
*
* @param array $attributes The block attributes.
*/
public function set_attributes( array $attributes );
/**
* Get the parent container that the block belongs to.
*/
public function &get_parent(): ContainerInterface;
/**
* Get the root template that the block belongs to.
*/
public function &get_root_template(): BlockTemplateInterface;
/**
* Remove the block from its parent.
*/
public function remove();
/**
* Check if the block is detached from its parent or root template.
*
* @return bool True if the block is detached from its parent or root template.
*/
public function is_detached(): bool;
/**
* Add a hide condition to the block.
*
* The hide condition is a JavaScript-like expression that will be evaluated on the client to determine if the block should be hidden.
* See [@woocommerce/expression-evaluation](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/expression-evaluation/README.md) for more details.
*
* @param string $expression An expression, which if true, will hide the block.
* @return string The key of the hide condition, which can be used to remove the hide condition.
*/
public function add_hide_condition( string $expression ): string;
/**
* Remove a hide condition from the block.
*
* @param string $key The key of the hide condition to remove.
*/
public function remove_hide_condition( string $key );
/**
* Get the hide conditions of the block.
*/
public function get_hide_conditions(): array;
/**
* Get the block configuration as a formatted template.
*
* @return array The block configuration as a formatted template.
*/
public function get_formatted_template(): array;
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Automattic\WooCommerce\Admin\BlockTemplates;
/**
* Interface for block-based template.
*/
interface BlockTemplateInterface extends ContainerInterface {
/**
* Get the template ID.
*/
public function get_id(): string;
/**
* Get the template title.
*/
public function get_title(): string;
/**
* Get the template description.
*/
public function get_description(): string;
/**
* Get the template area.
*/
public function get_area(): string;
/**
* Generate a block ID based on a base.
*
* @param string $id_base The base to use when generating an ID.
* @return string
*/
public function generate_block_id( string $id_base ): string;
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Automattic\WooCommerce\Admin\BlockTemplates;
/**
* Interface for block containers.
*/
interface ContainerInterface {
/**
* Get the root template that the block belongs to.
*/
public function &get_root_template(): BlockTemplateInterface;
/**
* Get the block configuration as a formatted template.
*/
public function get_formatted_template(): array;
/**
* Get a block by ID.
*
* @param string $block_id The block ID.
*/
public function get_block( string $block_id ): ?BlockInterface;
/**
* Removes a block from the container.
*
* @param string $block_id The block ID.
*
* @throws \UnexpectedValueException If the block container is not an ancestor of the block.
*/
public function remove_block( string $block_id );
/**
* Removes all blocks from the container.
*/
public function remove_blocks();
}

View File

@@ -48,7 +48,7 @@ class Package {
*/
public static function init() {
// Avoid double initialization when the feature plugin is in use.
if ( defined( 'WC_ADMIN_VERSION_NUMBER' ) ) {
if (defined( 'WC_ADMIN_VERSION_NUMBER' ) ) {
self::$active_version = WC_ADMIN_VERSION_NUMBER;
return;
}

View File

@@ -25,6 +25,7 @@ defined( 'ABSPATH' ) || exit;
* A facade to allow deprecating an entire class.
*/
class DeprecatedClassFacade {
/**
* The instance that this facade covers over.
*
@@ -32,6 +33,21 @@ class DeprecatedClassFacade {
*/
protected $instance;
/**
* The name of the non-deprecated class that this facade covers.
*
* @var string
*/
protected static $facade_over_classname;
/**
* The version that this class was deprecated in.
*
* @var string
*/
protected static $deprecated_in_version = '';
/**
* Constructor.
*/

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.
*/

View File

@@ -496,27 +496,6 @@ class WC_Admin_Notes_Selling_Online_Courses extends DeprecatedClassFacade {
protected static $deprecated_in_version = '4.8.0';
}
/**
* WC_Admin_Notes_Test_Checkout.
*
* @deprecated since 4.8.0, use TestCheckout
*/
class WC_Admin_Notes_Test_Checkout extends DeprecatedClassFacade {
/**
* The name of the non-deprecated class that this facade covers.
*
* @var string
*/
protected static $facade_over_classname = 'Automattic\WooCommerce\Internal\Admin\Notes\TestCheckout';
/**
* The version that this class was deprecated in.
*
* @var string
*/
protected static $deprecated_in_version = '4.8.0';
}
/**
* WC_Admin_Notes_Tracking_Opt_In.
*

View File

@@ -450,6 +450,10 @@ class PageController {
$options['path'] = self::PAGE_ROOT . '&path=' . $options['path'];
}
if ( null !== $options['position'] ) {
$options['position'] = intval( round( $options['position'] ) );
}
if ( is_null( $options['parent'] ) ) {
add_menu_page(
$options['title'],
@@ -458,7 +462,7 @@ class PageController {
$options['path'],
array( __CLASS__, 'page_wrapper' ),
$options['icon'],
intval( round( $options['position'] ) )
$options['position']
);
} else {
$parent_path = $this->get_path_from_id( $options['parent'] );

View File

@@ -186,11 +186,12 @@ class PluginsHelper {
include_once ABSPATH . '/wp-admin/includes/class-wp-upgrader.php';
include_once ABSPATH . '/wp-admin/includes/class-plugin-upgrader.php';
$existing_plugins = self::get_installed_plugins_paths();
$installed_plugins = array();
$results = array();
$time = array();
$errors = new WP_Error();
$existing_plugins = self::get_installed_plugins_paths();
$installed_plugins = array();
$results = array();
$time = array();
$errors = new WP_Error();
$install_start_time = time();
foreach ( $plugins as $plugin ) {
$slug = sanitize_key( $plugin );
@@ -305,8 +306,6 @@ class PluginsHelper {
$logger && $logger->installed( $plugin, $time[ $plugin ] );
}
$logger && $logger->complete();
$data = array(
'installed' => $installed_plugins,
'results' => $results,
@@ -314,6 +313,8 @@ class PluginsHelper {
'time' => $time,
);
$logger && $logger->complete( array_merge( $data, array( 'start_time' => $install_start_time ) ) );
return $data;
}

View File

@@ -20,7 +20,7 @@ class AsyncPluginsInstallLogger implements PluginsInstallLogger {
* @param string $option_name option name.
*/
public function __construct( string $option_name ) {
$this->option_name = $option_name;
$this->option_name = $option_name;
add_option(
$this->option_name,
array(
@@ -135,14 +135,97 @@ class AsyncPluginsInstallLogger implements PluginsInstallLogger {
/**
* Record completed_time.
*
* @param array $data return data from install_plugins().
* @return void
*/
public function complete() {
public function complete( $data = array() ) {
$option = $this->get();
$option['complete_time'] = time();
$option['status'] = 'complete';
$this->track( $data );
$this->update( $option );
}
private function get_plugin_track_key( $id ) {
$slug = explode( ':', $id )[0];
$key = preg_match( '/^woocommerce(-|_)payments$/', $slug )
? 'wcpay'
: explode( ':', str_replace( '-', '_', $slug ) )[0];
return $key;
}
/**
* Returns time frame for a given time in milliseconds.
*
* @param int $timeInMs - time in milliseconds
*
* @return string - Time frame.
*/
function get_timeframe( $timeInMs ) {
$time_frames = [
[
'name' => '0-2s',
'max' => 2,
],
[
'name' => '2-5s',
'max' => 5,
],
[
'name' => '5-10s',
'max' => 10,
],
[
'name' => '10-15s',
'max' => 15,
],
[
'name' => '15-20s',
'max' => 20,
],
[
'name' => '20-30s',
'max' => 30,
],
[
'name' => '30-60s',
'max' => 60,
],
[ 'name' => '>60s' ],
];
foreach ( $time_frames as $time_frame ) {
if ( ! isset( $time_frame['max'] ) ) {
return $time_frame['name'];
}
if ( $timeInMs < $time_frame['max'] * 1000 ) {
return $time_frame['name'];
}
}
}
private function track( $data ) {
$track_data = array(
'success' => true,
'installed_extensions' => array_map(
function( $extension ) {
return $this->get_plugin_track_key( $extension );
},
$data['installed']
),
'total_time' => $this->get_timeframe( ( time() - $data['start_time'] ) * 1000 ),
);
foreach ( $data['installed'] as $plugin ) {
if ( ! isset( $data['time'][ $plugin ] ) ) {
continue;
}
$track_data[ 'install_time_' . $this->get_plugin_track_key( $plugin ) ] = $this->get_timeframe( $data['time'][ $plugin ] );
}
wc_admin_record_tracks_event( 'coreprofiler_store_extensions_installed_and_activated', $track_data );
}
}

View File

@@ -44,8 +44,9 @@ interface PluginsInstallLogger {
/**
* Called when all plugins are processed.
*
* @param array $data return data from install_plugins().
* @return mixed
*/
public function complete();
public function complete( $data = array() );
}

View File

@@ -42,6 +42,16 @@ class ComparisonOperation {
return ! in_array( $right_operand, $left_operand, true );
}
return strpos( $right_operand, $left_operand ) === false;
case 'in':
if ( is_array( $right_operand ) && is_string( $left_operand ) ) {
return in_array( $left_operand, $right_operand, true );
}
return strpos( $left_operand, $right_operand ) !== false;
case '!in':
if ( is_array( $right_operand ) && is_string( $left_operand ) ) {
return ! in_array( $left_operand, $right_operand, true );
}
return strpos( $left_operand, $right_operand ) === false;
}
return false;

View File

@@ -11,6 +11,14 @@ defined( 'ABSPATH' ) || exit;
* Rule processor that negates the rules in the rule's operand.
*/
class NotRuleProcessor implements RuleProcessorInterface {
/**
* The rule evaluator to use.
*
* @var RuleEvaluator
*/
protected $rule_evaluator;
/**
* Constructor.
*

View File

@@ -11,6 +11,14 @@ defined( 'ABSPATH' ) || exit;
* Rule processor for publishing based on the number of orders.
*/
class OrderCountRuleProcessor implements RuleProcessorInterface {
/**
* The orders provider.
*
* @var OrdersProvider
*/
protected $orders_provider;
/**
* Constructor.
*

View File

@@ -13,6 +13,14 @@ use Automattic\WooCommerce\Admin\PluginsProvider\PluginsProvider;
* Rule processor for sending when the provided plugins are activated.
*/
class PluginsActivatedRuleProcessor implements RuleProcessorInterface {
/**
* The plugins provider.
*
* @var PluginsProviderInterface
*/
protected $plugins_provider;
/**
* Constructor.
*

View File

@@ -13,6 +13,14 @@ defined( 'ABSPATH' ) || exit;
* products.
*/
class ProductCountRuleProcessor implements RuleProcessorInterface {
/**
* The product query.
*
* @var WC_Product_Query
*/
protected $product_query;
/**
* Constructor.
*

View File

@@ -13,6 +13,14 @@ use Automattic\WooCommerce\Admin\DateTimeProvider\CurrentDateTimeProvider;
* Rule processor for sending after a specified date/time.
*/
class PublishAfterTimeRuleProcessor implements RuleProcessorInterface {
/**
* The DateTime provider.
*
* @var DateTimeProviderInterface
*/
protected $date_time_provider;
/**
* Constructor.
*

View File

@@ -13,6 +13,14 @@ use Automattic\WooCommerce\Admin\DateTimeProvider\CurrentDateTimeProvider;
* Rule processor for sending before a specified date/time.
*/
class PublishBeforeTimeRuleProcessor implements RuleProcessorInterface {
/**
* The DateTime provider.
*
* @var DateTimeProviderInterface
*/
protected $date_time_provider;
/**
* Constructor.
*

View File

@@ -13,6 +13,14 @@ defined( 'ABSPATH' ) || exit;
* given number of seconds.
*/
class WCAdminActiveForRuleProcessor implements RuleProcessorInterface {
/**
* Provides the amount of time wcadmin has been active for.
*
* @var WCAdminActiveForProvider
*/
protected $wcadmin_active_for_provider;
/**
* Constructor
*

View File

@@ -20,6 +20,28 @@ if ( ! class_exists( 'WC_Email', false ) ) {
* ReportCSVEmail Class.
*/
class ReportCSVEmail extends \WC_Email {
/**
* Report labels.
*
* @var array
*/
protected $report_labels;
/**
* Report type (e.g. 'customers').
*
* @var string
*/
protected $report_type;
/**
* Download URL.
*
* @var string
*/
protected $download_url;
/**
* Constructor.
*/