| 1 |
<?php |
| 2 |
/** |
| 3 |
* Class Admin\Api_Key_Banner file. |
| 4 |
* |
| 5 |
* Renders the admin banner that asks merchants to enter the new PostNL API |
| 6 |
* key ahead of the upcoming API migration. The banner is shown on the PostNL |
| 7 |
* settings screen and on the WooCommerce orders list. Two dismissal modes |
| 8 |
* are supported: "remind me later" (cleared on the next login) and a |
| 9 |
* permanent dismiss stored per user. |
| 10 |
* |
| 11 |
* @package PostNLWooCommerce\Admin |
| 12 |
*/ |
| 13 |
|
| 14 |
namespace PostNLWooCommerce\Admin; |
| 15 |
|
| 16 |
use PostNLWooCommerce\Shipping_Method\Settings; |
| 17 |
|
| 18 |
if ( ! defined( 'ABSPATH' ) ) { |
| 19 |
exit; |
| 20 |
} |
| 21 |
|
| 22 |
/** |
| 23 |
* Class Api_Key_Banner |
| 24 |
*/ |
| 25 |
class Api_Key_Banner { |
| 26 |
|
| 27 |
const META_DISMISSED = 'postnl_new_api_key_banner_dismissed'; |
| 28 |
const META_REMIND_LATER = 'postnl_new_api_key_banner_remind_later'; |
| 29 |
const NONCE_ACTION = 'postnl_new_api_key_banner'; |
| 30 |
const AJAX_ACTION = 'postnl_dismiss_new_api_key_banner'; |
| 31 |
|
| 32 |
/** |
| 33 |
* Memoized result of is_target_screen() for the current request. |
| 34 |
* |
| 35 |
* @var bool|null |
| 36 |
*/ |
| 37 |
protected $is_target_screen = null; |
| 38 |
|
| 39 |
/** |
| 40 |
* Register hooks. |
| 41 |
*/ |
| 42 |
public function __construct() { |
| 43 |
add_action( 'admin_notices', array( $this, 'maybe_render' ) ); |
| 44 |
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); |
| 45 |
add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'handle_ajax_dismiss' ) ); |
| 46 |
add_action( 'wp_login', array( $this, 'clear_remind_later_on_login' ), 10, 2 ); |
| 47 |
} |
| 48 |
|
| 49 |
/** |
| 50 |
* Is the current screen one where the banner should appear? |
| 51 |
*/ |
| 52 |
protected function is_target_screen() { |
| 53 |
if ( null !== $this->is_target_screen ) { |
| 54 |
return $this->is_target_screen; |
| 55 |
} |
| 56 |
|
| 57 |
$this->is_target_screen = $this->resolve_target_screen(); |
| 58 |
|
| 59 |
return $this->is_target_screen; |
| 60 |
} |
| 61 |
|
| 62 |
/** |
| 63 |
* Determine whether the current screen is one where the banner appears. |
| 64 |
*/ |
| 65 |
protected function resolve_target_screen() { |
| 66 |
if ( ! function_exists( 'get_current_screen' ) ) { |
| 67 |
return false; |
| 68 |
} |
| 69 |
|
| 70 |
$screen = get_current_screen(); |
| 71 |
if ( empty( $screen ) ) { |
| 72 |
return false; |
| 73 |
} |
| 74 |
|
| 75 |
if ( 'woocommerce_page_wc-settings' === $screen->id |
| 76 |
&& isset( $_GET['section'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 77 |
&& POSTNL_SETTINGS_ID === sanitize_text_field( wp_unslash( $_GET['section'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 78 |
) { |
| 79 |
return true; |
| 80 |
} |
| 81 |
|
| 82 |
if ( 'edit-shop_order' === $screen->id ) { |
| 83 |
return true; |
| 84 |
} |
| 85 |
|
| 86 |
if ( 'woocommerce_page_wc-orders' === $screen->id ) { |
| 87 |
return true; |
| 88 |
} |
| 89 |
|
| 90 |
return false; |
| 91 |
} |
| 92 |
|
| 93 |
/** |
| 94 |
* Should the banner be visible right now for this user? |
| 95 |
*/ |
| 96 |
protected function should_show() { |
| 97 |
if ( ! current_user_can( 'manage_woocommerce' ) ) { |
| 98 |
return false; |
| 99 |
} |
| 100 |
|
| 101 |
if ( ! $this->is_target_screen() ) { |
| 102 |
return false; |
| 103 |
} |
| 104 |
|
| 105 |
$settings = Settings::get_instance(); |
| 106 |
$new_key = $settings->get_api_key_new(); |
| 107 |
|
| 108 |
// If a valid new key has already been entered we have what we need. |
| 109 |
if ( '' !== $new_key && $settings->is_api_key_new_validated() ) { |
| 110 |
return false; |
| 111 |
} |
| 112 |
|
| 113 |
$user_id = get_current_user_id(); |
| 114 |
if ( get_user_meta( $user_id, self::META_DISMISSED, true ) ) { |
| 115 |
return false; |
| 116 |
} |
| 117 |
|
| 118 |
if ( get_user_meta( $user_id, self::META_REMIND_LATER, true ) ) { |
| 119 |
return false; |
| 120 |
} |
| 121 |
|
| 122 |
return true; |
| 123 |
} |
| 124 |
|
| 125 |
/** |
| 126 |
* Render the banner markup. |
| 127 |
*/ |
| 128 |
public function maybe_render() { |
| 129 |
if ( ! $this->should_show() ) { |
| 130 |
return; |
| 131 |
} |
| 132 |
|
| 133 |
$nonce = wp_create_nonce( self::NONCE_ACTION ); |
| 134 |
$has_old_key = '' !== trim( (string) Settings::get_instance()->get_original_api_key() ); |
| 135 |
|
| 136 |
// Existing merchants (who already have a key) get the amber "warning" |
| 137 |
// banner; a fresh install gets the blue "info" one, matching the mockup. |
| 138 |
$notice_class = $has_old_key ? 'notice-warning' : 'notice-info'; |
| 139 |
?> |
| 140 |
<div class="notice <?php echo esc_attr( $notice_class ); ?> postnl-new-api-key-banner" data-nonce="<?php echo esc_attr( $nonce ); ?>"> |
| 141 |
<p><strong><?php esc_html_e( 'PostNL:', 'postnl-for-woocommerce' ); ?></strong> <?php echo wp_kses_post( $this->get_message( $has_old_key ) ); ?></p> |
| 142 |
<p> |
| 143 |
<a href="<?php echo esc_url( Settings::SELF_SERVICE_URL ); ?>" target="_blank" rel="noopener noreferrer" class="button button-primary"> |
| 144 |
<?php esc_html_e( 'Get your API key', 'postnl-for-woocommerce' ); ?> |
| 145 |
</a> |
| 146 |
<button type="button" class="button button-secondary postnl-new-api-key-remind"> |
| 147 |
<?php esc_html_e( 'Remind me later', 'postnl-for-woocommerce' ); ?> |
| 148 |
</button> |
| 149 |
<button type="button" class="button button-secondary postnl-new-api-key-dismiss"> |
| 150 |
<?php esc_html_e( 'Dismiss', 'postnl-for-woocommerce' ); ?> |
| 151 |
</button> |
| 152 |
</p> |
| 153 |
</div> |
| 154 |
<?php |
| 155 |
} |
| 156 |
|
| 157 |
/** |
| 158 |
* Banner body text (final copy supplied by PostNL). Existing merchants (who |
| 159 |
* already have a legacy key stored) are told an extra field was added; a fresh |
| 160 |
* install, which may still hold a pre-migration v2 key, is pointed at the "Get |
| 161 |
* your API key" button below to request a v4 key. Neither variant carries an |
| 162 |
* inline link (the button below is the single call to action). |
| 163 |
* |
| 164 |
* @param bool $has_old_key Whether a legacy key is already stored. |
| 165 |
*/ |
| 166 |
protected function get_message( $has_old_key ) { |
| 167 |
if ( $has_old_key ) { |
| 168 |
return __( |
| 169 |
'Important: In the latest update of the plug-in, an additional API key field has been added to the account configuration of the PostNL plug-in. This field must be filled in with the new API key that can be obtained via the Self Service module on the PostNL Business Portal. This API key is required to gain access to the new APIs that will be rolled out in a future update of the plug-in. It is very important that this key is entered before the relevant update is performed; otherwise, no connection can be made to the new PostNL APIs, and it will not be possible to create labels or use checkout features such as delivery days and pickup points.', |
| 170 |
'postnl-for-woocommerce' |
| 171 |
); |
| 172 |
} |
| 173 |
|
| 174 |
return __( |
| 175 |
'Important: PostNL is migrating to the new Future-proof API v4. Don\'t you have an API key yet, or do you have an API key issued before September 14, 2026? Request a new API key below. Enter your PostNL API key in the account configuration of the PostNL plug-in. This key is required to connect to the PostNL APIs. Without it you cannot create labels or use checkout features such as delivery days and pickup points.', |
| 176 |
'postnl-for-woocommerce' |
| 177 |
); |
| 178 |
} |
| 179 |
|
| 180 |
/** |
| 181 |
* Enqueue the dismissal JS on screens where the banner can appear. |
| 182 |
* |
| 183 |
* @param string $hook_suffix Current admin page hook. |
| 184 |
*/ |
| 185 |
public function enqueue_assets( $hook_suffix ) { |
| 186 |
unset( $hook_suffix ); |
| 187 |
|
| 188 |
if ( ! $this->is_target_screen() ) { |
| 189 |
return; |
| 190 |
} |
| 191 |
|
| 192 |
wp_enqueue_script( |
| 193 |
'postnl-new-api-key-banner', |
| 194 |
POSTNL_WC_PLUGIN_DIR_URL . '/assets/js/new-api-key-banner.js', |
| 195 |
array( 'jquery' ), |
| 196 |
POSTNL_WC_VERSION, |
| 197 |
true |
| 198 |
); |
| 199 |
|
| 200 |
wp_localize_script( |
| 201 |
'postnl-new-api-key-banner', |
| 202 |
'postnlNewApiKeyBanner', |
| 203 |
array( |
| 204 |
'ajaxUrl' => admin_url( 'admin-ajax.php' ), |
| 205 |
'action' => self::AJAX_ACTION, |
| 206 |
) |
| 207 |
); |
| 208 |
} |
| 209 |
|
| 210 |
/** |
| 211 |
* AJAX handler for both dismiss modes. |
| 212 |
*/ |
| 213 |
public function handle_ajax_dismiss() { |
| 214 |
if ( ! current_user_can( 'manage_woocommerce' ) ) { |
| 215 |
wp_send_json_error( array( 'message' => 'forbidden' ), 403 ); |
| 216 |
} |
| 217 |
|
| 218 |
check_ajax_referer( self::NONCE_ACTION, 'nonce' ); |
| 219 |
|
| 220 |
$mode = isset( $_POST['mode'] ) ? sanitize_key( wp_unslash( $_POST['mode'] ) ) : ''; |
| 221 |
$user_id = get_current_user_id(); |
| 222 |
|
| 223 |
if ( 'remind' === $mode ) { |
| 224 |
update_user_meta( $user_id, self::META_REMIND_LATER, time() ); |
| 225 |
wp_send_json_success(); |
| 226 |
} |
| 227 |
|
| 228 |
if ( 'dismiss' === $mode ) { |
| 229 |
update_user_meta( $user_id, self::META_DISMISSED, 1 ); |
| 230 |
wp_send_json_success(); |
| 231 |
} |
| 232 |
|
| 233 |
wp_send_json_error( array( 'message' => 'invalid mode' ), 400 ); |
| 234 |
} |
| 235 |
|
| 236 |
/** |
| 237 |
* Wipe the "remind me later" flag whenever the user logs in, so the |
| 238 |
* banner comes back the next session. |
| 239 |
* |
| 240 |
* @param string $user_login Username. |
| 241 |
* @param \WP_User $user Logged-in user. |
| 242 |
*/ |
| 243 |
public function clear_remind_later_on_login( $user_login, $user ) { |
| 244 |
unset( $user_login ); |
| 245 |
|
| 246 |
if ( $user instanceof \WP_User ) { |
| 247 |
delete_user_meta( $user->ID, self::META_REMIND_LATER ); |
| 248 |
} |
| 249 |
} |
| 250 |
} |
| 251 |
|