plugin updates
This commit is contained in:
@@ -57,83 +57,98 @@ class Init {
|
||||
* Init REST API.
|
||||
*/
|
||||
public function rest_api_init() {
|
||||
$controllers = array(
|
||||
'Automattic\WooCommerce\Admin\API\Features',
|
||||
'Automattic\WooCommerce\Admin\API\Notes',
|
||||
'Automattic\WooCommerce\Admin\API\NoteActions',
|
||||
'Automattic\WooCommerce\Admin\API\Coupons',
|
||||
'Automattic\WooCommerce\Admin\API\Data',
|
||||
'Automattic\WooCommerce\Admin\API\DataCountries',
|
||||
'Automattic\WooCommerce\Admin\API\DataDownloadIPs',
|
||||
'Automattic\WooCommerce\Admin\API\Experiments',
|
||||
'Automattic\WooCommerce\Admin\API\Marketing',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingOverview',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingRecommendations',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingChannels',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingCampaigns',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingCampaignTypes',
|
||||
'Automattic\WooCommerce\Admin\API\Notice',
|
||||
'Automattic\WooCommerce\Admin\API\Options',
|
||||
'Automattic\WooCommerce\Admin\API\Orders',
|
||||
'Automattic\WooCommerce\Admin\API\PaymentGatewaySuggestions',
|
||||
'Automattic\WooCommerce\Admin\API\Products',
|
||||
'Automattic\WooCommerce\Admin\API\ProductAttributes',
|
||||
'Automattic\WooCommerce\Admin\API\ProductAttributeTerms',
|
||||
'Automattic\WooCommerce\Admin\API\ProductCategories',
|
||||
'Automattic\WooCommerce\Admin\API\ProductVariations',
|
||||
'Automattic\WooCommerce\Admin\API\ProductReviews',
|
||||
'Automattic\WooCommerce\Admin\API\ProductVariations',
|
||||
'Automattic\WooCommerce\Admin\API\ProductsLowInStock',
|
||||
'Automattic\WooCommerce\Admin\API\SettingOptions',
|
||||
'Automattic\WooCommerce\Admin\API\Themes',
|
||||
'Automattic\WooCommerce\Admin\API\Plugins',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingFreeExtensions',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProductTypes',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProfile',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingTasks',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingThemes',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingPlugins',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProducts',
|
||||
'Automattic\WooCommerce\Admin\API\NavigationFavorites',
|
||||
'Automattic\WooCommerce\Admin\API\Taxes',
|
||||
'Automattic\WooCommerce\Admin\API\MobileAppMagicLink',
|
||||
'Automattic\WooCommerce\Admin\API\ShippingPartnerSuggestions',
|
||||
);
|
||||
$controllers = array();
|
||||
$analytics_controllers = array();
|
||||
|
||||
if ( wc_rest_should_load_namespace( 'wc-admin' ) ) {
|
||||
// Controllers in the wc-admin namespace.
|
||||
$controllers = array(
|
||||
'Automattic\WooCommerce\Admin\API\Notice',
|
||||
'Automattic\WooCommerce\Admin\API\Features',
|
||||
'Automattic\WooCommerce\Admin\API\Experiments',
|
||||
'Automattic\WooCommerce\Admin\API\Marketing',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingOverview',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingRecommendations',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingChannels',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingCampaigns',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingCampaignTypes',
|
||||
'Automattic\WooCommerce\Admin\API\Options',
|
||||
'Automattic\WooCommerce\Admin\API\PaymentGatewaySuggestions',
|
||||
'Automattic\WooCommerce\Admin\API\Themes',
|
||||
'Automattic\WooCommerce\Admin\API\Plugins',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingFreeExtensions',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProductTypes',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProfile',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingTasks',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingThemes',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingPlugins',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProducts',
|
||||
'Automattic\WooCommerce\Admin\API\NavigationFavorites',
|
||||
'Automattic\WooCommerce\Admin\API\MobileAppMagicLink',
|
||||
'Automattic\WooCommerce\Admin\API\ShippingPartnerSuggestions',
|
||||
);
|
||||
}
|
||||
|
||||
if ( Features::is_enabled( 'launch-your-store' ) ) {
|
||||
$controllers[] = 'Automattic\WooCommerce\Admin\API\LaunchYourStore';
|
||||
}
|
||||
|
||||
if ( Features::is_enabled( 'analytics' ) ) {
|
||||
$analytics_controllers = array(
|
||||
'Automattic\WooCommerce\Admin\API\Customers',
|
||||
'Automattic\WooCommerce\Admin\API\Leaderboards',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Import\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Export\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Products\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Variations\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Products\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Variations\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Revenue\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Orders\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Categories\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Taxes\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Coupons\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Stock\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Stock\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Downloads\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Downloads\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Customers\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Customers\Stats\Controller',
|
||||
if ( wc_rest_should_load_namespace( 'wc-analytics' ) ) {
|
||||
// Controllers in wc-analytics namespace, but loaded irrespective of analytics feature value.
|
||||
$analytic_mu_controllers = array(
|
||||
'Automattic\WooCommerce\Admin\API\Notes',
|
||||
'Automattic\WooCommerce\Admin\API\NoteActions',
|
||||
'Automattic\WooCommerce\Admin\API\Coupons',
|
||||
'Automattic\WooCommerce\Admin\API\Data',
|
||||
'Automattic\WooCommerce\Admin\API\DataCountries',
|
||||
'Automattic\WooCommerce\Admin\API\DataDownloadIPs',
|
||||
'Automattic\WooCommerce\Admin\API\Orders',
|
||||
'Automattic\WooCommerce\Admin\API\Products',
|
||||
'Automattic\WooCommerce\Admin\API\ProductAttributes',
|
||||
'Automattic\WooCommerce\Admin\API\ProductAttributeTerms',
|
||||
'Automattic\WooCommerce\Admin\API\ProductCategories',
|
||||
'Automattic\WooCommerce\Admin\API\ProductVariations',
|
||||
'Automattic\WooCommerce\Admin\API\ProductReviews',
|
||||
'Automattic\WooCommerce\Admin\API\ProductVariations',
|
||||
'Automattic\WooCommerce\Admin\API\ProductsLowInStock',
|
||||
'Automattic\WooCommerce\Admin\API\SettingOptions',
|
||||
'Automattic\WooCommerce\Admin\API\Taxes',
|
||||
);
|
||||
|
||||
// The performance indicators controller must be registered last, after other /stats endpoints have been registered.
|
||||
$analytics_controllers[] = 'Automattic\WooCommerce\Admin\API\Reports\PerformanceIndicators\Controller';
|
||||
$controllers = array_merge( $controllers, $analytics_controllers );
|
||||
if ( Features::is_enabled( 'analytics' ) ) {
|
||||
$analytics_controllers = array(
|
||||
'Automattic\WooCommerce\Admin\API\Customers',
|
||||
'Automattic\WooCommerce\Admin\API\Leaderboards',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Import\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Export\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Products\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Variations\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Products\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Variations\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Revenue\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Orders\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Categories\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Taxes\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Coupons\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Stock\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Stock\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Downloads\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Downloads\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Customers\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Customers\Stats\Controller',
|
||||
);
|
||||
|
||||
// The performance indicators controllerq must be registered last, after other /stats endpoints have been registered.
|
||||
$analytics_controllers[] = 'Automattic\WooCommerce\Admin\API\Reports\PerformanceIndicators\Controller';
|
||||
|
||||
$analytics_controllers = array_merge( $analytics_controllers, $analytic_mu_controllers );
|
||||
}
|
||||
|
||||
$controllers = array_merge( $controllers, $analytics_controllers, $analytic_mu_controllers );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -130,10 +130,12 @@ class Leaderboards extends \WC_REST_Data_Controller {
|
||||
array(
|
||||
'display' => wc_admin_number_format( $coupon['orders_count'] ),
|
||||
'value' => $coupon['orders_count'],
|
||||
'format' => 'number',
|
||||
),
|
||||
array(
|
||||
'display' => wc_price( $coupon['amount'] ),
|
||||
'value' => $coupon['amount'],
|
||||
'format' => 'currency',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -199,10 +201,12 @@ class Leaderboards extends \WC_REST_Data_Controller {
|
||||
array(
|
||||
'display' => wc_admin_number_format( $category['items_sold'] ),
|
||||
'value' => $category['items_sold'],
|
||||
'format' => 'number',
|
||||
),
|
||||
array(
|
||||
'display' => wc_price( $category['net_revenue'] ),
|
||||
'value' => $category['net_revenue'],
|
||||
'format' => 'currency',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -266,10 +270,12 @@ class Leaderboards extends \WC_REST_Data_Controller {
|
||||
array(
|
||||
'display' => wc_admin_number_format( $customer['orders_count'] ),
|
||||
'value' => $customer['orders_count'],
|
||||
'format' => 'number',
|
||||
),
|
||||
array(
|
||||
'display' => wc_price( $customer['total_spend'] ),
|
||||
'value' => $customer['total_spend'],
|
||||
'format' => 'currency',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -335,10 +341,12 @@ class Leaderboards extends \WC_REST_Data_Controller {
|
||||
array(
|
||||
'display' => wc_admin_number_format( $product['items_sold'] ),
|
||||
'value' => $product['items_sold'],
|
||||
'format' => 'number',
|
||||
),
|
||||
array(
|
||||
'display' => wc_price( $product['net_revenue'] ),
|
||||
'value' => $product['net_revenue'],
|
||||
'format' => 'currency',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -578,6 +586,14 @@ class Leaderboards extends \WC_REST_Data_Controller {
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'format' => array(
|
||||
'description' => __( 'Table cell format.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
'enum' => array( 'currency', 'number' ),
|
||||
'readonly' => true,
|
||||
'required' => false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -97,37 +97,6 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@ class Options extends \WC_REST_Data_Controller {
|
||||
'woocommerce_product_tour_modal_hidden',
|
||||
'woocommerce_block_product_tour_shown',
|
||||
'woocommerce_revenue_report_date_tour_shown',
|
||||
'woocommerce_orders_report_date_tour_shown',
|
||||
'woocommerce_show_prepublish_checks_enabled',
|
||||
'woocommerce_date_type',
|
||||
'date_format',
|
||||
|
||||
@@ -239,7 +239,7 @@ class Orders extends \WC_REST_Orders_Controller {
|
||||
}
|
||||
|
||||
// Format the order status.
|
||||
$data['status'] = 'wc-' === substr( $data['status'], 0, 3 ) ? substr( $data['status'], 3 ) : $data['status'];
|
||||
$data['status'] = OrderUtil::remove_status_prefix( $data['status'] );
|
||||
|
||||
// Format requested line items.
|
||||
$formatted_line_items = array();
|
||||
|
||||
@@ -140,17 +140,22 @@ class Controller extends GenericController {
|
||||
* Returns the parent order number if the order is actually a refund.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return string
|
||||
* @return string|null The Order Number or null if the order doesn't exist.
|
||||
*/
|
||||
protected function get_order_number( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $order instanceof \WC_Order && ! $order instanceof \WC_Order_Refund ) {
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( 'shop_order_refund' === $order->get_type() ) {
|
||||
$order = wc_get_order( $order->get_parent_id() );
|
||||
|
||||
// If the parent order doesn't exist, return null.
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! has_filter( 'woocommerce_order_number' ) ) {
|
||||
@@ -160,22 +165,36 @@ class Controller extends GenericController {
|
||||
return $order->get_order_number();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the order is valid.
|
||||
*
|
||||
* @param bool|WC_Order|WC_Order_Refund $order Order object.
|
||||
* @return bool True if the order is valid, false otherwise.
|
||||
*/
|
||||
protected function is_valid_order( $order ) {
|
||||
return $order instanceof \WC_Order || $order instanceof \WC_Order_Refund;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order total with the related currency formatting.
|
||||
* Returns the parent order total if the order is actually a refund.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return string
|
||||
* @return string|null The Order Number or null if the order doesn't exist.
|
||||
*/
|
||||
protected function get_total_formatted( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $order instanceof \WC_Order && ! $order instanceof \WC_Order_Refund ) {
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( 'shop_order_refund' === $order->get_type() ) {
|
||||
$order = wc_get_order( $order->get_parent_id() );
|
||||
|
||||
if ( ! $this->is_valid_order( $order ) ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return wp_strip_all_tags( html_entity_decode( $order->get_formatted_order_total() ), true );
|
||||
|
||||
@@ -524,14 +524,13 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
$export_columns = array(
|
||||
'date_created' => __( 'Date', 'woocommerce' ),
|
||||
'order_number' => __( 'Order #', 'woocommerce' ),
|
||||
'total_formatted' => __( 'N. Revenue (formatted)', 'woocommerce' ),
|
||||
'status' => __( 'Status', 'woocommerce' ),
|
||||
'customer_name' => __( 'Customer', 'woocommerce' ),
|
||||
'customer_type' => __( 'Customer type', 'woocommerce' ),
|
||||
'products' => __( 'Product(s)', 'woocommerce' ),
|
||||
'num_items_sold' => __( 'Items sold', 'woocommerce' ),
|
||||
'coupons' => __( 'Coupon(s)', 'woocommerce' ),
|
||||
'net_total' => __( 'N. Revenue', 'woocommerce' ),
|
||||
'net_total' => __( 'Net Sales', 'woocommerce' ),
|
||||
'attribution' => __( 'Attribution', 'woocommerce' ),
|
||||
);
|
||||
|
||||
@@ -555,16 +554,15 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
*/
|
||||
public function prepare_item_for_export( $item ) {
|
||||
$export_item = array(
|
||||
'date_created' => $item['date_created'],
|
||||
'date_created' => $item['date'],
|
||||
'order_number' => $item['order_number'],
|
||||
'total_formatted' => $item['total_formatted'],
|
||||
'status' => $item['status'],
|
||||
'customer_name' => isset( $item['extended_info']['customer'] ) ? $this->get_customer_name( $item['extended_info']['customer'] ) : null,
|
||||
'customer_type' => $item['customer_type'],
|
||||
'products' => isset( $item['extended_info']['products'] ) ? $this->get_products( $item['extended_info']['products'] ) : null,
|
||||
'num_items_sold' => $item['num_items_sold'],
|
||||
'coupons' => isset( $item['extended_info']['coupons'] ) ? $this->get_coupons( $item['extended_info']['coupons'] ) : null,
|
||||
'net_total' => $item['net_total'],
|
||||
'net_total' => $item['net_total'],
|
||||
'attribution' => $item['extended_info']['attribution']['origin'],
|
||||
);
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
'total_tax' => 'SUM(total_tax) as total_tax',
|
||||
'order_tax' => 'SUM(order_tax) as order_tax',
|
||||
'shipping_tax' => 'SUM(shipping_tax) as shipping_tax',
|
||||
'orders_count' => "COUNT({$table_name}.order_id) as orders_count",
|
||||
'orders_count' => "COUNT( DISTINCT ( CASE WHEN parent_id = 0 THEN {$table_name}.order_id END ) ) as orders_count",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -146,8 +146,6 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
public function get_data( $query_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = self::get_db_table_name();
|
||||
|
||||
// These defaults are only partially applied when used via REST API, as that has its own defaults.
|
||||
$defaults = array(
|
||||
'per_page' => get_option( 'posts_per_page' ),
|
||||
@@ -204,6 +202,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
|
||||
$this->subquery->add_sql_clause( 'group_by', ", {$wpdb->prefix}woocommerce_order_items.order_item_name, {$wpdb->prefix}woocommerce_order_itemmeta.meta_value" );
|
||||
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
|
||||
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
|
||||
|
||||
$taxes_query = $this->subquery->get_query_statement();
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
*/
|
||||
protected $param_mapping = array(
|
||||
'variations' => 'variation_includes',
|
||||
'products' => 'product_includes',
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -382,6 +383,15 @@ class Controller extends ReportsController implements ExportableInterface {
|
||||
'sanitize_callback' => 'wp_validate_boolean',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['products'] = array(
|
||||
'description' => __( 'Limit result to items with specified product ids.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -179,10 +179,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
|
||||
|
||||
if ( $included_variations ) {
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.variation_id IN ({$included_variations})" );
|
||||
} elseif ( ! $included_products ) {
|
||||
if ( $this->should_exclude_simple_products( $query_args ) ) {
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.variation_id != 0" );
|
||||
}
|
||||
} elseif ( $this->should_exclude_simple_products( $query_args ) ) {
|
||||
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.variation_id != 0" );
|
||||
}
|
||||
|
||||
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||
|
||||
@@ -56,7 +56,7 @@ class DefaultMarketingRecommendations {
|
||||
|
||||
return array(
|
||||
array(
|
||||
'title' => 'Google Listings and Ads',
|
||||
'title' => 'Google for WooCommerce',
|
||||
'description' => __( 'Get in front of shoppers and drive traffic so you can grow your business with Smart Shopping Campaigns and free listings.', 'woocommerce' ),
|
||||
'url' => "https://woocommerce.com/products/google-listings-and-ads/{$utm_string}",
|
||||
'direct_install' => true,
|
||||
|
||||
@@ -165,7 +165,7 @@ class TaskLists {
|
||||
),
|
||||
),
|
||||
'tasks' => array(
|
||||
'StoreConnect',
|
||||
'ExtendStore',
|
||||
'AdditionalPayments',
|
||||
'GetMobileApp',
|
||||
),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
use Jetpack_Gutenberg;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Customize Your Store Task
|
||||
@@ -24,23 +24,24 @@ class CustomizeStore extends Task {
|
||||
// Hook to remove unwanted UI elements when users are viewing with ?cys-hide-admin-bar=true.
|
||||
add_action( 'wp_head', array( $this, 'possibly_remove_unwanted_ui_elements' ) );
|
||||
|
||||
add_action( 'save_post_wp_global_styles', array( $this, 'mark_task_as_complete' ), 10, 3 );
|
||||
add_action( 'save_post_wp_template', array( $this, 'mark_task_as_complete' ), 10, 3 );
|
||||
add_action( 'save_post_wp_template_part', array( $this, 'mark_task_as_complete' ), 10, 3 );
|
||||
add_action( 'save_post_wp_global_styles', array( $this, 'mark_task_as_complete_block_theme' ), 10, 3 );
|
||||
add_action( 'save_post_wp_template', array( $this, 'mark_task_as_complete_block_theme' ), 10, 3 );
|
||||
add_action( 'save_post_wp_template_part', array( $this, 'mark_task_as_complete_block_theme' ), 10, 3 );
|
||||
add_action( 'customize_save_after', array( $this, 'mark_task_as_complete_classic_theme' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the CYS task as complete whenever the user updates their global styles.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param \WP_Post $post Post object.
|
||||
* @param bool $update Whether this is an existing post being updated.
|
||||
* @param int $post_id Post ID.
|
||||
* @param WP_Post $post Post object.
|
||||
* @param bool $update Whether this is an existing post being updated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mark_task_as_complete( $post_id, $post, $update ) {
|
||||
if ( $post instanceof \WP_Post ) {
|
||||
$is_cys_complete = '{"version": 2, "isGlobalStylesUserThemeJSON": true }' !== $post->post_content || in_array( $post->post_type, array( 'wp_template', 'wp_template_part' ), true );
|
||||
public function mark_task_as_complete_block_theme( $post_id, $post, $update ) {
|
||||
if ( $post instanceof WP_Post ) {
|
||||
$is_cys_complete = $this->has_custom_global_styles( $post ) || $this->has_custom_template( $post );
|
||||
|
||||
if ( $is_cys_complete ) {
|
||||
update_option( 'woocommerce_admin_customize_store_completed', 'yes' );
|
||||
@@ -48,6 +49,15 @@ class CustomizeStore extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the CYS task as complete whenever the user saves the customizer changes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mark_task_as_complete_classic_theme() {
|
||||
update_option( 'woocommerce_admin_customize_store_completed', 'yes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
@@ -227,11 +237,6 @@ class CustomizeStore extends Task {
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,4 +265,33 @@ class CustomizeStore extends Task {
|
||||
</style>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the post has custom global styles stored (if it is different from the default global styles).
|
||||
*
|
||||
* @param WP_Post $post The post object.
|
||||
* @return bool
|
||||
*/
|
||||
private function has_custom_global_styles( WP_Post $post ) {
|
||||
$required_keys = array( 'version', 'isGlobalStylesUserThemeJSON' );
|
||||
|
||||
$json_post_content = json_decode( $post->post_content, true );
|
||||
if ( is_null( $json_post_content ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$post_content_keys = array_keys( $json_post_content );
|
||||
|
||||
return ! empty( array_diff( $post_content_keys, $required_keys ) ) || ! empty( array_diff( $required_keys, $post_content_keys ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the post is a template or a template part.
|
||||
*
|
||||
* @param WP_Post $post The post object.
|
||||
* @return bool Whether the post is a template or a template part.
|
||||
*/
|
||||
private function has_custom_template( WP_Post $post ) {
|
||||
return in_array( $post->post_type, array( 'wp_template', 'wp_template_part' ), true );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,9 @@ class ExperimentalShippingRecommendation extends Task {
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
return Features::is_enabled( 'shipping-smart-defaults' );
|
||||
return Features::is_enabled( 'shipping-smart-defaults' ) &&
|
||||
! PluginsHelper::is_plugin_active( 'woocommerce-shipping' ) &&
|
||||
! PluginsHelper::is_plugin_active( 'woocommerce-tax' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,16 +5,16 @@ namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Connect store to WooCommerce.com Task
|
||||
* ExtendStore Task
|
||||
*/
|
||||
class StoreConnect extends Task {
|
||||
class ExtendStore extends Task {
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'connect-store';
|
||||
return 'extend-store';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ class StoreConnect extends Task {
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Manage your WooCommerce.com Marketplace subscriptions', 'woocommerce' );
|
||||
return __( 'Enhance your store with extensions', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ class StoreConnect extends Task {
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return \WC_Helper::is_site_connected();
|
||||
return $this->is_visited();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +59,7 @@ class StoreConnect extends Task {
|
||||
* @return bool
|
||||
*/
|
||||
public function is_dismissable() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +68,6 @@ class StoreConnect extends Task {
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
return admin_url( 'admin.php?page=wc-admin&tab=my-subscriptions&path=/extensions' );
|
||||
return admin_url( 'admin.php?page=wc-admin&path=/extensions' );
|
||||
}
|
||||
}
|
||||
@@ -77,11 +77,12 @@ class Payments extends Task {
|
||||
*/
|
||||
public function can_view() {
|
||||
$woocommerce_payments = $this->task_list->get_task( 'woocommerce-payments' );
|
||||
// Make sure the task is mutually exclusive with the WooPayments task.
|
||||
return Features::is_enabled( 'payment-gateway-suggestions' ) && ! $woocommerce_payments->can_view();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any enabled gateways.
|
||||
* Check if the store has any enabled gateways, other than WooPayments.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
@@ -90,7 +91,8 @@ class Payments extends Task {
|
||||
$enabled_gateways = array_filter(
|
||||
$gateways,
|
||||
function( $gateway ) {
|
||||
return 'yes' === $gateway->enabled && 'woocommerce_payments' !== $gateway->id;
|
||||
// Filter out any WooPayments gateways as this task is mutually exclusive with the WooPayments task.
|
||||
return 'yes' === $gateway->enabled && 0 !== strpos( $gateway->id, 'woocommerce_payments' );
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -127,9 +127,11 @@ class Tax extends Task {
|
||||
*/
|
||||
public function get_additional_data() {
|
||||
return array(
|
||||
'avalara_activated' => PluginsHelper::is_plugin_active( 'woocommerce-avatax' ),
|
||||
'tax_jar_activated' => class_exists( 'WC_Taxjar' ),
|
||||
'woocommerce_tax_countries' => self::get_automated_support_countries(),
|
||||
'avalara_activated' => PluginsHelper::is_plugin_active( 'woocommerce-avatax' ),
|
||||
'tax_jar_activated' => class_exists( 'WC_Taxjar' ),
|
||||
'woocommerce_tax_activated' => PluginsHelper::is_plugin_active( 'woocommerce-tax' ),
|
||||
'woocommerce_shipping_activated' => PluginsHelper::is_plugin_active( 'woocommerce-shipping' ),
|
||||
'woocommerce_tax_countries' => self::get_automated_support_countries(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,8 +103,7 @@ class WooCommercePayments extends Task {
|
||||
public function can_view() {
|
||||
$payments = $this->task_list->get_task( 'payments' );
|
||||
|
||||
return ! $payments->is_complete() && // Do not re-display the task if the "add payments" task has already been completed.
|
||||
self::is_installed() &&
|
||||
return ! $payments->is_complete() && // Do not re-display the task if the general "Payments" task has already been completed.
|
||||
self::is_supported();
|
||||
}
|
||||
|
||||
@@ -175,7 +174,7 @@ class WooCommercePayments extends Task {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store is in a supported country.
|
||||
* Check if the store is in a WooPayments supported country.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
@@ -65,6 +65,13 @@ class DefaultPaymentGateways {
|
||||
'CA',
|
||||
)
|
||||
),
|
||||
(object) array(
|
||||
'type' => 'or',
|
||||
'operands' => array(
|
||||
self::get_rules_for_wcpay_activated( false ),
|
||||
self::get_rules_for_wcpay_connected( false ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'category_other' => array(),
|
||||
'category_additional' => array(
|
||||
@@ -87,6 +94,13 @@ class DefaultPaymentGateways {
|
||||
'AU',
|
||||
)
|
||||
),
|
||||
(object) array(
|
||||
'type' => 'or',
|
||||
'operands' => array(
|
||||
self::get_rules_for_wcpay_activated( false ),
|
||||
self::get_rules_for_wcpay_connected( false ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'category_other' => array(),
|
||||
'category_additional' => array(
|
||||
@@ -250,6 +264,19 @@ class DefaultPaymentGateways {
|
||||
)
|
||||
),
|
||||
self::get_rules_for_cbd( false ),
|
||||
(object) array(
|
||||
'type' => 'or',
|
||||
'operands' => array(
|
||||
(object) array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
self::get_rules_for_countries( self::get_wcpay_countries() ),
|
||||
),
|
||||
),
|
||||
self::get_rules_for_wcpay_activated( false ),
|
||||
self::get_rules_for_wcpay_connected( false ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'category_other' => array(),
|
||||
'category_additional' => array(
|
||||
@@ -862,6 +889,47 @@ class DefaultPaymentGateways {
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'woocommerce_payments:bnpl',
|
||||
'title' => __( 'Activate BNPL instantly on WooPayments', 'woocommerce' ),
|
||||
'content' => __(
|
||||
'The world’s favorite buy now, pay later options and many more are right at your fingertips with WooPayments — all from one dashboard, without needing multiple extensions and logins.',
|
||||
'woocommerce'
|
||||
),
|
||||
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay-bnpl.svg',
|
||||
'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay-bnpl.svg',
|
||||
'plugins' => array( 'woocommerce-payments' ),
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries(
|
||||
array_intersect(
|
||||
array(
|
||||
'US',
|
||||
'CA',
|
||||
'AU',
|
||||
'AT',
|
||||
'BE',
|
||||
'CH',
|
||||
'DK',
|
||||
'ES',
|
||||
'FI',
|
||||
'FR',
|
||||
'DE',
|
||||
'GB',
|
||||
'IT',
|
||||
'NL',
|
||||
'NO',
|
||||
'PL',
|
||||
'SE',
|
||||
'NZ',
|
||||
),
|
||||
self::get_wcpay_countries()
|
||||
),
|
||||
),
|
||||
self::get_rules_for_cbd( false ),
|
||||
self::get_rules_for_wcpay_activated( true ),
|
||||
self::get_rules_for_wcpay_connected( true ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'id' => 'zipmoney',
|
||||
'title' => __( 'Zip Co - Buy Now, Pay Later', 'woocommerce' ),
|
||||
@@ -976,7 +1044,7 @@ class DefaultPaymentGateways {
|
||||
* Get default rules for CBD based on given argument.
|
||||
*
|
||||
* @param bool $should_have Whether or not the store should have CBD as an industry (true) or not (false).
|
||||
* @return array Rules to match.
|
||||
* @return object Rules to match.
|
||||
*/
|
||||
public static function get_rules_for_cbd( $should_have ) {
|
||||
return (object) array(
|
||||
@@ -1002,6 +1070,62 @@ class DefaultPaymentGateways {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default rules for the WooPayments plugin being installed and activated.
|
||||
*
|
||||
* @param bool $should_be Whether WooPayments should be activated.
|
||||
*
|
||||
* @return object Rules to match.
|
||||
*/
|
||||
public static function get_rules_for_wcpay_activated( $should_be ) {
|
||||
$active_rule = (object) array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-payments' ),
|
||||
);
|
||||
|
||||
if ( $should_be ) {
|
||||
return $active_rule;
|
||||
}
|
||||
|
||||
return (object) array(
|
||||
'type' => 'not',
|
||||
'operand' => array( $active_rule ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default rules for WooPayments being connected or not.
|
||||
*
|
||||
* This does not include the check for the WooPayments plugin to be active.
|
||||
*
|
||||
* @param bool $should_be Whether WooPayments should be connected.
|
||||
*
|
||||
* @return object Rules to match.
|
||||
*/
|
||||
public static function get_rules_for_wcpay_connected( $should_be ) {
|
||||
return (object) array(
|
||||
'type' => 'option',
|
||||
'transformers' => array(
|
||||
// Extract only the 'data' key from the option.
|
||||
(object) array(
|
||||
'use' => 'dot_notation',
|
||||
'arguments' => (object) array(
|
||||
'path' => 'data',
|
||||
),
|
||||
),
|
||||
// Extract the keys from the data array.
|
||||
(object) array(
|
||||
'use' => 'array_keys',
|
||||
),
|
||||
),
|
||||
'option_name' => 'wcpay_account_data',
|
||||
// The rule will be look for the 'account_id' key in the account data array.
|
||||
'operation' => $should_be ? 'contains' : '!contains',
|
||||
'value' => 'account_id',
|
||||
'default' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recommendation priority for a given payment gateway by id and country.
|
||||
* If country is not supported, return null.
|
||||
@@ -1013,6 +1137,8 @@ class DefaultPaymentGateways {
|
||||
private static function get_recommendation_priority( $gateway_id, $country_code ) {
|
||||
$recommendation_priority_map = array(
|
||||
'US' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1023,6 +1149,8 @@ class DefaultPaymentGateways {
|
||||
'klarna_payments',
|
||||
),
|
||||
'CA' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1032,6 +1160,8 @@ class DefaultPaymentGateways {
|
||||
'klarna_payments',
|
||||
),
|
||||
'AT' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1041,6 +1171,8 @@ class DefaultPaymentGateways {
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'BE' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1049,26 +1181,63 @@ class DefaultPaymentGateways {
|
||||
'klarna_payments',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'BG' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway' ),
|
||||
'HR' => array( 'woocommerce_payments', 'ppcp-gateway' ),
|
||||
'BG' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
),
|
||||
'HR' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'ppcp-gateway',
|
||||
),
|
||||
'CH' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'mollie_wc_gateway_banktransfer',
|
||||
'klarna_payments',
|
||||
),
|
||||
'CY' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'amazon_payments_advanced' ),
|
||||
'CZ' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway' ),
|
||||
'CY' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'CZ' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
),
|
||||
'DK' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'klarna_payments',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'EE' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main' ),
|
||||
'EE' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'airwallex_main',
|
||||
),
|
||||
'ES' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1078,6 +1247,8 @@ class DefaultPaymentGateways {
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'FI' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1086,6 +1257,8 @@ class DefaultPaymentGateways {
|
||||
'klarna_payments',
|
||||
),
|
||||
'FR' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1096,6 +1269,8 @@ class DefaultPaymentGateways {
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'DE' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1105,6 +1280,8 @@ class DefaultPaymentGateways {
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'GB' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1114,9 +1291,25 @@ class DefaultPaymentGateways {
|
||||
'klarna_payments',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'GR' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'airwallex_main' ),
|
||||
'HU' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'amazon_payments_advanced' ),
|
||||
'GR' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'airwallex_main',
|
||||
),
|
||||
'HU' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'IE' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1125,6 +1318,8 @@ class DefaultPaymentGateways {
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'IT' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1133,11 +1328,38 @@ class DefaultPaymentGateways {
|
||||
'klarna_payments',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'LV' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway' ),
|
||||
'LT' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway' ),
|
||||
'LU' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'amazon_payments_advanced' ),
|
||||
'MT' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway' ),
|
||||
'LV' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
),
|
||||
'LT' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
),
|
||||
'LU' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'MT' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
),
|
||||
'NL' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1146,8 +1368,18 @@ class DefaultPaymentGateways {
|
||||
'klarna_payments',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'NO' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'kco', 'klarna_payments' ),
|
||||
'NO' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'kco',
|
||||
'klarna_payments',
|
||||
),
|
||||
'PL' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1156,16 +1388,39 @@ class DefaultPaymentGateways {
|
||||
'klarna_payments',
|
||||
),
|
||||
'PT' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'airwallex_main',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'RO' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway' ),
|
||||
'SK' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway' ),
|
||||
'SL' => array( 'woocommerce_payments', 'stripe', 'ppcp-gateway', 'amazon_payments_advanced' ),
|
||||
'RO' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
),
|
||||
'SK' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
),
|
||||
'SL' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'SE' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
@@ -1194,6 +1449,8 @@ class DefaultPaymentGateways {
|
||||
'UY' => array( 'woo-mercado-pago-custom', 'ppcp-gateway' ),
|
||||
'VE' => array( 'ppcp-gateway' ),
|
||||
'AU' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'airwallex_main',
|
||||
@@ -1203,6 +1460,8 @@ class DefaultPaymentGateways {
|
||||
'klarna_payments',
|
||||
),
|
||||
'NZ' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'airwallex_main',
|
||||
@@ -1210,6 +1469,8 @@ class DefaultPaymentGateways {
|
||||
'klarna_payments',
|
||||
),
|
||||
'HK' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'airwallex_main',
|
||||
@@ -1217,13 +1478,22 @@ class DefaultPaymentGateways {
|
||||
'payoneer-checkout',
|
||||
),
|
||||
'JP' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'ppcp-gateway',
|
||||
'square_credit_card',
|
||||
'amazon_payments_advanced',
|
||||
),
|
||||
'SG' => array( 'woocommerce_payments', 'stripe', 'airwallex_main', 'ppcp-gateway' ),
|
||||
'SG' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
'stripe',
|
||||
'airwallex_main',
|
||||
'ppcp-gateway',
|
||||
),
|
||||
'CN' => array( 'airwallex_main', 'ppcp-gateway', 'payoneer-checkout' ),
|
||||
'FJ' => array(),
|
||||
'GU' => array(),
|
||||
@@ -1232,7 +1502,11 @@ class DefaultPaymentGateways {
|
||||
'ZA' => array( 'payfast', 'paystack' ),
|
||||
'NG' => array( 'paystack' ),
|
||||
'GH' => array( 'paystack' ),
|
||||
'AE' => array( 'woocommerce_payments' ),
|
||||
'AE' => array(
|
||||
'woocommerce_payments:with-in-person-payments',
|
||||
'woocommerce_payments:without-in-person-payments',
|
||||
'woocommerce_payments',
|
||||
),
|
||||
);
|
||||
|
||||
// If the country code is not in the list, return default priority.
|
||||
|
||||
@@ -16,15 +16,33 @@ class EvaluateSuggestion {
|
||||
/**
|
||||
* Evaluates the spec and returns the suggestion.
|
||||
*
|
||||
* @param object|array $spec The suggestion to evaluate.
|
||||
* @param object|array $spec The suggestion to evaluate.
|
||||
* @param array $logger_args Optional. Arguments for the rule evaluator logger.
|
||||
*
|
||||
* @return object The evaluated suggestion.
|
||||
*/
|
||||
public static function evaluate( $spec ) {
|
||||
public static function evaluate( $spec, $logger_args = array() ) {
|
||||
$rule_evaluator = new RuleEvaluator();
|
||||
$suggestion = is_array( $spec ) ? (object) $spec : clone $spec;
|
||||
|
||||
if ( isset( $suggestion->is_visible ) ) {
|
||||
$is_visible = $rule_evaluator->evaluate( $suggestion->is_visible );
|
||||
// Determine the suggestion's logger slug.
|
||||
$logger_slug = ! empty( $suggestion->id ) ? $suggestion->id : '';
|
||||
// If the suggestion has no ID, use the title to generate a slug.
|
||||
if ( empty( $logger_slug ) ) {
|
||||
$logger_slug = ! empty( $suggestion->title ) ? sanitize_title_with_dashes( trim( $suggestion->title ) ) : 'anonymous-suggestion';
|
||||
}
|
||||
|
||||
// Evaluate the visibility of the suggestion.
|
||||
$is_visible = $rule_evaluator->evaluate(
|
||||
$suggestion->is_visible,
|
||||
null,
|
||||
array(
|
||||
'slug' => $logger_slug,
|
||||
'source' => $logger_args['source'] ?? 'wc-payment-gateway-suggestions',
|
||||
)
|
||||
);
|
||||
|
||||
$suggestion->is_visible = $is_visible;
|
||||
}
|
||||
|
||||
@@ -35,15 +53,17 @@ class EvaluateSuggestion {
|
||||
* Evaluates the specs and returns the visible suggestions.
|
||||
*
|
||||
* @param array $specs payment suggestion spec array.
|
||||
* @param array $logger_args Optional. Arguments for the rule evaluator logger.
|
||||
*
|
||||
* @return array The visible suggestions and errors.
|
||||
*/
|
||||
public static function evaluate_specs( $specs ) {
|
||||
public static function evaluate_specs( $specs, $logger_args = array() ) {
|
||||
$suggestions = array();
|
||||
$errors = array();
|
||||
|
||||
foreach ( $specs as $spec ) {
|
||||
try {
|
||||
$suggestion = self::evaluate( $spec );
|
||||
$suggestion = self::evaluate( $spec, $logger_args );
|
||||
if ( ! property_exists( $suggestion, 'is_visible' ) || $suggestion->is_visible ) {
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ class Init {
|
||||
$editor_settings = $this->get_product_editor_settings();
|
||||
|
||||
$script_handle = 'wc-admin-edit-product';
|
||||
wp_register_script( $script_handle, '', array(), '0.1.0', true );
|
||||
wp_register_script( $script_handle, '', array( 'wp-blocks' ), '0.1.0', true );
|
||||
wp_enqueue_script( $script_handle );
|
||||
wp_add_inline_script(
|
||||
$script_handle,
|
||||
@@ -150,9 +150,10 @@ class Init {
|
||||
* Enqueue styles needed for the rich text editor.
|
||||
*/
|
||||
public function enqueue_styles() {
|
||||
if ( ! PageController::is_admin_or_embed_page() ) {
|
||||
if ( ! PageController::is_admin_page() ) {
|
||||
return;
|
||||
}
|
||||
wp_enqueue_style( 'wc-product-editor' );
|
||||
wp_enqueue_style( 'wp-edit-blocks' );
|
||||
wp_enqueue_style( 'wp-format-library' );
|
||||
wp_enqueue_editor();
|
||||
@@ -168,7 +169,7 @@ class Init {
|
||||
* Dequeue conflicting styles.
|
||||
*/
|
||||
public function dequeue_conflicting_styles() {
|
||||
if ( ! PageController::is_admin_or_embed_page() ) {
|
||||
if ( ! PageController::is_admin_page() ) {
|
||||
return;
|
||||
}
|
||||
// Dequeing this to avoid conflicts, until we remove the 'woocommerce-page' class.
|
||||
|
||||
@@ -285,6 +285,24 @@ class DefaultShippingPartners {
|
||||
'learn_more_link' => 'https://woocommerce.com/products/shipping/',
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_countries( array( 'US' ) ),
|
||||
(object) array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
(object) array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-shipping' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
(object) array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
(object) array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-tax' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'available_layouts' => array( 'column' ),
|
||||
),
|
||||
|
||||
@@ -20,14 +20,14 @@ class ShippingPartnerSuggestions extends RemoteSpecsEngine {
|
||||
$locale = get_user_locale();
|
||||
|
||||
$specs = is_array( $specs ) ? $specs : self::get_specs();
|
||||
$results = EvaluateSuggestion::evaluate_specs( $specs );
|
||||
$results = EvaluateSuggestion::evaluate_specs( $specs, array( 'source' => 'wc-shipping-partner-suggestions' ) );
|
||||
$specs_to_return = $results['suggestions'];
|
||||
$specs_to_save = null;
|
||||
|
||||
if ( empty( $specs_to_return ) ) {
|
||||
// When suggestions is empty, replace it with defaults and save for 3 hours.
|
||||
$specs_to_save = DefaultShippingPartners::get_all();
|
||||
$specs_to_return = EvaluateSuggestion::evaluate_specs( $specs_to_save )['suggestions'];
|
||||
$specs_to_return = EvaluateSuggestion::evaluate_specs( $specs_to_save, array( 'source' => 'wc-shipping-partner-suggestions' ) )['suggestions'];
|
||||
} elseif ( count( $results['errors'] ) > 0 ) {
|
||||
// When suggestions is not empty but has errors, save it for 3 hours.
|
||||
$specs_to_save = $specs;
|
||||
|
||||
@@ -423,7 +423,7 @@ class Notes {
|
||||
unset( $_COOKIE['tk_ai'] );
|
||||
wc_admin_record_tracks_event( $event_name, $params );
|
||||
if ( isset( $anon_id ) ) {
|
||||
setcookie( 'tk_ai', $anon_id );
|
||||
WC_Site_Tracking::set_tracking_cookie( 'tk_ai', $anon_id );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,11 @@ class PluginsHelper {
|
||||
*/
|
||||
const WOO_SUBSCRIPTION_PAGE_URL = 'https://woocommerce.com/my-account/my-subscriptions/';
|
||||
|
||||
/**
|
||||
* The URL for the WooCommerce.com add payment method page.
|
||||
*/
|
||||
const WOO_ADD_PAYMENT_METHOD_URL = 'https://woocommerce.com/my-account/add-payment-method/';
|
||||
|
||||
/**
|
||||
* Meta key for dismissing expired subscription notices.
|
||||
*/
|
||||
@@ -588,9 +593,11 @@ class PluginsHelper {
|
||||
|
||||
$connect_page_url = add_query_arg(
|
||||
array(
|
||||
'page' => 'wc-admin',
|
||||
'tab' => 'my-subscriptions',
|
||||
'path' => rawurlencode( '/extensions' ),
|
||||
'page' => 'wc-admin',
|
||||
'tab' => 'my-subscriptions',
|
||||
'path' => rawurlencode( '/extensions' ),
|
||||
'utm_source' => 'pu',
|
||||
'utm_campaign' => 'pu_setting_screen_connect',
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
@@ -706,7 +713,7 @@ class PluginsHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the subscritpion notice data based on user subscriptions data.
|
||||
* Construct the subscription notice data based on user subscriptions data.
|
||||
*
|
||||
* @param array $all_subs all subscription data.
|
||||
* @param array $subs_to_show filtered subscriptions as condition.
|
||||
@@ -717,10 +724,19 @@ class PluginsHelper {
|
||||
*/
|
||||
public static function get_subscriptions_notice_data( array $all_subs, array $subs_to_show, int $total, array $messages, string $type ) {
|
||||
if ( 1 < $total ) {
|
||||
$hyperlink_url = add_query_arg(
|
||||
array(
|
||||
'utm_source' => 'pu',
|
||||
'utm_campaign' => 'expired' === $type ? 'pu_settings_screen_renew' : 'pu_settings_screen_enable_autorenew',
|
||||
|
||||
),
|
||||
self::WOO_SUBSCRIPTION_PAGE_URL
|
||||
);
|
||||
|
||||
$parsed_message = sprintf(
|
||||
$messages['different_subscriptions'],
|
||||
esc_attr( $total ),
|
||||
esc_url( self::WOO_SUBSCRIPTION_PAGE_URL ),
|
||||
esc_url( $hyperlink_url ),
|
||||
esc_attr( $total ),
|
||||
);
|
||||
|
||||
@@ -752,8 +768,11 @@ class PluginsHelper {
|
||||
$expiry_date = date_i18n( 'F jS', $subscription['expires'] );
|
||||
$hyperlink_url = add_query_arg(
|
||||
array(
|
||||
'product_id' => $product_id,
|
||||
'type' => $type,
|
||||
'product_id' => $product_id,
|
||||
'type' => $type,
|
||||
'utm_source' => 'pu',
|
||||
'utm_campaign' => 'expired' === $type ? 'pu_settings_screen_renew' : 'pu_settings_screen_enable_autorenew',
|
||||
|
||||
),
|
||||
self::WOO_SUBSCRIPTION_PAGE_URL
|
||||
);
|
||||
@@ -810,6 +829,7 @@ class PluginsHelper {
|
||||
$subscriptions,
|
||||
function ( $sub ) {
|
||||
return ( ! empty( $sub['local']['installed'] ) && ! empty( $sub['product_key'] ) )
|
||||
&& $sub['active']
|
||||
&& $sub['expiring']
|
||||
&& ! $sub['autorenew'];
|
||||
},
|
||||
@@ -824,29 +844,7 @@ class PluginsHelper {
|
||||
// When payment method is missing on WooCommerce.com.
|
||||
$helper_notices = WC_Helper::get_notices();
|
||||
if ( ! empty( $helper_notices['missing_payment_method_notice'] ) ) {
|
||||
$description = $allowed_link
|
||||
? sprintf(
|
||||
/* translators: %s: WooCommerce.com URL to add payment method */
|
||||
_n(
|
||||
'Your WooCommerce extension subscription is missing a payment method for renewal. <a href="%s">Add a payment method</a> to ensure you continue receiving updates and streamlined support.',
|
||||
'Your WooCommerce extension subscriptions are missing a payment method for renewal. <a href="%s">Add a payment method</a> to ensure you continue receiving updates and streamlined support.',
|
||||
$total_expiring_subscriptions,
|
||||
'woocommerce'
|
||||
),
|
||||
'https://woocommerce.com/my-account/add-payment-method/'
|
||||
)
|
||||
: _n(
|
||||
'Your WooCommerce extension subscription is missing a payment method for renewal. Add a payment method to ensure you continue receiving updates and streamlined support.',
|
||||
'Your WooCommerce extension subscriptions are missing a payment method for renewal. Add a payment method to ensure you continue receiving updates and streamlined support.',
|
||||
$total_expiring_subscriptions,
|
||||
'woocommerce'
|
||||
);
|
||||
|
||||
return array(
|
||||
'description' => $description,
|
||||
'button_text' => __( 'Add payment method', 'woocommerce' ),
|
||||
'button_link' => 'https://woocommerce.com/my-account/add-payment-method/',
|
||||
);
|
||||
return self::get_missing_payment_method_notice( $allowed_link, $total_expiring_subscriptions );
|
||||
}
|
||||
|
||||
// Payment method is available but there are expiring subscriptions.
|
||||
@@ -865,14 +863,20 @@ class PluginsHelper {
|
||||
'expiring',
|
||||
);
|
||||
|
||||
$button_link = self::WOO_SUBSCRIPTION_PAGE_URL;
|
||||
$button_link = add_query_arg(
|
||||
array(
|
||||
'utm_source' => 'pu',
|
||||
'utm_campaign' => 'pu_in_apps_screen_enable_autorenew',
|
||||
),
|
||||
self::WOO_SUBSCRIPTION_PAGE_URL
|
||||
);
|
||||
if ( in_array( $notice_data['type'], array( 'single_manage', 'multiple_manage' ), true ) ) {
|
||||
$button_link = add_query_arg(
|
||||
array(
|
||||
'product_id' => $notice_data['product_id'],
|
||||
'type' => 'expiring',
|
||||
),
|
||||
self::WOO_SUBSCRIPTION_PAGE_URL
|
||||
$button_link
|
||||
);
|
||||
}
|
||||
|
||||
@@ -903,6 +907,7 @@ class PluginsHelper {
|
||||
$subscriptions,
|
||||
function ( $sub ) {
|
||||
return ( ! empty( $sub['local']['installed'] ) && ! empty( $sub['product_key'] ) )
|
||||
&& $sub['active']
|
||||
&& $sub['expired']
|
||||
&& ! $sub['lifetime'];
|
||||
},
|
||||
@@ -923,21 +928,28 @@ class PluginsHelper {
|
||||
/* translators: 1) product name 3) URL to My Subscriptions page 4) Renew product price string */
|
||||
'single_manage' => __( 'Your subscription for <strong>%1$s</strong> expired. <a href="%3$s">%4$s</a> to continue receiving updates and streamlined support.', 'woocommerce' ),
|
||||
/* translators: 1) product name 3) URL to My Subscriptions page 4) Renew product price string */
|
||||
'multiple_manage' => __( 'One of your subscriptions for <strong>%1$s</strong> has expired. <a href="%3$s">%4$s.</a> to continue receiving updates and streamlined support.', 'woocommerce' ),
|
||||
'multiple_manage' => __( 'One of your subscriptions for <strong>%1$s</strong> has expired. <a href="%3$s">%4$s</a> to continue receiving updates and streamlined support.', 'woocommerce' ),
|
||||
/* translators: 1) total expired subscriptions 2) URL to My Subscriptions page */
|
||||
'different_subscriptions' => __( 'You have <strong>%1$s Woo extension subscriptions</strong> that expired. <a href="%2$s">Renew</a> to continue receiving updates and streamlined support.', 'woocommerce' ),
|
||||
),
|
||||
'expired',
|
||||
);
|
||||
|
||||
$button_link = self::WOO_SUBSCRIPTION_PAGE_URL;
|
||||
$button_link = add_query_arg(
|
||||
array(
|
||||
'utm_source' => 'pu',
|
||||
'utm_campaign' => $allowed_link ? 'pu_settings_screen_renew' : 'pu_in_apps_screen_renew',
|
||||
),
|
||||
self::WOO_SUBSCRIPTION_PAGE_URL
|
||||
);
|
||||
|
||||
if ( in_array( $notice_data['type'], array( 'single_manage', 'multiple_manage' ), true ) ) {
|
||||
$button_link = add_query_arg(
|
||||
array(
|
||||
'product_id' => $notice_data['product_id'],
|
||||
'type' => 'expiring',
|
||||
),
|
||||
self::WOO_SUBSCRIPTION_PAGE_URL
|
||||
$button_link
|
||||
);
|
||||
}
|
||||
|
||||
@@ -973,4 +985,45 @@ class PluginsHelper {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notice data for missing payment method.
|
||||
*
|
||||
* @param bool $allowed_link whether should show link on the notice or not.
|
||||
* @param int $total_expiring_subscriptions total expiring subscriptions.
|
||||
*
|
||||
* @return array the notices data.
|
||||
*/
|
||||
public static function get_missing_payment_method_notice( $allowed_link = true, $total_expiring_subscriptions = 1 ) {
|
||||
$add_payment_method_link = add_query_arg(
|
||||
array(
|
||||
'utm_source' => 'pu',
|
||||
'utm_campaign' => $allowed_link ? 'pu_settings_screen_add_payment_method' : 'pu_in_apps_screen_add_payment_method',
|
||||
),
|
||||
self::WOO_ADD_PAYMENT_METHOD_URL
|
||||
);
|
||||
$description = $allowed_link
|
||||
? sprintf(
|
||||
/* translators: %s: WooCommerce.com URL to add payment method */
|
||||
_n(
|
||||
'Your WooCommerce extension subscription is missing a payment method for renewal. <a href="%s">Add a payment method</a> to ensure you continue receiving updates and streamlined support.',
|
||||
'Your WooCommerce extension subscriptions are missing a payment method for renewal. <a href="%s">Add a payment method</a> to ensure you continue receiving updates and streamlined support.',
|
||||
$total_expiring_subscriptions,
|
||||
'woocommerce'
|
||||
),
|
||||
$add_payment_method_link
|
||||
)
|
||||
: _n(
|
||||
'Your WooCommerce extension subscription is missing a payment method for renewal. Add a payment method to ensure you continue receiving updates and streamlined support.',
|
||||
'Your WooCommerce extension subscriptions are missing a payment method for renewal. Add a payment method to ensure you continue receiving updates and streamlined support.',
|
||||
$total_expiring_subscriptions,
|
||||
'woocommerce'
|
||||
);
|
||||
|
||||
return array(
|
||||
'description' => $description,
|
||||
'button_text' => __( 'Add payment method', 'woocommerce' ),
|
||||
'button_link' => $add_payment_method_link,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,14 @@ namespace Automattic\WooCommerce\Admin\RemoteInboxNotifications;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Notes\Notes;
|
||||
use Automattic\WooCommerce\Admin\PluginsProvider\PluginsProvider;
|
||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
|
||||
use Automattic\WooCommerce\Admin\Notes\Note;
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\RemoteSpecsEngine;
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\StoredStateSetupForProducts;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
|
||||
/**
|
||||
* Remote Inbox Notifications engine.
|
||||
@@ -19,11 +22,15 @@ use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\StoredStateSetupForP
|
||||
* specs that are able to be triggered.
|
||||
*/
|
||||
class RemoteInboxNotificationsEngine extends RemoteSpecsEngine {
|
||||
use AccessiblePrivateMethods;
|
||||
|
||||
const STORED_STATE_OPTION_NAME = 'wc_remote_inbox_notifications_stored_state';
|
||||
const WCA_UPDATED_OPTION_NAME = 'wc_remote_inbox_notifications_wca_updated';
|
||||
|
||||
/**
|
||||
* Initialize the engine.
|
||||
* phpcs:disable WooCommerce.Functions.InternalInjectionMethod.MissingFinal
|
||||
* phpcs:disable WooCommerce.Functions.InternalInjectionMethod.MissingInternalTag
|
||||
*/
|
||||
public static function init() {
|
||||
// Init things that need to happen before admin_init.
|
||||
@@ -42,10 +49,13 @@ class RemoteInboxNotificationsEngine extends RemoteSpecsEngine {
|
||||
|
||||
// Hook into WCA updated. This is hooked up here rather than in
|
||||
// on_admin_init because that runs too late to hook into the action.
|
||||
add_action( 'woocommerce_run_on_woocommerce_admin_updated', array( __CLASS__, 'run_on_woocommerce_admin_updated' ) );
|
||||
add_action(
|
||||
'woocommerce_run_on_woocommerce_admin_updated',
|
||||
array( __CLASS__, 'run_on_woocommerce_admin_updated' )
|
||||
);
|
||||
add_action(
|
||||
'woocommerce_updated',
|
||||
function() {
|
||||
function () {
|
||||
$next_hook = WC()->queue()->get_next(
|
||||
'woocommerce_run_on_woocommerce_admin_updated',
|
||||
array(),
|
||||
@@ -63,6 +73,11 @@ class RemoteInboxNotificationsEngine extends RemoteSpecsEngine {
|
||||
);
|
||||
|
||||
add_filter( 'woocommerce_get_note_from_db', array( __CLASS__, 'get_note_from_db' ), 10, 1 );
|
||||
self::add_filter( 'woocommerce_debug_tools', array( __CLASS__, 'add_debug_tools' ) );
|
||||
self::add_action(
|
||||
'wp_ajax_woocommerce_json_inbox_notifications_search',
|
||||
array( __CLASS__, 'ajax_action_inbox_notification_search' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +170,7 @@ class RemoteInboxNotificationsEngine extends RemoteSpecsEngine {
|
||||
public static function get_stored_state() {
|
||||
$stored_state = get_option( self::STORED_STATE_OPTION_NAME );
|
||||
|
||||
if ( $stored_state === false ) {
|
||||
if ( false === $stored_state ) {
|
||||
$stored_state = new \stdClass();
|
||||
|
||||
$stored_state = StoredStateSetupForProducts::init_stored_state(
|
||||
@@ -199,6 +214,7 @@ class RemoteInboxNotificationsEngine extends RemoteSpecsEngine {
|
||||
* Get the note. This is used to display localized note.
|
||||
*
|
||||
* @param Note $note_from_db The note object created from db.
|
||||
*
|
||||
* @return Note The note.
|
||||
*/
|
||||
public static function get_note_from_db( $note_from_db ) {
|
||||
@@ -211,7 +227,7 @@ class RemoteInboxNotificationsEngine extends RemoteSpecsEngine {
|
||||
continue;
|
||||
}
|
||||
$locale = SpecRunner::get_locale( $spec->locales, true );
|
||||
if ( $locale === null ) {
|
||||
if ( null === $locale ) {
|
||||
// No locale found, so don't update the note.
|
||||
break;
|
||||
}
|
||||
@@ -233,4 +249,94 @@ class RemoteInboxNotificationsEngine extends RemoteSpecsEngine {
|
||||
|
||||
return $note_from_db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the debug tools to the WooCommerce debug tools (WooCommerce > Status > Tools).
|
||||
*
|
||||
* @param array $tools a list of tools.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function add_debug_tools( $tools ) {
|
||||
// Check if the feature flag is disabled.
|
||||
if ( ! Features::is_enabled( 'remote-inbox-notifications' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the site has opted out of marketplace suggestions.
|
||||
if ( get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) !== 'yes' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tools['refresh_remote_inbox_notifications'] = array(
|
||||
'name' => __( 'Refresh Remote Inbox Notifications', 'woocommerce' ),
|
||||
'button' => __( 'Refresh', 'woocommerce' ),
|
||||
'desc' => __( 'This will refresh the remote inbox notifications', 'woocommerce' ),
|
||||
'callback' => function () {
|
||||
RemoteInboxNotificationsDataSourcePoller::get_instance()->read_specs_from_data_sources();
|
||||
RemoteInboxNotificationsEngine::run();
|
||||
|
||||
return __( 'Remote inbox notifications have been refreshed', 'woocommerce' );
|
||||
},
|
||||
);
|
||||
|
||||
$tools['delete_inbox_notification'] = array(
|
||||
'name' => __( 'Delete an Inbox Notification', 'woocommerce' ),
|
||||
'button' => __( 'Delete', 'woocommerce' ),
|
||||
'desc' => __( 'This will delete an inbox notification by slug', 'woocommerce' ),
|
||||
'selector' => array(
|
||||
'description' => __( 'Select an inbox notification to delete:', 'woocommerce' ),
|
||||
'class' => 'wc-product-search',
|
||||
'search_action' => 'woocommerce_json_inbox_notifications_search',
|
||||
'name' => 'delete_inbox_notification_note_id',
|
||||
'placeholder' => esc_attr__( 'Search for an inbox notification…', 'woocommerce' ),
|
||||
),
|
||||
'callback' => function () {
|
||||
check_ajax_referer( 'debug_action', '_wpnonce' );
|
||||
|
||||
if ( ! isset( $_GET['delete_inbox_notification_note_id'] ) ) {
|
||||
return __( 'No inbox notification selected', 'woocommerce' );
|
||||
}
|
||||
$note_id = wc_clean( sanitize_text_field( wp_unslash( $_GET['delete_inbox_notification_note_id'] ) ) );
|
||||
$note = Notes::get_note( $note_id );
|
||||
|
||||
if ( ! $note ) {
|
||||
return __( 'Inbox notification not found', 'woocommerce' );
|
||||
}
|
||||
|
||||
$note->delete( true );
|
||||
return __( 'Inbox notification has been deleted', 'woocommerce' );
|
||||
},
|
||||
);
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ajax action for remote inbox notification search.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function ajax_action_inbox_notification_search() {
|
||||
global $wpdb;
|
||||
|
||||
check_ajax_referer( 'search-products', 'security' );
|
||||
|
||||
if ( ! isset( $_GET['term'] ) ) {
|
||||
wp_send_json( array() );
|
||||
}
|
||||
|
||||
$search = wc_clean( sanitize_text_field( wp_unslash( $_GET['term'] ) ) );
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT note_id, name FROM {$wpdb->prefix}wc_admin_notes WHERE name LIKE %s",
|
||||
'%' . $wpdb->esc_like( $search ) . '%'
|
||||
)
|
||||
);
|
||||
$rows = array();
|
||||
foreach ( $results as $result ) {
|
||||
$rows[ $result->note_id ] = $result->name;
|
||||
}
|
||||
wp_send_json( $rows );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,23 +25,23 @@ class EvaluationLogger {
|
||||
/**
|
||||
* Logger class to use.
|
||||
*
|
||||
* @var WC_Logger_Interface|null
|
||||
* @var \WC_Logger_Interface|null
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Logger source.
|
||||
*
|
||||
* @var string logger source.
|
||||
* @var string Logger source.
|
||||
*/
|
||||
private $source = '';
|
||||
|
||||
/**
|
||||
* EvaluationLogger constructor.
|
||||
*
|
||||
* @param string $slug Slug of a spec that is being evaluated.
|
||||
* @param null $source Logger source.
|
||||
* @param \WC_Logger_Interface $logger Logger class to use.
|
||||
* @param string $slug Slug/ID of a spec that is being evaluated.
|
||||
* @param string|null $source Logger source.
|
||||
* @param \WC_Logger_Interface|null $logger Logger class to use. Default to using the WC logger.
|
||||
*/
|
||||
public function __construct( $slug, $source = null, \WC_Logger_Interface $logger = null ) {
|
||||
$this->slug = $slug;
|
||||
@@ -59,16 +59,13 @@ class EvaluationLogger {
|
||||
/**
|
||||
* Add evaluation result of a rule.
|
||||
*
|
||||
* @param string $rule_type name of the rule being tested.
|
||||
* @param boolean $result result of a given rule.
|
||||
* @param string $rule_type Name of the rule being tested.
|
||||
* @param boolean $result Result of a given rule.
|
||||
*/
|
||||
public function add_result( $rule_type, $result ) {
|
||||
array_push(
|
||||
$this->results,
|
||||
array(
|
||||
'rule' => $rule_type,
|
||||
'result' => $result ? 'passed' : 'failed',
|
||||
)
|
||||
$this->results[] = array(
|
||||
'rule' => $rule_type,
|
||||
'result' => $result ? 'passed' : 'failed',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,7 +73,16 @@ class EvaluationLogger {
|
||||
* Log the results.
|
||||
*/
|
||||
public function log() {
|
||||
if ( false === defined( 'WC_ADMIN_DEBUG_RULE_EVALUATOR' ) || true !== constant( 'WC_ADMIN_DEBUG_RULE_EVALUATOR' ) ) {
|
||||
$should_log = defined( 'WC_ADMIN_DEBUG_RULE_EVALUATOR' ) && true === constant( 'WC_ADMIN_DEBUG_RULE_EVALUATOR' );
|
||||
|
||||
/**
|
||||
* Filter to determine if the rule evaluator should log the results.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*
|
||||
* @param bool $should_log Whether the rule evaluator should log the results.
|
||||
*/
|
||||
if ( ! apply_filters( 'woocommerce_admin_remote_specs_evaluator_should_log', $should_log ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ class RuleEvaluator {
|
||||
* Evaluate the given rules as an AND operation - return false early if a
|
||||
* rule evaluates to false.
|
||||
*
|
||||
* @param array|object $rules The rule or rules being processed.
|
||||
* @param array|object $rules The rule or rules being processed.
|
||||
* @param object|null $stored_state Stored state.
|
||||
* @param array $logger_args Arguments for the event logger. `slug` is required.
|
||||
* @param array $logger_args Arguments for the rule evaluator logger. `slug` is required.
|
||||
*
|
||||
* @throws \InvalidArgumentException Thrown when $logger_args is missing slug.
|
||||
*
|
||||
@@ -65,13 +65,16 @@ class RuleEvaluator {
|
||||
throw new \InvalidArgumentException( 'Missing required field: slug in $logger_args.' );
|
||||
}
|
||||
|
||||
array_key_exists( 'source', $logger_args ) ? $source = $logger_args['source'] : $source = null;
|
||||
$source = isset( $logger_args['source'] ) ? $logger_args['source'] : null;
|
||||
|
||||
$evaluation_logger = new EvaluationLogger( $logger_args['slug'], $source );
|
||||
}
|
||||
|
||||
foreach ( $rules as $rule ) {
|
||||
if ( ! is_object( $rule ) ) {
|
||||
$evaluation_logger && $evaluation_logger->add_result( 'rule not an object', false );
|
||||
$evaluation_logger && $evaluation_logger->log();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,6 @@ class WCAdminHelper {
|
||||
'shop' => wc_get_page_id( 'shop' ),
|
||||
'cart' => wc_get_page_id( 'cart' ),
|
||||
'checkout' => wc_get_page_id( 'checkout' ),
|
||||
'privacy' => wc_privacy_policy_page_id(),
|
||||
'terms' => wc_terms_and_conditions_page_id(),
|
||||
'coming_soon' => wc_get_page_id( 'coming_soon' ),
|
||||
);
|
||||
|
||||
@@ -47,8 +47,9 @@ class Autoloader {
|
||||
*/
|
||||
protected static function missing_autoloader() {
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
// This message is not translated as at this point it's too early to load translations.
|
||||
error_log( // phpcs:ignore
|
||||
esc_html__( 'Your installation of WooCommerce is incomplete. If you installed WooCommerce from GitHub, please refer to this document to set up your development environment: https://github.com/woocommerce/woocommerce/wiki/How-to-set-up-WooCommerce-development-environment', 'woocommerce' )
|
||||
esc_html( 'Your installation of WooCommerce is incomplete. If you installed WooCommerce from GitHub, please refer to this document to set up your development environment: https://github.com/woocommerce/woocommerce/wiki/How-to-set-up-WooCommerce-development-environment' )
|
||||
);
|
||||
}
|
||||
add_action(
|
||||
|
||||
@@ -0,0 +1,673 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\AIContent;
|
||||
|
||||
/**
|
||||
* Patterns Dictionary class.
|
||||
*/
|
||||
class PatternsDictionary {
|
||||
/**
|
||||
* Returns the patterns' dictionary.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public static function get() {
|
||||
return [
|
||||
[
|
||||
'name' => 'Banner',
|
||||
'slug' => 'woocommerce-blocks/banner',
|
||||
'images_total' => 1,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Up to 60% off', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A four words title advertising the sale', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Holiday Sale', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words label with the sale name', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Get your favorite vinyl at record-breaking prices.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The main description of the sale with at least 65 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'default' => __( 'Shop vinyl records', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A 3 words button text to go to the sale page', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Discount Banner',
|
||||
'slug' => 'woocommerce-blocks/discount-banner',
|
||||
'content' => [
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Select products', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words description of the products on sale', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Discount Banner with Image',
|
||||
'slug' => 'woocommerce-blocks/discount-banner-with-image',
|
||||
'images_total' => 1,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Select products', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words description of the products on sale', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Featured Category Focus',
|
||||
'slug' => 'woocommerce-blocks/featured-category-focus',
|
||||
'images_total' => 1,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Black and white high-quality prints', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The four words title of the featured category related to the following image description: [image.0]', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'default' => __( 'Shop prints', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words button text to go to the featured category', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Featured Category Triple',
|
||||
'slug' => 'woocommerce-blocks/featured-category-triple',
|
||||
'images_total' => 3,
|
||||
'images_format' => 'portrait',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Home decor', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A one-word graphic title that encapsulates the essence of the business, inspired by the following image description: [image.0] and the nature of the business. The title should reflect the key elements and characteristics of the business, as portrayed in the image', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Retro photography', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two-words graphic title that encapsulates the essence of the business, inspired by the following image description: [image.1] and the nature of the business. The title should reflect the key elements and characteristics of the business, as portrayed in the image', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Handmade gifts', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two-words graphic title that encapsulates the essence of the business, inspired by the following image description: [image.2] and the nature of the business. The title should reflect the key elements and characteristics of the business, as portrayed in the image', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Featured Products: Fresh & Tasty',
|
||||
'slug' => 'woocommerce-blocks/featured-products-fresh-and-tasty',
|
||||
'images_total' => 4,
|
||||
'images_format' => 'portrait',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Fresh & tasty goods', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The title of the featured products with at least 20 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Sweet Organic Lemons', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The three words description of the featured product related to the following image description: [image.0]', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Fresh Organic Tomatoes', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The three words description of the featured product related to the following image description: [image.1]', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Fresh Lettuce (Washed)', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The three words description of the featured product related to the following image description: [image.2]', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Russet Organic Potatoes', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The three words description of the featured product related to the following image description: [image.3]', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Hero Product 3 Split',
|
||||
'slug' => 'woocommerce-blocks/hero-product-3-split',
|
||||
'images_total' => 1,
|
||||
'images_format' => 'portrait',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Timeless elegance', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write a two words title for advertising the store', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Durable glass', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write a two words title for advertising the store', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Versatile charm', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write a two words title for advertising the store', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'New: Retro Glass Jug', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write a title with less than 20 characters for advertising the store', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Elevate your table with a 330ml Retro Glass Jug, blending classic design and durable hardened glass.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write a text with approximately 130 characters, to describe a product the business is selling', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Crafted from resilient thick glass, this jug ensures lasting quality, making it perfect for everyday use with a touch of vintage charm.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write a text with approximately 130 characters, to describe a product the business is selling', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( "The Retro Glass Jug's classic silhouette effortlessly complements any setting, making it the ideal choice for serving beverages with style and flair.", 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write a long text, with at least 130 characters, to describe a product the business is selling', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Hero Product Chessboard',
|
||||
'slug' => 'woocommerce-blocks/hero-product-chessboard',
|
||||
'images_total' => 2,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Quality Materials', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words title describing the first displayed product feature', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Unique design', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words title describing the second displayed product feature', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Make your house feel like home', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words title describing the fourth displayed product feature', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'We use only the highest-quality materials in our products, ensuring that they look great and last for years to come.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A description of the product feature with at least 115 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'From bold prints to intricate details, our products are a perfect combination of style and function.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A description of the product feature with at least 115 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Add a touch of charm and coziness this holiday season with a wide selection of hand-picked decorations — from minimalist vases to designer furniture.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A description of the product feature with at least 115 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'default' => __( 'Shop home decor', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words button text to go to the product page', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Hero Product Split',
|
||||
'slug' => 'woocommerce-blocks/hero-product-split',
|
||||
'images_total' => 1,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Keep dry with 50% off rain jackets', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the product the store is selling with at least 35 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Just Arrived Full Hero',
|
||||
'slug' => 'woocommerce-blocks/just-arrived-full-hero',
|
||||
'images_total' => 1,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Sound like no other', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the displayed product collection with at least 10 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Experience your music like never before with our latest generation of hi-fidelity headphones.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A description of the product collection with at least 35 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'default' => __( 'Shop now', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words button text to go to the product collection page', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Product Collection Banner',
|
||||
'slug' => 'woocommerce-blocks/product-collection-banner',
|
||||
'images_total' => 1,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Brand New for the Holidays', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the displayed product collection with at least 25 characters related to the following image description: [image.0]', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Check out our brand new collection of holiday products and find the right gift for anyone.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A description of the product collection with at least 90 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Product Collections Featured Collection',
|
||||
'slug' => 'woocommerce-blocks/product-collections-featured-collection',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => "This week's popular products",
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the displayed product collection with at least 30 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Product Collections Featured Collections',
|
||||
'slug' => 'woocommerce-blocks/product-collections-featured-collections',
|
||||
'images_total' => 4,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Tech gifts under $100', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the product collection with at least 20 characters related to the following image descriptions: [image.0], [image.1]', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'For the gamers', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the product collection with at least 15 characters related to the following image descriptions: [image.2], [image.3]', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'default' => __( 'Shop tech', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words button text to go to the product collection page', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Shop games', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words button text to go to the product collection page', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Product Collections Newest Arrivals',
|
||||
'slug' => 'woocommerce-blocks/product-collections-newest-arrivals',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Our newest arrivals', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the displayed product collection with at least 20 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'default' => __( 'More new products', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The button text to go to the product collection page with at least 15 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Product Collection 4 Columns',
|
||||
'slug' => 'woocommerce-blocks/product-collection-4-columns',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Staff picks', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the displayed product collection with at least 20 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Product Collection 5 Columns',
|
||||
'slug' => 'woocommerce-blocks/product-collection-5-columns',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Our latest and greatest', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase with that advertises the product collection with at least 20 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Product Gallery',
|
||||
'slug' => 'woocommerce-blocks/product-query-product-gallery',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Bestsellers', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the featured products with at least 10 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Featured Products 2 Columns',
|
||||
'slug' => 'woocommerce-blocks/featured-products-2-cols',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Fan favorites', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the featured products with at least 10 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Get ready to start the season right. All the fan favorites in one place at the best price.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A description of the featured products with at least 90 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'default' => __( 'Shop All', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words button text to go to the featured products page', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Product Hero 2 Column 2 Row',
|
||||
'slug' => 'woocommerce-blocks/product-hero-2-col-2-row',
|
||||
'images_total' => 2,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'The Eden Jacket', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A three words title that advertises a product related to the following image description: [image.0]', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( '100% Woolen', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words title that advertises a product feature', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Fits your wardrobe', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A three words title that advertises a product feature', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Versatile', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An one word title that advertises a product feature', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Normal Fit', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words title that advertises a product feature', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Perfect for any look featuring a mid-rise, relax fitting silhouette.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The description of a product with at least 65 characters related to the following image: [image.0]', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Reflect your fashionable style.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The description of a product feature with at least 30 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Half tuck into your pants or layer over.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The description of a product feature with at least 30 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Button-down front for any type of mood or look.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The description of a product feature with at least 30 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( '42% Cupro 34% Linen 24% Viscose', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'The description of a product feature with at least 30 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'default' => __( 'View product', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words button text to go to the product page', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Shop by Price',
|
||||
'slug' => 'woocommerce-blocks/shop-by-price',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Outdoor Furniture & Accessories', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the first product collection with at least 30 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Summer Dinning', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the second product collection with at least 20 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => "Women's Styles",
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the third product collection with at least 20 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => "Kids' Styles",
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the fourth product collection with at least 20 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Small Discount Banner with Image',
|
||||
'slug' => 'woocommerce-blocks/small-discount-banner-with-image',
|
||||
'images_total' => 1,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Chairs', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A single word that advertises the product and is related to the following image description: [image.0]', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Social: Follow us on social media',
|
||||
'slug' => 'woocommerce-blocks/social-follow-us-in-social-media',
|
||||
'images_total' => 4,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Stay in the loop', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A phrase that advertises the social media accounts of the store with at least 25 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Alternating Image and Text',
|
||||
'slug' => 'woocommerce-blocks/alt-image-and-text',
|
||||
'images_total' => 2,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Our products', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words impact phrase that advertises the products', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Sustainable blends, stylish accessories', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the products with at least 40 characters and related to the following image description: [image.0]', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'About us', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words impact phrase that advertises the brand', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Committed to a greener lifestyle', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the brand with at least 50 characters related to the following image description: [image.1]', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Indulge in the finest organic coffee beans, teas, and hand-picked accessories, all locally sourced and sustainable for a mindful lifestyle.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A description of the products with at least 180 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => "Our passion is crafting mindful moments with locally sourced, organic, and sustainable products. We're more than a store; we're your path to a community-driven, eco-friendly lifestyle that embraces premium quality.",
|
||||
'ai_prompt' => __( 'A description of the products with at least 180 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Locally sourced ingredients', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A three word description of the products', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Premium organic blends', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A three word description of the products', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Hand-picked accessories', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A three word description of the products', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Sustainable business practices', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A three word description of the products', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'default' => __( 'Meet us', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words button text to go to the product page', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Testimonials 3 Columns',
|
||||
'slug' => 'woocommerce-blocks/testimonials-3-columns',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Eclectic finds, ethical delights', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write a short title advertising a testimonial from a customer', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'Sip, Shop, Savor', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write a short title advertising a testimonial from a customer', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'LOCAL LOVE', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write a short title advertising a testimonial from a customer', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'What our customers say', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write just 4 words to advertise testimonials from customers', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Transformed my daily routine with unique, eco-friendly treasures. Exceptional quality and service. Proud to support a store that aligns with my values.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write the testimonial from a customer with approximately 150 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'The organic coffee beans are a revelation. Each sip feels like a journey. Beautifully crafted accessories add a touch of elegance to my home.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write the testimonial from a customer with approximately 150 characters', 'woocommerce' ),
|
||||
],
|
||||
[
|
||||
'default' => __( 'From sustainably sourced teas to chic vases, this store is a treasure trove. Love knowing my purchases contribute to a greener planet.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'Write the testimonial from a customer with approximately 150 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Testimonials Single',
|
||||
'slug' => 'woocommerce-blocks/testimonials-single',
|
||||
'images_total' => 1,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'A ‘brewtiful’ experience :-)', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words title that advertises the testimonial', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'Exceptional flavors, sustainable choices. The carefully curated collection of coffee pots and accessories turned my kitchen into a haven of style and taste.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A description of the testimonial with at least 225 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Featured Category Cover Image',
|
||||
'slug' => 'woocommerce-blocks/featured-category-cover-image',
|
||||
'images_total' => 1,
|
||||
'images_format' => 'landscape',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Sit back and relax', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A description for a product with at least 20 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'descriptions' => [
|
||||
[
|
||||
'default' => __( 'With a wide range of designer chairs to elevate your living space.', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the products with at least 55 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
'buttons' => [
|
||||
[
|
||||
'default' => __( 'Shop chairs', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'A two words button text to go to the shop page', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Product Collection: Featured Products 5 Columns',
|
||||
'slug' => 'woocommerce-blocks/product-collection-featured-products-5-columns',
|
||||
'content' => [
|
||||
'titles' => [
|
||||
[
|
||||
'default' => __( 'Shop new arrivals', 'woocommerce' ),
|
||||
'ai_prompt' => __( 'An impact phrase that advertises the newest additions to the store with at least 20 characters', 'woocommerce' ),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ class PatternsHelper {
|
||||
/**
|
||||
* Upsert the patterns AI data.
|
||||
*
|
||||
* @param array $patterns_dictionary The patterns dictionary.
|
||||
* @param array $patterns_dictionary The patterns' dictionary.
|
||||
*
|
||||
* @return WP_Error|null
|
||||
*/
|
||||
@@ -92,18 +92,12 @@ class PatternsHelper {
|
||||
* @return array|WP_Error Returns pattern dictionary or WP_Error on failure.
|
||||
*/
|
||||
public static function get_patterns_dictionary( $pattern_slug = null ) {
|
||||
$patterns_dictionary_file = plugin_dir_path( __FILE__ ) . 'dictionary.json';
|
||||
$default_patterns_dictionary = PatternsDictionary::get();
|
||||
|
||||
if ( ! file_exists( $patterns_dictionary_file ) ) {
|
||||
if ( empty( $default_patterns_dictionary ) ) {
|
||||
return new WP_Error( 'missing_patterns_dictionary', __( 'The patterns dictionary is missing.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$default_patterns_dictionary = wp_json_file_decode( $patterns_dictionary_file, array( 'associative' => true ) );
|
||||
|
||||
if ( json_last_error() !== JSON_ERROR_NONE ) {
|
||||
return new WP_Error( 'json_decode_error', __( 'Error decoding JSON.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$patterns_dictionary = '';
|
||||
$ai_connection_allowed = get_option( 'woocommerce_blocks_allow_ai_connection' );
|
||||
|
||||
|
||||
@@ -402,13 +402,13 @@ class UpdatePatterns {
|
||||
* @return mixed|WP_Error|null
|
||||
*/
|
||||
public static function get_patterns_dictionary() {
|
||||
$patterns_dictionary = plugin_dir_path( __FILE__ ) . 'dictionary.json';
|
||||
$patterns_dictionary = PatternsDictionary::get();
|
||||
|
||||
if ( ! file_exists( $patterns_dictionary ) ) {
|
||||
if ( empty( $patterns_dictionary ) ) {
|
||||
return new WP_Error( 'missing_patterns_dictionary', __( 'The patterns dictionary is missing.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
return wp_json_file_decode( $patterns_dictionary, array( 'associative' => true ) );
|
||||
return $patterns_dictionary;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ class UpdateProducts {
|
||||
'price' => 249,
|
||||
],
|
||||
[
|
||||
'title' => 'Black and White Summer Portrait',
|
||||
'title' => 'Black and White',
|
||||
'image' => 'assets/images/pattern-placeholders/white-black-black-and-white-photograph-monochrome-photography.jpg',
|
||||
'description' => 'This 24" x 30" high-quality print just exudes summer. Hang it on the wall and forget about the world outside.',
|
||||
'price' => 115,
|
||||
@@ -113,7 +113,7 @@ class UpdateProducts {
|
||||
$products_to_create = max( 0, 6 - $real_products_count - $dummy_products_count );
|
||||
while ( $products_to_create > 0 ) {
|
||||
$this->create_new_product( self::DUMMY_PRODUCTS[ $products_to_create - 1 ] );
|
||||
$products_to_create--;
|
||||
--$products_to_create;
|
||||
}
|
||||
|
||||
// Identify dummy products that need to have their content updated.
|
||||
@@ -327,7 +327,7 @@ class UpdateProducts {
|
||||
public function assign_ai_selected_images_to_dummy_products( $dummy_products_to_update, $ai_selected_images ) {
|
||||
$products_information_list = [];
|
||||
$dummy_products_count = count( $dummy_products_to_update );
|
||||
for ( $i = 0; $i < $dummy_products_count; $i ++ ) {
|
||||
for ( $i = 0; $i < $dummy_products_count; $i++ ) {
|
||||
$image_src = $ai_selected_images[ $i ]['URL'] ?? '';
|
||||
|
||||
if ( wc_is_valid_url( $image_src ) ) {
|
||||
@@ -396,7 +396,7 @@ class UpdateProducts {
|
||||
$ai_request_retries = 0;
|
||||
$success = false;
|
||||
while ( $ai_request_retries < 5 && ! $success ) {
|
||||
$ai_request_retries ++;
|
||||
++$ai_request_retries;
|
||||
$ai_response = $ai_connection->fetch_ai_response( $token, $formatted_prompt, 30 );
|
||||
if ( is_wp_error( $ai_response ) ) {
|
||||
continue;
|
||||
@@ -464,7 +464,7 @@ class UpdateProducts {
|
||||
|
||||
$this->product_update( $product, $product_image_id, self::DUMMY_PRODUCTS[ $i ]['title'], self::DUMMY_PRODUCTS[ $i ]['description'], self::DUMMY_PRODUCTS[ $i ]['price'] );
|
||||
|
||||
$i++;
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,656 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Banner",
|
||||
"slug": "woocommerce-blocks/banner",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Up to 60% off",
|
||||
"ai_prompt": "A four words title advertising the sale"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Holiday Sale",
|
||||
"ai_prompt": "A two words label with the sale name"
|
||||
},
|
||||
{
|
||||
"default": "Get your favorite vinyl at record-breaking prices.",
|
||||
"ai_prompt": "The main description of the sale with at least 65 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop vinyl records",
|
||||
"ai_prompt": "A 3 words button text to go to the sale page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Discount Banner",
|
||||
"slug": "woocommerce-blocks/discount-banner",
|
||||
"content": {
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Select products",
|
||||
"ai_prompt": "A two words description of the products on sale"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Discount Banner with Image",
|
||||
"slug": "woocommerce-blocks/discount-banner-with-image",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Select products",
|
||||
"ai_prompt": "A two words description of the products on sale"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Featured Category Focus",
|
||||
"slug": "woocommerce-blocks/featured-category-focus",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Black and white high-quality prints",
|
||||
"ai_prompt": "The four words title of the featured category related to the following image description: {image.0}"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop prints",
|
||||
"ai_prompt": "A two words button text to go to the featured category"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Featured Category Triple",
|
||||
"slug": "woocommerce-blocks/featured-category-triple",
|
||||
"images_total": 3,
|
||||
"images_format": "portrait",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Home decor",
|
||||
"ai_prompt": "A one-word graphic title that encapsulates the essence of the business, inspired by the following image description: {image.0} and the nature of the business. The title should reflect the key elements and characteristics of the business, as portrayed in the image"
|
||||
},
|
||||
{
|
||||
"default": "Retro photography",
|
||||
"ai_prompt": "A two-words graphic title that encapsulates the essence of the business, inspired by the following image description: {image.1} and the nature of the business. The title should reflect the key elements and characteristics of the business, as portrayed in the image"
|
||||
},
|
||||
{
|
||||
"default": "Handmade gifts",
|
||||
"ai_prompt": "A two-words graphic title that encapsulates the essence of the business, inspired by the following image description: {image.2} and the nature of the business. The title should reflect the key elements and characteristics of the business, as portrayed in the image"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Featured Products: Fresh & Tasty",
|
||||
"slug": "woocommerce-blocks/featured-products-fresh-and-tasty",
|
||||
"images_total": 4,
|
||||
"images_format": "portrait",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Fresh & tasty goods",
|
||||
"ai_prompt": "The title of the featured products with at least 20 characters"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Sweet Organic Lemons",
|
||||
"ai_prompt": "The three words description of the featured product related to the following image description: {image.0}"
|
||||
},
|
||||
{
|
||||
"default": "Fresh Organic Tomatoes",
|
||||
"ai_prompt": "The three words description of the featured product related to the following image description: {image.1}"
|
||||
},
|
||||
{
|
||||
"default": "Fresh Lettuce (Washed)",
|
||||
"ai_prompt": "The three words description of the featured product related to the following image description: {image.2}"
|
||||
},
|
||||
{
|
||||
"default": "Russet Organic Potatoes",
|
||||
"ai_prompt": "The three words description of the featured product related to the following image description: {image.3}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hero Product 3 Split",
|
||||
"slug": "woocommerce-blocks/hero-product-3-split",
|
||||
"images_total": 1,
|
||||
"images_format": "portrait",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Timeless elegance",
|
||||
"ai_prompt": "Write a two words title for advertising the store"
|
||||
},
|
||||
{
|
||||
"default": "Durable glass",
|
||||
"ai_prompt": "Write a two words title for advertising the store"
|
||||
},
|
||||
{
|
||||
"default": "Versatile charm",
|
||||
"ai_prompt": "Write a two words title for advertising the store"
|
||||
},
|
||||
{
|
||||
"default": "New: Retro Glass Jug",
|
||||
"ai_prompt": "Write a title with less than 20 characters for advertising the store"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Elevate your table with a 330ml Retro Glass Jug, blending classic design and durable hardened glass.",
|
||||
"ai_prompt": "Write a text with approximately 130 characters, to describe a product the business is selling"
|
||||
},
|
||||
{
|
||||
"default": "Crafted from resilient thick glass, this jug ensures lasting quality, making it perfect for everyday use with a touch of vintage charm.",
|
||||
"ai_prompt": "Write a text with approximately 130 characters, to describe a product the business is selling"
|
||||
},
|
||||
{
|
||||
"default": "The Retro Glass Jug's classic silhouette effortlessly complements any setting, making it the ideal choice for serving beverages with style and flair.",
|
||||
"ai_prompt": "Write a long text, with at least 130 characters, to describe a product the business is selling"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hero Product Chessboard",
|
||||
"slug": "woocommerce-blocks/hero-product-chessboard",
|
||||
"images_total": 2,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Quality Materials",
|
||||
"ai_prompt": "A two words title describing the first displayed product feature"
|
||||
},
|
||||
{
|
||||
"default": "Unique design",
|
||||
"ai_prompt": "A two words title describing the second displayed product feature"
|
||||
},
|
||||
{
|
||||
"default": "Make your house feel like home",
|
||||
"ai_prompt": "A two words title describing the fourth displayed product feature"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "We use only the highest-quality materials in our products, ensuring that they look great and last for years to come.",
|
||||
"ai_prompt": "A description of the product feature with at least 115 characters"
|
||||
},
|
||||
{
|
||||
"default": "From bold prints to intricate details, our products are a perfect combination of style and function.",
|
||||
"ai_prompt": "A description of the product feature with at least 115 characters"
|
||||
},
|
||||
{
|
||||
"default": "Add a touch of charm and coziness this holiday season with a wide selection of hand-picked decorations — from minimalist vases to designer furniture.",
|
||||
"ai_prompt": "A description of the product feature with at least 115 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop home decor",
|
||||
"ai_prompt": "A two words button text to go to the product page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hero Product Split",
|
||||
"slug": "woocommerce-blocks/hero-product-split",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Keep dry with 50% off rain jackets",
|
||||
"ai_prompt": "An impact phrase that advertises the product the store is selling with at least 35 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Just Arrived Full Hero",
|
||||
"slug": "woocommerce-blocks/just-arrived-full-hero",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Sound like no other",
|
||||
"ai_prompt": "An impact phrase that advertises the displayed product collection with at least 10 characters"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Experience your music like never before with our latest generation of hi-fidelity headphones.",
|
||||
"ai_prompt": "A description of the product collection with at least 35 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop now",
|
||||
"ai_prompt": "A two words button text to go to the product collection page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collection Banner",
|
||||
"slug": "woocommerce-blocks/product-collection-banner",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Brand New for the Holidays",
|
||||
"ai_prompt": "An impact phrase that advertises the displayed product collection with at least 25 characters related to the following image description: {image.0}"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Check out our brand new collection of holiday products and find the right gift for anyone.",
|
||||
"ai_prompt": "A description of the product collection with at least 90 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collections Featured Collection",
|
||||
"slug": "woocommerce-blocks/product-collections-featured-collection",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "This week's popular products",
|
||||
"ai_prompt": "An impact phrase that advertises the displayed product collection with at least 30 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collections Featured Collections",
|
||||
"slug": "woocommerce-blocks/product-collections-featured-collections",
|
||||
"images_total": 4,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Tech gifts under $100",
|
||||
"ai_prompt": "An impact phrase that advertises the product collection with at least 20 characters related to the following image descriptions: {image.0}, {image.1}"
|
||||
},
|
||||
{
|
||||
"default": "For the gamers",
|
||||
"ai_prompt": "An impact phrase that advertises the product collection with at least 15 characters related to the following image descriptions: {image.2}, {image.3}"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop tech",
|
||||
"ai_prompt": "A two words button text to go to the product collection page"
|
||||
},
|
||||
{
|
||||
"default": "Shop games",
|
||||
"ai_prompt": "A two words button text to go to the product collection page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collections Newest Arrivals",
|
||||
"slug": "woocommerce-blocks/product-collections-newest-arrivals",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Our newest arrivals",
|
||||
"ai_prompt": "An impact phrase that advertises the displayed product collection with at least 20 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "More new products",
|
||||
"ai_prompt": "The button text to go to the product collection page with at least 15 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collection 4 Columns",
|
||||
"slug": "woocommerce-blocks/product-collection-4-columns",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Staff picks",
|
||||
"ai_prompt": "An impact phrase that advertises the displayed product collection with at least 20 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collection 5 Columns",
|
||||
"slug": "woocommerce-blocks/product-collection-5-columns",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Our latest and greatest",
|
||||
"ai_prompt": "An impact phrase with that advertises the product collection with at least 20 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Gallery",
|
||||
"slug": "woocommerce-blocks/product-query-product-gallery",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Bestsellers",
|
||||
"ai_prompt": "An impact phrase that advertises the featured products with at least 10 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Featured Products 2 Columns",
|
||||
"slug": "woocommerce-blocks/featured-products-2-cols",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Fan favorites",
|
||||
"ai_prompt": "An impact phrase that advertises the featured products with at least 10 characters"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Get ready to start the season right. All the fan favorites in one place at the best price.",
|
||||
"ai_prompt": "A description of the featured products with at least 90 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop All",
|
||||
"ai_prompt": "A two words button text to go to the featured products page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Hero 2 Column 2 Row",
|
||||
"slug": "woocommerce-blocks/product-hero-2-col-2-row",
|
||||
"images_total": 2,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "The Eden Jacket",
|
||||
"ai_prompt": "A three words title that advertises a product related to the following image description: {image.0}"
|
||||
},
|
||||
{
|
||||
"default": "100% Woolen",
|
||||
"ai_prompt": "A two words title that advertises a product feature"
|
||||
},
|
||||
{
|
||||
"default": "Fits your wardrobe",
|
||||
"ai_prompt": "A three words title that advertises a product feature"
|
||||
},
|
||||
{
|
||||
"default": "Versatile",
|
||||
"ai_prompt": "An one word title that advertises a product feature"
|
||||
},
|
||||
{
|
||||
"default": "Normal Fit",
|
||||
"ai_prompt": "A two words title that advertises a product feature"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Perfect for any look featuring a mid-rise, relax fitting silhouette.",
|
||||
"ai_prompt": "The description of a product with at least 65 characters related to the following image: {image.0}"
|
||||
},
|
||||
{
|
||||
"default": "Reflect your fashionable style.",
|
||||
"ai_prompt": "The description of a product feature with at least 30 characters"
|
||||
},
|
||||
{
|
||||
"default": "Half tuck into your pants or layer over.",
|
||||
"ai_prompt": "The description of a product feature with at least 30 characters"
|
||||
},
|
||||
{
|
||||
"default": "Button-down front for any type of mood or look.",
|
||||
"ai_prompt": "The description of a product feature with at least 30 characters"
|
||||
},
|
||||
{
|
||||
"default": "42% Cupro 34% Linen 24% Viscose",
|
||||
"ai_prompt": "The description of a product feature with at least 30 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "View product",
|
||||
"ai_prompt": "A two words button text to go to the product page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Shop by Price",
|
||||
"slug": "woocommerce-blocks/shop-by-price",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Outdoor Furniture & Accessories",
|
||||
"ai_prompt": "An impact phrase that advertises the first product collection with at least 30 characters"
|
||||
},
|
||||
{
|
||||
"default": "Summer Dinning",
|
||||
"ai_prompt": "An impact phrase that advertises the second product collection with at least 20 characters"
|
||||
},
|
||||
{
|
||||
"default": "Women's Styles",
|
||||
"ai_prompt": "An impact phrase that advertises the third product collection with at least 20 characters"
|
||||
},
|
||||
{
|
||||
"default": "Kids' Styles",
|
||||
"ai_prompt": "An impact phrase that advertises the fourth product collection with at least 20 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Small Discount Banner with Image",
|
||||
"slug": "woocommerce-blocks/small-discount-banner-with-image",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Chairs",
|
||||
"ai_prompt": "A single word that advertises the product and is related to the following image description: {image.0}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Social: Follow us on social media",
|
||||
"slug": "woocommerce-blocks/social-follow-us-in-social-media",
|
||||
"images_total": 4,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Stay in the loop",
|
||||
"ai_prompt": "A phrase that advertises the social media accounts of the store with at least 25 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Alternating Image and Text",
|
||||
"slug": "woocommerce-blocks/alt-image-and-text",
|
||||
"images_total": 2,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Our products",
|
||||
"ai_prompt": "A two words impact phrase that advertises the products"
|
||||
},
|
||||
{
|
||||
"default": "Sustainable blends, stylish accessories",
|
||||
"ai_prompt": "An impact phrase that advertises the products with at least 40 characters and related to the following image description: {image.0}"
|
||||
},
|
||||
{
|
||||
"default": "About us",
|
||||
"ai_prompt": "A two words impact phrase that advertises the brand"
|
||||
},
|
||||
{
|
||||
"default": "Committed to a greener lifestyle",
|
||||
"ai_prompt": "An impact phrase that advertises the brand with at least 50 characters related to the following image description: {image.1}"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Indulge in the finest organic coffee beans, teas, and hand-picked accessories, all locally sourced and sustainable for a mindful lifestyle.",
|
||||
"ai_prompt": "A description of the products with at least 180 characters"
|
||||
},
|
||||
{
|
||||
"default": "Our passion is crafting mindful moments with locally sourced, organic, and sustainable products. We're more than a store; we're your path to a community-driven, eco-friendly lifestyle that embraces premium quality.",
|
||||
"ai_prompt": "A description of the products with at least 180 characters"
|
||||
},
|
||||
{
|
||||
"default": "Locally sourced ingredients",
|
||||
"ai_prompt": "A three word description of the products"
|
||||
},
|
||||
{
|
||||
"default": "Premium organic blends",
|
||||
"ai_prompt": "A three word description of the products"
|
||||
},
|
||||
{
|
||||
"default": "Hand-picked accessories",
|
||||
"ai_prompt": "A three word description of the products"
|
||||
},
|
||||
{
|
||||
"default": "Sustainable business practices",
|
||||
"ai_prompt": "A three word description of the products"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Meet us",
|
||||
"ai_prompt": "A two words button text to go to the product page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Testimonials 3 Columns",
|
||||
"slug": "woocommerce-blocks/testimonials-3-columns",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Eclectic finds, ethical delights",
|
||||
"ai_prompt": "Write a short title advertising a testimonial from a customer"
|
||||
},
|
||||
{
|
||||
"default": "Sip, Shop, Savor",
|
||||
"ai_prompt": "Write a short title advertising a testimonial from a customer"
|
||||
},
|
||||
{
|
||||
"default": "LOCAL LOVE",
|
||||
"ai_prompt": "Write a short title advertising a testimonial from a customer"
|
||||
},
|
||||
{
|
||||
"default": "What our customers say",
|
||||
"ai_prompt": "Write just 4 words to advertise testimonials from customers"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Transformed my daily routine with unique, eco-friendly treasures. Exceptional quality and service. Proud to support a store that aligns with my values.",
|
||||
"ai_prompt": "Write the testimonial from a customer with approximately 150 characters"
|
||||
},
|
||||
{
|
||||
"default": "The organic coffee beans are a revelation. Each sip feels like a journey. Beautifully crafted accessories add a touch of elegance to my home.",
|
||||
"ai_prompt": "Write the testimonial from a customer with approximately 150 characters"
|
||||
},
|
||||
{
|
||||
"default": "From sustainably sourced teas to chic vases, this store is a treasure trove. Love knowing my purchases contribute to a greener planet.",
|
||||
"ai_prompt": "Write the testimonial from a customer with approximately 150 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Testimonials Single",
|
||||
"slug": "woocommerce-blocks/testimonials-single",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "A ‘brewtiful’ experience :-)",
|
||||
"ai_prompt": "A two words title that advertises the testimonial"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "Exceptional flavors, sustainable choices. The carefully curated collection of coffee pots and accessories turned my kitchen into a haven of style and taste.",
|
||||
"ai_prompt": "A description of the testimonial with at least 225 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Featured Category Cover Image",
|
||||
"slug": "woocommerce-blocks/featured-category-cover-image",
|
||||
"images_total": 1,
|
||||
"images_format": "landscape",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Sit back and relax",
|
||||
"ai_prompt": "A description for a product with at least 20 characters"
|
||||
}
|
||||
],
|
||||
"descriptions": [
|
||||
{
|
||||
"default": "With a wide range of designer chairs to elevate your living space.",
|
||||
"ai_prompt": "An impact phrase that advertises the products with at least 55 characters"
|
||||
}
|
||||
],
|
||||
"buttons": [
|
||||
{
|
||||
"default": "Shop chairs",
|
||||
"ai_prompt": "A two words button text to go to the shop page"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Product Collection: Featured Products 5 Columns",
|
||||
"slug": "woocommerce-blocks/product-collection-featured-products-5-columns",
|
||||
"content": {
|
||||
"titles": [
|
||||
{
|
||||
"default": "Shop new arrivals",
|
||||
"ai_prompt": "An impact phrase that advertises the newest additions to the store with at least 20 characters"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -40,6 +40,7 @@ final class AssetsController {
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'update_block_style_dependencies' ), 20 );
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'update_block_settings_dependencies' ), 100 );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'update_block_settings_dependencies' ), 100 );
|
||||
add_filter( 'js_do_concat', array( $this, 'skip_boost_minification_for_cart_checkout' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,9 +63,14 @@ final class AssetsController {
|
||||
// The price package is shared externally so has no blocks prefix.
|
||||
$this->api->register_script( 'wc-price-format', 'assets/client/blocks/price-format.js', array(), false );
|
||||
|
||||
$this->api->register_script( 'wc-blocks-vendors-frontend', $this->api->get_block_asset_build_path( 'wc-blocks-vendors-frontend' ), array(), false );
|
||||
$this->api->register_script( 'wc-blocks-checkout', 'assets/client/blocks/blocks-checkout.js', array( 'wc-blocks-vendors-frontend' ) );
|
||||
$this->api->register_script( 'wc-blocks-components', 'assets/client/blocks/blocks-components.js', array( 'wc-blocks-vendors-frontend' ) );
|
||||
// Vendor scripts for blocks frontends (not including cart and checkout).
|
||||
$this->api->register_script( 'wc-blocks-frontend-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-frontend-vendors-frontend' ), array(), true );
|
||||
|
||||
// Cart and checkout frontend scripts.
|
||||
$this->api->register_script( 'wc-cart-checkout-vendors', $this->api->get_block_asset_build_path( 'wc-cart-checkout-vendors-frontend' ), array(), true );
|
||||
$this->api->register_script( 'wc-cart-checkout-base', $this->api->get_block_asset_build_path( 'wc-cart-checkout-base-frontend' ), array(), true );
|
||||
$this->api->register_script( 'wc-blocks-checkout', 'assets/client/blocks/blocks-checkout.js' );
|
||||
$this->api->register_script( 'wc-blocks-components', 'assets/client/blocks/blocks-components.js' );
|
||||
|
||||
// Register the interactivity components here for now.
|
||||
$this->api->register_script( 'wc-interactivity-dropdown', 'assets/client/blocks/wc-interactivity-dropdown.js', array() );
|
||||
@@ -253,6 +259,23 @@ final class AssetsController {
|
||||
return $src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip Jetpack Boost minification on older versions of Jetpack Boost where it causes issues.
|
||||
*
|
||||
* @param mixed $do_concat Whether to concatenate the script or not.
|
||||
* @param mixed $handle The script handle.
|
||||
* @return mixed
|
||||
*/
|
||||
public function skip_boost_minification_for_cart_checkout( $do_concat, $handle ) {
|
||||
$boost_is_outdated = defined( 'JETPACK_BOOST_VERSION' ) && version_compare( JETPACK_BOOST_VERSION, '3.4.2', '<' );
|
||||
$scripts_to_ignore = [
|
||||
'wc-cart-checkout-vendors',
|
||||
'wc-cart-checkout-base',
|
||||
];
|
||||
|
||||
return $boost_is_outdated && in_array( $handle, $scripts_to_ignore, true ) ? false : $do_concat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add body classes to the frontend and within admin.
|
||||
*
|
||||
|
||||
@@ -34,6 +34,8 @@ use WP_Error;
|
||||
* @internal
|
||||
*/
|
||||
class BlockPatterns {
|
||||
const CATEGORIES_PREFIXES = [ '_woo_', '_dotcom_imported_' ];
|
||||
|
||||
/**
|
||||
* Path to the patterns' directory.
|
||||
*
|
||||
@@ -145,6 +147,8 @@ class BlockPatterns {
|
||||
return;
|
||||
}
|
||||
|
||||
$patterns = $this->parse_categories( $patterns );
|
||||
|
||||
foreach ( $patterns as $pattern ) {
|
||||
$pattern['slug'] = $pattern['name'];
|
||||
$pattern['content'] = $pattern['html'];
|
||||
@@ -152,4 +156,33 @@ class BlockPatterns {
|
||||
$this->pattern_registry->register_block_pattern( $pattern['ID'], $pattern, $this->dictionary );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse prefixed categories from the PTK patterns into the actual WooCommerce categories.
|
||||
*
|
||||
* @param array $patterns The patterns to parse.
|
||||
* @return array The parsed patterns.
|
||||
*/
|
||||
private function parse_categories( array $patterns ) {
|
||||
return array_map(
|
||||
function ( $pattern ) {
|
||||
$pattern['categories'] = array_map(
|
||||
function ( $category ) {
|
||||
foreach ( self::CATEGORIES_PREFIXES as $prefix ) {
|
||||
if ( strpos( $category['title'], $prefix ) !== false ) {
|
||||
$parsed_category = str_replace( $prefix, '', $category['title'] );
|
||||
$parsed_category = str_replace( '_', ' ', $parsed_category );
|
||||
$category['title'] = ucfirst( $parsed_category );
|
||||
}
|
||||
}
|
||||
|
||||
return $category;
|
||||
},
|
||||
$pattern['categories']
|
||||
);
|
||||
return $pattern;
|
||||
},
|
||||
$patterns
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,76 +28,9 @@ class BlockTemplatesController {
|
||||
add_filter( 'get_block_template', array( $this, 'add_block_template_details' ), 10, 3 );
|
||||
add_filter( 'get_block_templates', array( $this, 'add_block_templates' ), 10, 3 );
|
||||
add_filter( 'taxonomy_template_hierarchy', array( $this, 'add_archive_product_to_eligible_for_fallback_templates' ), 10, 1 );
|
||||
add_action( 'after_switch_theme', array( $this, 'check_should_use_blockified_product_grid_templates' ), 10, 2 );
|
||||
|
||||
if ( wc_current_theme_is_fse_theme() ) {
|
||||
// By default, the Template Part Block only supports template parts that are in the current theme directory.
|
||||
// This render_callback wrapper allows us to add support for plugin-housed template parts.
|
||||
add_filter(
|
||||
'block_type_metadata_settings',
|
||||
function ( $settings, $metadata ) {
|
||||
if (
|
||||
isset( $metadata['name'], $settings['render_callback'] ) &&
|
||||
'core/template-part' === $metadata['name'] &&
|
||||
in_array( $settings['render_callback'], array( 'render_block_core_template_part', 'gutenberg_render_block_core_template_part' ), true )
|
||||
) {
|
||||
$settings['render_callback'] = array( $this, 'render_woocommerce_template_part' );
|
||||
}
|
||||
return $settings;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
// Prevents shortcodes in templates having their HTML content broken by wpautop.
|
||||
// @see https://core.trac.wordpress.org/ticket/58366 for more info.
|
||||
add_filter(
|
||||
'block_type_metadata_settings',
|
||||
function ( $settings, $metadata ) {
|
||||
if (
|
||||
isset( $metadata['name'], $settings['render_callback'] ) &&
|
||||
'core/shortcode' === $metadata['name']
|
||||
) {
|
||||
$settings['original_render_callback'] = $settings['render_callback'];
|
||||
$settings['render_callback'] = function ( $attributes, $content ) use ( $settings ) {
|
||||
// The shortcode has already been rendered, so look for the cart/checkout HTML.
|
||||
if ( strstr( $content, 'woocommerce-cart-form' ) || strstr( $content, 'wc-empty-cart-message' ) || strstr( $content, 'woocommerce-checkout-form' ) ) {
|
||||
// Return early before wpautop runs again.
|
||||
return $content;
|
||||
}
|
||||
|
||||
$render_callback = $settings['original_render_callback'];
|
||||
|
||||
return $render_callback( $attributes, $content );
|
||||
};
|
||||
}
|
||||
return $settings;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
/**
|
||||
* Prevents the pages that are assigned as cart/checkout from showing the "template" selector in the page-editor.
|
||||
* We want to avoid this flow and point users towards the site editor instead.
|
||||
*/
|
||||
add_action(
|
||||
'current_screen',
|
||||
function () {
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_screen = get_current_screen();
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( $current_screen && 'page' === $current_screen->id && ! empty( $_GET['post'] ) && in_array( absint( $_GET['post'] ), array( wc_get_page_id( 'cart' ), wc_get_page_id( 'checkout' ) ), true ) ) {
|
||||
wp_add_inline_style( 'wc-blocks-editor-style', '.edit-post-post-template { display: none; }' );
|
||||
}
|
||||
},
|
||||
10
|
||||
);
|
||||
}
|
||||
add_filter( 'block_type_metadata_settings', array( $this, 'add_plugin_templates_parts_support' ), 10, 2 );
|
||||
add_filter( 'block_type_metadata_settings', array( $this, 'prevent_shortcodes_html_breakage' ), 10, 2 );
|
||||
add_action( 'current_screen', array( $this, 'hide_template_selector_in_cart_checkout_pages' ), 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,22 +147,69 @@ class BlockTemplatesController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the old and current themes and determines if the "wc_blocks_use_blockified_product_grid_block_as_template"
|
||||
* option need to be updated accordingly.
|
||||
* By default, the Template Part Block only supports template parts that are in the current theme directory.
|
||||
* This render_callback wrapper allows us to add support for plugin-housed template parts.
|
||||
*
|
||||
* @param array $settings Array of determined settings for registering a block type.
|
||||
* @param array $metadata Metadata provided for registering a block type.
|
||||
*/
|
||||
public function add_plugin_templates_parts_support( $settings, $metadata ) {
|
||||
if (
|
||||
isset( $metadata['name'], $settings['render_callback'] ) &&
|
||||
'core/template-part' === $metadata['name'] &&
|
||||
in_array( $settings['render_callback'], array( 'render_block_core_template_part', 'gutenberg_render_block_core_template_part' ), true )
|
||||
) {
|
||||
$settings['render_callback'] = array( $this, 'render_woocommerce_template_part' );
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prevents shortcodes in templates having their HTML content broken by wpautop.
|
||||
*
|
||||
* @see https://core.trac.wordpress.org/ticket/58366 for more info.
|
||||
*
|
||||
* @param array $settings Array of determined settings for registering a block type.
|
||||
* @param array $metadata Metadata provided for registering a block type.
|
||||
*/
|
||||
public function prevent_shortcodes_html_breakage( $settings, $metadata ) {
|
||||
if (
|
||||
isset( $metadata['name'], $settings['render_callback'] ) &&
|
||||
'core/shortcode' === $metadata['name']
|
||||
) {
|
||||
$settings['original_render_callback'] = $settings['render_callback'];
|
||||
$settings['render_callback'] = function ( $attributes, $content ) use ( $settings ) {
|
||||
// The shortcode has already been rendered, so look for the cart/checkout HTML.
|
||||
if ( strstr( $content, 'woocommerce-cart-form' ) || strstr( $content, 'wc-empty-cart-message' ) || strstr( $content, 'woocommerce-checkout-form' ) ) {
|
||||
// Return early before wpautop runs again.
|
||||
return $content;
|
||||
}
|
||||
|
||||
$render_callback = $settings['original_render_callback'];
|
||||
|
||||
return $render_callback( $attributes, $content );
|
||||
};
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents the pages that are assigned as Cart/Checkout from showing the "template" selector in the page-editor.
|
||||
* We want to avoid this flow and point users towards the Site Editor instead.
|
||||
*
|
||||
* @param string $old_name Old theme name.
|
||||
* @param \WP_Theme $old_theme Instance of the old theme.
|
||||
* @return void
|
||||
*/
|
||||
public function check_should_use_blockified_product_grid_templates( $old_name, $old_theme ) {
|
||||
if ( ! wc_current_theme_is_fse_theme() ) {
|
||||
update_option( Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE, wc_bool_to_string( false ) );
|
||||
public function hide_template_selector_in_cart_checkout_pages() {
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $old_theme->is_block_theme() && wc_current_theme_is_fse_theme() ) {
|
||||
update_option( Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE, wc_bool_to_string( true ) );
|
||||
return;
|
||||
$current_screen = get_current_screen();
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( $current_screen && 'page' === $current_screen->id && ! empty( $_GET['post'] ) && in_array( absint( $_GET['post'] ), array( wc_get_page_id( 'cart' ), wc_get_page_id( 'checkout' ) ), true ) ) {
|
||||
wp_add_inline_style( 'wc-blocks-editor-style', '.edit-post-post-template { display: none; }' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,16 +335,26 @@ class BlockTemplatesController {
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_not_custom = false === array_search(
|
||||
$possible_template_ids = [
|
||||
$theme_slug . '//' . $template_file->slug,
|
||||
array_column( $query_result, 'id' ),
|
||||
true
|
||||
);
|
||||
$theme_slug . '//' . BlockTemplateUtils::DIRECTORY_NAMES['TEMPLATE_PARTS'] . '/' . $template_file->slug,
|
||||
$theme_slug . '//' . BlockTemplateUtils::DIRECTORY_NAMES['DEPRECATED_TEMPLATE_PARTS'] . '/' . $template_file->slug,
|
||||
];
|
||||
|
||||
$is_custom = false;
|
||||
$query_result_template_ids = array_column( $query_result, 'id' );
|
||||
|
||||
foreach ( $possible_template_ids as $template_id ) {
|
||||
if ( in_array( $template_id, $query_result_template_ids, true ) ) {
|
||||
$is_custom = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$fits_slug_query =
|
||||
! isset( $query['slug__in'] ) || in_array( $template_file->slug, $query['slug__in'], true );
|
||||
$fits_area_query =
|
||||
! isset( $query['area'] ) || ( property_exists( $template_file, 'area' ) && $template_file->area === $query['area'] );
|
||||
$should_include = $is_not_custom && $fits_slug_query && $fits_area_query;
|
||||
$should_include = ! $is_custom && $fits_slug_query && $fits_area_query;
|
||||
if ( $should_include ) {
|
||||
$template = BlockTemplateUtils::build_template_result_from_file( $template_file, $template_type );
|
||||
$query_result[] = $template;
|
||||
@@ -469,14 +459,6 @@ class BlockTemplatesController {
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point the template only exists in the Blocks filesystem, if is a taxonomy-product_cat/tag/attribute.html template
|
||||
// let's use the archive-product.html template from Blocks.
|
||||
if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $template_slug ) ) {
|
||||
$template_file = $this->get_template_path_from_woocommerce( ProductCatalogTemplate::SLUG );
|
||||
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug, false );
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point the template only exists in the Blocks filesystem and has not been saved in the DB,
|
||||
// or superseded by the theme.
|
||||
$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug );
|
||||
@@ -500,18 +482,6 @@ class BlockTemplatesController {
|
||||
return array_merge( $templates_from_db, $templates_from_woo );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of a template on the Blocks template folder.
|
||||
*
|
||||
* @param string $template_slug Block template slug e.g. single-product.
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_template_path_from_woocommerce( $template_slug, $template_type = 'wp_template' ) {
|
||||
return BlockTemplateUtils::get_templates_directory( $template_type ) . '/' . $template_slug . '.html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a block template with that name exists in Woo Blocks
|
||||
*
|
||||
|
||||
@@ -19,6 +19,7 @@ use Automattic\WooCommerce\Blocks\Templates\ProductCategoryTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductTagTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductFiltersTemplate;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ProductFiltersOverlayTemplate;
|
||||
|
||||
/**
|
||||
@@ -59,10 +60,14 @@ class BlockTemplatesRegistry {
|
||||
}
|
||||
if ( BlockTemplateUtils::supports_block_templates( 'wp_template_part' ) ) {
|
||||
$template_parts = array(
|
||||
MiniCartTemplate::SLUG => new MiniCartTemplate(),
|
||||
CheckoutHeaderTemplate::SLUG => new CheckoutHeaderTemplate(),
|
||||
ProductFiltersOverlayTemplate::SLUG => new ProductFiltersOverlayTemplate(),
|
||||
MiniCartTemplate::SLUG => new MiniCartTemplate(),
|
||||
CheckoutHeaderTemplate::SLUG => new CheckoutHeaderTemplate(),
|
||||
);
|
||||
|
||||
if ( Features::is_enabled( 'experimental-blocks' ) ) {
|
||||
$template_parts[ ProductFiltersTemplate::SLUG ] = new ProductFiltersTemplate();
|
||||
$template_parts[ ProductFiltersOverlayTemplate::SLUG ] = new ProductFiltersOverlayTemplate();
|
||||
}
|
||||
} else {
|
||||
$template_parts = array();
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ class Checkout extends AbstractBlock {
|
||||
// This prevents the page redirecting when the cart is empty. This is so the editor still loads the page preview.
|
||||
add_filter(
|
||||
'woocommerce_checkout_redirect_empty_cart',
|
||||
function( $return ) {
|
||||
function ( $redirect_empty_cart ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return isset( $_GET['_wp-find-template'] ) ? false : $return;
|
||||
return isset( $_GET['_wp-find-template'] ) ? false : $redirect_empty_cart;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -94,10 +94,17 @@ class Checkout extends AbstractBlock {
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$dependencies = [];
|
||||
|
||||
// Load password strength meter script asynchronously if needed.
|
||||
if ( ! is_user_logged_in() && 'no' === get_option( 'woocommerce_registration_generate_password' ) ) {
|
||||
$dependencies[] = 'zxcvbn-async';
|
||||
}
|
||||
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
|
||||
'dependencies' => [],
|
||||
'dependencies' => $dependencies,
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
@@ -354,6 +361,7 @@ class Checkout extends AbstractBlock {
|
||||
$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ) );
|
||||
$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ) );
|
||||
$this->asset_data_registry->add( 'forcedBillingAddress', 'billing_only' === get_option( 'woocommerce_ship_to_destination' ) );
|
||||
$this->asset_data_registry->add( 'generatePassword', filter_var( get_option( 'woocommerce_registration_generate_password' ), FILTER_VALIDATE_BOOLEAN ) );
|
||||
$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled() );
|
||||
$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled() );
|
||||
$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled() );
|
||||
@@ -377,7 +385,7 @@ class Checkout extends AbstractBlock {
|
||||
$shipping_methods = WC()->shipping()->get_shipping_methods();
|
||||
$formatted_shipping_methods = array_reduce(
|
||||
$shipping_methods,
|
||||
function( $acc, $method ) {
|
||||
function ( $acc, $method ) {
|
||||
if ( in_array( $method->id, LocalPickupUtils::get_local_pickup_method_ids(), true ) ) {
|
||||
return $acc;
|
||||
}
|
||||
@@ -405,7 +413,7 @@ class Checkout extends AbstractBlock {
|
||||
$payment_methods = $this->get_enabled_payment_gateways();
|
||||
$formatted_payment_methods = array_reduce(
|
||||
$payment_methods,
|
||||
function( $acc, $method ) {
|
||||
function ( $acc, $method ) {
|
||||
$acc[] = [
|
||||
'id' => $method->id,
|
||||
'title' => $method->method_title,
|
||||
@@ -427,7 +435,7 @@ class Checkout extends AbstractBlock {
|
||||
$all_plugins = \get_plugins(); // Note that `get_compatible_plugins_for_feature` calls `get_plugins` internally, so this is already in cache.
|
||||
$incompatible_extensions = array_reduce(
|
||||
$declared_extensions['incompatible'],
|
||||
function( $acc, $item ) use ( $all_plugins ) {
|
||||
function ( $acc, $item ) use ( $all_plugins ) {
|
||||
$plugin = $all_plugins[ $item ] ?? null;
|
||||
$plugin_id = $plugin['TextDomain'] ?? dirname( $item, 2 );
|
||||
$plugin_name = $plugin['Name'] ?? $plugin_id;
|
||||
@@ -465,7 +473,7 @@ class Checkout extends AbstractBlock {
|
||||
$payment_gateways = WC()->payment_gateways->payment_gateways();
|
||||
return array_filter(
|
||||
$payment_gateways,
|
||||
function( $payment_gateway ) {
|
||||
function ( $payment_gateway ) {
|
||||
return 'yes' === $payment_gateway->enabled;
|
||||
}
|
||||
);
|
||||
@@ -500,7 +508,7 @@ class Checkout extends AbstractBlock {
|
||||
$payment_methods[ $payment_method_group ] = array_values(
|
||||
array_filter(
|
||||
$saved_payment_methods,
|
||||
function( $saved_payment_method ) use ( $payment_gateways ) {
|
||||
function ( $saved_payment_method ) use ( $payment_gateways ) {
|
||||
return in_array( $saved_payment_method['method']['gateway'], array_keys( $payment_gateways ), true );
|
||||
}
|
||||
)
|
||||
|
||||
@@ -21,12 +21,24 @@ class ComingSoon extends AbstractBlock {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @return null
|
||||
* @internal This prevents the block script being enqueued on all pages. It is only enqueued as needed. Note that
|
||||
* we intentionally do not pass 'script' to register_block_type.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* @param string $content The block content.
|
||||
* @param WP_Block $block The block object.
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
protected function enqueue_assets( array $attributes, $content, $block ) {
|
||||
parent::enqueue_assets( $attributes, $content, $block );
|
||||
|
||||
if ( isset( $attributes['color'] ) ) {
|
||||
wp_add_inline_style(
|
||||
'wc-blocks-style',
|
||||
':root{--woocommerce-coming-soon-color: ' . esc_html( $attributes['color'] ) . '}'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,9 +11,10 @@ use Automattic\WooCommerce\Blocks\Utils\BlockHooksTrait;
|
||||
class CustomerAccount extends AbstractBlock {
|
||||
use BlockHooksTrait;
|
||||
|
||||
const TEXT_ONLY = 'text_only';
|
||||
const ICON_ONLY = 'icon_only';
|
||||
const DISPLAY_ALT = 'alt';
|
||||
const TEXT_ONLY = 'text_only';
|
||||
const ICON_ONLY = 'icon_only';
|
||||
const DISPLAY_ALT = 'alt';
|
||||
const DISPLAY_LINE = 'line';
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
@@ -33,6 +34,7 @@ class CustomerAccount extends AbstractBlock {
|
||||
'anchor' => 'core/navigation',
|
||||
'area' => 'header',
|
||||
'callback' => 'should_unhook_block',
|
||||
'version' => '8.4.0',
|
||||
),
|
||||
);
|
||||
|
||||
@@ -65,6 +67,7 @@ class CustomerAccount extends AbstractBlock {
|
||||
*/
|
||||
public function modify_hooked_block_attributes( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context ) {
|
||||
$parsed_hooked_block['attrs']['displayStyle'] = 'icon_only';
|
||||
$parsed_hooked_block['attrs']['iconStyle'] = 'line';
|
||||
|
||||
/*
|
||||
* The Mini Cart block (which is hooked into the header) has a margin of 0.5em on the left side.
|
||||
@@ -116,16 +119,26 @@ class CustomerAccount extends AbstractBlock {
|
||||
$account_link = get_option( 'woocommerce_myaccount_page_id' ) ? wc_get_account_endpoint_url( 'dashboard' ) : wp_login_url();
|
||||
|
||||
$allowed_svg = array(
|
||||
'svg' => array(
|
||||
'svg' => array(
|
||||
'class' => true,
|
||||
'xmlns' => true,
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'viewbox' => true,
|
||||
),
|
||||
'path' => array(
|
||||
'd' => true,
|
||||
'fill' => true,
|
||||
'path' => array(
|
||||
'd' => true,
|
||||
'fill' => true,
|
||||
'fill-rule' => true,
|
||||
'clip-rule' => true,
|
||||
),
|
||||
'circle' => array(
|
||||
'cx' => true,
|
||||
'cy' => true,
|
||||
'r' => true,
|
||||
'stroke' => true,
|
||||
'stroke-width' => true,
|
||||
'fill' => true,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -153,8 +166,27 @@ class CustomerAccount extends AbstractBlock {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( self::DISPLAY_LINE === $attributes['iconStyle'] ) {
|
||||
return '<svg class="' . $attributes['iconClass'] . '" viewBox="5 5 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle
|
||||
cx="16"
|
||||
cy="10.5"
|
||||
r="3.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M11.5 18.5H20.5C21.8807 18.5 23 19.6193 23 21V25.5H25V21C25 18.5147 22.9853 16.5 20.5 16.5H11.5C9.01472 16.5 7 18.5147 7 21V25.5H9V21C9 19.6193 10.1193 18.5 11.5 18.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>';
|
||||
}
|
||||
|
||||
if ( self::DISPLAY_ALT === $attributes['iconStyle'] ) {
|
||||
return '<svg class="' . $attributes['iconClass'] . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" width="18" height="18">
|
||||
return '<svg class="' . $attributes['iconClass'] . '" xmlns="http://www.w3.org/2000/svg" viewBox="-0.5 -0.5 19 19" width="18" height="18">
|
||||
<path
|
||||
d="M9 0C4.03579 0 0 4.03579 0 9C0 13.9642 4.03579 18 9 18C13.9642 18 18 13.9642 18 9C18 4.03579 13.9642 0 9
|
||||
0ZM9 4.32C10.5347 4.32 11.7664 5.57056 11.7664 7.08638C11.7664 8.62109 10.5158 9.85277 9 9.85277C7.4653
|
||||
@@ -166,7 +198,7 @@ class CustomerAccount extends AbstractBlock {
|
||||
</svg>';
|
||||
}
|
||||
|
||||
return '<svg class="' . $attributes['iconClass'] . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
||||
return '<svg class="' . $attributes['iconClass'] . '" xmlns="http://www.w3.org/2000/svg" viewBox="-0.5 -0.5 17 17" width="16" height="16">
|
||||
<path
|
||||
d="M8.00009 8.34785C10.3096 8.34785 12.1819 6.47909 12.1819 4.17393C12.1819 1.86876 10.3096 0 8.00009 0C5.69055
|
||||
0 3.81824 1.86876 3.81824 4.17393C3.81824 6.47909 5.69055 8.34785 8.00009 8.34785ZM0.333496 15.6522C0.333496
|
||||
|
||||
@@ -65,6 +65,7 @@ class MiniCart extends AbstractBlock {
|
||||
'position' => 'after',
|
||||
'anchor' => 'core/navigation',
|
||||
'area' => 'header',
|
||||
'version' => '8.4.0',
|
||||
),
|
||||
);
|
||||
|
||||
@@ -459,7 +460,7 @@ class MiniCart extends AbstractBlock {
|
||||
|
||||
// It is not necessary to load the Mini-Cart Block on Cart and Checkout page.
|
||||
return '<div class="' . esc_attr( $wrapper_classes ) . '" style="visibility:hidden" aria-hidden="true">
|
||||
<button class="wc-block-mini-cart__button" disabled>' . $button_html . '</button>
|
||||
<button class="wc-block-mini-cart__button" disabled aria-label="' . __( 'Cart', 'woocommerce' ) . '">' . $button_html . '</button>
|
||||
</div>';
|
||||
}
|
||||
|
||||
@@ -487,7 +488,7 @@ class MiniCart extends AbstractBlock {
|
||||
}
|
||||
|
||||
return '<div class="' . esc_attr( $wrapper_classes ) . '" style="' . esc_attr( $wrapper_styles ) . '">
|
||||
<button class="wc-block-mini-cart__button">' . $button_html . '</button>
|
||||
<button class="wc-block-mini-cart__button" aria-label="' . __( 'Cart', 'woocommerce' ) . '">' . $button_html . '</button>
|
||||
<div class="is-loading wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true">
|
||||
<div class="wc-block-mini-cart__drawer wc-block-components-drawer">
|
||||
<div class="wc-block-components-drawer__content">
|
||||
@@ -546,7 +547,7 @@ class MiniCart extends AbstractBlock {
|
||||
'tax_label' => $tax_label,
|
||||
'display_cart_prices_including_tax' => false,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return array(
|
||||
'tax_label' => '',
|
||||
|
||||
@@ -40,13 +40,27 @@ class Status extends AbstractOrderConfirmationBlock {
|
||||
return '';
|
||||
}
|
||||
|
||||
$account_notice = $this->render_account_notice( $order );
|
||||
|
||||
if ( $account_notice ) {
|
||||
$block = sprintf(
|
||||
'<div class="wc-block-order-confirmation-status-notices %1$s">%2$s</div>',
|
||||
esc_attr( trim( $classname ) ),
|
||||
$account_notice
|
||||
) . $block;
|
||||
}
|
||||
|
||||
$additional_content = $this->render_confirmation_notice( $order );
|
||||
|
||||
return $additional_content ? $block . sprintf(
|
||||
'<div class="wc-block-order-confirmation-status-description %1$s">%2$s</div>',
|
||||
esc_attr( trim( $classname ) ),
|
||||
$additional_content
|
||||
) : $block;
|
||||
if ( $additional_content ) {
|
||||
$block = $block . sprintf(
|
||||
'<div class="wc-block-order-confirmation-status-description %1$s">%2$s</div>',
|
||||
esc_attr( trim( $classname ) ),
|
||||
$additional_content
|
||||
);
|
||||
}
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,6 +156,33 @@ class Status extends AbstractOrderConfirmationBlock {
|
||||
return '<p>' . esc_html__( 'Please check your email for the order confirmation.', 'woocommerce' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user associated with the order needs to set a password (new account) show a notice.
|
||||
*
|
||||
* @param \WC_Order|null $order Order object.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_account_notice( $order = null ) {
|
||||
if ( $order && $order->get_customer_id() && 'store-api' === $order->get_created_via() ) {
|
||||
$nag = get_user_option( 'default_password_nag', $order->get_customer_id() );
|
||||
$generate = filter_var( get_option( 'woocommerce_registration_generate_password', false ), FILTER_VALIDATE_BOOLEAN );
|
||||
|
||||
if ( $nag && $generate ) {
|
||||
return wc_print_notice(
|
||||
sprintf(
|
||||
// translators: %s: site name.
|
||||
__( 'Your account with %s has been successfully created. We emailed you a link to set your account password.', 'woocommerce' ),
|
||||
esc_html( wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) )
|
||||
),
|
||||
'notice',
|
||||
array(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* If the order is invalid or there is no permission to view the details, tell the user to check email or log-in.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\ProductCollectionUtils;
|
||||
use WP_Query;
|
||||
use WC_Tax;
|
||||
|
||||
@@ -76,6 +77,9 @@ class ProductCollection extends AbstractBlock {
|
||||
// Extend allowed `collection_params` for the REST API.
|
||||
add_filter( 'rest_product_collection_params', array( $this, 'extend_rest_query_allowed_params' ), 10, 1 );
|
||||
|
||||
// Provide location context into block's context.
|
||||
add_filter( 'render_block_context', array( $this, 'provide_location_context_for_inner_blocks' ), 11, 1 );
|
||||
|
||||
// Interactivity API: Add navigation directives to the product collection block.
|
||||
add_filter( 'render_block_woocommerce/product-collection', array( $this, 'enhance_product_collection_with_interactivity' ), 10, 2 );
|
||||
add_filter( 'render_block_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 );
|
||||
@@ -86,6 +90,71 @@ class ProductCollection extends AbstractBlock {
|
||||
add_filter( 'render_block_data', array( $this, 'disable_enhanced_pagination' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the location context to each inner block of the product collection block.
|
||||
* Hint: Only blocks using the 'query' context will be affected.
|
||||
*
|
||||
* The sourceData structure depends on the context type as follows:
|
||||
* - site: [ ]
|
||||
* - order: [ 'orderId' => int ]
|
||||
* - cart: [ 'productIds' => int[] ]
|
||||
* - archive: [ 'taxonomy' => string, 'termId' => int ]
|
||||
* - product: [ 'productId' => int ]
|
||||
*
|
||||
* @example array(
|
||||
* 'type' => 'product',
|
||||
* 'sourceData' => array( 'productId' => 123 ),
|
||||
* )
|
||||
*
|
||||
* @param array $context The block context.
|
||||
* @return array $context {
|
||||
* The block context including the product collection location context.
|
||||
*
|
||||
* @type array $productCollectionLocation {
|
||||
* @type string $type The context type. Possible values are 'site', 'order', 'cart', 'archive', 'product'.
|
||||
* @type array $sourceData The context source data. Can be the product ID of the viewed product, the order ID of the current order viewed, etc. See structure above for more details.
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
public function provide_location_context_for_inner_blocks( $context ) {
|
||||
// Run only on frontend.
|
||||
// This is needed to avoid SSR renders while in editor. @see https://github.com/woocommerce/woocommerce/issues/45181.
|
||||
if ( is_admin() || \WC()->is_rest_api_request() ) {
|
||||
return $context;
|
||||
}
|
||||
|
||||
// Target only product collection's inner blocks that use the 'query' context.
|
||||
if ( ! isset( $context['query'] ) || ! isset( $context['query']['isProductCollectionBlock'] ) || ! $context['query']['isProductCollectionBlock'] ) {
|
||||
return $context;
|
||||
}
|
||||
|
||||
$is_in_single_product = isset( $context['singleProduct'] ) && ! empty( $context['postId'] );
|
||||
$context['productCollectionLocation'] = $is_in_single_product ? array(
|
||||
'type' => 'product',
|
||||
'sourceData' => array(
|
||||
'productId' => absint( $context['postId'] ),
|
||||
),
|
||||
) : $this->get_location_context();
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global location context.
|
||||
* Serve as a runtime cache for the location context.
|
||||
*
|
||||
* @see ProductCollectionUtils::parse_frontend_location_context()
|
||||
*
|
||||
* @return array The location context.
|
||||
*/
|
||||
private function get_location_context() {
|
||||
static $location_context = null;
|
||||
if ( null === $location_context ) {
|
||||
$location_context = ProductCollectionUtils::parse_frontend_location_context();
|
||||
}
|
||||
return $location_context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhances the Product Collection block with client-side pagination.
|
||||
*
|
||||
@@ -451,10 +520,14 @@ class ProductCollection extends AbstractBlock {
|
||||
}
|
||||
|
||||
$block_context_query = $block->context['query'];
|
||||
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery
|
||||
$block_context_query['tax_query'] = ! empty( $query['tax_query'] ) ? $query['tax_query'] : array();
|
||||
|
||||
$is_exclude_applied_filters = ! ( $block->context['query']['inherit'] ?? false );
|
||||
$inherit = $block->context['query']['inherit'] ?? false;
|
||||
$filterable = $block->context['query']['filterable'] ?? false;
|
||||
|
||||
$is_exclude_applied_filters = ! ( $inherit || $filterable );
|
||||
|
||||
return $this->get_final_frontend_query( $block_context_query, $page, $is_exclude_applied_filters );
|
||||
}
|
||||
|
||||
@@ -39,8 +39,6 @@ class ProductFilters extends AbstractBlock {
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
return <<<HTML
|
||||
<div>Product Filters</div>
|
||||
HTML;
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
|
||||
/**
|
||||
* ProductFiltersOverlay class.
|
||||
*/
|
||||
@@ -30,10 +33,49 @@ class ProductFiltersOverlay extends AbstractBlock {
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
ob_start();
|
||||
printf( '<div>%s</div>', esc_html__( 'Filters Overlay', 'woocommerce' ) );
|
||||
$html = ob_get_clean();
|
||||
return $content;
|
||||
}
|
||||
|
||||
return $html;
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
$template_part_edit_uri = '';
|
||||
|
||||
if (
|
||||
current_user_can( 'edit_theme_options' ) &&
|
||||
( wc_current_theme_is_fse_theme() || current_theme_supports( 'block-template-parts' ) )
|
||||
) {
|
||||
$theme_slug = BlockTemplateUtils::theme_has_template_part( 'product-filters-overlay' ) ? wp_get_theme()->get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;
|
||||
|
||||
$site_editor_uri = add_query_arg(
|
||||
array(
|
||||
'canvas' => 'edit',
|
||||
'path' => '/template-parts/single',
|
||||
),
|
||||
admin_url( 'site-editor.php' )
|
||||
);
|
||||
|
||||
$template_part_edit_uri = esc_url_raw(
|
||||
add_query_arg(
|
||||
array(
|
||||
'postId' => sprintf( '%s//%s', $theme_slug, 'product-filters-overlay' ),
|
||||
'postType' => 'wp_template_part',
|
||||
),
|
||||
$site_editor_uri
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'templatePartProductFiltersOverlayEditUri',
|
||||
$template_part_edit_uri
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductFilters class.
|
||||
*/
|
||||
class ProductFiltersOverlayNavigation extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-filters-overlay-navigation';
|
||||
}
|
||||
@@ -48,6 +48,8 @@ class ProductTemplate extends AbstractBlock {
|
||||
|
||||
$classnames = '';
|
||||
if ( isset( $block->context['displayLayout'] ) && isset( $block->context['query'] ) ) {
|
||||
$classnames = 'is-product-collection-layout-' . $block->context['displayLayout']['type'] . ' ';
|
||||
|
||||
if ( isset( $block->context['displayLayout']['type'] ) && 'flex' === $block->context['displayLayout']['type'] ) {
|
||||
if ( isset( $block->context['displayLayout']['shrinkColumns'] ) && $block->context['displayLayout']['shrinkColumns'] ) {
|
||||
$classnames = "wc-block-product-template__responsive columns-{$block->context['displayLayout']['columns']}";
|
||||
@@ -56,6 +58,7 @@ class ProductTemplate extends AbstractBlock {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
|
||||
$classnames .= ' has-link-color';
|
||||
}
|
||||
|
||||
@@ -31,6 +31,13 @@ final class BlockTypesController {
|
||||
*/
|
||||
protected $asset_data_registry;
|
||||
|
||||
/**
|
||||
* Holds the registered blocks that have WooCommerce blocks as their parents.
|
||||
*
|
||||
* @var array List of registered blocks.
|
||||
*/
|
||||
private $registered_blocks_with_woocommerce_parents;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -67,6 +74,45 @@ final class BlockTypesController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get registered blocks that have WooCommerce blocks as their parents. Adds the value to the
|
||||
* `registered_blocks_with_woocommerce_parents` cache if `init` has been fired.
|
||||
*
|
||||
* @return array Registered blocks with WooCommerce blocks as parents.
|
||||
*/
|
||||
public function get_registered_blocks_with_woocommerce_parent() {
|
||||
// If init has run and the cache is already set, return it.
|
||||
if ( did_action( 'init' ) && ! empty( $this->registered_blocks_with_woocommerce_parents ) ) {
|
||||
return $this->registered_blocks_with_woocommerce_parents;
|
||||
}
|
||||
|
||||
$registered_blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered();
|
||||
|
||||
if ( ! is_array( $registered_blocks ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$this->registered_blocks_with_woocommerce_parents = array_filter(
|
||||
$registered_blocks,
|
||||
function ( $block ) {
|
||||
if ( empty( $block->parent ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( ! is_array( $block->parent ) ) {
|
||||
$block->parent = array( $block->parent );
|
||||
}
|
||||
$woocommerce_blocks = array_filter(
|
||||
$block->parent,
|
||||
function ( $parent_block_name ) {
|
||||
return 'woocommerce' === strtok( $parent_block_name, '/' );
|
||||
}
|
||||
);
|
||||
return ! empty( $woocommerce_blocks );
|
||||
}
|
||||
);
|
||||
return $this->registered_blocks_with_woocommerce_parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current post has a block with a specific attribute value.
|
||||
*
|
||||
@@ -132,14 +178,15 @@ final class BlockTypesController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data- attributes to blocks when rendered if the block is under the woocommerce/ namespace.
|
||||
* Check if a block should have data attributes appended on render. If it's in an allowed namespace, or the block
|
||||
* has explicitly been added to the allowed block list, or if one of the block's parents is in the WooCommerce
|
||||
* namespace it can have data attributes.
|
||||
*
|
||||
* @param string $content Block content.
|
||||
* @param array $block Parsed block data.
|
||||
* @return string
|
||||
* @param string $block_name Name of the block to check.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function add_data_attributes( $content, $block ) {
|
||||
$block_name = $block['blockName'];
|
||||
public function block_should_have_data_attributes( $block_name ) {
|
||||
$block_namespace = strtok( $block_name ?? '', '/' );
|
||||
|
||||
/**
|
||||
@@ -164,18 +211,44 @@ final class BlockTypesController {
|
||||
*/
|
||||
$allowed_blocks = (array) apply_filters( '__experimental_woocommerce_blocks_add_data_attributes_to_block', array() );
|
||||
|
||||
if ( ! in_array( $block_namespace, $allowed_namespaces, true ) && ! in_array( $block_name, $allowed_blocks, true ) ) {
|
||||
$blocks_with_woo_parents = $this->get_registered_blocks_with_woocommerce_parent();
|
||||
$block_has_woo_parent = in_array( $block_name, array_keys( $blocks_with_woo_parents ), true );
|
||||
$in_allowed_namespace_list = in_array( $block_namespace, $allowed_namespaces, true );
|
||||
$in_allowed_block_list = in_array( $block_name, $allowed_blocks, true );
|
||||
|
||||
return $block_has_woo_parent || $in_allowed_block_list || $in_allowed_namespace_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data- attributes to blocks when rendered if the block is under the woocommerce/ namespace.
|
||||
*
|
||||
* @param string $content Block content.
|
||||
* @param array $block Parsed block data.
|
||||
* @return string
|
||||
*/
|
||||
public function add_data_attributes( $content, $block ) {
|
||||
|
||||
$content = trim( $content );
|
||||
|
||||
if ( ! $this->block_should_have_data_attributes( $block['blockName'] ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$attributes = (array) $block['attrs'];
|
||||
$exclude_attributes = array( 'className', 'align' );
|
||||
$escaped_data_attributes = array(
|
||||
'data-block-name="' . esc_attr( $block['blockName'] ) . '"',
|
||||
);
|
||||
$attributes = (array) $block['attrs'];
|
||||
$exclude_attributes = array( 'className', 'align' );
|
||||
|
||||
foreach ( $attributes as $key => $value ) {
|
||||
if ( in_array( $key, $exclude_attributes, true ) ) {
|
||||
$processor = new \WP_HTML_Tag_Processor( $content );
|
||||
|
||||
if (
|
||||
false === $processor->next_token() ||
|
||||
'DIV' !== $processor->get_token_name() ||
|
||||
$processor->is_tag_closer()
|
||||
) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
foreach ( $attributes as $key => $value ) {
|
||||
if ( ! is_string( $key ) || in_array( $key, $exclude_attributes, true ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( is_bool( $value ) ) {
|
||||
@@ -184,10 +257,16 @@ final class BlockTypesController {
|
||||
if ( ! is_scalar( $value ) ) {
|
||||
$value = wp_json_encode( $value );
|
||||
}
|
||||
$escaped_data_attributes[] = 'data-' . esc_attr( strtolower( preg_replace( '/(?<!\ )[A-Z]/', '-$0', $key ) ) ) . '="' . esc_attr( $value ) . '"';
|
||||
|
||||
// For output consistency, we convert camelCase to kebab-case and output in lowercase.
|
||||
$key = strtolower( preg_replace( '/(?<!^|\ )[A-Z]/', '-$0', $key ) );
|
||||
|
||||
$processor->set_attribute( "data-{$key}", $value );
|
||||
}
|
||||
|
||||
return preg_replace( '/^<div /', '<div ' . implode( ' ', $escaped_data_attributes ) . ' ', trim( $content ) );
|
||||
// Set this last to prevent user-input from overriding it.
|
||||
$processor->set_attribute( 'data-block-name', $block['blockName'] );
|
||||
return $processor->get_updated_html();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -327,6 +406,7 @@ final class BlockTypesController {
|
||||
$block_types[] = 'ProductFilter';
|
||||
$block_types[] = 'ProductFilters';
|
||||
$block_types[] = 'ProductFiltersOverlay';
|
||||
$block_types[] = 'ProductFiltersOverlayNavigation';
|
||||
$block_types[] = 'ProductFilterStockStatus';
|
||||
$block_types[] = 'ProductFilterPrice';
|
||||
$block_types[] = 'ProductFilterAttribute';
|
||||
|
||||
@@ -41,6 +41,8 @@ use Automattic\WooCommerce\Blocks\Shipping\ShippingController;
|
||||
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplateCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\OnboardingTasks\TasksController;
|
||||
use Automattic\WooCommerce\Blocks\TemplateOptions;
|
||||
|
||||
|
||||
/**
|
||||
* Takes care of bootstrapping the plugin.
|
||||
@@ -122,9 +124,23 @@ class Bootstrap {
|
||||
0
|
||||
);
|
||||
|
||||
$is_rest = wc()->is_rest_api_request();
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$is_store_api_request = $is_rest && ! empty( $_SERVER['REQUEST_URI'] ) && ( false !== strpos( $_SERVER['REQUEST_URI'], trailingslashit( rest_get_url_prefix() ) . 'wc/store/' ) );
|
||||
// We need to initialize BlockTemplatesController and BlockTemplatesRegistry at the end of `after_setup_theme`
|
||||
// so themes had the opportunity to declare support for template parts.
|
||||
add_action(
|
||||
'after_setup_theme',
|
||||
function () {
|
||||
$is_store_api_request = wc()->is_store_api_request();
|
||||
|
||||
if ( ! $is_store_api_request && ( wc_current_theme_is_fse_theme() || current_theme_supports( 'block-template-parts' ) ) ) {
|
||||
$this->container->get( BlockTemplatesRegistry::class )->init();
|
||||
$this->container->get( BlockTemplatesController::class )->init();
|
||||
}
|
||||
},
|
||||
999
|
||||
);
|
||||
|
||||
$is_rest = wc()->is_rest_api_request();
|
||||
$is_store_api_request = wc()->is_store_api_request();
|
||||
|
||||
// Load and init assets.
|
||||
$this->container->get( StoreApi::class )->init();
|
||||
@@ -152,13 +168,12 @@ class Bootstrap {
|
||||
$this->container->get( AIPatterns::class );
|
||||
$this->container->get( BlockPatterns::class );
|
||||
$this->container->get( BlockTypesController::class );
|
||||
$this->container->get( BlockTemplatesRegistry::class )->init();
|
||||
$this->container->get( BlockTemplatesController::class )->init();
|
||||
$this->container->get( ClassicTemplatesCompatibility::class );
|
||||
$this->container->get( ArchiveProductTemplatesCompatibility::class )->init();
|
||||
$this->container->get( SingleProductTemplateCompatibility::class )->init();
|
||||
$this->container->get( Notices::class )->init();
|
||||
$this->container->get( PTKPatternsStore::class );
|
||||
$this->container->get( TemplateOptions::class )->init();
|
||||
}
|
||||
|
||||
$this->container->get( QueryFilters::class )->init();
|
||||
@@ -254,18 +269,6 @@ class Bootstrap {
|
||||
return new BlockTypesController( $asset_api, $asset_data_registry );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BlockTemplatesRegistry::class,
|
||||
function () {
|
||||
return new BlockTemplatesRegistry();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BlockTemplatesController::class,
|
||||
function () {
|
||||
return new BlockTemplatesController();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
ClassicTemplatesCompatibility::class,
|
||||
function ( Container $container ) {
|
||||
@@ -350,6 +353,12 @@ class Bootstrap {
|
||||
return new StoreApi();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
TemplateOptions::class,
|
||||
function () {
|
||||
return new TemplateOptions();
|
||||
}
|
||||
);
|
||||
// Maintains backwards compatibility with previous Store API namespace.
|
||||
$this->container->register(
|
||||
'Automattic\WooCommerce\Blocks\StoreApi\Formatters',
|
||||
@@ -427,6 +436,18 @@ class Bootstrap {
|
||||
return new QueryFilters();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BlockTemplatesRegistry::class,
|
||||
function () {
|
||||
return new BlockTemplatesRegistry();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BlockTemplatesController::class,
|
||||
function () {
|
||||
return new BlockTemplatesController();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,13 +13,6 @@ use WP_Error;
|
||||
*/
|
||||
class CheckoutFields {
|
||||
|
||||
/**
|
||||
* Core checkout fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $core_fields;
|
||||
|
||||
/**
|
||||
* Additional checkout fields.
|
||||
*
|
||||
@@ -91,144 +84,10 @@ class CheckoutFields {
|
||||
*/
|
||||
public function __construct( AssetDataRegistry $asset_data_registry ) {
|
||||
$this->asset_data_registry = $asset_data_registry;
|
||||
$this->core_fields = [
|
||||
'email' => [
|
||||
'label' => __( 'Email address', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Email address (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'email',
|
||||
'autocapitalize' => 'none',
|
||||
'index' => 0,
|
||||
],
|
||||
'country' => [
|
||||
'label' => __( 'Country/Region', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Country/Region (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'country',
|
||||
'index' => 1,
|
||||
],
|
||||
'first_name' => [
|
||||
'label' => __( 'First name', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'First name (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'given-name',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 10,
|
||||
],
|
||||
'last_name' => [
|
||||
'label' => __( 'Last name', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Last name (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'family-name',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 20,
|
||||
],
|
||||
'company' => [
|
||||
'label' => __( 'Company', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Company (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => false,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'organization',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 30,
|
||||
],
|
||||
'address_1' => [
|
||||
'label' => __( 'Address', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Address (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'address-line1',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 40,
|
||||
],
|
||||
'address_2' => [
|
||||
'label' => __( 'Apartment, suite, etc.', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Apartment, suite, etc. (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => false,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'address-line2',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 50,
|
||||
],
|
||||
'city' => [
|
||||
'label' => __( 'City', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'City (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'address-level2',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 70,
|
||||
],
|
||||
'state' => [
|
||||
'label' => __( 'State/County', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'State/County (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'address-level1',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 80,
|
||||
],
|
||||
'postcode' => [
|
||||
'label' => __( 'Postal code', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Postal code (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'postal-code',
|
||||
'autocapitalize' => 'characters',
|
||||
'index' => 90,
|
||||
],
|
||||
'phone' => [
|
||||
'label' => __( 'Phone', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Phone (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => false,
|
||||
'hidden' => false,
|
||||
'type' => 'tel',
|
||||
'autocomplete' => 'tel',
|
||||
'autocapitalize' => 'characters',
|
||||
'index' => 100,
|
||||
],
|
||||
];
|
||||
|
||||
$this->fields_locations = [
|
||||
// omit email from shipping and billing fields.
|
||||
'address' => array_merge( \array_diff_key( array_keys( $this->core_fields ), array( 'email' ) ) ),
|
||||
'address' => array_merge( \array_diff_key( $this->get_core_fields_keys(), array( 'email' ) ) ),
|
||||
'contact' => array( 'email' ),
|
||||
'order' => [],
|
||||
];
|
||||
@@ -527,17 +386,8 @@ class CheckoutFields {
|
||||
|
||||
$field_data['options'] = $cleaned_options;
|
||||
|
||||
// If the field is not required, inject an empty option at the start.
|
||||
if ( isset( $field_data['required'] ) && false === $field_data['required'] && ! in_array( '', $added_values, true ) ) {
|
||||
$field_data['options'] = array_merge(
|
||||
[
|
||||
[
|
||||
'value' => '',
|
||||
'label' => '',
|
||||
],
|
||||
],
|
||||
$field_data['options']
|
||||
);
|
||||
if ( isset( $field_data['placeholder'] ) ) {
|
||||
$field_data['placeholder'] = sanitize_text_field( $field_data['placeholder'] );
|
||||
}
|
||||
|
||||
return $field_data;
|
||||
@@ -620,13 +470,167 @@ class CheckoutFields {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keys of all core fields.
|
||||
*
|
||||
* @return array An array of field keys.
|
||||
*/
|
||||
public function get_core_fields_keys() {
|
||||
return [
|
||||
'email',
|
||||
'country',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'company',
|
||||
'address_1',
|
||||
'address_2',
|
||||
'city',
|
||||
'state',
|
||||
'postcode',
|
||||
'phone',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all core fields.
|
||||
*
|
||||
* @return array An array of fields.
|
||||
*/
|
||||
public function get_core_fields() {
|
||||
return $this->core_fields;
|
||||
return [
|
||||
'email' => [
|
||||
'label' => __( 'Email address', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Email address (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'email',
|
||||
'autocapitalize' => 'none',
|
||||
'index' => 0,
|
||||
],
|
||||
'country' => [
|
||||
'label' => __( 'Country/Region', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Country/Region (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'country',
|
||||
'index' => 1,
|
||||
],
|
||||
'first_name' => [
|
||||
'label' => __( 'First name', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'First name (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'given-name',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 10,
|
||||
],
|
||||
'last_name' => [
|
||||
'label' => __( 'Last name', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Last name (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'family-name',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 20,
|
||||
],
|
||||
'company' => [
|
||||
'label' => __( 'Company', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Company (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => false,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'organization',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 30,
|
||||
],
|
||||
'address_1' => [
|
||||
'label' => __( 'Address', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Address (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'address-line1',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 40,
|
||||
],
|
||||
'address_2' => [
|
||||
'label' => __( 'Apartment, suite, etc.', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Apartment, suite, etc. (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => false,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'address-line2',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 50,
|
||||
],
|
||||
'city' => [
|
||||
'label' => __( 'City', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'City (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'address-level2',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 70,
|
||||
],
|
||||
'state' => [
|
||||
'label' => __( 'State/County', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'State/County (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'address-level1',
|
||||
'autocapitalize' => 'sentences',
|
||||
'index' => 80,
|
||||
],
|
||||
'postcode' => [
|
||||
'label' => __( 'Postal code', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Postal code (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => true,
|
||||
'hidden' => false,
|
||||
'autocomplete' => 'postal-code',
|
||||
'autocapitalize' => 'characters',
|
||||
'index' => 90,
|
||||
],
|
||||
'phone' => [
|
||||
'label' => __( 'Phone', 'woocommerce' ),
|
||||
'optionalLabel' => __(
|
||||
'Phone (optional)',
|
||||
'woocommerce'
|
||||
),
|
||||
'required' => false,
|
||||
'hidden' => false,
|
||||
'type' => 'tel',
|
||||
'autocomplete' => 'tel',
|
||||
'autocapitalize' => 'characters',
|
||||
'index' => 100,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,7 @@ class Notices {
|
||||
public function init() {
|
||||
add_action(
|
||||
'after_setup_theme',
|
||||
function() {
|
||||
function () {
|
||||
/**
|
||||
* Allow classic theme developers to opt-in to using block notices.
|
||||
*
|
||||
@@ -104,13 +104,14 @@ class Notices {
|
||||
* @return string
|
||||
*/
|
||||
public function get_notices_template( $template, $template_name, $args, $template_path, $default_path ) {
|
||||
$directory = get_stylesheet_directory();
|
||||
$file = $directory . '/woocommerce/' . $template_name;
|
||||
if ( file_exists( $file ) ) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
if ( in_array( $template_name, $this->notice_templates, true ) ) {
|
||||
$directory = get_stylesheet_directory();
|
||||
$file = $directory . '/woocommerce/' . $template_name;
|
||||
|
||||
if ( file_exists( $file ) ) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$template = $this->package->get_path( 'templates/block-' . $template_name );
|
||||
wp_enqueue_style( 'wc-blocks-style' );
|
||||
}
|
||||
|
||||
@@ -11,10 +11,9 @@ use WP_Upgrader;
|
||||
class PTKPatternsStore {
|
||||
const TRANSIENT_NAME = 'ptk_patterns';
|
||||
|
||||
// Some patterns need to be excluded because they have dependencies which
|
||||
// are not installed by default (like Jetpack). Otherwise, the user
|
||||
// would see an error when trying to insert them in the editor.
|
||||
const EXCLUDED_PATTERNS = array( '13923', '14781', '14779', '13666', '13664', '13660', '13588', '14922', '14880', '13596', '13967', '13958', '15050', '15027' );
|
||||
const CATEGORY_MAPPING = array(
|
||||
'testimonials' => 'reviews',
|
||||
);
|
||||
|
||||
/**
|
||||
* PatternsToolkit instance.
|
||||
@@ -92,11 +91,13 @@ class PTKPatternsStore {
|
||||
* @return void
|
||||
*/
|
||||
private function schedule_action_if_not_pending( $action ) {
|
||||
if ( as_has_scheduled_action( $action ) ) {
|
||||
$last_request = get_transient( 'last_fetch_patterns_request' );
|
||||
if ( as_has_scheduled_action( $action ) || false !== $last_request ) {
|
||||
return;
|
||||
}
|
||||
|
||||
as_schedule_single_action( time(), $action );
|
||||
set_transient( 'last_fetch_patterns_request', time(), HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,26 +118,31 @@ class PTKPatternsStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter patterns to exclude those with the given IDs.
|
||||
* Filter the patterns that have external dependencies.
|
||||
*
|
||||
* @param array $patterns The patterns to filter.
|
||||
* @param array $pattern_ids The pattern IDs to exclude.
|
||||
* @return array
|
||||
*/
|
||||
private function filter_patterns( array $patterns, array $pattern_ids ) {
|
||||
return array_filter(
|
||||
$patterns,
|
||||
function ( $pattern ) use ( $pattern_ids ) {
|
||||
if ( ! isset( $pattern['ID'] ) ) {
|
||||
private function filter_patterns( array $patterns ) {
|
||||
return array_values(
|
||||
array_filter(
|
||||
$patterns,
|
||||
function ( $pattern ) {
|
||||
if ( ! isset( $pattern['ID'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( isset( $pattern['post_type'] ) && 'wp_block' !== $pattern['post_type'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->has_external_dependencies( $pattern ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( isset( $pattern['post_type'] ) && 'wp_block' !== $pattern['post_type'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! in_array( (string) $pattern['ID'], $pattern_ids, true );
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -181,22 +187,35 @@ class PTKPatternsStore {
|
||||
|
||||
$patterns = $this->ptk_client->fetch_patterns(
|
||||
array(
|
||||
'categories' => array( 'intro', 'about', 'services', 'testimonials' ),
|
||||
// This is the site where the patterns are stored. Despite the 'wpcomstaging.com' domain suggesting a staging environment, this URL points to the production environment where stable versions of the patterns are maintained.
|
||||
'site' => 'wooblockpatterns.wpcomstaging.com',
|
||||
'categories' => array(
|
||||
'_woo_intro',
|
||||
'_woo_featured_selling',
|
||||
'_woo_about',
|
||||
'_woo_reviews',
|
||||
'_woo_social_media',
|
||||
'_woo_woocommerce',
|
||||
'_dotcom_imported_intro',
|
||||
'_dotcom_imported_about',
|
||||
'_dotcom_imported_services',
|
||||
'_dotcom_imported_reviews',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $patterns ) ) {
|
||||
wc_get_logger()->warning(
|
||||
sprintf(
|
||||
// translators: %s is a generated error message.
|
||||
__( 'Failed to get the patterns from the PTK: "%s"', 'woocommerce' ),
|
||||
__( 'Failed to get WooCommerce patterns from the PTK: "%s"', 'woocommerce' ),
|
||||
$patterns->get_error_message()
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$patterns = $this->filter_patterns( $patterns, self::EXCLUDED_PATTERNS );
|
||||
$patterns = $this->filter_patterns( $patterns );
|
||||
$patterns = $this->map_categories( $patterns );
|
||||
|
||||
set_transient( self::TRANSIENT_NAME, $patterns );
|
||||
}
|
||||
@@ -209,4 +228,51 @@ class PTKPatternsStore {
|
||||
private function allowed_tracking_is_enabled(): bool {
|
||||
return 'yes' === get_option( 'woocommerce_allow_tracking' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the categories of the patterns to match the ones used in the CYS flow
|
||||
*
|
||||
* @param array $patterns The patterns to map categories for.
|
||||
* @return array The patterns with the categories mapped.
|
||||
*/
|
||||
private function map_categories( array $patterns ) {
|
||||
return array_map(
|
||||
function ( $pattern ) {
|
||||
if ( isset( $pattern['categories'] ) ) {
|
||||
foreach ( $pattern['categories'] as $key => $category ) {
|
||||
if ( isset( $category['slug'] ) && isset( self::CATEGORY_MAPPING[ $key ] ) ) {
|
||||
$new_category = self::CATEGORY_MAPPING[ $key ];
|
||||
unset( $pattern['categories'][ $key ] );
|
||||
$pattern['categories'][ $new_category ]['slug'] = $new_category;
|
||||
$pattern['categories'][ $new_category ]['title'] = ucfirst( $new_category );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $pattern;
|
||||
},
|
||||
$patterns
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the pattern has external dependencies.
|
||||
*
|
||||
* @param array $pattern The pattern to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function has_external_dependencies( $pattern ) {
|
||||
if ( ! isset( $pattern['dependencies'] ) || ! is_array( $pattern['dependencies'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $pattern['dependencies'] as $dependency ) {
|
||||
if ( 'woocommerce' !== $dependency ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,30 @@ class PatternRegistry {
|
||||
const SLUG_REGEX = '/^[A-z0-9\/_-]+$/';
|
||||
const COMMA_SEPARATED_REGEX = '/[\s,]+/';
|
||||
|
||||
|
||||
/**
|
||||
* Associates pattern slugs with their localized labels for categorization.
|
||||
* Each key represents a unique pattern slug, while the value is the localized label.
|
||||
*
|
||||
* @var array $category_labels
|
||||
*/
|
||||
private $category_labels;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->category_labels = [
|
||||
'woo-commerce' => __( 'WooCommerce', 'woocommerce' ),
|
||||
'intro' => __( 'Intro', 'woocommerce' ),
|
||||
'featured-selling' => __( 'Featured Selling', 'woocommerce' ),
|
||||
'about' => __( 'About', 'woocommerce' ),
|
||||
'social-media' => __( 'Social Media', 'woocommerce' ),
|
||||
'services' => __( 'Services', 'woocommerce' ),
|
||||
'reviews' => __( 'Reviews', 'woocommerce' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a block pattern.
|
||||
*
|
||||
@@ -166,10 +190,13 @@ class PatternRegistry {
|
||||
|
||||
$pattern_data['categories'][ $key ] = $category_slug;
|
||||
|
||||
$label = isset( $this->category_labels[ $category_slug ] ) ? $this->category_labels[ $category_slug ] : self::kebab_to_capital_case( $category_slug );
|
||||
|
||||
register_block_pattern_category(
|
||||
$category_slug,
|
||||
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
|
||||
array( 'label' => __( $category, 'woocommerce' ) )
|
||||
array(
|
||||
'label' => $label,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -194,4 +221,18 @@ class PatternRegistry {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a kebab-case string to capital case.
|
||||
*
|
||||
* @param string $value The kebab-case string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function kebab_to_capital_case( $value ) {
|
||||
$string = str_replace( '-', ' ', $value );
|
||||
$string = ucwords( $string );
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Options;
|
||||
|
||||
/**
|
||||
* TemplateOptions class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TemplateOptions {
|
||||
|
||||
/**
|
||||
* Initialization method.
|
||||
*/
|
||||
public function init() {
|
||||
add_action( 'after_switch_theme', array( $this, 'check_should_use_blockified_product_grid_templates' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the old and current themes and determines if the "wc_blocks_use_blockified_product_grid_block_as_template"
|
||||
* option need to be updated accordingly.
|
||||
*
|
||||
* @param string $old_name Old theme name.
|
||||
* @param \WP_Theme $old_theme Instance of the old theme.
|
||||
* @return void
|
||||
*/
|
||||
public function check_should_use_blockified_product_grid_templates( $old_name, $old_theme ) {
|
||||
if ( ! $old_theme->is_block_theme() && wc_current_theme_is_fse_theme() ) {
|
||||
$option_name = Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE;
|
||||
// We previously stored "yes" or "no" values. This will convert them to true or false.
|
||||
$option_value = wc_string_to_bool( get_option( $option_name ) );
|
||||
|
||||
// We don't need to do anything if the option is already set to true.
|
||||
if ( ! $option_value ) {
|
||||
update_option( $option_name, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,14 +20,12 @@ class ProductFiltersOverlayTemplate extends AbstractTemplatePart {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $template_area = 'product-filters-overlay';
|
||||
public $template_area = 'uncategorized';
|
||||
|
||||
/**
|
||||
* Initialization method.
|
||||
*/
|
||||
public function init() {
|
||||
add_filter( 'default_wp_template_part_areas', array( $this, 'register_product_filters_overlay_template_part_area' ), 10, 1 );
|
||||
}
|
||||
public function init() {}
|
||||
|
||||
/**
|
||||
* Returns the title of the template.
|
||||
@@ -46,21 +44,4 @@ class ProductFiltersOverlayTemplate extends AbstractTemplatePart {
|
||||
public function get_template_description() {
|
||||
return __( 'Template used to display the Product Filters Overlay.', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Filters Overlay to the default template part areas.
|
||||
*
|
||||
* @param array $default_area_definitions An array of supported area objects.
|
||||
* @return array The supported template part areas including the Filters Overlay one.
|
||||
*/
|
||||
public function register_product_filters_overlay_template_part_area( $default_area_definitions ) {
|
||||
$product_filters_overlay_template_part_area = array(
|
||||
'area' => 'product-filters-overlay',
|
||||
'label' => $this->get_template_title(),
|
||||
'description' => $this->get_template_description(),
|
||||
'icon' => 'filter',
|
||||
'area_tag' => 'product-filters-overlay',
|
||||
);
|
||||
return array_merge( $default_area_definitions, array( $product_filters_overlay_template_part_area ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Automattic\WooCommerce\Blocks\Templates;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
|
||||
/**
|
||||
* ProductFiltersTemplate class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ProductFiltersTemplate extends AbstractTemplatePart {
|
||||
|
||||
/**
|
||||
* The slug of the template.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SLUG = 'product-filters';
|
||||
|
||||
/**
|
||||
* The template part area where the template part belongs.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $template_area = 'uncategorized';
|
||||
|
||||
/**
|
||||
* Initialization method.
|
||||
*/
|
||||
public function init() {
|
||||
add_filter( 'get_block_type_variations', array( $this, 'register_block_type_variation' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title of the template.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_template_title() {
|
||||
return _x( 'Product Filters (Experimental)', 'Template name', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the template.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_template_description() {
|
||||
return __(
|
||||
'This is the template part for the product filters displayed on different pages across your store.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add variation for this template part to make it available in the block inserter.
|
||||
*
|
||||
* @param array $variations Array of registered variations for a block type.
|
||||
* @param WP_Block_Type $block_type The full block type object.
|
||||
*/
|
||||
public function register_block_type_variation( $variations, $block_type ) {
|
||||
if ( 'core/template-part' !== $block_type->name ) {
|
||||
return $variations;
|
||||
}
|
||||
|
||||
// If template part is modified, Core will pick it up and register a variation
|
||||
// for it. Check if the variation already exists before adding it.
|
||||
foreach ( $variations as $variation ) {
|
||||
if ( ! empty( $variation['attributes']['slug'] ) && 'product-filters' === $variation['attributes']['slug'] ) {
|
||||
return $variations;
|
||||
}
|
||||
}
|
||||
|
||||
$theme = 'woocommerce/woocommerce';
|
||||
// Check if current theme overrides this template part.
|
||||
if ( BlockTemplateUtils::theme_has_template_part( 'product-filters' ) ) {
|
||||
$theme = wp_get_theme()->get( 'TextDomain' );
|
||||
}
|
||||
|
||||
$variations[] = array(
|
||||
'name' => 'file_' . self::SLUG,
|
||||
'title' => $this->get_template_title(),
|
||||
'description' => true,
|
||||
'attributes' => array(
|
||||
'slug' => self::SLUG,
|
||||
'theme' => $theme,
|
||||
'area' => $this->template_area,
|
||||
),
|
||||
'scope' => array( 'inserter' ),
|
||||
'icon' => 'layout',
|
||||
);
|
||||
return $variations;
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,6 @@ class ProductSearchResultsTemplate extends AbstractTemplate {
|
||||
*/
|
||||
const SLUG = 'product-search-results';
|
||||
|
||||
/**
|
||||
* The template used as a fallback if that one is customized.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $fallback_template = ProductCatalogTemplate::SLUG;
|
||||
|
||||
/**
|
||||
* Initialization method.
|
||||
*/
|
||||
|
||||
@@ -18,31 +18,34 @@ trait BlockHooksTrait {
|
||||
* @return array An array of block slugs hooked into a given context.
|
||||
*/
|
||||
public function register_hooked_block( $hooked_blocks, $position, $anchor_block, $context ) {
|
||||
|
||||
/**
|
||||
* If the block has no hook placements, return early.
|
||||
*/
|
||||
// If the block has no hook placements, return early.
|
||||
if ( ! isset( $this->hooked_block_placements ) || empty( $this->hooked_block_placements ) ) {
|
||||
return $hooked_blocks;
|
||||
}
|
||||
|
||||
// Cache for active theme.
|
||||
static $active_theme_name = null;
|
||||
if ( is_null( $active_theme_name ) ) {
|
||||
$active_theme_name = wp_get_theme()->get( 'Name' );
|
||||
// Cache the block hooks version.
|
||||
static $block_hooks_version = null;
|
||||
if ( defined( 'WP_RUN_CORE_TESTS' ) || is_null( $block_hooks_version ) ) {
|
||||
$block_hooks_version = get_option( 'woocommerce_hooked_blocks_version' );
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of theme slugs to execute this with. This is a temporary
|
||||
* measure until improvements to the Block Hooks API allow for exposing
|
||||
* to all block themes.
|
||||
*
|
||||
* @since 8.4.0
|
||||
*/
|
||||
$theme_include_list = apply_filters( 'woocommerce_hooked_blocks_theme_include_list', array( 'Twenty Twenty-Four', 'Twenty Twenty-Three', 'Twenty Twenty-Two', 'Tsubaki', 'Zaino', 'Thriving Artist', 'Amulet', 'Tazza' ) );
|
||||
// If block hooks are disabled or the version is not set, return early.
|
||||
if ( 'no' === $block_hooks_version || false === $block_hooks_version ) {
|
||||
return $hooked_blocks;
|
||||
}
|
||||
|
||||
if ( $context && in_array( $active_theme_name, $theme_include_list, true ) ) {
|
||||
foreach ( $this->hooked_block_placements as $placement ) {
|
||||
// Valid placements are those that have no version specified,
|
||||
// or have a version that is less than or equal to version specified in the woocommerce_hooked_blocks_version option.
|
||||
$valid_placements = array_filter(
|
||||
$this->hooked_block_placements,
|
||||
function ( $placement ) use ( $block_hooks_version ) {
|
||||
$placement_version = isset( $placement['version'] ) ? $placement['version'] : null;
|
||||
return is_null( $placement_version ) || ! is_null( $placement_version ) && version_compare( $block_hooks_version, $placement_version, '>=' );
|
||||
}
|
||||
);
|
||||
|
||||
if ( $context && ! empty( $valid_placements ) ) {
|
||||
foreach ( $valid_placements as $placement ) {
|
||||
|
||||
if ( $placement['position'] === $position && $placement['anchor'] === $anchor_block ) {
|
||||
// If an area has been specified for this placement.
|
||||
|
||||
@@ -316,9 +316,13 @@ class BlockTemplateUtils {
|
||||
$wp_template_part_filenames = array(
|
||||
'checkout-header.html',
|
||||
'mini-cart.html',
|
||||
'product-filters-overlay.html',
|
||||
);
|
||||
|
||||
if ( Features::is_enabled( 'experimental-blocks' ) ) {
|
||||
$wp_template_part_filenames[] = 'product-filters.html';
|
||||
$wp_template_part_filenames[] = 'product-filters-overlay.html';
|
||||
}
|
||||
|
||||
/*
|
||||
* This may return the blockified directory for wp_templates.
|
||||
* At the moment every template file has a corresponding blockified file.
|
||||
|
||||
@@ -8,6 +8,7 @@ use WP_Query;
|
||||
* {@internal This class and its methods are not intended for public use.}
|
||||
*/
|
||||
class ProductCollectionUtils {
|
||||
|
||||
/**
|
||||
* Prepare and execute a query for the Product Collection block.
|
||||
* This method is used by the Product Collection block and the No Results block.
|
||||
@@ -78,6 +79,87 @@ class ProductCollectionUtils {
|
||||
return self::remove_empty_array_recursive( $queries );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse WP Query's front-end context for the Product Collection block.
|
||||
*
|
||||
* The sourceData structure depends on the context type as follows:
|
||||
* - site: [ ]
|
||||
* - order: [ 'orderId' => int ]
|
||||
* - cart: [ 'productIds' => int[] ]
|
||||
* - archive: [ 'taxonomy' => string, 'termId' => int ]
|
||||
* - product: [ 'productId' => int ]
|
||||
*
|
||||
* @return array $context {
|
||||
* @type string $type The context type. Possible values are 'site', 'order', 'cart', 'archive', 'product'.
|
||||
* @type array $sourceData The context source data. Can be the product ID of the viewed product, the order ID of the current order, etc.
|
||||
* }
|
||||
*/
|
||||
public static function parse_frontend_location_context() {
|
||||
global $wp_query;
|
||||
|
||||
// Default context.
|
||||
// Hint: The Shop page uses the default context.
|
||||
$type = 'site';
|
||||
$source_data = array();
|
||||
|
||||
if ( ! ( $wp_query instanceof WP_Query ) ) {
|
||||
|
||||
return array(
|
||||
'type' => $type,
|
||||
'sourceData' => $source_data,
|
||||
);
|
||||
}
|
||||
|
||||
// As more areas are blockified, expected future contexts include:
|
||||
// - is_checkout_pay_page().
|
||||
// - is_view_order_page().
|
||||
if ( is_order_received_page() ) {
|
||||
|
||||
$type = 'order';
|
||||
$source_data = array( 'orderId' => absint( $wp_query->query_vars['order-received'] ) );
|
||||
|
||||
} elseif ( ( is_cart() || is_checkout() ) && isset( WC()->cart ) && is_a( WC()->cart, 'WC_Cart' ) ) {
|
||||
|
||||
$type = 'cart';
|
||||
$items = array();
|
||||
foreach ( WC()->cart->get_cart() as $cart_item ) {
|
||||
if ( ! isset( $cart_item['product_id'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$items[] = absint( $cart_item['product_id'] );
|
||||
}
|
||||
$items = array_unique( array_filter( $items ) );
|
||||
$source_data = array( 'productIds' => $items );
|
||||
|
||||
} elseif ( is_product_taxonomy() ) {
|
||||
|
||||
$source = $wp_query->get_queried_object();
|
||||
$is_valid = is_a( $source, 'WP_Term' );
|
||||
$taxonomy = $is_valid ? $source->taxonomy : '';
|
||||
$term_id = $is_valid ? $source->term_id : '';
|
||||
$type = 'archive';
|
||||
$source_data = array(
|
||||
'taxonomy' => wc_clean( $taxonomy ),
|
||||
'termId' => absint( $term_id ),
|
||||
);
|
||||
|
||||
} elseif ( is_product() ) {
|
||||
|
||||
$source = $wp_query->get_queried_object();
|
||||
$product_id = is_a( $source, 'WP_Post' ) ? absint( $source->ID ) : 0;
|
||||
$type = 'product';
|
||||
$source_data = array( 'productId' => $product_id );
|
||||
}
|
||||
|
||||
$context = array(
|
||||
'type' => $type,
|
||||
'sourceData' => $source_data,
|
||||
);
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove falsy item from array, recursively.
|
||||
*
|
||||
|
||||
@@ -88,7 +88,7 @@ final class ReserveStock {
|
||||
try {
|
||||
$items = array_filter(
|
||||
$order->get_items(),
|
||||
function( $item ) {
|
||||
function ( $item ) {
|
||||
return $item->is_type( 'line_item' ) && $item->get_product() instanceof \WC_Product && $item->get_quantity() > 0;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable;
|
||||
namespace Automattic\WooCommerce\Database\Migrations\CustomOrderTable;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\LegacyDataHandler;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Utilities\PluginUtil;
|
||||
use WP_CLI;
|
||||
|
||||
/**
|
||||
@@ -76,6 +77,7 @@ class CLIRunner {
|
||||
WP_CLI::add_command( 'wc hpos status', array( $this, 'status' ) );
|
||||
WP_CLI::add_command( 'wc hpos diff', array( $this, 'diff' ) );
|
||||
WP_CLI::add_command( 'wc hpos backfill', array( $this, 'backfill' ) );
|
||||
WP_CLI::add_command( 'wc hpos compatibility-info', array( $this, 'compatibility_info' ) );
|
||||
WP_CLI::add_command( 'wc hpos compatibility-mode enable', array( $this, 'enable_compat_mode' ) );
|
||||
WP_CLI::add_command( 'wc hpos compatibility-mode disable', array( $this, 'disable_compat_mode' ) );
|
||||
|
||||
@@ -736,6 +738,9 @@ ORDER BY $meta_table.order_id ASC, $meta_table.meta_key ASC;
|
||||
* default: false
|
||||
* ---
|
||||
*
|
||||
* [--ignore-plugin-compatibility]
|
||||
* : Enable even if there are active plugins that are incompatible with HPOS.
|
||||
*
|
||||
* ### EXAMPLES
|
||||
*
|
||||
* # Enable HPOS on new shops.
|
||||
@@ -750,8 +755,9 @@ ORDER BY $meta_table.order_id ASC, $meta_table.meta_key ASC;
|
||||
$assoc_args = wp_parse_args(
|
||||
$assoc_args,
|
||||
array(
|
||||
'for-new-shop' => false,
|
||||
'with-sync' => false,
|
||||
'for-new-shop' => false,
|
||||
'with-sync' => false,
|
||||
'ignore-plugin-compatibility' => false,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -763,12 +769,18 @@ ORDER BY $meta_table.order_id ASC, $meta_table.meta_key ASC;
|
||||
WP_CLI::error( __( '[Failed] This is not a new shop, but --for-new-shop flag was passed.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$container = wc_get_container();
|
||||
/** Feature controller instance @var FeaturesController $feature_controller */
|
||||
$feature_controller = wc_get_container()->get( FeaturesController::class );
|
||||
$plugin_info = $feature_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true );
|
||||
if ( count( array_merge( $plugin_info['uncertain'], $plugin_info['incompatible'] ) ) > 0 ) {
|
||||
WP_CLI::warning( __( '[Failed] Some installed plugins are incompatible. Please review the plugins by going to WooCommerce > Settings > Advanced > Features and see the "Order data storage" section.', 'woocommerce' ) );
|
||||
$enable_hpos = false;
|
||||
$feature_controller = $container->get( FeaturesController::class );
|
||||
if ( ! $assoc_args['ignore-plugin-compatibility'] ) {
|
||||
$compatibility_info = $feature_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true );
|
||||
/** Plugin util instance @var PluginUtil $plugin_util */
|
||||
$plugin_util = $container->get( PluginUtil::class );
|
||||
$incompatibles = $plugin_util->get_items_considered_incompatible( 'custom_order_tables', $compatibility_info );
|
||||
if ( count( $incompatibles ) > 0 ) {
|
||||
WP_CLI::warning( __( '[Failed] Some installed plugins are incompatible. Please review the plugins by going to WooCommerce > Settings > Advanced > Features and see the "Order data storage" section.', 'woocommerce' ) );
|
||||
$enable_hpos = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** DataSynchronizer instance @var DataSynchronizer $data_synchronizer */
|
||||
@@ -1213,6 +1225,104 @@ ORDER BY $meta_table.order_id ASC, $meta_table.meta_key ASC;
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the list of WooCommerce-aware plugins known to be compatible, incompatible or without compatibility declaration for HPOS. Note that inactive plugins will always be listed in the "uncertain" list.
|
||||
*
|
||||
* [--include-inactive]
|
||||
* : Include inactive plugins in the list.
|
||||
*
|
||||
* [--display-filenames]
|
||||
* : Print plugin file names instead of plugin names.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @param array $args Positional arguments passed to the command.
|
||||
* @param array $assoc_args Associative arguments (options) passed to the command.
|
||||
*/
|
||||
public function compatibility_info( array $args = array(), array $assoc_args = array() ): void {
|
||||
$container = wc_get_container();
|
||||
$feature_controller = $container->get( FeaturesController::class );
|
||||
$plugin_info = $feature_controller->get_compatible_plugins_for_feature( 'custom_order_tables', ! ( (bool) ( $assoc_args['include-inactive'] ?? null ) ) );
|
||||
$display_filenames = (bool) ( $assoc_args['display-filenames'] ?? null );
|
||||
|
||||
$compatibles = $this->get_printable_plugin_names( $plugin_info['compatible'], $display_filenames );
|
||||
$compatibles_count = count( $compatibles );
|
||||
$this->log(
|
||||
sprintf(
|
||||
// translators: $1$d = plugins count, %2$s = colon (if list follows) or empty.
|
||||
_n( "\n%%C%1\$d%%n compatible plugin found%2\$s", "\n%%C%1\$d%%n compatible plugins found%2\$s", $compatibles_count, 'woocommerce' ),
|
||||
$compatibles_count,
|
||||
$compatibles_count > 0 ? ":\n" : ''
|
||||
)
|
||||
);
|
||||
$this->print_plugin_names( $compatibles );
|
||||
|
||||
$incompatibles = $this->get_printable_plugin_names( $plugin_info['incompatible'], $display_filenames );
|
||||
$incompatibles_count = count( $incompatibles );
|
||||
$this->log(
|
||||
sprintf(
|
||||
// translators: $1$d = plugins count, %2$s = colon (if list follows) or empty.
|
||||
_n( "\n%%C%1\$d%%n incompatible plugin found%2\$s", "\n%%C%1\$d%%n incompatible plugins found%2\$s", $incompatibles_count, 'woocommerce' ),
|
||||
$incompatibles_count,
|
||||
$incompatibles_count > 0 ? ":\n" : ''
|
||||
)
|
||||
);
|
||||
$this->print_plugin_names( $incompatibles );
|
||||
|
||||
$uncertain = $this->get_printable_plugin_names( $plugin_info['uncertain'], $display_filenames );
|
||||
$uncertain_count = count( $uncertain );
|
||||
$this->log(
|
||||
sprintf(
|
||||
// translators: $1$d = plugins count, %2$s = colon (if list follows) or empty.
|
||||
_n( "\n%%C%1\$d%%n uncertain plugin found%2\$s", "\n%%C%1\$d%%n uncertain plugins found%2\$s", $uncertain_count, 'woocommerce' ),
|
||||
$uncertain_count,
|
||||
$uncertain_count > 0 ? ":\n" : ''
|
||||
)
|
||||
);
|
||||
$this->print_plugin_names( $uncertain );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the printable names for a set of plugins given their file names.
|
||||
*
|
||||
* @param array $plugins The plugin file names.
|
||||
* @param bool $display_filenames True to simply return the sorted list of plugin file names.
|
||||
* @return array A sorted array of plugin names or file names.
|
||||
*/
|
||||
private function get_printable_plugin_names( array $plugins, bool $display_filenames ): array {
|
||||
if ( $display_filenames ) {
|
||||
sort( $plugins );
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
$plugin_names = array_map(
|
||||
fn( $plugin_file ) => get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_file, false )['Name'] ?? $plugin_file,
|
||||
$plugins
|
||||
);
|
||||
sort( $plugin_names );
|
||||
return $plugin_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a list of plugin names.
|
||||
*
|
||||
* @param array $plugins The names to print.
|
||||
*/
|
||||
private function print_plugin_names( array $plugins ): void {
|
||||
foreach ( $plugins as $plugin_file ) {
|
||||
$this->log( ' ' . $plugin_file );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a log message using the WP_CLI text colorization feature.
|
||||
*
|
||||
* @param string $text Text to show.
|
||||
*/
|
||||
private function log( string $text ) {
|
||||
WP_CLI::log( WP_CLI::colorize( $text ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables compatibility mode, which keeps the HPOS and posts datastore in sync.
|
||||
*
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Automattic\WooCommerce\Database\Migrations;
|
||||
* Base class for implementing migrations from the standard WordPress meta table
|
||||
* to custom structured tables.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Database\Migrations\CustomOrderTable
|
||||
* @package Automattic\WooCommerce\Database\Migrations
|
||||
*/
|
||||
abstract class MetaToCustomTableMigrator extends TableMigrator {
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Automattic\WooCommerce\Database\Migrations;
|
||||
* Base class for implementing migrations from the standard WordPress meta table
|
||||
* to custom meta (key-value pairs) tables.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Database\Migrations\CustomOrderTable
|
||||
* @package Automattic\WooCommerce\Database\Migrations
|
||||
*/
|
||||
abstract class MetaToMetaTableMigrator extends TableMigrator {
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Automattic\WooCommerce\Database\Migrations;
|
||||
* Base class for implementing WP posts to order tables migrations handlers.
|
||||
* It mainly contains methods to deal with error handling.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Database\Migrations\CustomOrderTable
|
||||
* @package Automattic\WooCommerce\Database\Migrations
|
||||
*/
|
||||
abstract class TableMigrator {
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ class Marketplace {
|
||||
* @internal
|
||||
*/
|
||||
final public function init() {
|
||||
add_action( 'init', array( $this, 'on_init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into WordPress on init.
|
||||
*/
|
||||
public function on_init() {
|
||||
if ( false === FeaturesUtil::feature_is_enabled( 'marketplace' ) ) {
|
||||
/** Feature controller instance @var FeaturesController $feature_controller */
|
||||
$feature_controller = wc_get_container()->get( FeaturesController::class );
|
||||
|
||||
@@ -19,7 +19,7 @@ use Automattic\WooCommerce\Admin\PageController;
|
||||
class WooSubscriptionsNotes {
|
||||
const LAST_REFRESH_OPTION_KEY = 'woocommerce_admin-wc-helper-last-refresh';
|
||||
const NOTE_NAME = 'wc-admin-wc-helper-connection';
|
||||
const CONNECTION_NOTE_NAME = 'wc-admin-wc-helper-connection';
|
||||
const CONNECTION_NOTE_NAME = 'wc-admin-wc-helper-connection'; // deprecated.
|
||||
const SUBSCRIPTION_NOTE_NAME = 'wc-admin-wc-helper-subscription';
|
||||
const NOTIFY_WHEN_DAYS_LEFT = 60;
|
||||
|
||||
@@ -63,7 +63,6 @@ class WooSubscriptionsNotes {
|
||||
// The site just disconnected.
|
||||
if ( ! empty( $old_token ) && empty( $new_token ) ) {
|
||||
$this->remove_notes();
|
||||
$this->add_no_connection_note();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,7 +123,6 @@ class WooSubscriptionsNotes {
|
||||
}
|
||||
|
||||
$this->remove_notes();
|
||||
$this->add_no_connection_note();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,34 +184,6 @@ class WooSubscriptionsNotes {
|
||||
Notes::delete_notes_with_name( self::SUBSCRIPTION_NOTE_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a note prompting to connect to WooCommerce.com.
|
||||
*/
|
||||
public function add_no_connection_note() {
|
||||
$note = self::get_note();
|
||||
$note->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WooCommerce.com connection note
|
||||
*/
|
||||
public static function get_note() {
|
||||
$note = new Note();
|
||||
$note->set_title( __( 'Connect to WooCommerce.com', 'woocommerce' ) );
|
||||
$note->set_content( __( 'Connect to get important product notifications and updates.', 'woocommerce' ) );
|
||||
$note->set_content_data( (object) array() );
|
||||
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
|
||||
$note->set_name( self::CONNECTION_NOTE_NAME );
|
||||
$note->set_source( 'woocommerce-admin' );
|
||||
$note->add_action(
|
||||
'connect',
|
||||
__( 'Connect', 'woocommerce' ),
|
||||
'?page=wc-addons§ion=helper',
|
||||
Note::E_WC_ADMIN_NOTE_UNACTIONED
|
||||
);
|
||||
return $note;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the product_id (if any) associated with a note.
|
||||
*
|
||||
|
||||
@@ -976,6 +976,14 @@ class ListTable extends WP_List_Table {
|
||||
echo '<a href="#" class="order-preview" data-order-id="' . absint( $order->get_id() ) . '" title="' . esc_attr( __( 'Preview', 'woocommerce' ) ) . '">' . esc_html( __( 'Preview', 'woocommerce' ) ) . '</a>';
|
||||
echo '<a href="' . esc_url( $this->get_order_edit_link( $order ) ) . '" class="order-view"><strong>#' . esc_attr( $order->get_order_number() ) . ' ' . esc_html( $buyer ) . '</strong></a>';
|
||||
}
|
||||
|
||||
// Used for showing date & status next to order number/buyer name on small screens.
|
||||
echo '<div class="order_date small-screen-only">';
|
||||
$this->render_order_date_column( $order );
|
||||
echo '</div>';
|
||||
echo '<div class="order_status small-screen-only">';
|
||||
$this->render_order_status_column( $order );
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1056,14 +1064,14 @@ class ListTable extends WP_List_Table {
|
||||
*/
|
||||
private function get_order_status_label( WC_Order $order ): string {
|
||||
$status_names = array(
|
||||
'Pending payment' => __( 'The order has been received, but no payment has been made. Pending payment orders are generally awaiting customer action.', 'woocommerce' ),
|
||||
'On hold' => __( 'The order is awaiting payment confirmation. Stock is reduced, but you need to confirm payment.', 'woocommerce' ),
|
||||
'Processing' => __( 'Payment has been received (paid), and the stock has been reduced. The order is awaiting fulfillment.', 'woocommerce' ),
|
||||
'Completed' => __( 'Order fulfilled and complete.', 'woocommerce' ),
|
||||
'Failed' => __( 'The customer’s payment failed or was declined, and no payment has been successfully made.', 'woocommerce' ),
|
||||
'Draft' => __( 'Draft orders are created when customers start the checkout process while the block version of the checkout is in place.', 'woocommerce' ),
|
||||
'Canceled' => __( 'The order was canceled by an admin or the customer.', 'woocommerce' ),
|
||||
'Refunded' => __( 'Orders are automatically put in the Refunded status when an admin or shop manager has fully refunded the order’s value after payment.', 'woocommerce' ),
|
||||
'pending' => __( 'The order has been received, but no payment has been made. Pending payment orders are generally awaiting customer action.', 'woocommerce' ),
|
||||
'on-hold' => __( 'The order is awaiting payment confirmation. Stock is reduced, but you need to confirm payment.', 'woocommerce' ),
|
||||
'processing' => __( 'Payment has been received (paid), and the stock has been reduced. The order is awaiting fulfillment.', 'woocommerce' ),
|
||||
'completed' => __( 'Order fulfilled and complete.', 'woocommerce' ),
|
||||
'failed' => __( 'The customer’s payment failed or was declined, and no payment has been successfully made.', 'woocommerce' ),
|
||||
'checkout-draft' => __( 'Draft orders are created when customers start the checkout process while the block version of the checkout is in place.', 'woocommerce' ),
|
||||
'cancelled' => __( 'The order was canceled by an admin or the customer.', 'woocommerce' ),
|
||||
'refunded' => __( 'Orders are automatically put in the Refunded status when an admin or shop manager has fully refunded the order’s value after payment.', 'woocommerce' ),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -1073,9 +1081,9 @@ class ListTable extends WP_List_Table {
|
||||
* @param WC_Order $order Current order object.
|
||||
* @since 9.1.0
|
||||
*/
|
||||
$status_names = apply_filters( 'woocommerce_get_order_status_labels', $status_names );
|
||||
$status_names = apply_filters( 'woocommerce_get_order_status_labels', $status_names, $order );
|
||||
|
||||
$status_name = wc_get_order_status_name( $order->get_status() );
|
||||
$status_name = $order->get_status();
|
||||
|
||||
return isset( $status_names[ $status_name ] ) ? $status_names[ $status_name ] : '';
|
||||
}
|
||||
|
||||
@@ -93,10 +93,10 @@ class DefaultFreeExtensions {
|
||||
$plugins = array(
|
||||
'google-listings-and-ads' => array(
|
||||
'min_php_version' => '7.4',
|
||||
'name' => __( 'Google Listings & Ads', 'woocommerce' ),
|
||||
'name' => __( 'Google for WooCommerce', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Drive sales with %1$sGoogle Listings and Ads%2$s', 'woocommerce' ),
|
||||
__( 'Drive sales with %1$sGoogle for WooCommerce%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/google-listings-and-ads" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
@@ -116,7 +116,7 @@ class DefaultFreeExtensions {
|
||||
),
|
||||
),
|
||||
'google-listings-and-ads:alt' => array(
|
||||
'name' => __( 'Google Listings & Ads', 'woocommerce' ),
|
||||
'name' => __( 'Google for WooCommerce', 'woocommerce' ),
|
||||
'description' => __( 'Reach more shoppers and drive sales for your store. Integrate with Google to list your products for free and launch paid ad campaigns.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/google.svg', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=wc-admin&path=%2Fgoogle%2Fstart',
|
||||
@@ -413,6 +413,24 @@ class DefaultFreeExtensions {
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-shipping' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-tax' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'type' => 'or',
|
||||
'operands' => array(
|
||||
@@ -468,63 +486,13 @@ class DefaultFreeExtensions {
|
||||
'</a>'
|
||||
),
|
||||
'is_visible' => array(
|
||||
self::get_rules_for_wcservices_tax_countries(),
|
||||
array(
|
||||
'type' => 'or',
|
||||
'operands' => array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'US',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'FR',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'GB',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'DE',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'CA',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'AU',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'GR',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'BE',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'PT',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'DK',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'SE',
|
||||
'operation' => '=',
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-services' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -533,7 +501,16 @@ class DefaultFreeExtensions {
|
||||
'operand' => array(
|
||||
array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-services' ),
|
||||
'plugins' => array( 'woocommerce-shipping' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-tax' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -885,7 +862,7 @@ class DefaultFreeExtensions {
|
||||
'install_priority' => 1,
|
||||
),
|
||||
'google-listings-and-ads' => array(
|
||||
'label' => __( 'Drive sales with Google Listings & Ads', 'woocommerce' ),
|
||||
'label' => __( 'Drive sales with Google for WooCommerce', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/core-profiler/logo-google.svg', WC_PLUGIN_FILE ),
|
||||
'description' => __( 'Reach millions of active shoppers across Google with free product listings and ads.', 'woocommerce' ),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/google-listings-and-ads?utm_source=storeprofiler&utm_medium=product&utm_campaign=freefeatures',
|
||||
@@ -900,13 +877,57 @@ class DefaultFreeExtensions {
|
||||
),
|
||||
);
|
||||
|
||||
// Copy shipping for the core-profiler and remove is_visible conditions, except for the country restriction.
|
||||
/*
|
||||
* Overwrite the is_visible conditions to just the country restriction
|
||||
* and the requirement for WooCommerce Shipping and WooCommerce Tax
|
||||
* to not be active.
|
||||
*/
|
||||
$_plugins['woocommerce-services:shipping']['is_visible'] = array(
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'US',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-shipping' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-tax' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$_plugins['woocommerce-services:tax']['is_visible'] = array(
|
||||
self::get_rules_for_wcservices_tax_countries(),
|
||||
array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-shipping' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'type' => 'not',
|
||||
'operand' => array(
|
||||
array(
|
||||
'type' => 'plugins_activated',
|
||||
'plugins' => array( 'woocommerce-tax' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$remove_plugins_activated_rule = function ( $is_visible ) {
|
||||
@@ -934,7 +955,32 @@ class DefaultFreeExtensions {
|
||||
foreach ( $plugins as &$plugin ) {
|
||||
if ( isset( $_plugins[ $plugin['key'] ] ) ) {
|
||||
$plugin = array_merge( $plugin, $_plugins[ $plugin['key'] ] );
|
||||
if ( isset( $plugin['is_visible'] ) && is_array( $plugin['is_visible'] ) ) {
|
||||
|
||||
/*
|
||||
* Removes the "not plugins_activated" rules from the "is_visible"
|
||||
* ruleset except for the WooCommerce Services plugin.
|
||||
*
|
||||
* WC Services is a plugin that provides shipping and tax features.
|
||||
* WC Services is sometimes labelled as "WooCommerce Shipping" or
|
||||
* "WooCommerce Tax", depending on which functionality of the plugin
|
||||
* is advertised.
|
||||
*
|
||||
* We have two new upcoming, standalone plugins: "WooCommerce Shipping" and
|
||||
* "WooCommerce Tax" (same names as sometimes used for WC Services).
|
||||
* The new plugins are incompatible with the old WC Services plugin.
|
||||
* In order to prevent merchants from running into this plugin conflict,
|
||||
* we want to keep the "not plugins_activated" rules for recommending
|
||||
* WC Services.
|
||||
*
|
||||
* If WC Services and the new plugins are installed together,
|
||||
* a notice is displayed and the plugin functionality is not registered
|
||||
* by either WC Services or WC Shipping and WC Tax.
|
||||
*/
|
||||
if (
|
||||
isset( $plugin['is_visible'] ) &&
|
||||
is_array( $plugin['is_visible'] ) &&
|
||||
! in_array( $plugin['key'], array( 'woocommerce-services:shipping', 'woocommerce-services:tax' ), true )
|
||||
) {
|
||||
$plugin['is_visible'] = $remove_plugins_activated_rule( $plugin['is_visible'] );
|
||||
}
|
||||
}
|
||||
@@ -942,4 +988,73 @@ class DefaultFreeExtensions {
|
||||
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the country restrictions for use in the `is_visible` key for
|
||||
* recommending the tax functionality of WooCommerce Shipping & Tax.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_rules_for_wcservices_tax_countries() {
|
||||
return array(
|
||||
'type' => 'or',
|
||||
'operands' => array(
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'US',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'FR',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'GB',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'DE',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'CA',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'AU',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'GR',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'BE',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'PT',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'DK',
|
||||
'operation' => '=',
|
||||
),
|
||||
array(
|
||||
'type' => 'base_location_country',
|
||||
'value' => 'SE',
|
||||
'operation' => '=',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,9 @@ class ShippingLabelBanner {
|
||||
$incompatible_plugins = class_exists( '\WC_Shipping_Fedex_Init' ) ||
|
||||
class_exists( '\WC_Shipping_UPS_Init' ) ||
|
||||
class_exists( '\WC_Integration_ShippingEasy' ) ||
|
||||
class_exists( '\WC_ShipStation_Integration' );
|
||||
class_exists( '\WC_ShipStation_Integration' ) ||
|
||||
class_exists( '\Automattic\WCShipping\Loader' ) ||
|
||||
class_exists( '\Automattic\WCTax\Loader' );
|
||||
|
||||
$this->shipping_label_banner_display_rules =
|
||||
new ShippingLabelBannerDisplayRules(
|
||||
|
||||
@@ -373,7 +373,7 @@ class WCAdminAssets {
|
||||
),
|
||||
array(
|
||||
'handle' => WC_ADMIN_APP,
|
||||
'dependencies' => array( 'wc-components', 'wc-admin-layout', 'wc-customer-effort-score', 'wc-product-editor', 'wp-components', 'wc-experimental' ),
|
||||
'dependencies' => array( 'wc-components', 'wc-admin-layout', 'wc-customer-effort-score', 'wp-components', 'wc-experimental' ),
|
||||
),
|
||||
array(
|
||||
'handle' => 'wc-onboarding',
|
||||
|
||||
@@ -114,7 +114,7 @@ class Init extends RemoteSpecsEngine {
|
||||
$locale = get_user_locale();
|
||||
|
||||
$specs = self::get_specs();
|
||||
$results = EvaluateSuggestion::evaluate_specs( $specs );
|
||||
$results = EvaluateSuggestion::evaluate_specs( $specs, array( 'source' => 'wc-wcpay-promotions' ) );
|
||||
|
||||
if ( count( $results['errors'] ) > 0 ) {
|
||||
// Unlike payment gateway suggestions, we don't have a non-empty default set of promotions to fall back to.
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Automattic\WooCommerce\Internal\BatchProcessing;
|
||||
/**
|
||||
* Class BatchProcessingController
|
||||
*
|
||||
* @package Automattic\WooCommerce\Internal\Updates.
|
||||
* @package Automattic\WooCommerce\Internal\BatchProcessing.
|
||||
*/
|
||||
class BatchProcessingController {
|
||||
/*
|
||||
@@ -220,17 +220,19 @@ class BatchProcessingController {
|
||||
* @return array Current state for the processor, or a "blank" state if none exists yet.
|
||||
*/
|
||||
private function get_process_details( BatchProcessorInterface $batch_processor ): array {
|
||||
return get_option(
|
||||
$this->get_processor_state_option_name( $batch_processor ),
|
||||
array(
|
||||
'total_time_spent' => 0,
|
||||
'current_batch_size' => $batch_processor->get_default_batch_size(),
|
||||
'last_error' => null,
|
||||
'recent_failures' => 0,
|
||||
'batch_first_failure' => null,
|
||||
'batch_last_failure' => null,
|
||||
)
|
||||
$defaults = array(
|
||||
'total_time_spent' => 0,
|
||||
'current_batch_size' => $batch_processor->get_default_batch_size(),
|
||||
'last_error' => null,
|
||||
'recent_failures' => 0,
|
||||
'batch_first_failure' => null,
|
||||
'batch_last_failure' => null,
|
||||
);
|
||||
|
||||
$process_details = get_option( $this->get_processor_state_option_name( $batch_processor ) );
|
||||
$process_details = wp_parse_args( is_array( $process_details ) ? $process_details : array(), $defaults );
|
||||
|
||||
return $process_details;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,7 +351,15 @@ class BatchProcessingController {
|
||||
* @return array List (of string) of the class names of the enqueued processors.
|
||||
*/
|
||||
public function get_enqueued_processors(): array {
|
||||
return get_option( self::ENQUEUED_PROCESSORS_OPTION_NAME, array() );
|
||||
$enqueued_processors = get_option( self::ENQUEUED_PROCESSORS_OPTION_NAME, array() );
|
||||
|
||||
if ( ! is_array( $enqueued_processors ) ) {
|
||||
$this->logger->error( 'Could not fetch list of processors. Clearing up queue.', array( 'source' => 'batch-processing' ) );
|
||||
delete_option( self::ENQUEUED_PROCESSORS_OPTION_NAME );
|
||||
$enqueued_processors = array();
|
||||
}
|
||||
|
||||
return $enqueued_processors;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Automattic\WooCommerce\Internal\BatchProcessing;
|
||||
/**
|
||||
* Interface BatchProcessorInterface
|
||||
*
|
||||
* @package Automattic\WooCommerce\DataBase
|
||||
* @package Automattic\WooCommerce\Internal\BatchProcessing
|
||||
*/
|
||||
interface BatchProcessorInterface {
|
||||
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
namespace Automattic\WooCommerce\Internal\ComingSoon;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Blocks\BlockTemplatesController;
|
||||
use Automattic\WooCommerce\Blocks\BlockTemplatesRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Package as BlocksPackage;
|
||||
|
||||
/**
|
||||
* Handles the template_include hook to determine whether the current page needs
|
||||
* to be replaced with a comiing soon screen.
|
||||
* to be replaced with a coming soon screen.
|
||||
*/
|
||||
class ComingSoonRequestHandler {
|
||||
|
||||
@@ -48,13 +51,21 @@ class ComingSoonRequestHandler {
|
||||
// A coming soon page needs to be displayed. Don't cache this response.
|
||||
nocache_headers();
|
||||
|
||||
$is_fse_theme = wc_current_theme_is_fse_theme();
|
||||
$is_store_coming_soon = $this->coming_soon_helper->is_store_coming_soon();
|
||||
|
||||
if ( ! $is_fse_theme && ! current_theme_supports( 'block-template-parts' ) ) {
|
||||
// Initialize block templates for use in classic theme.
|
||||
BlocksPackage::init();
|
||||
$container = BlocksPackage::container();
|
||||
$container->get( BlockTemplatesRegistry::class )->init();
|
||||
$container->get( BlockTemplatesController::class )->init();
|
||||
}
|
||||
|
||||
add_theme_support( 'block-templates' );
|
||||
|
||||
$coming_soon_template = get_query_template( 'coming-soon' );
|
||||
|
||||
$is_fse_theme = wc_current_theme_is_fse_theme();
|
||||
$is_store_coming_soon = $this->coming_soon_helper->is_store_coming_soon();
|
||||
|
||||
if ( ! $is_fse_theme && $is_store_coming_soon ) {
|
||||
get_header();
|
||||
}
|
||||
@@ -66,7 +77,9 @@ class ComingSoonRequestHandler {
|
||||
}
|
||||
);
|
||||
|
||||
include $coming_soon_template;
|
||||
if ( ! empty( $coming_soon_template ) && file_exists( $coming_soon_template ) ) {
|
||||
include $coming_soon_template;
|
||||
}
|
||||
|
||||
if ( ! $is_fse_theme && $is_store_coming_soon ) {
|
||||
get_footer();
|
||||
@@ -185,7 +198,7 @@ class ComingSoonRequestHandler {
|
||||
foreach ( $fonts_to_add as $font_to_add ) {
|
||||
$found = false;
|
||||
foreach ( $font_data as $font ) {
|
||||
if ( $font['name'] === $font_to_add['name'] ) {
|
||||
if ( isset( $font['name'] ) && $font['name'] === $font_to_add['name'] ) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -501,12 +501,13 @@ class CustomOrdersTableController {
|
||||
*/
|
||||
private function add_feature_definition( $features_controller ) {
|
||||
$definition = array(
|
||||
'option_key' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION,
|
||||
'is_experimental' => false,
|
||||
'enabled_by_default' => false,
|
||||
'order' => 50,
|
||||
'setting' => $this->get_hpos_setting_for_feature(),
|
||||
'additional_settings' => array(
|
||||
'option_key' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION,
|
||||
'is_experimental' => false,
|
||||
'enabled_by_default' => false,
|
||||
'order' => 50,
|
||||
'setting' => $this->get_hpos_setting_for_feature(),
|
||||
'plugins_are_incompatible_by_default' => true,
|
||||
'additional_settings' => array(
|
||||
$this->get_hpos_setting_for_sync(),
|
||||
),
|
||||
);
|
||||
@@ -544,11 +545,11 @@ class CustomOrdersTableController {
|
||||
};
|
||||
|
||||
$get_disabled = function () {
|
||||
$plugin_compatibility = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true );
|
||||
$sync_complete = 0 === $this->data_synchronizer->get_current_orders_pending_sync_count();
|
||||
$disabled = array();
|
||||
// Changing something here? might also want to look at `enable|disable` functions in CLIRunner.
|
||||
$incompatible_plugins = array_merge( $plugin_compatibility['uncertain'], $plugin_compatibility['incompatible'] );
|
||||
$compatibility_info = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true );
|
||||
$sync_complete = 0 === $this->data_synchronizer->get_current_orders_pending_sync_count();
|
||||
$disabled = array();
|
||||
// Changing something here? You might also want to look at `enable|disable` functions in Automattic\WooCommerce\Database\Migrations\CustomOrderTable\CLIRunner.
|
||||
$incompatible_plugins = $this->plugin_util->get_items_considered_incompatible( 'custom_order_tables', $compatibility_info );
|
||||
$incompatible_plugins = array_diff( $incompatible_plugins, $this->plugin_util->get_plugins_excluded_from_compatibility_ui() );
|
||||
if ( count( $incompatible_plugins ) > 0 ) {
|
||||
$disabled = array( 'yes' );
|
||||
|
||||
@@ -2524,7 +2524,7 @@ FROM $order_meta_table
|
||||
$this->persist_save( $order );
|
||||
|
||||
// Do not fire 'woocommerce_new_order' for draft statuses for backwards compatibility.
|
||||
if ( 'auto-draft' === $order->get_status( 'edit' ) ) {
|
||||
if ( in_array( $order->get_status( 'edit' ), array( 'auto-draft', 'draft', 'checkout-draft' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2586,8 +2586,7 @@ FROM $order_meta_table
|
||||
* @param \WC_Order $order Order object.
|
||||
*/
|
||||
public function update( &$order ) {
|
||||
$previous_status = ArrayUtil::get_value_or_default( $order->get_data(), 'status' );
|
||||
$changes = $order->get_changes();
|
||||
$previous_status = ArrayUtil::get_value_or_default( $order->get_data(), 'status', 'new' );
|
||||
|
||||
// Before updating, ensure date paid is set if missing.
|
||||
if (
|
||||
@@ -2622,8 +2621,15 @@ FROM $order_meta_table
|
||||
$order->apply_changes();
|
||||
$this->clear_caches( $order );
|
||||
|
||||
// For backwards compatibility, moving an auto-draft order to a valid status triggers the 'woocommerce_new_order' hook.
|
||||
if ( ! empty( $changes['status'] ) && 'auto-draft' === $previous_status ) {
|
||||
$draft_statuses = array( 'new', 'auto-draft', 'draft', 'checkout-draft' );
|
||||
|
||||
// For backwards compatibility, this hook should be fired only if the new status is not one of the draft statuses and the previous status was one of the draft statuses.
|
||||
if (
|
||||
! empty( $changes['status'] )
|
||||
&& $changes['status'] !== $previous_status
|
||||
&& ! in_array( $changes['status'], $draft_statuses, true )
|
||||
&& in_array( $previous_status, $draft_statuses, true )
|
||||
) {
|
||||
do_action( 'woocommerce_new_order', $order->get_id(), $order ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\CLIRunner;
|
||||
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\CLIRunner;
|
||||
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\PostsToOrdersMigrationController;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\{ PageController, Settings };
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\FileController;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\Logging\RemoteLogger;
|
||||
|
||||
/**
|
||||
* LoggingServiceProvider class.
|
||||
@@ -19,6 +20,7 @@ class LoggingServiceProvider extends AbstractServiceProvider {
|
||||
FileController::class,
|
||||
PageController::class,
|
||||
Settings::class,
|
||||
RemoteLogger::class,
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -37,5 +39,7 @@ class LoggingServiceProvider extends AbstractServiceProvider {
|
||||
);
|
||||
|
||||
$this->share( Settings::class );
|
||||
|
||||
$this->share( RemoteLogger::class );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Caches\OrderCache;
|
||||
use Automattic\WooCommerce\Caches\OrderCacheController;
|
||||
use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\CLIRunner;
|
||||
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\CLIRunner;
|
||||
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\PostsToOrdersMigrationController;
|
||||
use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessingController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableRefundDataStore;
|
||||
|
||||
@@ -11,6 +11,7 @@ use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider
|
||||
use Automattic\WooCommerce\Internal\Utilities\COTMigrationUtil;
|
||||
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
|
||||
use Automattic\WooCommerce\Internal\Utilities\HtmlSanitizer;
|
||||
use Automattic\WooCommerce\Internal\Utilities\LegacyRestApiStub;
|
||||
use Automattic\WooCommerce\Internal\Utilities\PluginInstaller;
|
||||
use Automattic\WooCommerce\Internal\Utilities\WebhookUtil;
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
@@ -39,6 +40,7 @@ class UtilsClassesServiceProvider extends AbstractInterfaceServiceProvider {
|
||||
RestApiUtil::class,
|
||||
TimeUtil::class,
|
||||
PluginInstaller::class,
|
||||
LegacyRestApiStub::class,
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -56,5 +58,6 @@ class UtilsClassesServiceProvider extends AbstractInterfaceServiceProvider {
|
||||
$this->share( RestApiUtil::class );
|
||||
$this->share( TimeUtil::class );
|
||||
$this->share_with_implements_tags( PluginInstaller::class );
|
||||
$this->share_with_implements_tags( LegacyRestApiStub::class )->addArgument( RestApiUtil::class );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ class FeaturesController {
|
||||
|
||||
public const FEATURE_ENABLED_CHANGED_ACTION = 'woocommerce_feature_enabled_changed';
|
||||
|
||||
public const PLUGINS_COMPATIBLE_BY_DEFAULT_OPTION = 'woocommerce_plugins_are_compatible_with_features_by_default';
|
||||
|
||||
/**
|
||||
* The existing feature definitions.
|
||||
*
|
||||
@@ -142,12 +144,13 @@ class FeaturesController {
|
||||
*/
|
||||
public function add_feature_definition( $slug, $name, array $args = array() ) {
|
||||
$defaults = array(
|
||||
'disable_ui' => false,
|
||||
'enabled_by_default' => false,
|
||||
'is_experimental' => true,
|
||||
'is_legacy' => false,
|
||||
'name' => $name,
|
||||
'order' => 10,
|
||||
'disable_ui' => false,
|
||||
'enabled_by_default' => false,
|
||||
'is_experimental' => true,
|
||||
'is_legacy' => false,
|
||||
'plugins_are_incompatible_by_default' => false,
|
||||
'name' => $name,
|
||||
'order' => 10,
|
||||
);
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
|
||||
@@ -244,6 +247,17 @@ class FeaturesController {
|
||||
'is_legacy' => true,
|
||||
'option_key' => CustomOrdersTableController::HPOS_FTS_INDEX_OPTION,
|
||||
),
|
||||
'remote_logging' => array(
|
||||
'name' => __( 'Remote Logging', 'woocommerce' ),
|
||||
'description' => __(
|
||||
'Enable this feature to log errors and related data to Automattic servers for debugging purposes and to improve WooCommerce',
|
||||
'woocommerce'
|
||||
),
|
||||
'enabled_by_default' => false,
|
||||
'disable_ui' => true,
|
||||
'is_legacy' => false,
|
||||
'is_experimental' => true,
|
||||
),
|
||||
);
|
||||
|
||||
foreach ( $legacy_features as $slug => $definition ) {
|
||||
@@ -322,6 +336,34 @@ class FeaturesController {
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if plugins that don't declare compatibility nor incompatibility with a given feature
|
||||
* are to be considered incompatible with that feature.
|
||||
*
|
||||
* @param string $feature_id Feature id to check.
|
||||
* @return bool True if plugins that don't declare compatibility nor incompatibility with the feature will be considered incompatible with the feature.
|
||||
* @throws \InvalidArgumentException The feature doesn't exist.
|
||||
*/
|
||||
public function get_plugins_are_incompatible_by_default( string $feature_id ): bool {
|
||||
$feature_definition = $this->get_feature_definitions()[ $feature_id ] ?? null;
|
||||
if ( is_null( $feature_definition ) ) {
|
||||
throw new \InvalidArgumentException( esc_html( "The WooCommerce feature '$feature_id' doesn't exist" ) );
|
||||
}
|
||||
|
||||
$incompatible_by_default = $feature_definition['plugins_are_incompatible_by_default'] ?? false;
|
||||
|
||||
/**
|
||||
* Filter to determine if plugins that don't declare compatibility nor incompatibility with a given feature
|
||||
* are to be considered incompatible with that feature.
|
||||
*
|
||||
* @param bool $incompatible_by_default Default value, true if plugins are to be considered incompatible by default with the feature.
|
||||
* @param string $feature_id The feature to check.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*/
|
||||
return (bool) apply_filters( 'woocommerce_plugins_are_incompatible_with_feature_by_default', $incompatible_by_default, $feature_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given feature is currently enabled.
|
||||
*
|
||||
@@ -476,7 +518,7 @@ class FeaturesController {
|
||||
*
|
||||
* @param string $feature_id Feature id.
|
||||
* @param bool $active_only True to return only active plugins.
|
||||
* @return array An array having a 'compatible' and an 'incompatible' key, each holding an array of plugin names.
|
||||
* @return array An array having a 'compatible', an 'incompatible' and an 'uncertain' key, each holding an array of plugin names.
|
||||
*/
|
||||
public function get_compatible_plugins_for_feature( string $feature_id, bool $active_only = false ): array {
|
||||
$this->verify_did_woocommerce_init( __FUNCTION__ );
|
||||
@@ -884,7 +926,7 @@ class FeaturesController {
|
||||
*
|
||||
* @param array $plugin_list The original list of plugins.
|
||||
*/
|
||||
private function filter_plugins_list( $plugin_list ): array {
|
||||
public function filter_plugins_list( $plugin_list ): array {
|
||||
if ( ! $this->verify_did_woocommerce_init() ) {
|
||||
return $plugin_list;
|
||||
}
|
||||
@@ -910,10 +952,11 @@ class FeaturesController {
|
||||
*
|
||||
* @return array List of plugins incompatible with the given feature.
|
||||
*/
|
||||
private function get_incompatible_plugins( $feature_id, $plugin_list ) {
|
||||
$incompatibles = array();
|
||||
|
||||
$plugin_list = array_diff_key( $plugin_list, array_flip( $this->plugins_excluded_from_compatibility_ui ) );
|
||||
public function get_incompatible_plugins( $feature_id, $plugin_list ) {
|
||||
$incompatibles = array();
|
||||
$plugin_list = array_diff_key( $plugin_list, array_flip( $this->plugins_excluded_from_compatibility_ui ) );
|
||||
$feature_ids = 'all' === $feature_id ? array_keys( $this->get_feature_definitions() ) : array( $feature_id );
|
||||
$only_enabled_features = 'all' === $feature_id;
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
|
||||
foreach ( array_keys( $plugin_list ) as $plugin_name ) {
|
||||
@@ -921,16 +964,17 @@ class FeaturesController {
|
||||
continue;
|
||||
}
|
||||
|
||||
$compatibility = $this->get_compatible_features_for_plugin( $plugin_name );
|
||||
$incompatible_with = array_filter(
|
||||
array_merge( $compatibility['incompatible'], $compatibility['uncertain'] ),
|
||||
function ( $feature_id ) {
|
||||
return ! $this->is_legacy_feature( $feature_id );
|
||||
$compatibility_info = $this->get_compatible_features_for_plugin( $plugin_name );
|
||||
foreach ( $feature_ids as $feature_id ) {
|
||||
$features_considered_incompatible = array_filter(
|
||||
$this->plugin_util->get_items_considered_incompatible( $feature_id, $compatibility_info ),
|
||||
$only_enabled_features ?
|
||||
fn( $feature_id ) => $this->feature_is_enabled( $feature_id ) && ! $this->is_legacy_feature( $feature_id ) :
|
||||
fn( $feature_id ) => ! $this->is_legacy_feature( $feature_id )
|
||||
);
|
||||
if ( in_array( $feature_id, $features_considered_incompatible, true ) ) {
|
||||
$incompatibles[] = $plugin_name;
|
||||
}
|
||||
);
|
||||
|
||||
if ( ( 'all' === $feature_id && ! empty( $incompatible_with ) ) || in_array( $feature_id, $incompatible_with, true ) ) {
|
||||
$incompatibles[] = $plugin_name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,7 +998,7 @@ class FeaturesController {
|
||||
/**
|
||||
* Shows a warning when there are any incompatibility between active plugins and enabled features.
|
||||
* The warning is shown in on any admin screen except the plugins screen itself, since
|
||||
* there's already a "You are viewing
|
||||
* there's already a "You are viewing plugins that are incompatible" notice.
|
||||
*/
|
||||
private function maybe_display_feature_incompatibility_warning(): void {
|
||||
if ( ! current_user_can( 'activate_plugins' ) ) {
|
||||
@@ -965,18 +1009,25 @@ class FeaturesController {
|
||||
$relevant_plugins = array_diff( $this->plugin_util->get_woocommerce_aware_plugins( true ), $this->plugins_excluded_from_compatibility_ui );
|
||||
|
||||
foreach ( $relevant_plugins as $plugin ) {
|
||||
$compatibility = $this->get_compatible_features_for_plugin( $plugin, true );
|
||||
$incompatible_with = array_filter(
|
||||
array_merge( $compatibility['incompatible'], $compatibility['uncertain'] ),
|
||||
function ( $feature_id ) {
|
||||
return ! $this->is_legacy_feature( $feature_id );
|
||||
}
|
||||
);
|
||||
$compatibility_info = $this->get_compatible_features_for_plugin( $plugin, true );
|
||||
|
||||
if ( $incompatible_with ) {
|
||||
$incompatibles = array_filter( $compatibility_info['incompatible'], fn( $feature_id ) => ! $this->is_legacy_feature( $feature_id ) );
|
||||
if ( ! empty( $incompatibles ) ) {
|
||||
$incompatible_plugins = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$uncertains = array_filter( $compatibility_info['uncertain'], fn( $feature_id ) => ! $this->is_legacy_feature( $feature_id ) );
|
||||
foreach ( $uncertains as $feature_id ) {
|
||||
if ( $this->get_plugins_are_incompatible_by_default( $feature_id ) ) {
|
||||
$incompatible_plugins = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $incompatible_plugins ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $incompatible_plugins ) {
|
||||
|
||||
@@ -309,13 +309,45 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$product_inventory_inner_section->add_block(
|
||||
$inventory_columns = $product_inventory_inner_section->add_block(
|
||||
array(
|
||||
'id' => 'product-inventory-inner-columns',
|
||||
'blockName' => 'core/columns',
|
||||
)
|
||||
);
|
||||
$inventory_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-inventory-inner-column1',
|
||||
'blockName' => 'core/column',
|
||||
)
|
||||
)->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-sku-field',
|
||||
'blockName' => 'woocommerce/product-sku-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$inventory_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-inventory-inner-column2',
|
||||
'blockName' => 'core/column',
|
||||
)
|
||||
)->add_block(
|
||||
array(
|
||||
'id' => 'product-unique-id-field',
|
||||
'blockName' => 'woocommerce/product-text-field',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'property' => 'global_unique_id',
|
||||
'label' => __( 'GTIN, UPC, EAN or ISBN', 'woocommerce' ),
|
||||
'tooltip' => __( 'Enter a barcode or any other identifier unique to this product. It can help you list this product on other channels or marketplaces.', 'woocommerce' ),
|
||||
'pattern' => array(
|
||||
'value' => '[0-9\-]*',
|
||||
'message' => __( 'Please enter only numbers and hyphens (-).', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_inventory_inner_section->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-track-stock',
|
||||
|
||||
@@ -730,7 +730,18 @@ class SimpleProductTemplate extends AbstractProductFormTemplate implements Produ
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$product_inventory_inner_section->add_block(
|
||||
$inventory_columns = $product_inventory_inner_section->add_block(
|
||||
array(
|
||||
'id' => 'product-inventory-inner-columns',
|
||||
'blockName' => 'core/columns',
|
||||
)
|
||||
);
|
||||
$inventory_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-inventory-inner-column1',
|
||||
'blockName' => 'core/column',
|
||||
)
|
||||
)->add_block(
|
||||
array(
|
||||
'id' => 'product-sku-field',
|
||||
'blockName' => 'woocommerce/product-sku-field',
|
||||
@@ -742,6 +753,32 @@ class SimpleProductTemplate extends AbstractProductFormTemplate implements Produ
|
||||
),
|
||||
)
|
||||
);
|
||||
$inventory_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-inventory-inner-column2',
|
||||
'blockName' => 'core/column',
|
||||
)
|
||||
)->add_block(
|
||||
array(
|
||||
'id' => 'product-unique-id-field',
|
||||
'blockName' => 'woocommerce/product-text-field',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'property' => 'global_unique_id',
|
||||
'label' => __( 'GTIN, UPC, EAN or ISBN', 'woocommerce' ),
|
||||
'tooltip' => __( 'Enter a barcode or any other identifier unique to this product. It can help you list this product on other channels or marketplaces.', 'woocommerce' ),
|
||||
'pattern' => array(
|
||||
'value' => '[0-9\-]*',
|
||||
'message' => __( 'Please enter only numbers and hyphens (-).', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
'disableConditions' => array(
|
||||
array(
|
||||
'expression' => 'editedProduct.type === "variable"',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$manage_stock = 'yes' === get_option( 'woocommerce_manage_stock' );
|
||||
$product_inventory_inner_section->add_block(
|
||||
|
||||
@@ -31,19 +31,20 @@ class WPConsentAPI {
|
||||
*/
|
||||
public function register() {
|
||||
add_action(
|
||||
'plugins_loaded',
|
||||
'init',
|
||||
function() {
|
||||
$this->on_plugins_loaded();
|
||||
}
|
||||
$this->on_init();
|
||||
},
|
||||
20 // After OrderAttributionController.
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register our hooks on plugins_loaded.
|
||||
* Register our hooks on init.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function on_plugins_loaded() {
|
||||
protected function on_init() {
|
||||
// Include integration to WP Consent Level API if available.
|
||||
if ( ! $this->is_wp_consent_api_active() ) {
|
||||
return;
|
||||
@@ -61,7 +62,7 @@ class WPConsentAPI {
|
||||
/**
|
||||
* Modify the "allowTracking" flag consent if the user has consented to marketing.
|
||||
*
|
||||
* Wp-consent-api will initialize the modules on "plugins_loaded" with priority 9,
|
||||
* Wp-consent-api will initialize the modules on "init" with priority 9,
|
||||
* So this code needs to be run after that.
|
||||
*/
|
||||
add_filter(
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Logging;
|
||||
|
||||
use Automattic\WooCommerce\Utilities\FeaturesUtil;
|
||||
|
||||
/**
|
||||
* WooCommerce Remote Logger
|
||||
*
|
||||
* The WooCommerce remote logger class adds functionality to log WooCommerce errors remotely based on if the customer opted in and several other conditions.
|
||||
*
|
||||
* No personal information is logged, only error information and relevant context.
|
||||
*
|
||||
* @class RemoteLogger
|
||||
* @since 9.2.0
|
||||
* @package WooCommerce\Classes
|
||||
*/
|
||||
class RemoteLogger {
|
||||
const WC_LATEST_VERSION_TRANSIENT = 'latest_woocommerce_version';
|
||||
const FETCH_LATEST_VERSION_RETRY = 'fetch_latest_woocommerce_version_retry';
|
||||
|
||||
/**
|
||||
* Determines if remote logging is allowed based on the following conditions:
|
||||
*
|
||||
* 1. The feature flag for remote error logging is enabled.
|
||||
* 2. The user has opted into tracking/logging.
|
||||
* 3. The store is allowed to log based on the variant assignment percentage.
|
||||
* 4. The current WooCommerce version is the latest so we don't log errors that might have been fixed in a newer version.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_remote_logging_allowed() {
|
||||
if ( ! FeaturesUtil::feature_is_enabled( 'remote_logging' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->is_tracking_opted_in() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->is_variant_assignment_allowed() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->is_latest_woocommerce_version() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has opted into tracking/logging.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_tracking_opted_in() {
|
||||
return 'yes' === get_option( 'woocommerce_allow_tracking', 'no' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store is allowed to log based on the variant assignment percentage.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_variant_assignment_allowed() {
|
||||
$assignment = get_option( 'woocommerce_remote_variant_assignment', 0 );
|
||||
return ( $assignment <= 12 ); // Considering 10% of the 0-120 range.
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current WooCommerce version is the latest.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_latest_woocommerce_version() {
|
||||
$latest_wc_version = $this->fetch_latest_woocommerce_version();
|
||||
|
||||
if ( is_null( $latest_wc_version ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return version_compare( WC()->version, $latest_wc_version, '>=' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the latest WooCommerce version using the WordPress API and cache it.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function fetch_latest_woocommerce_version() {
|
||||
$cached_version = get_transient( self::WC_LATEST_VERSION_TRANSIENT );
|
||||
if ( $cached_version ) {
|
||||
return $cached_version;
|
||||
}
|
||||
|
||||
$retry_count = get_transient( self::FETCH_LATEST_VERSION_RETRY );
|
||||
if ( false === $retry_count || ! is_numeric( $retry_count ) ) {
|
||||
$retry_count = 0;
|
||||
}
|
||||
|
||||
if ( $retry_count >= 3 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'plugins_api' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||
}
|
||||
// Fetch the latest version from the WordPress API.
|
||||
$plugin_info = plugins_api( 'plugin_information', array( 'slug' => 'woocommerce' ) );
|
||||
|
||||
if ( is_wp_error( $plugin_info ) ) {
|
||||
++$retry_count;
|
||||
set_transient( self::FETCH_LATEST_VERSION_RETRY, $retry_count, HOUR_IN_SECONDS );
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! empty( $plugin_info->version ) ) {
|
||||
$latest_version = $plugin_info->version;
|
||||
set_transient( self::WC_LATEST_VERSION_TRANSIENT, $latest_version, WEEK_IN_SECONDS );
|
||||
delete_transient( self::FETCH_LATEST_VERSION_RETRY );
|
||||
return $latest_version;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -61,11 +61,18 @@ class OrderAttributionBlocksController implements RegisterHooksInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into WP.
|
||||
* Register this class instance to the appropriate hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
add_action( 'init', array( $this, 'on_init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into WordPress on init.
|
||||
*/
|
||||
public function on_init() {
|
||||
// Bail if the feature is not enabled.
|
||||
if ( ! $this->features_controller->feature_is_enabled( 'order_attribution' ) ) {
|
||||
return;
|
||||
|
||||
@@ -95,6 +95,13 @@ class OrderAttributionController implements RegisterHooksInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'init', array( $this, 'on_init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into WordPress on init.
|
||||
*/
|
||||
public function on_init() {
|
||||
// Bail if the feature is not enabled.
|
||||
if ( ! $this->feature_controller->feature_is_enabled( 'order_attribution' ) ) {
|
||||
return;
|
||||
|
||||
@@ -431,9 +431,14 @@ class LookupDataStore {
|
||||
* Create all the necessary lookup data for a given variation.
|
||||
*
|
||||
* @param \WC_Product_Variation $variation The variation to create entries for.
|
||||
* @throws \Exception Can't retrieve the details of the parent product.
|
||||
*/
|
||||
private function create_data_for_variation( \WC_Product_Variation $variation ) {
|
||||
$main_product = WC()->call_function( 'wc_get_product', $variation->get_parent_id() );
|
||||
if ( false === $main_product ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
throw new \Exception( "The product is a variation, and the retrieval of data for the parent product (id {$variation->get_parent_id()}) failed." );
|
||||
}
|
||||
|
||||
$product_attributes_data = $this->get_attribute_taxonomies( $main_product );
|
||||
$variation_attributes_data = array_filter(
|
||||
|
||||
@@ -198,8 +198,34 @@ class ReceiptRenderingEngine {
|
||||
*/
|
||||
$data['css'] = apply_filters( 'woocommerce_printable_order_receipt_css', $css, $order );
|
||||
|
||||
$default_template_path = __DIR__ . '/Templates/order-receipt.php';
|
||||
|
||||
/**
|
||||
* Filter the order receipt template path.
|
||||
*
|
||||
* @since 9.2.0
|
||||
* @hook wc_get_template
|
||||
* @param string $template The template path.
|
||||
* @param string $template_name The template name.
|
||||
* @param array $args The available data for the template.
|
||||
* @param string $template_path The template path.
|
||||
* @param string $default_path The default template path.
|
||||
*/
|
||||
$template_path = apply_filters(
|
||||
'wc_get_template',
|
||||
$default_template_path,
|
||||
'ReceiptRendering/order-receipt.php',
|
||||
$data,
|
||||
$default_template_path,
|
||||
$default_template_path
|
||||
);
|
||||
|
||||
if ( ! file_exists( $template_path ) ) {
|
||||
$template_path = $default_template_path;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
include __DIR__ . '/Templates/order-receipt.php';
|
||||
include $template_path;
|
||||
$rendered_template = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ use Automattic\WooCommerce\Internal\TransientFiles\TransientFilesEngine;
|
||||
use \WP_REST_Server;
|
||||
use \WP_REST_Request;
|
||||
use \WP_Error;
|
||||
use \InvalidArgumentException;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use Automattic\WooCommerce\Internal\RestApiControllerBase;
|
||||
|
||||
/**
|
||||
* Controller for the REST endpoints associated to the receipt rendering engine.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\ReceiptRendering;
|
||||
namespace Automattic\WooCommerce\Internal;
|
||||
|
||||
use Automattic\WooCommerce\Internal\RegisterHooksInterface;
|
||||
use Automattic\WooCommerce\Utilities\StringUtil;
|
||||
@@ -88,33 +88,6 @@ abstract class RestApiControllerBase implements RegisterHooksInterface {
|
||||
*/
|
||||
protected string $route_namespace = 'wc/v3';
|
||||
|
||||
/**
|
||||
* Holds authentication error messages for each HTTP verb.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $authentication_errors_by_method;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->authentication_errors_by_method = array(
|
||||
'GET' => array(
|
||||
'code' => 'woocommerce_rest_cannot_view',
|
||||
'message' => __( 'Sorry, you cannot view resources.', 'woocommerce' ),
|
||||
),
|
||||
'POST' => array(
|
||||
'code' => 'woocommerce_rest_cannot_create',
|
||||
'message' => __( 'Sorry, you cannot create resources.', 'woocommerce' ),
|
||||
),
|
||||
'DELETE' => array(
|
||||
'code' => 'woocommerce_rest_cannot_delete',
|
||||
'message' => __( 'Sorry, you cannot delete resources.', 'woocommerce' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the hooks used by the class.
|
||||
*/
|
||||
@@ -193,6 +166,31 @@ abstract class RestApiControllerBase implements RegisterHooksInterface {
|
||||
return new WP_Error( 'woocommerce_rest_internal_error', __( 'Internal server error', 'woocommerce' ), $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an authentication error message for a given HTTP verb.
|
||||
*
|
||||
* @param string $method HTTP method.
|
||||
* @return array|null Error information on success, null otherwise.
|
||||
*/
|
||||
protected function get_authentication_error_by_method( string $method ) {
|
||||
$errors = array(
|
||||
'GET' => array(
|
||||
'code' => 'woocommerce_rest_cannot_view',
|
||||
'message' => __( 'Sorry, you cannot view resources.', 'woocommerce' ),
|
||||
),
|
||||
'POST' => array(
|
||||
'code' => 'woocommerce_rest_cannot_create',
|
||||
'message' => __( 'Sorry, you cannot create resources.', 'woocommerce' ),
|
||||
),
|
||||
'DELETE' => array(
|
||||
'code' => 'woocommerce_rest_cannot_delete',
|
||||
'message' => __( 'Sorry, you cannot delete resources.', 'woocommerce' ),
|
||||
),
|
||||
);
|
||||
|
||||
return $errors[ $method ] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check for REST API endpoints, given the request method.
|
||||
*
|
||||
@@ -206,7 +204,7 @@ abstract class RestApiControllerBase implements RegisterHooksInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
$error_information = $this->authentication_errors_by_method[ $request->get_method() ] ?? null;
|
||||
$error_information = $this->get_authentication_error_by_method( $request->get_method() );
|
||||
if ( is_null( $error_information ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,26 +2,52 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Utilities;
|
||||
|
||||
use Automattic\WooCommerce\Internal\RegisterHooksInterface;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use Automattic\WooCommerce\Utilities\RestApiUtil;
|
||||
|
||||
/**
|
||||
* The Legacy REST API was removed in WooCommerce 9.0 and is now available as a dedicated extension.
|
||||
* A stub is kept in WooCommerce core that just returns a "The WooCommerce API is disabled on this site"
|
||||
* error if the extension is not installed or is inactive. See:
|
||||
* https://developer.woocommerce.com/2023/10/03/the-legacy-rest-api-will-move-to-a-dedicated-extension-in-woocommerce-9-0/
|
||||
* A stub is kept in WooCommerce core that acts when the extension is not installed and has two purposes:
|
||||
*
|
||||
* 1. Return a "The WooCommerce API is disabled on this site" error for any request to the Legacy REST API endpoints.
|
||||
*
|
||||
* 2. Provide the not-endpoint related utility methods that were previously supplied by the WC_API class,
|
||||
* this is achieved by setting the value of WooCommerce::api (typically accessed via 'WC()->api') to an instance of this class.
|
||||
*
|
||||
* DO NOT add any additional public method to this class unless the method existed with the same signature in the old WC_API class.
|
||||
*
|
||||
* See: https://developer.woocommerce.com/2023/10/03/the-legacy-rest-api-will-move-to-a-dedicated-extension-in-woocommerce-9-0/
|
||||
*/
|
||||
class LegacyRestApiStub {
|
||||
class LegacyRestApiStub implements RegisterHooksInterface {
|
||||
use AccessiblePrivateMethods;
|
||||
|
||||
/**
|
||||
* Set up the Legacy REST API stub.
|
||||
* The instance of RestApiUtil to use.
|
||||
*
|
||||
* @var RestApiUtil
|
||||
*/
|
||||
public static function setup() {
|
||||
private RestApiUtil $rest_api_util;
|
||||
|
||||
/**
|
||||
* Set up the Legacy REST API endpoints stub.
|
||||
*/
|
||||
public function register() {
|
||||
self::add_action( 'init', array( __CLASS__, 'add_rewrite_rules_for_legacy_rest_api_stub' ), 0 );
|
||||
self::add_action( 'query_vars', array( __CLASS__, 'add_query_vars_for_legacy_rest_api_stub' ), 0 );
|
||||
self::add_action( 'parse_request', array( __CLASS__, 'parse_legacy_rest_api_request' ), 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the class dependencies.
|
||||
*
|
||||
* @internal
|
||||
* @param RestApiUtil $rest_api_util The instance of RestApiUtil to use.
|
||||
*/
|
||||
final public function init( RestApiUtil $rest_api_util ) {
|
||||
$this->rest_api_util = $rest_api_util;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the necessary rewrite rules for the Legacy REST API
|
||||
* (either the dedicated extension if it's installed, or the stub otherwise).
|
||||
@@ -142,4 +168,24 @@ class LegacyRestApiStub {
|
||||
die( '-1' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from a WooCommerce API endpoint.
|
||||
* This method used to be part of the WooCommerce Legacy REST API.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @param string $endpoint Endpoint.
|
||||
* @param array $params Params to pass with request.
|
||||
* @return array|\WP_Error
|
||||
*/
|
||||
public function get_endpoint_data( $endpoint, $params = array() ) {
|
||||
wc_doing_it_wrong(
|
||||
'get_endpoint_data',
|
||||
"'WC()->api->get_endpoint_data' is deprecated, please use the following instead: wc_get_container()->get(Automattic\WooCommerce\Utilities\RestApiUtil::class)->get_endpoint_data",
|
||||
'9.1.0'
|
||||
);
|
||||
|
||||
return $this->rest_api_util->get_endpoint_data( $endpoint, $params );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Utilities;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Utilities to help ensure type safety.
|
||||
*/
|
||||
class Types {
|
||||
/**
|
||||
* Checks if $thing is an instance of $desired_type.
|
||||
*
|
||||
* If the check succeeds, $thing will be returned without further modification. If the check fails, then either
|
||||
* an exception will be thrown or, if an $on_failure callback was supplied, it will be invoked to either generate
|
||||
* an appropriate return value or to throw a more specific exception.
|
||||
*
|
||||
* Please note that the failure handler will be passed two arguments:
|
||||
*
|
||||
* $on_failure( $object, $desired_type )
|
||||
*
|
||||
* @since 9.1.0
|
||||
* @throws InvalidArgumentException If $object does not match $desired_type, and an $on_failure callback was not supplied.
|
||||
*
|
||||
* @param mixed $thing The value or reference to be assessed.
|
||||
* @param string $desired_type What we expect the return type to be, if it is not a WP_Error.
|
||||
* @param ?callable $on_failure If provided, and if evaluation fails, this will be invoked to generate a return value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function ensure_instance_of( $thing, string $desired_type, callable $on_failure = null ) {
|
||||
// If everything looks good, return early.
|
||||
if ( $thing instanceof $desired_type ) {
|
||||
return $thing;
|
||||
}
|
||||
|
||||
// Summarize the error for use in logging and in case we have to throw an exception.
|
||||
$summary = sprintf(
|
||||
'Object was not of expected type %1$s.',
|
||||
$desired_type
|
||||
);
|
||||
|
||||
// Otherwise, let's log the problem so the site operator has a record of where things went wrong.
|
||||
$logger = wc_get_logger();
|
||||
|
||||
if ( $logger ) {
|
||||
$logger->error(
|
||||
$summary,
|
||||
array(
|
||||
'source' => 'wc-type-check-utility',
|
||||
'backtrace' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Invoke the $on_failure handler, if specified.
|
||||
if ( null !== $on_failure ) {
|
||||
return $on_failure( $thing, $desired_type );
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException( esc_html( $summary ) );
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,24 @@ namespace Automattic\WooCommerce\StoreApi\Formatters;
|
||||
*/
|
||||
class MoneyFormatter implements FormatterInterface {
|
||||
/**
|
||||
* Format a given value and return the result.
|
||||
* Format a given price value and return the result as a string without decimals.
|
||||
*
|
||||
* @param mixed $value Value to format.
|
||||
* @param array $options Options that influence the formatting.
|
||||
* @return mixed
|
||||
* @param int|float|string $value Value to format. Int is allowed, as it may also represent a valid price.
|
||||
* @param array $options Options that influence the formatting.
|
||||
* @return string
|
||||
*/
|
||||
public function format( $value, array $options = [] ) {
|
||||
|
||||
if ( ! is_int( $value ) && ! is_string( $value ) && ! is_float( $value ) ) {
|
||||
wc_doing_it_wrong(
|
||||
__FUNCTION__,
|
||||
'Function expects a $value arg of type INT, STRING or FLOAT.',
|
||||
'9.2'
|
||||
);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$options = wp_parse_args(
|
||||
$options,
|
||||
[
|
||||
@@ -23,12 +34,20 @@ class MoneyFormatter implements FormatterInterface {
|
||||
]
|
||||
);
|
||||
|
||||
return (string) intval(
|
||||
round(
|
||||
( (float) wc_format_decimal( $value ) ) * ( 10 ** absint( $options['decimals'] ) ),
|
||||
0,
|
||||
absint( $options['rounding_mode'] )
|
||||
)
|
||||
);
|
||||
// Ensure rounding mode is valid.
|
||||
$rounding_modes = [ PHP_ROUND_HALF_UP, PHP_ROUND_HALF_DOWN, PHP_ROUND_HALF_EVEN, PHP_ROUND_HALF_ODD ];
|
||||
$options['rounding_mode'] = absint( $options['rounding_mode'] );
|
||||
if ( ! in_array( $options['rounding_mode'], $rounding_modes, true ) ) {
|
||||
$options['rounding_mode'] = PHP_ROUND_HALF_UP;
|
||||
}
|
||||
|
||||
$value = floatval( $value );
|
||||
|
||||
// Remove the price decimal points for rounding purposes.
|
||||
$value = $value * pow( 10, absint( $options['decimals'] ) );
|
||||
$value = round( $value, 0, $options['rounding_mode'] );
|
||||
|
||||
// This ensures returning the value as a string without decimal points ready for price parsing.
|
||||
return wc_format_decimal( $value, 0, true );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,11 @@ class Legacy {
|
||||
// and a generic notice will be shown instead if payment failed.
|
||||
wc_clear_notices();
|
||||
|
||||
// Handle result.
|
||||
$result->set_status( isset( $gateway_result['result'] ) && 'success' === $gateway_result['result'] ? 'success' : 'failure' );
|
||||
// Handle result. If status was not returned we consider this invalid and return failure.
|
||||
$result_status = $gateway_result['result'] ?? 'failure';
|
||||
// These are the same statuses supported by the API and indicate processing status. This is not the same as order status.
|
||||
$valid_status = array( 'success', 'failure', 'pending', 'error' );
|
||||
$result->set_status( in_array( $result_status, $valid_status, true ) ? $result_status : 'failure' );
|
||||
|
||||
// set payment_details from result.
|
||||
$result->set_payment_details( array_merge( $result->payment_details, $gateway_result ) );
|
||||
|
||||
@@ -105,7 +105,6 @@ abstract class AbstractCartRoute extends AbstractRoute {
|
||||
*/
|
||||
public function get_response( \WP_REST_Request $request ) {
|
||||
$this->load_cart_session( $request );
|
||||
$this->cart_controller->calculate_totals();
|
||||
|
||||
$response = null;
|
||||
$nonce_check = $this->requires_nonce( $request ) ? $this->check_nonce( $request ) : null;
|
||||
@@ -332,13 +331,11 @@ abstract class AbstractCartRoute extends AbstractRoute {
|
||||
* @return \WP_Error WP Error object.
|
||||
*/
|
||||
protected function get_route_error_response( $error_code, $error_message, $http_status_code = 500, $additional_data = [] ) {
|
||||
|
||||
$additional_data['status'] = $http_status_code;
|
||||
|
||||
// If there was a conflict, return the cart so the client can resolve it.
|
||||
if ( 409 === $http_status_code ) {
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
$additional_data['cart'] = $this->cart_schema->get_item_response( $cart );
|
||||
$additional_data['cart'] = $this->cart_schema->get_item_response( $this->cart_controller->get_cart_for_response() );
|
||||
}
|
||||
|
||||
return new \WP_Error( $error_code, $error_message, $additional_data );
|
||||
|
||||
@@ -6,8 +6,6 @@ use Automattic\WooCommerce\StoreApi\Routes\RouteInterface;
|
||||
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
|
||||
use Automattic\WooCommerce\StoreApi\Exceptions\InvalidCartException;
|
||||
use Automattic\WooCommerce\StoreApi\Schemas\v1\AbstractSchema;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user