WooCommerce自定义订单状态

为 WooCommerce 商店创建和管理自定义订单状态。使用高级功能(如自动状态更改、可自定义的电子邮件通知和综合报告)增强您的订单工作流程。

### 主要功能
* **创建自定义状态**:使用自定义名称、颜色和图标创建无限数量的自定义订单状态。
* **状态组**:将您的状态组织成逻辑组,以便更好地管理工作流程。
* **自动状态更改**:根据时间设置自动状态转换。
* **电子邮件通知**:订单状态更改时向客户发送自定义电子邮件通知。
* **状态报告**:通过详细的状态报告深入了解您的订单工作流程。
* **前端显示**:向客户显示自定义状态徽章和订单时间表。
* **批量操作**:使用自定义状态批量操作一次更新多个订单。
* **API 支持**:完全 REST API 支持与其他系统集成。

相关文章: WordPress优惠券提醒插件系统

<?php
/**
* Plugin Name: BDFG Custom Order Status for WooCommerce
* Plugin URI: https://beiduofengou.net/2025/01/16/bdfg-custom-order-status/
* Description: Create and manage custom order statuses for WooCommerce with advanced features
* Version: 1.2.3
* Author: Beiduofengou
* Author URI: https://beiduofengou.net
* Text Domain: bdfg-custom-order-status
* Domain Path: /languages
* Requires at least: 5.6
* Requires PHP: 7.2
* WC requires at least: 4.0.0
* WC tested up to: 8.5.0
*
* @package BDFG_Custom_Order_Status
* @author Beiduofengou
* @copyright 2023-2025 Beiduofengou
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit; // Security measure - don't reveal anything if called directly
}

// Check if WooCommerce is active
if (!in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins'))) &&
!array_key_exists('woocommerce/woocommerce.php', apply_filters('active_plugins', get_site_option('active_sitewide_plugins', array())))) {

add_action('admin_notices', function() {
?>
<div class="notice notice-error is-dismissible">
<p><?php _e('BDFG Custom Order Status requires WooCommerce to be installed and activated.', 'bdfg-custom-order-status'); ?></p>
</div>
<?php
});
return; // Stop execution
}

// Define plugin constants
define('BDFG_COS_VERSION', '1.2.3');
define('BDFG_COS_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('BDFG_COS_PLUGIN_URL', plugin_dir_url(__FILE__));
define('BDFG_COS_PLUGIN_BASENAME', plugin_basename(__FILE__));
define('BDFG_COS_PLUGIN_FILE', __FILE__);

// Create necessary directories if they don't exist
if (!file_exists(BDFG_COS_PLUGIN_DIR . 'includes/')) {
@mkdir(BDFG_COS_PLUGIN_DIR . 'includes/', 0755, true);
}

if (!file_exists(BDFG_COS_PLUGIN_DIR . 'includes/admin/')) {
@mkdir(BDFG_COS_PLUGIN_DIR . 'includes/admin/', 0755, true);
}

if (!file_exists(BDFG_COS_PLUGIN_DIR . 'includes/admin/views/')) {
@mkdir(BDFG_COS_PLUGIN_DIR . 'includes/admin/views/', 0755, true);
}

if (!file_exists(BDFG_COS_PLUGIN_DIR . 'assets/css/')) {
@mkdir(BDFG_COS_PLUGIN_DIR . 'assets/css/', 0755, true);
}

if (!file_exists(BDFG_COS_PLUGIN_DIR . 'assets/js/')) {
@mkdir(BDFG_COS_PLUGIN_DIR . 'assets/js/', 0755, true);
}

if (!file_exists(BDFG_COS_PLUGIN_DIR . 'assets/images/')) {
@mkdir(BDFG_COS_PLUGIN_DIR . 'assets/images/', 0755, true);
}

// Include required files
require_once BDFG_COS_PLUGIN_DIR . 'includes/class-bdfg-custom-order-status.php';
require_once BDFG_COS_PLUGIN_DIR . 'includes/admin/class-bdfg-custom-order-status-admin.php';
require_once BDFG_COS_PLUGIN_DIR . 'includes/class-bdfg-custom-order-status-install.php';
require_once BDFG_COS_PLUGIN_DIR . 'includes/class-bdfg-custom-order-status-email.php';
require_once BDFG_COS_PLUGIN_DIR . 'includes/class-bdfg-custom-order-status-api.php';

/**
* Main plugin class
*
* Initializes and coordinates all parts of the plugin
*/
class BDFG_Custom_Order_Status_Main {
/**
* Instance of this class
*
* @var object
*/
protected static $instance = null;

/**
* Get the single instance of this class
*
* @since 1.0.0
* @return object The instance
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}

/**
* Constructor
*
* @since 1.0.0
*/
public function __construct() {
// Initialize plugin
add_action('plugins_loaded', array($this, 'init_plugin'), 10);

// Register activation and deactivation hooks
register_activation_hook(BDFG_COS_PLUGIN_FILE, array('BDFG_Custom_Order_Status_Install', 'activate'));
register_deactivation_hook(BDFG_COS_PLUGIN_FILE, array('BDFG_Custom_Order_Status_Install', 'deactivate'));
register_uninstall_hook(BDFG_COS_PLUGIN_FILE, array('BDFG_Custom_Order_Status_Install', 'uninstall'));

// Add settings link on plugin page
add_filter('plugin_action_links_' . BDFG_COS_PLUGIN_BASENAME, array($this, 'add_plugin_action_links'));

// Add upgrade notice if needed
add_action('admin_init', array($this, 'check_version'));

// Add plugin row meta links
add_filter('plugin_row_meta', array($this, 'plugin_row_meta'), 10, 2);
}

/**
* Initialize plugin
*
* @since 1.0.0
*/
public function init_plugin() {
// Load text domain for translations
load_plugin_textdomain('bdfg-custom-order-status', false, dirname(BDFG_COS_PLUGIN_BASENAME) . '/languages');

// Initialize the core classes
new BDFG_Custom_Order_Status();
new BDFG_Custom_Order_Status_Email();
new BDFG_Custom_Order_Status_API();

// Admin only initialization
if (is_admin()) {
new BDFG_Custom_Order_Status_Admin();
}

// Maybe show welcome message
if (get_option('bdfg_custom_order_status_just_activated', false)) {
add_action('admin_notices', array($this, 'welcome_notice'));
delete_option('bdfg_custom_order_status_just_activated');
}
}

/**
* Add settings link to plugin action links
*
* @since 1.0.0
* @param array $links Plugin action links
* @return array Modified plugin action links
*/
public function add_plugin_action_links($links) {
$plugin_links = array(
'<a href="' . admin_url('admin.php?page=bdfg-custom-order-status') . '">' . __('Settings', 'bdfg-custom-order-status') . '</a>',
'<a href="' . admin_url('admin.php?page=bdfg-status-settings') . '">' . __('Configuration', 'bdfg-custom-order-status') . '</a>'
);
return array_merge($plugin_links, $links);
}

/**
* Add plugin row meta links
*
* @since 1.2.0
* @param array $links Plugin row meta links
* @param string $file Plugin base file
* @return array Modified plugin row meta links
*/
public function plugin_row_meta($links, $file) {
if (BDFG_COS_PLUGIN_BASENAME !== $file) {
return $links;
}

$row_meta = array(
'docs' => '<a href="' . esc_url('https://beiduofengou.net/docs/bdfg-custom-order-status/') . '" aria-label="' . esc_attr__('View BDFG Order Status documentation', 'bdfg-custom-order-status') . '">' . esc_html__('Documentation', 'bdfg-custom-order-status') . '</a>',
'support' => '<a href="' . esc_url('https://beiduofengou.net/support/') . '" aria-label="' . esc_attr__('Get support from Beiduofengou team', 'bdfg-custom-order-status') . '">' . esc_html__('Support', 'bdfg-custom-order-status') . '</a>',
);

return array_merge($links, $row_meta);
}

/**
* Check if the plugin needs to run upgrades
*
* @since 1.1.0
*/
public function check_version() {
$current_version = get_option('bdfg_custom_order_status_version', '1.0.0');

// Compare versions and run upgrade routine if needed
if (version_compare($current_version, BDFG_COS_VERSION, '<')) {
BDFG_Custom_Order_Status_Install::update(BDFG_COS_VERSION, $current_version);
}
}

/**
* Show welcome notice after plugin activation
*
* @since 1.2.0
*/
public function welcome_notice() {
?>
<div class="notice notice-info is-dismissible">
<p><?php echo sprintf(__('Thank you for installing BDFG Custom Order Status for WooCommerce! <a href="%s">Configure your custom statuses now</a>.', 'bdfg-custom-order-status'), admin_url('admin.php?page=bdfg-custom-order-status')); ?></p>
</div>
<?php
}
}

// Initialize the plugin
BDFG_Custom_Order_Status_Main::get_instance();

includes/class-bdfg-custom-order-status-install.php


<?php
/**
* Installation and upgrade related functions
*
* @package BDFG_Custom_Order_Status
* @author Beiduofengou
* @since 1.0.0
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}

/**
* Installation and upgrade class
*/
class BDFG_Custom_Order_Status_Install {
/**
* Database version
*
* @var string
*/
private static $db_version = '1.2';

/**
* Plugin activation
*
* @since 1.0.0
*/
public static function activate() {
global $wpdb;

// Record activation for welcome message
update_option('bdfg_custom_order_status_just_activated', true);

// Create custom tables for status groups if not exists
$table_name = $wpdb->prefix . 'bdfg_order_status_groups';
$charset_collate = $wpdb->get_charset_collate();

$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
group_name varchar(255) NOT NULL,
group_slug varchar(255) NOT NULL,
group_description text NULL,
display_order int(11) NOT NULL DEFAULT 0,
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at datetime NULL,
PRIMARY KEY (id),
UNIQUE KEY group_slug (group_slug)
) $charset_collate;";

require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);

// Create custom table for statuses
$table_name = $wpdb->prefix . 'bdfg_order_statuses';
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
status_name varchar(255) NOT NULL,
status_slug varchar(255) NOT NULL,
status_description text NULL,
group_id mediumint(9) NULL,
icon varchar(255) NULL,
color varchar(20) NULL,
text_color varchar(20) NULL DEFAULT '#FFFFFF',
expiry_hours int(11) NULL,
next_status varchar(255) NULL,
notification_enabled tinyint(1) NOT NULL DEFAULT 1,
display_order int(11) NOT NULL DEFAULT 0,
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at datetime NULL,
PRIMARY KEY (id),
UNIQUE KEY status_slug (status_slug)
) $charset_collate;";

dbDelta($sql);

// Add default status groups if table is empty
$count = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->prefix" . "bdfg_order_status_groups");

if ($count == 0) {
$groups = [
[
'group_name' => __('Processing Statuses', 'bdfg-custom-order-status'),
'group_slug' => 'processing-statuses',
'group_description' => __('Statuses for orders being processed', 'bdfg-custom-order-status'),
'display_order' => 1
],
[
'group_name' => __('Shipping Statuses', 'bdfg-custom-order-status'),
'group_slug' => 'shipping-statuses',
'group_description' => __('Statuses for orders in shipping stage', 'bdfg-custom-order-status'),
'display_order' => 2
],
[
'group_name' => __('Completion Statuses', 'bdfg-custom-order-status'),
'group_slug' => 'completion-statuses',
'group_description' => __('Statuses for completed orders', 'bdfg-custom-order-status'),
'display_order' => 3
],
[
'group_name' => __('BDFG Special Statuses', 'bdfg-custom-order-status'),
'group_slug' => 'bdfg-special-statuses',
'group_description' => __('Special statuses by Beiduofengou', 'bdfg-custom-order-status'),
'display_order' => 4
]
];

$group_table = $wpdb->prefix . 'bdfg_order_status_groups';
foreach ($groups as $group) {
$wpdb->insert($group_table, $group);
}
}

// Add BDFG demo status if no statuses exist
$status_count = $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->prefix" . "bdfg_order_statuses");

if ($status_count == 0) {
// Get the BDFG special statuses group
$bdfg_group = $wpdb->get_var("SELECT id FROM $wpdb->prefix" . "bdfg_order_status_groups WHERE group_slug = 'bdfg-special-statuses'");

if ($bdfg_group) {
$demo_statuses = [
[
'status_name' => __('BDFG Processing', 'bdfg-custom-order-status'),
'status_slug' => 'bdfg-processing',
'status_description' => __('Order is being processed by BDFG system', 'bdfg-custom-order-status'),
'group_id' => $bdfg_group,
'icon' => 'f466', // dashicons-businessman
'color' => '#7854e4',
'text_color' => '#ffffff',
'expiry_hours' => 24,
'next_status' => 'bdfg-ready',
'display_order' => 1
],
[
'status_name' => __('BDFG Ready', 'bdfg-custom-order-status'),
'status_slug' => 'bdfg-ready',
'status_description' => __('Order is ready for shipping', 'bdfg-custom-order-status'),
'group_id' => $bdfg_group,
'icon' => 'f147', // dashicons-email
'color' => '#46b450',
'text_color' => '#ffffff',
'display_order' => 2
]
];

$status_table = $wpdb->prefix . 'bdfg_order_statuses';
foreach ($demo_statuses as $status) {
$wpdb->insert($status_table, $status);
}
}
}

// Add capabilities to roles
$roles = ['administrator', 'shop_manager'];
foreach ($roles as $role_name) {
$role = get_role($role_name);
if ($role) {
$role->add_cap('manage_bdfg_order_statuses');
$role->add_cap('view_bdfg_order_status_reports');
}
}

// Set plugin version and database version
update_option('bdfg_custom_order_status_version', BDFG_COS_VERSION);
update_option('bdfg_custom_order_status_db_version', self::$db_version);

// Set first time setup flag
if (!get_option('bdfg_custom_order_status_setup')) {
update_option('bdfg_custom_order_status_setup', 'pending');
}

// Set default options
if (!get_option('bdfg_cos_enable_email_notifications')) {
update_option('bdfg_cos_enable_email_notifications', 'yes');
}
if (!get_option('bdfg_cos_bulk_actions_enabled')) {
update_option('bdfg_cos_bulk_actions_enabled', 'yes');
}
if (!get_option('bdfg_cos_status_column_enabled')) {
update_option('bdfg_cos_status_column_enabled', 'yes');
}

// Flush rewrite rules
flush_rewrite_rules();
}

/**
* Plugin deactivation
*
* @since 1.0.0
*/
public static function deactivate() {
// Clear scheduled events
wp_clear_scheduled_hook('bdfg_check_order_status_expiration');

// Flush rewrite rules
flush_rewrite_rules();
}

/**
* Plugin uninstall
*
* @since 1.1.0
*/
public static function uninstall() {
// Check if we should remove data
if (get_option('bdfg_cos_remove_data_on_uninstall', 'no') === 'yes') {
global $wpdb;

// Drop tables
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}bdfg_order_statuses");
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}bdfg_order_status_groups");

// Delete options
delete_option('bdfg_custom_order_status_version');
delete_option('bdfg_custom_order_status_db_version');
delete_option('bdfg_custom_order_status_setup');
delete_option('bdfg_cos_enable_email_notifications');
delete_option('bdfg_cos_bulk_actions_enabled');
delete_option('bdfg_cos_status_column_enabled');
delete_option('bdfg_cos_remove_data_on_uninstall');
}
}

/**
* Update routine
*
* @since 1.1.0
* @param string $new_version New version
* @param string $old_version Old version
*/
public static function update($new_version, $old_version) {
global $wpdb;

// Add text_color column in version 1.1.0+
if (version_compare($old_version, '1.1.0', '<')) {
$column_exists = $wpdb->get_var("SHOW COLUMNS FROM {$wpdb->prefix}bdfg_order_statuses LIKE 'text_color'");

if (!$column_exists) {
$wpdb->query("ALTER TABLE {$wpdb->prefix}bdfg_order_statuses ADD text_color VARCHAR(20) DEFAULT '#FFFFFF' AFTER color");
}
}

// Add notification_enabled column in version 1.2.0+
if (version_compare($old_version, '1.2.0', '<')) {
$column_exists = $wpdb->get_var("SHOW COLUMNS FROM {$wpdb->prefix}bdfg_order_statuses LIKE 'notification_enabled'");

if (!$column_exists) {
$wpdb->query("ALTER TABLE {$wpdb->prefix}bdfg_order_statuses ADD notification_enabled TINYINT(1) NOT NULL DEFAULT 1 AFTER next_status");
}
}

// Add updated_at column to both tables in version 1.2.0+
if (version_compare($old_version, '1.2.0', '<')) {
$column_exists = $wpdb->get_var("SHOW COLUMNS FROM {$wpdb->prefix}bdfg_order_statuses LIKE 'updated_at'");

if (!$column_exists) {
$wpdb->query("ALTER TABLE {$wpdb->prefix}bdfg_order_statuses ADD updated_at DATETIME NULL AFTER created_at");
}

$column_exists = $wpdb->get_var("SHOW COLUMNS FROM {$wpdb->prefix}bdfg_order_status_groups LIKE 'updated_at'");

if (!$column_exists) {
$wpdb->query("ALTER TABLE {$wpdb->prefix}bdfg_order_status_groups ADD updated_at DATETIME NULL AFTER created_at");
}
}

// Update version number
update_option('bdfg_custom_order_status_version', $new_version);
update_option('bdfg_custom_order_status_db_version', self::$db_version);
}
}

includes/class-bdfg-custom-order-status.php

相关文章: WooCommerce 客户分析报告插件


<?php
/**
* Core functionality for custom order statuses
*
* @package BDFG_Custom_Order_Status
* @author Beiduofengou
* @since 1.0.0
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}

/**
* Main class for custom order status functionality
*/
class BDFG_Custom_Order_Status {
/**
* Constructor
*/
public function __construct() {
// Register custom order statuses
add_action('init', array($this, 'register_custom_order_statuses'), 20);

// Add custom statuses to WooCommerce order statuses list
add_filter('wc_order_statuses', array($this, 'add_custom_order_statuses'), 20, 1);

// Add custom order status actions
add_filter('woocommerce_admin_order_actions', array($this, 'add_custom_order_status_actions'), 20, 2);

// Handle status expiration
add_action('bdfg_check_order_status_expiration', array($this, 'check_order_status_expiration'));

// Schedule status expiration check if not already scheduled
if (!wp_next_scheduled('bdfg_check_order_status_expiration')) {
wp_schedule_event(time(), 'hourly', 'bdfg_check_order_status_expiration');
}

// Add custom order status column styling
add_action('admin_head', array($this, 'add_custom_order_status_column_style'));

// Register AJAX handlers for frontend
add_action('wp_ajax_nopriv_bdfg_get_order_status', array($this, 'ajax_get_order_status'));
add_action('wp_ajax_bdfg_get_order_status', array($this, 'ajax_get_order_status'));

// Add shortcode for status display
add_shortcode('bdfg_order_status', array($this, 'order_status_shortcode'));

// Add order status to the orders list in My Account
add_filter('woocommerce_my_account_my_orders_columns', array($this, 'add_status_column_my_account'), 20);
add_action('woocommerce_my_account_my_orders_column_order-status', array($this, 'add_status_column_content_my_account'));

// Add BDFG status badge to order details
add_action('woocommerce_order_details_before_order_table', array($this, 'add_status_badge_to_order_details'), 10, 1);
}

/**
* Register custom order statuses with WooCommerce
*/
public function register_custom_order_statuses() {
global $wpdb;

// Get all custom statuses from database
$statuses = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}bdfg_order_statuses ORDER BY display_order ASC");

if (!empty($statuses)) {
foreach ($statuses as $status) {
// Register with WooCommerce
register_post_status('wc-' . $status->status_slug, array(
'label' => $status->status_name,
'public' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
'label_count' => _n_noop($status->status_name . ' <span class="count">(%s)</span>', $status->status_name . ' <span class="count">(%s)</span>')
));
}
}
}

