plugin updates

This commit is contained in:
Tony Volpe
2024-09-17 10:43:54 -04:00
parent 44b413346f
commit b7c8882c8c
1359 changed files with 58219 additions and 11364 deletions

View File

@@ -0,0 +1,59 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
use Automattic\WooCommerce\Blueprint\Exporters\HasAlias;
use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions;
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
/**
* ExportWCCoreProfilerOptions class
*/
class ExportWCCoreProfilerOptions implements StepExporter, HasAlias {
use UseWPFunctions;
/**
* Export the step
*
* @return SetSiteOptions
*/
public function export() {
$step = new SetSiteOptions(
array(
'blogname' => $this->wp_get_option( 'blogname' ),
'woocommerce_allow_tracking' => $this->wp_get_option( 'woocommerce_allow_tracking' ),
'woocommerce_onboarding_profile' => $this->wp_get_option( 'woocommerce_onboarding_profile', array() ),
'woocommerce_default_country' => $this->wp_get_option( 'woocommerce_default_country' ),
)
);
$step->set_meta_values(
array(
'plugin' => 'woocommerce',
'alias' => $this->get_alias(),
)
);
return $step;
}
/**
* Get the step name
*
* @return string
*/
public function get_step_name() {
return 'setSiteOptions';
}
/**
* Get the alias
*
* @return string
*/
public function get_alias() {
return 'setWCCoreProfilerOptions';
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCPaymentGateways;
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
use Automattic\WooCommerce\Blueprint\Steps\Step;
/**
* ExportWCPaymentGateways class
*/
class ExportWCPaymentGateways implements StepExporter {
/**
* Payment gateway IDs to exclude from export
*
* @var array|string[] Payment gateway IDs to exclude from export
*/
protected array $exclude_ids = array( 'pre_install_woocommerce_payments_promotion' );
/**
* Export the step
*
* @return Step
*/
public function export(): Step {
$step = new SetWCPaymentGateways();
$this->maybe_hide_wcpay_gateways();
foreach ( $this->get_wc_payment_gateways() as $id => $payment_gateway ) {
if ( in_array( $id, $this->exclude_ids, true ) ) {
continue;
}
$step->add_payment_gateway(
$id,
$payment_gateway->get_title(),
$payment_gateway->get_description(),
$payment_gateway->is_available() ? 'yes' : 'no'
);
}
return $step;
}
/**
* Return the payment gateways resgietered in WooCommerce
*
* @return string
*/
public function get_wc_payment_gateways() {
return WC()->payment_gateways->payment_gateways();
}
/**
* Get the step name
*
* @return string
*/
public function get_step_name() {
return SetWCPaymentGateways::get_step_name();
}
/**
* Maybe hide WooCommerce Payments gateways
*
* @return void
*/
protected function maybe_hide_wcpay_gateways() {
if ( class_exists( 'WC_Payments' ) ) {
\WC_Payments::hide_gateways_on_settings_page();
}
}
}

View File

@@ -0,0 +1,224 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
use Automattic\WooCommerce\Blueprint\Exporters\HasAlias;
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions;
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
use Automattic\WooCommerce\Blueprint\Util;
use WC_Admin_Settings;
use WC_Settings_Page;
/**
* Class ExportWCSettings
*
* This class exports WooCommerce settings and implements the StepExporter and HasAlias interfaces.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters
*/
class ExportWCSettings implements StepExporter, HasAlias {
use UseWPFunctions;
/**
* Array of WC_Settings_Page objects.
*
* @var WC_Settings_Page[]
*/
private array $setting_pages;
/**
* Array of page IDs to exclude from export.
*
* @var array
*/
private array $exclude_pages = array( 'integration', 'site-visibility' );
/**
* Constructor.
*
* @param array $setting_pages Optional array of setting pages.
*/
public function __construct( array $setting_pages = array() ) {
if ( empty( $setting_pages ) ) {
$setting_pages = WC_Admin_Settings::get_settings_pages();
}
$this->setting_pages = $setting_pages;
$this->wp_add_filter( 'wooblueprint_export_settings', array( $this, 'add_site_visibility_settings' ), 10, 3 );
}
/**
* Export WooCommerce settings.
*
* @return SetSiteOptions
*/
public function export() {
$pages = array();
$options = array();
$option_info = array();
foreach ( $this->setting_pages as $page ) {
$id = $page->get_id();
if ( in_array( $id, $this->exclude_pages, true ) ) {
continue;
}
$pages[ $id ] = $this->get_page_info( $page );
foreach ( $pages[ $id ]['options'] as $option ) {
$options[ $option['id'] ] = $option['value'];
$option_info[ $option['id'] ] = array(
'location' => $option['location'],
'title' => $option['title'],
);
}
unset( $pages[ $id ]['options'] );
}
$filtered = $this->wp_apply_filters( 'wooblueprint_export_settings', $options, $pages, $option_info );
$step = new SetSiteOptions( $filtered['options'] );
$step->set_meta_values(
array(
'plugin' => 'woocommerce',
'pages' => $filtered['pages'],
'info' => $option_info,
'alias' => $this->get_alias(),
)
);
return $step;
}
/**
* Get information about a settings page.
*
* @param WC_Settings_Page $page The settings page.
* @return array
*/
protected function get_page_info( WC_Settings_Page $page ) {
$info = array(
'label' => $page->get_label(),
'sections' => array(),
);
foreach ( $page->get_sections() as $id => $section ) {
$section_id = Util::camel_to_snake( strtolower( $section ) );
$info['sections'][ $section_id ] = array(
'label' => $section,
'subsections' => array(),
);
$settings = $page->get_settings_for_section( $id );
// Get subsections.
$subsections = array_filter(
$settings,
function ( $setting ) {
return isset( $setting['type'] ) && 'title' === $setting['type'] && isset( $setting['title'] );
}
);
foreach ( $subsections as $subsection ) {
if ( ! isset( $subsection['id'] ) ) {
$subsection['id'] = Util::camel_to_snake( strtolower( $subsection['title'] ) );
}
$info['sections'][ $section_id ]['subsections'][ $subsection['id'] ] = array(
'label' => $subsection['title'],
);
}
// Get options.
$info['options'] = $this->get_page_section_settings( $settings, $page->get_id(), $section_id );
}
return $info;
}
/**
* Get settings for a specific page section.
*
* @param array $settings The settings.
* @param string $page The page ID.
* @param string $section The section ID.
* @return array
*/
private function get_page_section_settings( $settings, $page, $section = '' ) {
$current_title = '';
$data = array();
foreach ( $settings as $setting ) {
if ( 'sectionend' === $setting['type'] || 'slotfill_placeholder' === $setting['type'] || ! isset( $setting['id'] ) ) {
continue;
}
if ( 'title' === $setting['type'] ) {
$current_title = Util::camel_to_snake( strtolower( $setting['title'] ) );
} else {
$location = $page . '.' . $section;
if ( $current_title ) {
$location .= '.' . $current_title;
}
$data[] = array(
'id' => $setting['id'],
'value' => $this->wp_get_option( $setting['id'], $setting['default'] ?? null ),
'title' => $setting['title'] ?? $setting['desc'] ?? '',
'location' => $location,
);
}
}
return $data;
}
/**
* Add site visibility settings.
*
* @param array $options The options array.
* @param array $pages The pages array.
* @param array $option_info The option information array.
* @return array
*/
public function add_site_visibility_settings( array $options, array $pages, array $option_info ) {
$pages['site_visibility'] = array(
'label' => 'Site Visibility',
'sections' => array(
'general' => array(
'label' => 'General',
),
),
);
$options['woocommerce_coming_soon'] = $this->wp_get_option( 'woocommerce_coming_soon' );
$options['woocommerce_store_pages_only'] = $this->wp_get_option( 'woocommerce_store_pages_only' );
$option_info['woocommerce_coming_soon'] = array(
'location' => 'site_visibility.general',
'title' => 'Coming soon',
);
$option_info['woocommerce_store_pages_only'] = array(
'location' => 'site_visibility.general',
'title' => 'Apply to store pages only',
);
return compact( 'options', 'pages', 'option_info' );
}
/**
* Get the name of the step.
*
* @return string
*/
public function get_step_name() {
return 'setSiteOptions';
}
/**
* Get the alias for this exporter.
*
* @return string
*/
public function get_alias() {
return 'setWCSettings';
}
}

View File

@@ -0,0 +1,163 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCShipping;
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
use Automattic\WooCommerce\Blueprint\Util;
/**
* Class ExportWCShipping
*
* This class exports WooCommerce shipping settings and implements the StepExporter interface.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters
*/
class ExportWCShipping implements StepExporter {
/**
* Export WooCommerce shipping settings.
*
* @return SetWCShipping
*/
public function export() {
global $wpdb;
// Fetch shipping classes from the database.
$classes = $wpdb->get_results(
"
SELECT *
FROM {$wpdb->prefix}term_taxonomy
WHERE taxonomy = 'product_shipping_class'
"
);
$term_ids = array();
// Collect term IDs.
foreach ( $classes as $term ) {
$term_ids[] = (int) $term->term_id;
}
$term_ids = implode( ', ', $term_ids );
// Fetch terms based on term IDs.
if ( ! empty( $term_ids ) ) {
$terms = $wpdb->get_results(
$wpdb->prepare(
"
SELECT *
FROM {$wpdb->prefix}terms
WHERE term_id IN (%s)
",
$term_ids
)
);
} else {
$terms = array();
}
// Fetch local pickup settings.
$local_pickup = array(
'general' => get_option( 'woocommerce_pickup_location_settings', array() ),
'locations' => get_option( 'pickup_location_pickup_locations', array() ),
);
if ( empty( $local_pickup['general'] ) ) {
$local_pickup['general'] = new \stdClass();
}
// Fetch shipping zones from the database.
$zones = $wpdb->get_results(
"
SELECT *
FROM {$wpdb->prefix}woocommerce_shipping_zones
"
);
// Fetch shipping zone methods from the database.
$methods = $wpdb->get_results(
"
SELECT *
FROM {$wpdb->prefix}woocommerce_shipping_zone_methods
"
);
// Fetch shipping method options.
// Each method has a corresponding option in the options table.
$method_options = $wpdb->get_results(
"
SELECT *
FROM {$wpdb->prefix}options
WHERE option_name LIKE 'woocommerce_flat_rate_%_settings'
or option_name LIKE 'woocommerce_free_shipping_%_settings'
",
ARRAY_A
);
$method_options = Util::index_array(
$method_options,
function ( $key, $option ) {
return $option['option_name'];
}
);
foreach ( $methods as $method ) {
$key_name = 'woocommerce_' . $method->method_id . '_' . $method->instance_id . '_settings';
if ( isset( $method_options[ $key_name ] ) ) {
$method->settings = array(
'option_name' => $key_name,
'option_value' => maybe_unserialize( $method_options[ $key_name ]['option_value'] ),
);
}
}
$methods_by_zone_id = array();
// Organize methods by zone ID.
foreach ( $methods as $method ) {
if ( ! isset( $methods_by_zone_id[ $method->zone_id ] ) ) {
$methods_by_zone_id[ $method->zone_id ] = array();
}
$methods_by_zone_id[ $method->zone_id ][] = $method->method_id;
}
// Fetch shipping zone locations from the database.
$locations = $wpdb->get_results(
"
SELECT *
FROM {$wpdb->prefix}woocommerce_shipping_zone_locations
"
);
$locations_by_zone_id = array();
// Organize locations by zone ID.
foreach ( $locations as $location ) {
if ( ! isset( $locations_by_zone_id[ $location->zone_id ] ) ) {
$locations_by_zone_id[ $location->zone_id ] = array();
}
$locations_by_zone_id[ $location->zone_id ][] = $location->location_id;
}
// Create a new SetWCShipping step with the fetched data.
$step = new SetWCShipping( $methods, $locations, $zones, $terms, $classes, $local_pickup );
$step->set_meta_values(
array(
'plugin' => 'woocommerce',
)
);
return $step;
}
/**
* Get the name of the step.
*
* @return string
*/
public function get_step_name() {
return SetWCShipping::get_step_name();
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
use Automattic\WooCommerce\Blueprint\Exporters\ExportsStep;
use Automattic\WooCommerce\Blueprint\Exporters\HasAlias;
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions;
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
/**
* Class ExportWCTaskOptions
*
* This class exports WooCommerce task options and implements the StepExporter and HasAlias interfaces.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters
*/
class ExportWCTaskOptions implements StepExporter, HasAlias {
use UseWPFunctions;
/**
* Export WooCommerce task options.
*
* @return SetSiteOptions
*/
public function export() {
$step = new SetSiteOptions(
array(
'woocommerce_admin_customize_store_completed' => $this->wp_get_option( 'woocommerce_admin_customize_store_completed', 'no' ),
'woocommerce_task_list_tracked_completed_actions' => $this->wp_get_option( 'woocommerce_task_list_tracked_completed_actions', array() ),
)
);
$step->set_meta_values(
array(
'plugin' => 'woocommerce',
'alias' => $this->get_alias(),
)
);
return $step;
}
/**
* Get the name of the step.
*
* @return string
*/
public function get_step_name() {
return 'setOptions';
}
/**
* Get the alias for this exporter.
*
* @return string
*/
public function get_alias() {
return 'setWCTaskOptions';
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Exporters;
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCTaxRates;
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
/**
* Class ExportWCTaxRates
*
* This class exports WooCommerce tax rates and implements the StepExporter interface.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Exporters
*/
class ExportWCTaxRates implements StepExporter {
/**
* Export WooCommerce tax rates.
*
* @return SetWCTaxRates
*/
public function export() {
global $wpdb;
// Fetch tax rates from the database.
$rates = $wpdb->get_results(
"
SELECT *
FROM {$wpdb->prefix}woocommerce_tax_rates as tax_rates
",
ARRAY_A
);
// Fetch tax rate locations from the database.
$locations = $wpdb->get_results(
"
SELECT *
FROM {$wpdb->prefix}woocommerce_tax_rate_locations as locations
",
ARRAY_A
);
// Create a new SetWCTaxRates step with the fetched data.
$step = new SetWCTaxRates( $rates, $locations );
$step->set_meta_values(
array(
'plugin' => 'woocommerce',
)
);
return $step;
}
/**
* Get the name of the step.
*
* @return string
*/
public function get_step_name() {
return 'setWCTaxRates';
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Importers;
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCPaymentGateways;
use Automattic\WooCommerce\Blueprint\StepProcessor;
use Automattic\WooCommerce\Blueprint\StepProcessorResult;
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
/**
* Class ImportSetWCPaymentGateways
*
* This class imports WooCommerce payment gateways settings and implements the StepProcessor interface.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Importers
*/
class ImportSetWCPaymentGateways implements StepProcessor {
use UseWPFunctions;
/**
* Process the import of WooCommerce payment gateways settings.
*
* @param object $schema The schema object containing import details.
* @return StepProcessorResult
*/
public function process( $schema ): StepProcessorResult {
$result = StepProcessorResult::success( SetWCPaymentGateways::get_step_name() );
$payment_gateways = $this->get_wc_payment_gateways();
$fields = array( 'title', 'description', 'enabled' );
foreach ( $schema->payment_gateways as $id => $payment_gateway_data ) {
if ( ! isset( $payment_gateways[ $id ] ) ) {
$result->add_info( "Skipping {$id}. The payment gateway is not available" );
continue;
}
$payment_gateway = $payment_gateways[ $id ];
// Refer to https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/class-wc-ajax.php#L3564.
foreach ( $fields as $field ) {
if ( isset( $payment_gateway_data->{$field} ) ) {
$payment_gateway->update_option( $field, $payment_gateway_data->{$field} );
}
}
$result->add_info( "{$id} has been updated." );
$this->wp_do_action( 'woocommerce_update_options' );
}
return $result;
}
/**
* Return the payment gateways resgietered in WooCommerce
*
* @return string
*/
public function get_wc_payment_gateways() {
return WC()->payment_gateways->payment_gateways();
}
/**
* Get the class name for the step.
*
* @return string
*/
public function get_step_class(): string {
return SetWCPaymentGateways::class;
}
}

View File

@@ -0,0 +1,146 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Importers;
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCShipping;
use Automattic\WooCommerce\Blueprint\StepProcessor;
use Automattic\WooCommerce\Blueprint\StepProcessorResult;
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
use WC_Tax;
/**
* Class ImportSetWCShipping
*
* This class imports WooCommerce shipping settings and implements the StepProcessor interface.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Importers
*/
class ImportSetWCShipping implements StepProcessor {
use UseWPFunctions;
/**
* Process the import of WooCommerce shipping settings.
*
* @param object $schema The schema object containing import details.
* @return StepProcessorResult
*/
public function process( $schema ): StepProcessorResult {
$result = StepProcessorResult::success( SetWCShipping::get_step_name() );
$fields = array(
'terms' => array( 'terms', array( '%d', '%s', '%s', '%d' ) ),
'classes' => array( 'term_taxonomy', array( '%d', '%d', '%s', '%s', '%d', '%d' ) ),
'shipping_zones' => array( 'woocommerce_shipping_zones', array( '%d', '%s', '%d' ) ),
'shipping_methods' => array( 'woocommerce_shipping_zone_methods', array( '%d', '%d', '%s', '%d', '%d' ) ),
'shipping_locations' => array( 'woocommerce_shipping_zone_locations', array( '%d', '%d', '%s', '%s' ) ),
);
foreach ( $fields as $name => $data ) {
if ( isset( $schema->values->{$name} ) ) {
$filter_method = 'filter_' . $name . '_data';
if ( method_exists( $this, $filter_method ) ) {
$insert_values = $this->$filter_method( $schema->values->{$name} );
} else {
$insert_values = $schema->values->{$name};
}
$this->insert( $data[0], $data[1], $insert_values );
// check if function with process_$name exist and call it.
$method = 'post_process_' . $name;
if ( method_exists( $this, $method ) ) {
$this->$method( $schema->values->{$name} );
}
}
}
if ( isset( $schema->values->local_pickup ) ) {
$this->add_local_pickup( $schema->values->local_pickup );
}
return $result;
}
/**
* Filter shipping methods data.
*
* @param array $methods The shipping methods.
*
* @return mixed
*/
protected function filter_shipping_methods_data( $methods ) {
return array_map(
function ( $method ) {
unset( $method->settings );
return $method;
},
$methods
);
}
/**
* Post process shipping methods.
*
* @param array $methods The shipping methods.
*
* @return void
*/
protected function post_process_shipping_methods( $methods ) {
foreach ( $methods as $method ) {
if ( isset( $method->settings ) ) {
update_option( $method->option_name, $method->option_value );
}
}
}
/**
* Insert data into the specified table.
*
* @param string $table The table name.
* @param array $format The data format.
* @param array $rows The rows to insert.
* @global \wpdb $wpdb WordPress database abstraction object.
* @return array The IDs of the inserted rows.
*/
protected function insert( $table, $format, $rows ) {
global $wpdb;
$inserted_ids = array();
$table = $wpdb->prefix . $table;
$format = implode( ', ', $format );
foreach ( $rows as $row ) {
$row = (array) $row;
$columns = implode( ', ', array_keys( $row ) );
// phpcs:ignore
$sql = $wpdb->prepare( "REPLACE INTO $table ($columns) VALUES ($format)", $row );
// phpcs:ignore
$wpdb->query( $sql );
}
return $inserted_ids;
}
/**
* Add local pickup settings.
*
* @param object $local_pickup The local pickup settings.
*/
private function add_local_pickup( $local_pickup ) {
if ( isset( $local_pickup->general ) ) {
$this->wp_update_option( 'woocommerce_pickup_location_settings', (array) $local_pickup->general );
}
if ( isset( $local_pickup->locations ) ) {
$local_pickup->locations = json_decode( wp_json_encode( $local_pickup->locations ), true );
$this->wp_update_option( 'pickup_location_pickup_locations', $local_pickup->locations );
}
}
/**
* Get the class name for the step.
*
* @return string
*/
public function get_step_class(): string {
return SetWCShipping::class;
}
}

View File

@@ -0,0 +1,124 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Importers;
use Automattic\WooCommerce\Admin\Features\Blueprint\Steps\SetWCTaxRates;
use Automattic\WooCommerce\Blueprint\StepProcessor;
use Automattic\WooCommerce\Blueprint\StepProcessorResult;
use WC_Tax;
/**
* Class ImportSetWCTaxRates
*
* This class imports WooCommerce tax rates and implements the StepProcessor interface.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Importers
*/
class ImportSetWCTaxRates implements StepProcessor {
/**
* The result of the step processing.
*
* @var StepProcessorResult $result The result of the step processing.
*/
private StepProcessorResult $result;
/**
* Process the import of WooCommerce tax rates.
*
* @param object $schema The schema object containing import details.
* @return StepProcessorResult
*/
public function process( $schema ): StepProcessorResult {
$this->result = StepProcessorResult::success( SetWCTaxRates::get_step_name() );
foreach ( $schema->values->rates as $rate ) {
$this->add_rate( $rate );
}
foreach ( $schema->values->locations as $location ) {
$this->add_location( $location );
}
return $this->result;
}
/**
* Check if a tax rate exists in the database.
*
* @param int $id The tax rate ID.
* @global \wpdb $wpdb WordPress database abstraction object.
* @return array|null The tax rate row if found, null otherwise.
*/
protected function exist( $id ) {
global $wpdb;
return $wpdb->get_row(
$wpdb->prepare(
"
SELECT *
FROM {$wpdb->prefix}woocommerce_tax_rates
WHERE tax_rate_id = %d
",
$id
),
ARRAY_A
);
}
/**
* Add a tax rate to the database.
*
* @param object $rate The tax rate object.
* @return int|false The tax rate ID if successfully added, false otherwise.
*/
protected function add_rate( $rate ) {
$tax_rate = (array) $rate;
if ( $this->exist( $tax_rate['tax_rate_id'] ) ) {
$this->result->add_info( "Tax rate with I.D {$tax_rate['tax_rate_id']} already exists. Skipped creating it." );
return false;
}
$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
if ( isset( $rate->postcode ) ) {
$postcode = array_map( 'wc_clean', explode( ';', $rate->postcode ) );
$postcode = array_map( 'wc_normalize_postcode', $postcode );
WC_Tax::_update_tax_rate_postcodes( $tax_rate_id, $postcode );
}
if ( isset( $rate->city ) ) {
$cities = explode( ';', $rate->city );
WC_Tax::_update_tax_rate_cities( $tax_rate_id, array_map( 'wc_clean', array_map( 'wp_unslash', $cities ) ) );
}
return $tax_rate_id;
}
/**
* Add a tax rate location to the database.
*
* @param object $location The location object.
* @global \wpdb $wpdb WordPress database abstraction object.
*/
public function add_location( $location ) {
global $wpdb;
$location = (array) $location;
$columns = implode( ',', array_keys( $location ) );
$format = implode( ',', array( '%d', '%s', '%d', '%s' ) );
$table = $wpdb->prefix . 'woocommerce_tax_rate_locations';
// phpcs:ignore
$sql = $wpdb->prepare( "REPLACE INTO $table ($columns) VALUES ($format)", $location );
// phpcs:ignore
$wpdb->query( $sql );
}
/**
* Get the class name for the step.
*
* @return string
*/
public function get_step_class(): string {
return SetWCTaxRates::class;
}
}

View File

@@ -0,0 +1,115 @@
<?php
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Admin\Features\Blueprint;
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCCoreProfilerOptions;
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCPaymentGateways;
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettings;
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCShipping;
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCTaskOptions;
use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCTaxRates;
use Automattic\WooCommerce\Admin\Features\Blueprint\Importers\ImportSetWCPaymentGateways;
use Automattic\WooCommerce\Admin\Features\Blueprint\Importers\ImportSetWCShipping;
use Automattic\WooCommerce\Admin\Features\Blueprint\Importers\ImportSetWCTaxRates;
use Automattic\WooCommerce\Admin\PageController;
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
use Automattic\WooCommerce\Blueprint\StepProcessor;
/**
* Class Init
*
* This class initializes the Blueprint feature for WooCommerce.
*/
class Init {
/**
* Init constructor.
*/
public function __construct() {
add_action( 'rest_api_init', array( $this, 'init_rest_api' ) );
add_filter( 'woocommerce_admin_shared_settings', array( $this, 'add_upload_nonce_to_settings' ) );
add_filter(
'wooblueprint_export_landingpage',
function () {
return 'admin.php?page=wc-admin';
}
);
add_filter( 'wooblueprint_exporters', array( $this, 'add_woo_exporters' ) );
add_filter( 'wooblueprint_importers', array( $this, 'add_woo_importers' ) );
}
/**
* Register REST API routes.
*
* @return void
*/
public function init_rest_api() {
( new RestApi() )->register_routes();
}
/**
* Add upload nonce to global JS settings.
*
* The value can be accessed at wcSettings.admin.blueprint_upload_nonce
*
* @param array $settings Global JS settings.
*
* @return array
*/
public function add_upload_nonce_to_settings( array $settings ) {
if ( ! is_admin() ) {
return $settings;
}
$page_id = PageController::get_instance()->get_current_screen_id();
if ( 'woocommerce_page_wc-admin' === $page_id ) {
$settings['blueprint_upload_nonce'] = wp_create_nonce( 'blueprint_upload_nonce' );
return $settings;
}
return $settings;
}
/**
* Add Woo Specific Exporters.
*
* @param StepExporter[] $exporters Array of step exporters.
*
* @return StepExporter[]
*/
public function add_woo_exporters( array $exporters ) {
return array_merge(
$exporters,
array(
new ExportWCCoreProfilerOptions(),
new ExportWCSettings(),
new ExportWCPaymentGateways(),
new ExportWCShipping(),
new ExportWCTaskOptions(),
new ExportWCTaxRates(),
)
);
}
/**
* Add Woo Specific Importers.
*
* @param StepProcessor[] $importers Array of step processors.
*
* @return array
*/
public function add_woo_importers( array $importers ) {
return array_merge(
$importers,
array(
new ImportSetWCPaymentGateways(),
new ImportSetWCShipping(),
new ImportSetWCTaxRates(),
)
);
}
}

View File

@@ -0,0 +1,213 @@
<?php
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Admin\Features\Blueprint;
use Automattic\WooCommerce\Blueprint\ExportSchema;
use Automattic\WooCommerce\Blueprint\ImportSchema;
use Automattic\WooCommerce\Blueprint\JsonResultFormatter;
use Automattic\WooCommerce\Blueprint\ZipExportedSchema;
/**
* Class RestApi
*
* This class handles the REST API endpoints for importing and exporting WooCommerce Blueprints.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint
*/
class RestApi {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'blueprint';
/**
* Register routes.
*
* @since 9.3.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/import',
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'import' ),
'permission_callback' => array( $this, 'check_permission' ),
),
)
);
register_rest_route(
$this->namespace,
'/export',
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'export' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'steps' => array(
'description' => __( 'A list of plugins to install', 'woocommerce' ),
'type' => 'array',
'items' => 'string',
'default' => array(),
'sanitize_callback' => function ( $value ) {
return array_map(
function ( $value ) {
return sanitize_text_field( $value );
},
$value
);
},
'required' => false,
),
'export_as_zip' => array(
'description' => __( 'Export as a zip file', 'woocommerce' ),
'type' => 'boolean',
'default' => false,
'required' => false,
),
),
),
)
);
}
/**
* Check if the current user has permission to perform the request.
*
* @return bool|\WP_Error
*/
public function check_permission() {
if ( ! current_user_can( 'install_plugins' ) ) {
return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Handle the export request.
*
* @param \WP_REST_Request $request The request object.
* @return \WP_HTTP_Response The response object.
*/
public function export( $request ) {
$steps = $request->get_param( 'steps' );
$export_as_zip = $request->get_param( 'export_as_zip' );
$exporter = new ExportSchema();
$data = $exporter->export( $steps, $export_as_zip );
if ( $export_as_zip ) {
$zip = new ZipExportedSchema( $data );
$data = $zip->zip();
$data = site_url( str_replace( ABSPATH, '', $data ) );
}
return new \WP_HTTP_Response(
array(
'data' => $data,
'type' => $export_as_zip ? 'zip' : 'json',
)
);
}
/**
* Handle the import request.
*
* @return \WP_HTTP_Response The response object.
* @throws \InvalidArgumentException If the import fails.
*/
public function import() {
// Check for nonce to prevent CSRF.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
if ( ! isset( $_POST['blueprint_upload_nonce'] ) || ! \wp_verify_nonce( $_POST['blueprint_upload_nonce'], 'blueprint_upload_nonce' ) ) {
return new \WP_HTTP_Response(
array(
'status' => 'error',
'message' => __( 'Invalid nonce', 'woocommerce' ),
),
400
);
}
// phpcs:ignore
if ( ! empty( $_FILES['file'] ) && $_FILES['file']['error'] === UPLOAD_ERR_OK ) {
// phpcs:ignore
$uploaded_file = $_FILES['file']['tmp_name'];
// phpcs:ignore
$mime_type = $_FILES['file']['type'];
if ( 'application/json' !== $mime_type && 'application/zip' !== $mime_type ) {
return new \WP_HTTP_Response(
array(
'status' => 'error',
'message' => __( 'Invalid file type', 'woocommerce' ),
),
400
);
}
try {
// phpcs:ignore
if ( $mime_type === 'application/zip' ) {
// phpcs:ignore
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
$movefile = \wp_handle_upload( $_FILES['file'], array( 'test_form' => false ) );
if ( $movefile && ! isset( $movefile['error'] ) ) {
$blueprint = ImportSchema::create_from_zip( $movefile['file'] );
} else {
throw new InvalidArgumentException( $movefile['error'] );
}
} else {
$blueprint = ImportSchema::create_from_json( $uploaded_file );
}
} catch ( \Exception $e ) {
return new \WP_HTTP_Response(
array(
'status' => 'error',
'message' => $e->getMessage(),
),
400
);
}
$results = $blueprint->import();
$result_formatter = new JsonResultFormatter( $results );
$redirect = $blueprint->get_schema()->landingPage ?? null;
$redirect_url = $redirect->url ?? 'admin.php?page=wc-admin';
$is_success = $result_formatter->is_success() ? 'success' : 'error';
return new \WP_HTTP_Response(
array(
'status' => $is_success,
'message' => 'error' === $is_success ? __( 'There was an error while processing your schema', 'woocommerce' ) : 'success',
'data' => array(
'redirect' => admin_url( $redirect_url ),
'result' => $result_formatter->format(),
),
),
200
);
}
return new \WP_HTTP_Response(
array(
'status' => 'error',
'message' => __( 'No file uploaded', 'woocommerce' ),
),
400
);
}
}

View File

@@ -0,0 +1,111 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Steps;
use Automattic\WooCommerce\Blueprint\Steps\Step;
/**
* Class SetWCPaymentGateways
*
* This class sets WooCommerce payment gateways and extends the Step class.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Steps
*/
class SetWCPaymentGateways extends Step {
/**
* Payment gateways.
*
* @var array $payment_gateways Array of payment gateways.
*/
protected array $payment_gateways = array();
/**
* Constructor.
*
* @param array $payment_gateways Optional array of payment gateways.
*/
public function __construct( array $payment_gateways = array() ) {
$this->payment_gateways = $payment_gateways;
}
/**
* Add a payment gateway.
*
* @param string $id The ID of the payment gateway.
* @param string $title The title of the payment gateway.
* @param string $description The description of the payment gateway.
* @param string $enabled Whether the payment gateway is enabled ('yes' or 'no').
*/
public function add_payment_gateway( $id, $title, $description, $enabled ) {
$this->payment_gateways[ $id ] = array(
'title' => $title,
'description' => $description,
'enabled' => $enabled,
);
}
/**
* Get the name of the step.
*
* @return string
*/
public static function get_step_name(): string {
return 'setWCPaymentGateways';
}
/**
* Get the schema for the step.
*
* @param int $version Optional version number of the schema.
* @return array The schema array.
*/
public static function get_schema( $version = 1 ): array {
return array(
'type' => 'object',
'properties' => array(
'step' => array(
'type' => 'string',
'enum' => array( 'setWCPaymentGateways' ),
),
'payment_gateways' => array(
'type' => 'object',
'patternProperties' => array(
'^[a-zA-Z0-9_]+$' => array(
'type' => 'object',
'properties' => array(
'title' => array(
'type' => 'string',
),
'description' => array(
'type' => 'string',
),
'enabled' => array(
'type' => 'string',
'enum' => array( 'yes', 'no' ),
),
),
'required' => array( 'title', 'description', 'enabled' ),
),
),
'additionalProperties' => false,
),
),
'required' => array( 'step', 'payment_gateways' ),
);
}
/**
* Prepare the JSON array for the step.
*
* @return array The JSON array.
*/
public function prepare_json_array(): array {
return array(
'step' => static::get_step_name(),
'payment_gateways' => $this->payment_gateways,
);
}
}

View File

@@ -0,0 +1,232 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Steps;
use Automattic\WooCommerce\Blueprint\Steps\Step;
/**
* Class SetWCShipping
*
* This class sets WooCommerce shipping settings and extends the Step class.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Steps
*/
class SetWCShipping extends Step {
/**
* Shipping methods.
*
* @var array $methods Shipping methods.
*/
private array $methods;
/**
* Shipping locations.
*
* @var array $locations Shipping locations.
*/
private array $locations;
/**
* Shipping zones.
*
* @var array $zones Shipping zones.
*/
private array $zones;
/**
* Shipping terms.
*
* @var array $terms Shipping terms.
*/
private array $terms;
/**
* Shipping classes.
*
* @var array $classes Shipping classes.
*/
private array $classes;
/**
* Local pickup settings.
*
* @var array $local_pickup Local pickup settings.
*/
private array $local_pickup;
/**
* Constructor.
*
* @param array $methods Shipping methods.
* @param array $locations Shipping locations.
* @param array $zones Shipping zones.
* @param array $terms Shipping terms.
* @param array $classes Shipping classes.
* @param array $local_pickup Local pickup settings.
*/
public function __construct( array $methods, array $locations, array $zones, array $terms, array $classes, array $local_pickup ) {
$this->methods = $methods;
$this->locations = $locations;
$this->zones = $zones;
$this->terms = $terms;
$this->classes = $classes;
$this->local_pickup = $local_pickup;
}
/**
* Prepare the JSON array for the step.
*
* @return array The JSON array.
*/
public function prepare_json_array(): array {
return array(
'step' => static::get_step_name(),
'values' => array(
'shipping_methods' => $this->methods,
'shipping_locations' => $this->locations,
'shipping_zones' => $this->zones,
'terms' => $this->terms,
'classes' => $this->classes,
'local_pickup' => $this->local_pickup,
),
);
}
/**
* Get the name of the step.
*
* @return string
*/
public static function get_step_name(): string {
return 'setWCShipping';
}
/**
* Get the schema for the step.
*
* @param int $version Optional version number of the schema.
* @return array The schema array.
*/
public static function get_schema( $version = 1 ): array {
return array(
'type' => 'object',
'properties' => array(
'step' => array(
'type' => 'string',
'enum' => array( static::get_step_name() ),
),
'values' => array(
'type' => 'object',
'properties' => array(
'classes' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'term_taxonomy_id' => array( 'type' => 'string' ),
'term_id' => array( 'type' => 'string' ),
'taxonomy' => array( 'type' => 'string' ),
'description' => array( 'type' => 'string' ),
'parent' => array( 'type' => 'string' ),
'count' => array( 'type' => 'string' ),
),
'required' => array( 'term_taxonomy_id', 'term_id', 'taxonomy', 'description', 'parent', 'count' ),
),
),
'terms' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'term_id' => array( 'type' => 'string' ),
'name' => array( 'type' => 'string' ),
'slug' => array( 'type' => 'string' ),
'term_group' => array( 'type' => 'string' ),
),
'required' => array( 'term_id', 'name', 'slug', 'term_group' ),
),
),
'local_pickup' => array(
'type' => 'object',
'properties' => array(
'general' => array(
'type' => 'object',
'properties' => array(
'enabled' => array( 'type' => 'string' ),
'title' => array( 'type' => 'string' ),
'tax_status' => array( 'type' => 'string' ),
'cost' => array( 'type' => 'string' ),
),
),
'locations' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'name' => array( 'type' => 'string' ),
'address' => array(
'type' => 'object',
'properties' => array(
'address_1' => array( 'type' => 'string' ),
'city' => array( 'type' => 'string' ),
'state' => array( 'type' => 'string' ),
'postcode' => array( 'type' => 'string' ),
'country' => array( 'type' => 'string' ),
),
),
'details' => array( 'type' => 'string' ),
'enabled' => array( 'type' => 'boolean' ),
),
),
),
),
),
'shipping_methods' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'zone_id' => array( 'type' => 'string' ),
'instance_id' => array( 'type' => 'string' ),
'method_id' => array( 'type' => 'string' ),
'method_order' => array( 'type' => 'string' ),
'is_enabled' => array( 'type' => 'string' ),
),
'required' => array( 'zone_id', 'instance_id', 'method_id', 'method_order', 'is_enabled' ),
),
),
'shipping_locations' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'location_id' => array( 'type' => 'string' ),
'zone_id' => array( 'type' => 'string' ),
'location_code' => array( 'type' => 'string' ),
'location_type' => array( 'type' => 'string' ),
),
'required' => array( 'location_id', 'zone_id', 'location_code', 'location_type' ),
),
),
'shipping_zones' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'zone_id' => array( 'type' => 'string' ),
'zone_name' => array( 'type' => 'string' ),
'zone_order' => array( 'type' => 'string' ),
),
'required' => array( 'zone_id', 'zone_name', 'zone_order' ),
),
),
),
),
),
'required' => array( 'step', 'values' ),
);
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Admin\Features\Blueprint\Steps;
use Automattic\WooCommerce\Blueprint\Steps\Step;
/**
* Class SetWCTaxRates
*
* This class sets WooCommerce tax rates and extends the Step class.
*
* @package Automattic\WooCommerce\Admin\Features\Blueprint\Steps
*/
class SetWCTaxRates extends Step {
/**
* Tax rates.
*
* @var array $rates Tax rates.
*/
private array $rates;
/**
* Tax rate locations.
*
* @var array $locations Tax rate locations.
*/
private array $locations;
/**
* Constructor.
*
* @param array $rates Tax rates.
* @param array $locations Tax rate locations.
*/
public function __construct( array $rates, array $locations ) {
$this->rates = $rates;
$this->locations = $locations;
}
/**
* Prepare the JSON array for the step.
*
* @return array The JSON array.
*/
public function prepare_json_array(): array {
return array(
'step' => static::get_step_name(),
'values' => array(
'rates' => $this->rates,
'locations' => $this->locations,
),
);
}
/**
* Get the name of the step.
*
* @return string
*/
public static function get_step_name(): string {
return 'setWCTaxRates';
}
/**
* Get the schema for the step.
*
* @param int $version Optional version number of the schema.
* @return array The schema array.
*/
public static function get_schema( $version = 1 ): array {
return array(
'type' => 'object',
'properties' => array(
'step' => array(
'type' => 'string',
'enum' => array( static::get_step_name() ),
),
'values' => array(
'type' => 'object',
'properties' => array(
'rates' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'tax_rate_id' => array( 'type' => 'string' ),
'tax_rate_country' => array( 'type' => 'string' ),
'tax_rate_state' => array( 'type' => 'string' ),
'tax_rate' => array( 'type' => 'string' ),
'tax_rate_name' => array( 'type' => 'string' ),
'tax_rate_priority' => array( 'type' => 'string' ),
'tax_rate_compound' => array( 'type' => 'string' ),
'tax_rate_shipping' => array( 'type' => 'string' ),
'tax_rate_order' => array( 'type' => 'string' ),
'tax_rate_class' => array( 'type' => 'string' ),
),
'required' => array(
'tax_rate_id',
'tax_rate_country',
'tax_rate_state',
'tax_rate',
'tax_rate_name',
'tax_rate_priority',
'tax_rate_compound',
'tax_rate_shipping',
'tax_rate_order',
'tax_rate_class',
),
),
),
'locations' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'location_id' => array( 'type' => 'string' ),
'location_code' => array( 'type' => 'string' ),
'tax_rate_id' => array( 'type' => 'string' ),
'location_type' => array( 'type' => 'string' ),
),
'required' => array( 'location_id', 'location_code', 'tax_rate_id', 'location_type' ),
),
),
),
'required' => array( 'rates' ),
),
),
'required' => array( 'step', 'values' ),
);
}
}

