plugin updates

This commit is contained in:
Tony Volpe
2024-02-21 16:19:46 +00:00
parent c72f206574
commit 21d4c85c00
1214 changed files with 102269 additions and 179257 deletions

View File

@@ -556,6 +556,25 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
}
}
/**
* Enqueue a script in the block editor.
* Similar to `WCAdminAssets::register_script()` but without enqueuing unnecessary dependencies.
*
* @return void
*/
private function enqueue_block_editor_script( $script_path_name, $script_name ) {
$script_assets_filename = WCAdminAssets::get_script_asset_filename( $script_path_name, $script_name );
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . $script_path_name . '/' . $script_assets_filename;
wp_enqueue_script(
'wc-admin-' . $script_name,
WCAdminAssets::get_url( $script_path_name . '/' . $script_name, 'js' ),
$script_assets['dependencies'],
WCAdminAssets::get_file_version( 'js' ),
true
);
}
/**
* Enqueue block editor assets.
*
@@ -578,7 +597,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
}
}
WCAdminAssets::register_script( 'wp-admin-scripts', 'command-palette' );
self::enqueue_block_editor_script( 'wp-admin-scripts', 'command-palette' );
wp_localize_script(
'wc-admin-command-palette',
'wcCommandPaletteSettings',
@@ -611,7 +630,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
}, $analytics_reports );
$formatted_analytics_reports = array_filter( $formatted_analytics_reports, 'is_array' );
WCAdminAssets::register_script( 'wp-admin-scripts', 'command-palette-analytics' );
self::enqueue_block_editor_script( 'wp-admin-scripts', 'command-palette-analytics' );
wp_localize_script(
'wc-admin-command-palette-analytics',
'wcCommandPaletteAnalytics',

View File

@@ -43,40 +43,19 @@ class WC_Admin_Log_Table_List extends WP_List_Table {
* @global wpdb $wpdb
*/
public function level_dropdown() {
$labels = WC_Log_Levels::get_all_level_labels();
$levels = array(
array(
'value' => WC_Log_Levels::EMERGENCY,
'label' => __( 'Emergency', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::ALERT,
'label' => __( 'Alert', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::CRITICAL,
'label' => __( 'Critical', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::ERROR,
'label' => __( 'Error', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::WARNING,
'label' => __( 'Warning', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::NOTICE,
'label' => __( 'Notice', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::INFO,
'label' => __( 'Info', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::DEBUG,
'label' => __( 'Debug', 'woocommerce' ),
),
$levels = array_reduce(
array_keys( $labels ),
function( $carry, $item ) use ( $labels ) {
$carry[] = array(
'value' => $item,
'label' => $labels[ $item ],
);
return $carry;
},
array()
);
$selected_level = isset( $_REQUEST['level'] ) ? $_REQUEST['level'] : '';
@@ -181,16 +160,7 @@ class WC_Admin_Log_Table_List extends WP_List_Table {
*/
public function column_level( $log ) {
$level_key = WC_Log_Levels::get_severity_level( $log['level'] );
$levels = array(
'emergency' => __( 'Emergency', 'woocommerce' ),
'alert' => __( 'Alert', 'woocommerce' ),
'critical' => __( 'Critical', 'woocommerce' ),
'error' => __( 'Error', 'woocommerce' ),
'warning' => __( 'Warning', 'woocommerce' ),
'notice' => __( 'Notice', 'woocommerce' ),
'info' => __( 'Info', 'woocommerce' ),
'debug' => __( 'Debug', 'woocommerce' ),
);
$levels = WC_Log_Levels::get_all_level_labels();
if ( ! isset( $levels[ $level_key ] ) ) {
return '';

View File

@@ -147,8 +147,9 @@ class WC_Admin_Notices {
),
sprintf(
// translators: Placeholders are URLs.
wpautop( wp_kses_data( __( 'The WooCommerce Legacy REST API, <a href="%1$s">currently enabled in this site</a>, will be removed in WooCommerce 9.0. A separate WooCommerce extension will be available to keep it enabled. <b><a target="_blank" href="%2$s">Learn more about this change.</a></b>', 'woocommerce' ) ) ),
wpautop( __( 'The WooCommerce Legacy REST API, <a href="%1$s">currently enabled in this site</a>, will be removed in WooCommerce 9.0. <a target="_blank" href="%2$s">A separate WooCommerce extension is available</a> to keep it enabled. <b><a target="_blank" href="%3$s">Learn more about this change.</a></b>', 'woocommerce' ) ),
admin_url( 'admin.php?page=wc-settings&tab=advanced&section=legacy_api' ),
'https://wordpress.org/plugins/woocommerce-legacy-rest-api/',
'https://developer.woo.com/2023/10/03/the-legacy-rest-api-will-move-to-a-dedicated-extension-in-woocommerce-9-0/'
)
)
@@ -166,8 +167,9 @@ class WC_Admin_Notices {
),
sprintf(
// translators: Placeholders are URLs.
wpautop( wp_kses_data( __( 'The WooCommerce Legacy REST API will be removed in WooCommerce 9.0, and this will cause <a href="%1$s">webhooks on this site that are configured to use the Legacy REST API</a> to stop working. A separate WooCommerce extension will be available to allow these webhooks to keep using the Legacy REST API without interruption. You can also edit these webhooks to use the current REST API version to generate the payload instead. <b><a target="_blank" href="%2$s">Learn more about this change.</a></b>', 'woocommerce' ) ) ),
wpautop( __( 'The WooCommerce Legacy REST API will be removed in WooCommerce 9.0, and this will cause <a href="%1$s">webhooks on this site that are configured to use the Legacy REST API</a> to stop working. <a target="_blank" href="%2$s">A separate WooCommerce extension is available</a> to allow these webhooks to keep using the Legacy REST API without interruption. You can also edit these webhooks to use the current REST API version to generate the payload instead. <b><a target="_blank" href="%3$s">Learn more about this change.</a></b>', 'woocommerce' ) ),
admin_url( 'admin.php?page=wc-settings&tab=advanced&section=webhooks&legacy=true' ),
'https://wordpress.org/plugins/woocommerce-legacy-rest-api/',
'https://developer.woo.com/2023/10/03/the-legacy-rest-api-will-move-to-a-dedicated-extension-in-woocommerce-9-0/'
)
)

View File

@@ -287,8 +287,9 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
break;
case 'info':
?><tr valign="top"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<th scope="row" class="titledesc"/><td style="<?php echo esc_attr( $value['css'] ); ?>">
?><tr class="<?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ); ?></th>
<td style="<?php echo esc_attr( $value['css'] ); ?>">
<?php
echo wp_kses_post( wpautop( wptexturize( $value['text'] ) ) );
echo '</td></tr>';
@@ -320,7 +321,8 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
case 'tel':
$option_value = $value['value'];
?><tr valign="top"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
?>
<tr class="<?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
@@ -345,7 +347,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
$option_value = $value['value'];
?>
<tr valign="top"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<tr class="<?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
@@ -373,7 +375,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
$option_value = $value['value'];
?>
<tr valign="top"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<tr class="<?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
@@ -399,7 +401,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
$option_value = $value['value'];
?>
<tr valign="top"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<tr class="<?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
@@ -442,7 +444,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
$show_desc_at_end = $value['desc_at_end'] ?? false;
?>
<tr valign="top"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<tr class="<?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
@@ -509,13 +511,14 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
$visibility_class[] = $value['row_class'];
}
$must_disable = $value['disabled'] ?? false;
$container_class = implode( ' ', $visibility_class );
$must_disable = $value['disabled'] ?? false;
if ( ! isset( $value['checkboxgroup'] ) || 'start' === $value['checkboxgroup'] ) {
$has_tooltip = isset( $value['tooltip'] ) && '' !== $value['tooltip'];
$tooltip_container_class = $has_tooltip ? 'with-tooltip' : '';
?>
<tr valign="top" class="<?php echo esc_attr( implode( ' ', $visibility_class ) ); ?>">
<tr class="<?php echo esc_attr( $container_class ); ?>">
<th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ); ?></th>
<td class="forminp forminp-checkbox <?php echo esc_html( $tooltip_container_class ); ?>">
<?php if ( $has_tooltip ) : ?>
@@ -525,7 +528,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
<?php
} else {
?>
<fieldset class="<?php echo esc_attr( implode( ' ', $visibility_class ) ); ?>">
<fieldset class="<?php echo esc_attr( $container_class ); ?>">
<?php
}
@@ -580,7 +583,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
}
?>
<tr valign="top"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<tr class="<?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html . $disabled_message; // WPCS: XSS ok. ?></label>
</th>
@@ -614,7 +617,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
}
?>
<tr valign="top" class="single_select_page"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<tr class="single_select_page <?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
@@ -639,7 +642,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
);
}
?>
<tr valign="top" class="single_select_page"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<tr class="single_select_page <?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></label>
</th>
@@ -679,7 +682,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
$state = '*';
}
?>
<tr valign="top"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<tr class="<?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
@@ -703,7 +706,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
asort( $countries );
?>
<tr valign="top"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<tr class="<?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
@@ -732,7 +735,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
);
$option_value = wc_parse_relative_date_option( $value['value'] );
?>
<tr valign="top"<?php echo $value['row_class'] ? ' class="' . esc_attr( $value['row_class'] ) . '"' : '' ?>">
<tr class="<?php echo esc_attr( $value['row_class'] ); ?>">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>

View File

@@ -39,13 +39,23 @@ class WC_Helper_Admin {
$auth_user_data = WC_Helper_Options::get( 'auth_user_data', array() );
$auth_user_email = isset( $auth_user_data['email'] ) ? $auth_user_data['email'] : '';
// Get the all installed themes and plugins. Knowing this will help us decide to show Add to Store button on product cards.
$installed_products = array_merge( WC_Helper::get_local_plugins(), WC_Helper::get_local_themes() );
$installed_products = array_map(
function ( $product ) {
return $product['slug'];
},
$installed_products
);
$settings['wccomHelper'] = array(
'isConnected' => WC_Helper::is_site_connected(),
'connectURL' => self::get_connection_url(),
'userEmail' => $auth_user_email,
'userAvatar' => get_avatar_url( $auth_user_email, array( 'size' => '48' ) ),
'storeCountry' => wc_get_base_location()['country'],
'isConnected' => WC_Helper::is_site_connected(),
'connectURL' => self::get_connection_url(),
'userEmail' => $auth_user_email,
'userAvatar' => get_avatar_url( $auth_user_email, array( 'size' => '48' ) ),
'storeCountry' => wc_get_base_location()['country'],
'inAppPurchaseURLParams' => WC_Admin_Addons::get_in_app_purchase_url_params(),
'installedProducts' => $installed_products,
);
return $settings;
@@ -74,10 +84,6 @@ class WC_Helper_Admin {
$connect_url_args['wc-helper-nonce'] = wp_create_nonce( 'connect' );
}
if ( isset( $current_screen->id ) && 'woocommerce_page_wc-admin' === $current_screen->id ) {
$connect_url_args['redirect-to-wc-admin'] = 1;
}
return add_query_arg(
$connect_url_args,
admin_url( 'admin.php' )

View File

@@ -0,0 +1,107 @@
<?php
/**
* WooCommerce Admin Helper - React admin interface
*
* @package WooCommerce\Admin\Helper
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Helper_Orders_API
*
* Pings Woo.com to create an order and pull in the necessary data to start the installation process.
*/
class WC_Helper_Orders_API {
/**
* Loads the class, runs on init
*
* @return void
*/
public static function load() {
add_filter( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
}
/**
* Registers the REST routes for the Marketplace Orders API.
* These endpoints are used by the Marketplace Subscriptions React UI.
*/
public static function register_rest_routes() {
register_rest_route(
'wc/v3',
'/marketplace/create-order',
array(
'methods' => 'POST',
'callback' => array( __CLASS__, 'create_order' ),
'permission_callback' => array( __CLASS__, 'get_permission' ),
'args' => array(
'product_id' => array(
'required' => true,
'validate_callback' => function( $argument ) {
return is_int( $argument );
},
),
),
)
);
}
/**
* The Extensions page can only be accessed by users with the manage_woocommerce
* capability. So the API mimics that behavior.
*
* @return bool
*/
public static function get_permission() {
return WC_Helper_Subscriptions_API::get_permission();
}
/**
* Core function to create an order on Woo.com. Pings the API and catches the exceptions if any.
*
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response
*/
public static function create_order( $request ) {
if ( ! current_user_can( 'install_plugins' ) ) {
return new \WP_REST_Response(
array(
'message' => __( 'You do not have permission to install plugins.', 'woocommerce' ),
),
403
);
}
try {
$response = WC_Helper_API::post(
'create-order',
array(
'authenticated' => true,
'body' => http_build_query(
array(
'product_id' => $request['product_id'],
),
),
)
);
return new \WP_REST_Response(
json_decode( wp_remote_retrieve_body( $response ), true ),
wp_remote_retrieve_response_code( $response )
);
} catch ( Exception $e ) {
return new \WP_REST_Response(
array(
'message' => __( 'Could not start the installation process. Reason: ', 'woocommerce' ) . $e->getMessage(),
'code' => 'could-not-install',
),
500
);
}
}
}
WC_Helper_Orders_API::load();

View File

@@ -61,6 +61,7 @@ class WC_Helper {
include_once dirname( __FILE__ ) . '/class-wc-helper-compat.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-admin.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-subscriptions-api.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-orders-api.php';
}
/**
@@ -699,40 +700,53 @@ class WC_Helper {
array(
'page' => 'wc-addons',
'section' => 'helper',
),
isset( $_GET['redirect-to-wc-admin'] ),
sanitize_text_field( wp_unslash( $_GET['install'] ) )
)
)
);
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* Get helper redirect URL.
*
* @param array $args Query args.
* @param bool $redirect_to_wc_admin Whether to redirect to WC Admin.
* @param string $install_product_key Optional Product key to install.
* @param array $args Query args.
* @return string
*/
private static function get_helper_redirect_url( $args = array(), $redirect_to_wc_admin = false, $install_product_key = '' ) {
private static function get_helper_redirect_url( $args = array() ) {
global $current_screen;
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$redirect_admin_url = isset( $_GET['redirect_admin_url'] )
? esc_url_raw(
urldecode(
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
wp_unslash( $_GET['redirect_admin_url'] )
)
)
: '';
$install_product_key = isset( $_GET['install'] ) ? sanitize_text_field( wp_unslash( $_GET['install'] ) ) : '';
// phpcs:enable WordPress.Security.NonceVerification.Recommended
if (
'woocommerce_page_wc-addons' === $current_screen->id &&
FeaturesUtil::feature_is_enabled( 'marketplace' ) &&
(
true === $redirect_to_wc_admin ||
false === empty( $redirect_admin_url ) ||
false === empty( $install_product_key )
)
) {
$new_url = add_query_arg(
array(
'page' => 'wc-admin',
'tab' => 'my-subscriptions',
'path' => rawurlencode( '/extensions' ),
),
admin_url( 'admin.php' )
);
if ( strpos( $redirect_admin_url, admin_url( 'admin.php' ) ) === 0 ) {
$new_url = $redirect_admin_url;
} else {
$new_url = add_query_arg(
array(
'page' => 'wc-admin',
'tab' => 'my-subscriptions',
'path' => rawurlencode( '/extensions' ),
),
admin_url( 'admin.php' )
);
}
if ( ! empty( $install_product_key ) ) {
$new_url = add_query_arg(
array(
@@ -766,10 +780,6 @@ class WC_Helper {
'wc-helper-nonce' => wp_create_nonce( 'connect' ),
);
if ( isset( $_GET['redirect-to-wc-admin'] ) ) {
$redirect_url_args['redirect-to-wc-admin'] = 1;
}
if ( isset( $_GET['install'] ) ) {
$redirect_url_args['install'] = sanitize_text_field( wp_unslash( $_GET['install'] ) );
}
@@ -809,9 +819,19 @@ class WC_Helper {
$connect_url = add_query_arg(
array(
'home_url' => rawurlencode( home_url() ),
'redirect_uri' => rawurlencode( $redirect_uri ),
'secret' => rawurlencode( $secret ),
'home_url' => rawurlencode( home_url() ),
'redirect_uri' => rawurlencode( $redirect_uri ),
'secret' => rawurlencode( $secret ),
'redirect_admin_url' => isset( $_GET['redirect_admin_url'] )
? rawurlencode(
esc_url_raw(
urldecode(
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
wp_unslash( $_GET['redirect_admin_url'] )
)
)
)
: '',
),
WC_Helper_API::url( 'oauth/authorize' )
);
@@ -841,9 +861,7 @@ class WC_Helper {
array(
'page' => 'wc-addons',
'section' => 'helper',
),
isset( $_GET['redirect-to-wc-admin'] ),
isset( $_GET['install'] ) ? sanitize_text_field( wp_unslash( $_GET['install'] ) ) : ''
)
)
);
die();
@@ -905,9 +923,7 @@ class WC_Helper {
'page' => 'wc-addons',
'section' => 'helper',
'wc-helper-status' => 'helper-connected',
),
isset( $_GET['redirect-to-wc-admin'] ),
isset( $_GET['install'] ) ? sanitize_text_field( wp_unslash( $_GET['install'] ) ) : ''
)
)
);
die();
@@ -932,9 +948,7 @@ class WC_Helper {
'page' => 'wc-addons',
'section' => 'helper',
'wc-helper-status' => 'helper-disconnected',
),
isset( $_GET['redirect-to-wc-admin'] ),
isset( $_GET['install'] ) ? sanitize_text_field( wp_unslash( $_GET['install'] ) ) : ''
)
);
self::disconnect();
@@ -960,9 +974,7 @@ class WC_Helper {
'section' => 'helper',
'filter' => self::get_current_filter(),
'wc-helper-status' => 'helper-refreshed',
),
isset( $_GET['redirect-to-wc-admin'] ),
isset( $_GET['install'] ) ? sanitize_text_field( wp_unslash( $_GET['install'] ) ) : ''
)
);
wp_safe_redirect( $redirect_uri );

View File

@@ -34,18 +34,23 @@ class WC_Meta_Box_Order_Data {
protected static $shipping_fields = array();
/**
* Init billing and shipping fields we display + save.
* Get billing fields for the meta box.
*
* @param \WC_Order $order Order object.
* @param string $context Context of fields (view or edit).
* @return array
*/
public static function init_address_fields() {
protected static function get_billing_fields( $order = false, $context = 'edit' ) {
/**
* Provides an opportunity to modify the list of order billing fields displayed on the admin.
*
* @since 1.4.0
*
* @param array Billing fields.
* @param WC_Order|false $order Order object.
* @param string $context Context of fields (view or edit).
*/
self::$billing_fields = apply_filters(
return apply_filters(
'woocommerce_admin_billing_fields',
array(
'first_name' => array(
@@ -94,17 +99,30 @@ class WC_Meta_Box_Order_Data {
'phone' => array(
'label' => __( 'Phone', 'woocommerce' ),
),
)
),
$order,
$context
);
}
/**
* Get shipping fields for the meta box.
*
* @param \WC_Order $order Order object.
* @param string $context Context of fields (view or edit).
* @return array
*/
protected static function get_shipping_fields( $order = false, $context = 'edit' ) {
/**
* Provides an opportunity to modify the list of order shipping fields displayed on the admin.
*
* @since 1.4.0
*
* @param array Shipping fields.
* @param WC_Order|false $order Order object.
* @param string $context Context of fields (view or edit).
*/
self::$shipping_fields = apply_filters(
return apply_filters(
'woocommerce_admin_shipping_fields',
array(
'first_name' => array(
@@ -150,10 +168,20 @@ class WC_Meta_Box_Order_Data {
'phone' => array(
'label' => __( 'Phone', 'woocommerce' ),
),
)
),
$order,
$context
);
}
/**
* Init billing and shipping fields we display + save. Maintained for backwards compat.
*/
public static function init_address_fields() {
self::$billing_fields = self::get_billing_fields();
self::$shipping_fields = self::get_shipping_fields();
}
/**
* Output the metabox.
*
@@ -166,8 +194,6 @@ class WC_Meta_Box_Order_Data {
$order = $theorder;
self::init_address_fields();
if ( WC()->payment_gateways() ) {
$payment_gateways = WC()->payment_gateways->payment_gateways();
} else {
@@ -373,7 +399,6 @@ class WC_Meta_Box_Order_Data {
</h3>
<div class="address">
<?php
// Display values.
if ( $order->get_formatted_billing_address() ) {
echo '<p>' . wp_kses( $order->get_formatted_billing_address(), array( 'br' => array() ) ) . '</p>';
@@ -381,7 +406,9 @@ class WC_Meta_Box_Order_Data {
echo '<p class="none_set"><strong>' . esc_html__( 'Address:', 'woocommerce' ) . '</strong> ' . esc_html__( 'No billing address set.', 'woocommerce' ) . '</p>';
}
foreach ( self::$billing_fields as $key => $field ) {
$billing_fields = self::get_billing_fields( $order, 'view' );
foreach ( $billing_fields as $key => $field ) {
if ( isset( $field['show'] ) && false === $field['show'] ) {
continue;
}
@@ -413,9 +440,10 @@ class WC_Meta_Box_Order_Data {
<div class="edit_address">
<?php
// Display form.
foreach ( self::$billing_fields as $key => $field ) {
$billing_fields = self::get_billing_fields( $order, 'edit' );
foreach ( $billing_fields as $key => $field ) {
if ( ! isset( $field['type'] ) ) {
$field['type'] = 'text';
}
@@ -437,6 +465,9 @@ class WC_Meta_Box_Order_Data {
case 'select':
woocommerce_wp_select( $field, $order );
break;
case 'checkbox':
woocommerce_wp_checkbox( $field, $order );
break;
default:
woocommerce_wp_text_input( $field, $order );
break;
@@ -493,7 +524,6 @@ class WC_Meta_Box_Order_Data {
</h3>
<div class="address">
<?php
// Display values.
if ( $order->get_formatted_shipping_address() ) {
echo '<p>' . wp_kses( $order->get_formatted_shipping_address(), array( 'br' => array() ) ) . '</p>';
@@ -501,15 +531,19 @@ class WC_Meta_Box_Order_Data {
echo '<p class="none_set"><strong>' . esc_html__( 'Address:', 'woocommerce' ) . '</strong> ' . esc_html__( 'No shipping address set.', 'woocommerce' ) . '</p>';
}
if ( ! empty( self::$shipping_fields ) ) {
foreach ( self::$shipping_fields as $key => $field ) {
$shipping_fields = self::get_shipping_fields( $order, 'view' );
if ( ! empty( $shipping_fields ) ) {
foreach ( $shipping_fields as $key => $field ) {
if ( isset( $field['show'] ) && false === $field['show'] ) {
continue;
}
$field_name = 'shipping_' . $key;
if ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
if ( isset( $field['value'] ) ) {
$field_value = $field['value'];
} elseif ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
$field_value = $order->{"get_$field_name"}( 'edit' );
} else {
$field_value = $order->get_meta( '_' . $field_name );
@@ -519,9 +553,11 @@ class WC_Meta_Box_Order_Data {
$field_value = wc_make_phone_clickable( $field_value );
}
if ( $field_value ) {
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( $field_value ) . '</p>';
if ( ! $field_value ) {
continue;
}
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( $field_value ) . '</p>';
}
}
@@ -532,10 +568,11 @@ class WC_Meta_Box_Order_Data {
</div>
<div class="edit_address">
<?php
// Display form.
if ( ! empty( self::$shipping_fields ) ) {
foreach ( self::$shipping_fields as $key => $field ) {
$shipping_fields = self::get_shipping_fields( $order, 'edit' );
if ( ! empty( $shipping_fields ) ) {
foreach ( $shipping_fields as $key => $field ) {
if ( ! isset( $field['type'] ) ) {
$field['type'] = 'text';
}
@@ -545,16 +582,21 @@ class WC_Meta_Box_Order_Data {
$field_name = 'shipping_' . $key;
if ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
$field['value'] = $order->{"get_$field_name"}( 'edit' );
} else {
$field['value'] = $order->get_meta( '_' . $field_name );
if ( ! isset( $field['value'] ) ) {
if ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
$field['value'] = $order->{"get_$field_name"}( 'edit' );
} else {
$field['value'] = $order->get_meta( '_' . $field_name );
}
}
switch ( $field['type'] ) {
case 'select':
woocommerce_wp_select( $field, $order );
break;
case 'checkbox':
woocommerce_wp_checkbox( $field, $order );
break;
default:
woocommerce_wp_text_input( $field, $order );
break;
@@ -604,8 +646,6 @@ class WC_Meta_Box_Order_Data {
throw new Exception( __( 'Payment method is missing.', 'woocommerce' ), 400 );
}
self::init_address_fields();
// Ensure gateways are loaded in case they need to insert data into the emails.
WC()->payment_gateways();
WC()->shipping();
@@ -626,8 +666,10 @@ class WC_Meta_Box_Order_Data {
}
// Update billing fields.
if ( ! empty( self::$billing_fields ) ) {
foreach ( self::$billing_fields as $key => $field ) {
$billing_fields = self::get_billing_fields( $order, 'edit' );
if ( ! empty( $billing_fields ) ) {
foreach ( $billing_fields as $key => $field ) {
if ( ! isset( $field['id'] ) ) {
$field['id'] = '_billing_' . $key;
}
@@ -636,17 +678,24 @@ class WC_Meta_Box_Order_Data {
continue;
}
if ( is_callable( array( $order, 'set_billing_' . $key ) ) ) {
$props[ 'billing_' . $key ] = wc_clean( wp_unslash( $_POST[ $field['id'] ] ) );
$value = wc_clean( wp_unslash( $_POST[ $field['id'] ] ) );
// Update a field if it includes an update callback.
if ( isset( $field['update_callback'] ) ) {
call_user_func( $field['update_callback'], $field['id'], $value, $order );
} elseif ( is_callable( array( $order, 'set_billing_' . $key ) ) ) {
$props[ 'billing_' . $key ] = $value;
} else {
$order->update_meta_data( $field['id'], wc_clean( wp_unslash( $_POST[ $field['id'] ] ) ) );
$order->update_meta_data( $field['id'], $value );
}
}
}
// Update shipping fields.
if ( ! empty( self::$shipping_fields ) ) {
foreach ( self::$shipping_fields as $key => $field ) {
$shipping_fields = self::get_shipping_fields( $order, 'edit' );
if ( ! empty( $shipping_fields ) ) {
foreach ( $shipping_fields as $key => $field ) {
if ( ! isset( $field['id'] ) ) {
$field['id'] = '_shipping_' . $key;
}
@@ -655,10 +704,15 @@ class WC_Meta_Box_Order_Data {
continue;
}
if ( is_callable( array( $order, 'set_shipping_' . $key ) ) ) {
$props[ 'shipping_' . $key ] = wc_clean( wp_unslash( $_POST[ $field['id'] ] ) );
$value = isset( $_POST[ $field['id'] ] ) ? wc_clean( wp_unslash( $_POST[ $field['id'] ] ) ) : '';
// Update a field if it includes an update callback.
if ( isset( $field['update_callback'] ) ) {
call_user_func( $field['update_callback'], $field['id'], $value, $order );
} elseif ( is_callable( array( $order, 'set_shipping_' . $key ) ) ) {
$props[ 'shipping_' . $key ] = $value;
} else {
$order->update_meta_data( $field['id'], wc_clean( wp_unslash( $_POST[ $field['id'] ] ) ) );
$order->update_meta_data( $field['id'], $value );
}
}
}

View File

@@ -361,7 +361,7 @@ class WC_Settings_Advanced extends WC_Settings_Page {
array(
'title' => __( 'Show Suggestions', 'woocommerce' ),
'desc' => __( 'Display suggestions within WooCommerce', 'woocommerce' ),
'desc_tip' => esc_html__( 'Leave this box unchecked if you do not want to see suggested extensions.', 'woocommerce' ),
'desc_tip' => esc_html__( 'Leave this box unchecked if you do not want to pull suggested extensions from Woo.com. You will see a static list of extensions instead.', 'woocommerce' ),
'id' => 'woocommerce_show_marketplace_suggestions',
'type' => 'checkbox',
'checkboxgroup' => 'start',
@@ -395,9 +395,10 @@ class WC_Settings_Advanced extends WC_Settings_Page {
$enable_legacy_api_setting['desc_tip'] = sprintf(
// translators: Placeholders are URLs.
__(
'⚠️ <b>The Legacy REST API will be removed in WooCommerce 9.0.</b> A separate WooCommerce extension will soon be available to keep it enabled. You can check Legacy REST API usages in <b><a target="_blank" href="%1$s">the WooCommerce log files</a></b> (file names start with <code>legacy_rest_api_usages</code>). <b><a target="_blank" href="%2$s">Learn more about this change.</a></b>',
'⚠️ <b>The Legacy REST API will be removed in WooCommerce 9.0.</b> <a target="_blank" href="%1$s">A separate WooCommerce extension is available</a> to keep it enabled. You can check Legacy REST API usages in <b><a target="_blank" href="%2$s">the WooCommerce log files</a></b> (file names start with <code>legacy_rest_api_usages</code>). <b><a target="_blank" href="%3$s">Learn more about this change.</a></b>',
'woocommerce'
),
'https://wordpress.org/plugins/woocommerce-legacy-rest-api/',
admin_url( 'admin.php?page=wc-status&tab=logs' ),
'https://developer.woo.com/2023/10/03/the-legacy-rest-api-will-move-to-a-dedicated-extension-in-woocommerce-9-0/'
);

View File

@@ -158,7 +158,14 @@ function woocommerce_wp_checkbox( $field, WC_Data $data = null ) {
$field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id'];
$field['desc_tip'] = isset( $field['desc_tip'] ) ? $field['desc_tip'] : false;
// Custom attribute handling
/**
* These values are what get passed vis $_POST depending on if the field is checked or not. If no unchecked_value is
* provided, the $_POST will not be set. This maintains backwards compatibility where consumers would use `isset`.
*/
$field['checked_value'] = isset( $field['checked_value'] ) ? $field['checked_value'] : $field['cbvalue'];
$field['unchecked_value'] = isset( $field['unchecked_value'] ) ? $field['unchecked_value'] : null;
// Custom attribute handling.
$custom_attributes = array();
if ( ! empty( $field['custom_attributes'] ) && is_array( $field['custom_attributes'] ) ) {
@@ -168,6 +175,14 @@ function woocommerce_wp_checkbox( $field, WC_Data $data = null ) {
}
}
if ( ! empty( $field['style'] ) ) {
$custom_attributes[] = 'style="' . esc_attr( $field['style'] ) . '"';
}
if ( ! empty( $field['class'] ) ) {
$custom_attributes[] = 'class="' . esc_attr( $field['class'] ) . '"';
}
echo '<p class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '">
<label for="' . esc_attr( $field['id'] ) . '">' . wp_kses_post( $field['label'] ) . '</label>';
@@ -175,7 +190,19 @@ function woocommerce_wp_checkbox( $field, WC_Data $data = null ) {
echo wc_help_tip( $field['description'] );
}
echo '<input type="checkbox" class="' . esc_attr( $field['class'] ) . '" style="' . esc_attr( $field['style'] ) . '" name="' . esc_attr( $field['name'] ) . '" id="' . esc_attr( $field['id'] ) . '" value="' . esc_attr( $field['cbvalue'] ) . '" ' . checked( $field['value'], $field['cbvalue'], false ) . ' ' . implode( ' ', $custom_attributes ) . '/> ';
// Output a hidden field so a value is POSTed if the box is not checked.
if ( ! is_null( $field['unchecked_value'] ) ) {
printf( '<input type="hidden" name="%1$s" value="%2$s" />', esc_attr( $field['name'] ), esc_attr( $field['unchecked_value'] ) );
}
printf(
'<input type="checkbox" name="%1$s" id="%2$s" value="%3$s" %4$s %5$s />',
esc_attr( $field['name'] ),
esc_attr( $field['id'] ),
esc_attr( $field['checked_value'] ),
checked( $field['value'], $field['checked_value'], false ),
implode( ' ', $custom_attributes ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
if ( ! empty( $field['description'] ) && false === $field['desc_tip'] ) {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
@@ -194,7 +221,8 @@ function woocommerce_wp_select( $field, WC_Data $data = null ) {
global $post;
$field = wp_parse_args(
$field, array(
$field,
array(
'class' => 'select short',
'style' => '',
'wrapper_class' => '',

View File

@@ -9,6 +9,8 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
defined( 'ABSPATH' ) || exit;
@@ -197,6 +199,8 @@ class WC_Emails {
add_action( 'woocommerce_email_order_meta', array( $this, 'order_meta' ), 10, 3 );
add_action( 'woocommerce_email_customer_details', array( $this, 'customer_details' ), 10, 3 );
add_action( 'woocommerce_email_customer_details', array( $this, 'email_addresses' ), 20, 3 );
add_action( 'woocommerce_email_customer_details', array( $this, 'additional_checkout_fields' ), 30, 3 );
add_action( 'woocommerce_email_customer_address_section', array( $this, 'additional_address_fields' ), 30, 4 );
// Hooks for sending emails during store events.
add_action( 'woocommerce_low_stock_notification', array( $this, 'low_stock' ) );
@@ -592,6 +596,72 @@ class WC_Emails {
}
}
/**
* Renders any additional fields captured during block based checkout.
*
* @param WC_Order $order Order instance.
* @param bool $sent_to_admin If email is sent to admin.
* @param bool $plain_text If this is a plain text email.
*/
public function additional_checkout_fields( $order, $sent_to_admin = false, $plain_text = false ) {
if ( ! is_a( $order, 'WC_Order' ) ) {
return;
}
$checkout_fields = Package::container()->get( CheckoutFields::class );
$fields = array_merge(
$checkout_fields->get_order_additional_fields_with_values( $order, 'contact', '', 'view' ),
$checkout_fields->get_order_additional_fields_with_values( $order, 'additional', '', 'view' ),
);
if ( ! $fields ) {
return;
}
if ( $plain_text ) {
echo "\n" . esc_html( wc_strtoupper( __( 'Additional information', 'woocommerce' ) ) ) . "\n\n";
foreach ( $fields as $field ) {
printf( "%s: %s\n", wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
}
} else {
echo '<h2>' . esc_html__( 'Additional information', 'woocommerce' ) . '</h2>';
echo '<ul class="additional-fields" style="margin-bottom: 40px;">';
foreach ( $fields as $field ) {
printf( '<li><strong>%s</strong>: %s</li>', wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
}
echo '</ul>';
}
}
/**
* Renders any additional address fields captured during block based checkout.
*
* @param string $address_type Address type.
* @param WC_Order $order Order instance.
* @param bool $sent_to_admin If email is sent to admin.
* @param bool $plain_text If this is a plain text email.
*/
public function additional_address_fields( $address_type, $order, $sent_to_admin = false, $plain_text = false ) {
if ( ! is_a( $order, 'WC_Order' ) ) {
return;
}
$checkout_fields = Package::container()->get( CheckoutFields::class );
$fields = $checkout_fields->get_order_additional_fields_with_values( $order, 'address', $address_type, 'view' );
if ( ! $fields ) {
return;
}
foreach ( $fields as $field ) {
if ( $plain_text ) {
printf( "%s: %s\n", wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
} else {
printf( '<br><strong>%s</strong>: %s', wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
}
}
}
/**
* Get blog name formatted for emails.
*

View File

@@ -240,6 +240,9 @@ class WC_Install {
'8.1.0' => array(
'wc_update_810_migrate_transactional_metadata_for_hpos',
),
'8.6.0' => array(
'wc_update_860_remove_recommended_marketing_plugins_transient',
),
);
/**
@@ -565,9 +568,11 @@ class WC_Install {
* @return boolean
*/
public static function is_new_install() {
$product_count = array_sum( (array) wp_count_posts( 'product' ) );
return is_null( get_option( 'woocommerce_version', null ) ) || ( 0 === $product_count && -1 === wc_get_page_id( 'shop' ) );
return is_null( get_option( 'woocommerce_version', null ) )
|| (
-1 === wc_get_page_id( 'shop' )
&& 0 === array_sum( (array) wp_count_posts( 'product' ) )
);
}
/**
@@ -2586,6 +2591,10 @@ EOT;
<div class="wp-block-woocommerce-checkout-payment-block"></div>
<!-- /wp:woocommerce/checkout-payment-block -->
<!-- wp:woocommerce/checkout-additional-information-block -->
<div class="wp-block-woocommerce-checkout-additional-information-block"></div>
<!-- /wp:woocommerce/checkout-additional-information-block -->
<!-- wp:woocommerce/checkout-order-note-block -->
<div class="wp-block-woocommerce-checkout-order-note-block"></div>
<!-- /wp:woocommerce/checkout-order-note-block -->

View File

@@ -12,7 +12,6 @@ defined( 'ABSPATH' ) || exit;
* Log levels class.
*/
abstract class WC_Log_Levels {
/**
* Log Levels
*
@@ -79,7 +78,7 @@ abstract class WC_Log_Levels {
* @return bool True if $level is a valid level.
*/
public static function is_valid_level( $level ) {
return array_key_exists( strtolower( $level ), self::$level_to_severity );
return is_string( $level ) && array_key_exists( strtolower( $level ), self::$level_to_severity );
}
/**
@@ -122,4 +121,39 @@ abstract class WC_Log_Levels {
public static function get_all_severity_levels() {
return self::$severity_to_level;
}
/**
* Get the UI label for a log level.
*
* @param string $level Log level, options: emergency|alert|critical|error|warning|notice|info|debug.
*
* @return string
*/
public static function get_level_label( $level ) {
$labels = self::get_all_level_labels();
if ( ! array_key_exists( $level, $labels ) ) {
return '';
}
return $labels[ $level ];
}
/**
* Get the UI labels for all log levels.
*
* @return string[]
*/
public static function get_all_level_labels() {
return array(
self::EMERGENCY => __( 'Emergency', 'woocommerce' ),
self::ALERT => __( 'Alert', 'woocommerce' ),
self::CRITICAL => __( 'Critical', 'woocommerce' ),
self::ERROR => __( 'Error', 'woocommerce' ),
self::WARNING => __( 'Warning', 'woocommerce' ),
self::NOTICE => __( 'Notice', 'woocommerce' ),
self::INFO => __( 'Info', 'woocommerce' ),
self::DEBUG => __( 'Debug', 'woocommerce' ),
);
}
}

View File

@@ -7,7 +7,7 @@
* @package WooCommerce\Classes
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Utilities\LoggingUtil;
defined( 'ABSPATH' ) || exit;
@@ -15,7 +15,6 @@ defined( 'ABSPATH' ) || exit;
* WC_Logger class.
*/
class WC_Logger implements WC_Logger_Interface {
/**
* Stores registered log handlers.
*
@@ -38,15 +37,24 @@ class WC_Logger implements WC_Logger_Interface {
*/
public function __construct( $handlers = null, $threshold = null ) {
if ( null === $handlers ) {
$handlers = apply_filters( 'woocommerce_register_log_handlers', array() );
$default_handler = LoggingUtil::get_default_handler();
$handler_instance = new $default_handler();
/**
* Filter the list of log handler class instances that will run whenever a log entry is added.
*
* @param WC_Log_Handler_Interface[]
*
* @since 3.0.0
*/
$handlers = apply_filters( 'woocommerce_register_log_handlers', array( $handler_instance ) );
}
$register_handlers = array();
if ( ! empty( $handlers ) && is_array( $handlers ) ) {
foreach ( $handlers as $handler ) {
$implements = class_implements( $handler );
if ( is_object( $handler ) && is_array( $implements ) && in_array( 'WC_Log_Handler_Interface', $implements, true ) ) {
if ( $handler instanceof WC_Log_Handler_Interface ) {
$register_handlers[] = $handler;
} else {
wc_doing_it_wrong(
@@ -63,20 +71,12 @@ class WC_Logger implements WC_Logger_Interface {
}
}
// Support the constant as long as a valid log level has been set for it.
if ( null === $threshold ) {
$threshold = Constants::get_constant( 'WC_LOG_THRESHOLD' );
if ( null !== $threshold && ! WC_Log_Levels::is_valid_level( $threshold ) ) {
$threshold = null;
}
}
if ( null !== $threshold ) {
$threshold = WC_Log_Levels::get_level_severity( $threshold );
if ( ! WC_Log_Levels::is_valid_level( $threshold ) ) {
$threshold = LoggingUtil::get_level_threshold();
}
$this->handlers = $register_handlers;
$this->threshold = $threshold;
$this->threshold = WC_Log_Levels::get_level_severity( $threshold );
}
/**
@@ -86,9 +86,14 @@ class WC_Logger implements WC_Logger_Interface {
* @return bool True if the log should be handled.
*/
protected function should_handle( $level ) {
if ( ! LoggingUtil::logging_is_enabled() ) {
return false;
}
if ( null === $this->threshold ) {
return true;
}
return $this->threshold <= WC_Log_Levels::get_level_severity( $level );
}
@@ -131,6 +136,8 @@ class WC_Logger implements WC_Logger_Interface {
* 'debug': Debug-level messages.
* @param string $message Log message.
* @param array $context Optional. Additional information for log handlers.
*
* @return void
*/
public function log( $level, $message, $context = array() ) {
if ( ! WC_Log_Levels::is_valid_level( $level ) ) {
@@ -300,15 +307,10 @@ class WC_Logger implements WC_Logger_Interface {
/**
* Clear all logs older than a defined number of days. Defaults to 30 days.
*
* @since 3.4.0
* @return void
*/
public function clear_expired_logs() {
/**
* Filter the retention period of log entries.
*
* @param int $days The number of days to retain log entries.
*/
$days = absint( apply_filters( 'woocommerce_logger_days_to_retain_logs', 30 ) );
$days = LoggingUtil::get_retention_period();
$timestamp = strtotime( "-{$days} days" );
foreach ( $this->handlers as $handler ) {

View File

@@ -130,13 +130,7 @@ class WC_Product_Download implements ArrayAccess {
// Validate the file exists.
if ( ! $this->file_exists() ) {
throw new Exception(
sprintf(
/* translators: %s: Downloadable file */
__( 'The downloadable file %s cannot be used as it does not exist on the server.', 'woocommerce' ),
'<code>' . $download_file . '</code>'
)
);
$this->raise_invalid_file_exception( $download_file );
}
$this->approved_directory_checks( $auto_add_to_approved_directory_list );
@@ -242,18 +236,31 @@ class WC_Product_Download implements ArrayAccess {
}
if ( ! $valid_storage_directory ) {
throw new Exception(
sprintf(
/* translators: %1$s is the downloadable file path, %2$s is an opening link tag, %3%s is a closing link tag. */
__( 'The downloadable file %1$s cannot be used: it is not located in an approved directory. Please contact a site administrator for help. %2$sLearn more.%3$s', 'woocommerce' ),
'<code>' . $download_file . '</code>',
'<a href="https://woo.com/document/approved-download-directories">',
'</a>'
)
);
$this->raise_invalid_file_exception( $download_file );
}
}
/**
* Convenience method, allows us to re-use the same exception messaging from different areas.
*
* @throws Exception
*
* @param string $download_file
*
* @return void
*/
private function raise_invalid_file_exception( string $download_file ): void {
throw new Exception(
sprintf(
/* translators: %1$s is the downloadable file path, %2$s is an opening link tag, %3%s is a closing link tag. */
__( 'The downloadable file %s cannot be used as it does not exist on the server, or is not located within an approved directory. Please contact a site administrator for help. %2$sLearn more.%3$s', 'woocommerce' ),
'<code>' . $download_file . '</code>',
'<a href="https://woo.com/document/approved-download-directories">',
'</a>'
)
);
}
/*
|--------------------------------------------------------------------------
| Setters

View File

@@ -509,10 +509,41 @@ class WC_Shortcodes {
return '';
}
$product_id = isset( $atts['id'] ) ? absint( $atts['id'] ) : 0;
if ( ! $product_id && isset( $atts['sku'] ) ) {
$product_id = wc_get_product_id_by_sku( $atts['sku'] );
}
$product_status = empty( $atts['status'] ) ? 'publish' : $atts['status'];
/**
* Filters the list of invalid statuses for the `product_page` shortcode.
*
* @since 8.6.0
* @param array $invalid_statuses List of invalid statuses.
* @param int $product_id Product ID.
* @return array
*/
$invalid_statuses = apply_filters( 'woocommerce_shortcode_product_page_invalid_statuses', array( 'trash' ), $product_id );
if ( in_array( $product_status, $invalid_statuses, true ) ) {
return '';
}
/**
* Filters whether to override read permissions for unpublished products.
*
* @since 8.6.0
* @param bool $force_rendering Whether to override read permissions for unpublished products. `true` to force rendering the product page, `false` to block rendering, or `null` to use the default behavior.
* @param int $product_id Product ID.
* @return bool
*/
$force_rendering = apply_filters( 'woocommerce_shortcode_product_page_force_rendering', null, $product_id );
if ( isset( $force_rendering ) && ! $force_rendering ) {
return '';
}
$args = array(
'posts_per_page' => 1,
'post_type' => 'product',
'post_status' => ( ! empty( $atts['status'] ) ) ? $atts['status'] : 'publish',
'post_status' => $product_status,
'ignore_sticky_posts' => 1,
'no_found_rows' => 1,
);
@@ -541,6 +572,15 @@ class WC_Shortcodes {
$single_product = new WP_Query( $args );
if (
! isset( $force_rendering ) &&
$single_product->have_posts() &&
'publish' !== $single_product->post->post_status &&
! current_user_can( 'read_product', $single_product->post->ID )
) {
return '';
}
$preselected_id = '0';
// Check if sku is a variation.

View File

@@ -13,7 +13,7 @@
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Utilities\NumberUtil;
use Automattic\WooCommerce\Utilities\OrderUtil;
use Automattic\WooCommerce\Utilities\{ LoggingUtil, OrderUtil };
defined( 'ABSPATH' ) || exit;
@@ -614,7 +614,7 @@ class WC_Webhook extends WC_Legacy_Webhook {
* @return string
*/
public function get_delivery_logs() {
return esc_url( add_query_arg( 'log_file', wc_get_log_file_name( 'webhooks-delivery' ), admin_url( 'admin.php?page=wc-status&tab=logs' ) ) );
return add_query_arg( 'source', 'webhooks-delivery', LoggingUtil::get_logs_tab_url() );
}
/**

View File

@@ -23,6 +23,7 @@ use Automattic\WooCommerce\Internal\Settings\OptionSanitizer;
use Automattic\WooCommerce\Internal\Utilities\WebhookUtil;
use Automattic\WooCommerce\Internal\Admin\Marketplace;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Utilities\TimeUtil;
/**
* Main WooCommerce Class.
@@ -36,7 +37,7 @@ final class WooCommerce {
*
* @var string
*/
public $version = '8.5.2';
public $version = '8.6.1';
/**
* WooCommerce Schema version.
@@ -265,6 +266,7 @@ final class WooCommerce {
$container->get( FeaturesController::class );
$container->get( WebhookUtil::class );
$container->get( Marketplace::class );
$container->get( TimeUtil::class );
/**
* These classes have a register method for attaching hooks.
@@ -296,13 +298,32 @@ final class WooCommerce {
public function log_errors() {
$error = error_get_last();
if ( $error && in_array( $error['type'], array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ), true ) ) {
$error_copy = $error;
$message = $error_copy['message'];
unset( $error_copy['message'] );
$context = array(
'source' => 'fatal-errors',
'error' => $error_copy,
);
if ( false !== strpos( $message, 'Stack trace:' ) ) {
$segments = explode( 'Stack trace:', $message );
$message = str_replace( PHP_EOL, ' ', trim( $segments[0] ) );
$backtrace = array_map(
'trim',
explode( PHP_EOL, $segments[1] )
);
$context['backtrace'] = $backtrace;
} else {
$context['backtrace'] = true;
}
$logger = wc_get_logger();
$logger->critical(
/* translators: 1: error message 2: file name and path 3: line number */
sprintf( __( '%1$s in %2$s on line %3$s', 'woocommerce' ), $error['message'], $error['file'], $error['line'] ) . PHP_EOL,
array(
'source' => 'fatal-errors',
)
$message,
$context
);
/**

View File

@@ -441,7 +441,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
global $wpdb;
$usage_count = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT( meta_id ) FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_used_by' AND meta_value = %d;",
"SELECT COUNT( meta_id ) FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_used_by' AND meta_value = %s;",
$coupon->get_id(),
$user_id
)

View File

@@ -146,7 +146,7 @@ class WC_Gateway_BACS extends WC_Payment_Gateway {
<th scope="row" class="titledesc">
<label>
<?php esc_html_e( 'Account details:', 'woocommerce' ); ?>
<?php echo wp_kses_post( wc_help_tip( __( 'These account details will be displayed within the order thank you page and confirmation email.', 'woococommerce' ) ) ); ?>
<?php echo wp_kses_post( wc_help_tip( __( 'These account details will be displayed within the order thank you page and confirmation email.', 'woocommerce' ) ) ); ?>
</label>
</th>
<td class="forminp" id="bacs_accounts">

View File

@@ -114,7 +114,14 @@ class WC_Legacy_API {
* @since 8.5.0
*/
if ( apply_filters( 'woocommerce_log_legacy_rest_api_usages', true ) ) {
wc_get_logger()->info( 'Version: ' . WC_API_REQUEST_VERSION . ", Route: $route, User agent: $user_agent", array( 'source' => 'legacy_rest_api_usages' ) );
$user_agent = sanitize_text_field( wp_unslash( $user_agent ) );
$route = sanitize_text_field( wp_unslash( $route ) );
$info = 'Version: ' . WC_API_REQUEST_VERSION . ", Route: $route, User agent: $user_agent";
$ip_address = WC_Geolocation::get_ip_address();
if( '' !== $ip_address ) {
$info .= ", IP: $ip_address";
}
wc_get_logger()->info( $info, array( 'source' => 'legacy_rest_api_usages' ) );
}
}

View File

@@ -27,7 +27,8 @@ if ( ! function_exists( 'wc_admin_get_feature_config' ) ) {
'product-virtual-downloadable' => true,
'product-external-affiliate' => true,
'product-grouped' => true,
'product-linked' => false,
'product-linked' => true,
'product-pre-publish-modal' => false,
'remote-inbox-notifications' => true,
'remote-free-extensions' => true,
'payment-gateway-suggestions' => true,

View File

@@ -180,15 +180,15 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
$limit = apply_filters( 'woocommerce_rest_batch_items_limit', 100, $this->get_normalized_rest_base() );
$total = 0;
if ( ! empty( $items['create'] ) ) {
if ( ! empty( $items['create'] ) && is_countable( $items['create'] ) ) {
$total += count( $items['create'] );
}
if ( ! empty( $items['update'] ) ) {
if ( ! empty( $items['update'] ) && is_countable( $items['update'] ) ) {
$total += count( $items['update'] );
}
if ( ! empty( $items['delete'] ) ) {
if ( ! empty( $items['delete'] ) && is_countable( $items['delete'] ) ) {
$total += count( $items['delete'] );
}

View File

@@ -111,6 +111,7 @@ class WC_REST_Data_Currencies_Controller extends WC_REST_Data_Controller {
*/
public function get_items( $request ) {
$currencies = get_woocommerce_currencies();
$data = array();
foreach ( array_keys( $currencies ) as $code ) {
$currency = $this->get_currency( $code, $request );
$response = $this->prepare_item_for_response( $currency, $request );

View File

@@ -0,0 +1,146 @@
<?php
/**
* REST API Layout Templates controller
*
* Handles requests to /layout-templates.
*
* @package WooCommerce\RestApi
* @since 8.6.0
*/
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\LayoutTemplates\LayoutTemplateRegistry;
/**
* REST API Layout Templates controller class.
*/
class WC_REST_Layout_Templates_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'layout-templates';
/**
* Register the routes for template layouts.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
'area' => array(
'description' => __( 'Area to get templates for.', 'woocommerce' ),
'type' => 'string',
'default' => '',
),
),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>\w[\w\s\-]*)',
array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(),
),
)
);
}
/**
* Check if a given request has access to read template layouts.
*
* @param WP_REST_Request $request The request.
*/
public function get_items_permissions_check( $request ): bool {
return true;
}
/**
* Check if a given request has access to read a template layout.
*
* @param WP_REST_Request $request The request.
*/
public function get_item_permissions_check( $request ): bool {
return true;
}
/**
* Handle request for template layouts.
*
* @param WP_REST_Request $request The request.
*/
public function get_items( $request ) {
$layout_templates = $this->get_layout_templates(
array(
'area' => $request['area'],
)
);
$response = rest_ensure_response( $layout_templates );
return $response;
}
/**
* Handle request for a single template layout.
*
* @param WP_REST_Request $request The request.
*/
public function get_item( $request ) {
$layout_templates = $this->get_layout_templates(
array(
'id' => $request['id'],
)
);
if ( count( $layout_templates ) !== 1 ) {
return new WP_Error( 'woocommerce_rest_layout_template_invalid_id', __( 'Invalid layout template ID.', 'woocommerce' ), array( 'status' => 404 ) );
}
$response = rest_ensure_response( current( $layout_templates ) );
return $response;
}
/**
* Get layout templates.
*
* @param array $query_params Query params.
*/
private function get_layout_templates( array $query_params ): array {
$layout_template_registry = wc_get_container()->get( LayoutTemplateRegistry::class );
return array_map(
function( $layout_template ) {
return $layout_template->to_json();
},
$layout_template_registry->instantiate_layout_templates( $query_params )
);
}
}

View File

@@ -735,6 +735,7 @@ class WC_REST_Product_Reviews_Controller extends WC_REST_Controller {
* @return array|WP_Error $prepared_review
*/
protected function prepare_item_for_database( $request ) {
$prepared_review = array();
if ( isset( $request['id'] ) ) {
$prepared_review['comment_ID'] = (int) $request['id'];
}

View File

@@ -36,6 +36,34 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
*/
private $search_sku_in_product_lookup_table = '';
/**
* Suggested product ids.
*
* @var array
*/
private $suggested_products_ids = array();
/**
* Register the routes for products.
*/
public function register_routes() {
parent::register_routes();
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/suggested-products',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_suggested_products' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_suggested_products_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Get the images for a product or product variation.
*
@@ -235,6 +263,15 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
$args['meta_key'] = $ordering_args['meta_key']; // WPCS: slow query ok.
}
/*
* When the suggested products ids is not empty,
* filter the query to return only the suggested products,
* overwriting the post__in parameter.
*/
if ( ! empty( $this->suggested_products_ids ) ) {
$args['post__in'] = $this->suggested_products_ids;
}
return $args;
}
@@ -1483,6 +1520,72 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
return $params;
}
/**
* Add new options for the suggested-products endpoint.
*
* @return array
*/
public function get_suggested_products_collection_params() {
$params = parent::get_collection_params();
$params['categories'] = array(
'description' => __( 'Limit result set to specific product categorie ids.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
'sanitize_callback' => 'wp_parse_id_list',
'validate_callback' => 'rest_validate_request_arg',
);
$params['tags'] = array(
'description' => __( 'Limit result set to specific product tag ids.', 'woocommerce' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
'validate_callback' => 'rest_validate_request_arg',
'sanitize_callback' => 'wp_parse_id_list',
);
$params['limit'] = array(
'description' => __( 'Limit result set to specific amount of suggested products.', 'woocommerce' ),
'type' => 'integer',
'default' => 5,
'validate_callback' => 'rest_validate_request_arg',
'sanitize_callback' => 'absint',
);
return $params;
}
/**
* Get the downloads for a product.
*
* @param WC_Product $product Product instance.
*
* @return array
*/
protected function get_downloads( $product ) {
$downloads = array();
$context = isset( $this->request ) && isset( $this->request['context'] ) ? $this->request['context'] : 'view';
if ( $product->is_downloadable() || 'edit' === $context ) {
foreach ( $product->get_downloads() as $file_id => $file ) {
$downloads[] = array(
'id' => $file_id, // MD5 hash.
'name' => $file['name'],
'file' => $file['file'],
);
}
}
return $downloads;
}
/**
* Get product data.
*
@@ -1538,4 +1641,36 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
return $data;
}
/**
* Get the suggested products.
*
* @param WP_REST_Request $request Request object.
* @return object
*/
public function get_suggested_products( $request ) {
$categories = $request->get_param( 'categories' );
$tags = $request->get_param( 'tags' );
$exclude_ids = $request->get_param( 'exclude' );
$limit = $request->get_param( 'limit' ) ? $request->get_param( 'limit' ) : 5;
$data_store = WC_Data_Store::load( 'product' );
$this->suggested_products_ids = $data_store->get_related_products(
$categories,
$tags,
$exclude_ids,
$limit,
null // No need to pass the product ID.
);
// When no suggested products are found, return an empty array.
if ( empty( $this->suggested_products_ids ) ) {
return array();
}
// Ensure to respect the limit, since the data store may return more than the limit.
$this->suggested_products_ids = array_slice( $this->suggested_products_ids, 0, $limit );
return parent::get_items( $request );
}
}

View File

@@ -143,6 +143,7 @@ class Server {
'coupons' => 'WC_REST_Coupons_Controller',
'customer-downloads' => 'WC_REST_Customer_Downloads_Controller',
'customers' => 'WC_REST_Customers_Controller',
'layout-templates' => 'WC_REST_Layout_Templates_Controller',
'network-orders' => 'WC_REST_Network_Orders_Controller',
'order-notes' => 'WC_REST_Order_Notes_Controller',
'order-refunds' => 'WC_REST_Order_Refunds_Controller',

View File

@@ -10,6 +10,8 @@
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\Utilities\Users;
/**
* Shortcode checkout class.
*/
@@ -382,75 +384,15 @@ class WC_Shortcode_Checkout {
* @return bool
*/
private static function guest_should_verify_email( WC_Order $order, string $context ): bool {
$order_email = $order->get_billing_email();
$order_customer_id = $order->get_customer_id();
// If we cannot match the order with the current user, ask that they verify their email address.
$nonce_is_valid = wp_verify_nonce( filter_input( INPUT_POST, 'check_submission' ), 'wc_verify_email' );
$supplied_email = null;
$order_id = $order->get_id();
// If we do not have a billing email for the order (could happen in the order is created manually, or if the
// requirement for this has been removed from the checkout flow), email verification does not make sense.
if ( empty( $order_email ) ) {
return false;
if ( $nonce_is_valid ) {
$supplied_email = sanitize_email( wp_unslash( filter_input( INPUT_POST, 'email' ) ) );
}
// No verification step is needed if the user is logged in and is already associated with the order.
if ( $order_customer_id && get_current_user_id() === $order_customer_id ) {
return false;
}
/**
* Controls the grace period within which we do not require any sort of email verification step before rendering
* the 'order received' or 'order pay' pages.
*
* To eliminate the grace period, set to zero (or to a negative value). Note that this filter is not invoked
* at all if email verification is deemed to be unnecessary (in other words, it cannot be used to force
* verification in *all* cases).
*
* @since 8.0.0
*
* @param int $grace_period Time in seconds after an order is placed before email verification may be required.
* @param WC_Order $order The order for which this grace period is being assessed.
* @param string $context Indicates the context in which we might verify the email address. Typically 'order-pay' or 'order-received'.
*/
$verification_grace_period = (int) apply_filters( 'woocommerce_order_email_verification_grace_period', 10 * MINUTE_IN_SECONDS, $order, $context );
$date_created = $order->get_date_created();
// We do not need to verify the email address if we are within the grace period immediately following order creation.
if (
is_a( $date_created, WC_DateTime::class )
&& time() - $date_created->getTimestamp() <= $verification_grace_period
) {
return false;
}
$session = wc()->session;
$session_email = '';
if ( is_a( $session, WC_Session::class ) ) {
$customer = $session->get( 'customer' );
$session_email = is_array( $customer ) && isset( $customer['email'] ) ? $customer['email'] : '';
}
// Email verification is required if the user cannot be identified, or if they supplied an email address but the nonce check failed.
$can_view_orders = current_user_can( 'read_private_shop_orders' );
$session_email_match = $session_email === $order->get_billing_email();
$supplied_email_match = sanitize_email( wp_unslash( filter_input( INPUT_POST, 'email' ) ) ) === $order->get_billing_email()
&& wp_verify_nonce( filter_input( INPUT_POST, 'check_submission' ), 'wc_verify_email' );
$email_verification_required = ! $session_email_match && ! $supplied_email_match && ! $can_view_orders;
/**
* Provides an opportunity to override the (potential) requirement for shoppers to verify their email address
* before we show information such as the order summary, or order payment page.
*
* Note that this hook is not always triggered, therefore it is (for example) unsuitable as a way of forcing
* email verification across all order confirmation/order payment scenarios. Instead, the filter primarily
* exists as a way to *remove* the email verification step.
*
* @since 7.9.0
*
* @param bool $email_verification_required If email verification is required.
* @param WC_Order $order The relevant order.
* @param string $context The context under which we are performing this check.
*/
return (bool) apply_filters( 'woocommerce_order_email_verification_required', $email_verification_required, $order, $context );
return Users::should_user_verify_order_email( $order_id, $supplied_email, $context );
}
}

View File

@@ -26,6 +26,9 @@ class WC_Twenty_Twelve {
add_action( 'woocommerce_before_main_content', array( __CLASS__, 'output_content_wrapper' ) );
add_action( 'woocommerce_after_main_content', array( __CLASS__, 'output_content_wrapper_end' ) );
// Enqueue theme compatibility styles.
add_action( 'wp_head', array( __CLASS__, 'enqueue_styles' ) );
// Declare theme support for features.
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
@@ -52,6 +55,21 @@ class WC_Twenty_Twelve {
public static function output_content_wrapper_end() {
echo '</div></div>';
}
/**
* Add theme compatibility styles.
*
* @return void
*/
public static function enqueue_styles() {
?>
<style type="text/css">
.wc-block-components-notice-banner.is-error li {
margin: 0;
}
</style>
<?php
}
}
WC_Twenty_Twelve::init();

View File

@@ -19,23 +19,35 @@ class WC_Orders_Tracking {
*/
public function init() {
add_action( 'woocommerce_order_status_changed', array( $this, 'track_order_status_change' ), 10, 3 );
add_action( 'load-edit.php', array( $this, 'track_orders_view' ), 10 );
add_action( 'pre_post_update', array( $this, 'track_created_date_change' ), 10 );
// WC_Meta_Box_Order_Actions::save() hooks in at priority 50.
add_action( 'woocommerce_process_shop_order_meta', array( $this, 'track_order_action' ), 51 );
add_action( 'load-edit.php', array( $this, 'track_orders_view' ), 10 );
add_action( 'load-woocommerce_page_wc-orders', array( $this, 'track_orders_view' ), 999 ); // HPOS.
add_action( 'load-post-new.php', array( $this, 'track_add_order_from_edit' ), 10 );
add_filter( 'woocommerce_shop_order_search_results', array( $this, 'track_order_search' ), 10, 3 );
add_action( 'load-woocommerce_page_wc-orders', array( $this, 'track_add_order_from_edit' ), 999 ); // HPOS.
add_action( 'woocommerce_process_shop_order_meta', array( $this, 'track_created_date_change' ), 10 );
add_action( 'load-edit.php', array( $this, 'track_search_in_orders_list' ) );
add_action( 'load-woocommerce_page_wc-orders', array( $this, 'track_search_in_orders_list' ), 999 ); // HPOS.
add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_order_tracking_scripts' ) );
}
/**
* Send a track event when on the Order Listing page, and search results are being displayed.
*
* @deprecated 8.6.0
*
* @param array $order_ids Array of order_ids that are matches for the search.
* @param string $term The string that was used in the search.
* @param array $search_fields Fields that were used in the original search.
*/
public function track_order_search( $order_ids, $term, $search_fields ) {
wc_deprecated_function( __METHOD__, '8.6.0', 'WC_Orders_Tracking::track_search_in_orders_list' );
// Since `woocommerce_shop_order_search_results` can run in the front-end context, exit if get_current_screen isn't defined.
if ( ! function_exists( 'get_current_screen' ) ) {
return $order_ids;
@@ -52,20 +64,34 @@ class WC_Orders_Tracking {
return $order_ids;
}
/**
* Send a track event when on the Order Listing page, and search results are being displayed.
*
* @since 8.6.0
*/
public function track_search_in_orders_list() {
if ( ! OrderUtil::is_order_list_table_screen() || empty( $_REQUEST['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
WC_Tracks::record_event( 'orders_view_search' );
}
/**
* Send a Tracks event when the Orders page is viewed.
*/
public function track_orders_view() {
if ( isset( $_GET['post_type'] ) && 'shop_order' === wp_unslash( $_GET['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
// phpcs:disable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
$properties = array(
'status' => isset( $_GET['post_status'] ) ? sanitize_text_field( $_GET['post_status'] ) : 'all',
);
// phpcs:enable
WC_Tracks::record_event( 'orders_view', $properties );
if ( ! OrderUtil::is_order_list_table_screen() ) {
return;
}
// phpcs:disable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
$properties = array(
'status' => sanitize_text_field( $_GET['post_status'] ?? ( $_GET['status'] ?? 'all' ) ),
);
// phpcs:enable
WC_Tracks::record_event( 'orders_view', $properties );
}
/**
@@ -100,15 +126,15 @@ class WC_Orders_Tracking {
return;
}
if ( 'auto-draft' === get_post_status( $id ) ) {
$order = wc_get_order( $id );
if ( ! $order || 'auto-draft' === $order->get_status() ) {
return;
}
$order = wc_get_order( $id );
$date_created = $order->get_date_created() ? $order->get_date_created()->date( 'Y-m-d H:i:s' ) : '';
// phpcs:disable WordPress.Security.NonceVerification
$new_date = sprintf(
'%s %2d:%2d:%2d',
'%s %2d:%02d:%02d',
isset( $_POST['order_date'] ) ? wc_clean( wp_unslash( $_POST['order_date'] ) ) : '',
isset( $_POST['order_date_hour'] ) ? wc_clean( wp_unslash( $_POST['order_date_hour'] ) ) : '',
isset( $_POST['order_date_minute'] ) ? wc_clean( wp_unslash( $_POST['order_date_minute'] ) ) : '',
@@ -151,46 +177,50 @@ class WC_Orders_Tracking {
* Track "add order" button on the Edit Order screen.
*/
public function track_add_order_from_edit() {
// phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( isset( $_GET['post_type'] ) && 'shop_order' === wp_unslash( $_GET['post_type'] ) ) {
$referer = wp_get_referer();
if ( ! OrderUtil::is_new_order_screen() ) {
return;
}
if ( $referer ) {
$referring_page = wp_parse_url( $referer );
$referring_args = array();
$post_edit_page = wp_parse_url( admin_url( 'post.php' ) );
$referer = wp_get_referer();
if ( ! $referer ) {
return;
}
if ( ! empty( $referring_page['query'] ) ) {
parse_str( $referring_page['query'], $referring_args );
}
$referring_page = wp_parse_url( $referer );
// Determine if we arrived from an Order Edit screen.
if (
$post_edit_page['path'] === $referring_page['path'] &&
isset( $referring_args['action'] ) &&
'edit' === $referring_args['action'] &&
isset( $referring_args['post'] ) &&
'shop_order' === OrderUtil::get_order_type( $referring_args['post'] )
) {
WC_Tracks::record_event( 'order_edit_add_order' );
}
}
if ( empty( $referring_page['query'] ) ) {
// Edit Order screen has query args.
return;
}
parse_str( $referring_page['query'], $referring_args );
if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
$post_edit_page = admin_url( 'admin.php?page=wc-orders' );
$order_id = $referring_args['id'] ?? 0;
} else {
$post_edit_page = admin_url( 'post.php' );
$order_id = $referring_args['post'] ?? 0;
}
$post_edit_page = wp_parse_url( $post_edit_page );
if (
( $post_edit_page['path'] === $referring_page['path'] ) &&
( ! isset( $post_edit_page['query'] ) || false !== strpos( $referring_page['query'], $post_edit_page['query'] ) ) &&
( isset( $referring_args['action'] ) && 'edit' === $referring_args['action'] ) &&
'shop_order' === OrderUtil::get_order_type( $order_id )
) {
WC_Tracks::record_event( 'order_edit_add_order' );
}
}
/**
* Adds the tracking scripts for product setting pages.
*
* @param string $hook Page hook.
*/
public function possibly_add_order_tracking_scripts( $hook ) {
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification
if (
( isset( $_GET['post_type'] ) && 'shop_order' === wp_unslash( $_GET['post_type'] ) ) ||
( 'post.php' === $hook && isset( $_GET['post'] ) && 'shop_order' === get_post_type( intval( $_GET['post'] ) ) )
) {
WCAdminAssets::register_script( 'wp-admin-scripts', 'order-tracking', false );
public function possibly_add_order_tracking_scripts() {
if ( ! OrderUtil::is_new_order_screen() && ! OrderUtil::is_order_edit_screen() && ! OrderUtil::is_order_list_table_screen() ) {
return;
}
// phpcs:enable
WCAdminAssets::register_script( 'wp-admin-scripts', 'order-tracking', false );
}
}

View File

@@ -1124,30 +1124,6 @@ function get_woocommerce_api_url( $path ) {
return $url;
}
/**
* Get a log file path.
*
* @since 2.2
*
* @param string $handle name.
* @return string the log file path.
*/
function wc_get_log_file_path( $handle ) {
return WC_Log_Handler_File::get_log_file_path( $handle );
}
/**
* Get a log file name.
*
* @since 3.3
*
* @param string $handle Name.
* @return string The log file name.
*/
function wc_get_log_file_name( $handle ) {
return WC_Log_Handler_File::get_log_file_name( $handle );
}
/**
* Recursively get page children.
*
@@ -2101,25 +2077,6 @@ function wc_print_r( $expression, $return = false ) {
return false;
}
/**
* Registers the default log handler.
*
* @since 3.0
* @param array $handlers Handlers.
* @return array
*/
function wc_register_default_log_handler( $handlers ) {
$handler_class = Constants::get_constant( 'WC_LOG_HANDLER' );
if ( is_null( $handler_class ) || ! class_exists( $handler_class ) ) {
$handler_class = WC_Log_Handler_File::class;
}
array_push( $handlers, new $handler_class() );
return $handlers;
}
add_filter( 'woocommerce_register_log_handlers', 'wc_register_default_log_handler' );
/**
* Based on wp_list_pluck, this calls a method instead of returning a property.
*

View File

@@ -11,6 +11,8 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\Logging\Settings;
use Automattic\WooCommerce\Utilities\LoggingUtil;
if ( ! defined( 'ABSPATH' ) ) {
exit;
@@ -1123,3 +1125,57 @@ function get_woocommerce_term_meta( $term_id, $key, $single = true ) {
wc_deprecated_function( 'get_woocommerce_term_meta', '3.6', 'get_term_meta' );
return function_exists( 'get_term_meta' ) ? get_term_meta( $term_id, $key, $single ) : get_metadata( 'woocommerce_term', $term_id, $key, $single );
}
/**
* Registers the default log handler.
*
* @deprecated 8.6.0
* @since 3.0
* @param array $handlers Handlers.
* @return array
*/
function wc_register_default_log_handler( $handlers = array() ) {
wc_deprecated_function( 'wc_register_default_log_handler', '8.6.0' );
$default_handler = wc_get_container()->get( Settings::class )->get_default_handler();
array_push( $handlers, new $default_handler() );
return $handlers;
}
/**
* Get a log file path.
*
* @deprecated 8.6.0
* @since 2.2
*
* @param string $handle name.
* @return string the log file path.
*/
function wc_get_log_file_path( $handle ) {
wc_deprecated_function( 'wc_get_log_file_path', '8.6.0' );
$directory = trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) );
$file_id = LoggingUtil::generate_log_file_id( $handle, null, time() );
$hash = LoggingUtil::generate_log_file_hash( $file_id );
return "{$directory}{$file_id}-{$hash}.log";
}
/**
* Get a log file name.
*
* @since 3.3
*
* @param string $handle Name.
* @return string The log file name.
*/
function wc_get_log_file_name( $handle ) {
wc_deprecated_function( 'wc_get_log_file_name', '8.6.0' );
$file_id = LoggingUtil::generate_log_file_id( $handle, null, time() );
$hash = LoggingUtil::generate_log_file_hash( $file_id );
return "{$file_id}-{$hash}";
}

View File

@@ -2460,7 +2460,7 @@ function wc_update_700_remove_download_log_fk() {
* Remove the transient data for recommended marketing extensions.
*/
function wc_update_700_remove_recommended_marketing_plugins_transient() {
delete_transient( MarketingSpecs::RECOMMENDED_PLUGINS_TRANSIENT );
delete_transient( 'wc_marketing_recommended_plugins' );
}
/**
@@ -2639,3 +2639,13 @@ LIMIT 250
return ! empty( $has_pending );
}
/**
* Remove the transient data for recommended marketing extensions.
*
* This is removed because it is not used anymore.
* It is replaced by `woocommerce_admin_marketing_recommendations_specs` transient that is created by `MarketingRecommendationsDataSourcePoller`.
*/
function wc_update_860_remove_recommended_marketing_plugins_transient() {
delete_transient( 'wc_marketing_recommended_plugins' );
}

View File

@@ -1,62 +0,0 @@
<?php
/**
* Woo.com Product Installation Requirements Check.
*
* @package WooCommerce\WCCom
* @since 3.8.0
*/
use Automattic\Jetpack\Constants;
defined( 'ABSPATH' ) || exit;
/**
* WC_WCCOM_Site_Installer_Requirements_Check Class
* Contains functionality to check the necessary requirements for the installer.
*/
class WC_WCCOM_Site_Installer_Requirements_Check {
/**
* Check if the site met the requirements
*
* @version 3.8.0
* @return bool|WP_Error Does the site met the requirements?
*/
public static function met_requirements() {
$errs = array();
if ( ! self::met_wp_cron_requirement() ) {
$errs[] = 'wp-cron';
}
if ( ! self::met_filesystem_requirement() ) {
$errs[] = 'filesystem';
}
if ( ! empty( $errs ) ) {
// translators: %s: Requirements unmet.
return new WP_Error( 'requirements_not_met', sprintf( __( 'Server requirements not met, missing requirement(s): %s.', 'woocommerce' ), implode( ', ', $errs ) ), array( 'status' => 400 ) );
}
return true;
}
/**
* Validates if WP CRON is enabled.
*
* @since 3.8.0
* @return bool
*/
private static function met_wp_cron_requirement() {
return ! Constants::is_true( 'DISABLE_WP_CRON' );
}
/**
* Validates if `WP_CONTENT_DIR` is writable.
*
* @since 3.8.0
* @return bool
*/
private static function met_filesystem_requirement() {
return is_writable( WP_CONTENT_DIR );
}
}

View File

@@ -15,54 +15,6 @@ defined( 'ABSPATH' ) || exit;
*/
class WC_WCCOM_Site_Installer {
/**
* Error message returned install_package if the folder already exists.
*
* @var string
*/
private static $folder_exists = 'folder_exists';
/**
* Default state.
*
* @var array
*/
private static $default_state = array(
'status' => 'idle',
'steps' => array(),
'current_step' => null,
);
/**
* Represents product step state.
*
* @var array
*/
private static $default_step_state = array(
'download_url' => '',
'product_type' => '',
'last_step' => '',
'last_error' => '',
'download_path' => '',
'unpacked_path' => '',
'installed_path' => '',
'activate' => false,
);
/**
* Product install steps. Each step is a method name in this class that
* will be passed with product ID arg \WP_Upgrader instance.
*
* @var array
*/
private static $install_steps = array(
'get_product_info',
'download_product',
'unpack_product',
'move_product',
'activate_product',
);
/**
* An instance of the WP_Upgrader class to be used for installation.
*
@@ -70,447 +22,6 @@ class WC_WCCOM_Site_Installer {
*/
private static $wp_upgrader;
/**
* Get the product install state.
*
* @since 3.7.0
* @param string $key Key in state data. If empty key is passed array of
* state will be returned.
* @return array Product install state.
*/
public static function get_state( $key = '' ) {
$state = WC_Helper_Options::get( 'product_install', self::$default_state );
if ( ! empty( $key ) ) {
return isset( $state[ $key ] ) ? $state[ $key ] : null;
}
return $state;
}
/**
* Update the product install state.
*
* @since 3.7.0
* @param string $key Key in state data.
* @param mixed $value Value.
*/
public static function update_state( $key, $value ) {
$state = WC_Helper_Options::get( 'product_install', self::$default_state );
$state[ $key ] = $value;
WC_Helper_Options::update( 'product_install', $state );
}
/**
* Reset product install state.
*
* @since 3.7.0
* @param array $products List of product IDs.
*/
public static function reset_state( $products = array() ) {
WC()->queue()->cancel_all( 'woocommerce_wccom_install_products' );
WC_Helper_Options::update( 'product_install', self::$default_state );
}
/**
* Schedule installing given list of products.
*
* @since 3.7.0
* @param array $products Array of products where key is product ID and
* element is install args.
* @return array State.
*/
public static function schedule_install( $products ) {
$state = self::get_state();
$status = ! empty( $state['status'] ) ? $state['status'] : '';
if ( 'in-progress' === $status ) {
return $state;
}
self::update_state( 'status', 'in-progress' );
$steps = array_fill_keys( array_keys( $products ), self::$default_step_state );
self::update_state( 'steps', $steps );
self::update_state( 'current_step', null );
$args = array(
'products' => $products,
);
// Clear the cache of customer's subscription before asking for them.
// Thus, they will be re-fetched from Woo.com after a purchase.
WC_Helper::_flush_subscriptions_cache();
WC()->queue()->cancel_all( 'woocommerce_wccom_install_products', $args );
WC()->queue()->add( 'woocommerce_wccom_install_products', $args );
return self::get_state();
}
/**
* Install a given product IDs.
*
* Run via `woocommerce_wccom_install_products` hook.
*
* @since 3.7.0
* @param array $products Array of products where key is product ID and
* element is install args.
*/
public static function install( $products ) {
$upgrader = self::get_wp_upgrader();
foreach ( $products as $product_id => $install_args ) {
self::install_product( $product_id, $install_args, $upgrader );
}
self::finish_installation();
}
/**
* Finish installation by updating the state.
*
* @since 3.7.0
*/
private static function finish_installation() {
$state = self::get_state();
if ( empty( $state['steps'] ) ) {
return;
}
foreach ( $state['steps'] as $step ) {
if ( ! empty( $step['last_error'] ) ) {
$state['status'] = 'has_error';
break;
}
}
if ( 'has_error' !== $state['status'] ) {
$state['status'] = 'finished';
}
WC_Helper_Options::update( 'product_install', $state );
}
/**
* Install a single product given its ID.
*
* @since 3.7.0
* @param int $product_id Product ID.
* @param array $install_args Install args.
* @param \WP_Upgrader $upgrader Core class to handle installation.
*/
private static function install_product( $product_id, $install_args, $upgrader ) {
foreach ( self::$install_steps as $step ) {
self::do_install_step( $product_id, $install_args, $step, $upgrader );
}
}
/**
* Perform product installation step.
*
* @since 3.7.0
* @param int $product_id Product ID.
* @param array $install_args Install args.
* @param string $step Installation step.
* @param \WP_Upgrader $upgrader Core class to handle installation.
*/
private static function do_install_step( $product_id, $install_args, $step, $upgrader ) {
$state_steps = self::get_state( 'steps' );
if ( empty( $state_steps[ $product_id ] ) ) {
$state_steps[ $product_id ] = self::$default_step_state;
}
if ( ! empty( $state_steps[ $product_id ]['last_error'] ) ) {
return;
}
$state_steps[ $product_id ]['last_step'] = $step;
if ( ! empty( $install_args['activate'] ) ) {
$state_steps[ $product_id ]['activate'] = true;
}
self::update_state(
'current_step',
array(
'product_id' => $product_id,
'step' => $step,
)
);
$result = call_user_func( array( __CLASS__, $step ), $product_id, $upgrader );
if ( is_wp_error( $result ) ) {
$state_steps[ $product_id ]['last_error'] = $result->get_error_message();
} else {
switch ( $step ) {
case 'get_product_info':
$state_steps[ $product_id ]['download_url'] = $result['download_url'];
$state_steps[ $product_id ]['product_type'] = $result['product_type'];
$state_steps[ $product_id ]['product_name'] = $result['product_name'];
break;
case 'download_product':
$state_steps[ $product_id ]['download_path'] = $result;
break;
case 'unpack_product':
$state_steps[ $product_id ]['unpacked_path'] = $result;
break;
case 'move_product':
$state_steps[ $product_id ]['installed_path'] = $result['destination'];
if ( isset( $result[ self::$folder_exists ] ) ) {
$state_steps[ $product_id ]['warning'] = array(
'message' => self::$folder_exists,
'plugin_info' => self::get_plugin_info( $state_steps[ $product_id ]['installed_path'] ),
);
}
break;
}
}
self::update_state( 'steps', $state_steps );
}
/**
* Get product info from its ID.
*
* @since 3.7.0
* @param int $product_id Product ID.
* @return array|\WP_Error
*/
private static function get_product_info( $product_id ) {
$product_info = array(
'download_url' => '',
'product_type' => '',
);
// Get product info from Woo.com.
$request = WC_Helper_API::get(
add_query_arg(
array( 'product_id' => absint( $product_id ) ),
'info'
),
array(
'authenticated' => true,
)
);
if ( 200 !== wp_remote_retrieve_response_code( $request ) ) {
return new WP_Error( 'product_info_failed', __( 'Failed to retrieve product info from Woo.com', 'woocommerce' ) );
}
$result = json_decode( wp_remote_retrieve_body( $request ), true );
$product_info['product_type'] = $result['_product_type'];
$product_info['product_name'] = $result['name'];
if ( ! empty( $result['_wporg_product'] ) && ! empty( $result['download_link'] ) ) {
// For wporg product, download is set already from info response.
$product_info['download_url'] = $result['download_link'];
} elseif ( ! WC_Helper::has_product_subscription( $product_id ) ) {
// Non-wporg product needs subscription.
return new WP_Error( 'missing_subscription', __( 'Missing product subscription', 'woocommerce' ) );
} else {
// Retrieve download URL for non-wporg product.
WC_Helper_Updater::flush_updates_cache();
$updates = WC_Helper_Updater::get_update_data();
if ( empty( $updates[ $product_id ]['package'] ) ) {
return new WP_Error( 'missing_product_package', __( 'Could not find product package.', 'woocommerce' ) );
}
$product_info['download_url'] = $updates[ $product_id ]['package'];
}
return $product_info;
}
/**
* Download product by its ID and returns the path of the zip package.
*
* @since 3.7.0
* @param int $product_id Product ID.
* @param \WP_Upgrader $upgrader Core class to handle installation.
* @return \WP_Error|string
*/
private static function download_product( $product_id, $upgrader ) {
$steps = self::get_state( 'steps' );
if ( empty( $steps[ $product_id ]['download_url'] ) ) {
return new WP_Error( 'missing_download_url', __( 'Could not find download url for the product.', 'woocommerce' ) );
}
return $upgrader->download_package( $steps[ $product_id ]['download_url'] );
}
/**
* Unpack downloaded product.
*
* @since 3.7.0
* @param int $product_id Product ID.
* @param \WP_Upgrader $upgrader Core class to handle installation.
* @return \WP_Error|string
*/
private static function unpack_product( $product_id, $upgrader ) {
$steps = self::get_state( 'steps' );
if ( empty( $steps[ $product_id ]['download_path'] ) ) {
return new WP_Error( 'missing_download_path', __( 'Could not find download path.', 'woocommerce' ) );
}
return $upgrader->unpack_package( $steps[ $product_id ]['download_path'], true );
}
/**
* Move product to plugins directory.
*
* @since 3.7.0
* @param int $product_id Product ID.
* @param \WP_Upgrader $upgrader Core class to handle installation.
* @return array|\WP_Error
*/
private static function move_product( $product_id, $upgrader ) {
$steps = self::get_state( 'steps' );
if ( empty( $steps[ $product_id ]['unpacked_path'] ) ) {
return new WP_Error( 'missing_unpacked_path', __( 'Could not find unpacked path.', 'woocommerce' ) );
}
$destination = 'plugin' === $steps[ $product_id ]['product_type']
? WP_PLUGIN_DIR
: get_theme_root();
$package = array(
'source' => $steps[ $product_id ]['unpacked_path'],
'destination' => $destination,
'clear_working' => true,
'hook_extra' => array(
'type' => $steps[ $product_id ]['product_type'],
'action' => 'install',
),
);
$result = $upgrader->install_package( $package );
/**
* If install package returns error 'folder_exists' threat as success.
*/
if ( is_wp_error( $result ) && array_key_exists( self::$folder_exists, $result->errors ) ) {
return array(
self::$folder_exists => true,
'destination' => $result->error_data[ self::$folder_exists ],
);
}
return $result;
}
/**
* Activate product given its product ID.
*
* @since 3.7.0
* @param int $product_id Product ID.
* @return \WP_Error|null
*/
private static function activate_product( $product_id ) {
$steps = self::get_state( 'steps' );
if ( ! $steps[ $product_id ]['activate'] ) {
return null;
}
if ( 'plugin' === $steps[ $product_id ]['product_type'] ) {
return self::activate_plugin( $product_id );
}
return self::activate_theme( $product_id );
}
/**
* Activate plugin given its product ID.
*
* @since 3.7.0
* @param int $product_id Product ID.
* @return \WP_Error|null
*/
public static function activate_plugin( $product_id ) {
// Clear plugins cache used in `WC_Helper::get_local_woo_plugins`.
wp_clean_plugins_cache();
$filename = false;
// If product is WP.org one, find out its filename.
$dir_name = self::get_wporg_product_dir_name( $product_id );
if ( false !== $dir_name ) {
$filename = self::get_wporg_plugin_main_file( $dir_name );
}
if ( false === $filename ) {
$plugins = wp_list_filter(
WC_Helper::get_local_woo_plugins(),
array(
'_product_id' => $product_id,
)
);
$filename = is_array( $plugins ) && ! empty( $plugins ) ? key( $plugins ) : '';
}
if ( empty( $filename ) ) {
return new WP_Error( 'unknown_filename', __( 'Unknown product filename.', 'woocommerce' ) );
}
return activate_plugin( $filename );
}
/**
* Activate theme given its product ID.
*
* @since 3.7.0
* @param int $product_id Product ID.
* @return \WP_Error|null
*/
private static function activate_theme( $product_id ) {
// Clear plugins cache used in `WC_Helper::get_local_woo_themes`.
wp_clean_themes_cache();
$theme_slug = false;
// If product is WP.org theme, find out its slug.
$dir_name = self::get_wporg_product_dir_name( $product_id );
if ( false !== $dir_name ) {
$theme_slug = basename( $dir_name );
}
if ( false === $theme_slug ) {
$themes = wp_list_filter(
WC_Helper::get_local_woo_themes(),
array(
'_product_id' => $product_id,
)
);
$theme_slug = is_array( $themes ) && ! empty( $themes ) ? dirname( key( $themes ) ) : '';
}
if ( empty( $theme_slug ) ) {
return new WP_Error( 'unknown_filename', __( 'Unknown product filename.', 'woocommerce' ) );
}
return switch_theme( $theme_slug );
}
/**
* Get installed directory of WP.org product.
*
* @since 3.7.0
* @param int $product_id Product ID.
* @return bool|string
*/
private static function get_wporg_product_dir_name( $product_id ) {
$steps = self::get_state( 'steps' );
$product = $steps[ $product_id ];
if ( empty( $product['download_url'] ) || empty( $product['installed_path'] ) ) {
return false;
}
// Check whether product was downloaded from WordPress.org.
$parsed_url = wp_parse_url( $product['download_url'] );
if ( ! empty( $parsed_url['host'] ) && 'downloads.wordpress.org' !== $parsed_url['host'] ) {
return false;
}
return basename( $product['installed_path'] );
}
/**
* Get WP.org plugin's main file.

View File

@@ -41,7 +41,6 @@ class WC_WCCOM_Site {
protected static function includes() {
require_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper.php';
require_once WC_ABSPATH . 'includes/wccom-site/class-wc-wccom-site-installer.php';
require_once WC_ABSPATH . 'includes/wccom-site/class-wc-wccom-site-installer-requirements-check.php';
}
/**
@@ -221,8 +220,8 @@ class WC_WCCOM_Site {
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/class-wc-rest-wccom-site-installer-error-codes.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/class-wc-rest-wccom-site-installer-error.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/abstract-wc-rest-wccom-site-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-installer-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-installer-controller-v2.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-ssr-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/class-wc-wccom-site-installation-state.php';
@@ -236,15 +235,11 @@ class WC_WCCOM_Site {
require_once WC_ABSPATH . 'includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-move-product.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-activate-product.php';
$namespaces['wccom-site/v1'] = array(
$namespaces['wccom-site/v2'] = array(
'installer' => 'WC_REST_WCCOM_Site_Installer_Controller',
'ssr' => 'WC_REST_WCCOM_Site_SSR_Controller',
);
$namespaces['wccom-site/v2'] = array(
'installer' => 'WC_REST_WCCOM_Site_Installer_Controller_V2',
);
return $namespaces;
}
}

View File

@@ -50,6 +50,8 @@ class WC_WCCOM_Site_Installation_Step_Activate_Product implements WC_WCCOM_Site_
* Activate plugin.
*
* @param int $product_id Product ID.
* @return void
* @throws WC_REST_WCCOM_Site_Installer_Error If plugin activation failed.
*/
private function activate_plugin( $product_id ) {
// Clear plugins cache used in `WC_Helper::get_local_woo_plugins`.
@@ -74,13 +76,13 @@ class WC_WCCOM_Site_Installation_Step_Activate_Product implements WC_WCCOM_Site_
}
if ( empty( $filename ) ) {
return new Installer_Error( Installer_Error_Codes::UNKNOWN_FILENAME );
throw new Installer_Error( Installer_Error_Codes::UNKNOWN_FILENAME );
}
$result = activate_plugin( $filename );
if ( is_wp_error( $result ) ) {
return new Installer_Error( Installer_Error_Codes::PLUGIN_ACTIVATION_ERROR, $result->get_error_message() );
throw new Installer_Error( Installer_Error_Codes::PLUGIN_ACTIVATION_ERROR, $result->get_error_message() );
}
}
@@ -88,6 +90,8 @@ class WC_WCCOM_Site_Installation_Step_Activate_Product implements WC_WCCOM_Site_
* Activate theme.
*
* @param int $product_id Product ID.
* @return void
* @throws WC_REST_WCCOM_Site_Installer_Error If theme activation failed.
*/
private function activate_theme( $product_id ) {
// Clear plugins cache used in `WC_Helper::get_local_woo_themes`.
@@ -112,7 +116,7 @@ class WC_WCCOM_Site_Installation_Step_Activate_Product implements WC_WCCOM_Site_
}
if ( empty( $theme_slug ) ) {
return new Installer_Error( Installer_Error_Codes::UNKNOWN_FILENAME );
throw new Installer_Error( Installer_Error_Codes::UNKNOWN_FILENAME );
}
switch_theme( $theme_slug );

View File

@@ -114,7 +114,7 @@ class WC_WCCOM_Site_Installation_Step_Get_Product_Info implements WC_WCCOM_Site_
$updates = WC_Helper_Updater::get_update_data();
if ( empty( $updates[ $product_id ]['package'] ) ) {
return new Installer_Error( Installer_Error_Codes::WCCOM_PRODUCT_MISSING_PACKAGE );
throw new Installer_Error( Installer_Error_Codes::WCCOM_PRODUCT_MISSING_PACKAGE );
}
return $updates[ $product_id ]['package'];

View File

@@ -33,13 +33,16 @@ class WC_WCCOM_Site_Installation_Step_Unpack_Product implements WC_WCCOM_Site_In
/**
* Run the step installation process.
*
* @return WC_WCCOM_Site_Installation_State
* @throws WC_REST_WCCOM_Site_Installer_Error If the package unpacked path is not returned.
*/
public function run() {
$upgrader = WC_WCCOM_Site_Installer::get_wp_upgrader();
$unpacked_path = $upgrader->unpack_package( $this->state->get_download_path(), true );
if ( empty( $unpacked_path ) ) {
return new Installer_Error( Installer_Error_Codes::MISSING_UNPACKED_PATH );
throw new Installer_Error( Installer_Error_Codes::MISSING_UNPACKED_PATH );
}
$this->state->set_unpacked_path( $unpacked_path );

View File

@@ -0,0 +1,78 @@
<?php
/**
* WCCOM Site Base REST API Controller
*
* Handles requests to /ssr.
*
* @package WooCommerce\WCCom\API
* @since 8.6.0
*/
use WC_REST_WCCOM_Site_Installer_Error_Codes as Installer_Error_Codes;
use WC_REST_WCCOM_Site_Installer_Error as Installer_Error;
defined( 'ABSPATH' ) || exit;
/**
* REST API WCCOM Site Base REST API Controller Astract Class.
*
* @extends WC_REST_Controller
*/
abstract class WC_REST_WCCOM_Site_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wccom-site/v3';
/**
* Check whether user has permission to access controller's endpoints.
*
* @since 8.6.0
* @param WP_USER $user User object.
* @return bool
*/
abstract protected function user_has_permission( $user ) : bool;
/**
* Check permissions.
*
* Please note that access to this endpoint is also governed by the WC_WCCOM_Site::authenticate_wccom() method.
*
* @since 7.8.0
* @return bool|WP_Error
*/
public function check_permission() {
$current_user = wp_get_current_user();
if ( empty( $current_user ) || ( $current_user instanceof WP_User && ! $current_user->exists() ) ) {
/**
* This filter allows to provide a custom error message when the user is not authenticated.
*
* @since 3.7.0
*/
$error = apply_filters(
WC_WCCOM_Site::AUTH_ERROR_FILTER_NAME,
new Installer_Error( Installer_Error_Codes::NOT_AUTHENTICATED )
);
return new WP_Error(
$error->get_error_code(),
$error->get_error_message(),
array( 'status' => $error->get_http_code() )
);
}
if ( ! $this->user_has_permission( $current_user ) ) {
$error = new Installer_Error( Installer_Error_Codes::NO_PERMISSION );
return new WP_Error(
$error->get_error_code(),
$error->get_error_message(),
array( 'status' => $error->get_http_code() )
);
}
return true;
}
}

View File

@@ -1,234 +0,0 @@
<?php
/**
* WCCOM Site Installer REST API Controller Version 2
*
* Handles requests to /installer.
*
* @package WooCommerce\WCCom\API
* @since 7.7.0
*/
use WC_REST_WCCOM_Site_Installer_Error_Codes as Installer_Error_Codes;
use WC_REST_WCCOM_Site_Installer_Error as Installer_Error;
defined( 'ABSPATH' ) || exit;
/**
* REST API WCCOM Site Installer Controller Class.
*
* @extends WC_REST_Controller
*/
class WC_REST_WCCOM_Site_Installer_Controller_V2 extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wccom-site/v2';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'installer';
/**
* Register the routes for product reviews.
*
* @since 7.7.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'install' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'product-id' => array(
'required' => true,
'type' => 'integer',
),
'run-until-step' => array(
'required' => true,
'type' => 'string',
'enum' => WC_WCCOM_Site_Installation_Manager::STEPS,
),
'idempotency-key' => array(
'required' => true,
'type' => 'string',
),
),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'reset_install' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'product-id' => array(
'required' => true,
'type' => 'integer',
),
'idempotency-key' => array(
'required' => true,
'type' => 'string',
),
),
),
)
);
}
/**
* Check permissions.
*
* @since 7.7.0
* @param WP_REST_Request $request Full details about the request.
* @return bool|WP_Error
*/
public function check_permission( $request ) {
$current_user = wp_get_current_user();
if ( empty( $current_user ) || ( $current_user instanceof WP_User && ! $current_user->exists() ) ) {
/**
* This filter allows to provide a custom error message when the user is not authenticated.
*
* @since 3.7.0
*/
$error = apply_filters(
WC_WCCOM_Site::AUTH_ERROR_FILTER_NAME,
new Installer_Error( Installer_Error_Codes::NOT_AUTHENTICATED )
);
return new WP_Error(
$error->get_error_code(),
$error->get_error_message(),
array( 'status' => $error->get_http_code() )
);
}
if ( ! user_can( $current_user, 'install_plugins' ) || ! user_can( $current_user, 'install_themes' ) ) {
$error = new Installer_Error( Installer_Error_Codes::NO_PERMISSION );
return new WP_Error(
$error->get_error_code(),
$error->get_error_message(),
array( 'status' => $error->get_http_code() )
);
}
return true;
}
/**
* Install Woo.com products.
*
* @since 7.7.0
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function install( $request ) {
try {
$product_id = $request['product-id'];
$run_until_step = $request['run-until-step'];
$idempotency_key = $request['idempotency-key'];
$installation_manager = new WC_WCCOM_Site_Installation_Manager( $product_id, $idempotency_key );
$installation_manager->run_installation( $run_until_step );
$response = $this->success_response( $product_id );
} catch ( Installer_Error $exception ) {
$response = $this->failure_response( $product_id, $exception );
}
return $response;
}
/**
* Reset installation state.
*
* @since 7.7.0
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function reset_install( $request ) {
try {
$product_id = $request['product-id'];
$idempotency_key = $request['idempotency-key'];
$installation_manager = new WC_WCCOM_Site_Installation_Manager( $product_id, $idempotency_key );
$installation_manager->reset_installation();
$response = $this->success_response( $product_id );
} catch ( Installer_Error $exception ) {
$response = $this->failure_response( $product_id, $exception );
}
return $response;
}
/**
* Generate a standardized response for a successful request.
*
* @param int $product_id Product ID.
* @return WP_REST_Response|WP_Error
*/
protected function success_response( $product_id ) {
$state = WC_WCCOM_Site_Installation_State_Storage::get_state( $product_id );
$response = rest_ensure_response(
array(
'success' => true,
'state' => $state ? $this->map_state_to_response( $state ) : null,
)
);
$response->set_status( 200 );
return $response;
}
/**
* Generate a standardized response for a failed request.
*
* @param int $product_id Product ID.
* @param Installer_Error $exception The exception.
* @return WP_REST_Response|WP_Error
*/
protected function failure_response( $product_id, $exception ) {
$state = WC_WCCOM_Site_Installation_State_Storage::get_state( $product_id );
$response = rest_ensure_response(
array(
'success' => false,
'error_code' => $exception->get_error_code(),
'error_message' => $exception->get_error_message(),
'state' => $state ? $this->map_state_to_response( $state ) : null,
)
);
$response->set_status( $exception->get_http_code() );
return $response;
}
/**
* Map the installation state to a response.
*
* @param WC_WCCOM_Site_Installation_State $state The installation state.
* @return array
*/
protected function map_state_to_response( $state ) {
return array(
'product_id' => $state->get_product_id(),
'idempotency_key' => $state->get_idempotency_key(),
'last_step_name' => $state->get_last_step_name(),
'last_step_status' => $state->get_last_step_status(),
'last_step_error' => $state->get_last_step_error(),
'product_type' => $state->get_product_type(),
'product_name' => $state->get_product_name(),
'already_installed_plugin_info' => $state->get_already_installed_plugin_info(),
'started_seconds_ago' => time() - $state->get_started_date(),
);
}
}

View File

@@ -1,14 +1,13 @@
<?php
/**
* WCCOM Site Installer REST API Controller
* WCCOM Site Installer REST API Controller Version
*
* Handles requests to /installer.
*
* @package WooCommerce\WCCom\API
* @since 3.7.0
* @since 7.7.0
*/
use WC_REST_WCCOM_Site_Installer_Error_Codes as Installer_Error_Codes;
use WC_REST_WCCOM_Site_Installer_Error as Installer_Error;
defined( 'ABSPATH' ) || exit;
@@ -16,16 +15,9 @@ defined( 'ABSPATH' ) || exit;
/**
* REST API WCCOM Site Installer Controller Class.
*
* @extends WC_REST_Controller
* @extends WC_REST_WCCOM_Site_Controller
*/
class WC_REST_WCCOM_Site_Installer_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wccom-site/v1';
class WC_REST_WCCOM_Site_Installer_Controller extends WC_REST_WCCOM_Site_Controller {
/**
* Route base.
@@ -35,159 +27,179 @@ class WC_REST_WCCOM_Site_Installer_Controller extends WC_REST_Controller {
protected $rest_base = 'installer';
/**
* Register the routes for WCCCOM Installer Controller.
* Register the routes for plugin auto-installer.
*
* @since 3.7.0
* @since 7.7.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_install_state' ),
'permission_callback' => array( $this, 'check_permission' ),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'install' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'products' => array(
'product-id' => array(
'required' => true,
'type' => 'object',
'type' => 'integer',
),
'run-until-step' => array(
'required' => true,
'type' => 'string',
'enum' => WC_WCCOM_Site_Installation_Manager::STEPS,
),
'idempotency-key' => array(
'required' => true,
'type' => 'string',
),
),
),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/reset',
array(
array(
'methods' => WP_REST_Server::DELETABLE,
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'reset_install' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'product-id' => array(
'required' => true,
'type' => 'integer',
),
'idempotency-key' => array(
'required' => true,
'type' => 'string',
),
),
),
)
);
}
/**
* Check permissions.
* Check whether user has permission to access controller's endpoints.
*
* @since 3.7.0
* @param WP_REST_Request $request Full details about the request.
* @return bool|WP_Error
* @since 8.6.0
* @param WP_USER $user User object.
* @return bool
*/
public function check_permission( $request ) {
$current_user = wp_get_current_user();
if ( empty( $current_user ) || ( $current_user instanceof WP_User && ! $current_user->exists() ) ) {
/**
* This filter allows to provide a custom error message when the user is not authenticated.
*
* @since 3.7.0
*/
$error = apply_filters(
WC_WCCOM_Site::AUTH_ERROR_FILTER_NAME,
new Installer_Error( Installer_Error_Codes::NOT_AUTHENTICATED )
);
return new WP_Error(
$error->get_error_code(),
$error->get_error_message(),
array( 'status' => $error->get_http_code() )
);
}
if ( ! user_can( $current_user, 'install_plugins' ) || ! user_can( $current_user, 'install_themes' ) ) {
$error = new Installer_Error( Installer_Error_Codes::NO_PERMISSION );
return new WP_Error(
$error->get_error_code(),
$error->get_error_message(),
array( 'status' => $error->get_http_code() )
);
}
return true;
}
/**
* Get installation state.
*
* @since 3.7.0
* @param WP_REST_Request $request Full details about the request.
* @return bool|WP_Error
*/
public function get_install_state( $request ) {
$requirements_met = WC_WCCOM_Site_Installer_Requirements_Check::met_requirements();
if ( is_wp_error( $requirements_met ) ) {
return $requirements_met;
}
return rest_ensure_response( WC_WCCOM_Site_Installer::get_state() );
public function user_has_permission( $user ) : bool {
return user_can( $user, 'install_plugins' ) && user_can( $user, 'install_themes' );
}
/**
* Install Woo.com products.
*
* @since 3.7.0
* @since 7.7.0
* @param WP_REST_Request $request Full details about the request.
* @return bool|WP_Error
* @return WP_REST_Response|WP_Error
*/
public function install( $request ) {
$requirements_met = WC_WCCOM_Site_Installer_Requirements_Check::met_requirements();
if ( is_wp_error( $requirements_met ) ) {
return $requirements_met;
try {
$product_id = $request['product-id'];
$run_until_step = $request['run-until-step'];
$idempotency_key = $request['idempotency-key'];
$installation_manager = new WC_WCCOM_Site_Installation_Manager( $product_id, $idempotency_key );
$installation_manager->run_installation( $run_until_step );
$response = $this->success_response( $product_id );
} catch ( Installer_Error $exception ) {
$response = $this->failure_response( $product_id, $exception );
}
if ( empty( $request['products'] ) ) {
return new WP_Error( 'missing_products', __( 'Missing products in request body.', 'woocommerce' ), array( 'status' => 400 ) );
}
$validation_result = $this->validate_products( $request['products'] );
if ( is_wp_error( $validation_result ) ) {
return $validation_result;
}
return rest_ensure_response( WC_WCCOM_Site_Installer::schedule_install( $request['products'] ) );
return $response;
}
/**
* Reset installation state.
*
* @since 3.7.0
* @since 7.7.0
* @param WP_REST_Request $request Full details about the request.
* @return bool|WP_Error
* @return WP_REST_Response|WP_Error
*/
public function reset_install( $request ) {
$resp = rest_ensure_response( WC_WCCOM_Site_Installer::reset_state() );
$resp->set_status( 204 );
try {
$product_id = $request['product-id'];
$idempotency_key = $request['idempotency-key'];
return $resp;
$installation_manager = new WC_WCCOM_Site_Installation_Manager( $product_id, $idempotency_key );
$installation_manager->reset_installation();
$response = $this->success_response( $product_id );
} catch ( Installer_Error $exception ) {
$response = $this->failure_response( $product_id, $exception );
}
return $response;
}
/**
* Validate products from request body.
* Generate a standardized response for a successful request.
*
* @since 3.7.0
* @param array $products Array of products where key is product ID and
* element is install args.
* @return bool|WP_Error
* @param int $product_id Product ID.
* @return WP_REST_Response|WP_Error
*/
protected function validate_products( $products ) {
$err = new WP_Error( 'invalid_products', __( 'Invalid products in request body.', 'woocommerce' ), array( 'status' => 400 ) );
protected function success_response( $product_id ) {
$state = WC_WCCOM_Site_Installation_State_Storage::get_state( $product_id );
$response = rest_ensure_response(
array(
'success' => true,
'state' => $state ? $this->map_state_to_response( $state ) : null,
)
);
$response->set_status( 200 );
return $response;
}
if ( ! is_array( $products ) ) {
return $err;
}
/**
* Generate a standardized response for a failed request.
*
* @param int $product_id Product ID.
* @param Installer_Error $exception The exception.
* @return WP_REST_Response|WP_Error
*/
protected function failure_response( $product_id, $exception ) {
$state = WC_WCCOM_Site_Installation_State_Storage::get_state( $product_id );
$response = rest_ensure_response(
array(
'success' => false,
'error_code' => $exception->get_error_code(),
'error_message' => $exception->get_error_message(),
'state' => $state ? $this->map_state_to_response( $state ) : null,
)
);
$response->set_status( $exception->get_http_code() );
return $response;
}
foreach ( $products as $product_id => $install_args ) {
if ( ! absint( $product_id ) ) {
return $err;
}
if ( empty( $install_args ) || ! is_array( $install_args ) ) {
return $err;
}
}
return true;
/**
* Map the installation state to a response.
*
* @param WC_WCCOM_Site_Installation_State $state The installation state.
* @return array
*/
protected function map_state_to_response( $state ) {
return array(
'product_id' => $state->get_product_id(),
'idempotency_key' => $state->get_idempotency_key(),
'last_step_name' => $state->get_last_step_name(),
'last_step_status' => $state->get_last_step_status(),
'last_step_error' => $state->get_last_step_error(),
'product_type' => $state->get_product_type(),
'product_name' => $state->get_product_name(),
'already_installed_plugin_info' => $state->get_already_installed_plugin_info(),
'started_seconds_ago' => time() - $state->get_started_date(),
);
}
}

View File

@@ -8,24 +8,14 @@
* @since 7.8.0
*/
use WC_REST_WCCOM_Site_Installer_Error_Codes as Installer_Error_Codes;
use WC_REST_WCCOM_Site_Installer_Error as Installer_Error;
defined( 'ABSPATH' ) || exit;
/**
* REST API WCCOM System Status Report Controller Class.
*
* @extends WC_REST_Controller
* @extends WC_REST_WCCOM_Site_Controller
*/
class WC_REST_WCCOM_Site_SSR_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wccom-site/v1';
class WC_REST_WCCOM_Site_SSR_Controller extends WC_REST_WCCOM_Site_Controller {
/**
* Route base.
@@ -54,44 +44,14 @@ class WC_REST_WCCOM_Site_SSR_Controller extends WC_REST_Controller {
}
/**
* Check permissions.
* Check whether user has permission to access controller's endpoints.
*
* Please note that access to this endpoint is also governed by the WC_WCCOM_Site::authenticate_wccom() method.
*
* @since 7.8.0
* @param WP_REST_Request $request Full details about the request.
* @return bool|WP_Error
* @since 8.6.0
* @param WP_USER $user User object.
* @return bool
*/
public function check_permission( $request ) {
$current_user = wp_get_current_user();
if ( empty( $current_user ) || ( $current_user instanceof WP_User && ! $current_user->exists() ) ) {
/**
* This filter allows to provide a custom error message when the user is not authenticated.
*
* @since 7.8.0
*/
$error = apply_filters(
WC_WCCOM_Site::AUTH_ERROR_FILTER_NAME,
new Installer_Error( Installer_Error_Codes::NOT_AUTHENTICATED )
);
return new WP_Error(
$error->get_error_code(),
$error->get_error_message(),
array( 'status' => $error->get_http_code() )
);
}
if ( ! user_can( $current_user, 'manage_woocommerce' ) ) {
$error = new Installer_Error( Installer_Error_Codes::NO_PERMISSION );
return new WP_Error(
$error->get_error_code(),
$error->get_error_message(),
array( 'status' => $error->get_http_code() )
);
}
return true;
public function user_has_permission( $user ) : bool {
return user_can( $user, 'manage_woocommerce' );
}
/**