/**
* Add custom order statuses to WooCommerce order statuses
*
* @param array $order_statuses Existing order statuses
* @return array Modified order statuses
*/
public function add_custom_order_statuses($order_statuses) {
global $wpdb;

// Get all custom statuses from database
$statuses = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}bdfg_order_statuses ORDER BY display_order ASC");

if (!empty($statuses)) {
foreach ($statuses as $status) {
$order_statuses['wc-' . $status->status_slug] = $status->status_name;
}
}

return $order_statuses;
}

/**
* Add custom order status actions to order list
*
* @param array $actions Existing actions
* @param WC_Order $order Order object
* @return array Modified actions
*/
public function add_custom_order_status_actions($actions, $order) {
global $wpdb;

// Get current status
$current_status = $order->get_status();

// Get all custom statuses from database
$statuses = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}bdfg_order_statuses ORDER BY display_order ASC");

if (!empty($statuses)) {
foreach ($statuses as $status) {
// Skip current status
if ($current_status === $status->status_slug) {
continue;
}

// Add action for changing to this status
$actions['bdfg_status_' . $status->status_slug] = array(
'url' => wp_nonce_url(admin_url('admin-ajax.php?action=bdfg_change_order_status&order_id=' . $order->get_id() . '&status=' . $status->status_slug), 'bdfg-change-order-status'),
'name' => $status->status_name,
'action' => 'bdfg_status_' . $status->status_slug,
);
}
}

return $actions;
}

/**
* Check for orders with expired statuses and update them
*/
public function check_order_status_expiration() {
global $wpdb;

// Get statuses with expiration
$statuses = $wpdb->get_results("
SELECT * FROM {$wpdb->prefix}bdfg_order_statuses
WHERE expiry_hours IS NOT NULL
AND expiry_hours > 0
AND next_status IS NOT NULL
");

if (empty($statuses)) {
return;
}

foreach ($statuses as $status) {
// Get orders with this status
$args = array(
'status' => $status->status_slug,
'limit' => -1,
'return' => 'ids',
);

$orders = wc_get_orders($args);

if (empty($orders)) {
continue;
}

foreach ($orders as $order_id) {
$order = wc_get_order($order_id);
if (!$order) continue;

// Get order status date
$status_date = $order->get_date_modified();
if (!$status_date) {
$status_date = $order->get_date_created();
}

// Calculate expiry time
$expiry_time = strtotime('+' . $status->expiry_hours . ' hours', $status_date->getTimestamp());

// Check if expired
if (time() >= $expiry_time) {
// Get next status name for logging purposes
$next_status_name = $wpdb->get_var($wpdb->prepare(
"SELECT status_name FROM {$wpdb->prefix}bdfg_order_statuses WHERE status_slug = %s",
$status->next_status
));

if (empty($next_status_name)) {
$next_status_name = $status->next_status;
}

// Update to next status
$order->update_status(
$status->next_status,
sprintf(
__('BDFG Status Auto-Update: Status automatically changed from "%1$s" to "%2$s" after %3$s hours', 'bdfg-custom-order-status'),
$status->status_name,
$next_status_name,
$status->expiry_hours
)
);

// Add additional logged information
$order->add_order_note(
sprintf(
__('BDFG Status System: Automatic status change processed. Previous status: %1$s, New status: %2$s, Trigger: %3$d hour expiration reached. Timestamp: %4$s', 'bdfg-custom-order-status'),
$status->status_name,
$next_status_name,
$status->expiry_hours,
current_time('mysql')
)
);

// Allow other systems to hook into this automatic status change
do_action('bdfg_status_auto_changed', $order, $status->status_slug, $status->next_status);
}
}
}
}

/**
* Add CSS for custom order status icons and colors
*/
public function add_custom_order_status_column_style() {
global $wpdb, $current_screen;

// Only on order list page
if (!$current_screen || $current_screen->id != 'edit-shop_order') {
return;
}

// Get all custom statuses
$statuses = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}bdfg_order_statuses WHERE color IS NOT NULL");

if (empty($statuses)) {
return;
}

echo '<style type="text/css">';

foreach ($statuses as $status) {
if (!empty($status->color)) {
echo '.order-status.status-' . esc_attr($status->status_slug) . ' {';
echo 'background-color: ' . esc_attr($status->color) . ' !important;';
echo 'color: ' . esc_attr($status->text_color ? $status->text_color : $this->get_contrast_color($status->color)) . ' !important;';
echo '}';
}

if (!empty($status->icon)) {
echo '.widefat .column-order_status mark.bdfg_status_' . esc_attr($status->status_slug) . '::after,';
echo '.widefat .column-order_status mark.status-' . esc_attr($status->status_slug) . '::after {';
echo 'font-family: "dashicons";';
echo 'content: "\\' . esc_attr($status->icon) . '";';
echo 'margin-left: 5px;';
echo '}';
}

// Add styling for actions
echo '.button.bdfg_status_' . esc_attr($status->status_slug) . '::after {';
if (!empty($status->icon)) {
echo 'font-family: "dashicons";';
echo 'content: "\\' . esc_attr($status->icon) . '";';
}
if (!empty($status->color)) {
echo 'background-color: ' . esc_attr($status->color) . ';';
echo 'color: ' . esc_attr($status->text_color ? $status->text_color : $this->get_contrast_color($status->color)) . ';';
}
echo '}';
}

// Add BDFG branding to status column
echo '.bdfg-status-badge {';
echo ' display: inline-block;';
echo ' padding: 3px 8px;';
echo ' border-radius: 12px;';
echo ' font-size: 12px;';
echo ' line-height: 1.4;';
echo ' font-weight: 600;';
echo ' margin: 3px 0;';
echo ' position: relative;';
echo '}';

echo '.bdfg-status-badge::before {';
echo ' content: "BDFG";';
echo ' position: absolute;';
echo ' top: -8px;';
echo ' left: 0;';
echo ' font-size: 8px;';
echo ' background: #333;';
echo ' color: white;';
echo ' padding: 0 4px;';
echo ' border-radius: 4px;';
echo ' opacity: 0.7;';
echo '}';

echo '</style>';
}

/**
* AJAX handler to get order status
*/
public function ajax_get_order_status() {
$order_id = isset($_POST['order_id']) ? intval($_POST['order_id']) : 0;
$order_email = isset($_POST['order_email']) ? sanitize_email($_POST['order_email']) : '';

// Security checks
if (!$order_id || !$order_email || !wp_verify_nonce($_POST['nonce'], 'bdfg-status-check')) {
wp_send_json_error(['message' => __('Invalid request', 'bdfg-custom-order-status')]);
exit;
}

$order = wc_get_order($order_id);

// Check if order exists and email matches
if (!$order || $order->get_billing_email() !== $order_email) {
wp_send_json_error(['message' => __('Order not found or email does not match', 'bdfg-custom-order-status')]);
exit;
}

// Get status details
$status_slug = $order->get_status();
$status_details = $this->get_status_details($status_slug);

// Build response
$response = [
'order_id' => $order_id,
'status_slug' => $status_slug,
'status_name' => wc_get_order_status_name($order->get_status()),
'status_color' => $status_details ? $status_details->color : '',
'status_text_color' => $status_details ? ($status_details->text_color ? $status_details->text_color : $this->get_contrast_color($status_details->color)) : '',
'status_icon' => $status_details ? $status_details->icon : '',
'date_modified' => $order->get_date_modified() ? $order->get_date_modified()->date_i18n(get_option('date_format') . ' ' . get_option('time_format')) : '',
];

wp_send_json_success($response);
exit;
}

/**
* Get status details from database
*
* @param string $status_slug Status slug without wc- prefix
* @return object|null Status details or null if not found
*/
public function get_status_details($status_slug) {
global $wpdb;

// Remove wc- prefix if present
$status_slug = str_replace('wc-', '', $status_slug);

return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}bdfg_order_statuses WHERE status_slug = %s",
$status_slug
));
}
/**
* Get contrasting color (black or white) for a given background color
*
* @param string $hexcolor Background color in hex format
* @return string Text color (black or white)
*/
private function get_contrast_color($hexcolor) {
// Remove # if present
$hexcolor = ltrim($hexcolor, '#');

// Ensure valid hex color
if (strlen($hexcolor) != 6) {
return '#ffffff';
}

// Convert to RGB
$r = hexdec(substr($hexcolor, 0, 2));
$g = hexdec(substr($hexcolor, 2, 2));
$b = hexdec(substr($hexcolor, 4, 2));

// Calculate luminance - human eye favors green
$yiq = (($r * 299) + ($g * 587) + ($b * 114)) / 1000;

return ($yiq >= 128) ? '#000000' : '#ffffff';
}

/**
* Order status shortcode function
*
* @param array $atts Shortcode attributes
* @return string HTML output
*/
public function order_status_shortcode($atts) {
$atts = shortcode_atts(array(
'order_id' => '',
'show_name' => 'yes',
'show_date' => 'yes',
'show_icon' => 'yes',
), $atts);

// Check for order ID
if (empty($atts['order_id'])) {
return '<div class="bdfg-status-error">' . __('No order ID provided', 'bdfg-custom-order-status') . '</div>';
}

$order = wc_get_order($atts['order_id']);
if (!$order) {
return '<div class="bdfg-status-error">' . __('Order not found', 'bdfg-custom-order-status') . '</div>';
}

$status_slug = $order->get_status();
$status_details = $this->get_status_details($status_slug);

$output = '<div class="bdfg-status-container">';

// Build status badge
$badge_style = '';
$icon = '';

if ($status_details) {
$text_color = !empty($status_details->text_color) ? $status_details->text_color : $this->get_contrast_color($status_details->color);
$badge_style = 'background-color:' . esc_attr($status_details->color) . ';color:' . esc_attr($text_color) . ';';

if ($atts['show_icon'] === 'yes' && !empty($status_details->icon)) {
$icon = '<span class="bdfg-status-icon dashicons dashicons-' . esc_attr($status_details->icon) . '"></span>';
}
}

$output .= '<span class="bdfg-status-badge" style="' . $badge_style . '">';
$output .= $icon;

if ($atts['show_name'] === 'yes') {
$output .= '<span class="bdfg-status-name">' . wc_get_order_status_name($status_slug) . '</span>';
}

$output .= '</span>';

// Add date if enabled
if ($atts['show_date'] === 'yes' && $order->get_date_modified()) {
$output .= '<div class="bdfg-status-date">';
$output .= __('Last updated: ', 'bdfg-custom-order-status') . $order->get_date_modified()->date_i18n(get_option('date_format') . ' ' . get_option('time_format'));
$output .= '</div>';
}

$output .= '</div>';

return $output;
}

/**
* Add status column to My Account orders table
*
* @param array $columns Existing columns
* @return array Modified columns
*/
public function add_status_column_my_account($columns) {
// The status column already exists, so we don't need to add it
return $columns;
}

/**
* Add stylized status to My Account orders table
*
* @param WC_Order $order Order object
*/
public function add_status_column_content_my_account($order) {
$status_slug = $order->get_status();
$status_details = $this->get_status_details($status_slug);

if ($status_details) {
$text_color = !empty($status_details->text_color) ? $status_details->text_color : $this->get_contrast_color($status_details->color);
$style = 'background-color:' . esc_attr($status_details->color) . ';color:' . esc_attr($text_color) . ';';
$icon = !empty($status_details->icon) ? '<span class="dashicons dashicons-' . esc_attr($status_details->icon) . '"></span> ' : '';

echo '<span class="bdfg-status-badge" style="' . $style . '">' . $icon . wc_get_order_status_name($status_slug) . '</span>';
} else {
echo wc_get_order_status_name($status_slug);
}
}

/**
* Add status badge to order details
*
* @param WC_Order $order Order object
*/
public function add_status_badge_to_order_details($order) {
$status_slug = $order->get_status();
$status_details = $this->get_status_details($status_slug);

if ($status_details) {
$text_color = !empty($status_details->text_color) ? $status_details->text_color : $this->get_contrast_color($status_details->color);
$style = 'background-color:' . esc_attr($status_details->color) . ';color:' . esc_attr($text_color) . ';';
$icon = !empty($status_details->icon) ? '<span class="dashicons dashicons-' . esc_attr($status_details->icon) . '"></span> ' : '';

echo '<div class="bdfg-order-status-badge-container">';
echo '<p>' . __('Order Status:', 'bdfg-custom-order-status') . ' ';
echo '<span class="bdfg-status-badge" style="' . $style . '">' . $icon . wc_get_order_status_name($status_slug) . '</span>';
echo '</p>';
echo '</div>';
}
}
}

includes/class-bdfg-custom-order-status-admin.php


<?php
/**
* Admin interface for managing custom order statuses
*
* @package BDFG_Custom_Order_Status
* @author Beiduofengou
* @copyright 2023-2025
* @since 1.0.0
* @last_modified 2025-03-06 13:45:48
* @modified_by Beiduofengou
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}

/**
* Admin class for managing custom order statuses
*/
class BDFG_Custom_Order_Status_Admin {
/**
* Constructor
*/
public function __construct() {
// Add admin menu
add_action('admin_menu', array($this, 'add_admin_menu'));

// Register settings
add_action('admin_init', array($this, 'register_settings'));

// Admin scripts and styles
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));

// AJAX handlers
add_action('wp_ajax_bdfg_save_status', array($this, 'ajax_save_status'));
add_action('wp_ajax_bdfg_delete_status', array($this, 'ajax_delete_status'));
add_action('wp_ajax_bdfg_save_group', array($this, 'ajax_save_group'));
add_action('wp_ajax_bdfg_delete_group', array($this, 'ajax_delete_group'));
add_action('wp_ajax_bdfg_change_order_status', array($this, 'ajax_change_order_status'));
add_action('wp_ajax_bdfg_update_status_order', array($this, 'ajax_update_status_order'));
add_action('wp_ajax_bdfg_update_group_order', array($this, 'ajax_update_group_order'));

// Custom bulk actions
add_action('admin_footer-edit.php', array($this, 'add_custom_bulk_actions'));
add_action('load-edit.php', array($this, 'process_custom_bulk_actions'));

// Custom order status column
add_filter('manage_edit-shop_order_columns', array($this, 'add_order_status_column'), 20);
add_action('manage_shop_order_posts_custom_column', array($this, 'add_order_status_column_content'), 20, 2);

// Add help tabs
add_action('admin_head', array($this, 'add_help_tabs'));

// Add metabox to order page
add_action('add_meta_boxes', array($this, 'add_order_status_meta_box'), 10);

// Add notice about BDFG service
add_action('admin_notices', array($this, 'show_bdfg_service_notice'));
}

/**
* Add admin menu pages
*/
public function add_admin_menu() {
$menu_icon = 'data:image/svg+xml;base64,' . base64_encode('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="black" d="M17.3 3H2.7C1.5 3 0.5 4 0.5 5.2v9.5c0 1.2 1 2.2 2.2 2.2h14.7c1.2 0 2.2-1 2.2-2.2V5.2C19.5 4 18.6 3 17.3 3zM6.7 13.8H3.5V6.2h3.2V13.8zM11.9 16H8.1c-0.4 0-0.8-0.3-0.8-0.8s0.3-0.8 0.8-0.8h3.8c0.4 0 0.8 0.3 0.8 0.8S12.3 16 11.9 16zM16.5 13.8h-8V6.2h8V13.8z"/></svg>');

add_menu_page(
__('BDFG Order Status', 'bdfg-custom-order-status'),
__('BDFG Status', 'bdfg-custom-order-status'),
'manage_bdfg_order_statuses',
'bdfg-custom-order-status',
array($this, 'render_main_page'),
$menu_icon,
56
);

add_submenu_page(
'bdfg-custom-order-status',
__('All Statuses', 'bdfg-custom-order-status'),
__('All Statuses', 'bdfg-custom-order-status'),
'manage_bdfg_order_statuses',
'bdfg-custom-order-status',
array($this, 'render_main_page')
);

add_submenu_page(
'bdfg-custom-order-status',
__('Add New Status', 'bdfg-custom-order-status'),
__('Add New Status', 'bdfg-custom-order-status'),
'manage_bdfg_order_statuses',
'bdfg-add-order-status',
array($this, 'render_add_status_page')
);

add_submenu_page(
'bdfg-custom-order-status',
__('Status Groups', 'bdfg-custom-order-status'),
__('Status Groups', 'bdfg-custom-order-status'),
'manage_bdfg_order_statuses',
'bdfg-status-groups',
array($this, 'render_groups_page')
);

add_submenu_page(
'bdfg-custom-order-status',
__('Settings', 'bdfg-custom-order-status'),
__('Settings', 'bdfg-custom-order-status'),
'manage_bdfg_order_statuses',
'bdfg-status-settings',
array($this, 'render_settings_page')
);

add_submenu_page(
'bdfg-custom-order-status',
__('Status Reports', 'bdfg-custom-order-status'),
__('Reports', 'bdfg-custom-order-status'),
'view_bdfg_order_status_reports',
'bdfg-status-reports',
array($this, 'render_reports_page')
);
}

/**
* Register plugin settings
*/
public function register_settings() {
register_setting('bdfg_custom_order_status_settings', 'bdfg_cos_enable_email_notifications');
register_setting('bdfg_custom_order_status_settings', 'bdfg_cos_bulk_actions_enabled');
register_setting('bdfg_custom_order_status_settings', 'bdfg_cos_status_column_enabled');
register_setting('bdfg_custom_order_status_settings', 'bdfg_cos_frontend_styling');
register_setting('bdfg_custom_order_status_settings', 'bdfg_cos_remove_data_on_uninstall');
register_setting('bdfg_custom_order_status_settings', 'bdfg_cos_enable_status_tracking');
}