View File

@@ -5,12 +5,13 @@ namespace Automattic\WooCommerce\Admin\Features;
use Automattic\WooCommerce\Admin\PageController;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
use Automattic\WooCommerce\Admin\WCAdminHelper;
use Automattic\WooCommerce\Internal\Admin\WCAdminUser;
/**
* Takes care of Launch Your Store related actions.
*/
class LaunchYourStore {
const BANNER_DISMISS_USER_META_KEY = 'woocommerce_coming_soon_banner_dismissed';
const BANNER_DISMISS_USER_META_KEY = 'coming_soon_banner_dismissed';
/**
* Constructor.
*/
@@ -21,6 +22,7 @@ class LaunchYourStore {
add_action( 'init', array( $this, 'register_launch_your_store_user_meta_fields' ) );
add_filter( 'woocommerce_tracks_event_properties', array( $this, 'append_coming_soon_global_tracks' ), 10, 2 );
add_action( 'wp_login', array( $this, 'reset_woocommerce_coming_soon_banner_dismissed' ), 10, 2 );
add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) );
}
/**
@@ -160,7 +162,10 @@ class LaunchYourStore {
return false;
}
if ( get_user_meta( $current_user_id, self::BANNER_DISMISS_USER_META_KEY, true ) === 'yes' ) {
$has_dismissed_banner = WCAdminUser::get_user_data_field( $current_user_id, self::BANNER_DISMISS_USER_META_KEY )
// Remove this check in WC 9.4.
|| get_user_meta( $current_user_id, 'woocommerce_' . self::BANNER_DISMISS_USER_META_KEY, true ) === 'yes';
if ( $has_dismissed_banner ) {
return false;
}
@@ -198,6 +203,8 @@ class LaunchYourStore {
/**
* Register user meta fields for Launch Your Store.
*
* This should be removed in WC 9.4.
*/
public function register_launch_your_store_user_meta_fields() {
if ( ! $this->is_manager_or_admin() ) {
@@ -217,7 +224,7 @@ class LaunchYourStore {
register_meta(
'user',
self::BANNER_DISMISS_USER_META_KEY,
'woocommerce_coming_soon_banner_dismissed',
array(
'type' => 'string',
'description' => 'Indicate whether the user has dismissed the coming soon notice or not.',
@@ -227,6 +234,22 @@ class LaunchYourStore {
);
}
/**
* Register user meta fields for Launch Your Store.
*
* @param array $user_data_fields user data fields.
* @return array
*/
public function add_user_data_fields( $user_data_fields ) {
return array_merge(
$user_data_fields,
array(
'launch_your_store_tour_hidden',
self::BANNER_DISMISS_USER_META_KEY,
)
);
}
/**
* Reset 'woocommerce_coming_soon_banner_dismissed' user meta to 'no'.
*
@@ -236,9 +259,9 @@ class LaunchYourStore {
* @param object $user user object.
*/
public function reset_woocommerce_coming_soon_banner_dismissed( $user_login, $user ) {
$existing_meta = get_user_meta( $user->ID, self::BANNER_DISMISS_USER_META_KEY, true );
$existing_meta = WCAdminUser::get_user_data_field( $user->ID, self::BANNER_DISMISS_USER_META_KEY );
if ( 'yes' === $existing_meta ) {
update_user_meta( $user->ID, self::BANNER_DISMISS_USER_META_KEY, 'no' );
WCAdminUser::update_user_data_field( $user->ID, self::BANNER_DISMISS_USER_META_KEY, 'no' );
}
}
}

View File

@@ -2,6 +2,7 @@
/**
* WooCommerce Navigation Core Menu
*
* @deprecated 9.3.0 Navigation is no longer a feature and its classes will be removed in WooCommerce 9.4.
* @package Woocommerce Admin
*/

View File

@@ -2,6 +2,7 @@
/**
* WooCommerce Navigation Favorite
*
* @deprecated 9.3.0 Navigation is no longer a feature and its classes will be removed in WooCommerce 9.4.
* @package Woocommerce Navigation
*/

View File

@@ -2,17 +2,17 @@
/**
* Navigation Experience
*
* @deprecated 9.3.0 Navigation is no longer a feature and its classes will be removed in WooCommerce 9.4.
* @package Woocommerce Admin
*/
namespace Automattic\WooCommerce\Admin\Features\Navigation;
use Automattic\WooCommerce\Internal\Admin\Survey;
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Admin\Features\Navigation\Screen;
use Automattic\WooCommerce\Admin\Features\Navigation\Menu;
use Automattic\WooCommerce\Admin\Features\Navigation\CoreMenu;
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
use WC_Tracks;
/**
* Contains logic for the Navigation
@@ -23,115 +23,27 @@ class Init {
*/
const TOGGLE_OPTION_NAME = 'woocommerce_navigation_enabled';
/**
* Determines if the feature has been toggled on or off.
*
* @var boolean
*/
protected static $is_updated = false;
/**
* Hook into WooCommerce.
*/
public function __construct() {
add_action( 'update_option_' . self::TOGGLE_OPTION_NAME, array( $this, 'reload_page_on_toggle' ), 10, 2 );
add_action( 'woocommerce_settings_saved', array( $this, 'maybe_reload_page' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_opt_out_scripts' ) );
if ( Features::is_enabled( 'navigation' ) ) {
Menu::instance()->init();
CoreMenu::instance()->init();
Screen::instance()->init();
// Disable the option to turn off the feature.
update_option( self::TOGGLE_OPTION_NAME, 'no' );
if ( class_exists( 'WC_Tracks' ) ) {
WC_Tracks::record_event( 'deprecated_navigation_in_use' );
}
}
}
/**
* Add the feature toggle to the features settings.
* Create a deprecation notice.
*
* @deprecated 7.0 The WooCommerce Admin features are now handled by the WooCommerce features engine (see the FeaturesController class).
*
* @param array $features Feature sections.
* @return array
* @param string $fcn The function that is deprecated.
*/
public static function add_feature_toggle( $features ) {
return $features;
}
/**
* Determine if sufficient versions are present to support Navigation feature
*/
public function is_nav_compatible() {
include_once ABSPATH . 'wp-admin/includes/plugin.php';
$gutenberg_minimum_version = '9.0.0'; // https://github.com/WordPress/gutenberg/releases/tag/v9.0.0.
$wp_minimum_version = '5.6';
$has_gutenberg = is_plugin_active( 'gutenberg/gutenberg.php' );
$gutenberg_version = $has_gutenberg ? get_plugin_data( WP_PLUGIN_DIR . '/gutenberg/gutenberg.php' )['Version'] : false;
if ( $gutenberg_version && version_compare( $gutenberg_version, $gutenberg_minimum_version, '>=' ) ) {
return true;
}
// Get unmodified $wp_version.
include ABSPATH . WPINC . '/version.php';
// Strip '-src' from the version string. Messes up version_compare().
$wp_version = str_replace( '-src', '', $wp_version );
if ( version_compare( $wp_version, $wp_minimum_version, '>=' ) ) {
return true;
}
return false;
}
/**
* Reloads the page when the option is toggled to make sure all nav features are loaded.
*
* @param string $old_value Old value.
* @param string $value New value.
*/
public static function reload_page_on_toggle( $old_value, $value ) {
if ( $old_value === $value ) {
return;
}
if ( 'yes' !== $value ) {
update_option( 'woocommerce_navigation_show_opt_out', 'yes' );
}
self::$is_updated = true;
}
/**
* Reload the page if the setting has been updated.
*/
public static function maybe_reload_page() {
if ( ! isset( $_SERVER['REQUEST_URI'] ) || ! self::$is_updated ) {
return;
}
wp_safe_redirect( wp_unslash( $_SERVER['REQUEST_URI'] ) );
exit();
}
/**
* Enqueue the opt out scripts.
*/
public function maybe_enqueue_opt_out_scripts() {
if ( get_option( 'woocommerce_navigation_show_opt_out', 'no' ) !== 'yes' ) {
return;
}
WCAdminAssets::register_style( 'navigation-opt-out', 'style', array( 'wp-components' ) );
WCAdminAssets::register_script( 'wp-admin-scripts', 'navigation-opt-out', true );
wp_localize_script(
'wc-admin-navigation-opt-out',
'surveyData',
array(
'url' => Survey::get_url( '/new-navigation-opt-out' ),
)
);
delete_option( 'woocommerce_navigation_show_opt_out' );
public static function deprecation_notice( $fcn ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log( 'Automattic\WooCommerce\Admin\Features\Navigation\\' . $fcn . ' is deprecated since 9.3 with no alternative. Navigation classes will be removed in WooCommerce 9.4' );
}
}

View File

@@ -2,6 +2,7 @@
/**
* WooCommerce Navigation Menu
*
* @deprecated 9.3.0 Navigation is no longer a feature and its classes will be removed in WooCommerce 9.4.
* @package Woocommerce Navigation
*/
@@ -10,6 +11,7 @@ namespace Automattic\WooCommerce\Admin\Features\Navigation;
use Automattic\WooCommerce\Admin\Features\Navigation\Favorites;
use Automattic\WooCommerce\Admin\Features\Navigation\Screen;
use Automattic\WooCommerce\Admin\Features\Navigation\CoreMenu;
use Automattic\WooCommerce\Admin\Features\Navigation\Init;
/**
* Contains logic for the WooCommerce Navigation menu.
@@ -95,172 +97,33 @@ class Menu {
/**
* Init.
*
* @internal
*/
public function init() {
add_action( 'admin_menu', array( $this, 'add_core_items' ), 100 );
add_filter( 'admin_enqueue_scripts', array( $this, 'enqueue_data' ), 20 );
add_filter( 'admin_menu', array( $this, 'migrate_core_child_items' ), PHP_INT_MAX - 1 );
add_filter( 'admin_menu', array( $this, 'migrate_menu_items' ), PHP_INT_MAX - 2 );
}
final public function init() {}
/**
* Convert a WordPress menu callback to a URL.
*
* @param string $callback Menu callback.
* @return string
*/
public static function get_callback_url( $callback ) {
// Return the full URL.
if ( strpos( $callback, 'http' ) === 0 ) {
return $callback;
}
$pos = strpos( $callback, '?' );
$file = $pos > 0 ? substr( $callback, 0, $pos ) : $callback;
if ( file_exists( ABSPATH . "/wp-admin/$file" ) ) {
return $callback;
}
return 'admin.php?page=' . $callback;
}
public static function get_callback_url() {}
/**
* Get the parent key if one exists.
*
* @param string $callback Callback or URL.
* @return string|null
*/
public static function get_parent_key( $callback ) {
global $submenu;
if ( ! $submenu ) {
return null;
}
// This is already a parent item.
if ( isset( $submenu[ $callback ] ) ) {
return null;
}
foreach ( $submenu as $key => $menu ) {
foreach ( $menu as $item ) {
if ( $item[ self::CALLBACK ] === $callback ) {
return $key;
}
}
}
return null;
}
public static function get_parent_key() {}
/**
* Adds a top level menu item to the navigation.
*
* @param array $args Array containing the necessary arguments.
* $args = array(
* 'id' => (string) The unique ID of the menu item. Required.
* 'title' => (string) Title of the menu item. Required.
* 'url' => (string) URL or callback to be used. Required.
* 'order' => (int) Menu item order.
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
* 'menuId' => (string) The ID of the menu to add the category to.
* ).
*/
private static function add_category( $args ) {
if ( ! isset( $args['id'] ) || isset( self::$menu_items[ $args['id'] ] ) ) {
return;
}
$defaults = array(
'id' => '',
'title' => '',
'order' => 100,
'migrate' => true,
'menuId' => 'primary',
'isCategory' => true,
);
$menu_item = wp_parse_args( $args, $defaults );
$menu_item['title'] = wp_strip_all_tags( wp_specialchars_decode( $menu_item['title'] ) );
unset( $menu_item['url'] );
unset( $menu_item['capability'] );
if ( ! isset( $menu_item['parent'] ) ) {
$menu_item['parent'] = 'woocommerce';
$menu_item['backButtonLabel'] = __(
'WooCommerce Home',
'woocommerce'
);
}
self::$menu_items[ $menu_item['id'] ] = $menu_item;
self::$categories[ $menu_item['id'] ] = array();
self::$categories[ $menu_item['parent'] ][] = $menu_item['id'];
if ( isset( $args['url'] ) ) {
self::$callbacks[ $args['url'] ] = $menu_item['migrate'];
}
private static function add_category() {
Init::deprecation_notice( 'Menu::add_category' );
}
/**
* Adds a child menu item to the navigation.
*
* @param array $args Array containing the necessary arguments.
* $args = array(
* 'id' => (string) The unique ID of the menu item. Required.
* 'title' => (string) Title of the menu item. Required.
* 'parent' => (string) Parent menu item ID.
* 'capability' => (string) Capability to view this menu item.
* 'url' => (string) URL or callback to be used. Required.
* 'order' => (int) Menu item order.
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
* 'menuId' => (string) The ID of the menu to add the item to.
* 'matchExpression' => (string) A regular expression used to identify if the menu item is active.
* ).
*/
private static function add_item( $args ) {
if ( ! isset( $args['id'] ) ) {
return;
}
if ( isset( self::$menu_items[ $args['id'] ] ) ) {
wc_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: 1: Duplicate menu item path. */
esc_html__( 'You have attempted to register a duplicate item with WooCommerce Navigation: %1$s', 'woocommerce' ),
'`' . $args['id'] . '`'
),
'6.5.0'
);
return;
}
$defaults = array(
'id' => '',
'title' => '',
'capability' => 'manage_woocommerce',
'url' => '',
'order' => 100,
'migrate' => true,
'menuId' => 'primary',
);
$menu_item = wp_parse_args( $args, $defaults );
$menu_item['title'] = wp_strip_all_tags( wp_specialchars_decode( $menu_item['title'] ) );
$menu_item['url'] = self::get_callback_url( $menu_item['url'] );
if ( ! isset( $menu_item['parent'] ) ) {
$menu_item['parent'] = 'woocommerce';
}
$menu_item['menuId'] = self::get_item_menu_id( $menu_item );
self::$menu_items[ $menu_item['id'] ] = $menu_item;
self::$categories[ $menu_item['parent'] ][] = $menu_item['id'];
if ( isset( $args['url'] ) ) {
self::$callbacks[ $args['url'] ] = $menu_item['migrate'];
}
private static function add_item() {
Init::deprecation_notice( 'Menu::add_item' );
}
/**
@@ -287,112 +150,25 @@ class Menu {
/**
* Adds a plugin category.
*
* @param array $args Array containing the necessary arguments.
* $args = array(
* 'id' => (string) The unique ID of the menu item. Required.
* 'title' => (string) Title of the menu item. Required.
* 'url' => (string) URL or callback to be used. Required.
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
* 'order' => (int) Menu item order.
* ).
*/
public static function add_plugin_category( $args ) {
$category_args = array_merge(
$args,
array(
'menuId' => 'plugins',
)
);
if ( ! isset( $category_args['parent'] ) ) {
unset( $category_args['order'] );
}
$menu_id = self::get_item_menu_id( $category_args );
if ( ! in_array( $menu_id, array( 'plugins', 'favorites' ), true ) ) {
return;
}
$category_args['menuId'] = $menu_id;
self::add_category( $category_args );
public static function add_plugin_category() {
Init::deprecation_notice( 'Menu::add_plugin_category' );
}
/**
* Adds a plugin item.
*
* @param array $args Array containing the necessary arguments.
* $args = array(
* 'id' => (string) The unique ID of the menu item. Required.
* 'title' => (string) Title of the menu item. Required.
* 'parent' => (string) Parent menu item ID.
* 'capability' => (string) Capability to view this menu item.
* 'url' => (string) URL or callback to be used. Required.
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
* 'order' => (int) Menu item order.
* 'matchExpression' => (string) A regular expression used to identify if the menu item is active.
* ).
*/
public static function add_plugin_item( $args ) {
if ( ! isset( $args['parent'] ) ) {
unset( $args['order'] );
}
$item_args = array_merge(
$args,
array(
'menuId' => 'plugins',
)
);
$menu_id = self::get_item_menu_id( $item_args );
if ( 'plugins' !== $menu_id ) {
return;
}
self::add_item( $item_args );
public static function add_plugin_item() {
Init::deprecation_notice( 'Menu::add_plugin_item' );
}
/**
* Adds a plugin setting item.
*
* @param array $args Array containing the necessary arguments.
* $args = array(
* 'id' => (string) The unique ID of the menu item. Required.
* 'title' => (string) Title of the menu item. Required.
* 'capability' => (string) Capability to view this menu item.
* 'url' => (string) URL or callback to be used. Required.
* 'migrate' => (bool) Whether or not to hide the item in the wp admin menu.
* ).
*/
public static function add_setting_item( $args ) {
unset( $args['order'] );
if ( isset( $args['parent'] ) || isset( $args['menuId'] ) ) {
error_log( // phpcs:ignore
sprintf(
/* translators: 1: Duplicate menu item path. */
esc_html__( 'The item ID %1$s attempted to register using an invalid option. The arguments `menuId` and `parent` are not allowed for add_setting_item()', 'woocommerce' ),
'`' . $args['id'] . '`'
)
);
}
$item_args = array_merge(
$args,
array(
'menuId' => 'secondary',
'parent' => 'woocommerce-settings',
)
);
self::add_item( $item_args );
public static function add_setting_item() {
Init::deprecation_notice( 'Menu::add_setting_item' );
}
/**
* Get menu item templates for a given post type.
*

View File

@@ -2,12 +2,14 @@
/**
* WooCommerce Navigation Screen
*
* @deprecated 9.3.0 Navigation is no longer a feature and its classes will be removed in WooCommerce 9.4.
* @package Woocommerce Navigation
*/
namespace Automattic\WooCommerce\Admin\Features\Navigation;
use Automattic\WooCommerce\Admin\Features\Navigation\Menu;
use Automattic\WooCommerce\Admin\Features\Navigation\Init;
/**
* Contains logic for the WooCommerce Navigation menu.
@@ -85,6 +87,8 @@ class Screen {
* @return bool
*/
public static function is_woocommerce_page() {
Init::deprecation_notice( 'Screen::is_woocommerce_page' );
global $pagenow;
// Get taxonomy if on a taxonomy screen.
@@ -218,23 +222,15 @@ class Screen {
/**
* Register post type for use in WooCommerce Navigation screens.
*
* @param string $post_type Post type to add.
*/
public static function register_post_type( $post_type ) {
if ( ! in_array( $post_type, self::$post_types, true ) ) {
self::$post_types[] = $post_type;
}
public static function register_post_type() {
Init::deprecation_notice( 'Screen::register_post_type' );
}
/**
* Register taxonomy for use in WooCommerce Navigation screens.
*
* @param string $taxonomy Taxonomy to add.
*/
public static function register_taxonomy( $taxonomy ) {
if ( ! in_array( $taxonomy, self::$taxonomies, true ) ) {
self::$taxonomies[] = $taxonomy;
}
public static function register_taxonomy() {
Init::deprecation_notice( 'Screen::register_taxonomy' );
}
}

View File

@@ -117,7 +117,6 @@ class TaskLists {
'Payments',
'Tax',
'Shipping',
'Marketing',
'LaunchYourStore',
);
@@ -165,6 +164,7 @@ class TaskLists {
),
),
'tasks' => array(
'Marketing',
'ExtendStore',
'AdditionalPayments',
'GetMobileApp',
@@ -297,7 +297,6 @@ class TaskLists {
$task_list->add_task( $task );
}
}
}
/**
@@ -318,8 +317,8 @@ class TaskLists {
public static function get_lists_by_ids( $ids ) {
return array_filter(
self::$lists,
function( $list ) use ( $ids ) {
return in_array( $list->get_list_id(), $ids, true );
function ( $task_list ) use ( $ids ) {
return in_array( $task_list->get_list_id(), $ids, true );
}
);
}
@@ -404,25 +403,31 @@ class TaskLists {
/**
* Return number of setup tasks remaining
*
* @return number
* This is not updated immediately when a task is completed, but rather when task is marked as complete in the database to reduce performance impact.
*
* @return int|null
*/
public static function setup_tasks_remaining() {
$setup_list = self::get_list( 'setup' );
if ( ! $setup_list || $setup_list->is_hidden() || $setup_list->has_previously_completed() || $setup_list->is_complete() ) {
if ( ! $setup_list || $setup_list->is_hidden() || $setup_list->has_previously_completed() ) {
return;
}
$remaining_tasks = array_values(
$viewable_tasks = $setup_list->get_viewable_tasks();
$completed_tasks = get_option( Task::COMPLETED_OPTION, array() );
if ( ! is_array( $completed_tasks ) ) {
$completed_tasks = array();
}
return count(
array_filter(
$setup_list->get_viewable_tasks(),
function( $task ) {
return ! $task->is_complete();
$viewable_tasks,
function ( $task ) use ( $completed_tasks ) {
return ! in_array( $task->get_id(), $completed_tasks, true );
}
)
);
return count( $remaining_tasks );
}
/**
@@ -443,7 +448,6 @@ class TaskLists {
break;
}
}
}
/**

View File

@@ -188,7 +188,7 @@ class AdditionalPayments extends Payments {
*/
private static function get_suggestion_gateways( $filter_by = 'category_additional' ) {
$country = wc_get_base_location()['country'];
$plugin_suggestions = Init::get_suggestions();
$plugin_suggestions = Init::get_cached_or_default_suggestions();
$plugin_suggestions = array_filter(
$plugin_suggestions,
function( $plugin ) use ( $country, $filter_by ) {

View File

@@ -56,79 +56,40 @@ class Marketing extends Task {
return __( '2 minutes', 'woocommerce' );
}
/**
* Task completion.
*
* @return bool
*/
public function is_complete() {
if ( null === $this->is_complete_result ) {
$this->is_complete_result = self::has_installed_extensions();
}
return $this->is_complete_result;
}
/**
* Task visibility.
*
* @return bool
*/
public function can_view() {
return Features::is_enabled( 'remote-free-extensions' ) && count( self::get_plugins() ) > 0;
return Features::is_enabled( 'remote-free-extensions' );
}
/**
* Get the marketing plugins.
*
* @deprecated 9.3.0 Removed to improve performance.
* @return array
*/
public static function get_plugins() {
$bundles = RemoteFreeExtensions::get_extensions(
array(
'task-list/reach',
'task-list/grow',
)
);
return array_reduce(
$bundles,
function( $plugins, $bundle ) {
$visible = array();
foreach ( $bundle['plugins'] as $plugin ) {
if ( $plugin->is_visible ) {
$visible[] = $plugin;
}
}
return array_merge( $plugins, $visible );
},
array()
wc_deprecated_function(
__METHOD__,
'9.3.0'
);
return array();
}
/**
* Check if the store has installed marketing extensions.
*
* @deprecated 9.3.0 Removed to improve performance.
* @return bool
*/
public static function has_installed_extensions() {
$plugins = self::get_plugins();
$remaining = array();
$installed = array();
foreach ( $plugins as $plugin ) {
if ( ! $plugin->is_installed ) {
$remaining[] = $plugin;
} else {
$installed[] = $plugin;
}
}
// Make sure the task has been actioned and a marketing extension has been installed.
if ( count( $installed ) > 0 && Task::is_task_actioned( 'marketing' ) ) {
return true;
}
wc_deprecated_function(
__METHOD__,
'9.3.0'
);
return false;
}
}

View File

@@ -7,6 +7,7 @@ use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
use Automattic\WooCommerce\Admin\PluginsHelper;
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\Init as Suggestions;
use Automattic\WooCommerce\Internal\Admin\WCPayPromotion\Init as WCPayPromotionInit;
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\DefaultPaymentGateways;
/**
* WooCommercePayments Task
@@ -179,11 +180,11 @@ class WooCommercePayments extends Task {
* @return bool
*/
public static function is_supported() {
$suggestions = Suggestions::get_suggestions();
$suggestions = Suggestions::get_suggestions( DefaultPaymentGateways::get_all() );
$suggestion_plugins = array_merge(
...array_filter(
array_column( $suggestions, 'plugins' ),
function( $plugins ) {
function ( $plugins ) {
return is_array( $plugins );
}
)

View File

@@ -7,8 +7,6 @@ namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\DefaultPaymentGateways;
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\PaymentGatewaysController;
use Automattic\WooCommerce\Admin\RemoteSpecs\RemoteSpecsEngine;
/**
@@ -63,6 +61,31 @@ class Init extends RemoteSpecsEngine {
return $specs_to_return;
}
/**
* Gets either cached or default suggestions.
*
* @return array
*/
public static function get_cached_or_default_suggestions() {
$specs = 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' )
? DefaultPaymentGateways::get_all()
: PaymentGatewaySuggestionsDataSourcePoller::get_instance()->get_cached_specs();
if ( ! is_array( $specs ) || 0 === count( $specs ) ) {
$specs = DefaultPaymentGateways::get_all();
}
/**
* Allows filtering of payment gateway suggestion specs
*
* @since 6.4.0
*
* @param array Gateway specs.
*/
$specs = apply_filters( 'woocommerce_admin_payment_gateway_suggestion_specs', $specs );
$results = EvaluateSuggestion::evaluate_specs( $specs );
return $results['suggestions'];
}
/**
* Delete the specs transient.
*/

View File

@@ -2,7 +2,7 @@
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
use Automattic\WooCommerce\Admin\DataSourcePoller;
use Automattic\WooCommerce\Admin\RemoteSpecs\DataSourcePoller;
/**
* Specs data source poller class for payment gateway suggestions.