为 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 += '¬ification_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; } }