/**
* Enqueue admin scripts and styles
*
* @param string $hook Current admin page
*/
public function enqueue_admin_scripts($hook) {
// Only on our plugin pages or edit order page
$plugin_pages = array(
'toplevel_page_bdfg-custom-order-status',
'bdfg-status_page_bdfg-add-order-status',
'bdfg-status_page_bdfg-status-groups',
'bdfg-status_page_bdfg-status-settings',
'bdfg-status_page_bdfg-status-reports',
'post.php',
);

if (!in_array($hook, $plugin_pages) && strpos($hook, 'bdfg-') === false) {
return;
}

// Enqueue color picker
wp_enqueue_style('wp-color-picker');
wp_enqueue_script('wp-color-picker');

// Enqueue jQuery UI for sortable elements
wp_enqueue_script('jquery-ui-sortable');
wp_enqueue_script('jquery-ui-datepicker');
wp_enqueue_style('jquery-ui-datepicker-css', '//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css');

// Dashicons for status icons
wp_enqueue_style('dashicons');

// Enqueue Chart.js for reports
if (strpos($hook, 'bdfg-status-reports') !== false) {
wp_enqueue_script('chart-js', BDFG_COS_PLUGIN_URL . 'assets/js/chart.min.js', array(), '3.7.0', true);
}

// Plugin specific styles
wp_enqueue_style(
'bdfg-custom-order-status-admin',
BDFG_COS_PLUGIN_URL . 'assets/css/admin.css',
array(),
BDFG_COS_VERSION
);

// Plugin specific scripts
wp_enqueue_script(
'bdfg-custom-order-status-admin',
BDFG_COS_PLUGIN_URL . 'assets/js/admin.js',
array('jquery', 'wp-color-picker', 'jquery-ui-sortable'),
BDFG_COS_VERSION,
true
);

// Localize script with data and translations
wp_localize_script(
'bdfg-custom-order-status-admin',
'bdfg_cos_vars',
array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bdfg-cos-admin-nonce'),
'confirm_delete' => __('Are you sure you want to delete this item? This action cannot be undone.', 'bdfg-custom-order-status'),
'saving' => __('Saving...', 'bdfg-custom-order-status'),
'saved' => __('Saved!', 'bdfg-custom-order-status'),
'error' => __('Error saving data. Please try again.', 'bdfg-custom-order-status'),
'slug_exists' => __('This slug already exists. Please choose another one.', 'bdfg-custom-order-status'),
'select_group' => __('Select a group', 'bdfg-custom-order-status'),
'select_status' => __('Select a status', 'bdfg-custom-order-status'),
'plugin_url' => BDFG_COS_PLUGIN_URL,
'version' => BDFG_COS_VERSION,
'last_update' => '2025-03-06'
)
);
}

/**
* Render main admin page
*/
public function render_main_page() {
global $wpdb;

// Get all statuses
$statuses = $wpdb->get_results("
SELECT s.*, g.group_name
FROM {$wpdb->prefix}bdfg_order_statuses s
LEFT JOIN {$wpdb->prefix}bdfg_order_status_groups g ON s.group_id = g.id
ORDER BY s.display_order ASC
");

// Count orders for each status
$orders_count = array();
$wc_statuses = wc_get_order_statuses();

foreach ($statuses as $status) {
$key = 'wc-' . $status->status_slug;
if (isset($wc_statuses[$key])) {
$count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*)
FROM {$wpdb->posts}
WHERE post_type = 'shop_order'
AND post_status = %s
", $key));

$orders_count[$status->status_slug] = $count;
} else {
$orders_count[$status->status_slug] = 0;
}
}

// Render the page
include_once BDFG_COS_PLUGIN_DIR . 'includes/admin/views/html-admin-statuses.php';
}

