Skip to content

Conversation

@MrGKanev
Copy link
Contributor

@MrGKanev MrGKanev commented Jan 1, 2026

No description provided.

@MrGKanev MrGKanev self-assigned this Jan 1, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the WooCommerce Priority Processing plugin to version 1.4.2, implementing comprehensive modernization to meet WordPress 6.9 and WooCommerce standards. The update focuses on PHP 7.4+ features (strict types, type hints), security improvements (input sanitization, output escaping), enhanced WooCommerce Blocks support, and a fundamental change in fee implementation from separate cart fees to direct shipping rate modification.

Key Changes:

  • Modernized main plugin file with strict types, type hints, and WordPress coding standards
  • Enhanced AJAX handler with comprehensive input sanitization and structured error handling
  • Implemented WooCommerce Blocks integration with Store API extensions
  • Changed fee implementation approach to add priority fee directly to shipping rates instead of separate line item
  • Added extensive developer documentation in .claude/ directory covering architecture, testing, and implementation details

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 22 comments.

Show a summary per file
File Description
woocommerce-priority-processing.php Core plugin file modernized with PHP 7.4+ type hints, strict types, WordPress coding standards, improved documentation, and singleton pattern refinements
includes/frontend/shipping.php Refactored to add priority fee directly to shipping rates instead of modifying packages; added early session update hook
includes/frontend/fees.php Removed separate cart fee logic; now only handles saving priority status to order meta
includes/frontend/checkout.php Updated checkout display to clarify fee is "added to shipping"
includes/frontend/ajax.php Completely modernized with strict types, comprehensive input sanitization, proper error handling with HTTP status codes, and structured methods
includes/frontend/blocks-integration.php New file adding WooCommerce Blocks support with Store API extensions and React-based checkout integration
assets/js/frontend-blocks.js Enhanced AJAX handling with fragment updates and timing delays to prevent race conditions
assets/js/blocks-checkout.js New React-based block checkout integration using WooCommerce Blocks APIs
.claude/*.md Comprehensive developer documentation including implementation notes, testing guides, standards audit, and changelog

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 11 to 239
class Frontend_Blocks_Integration
{
/**
* Constructor
*/
public function __construct()
{
// Register the block integration
add_action('woocommerce_blocks_loaded', [$this, 'register_blocks_integration']);

// Enqueue block scripts
add_action('wp_enqueue_scripts', [$this, 'enqueue_block_scripts']);
}

/**
* Register integration with WooCommerce Blocks
*/
public function register_blocks_integration()
{
if (!class_exists('\Automattic\WooCommerce\Blocks\Package')) {
return;
}

// Extend the Store API with our custom data
$this->extend_store_api();
}

/**
* Extend WooCommerce Store API
*/
private function extend_store_api()
{
if (!function_exists('woocommerce_store_api_register_endpoint_data')) {
return;
}

woocommerce_store_api_register_endpoint_data([
'endpoint' => CheckoutSchema::IDENTIFIER,
'namespace' => 'wpp-priority',
'data_callback' => [$this, 'extend_checkout_data'],
'schema_callback' => [$this, 'extend_checkout_schema'],
'schema_type' => ARRAY_A,
]);

woocommerce_store_api_register_endpoint_data([
'endpoint' => \Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema::IDENTIFIER,
'namespace' => 'wpp-priority',
'data_callback' => [$this, 'extend_cart_data'],
'schema_callback' => [$this, 'extend_cart_schema'],
'schema_type' => ARRAY_A,
]);

// Register update callback for priority processing checkbox
woocommerce_store_api_register_update_callback([
'namespace' => 'wpp-priority',
'callback' => [$this, 'update_priority_from_blocks'],
]);
}

/**
* Extend checkout data for blocks
*/
public function extend_checkout_data()
{
return $this->get_priority_data();
}

/**
* Extend cart data for blocks
*/
public function extend_cart_data()
{
return $this->get_priority_data();
}

/**
* Get priority processing data for blocks
*/
private function get_priority_data()
{
$is_enabled = get_option('wpp_enabled') === 'yes' || get_option('wpp_enabled') === '1';
$can_access = Core_Permissions::can_access_priority_processing();
$is_active = $this->is_priority_active();

return [
'enabled' => $is_enabled && $can_access,
'is_active' => $is_active,
'fee_amount' => floatval(get_option('wpp_fee_amount', '5.00')),
'fee_label' => get_option('wpp_fee_label', __('Priority Processing & Express Shipping', 'woo-priority')),
'section_title' => get_option('wpp_section_title', __('Express Options', 'woo-priority')),
'checkbox_label' => get_option('wpp_checkbox_label', __('Priority processing + Express shipping', 'woo-priority')),
'description' => get_option('wpp_description', __('Your order will be processed with priority and shipped via express delivery', 'woo-priority')),
];
}

/**
* Define checkout schema extension
*/
public function extend_checkout_schema()
{
return [
'enabled' => [
'description' => __('Whether priority processing is available', 'woo-priority'),
'type' => 'boolean',
'readonly' => true,
],
'is_active' => [
'description' => __('Whether priority processing is currently active', 'woo-priority'),
'type' => 'boolean',
'readonly' => true,
],
'fee_amount' => [
'description' => __('Priority processing fee amount', 'woo-priority'),
'type' => 'number',
'readonly' => true,
],
'fee_label' => [
'description' => __('Fee label text', 'woo-priority'),
'type' => 'string',
'readonly' => true,
],
'section_title' => [
'description' => __('Section title', 'woo-priority'),
'type' => 'string',
'readonly' => true,
],
'checkbox_label' => [
'description' => __('Checkbox label text', 'woo-priority'),
'type' => 'string',
'readonly' => true,
],
'description' => [
'description' => __('Description text', 'woo-priority'),
'type' => 'string',
'readonly' => true,
],
];
}

