WooCommerce的BDFG预订无缝地为您的WooCommerce商店添加预订功能,使客户在正式发布或可用之前购买产品。允许客户预订尚未发布或可用的产品,并具有可自定义的发布日期,定价选项和自动通知。
***可自定义的发布日期**:设置产品可用性的特定日期和时间
***灵活的定价选项**:提供预订的折扣或保费
***自动状态更新**:产品自动从预订过渡到可用
***电子邮件通知**:预订产品可用时提醒客户
***倒计时计时器**:使用可自定义的倒数计时器建立预期
***订单管理**:用于管理预订的专用仪表板
***客户帐户集成**:客户可以查看和管理其预订
相关文章: WooCommerce 产品比较工具
<?php /** * Plugin Name: BDFG Pre-Orders for WooCommerce * Plugin URI: https://beiduofengou.net/2024/12/05/bdfg-pre-orders-for-woocommerce/ * Description: Allow customers to pre-order products before they are available. * Version: 2.0.0 * Author: Beiduofengou * Author URI: https://beiduofengou.net * Text Domain: bdfg-preorders * Domain Path: /languages * Requires at least: 5.6 * Requires PHP: 7.2 * WC requires at least: 5.0.0 * WC tested up to: 7.5.0 * * @package BDFG_PreOrders */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } // Define constants define('BDFG_PREORDERS_VERSION', '2.0.0'); define('BDFG_PREORDERS_PATH', plugin_dir_path(__FILE__)); define('BDFG_PREORDERS_URL', plugin_dir_url(__FILE__)); define('BDFG_PREORDERS_BASENAME', plugin_basename(__FILE__)); /** * Main plugin class */ class BDFG_PreOrders { /** * Instance * * @var BDFG_PreOrders */ private static $instance; /** * Admin class * * @var BDFG_PreOrders_Admin */ public $admin; /** * Frontend class * * @var BDFG_PreOrders_Frontend */ public $frontend; /** * Orders class * * @var BDFG_PreOrders_Orders */ public $orders; /** * Emails class * * @var BDFG_PreOrders_Emails */ public $emails; /** * Constructor */ private function __construct() { // Load textdomain add_action('plugins_loaded', array($this, 'load_textdomain')); // Check if WooCommerce is active if ($this->check_woocommerce()) { $this->includes(); $this->init(); // Register activation/deactivation hooks register_activation_hook(__FILE__, array('BDFG_PreOrders_Install', 'install')); register_deactivation_hook(__FILE__, array($this, 'deactivate')); } } /** * Get instance * * @return BDFG_PreOrders */ public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } /** * Load textdomain */ public function load_textdomain() { load_plugin_textdomain('bdfg-preorders', false, dirname(plugin_basename(__FILE__)) . '/languages'); } /** * Check if WooCommerce is active * * @return bool */ private function check_woocommerce() { if (in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) { return true; } // Check for WooCommerce if in a multisite if (is_multisite()) { $plugins = get_site_option('active_sitewide_plugins'); if (isset($plugins['woocommerce/woocommerce.php'])) { return true; } } // Show admin notice if WooCommerce is not active add_action('admin_notices', array($this, 'woocommerce_missing_notice')); return false; } /** * Include required files */ private function includes() { // Helper classes require_once BDFG_PREORDERS_PATH . 'includes/helpers/class-bdfg-preorders-helper.php'; // Core classes require_once BDFG_PREORDERS_PATH . 'includes/class-bdfg-preorders-admin.php'; require_once BDFG_PREORDERS_PATH . 'includes/class-bdfg-preorders-frontend.php'; require_once BDFG_PREORDERS_PATH . 'includes/class-bdfg-preorders-product.php'; require_once BDFG_PREORDERS_PATH . 'includes/class-bdfg-preorders-orders.php'; require_once BDFG_PREORDERS_PATH . 'includes/class-bdfg-preorders-emails.php'; require_once BDFG_PREORDERS_PATH . 'includes/class-bdfg-preorders-install.php'; // Template functions require_once BDFG_PREORDERS_PATH . 'includes/bdfg-preorders-template-functions.php'; } /** * Initialize classes */ private function init() { $this->admin = new BDFG_PreOrders_Admin(); if (!is_admin() || (defined('DOING_AJAX') && DOING_AJAX)) { $this->frontend = new BDFG_PreOrders_Frontend(); } $this->orders = new BDFG_PreOrders_Orders(); $this->emails = new BDFG_PreOrders_Emails(); // Add settings link add_filter('plugin_action_links_' . plugin_basename(__FILE__), array($this, 'add_plugin_action_links')); // Add plugin row meta add_filter('plugin_row_meta', array($this, 'add_plugin_row_meta'), 10, 2); } /** * Deactivate plugin */ public function deactivate() { // Remove scheduled events wp_clear_scheduled_hook('bdfg_preorders_check_availability'); } /** * Show admin notice if WooCommerce is not active */ public function woocommerce_missing_notice() { ?> <div class="error"> <p><?php echo sprintf( esc_html__('%1$s requires WooCommerce to be installed and active. You can download %2$s here.', 'bdfg-preorders'), '<strong>BDFG Pre-Orders for WooCommerce</strong>', '<a href="https://woocommerce.com/" target="_blank">WooCommerce</a>' ); ?></p> </div> <?php } /** * Add plugin action links * * @param array $links * @return array */ public function add_plugin_action_links($links) { $plugin_links = array( '<a href="' . admin_url('admin.php?page=bdfg-preorders-settings') . '">' . __('Settings', 'bdfg-preorders') . '</a>', '<a href="' . admin_url('admin.php?page=bdfg-preorders-management') . '">' . __('Manage Pre-Orders', 'bdfg-preorders') . '</a>' ); return array_merge($plugin_links, $links); } /** * Add plugin row meta * * @param array $links * @param string $file * @return array */ public function add_plugin_row_meta($links, $file) { if (BDFG_PREORDERS_BASENAME === $file) { $row_meta = array( 'docs' => '<a href="https://beiduofengou.net/docs/bdfg-preorders/" target="_blank" aria-label="' . esc_attr__('View BDFG Pre-Orders documentation', 'bdfg-preorders') . '">' . esc_html__('Docs', 'bdfg-preorders') . '</a>', 'support' => '<a href="https://beiduofengou.net/support/" target="_blank" aria-label="' . esc_attr__('Visit customer support', 'bdfg-preorders') . '">' . esc_html__('Support', 'bdfg-preorders') . '</a>' ); return array_merge($links, $row_meta); } return $links; } } // Initialize the plugin function BDFG_PreOrders() { return BDFG_PreOrders::get_instance(); } // Start the plugin BDFG_PreOrders();
includes/class-bdfg-preorders-admin.php
<?php /** * Admin functionality for BDFG Pre-Orders * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } /** * Admin class for handling backend operations */ class BDFG_PreOrders_Admin { /** * Constructor */ public function __construct() { // Add product tab add_filter('woocommerce_product_data_tabs', array($this, 'add_product_tab')); // Add product panel add_action('woocommerce_product_data_panels', array($this, 'add_product_panel')); // Save product data add_action('woocommerce_process_product_meta', array($this, 'save_product_data')); // Add variation specific fields add_action('woocommerce_product_after_variable_attributes', array($this, 'add_variation_preorder_fields'), 10, 3); // Save variation fields add_action('woocommerce_save_product_variation', array($this, 'save_variation_preorder_fields'), 10, 2); // Add settings page add_action('admin_menu', array($this, 'add_settings_page')); // Register settings add_action('admin_init', array($this, 'register_settings')); // Add custom order status to WooCommerce add_filter('wc_order_statuses', array($this, 'add_order_statuses')); // Add custom column to orders list add_filter('manage_edit-shop_order_columns', array($this, 'add_order_column')); add_action('manage_shop_order_posts_custom_column', array($this, 'populate_order_column'), 10, 2); // Add preorder filter for orders add_action('restrict_manage_posts', array($this, 'add_preorder_filter')); add_filter('request', array($this, 'filter_preorders')); // Admin dashboard widgets add_action('wp_dashboard_setup', array($this, 'add_dashboard_widgets')); // Load admin JS and CSS add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); // Add welcome page redirect after activation add_action('admin_init', array($this, 'welcome_page_redirect')); } /** * Add product tab * * @param array $tabs * @return array */ public function add_product_tab($tabs) { $tabs['bdfg_preorder'] = array( 'label' => __('Pre-Order', 'bdfg-preorders'), 'target' => 'bdfg_preorder_data', 'class' => array('show_if_simple', 'show_if_variable'), 'priority' => 21 ); return $tabs; } /** * Add product panel */ public function add_product_panel() { global $post; ?> <div id="bdfg_preorder_data" class="panel woocommerce_options_panel"> <div class="options_group bdfg-preorder-options"> <h4 class="bdfg-section-title"><?php esc_html_e('Pre-Order Settings', 'bdfg-preorders'); ?></h4> <?php woocommerce_wp_checkbox(array( 'id' => '_bdfg_preorder_enabled', 'label' => __('Enable Pre-Order', 'bdfg-preorders'), 'desc_tip' => true, 'description' => __('Enable pre-order functionality for this product.', 'bdfg-preorders') )); woocommerce_wp_text_input(array( 'id' => '_bdfg_preorder_available_date', 'label' => __('Available Date', 'bdfg-preorders'), 'desc_tip' => true, 'description' => __('The date when the product will be available.', 'bdfg-preorders'), 'type' => 'text', 'class' => 'date-picker short', 'custom_attributes' => array( 'placeholder' => _x('YYYY-MM-DD', 'placeholder', 'bdfg-preorders') ) )); woocommerce_wp_text_input(array( 'id' => '_bdfg_preorder_available_time', 'label' => __('Available Time', 'bdfg-preorders'), 'desc_tip' => true, 'description' => __('The time when the product will be available.', 'bdfg-preorders'), 'type' => 'time', 'class' => 'short', 'custom_attributes' => array( 'placeholder' => _x('HH:MM', 'placeholder', 'bdfg-preorders') ) )); ?> </div> <div class="options_group bdfg-preorder-pricing"> <h4 class="bdfg-section-title"><?php esc_html_e('Pre-Order Pricing', 'bdfg-preorders'); ?></h4> <?php woocommerce_wp_select(array( 'id' => '_bdfg_preorder_pricing_type', 'label' => __('Price Adjustment Type', 'bdfg-preorders'), 'desc_tip' => true, 'description' => __('Select how the pre-order price should be adjusted.', 'bdfg-preorders'), 'options' => array( '' => __('No adjustment', 'bdfg-preorders'), 'fixed' => __('Fixed amount', 'bdfg-preorders'), 'percentage' => __('Percentage', 'bdfg-preorders') ) )); woocommerce_wp_text_input(array( 'id' => '_bdfg_preorder_price_adjustment', 'label' => __('Price Adjustment', 'bdfg-preorders'), 'desc_tip' => true, 'description' => __('Enter an amount to adjust the price for pre-orders. Use a negative value for a discount.', 'bdfg-preorders'), 'type' => 'number', 'class' => 'short', 'custom_attributes' => array( 'step' => '0.01' ) )); ?> </div> <div class="options_group bdfg-preorder-display"> <h4 class="bdfg-section-title"><?php esc_html_e('Display Settings', 'bdfg-preorders'); ?></h4> <?php woocommerce_wp_text_input(array( 'id' => '_bdfg_preorder_button_text', 'label' => __('Button Text', 'bdfg-preorders'), 'desc_tip' => true, 'description' => __('Customize the pre-order button text. Leave empty to use the global setting.', 'bdfg-preorders'), 'type' => 'text', 'placeholder' => get_option('bdfg_preorders_button_text', __('Pre-Order Now', 'bdfg-preorders')) )); woocommerce_wp_textarea_input(array( 'id' => '_bdfg_preorder_custom_message', 'label' => __('Custom Message', 'bdfg-preorders'), 'desc_tip' => true, 'description' => __('Add a custom message to display on the product page for pre-orders.', 'bdfg-preorders'), 'placeholder' => __('e.g., "This is a limited-time pre-order!"', 'bdfg-preorders') )); ?> </div> <div class="options_group bdfg-preorder-advanced"> <h4 class="bdfg-section-title"><?php esc_html_e('Advanced Settings', 'bdfg-preorders'); ?></h4> <?php woocommerce_wp_select(array( 'id' => '_bdfg_preorder_stock_handling', 'label' => __('Stock Handling', 'bdfg-preorders'), 'desc_tip' => true, 'description' => __('Choose how stock should be handled for pre-orders.', 'bdfg-preorders'), 'options' => array( '' => __('Use global setting', 'bdfg-preorders'), 'normal' => __('Normal stock management', 'bdfg-preorders'), 'reserved' => __('Reserve stock for pre-orders', 'bdfg-preorders'), 'unlimited' => __('Unlimited pre-orders (ignore stock)', 'bdfg-preorders') ) )); woocommerce_wp_checkbox(array( 'id' => '_bdfg_preorder_enable_cancellations', 'label' => __('Allow Cancellations', 'bdfg-preorders'), 'desc_tip' => true, 'description' => __('Allow customers to cancel pre-orders before the release date.', 'bdfg-preorders') )); ?> </div> </div> <?php } /** * Save product data * * @param int $post_id */ public function save_product_data($post_id) { // Save preorder enabled status $preorder_enabled = isset($_POST['_bdfg_preorder_enabled']) ? 'yes' : 'no'; update_post_meta($post_id, '_bdfg_preorder_enabled', $preorder_enabled); // Save available date if (isset($_POST['_bdfg_preorder_available_date'])) { update_post_meta($post_id, '_bdfg_preorder_available_date', sanitize_text_field($_POST['_bdfg_preorder_available_date'])); } // Save available time if (isset($_POST['_bdfg_preorder_available_time'])) { update_post_meta($post_id, '_bdfg_preorder_available_time', sanitize_text_field($_POST['_bdfg_preorder_available_time'])); } // Save pricing type if (isset($_POST['_bdfg_preorder_pricing_type'])) { update_post_meta($post_id, '_bdfg_preorder_pricing_type', sanitize_text_field($_POST['_bdfg_preorder_pricing_type'])); } // Save price adjustment if (isset($_POST['_bdfg_preorder_price_adjustment'])) { update_post_meta($post_id, '_bdfg_preorder_price_adjustment', wc_format_decimal($_POST['_bdfg_preorder_price_adjustment'])); } // Save button text if (isset($_POST['_bdfg_preorder_button_text'])) { update_post_meta($post_id, '_bdfg_preorder_button_text', sanitize_text_field($_POST['_bdfg_preorder_button_text'])); } // Save custom message if (isset($_POST['_bdfg_preorder_custom_message'])) { update_post_meta($post_id, '_bdfg_preorder_custom_message', sanitize_textarea_field($_POST['_bdfg_preorder_custom_message'])); } // Save stock handling if (isset($_POST['_bdfg_preorder_stock_handling'])) { update_post_meta($post_id, '_bdfg_preorder_stock_handling', sanitize_text_field($_POST['_bdfg_preorder_stock_handling'])); } // Save cancellations setting $enable_cancellations = isset($_POST['_bdfg_preorder_enable_cancellations']) ? 'yes' : 'no'; update_post_meta($post_id, '_bdfg_preorder_enable_cancellations', $enable_cancellations); } /** * Add variation specific preorder fields * * @param int $loop * @param array $variation_data * @param WP_Post $variation */ public function add_variation_preorder_fields($loop, $variation_data, $variation) { $variation_id = $variation->ID; $enabled = get_post_meta($variation_id, '_bdfg_preorder_enabled', true); $date = get_post_meta($variation_id, '_bdfg_preorder_available_date', true); $time = get_post_meta($variation_id, '_bdfg_preorder_available_time', true); $adjustment = get_post_meta($variation_id, '_bdfg_preorder_price_adjustment', true); ?> <div class="bdfg-variation-preorder-fields"> <p class="form-row form-row-full"> <label><?php esc_html_e('Pre-Order Settings', 'bdfg-preorders'); ?></label> <span class="woocommerce-help-tip" data-tip="<?php esc_attr_e('Configure pre-order settings for this variation.', 'bdfg-preorders'); ?>"></span> </p> <p class="form-row form-row-first"> <label> <input type="checkbox" class="checkbox" name="_bdfg_variation_preorder_enabled[<?php echo esc_attr($loop); ?>]" <?php checked($enabled, 'yes'); ?> /> <?php esc_html_e('Enable Pre-Order', 'bdfg-preorders'); ?> </label> </p> <p class="form-row form-row-last"> <label><?php esc_html_e('Available Date', 'bdfg-preorders'); ?></label> <input type="text" class="short date-picker" name="_bdfg_variation_preorder_available_date[<?php echo esc_attr($loop); ?>]" value="<?php echo esc_attr($date); ?>" placeholder="<?php esc_attr_e('YYYY-MM-DD', 'bdfg-preorders'); ?>" /> </p> <p class="form-row form-row-first"> <label><?php esc_html_e('Available Time', 'bdfg-preorders'); ?></label> <input type="time" class="short" name="_bdfg_variation_preorder_available_time[<?php echo esc_attr($loop); ?>]" value="<?php echo esc_attr($time); ?>" /> </p> <p class="form-row form-row-last"> <label><?php esc_html_e('Price Adjustment', 'bdfg-preorders'); ?></label> <input type="number" class="short" name="_bdfg_variation_preorder_price_adjustment[<?php echo esc_attr($loop); ?>]" value="<?php echo esc_attr($adjustment); ?>" step="0.01" /> </p> </div> <?php } /** * Save variation preorder fields * * @param int $variation_id * @param int $i */ public function save_variation_preorder_fields($variation_id, $i) { // Save preorder enabled status for variation $enabled = isset($_POST['_bdfg_variation_preorder_enabled'][$i]) ? 'yes' : 'no'; update_post_meta($variation_id, '_bdfg_preorder_enabled', $enabled); // Save available date for variation if (isset($_POST['_bdfg_variation_preorder_available_date'][$i])) { update_post_meta($variation_id, '_bdfg_preorder_available_date', sanitize_text_field($_POST['_bdfg_variation_preorder_available_date'][$i])); } // Save available time for variation if (isset($_POST['_bdfg_variation_preorder_available_time'][$i])) { update_post_meta($variation_id, '_bdfg_preorder_available_time', sanitize_text_field($_POST['_bdfg_variation_preorder_available_time'][$i])); } // Save price adjustment for variation if (isset($_POST['_bdfg_variation_preorder_price_adjustment'][$i])) { update_post_meta($variation_id, '_bdfg_preorder_price_adjustment', wc_format_decimal($_POST['_bdfg_variation_preorder_price_adjustment'][$i])); } } /** * Add settings page */ public function add_settings_page() { add_submenu_page( 'woocommerce', __('BDFG Pre-Orders Settings', 'bdfg-preorders'), __('Pre-Orders', 'bdfg-preorders'), 'manage_woocommerce', 'bdfg-preorders-settings', array($this, 'render_settings_page') ); // Add management page for preorders add_submenu_page( 'woocommerce', __('Manage Pre-Orders', 'bdfg-preorders'), __('Manage Pre-Orders', 'bdfg-preorders'), 'manage_woocommerce', 'bdfg-preorders-management', array($this, 'render_management_page') ); } /** * Render settings page */ public function render_settings_page() { ?> <div class="wrap bdfg-preorders-settings-wrap"> <div class="bdfg-preorders-header"> <h1><?php echo esc_html__('BDFG Pre-Orders Settings', 'bdfg-preorders'); ?></h1> <div class="bdfg-preorders-logo"> <img src="<?php echo esc_url(BDFG_PREORDERS_URL . 'assets/images/bdfg-logo.png'); ?>" alt="Beiduofengou" /> </div> </div> <div class="bdfg-preorders-settings-content"> <form method="post" action="options.php"> <?php settings_fields('bdfg_preorders_settings'); ?> <div class="bdfg-preorders-settings-tabs"> <ul class="tabs-nav"> <li class="active"><a href="#general"><?php esc_html_e('General', 'bdfg-preorders'); ?></a></li> <li><a href="#display"><?php esc_html_e('Display', 'bdfg-preorders'); ?></a></li> <li><a href="#emails"><?php esc_html_e('Emails', 'bdfg-preorders'); ?></a></li> <li><a href="#advanced"><?php esc_html_e('Advanced', 'bdfg-preorders'); ?></a></li> </ul> <div class="tabs-content"> <!-- General Settings Tab --> <div id="general" class="tab-content active"> <h2><?php esc_html_e('General Settings', 'bdfg-preorders'); ?></h2> <table class="form-table"> <?php do_settings_fields('bdfg_preorders_settings', 'bdfg_preorders_general_section'); ?> </table> </div> <!-- Display Settings Tab --> <div id="display" class="tab-content"> <h2><?php esc_html_e('Display Settings', 'bdfg-preorders'); ?></h2> <table class="form-table"> <?php do_settings_fields('bdfg_preorders_settings', 'bdfg_preorders_appearance_section'); ?> </table> </div> <!-- Email Settings Tab --> <div id="emails" class="tab-content"> <h2><?php esc_html_e('Email Notification Settings', 'bdfg-preorders'); ?></h2> <table class="form-table"> <?php do_settings_fields('bdfg_preorders_settings', 'bdfg_preorders_email_section'); ?> </table> </div> <!-- Advanced Settings Tab --> <div id="advanced" class="tab-content"> <h2><?php esc_html_e('Advanced Settings', 'bdfg-preorders'); ?></h2> <table class="form-table"> <?php do_settings_fields('bdfg_preorders_settings', 'bdfg_preorders_advanced_section'); ?> </table> </div> </div> </div> <?php submit_button(); ?> </form> </div> <div class="bdfg-preorders-settings-sidebar"> <div class="bdfg-preorders-help-box"> <h3><?php esc_html_e('Need Help?', 'bdfg-preorders'); ?></h3> <p><?php esc_html_e('Check our documentation or contact support if you need assistance.', 'bdfg-preorders'); ?></p> <a href="https://beiduofengou.net/docs/bdfg-preorders/" class="button button-primary" target="_blank"> <?php esc_html_e('Documentation', 'bdfg-preorders'); ?> </a> <a href="https://beiduofengou.net/support/" class="button" target="_blank"> <?php esc_html_e('Contact Support', 'bdfg-preorders'); ?> </a> </div> <div class="bdfg-preorders-version-info"> <p><?php echo sprintf(esc_html__('Version: %s', 'bdfg-preorders'), BDFG_PREORDERS_VERSION); ?></p> <p><?php echo sprintf( esc_html__('Made with %s by %s', 'bdfg-preorders'), '<span class="dashicons dashicons-heart"></span>', '<a href="https://beiduofengou.net" target="_blank">Beiduofengou</a>' ); ?></p> </div> </div> </div> <?php } /** * Render management page for preorders */ public function render_management_page() { ?> <div class="wrap bdfg-preorders-management-wrap"> <div class="bdfg-preorders-header"> <h1><?php echo esc_html__('Manage Pre-Orders', 'bdfg-preorders'); ?></h1> </div> <?php // Include the preorder management list table require_once BDFG_PREORDERS_PATH . 'includes/admin/class-bdfg-preorders-list-table.php'; // Display filters ?> <div class="bdfg-preorders-filters"> <form method="get"> <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>" /> <?php // Create an instance of our list table $preorders_table = new BDFG_PreOrders_List_Table(); // Display the list table $preorders_table->prepare_items(); $preorders_table->search_box(__('Search Pre-Orders', 'bdfg-preorders'), 'bdfg-preorders-search'); $preorders_table->display(); ?> </form> </div> </div> <?php } /** * Register settings */ public function register_settings() { // General Settings register_setting('bdfg_preorders_settings', 'bdfg_preorders_stock_handling'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_payment_method'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_deposit_amount'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_enable_cancellations'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_cancellation_limit'); // Display Settings register_setting('bdfg_preorders_settings', 'bdfg_preorders_button_text'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_button_color'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_availability_text'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_show_countdown'); // Email Settings register_setting('bdfg_preorders_settings', 'bdfg_preorders_enable_emails'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_email_subject'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_email_heading'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_email_content'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_admin_notification'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_admin_email_subject'); // Advanced Settings register_setting('bdfg_preorders_settings', 'bdfg_preorders_delete_data_uninstall'); register_setting('bdfg_preorders_settings', 'bdfg_preorders_debug_mode'); // General Settings Section add_settings_section( 'bdfg_preorders_general_section', __('General Settings', 'bdfg-preorders'), array($this, 'render_general_section'), 'bdfg_preorders_settings' ); // Display Settings Section add_settings_section( 'bdfg_preorders_appearance_section', __('Display Settings', 'bdfg-preorders'), array($this, 'render_appearance_section'), 'bdfg_preorders_settings' ); // Email Settings Section add_settings_section( 'bdfg_preorders_email_section', __('Email Notification Settings', 'bdfg-preorders'), array($this, 'render_email_section'), 'bdfg_preorders_settings' ); // Advanced Settings Section add_settings_section( 'bdfg_preorders_advanced_section', __('Advanced Settings', 'bdfg-preorders'), array($this, 'render_advanced_section'), 'bdfg_preorders_settings' ); // General Settings Fields add_settings_field( 'bdfg_preorders_stock_handling', __('Stock Handling', 'bdfg-preorders'), array($this, 'render_select_field'), 'bdfg_preorders_settings', 'bdfg_preorders_general_section', array( 'id' => 'bdfg_preorders_stock_handling', 'default' => 'normal', 'options' => array( 'normal' => __('Normal stock management', 'bdfg-preorders'), 'reserved' => __('Reserve stock for pre-orders', 'bdfg-preorders'), 'unlimited' => __('Unlimited pre-orders (ignore stock)', 'bdfg-preorders') ), 'description' => __('Choose how stock should be handled for pre-orders.', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_payment_method', __('Payment Method', 'bdfg-preorders'), array($this, 'render_select_field'), 'bdfg_preorders_settings', 'bdfg_preorders_general_section', array( 'id' => 'bdfg_preorders_payment_method', 'default' => 'full', 'options' => array( 'full' => __('Full payment upfront', 'bdfg-preorders'), 'deposit' => __('Deposit only', 'bdfg-preorders'), 'authorize' => __('Authorize payment only (charge on release)', 'bdfg-preorders') ), 'description' => __('Choose how payments should be processed for pre-orders.', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_deposit_amount', __('Deposit Amount (%)', 'bdfg-preorders'), array($this, 'render_text_field'), 'bdfg_preorders_settings', 'bdfg_preorders_general_section', array( 'id' => 'bdfg_preorders_deposit_amount', 'default' => '50', 'description' => __('Percentage of product price to charge as deposit. Only applies if deposit payment method is selected.', 'bdfg-preorders'), 'type' => 'number', 'min' => '1', 'max' => '99' ) ); add_settings_field( 'bdfg_preorders_enable_cancellations', __('Allow Cancellations', 'bdfg-preorders'), array($this, 'render_checkbox_field'), 'bdfg_preorders_settings', 'bdfg_preorders_general_section', array( 'id' => 'bdfg_preorders_enable_cancellations', 'default' => 'yes', 'description' => __('Allow customers to cancel pre-orders before the release date.', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_cancellation_limit', __('Cancellation Time Limit (hours)', 'bdfg-preorders'), array($this, 'render_text_field'), 'bdfg_preorders_settings', 'bdfg_preorders_general_section', array( 'id' => 'bdfg_preorders_cancellation_limit', 'default' => '24', 'description' => __('Number of hours before release date after which cancellations are no longer allowed.', 'bdfg-preorders'), 'type' => 'number', 'min' => '0' ) ); // Display Settings Fields add_settings_field( 'bdfg_preorders_button_text', __('Pre-Order Button Text', 'bdfg-preorders'), array($this, 'render_text_field'), 'bdfg_preorders_settings', 'bdfg_preorders_appearance_section', array( 'id' => 'bdfg_preorders_button_text', 'default' => __('Pre-Order Now', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_button_color', __('Pre-Order Button Color', 'bdfg-preorders'), array($this, 'render_color_field'), 'bdfg_preorders_settings', 'bdfg_preorders_appearance_section', array( 'id' => 'bdfg_preorders_button_color', 'default' => '#3d9cd2' ) ); add_settings_field( 'bdfg_preorders_availability_text', __('Availability Text Format', 'bdfg-preorders'), array($this, 'render_text_field'), 'bdfg_preorders_settings', 'bdfg_preorders_appearance_section', array( 'id' => 'bdfg_preorders_availability_text', 'default' => __('Available on: %date%', 'bdfg-preorders'), 'description' => __('Use %date% as a placeholder for the availability date.', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_show_countdown', __('Show Countdown Timer', 'bdfg-preorders'), array($this, 'render_checkbox_field'), 'bdfg_preorders_settings', 'bdfg_preorders_appearance_section', array( 'id' => 'bdfg_preorders_show_countdown', 'default' => 'yes', 'description' => __('Display a countdown timer for upcoming pre-order releases.', 'bdfg-preorders') ) ); // Email Settings Fields add_settings_field( 'bdfg_preorders_enable_emails', __('Enable Customer Notifications', 'bdfg-preorders'), array($this, 'render_checkbox_field'), 'bdfg_preorders_settings', 'bdfg_preorders_email_section', array( 'id' => 'bdfg_preorders_enable_emails', 'default' => 'yes', 'description' => __('Send email notifications to customers when pre-ordered products become available.', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_email_subject', __('Email Subject', 'bdfg-preorders'), array($this, 'render_text_field'), 'bdfg_preorders_settings', 'bdfg_preorders_email_section', array( 'id' => 'bdfg_preorders_email_subject', 'default' => __('Your pre-ordered product is now available!', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_email_heading', __('Email Heading', 'bdfg-preorders'), array($this, 'render_text_field'), 'bdfg_preorders_settings', 'bdfg_preorders_email_section', array( 'id' => 'bdfg_preorders_email_heading', 'default' => __('Pre-order Available', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_email_content', __('Email Content', 'bdfg-preorders'), array($this, 'render_textarea_field'), 'bdfg_preorders_settings', 'bdfg_preorders_email_section', array( 'id' => 'bdfg_preorders_email_content', 'default' => __("Hello {customer_name},\n\nYour pre-ordered product '{product_name}' is now available.\n\nThank you for your patience and support!\n\nBeiduofengou Team", 'bdfg-preorders'), 'description' => __('Available placeholders: {customer_name}, {product_name}, {order_id}, {availability_date}', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_admin_notification', __('Enable Admin Notifications', 'bdfg-preorders'), array($this, 'render_checkbox_field'), 'bdfg_preorders_settings', 'bdfg_preorders_email_section', array( 'id' => 'bdfg_preorders_admin_notification', 'default' => 'yes', 'description' => __('Send email notifications to admin when pre-ordered products become available.', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_admin_email_subject', __('Admin Email Subject', 'bdfg-preorders'), array($this, 'render_text_field'), 'bdfg_preorders_settings', 'bdfg_preorders_email_section', array( 'id' => 'bdfg_preorders_admin_email_subject', 'default' => __('Pre-ordered product now available', 'bdfg-preorders') ) ); // Advanced Settings Fields add_settings_field( 'bdfg_preorders_delete_data_uninstall', __('Delete Plugin Data on Uninstall', 'bdfg-preorders'), array($this, 'render_checkbox_field'), 'bdfg_preorders_settings', 'bdfg_preorders_advanced_section', array( 'id' => 'bdfg_preorders_delete_data_uninstall', 'default' => 'no', 'description' => __('Delete all plugin data when uninstalling the plugin. This cannot be undone!', 'bdfg-preorders') ) ); add_settings_field( 'bdfg_preorders_debug_mode', __('Debug Mode', 'bdfg-preorders'), array($this, 'render_checkbox_field'), 'bdfg_preorders_settings', 'bdfg_preorders_advanced_section', array( 'id' => 'bdfg_preorders_debug_mode', 'default' => 'no', 'description' => __('Enable debug mode for troubleshooting. Only use when needed.', 'bdfg-preorders') ) ); } /** * Render section headers */ public function render_general_section() { echo '<p>' . esc_html__('Configure general settings for pre-orders.', 'bdfg-preorders') . '</p>'; } public function render_appearance_section() { echo '<p>' . esc_html__('Customize the appearance of pre-order elements.', 'bdfg-preorders') . '</p>'; } public function render_email_section() { echo '<p>' . esc_html__('Configure email notifications sent to customers when pre-ordered products become available.', 'bdfg-preorders') . '</p>'; } public function render_advanced_section() { echo '<p>' . esc_html__('Advanced settings for BDFG Pre-Orders. Only change these settings if you know what you are doing.', 'bdfg-preorders') . '</p>'; } /** * Render field types */ public function render_text_field($args) { $id = $args['id']; $default = isset($args['default']) ? $args['default'] : ''; $value = get_option($id, $default); $type = isset($args['type']) ? $args['type'] : 'text'; $min = isset($args['min']) ? 'min="' . esc_attr($args['min']) . '"' : ''; $max = isset($args['max']) ? 'max="' . esc_attr($args['max']) . '"' : ''; $description = isset($args['description']) ? $args['description'] : ''; echo '<input type="' . esc_attr($type) . '" id="' . esc_attr($id) . '" name="' . esc_attr($id) . '" value="' . esc_attr($value) . '" class="regular-text" ' . $min . ' ' . $max . ' />'; if ($description) { echo '<p class="description">' . esc_html($description) . '</p>'; } } public function render_color_field($args) { $id = $args['id']; $default = isset($args['default']) ? $args['default'] : '#000000'; $value = get_option($id, $default); echo '<input type="color" id="' . esc_attr($id) . '" name="' . esc_attr($id) . '" value="' . esc_attr($value) . '" class="bdfg-color-picker" />'; } public function render_checkbox_field($args) { $id = $args['id']; $default = isset($args['default']) ? $args['default'] : 'no'; $value = get_option($id, $default); $description = isset($args['description']) ? $args['description'] : ''; echo '<input type="checkbox" id="' . esc_attr($id) . '" name="' . esc_attr($id) . '" value="yes" ' . checked($value, 'yes', false) . ' />'; if ($description) { echo '<p class="description">' . esc_html($description) . '</p>'; } } public function render_select_field($args) { $id = $args['id']; $default = isset($args['default']) ? $args['default'] : ''; $value = get_option($id, $default); $options = isset($args['options']) ? $args['options'] : array(); $description = isset($args['description']) ? $args['description'] : ''; echo '<select id="' . esc_attr($id) . '" name="' . esc_attr($id) . '">'; foreach ($options as $option_value => $option_label) { echo '<option value="' . esc_attr($option_value) . '" ' . selected($value, $option_value, false) . '>' . esc_html($option_label) . '</option>'; } echo '</select>'; if ($description) { echo '<p class="description">' . esc_html($description) . '</p>'; } } public function render_textarea_field($args) { $id = $args['id']; $default = isset($args['default']) ? $args['default'] : ''; $value = get_option($id, $default); $description = isset($args['description']) ? $args['description'] : ''; echo '<textarea id="' . esc_attr($id) . '" name="' . esc_attr($id) . '" rows="5" class="large-text">' . esc_textarea($value) . '</textarea>'; if ($description) { echo '<p class="description">' . esc_html($description) . '</p>'; } } /** * Add custom order status * * @param array $order_statuses * @return array */ public function add_order_statuses($order_statuses) { $new_statuses = array(); // Add preordered status after processing foreach ($order_statuses as $key => $status) { $new_statuses[$key] = $status; if ($key === 'wc-processing') { $new_statuses['wc-preordered'] = __('Pre-ordered', 'bdfg-preorders'); } } return $new_statuses; } /** * Add custom column to orders list * * @param array $columns * @return array */ public function add_order_column($columns) { $new_columns = array(); foreach ($columns as $key => $column) { $new_columns[$key] = $column; if ($key === 'order_status') { $new_columns['bdfg_preorder'] = __('Pre-Order', 'bdfg-preorders'); } } return $new_columns; } /** * Populate custom order column * * @param string $column * @param int $order_id */ public function populate_order_column($column, $order_id) { if ($column === 'bdfg_preorder') { $order = wc_get_order($order_id); $has_preorder = false; $available_date = ''; $closest_date = null; // Check each line item for preorder products foreach ($order->get_items() as $item) { $product_id = $item->get_product_id(); $variation_id = $item->get_variation_id(); $target_id = $variation_id ? $variation_id : $product_id; if (BDFG_PreOrders_Product::is_preorder($target_id)) { $has_preorder = true; $date = get_post_meta($target_id, '_bdfg_preorder_available_date', true); $time = get_post_meta($target_id, '_bdfg_preorder_available_time', true); if ($date) { // Format the full date/time $temp_date = $date; if ($time) { $temp_date .= ' ' . $time; } // Track the earliest release date if (is_null($closest_date) || strtotime($temp_date) < strtotime($closest_date)) { $closest_date = $temp_date; $available_date = $temp_date; } } } } // Display preorder status if applicable if ($has_preorder) { echo '<mark class="preorder"><span>' . esc_html__('Pre-Order', 'bdfg-preorders') . '</span></mark>'; if ($available_date) { $formatted_date = BDFG_PreOrders_Helper::format_date($available_date); echo '<br><small>' . esc_html__('Available on:', 'bdfg-preorders') . ' ' . esc_html($formatted_date) . '</small>'; // Show status indicator $current_time = current_time('timestamp'); $release_time = strtotime($available_date); if ($release_time < $current_time) { echo ' <span class="bdfg-status-indicator released" title="' . esc_attr__('Released', 'bdfg-preorders') . '"></span>'; } else { echo ' <span class="bdfg-status-indicator pending" title="' . esc_attr__('Pending Release', 'bdfg-preorders') . '"></span>'; } } } } } /** * Add preorder filter for orders * * @param string $post_type */ public function add_preorder_filter($post_type) { global $pagenow; if ($pagenow === 'edit.php' && $post_type === 'shop_order') { $current = isset($_GET['bdfg_preorder_filter']) ? sanitize_text_field($_GET['bdfg_preorder_filter']) : ''; ?> <select name="bdfg_preorder_filter"> <option value=""><?php esc_html_e('All Orders', 'bdfg-preorders'); ?></option> <option value="preorder" <?php selected($current, 'preorder'); ?>><?php esc_html_e('Pre-Orders Only', 'bdfg-preorders'); ?></option> <option value="pending_release" <?php selected($current, 'pending_release'); ?>><?php esc_html_e('Pending Release', 'bdfg-preorders'); ?></option> <option value="released" <?php selected($current, 'released'); ?>><?php esc_html_e('Released Pre-Orders', 'bdfg-preorders'); ?></option> </select> <?php } } /** * Filter preorders in the admin order list * * @param array $vars * @return array */ public function filter_preorders($vars) { global $pagenow, $typenow; if ($pagenow === 'edit.php' && $typenow === 'shop_order' && isset($_GET['bdfg_preorder_filter']) && !empty($_GET['bdfg_preorder_filter'])) { $filter_value = sanitize_text_field($_GET['bdfg_preorder_filter']); // Set up meta query if ($filter_value === 'preorder') { $vars['meta_key'] = '_bdfg_has_preorder'; $vars['meta_value'] = 'yes'; } elseif ($filter_value === 'pending_release' || $filter_value === 'released') { $vars['meta_key'] = '_bdfg_has_preorder'; $vars['meta_value'] = 'yes'; // This is simplified - would need custom filtering logic based on release dates in a real implementation if ($filter_value === 'pending_release') { $vars['post_status'] = 'wc-preordered'; } elseif ($filter_value === 'released') { $vars['post_status'] = array('wc-completed', 'wc-processing'); } } } return $vars; } /** * Add dashboard widgets */ public function add_dashboard_widgets() { // Only add if user has WooCommerce permissions if (current_user_can('manage_woocommerce')) { wp_add_dashboard_widget( 'bdfg_preorders_dashboard_widget', __('BDFG Pre-Orders Summary', 'bdfg-preorders'), array($this, 'render_dashboard_widget') ); } } /** * Render dashboard widget */ public function render_dashboard_widget() { global $wpdb; $table_name = $wpdb->prefix . 'bdfg_preorders'; $today = date('Y-m-d'); $thirty_days = date('Y-m-d', strtotime('+30 days')); // Get counts $total_preorders = $wpdb->get_var("SELECT COUNT(*) FROM $table_name"); $pending_release = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'pending'"); $upcoming_releases = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE status = 'pending' AND available_date BETWEEN %s AND %s", $today, $thirty_days )); // Get upcoming releases $upcoming = $wpdb->get_results($wpdb->prepare( "SELECT p.ID, p.post_title, po.available_date FROM $table_name po JOIN {$wpdb->posts} p ON p.ID = po.product_id WHERE po.status = 'pending' AND po.available_date BETWEEN %s AND %s ORDER BY po.available_date ASC LIMIT 5", $today, $thirty_days )); ?> <div class="bdfg-preorders-widget"> <div class="bdfg-preorders-stats"> <div class="bdfg-stat-item"> <span class="bdfg-stat-value"><?php echo esc_html($total_preorders); ?></span> <span class="bdfg-stat-label"><?php esc_html_e('Total Pre-Orders', 'bdfg-preorders'); ?></span> </div> <div class="bdfg-stat-item"> <span class="bdfg-stat-value"><?php echo esc_html($pending_release); ?></span> <span class="bdfg-stat-label"><?php esc_html_e('Pending Release', 'bdfg-preorders'); ?></span> </div> <div class="bdfg-stat-item"> <span class="bdfg-stat-value"><?php echo esc_html($upcoming_releases); ?></span> <span class="bdfg-stat-label"><?php esc_html_e('Upcoming (30 days)', 'bdfg-preorders'); ?></span> </div> </div> <?php if (!empty($upcoming)) : ?> <h4><?php esc_html_e('Upcoming Releases', 'bdfg-preorders'); ?></h4> <ul class="bdfg-upcoming-releases"> <?php foreach ($upcoming as $product) : ?> <li> <span class="bdfg-release-date"> <?php echo esc_html(BDFG_PreOrders_Helper::format_date($product->available_date)); ?> </span> <a href="<?php echo esc_url(get_edit_post_link($product->ID)); ?>" class="bdfg-product-title"> <?php echo esc_html($product->post_title); ?> </a> </li> <?php endforeach; ?> </ul> <?php endif; ?> <p class="bdfg-widget-footer"> <a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-preorders-management')); ?>"> <?php esc_html_e('Manage Pre-Orders', 'bdfg-preorders'); ?> → </a> </p> </div> <?php } /** * Welcome page redirect */ public function welcome_page_redirect() { // Check if we need to show the welcome page if (get_transient('bdfg_preorders_activation_redirect')) { delete_transient('bdfg_preorders_activation_redirect'); // Only redirect if not already on the page and not doing AJAX if (!isset($_GET['page']) || $_GET['page'] !== 'bdfg-preorders-welcome' && !wp_doing_ajax()) { wp_safe_redirect(admin_url('admin.php?page=bdfg-preorders-settings&tab=welcome')); exit; } } } /** * Load admin scripts and styles * * @param string $hook */ public function enqueue_admin_scripts($hook) { $screen = get_current_screen(); // Only load on product edit page, our settings page, or orders page if (($hook === 'post.php' || $hook === 'post-new.php') && $screen->post_type === 'product' || strpos($hook, 'bdfg-preorders') !== false || ($screen->base === 'edit' && $screen->post_type === 'shop_order')) { wp_enqueue_style('bdfg-preorders-admin', BDFG_PREORDERS_URL . 'assets/css/admin.css', array(), BDFG_PREORDERS_VERSION); wp_enqueue_script('bdfg-preorders-admin', BDFG_PREORDERS_URL . 'assets/js/admin.js', array('jquery', 'jquery-ui-datepicker'), BDFG_PREORDERS_VERSION, true); // Add localization for datepicker wp_localize_script('bdfg-preorders-admin', 'bdfg_preorders', array( 'date_format' => _x('yy-mm-dd', 'jQuery date format', 'bdfg-preorders'), 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('bdfg_preorders_admin_nonce'), 'i18n' => array( 'confirm_delete' => __('Are you sure you want to delete this pre-order? This cannot be undone.', 'bdfg-preorders'), 'error' => __('An error occurred. Please try again.', 'bdfg-preorders'), 'success' => __('Operation completed successfully.', 'bdfg-preorders') ) )); } } }
includes/class-bdfg-preorders-frontend.php
相关文章: WordPress 智能缓存管理
<?php /** * Frontend functionality for BDFG Pre-Orders * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } /** * Frontend class for handling customer-facing operations */ class BDFG_PreOrders_Frontend { /** * Constructor */ public function __construct() { // Modify add to cart button add_filter('woocommerce_product_single_add_to_cart_text', array($this, 'change_add_to_cart_text'), 10, 2); add_filter('woocommerce_product_add_to_cart_text', array($this, 'change_add_to_cart_text'), 10, 2); // Modify product price display add_filter('woocommerce_get_price_html', array($this, 'modify_price_display'), 10, 2); // Add availability info add_action('woocommerce_before_add_to_cart_button', array($this, 'add_availability_info')); add_action('woocommerce_before_add_to_cart_form', array($this, 'add_preorder_badge')); // Add custom message add_action('woocommerce_before_single_product_summary', array($this, 'add_custom_message'), 15); // Add countdown timer add_action('woocommerce_before_add_to_cart_form', array($this, 'add_countdown_timer')); // Modify cart item data add_filter('woocommerce_add_cart_item_data', array($this, 'add_preorder_cart_item_data'), 10, 3); add_filter('woocommerce_get_item_data', array($this, 'add_preorder_cart_item_meta'), 10, 2); // Modify cart and checkout totals add_action('woocommerce_cart_totals_before_order_total', array($this, 'add_preorder_cart_totals')); add_action('woocommerce_review_order_before_order_total', array($this, 'add_preorder_cart_totals')); // Add custom styles and scripts add_action('wp_enqueue_scripts', array($this, 'enqueue_styles')); // Add variation data add_filter('woocommerce_available_variation', array($this, 'add_variation_data'), 10, 3); // Add account actions for preorder management add_filter('woocommerce_my_account_my_orders_actions', array($this, 'add_order_actions'), 10, 2); // Add my account endpoint for preorder management add_action('init', array($this, 'add_preorders_endpoint')); add_filter('woocommerce_account_menu_items', array($this, 'add_preorders_menu_item')); add_action('woocommerce_account_preorders_endpoint', array($this, 'preorders_content')); // AJAX handlers for customer actions add_action('wp_ajax_bdfg_preorders_cancel', array($this, 'cancel_preorder_ajax')); } /** * Modify add to cart button text * * @param string $text * @param WC_Product $product * @return string */ public function change_add_to_cart_text($text, $product) { if (BDFG_PreOrders_Product::is_preorder($product->get_id())) { $custom_text = get_post_meta($product->get_id(), '_bdfg_preorder_button_text', true); if (!empty($custom_text)) { return $custom_text; } return get_option('bdfg_preorders_button_text', __('Pre-Order Now', 'bdfg-preorders')); } return $text; } /** * Modify price display * * @param string $price_html * @param WC_Product $product * @return string */ public function modify_price_display($price_html, $product) { if (BDFG_PreOrders_Product::is_preorder($product->get_id())) { $pricing_type = get_post_meta($product->get_id(), '_bdfg_preorder_pricing_type', true); $adjustment = get_post_meta($product->get_id(), '_bdfg_preorder_price_adjustment', true); if (!empty($pricing_type) && !empty($adjustment) && $adjustment != 0) { $regular_price = $product->get_regular_price(); if ($pricing_type === 'fixed') { $adjusted_price = $regular_price + $adjustment; } else { // percentage $adjusted_price = $regular_price * (1 + ($adjustment / 100)); } if ($adjusted_price < 0) { $adjusted_price = 0; } // For preorder products, add custom price label $price_html = '<span class="bdfg-preorder-price">' . wc_price($adjusted_price) . '</span>'; // Show original price and discount/premium indicators if ($adjustment < 0) { // Show discount $price_html .= ' <span class="bdfg-preorder-original-price"><del>' . wc_price($regular_price) . '</del></span>'; if ($pricing_type === 'fixed') { $discount_text = wc_price(abs($adjustment)) . ' ' . __('off', 'bdfg-preorders'); } else { $discount_text = abs($adjustment) . '% ' . __('off', 'bdfg-preorders'); } $price_html .= ' <span class="bdfg-preorder-discount">(' . $discount_text . ')</span>'; } elseif ($adjustment > 0) { // Show premium $price_html .= ' <span class="bdfg-preorder-original-price"><del>' . wc_price($regular_price) . '</del></span>'; if ($pricing_type === 'fixed') { $premium_text = wc_price($adjustment) . ' ' . __('premium', 'bdfg-preorders'); } else { $premium_text = $adjustment . '% ' . __('premium', 'bdfg-preorders'); } $price_html .= ' <span class="bdfg-preorder-premium">(' . $premium_text . ')</span>'; } return $price_html; } // Add preorder label even if no price adjustment return '<span class="bdfg-preorder-price">' . $price_html . '</span>'; } return $price_html; } /** * Add preorder badge to product */ public function add_preorder_badge() { global $product; if (!$product || !BDFG_PreOrders_Product::is_preorder($product->get_id())) { return; } echo '<span class="bdfg-preorder-badge">' . esc_html__('Pre-Order', 'bdfg-preorders') . '</span>'; } /** * Add availability info */ public function add_availability_info() { global $product; if (!$product || !BDFG_PreOrders_Product::is_preorder($product->get_id())) { return; } $date = get_post_meta($product->get_id(), '_bdfg_preorder_available_date', true); $time = get_post_meta($product->get_id(), '_bdfg_preorder_available_time', true); if (empty($date)) { return; } $formatted_date = date_i18n(get_option('date_format'), strtotime($date)); if (!empty($time)) { $formatted_date .= ' ' . date_i18n(get_option('time_format'), strtotime($time)); } $availability_text = get_option('bdfg_preorders_availability_text', __('Available on: %date%', 'bdfg-preorders')); $availability_text = str_replace('%date%', $formatted_date, $availability_text); echo '<div class="bdfg-preorder-availability">' . esc_html($availability_text) . '</div>'; } /** * Add custom message to product page */ public function add_custom_message() { global $product; if (!$product || !BDFG_PreOrders_Product::is_preorder($product->get_id())) { return; } $custom_message = get_post_meta($product->get_id(), '_bdfg_preorder_custom_message', true); if (!empty($custom_message)) { echo '<div class="bdfg-preorder-message">' . wp_kses_post(wpautop($custom_message)) . '</div>'; } } /** * Add countdown timer */ public function add_countdown_timer() { global $product; if (!$product || !BDFG_PreOrders_Product::is_preorder($product->get_id())) { return; } // Check if countdown timer is enabled if (get_option('bdfg_preorders_show_countdown', 'yes') !== 'yes') { return; } $date = get_post_meta($product->get_id(), '_bdfg_preorder_available_date', true); $time = get_post_meta($product->get_id(), '_bdfg_preorder_available_time', true); if (empty($date)) { return; } $datetime = $date; if (!empty($time)) { $datetime .= ' ' . $time; } else { $datetime .= ' 00:00:00'; } $target_time = strtotime($datetime); $current_time = current_time('timestamp'); // Only show countdown if not yet released if ($target_time > $current_time) { $countdown_data = array( 'year' => date('Y', $target_time), 'month' => date('m', $target_time), 'day' => date('d', $target_time), 'hour' => date('H', $target_time), 'minute' => date('i', $target_time), 'second' => date('s', $target_time) ); echo '<div class="bdfg-preorder-countdown" data-countdown="' . esc_attr(json_encode($countdown_data)) . '">'; echo '<div class="countdown-label">' . esc_html__('Available in:', 'bdfg-preorders') . '</div>'; echo '<div class="countdown-timer">'; echo '<span class="days">00</span><span class="countdown-sep">:</span>'; echo '<span class="hours">00</span><span class="countdown-sep">:</span>'; echo '<span class="minutes">00</span><span class="countdown-sep">:</span>'; echo '<span class="seconds">00</span>'; echo '</div>'; echo '<div class="countdown-labels">'; echo '<span class="days-label">' . esc_html__('Days', 'bdfg-preorders') . '</span> '; echo '<span class="hours-label">' . esc_html__('Hours', 'bdfg-preorders') . '</span> '; echo '<span class="minutes-label">' . esc_html__('Min', 'bdfg-preorders') . '</span> '; echo '<span class="seconds-label">' . esc_html__('Sec', 'bdfg-preorders') . '</span>'; echo '</div>'; echo '</div>'; } } /** * Add preorder data to cart item * * @param array $cart_item_data * @param int $product_id * @param int $variation_id * @return array */ public function add_preorder_cart_item_data($cart_item_data, $product_id, $variation_id) { $target_id = $variation_id ? $variation_id : $product_id; if (BDFG_PreOrders_Product::is_preorder($target_id)) { $cart_item_data['bdfg_preorder'] = array( 'is_preorder' => true, 'available_date' => BDFG_PreOrders_Product::get_available_date($target_id) ); } return $cart_item_data; } /** * Add preorder info to cart item meta * * @param array $item_data * @param array $cart_item * @return array */ public function add_preorder_cart_item_meta($item_data, $cart_item) { if (isset($cart_item['bdfg_preorder']) && $cart_item['bdfg_preorder']['is_preorder']) { $available_date = $cart_item['bdfg_preorder']['available_date']; if ($available_date) { $formatted_date = BDFG_PreOrders_Helper::format_date($available_date); $item_data[] = array( 'key' => __('Pre-order', 'bdfg-preorders'), 'value' => sprintf(__('Available on %s', 'bdfg-preorders'), $formatted_date), 'display' => '' ); } else { $item_data[] = array( 'key' => __('Pre-order', 'bdfg-preorders'), 'value' => __('This is a pre-order item', 'bdfg-preorders'), 'display' => '' ); } } return $item_data; } /** * Add preorder totals to cart */ public function add_preorder_cart_totals() { $cart = WC()->cart; $has_preorder = false; $total_preorder = 0; // Check if the cart contains preorders foreach ($cart->get_cart() as $cart_item) { if (isset($cart_item['bdfg_preorder']) && $cart_item['bdfg_preorder']['is_preorder']) { $has_preorder = true; $total_preorder += $cart_item['line_total']; } } if ($has_preorder) { // Display preorder subtotal ?> <tr class="bdfg-preorder-total"> <th><?php esc_html_e('Pre-Order Total', 'bdfg-preorders'); ?></th> <td data-title="<?php esc_attr_e('Pre-Order Total', 'bdfg-preorders'); ?>"> <?php echo wc_price($total_preorder); ?> </td> </tr> <?php } } /** * Load frontend styles and scripts */ public function enqueue_styles() { // Load CSS for the frontend wp_enqueue_style('bdfg-preorders-frontend', BDFG_PREORDERS_URL . 'assets/css/frontend.css', array(), BDFG_PREORDERS_VERSION); // Load JavaScript for the frontend wp_enqueue_script('bdfg-preorders-frontend', BDFG_PREORDERS_URL . 'assets/js/frontend.js', array('jquery'), BDFG_PREORDERS_VERSION, true); // Add dynamic styles $button_color = get_option('bdfg_preorders_button_color', '#3d9cd2'); $custom_css = " .single_add_to_cart_button.bdfg-preorder-button { background-color: {$button_color} !important; border-color: {$button_color} !important; } .bdfg-preorder-badge { background-color: {$button_color}; } "; wp_add_inline_style('bdfg-preorders-frontend', $custom_css); // Localize script for the frontend wp_localize_script('bdfg-preorders-frontend', 'bdfg_preorders_params', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('bdfg_preorders_nonce'), 'i18n' => array( 'days' => __('Days', 'bdfg-preorders'), 'hours' => __('Hours', 'bdfg-preorders'), 'minutes' => __('Minutes', 'bdfg-preorders'), 'seconds' => __('Seconds', 'bdfg-preorders') ) )); } /** * Add variation data * * @param array $data * @param WC_Product $product * @param WC_Product_Variation $variation * @return array */ public function add_variation_data($data, $product, $variation) { if (BDFG_PreOrders_Product::is_preorder($variation->get_id())) { $data['is_preorder'] = true; $date = get_post_meta($variation->get_id(), '_bdfg_preorder_available_date', true); $time = get_post_meta($variation->get_id(), '_bdfg_preorder_available_time', true); if (!empty($date)) { $formatted_date = date_i18n(get_option('date_format'), strtotime($date)); if (!empty($time)) { $formatted_date .= ' ' . date_i18n(get_option('time_format'), strtotime($time)); } $availability_text = get_option('bdfg_preorders_availability_text', __('Available on: %date%', 'bdfg-preorders')); $availability_text = str_replace('%date%', $formatted_date, $availability_text); $data['availability_html'] = '<div class="bdfg-preorder-availability">' . esc_html($availability_text) . '</div>'; } $custom_text = get_post_meta($variation->get_id(), '_bdfg_preorder_button_text', true); if (!empty($custom_text)) { $data['button_text'] = $custom_text; } else { $data['button_text'] = get_option('bdfg_preorders_button_text', __('Pre-Order Now', 'bdfg-preorders')); } } return $data; } /** * Add endpoint for my account page */ public function add_preorders_endpoint() { add_rewrite_endpoint('preorders', EP_ROOT | EP_PAGES); } /** * Add menu item to my account page * * @param array $items * @return array */ public function add_preorders_menu_item($items) { // Add the preorders item after the orders item $new_items = array(); foreach ($items as $key => $value) { $new_items[$key] = $value; if ($key === 'orders') { $new_items['preorders'] = __('Pre-Orders', 'bdfg-preorders'); } } return $new_items; } /** * Display preorders content on my account page */ public function preorders_content() { $customer_orders = $this->get_customer_preorders(); if ($customer_orders) { ?> <table class="woocommerce-orders-table woocommerce-MyAccount-orders shop_table shop_table_responsive my_account_orders account-orders-table"> <thead> <tr> <th class="woocommerce-orders-table__header woocommerce-orders-table__header-order-number"> <span class="nobr"><?php esc_html_e('Order', 'bdfg-preorders'); ?></span> </th> <th class="woocommerce-orders-table__header woocommerce-orders-table__header-order-date"> <span class="nobr"><?php esc_html_e('Date', 'bdfg-preorders'); ?></span> </th> <th class="woocommerce-orders-table__header woocommerce-orders-table__header-order-status"> <span class="nobr"><?php esc_html_e('Status', 'bdfg-preorders'); ?></span> </th> <th class="woocommerce-orders-table__header woocommerce-orders-table__header-preorder-release"> <span class="nobr"><?php esc_html_e('Release Date', 'bdfg-preorders'); ?></span> </th> <th class="woocommerce-orders-table__header woocommerce-orders-table__header-order-actions"> <span class="nobr"><?php esc_html_e('Actions', 'bdfg-preorders'); ?></span> </th> </tr> </thead> <tbody> <?php foreach ($customer_orders as $customer_order) : ?> <?php $order = wc_get_order($customer_order->ID); $item_count = $order->get_item_count(); $release_date = $this->get_preorder_release_date($order); ?> <tr class="woocommerce-orders-table__row order"> <td class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-number" data-title="<?php esc_attr_e('Order', 'bdfg-preorders'); ?>"> <a href="<?php echo esc_url($order->get_view_order_url()); ?>"> <?php echo esc_html(_x('#', 'hash before order number', 'bdfg-preorders') . $order->get_order_number()); ?> </a> </td> <td class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-date" data-title="<?php esc_attr_e('Date', 'bdfg-preorders'); ?>"> <time datetime="<?php echo esc_attr($order->get_date_created()->date('c')); ?>"><?php echo esc_html(wc_format_datetime($order->get_date_created())); ?></time> </td> <td class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-status" data-title="<?php esc_attr_e('Status', 'bdfg-preorders'); ?>"> <?php echo esc_html(wc_get_order_status_name($order->get_status())); ?> </td> <td class="woocommerce-orders-table__cell woocommerce-orders-table__cell-preorder-release" data-title="<?php esc_attr_e('Release Date', 'bdfg-preorders'); ?>"> <?php echo !empty($release_date) ? esc_html(BDFG_PreOrders_Helper::format_date($release_date)) : esc_html__('To be announced', 'bdfg-preorders'); ?> </td> <td class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-actions" data-title="<?php esc_attr_e('Actions', 'bdfg-preorders'); ?>"> <?php $actions = $this->get_preorder_actions($order); if (!empty($actions)) { foreach ($actions as $key => $action) { echo '<a href="' . esc_url($action['url']) . '" class="woocommerce-button button ' . sanitize_html_class($key) . '">' . esc_html($action['name']) . '</a>'; } } ?> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php } else { ?> <div class="woocommerce-message woocommerce-message--info woocommerce-Message woocommerce-Message--info woocommerce-info"> <a class="woocommerce-Button button" href="<?php echo esc_url(apply_filters('woocommerce_return_to_shop_redirect', wc_get_page_permalink('shop'))); ?>"> <?php esc_html_e('Browse products', 'bdfg-preorders'); ?> </a> <?php esc_html_e('No pre-orders have been placed yet.', 'bdfg-preorders'); ?> </div> <?php } } /** * Get customer preorders * * @return array */ private function get_customer_preorders() { $customer_id = get_current_user_id(); if (!$customer_id) { return array(); } $args = array( 'meta_key' => '_bdfg_has_preorder', 'meta_value' => 'yes', 'post_type' => 'shop_order', 'post_status' => array_keys(wc_get_order_statuses()), 'numberposts' => -1, 'meta_query' => array( array( 'key' => '_customer_user', 'value' => $customer_id, 'compare' => '=' ) ) ); return get_posts($args); } /** * Get preorder release date * * @param WC_Order $order * @return string|false */ private function get_preorder_release_date($order) { $earliest_date = false; foreach ($order->get_items() as $item) { if ($item->get_meta('_bdfg_is_preorder') === 'yes') { $date = $item->get_meta('_bdfg_preorder_available_date'); $time = $item->get_meta('_bdfg_preorder_available_time'); if ($date) { $datetime = $date; if ($time) { $datetime .= ' ' . $time; } if (!$earliest_date || strtotime($datetime) < strtotime($earliest_date)) { $earliest_date = $datetime; } } } } return $earliest_date; } /** * Add order actions * * @param array $actions * @param WC_Order $order * @return array */ public function add_order_actions($actions, $order) { // Only process if this is a preorder if ($order->get_meta('_bdfg_has_preorder') !== 'yes') { return $actions; } // Get custom actions $custom_actions = $this->get_preorder_actions($order); // Merge with existing actions return array_merge($actions, $custom_actions); } /** * Get preorder actions * * @param WC_Order $order * @return array */ private function get_preorder_actions($order) { $actions = array(); $release_date = $this->get_preorder_release_date($order); // Only allow cancellation if enabled globally and for this product if (get_option('bdfg_preorders_enable_cancellations', 'yes') === 'yes' && $order->has_status('preordered')) { // Check if we're before the cancellation limit if ($release_date) { $cancellation_limit = get_option('bdfg_preorders_cancellation_limit', '24'); $limit_time = strtotime($release_date) - ($cancellation_limit * HOUR_IN_SECONDS); if (current_time('timestamp') < $limit_time) { $actions['cancel-preorder'] = array( 'url' => wp_nonce_url( add_query_arg( array( 'action' => 'bdfg_cancel_preorder', 'order_id' => $order->get_id() ), wc_get_endpoint_url('preorders') ), 'bdfg_cancel_preorder_' . $order->get_id() ), 'name' => __('Cancel Pre-order', 'bdfg-preorders') ); } } else { // If no release date is set, always allow cancellation $actions['cancel-preorder'] = array( 'url' => wp_nonce_url( add_query_arg( array( 'action' => 'bdfg_cancel_preorder', 'order_id' => $order->get_id() ), wc_get_endpoint_url('preorders') ), 'bdfg_cancel_preorder_' . $order->get_id() ), 'name' => __('Cancel Pre-order', 'bdfg-preorders') ); } } return $actions; } /** * Cancel preorder via AJAX */ public function cancel_preorder_ajax() { check_ajax_referer('bdfg_preorders_nonce', 'nonce'); $order_id = isset($_POST['order_id']) ? absint($_POST['order_id']) : 0; if (!$order_id) { wp_send_json_error(array('message' => __('Invalid order ID.', 'bdfg-preorders'))); } $order = wc_get_order($order_id); if (!$order || $order->get_customer_id() !== get_current_user_id()) { wp_send_json_error(array('message' => __('You do not have permission to cancel this pre-order.', 'bdfg-preorders'))); } if ($order->get_meta('_bdfg_has_preorder') !== 'yes' || !$order->has_status('preordered')) { wp_send_json_error(array('message' => __('This order cannot be cancelled.', 'bdfg-preorders'))); } // Process cancellation $order->update_status('cancelled', __('Pre-order cancelled by customer.', 'bdfg-preorders')); // Update preorder database records global $wpdb; $table_name = $wpdb->prefix . 'bdfg_preorders'; $wpdb->update( $table_name, array( 'status' => 'cancelled', 'updated_at' => current_time('mysql') ), array('order_id' => $order_id), array('%s', '%s'), array('%d') ); wp_send_json_success(array( 'message' => __('Your pre-order has been cancelled.', 'bdfg-preorders'), 'redirect' => wc_get_endpoint_url('preorders') )); } }
includes/class-bdfg-preorders-orders.php
<?php /** * Order handling for BDFG Pre-Orders * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } /** * Orders class for handling order-related operations */ class BDFG_PreOrders_Orders { /** * Constructor */ public function __construct() { // Add preorder data to order items add_action('woocommerce_checkout_create_order_line_item', array($this, 'add_preorder_data_to_order_items'), 10, 4); // Process preorder after order is placed add_action('woocommerce_checkout_order_processed', array($this, 'process_preorder'), 10, 3); // Add preorder label to order items add_action('woocommerce_before_order_itemmeta', array($this, 'add_preorder_order_item_label'), 10, 2); // Add preorder details to order emails add_action('woocommerce_email_order_details', array($this, 'add_preorder_details_to_emails'), 10, 4); // Add preorder details to order page add_action('woocommerce_order_details_after_order_table', array($this, 'add_preorder_details_to_order_page')); // Schedule availability check add_action('init', array($this, 'schedule_availability_check')); add_action('bdfg_preorders_check_availability', array($this, 'check_preorder_availability')); // Handle status transitions add_action('woocommerce_order_status_changed', array($this, 'handle_order_status_change'), 10, 4); // Register custom order statuses add_action('init', array($this, 'register_custom_order_statuses')); // Filter valid order statuses for preorders add_filter('wc_order_statuses', array($this, 'add_order_statuses')); add_filter('woocommerce_valid_order_statuses_for_payment', array($this, 'add_valid_order_statuses_for_payment')); add_filter('woocommerce_payment_complete_order_status', array($this, 'payment_complete_order_status'), 10, 3); } /** * Add preorder data to order items * * @param WC_Order_Item_Product $item * @param string $cart_item_key * @param array $values * @param WC_Order $order */ public function add_preorder_data_to_order_items($item, $cart_item_key, $values, $order) { if (isset($values['bdfg_preorder']) && $values['bdfg_preorder']['is_preorder']) { // Mark as preorder $item->add_meta_data('_bdfg_is_preorder', 'yes', true); // Save product and variation IDs $product_id = $item->get_product_id(); $variation_id = $item->get_variation_id(); $target_id = $variation_id ? $variation_id : $product_id; // Save available date and time $date = get_post_meta($target_id, '_bdfg_preorder_available_date', true); $time = get_post_meta($target_id, '_bdfg_preorder_available_time', true); if ($date) { $item->add_meta_data('_bdfg_preorder_available_date', $date, true); if ($time) { $item->add_meta_data('_bdfg_preorder_available_time', $time, true); } // Format date for display $formatted_date = date_i18n(get_option('date_format'), strtotime($date)); if ($time) { $formatted_date .= ' ' . date_i18n(get_option('time_format'), strtotime($time)); } $item->add_meta_data(__('Available on', 'bdfg-preorders'), $formatted_date, true); } // Save price adjustment $pricing_type = get_post_meta($target_id, '_bdfg_preorder_pricing_type', true); $price_adjustment = get_post_meta($target_id, '_bdfg_preorder_price_adjustment', true); if ($pricing_type && $price_adjustment) { $item->add_meta_data('_bdfg_preorder_pricing_type', $pricing_type, true); $item->add_meta_data('_bdfg_preorder_price_adjustment', $price_adjustment, true); } } } /** * Process preorder * * @param int $order_id * @param array $posted_data * @param WC_Order $order */ public function process_preorder($order_id, $posted_data, $order) { $has_preorder = false; // Check if order contains preorder products foreach ($order->get_items() as $item) { if ($item->get_meta('_bdfg_is_preorder') === 'yes') { $has_preorder = true; break; } } // If order contains preorder products, update order status and record to database if ($has_preorder) { // Add preorder flag to order $order->add_meta_data('_bdfg_has_preorder', 'yes', true); $order->save(); // Update order status for preorder if needed if ($order->has_status(array('processing', 'completed'))) { $order->update_status('preordered', __('Order contains pre-order products.', 'bdfg-preorders')); } // Record preorder items to custom table for tracking global $wpdb; $table_name = $wpdb->prefix . 'bdfg_preorders'; foreach ($order->get_items() as $item) { if ($item->get_meta('_bdfg_is_preorder') === 'yes') { $product_id = $item->get_product_id(); $variation_id = $item->get_variation_id(); $available_date = $item->get_meta('_bdfg_preorder_available_date'); $available_time = $item->get_meta('_bdfg_preorder_available_time'); // Build complete datetime $datetime = $available_date; if ($available_time) { $datetime .= ' ' . $available_time; } // Calculate preorder price $pricing_type = $item->get_meta('_bdfg_preorder_pricing_type'); $price_adjustment = $item->get_meta('_bdfg_preorder_price_adjustment'); $preorder_price = $item->get_total(); // Insert record $wpdb->insert( $table_name, array( 'order_id' => $order_id, 'product_id' => $product_id, 'variation_id' => $variation_id ? $variation_id : null, 'available_date' => $datetime ? date('Y-m-d H:i:s', strtotime($datetime)) : null, 'preorder_price' => $preorder_price, 'status' => 'pending', 'created_at' => current_time('mysql'), 'updated_at' => current_time('mysql') ), array( '%d', '%d', '%d', '%s', '%f', '%s', '%s', '%s' ) ); // Add preorder meta if needed $preorder_id = $wpdb->insert_id; $meta_table_name = $wpdb->prefix . 'bdfg_preorder_meta'; if ($pricing_type && $price_adjustment) { $wpdb->insert( $meta_table_name, array( 'preorder_id' => $preorder_id, 'meta_key' => 'pricing_type', 'meta_value' => $pricing_type ), array('%d', '%s', '%s') ); $wpdb->insert( $meta_table_name, array( 'preorder_id' => $preorder_id, 'meta_key' => 'price_adjustment', 'meta_value' => $price_adjustment ), array('%d', '%s', '%s') ); } } } } } /** * Add preorder label to order items * * @param int $item_id * @param WC_Order_Item $item */ public function add_preorder_order_item_label($item_id, $item) { if ($item->get_meta('_bdfg_is_preorder') === 'yes') { $date = $item->get_meta('_bdfg_preorder_available_date'); $time = $item->get_meta('_bdfg_preorder_available_time'); echo '<div class="bdfg-preorder-label">' . esc_html__('Pre-Order', 'bdfg-preorders'); if ($date) { $formatted_date = date_i18n(get_option('date_format'), strtotime($date)); if ($time) { $formatted_date .= ' ' . date_i18n(get_option('time_format'), strtotime($time)); } echo ' - ' . esc_html__('Available on', 'bdfg-preorders') . ': ' . esc_html($formatted_date); } echo '</div>'; } } /** * Add preorder details to emails * * @param WC_Order $order * @param bool $sent_to_admin * @param bool $plain_text * @param WC_Email $email */ public function add_preorder_details_to_emails($order, $sent_to_admin, $plain_text, $email) { if ($order->get_meta('_bdfg_has_preorder') === 'yes') { if ($plain_text) { echo "\n\n" . __('Pre-Order Information', 'bdfg-preorders') . "\n\n"; foreach ($order->get_items() as $item) { if ($item->get_meta('_bdfg_is_preorder') === 'yes') { $product = $item->get_product(); $date = $item->get_meta('_bdfg_preorder_available_date'); $time = $item->get_meta('_bdfg_preorder_available_time'); echo $product->get_name() . ': ' . __('Pre-ordered', 'bdfg-preorders') . "\n"; if ($date) { $formatted_date = date_i18n(get_option('date_format'), strtotime($date)); if ($time) { $formatted_date .= ' ' . date_i18n(get_option('time_format'), strtotime($time)); } echo __('Available on', 'bdfg-preorders') . ': ' . $formatted_date . "\n"; } echo "\n"; } } } else { echo '<h2>' . __('Pre-Order Information', 'bdfg-preorders') . '</h2>'; echo '<div class="bdfg-preorder-info-box">'; echo '<p>' . __('Your order contains pre-order items that will be available at a later date.', 'bdfg-preorders') . '</p>'; echo '<table class="td" cellspacing="0" cellpadding="6" style="width: 100%; margin-bottom: 20px;">'; echo '<thead><tr>'; echo '<th>' . __('Product', 'bdfg-preorders') . '</th>'; echo '<th>' . __('Status', 'bdfg-preorders') . '</th>'; echo '<th>' . __('Availability', 'bdfg-preorders') . '</th>'; echo '</tr></thead><tbody>'; foreach ($order->get_items() as $item) { if ($item->get_meta('_bdfg_is_preorder') === 'yes') { $product = $item->get_product(); $date = $item->get_meta('_bdfg_preorder_available_date'); $time = $item->get_meta('_bdfg_preorder_available_time'); echo '<tr>'; echo '<td>' . esc_html($product->get_name()) . '</td>'; echo '<td>' . esc_html__('Pre-ordered', 'bdfg-preorders') . '</td>'; if ($date) { $formatted_date = date_i18n(get_option('date_format'), strtotime($date)); if ($time) { $formatted_date .= ' ' . date_i18n(get_option('time_format'), strtotime($time)); } echo '<td>' . esc_html($formatted_date) . '</td>'; } else { echo '<td>' . esc_html__('To be announced', 'bdfg-preorders') . '</td>'; } echo '</tr>'; } } echo '</tbody></table>'; echo '</div>'; } } } /** * Add preorder details to order page * * @param WC_Order $order */ public function add_preorder_details_to_order_page($order) { if ($order->get_meta('_bdfg_has_preorder') === 'yes') { echo '<h2>' . esc_html__('Pre-Order Information', 'bdfg-preorders') . '</h2>'; echo '<div class="bdfg-preorder-details">'; echo '<p>' . esc_html__('Your order contains the following pre-order items:', 'bdfg-preorders') . '</p>'; echo '<table class="woocommerce-table shop_table preorder_details">'; echo '<thead>'; echo '<tr>'; echo '<th>' . esc_html__('Product', 'bdfg-preorders') . '</th>'; echo '<th>' . esc_html__('Status', 'bdfg-preorders') . '</th>'; echo '<th>' . esc_html__('Availability', 'bdfg-preorders') . '</th>'; echo '</tr>'; echo '</thead>'; echo '<tbody>'; $current_time = current_time('timestamp'); foreach ($order->get_items() as $item) { if ($item->get_meta('_bdfg_is_preorder') === 'yes') { $product = $item->get_product(); $date = $item->get_meta('_bdfg_preorder_available_date'); $time = $item->get_meta('_bdfg_preorder_available_time'); echo '<tr>'; if ($product) { echo '<td>' . esc_html($product->get_name()) . '</td>'; } else { echo '<td>' . esc_html($item->get_name()) . '</td>'; } $status_class = 'bdfg-preorder-status-pending'; $status_text = __('Pending Release', 'bdfg-preorders'); if ($date) { $datetime = $date; if ($time) { $datetime .= ' ' . $time; } $release_time = strtotime($datetime); if ($release_time <= $current_time) { $status_class = 'bdfg-preorder-status-released'; $status_text = __('Released', 'bdfg-preorders'); } } echo '<td><span class="' . esc_attr($status_class) . '">' . esc_html($status_text) . '</span></td>'; if ($date) { $formatted_date = date_i18n(get_option('date_format'), strtotime($date)); if ($time) { $formatted_date .= ' ' . date_i18n(get_option('time_format'), strtotime($time)); } echo '<td>' . esc_html($formatted_date); // Show countdown if needed if (get_option('bdfg_preorders_show_countdown', 'yes') === 'yes' && $release_time > $current_time) { $countdown_data = array( 'year' => date('Y', $release_time), 'month' => date('m', $release_time), 'day' => date('d', $release_time), 'hour' => date('H', $release_time), 'minute' => date('i', $release_time), 'second' => date('s', $release_time) ); echo '<div class="bdfg-preorder-countdown-small" data-countdown="' . esc_attr(json_encode($countdown_data)) . '"></div>'; } echo '</td>'; } else { echo '<td>' . esc_html__('To be announced', 'bdfg-preorders') . '</td>'; } echo '</tr>'; } } echo '</tbody>'; echo '</table>'; echo '</div>'; } } /** * Schedule availability check */ public function schedule_availability_check() { if (!wp_next_scheduled('bdfg_preorders_check_availability')) { wp_schedule_event(time(), 'hourly', 'bdfg_preorders_check_availability'); } } /** * Check preorder availability */ public function check_preorder_availability() { global $wpdb; $table_name = $wpdb->prefix . 'bdfg_preorders'; $current_datetime = current_time('mysql'); // Get preorders that have reached their availability date but are still pending $preorders = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name WHERE status = 'pending' AND available_date IS NOT NULL AND available_date <= %s", $current_datetime ) ); if (!empty($preorders)) { foreach ($preorders as $preorder) { // Update preorder status $wpdb->update( $table_name, array( 'status' => 'available', 'updated_at' => current_time('mysql') ), array('id' => $preorder->id), array('%s', '%s'), array('%d') ); // Get the order $order = wc_get_order($preorder->order_id); if ($order) { // Add order note $product = wc_get_product($preorder->product_id); if ($product) { $order->add_order_note( sprintf( __('Pre-ordered product "%s" is now available.', 'bdfg-preorders'), $product->get_name() ) ); } else { $order->add_order_note(__('A pre-ordered product is now available.', 'bdfg-preorders')); } // Check if all preordered products are available $this->maybe_complete_preorder($order); // Send email notifications if enabled if (get_option('bdfg_preorders_enable_emails', 'yes') === 'yes') { // Get product information $product_id = $preorder->product_id; $variation_id = $preorder->variation_id; if ($variation_id) { $product = wc_get_product($variation_id); } else { $product = wc_get_product($product_id); } if ($product) { // Send email to customer $emails = new BDFG_PreOrders_Emails(); $emails->send_availability_notification($order, $product); // Send email to admin if enabled if (get_option('bdfg_preorders_admin_notification', 'yes') === 'yes') { $emails->send_admin_notification($order, $product); } } } } } // Clear any caches that might be storing the preorders if (function_exists('wc_delete_shop_order_transients')) { wc_delete_shop_order_transients(); } } } /** * Check if all preordered products are available and update order status if needed * * @param WC_Order $order */ private function maybe_complete_preorder($order) { if (!$order || !$order->has_status('preordered')) { return; } global $wpdb; $table_name = $wpdb->prefix . 'bdfg_preorders'; // Check if any preordered items are still pending $pending_items = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE order_id = %d AND status = 'pending'", $order->get_id() )); // If no pending items, update order status to processing if ($pending_items == 0) { $order->update_status('processing', __('All pre-ordered products are now available.', 'bdfg-preorders')); } } /** * Handle order status changes * * @param int $order_id * @param string $from * @param string $to * @param WC_Order $order */ public function handle_order_status_change($order_id, $from, $to, $order) { if ($order->get_meta('_bdfg_has_preorder') !== 'yes') { return; } global $wpdb; $table_name = $wpdb->prefix . 'bdfg_preorders'; // Handle cancellation if ($to === 'cancelled') { $wpdb->update( $table_name, array( 'status' => 'cancelled', 'updated_at' => current_time('mysql') ), array('order_id' => $order_id), array('%s', '%s'), array('%d') ); } // Handle refunds if ($to === 'refunded') { $wpdb->update( $table_name, array( 'status' => 'refunded', 'updated_at' => current_time('mysql') ), array('order_id' => $order_id), array('%s', '%s'), array('%d') ); } } /** * Register custom order statuses */ public function register_custom_order_statuses() { register_post_status('wc-preordered', array( 'label' => __('Pre-ordered', 'bdfg-preorders'), 'public' => true, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop('Pre-ordered <span class="count">(%s)</span>', 'Pre-ordered <span class="count">(%s)</span>', 'bdfg-preorders') )); } /** * Add custom order statuses to WooCommerce * * @param array $order_statuses * @return array */ public function add_order_statuses($order_statuses) { $new_statuses = array(); // Add preordered status after processing foreach ($order_statuses as $key => $status) { $new_statuses[$key] = $status; if ($key === 'wc-processing') { $new_statuses['wc-preordered'] = __('Pre-ordered', 'bdfg-preorders'); } } return $new_statuses; } /** * Add preordered status to valid statuses for payment * * @param array $statuses * @return array */ public function add_valid_order_statuses_for_payment($statuses) { $statuses[] = 'preordered'; return $statuses; } /** * Modify payment complete order status for preorders * * @param string $status * @param int $order_id * @param WC_Order $order * @return string */ public function payment_complete_order_status($status, $order_id, $order) { if ($order && $order->get_meta('_bdfg_has_preorder') === 'yes') { return 'preordered'; } return $status; } }
includes/class-bdfg-preorders-product.php
相关文章: WooCommerce 产品过滤器
<?php /** * Product helper class for BDFG Pre-Orders * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } /** * Product helper class */ class BDFG_PreOrders_Product { /** * Check if product is a preorder * * @param int $product_id * @return bool */ public static function is_preorder($product_id) { return get_post_meta($product_id, '_bdfg_preorder_enabled', true) === 'yes'; } /** * Get preorder available date * * @param int $product_id * @return string|false */ public static function get_available_date($product_id) { $date = get_post_meta($product_id, '_bdfg_preorder_available_date', true); $time = get_post_meta($product_id, '_bdfg_preorder_available_time', true); if (empty($date)) { return false; } $datetime = $date; if (!empty($time)) { $datetime .= ' ' . $time; } return $datetime; } /** * Check if product is available (release date has passed) * * @param int $product_id * @return bool */ public static function is_available($product_id) { $datetime = self::get_available_date($product_id); if (!$datetime) { return true; // If no date set, consider it available } $timestamp = strtotime($datetime); $current_time = current_time('timestamp'); return $timestamp <= $current_time; } /** * Get adjusted price * * @param WC_Product $product * @return float */ public static function get_adjusted_price($product) { $product_id = $product->get_id(); $pricing_type = get_post_meta($product_id, '_bdfg_preorder_pricing_type', true); $adjustment = get_post_meta($product_id, '_bdfg_preorder_price_adjustment', true); if (empty($pricing_type) || empty($adjustment) || $adjustment == 0) { return $product->get_price(); } $price = $product->get_regular_price(); if ($pricing_type === 'fixed') { $adjusted_price = $price + $adjustment; } else { // percentage $adjusted_price = $price * (1 + ($adjustment / 100)); } return max(0, $adjusted_price); } /** * Get all preorder products * * @param array $args Additional query args * @return array */ public static function get_preorder_products($args = array()) { $query_args = wp_parse_args($args, array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => array( array( 'key' => '_bdfg_preorder_enabled', 'value' => 'yes', 'compare' => '=' ) ) )); $products = get_posts($query_args); return $products; } /** * Get upcoming preorder products * * @param int $limit Number of products to return * @return array */ public static function get_upcoming_preorders($limit = 5) { global $wpdb; $products = $wpdb->get_results($wpdb->prepare( "SELECT p.ID, p.post_title, pm1.meta_value AS date, pm2.meta_value AS time FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_bdfg_preorder_enabled' AND pm.meta_value = 'yes' JOIN {$wpdb->postmeta} pm1 ON p.ID = pm1.post_id AND pm1.meta_key = '_bdfg_preorder_available_date' LEFT JOIN {$wpdb->postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_bdfg_preorder_available_time' WHERE p.post_type = 'product' AND p.post_status = 'publish' AND STR_TO_DATE(CONCAT(pm1.meta_value, ' ', IFNULL(pm2.meta_value, '00:00:00')), '%%Y-%%m-%%d %%H:%%i:%%s') > NOW() ORDER BY STR_TO_DATE(CONCAT(pm1.meta_value, ' ', IFNULL(pm2.meta_value, '00:00:00')), '%%Y-%%m-%%d %%H:%%i:%%s') ASC LIMIT %d", $limit )); return $products; } /** * Get preorder statistics * * @return array */ public static function get_preorder_stats() { global $wpdb; $stats = array( 'total_products' => 0, 'pending_products' => 0, 'active_preorders' => 0, 'revenue' => 0 ); // Count total preorder products $stats['total_products'] = $wpdb->get_var( "SELECT COUNT(post_id) FROM {$wpdb->postmeta} WHERE meta_key = '_bdfg_preorder_enabled' AND meta_value = 'yes'" ); // Count pending release products $current_date = date('Y-m-d'); $stats['pending_products'] = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(p.ID) FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_bdfg_preorder_enabled' AND pm.meta_value = 'yes' JOIN {$wpdb->postmeta} pm1 ON p.ID = pm1.post_id AND pm1.meta_key = '_bdfg_preorder_available_date' WHERE p.post_type = 'product' AND p.post_status = 'publish' AND pm1.meta_value >= %s", $current_date )); // Count active preorders and revenue $table_name = $wpdb->prefix . 'bdfg_preorders'; if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name) { $stats['active_preorders'] = $wpdb->get_var( "SELECT COUNT(id) FROM $table_name WHERE status = 'pending'" ); $stats['revenue'] = $wpdb->get_var( "SELECT SUM(preorder_price) FROM $table_name WHERE status IN ('pending', 'available')" ); } return $stats; } }
includes/class-bdfg-preorders-emails.php
<?php /** * Email handling for BDFG Pre-Orders * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } /** * Emails class for handling email notifications */ class BDFG_PreOrders_Emails { /** * Constructor */ public function __construct() { // Add new emails to WooCommerce add_filter('woocommerce_email_classes', array($this, 'add_emails')); } /** * Add new emails * * @param array $email_classes * @return array */ public function add_emails($email_classes) { // Add custom email classes here if needed return $email_classes; } /** * Send availability notification to customer * * @param WC_Order $order * @param WC_Product $product * @return bool */ public function send_availability_notification($order, $product) { if (!$order || !$product) { return false; } // Get settings $subject = get_option('bdfg_preorders_email_subject', __('Your pre-ordered product is now available!', 'bdfg-preorders')); $heading = get_option('bdfg_preorders_email_heading', __('Pre-order Available', 'bdfg-preorders')); $content = get_option('bdfg_preorders_email_content', __("Hello {customer_name},\n\nYour pre-ordered product '{product_name}' is now available.\n\nThank you for your patience and support!\n\nBeiduofengou Team", 'bdfg-preorders')); // Replace placeholders $customer_name = $order->get_billing_first_name(); $product_name = $product->get_name(); $order_id = $order->get_id(); $available_date = date_i18n(get_option('date_format')); $content = str_replace('{customer_name}', $customer_name, $content); $content = str_replace('{product_name}', $product_name, $content); $content = str_replace('{order_id}', $order_id, $content); $content = str_replace('{availability_date}', $available_date, $content); // Prepare email $email_content = $this->get_email_template($heading, $content, $order); // Send email $customer_email = $order->get_billing_email(); if (!empty($customer_email)) { $headers = array('Content-Type: text/html; charset=UTF-8'); $result = wp_mail($customer_email, $subject, $email_content, $headers); if ($result) { $order->add_order_note( sprintf( __('Pre-order availability notification sent to customer for product: %s', 'bdfg-preorders'), $product->get_name() ) ); } return $result; } return false; } /** * Send notification to admin * * @param WC_Order $order * @param WC_Product $product * @return bool */ public function send_admin_notification($order, $product) { if (!$order || !$product) { return false; } // Get admin email $admin_email = get_option('admin_email'); if (empty($admin_email)) { return false; } // Get subject $subject = get_option('bdfg_preorders_admin_email_subject', __('Pre-ordered product now available', 'bdfg-preorders')); // Prepare message $message = sprintf( __('Pre-ordered product "%1$s" (ID: %2$s) is now available. Order #%3$s.', 'bdfg-preorders'), $product->get_name(), $product->get_id(), $order->get_order_number() ); // Add order link $message .= "\n\n"; $message .= sprintf( __('View order: %s', 'bdfg-preorders'), admin_url('post.php?post=' . $order->get_id() . '&action=edit') ); // Send email $headers = array('Content-Type: text/html; charset=UTF-8'); $email_content = '<p>' . nl2br(esc_html($message)) . '</p>'; return wp_mail($admin_email, $subject, $email_content, $headers); } /** * Get email template * * @param string $heading * @param string $content * @param WC_Order $order * @return string */ private function get_email_template($heading, $content, $order) { ob_start(); // Get email template $template = WC()->mailer()->get_template('customer-completed-order'); // Create WooCommerce email instance $email = new WC_Email(); // Prepare content $email_heading = $heading; $plain_text = false; $sent_to_admin = false; // Convert newlines to HTML $content = wpautop($content); // Add BDFG branding $content .= '<div class="bdfg-preorder-email-branding" style="margin-top: 20px; text-align: center; padding-top: 10px; border-top: 1px solid #f2f2f2;">'; $content .= '<a href="https://beiduofengou.net" target="_blank" style="text-decoration: none; color: #3d9cd2;">'; $content .= '<img src="' . esc_url(BDFG_PREORDERS_URL . 'assets/images/bdfg-logo-email.png') . '" alt="Beiduofengou" style="max-width: 150px; margin-bottom: 10px;" />'; $content .= '</a>'; $content .= '<p style="font-size: 12px; color: #999;">' . esc_html__('Powered by BDFG Pre-Orders for WooCommerce', 'bdfg-preorders') . '</p>'; $content .= '</div>'; // Load WooCommerce email template wc_get_template('emails/email-header.php', array('email_heading' => $email_heading)); echo wp_kses_post($content); wc_get_template('emails/email-order-details.php', array('order' => $order, 'sent_to_admin' => $sent_to_admin, 'plain_text' => $plain_text, 'email' => $email)); wc_get_template('emails/email-customer-details.php', array('order' => $order, 'sent_to_admin' => $sent_to_admin, 'plain_text' => $plain_text, 'email' => $email)); wc_get_template('emails/email-footer.php'); return ob_get_clean(); } }
includes/helpers/class-bdfg-preorders-helper.php
相关文章: WooCommerce 高级产品定价和折扣管理
<?php /** * Helper functions for BDFG Pre-Orders * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } /** * Helper class with utility functions */ class BDFG_PreOrders_Helper { /** * Format date for display * * @param string $date Date or datetime string * @param string $time Time string (optional) * @return string */ public static function format_date($date, $time = '') { if (empty($date)) { return ''; } // Check if date contains time if (strpos($date, ' ') !== false) { list($date_part, $time_part) = explode(' ', $date); $formatted_date = date_i18n(get_option('date_format'), strtotime($date_part)); $formatted_time = date_i18n(get_option('time_format'), strtotime($time_part)); return $formatted_date . ' ' . $formatted_time; } $formatted_date = date_i18n(get_option('date_format'), strtotime($date)); if (!empty($time)) { $formatted_time = date_i18n(get_option('time_format'), strtotime($time)); return $formatted_date . ' ' . $formatted_time; } return $formatted_date; } /** * Log debug information * * @param mixed $message * @return void */ public static function log($message) { // Only log if debug mode is enabled if (get_option('bdfg_preorders_debug_mode') !== 'yes') { return; } $log_file = BDFG_PREORDERS_PATH . 'logs/debug.log'; // Create logs directory if it doesn't exist if (!file_exists(dirname($log_file))) { wp_mkdir_p(dirname($log_file)); } // Format message if (is_array($message) || is_object($message)) { $message = print_r($message, true); } // Add timestamp $message = '[' . date('Y-m-d H:i:s') . '] ' . $message . "\n"; // Write to log file file_put_contents($log_file, $message, FILE_APPEND); } /** * Get formatted price adjustment * * @param string $pricing_type Fixed or percentage * @param float $adjustment Amount * @return string */ public static function get_price_adjustment_text($pricing_type, $adjustment) { if (empty($pricing_type) || empty($adjustment) || $adjustment == 0) { return __('No adjustment', 'bdfg-preorders'); } if ($pricing_type === 'fixed') { if ($adjustment > 0) { return sprintf(__('Add %s', 'bdfg-preorders'), wc_price($adjustment)); } else { return sprintf(__('Subtract %s', 'bdfg-preorders'), wc_price(abs($adjustment))); } } else { // percentage if ($adjustment > 0) { return sprintf(__('Add %d%%', 'bdfg-preorders'), $adjustment); } else { return sprintf(__('Subtract %d%%', 'bdfg-preorders'), abs($adjustment)); } } } /** * Check if an order contains preorder products * * @param int|WC_Order $order Order ID or object * @return bool */ public static function order_has_preorders($order) { if (!$order instanceof WC_Order) { $order = wc_get_order($order); } if (!$order) { return false; } return $order->get_meta('_bdfg_has_preorder') === 'yes'; } /** * Get remaining time until release as a human-readable string * * @param string $date * @return string */ public static function get_time_until_release($date) { if (empty($date)) { return ''; } $timestamp = strtotime($date); $current = current_time('timestamp'); if ($timestamp <= $current) { return __('Released', 'bdfg-preorders'); } $diff = $timestamp - $current; if ($diff < DAY_IN_SECONDS) { $hours = floor($diff / HOUR_IN_SECONDS); return sprintf(_n('%d hour', '%d hours', $hours, 'bdfg-preorders'), $hours); } elseif ($diff < WEEK_IN_SECONDS) { $days = floor($diff / DAY_IN_SECONDS); return sprintf(_n('%d day', '%d days', $days, 'bdfg-preorders'), $days); } else { $weeks = floor($diff / WEEK_IN_SECONDS); return sprintf(_n('%d week', '%d weeks', $weeks, 'bdfg-preorders'), $weeks); } } /** * Get preorder payment method label * * @param string $method * @return string */ public static function get_payment_method_label($method) { $methods = array( 'full' => __('Full payment upfront', 'bdfg-preorders'), 'deposit' => __('Deposit only', 'bdfg-preorders'), 'authorize' => __('Authorize payment only', 'bdfg-preorders') ); return isset($methods[$method]) ? $methods[$method] : $method; } }
includes/bdfg-preorders-template-functions.php
<?php /** * Template functions for BDFG Pre-Orders * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } /** * Get template part * * @param string $slug * @param string $name * @param array $args */ function bdfg_preorders_get_template_part($slug, $name = '', $args = array()) { $template = ''; // Look in yourtheme/bdfg-preorders/slug-name.php and yourtheme/bdfg-preorders/slug.php if ($name) { $template = locate_template(array("bdfg-preorders/{$slug}-{$name}.php", "bdfg-preorders/{$slug}.php")); } // Get default template if (!$template) { if ($name) { $template = BDFG_PREORDERS_PATH . "templates/{$slug}-{$name}.php"; } else { $template = BDFG_PREORDERS_PATH . "templates/{$slug}.php"; } } // Allow third party plugins to filter template file $template = apply_filters('bdfg_preorders_get_template_part', $template, $slug, $name); if (file_exists($template)) { load_template($template, false, $args); } } /** * Get template * * @param string $template_name * @param array $args * @param string $template_path * @param string $default_path */ function bdfg_preorders_get_template($template_name, $args = array(), $template_path = '', $default_path = '') { if ($args && is_array($args)) { extract($args); } $located = bdfg_preorders_locate_template($template_name, $template_path, $default_path); if (!file_exists($located)) { return; } // Allow third party plugins to filter template file $located = apply_filters('bdfg_preorders_get_template', $located, $template_name, $args, $template_path, $default_path); include $located; } /** * Locate template * * @param string $template_name * @param string $template_path * @param string $default_path * @return string */ function bdfg_preorders_locate_template($template_name, $template_path = '', $default_path = '') { if (!$template_path) { $template_path = 'bdfg-preorders/'; } if (!$default_path) { $default_path = BDFG_PREORDERS_PATH . 'templates/'; } // Look within passed path within the theme - this is priority $template = locate_template(array( trailingslashit($template_path) . $template_name, $template_name )); // Get default template if (!$template) { $template = trailingslashit($default_path) . $template_name; } // Return what we found return apply_filters('bdfg_preorders_locate_template', $template, $template_name, $template_path); } /** * Display preorder badge * * @param WC_Product $product */ function bdfg_preorders_display_badge($product) { if (!$product || !BDFG_PreOrders_Product::is_preorder($product->get_id())) { return; } echo '<span class="bdfg-preorder-badge">' . esc_html__('Pre-Order', 'bdfg-preorders') . '</span>'; } /** * Display preorder availability * * @param WC_Product $product */ function bdfg_preorders_display_availability($product) { if (!$product || !BDFG_PreOrders_Product::is_preorder($product->get_id())) { return; } $date = get_post_meta($product->get_id(), '_bdfg_preorder_available_date', true); $time = get_post_meta($product->get_id(), '_bdfg_preorder_available_time', true); if (empty($date)) { return; } $formatted_date = BDFG_PreOrders_Helper::format_date($date, $time); $availability_text = get_option('bdfg_preorders_availability_text', __('Available on: %date%', 'bdfg-preorders')); $availability_text = str_replace('%date%', $formatted_date, $availability_text); echo '<div class="bdfg-preorder-availability">' . esc_html($availability_text) . '</div>'; } /** * Display countdown timer * * @param WC_Product $product */ function bdfg_preorders_display_countdown($product) { if (!$product || !BDFG_PreOrders_Product::is_preorder($product->get_id())) { return; } // Check if countdown timer is enabled if (get_option('bdfg_preorders_show_countdown', 'yes') !== 'yes') { return; } $date = get_post_meta($product->get_id(), '_bdfg_preorder_available_date', true); $time = get_post_meta($product->get_id(), '_bdfg_preorder_available_time', true); if (empty($date)) { return; } $datetime = $date; if (!empty($time)) { $datetime .= ' ' . $time; } else { $datetime .= ' 00:00:00'; } $target_time = strtotime($datetime); $current_time = current_time('timestamp'); // Only show countdown if not yet released if ($target_time > $current_time) { $countdown_data = array( 'year' => date('Y', $target_time), 'month' => date('m', $target_time), 'day' => date('d', $target_time), 'hour' => date('H', $target_time), 'minute' => date('i', $target_time), 'second' => date('s', $target_time) ); echo '<div class="bdfg-preorder-countdown" data-countdown="' . esc_attr(json_encode($countdown_data)) . '">'; echo '<div class="countdown-label">' . esc_html__('Available in:', 'bdfg-preorders') . '</div>'; echo '<div class="countdown-timer">'; echo '<span class="days">00</span><span class="countdown-sep">:</span>'; echo '<span class="hours">00</span><span class="countdown-sep">:</span>'; echo '<span class="minutes">00</span><span class="countdown-sep">:</span>'; echo '<span class="seconds">00</span>'; echo '</div>'; echo '<div class="countdown-labels">'; echo '<span class="days-label">' . esc_html__('Days', 'bdfg-preorders') . '</span> '; echo '<span class="hours-label">' . esc_html__('Hours', 'bdfg-preorders') . '</span> '; echo '<span class="minutes-label">' . esc_html__('Min', 'bdfg-preorders') . '</span> '; echo '<span class="seconds-label">' . esc_html__('Sec', 'bdfg-preorders') . '</span>'; echo '</div>'; echo '</div>'; } }
includes/admin/class-bdfg-preorders-list-table.php
<?php /** * Pre-Orders List Table * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } // Load WP_List_Table if not loaded if (!class_exists('WP_List_Table')) { require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php'); } /** * Pre-Orders List Table class */ class BDFG_PreOrders_List_Table extends WP_List_Table { /** * Constructor */ public function __construct() { parent::__construct(array( 'singular' => 'preorder', 'plural' => 'preorders', 'ajax' => false )); } /** * Get columns * * @return array */ public function get_columns() { return array( 'cb' => '<input type="checkbox" />', 'order_id' => __('Order', 'bdfg-preorders'), 'product' => __('Product', 'bdfg-preorders'), 'customer' => __('Customer', 'bdfg-preorders'), 'available_date' => __('Available Date', 'bdfg-preorders'), 'status' => __('Status', 'bdfg-preorders'), 'actions' => __('Actions', 'bdfg-preorders') ); } /** * Get sortable columns * * @return array */ public function get_sortable_columns() { return array( 'order_id' => array('order_id', true), 'available_date' => array('available_date', false), 'status' => array('status', false) ); } /** * Get bulk actions * * @return array */ public function get_bulk_actions() { return array( 'release' => __('Release Now', 'bdfg-preorders'), 'cancel' => __('Cancel', 'bdfg-preorders') ); } /** * Process bulk actions */ public function process_bulk_action() { // Security check if (isset($_REQUEST['_wpnonce']) && !empty($_REQUEST['_wpnonce'])) { $nonce = filter_input(INPUT_POST, '_wpnonce', FILTER_SANITIZE_STRING); $action = 'bulk-' . $this->_args['plural']; if (!wp_verify_nonce($nonce, $action)) { wp_die('Security check failed'); } } $action = $this->current_action(); if ($action) { global $wpdb; $table_name = $wpdb->prefix . 'bdfg_preorders'; if (isset($_REQUEST['preorder'])) { $preorder_ids = array_map('intval', (array) $_REQUEST['preorder']); if (!empty($preorder_ids)) { if ($action === 'release') { // Release preorders foreach ($preorder_ids as $id) { $wpdb->update( $table_name, array( 'status' => 'available', 'updated_at' => current_time('mysql') ), array('id' => $id), array('%s', '%s'), array('%d') ); $preorder = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $id)); if ($preorder) { $order = wc_get_order($preorder->order_id); if ($order) { $order->add_order_note(__('Pre-order manually released by admin.', 'bdfg-preorders')); } } } $message = __('Pre-orders released successfully.', 'bdfg-preorders'); echo '<div class="updated"><p>' . esc_html($message) . '</p></div>'; } elseif ($action === 'cancel') { // Cancel preorders foreach ($preorder_ids as $id) { $wpdb->update( $table_name, array( 'status' => 'cancelled', 'updated_at' => current_time('mysql') ), array('id' => $id), array('%s', '%s'), array('%d') ); $preorder = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $id)); if ($preorder) { $order = wc_get_order($preorder->order_id); if ($order) { $order->add_order_note(__('Pre-order cancelled by admin.', 'bdfg-preorders')); } } } $message = __('Pre-orders cancelled successfully.', 'bdfg-preorders'); echo '<div class="updated"><p>' . esc_html($message) . '</p></div>'; } } } } } /** * Prepare items */ public function prepare_items() { $columns = $this->get_columns(); $hidden = array(); $sortable = $this->get_sortable_columns(); $this->_column_headers = array($columns, $hidden, $sortable); $this->process_bulk_action(); $per_page = 20; $current_page = $this->get_pagenum(); $offset = ($current_page - 1) * $per_page; global $wpdb; $table_name = $wpdb->prefix . 'bdfg_preorders'; // Build query $where = array('1=1'); // Filter by status if (isset($_REQUEST['status']) && !empty($_REQUEST['status'])) { $status = sanitize_text_field($_REQUEST['status']); $where[] = $wpdb->prepare('status = %s', $status); } // Filter by date range if (isset($_REQUEST['date_from']) && !empty($_REQUEST['date_from'])) { $date_from = sanitize_text_field($_REQUEST['date_from']); $where[] = $wpdb->prepare('available_date >= %s', $date_from . ' 00:00:00'); } if (isset($_REQUEST['date_to']) && !empty($_REQUEST['date_to'])) { $date_to = sanitize_text_field($_REQUEST['date_to']); $where[] = $wpdb->prepare('available_date <= %s', $date_to . ' 23:59:59'); } // Search query if (isset($_REQUEST['s']) && !empty($_REQUEST['s'])) { $search = sanitize_text_field($_REQUEST['s']); // Join product and order tables for search $where[] = $wpdb->prepare( "(p.ID = %d OR p.post_title LIKE %s OR o.ID = %d OR om.meta_value LIKE %s)", is_numeric($search) ? (int)$search : 0, '%' . $wpdb->esc_like($search) . '%', is_numeric($search) ? (int)$search : 0, '%' . $wpdb->esc_like($search) . '%' ); } // Build WHERE clause $where_clause = implode(' AND ', $where); // Order by $orderby = !empty($_REQUEST['orderby']) ? sanitize_sql_orderby($_REQUEST['orderby']) : 'id'; $order = !empty($_REQUEST['order']) ? sanitize_text_field($_REQUEST['order']) : 'DESC'; if (!in_array($orderby, array('id', 'order_id', 'available_date', 'status'))) { $orderby = 'id'; } $order = strtoupper($order) === 'ASC' ? 'ASC' : 'DESC'; // Get total items count $count_query = "SELECT COUNT(po.id) FROM $table_name po LEFT JOIN {$wpdb->posts} p ON po.product_id = p.ID LEFT JOIN {$wpdb->posts} o ON po.order_id = o.ID LEFT JOIN {$wpdb->postmeta} om ON o.ID = om.post_id AND om.meta_key = '_billing_email' WHERE $where_clause"; $total_items = $wpdb->get_var($count_query); // Get items $query = "SELECT po.*, p.post_title as product_name, o.post_status as order_status FROM $table_name po LEFT JOIN {$wpdb->posts} p ON po.product_id = p.ID LEFT JOIN {$wpdb->posts} o ON po.order_id = o.ID LEFT JOIN {$wpdb->postmeta} om ON o.ID = om.post_id AND om.meta_key = '_billing_email' WHERE $where_clause ORDER BY $orderby $order LIMIT $per_page OFFSET $offset"; $this->items = $wpdb->get_results($query); // Set pagination arguments $this->set_pagination_args(array( 'total_items' => $total_items, 'per_page' => $per_page, 'total_pages' => ceil($total_items / $per_page) )); } /** * Column default * * @param object $item * @param string $column_name * @return string */ public function column_default($item, $column_name) { switch ($column_name) { case 'id': return $item->id; case 'status': return $this->get_status_label($item->status); default: return print_r($item, true); // Show the whole array for troubleshooting purposes } } /** * Get status label * * @param string $status * @return string */ private function get_status_label($status) { $statuses = array( 'pending' => __('Pending Release', 'bdfg-preorders'), 'available' => __('Released', 'bdfg-preorders'), 'cancelled' => __('Cancelled', 'bdfg-preorders'), 'refunded' => __('Refunded', 'bdfg-preorders') ); return isset($statuses[$status]) ? $statuses[$status] : $status; } /** * Column cb * * @param object $item * @return string */ public function column_cb($item) { return sprintf('<input type="checkbox" name="preorder[]" value="%s" />', $item->id); } /** * Column order_id * * @param object $item * @return string */ public function column_order_id($item) { $order = wc_get_order($item->order_id); if ($order) { $edit_link = get_edit_post_link($item->order_id); return '<a href="' . esc_url($edit_link) . '">#' . esc_html($order->get_order_number()) . '</a>'; } else { return '#' . esc_html($item->order_id) . ' (' . __('Deleted', 'bdfg-preorders') . ')'; } } /** * Column product * * @param object $item * @return string */ public function column_product($item) { if (empty($item->product_name)) { $product = wc_get_product($item->product_id); if ($product) { $product_name = $product->get_name(); $edit_link = get_edit_post_link($item->product_id); // Show variation info if available if ($item->variation_id && $variation = wc_get_product($item->variation_id)) { $attributes = $variation->get_variation_attributes(); $attribute_info = array(); foreach ($attributes as $attr_name => $attr_value) { $label = wc_attribute_label(str_replace('attribute_', '', $attr_name)); $value = $attr_value ? $attr_value : __('Any', 'bdfg-preorders'); $attribute_info[] = $label . ': ' . $value; } $variation_text = implode(', ', $attribute_info); return '<a href="' . esc_url($edit_link) . '">' . esc_html($product_name) . '</a><br><small>' . esc_html($variation_text) . '</small>'; } return '<a href="' . esc_url($edit_link) . '">' . esc_html($product_name) . '</a>'; } else { return __('Product not found', 'bdfg-preorders') . ' (ID: ' . esc_html($item->product_id) . ')'; } } else { $edit_link = get_edit_post_link($item->product_id); return '<a href="' . esc_url($edit_link) . '">' . esc_html($item->product_name) . '</a>'; } } /** * Column customer * * @param object $item * @return string */ public function column_customer($item) { $order = wc_get_order($item->order_id); if ($order) { $email = $order->get_billing_email(); $name = $order->get_formatted_billing_full_name(); if ($email) { return esc_html($name) . '<br><a href="mailto:' . esc_attr($email) . '">' . esc_html($email) . '</a>'; } else { return esc_html($name); } } return __('Unknown', 'bdfg-preorders'); } /** * Column available_date * * @param object $item * @return string */ public function column_available_date($item) { if (!empty($item->available_date)) { $formatted_date = BDFG_PreOrders_Helper::format_date($item->available_date); // Show countdown for future dates $current_time = current_time('timestamp'); $release_time = strtotime($item->available_date); if ($release_time > $current_time) { $time_until = BDFG_PreOrders_Helper::get_time_until_release($item->available_date); return sprintf( '%s<br><small>%s</small>', esc_html($formatted_date), esc_html($time_until) ); } return esc_html($formatted_date); } return __('Not specified', 'bdfg-preorders'); } /** * Column status * * @param object $item * @return string */ public function column_status($item) { $status_label = $this->get_status_label($item->status); $status_class = 'bdfg-preorder-status-' . sanitize_html_class($item->status); return '<span class="' . esc_attr($status_class) . '">' . esc_html($status_label) . '</span>'; } /** * Column actions * * @param object $item * @return string */ public function column_actions($item) { $actions = array(); // View order link $actions['view-order'] = sprintf( '<a href="%s" title="%s">%s</a>', esc_url(get_edit_post_link($item->order_id)), esc_attr__('View Order', 'bdfg-preorders'), __('View Order', 'bdfg-preorders') ); // Release action (only for pending preorders) if ($item->status === 'pending') { $release_url = wp_nonce_url( add_query_arg( array( 'action' => 'release', 'preorder' => $item->id ) ), 'release-preorder-' . $item->id ); $actions['release'] = sprintf( '<a href="%s" title="%s" class="bdfg-action-release">%s</a>', esc_url($release_url), esc_attr__('Release Now', 'bdfg-preorders'), __('Release Now', 'bdfg-preorders') ); } // Cancel action (only for pending preorders) if ($item->status === 'pending') { $cancel_url = wp_nonce_url( add_query_arg( array( 'action' => 'cancel', 'preorder' => $item->id ) ), 'cancel-preorder-' . $item->id ); $actions['cancel'] = sprintf( '<a href="%s" title="%s" class="bdfg-action-cancel">%s</a>', esc_url($cancel_url), esc_attr__('Cancel', 'bdfg-preorders'), __('Cancel', 'bdfg-preorders') ); } return implode(' | ', $actions); } /** * Extra table navigation * * @param string $which */ public function extra_tablenav($which) { if ($which === 'top') { // Status filter $current_status = isset($_REQUEST['status']) ? sanitize_text_field($_REQUEST['status']) : ''; ?> <div class="alignleft actions"> <label for="filter-by-status" class="screen-reader-text"><?php esc_html_e('Filter by status', 'bdfg-preorders'); ?></label> <select name="status" id="filter-by-status"> <option value=""><?php esc_html_e('All statuses', 'bdfg-preorders'); ?></option> <option value="pending" <?php selected($current_status, 'pending'); ?>><?php esc_html_e('Pending Release', 'bdfg-preorders'); ?></option> <option value="available" <?php selected($current_status, 'available'); ?>><?php esc_html_e('Released', 'bdfg-preorders'); ?></option> <option value="cancelled" <?php selected($current_status, 'cancelled'); ?>><?php esc_html_e('Cancelled', 'bdfg-preorders'); ?></option> <option value="refunded" <?php selected($current_status, 'refunded'); ?>><?php esc_html_e('Refunded', 'bdfg-preorders'); ?></option> </select> <?php // Date filter $date_from = isset($_REQUEST['date_from']) ? sanitize_text_field($_REQUEST['date_from']) : ''; $date_to = isset($_REQUEST['date_to']) ? sanitize_text_field($_REQUEST['date_to']) : ''; ?> <span class="date-filter"> <input type="text" name="date_from" id="date-from" class="date-picker" placeholder="<?php esc_attr_e('From date', 'bdfg-preorders'); ?>" value="<?php echo esc_attr($date_from); ?>" /> <span>–</span> <input type="text" name="date_to" id="date-to" class="date-picker" placeholder="<?php esc_attr_e('To date', 'bdfg-preorders'); ?>" value="<?php echo esc_attr($date_to); ?>" /> </span> <?php submit_button(__('Filter', 'bdfg-preorders'), '', 'filter_action', false); ?> </div> <?php } } }
includes/class-bdfg-preorders-install.php
<?php /** * Installation and database schema for BDFG Pre-Orders * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } /** * Installation class */ class BDFG_PreOrders_Install { /** * Database version * * @var string */ private static $db_version = '2.0.0'; /** * Install the plugin */ public static function install() { self::create_tables(); self::create_pages(); self::update_version(); // Set transient for welcome page redirect set_transient('bdfg_preorders_activation_redirect', 1, 30); } /** * Create database tables */ private static function create_tables() { global $wpdb; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); $charset_collate = $wpdb->get_charset_collate(); // Create preorders table $table_name = $wpdb->prefix . 'bdfg_preorders'; $sql = "CREATE TABLE $table_name ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, order_id bigint(20) unsigned NOT NULL, product_id bigint(20) unsigned NOT NULL, variation_id bigint(20) unsigned DEFAULT NULL, available_date datetime DEFAULT NULL, preorder_price decimal(19,4) DEFAULT NULL, status varchar(50) NOT NULL DEFAULT 'pending', created_at datetime NOT NULL, updated_at datetime NOT NULL, PRIMARY KEY (id), KEY order_id (order_id), KEY product_id (product_id), KEY status (status), KEY available_date (available_date) ) $charset_collate;"; dbDelta($sql); // Create preorder meta table $meta_table_name = $wpdb->prefix . 'bdfg_preorder_meta'; $sql = "CREATE TABLE $meta_table_name ( meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT, preorder_id bigint(20) unsigned NOT NULL, meta_key varchar(255) DEFAULT NULL, meta_value longtext, PRIMARY KEY (meta_id), KEY preorder_id (preorder_id), KEY meta_key (meta_key(191)) ) $charset_collate;"; dbDelta($sql); } /** * Create custom pages */ private static function create_pages() { // No custom pages needed for now, but can add in the future if needed } /** * Update version */ private static function update_version() { update_option('bdfg_preorders_db_version', self::$db_version); update_option('bdfg_preorders_version', BDFG_PREORDERS_VERSION); } /** * Uninstall the plugin */ public static function uninstall() { // Only delete data if setting is enabled if (get_option('bdfg_preorders_delete_data_uninstall', 'no') === 'yes') { self::delete_tables(); self::delete_options(); } } /** * Delete database tables */ private static function delete_tables() { global $wpdb; $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}bdfg_preorder_meta"); $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}bdfg_preorders"); } /** * Delete options */ private static function delete_options() { // Delete all plugin options $options = array( 'bdfg_preorders_db_version', 'bdfg_preorders_version', 'bdfg_preorders_stock_handling', 'bdfg_preorders_payment_method', 'bdfg_preorders_deposit_amount', 'bdfg_preorders_enable_cancellations', 'bdfg_preorders_cancellation_limit', 'bdfg_preorders_button_text', 'bdfg_preorders_button_color', 'bdfg_preorders_availability_text', 'bdfg_preorders_show_countdown', 'bdfg_preorders_enable_emails', 'bdfg_preorders_email_subject', 'bdfg_preorders_email_heading', 'bdfg_preorders_email_content', 'bdfg_preorders_admin_notification', 'bdfg_preorders_admin_email_subject', 'bdfg_preorders_delete_data_uninstall', 'bdfg_preorders_debug_mode' ); foreach ($options as $option) { delete_option($option); } // Delete post meta global $wpdb; $wpdb->query("DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '_bdfg_preorder%'"); } }
assets/js/frontend.js
/** * BDFG Pre-Orders frontend JavaScript * * @package BDFG_PreOrders */ (function($) { 'use strict'; // Initialize countdown timers function initCountdownTimers() { $('.bdfg-preorder-countdown').each(function() { var $this = $(this); var countdownData = $this.data('countdown'); if (!countdownData) return; var targetDate = new Date( countdownData.year, countdownData.month - 1, countdownData.day, countdownData.hour, countdownData.minute, countdownData.second ); // Update countdown every second var interval = setInterval(function() { var now = new Date(); var diff = Math.max(0, targetDate - now) / 1000; if (diff <= 0) { clearInterval(interval); $this.html('<div class="countdown-complete">' + bdfg_preorders_params.i18n.available_now + '</div>'); // Refresh the page after a slight delay setTimeout(function() { location.reload(); }, 3000); return; } var days = Math.floor(diff / 86400); diff -= days * 86400; var hours = Math.floor(diff / 3600); diff -= hours * 3600; var minutes = Math.floor(diff / 60); diff -= minutes * 60; var seconds = Math.floor(diff); // Update countdown elements $this.find('.days').text(padZero(days)); $this.find('.hours').text(padZero(hours)); $this.find('.minutes').text(padZero(minutes)); $this.find('.seconds').text(padZero(seconds)); }, 1000); }); } // Helper function to pad with zero function padZero(num) { return (num < 10 ? '0' : '') + num; } // Handle variation changes for preorders function handleVariationChanges() { if ($('form.variations_form').length) { $('form.variations_form').on('found_variation', function(event, variation) { if (variation.is_preorder) { // Update button text if provided if (variation.button_text) { $('.single_add_to_cart_button').text(variation.button_text); $('.single_add_to_cart_button').addClass('bdfg-preorder-button'); } // Update availability text if provided if (variation.availability_html) { $('.woocommerce-variation-availability').html(variation.availability_html); } // Add preorder class to form $(this).addClass('is-preorder-variation'); } else { // Restore default button text $('.single_add_to_cart_button').text($('.single_add_to_cart_button').data('original-text') || bdfg_preorders_params.i18n.add_to_cart); $('.single_add_to_cart_button').removeClass('bdfg-preorder-button'); // Remove preorder class from form $(this).removeClass('is-preorder-variation'); } }); // Reset when no variation is selected $('form.variations_form').on('reset_data', function() { $('.single_add_to_cart_button').text($('.single_add_to_cart_button').data('original-text') || bdfg_preorders_params.i18n.add_to_cart); $('.single_add_to_cart_button').removeClass('bdfg-preorder-button'); }); } } // Handle cancel preorder actions function handleCancelPreorder() { $('.bdfg-cancel-preorder').on('click', function(e) { if (!confirm(bdfg_preorders_params.i18n.confirm_cancel)) { e.preventDefault(); return false; } var $button = $(this); var orderId = $button.data('order-id'); if (orderId) { e.preventDefault(); $button.prop('disabled', true); $button.text(bdfg_preorders_params.i18n.processing); $.ajax({ type: 'POST', url: bdfg_preorders_params.ajax_url, data: { action: 'bdfg_preorders_cancel', order_id: orderId, nonce: bdfg_preorders_params.nonce }, success: function(response) { if (response.success) { if (response.data && response.data.redirect) { window.location.href = response.data.redirect; } else { window.location.reload(); } } else { alert(response.data.message || bdfg_preorders_params.i18n.error); $button.prop('disabled', false); $button.text(bdfg_preorders_params.i18n.cancel_preorder); } }, error: function() { alert(bdfg_preorders_params.i18n.error); $button.prop('disabled', false); $button.text(bdfg_preorders_params.i18n.cancel_preorder); } }); } }); } // Store original button text on page load function storeOriginalButtonText() { $('.single_add_to_cart_button').each(function() { $(this).data('original-text', $(this).text()); // Add preorder button class if needed if ($(this).closest('form').hasClass('is-preorder')) { $(this).addClass('bdfg-preorder-button'); } }); } // Initialize everything when document is ready $(document).ready(function() { initCountdownTimers(); handleVariationChanges(); handleCancelPreorder(); storeOriginalButtonText(); }); })(jQuery);
assets/js/admin.js
/** * BDFG Pre-Orders admin JavaScript * * @package BDFG_PreOrders */ (function($) { 'use strict'; // Initialize datepicker function initDatepicker() { $('.bdfg-datepicker').datepicker({ dateFormat: bdfg_preorders.date_format, changeMonth: true, changeYear: true }); } // Initialize timepicker function initTimepicker() { if ($.fn.timepicker) { $('.bdfg-timepicker').timepicker({ timeFormat: 'HH:mm', interval: 15, dynamic: false, dropdown: true, scrollbar: true }); } } // Handle preorder tab visibility function handlePreorderTab() { var $enablePreorder = $('#_bdfg_preorder_enabled'); var $preorderTab = $('.bdfg_preorder_options'); function togglePreorderTab() { if ($enablePreorder.is(':checked')) { $preorderTab.show(); } else { $preorderTab.hide(); } } togglePreorderTab(); $enablePreorder.on('change', togglePreorderTab); } // Handle pricing type changes function handlePricingType() { var $pricingType = $('#_bdfg_preorder_pricing_type'); var $priceAdjustment = $('#_bdfg_preorder_price_adjustment'); var $priceLabel = $('.bdfg-price-adjustment-label'); function updatePriceLabel() { var type = $pricingType.val(); if (type === 'percentage') { $priceLabel.text(bdfg_preorders.i18n.percentage); $priceAdjustment.attr('step', '0.01'); } else { $priceLabel.text(bdfg_preorders.i18n.fixed_amount); $priceAdjustment.attr('step', '0.01'); } } updatePriceLabel(); $pricingType.on('change', updatePriceLabel); } // Confirm delete action function confirmDelete() { $('.bdfg-delete-preorder').on('click', function(e) { if (!confirm(bdfg_preorders.i18n.confirm_delete)) { e.preventDefault(); return false; } }); } // Clear cache button handler function handleClearCache() { $('#bdfg-clear-cache').on('click', function(e) { e.preventDefault(); var $button = $(this); $button.prop('disabled', true); $button.text(bdfg_preorders.i18n.clearing_cache); $.ajax({ url: bdfg_preorders.ajax_url, type: 'POST', data: { action: 'bdfg_clear_cache', nonce: bdfg_preorders.nonce }, success: function(response) { if (response.success) { $button.text(bdfg_preorders.i18n.cache_cleared); setTimeout(function() { $button.prop('disabled', false); $button.text(bdfg_preorders.i18n.clear_cache); }, 2000); } else { $button.text(bdfg_preorders.i18n.error); setTimeout(function() { $button.prop('disabled', false); $button.text(bdfg_preorders.i18n.clear_cache); }, 2000); } }, error: function() { $button.text(bdfg_preorders.i18n.error); setTimeout(function() { $button.prop('disabled', false); $button.text(bdfg_preorders.i18n.clear_cache); }, 2000); } }); }); } // Initialize everything when document is ready $(document).ready(function() { initDatepicker(); initTimepicker(); handlePreorderTab(); handlePricingType(); confirmDelete(); handleClearCache(); }); })(jQuery);
assets/css/frontend.css
/** * BDFG Pre-Orders frontend styles * * @package BDFG_PreOrders */ /* Pre-order badge */ .bdfg-preorder-badge { display: inline-block; background-color: #3d9cd2; color: #fff; padding: 5px 10px; font-size: 12px; font-weight: bold; border-radius: 3px; margin-bottom: 15px; text-transform: uppercase; } /* Pre-order availability */ .bdfg-preorder-availability { margin-bottom: 15px; font-weight: 500; color: #555; } /* Pre-order custom message */ .bdfg-preorder-message { background-color: #f7f6f7; border-left: 3px solid #3d9cd2; padding: 10px 15px; margin-bottom: 20px; } /* Preorder button */ .single_add_to_cart_button.bdfg-preorder-button { background-color: #3d9cd2 !important; border-color: #3d9cd2 !important; } /* Countdown timer */ .bdfg-preorder-countdown { margin: 15px 0; padding: 10px; border: 1px solid #e5e5e5; border-radius: 4px; background-color: #f9f9f9; } .bdfg-preorder-countdown .countdown-label { font-weight: bold; margin-bottom: 5px; color: #333; } .bdfg-preorder-countdown .countdown-timer { font-size: 24px; font-weight: bold; text-align: center; color: #3d9cd2; } .bdfg-preorder-countdown .countdown-labels { display: flex; justify-content: space-around; font-size: 12px; color: #777; margin-top: 5px; } /* Smaller countdown timer for order details */ .bdfg-preorder-countdown-small { font-size: 14px; margin-top: 5px; color: #3d9cd2; } /* Price styling */ .bdfg-preorder-price { font-weight: bold; } .bdfg-preorder-original-price { opacity: 0.7; } .bdfg-preorder-discount { color: #77a464; } .bdfg-preorder-premium { color: #e2401c; } /* Preorder status indicators */ .bdfg-preorder-status-pending { color: #ffba00; font-weight: bold; } .bdfg-preorder-status-released { color: #77a464; font-weight: bold; } .bdfg-preorder-status-cancelled { color: #e2401c; font-weight: bold; } /* Preorder details on order page */ .bdfg-preorder-details { margin-bottom: 30px; } .preorder_details { width: 100%; margin-bottom: 20px; } .preorder_details th { text-align: left; padding: 10px; background-color: #f8f8f8; } .preorder_details td { padding: 10px; border-bottom: 1px solid #f8f8f8; } /* Responsive styles */ @media (max-width: 768px) { .bdfg-preorder-countdown .countdown-timer { font-size: 20px; } .bdfg-preorder-countdown .countdown-labels { font-size: 10px; } }
assets/css/admin.css
/** * BDFG Pre-Orders admin styles * * @package BDFG_PreOrders */ /* Tabs for product data panel */ #bdfg_preorder_data .panel-wrap { overflow: hidden; } /* Options visibility */ .bdfg_preorder_options.hide { display: none; } /* Preorder settings fields */ .bdfg-preorder-field { margin-bottom: 12px; } .bdfg-preorder-field label { display: block; margin-bottom: 5px; font-weight: bold; } .bdfg-preorder-field .description { color: #777; font-style: italic; font-size: 12px; margin-top: 5px; } .bdfg-preorder-field .bdfg-field-inline { display: flex; align-items: center; } .bdfg-preorder-field .bdfg-field-inline label { margin-right: 10px; margin-bottom: 0; } /* Date and time fields */ .bdfg-datepicker, .bdfg-timepicker { width: 150px; } /* Status indicators */ .bdfg-status-indicator { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-left: 5px; } .bdfg-status-indicator.pending { background-color: #ffba00; } .bdfg-status-indicator.released { background-color: #77a464; } .bdfg-status-indicator.cancelled { background-color: #e2401c; } /* Dashboard widget */ .bdfg-preorders-widget { padding: 0 12px; } .bdfg-preorders-stats { display: flex; justify-content: space-between; margin-bottom: 20px; padding-top: 12px; } .bdfg-stat-item { text-align: center; flex: 1; padding: 10px; background-color: #f9f9f9; border: 1px solid #eee; border-radius: 3px; margin: 0 5px; } .bdfg-stat-item:first-child { margin-left: 0; } .bdfg-stat-item:last-child { margin-right: 0; } .bdfg-stat-value { display: block; font-size: 24px; font-weight: bold; color: #3d9cd2; margin-bottom: 5px; } .bdfg-stat-label { display: block; color: #777; font-size: 12px; } .bdfg-upcoming-releases { margin-bottom: 15px; padding-left: 0; list-style: none; } .bdfg-upcoming-releases li { border-bottom: 1px solid #f0f0f0; padding: 8px 0; } .bdfg-upcoming-releases li:last-child { border-bottom: none; } .bdfg-release-date { font-size: 12px; color: #777; margin-right: 10px; } .bdfg-product-title { font-weight: 500; } .bdfg-widget-footer { margin-top: 15px; text-align: right; padding-bottom: 12px; } /* Order list styling */ .column-bdfg_preorder mark.preorder { background: #e8f4fa; color: #3d9cd2; padding: 4px 8px; border-radius: 3px; } /* Pre-orders management page */ .bdfg-preorders-management { margin-top: 20px; } .bdfg-preorders-management .date-filter { margin: 0 10px; } .bdfg-preorders-management .date-filter .date-picker { width: 120px; } .bdfg-preorder-status-pending { color: #ffba00; font-weight: bold; } .bdfg-preorder-status-available { color: #77a464; font-weight: bold; } .bdfg-preorder-status-cancelled { color: #e2401c; font-weight: bold; } .bdfg-preorder-status-refunded { color: #999; font-weight: bold; } /* Settings page */ .bdfg-settings-section { margin-bottom: 20px; padding: 15px; background-color: #fff; border: 1px solid #ddd; border-radius: 3px; } .bdfg-settings-section h3 { margin-top: 0; padding-bottom: 10px; border-bottom: 1px solid #eee; } .form-table th { padding-left: 0; } .bdfg-color-picker { width: 80px; } .bdfg-welcome-panel { padding: 20px; margin-bottom: 20px; background-color: #fff; border: 1px solid #ddd; border-radius: 3px; } .bdfg-welcome-panel h2 { margin-top: 0; color: #3d9cd2; } .bdfg-features-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 20px; } .bdfg-feature-card { border: 1px solid #eee; border-radius: 3px; padding: 15px; background-color: #f9f9f9; } .bdfg-feature-card h3 { margin-top: 0; color: #3d9cd2; } @media screen and (max-width: 782px) { .bdfg-features-grid { grid-template-columns: repeat(1, 1fr); } .bdfg-preorders-stats { flex-direction: column; } .bdfg-stat-item { margin: 5px 0; } } /* Action buttons in list table */ .bdfg-action-release { color: #77a464; } .bdfg-action-cancel { color: #e2401c; }
templates/preorder-info.php
<?php /** * Product preorder information template * * This template can be overridden by copying it to yourtheme/bdfg-preorders/preorder-info.php. * * @package BDFG_PreOrders * @version 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } /** * @var WC_Product $product */ ?> <div class="bdfg-preorder-info"> <?php if (BDFG_PreOrders_Product::is_preorder($product->get_id())) : ?> <?php bdfg_preorders_display_badge($product); ?> <?php bdfg_preorders_display_availability($product); ?> <?php if (get_option('bdfg_preorders_show_countdown', 'yes') === 'yes') : ?> <?php bdfg_preorders_display_countdown($product); ?> <?php endif; ?> <?php // Display custom message if available $custom_message = get_post_meta($product->get_id(), '_bdfg_preorder_custom_message', true); if (!empty($custom_message)) : ?> <div class="bdfg-preorder-message"><?php echo wp_kses_post(wpautop($custom_message)); ?></div> <?php endif; ?> <?php endif; ?> </div>
includes/admin/views/settings.php
<?php /** * Admin settings view for BDFG Pre-Orders * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } ?> <div class="wrap woocommerce"> <h1><?php esc_html_e('BDFG Pre-Orders Settings', 'bdfg-preorders'); ?></h1> <?php settings_errors(); ?> <nav class="nav-tab-wrapper woo-nav-tab-wrapper"> <?php $tabs = array( 'general' => __('General', 'bdfg-preorders'), 'display' => __('Display', 'bdfg-preorders'), 'emails' => __('Emails', 'bdfg-preorders'), 'advanced' => __('Advanced', 'bdfg-preorders'), 'license' => __('License', 'bdfg-preorders'), 'welcome' => __('About', 'bdfg-preorders'), ); $current_tab = isset($_GET['tab']) ? sanitize_title($_GET['tab']) : 'general'; foreach ($tabs as $tab_id => $tab_name) { $active = $current_tab === $tab_id ? 'nav-tab-active' : ''; echo '<a href="' . esc_url(admin_url('admin.php?page=bdfg-preorders-settings&tab=' . $tab_id)) . '" class="nav-tab ' . esc_attr($active) . '">' . esc_html($tab_name) . '</a>'; } ?> </nav> <div class="bdfg-settings-container"> <form method="post" action="options.php"> <?php if ($current_tab === 'welcome') { include BDFG_PREORDERS_PATH . 'includes/admin/views/welcome.php'; } else { // Output nonce, action, and option_page fields settings_fields('bdfg_preorders_' . $current_tab); // Output settings sections and fields do_settings_sections('bdfg_preorders_' . $current_tab); // Output save button submit_button(); } ?> </form> </div> </div>
includes/admin/views/welcome.php
<?php /** * Welcome page for BDFG Pre-Orders admin * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } ?> <div class="bdfg-welcome-panel"> <h2><?php esc_html_e('Welcome to BDFG Pre-Orders for WooCommerce', 'bdfg-preorders'); ?></h2> <p class="about-description"> <?php esc_html_e('Version 2.0.0 brings powerful new features and improvements to help you manage pre-orders more effectively.', 'bdfg-preorders'); ?> </p> <div class="bdfg-features-grid"> <div class="bdfg-feature-card"> <h3><?php esc_html_e('Enhanced Management Dashboard', 'bdfg-preorders'); ?></h3> <p><?php esc_html_e('The new dedicated dashboard makes it easy to track, filter, and manage all your pre-orders in one place.', 'bdfg-preorders'); ?></p> <a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-preorders-management')); ?>" class="button button-primary"><?php esc_html_e('Go to Dashboard', 'bdfg-preorders'); ?></a> </div> <div class="bdfg-feature-card"> <h3><?php esc_html_e('Flexible Pricing Options', 'bdfg-preorders'); ?></h3> <p><?php esc_html_e('Offer special pricing for pre-orders with fixed or percentage-based discounts or premiums.', 'bdfg-preorders'); ?></p> <a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-preorders-settings&tab=general')); ?>" class="button"><?php esc_html_e('Configure Settings', 'bdfg-preorders'); ?></a> </div> <div class="bdfg-feature-card"> <h3><?php esc_html_e('Automated Notifications', 'bdfg-preorders'); ?></h3> <p><?php esc_html_e('Customers automatically receive emails when their pre-ordered products become available.', 'bdfg-preorders'); ?></p> <a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-preorders-settings&tab=emails')); ?>" class="button"><?php esc_html_e('Email Settings', 'bdfg-preorders'); ?></a> </div> <div class="bdfg-feature-card"> <h3><?php esc_html_e('Countdown Timers', 'bdfg-preorders'); ?></h3> <p><?php esc_html_e('Build excitement with countdown timers showing when products will be available.', 'bdfg-preorders'); ?></p> <a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-preorders-settings&tab=display')); ?>" class="button"><?php esc_html_e('Display Settings', 'bdfg-preorders'); ?></a> </div> <div class="bdfg-feature-card"> <h3><?php esc_html_e('Documentation', 'bdfg-preorders'); ?></h3> <p><?php esc_html_e('Comprehensive guides and tutorials to help you get the most from the plugin.', 'bdfg-preorders'); ?></p> <a href="https://beiduofengou.net/docs/bdfg-preorders/" target="_blank" class="button"><?php esc_html_e('View Docs', 'bdfg-preorders'); ?></a> </div> <div class="bdfg-feature-card"> <h3><?php esc_html_e('Support', 'bdfg-preorders'); ?></h3> <p><?php esc_html_e('Need help? Our support team is ready to assist with any questions or issues.', 'bdfg-preorders'); ?></p> <a href="https://beiduofengou.net/support/" target="_blank" class="button"><?php esc_html_e('Get Support', 'bdfg-preorders'); ?></a> </div> </div> <div class="bdfg-system-info"> <h3><?php esc_html_e('System Information', 'bdfg-preorders'); ?></h3> <table class="widefat" cellspacing="0"> <tbody> <tr> <td><?php esc_html_e('BDFG Pre-Orders Version:', 'bdfg-preorders'); ?></td> <td><?php echo BDFG_PREORDERS_VERSION; ?></td> </tr> <tr> <td><?php esc_html_e('WordPress Version:', 'bdfg-preorders'); ?></td> <td><?php echo get_bloginfo('version'); ?></td> </tr> <tr> <td><?php esc_html_e('WooCommerce Version:', 'bdfg-preorders'); ?></td> <td><?php echo defined('WC_VERSION') ? WC_VERSION : __('Not Active', 'bdfg-preorders'); ?></td> </tr> <tr> <td><?php esc_html_e('PHP Version:', 'bdfg-preorders'); ?></td> <td><?php echo phpversion(); ?></td> </tr> <tr> <td><?php esc_html_e('Database Version:', 'bdfg-preorders'); ?></td> <td><?php echo get_option('bdfg_preorders_db_version', '1.0.0'); ?></td> </tr> <tr> <td><?php esc_html_e('Last Updated:', 'bdfg-preorders'); ?></td> <td><?php echo date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime('2025-03-05 14:23:48')); ?></td> </tr> </tbody> </table> </div> </div>
includes/admin/views/management.php
<?php /** * Pre-Orders management page * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } // Include the list table class require_once BDFG_PREORDERS_PATH . 'includes/admin/class-bdfg-preorders-list-table.php'; // Create an instance of the list table $list_table = new BDFG_PreOrders_List_Table(); $list_table->prepare_items(); // Get preorder statistics $stats = BDFG_PreOrders_Product::get_preorder_stats(); ?> <div class="wrap bdfg-preorders-management"> <h1 class="wp-heading-inline"><?php esc_html_e('Pre-Orders Management', 'bdfg-preorders'); ?></h1> <hr class="wp-header-end"> <div class="bdfg-preorders-stats"> <div class="bdfg-stat-item"> <span class="bdfg-stat-value"><?php echo esc_html($stats['active_preorders']); ?></span> <span class="bdfg-stat-label"><?php esc_html_e('Active Pre-Orders', 'bdfg-preorders'); ?></span> </div> <div class="bdfg-stat-item"> <span class="bdfg-stat-value"><?php echo esc_html($stats['pending_products']); ?></span> <span class="bdfg-stat-label"><?php esc_html_e('Pending Release Products', 'bdfg-preorders'); ?></span> </div> <div class="bdfg-stat-item"> <span class="bdfg-stat-value"><?php echo esc_html($stats['total_products']); ?></span> <span class="bdfg-stat-label"><?php esc_html_e('Total Pre-Order Products', 'bdfg-preorders'); ?></span> </div> <div class="bdfg-stat-item"> <span class="bdfg-stat-value"><?php echo wc_price($stats['revenue']); ?></span> <span class="bdfg-stat-label"><?php esc_html_e('Pre-Order Revenue', 'bdfg-preorders'); ?></span> </div> </div> <div class="bdfg-preorders-list"> <form method="post"> <?php $list_table->search_box(__('Search Pre-Orders', 'bdfg-preorders'), 'search_id'); ?> <?php $list_table->display(); ?> </form> </div> <div class="bdfg-management-help"> <h3><?php esc_html_e('Pre-Order Management Help', 'bdfg-preorders'); ?></h3> <p><?php esc_html_e('This page allows you to manage all pre-orders in your store. You can:', 'bdfg-preorders'); ?></p> <ul> <li><?php esc_html_e('Filter pre-orders by status or date', 'bdfg-preorders'); ?></li> <li><?php esc_html_e('Search for specific pre-orders', 'bdfg-preorders'); ?></li> <li><?php esc_html_e('Manually release pre-orders before their scheduled date', 'bdfg-preorders'); ?></li> <li><?php esc_html_e('Cancel pending pre-orders if needed', 'bdfg-preorders'); ?></li> </ul> <p> <?php echo wp_kses( sprintf( __('Need more help? Check our <a href="%s" target="_blank">documentation</a>.', 'bdfg-preorders'), esc_url('https://beiduofengou.net/docs/bdfg-preorders/') ), array('a' => array('href' => array(), 'target' => array())) ); ?> </p> </div> </div>
includes/admin/class-bdfg-preorders-dashboard.php
<?php /** * Dashboard widgets for BDFG Pre-Orders * * @package BDFG_PreOrders * @since 2.0.0 */ // Prevent direct file access if (!defined('ABSPATH')) { exit; } /** * Dashboard widget class */ class BDFG_PreOrders_Dashboard { /** * Constructor */ public function __construct() { // Add dashboard widget add_action('wp_dashboard_setup', array($this, 'add_dashboard_widget')); } /** * Add dashboard widget */ public function add_dashboard_widget() { wp_add_dashboard_widget( 'bdfg_preorders_dashboard_widget', __('BDFG Pre-Orders Overview', 'bdfg-preorders'), array($this, 'dashboard_widget_content') ); } /** * Dashboard widget content */ public function dashboard_widget_content() { // Get statistics $stats = BDFG_PreOrders_Product::get_preorder_stats(); // Get upcoming releases $upcoming = BDFG_PreOrders_Product::get_upcoming_preorders(5); ?> <div class="bdfg-preorders-widget"> <div class="bdfg-preorders-stats"> <div class="bdfg-stat-item"> <span class="bdfg-stat-value"><?php echo esc_html($stats['active_preorders']); ?></span> <span class="bdfg-stat-label"><?php esc_html_e('Active Pre-Orders', 'bdfg-preorders'); ?></span> </div> <div class="bdfg-stat-item"> <span class="bdfg-stat-value"><?php echo wc_price($stats['revenue']); ?></span> <span class="bdfg-stat-label"><?php esc_html_e('Pre-Order Revenue', 'bdfg-preorders'); ?></span> </div> </div> <?php if (!empty($upcoming)) : ?> <h4><?php esc_html_e('Upcoming Releases', 'bdfg-preorders'); ?></h4> <ul class="bdfg-upcoming-releases"> <?php foreach ($upcoming as $product) : ?> <?php $date = !empty($product->date) ? BDFG_PreOrders_Helper::format_date($product->date, $product->time) : ''; $edit_link = get_edit_post_link($product->ID); ?> <li> <span class="bdfg-release-date"><?php echo esc_html($date); ?></span> <a href="<?php echo esc_url($edit_link); ?>" class="bdfg-product-title"><?php echo esc_html($product->post_title); ?></a> </li> <?php endforeach; ?> </ul> <?php else : ?> <p><?php esc_html_e('No upcoming pre-order releases.', 'bdfg-preorders'); ?></p> <?php endif; ?> <div class="bdfg-widget-footer"> <a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-preorders-management')); ?>" class="button button-primary"><?php esc_html_e('Manage Pre-Orders', 'bdfg-preorders'); ?></a> </div> </div> <?php } } // Initialize the dashboard widget new BDFG_PreOrders_Dashboard();