/**
* Render add/edit status page
*/
public function render_add_status_page() {
global $wpdb;

// Check if editing
$status_id = isset($_GET['edit']) ? intval($_GET['edit']) : 0;
$status = null;

if ($status_id) {
$status = $wpdb->get_row($wpdb->prepare("
SELECT * FROM {$wpdb->prefix}bdfg_order_statuses WHERE id = %d
", $status_id));

if (!$status) {
wp_die(__('Status not found', 'bdfg-custom-order-status'));
}
}

// Get all statuses for next status dropdown
$all_statuses = $wpdb->get_results("
SELECT id, status_name, status_slug
FROM {$wpdb->prefix}bdfg_order_statuses
ORDER BY display_order ASC
");

// Get WooCommerce default statuses
$wc_default_statuses = wc_get_order_statuses();

// Get all groups for group dropdown
$groups = $wpdb->get_results("
SELECT id, group_name
FROM {$wpdb->prefix}bdfg_order_status_groups
ORDER BY display_order ASC
");

// Get dashicon options
$dashicons = $this->get_dashicons_list();

// Render the page
include_once BDFG_COS_PLUGIN_DIR . 'includes/admin/views/html-admin-add-status.php';
}

/**
* Render groups management page
*/
public function render_groups_page() {
global $wpdb;

// Check if editing
$group_id = isset($_GET['edit']) ? intval($_GET['edit']) : 0;
$group = null;

if ($group_id) {
$group = $wpdb->get_row($wpdb->prepare("
SELECT * FROM {$wpdb->prefix}bdfg_order_status_groups WHERE id = %d
", $group_id));

if (!$group) {
wp_die(__('Group not found', 'bdfg-custom-order-status'));
}
}

// Get all groups
$groups = $wpdb->get_results("
SELECT g.*, COUNT(s.id) as status_count
FROM {$wpdb->prefix}bdfg_order_status_groups g
LEFT JOIN {$wpdb->prefix}bdfg_order_statuses s ON g.id = s.group_id
GROUP BY g.id
ORDER BY g.display_order ASC
");

// Render the page
include_once BDFG_COS_PLUGIN_DIR . 'includes/admin/views/html-admin-groups.php';
}

/**
* Render settings page
*/
public function render_settings_page() {
$enable_email_notifications = get_option('bdfg_cos_enable_email_notifications', 'yes');
$bulk_actions_enabled = get_option('bdfg_cos_bulk_actions_enabled', 'yes');
$status_column_enabled = get_option('bdfg_cos_status_column_enabled', 'yes');
$frontend_styling = get_option('bdfg_cos_frontend_styling', 'yes');
$remove_data_on_uninstall = get_option('bdfg_cos_remove_data_on_uninstall', 'no');
$enable_status_tracking = get_option('bdfg_cos_enable_status_tracking', 'yes');

// Render the page
include_once BDFG_COS_PLUGIN_DIR . 'includes/admin/views/html-admin-settings.php';
}

/**
* Render reports page
*
* @since 1.2.0
*/
public function render_reports_page() {
global $wpdb;

// Get date range
$start_date = isset($_GET['start_date']) ? sanitize_text_field($_GET['start_date']) : date('Y-m-d', strtotime('-30 days'));
$end_date = isset($_GET['end_date']) ? sanitize_text_field($_GET['end_date']) : date('Y-m-d');

// Get report type
$report_type = isset($_GET['report_type']) ? sanitize_text_field($_GET['report_type']) : 'status_distribution';

// Get all statuses
$statuses = $wpdb->get_results("
SELECT s.*, g.group_name
FROM {$wpdb->prefix}bdfg_order_statuses s
LEFT JOIN {$wpdb->prefix}bdfg_order_status_groups g ON s.group_id = g.id
ORDER BY s.display_order ASC
");

// Add WooCommerce default statuses
$wc_statuses = wc_get_order_statuses();

// Generate report data
$report_data = array();

switch ($report_type) {
case 'status_distribution':
// Get order count per status
foreach ($statuses as $status) {
$key = 'wc-' . $status->status_slug;
if (isset($wc_statuses[$key])) {
$count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*)
FROM {$wpdb->posts}
WHERE post_type = 'shop_order'
AND post_status = %s
AND post_date BETWEEN %s AND %s
", $key, $start_date . ' 00:00:00', $end_date . ' 23:59:59'));

$report_data[] = array(
'label' => $status->status_name,
'value' => (int) $count,
'color' => $status->color ?: '#dddddd'
);
}
}

// Add counts for default WooCommerce statuses
foreach ($wc_statuses as $status_key => $status_name) {
// Skip custom statuses
$custom_status = false;
foreach ($statuses as $status) {
if ('wc-' . $status->status_slug === $status_key) {
$custom_status = true;
break;
}
}

if (!$custom_status) {
$count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*)
FROM {$wpdb->posts}
WHERE post_type = 'shop_order'
AND post_status = %s
AND post_date BETWEEN %s AND %s
", $status_key, $start_date . ' 00:00:00', $end_date . ' 23:59:59'));

$report_data[] = array(
'label' => $status_name,
'value' => (int) $count,
'color' => $this->get_default_status_color($status_key)
);
}
}
break;

case 'status_timeline':
// Get date range
$dates = array();
$current_date = strtotime($start_date);
$end = strtotime($end_date);

while ($current_date <= $end) {
$dates[] = date('Y-m-d', $current_date);
$current_date = strtotime('+1 day', $current_date);
}

// Get order count per status per day
$status_data = array();

foreach ($statuses as $status) {
$daily_counts = array();
$key = 'wc-' . $status->status_slug;

if (isset($wc_statuses[$key])) {
foreach ($dates as $date) {
$count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*)
FROM {$wpdb->posts}
WHERE post_type = 'shop_order'
AND post_status = %s
AND post_date LIKE %s
", $key, $date . '%'));

$daily_counts[] = (int) $count;
}

$status_data[] = array(
'label' => $status->status_name,
'data' => $daily_counts,
'borderColor' => $status->color ?: '#dddddd',
'backgroundColor' => $this->hex_to_rgba($status->color ?: '#dddddd', 0.2),
'fill' => false,
'tension' => 0.1
);
}
}

$report_data = array(
'labels' => $dates,
'datasets' => $status_data
);
break;

case 'average_time_in_status':
// Get average time in each status
$avg_time_data = array();

foreach ($statuses as $status) {
// Query to get average time in this status before moving to next
// This is complex and would require detailed order status change logs
// Simplified version for demonstration
$avg_hours = rand(1, 48); // This is just a placeholder

$avg_time_data[] = array(
'label' => $status->status_name,
'value' => $avg_hours,
'color' => $status->color ?: '#dddddd'
);
}

$report_data = $avg_time_data;
break;
}

// Render the page
include_once BDFG_COS_PLUGIN_DIR . 'includes/admin/views/html-admin-reports.php';
}

/**
* AJAX handler for saving status
*/
public function ajax_save_status() {
check_ajax_referer('bdfg-cos-admin-nonce', 'nonce');

if (!current_user_can('manage_bdfg_order_statuses')) {
wp_send_json_error(array('message' => __('Insufficient permissions', 'bdfg-custom-order-status')));
return;
}

global $wpdb;

$status_id = isset($_POST['status_id']) ? intval($_POST['status_id']) : 0;
$status_name = isset($_POST['status_name']) ? sanitize_text_field($_POST['status_name']) : '';
$status_slug = isset($_POST['status_slug']) ? sanitize_key($_POST['status_slug']) : '';
$status_description = isset($_POST['status_description']) ? sanitize_textarea_field($_POST['status_description']) : '';
$group_id = isset($_POST['group_id']) && $_POST['group_id'] ? intval($_POST['group_id']) : null;
$icon = isset($_POST['icon']) ? sanitize_text_field($_POST['icon']) : '';
$color = isset($_POST['color']) ? sanitize_hex_color($_POST['color']) : '';
$text_color = isset($_POST['text_color']) ? sanitize_hex_color($_POST['text_color']) : '';
$expiry_hours = isset($_POST['expiry_hours']) && is_numeric($_POST['expiry_hours']) ? intval($_POST['expiry_hours']) : null;
$next_status = isset($_POST['next_status']) && !empty($_POST['next_status']) ? sanitize_key($_POST['next_status']) : null;
$notification_enabled = isset($_POST['notification_enabled']) && $_POST['notification_enabled'] == '1' ? 1 : 0;

// Validate required fields
if (empty($status_name) || empty($status_slug)) {
wp_send_json_error(array('message' => __('Status name and slug are required.', 'bdfg-custom-order-status')));
return;
}

// Check slug uniqueness (if new or changed)
if ($status_id === 0 || $wpdb->get_var($wpdb->prepare("SELECT status_slug FROM {$wpdb->prefix}bdfg_order_statuses WHERE id = %d", $status_id)) !== $status_slug) {
$existing = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->prefix}bdfg_order_statuses WHERE status_slug = %s AND id != %d", $status_slug, $status_id));

if ($existing > 0) {
wp_send_json_error(array('message' => __('Status slug must be unique.', 'bdfg-custom-order-status')));
return;
}

// Also check built-in WooCommerce status slugs
$wc_statuses = array('pending', 'processing', 'on-hold', 'completed', 'cancelled', 'refunded', 'failed');
if (in_array($status_slug, $wc_statuses)) {
wp_send_json_error(array('message' => __('This slug is already used by WooCommerce core. Please choose another one.', 'bdfg-custom-order-status')));
return;
}
}

// Prepare data
$data = array(
'status_name' => $status_name,
'status_slug' => $status_slug,
'status_description' => $status_description,
'group_id' => $group_id,
'icon' => $icon,
'color' => $color,
'text_color' => $text_color,
'expiry_hours' => $expiry_hours,
'next_status' => $next_status,
'notification_enabled' => $notification_enabled,
'updated_at' => current_time('mysql')
);

// Insert or update
if ($status_id === 0) {
// Get max display order
$max_order = $wpdb->get_var("SELECT MAX(display_order) FROM {$wpdb->prefix}bdfg_order_statuses");
$data['display_order'] = $max_order ? $max_order + 1 : 1;

$result = $wpdb->insert("{$wpdb->prefix}bdfg_order_statuses", $data);
$status_id = $wpdb->insert_id;

// Log creation
error_log(sprintf('BDFG Status created: %s (%s) by user %s at %s', $status_name, $status_slug, 'Beiduofengou', '2025-03-06 13:47:50'));
} else {
$result = $wpdb->update(
"{$wpdb->prefix}bdfg_order_statuses",
$data,
array('id' => $status_id)
);

// Log update
error_log(sprintf('BDFG Status updated: ID %d (%s) by user %s at %s', $status_id, $status_slug, 'Beiduofengou', '2025-03-06 13:47:50'));
}

if ($result === false) {
wp_send_json_error(array('message' => __('Error saving status. Please try again.', 'bdfg-custom-order-status')));
return;
}

// Flush rewrite rules for new status
flush_rewrite_rules();

wp_send_json_success(array(
'message' => __('Status saved successfully.', 'bdfg-custom-order-status'),
'status_id' => $status_id
));
}

/**
* AJAX handler for deleting status
*/
public function ajax_delete_status() {
check_ajax_referer('bdfg-cos-admin-nonce', 'nonce');

if (!current_user_can('manage_bdfg_order_statuses')) {
wp_send_json_error(array('message' => __('Insufficient permissions', 'bdfg-custom-order-status')));
return;
}

global $wpdb;

$status_id = isset($_POST['status_id']) ? intval($_POST['status_id']) : 0;

if ($status_id === 0) {
wp_send_json_error(array('message' => __('Invalid status ID.', 'bdfg-custom-order-status')));
return;
}

// Get status slug before deleting
$status = $wpdb->get_row($wpdb->prepare("SELECT status_name, status_slug FROM {$wpdb->prefix}bdfg_order_statuses WHERE id = %d", $status_id));

if (!$status) {
wp_send_json_error(array('message' => __('Status not found.', 'bdfg-custom-order-status')));
return;
}

// Check if orders exist with this status
$orders_count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*)
FROM {$wpdb->posts}
WHERE post_type = 'shop_order'
AND post_status = %s
", 'wc-' . $status->status_slug));

if ($orders_count > 0) {
wp_send_json_error(array('message' => sprintf(__('Cannot delete status because it is currently being used by %d orders.', 'bdfg-custom-order-status'), $orders_count)));
return;
}

// Delete status
$result = $wpdb->delete("{$wpdb->prefix}bdfg_order_statuses", array('id' => $status_id));

if ($result === false) {
wp_send_json_error(array('message' => __('Error deleting status. Please try again.', 'bdfg-custom-order-status')));
return;
}

// Log deletion
error_log(sprintf('BDFG Status deleted: %s (%s) by user %s at %s', $status->status_name, $status->status_slug, 'Beiduofengou', '2025-03-06 13:51:14'));

// Flush rewrite rules after status deletion
flush_rewrite_rules();

wp_send_json_success(array('message' => __('Status deleted successfully.', 'bdfg-custom-order-status')));
}

/**
* AJAX handler for saving group
*/
public function ajax_save_group() {
check_ajax_referer('bdfg-cos-admin-nonce', 'nonce');

if (!current_user_can('manage_bdfg_order_statuses')) {
wp_send_json_error(array('message' => __('Insufficient permissions', 'bdfg-custom-order-status')));
return;
}

global $wpdb;

$group_id = isset($_POST['group_id']) ? intval($_POST['group_id']) : 0;
$group_name = isset($_POST['group_name']) ? sanitize_text_field($_POST['group_name']) : '';
$group_slug = isset($_POST['group_slug']) ? sanitize_key($_POST['group_slug']) : '';
$group_description = isset($_POST['group_description']) ? sanitize_textarea_field($_POST['group_description']) : '';

// Validate required fields
if (empty($group_name) || empty($group_slug)) {
wp_send_json_error(array('message' => __('Group name and slug are required.', 'bdfg-custom-order-status')));
return;
}

// Check slug uniqueness (if new or changed)
if ($group_id === 0 || $wpdb->get_var($wpdb->prepare("SELECT group_slug FROM {$wpdb->prefix}bdfg_order_status_groups WHERE id = %d", $group_id)) !== $group_slug) {
$existing = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->prefix}bdfg_order_status_groups WHERE group_slug = %s AND id != %d", $group_slug, $group_id));

if ($existing > 0) {
wp_send_json_error(array('message' => __('Group slug must be unique.', 'bdfg-custom-order-status')));
return;
}
}

// Prepare data
$data = array(
'group_name' => $group_name,
'group_slug' => $group_slug,
'group_description' => $group_description,
'updated_at' => current_time('mysql')
);

// Insert or update
if ($group_id === 0) {
// Get max display order
$max_order = $wpdb->get_var("SELECT MAX(display_order) FROM {$wpdb->prefix}bdfg_order_status_groups");
$data['display_order'] = $max_order ? $max_order + 1 : 1;

$result = $wpdb->insert("{$wpdb->prefix}bdfg_order_status_groups", $data);
$group_id = $wpdb->insert_id;

// Log creation
error_log(sprintf('BDFG Group created: %s (%s) by user %s at %s', $group_name, $group_slug, 'Beiduofengou', '2025-03-06 13:51:14'));
} else {
$result = $wpdb->update(
"{$wpdb->prefix}bdfg_order_status_groups",
$data,
array('id' => $group_id)
);

// Log update
error_log(sprintf('BDFG Group updated: ID %d (%s) by user %s at %s', $group_id, $group_slug, 'Beiduofengou', '2025-03-06 13:51:14'));
}

if ($result === false) {
wp_send_json_error(array('message' => __('Error saving group. Please try again.', 'bdfg-custom-order-status')));
return;
}

wp_send_json_success(array(
'message' => __('Group saved successfully.', 'bdfg-custom-order-status'),
'group_id' => $group_id
));
}

/**
* AJAX handler for deleting group
*/
public function ajax_delete_group() {
check_ajax_referer('bdfg-cos-admin-nonce', 'nonce');

if (!current_user_can('manage_bdfg_order_statuses')) {
wp_send_json_error(array('message' => __('Insufficient permissions', 'bdfg-custom-order-status')));
return;
}

global $wpdb;

$group_id = isset($_POST['group_id']) ? intval($_POST['group_id']) : 0;

if ($group_id === 0) {
wp_send_json_error(array('message' => __('Invalid group ID.', 'bdfg-custom-order-status')));
return;
}

// Get group name before deleting
$group_name = $wpdb->get_var($wpdb->prepare("SELECT group_name FROM {$wpdb->prefix}bdfg_order_status_groups WHERE id = %d", $group_id));

// Check if statuses exist in this group
$statuses_count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*)
FROM {$wpdb->prefix}bdfg_order_statuses
WHERE group_id = %d
", $group_id));

if ($statuses_count > 0) {
wp_send_json_error(array('message' => sprintf(__('Cannot delete group because it contains %d statuses.', 'bdfg-custom-order-status'), $statuses_count)));
return;
}

// Delete group
$result = $wpdb->delete("{$wpdb->prefix}bdfg_order_status_groups", array('id' => $group_id));

if ($result === false) {
wp_send_json_error(array('message' => __('Error deleting group. Please try again.', 'bdfg-custom-order-status')));
return;
}

// Log deletion
error_log(sprintf('BDFG Group deleted: ID %d (%s) by user %s at %s', $group_id, $group_name, 'Beiduofengou', '2025-03-06 13:51:14'));

wp_send_json_success(array('message' => __('Group deleted successfully.', 'bdfg-custom-order-status')));
}

/**
* AJAX handler for changing order status
*/
public function ajax_change_order_status() {
check_ajax_referer('bdfg-change-order-status', '_wpnonce');

if (!current_user_can('edit_shop_orders')) {
wp_die(__('Insufficient permissions', 'bdfg-custom-order-status'));
}

$order_id = isset($_GET['order_id']) ? intval($_GET['order_id']) : 0;
$status = isset($_GET['status']) ? sanitize_key($_GET['status']) : '';

if ($order_id === 0 || empty($status)) {
wp_die(__('Invalid request.', 'bdfg-custom-order-status'));
}

$order = wc_get_order($order_id);

if (!$order) {
wp_die(__('Invalid order.', 'bdfg-custom-order-status'));
}

// Get old status for logging
$old_status = $order->get_status();

// Update order status
$order->update_status(
$status,
sprintf(__('Status manually changed by %s.', 'bdfg-custom-order-status'), wp_get_current_user()->display_name)
);

// Log change
error_log(sprintf('BDFG Order status changed: Order #%d from %s to %s by user %s at %s', $order_id, $old_status, $status, 'Beiduofengou', '2025-03-06 13:51:14'));

// Maybe send notification
if (get_option('bdfg_cos_enable_email_notifications', 'yes') === 'yes') {
global $wpdb;

$notification_enabled = $wpdb->get_var($wpdb->prepare("
SELECT notification_enabled
FROM {$wpdb->prefix}bdfg_order_statuses
WHERE status_slug = %s
", $status));

if ($notification_enabled) {
// Trigger notification email
do_action('bdfg_order_status_changed', $order, $old_status, $status);
}
}

// Redirect back to orders page
wp_safe_redirect(wp_get_referer() ? wp_get_referer() : admin_url('edit.php?post_type=shop_order'));
exit;
}

/**
* AJAX handler for updating status display order
*/
public function ajax_update_status_order() {
check_ajax_referer('bdfg-cos-admin-nonce', 'nonce');

if (!current_user_can('manage_bdfg_order_statuses')) {
wp_send_json_error(array('message' => __('Insufficient permissions', 'bdfg-custom-order-status')));
return;
}

global $wpdb;

$status_ids = isset($_POST['status_ids']) ? array_map('intval', $_POST['status_ids']) : array();

if (empty($status_ids)) {
wp_send_json_error(array('message' => __('No statuses to update.', 'bdfg-custom-order-status')));
return;
}

// Update display order for each status
$order = 1;
foreach ($status_ids as $status_id) {
$wpdb->update(
"{$wpdb->prefix}bdfg_order_statuses",
array(
'display_order' => $order,
'updated_at' => current_time('mysql')
),
array('id' => $status_id)
);
$order++;
}

// Log update
error_log(sprintf('BDFG Status order updated by user %s at %s', 'Beiduofengou', '2025-03-06 13:51:14'));

wp_send_json_success(array('message' => __('Status order updated.', 'bdfg-custom-order-status')));
}

/**
* AJAX handler for updating group display order
*
* @since 1.1.0
*/
public function ajax_update_group_order() {
check_ajax_referer('bdfg-cos-admin-nonce', 'nonce');

if (!current_user_can('manage_bdfg_order_statuses')) {
wp_send_json_error(array('message' => __('Insufficient permissions', 'bdfg-custom-order-status')));
return;
}

global $wpdb;

$group_ids = isset($_POST['group_ids']) ? array_map('intval', $_POST['group_ids']) : array();

if (empty($group_ids)) {
wp_send_json_error(array('message' => __('No groups to update.', 'bdfg-custom-order-status')));
return;
}

// Update display order for each group
$order = 1;
foreach ($group_ids as $group_id) {
$wpdb->update(
"{$wpdb->prefix}bdfg_order_status_groups",
array(
'display_order' => $order,
'updated_at' => current_time('mysql')
),
array('id' => $group_id)
);
$order++;
}

// Log update
error_log(sprintf('BDFG Group order updated by user %s at %s', 'Beiduofengou', '2025-03-06 13:51:14'));

wp_send_json_success(array('message' => __('Group order updated.', 'bdfg-custom-order-status')));
}

/**
* Add custom bulk actions to orders list
*/
public function add_custom_bulk_actions() {
global $post_type, $wpdb;

if ($post_type !== 'shop_order') {
return;
}

// Check if bulk actions are enabled
if (get_option('bdfg_cos_bulk_actions_enabled', 'yes') !== 'yes') {
return;
}

// Get all custom statuses
$statuses = $wpdb->get_results("
SELECT s.*, g.group_name
FROM {$wpdb->prefix}bdfg_order_statuses s
LEFT JOIN {$wpdb->prefix}bdfg_order_status_groups g ON s.group_id = g.id
ORDER BY g.display_order ASC, s.display_order ASC
");

if (empty($statuses)) {
return;
}

// Group statuses
$grouped_statuses = array();
foreach ($statuses as $status) {
$group_name = $status->group_name ? $status->group_name : __('Ungrouped', 'bdfg-custom-order-status');
if (!isset($grouped_statuses[$group_name])) {
$grouped_statuses[$group_name] = array();
}
$grouped_statuses[$group_name][] = $status;
}

// Add bulk actions
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
<?php foreach ($grouped_statuses as $group_name => $group_statuses) : ?>
var $optgroup = $('<optgroup label="<?php echo esc_attr($group_name); ?>"></optgroup>');

<?php foreach ($group_statuses as $status) : ?>
$optgroup.append('<option value="bdfg_status_<?php echo esc_attr($status->status_slug); ?>"><?php echo esc_html(__('Change status to', 'bdfg-custom-order-status') . ' ' . $status->status_name); ?></option>');
<?php endforeach; ?>

$('select[name="action"], select[name="action2"]').append($optgroup);
<?php endforeach; ?>
});
</script>
<?php
}

/**
* Process custom bulk actions
*/
public function process_custom_bulk_actions() {
global $typenow;

if ($typenow != 'shop_order') {
return;
}

$action = '';

if (isset($_REQUEST['action']) && $_REQUEST['action'] != -1) {
$action = $_REQUEST['action'];
} elseif (isset($_REQUEST['action2']) && $_REQUEST['action2'] != -1) {
$action = $_REQUEST['action2'];
}

if (strpos($action, 'bdfg_status_') === 0) {
$new_status = str_replace('bdfg_status_', '', $action);
$report_action = 'bdfg_bulk_status_' . $new_status;

$changed = 0;
$post_ids = array_map('absint', (array) $_REQUEST['post']);

foreach ($post_ids as $post_id) {
$order = wc_get_order($post_id);
if ($order) {
$old_status = $order->get_status();

$order->update_status(
$new_status,
sprintf(__('Status bulk changed by %s.', 'bdfg-custom-order-status'), wp_get_current_user()->display_name)
);

// Log change
error_log(sprintf(
'BDFG Bulk Status Change: Order #%d from %s to %s by user %s at %s',
$post_id,
$old_status,
$new_status,
'Beiduofengou',
'2025-03-06 13:55:30'
));

$changed++;
}
}

// Build the redirect URL
$sendback = add_query_arg(array(
'post_type' => 'shop_order',
$report_action => true,
'changed' => $changed,
'ids' => join(',', $post_ids)
), '');

// Redirect
wp_redirect($sendback);
exit();
}
}

/**
* Add custom order status column
*
* @param array $columns Current columns
* @return array Modified columns
*/
public function add_order_status_column($columns) {
// Check if custom status column is enabled
if (get_option('bdfg_cos_status_column_enabled', 'yes') !== 'yes') {
return $columns;
}

// Add custom column after order status
$new_columns = array();

foreach ($columns as $column_name => $column_info) {
$new_columns[$column_name] = $column_info;

if ($column_name === 'order_status') {
$new_columns['bdfg_order_status_details'] = __('BDFG Status Details', 'bdfg-custom-order-status');
}
}

return $new_columns;
}

/**
* Add content to custom order status column
*
* @param string $column Current column
* @param int $post_id Current post ID
*/
public function add_order_status_column_content($column, $post_id) {
if ($column !== 'bdfg_order_status_details') {
return;
}

$order = wc_get_order($post_id);
if (!$order) {
return;
}

$status = str_replace('wc-', '', $order->get_status());

global $wpdb;

// Get status details
$status_info = $wpdb->get_row($wpdb->prepare("
SELECT s.*, g.group_name
FROM {$wpdb->prefix}bdfg_order_statuses s
LEFT JOIN {$wpdb->prefix}bdfg_order_status_groups g ON s.group_id = g.id
WHERE s.status_slug = %s
", $status));

if (!$status_info) {
echo '<em>' . __('Standard WooCommerce status', 'bdfg-custom-order-status') . '</em>';
return;
}

echo '<div class="bdfg-status-details">';

// Show group if available
if ($status_info->group_name) {
echo '<span class="bdfg-status-group">' . esc_html($status_info->group_name) . '</span><br>';
}

// Show time in status if expiration is set
if ($status_info->expiry_hours) {
$status_date = $order->get_date_modified();
if (!$status_date) {
$status_date = $order->get_date_created();
}

$time_in_status = human_time_diff($status_date->getTimestamp(), time());
$expiry_time = $status_date->getTimestamp() + ($status_info->expiry_hours * HOUR_IN_SECONDS);

echo '<span class="bdfg-status-time">';
echo sprintf(__('Time in status: %s', 'bdfg-custom-order-status'), $time_in_status) . '<br>';

if (time() < $expiry_time) {
$time_left = human_time_diff(time(), $expiry_time);
echo sprintf(__('Auto-changes in: %s', 'bdfg-custom-order-status'), $time_left);

// Add progress bar
$percent_complete = ((time() - $status_date->getTimestamp()) / ($status_info->expiry_hours * HOUR_IN_SECONDS)) * 100;
echo '<div class="bdfg-progress-bar">';
echo '<div class="bdfg-progress" style="width:' . esc_attr($percent_complete) . '%;background-color:' . esc_attr($status_info->color) . '"></div>';
echo '</div>';
} else {
echo '<em>' . __('Status change pending...', 'bdfg-custom-order-status') . '</em>';
}

echo '</span>';
}

// Add BDFG branding
echo '<div class="bdfg-branding">';
echo '<small><a href="https://beiduofengou.net" target="_blank" title="' . esc_attr__('Powered by Beiduofengou', 'bdfg-custom-order-status') . '">';
echo 'BDFG';
echo '</a></small>';
echo '</div>';

echo '</div>';
}

/**
* Add order status meta box
*
* @param string $post_type Post type
* @since 1.1.0
*/
public function add_order_status_meta_box($post_type) {
if ($post_type !== 'shop_order') {
return;
}

add_meta_box(
'bdfg_order_status_meta_box',
__('BDFG Status History', 'bdfg-custom-order-status'),
array($this, 'render_order_status_meta_box'),
'shop_order',
'side',
'default'
);
}

/**
* Render order status meta box
*
* @param WP_Post $post Post object
* @since 1.1.0
*/
public function render_order_status_meta_box($post) {
$order = wc_get_order($post->ID);
if (!$order) {
return;
}

$current_status = $order->get_status();
$notes = wc_get_order_notes(array('order_id' => $post->ID));

$status_notes = array();
foreach ($notes as $note) {
if (strpos($note->content, 'Status changed from') !== false ||
strpos($note->content, 'BDFG Status') !== false) {
$status_notes[] = $note;
}
}

echo '<div class="bdfg-status-history">';

if (empty($status_notes)) {
echo '<p>' . __('No status history available.', 'bdfg-custom-order-status') . '</p>';
} else {
echo '<ul class="bdfg-status-timeline">';
foreach ($status_notes as $note) {
echo '<li>';
echo '<span class="bdfg-status-date">' . date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($note->date_created)) . '</span>';
echo '<span class="bdfg-status-note">' . wp_kses_post($note->content) . '</span>';
if ($note->added_by !== 'system') {
echo '<span class="bdfg-status-user">' . esc_html($note->added_by) . '</span>';
}
echo '</li>';
}
echo '</ul>';
}

// Show status actions
global $wpdb;

// Get all custom statuses
$statuses = $wpdb->get_results("
SELECT s.*, g.group_name
FROM {$wpdb->prefix}bdfg_order_statuses s
LEFT JOIN {$wpdb->prefix}bdfg_order_status_groups g ON s.group_id = g.id
ORDER BY g.display_order ASC, s.display_order ASC
");

if (!empty($statuses)) {
echo '<div class="bdfg-status-actions">';
echo '<label for="bdfg_status_change">' . __('Change Status:', 'bdfg-custom-order-status') . '</label>';
echo '<select id="bdfg_status_change">';
echo '<option value="">' . __('Select...', 'bdfg-custom-order-status') . '</option>';

$grouped_statuses = array();
foreach ($statuses as $status) {
if ($status->status_slug === $current_status) {
continue;
}

$group_name = $status->group_name ? $status->group_name : __('Ungrouped', 'bdfg-custom-order-status');
if (!isset($grouped_statuses[$group_name])) {
$grouped_statuses[$group_name] = array();
}
$grouped_statuses[$group_name][] = $status;
}

foreach ($grouped_statuses as $group_name => $group_statuses) {
echo '<optgroup label="' . esc_attr($group_name) . '">';
foreach ($group_statuses as $status) {
echo '<option value="' . esc_attr($status->status_slug) . '" data-color="' . esc_attr($status->color) . '">' . esc_html($status->status_name) . '</option>';
}
echo '</optgroup>';
}

echo '</select>';
echo '<button type="button" id="bdfg_change_status_btn" class="button">' . __('Update', 'bdfg-custom-order-status') . '</button>';
echo '<span class="spinner" id="bdfg_status_spinner"></span>';
echo '</div>';

// Add inline script
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
$('#bdfg_change_status_btn').on('click', function() {
var status = $('#bdfg_status_change').val();
if (!status) {
return;
}

$('#bdfg_status_spinner').addClass('is-active');

$.ajax({
url: ajaxurl,
data: {
action: 'bdfg_change_order_status',
order_id: <?php echo intval($post->ID); ?>,
status: status,
_wpnonce: '<?php echo wp_create_nonce('bdfg-change-order-status'); ?>'
},
success: function() {
window.location.reload();
}
});
});
});
</script>
<?php
}

echo '</div>';
}

/**
* Add help tabs
*
* @since 1.1.0
*/
public function add_help_tabs() {
$screen = get_current_screen();

// Only add to our plugin pages
if (!$screen || strpos($screen->id, 'bdfg-') === false) {
return;
}

// Main help tab
$screen->add_help_tab(array(
'id' => 'bdfg-help-overview',
'title' => __('Overview', 'bdfg-custom-order-status'),
'content' => '<h2>' . __('BDFG Custom Order Status', 'bdfg-custom-order-status') . '</h2>' .
'<p>' . __('This plugin allows you to create custom order statuses for WooCommerce with advanced features.', 'bdfg-custom-order-status') . '</p>' .
'<p>' . sprintf(__('For more information, visit <a href="%s" target="_blank">our documentation</a>.', 'bdfg-custom-order-status'), 'https://beiduofengou.net/docs/bdfg-custom-order-status/') . '</p>'
));

// Feature-specific help tabs
if (strpos($screen->id, 'bdfg-add-order-status') !== false) {
$screen->add_help_tab(array(
'id' => 'bdfg-help-statuses',
'title' => __('Adding Statuses', 'bdfg-custom-order-status'),
'content' => '<h3>' . __('How to Add Custom Statuses', 'bdfg-custom-order-status') . '</h3>' .
'<p>' . __('Fill in the required fields: Status Name and Status Slug.', 'bdfg-custom-order-status') . '</p>' .
'<p>' . __('Optionally, you can assign a group, icon, color, and expiration rules.', 'bdfg-custom-order-status') . '</p>' .
'<p>' . __('When setting expiration, specify the hours and the next status to transition to automatically.', 'bdfg-custom-order-status') . '</p>'
));
}

// Add sidebar
$screen->set_help_sidebar(
'<p><strong>' . __('For more information:', 'bdfg-custom-order-status') . '</strong></p>' .
'<p><a href="https://beiduofengou.net/support/" target="_blank">' . __('Support', 'bdfg-custom-order-status') . '</a></p>' .
'<p><a href="https://beiduofengou.net/docs/" target="_blank">' . __('Documentation', 'bdfg-custom-order-status') . '</a></p>'
);
}

/**
* Show BDFG service notice
*
* @since 1.2.0
*/
public function show_bdfg_service_notice() {
// Only show on our plugin pages
$screen = get_current_screen();
if (!$screen || strpos($screen->id, 'bdfg-') === false) {
return;
}

// Check if notice has been dismissed
$dismissed = get_user_meta(get_current_user_id(), 'bdfg_service_notice_dismissed', true);
if ($dismissed) {
return;
}

?>
<div class="notice notice-info is-dismissible bdfg-service-notice">
<h3><?php _e('Need help with your WooCommerce order workflow?', 'bdfg-custom-order-status'); ?></h3>
<p><?php _e('Beiduofengou offers professional services to optimize your WooCommerce order processing workflow:', 'bdfg-custom-order-status'); ?></p>
<ul style="list-style-type: disc; margin-left: 20px;">
<li><?php _e('Custom status flow setup', 'bdfg-custom-order-status'); ?></li>
<li><?php _e('Order automation configuration', 'bdfg-custom-order-status'); ?></li>
<li><?php _e('Notification templates customization', 'bdfg-custom-order-status'); ?></li>
<li><?php _e('Integration with shipping carriers', 'bdfg-custom-order-status'); ?></li>
</ul>
<p>
<a href="https://beiduofengou.net/services/woocommerce-workflow/" target="_blank" class="button button-primary"><?php _e('Learn More', 'bdfg-custom-order-status'); ?></a>
<a href="#" class="dismiss-notice button" data-nonce="<?php echo wp_create_nonce('bdfg_dismiss_service_notice'); ?>"><?php _e('Dismiss', 'bdfg-custom-order-status'); ?></a>
</p>
<script>
jQuery(document).ready(function($) {
$('.bdfg-service-notice .dismiss-notice').on('click', function(e) {
e.preventDefault();

$.post(ajaxurl, {
action: 'bdfg_dismiss_service_notice',
nonce: $(this).data('nonce')
});

$(this).closest('.bdfg-service-notice').fadeOut();
});
});
</script>
</div>
<?php
}

/**
* Get list of dashicons for status icons
*
* @return array List of dashicons
*/
private function get_dashicons_list() {
return array(
'f319' => 'admin-appearance',
'f226' => 'admin-comments',
'f104' => 'admin-home',
'f109' => 'admin-media',
'f101' => 'admin-page',
'f100' => 'admin-plugins',
'f106' => 'admin-settings',
'f110' => 'admin-site',
'f112' => 'admin-tools',
'f115' => 'admin-users',
'f531' => 'airplane',
'f173' => 'cart',
'f481' => 'clipboard',
'f113' => 'clock',
'f155' => 'dismiss',
'f346' => 'download',
'f316' => 'edit',
'f147' => 'email',
'f227' => 'format-status',
'f206' => 'heart',
'f348' => 'hidden',
'f128' => 'location',
'f472' => 'lock',
'f513' => 'money',
'f508' => 'paperclip',
'f483' => 'phone',
'f338' => 'printer',
'f171' => 'products',
'f169' => 'shield',
'f486' => 'store',
'f176' => 'tag',
'f177' => 'tickets',
'f319' => 'translation',
'f118' => 'trash',
'f463' => 'unlock',
'f162' => 'update',
'f323' => 'visibility',
'f466' => 'businessman',
'f468' => 'businesswoman'
);
}

/**
* Get default color for WooCommerce default statuses
*
* @param string $status_key Status key
* @return string Color hex code
*/
private function get_default_status_color($status_key) {
$colors = array(
'wc-pending' => '#ffba00',
'wc-processing' => '#73a724',
'wc-on-hold' => '#999999',
'wc-completed' => '#2ea2cc',
'wc-cancelled' => '#aa0000',
'wc-refunded' => '#999999',
'wc-failed' => '#d0c21f'
);

return isset($colors[$status_key]) ? $colors[$status_key] : '#dddddd';
}

/**
* Convert hex color to rgba
*
* @param string $hex Hex color code
* @param float $alpha Alpha transparency value
* @return string RGBA color string
*/
private function hex_to_rgba($hex, $alpha = 1) {
$hex = str_replace('#', '', $hex);

if (strlen($hex) == 3) {
$r = hexdec(substr($hex, 0, 1) . substr($hex, 0, 1));
$g = hexdec(substr($hex, 1, 1) . substr($hex, 1, 1));
$b = hexdec(substr($hex, 2, 1) . substr($hex, 2, 1));
} else {
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
}

return "rgba($r, $g, $b, $alpha)";
}
}

includes/class-bdfg-custom-order-status-email.php

相关文章: WooCommerce 产品比较工具


<?php
/**
* Email notification handling for custom order statuses
*
* @package BDFG_Custom_Order_Status
* @author Beiduofengou
* @copyright 2023-2025
* @since 1.0.0
* @last_modified 2025-03-06 13:56:40
* @modified_by Beiduofengou
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}

/**
* Email class for custom order status notifications
*/
class BDFG_Custom_Order_Status_Email {
/**
* Constructor
*/
public function __construct() {
// Add email template
add_filter('woocommerce_email_classes', array($this, 'add_email_class'));

// Register custom email actions
add_action('bdfg_order_status_changed', array($this, 'trigger_status_change_notification'), 10, 3);

// Load email templates
add_filter('woocommerce_template_directory', array($this, 'template_directory'), 10, 2);
}

/**
* Add email class to WooCommerce emails
*
* @param array $emails Existing email classes
* @return array Modified email classes
*/
public function add_email_class($emails) {
// Include the custom email class
require_once BDFG_COS_PLUGIN_DIR . 'includes/emails/class-bdfg-custom-order-status-email-notification.php';

// Add the email to WooCommerce emails
$emails['BDFG_Custom_Order_Status_Email_Notification'] = new BDFG_Custom_Order_Status_Email_Notification();

return $emails;
}

/**
* Trigger email notification when order status changes
*
* @param WC_Order $order Order object
* @param string $old_status Old status
* @param string $new_status New status
*/
public function trigger_status_change_notification($order, $old_status, $new_status) {
// Check if email notifications are enabled
if (get_option('bdfg_cos_enable_email_notifications', 'yes') !== 'yes') {
return;
}

// Get mailer instance
$mailer = WC()->mailer();

// Get the notification email class
$email = $mailer->emails['BDFG_Custom_Order_Status_Email_Notification'];

// Trigger the notification
$email->trigger($order->get_id(), $order, $new_status);
}

/**
* Set template directory for custom email templates
*
* @param string $directory Template directory
* @param string $template Template name
* @return string Modified directory
*/
public function template_directory($directory, $template) {
// Check if this is our custom email template
if (strpos($template, 'emails/bdfg-custom-order-status') !== false) {
return 'bdfg-custom-order-status';
}

return $directory;
}
}

includes/class-bdfg-custom-order-status-api.php


<?php
/**
* API functionality for custom order statuses
*
* @package BDFG_Custom_Order_Status
* @author Beiduofengou
* @copyright 2023-2025
* @since 1.2.0
* @last_modified 2025-03-06 13:56:40
* @modified_by Beiduofengou
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}

/**
* API class for custom order status
*/
class BDFG_Custom_Order_Status_API {
/**
* Constructor
*/
public function __construct() {
// Register REST API endpoints
add_action('rest_api_init', array($this, 'register_rest_routes'));

// Add custom order statuses to WooCommerce REST API
add_filter('woocommerce_rest_prepare_shop_order', array($this, 'add_status_details_to_api'), 10, 3);
}

/**
* Register custom REST API routes
*/
public function register_rest_routes() {
register_rest_route('bdfg-custom-order-status/v1', '/statuses', array(
'methods' => 'GET',
'callback' => array($this, 'get_statuses'),
'permission_callback' => array($this, 'check_api_permissions'),
));

register_rest_route('bdfg-custom-order-status/v1', '/statuses/(?P<id>\d+)', array(
'methods' => 'GET',
'callback' => array($this, 'get_status'),
'permission_callback' => array($this, 'check_api_permissions'),
'args' => array(
'id' => array(
'validate_callback' => function($param) {
return is_numeric($param);
}
),
),
));

register_rest_route('bdfg-custom-order-status/v1', '/groups', array(
'methods' => 'GET',
'callback' => array($this, 'get_groups'),
'permission_callback' => array($this, 'check_api_permissions'),
));

register_rest_route('bdfg-custom-order-status/v1', '/orders/(?P<id>\d+)/status', array(
'methods' => 'PUT',
'callback' => array($this, 'update_order_status'),
'permission_callback' => array($this, 'check_api_permissions'),
'args' => array(
'id' => array(
'validate_callback' => function($param) {
return is_numeric($param);
}
),
'status' => array(
'required' => true,
'type' => 'string',
),
'note' => array(
'type' => 'string',
),
),
));
}

/**
* Check API permissions
*
* @return bool Whether the user has permission
*/
public function check_api_permissions() {
return current_user_can('edit_shop_orders');
}

/**
* Get all custom order statuses
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response Response object
*/
public function get_statuses($request) {
global $wpdb;

$statuses = $wpdb->get_results("
SELECT s.*, g.group_name
FROM {$wpdb->prefix}bdfg_order_statuses s
LEFT JOIN {$wpdb->prefix}bdfg_order_status_groups g ON s.group_id = g.id
ORDER BY s.display_order ASC
");

return rest_ensure_response($statuses);
}

/**
* Get a single custom order status
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function get_status($request) {
global $wpdb;

$status_id = $request['id'];

$status = $wpdb->get_row($wpdb->prepare("
SELECT s.*, g.group_name
FROM {$wpdb->prefix}bdfg_order_statuses s
LEFT JOIN {$wpdb->prefix}bdfg_order_status_groups g ON s.group_id = g.id
WHERE s.id = %d
", $status_id));

if (!$status) {
return new WP_Error('status_not_found', __('Status not found', 'bdfg-custom-order-status'), array('status' => 404));
}

return rest_ensure_response($status);
}

/**
* Get all status groups
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response Response object
*/
public function get_groups($request) {
global $wpdb;

$groups = $wpdb->get_results("
SELECT g.*, COUNT(s.id) as status_count
FROM {$wpdb->prefix}bdfg_order_status_groups g
LEFT JOIN {$wpdb->prefix}bdfg_order_statuses s ON g.id = s.group_id
GROUP BY g.id
ORDER BY g.display_order ASC
");

return rest_ensure_response($groups);
}

/**
* Update order status
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function update_order_status($request) {
$order_id = $request['id'];
$new_status = $request['status'];
$note = isset($request['note']) ? sanitize_text_field($request['note']) : '';

$order = wc_get_order($order_id);
if (!$order) {
return new WP_Error('order_not_found', __('Order not found', 'bdfg-custom-order-status'), array('status' => 404));
}

// Save old status for response
$old_status = $order->get_status();

// Check if status exists
if ($new_status !== $old_status) {
global $wpdb;

// Check if it's a custom status
$status_exists = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*)
FROM {$wpdb->prefix}bdfg_order_statuses
WHERE status_slug = %s
", $new_status));

// If not a custom status, check if it's a core WooCommerce status
if (!$status_exists) {
$wc_statuses = array_keys(wc_get_order_statuses());
$found = false;

foreach ($wc_statuses as $status_key) {
if ('wc-' . $new_status === $status_key) {
$found = true;
break;
}
}

if (!$found) {
return new WP_Error('invalid_status', __('Invalid status', 'bdfg-custom-order-status'), array('status' => 400));
}
}
}

// Generate note if none provided
if (empty($note)) {
$note = sprintf(
__('Status changed via API from %s to %s by user %s', 'bdfg-custom-order-status'),
wc_get_order_status_name($old_status),
wc_get_order_status_name($new_status),
'Beiduofengou'
);
}

// Update the order status
$order->update_status($new_status, $note);

// Log the change
error_log(sprintf(
'BDFG API Status Change: Order #%d from %s to %s by user %s at %s',
$order_id,
$old_status,
$new_status,
'Beiduofengou',
'2025-03-06 14:01:07'
));

return rest_ensure_response(array(
'success' => true,
'order_id' => $order_id,
'old_status' => $old_status,
'new_status' => $new_status,
'timestamp' => current_time('mysql')
));
}

/**
* Add custom status details to WooCommerce REST API
*
* @param WP_REST_Response $response Response object
* @param WP_Post $post Post object
* @param WP_REST_Request $request Request object
* @return WP_REST_Response Modified response
*/
public function add_status_details_to_api($response, $post, $request) {
$order = wc_get_order($post->ID);
if (!$order) {
return $response;
}

$status = str_replace('wc-', '', $order->get_status());
$data = $response->get_data();

global $wpdb;

// Get status details
$status_info = $wpdb->get_row($wpdb->prepare("
SELECT s.*, g.group_name
FROM {$wpdb->prefix}bdfg_order_statuses s
LEFT JOIN {$wpdb->prefix}bdfg_order_status_groups g ON s.group_id = g.id
WHERE s.status_slug = %s
", $status));

if ($status_info) {
$data['bdfg_status_details'] = array(
'id' => $status_info->id,
'name' => $status_info->status_name,
'slug' => $status_info->status_slug,
'description' => $status_info->status_description,
'group' => $status_info->group_name,
'color' => $status_info->color,
'icon' => $status_info->icon,
'expiry_hours' => $status_info->expiry_hours,
'next_status' => $status_info->next_status
);

// Add expiration info if applicable
if ($status_info->expiry_hours) {
$status_date = $order->get_date_modified();
if (!$status_date) {
$status_date = $order->get_date_created();
}

$expiry_time = $status_date->getTimestamp() + ($status_info->expiry_hours * HOUR_IN_SECONDS);

$data['bdfg_status_details']['time_in_status'] = human_time_diff($status_date->getTimestamp(), time());
$data['bdfg_status_details']['expires_at'] = date('c', $expiry_time);
$data['bdfg_status_details']['expires_in'] = time() < $expiry_time ? human_time_diff(time(), $expiry_time) : 'expired';
}
}

$response->set_data($data);
return $response;
}
}

includes/emails/class-bdfg-custom-order-status-email-notification.php

相关文章: WooCommerce 高级产品定价和折扣管理


<?php
/**
* Custom Order Status Email Notification
*
* @package BDFG_Custom_Order_Status
* @author Beiduofengou
* @copyright 2023-2025
* @since 1.0.0
* @last_modified 2025-03-06 14:01:07
* @modified_by Beiduofengou
*/

if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}

/**
* Custom Order Status Email Notification class
*/
class BDFG_Custom_Order_Status_Email_Notification extends WC_Email {
/**
* Current order status
*
* @var string
*/
public $current_status;

/**
* Status name for display
*
* @var string
*/
public $status_name;

/**
* Status color
*
* @var string
*/
public $status_color;

/**
* Constructor
*/
public function __construct() {
$this->id = 'bdfg_custom_order_status_notification';
$this->title = __('BDFG Custom Order Status Notification', 'bdfg-custom-order-status');
$this->description = __('Email sent to customers when an order status changes to a custom status', 'bdfg-custom-order-status');
$this->template_base = BDFG_COS_PLUGIN_DIR . 'templates/';
$this->template_html = 'emails/bdfg-custom-order-status-email.php';
$this->template_plain = 'emails/plain/bdfg-custom-order-status-email.php';
$this->placeholders = array(
'{site_title}' => $this->get_blogname(),
'{order_number}' => '',
'{order_date}' => '',
'{status_name}' => '',
);

// Call parent constructor
parent::__construct();

// Default recipient to customer
$this->customer_email = true;
$this->recipient = $this->get_option('recipient', '');

// Other settings
$this->enabled = $this->get_option('enabled');
}

/**
* Get email subject
*
* @return string
*/
public function get_default_subject() {
return __('Your {site_title} order #{order_number} status has been updated to {status_name}', 'bdfg-custom-order-status');
}

/**
* Get email heading
*
* @return string
*/
public function get_default_heading() {
return __('Order Status Update: {status_name}', 'bdfg-custom-order-status');
}

/**
* Trigger the email
*
* @param int $order_id Order ID
* @param WC_Order $order Order object
* @param string $status_slug Status slug
*/
public function trigger($order_id, $order = false, $status_slug = '') {
$this->setup_locale();

if ($order_id && !is_a($order, 'WC_Order')) {
$order = wc_get_order($order_id);
}

if (is_a($order, 'WC_Order')) {
$this->object = $order;
$this->recipient = $order->get_billing_email();

// Get status details
global $wpdb;

$status_info = $wpdb->get_row($wpdb->prepare("
SELECT * FROM {$wpdb->prefix}bdfg_order_statuses WHERE status_slug = %s
", $status_slug));

if ($status_info) {
$this->current_status = $status_slug;
$this->status_name = $status_info->status_name;
$this->status_color = $status_info->color;

// Set placeholders
$this->placeholders['{order_number}'] = $order->get_order_number();
$this->placeholders['{order_date}'] = wc_format_datetime($order->get_date_created());
$this->placeholders['{status_name}'] = $this->status_name;

// Only send if notification is enabled for this status
if ($status_info->notification_enabled) {
// Send the email
$this->send($this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments());

// Log the email send
error_log(sprintf(
'BDFG Status Email Sent: Order #%d, Status: %s, To: %s at %s',
$order_id,
$status_slug,
$this->get_recipient(),
'2025-03-06 14:01:07'
));
}
}
}

$this->restore_locale();
}

/**
* Get content html
*
* @return string
*/
public function get_content_html() {
return wc_get_template_html(
$this->template_html,
array(
'order' => $this->object,
'email_heading' => $this->get_heading(),
'additional_content' => $this->get_additional_content(),
'sent_to_admin' => false,
'plain_text' => false,
'email' => $this,
'status_name' => $this->status_name,
'status_color' => $this->status_color,
'current_status' => $this->current_status,
),
'',
$this->template_base
);
}

/**
* Get content plain
*
* @return string
*/
public function get_content_plain() {
return wc_get_template_html(
$this->template_plain,
array(
'order' => $this->object,
'email_heading' => $this->get_heading(),
'additional_content' => $this->get_additional_content(),
'sent_to_admin' => false,
'plain_text' => true,
'email' => $this,
'status_name' => $this->status_name,
'current_status' => $this->current_status,
),
'',
$this->template_base
);
}

/**
* Initialize settings form fields
*/
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __('Enable/Disable', 'bdfg-custom-order-status'),
'type' => 'checkbox',
'label' => __('Enable this email notification', 'bdfg-custom-order-status'),
'default' => 'yes',
),
'recipient' => array(
'title' => __('Additional Recipients', 'bdfg-custom-order-status'),
'type' => 'text',
'description' => __('Enter additional recipients (comma separated) that will receive this notification. Customer will always receive this email.', 'bdfg-custom-order-status'),
'placeholder' => '',
'default' => '',
),
'subject' => array(
'title' => __('Subject', 'bdfg-custom-order-status'),
'type' => 'text',
'description' => sprintf(__('Available placeholders: %s', 'bdfg-custom-order-status'), '<code>{site_title}, {order_number}, {order_date}, {status_name}</code>'),
'placeholder' => $this->get_default_subject(),
'default' => '',
),
'heading' => array(
'title' => __('Email Heading', 'bdfg-custom-order-status'),
'type' => 'text',
'description' => sprintf(__('Available placeholders: %s', 'bdfg-custom-order-status'), '<code>{site_title}, {order_number}, {order_date}, {status_name}</code>'),
'placeholder' => $this->get_default_heading(),
'default' => '',
),
'additional_content' => array(
'title' => __('Additional Content', 'bdfg-custom-order-status'),
'description' => __('Text to appear below the main email content.', 'bdfg-custom-order-status'),
'css' => 'width:400px; height: 75px;',
'placeholder' => __('N/A', 'bdfg-custom-order-status'),
'type' => 'textarea',
'default' => $this->get_default_additional_content(),
),
'email_type' => array(
'title' => __('Email Type', 'bdfg-custom-order-status'),
'type' => 'select',
'description' => __('Choose which format of email to send.', 'bdfg-custom-order-status'),
'default' => 'html',
'class' => 'email_type wc-enhanced-select',
'options' => $this->get_email_type_options(),
),
);
}

/**
* Get default additional content
*
* @since 1.1.0
* @return string
*/
public function get_default_additional_content() {
return __('Thanks for shopping with us.', 'bdfg-custom-order-status');
}
}

includes/admin/views/html-admin-statuses.php


<?php
/**
* Admin View: Statuses list
*
* @package BDFG_Custom_Order_Status
* @version 1.2.3
* @author Beiduofengou
* @last_modified 2025-03-06 14:03:20
* @modified_by Beiduofengou
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
?>

<div class="wrap bdfg-cos-admin">
<h1 class="wp-heading-inline"><?php esc_html_e('Custom Order Statuses', 'bdfg-custom-order-status'); ?></h1>
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-add-order-status')); ?>" class="page-title-action"><?php esc_html_e('Add New Status', 'bdfg-custom-order-status'); ?></a>

<hr class="wp-header-end">

<?php if (empty($statuses)) : ?>
<div class="bdfg-empty-state">
<div class="bdfg-empty-icon">
<span class="dashicons dashicons-marker"></span>
</div>
<h2><?php esc_html_e('No custom order statuses found', 'bdfg-custom-order-status'); ?></h2>
<p><?php esc_html_e('Create your first custom order status to enhance your order workflow.', 'bdfg-custom-order-status'); ?></p>
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-add-order-status')); ?>" class="button button-primary"><?php esc_html_e('Add New Status', 'bdfg-custom-order-status'); ?></a>
</div>
<?php else : ?>
<div class="bdfg-statuses-wrapper">
<div class="bdfg-admin-header">
<div class="bdfg-admin-heading">
<h2><?php esc_html_e('Manage Custom Order Statuses', 'bdfg-custom-order-status'); ?></h2>
<p><?php esc_html_e('Drag and drop to reorder statuses. Click to edit or manage each status.', 'bdfg-custom-order-status'); ?></p>
</div>
<div class="bdfg-admin-actions">
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-status-groups')); ?>" class="button"><?php esc_html_e('Manage Groups', 'bdfg-custom-order-status'); ?></a>
</div>
</div>

<div id="bdfg-statuses-list" class="bdfg-sortable">
<?php foreach ($statuses as $status) : ?>
<div class="bdfg-status-item" data-id="<?php echo esc_attr($status->id); ?>">
<div class="bdfg-status-color" style="background-color: <?php echo esc_attr($status->color); ?>"></div>
<div class="bdfg-status-content">
<div class="bdfg-status-header">
<h3 class="bdfg-status-name"><?php echo esc_html($status->status_name); ?></h3>
<?php if (!empty($status->group_name)) : ?>
<span class="bdfg-status-group"><?php echo esc_html($status->group_name); ?></span>
<?php endif; ?>
<span class="bdfg-status-count"><?php echo isset($orders_count[$status->status_slug]) ? esc_html($orders_count[$status->status_slug]) : '0'; ?> <?php esc_html_e('orders', 'bdfg-custom-order-status'); ?></span>
</div>
<div class="bdfg-status-description">
<?php echo !empty($status->status_description) ? esc_html($status->status_description) : '<em>' . esc_html__('No description', 'bdfg-custom-order-status') . '</em>'; ?>
</div>
<div class="bdfg-status-meta">
<span class="bdfg-status-slug"><strong><?php esc_html_e('Slug:', 'bdfg-custom-order-status'); ?></strong> <?php echo esc_html($status->status_slug); ?></span>
<?php if (!empty($status->icon)) : ?>
<span class="bdfg-status-icon-preview"><strong><?php esc_html_e('Icon:', 'bdfg-custom-order-status'); ?></strong> <span class="dashicons dashicons-<?php echo esc_attr($status->icon); ?>"></span></span>
<?php endif; ?>
<?php if (!empty($status->expiry_hours)) : ?>
<span class="bdfg-status-expiry"><strong><?php esc_html_e('Auto-change after:', 'bdfg-custom-order-status'); ?></strong> <?php echo esc_html($status->expiry_hours); ?> <?php esc_html_e('hours', 'bdfg-custom-order-status'); ?></span>
<?php endif; ?>
<?php if (!empty($status->next_status)) : ?>
<span class="bdfg-status-next"><strong><?php esc_html_e('Next status:', 'bdfg-custom-order-status'); ?></strong> <?php echo esc_html($status->next_status); ?></span>
<?php endif; ?>
</div>
</div>
<div class="bdfg-status-actions">
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-add-order-status&edit=' . $status->id)); ?>" class="button button-small"><?php esc_html_e('Edit', 'bdfg-custom-order-status'); ?></a>
<button class="bdfg-delete-status button button-small" data-id="<?php echo esc_attr($status->id); ?>" data-name="<?php echo esc_attr($status->status_name); ?>"><?php esc_html_e('Delete', 'bdfg-custom-order-status'); ?></button>
</div>
<div class="bdfg-status-handle">
<span class="dashicons dashicons-menu"></span>
</div>
</div>
<?php endforeach; ?>
</div>

<div class="bdfg-admin-footer">
<p class="description"><?php esc_html_e('Last updated:', 'bdfg-custom-order-status'); ?> <?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'))); ?></p>
</div>
</div>
<?php endif; ?>
</div>

includes/admin/views/html-admin-add-status.php


<?php
/**
* Admin View: Add/Edit Status
*
* @package BDFG_Custom_Order_Status
* @version 1.2.3
* @author Beiduofengou
* @last_modified 2025-03-06 14:03:20
* @modified_by Beiduofengou
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}

$editing = isset($status) && $status;
?>

<div class="wrap bdfg-cos-admin">
<h1 class="wp-heading-inline"><?php echo $editing ? esc_html__('Edit Status', 'bdfg-custom-order-status') : esc_html__('Add New Status', 'bdfg-custom-order-status'); ?></h1>
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-custom-order-status')); ?>" class="page-title-action"><?php esc_html_e('Back to Statuses', 'bdfg-custom-order-status'); ?></a>

<hr class="wp-header-end">

<form id="bdfg-status-form" method="post">
<input type="hidden" id="status_id" name="status_id" value="<?php echo $editing ? esc_attr($status->id) : '0'; ?>" />

<div class="bdfg-form-wrapper">
<div class="bdfg-form-main">
<div class="bdfg-form-field">
<label for="status_name"><?php esc_html_e('Status Name', 'bdfg-custom-order-status'); ?> <span class="required">*</span></label>
<input type="text" id="status_name" name="status_name" value="<?php echo $editing ? esc_attr($status->status_name) : ''; ?>" required autocomplete="off" />
<p class="description"><?php esc_html_e('This is the name that will be displayed to customers and in the admin area.', 'bdfg-custom-order-status'); ?></p>
</div>

<div class="bdfg-form-field">
<label for="status_slug"><?php esc_html_e('Status Slug', 'bdfg-custom-order-status'); ?> <span class="required">*</span></label>
<input type="text" id="status_slug" name="status_slug" value="<?php echo $editing ? esc_attr($status->status_slug) : ''; ?>" required autocomplete="off" />
<p class="description"><?php esc_html_e('Unique identifier for this status. Use only lowercase letters, numbers, and hyphens.', 'bdfg-custom-order-status'); ?></p>
</div>

<div class="bdfg-form-field">
<label for="status_description"><?php esc_html_e('Description', 'bdfg-custom-order-status'); ?></label>
<textarea id="status_description" name="status_description" rows="4"><?php echo $editing ? esc_textarea($status->status_description) : ''; ?></textarea>
<p class="description"><?php esc_html_e('Optional description for internal reference.', 'bdfg-custom-order-status'); ?></p>
</div>
</div>

<div class="bdfg-form-sidebar">
<div class="bdfg-form-box">
<h3><?php esc_html_e('Status Settings', 'bdfg-custom-order-status'); ?></h3>

<div class="bdfg-form-field">
<label for="group_id"><?php esc_html_e('Group', 'bdfg-custom-order-status'); ?></label>
<select id="group_id" name="group_id">
<option value=""><?php esc_html_e('None', 'bdfg-custom-order-status'); ?></option>
<?php foreach ($groups as $group) : ?>
<option value="<?php echo esc_attr($group->id); ?>" <?php selected($editing && $status->group_id == $group->id); ?>><?php echo esc_html($group->group_name); ?></option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e('Optional group for this status.', 'bdfg-custom-order-status'); ?></p>
</div>

<div class="bdfg-form-field">
<label for="color"><?php esc_html_e('Color', 'bdfg-custom-order-status'); ?></label>
<input type="text" id="color" name="color" class="bdfg-color-picker" value="<?php echo $editing ? esc_attr($status->color) : '#73a724'; ?>" />
<p class="description"><?php esc_html_e('Background color for this status.', 'bdfg-custom-order-status'); ?></p>
</div>

<div class="bdfg-form-field">
<label for="text_color"><?php esc_html_e('Text Color', 'bdfg-custom-order-status'); ?></label>
<input type="text" id="text_color" name="text_color" class="bdfg-color-picker" value="<?php echo $editing && $status->text_color ? esc_attr($status->text_color) : '#ffffff'; ?>" />
<p class="description"><?php esc_html_e('Text color for this status.', 'bdfg-custom-order-status'); ?></p>
</div>

<div class="bdfg-form-field">
<label for="icon"><?php esc_html_e('Icon', 'bdfg-custom-order-status'); ?></label>
<select id="icon" name="icon" class="bdfg-icon-select">
<option value=""><?php esc_html_e('None', 'bdfg-custom-order-status'); ?></option>
<?php foreach ($dashicons as $code => $name) : ?>
<option value="<?php echo esc_attr($code); ?>" <?php selected($editing && $status->icon == $code); ?> data-icon="<?php echo esc_attr($code); ?>"><?php echo esc_html($name); ?></option>
<?php endforeach; ?>
</select>
<div id="icon_preview" class="bdfg-icon-preview">
<?php if ($editing && $status->icon) : ?>
<span class="dashicons dashicons-<?php echo esc_attr($status->icon); ?>"></span>
<?php endif; ?>
</div>
<p class="description"><?php esc_html_e('Optional icon for this status.', 'bdfg-custom-order-status'); ?></p>
</div>
</div>

<div class="bdfg-form-box">
<h3><?php esc_html_e('Automation Rules', 'bdfg-custom-order-status'); ?></h3>

<div class="bdfg-form-field">
<label for="expiry_hours"><?php esc_html_e('Auto-change after (hours)', 'bdfg-custom-order-status'); ?></label>
<input type="number" id="expiry_hours" name="expiry_hours" value="<?php echo $editing ? esc_attr($status->expiry_hours) : ''; ?>" min="0" step="0.5" />
<p class="description"><?php esc_html_e('Number of hours to wait before automatically changing to the next status. Leave empty for no auto-change.', 'bdfg-custom-order-status'); ?></p>
</div>

<div class="bdfg-form-field">
<label for="next_status"><?php esc_html_e('Next Status', 'bdfg-custom-order-status'); ?></label>
<select id="next_status" name="next_status">
<option value=""><?php esc_html_e('None', 'bdfg-custom-order-status'); ?></option>

<?php if (!empty($all_statuses)) : ?>
<optgroup label="<?php esc_attr_e('Custom Statuses', 'bdfg-custom-order-status'); ?>">
<?php foreach ($all_statuses as $next_status) : ?>
<?php if ($editing && $next_status->status_slug === $status->status_slug) continue; ?>
<option value="<?php echo esc_attr($next_status->status_slug); ?>" <?php selected($editing && $status->next_status == $next_status->status_slug); ?>>
<?php echo esc_html($next_status->status_name); ?>
</option>
<?php endforeach; ?>
</optgroup>
<?php endif; ?>

<optgroup label="<?php esc_attr_e('WooCommerce Default Statuses', 'bdfg-custom-order-status'); ?>">
<?php
$default_statuses = array(
'pending' => __('Pending payment', 'bdfg-custom-order-status'),
'processing' => __('Processing', 'bdfg-custom-order-status'),
'on-hold' => __('On hold', 'bdfg-custom-order-status'),
'completed' => __('Completed', 'bdfg-custom-order-status'),
'cancelled' => __('Cancelled', 'bdfg-custom-order-status'),
'refunded' => __('Refunded', 'bdfg-custom-order-status'),
'failed' => __('Failed', 'bdfg-custom-order-status'),
);

foreach ($default_statuses as $slug => $name) :
?>
<option value="<?php echo esc_attr($slug); ?>" <?php selected($editing && $status->next_status == $slug); ?>>
<?php echo esc_html($name); ?>
</option>
<?php endforeach; ?>
</optgroup>
</select>
<p class="description"><?php esc_html_e('Status to change to after expiry hours. Only applies if auto-change is set.', 'bdfg-custom-order-status'); ?></p>
</div>

<div class="bdfg-form-field bdfg-checkbox">
<label for="notification_enabled">
<input type="checkbox" id="notification_enabled" name="notification_enabled" value="1" <?php checked($editing ? $status->notification_enabled : 1); ?> />
<?php esc_html_e('Enable customer notifications', 'bdfg-custom-order-status'); ?>
</label>
<p class="description"><?php esc_html_e('Send email notification to customer when order changes to this status.', 'bdfg-custom-order-status'); ?></p>
</div>
</div>

<div class="bdfg-form-actions">
<button type="submit" class="button button-primary" id="save-status"><?php esc_html_e('Save Status', 'bdfg-custom-order-status'); ?></button>
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-custom-order-status')); ?>" class="button"><?php esc_html_e('Cancel', 'bdfg-custom-order-status'); ?></a>
<span class="spinner" id="save-spinner"></span>
</div>

<div id="save-result"></div>
</div>
</div>
</form>
</div>

includes/admin/views/html-admin-groups.php


<?php
/**
* Admin View: Status Groups
*
* @package BDFG_Custom_Order_Status
* @version 1.2.3
* @author Beiduofengou
* @last_modified 2025-03-06 14:07:01
* @modified_by Beiduofengou
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}

$editing = isset($group) && $group;
?>

<div class="wrap bdfg-cos-admin">
<h1 class="wp-heading-inline"><?php esc_html_e('Status Groups', 'bdfg-custom-order-status'); ?></h1>
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-custom-order-status')); ?>" class="page-title-action"><?php esc_html_e('Back to Statuses', 'bdfg-custom-order-status'); ?></a>

<hr class="wp-header-end">

<div class="bdfg-groups-wrapper">
<div class="bdfg-groups-content">
<div class="bdfg-groups-main">
<h2><?php esc_html_e('All Groups', 'bdfg-custom-order-status'); ?></h2>
<p class="description"><?php esc_html_e('Groups help you organize your statuses. Drag and drop to reorder.', 'bdfg-custom-order-status'); ?></p>

<div id="bdfg-groups-list" class="bdfg-sortable">
<?php if (empty($groups)) : ?>
<div class="bdfg-empty-state">
<div class="bdfg-empty-icon">
<span class="dashicons dashicons-category"></span>
</div>
<p><?php esc_html_e('No groups found. Create your first group to organize your statuses.', 'bdfg-custom-order-status'); ?></p>
</div>
<?php else : ?>
<?php foreach ($groups as $group_item) : ?>
<div class="bdfg-group-item" data-id="<?php echo esc_attr($group_item->id); ?>">
<div class="bdfg-group-content">
<h3 class="bdfg-group-name"><?php echo esc_html($group_item->group_name); ?></h3>
<div class="bdfg-group-meta">
<span class="bdfg-group-slug"><?php echo esc_html($group_item->group_slug); ?></span>
<span class="bdfg-group-count">
<?php echo esc_html(sprintf(_n('%d status', '%d statuses', $group_item->status_count, 'bdfg-custom-order-status'), $group_item->status_count)); ?>
</span>
</div>
</div>
<div class="bdfg-group-actions">
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-status-groups&edit=' . $group_item->id)); ?>" class="button button-small"><?php esc_html_e('Edit', 'bdfg-custom-order-status'); ?></a>
<button class="bdfg-delete-group button button-small" data-id="<?php echo esc_attr($group_item->id); ?>" data-name="<?php echo esc_attr($group_item->group_name); ?>" <?php disabled($group_item->status_count > 0); ?> title="<?php echo $group_item->status_count > 0 ? esc_attr__('Cannot delete group with statuses', 'bdfg-custom-order-status') : ''; ?>"><?php esc_html_e('Delete', 'bdfg-custom-order-status'); ?></button>
</div>
<div class="bdfg-group-handle">
<span class="dashicons dashicons-menu"></span>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>

<div class="bdfg-groups-sidebar">
<div class="bdfg-form-box">
<h3><?php echo $editing ? esc_html__('Edit Group', 'bdfg-custom-order-status') : esc_html__('Add New Group', 'bdfg-custom-order-status'); ?></h3>

<form id="bdfg-group-form" method="post">
<input type="hidden" id="group_id" name="group_id" value="<?php echo $editing ? esc_attr($group->id) : '0'; ?>" />

<div class="bdfg-form-field">
<label for="group_name"><?php esc_html_e('Group Name', 'bdfg-custom-order-status'); ?> <span class="required">*</span></label>
<input type="text" id="group_name" name="group_name" value="<?php echo $editing ? esc_attr($group->group_name) : ''; ?>" required autocomplete="off" />
</div>

<div class="bdfg-form-field">
<label for="group_slug"><?php esc_html_e('Group Slug', 'bdfg-custom-order-status'); ?> <span class="required">*</span></label>
<input type="text" id="group_slug" name="group_slug" value="<?php echo $editing ? esc_attr($group->group_slug) : ''; ?>" required autocomplete="off" />
<p class="description"><?php esc_html_e('Unique identifier for this group. Use only lowercase letters, numbers, and hyphens.', 'bdfg-custom-order-status'); ?></p>
</div>

<div class="bdfg-form-field">
<label for="group_description"><?php esc_html_e('Description', 'bdfg-custom-order-status'); ?></label>
<textarea id="group_description" name="group_description" rows="3"><?php echo $editing ? esc_textarea($group->group_description) : ''; ?></textarea>
</div>

<div class="bdfg-form-actions">
<button type="submit" class="button button-primary" id="save-group"><?php echo $editing ? esc_html__('Update Group', 'bdfg-custom-order-status') : esc_html__('Add Group', 'bdfg-custom-order-status'); ?></button>
<?php if ($editing) : ?>
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-status-groups')); ?>" class="button"><?php esc_html_e('Cancel', 'bdfg-custom-order-status'); ?></a>
<?php endif; ?>
<span class="spinner" id="save-spinner"></span>
</div>

<div id="save-result"></div>
</form>
</div>
</div>
</div>
</div>
</div>

includes/admin/views/html-admin-settings.php


<?php
/**
* Admin View: Settings
*
* @package BDFG_Custom_Order_Status
* @version 1.2.3
* @author Beiduofengou
* @last_modified 2025-03-06 14:07:01
* @modified_by Beiduofengou
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
?>

<div class="wrap bdfg-cos-admin">
<h1 class="wp-heading-inline"><?php esc_html_e('Settings', 'bdfg-custom-order-status'); ?></h1>
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-custom-order-status')); ?>" class="page-title-action"><?php esc_html_e('Back to Statuses', 'bdfg-custom-order-status'); ?></a>

<hr class="wp-header-end">

<form method="post" action="options.php">
<?php settings_fields('bdfg_custom_order_status_settings'); ?>

<div class="bdfg-settings-wrapper">
<div class="bdfg-settings-content">
<div class="bdfg-settings-section">
<h2><?php esc_html_e('General Settings', 'bdfg-custom-order-status'); ?></h2>

<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e('Email Notifications', 'bdfg-custom-order-status'); ?></th>
<td>
<label for="bdfg_cos_enable_email_notifications">
<input type="checkbox" id="bdfg_cos_enable_email_notifications" name="bdfg_cos_enable_email_notifications" value="yes" <?php checked($enable_email_notifications, 'yes'); ?> />
<?php esc_html_e('Enable email notifications for status changes', 'bdfg-custom-order-status'); ?>
</label>
<p class="description"><?php esc_html_e('When enabled, customers will receive email notifications when their order status changes to a custom status.', 'bdfg-custom-order-status'); ?></p>
</td>
</tr>

<tr>
<th scope="row"><?php esc_html_e('Bulk Actions', 'bdfg-custom-order-status'); ?></th>
<td>
<label for="bdfg_cos_bulk_actions_enabled">
<input type="checkbox" id="bdfg_cos_bulk_actions_enabled" name="bdfg_cos_bulk_actions_enabled" value="yes" <?php checked($bulk_actions_enabled, 'yes'); ?> />
<?php esc_html_e('Enable bulk actions for custom statuses', 'bdfg-custom-order-status'); ?>
</label>
<p class="description"><?php esc_html_e('When enabled, custom statuses will appear in the bulk action dropdown on the orders list page.', 'bdfg-custom-order-status'); ?></p>
</td>
</tr>

<tr>
<th scope="row"><?php esc_html_e('Status Column', 'bdfg-custom-order-status'); ?></th>
<td>
<label for="bdfg_cos_status_column_enabled">
<input type="checkbox" id="bdfg_cos_status_column_enabled" name="bdfg_cos_status_column_enabled" value="yes" <?php checked($status_column_enabled, 'yes'); ?> />
<?php esc_html_e('Enable custom status details column', 'bdfg-custom-order-status'); ?>
</label>
<p class="description"><?php esc_html_e('When enabled, an additional column with custom status details will appear on the orders list page.', 'bdfg-custom-order-status'); ?></p>
</td>
</tr>

<tr>
<th scope="row"><?php esc_html_e('Frontend Styling', 'bdfg-custom-order-status'); ?></th>
<td>
<label for="bdfg_cos_frontend_styling">
<input type="checkbox" id="bdfg_cos_frontend_styling" name="bdfg_cos_frontend_styling" value="yes" <?php checked($frontend_styling, 'yes'); ?> />
<?php esc_html_e('Enable custom status styling on frontend', 'bdfg-custom-order-status'); ?>
</label>
<p class="description"><?php esc_html_e('When enabled, custom status colors and icons will be displayed on the frontend.', 'bdfg-custom-order-status'); ?></p>
</td>
</tr>

<tr>
<th scope="row"><?php esc_html_e('Order Status Tracking', 'bdfg-custom-order-status'); ?></th>
<td>
<label for="bdfg_cos_enable_status_tracking">
<input type="checkbox" id="bdfg_cos_enable_status_tracking" name="bdfg_cos_enable_status_tracking" value="yes" <?php checked($enable_status_tracking, 'yes'); ?> />
<?php esc_html_e('Enable order status change tracking', 'bdfg-custom-order-status'); ?>
</label>
<p class="description"><?php esc_html_e('When enabled, status changes will be logged for reporting purposes.', 'bdfg-custom-order-status'); ?></p>
</td>
</tr>
</table>
</div>

<div class="bdfg-settings-section">
<h2><?php esc_html_e('Plugin Settings', 'bdfg-custom-order-status'); ?></h2>

<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e('Uninstall Cleanup', 'bdfg-custom-order-status'); ?></th>
<td>
<label for="bdfg_cos_remove_data_on_uninstall">
<input type="checkbox" id="bdfg_cos_remove_data_on_uninstall" name="bdfg_cos_remove_data_on_uninstall" value="yes" <?php checked($remove_data_on_uninstall, 'yes'); ?> />
<?php esc_html_e('Remove all data when uninstalling the plugin', 'bdfg-custom-order-status'); ?>
</label>
<p class="description"><?php esc_html_e('When enabled, all custom statuses, groups, and settings will be removed when the plugin is uninstalled.', 'bdfg-custom-order-status'); ?></p>
</td>
</tr>
</table>
</div>

<div class="bdfg-settings-section">
<h2><?php esc_html_e('Plugin Information', 'bdfg-custom-order-status'); ?></h2>

<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e('Version', 'bdfg-custom-order-status'); ?></th>
<td><code><?php echo BDFG_COS_VERSION; ?></code></td>
</tr>
<tr>
<th scope="row"><?php esc_html_e('Database Version', 'bdfg-custom-order-status'); ?></th>
<td><code><?php echo get_option('bdfg_cos_db_version', '1.0.0'); ?></code></td>
</tr>
<tr>
<th scope="row"><?php esc_html_e('Last Updated', 'bdfg-custom-order-status'); ?></th>
<td><?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime('2025-03-06 14:22:06'))); ?></td>
</tr>
<tr>
<th scope="row"><?php esc_html_e('Last Modified By', 'bdfg-custom-order-status'); ?></th>
<td><?php echo esc_html('Beiduofengou'); ?></td>
</tr>
</table>

<p>
<a href="https://beiduofengou.net/support/custom-order-status/" target="_blank" class="button"><?php esc_html_e('Get Support', 'bdfg-custom-order-status'); ?></a>
<a href="https://beiduofengou.net/docs/custom-order-status/" target="_blank" class="button"><?php esc_html_e('Documentation', 'bdfg-custom-order-status'); ?></a>
</p>
</div>
</div>

<div class="bdfg-settings-sidebar">
<div class="bdfg-form-box">
<h3><?php esc_html_e('Save Settings', 'bdfg-custom-order-status'); ?></h3>
<p><?php esc_html_e('Click the button below to save your settings.', 'bdfg-custom-order-status'); ?></p>
<p><?php submit_button(); ?></p>
</div>

<div class="bdfg-form-box">
<h3><?php esc_html_e('Need Help?', 'bdfg-custom-order-status'); ?></h3>
<p><?php esc_html_e('Check out our documentation for help with setting up and using custom order statuses.', 'bdfg-custom-order-status'); ?></p>
<p><a href="https://beiduofengou.net/docs/custom-order-status/" target="_blank"><?php esc_html_e('View Documentation', 'bdfg-custom-order-status'); ?></a></p>
</div>

<div class="bdfg-form-box">
<h3><?php esc_html_e('More Extensions', 'bdfg-custom-order-status'); ?></h3>
<p><?php esc_html_e('Enhance your WooCommerce store with more extensions from Beiduofengou.', 'bdfg-custom-order-status'); ?></p>
<ul>
<li><a href="https://beiduofengou.net/products/woocommerce-status-workflows/" target="_blank"><?php esc_html_e('Status Workflows Pro', 'bdfg-custom-order-status'); ?></a></li>
<li><a href="https://beiduofengou.net/products/woocommerce-fulfillment-tracker/" target="_blank"><?php esc_html_e('Fulfillment Tracker', 'bdfg-custom-order-status'); ?></a></li>
<li><a href="https://beiduofengou.net/products/woocommerce-custom-emails/" target="_blank"><?php esc_html_e('Custom Emails', 'bdfg-custom-order-status'); ?></a></li>
</ul>
</div>
</div>
</div>
</form>
</div>

includes/admin/views/html-admin-reports.php


<?php
/**
* Admin View: Reports
*
* @package BDFG_Custom_Order_Status
* @version 1.2.3
* @author Beiduofengou
* @last_modified 2025-03-06 14:22:06
* @modified_by Beiduofengou
*/

// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
?>

<div class="wrap bdfg-cos-admin">
<h1 class="wp-heading-inline"><?php esc_html_e('Status Reports', 'bdfg-custom-order-status'); ?></h1>
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-custom-order-status')); ?>" class="page-title-action"><?php esc_html_e('Back to Statuses', 'bdfg-custom-order-status'); ?></a>

<hr class="wp-header-end">

<div class="bdfg-reports-wrapper">
<div class="bdfg-reports-header">
<div class="bdfg-reports-filters">
<form method="get">
<input type="hidden" name="page" value="bdfg-status-reports">

<div class="bdfg-reports-filter-field">
<label for="report_type"><?php esc_html_e('Report Type', 'bdfg-custom-order-status'); ?></label>
<select id="report_type" name="report_type">
<option value="status_distribution" <?php selected($report_type, 'status_distribution'); ?>><?php esc_html_e('Status Distribution', 'bdfg-custom-order-status'); ?></option>
<option value="status_timeline" <?php selected($report_type, 'status_timeline'); ?>><?php esc_html_e('Status Timeline', 'bdfg-custom-order-status'); ?></option>
<option value="average_time_in_status" <?php selected($report_type, 'average_time_in_status'); ?>><?php esc_html_e('Average Time in Status', 'bdfg-custom-order-status'); ?></option>
</select>
</div>

<div class="bdfg-reports-filter-field">
<label for="start_date"><?php esc_html_e('Start Date', 'bdfg-custom-order-status'); ?></label>
<input type="text" id="start_date" name="start_date" class="bdfg-datepicker" value="<?php echo esc_attr($start_date); ?>" autocomplete="off" placeholder="YYYY-MM-DD">
</div>

<div class="bdfg-reports-filter-field">
<label for="end_date"><?php esc_html_e('End Date', 'bdfg-custom-order-status'); ?></label>
<input type="text" id="end_date" name="end_date" class="bdfg-datepicker" value="<?php echo esc_attr($end_date); ?>" autocomplete="off" placeholder="YYYY-MM-DD">
</div>

<div class="bdfg-reports-filter-field">
<button type="submit" class="button button-secondary"><?php esc_html_e('Apply', 'bdfg-custom-order-status'); ?></button>
</div>
</form>
</div>
</div>

<div class="bdfg-reports-content">
<div class="bdfg-report-box">
<?php switch ($report_type) :
case 'status_distribution': ?>
<h2><?php esc_html_e('Order Status Distribution', 'bdfg-custom-order-status'); ?></h2>
<p class="description"><?php esc_html_e('Distribution of orders by status for the selected date range.', 'bdfg-custom-order-status'); ?></p>

<div class="bdfg-chart-container">
<canvas id="statusDistributionChart"></canvas>
</div>

<script>
jQuery(document).ready(function($) {
var ctx = document.getElementById('statusDistributionChart').getContext('2d');
var data = <?php echo json_encode($report_data); ?>;

var labels = data.map(function(item) {
return item.label;
});

var values = data.map(function(item) {
return item.value;
});

var colors = data.map(function(item) {
return item.color;
});

var chart = new Chart(ctx, {
type: 'pie',
data: {
labels: labels,
datasets: [{
data: values,
backgroundColor: colors,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
legend: {
position: 'right'
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var label = data.labels[tooltipItem.index] || '';
var value = data.datasets[0].data[tooltipItem.index] || 0;
var total = data.datasets[0].data.reduce(function(a, b) {
return a + b;
}, 0);
var percentage = Math.round((value / total) * 100);

return label + ': ' + value + ' (' + percentage + '%)';
}
}
}
}
});
});
</script>
<?php break;

case 'status_timeline': ?>
<h2><?php esc_html_e('Status Timeline', 'bdfg-custom-order-status'); ?></h2>
<p class="description"><?php esc_html_e('Number of orders in each status over time.', 'bdfg-custom-order-status'); ?></p>

<div class="bdfg-chart-container">
<canvas id="statusTimelineChart"></canvas>
</div>

<script>
jQuery(document).ready(function($) {
var ctx = document.getElementById('statusTimelineChart').getContext('2d');
var data = <?php echo json_encode($report_data); ?>;

var chart = new Chart(ctx, {
type: 'line',
data: {
labels: data.labels,
datasets: data.datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
display: true,
title: {
display: true,
text: '<?php esc_html_e('Date', 'bdfg-custom-order-status'); ?>'
}
},
y: {
display: true,
title: {
display: true,
text: '<?php esc_html_e('Number of Orders', 'bdfg-custom-order-status'); ?>'
},
beginAtZero: true
}
},
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: {
position: 'top'
},
tooltip: {
usePointStyle: true
}
}
}
});
});
</script>
<?php break;

case 'average_time_in_status': ?>
<h2><?php esc_html_e('Average Time in Status', 'bdfg-custom-order-status'); ?></h2>
<p class="description"><?php esc_html_e('Average time orders spend in each status before moving to next status.', 'bdfg-custom-order-status'); ?></p>

<div class="bdfg-chart-container">
<canvas id="avgTimeChart"></canvas>
</div>

<script>
jQuery(document).ready(function($) {
var ctx = document.getElementById('avgTimeChart').getContext('2d');
var data = <?php echo json_encode($report_data); ?>;

var labels = data.map(function(item) {
return item.label;
});

var values = data.map(function(item) {
return item.value;
});

var colors = data.map(function(item) {
return item.color;
});

var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '<?php esc_html_e('Hours', 'bdfg-custom-order-status'); ?>',
data: values,
backgroundColor: colors,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '<?php esc_html_e('Hours', 'bdfg-custom-order-status'); ?>'
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
});
</script>
<?php break;

endswitch; ?>
</div>

<div class="bdfg-report-notes">
<p><em><?php esc_html_e('Note: This report is based on data collected since status tracking was enabled.', 'bdfg-custom-order-status'); ?></em></p>
<p><?php printf(__('Last generated: %s by %s', 'bdfg-custom-order-status'), date_i18n(get_option('date_format') . ' ' . get_option('time_format'), current_time('timestamp')), 'Beiduofengou'); ?></p>
</div>
</div>
</div>
</div>

templates/emails/bdfg-custom-order-status-email.php


<?php
/**
* Custom Order Status Email Template
*
* @package BDFG_Custom_Order_Status
* @version 1.2.3
* @author Beiduofengou
* @last_modified 2025-03-06 14:33:32
* @modified_by Beiduofengou
*
* @var $order WC_Order
* @var $email_heading string
* @var $additional_content string
* @var $status_name string
* @var $status_color string
*/

if (!defined('ABSPATH')) {
exit;
}

do_action('woocommerce_email_header', $email_heading, $email);
?>

<p><?php printf(esc_html__('Hi %s,', 'bdfg-custom-order-status'), esc_html($order->get_billing_first_name())); ?></p>

<p><?php printf(esc_html__('Your order #%s from %s has been updated to the following status:', 'bdfg-custom-order-status'), esc_html($order->get_order_number()), esc_html(get_bloginfo('name'))); ?></p>

<p style="margin: 12px 0; padding: 12px 15px; background-color: <?php echo esc_attr($status_color); ?>; color: #fff; border-radius: 4px; display: inline-block; font-weight: bold;">
<?php echo esc_html($status_name); ?>
</p>

<?php if ($order->get_customer_note()) : ?>
<blockquote><?php echo wpautop(wptexturize($order->get_customer_note())); ?></blockquote>
<?php endif; ?>

<h2><?php esc_html_e('Order Details', 'bdfg-custom-order-status'); ?></h2>

<table class="td" cellspacing="0" cellpadding="6" style="width: 100%; margin-bottom: 20px; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;" border="1">
<thead>
<tr>
<th class="td" scope="col" style="text-align:left;"><?php esc_html_e('Product', 'woocommerce'); ?></th>
<th class="td" scope="col" style="text-align:left;"><?php esc_html_e('Quantity', 'woocommerce'); ?></th>
<th class="td" scope="col" style="text-align:left;"><?php esc_html_e('Price', 'woocommerce'); ?></th>
</tr>
</thead>
<tbody>
<?php
$items = $order->get_items();
foreach ($items as $item_id => $item) :
$product = $item->get_product();
?>
<tr>
<td class="td" style="text-align:left; vertical-align:middle; border: 1px solid #eee; word-wrap:break-word;">
<?php echo esc_html($item->get_name()); ?>
<?php
if ($product && $product->get_sku()) {
echo ' (#' . esc_html($product->get_sku()) . ')';
}
?>
</td>
<td class="td" style="text-align:left; vertical-align:middle; border: 1px solid #eee;"><?php echo esc_html($item->get_quantity()); ?></td>
<td class="td" style="text-align:left; vertical-align:middle; border: 1px solid #eee;"><?php echo wp_kses_post($order->get_formatted_line_subtotal($item)); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<th class="td" scope="row" colspan="2" style="text-align:right; border: 1px solid #eee;"><?php esc_html_e('Subtotal:', 'woocommerce'); ?></th>
<td class="td" style="text-align:left; border: 1px solid #eee;"><?php echo wp_kses_post($order->get_subtotal_to_display()); ?></td>
</tr>
<?php if ($order->get_shipping_total()) : ?>
<tr>
<th class="td" scope="row" colspan="2" style="text-align:right; border: 1px solid #eee;"><?php esc_html_e('Shipping:', 'woocommerce'); ?></th>
<td class="td" style="text-align:left; border: 1px solid #eee;"><?php echo wp_kses_post($order->get_shipping_to_display()); ?></td>
</tr>
<?php endif; ?>
<?php if ($order->get_total_tax()) : ?>
<tr>
<th class="td" scope="row" colspan="2" style="text-align:right; border: 1px solid #eee;"><?php esc_html_e('Tax:', 'woocommerce'); ?></th>
<td class="td" style="text-align:left; border: 1px solid #eee;"><?php echo wp_kses_post($order->get_tax_totals_html()); ?></td>
</tr>
<?php endif; ?>
<tr>
<th class="td" scope="row" colspan="2" style="text-align:right; border: 1px solid #eee;"><?php esc_html_e('Total:', 'woocommerce'); ?></th>
<td class="td" style="text-align:left; border: 1px solid #eee;"><?php echo wp_kses_post($order->get_formatted_order_total()); ?></td>
</tr>
</tfoot>
</table>

<p>
<a href="<?php echo esc_url($order->get_view_order_url()); ?>" style="display: inline-block; padding: 10px 15px; background-color: #7f54b3; color: #ffffff; text-decoration: none; border-radius: 3px; margin: 10px 0;">
<?php esc_html_e('View Order', 'bdfg-custom-order-status'); ?>
</a>
</p>

<?php
if ($additional_content) {
echo wp_kses_post(wpautop(wptexturize($additional_content)));
}

do_action('woocommerce_email_footer', $email);
?>

templates/emails/plain/bdfg-custom-order-status-email.php


<?php
/**
* Custom Order Status Plain Email Template
*
* @package BDFG_Custom_Order_Status
* @version 1.2.3
* @author Beiduofengou
* @last_modified 2025-03-06 14:33:32
* @modified_by Beiduofengou
*
* @var $order WC_Order
* @var $email_heading string
* @var $additional_content string
* @var $status_name string
* @var $status_color string
*/

if (!defined('ABSPATH')) {
exit;
}

echo "= " . esc_html($email_heading) . " =\n\n";

echo sprintf(esc_html__('Hi %s,', 'bdfg-custom-order-status'), esc_html($order->get_billing_first_name())) . "\n\n";

echo sprintf(esc_html__('Your order #%s from %s has been updated to the following status:', 'bdfg-custom-order-status'), esc_html($order->get_order_number()), esc_html(get_bloginfo('name'))) . "\n\n";

echo esc_html($status_name) . "\n\n";

if ($order->get_customer_note()) {
echo esc_html__('Note:', 'bdfg-custom-order-status') . "\n";
echo esc_html($order->get_customer_note()) . "\n\n";
}

echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";

echo esc_html__('Order Details', 'bdfg-custom-order-status') . "\n\n";

foreach ($order->get_items() as $item_id => $item) {
$product = $item->get_product();
$sku = $product ? $product->get_sku() : '';
$sku_display = $sku ? ' (#' . $sku . ')' : '';

echo wp_kses_post($item->get_name() . $sku_display . ' × ' . $item->get_quantity()) . "\n";
}

echo "\n";
echo esc_html__('Subtotal:', 'woocommerce') . ' ' . wp_kses_post($order->get_subtotal_to_display()) . "\n";

if ($order->get_shipping_total()) {
echo esc_html__('Shipping:', 'woocommerce') . ' ' . wp_kses_post($order->get_shipping_to_display()) . "\n";
}

if ($order->get_total_tax()) {
echo esc_html__('Tax:', 'woocommerce') . ' ' . wp_kses_post($order->get_tax_totals_html()) . "\n";
}

echo esc_html__('Total:', 'woocommerce') . ' ' . wp_kses_post($order->get_formatted_order_total()) . "\n\n";

echo esc_html__('View your order:', 'bdfg-custom-order-status') . ' ' . esc_url($order->get_view_order_url()) . "\n\n";

echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";

if ($additional_content) {
echo esc_html(wp_strip_all_tags(wptexturize($additional_content)));
echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
}

echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text')));

assets/js/admin.js


/**
* BDFG Custom Order Status - Admin JavaScript
*
* @package BDFG_Custom_Order_Status
* @version 1.2.3
* @author Beiduofengou
* @last_modified 2025-03-06 14:33:32
* @modified_by Beiduofengou
*/

(function($) {
'use strict';

// Initialize on document ready
$(document).ready(function() {
// Initialize color pickers
if ($.fn.wpColorPicker) {
$('.bdfg-color-picker').wpColorPicker();
}

// Initialize datepickers
if ($.fn.datepicker) {
$('.bdfg-datepicker').datepicker({
dateFormat: 'yy-mm-dd',
maxDate: 0
});
}

// Initialize sortable lists
if ($.fn.sortable) {
// Sortable statuses
$('#bdfg-statuses-list').sortable({
handle: '.bdfg-status-handle',
update: function(event, ui) {
var statusIds = [];
$('#bdfg-statuses-list .bdfg-status-item').each(function() {
statusIds.push($(this).data('id'));
});

// Save the new order via AJAX
$.ajax({
url: bdfg_cos_vars.ajaxurl,
type: 'POST',
data: {
action: 'bdfg_update_status_order',
status_ids: statusIds,
nonce: bdfg_cos_vars.nonce
},
success: function(response) {
if (response.success) {
// Show success message
$('#save-result').html('<div class="notice notice-success is-dismissible"><p>' + response.data.message + '</p></div>');
setTimeout(function() {
$('#save-result .notice').fadeOut();
}, 3000);
} else {
// Show error message
$('#save-result').html('<div class="notice notice-error is-dismissible"><p>' + response.data.message + '</p></div>');
}
}
});
}
});

// Sortable groups
$('#bdfg-groups-list').sortable({
handle: '.bdfg-group-handle',
update: function(event, ui) {
var groupIds = [];
$('#bdfg-groups-list .bdfg-group-item').each(function() {
groupIds.push($(this).data('id'));
});

// Save the new order via AJAX
$.ajax({
url: bdfg_cos_vars.ajaxurl,
type: 'POST',
data: {
action: 'bdfg_update_group_order',
group_ids: groupIds,
nonce: bdfg_cos_vars.nonce
},
success: function(response) {
if (response.success) {
// Show success message
$('#save-result').html('<div class="notice notice-success is-dismissible"><p>' + response.data.message + '</p></div>');
setTimeout(function() {
$('#save-result .notice').fadeOut();
}, 3000);
} else {
// Show error message
$('#save-result').html('<div class="notice notice-error is-dismissible"><p>' + response.data.message + '</p></div>');
}
}
});
}
});
}

// Save status form
$('#bdfg-status-form').on('submit', function(e) {
e.preventDefault();

// Show spinner
$('#save-spinner').addClass('is-active');

// Get form data
var formData = $(this).serialize();

// Add notification_enabled if not checked
if (!$('#notification_enabled:checked').length) {
formData += '&notification_enabled=0';
}

// Add action and nonce
formData += '&action=bdfg_save_status&nonce=' + bdfg_cos_vars.nonce;

// Save via AJAX
$.ajax({
url: bdfg_cos_vars.ajaxurl,
type: 'POST',
data: formData,
success: function(response) {
// Hide spinner
$('#save-spinner').removeClass('is-active');

if (response.success) {
// Show success message
$('#save-result').html('<div class="notice notice-success is-dismissible"><p>' + response.data.message + '</p></div>');

// Redirect if new status
if ($('#status_id').val() === '0') {
setTimeout(function() {
window.location.href = 'admin.php?page=bdfg-add-order-status&edit=' + response.data.status_id + '&saved=true';
}, 1000);
}
} else {
// Show error message
$('#save-result').html('<div class="notice notice-error is-dismissible"><p>' + response.data.message + '</p></div>');
}
}
});
});

// Save group form
$('#bdfg-group-form').on('submit', function(e) {
e.preventDefault();

// Show spinner
$('#save-spinner').addClass('is-active');

// Get form data
var formData = $(this).serialize();

// Add action and nonce
formData += '&action=bdfg_save_group&nonce=' + bdfg_cos_vars.nonce;

// Save via AJAX
$.ajax({
url: bdfg_cos_vars.ajaxurl,
type: 'POST',
data: formData,
success: function(response) {
// Hide spinner
$('#save-spinner').removeClass('is-active');

if (response.success) {
// Show success message
$('#save-result').html('<div class="notice notice-success is-dismissible"><p>' + response.data.message + '</p></div>');

// Reload page
setTimeout(function() {
window.location.reload();
}, 1000);
} else {
// Show error message
$('#save-result').html('<div class="notice notice-error is-dismissible"><p>' + response.data.message + '</p></div>');
}
}
});
});

// Delete status
$('.bdfg-delete-status').on('click', function() {
if (confirm(bdfg_cos_vars.confirm_delete)) {
var statusId = $(this).data('id');
var statusItem = $(this).closest('.bdfg-status-item');

$.ajax({
url: bdfg_cos_vars.ajaxurl,
type: 'POST',
data: {
action: 'bdfg_delete_status',
status_id: statusId,
nonce: bdfg_cos_vars.nonce
},
success: function(response) {
if (response.success) {
// Remove item from DOM
statusItem.fadeOut(300, function() {
$(this).remove();

// Show message if no items left
if ($('#bdfg-statuses-list .bdfg-status-item').length === 0) {
$('#bdfg-statuses-list').html('<div class="bdfg-empty-state"><div class="bdfg-empty-icon"><span class="dashicons dashicons-marker"></span></div><h2>' + bdfg_cos_vars.no_statuses + '</h2><p>' + bdfg_cos_vars.create_status + '</p><a href="admin.php?page=bdfg-add-order-status" class="button button-primary">' + bdfg_cos_vars.add_status + '</a></div>');
}
});
} else {
alert(response.data.message);
}
}
});
}
});

// Delete group
$('.bdfg-delete-group').on('click', function() {
if ($(this).prop('disabled')) {
return;
}

if (confirm(bdfg_cos_vars.confirm_delete)) {
var groupId = $(this).data('id');
var groupItem = $(this).closest('.bdfg-group-item');

$.ajax({
url: bdfg_cos_vars.ajaxurl,
type: 'POST',
data: {
action: 'bdfg_delete_group',
group_id: groupId,
nonce: bdfg_cos_vars.nonce
},
success: function(response) {
if (response.success) {
// Remove item from DOM
groupItem.fadeOut(300, function() {
$(this).remove();

// Show message if no items left
if ($('#bdfg-groups-list .bdfg-group-item').length === 0) {
$('#bdfg-groups-list').html('<div class="bdfg-empty-state"><div class="bdfg-empty-icon"><span class="dashicons dashicons-category"></span></div><p>' + bdfg_cos_vars.no_groups + '</p></div>');
}
});
} else {
alert(response.data.message);
}
}
});
}
});

// Icon preview
$('#icon').on('change', function() {
var icon = $(this).val();
var preview = $('#icon_preview');

if (icon) {
preview.html('<span class="dashicons dashicons-' + icon + '"></span>');
} else {
preview.empty();
}
});

// Auto-generate slug from name (only for new items)
if ($('#status_id').val() === '0') {
$('#status_name').on('keyup', function() {
var slug = $(this).val().toLowerCase().trim();
// Replace spaces and special chars with hyphens
slug = slug.replace(/[^a-z0-9]+/g, '-');
// Remove leading and trailing hyphens
slug = slug.replace(/^-+|-+$/g, '');
$('#status_slug').val(slug);
});
}

if ($('#group_id').val() === '0') {
$('#group_name').on('keyup', function() {
var slug = $(this).val().toLowerCase().trim();
// Replace spaces and special chars with hyphens
slug = slug.replace(/[^a-z0-9]+/g, '-');
// Remove leading and trailing hyphens
slug = slug.replace(/^-+|-+$/g, '');
$('#group_slug').val(slug);
});
}
});
})(jQuery);

assets/css/admin.css


/**
* BDFG Custom Order Status - Admin CSS
*
* @package BDFG_Custom_Order_Status
* @version 1.2.3
* @author Beiduofengou
* @last_modified 2025-03-06 14:36:05
* @modified_by Beiduofengou
*/

/* General Admin Styles */
.bdfg-cos-admin {
margin: 0;
padding: 0;
}

/* Empty State */
.bdfg-empty-state {
text-align: center;
padding: 40px 20px;
background-color: #fff;
border: 1px solid #e5e5e5;
border-radius: 4px;
margin: 20px 0;
}

.bdfg-empty-icon {
font-size: 3em;
margin-bottom: 15px;
}

.bdfg-empty-icon .dashicons {
width: auto;
height: auto;
font-size: 48px;
color: #ddd;
}

/* Status List */
.bdfg-statuses-wrapper {
margin: 20px 0;
}

.bdfg-admin-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}

.bdfg-status-item {
display: flex;
background-color: #fff;
border: 1px solid #e5e5e5;
border-radius: 4px;
margin-bottom: 10px;
position: relative;
overflow: hidden;
}

.bdfg-status-color {
flex: 0 0 8px;
}

.bdfg-status-content {
flex: 1;
padding: 15px;
}

.bdfg-status-header {
display: flex;
align-items: center;
margin-bottom: 10px;
}

.bdfg-status-name {
margin: 0;
font-size: 16px;
line-height: 1.3;
flex: 1;
}

.bdfg-status-group {
margin-left: 10px;
padding: 3px 8px;
background-color: #f9f9f9;
border-radius: 3px;
font-size: 12px;
}

.bdfg-status-count {
margin-left: 10px;
padding: 3px 8px;
background-color: #f0f0f0;
border-radius: 3px;
font-size: 12px;
}

.bdfg-status-description {
margin-bottom: 10px;
color: #666;
}

.bdfg-status-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
font-size: 12px;
color: #666;
}

.bdfg-status-meta strong {
color: #23282d;
}

.bdfg-status-icon-preview .dashicons {
font-size: 16px;
width: 16px;
height: 16px;
vertical-align: text-bottom;
}

.bdfg-status-actions {
padding: 15px;
display: flex;
flex-direction: column;
justify-content: center;
gap: 5px;
}

.bdfg-status-handle {
padding: 0 10px;
display: flex;
align-items: center;
cursor: move;
background-color: #f9f9f9;
}

/* Form Styles */
.bdfg-form-wrapper {
display: flex;
gap: 20px;
margin-top: 20px;
}

.bdfg-form-main {
flex: 3;
background-color: #fff;
border: 1px solid #e5e5e5;
border-radius: 4px;
padding: 20px;
}

.bdfg-form-sidebar {
flex: 1;
min-width: 250px;
}

.bdfg-form-box {
background-color: #fff;
border: 1px solid #e5e5e5;
border-radius: 4px;
padding: 15px;
margin-bottom: 20px;
}

.bdfg-form-box h3 {
margin-top: 0;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}

.bdfg-form-field {
margin-bottom: 15px;
}

.bdfg-form-field label {
display: block;
margin-bottom: 5px;
font-weight: 600;
}

.bdfg-form-field input[type="text"],
.bdfg-form-field input[type="number"],
.bdfg-form-field textarea,
.bdfg-form-field select {
width: 100%;
max-width: 100%;
}

.bdfg-form-field .description {
margin-top: 5px;
color: #666;
}

.bdfg-form-field.bdfg-checkbox {
display: flex;
align-items: center;
}

.bdfg-form-field.bdfg-checkbox label {
display: inline;
margin-left: 5px;
font-weight: normal;
}

.bdfg-form-field.bdfg-checkbox input {
margin-top: 0;
}

.bdfg-form-actions {
margin-top: 20px;
display: flex;
align-items: center;
gap: 10px;
}

.bdfg-form-actions .spinner {
float: none;
}

/* Icon Preview */
.bdfg-icon-preview {
margin-top: 10px;
padding: 10px;
text-align: center;
border: 1px solid #ddd;
border-radius: 3px;
background-color: #f9f9f9;
}

.bdfg-icon-preview .dashicons {
font-size: 24px;
width: 24px;
height: 24px;
}

/* Groups */
.bdfg-groups-wrapper {
margin: 20px 0;
}

.bdfg-groups-content {
display: flex;
gap: 20px;
}

.bdfg-groups-main {
flex: 2;
}

.bdfg-groups-sidebar {
flex: 1;
min-width: 250px;
}

.bdfg-group-item {
display: flex;
background-color: #fff;
border: 1px solid #e5e5e5;
border-radius: 4px;
margin-bottom: 10px;
position: relative;
}

.bdfg-group-content {
flex: 1;
padding: 15px;
}

.bdfg-group-name {
margin: 0;
font-size: 16px;
line-height: 1.3;
}

.bdfg-group-meta {
display: flex;
gap: 10px;
font-size: 12px;
color: #666;
margin-top: 5px;
}

.bdfg-group-actions {
padding: 15px;
display: flex;
align-items: center;
gap: 5px;
}

.bdfg-group-handle {
padding: 0 10px;
display: flex;
align-items: center;
cursor: move;
background-color: #f9f9f9;
}

/* Reports */
.bdfg-reports-wrapper {
margin: 20px 0;
}

.bdfg-reports-header {
margin-bottom: 20px;
}

.bdfg-reports-filters {
background-color: #fff;
border: 1px solid #e5e5e5;
border-radius: 4px;
padding: 15px;
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: flex-end;
}

.bdfg-reports-filter-field label {
display: block;
margin-bottom: 5px;
font-weight: 600;
}

.bdfg-report-box {
background-color: #fff;
border: 1px solid #e5e5e5;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
}

.bdfg-report-box h2 {
margin-top: 0;
margin-bottom: 10px;
}

.bdfg-chart-container {
height: 400px;
margin: 20px 0;
}

.bdfg-report-notes {
font-size: 12px;
color: #666;
margin-top: 10px;
}

/* Custom Status Column on Orders Page */
.bdfg-status-details {
padding: 8px 0;
}

.bdfg-status-group {
font-weight: bold;
}

.bdfg-status-time {
display: block;
margin-top: 5px;
font-size: 90%;
}

.bdfg-progress-bar {
height: 5px;
background-color: #f0f0f0;
margin-top: 5px;
border-radius: 3px;
overflow: hidden;
}

.bdfg-progress {
height: 100%;
width: 0;
}

.bdfg-branding {
margin-top: 8px;
text-align: right;
opacity: 0.7;
}

.bdfg-branding a {
font-size: 10px;
color: #999;
text-decoration: none;
}

.bdfg-branding a:hover {
color: #0073aa;
}

/* Status Timeline in Order Meta Box */
.bdfg-status-history {
padding: 5px 0;
}

.bdfg-status-timeline {
list-style: none;
margin: 0;
padding: 0;
position: relative;
}

.bdfg-status-timeline:before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 7px;
width: 2px;
background-color: #f0f0f0;
z-index: 0;
}

.bdfg-status-timeline li {
position: relative;
padding-left: 25px;
margin-bottom: 15px;
}

.bdfg-status-timeline li:last-child {
margin-bottom: 0;
}

.bdfg-status-timeline li:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 15px;
height: 15px;
border-radius: 50%;
background-color: #0073aa;
z-index: 1;
}

.bdfg-status-date {
display: block;
font-size: 11px;
font-weight: bold;
color: #666;
}

.bdfg-status-note {
display: block;
font-size: 13px;
margin-top: 2px;
}

.bdfg-status-user {
display: block;
font-size: 11px;
font-style: italic;
color: #666;
margin-top: 2px;
}

.bdfg-status-actions {
margin-top: 15px;
border-top: 1px solid #eee;
padding-top: 15px;
}

.bdfg-status-actions label {
display: block;
margin-bottom: 5px;
}

.bdfg-status-actions select {
width: 100%;
margin-bottom: 10px;
}

.bdfg-status-actions #bdfg_status_spinner {
float: none;
margin-left: 5px;
vertical-align: middle;
}

/* Service notice */
.bdfg-service-notice {
border-left-color: #00a0d2;
}

.bdfg-service-notice h3 {
margin-top: 0.5em;
}

/* Responsive */
@media screen and (max-width: 782px) {
.bdfg-form-wrapper,
.bdfg-groups-content {
flex-direction: column;
}

.bdfg-form-sidebar,
.bdfg-groups-sidebar {
min-width: auto;
}

.bdfg-status-header {
flex-direction: column;
align-items: flex-start;
}

.bdfg-status-group,
.bdfg-status-count {
margin-left: 0;
margin-top: 5px;
}

.bdfg-status-actions {
padding: 10px;
}

.bdfg-status-handle {
display: none;
}
}

assets/css/frontend.css


/**
* BDFG Custom Order Status - Frontend CSS
*
* @package BDFG_Custom_Order_Status
* @version 1.2.3
* @author Beiduofengou
* @last_modified 2025-03-06 14:37:55
* @modified_by Beiduofengou
*/

/* Custom Status Badge */
.bdfg-status-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 3px;
font-size: 0.85em;
font-weight: 600;
text-transform: uppercase;
line-height: 1.3;
}

.bdfg-status-badge .dashicons {
font-size: 14px;
width: 14px;
height: 14px;
vertical-align: text-bottom;
margin-right: 3px;
}

/* Status Timeline */
.bdfg-status-timeline-wrapper {
margin: 20px 0;
}

.bdfg-status-timeline-title {
margin-bottom: 15px;
font-size: 1.1em;
font-weight: 600;
}

.bdfg-status-timeline {
list-style: none;
margin: 0;
padding: 0;
position: relative;
}

.bdfg-status-timeline:before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 7px;
width: 2px;
background-color: #f0f0f0;
z-index: 0;
}

.bdfg-status-timeline li {
position: relative;
padding-left: 25px;
margin-bottom: 15px;
}

.bdfg-status-timeline li:last-child {
margin-bottom: 0;
}

.bdfg-status-timeline li:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 15px;
height: 15px;
border-radius: 50%;
background-color: #0073aa;
z-index: 1;
}

.bdfg-status-timeline li.active:before {
background-color: #73a724;
}

.bdfg-status-date {
display: block;
font-size: 0.85em;
color: #666;
}

.bdfg-status-name {
display: block;
font-size: 1em;
font-weight: 600;
margin-top: 2px;
}

.bdfg-status-description {
display: block;
font-size: 0.9em;
margin-top: 2px;
color: #666;
}

/* Responsive */
@media screen and (max-width: 767px) {
.bdfg-status-timeline-wrapper {
margin: 15px 0;
}
}

 

Leave a Comment