/**
* Define cart schema extension
*/
public function extend_cart_schema()
{
return $this->extend_checkout_schema();
}

/**
* Update priority processing from blocks checkout
*/
public function update_priority_from_blocks($data)
{
if (!isset($data['priority_enabled'])) {
return;
}

$priority_enabled = filter_var($data['priority_enabled'], FILTER_VALIDATE_BOOLEAN);

if (WC()->session) {
WC()->session->set('priority_processing', $priority_enabled);

// Recalculate cart totals
if (WC()->cart) {
WC()->cart->calculate_totals();
}

error_log("WPP Blocks: Priority processing " . ($priority_enabled ? 'enabled' : 'disabled'));
}
}

/**
* Check if priority processing is active
*/
private function is_priority_active()
{
if (!WC()->session) {
return false;
}

$priority = WC()->session->get('priority_processing', false);
return ($priority === true || $priority === '1' || $priority === 1);
}

/**
* Enqueue scripts for blocks checkout
*/
public function enqueue_block_scripts()
{
// Only load on checkout page
if (!is_checkout() && !has_block('woocommerce/checkout')) {
return;
}

// Check if feature is enabled
if (get_option('wpp_enabled') !== 'yes' && get_option('wpp_enabled') !== '1') {
return;
}

// Check permissions
if (!Core_Permissions::can_access_priority_processing()) {
return;
}

// Register and enqueue the block script
$script_path = WPP_PLUGIN_DIR . 'assets/js/blocks-checkout.js';
$script_url = WPP_PLUGIN_URL . 'assets/js/blocks-checkout.js';

if (file_exists($script_path)) {
wp_enqueue_script(
'wpp-blocks-checkout',
$script_url,
['wp-element', 'wp-i18n', 'wp-components', 'wc-blocks-checkout'],
WPP_VERSION,
true
);

// Pass data to the script
wp_localize_script('wpp-blocks-checkout', 'wppBlocksData', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wpp_nonce'),
'fee_amount' => get_option('wpp_fee_amount', '5.00'),
'fee_label' => get_option('wpp_fee_label', __('Priority Processing & Express Shipping', 'woo-priority')),
'section_title' => get_option('wpp_section_title', __('Express Options', 'woo-priority')),
'checkbox_label' => get_option('wpp_checkbox_label', __('Priority processing + Express shipping', 'woo-priority')),
'description' => get_option('wpp_description', __('Your order will be processed with priority and shipped via express delivery', 'woo-priority')),
]);
}
}
}
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is missing declare(strict_types=1), type hints, return type declarations, and comprehensive PHPDoc blocks. It also uses short array syntax [] instead of array() which is inconsistent with the WordPress Coding Standards applied to the main plugin file. For consistency with the stated modernization goals, these should be updated.

Copilot uses AI. Check for mistakes.
*/
public function clear_priority_session(): void {
if ( WC()->session ) {
WC()->session->set( 'priority_processing', false );
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removed log statement "error_log('WPP: Priority session cleared by main class');" was helpful for debugging. With the emphasis on debugging and testing in this PR (as shown in the testing guide documentation), consider whether removing this logging is beneficial. If debugging is important, this log statement could be useful for tracking session lifecycle.

Suggested change
WC()->session->set( 'priority_processing', false );
WC()->session->set( 'priority_processing', false );
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( 'WPP: Priority session cleared by main class' );
}

Copilot uses AI. Check for mistakes.
foreach ( $defaults as $option_name => $default_value ) {
add_option( $option_name, $default_value );
}

Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The on_activation method removes the conditional check for $this->core_statistics before calling methods on it, and also removes the schedule_daily_refresh() call and related error_log. The new version doesn't initialize statistics during activation. If the statistics component needs any setup during activation (like scheduling events), this functionality has been lost. Verify whether statistics initialization during activation is necessary.

Suggested change
// Initialize statistics scheduling on activation, if available.
if ( isset( $this->core_statistics ) && method_exists( $this->core_statistics, 'schedule_daily_refresh' ) ) {
$this->core_statistics->schedule_daily_refresh();
} else {
if ( function_exists( 'error_log' ) ) {
error_log( 'WooCommerce Priority Processing: Core_Statistics not initialized during activation; daily refresh not scheduled.' );
}
}

Copilot uses AI. Check for mistakes.
Comment on lines 314 to 336
flush_rewrite_rules();
}

