<?php
/**
* Services Error Notice
*
* @package Automattic\WCShipping\ServiceData
*/
namespace Automattic\WCShipping\ServiceData;
use Automattic\WCShipping\Connect\WC_Connect_Options;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Show admin notice when services endpoint fetch fails due to internal plugin errors.
*
* This notice is displayed when the plugin cannot connect to the WooCommerce Shipping
* server due to issues like Jetpack connection problems, token issues, etc.
*/
class ServicesErrorNotice {
/**
* Option key for storing the error notice state.
*/
const OPTION_KEY = 'services_fetch_error';
/**
* User meta key for dismissed notice.
*/
const DISMISSED_META_KEY = 'wcshipping_services_error_dismissed';
/**
* Error codes that indicate internal plugin errors (should show warning).
*
* @var array
*/
private static $internal_error_codes = array(
'jetpack_data_class_not_found',
'jetpack_data_get_access_token_not_found',
'request_body_should_be_array',
'unable_to_json_encode_body',
'missing_token',
'invalid_token',
'invalid_signature',
);
/**
* Initialize the notice hooks.
*
* @return void
*/
public function init() {
add_action( 'admin_notices', array( $this, 'maybe_render_notice' ) );
add_action( 'wp_ajax_wcshipping_dismiss_services_error', array( $this, 'dismiss_notice' ) );
}
/**
* Check if an error code is an internal plugin error.
*
* @param string $error_code The WP_Error code.
* @return bool
*/
public static function is_internal_error( $error_code ) {
return in_array( $error_code, self::$internal_error_codes, true );
}
/**
* Enable the error notice.
*
* @param string $error_code The error code that occurred.
* @param string $error_message The error message.
* @return void
*/
public function enable_notice( $error_code, $error_message ) {
WC_Connect_Options::update_option(
self::OPTION_KEY,
array(
'code' => $error_code,
'message' => $error_message,
'timestamp' => time(),
)
);
// Reset the dismissed state so the notice shows again.
$this->reset_dismissed_state();
}
/**
* Disable the error notice.
*
* @return void
*/
public function disable_notice() {
WC_Connect_Options::delete_option( self::OPTION_KEY );
}
/**
* Check if the notice is enabled.
*
* @return array|false Error data array or false if not enabled.
*/
public function is_notice_enabled() {
$error_data = WC_Connect_Options::get_option( self::OPTION_KEY, false );
if ( ! is_array( $error_data ) || empty( $error_data['code'] ) ) {
return false;
}
return $error_data;
}
/**
* Check if the notice was dismissed by the current user.
*
* @return bool
*/
private function is_dismissed() {
$user_id = get_current_user_id();
$dismissed_data = get_user_meta( $user_id, self::DISMISSED_META_KEY, true );
$error_data = $this->is_notice_enabled();
if ( ! $dismissed_data || ! is_array( $dismissed_data ) || ! $error_data ) {
return false;
}
// Check if dismissed for the same error timestamp.
return isset( $dismissed_data['timestamp'] )
&& $dismissed_data['timestamp'] === $error_data['timestamp'];
}
/**
* Reset the dismissed state for all users.
*
* @return void
*/
private function reset_dismissed_state() {
delete_metadata( 'user', 0, self::DISMISSED_META_KEY, '', true );
}
/**
* AJAX handler for dismissing the notice.
*
* @return void
*/
public function dismiss_notice() {
check_ajax_referer( 'wcshipping_dismiss_services_error', 'nonce' );
$user_id = get_current_user_id();
$error_data = $this->is_notice_enabled();
if ( $error_data ) {
update_user_meta(
$user_id,
self::DISMISSED_META_KEY,
array(
'timestamp' => $error_data['timestamp'],
)
);
}
wp_send_json_success();
}
/**
* Maybe render the notice.
*
* @return void
*/
public function maybe_render_notice() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
if ( ! $this->should_show_on_current_screen() ) {
return;
}
$error_data = $this->is_notice_enabled();
if ( ! $error_data ) {
return;
}
if ( $this->is_dismissed() ) {
return;
}
$this->render_notice( $error_data );
}
/**
* Check if the notice should be shown on the current screen.
*
* @return bool
*/
private function should_show_on_current_screen() {
if ( ! is_admin() ) {
return false;
}
$screen = get_current_screen();
if ( ! $screen ) {
return false;
}
// Show on WooCommerce pages.
$target_screens = array(
'woocommerce_page_wc-settings',
'woocommerce_page_wc-orders',
'edit-shop_order',
'shop_order',
);
return in_array( $screen->id, $target_screens, true )
|| 'woocommerce_page_wc-orders' === $screen->base;
}
/**
* Render the warning notice.
*
* @param array $error_data The error data (kept for future use/debugging).
*
* @return void
*/
private function render_notice( $error_data ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Parameter kept for future use.
$nonce = wp_create_nonce( 'wcshipping_dismiss_services_error' );
?>
<div class="notice notice-warning is-dismissible wcshipping-services-error-notice" data-nonce="<?php echo esc_attr( $nonce ); ?>">
<p>
<strong><?php esc_html_e( 'WooCommerce Shipping', 'woocommerce-shipping' ); ?>:</strong>
<?php
esc_html_e(
'Unable to connect to the WooCommerce Shipping server. Some features like promotions and service announcements may not be available.',
'woocommerce-shipping'
);
?>
</p>
<p>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-status&tab=woocommerce-shipping' ) ); ?>">
<?php esc_html_e( 'Check your connection status', 'woocommerce-shipping' ); ?>
</a>
</p>
</div>
<script type="text/javascript">
jQuery( document ).ready( function( $ ) {
$( '.wcshipping-services-error-notice' ).on( 'click', '.notice-dismiss', function() {
$.ajax( {
url: ajaxurl,
type: 'POST',
data: {
action: 'wcshipping_dismiss_services_error',
nonce: $( '.wcshipping-services-error-notice' ).data( 'nonce' )
}
} );
} );
} );
</script>
<?php
}
}