/**
* Plugin deactivation handler
*
* @since 1.0.0
* @return void
*/
public function on_deactivation(): void {
// Clear any sessions.
if ( class_exists( 'WooCommerce' ) && WC()->session ) {
WC()->session->set( 'priority_processing', false );
}

// Clear statistics cache if available.
if ( isset( $this->core_statistics ) ) {
$this->core_statistics->clear_cache();
$this->core_statistics->cleanup_scheduled_events();
}

// Flush rewrite rules.
flush_rewrite_rules();
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The activation and deactivation hooks now call flush_rewrite_rules(), but this plugin doesn't register any custom post types or rewrite rules. Calling flush_rewrite_rules() is expensive and should only be used when the plugin actually modifies rewrite rules. Unless there's a specific reason for flushing rewrite rules (such as integration with another component), these calls should be removed as they add unnecessary overhead during plugin activation/deactivation.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +79
if (window.wp?.data?.dispatch) {
const cartStore = window.wp.data.dispatch('wc/store/cart');
if (cartStore && typeof cartStore.invalidateResolutionForStore === 'function') {
cartStore.invalidateResolutionForStore();
}
}
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses optional chaining (window.wp?.data?.dispatch) which requires relatively modern JavaScript support. While this is generally fine, ensure this aligns with the browser support requirements for the plugin. The code doesn't appear to be transpiled based on the file structure, so verify that the minimum supported browser versions support optional chaining (introduced in 2020).

Copilot uses AI. Check for mistakes.
Comment on lines 32 to 37
$posted_data = [];
if (is_string($post_data) && !empty($post_data)) {
parse_str($post_data, $posted_data);
} elseif (is_array($post_data)) {
$posted_data = $post_data;
}
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method parse_str is being used without the second parameter initially being validated as an array. When $post_data is a string, parse_str modifies $posted_data, but there's no validation that parsing succeeded or that the result is safe. Additionally, the condition on line 35 allows $post_data to be an array, but if it's an array, it's directly assigned to $posted_data without any sanitization of its values. All values from $posted_data should be sanitized before use, even though this appears to be internal WooCommerce data.

Copilot uses AI. Check for mistakes.
Comment on lines 8 to 16
{
public function __construct()
{
// Fee calculation and application
add_action('woocommerce_cart_calculate_fees', [$this, 'add_priority_fee']);
add_action('woocommerce_checkout_create_order', [$this, 'save_priority_to_order'], 10, 2);
}

/**
* Add priority processing fee to cart
*/
public function add_priority_fee()
{
// Only process on checkout pages
if (!is_checkout()) {
return;
}

// Check if feature is enabled
if (get_option('wpp_enabled') !== 'yes' && get_option('wpp_enabled') !== '1') {
return;
}

// Check user permissions (removed log_permission_check call)
if (!Core_Permissions::can_access_priority_processing()) {
return;
}

// Check session availability
if (!WC()->session) {
return;
}

// Get priority state from session
$priority = WC()->session->get('priority_processing', false);
$should_add_fee = ($priority === true || $priority === 1 || $priority === '1');

if ($should_add_fee) {
$this->apply_priority_fee();
}
}

/**
* Apply the priority processing fee
*/
private function apply_priority_fee()
{
$fee_amount = floatval(get_option('wpp_fee_amount', '5.00'));
$fee_label = get_option('wpp_fee_label', 'Priority Processing & Express Shipping');

// Don't add fee if amount is zero or negative
if ($fee_amount <= 0) {
return;
}

// Check if fee already exists to avoid duplicates
if ($this->fee_already_exists($fee_label)) {
return;
}

// Add the fee to cart
WC()->cart->add_fee($fee_label, $fee_amount);
}

/**
* Check if priority fee already exists in cart
*/
private function fee_already_exists($fee_label)
{
$existing_fees = WC()->cart->get_fees();

foreach ($existing_fees as $fee) {
if ($fee->name === $fee_label) {
return true;
}
}
// NOTE: We NO LONGER add a separate cart fee
// The priority fee is added directly to shipping rates in Frontend_Shipping class
// This class now only handles saving priority status to orders

return false;
add_action('woocommerce_checkout_create_order', [$this, 'save_priority_to_order'], 10, 2);
}
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to shipping.php, this class is missing declare(strict_types=1), type hints, return type declarations, and uses inconsistent coding style (short array syntax instead of array()). The class also lacks PHPDoc blocks. These should be updated to match the modernization standards applied to the main plugin file and AJAX handler.

Copilot uses AI. Check for mistakes.
Comment on lines 43 to 68
if (!function_exists('woocommerce_store_api_register_endpoint_data')) {
return;
}

woocommerce_store_api_register_endpoint_data([
'endpoint' => CheckoutSchema::IDENTIFIER,
'namespace' => 'wpp-priority',
'data_callback' => [$this, 'extend_checkout_data'],
'schema_callback' => [$this, 'extend_checkout_schema'],
'schema_type' => ARRAY_A,
]);

woocommerce_store_api_register_endpoint_data([
'endpoint' => \Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema::IDENTIFIER,
'namespace' => 'wpp-priority',
'data_callback' => [$this, 'extend_cart_data'],
'schema_callback' => [$this, 'extend_cart_schema'],
'schema_type' => ARRAY_A,
]);

// Register update callback for priority processing checkbox
woocommerce_store_api_register_update_callback([
'namespace' => 'wpp-priority',
'callback' => [$this, 'update_priority_from_blocks'],
]);
}
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function woocommerce_store_api_register_endpoint_data() is being called without checking if it exists first on line 47. While there's a check for the function existence on line 43, if the function doesn't exist, the code returns early but doesn't log any error or provide feedback. More importantly, the function calls on lines 47, 55, and 64 should be wrapped in additional safety checks or the early return should ensure these are never reached if the function doesn't exist. Consider adding error logging when the function is not available to aid debugging.

Copilot uses AI. Check for mistakes.
Comment on lines 11 to 13
// NOTE: We NO LONGER add a separate cart fee
// The priority fee is added directly to shipping rates in Frontend_Shipping class
// This class now only handles saving priority status to orders
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "NOTE: We NO LONGER add a separate cart fee" but the structure suggests this class still has responsibilities. The comment should be more specific about what this class DOES do now (saves order meta), not just what it doesn't do. Consider updating to: "NOTE: This class handles saving priority status to order meta. The priority fee is added directly to shipping rates in the Frontend_Shipping class."

Suggested change
// NOTE: We NO LONGER add a separate cart fee
// The priority fee is added directly to shipping rates in Frontend_Shipping class
// This class now only handles saving priority status to orders
// NOTE: This class handles saving priority status and related fee information to order meta.
// The priority fee is added directly to shipping rates in the Frontend_Shipping class.

Copilot uses AI. Check for mistakes.
// Wait for WooCommerce Blocks to be ready
const initializeWhenReady = () => {
const { registerCheckoutBlock } = window.wc?.blocksCheckout || {};
const { ExperimentalOrderMeta } = window.wc?.blocksCheckout || {};
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable ExperimentalOrderMeta.

Suggested change
const { ExperimentalOrderMeta } = window.wc?.blocksCheckout || {};

Copilot uses AI. Check for mistakes.
Refactored PHP classes for blocks integration, fees, and shipping to use strict types, improved method signatures, and added docblocks for clarity. Enhanced error handling and user notifications in the JS checkout block, and standardized session management and metadata updates for priority processing. These changes improve code maintainability, reliability, and integration with WooCommerce Blocks and shipping plugins.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +64 to +67
} elseif ( isset( $_POST['priority_processing'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$priority_processing = sanitize_text_field( wp_unslash( $_POST['priority_processing'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( $priority_processing === '1' ) {
$priority_enabled = true;
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direct access to the $_POST superglobal without sanitization. The nonce verification uses sanitize_text_field and wp_unslash correctly, but the priority_processing value on line 65 is accessed directly from $_POST. Although it's immediately sanitized and unslashed on the next line, the direct array access pattern could lead to security issues. Consider extracting and sanitizing in a single operation.

Suggested change
} elseif ( isset( $_POST['priority_processing'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$priority_processing = sanitize_text_field( wp_unslash( $_POST['priority_processing'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( $priority_processing === '1' ) {
$priority_enabled = true;
} else {
$raw_priority_processing = filter_input( INPUT_POST, 'priority_processing', FILTER_UNSAFE_RAW ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( null !== $raw_priority_processing ) {
$priority_processing = sanitize_text_field( wp_unslash( $raw_priority_processing ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( $priority_processing === '1' ) {
$priority_enabled = true;
}

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +65
public function update_priority_status(): void {
// Verify nonce for security.
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'wpp_nonce' ) ) {
wp_send_json_error(
array(
'message' => __( 'Security check failed', 'woo-priority' ),
),
403
);
}

// Check if WooCommerce session is available.
if ( ! WC()->session ) {
wp_send_json_error(
array(
'message' => __( 'Session not available', 'woo-priority' ),
),
500
);
}
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AJAX handler calls wp_send_json_error without a return statement after it. While wp_send_json_error does call wp_die() internally and should terminate execution, it's a best practice to add explicit return statements for code clarity and to prevent any potential edge cases where execution might continue.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +18
const initializeWhenReady = () => {
const { registerCheckoutBlock } = window.wc?.blocksCheckout || {};
const { CheckboxControl } = window.wp?.components || {};
const { createElement, useState, useEffect } = window.wp?.element || {};
const { __ } = window.wp?.i18n || {};

// If dependencies aren't ready, wait and try again
if (!registerCheckoutBlock || !CheckboxControl || !createElement) {
console.log('WPP: Waiting for WooCommerce Blocks dependencies...');
setTimeout(initializeWhenReady, 500);
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code attempts to access the WooCommerce Blocks API and WordPress components using optional chaining (window.wc?.blocksCheckout), but then doesn't handle the case where these might be undefined after the checks. The setTimeout retry mechanism on line 18 could lead to infinite recursion if the dependencies never load. Consider adding a maximum retry count and a fallback or error notification to the user.

Suggested change
const initializeWhenReady = () => {
const { registerCheckoutBlock } = window.wc?.blocksCheckout || {};
const { CheckboxControl } = window.wp?.components || {};
const { createElement, useState, useEffect } = window.wp?.element || {};
const { __ } = window.wp?.i18n || {};
// If dependencies aren't ready, wait and try again
if (!registerCheckoutBlock || !CheckboxControl || !createElement) {
console.log('WPP: Waiting for WooCommerce Blocks dependencies...');
setTimeout(initializeWhenReady, 500);
const MAX_INITIALIZE_RETRIES = 20;
let initializeRetryCount = 0;
const initializeWhenReady = () => {
const { registerCheckoutBlock } = window.wc?.blocksCheckout || {};
const { CheckboxControl } = window.wp?.components || {};
const { createElement, useState, useEffect } = window.wp?.element || {};
const { __ } = window.wp?.i18n || {};
// If dependencies aren't ready, wait and try again (up to a limit)
if (!registerCheckoutBlock || !CheckboxControl || !createElement) {
if (initializeRetryCount < MAX_INITIALIZE_RETRIES) {
initializeRetryCount += 1;
console.log('WPP: Waiting for WooCommerce Blocks dependencies... Attempt ' + initializeRetryCount + ' of ' + MAX_INITIALIZE_RETRIES);
setTimeout(initializeWhenReady, 500);
} else {
console.error('WPP: WooCommerce Blocks dependencies did not load after ' + MAX_INITIALIZE_RETRIES + ' attempts. Aborting blocks checkout integration initialization.');
}

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +110
.catch(error => {
console.error('WPP: Error updating priority processing:', error);
setIsChecked(!checked); // Revert on error

// Show user-facing error notification
if (window.wp?.data?.dispatch) {
const noticesStore = window.wp.data.dispatch('core/notices');
if (noticesStore && typeof noticesStore.createErrorNotice === 'function') {
noticesStore.createErrorNotice(
'Unable to update priority processing. Please try again.',
{ type: 'snackbar', isDismissible: true }
);
}
}
})
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling in the catch block duplicates code from the then block's error handling. Both attempt to create error notices in the same way. This code duplication makes maintenance harder. Consider extracting the error notification logic into a separate function to improve code maintainability and reduce duplication.

Copilot uses AI. Check for mistakes.
Comment on lines +333 to +341
// Clear statistics cache if available.
if ( class_exists( 'Core_Statistics' ) ) {
$statistics = new Core_Statistics();
if ( method_exists( $statistics, 'clear_cache' ) ) {
$statistics->clear_cache();
}
if ( method_exists( $statistics, 'cleanup_scheduled_events' ) ) {
$statistics->cleanup_scheduled_events();
}
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor is declared as private to enforce singleton pattern, but the activation and deactivation handlers are now static methods that instantiate Core_Statistics directly. This bypasses the singleton pattern and could lead to multiple instances of Core_Statistics being created. Consider accessing the singleton instance instead or making these methods non-static and calling them through the singleton instance.

Suggested change
// Clear statistics cache if available.
if ( class_exists( 'Core_Statistics' ) ) {
$statistics = new Core_Statistics();
if ( method_exists( $statistics, 'clear_cache' ) ) {
$statistics->clear_cache();
}
if ( method_exists( $statistics, 'cleanup_scheduled_events' ) ) {
$statistics->cleanup_scheduled_events();
}
// Clear statistics cache if available using the existing singleton instance.
$plugin_instance = self::instance();
if ( null !== $plugin_instance ) {
$statistics = $plugin_instance->get_statistics();
if ( null !== $statistics ) {
if ( method_exists( $statistics, 'clear_cache' ) ) {
$statistics->clear_cache();
}
if ( method_exists( $statistics, 'cleanup_scheduled_events' ) ) {
$statistics->cleanup_scheduled_events();
}
}

Copilot uses AI. Check for mistakes.
Comment on lines 102 to 117
// Add the priority fee to the shipping cost.
$original_cost = $rate->cost;
$rates[ $rate_key ]->cost = $original_cost + $priority_fee;

// Optional: Update the label to indicate priority is included.
$show_priority_in_label = apply_filters( 'wpp_show_priority_in_shipping_label', false );
if ( $show_priority_in_label ) {
$rates[ $rate_key ]->label = $rate->label . ' ' . __( '(Priority)', 'woo-priority' );
}

// Add metadata for tracking.
$rates[ $rate_key ]->add_meta_data( 'wpp_priority_processing', 'yes', true );
$rates[ $rate_key ]->add_meta_data( 'wpp_priority_fee_added', $priority_fee, true );
$rates[ $rate_key ]->add_meta_data( 'wpp_original_cost', $original_cost, true );

$this->log_debug( "WPP: Modified rate '{$rate->label}': {$original_cost} -> " . $rates[ $rate_key ]->cost );
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method modifies the rate cost directly but doesn't check if the rate object has the expected properties or if it's the correct type. If a shipping plugin returns an unexpected rate object structure, this could cause a PHP warning or error. Consider adding type checking or try-catch around the cost modification.

Suggested change
// Add the priority fee to the shipping cost.
$original_cost = $rate->cost;
$rates[ $rate_key ]->cost = $original_cost + $priority_fee;
// Optional: Update the label to indicate priority is included.
$show_priority_in_label = apply_filters( 'wpp_show_priority_in_shipping_label', false );
if ( $show_priority_in_label ) {
$rates[ $rate_key ]->label = $rate->label . ' ' . __( '(Priority)', 'woo-priority' );
}
// Add metadata for tracking.
$rates[ $rate_key ]->add_meta_data( 'wpp_priority_processing', 'yes', true );
$rates[ $rate_key ]->add_meta_data( 'wpp_priority_fee_added', $priority_fee, true );
$rates[ $rate_key ]->add_meta_data( 'wpp_original_cost', $original_cost, true );
$this->log_debug( "WPP: Modified rate '{$rate->label}': {$original_cost} -> " . $rates[ $rate_key ]->cost );
// Ensure the rate object has the expected structure before modifying it.
if ( ! is_object( $rate ) || ! property_exists( $rate, 'cost' ) ) {
$this->log_debug( 'WPP: Skipping shipping rate with unexpected structure for key ' . $rate_key );
continue;
}
// Normalize original cost to a float if possible.
$original_cost = is_numeric( $rate->cost ) ? (float) $rate->cost : 0.0;
$rates[ $rate_key ]->cost = $original_cost + $priority_fee;
// Optional: Update the label to indicate priority is included.
$show_priority_in_label = apply_filters( 'wpp_show_priority_in_shipping_label', false );
$label = ( property_exists( $rate, 'label' ) && is_string( $rate->label ) ) ? $rate->label : '';
if ( $show_priority_in_label && '' !== $label ) {
$rates[ $rate_key ]->label = $label . ' ' . __( '(Priority)', 'woo-priority' );
}
// Add metadata for tracking (only if supported by the rate object).
if ( method_exists( $rates[ $rate_key ], 'add_meta_data' ) ) {
$rates[ $rate_key ]->add_meta_data( 'wpp_priority_processing', 'yes', true );
$rates[ $rate_key ]->add_meta_data( 'wpp_priority_fee_added', $priority_fee, true );
$rates[ $rate_key ]->add_meta_data( 'wpp_original_cost', $original_cost, true );
}
$this->log_debug( "WPP: Modified rate '{$label}': {$original_cost} -> " . $rates[ $rate_key ]->cost );

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +125
$priority_enabled = false;

// Check 'priority_enabled' parameter (Block checkout).
if ( isset( $_POST['priority_enabled'] ) ) {
$value = sanitize_text_field( wp_unslash( $_POST['priority_enabled'] ) );
$priority_enabled = in_array( $value, array( 'true', '1', 1 ), true );
}

// Check 'priority' parameter (Classic checkout).
if ( ! $priority_enabled && isset( $_POST['priority'] ) ) {
$value = sanitize_text_field( wp_unslash( $_POST['priority'] ) );
$priority_enabled = in_array( $value, array( 'true', '1', 1 ), true );
}

return $priority_enabled;
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_priority_status_from_request method uses in_array with strict comparison but checks for three different value types (string 'true', string '1', and integer 1). This is good, but the logic could be simplified. Also, the second condition checks if priority_enabled is already false before checking the 'priority' parameter, which means if 'priority_enabled' is set to any truthy value, the 'priority' parameter will be ignored. This could lead to unexpected behavior if both parameters are present with different values.

Suggested change
$priority_enabled = false;
// Check 'priority_enabled' parameter (Block checkout).
if ( isset( $_POST['priority_enabled'] ) ) {
$value = sanitize_text_field( wp_unslash( $_POST['priority_enabled'] ) );
$priority_enabled = in_array( $value, array( 'true', '1', 1 ), true );
}
// Check 'priority' parameter (Classic checkout).
if ( ! $priority_enabled && isset( $_POST['priority'] ) ) {
$value = sanitize_text_field( wp_unslash( $_POST['priority'] ) );
$priority_enabled = in_array( $value, array( 'true', '1', 1 ), true );
}
return $priority_enabled;
$raw_value = null;
// Prefer 'priority' parameter (Classic checkout) if present.
if ( isset( $_POST['priority'] ) ) {
$raw_value = wp_unslash( $_POST['priority'] );
} elseif ( isset( $_POST['priority_enabled'] ) ) {
// Fallback to 'priority_enabled' parameter (Block checkout).
$raw_value = wp_unslash( $_POST['priority_enabled'] );
}
if ( null === $raw_value ) {
return false;
}
$value = sanitize_text_field( $raw_value );
return in_array( $value, array( 'true', '1' ), true );

Copilot uses AI. Check for mistakes.
Comment on lines 128 to 146
/**
* Get cart fragments for AJAX update
*
* @since 1.4.2
* @return array Cart fragments
*/
private function get_cart_fragments(): array {
$fragments = array(
'.order-total' => '<tr class="order-total"><th>' . esc_html__( 'Total', 'woocommerce' ) . '</th><td>' . WC()->cart->get_total() . '</td></tr>',
);

/**
* Filter cart fragments
*
* @since 1.4.2
* @param array $fragments Cart fragments
*/
return apply_filters( 'woocommerce_update_order_review_fragments', $fragments );
}
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_cart_fragments method creates fragments but references a CSS class '.order-total' that may not exist on the block checkout, as block checkout uses different DOM structure than classic checkout. This could result in fragments not being applied correctly in the block checkout context.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +89
woocommerce_store_api_register_update_callback(
array(
'namespace' => 'wpp-priority',
'callback' => array( $this, 'update_priority_from_blocks' ),
)
);
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The blocks integration uses woocommerce_store_api_register_endpoint_data and woocommerce_store_api_register_update_callback functions without checking if they exist first. While there's a check for the Blocks Package class existence, these specific functions might not be available in all versions of WooCommerce that support blocks. Consider adding function_exists checks or wrapping in try-catch blocks.

Suggested change
woocommerce_store_api_register_update_callback(
array(
'namespace' => 'wpp-priority',
'callback' => array( $this, 'update_priority_from_blocks' ),
)
);
if ( function_exists( 'woocommerce_store_api_register_update_callback' ) ) {
woocommerce_store_api_register_update_callback(
array(
'namespace' => 'wpp-priority',
'callback' => array( $this, 'update_priority_from_blocks' ),
)
);
} else {
$this->log_debug( 'WPP Blocks: Store API update callback registration function not available' );
}

Copilot uses AI. Check for mistakes.
'checkbox_label' => get_option( 'wpp_checkbox_label', __( 'Priority processing + Express shipping', 'woo-priority' ) ),
'description' => get_option( 'wpp_description', __( 'Your order will be processed with priority and shipped via express delivery', 'woo-priority' ) ),
)
);
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enqueue_block_scripts method checks if the blocks-checkout.js file exists before enqueuing it, which is good practice. However, if the file doesn't exist, the method silently fails without any notification or fallback. Consider logging a warning or error when the required script file is missing, especially since this is new functionality in this version.

Suggested change
);
);
} else {
$this->log_debug(
sprintf(
'[WooCommerce Priority Processing] blocks-checkout.js not found at path: %s. The blocks checkout integration script was not enqueued.',
$script_path
)
);

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 14 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +69 to +70
// Notify WooCommerce and other scripts that checkout was updated
// This is a notification event, NOT a trigger for full refresh
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says 'This is a notification event, NOT a trigger for full refresh' but this is misleading. The 'updated_checkout' event is actually the standard WooCommerce event that can trigger various checkout updates. Other plugins listening to this event may perform full refreshes or other actions. The comment should be clarified to indicate that while the plugin intends this as a notification, the event itself may trigger additional behavior from other plugins.

Suggested change
// Notify WooCommerce and other scripts that checkout was updated
// This is a notification event, NOT a trigger for full refresh
// Notify WooCommerce and other scripts that checkout data has changed.
// Note: 'updated_checkout' is a standard WooCommerce event. While this plugin
// uses it as a notification, other plugins may listen to it and perform full
// refreshes or additional checkout updates.

Copilot uses AI. Check for mistakes.
<strong style="color: #28a745; font-weight: 600; display: block; font-size: 14px;">
<?php echo esc_html($checkbox_label); ?>
<span class="wpp-price" style="color: #dc3545; font-weight: 600; margin-left: 5px;">(+<?php echo wc_price($fee_amount); ?>)</span>
<span class="wpp-price" style="color: #dc3545; font-weight: 600; margin-left: 5px;">(+ <?php echo wc_price($fee_amount); ?> )</span>
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spacing change from '(+' to '(+ ' and ')' to ' )' is inconsistent with the same text in blocks-checkout.js line 160, which uses '(+' without the extra space before the closing parenthesis. Consider maintaining consistency across both classic and block checkout implementations.

Suggested change
<span class="wpp-price" style="color: #dc3545; font-weight: 600; margin-left: 5px;">(+ <?php echo wc_price($fee_amount); ?> )</span>
<span class="wpp-price" style="color: #dc3545; font-weight: 600; margin-left: 5px;">(+<?php echo wc_price($fee_amount); ?>)</span>

Copilot uses AI. Check for mistakes.
if (window.wp?.data?.dispatch) {
const cartStore = window.wp.data.dispatch('wc/store/cart');
if (cartStore && typeof cartStore.invalidateResolutionForStore === 'function') {
cartStore.invalidateResolutionForStore();
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cart store invalidation method name 'invalidateResolutionForStore' may not be the correct method. WooCommerce's cart store typically uses methods like 'receiveCart' or dispatches to update the cart state. Verify that this method exists and works as intended, as incorrect method names will cause silent failures in the block checkout update flow.

Suggested change
cartStore.invalidateResolutionForStore();
// Invalidate the cached resolution for getCart and trigger a refetch
cartStore.invalidateResolutionForStore('getCart');
if (typeof cartStore.getCart === 'function') {
cartStore.getCart();
}

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +19
if (!registerCheckoutBlock || !CheckboxControl || !createElement) {
console.log('WPP: Waiting for WooCommerce Blocks dependencies...');
setTimeout(initializeWhenReady, 500);
return;
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialization uses a 500ms setTimeout retry mechanism when dependencies aren't ready. While this works, it will continue retrying indefinitely if the dependencies never load. Consider adding a maximum retry count or timeout to prevent infinite loops, especially in cases where WooCommerce Blocks is not properly installed or has errors.

Copilot uses AI. Check for mistakes.
createElement(
'span',
{ style: { color: '#dc3545', fontWeight: '600', marginLeft: '5px' } },
'(+' + formattedPrice + ' added to shipping)'
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The text displays 'added to shipping' but according to the implementation, the fee is added to ALL shipping methods in the add_priority_to_shipping_rates function, not just the selected one. While technically correct since the customer will only pay for one selected method, the wording could be clearer. Consider 'included in shipping cost' or 'added to each shipping option' for better clarity.

Suggested change
'(+' + formattedPrice + ' added to shipping)'
'(+' + formattedPrice + ' included in shipping cost)'

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +147
if ( function_exists( 'wc_cart_totals_order_total_html' ) ) {
ob_start();
wc_cart_totals_order_total_html();
$order_total = ob_get_clean();

$fragments['.order-total'] = '<tr class="order-total"><th>' . esc_html__( 'Total', 'woocommerce' ) . '</th><td>' . $order_total . '</td></tr>';
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function wc_cart_totals_order_total_html() is called without checking if it exists. While this is a WooCommerce function, adding a function_exists check would make the code more defensive. If the function doesn't exist, the current code will proceed silently without adding the fragment, which is good, but it's better to be explicit about this expectation.

Copilot uses AI. Check for mistakes.
Comment on lines 118 to 127
$fees = WC()->cart->get_fees();

foreach ( $fees as $fee_key => $fee ) {
if ( $fee->name === $fee_label ) {
unset( $fees[ $fee_key ] );
return true;
}
}

return false;
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remove_priority_fee method attempts to remove a fee by unsetting it from the $fees array, but this doesn't actually remove the fee from the WooCommerce cart. The get_fees() method returns a copy of the fees array, so unsetting elements in this copy won't affect the actual cart fees. To properly remove a fee, you would need to use WC()->cart->fees_api() methods or manipulate the cart's fee data directly. This method will always return false or true without actually removing anything from the cart.

Suggested change
$fees = WC()->cart->get_fees();
foreach ( $fees as $fee_key => $fee ) {
if ( $fee->name === $fee_label ) {
unset( $fees[ $fee_key ] );
return true;
}
}
return false;
if ( ! method_exists( WC()->cart, 'fees_api' ) ) {
return false;
}
$fees_api = WC()->cart->fees_api();
$fees = $fees_api->get_fees();
$removed = false;
foreach ( $fees as $fee_key => $fee ) {
if ( isset( $fee->name ) && $fee->name === $fee_label ) {
$fees_api->remove_fee( $fee_key );
$removed = true;
}
}
if ( $removed ) {
// Ensure cart totals reflect the removed fee.
WC()->cart->calculate_totals();
}
return $removed;

Copilot uses AI. Check for mistakes.
Comment on lines +258 to +261
wp_enqueue_script(
'wpp-blocks-checkout',
$script_url,
array( 'wp-element', 'wp-i18n', 'wp-components', 'wc-blocks-checkout' ),
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script is enqueued with dependency 'wc-blocks-checkout', but there's no check to verify this dependency is available before enqueuing. If WooCommerce Blocks is not properly loaded or the handle has changed in newer versions, the script will fail to load properly or execute with undefined dependencies. Consider adding a check to verify the dependency is registered before enqueuing, or at minimum add error handling in the JavaScript to gracefully handle missing dependencies.

Suggested change
wp_enqueue_script(
'wpp-blocks-checkout',
$script_url,
array( 'wp-element', 'wp-i18n', 'wp-components', 'wc-blocks-checkout' ),
$deps = array( 'wp-element', 'wp-i18n', 'wp-components' );
// Ensure WooCommerce Blocks checkout script is registered before using it as a dependency.
if ( wp_script_is( 'wc-blocks-checkout', 'registered' ) ) {
$deps[] = 'wc-blocks-checkout';
} else {
$this->log_debug( 'WooCommerce Priority Processing: wc-blocks-checkout script is not registered; enqueuing without this dependency.' );
}
wp_enqueue_script(
'wpp-blocks-checkout',
$script_url,
$deps,

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +69
if ( isset( $posted_data['priority_processing'] ) && $posted_data['priority_processing'] === '1' ) {
$priority_enabled = true;
} elseif ( isset( $_POST['priority_processing'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$priority_processing = sanitize_text_field( wp_unslash( $_POST['priority_processing'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( $priority_processing === '1' ) {
$priority_enabled = true;
}
}
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable $posted_data['priority_processing'] is sanitized, but then the code falls back to checking $_POST['priority_processing'] directly with only sanitization (no wp_unslash). This creates inconsistency in how data is accessed. Since the code already parses and sanitizes $post_data into $posted_data array, the fallback to $_POST should also use wp_unslash for consistency with WordPress security best practices.

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +122
$priority_enabled = in_array( $value, array( 'true', '1', 1 ), true );
}

// Check 'priority' parameter (Classic checkout).
if ( ! $priority_enabled && isset( $_POST['priority'] ) ) {
$value = sanitize_text_field( wp_unslash( $_POST['priority'] ) );
$priority_enabled = in_array( $value, array( 'true', '1', 1 ), true );
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The strict comparison 'in_array($value, array('true', '1', 1), true)' will never match the integer 1 because $value is always a string from sanitize_text_field. The third parameter true enforces strict type checking, so comparing a string to an integer will always fail. Either remove the integer 1 from the array, or remove the strict parameter, or cast $value appropriately before comparison.

Suggested change
$priority_enabled = in_array( $value, array( 'true', '1', 1 ), true );
}
// Check 'priority' parameter (Classic checkout).
if ( ! $priority_enabled && isset( $_POST['priority'] ) ) {
$value = sanitize_text_field( wp_unslash( $_POST['priority'] ) );
$priority_enabled = in_array( $value, array( 'true', '1', 1 ), true );
$priority_enabled = in_array( $value, array( 'true', '1' ), true );
}
// Check 'priority' parameter (Classic checkout).
if ( ! $priority_enabled && isset( $_POST['priority'] ) ) {
$value = sanitize_text_field( wp_unslash( $_POST['priority'] ) );
$priority_enabled = in_array( $value, array( 'true', '1' ), true );

Copilot uses AI. Check for mistakes.
Removed the display of priority processing on the WooCommerce frontend order view page and simplified the thank you page notice. Added Bulgarian translation files and updated the translation template for the plugin.
@MrGKanev MrGKanev merged commit f05cc20 into main Jan 4, 2026
@MrGKanev MrGKanev deleted the 1.4.2 branch January 4, 2026 19:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants