功能包括储蓄,共享,自定义结帐字段和高级分析。
*具有可自定义位置的浮动迷你手推车
*购物车节省和共享功能
*自定义结帐字段经理
*详细的购物车分析和报告
*购物车放弃跟踪
*实时购物车更新
*针对客户的购物车笔记
*多个购物车模板
*移动友好的设计
相关文章: WooCommerce 自定义”加入购物车”按钮
<?php /** * Plugin Name: BDFG Advanced Cart * Plugin URI: https://beiduofengou.net/2024/03/15/bdfg-advanced-cart/ * Description: A premium WooCommerce extension that enhances your store's cart experience with features like cart saving, sharing, real-time analytics, and a beautiful floating mini-cart. * Version: 2.2.1 * Author: beiduofengou * Author URI: https://beiduofengou.net * Text Domain: bdfg-advanced-cart * Domain Path: /languages * Requires at least: 5.8 * Requires PHP: 7.4 * WC requires at least: 5.0 * WC tested up to: 8.0 * License: GPL v2 or later * * @package BDFG_Advanced_Cart * @author beiduofengou * @link https://beiduofengou.net */ if (!defined('ABSPATH')) { exit; // Exit if accessed directly } // Define BDFG_CART_VERSION define('BDFG_CART_VERSION', '2.2.1'); define('BDFG_CART_FILE', __FILE__); define('BDFG_CART_PATH', plugin_dir_path(BDFG_CART_FILE)); define('BDFG_CART_URL', plugin_dir_url(BDFG_CART_FILE)); if (!class_exists('BDFG_Advanced_Cart')): class BDFG_Advanced_Cart { /** * Single instance of the class * * @var BDFG_Advanced_Cart */ private static $instance = null; /** * Main plugin path * * @var string */ private $plugin_path; /** * Plugin URL for asset loading * * @var string */ private $plugin_url; /** * Plugin components * * @var array */ private $components = array(); /** * Returns the singleton instance of this class * * @return BDFG_Advanced_Cart */ public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } /** * Protected constructor to prevent direct object creation */ protected function __construct() { $this->plugin_path = BDFG_CART_PATH; $this->plugin_url = BDFG_CART_URL; // Initialize early hooks add_action('plugins_loaded', array($this, 'init'), 0); // Register activation and deactivation hooks register_activation_hook(BDFG_CART_FILE, array($this, 'activate')); register_deactivation_hook(BDFG_CART_FILE, array($this, 'deactivate')); } /** * Initialize plugin components and hooks */ public function init() { // First check if WooCommerce is active if (!$this->check_woocommerce()) { return; } // Load text domain for translations $this->load_textdomain(); // Initialize components $this->init_components(); // Set up hooks $this->init_hooks(); // Let other plugins know we're ready do_action('bdfg_cart_loaded'); } /** * Check if WooCommerce is active and compatible * * @return bool */ private function check_woocommerce() { if (!class_exists('WooCommerce')) { add_action('admin_notices', array($this, 'woocommerce_missing_notice')); return false; } // Add version compatibility check if needed return true; } /** * Load plugin text domain */ private function load_textdomain() { load_plugin_textdomain( 'bdfg-advanced-cart', false, dirname(plugin_basename(BDFG_CART_FILE)) . '/languages' ); } /** * Initialize plugin components */ private function init_components() { // Include core files require_once $this->plugin_path . 'includes/class-bdfg-cart-manager.php'; require_once $this->plugin_path . 'includes/class-bdfg-checkout-fields.php'; require_once $this->plugin_path . 'includes/class-bdfg-mini-cart.php'; require_once $this->plugin_path . 'includes/class-bdfg-cart-analytics.php'; // Initialize components $this->components['cart_manager'] = new BDFG_Cart_Manager(); $this->components['checkout_fields'] = new BDFG_Checkout_Fields(); $this->components['mini_cart'] = new BDFG_Mini_Cart(); $this->components['analytics'] = new BDFG_Cart_Analytics(); // Load admin if needed if (is_admin()) { require_once $this->plugin_path . 'admin/class-bdfg-admin.php'; $this->components['admin'] = new BDFG_Admin(); } } /** * Set up plugin hooks */ private function init_hooks() { // Assets add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); // Add settings link to plugins page add_filter('plugin_action_links_' . plugin_basename(BDFG_CART_FILE), array($this, 'add_plugin_links')); } /** * Plugin activation tasks */ public function activate() { // Create necessary database tables if (isset($this->components['analytics'])) { $this->components['analytics']->create_tables(); } // Set default options $this->set_default_options(); // Clear transients $this->clear_transients(); // Let other plugins know we've activated do_action('bdfg_cart_activated'); } /** * Plugin deactivation tasks */ public function deactivate() { // Clear any scheduled hooks wp_clear_scheduled_hooks('bdfg_daily_cleanup'); // Clear transients $this->clear_transients(); // Let other plugins know we're deactivating do_action('bdfg_cart_deactivated'); } /** * Set default plugin options */ private function set_default_options() { $defaults = array( 'bdfg_mini_cart_position' => 'right', 'bdfg_cart_expiry_days' => 30, 'bdfg_enable_cart_sharing' => 'yes', 'bdfg_popup_cart_template' => 'default', 'bdfg_analytics_enabled' => 'yes', 'bdfg_cart_threshold' => 0 ); foreach ($defaults as $key => $value) { if (false === get_option($key)) { update_option($key, $value); } } } /** * Clear plugin transients */ private function clear_transients() { global $wpdb; $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_bdfg_%' OR option_name LIKE '_transient_timeout_bdfg_%'" ); } /** * Enqueue frontend assets */ public function enqueue_frontend_assets() { if (is_cart() || is_checkout()) { wp_enqueue_style( 'bdfg-frontend', $this->plugin_url . 'assets/css/frontend.min.css', array(), BDFG_CART_VERSION ); wp_enqueue_script( 'bdfg-frontend', $this->plugin_url . 'assets/js/frontend.min.js', array('jquery'), BDFG_CART_VERSION, true ); wp_localize_script('bdfg-frontend', 'bdfgCart', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('bdfg-cart-nonce'), 'i18n' => array( 'cartSaved' => __('Cart saved successfully!', 'bdfg-advanced-cart'), 'cartRestored' => __('Cart restored successfully!', 'bdfg-advanced-cart'), 'error' => __('An error occurred. Please try again.', 'bdfg-advanced-cart') ) )); } } /** * Enqueue admin assets */ public function enqueue_admin_assets($hook) { // Only load on our settings page if ('woocommerce_page_bdfg-settings' !== $hook) { return; } wp_enqueue_style( 'bdfg-admin', $this->plugin_url . 'assets/css/admin.min.css', array(), BDFG_CART_VERSION ); wp_enqueue_script( 'bdfg-admin', $this->plugin_url . 'assets/js/admin.min.js', array('jquery'), BDFG_CART_VERSION, true ); } /** * Add plugin action links * * @param array $links * @return array */ public function add_plugin_links($links) { $plugin_links = array( '<a href="' . admin_url('admin.php?page=bdfg-settings') . '">' . __('Settings', 'bdfg-advanced-cart') . '</a>', '<a href="https://beiduofengou.net/docs/bdfg-advanced-cart" target="_blank">' . __('Documentation', 'bdfg-advanced-cart') . '</a>' ); return array_merge($plugin_links, $links); } /** * Display WooCommerce missing notice */ public function woocommerce_missing_notice() { ?> <div class="error"> <p> <?php printf( __('%s requires WooCommerce to be installed and activated. You can download %s here.', 'bdfg-advanced-cart'), '<strong>BDFG Advanced Cart</strong>', '<a href="https://wordpress.org/plugins/woocommerce/" target="_blank">WooCommerce</a>' ); ?> </p> </div> <?php } /** * Get plugin path * * @return string */ public function get_plugin_path() { return $this->plugin_path; } /** * Get plugin URL * * @return string */ public function get_plugin_url() { return $this->plugin_url; } /** * Get a component instance * * @param string $component * @return object|null */ public function get_component($component) { return isset($this->components[$component]) ? $this->components[$component] : null; } } endif; /** * Returns the main instance of BDFG_Advanced_Cart * * @return BDFG_Advanced_Cart */ function BDFG_Advanced_Cart() { return BDFG_Advanced_Cart::get_instance(); } // Initialize the plugin BDFG_Advanced_Cart();
includes/class-bdfg-cart-manager.php
<?php /** * Cart Manager Class * * Handles advanced cart management features including saving, sharing, * and restoring cart states. * * @package BDFG_Advanced_Cart * @author beiduofengou * @link https://beiduofengou.net */ if (!defined('ABSPATH')) { exit; // Exit if accessed directly } class BDFG_Cart_Manager { /** * Database table name for saved carts * * @var string */ private $table_name; /** * Cart expiry period in days * * @var int */ private $expiry_days; /** * Constructor */ public function __construct() { global $wpdb; $this->table_name = $wpdb->prefix . 'bdfg_saved_carts'; $this->expiry_days = get_option('bdfg_cart_expiry_days', 30); $this->init_hooks(); } /** * Initialize hooks */ private function init_hooks() { // AJAX handlers add_action('wp_ajax_bdfg_save_cart', array($this, 'ajax_save_cart')); add_action('wp_ajax_bdfg_restore_cart', array($this, 'ajax_restore_cart')); add_action('wp_ajax_bdfg_share_cart', array($this, 'ajax_share_cart')); add_action('wp_ajax_bdfg_delete_saved_cart', array($this, 'ajax_delete_saved_cart')); // Frontend display add_action('woocommerce_after_cart_table', array($this, 'add_cart_actions')); add_action('woocommerce_before_cart', array($this, 'handle_cart_actions')); // Cleanup add_action('bdfg_daily_cleanup', array($this, 'cleanup_expired_carts')); // Integration hooks add_filter('bdfg_cart_save_data', array($this, 'prepare_cart_data'), 10, 2); add_action('bdfg_after_cart_restore', array($this, 'trigger_cart_restored'), 10, 2); } /** * Add cart action buttons */ public function add_cart_actions() { if (!is_user_logged_in()) { return; } $saved_carts = $this->get_user_saved_carts(get_current_user_id()); // Include template include BDFG_CART_PATH . 'templates/cart/save-cart-buttons.php'; } /** * Handle cart actions from POST requests */ public function handle_cart_actions() { if (!isset($_POST['bdfg_cart_action']) || !wp_verify_nonce($_POST['bdfg_cart_nonce'], 'bdfg_cart_action')) { return; } $action = sanitize_text_field($_POST['bdfg_cart_action']); switch ($action) { case 'save': $this->save_current_cart(); break; case 'restore': if (isset($_POST['cart_key'])) { $this->restore_cart(sanitize_text_field($_POST['cart_key'])); } break; case 'delete': if (isset($_POST['cart_key'])) { $this->delete_saved_cart(sanitize_text_field($_POST['cart_key'])); } break; } } /** * AJAX handler for saving cart */ public function ajax_save_cart() { check_ajax_referer('bdfg-cart-nonce', 'nonce'); if (!is_user_logged_in()) { wp_send_json_error(array( 'message' => __('Please log in to save your cart.', 'bdfg-advanced-cart') )); } $cart_name = isset($_POST['cart_name']) ? sanitize_text_field($_POST['cart_name']) : ''; $result = $this->save_current_cart($cart_name); if (is_wp_error($result)) { wp_send_json_error(array( 'message' => $result->get_error_message() )); } wp_send_json_success(array( 'message' => __('Cart saved successfully!', 'bdfg-advanced-cart'), 'cart_key' => $result )); } /** * Save current cart state * * @param string $cart_name Optional name for the saved cart * @return string|WP_Error Cart key if successful, WP_Error on failure */ private function save_current_cart($cart_name = '') { global $wpdb; if (WC()->cart->is_empty()) { return new WP_Error( 'empty_cart', __('Cannot save an empty cart.', 'bdfg-advanced-cart') ); } $user_id = get_current_user_id(); $cart_key = $this->generate_cart_key(); // Prepare cart data $cart_data = array( 'items' => WC()->cart->get_cart_for_session(), 'applied_coupons' => WC()->cart->get_applied_coupons(), 'cart_totals' => WC()->cart->get_totals(), 'name' => $cart_name, 'timestamp' => current_time('mysql') ); // Allow plugins to modify cart data before saving $cart_data = apply_filters('bdfg_cart_save_data', $cart_data, $user_id); $result = $wpdb->insert( $this->table_name, array( 'user_id' => $user_id, 'cart_key' => $cart_key, 'cart_data' => wp_json_encode($cart_data), 'created_at' => current_time('mysql'), 'expires_at' => date('Y-m-d H:i:s', strtotime("+{$this->expiry_days} days")), 'cart_name' => $cart_name ), array( '%d', '%s', '%s', '%s', '%s', '%s' ) ); if (!$result) { return new WP_Error( 'save_failed', __('Failed to save cart. Please try again.', 'bdfg-advanced-cart') ); } do_action('bdfg_cart_saved', $cart_key, $user_id); return $cart_key; } /** * Get saved cart by key * * @param string $cart_key * @return object|null Cart data if found, null if not */ private function get_saved_cart($cart_key) { global $wpdb; $cart = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE cart_key = %s AND expires_at > NOW()", $cart_key )); if (!$cart) { return null; } $cart->cart_data = json_decode($cart->cart_data, true); return $cart; } /** * Get all saved carts for a user * * @param int $user_id * @return array */ public function get_user_saved_carts($user_id) { global $wpdb; $carts = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE user_id = %d AND expires_at > NOW() ORDER BY created_at DESC", $user_id )); foreach ($carts as &$cart) { $cart->cart_data = json_decode($cart->cart_data, true); } return $carts; } /** * Restore a saved cart * * @param string $cart_key * @return bool|WP_Error True on success, WP_Error on failure */ public function restore_cart($cart_key) { $saved_cart = $this->get_saved_cart($cart_key); if (!$saved_cart) { return new WP_Error( 'invalid_cart', __('Cart not found or expired.', 'bdfg-advanced-cart') ); } // Clear current cart WC()->cart->empty_cart(); // Restore cart items foreach ($saved_cart->cart_data['items'] as $cart_item_key => $cart_item) { WC()->cart->add_to_cart( $cart_item['product_id'], $cart_item['quantity'], $cart_item['variation_id'], $cart_item['variation'], $cart_item ); } // Restore coupons foreach ($saved_cart->cart_data['applied_coupons'] as $coupon) { WC()->cart->apply_coupon($coupon); } do_action('bdfg_cart_restored', $cart_key, $saved_cart); return true; } /** * Delete a saved cart * * @param string $cart_key * @return bool */ public function delete_saved_cart($cart_key) { global $wpdb; $result = $wpdb->delete( $this->table_name, array('cart_key' => $cart_key), array('%s') ); if ($result) { do_action('bdfg_cart_deleted', $cart_key); return true; } return false; } /** * Generate a unique cart key * * @return string */ private function generate_cart_key() { return wp_generate_password(12, false); } /** * Clean up expired carts */ public function cleanup_expired_carts() { global $wpdb; $wpdb->query( "DELETE FROM {$this->table_name} WHERE expires_at < NOW()" ); do_action('bdfg_expired_carts_cleaned'); } /** * Share cart via email * * @param string $cart_key * @param string $email * @return bool|WP_Error */ public function share_cart($cart_key, $email) { $saved_cart = $this->get_saved_cart($cart_key); if (!$saved_cart) { return new WP_Error( 'invalid_cart', __('Cart not found or expired.', 'bdfg-advanced-cart') ); } $share_url = add_query_arg(array( 'bdfg_restore_cart' => $cart_key ), wc_get_cart_url()); $mailer = WC()->mailer(); $recipient = sanitize_email($email); $subject = sprintf( __('[%s] Shared Shopping Cart', 'bdfg-advanced-cart'), get_bloginfo('name') ); // Get email template ob_start(); include BDFG_CART_PATH . 'templates/emails/shared-cart.php'; $content = ob_get_clean(); $headers = "Content-Type: text/html\r\n"; $result = $mailer->send($recipient, $subject, $content, $headers); if ($result) { do_action('bdfg_cart_shared', $cart_key, $email); return true; } return new WP_Error( 'share_failed', __('Failed to share cart. Please try again.', 'bdfg-advanced-cart') ); } }
includes/class-bdfg-checkout-fields.php
相关文章: WooCommerce 快速下单插件
<?php /** * Checkout Fields Manager Class * * Handles custom checkout fields functionality with dynamic field creation, * validation, and storage. * * @package BDFG_Advanced_Cart * @author beiduofengou * @link https://beiduofengou.net * @since 2.2.1 */ if (!defined('ABSPATH')) { exit; // Exit if accessed directly } class BDFG_Checkout_Fields { /** * Custom fields configuration * * @var array */ private $custom_fields; /** * Field types supported * * @var array */ private $supported_types = array( 'text' => 'Text', 'textarea' => 'Textarea', 'select' => 'Select', 'radio' => 'Radio', 'checkbox' => 'Checkbox', 'date' => 'Date', 'phone' => 'Phone', 'email' => 'Email' ); /** * Constructor */ public function __construct() { $this->custom_fields = $this->get_stored_fields(); $this->init_hooks(); } /** * Initialize hooks */ private function init_hooks() { // Checkout form modifications add_filter('woocommerce_checkout_fields', array($this, 'modify_checkout_fields'), 20); add_action('woocommerce_checkout_process', array($this, 'validate_custom_fields')); add_action('woocommerce_checkout_update_order_meta', array($this, 'save_custom_fields')); // Admin order display add_action('woocommerce_admin_order_data_after_billing_address', array($this, 'display_custom_fields_in_admin')); add_action('woocommerce_admin_order_data_after_shipping_address', array($this, 'display_custom_fields_in_admin')); // Email display add_action('woocommerce_email_after_order_table', array($this, 'display_custom_fields_in_email'), 10, 4); // AJAX handlers for admin add_action('wp_ajax_bdfg_save_custom_field', array($this, 'ajax_save_custom_field')); add_action('wp_ajax_bdfg_delete_custom_field', array($this, 'ajax_delete_custom_field')); } /** * Get stored custom fields * * @return array */ private function get_stored_fields() { $fields = get_option('bdfg_custom_checkout_fields', array()); return is_array($fields) ? $fields : array(); } /** * Modify checkout fields * * @param array $fields * @return array */ public function modify_checkout_fields($fields) { foreach ($this->custom_fields as $field) { $section = $field['section']; // billing, shipping, or order if (!isset($fields[$section])) { continue; } $field_id = sanitize_title($field['name']); $fields[$section][$field_id] = $this->prepare_field_config($field); } return $fields; } /** * Prepare field configuration * * @param array $field * @return array */ private function prepare_field_config($field) { $config = array( 'type' => $field['type'], 'label' => wp_kses_post($field['label']), 'placeholder' => isset($field['placeholder']) ? wp_kses_post($field['placeholder']) : '', 'required' => !empty($field['required']), 'class' => isset($field['class']) ? (array) $field['class'] : array('form-row-wide'), 'priority' => isset($field['priority']) ? absint($field['priority']) : 100 ); // Add options for select and radio types if (in_array($field['type'], array('select', 'radio')) && !empty($field['options'])) { $config['options'] = $this->parse_field_options($field['options']); } // Add custom validation if specified if (!empty($field['validation'])) { $config['validate'] = (array) $field['validation']; } return apply_filters('bdfg_checkout_field_config', $config, $field); } /** * Parse field options from string to array * * @param string $options * @return array */ private function parse_field_options($options) { $result = array(); $options = explode("\n", $options); foreach ($options as $option) { $option = trim($option); if (strpos($option, '|') !== false) { list($key, $value) = explode('|', $option); $result[trim($key)] = trim($value); } else { $result[trim($option)] = trim($option); } } return $result; } /** * Validate custom fields during checkout */ public function validate_custom_fields() { foreach ($this->custom_fields as $field) { $field_id = sanitize_title($field['name']); if (!empty($field['required']) && empty($_POST[$field_id])) { wc_add_notice( sprintf( __('%s is a required field.', 'bdfg-advanced-cart'), $field['label'] ), 'error' ); continue; } if (!empty($_POST[$field_id]) && !empty($field['validation'])) { $this->validate_field_value($_POST[$field_id], $field); } } } /** * Validate individual field value * * @param mixed $value * @param array $field */ private function validate_field_value($value, $field) { switch ($field['validation']) { case 'email': if (!is_email($value)) { wc_add_notice( sprintf( __('%s is not a valid email address.', 'bdfg-advanced-cart'), $field['label'] ), 'error' ); } break; case 'phone': if (!preg_match('/^[0-9\-\(\)\/\+\s]*$/', $value)) { wc_add_notice( sprintf( __('%s is not a valid phone number.', 'bdfg-advanced-cart'), $field['label'] ), 'error' ); } break; case 'numeric': if (!is_numeric($value)) { wc_add_notice( sprintf( __('%s must be a number.', 'bdfg-advanced-cart'), $field['label'] ), 'error' ); } break; default: do_action('bdfg_validate_custom_field', $value, $field); break; } } /** * Save custom field values to order * * @param int $order_id */ public function save_custom_fields($order_id) { foreach ($this->custom_fields as $field) { $field_id = sanitize_title($field['name']); if (isset($_POST[$field_id])) { $value = $this->sanitize_field_value($_POST[$field_id], $field['type']); update_post_meta($order_id, '_' . $field_id, $value); } } } /** * Sanitize field value based on type * * @param mixed $value * @param string $type * @return mixed */ private function sanitize_field_value($value, $type) { switch ($type) { case 'textarea': return sanitize_textarea_field($value); case 'email': return sanitize_email($value); case 'checkbox': return wc_string_to_bool($value); default: return sanitize_text_field($value); } } /** * Display custom fields in admin order page * * @param WC_Order $order */ public function display_custom_fields_in_admin($order) { foreach ($this->custom_fields as $field) { $field_id = sanitize_title($field['name']); $value = get_post_meta($order->get_id(), '_' . $field_id, true); if ($value) { echo '<p><strong>' . esc_html($field['label']) . ':</strong> ' . $this->format_field_value($value, $field) . '</p>'; } } } /** * Format field value for display * * @param mixed $value * @param array $field * @return string */ private function format_field_value($value, $field) { switch ($field['type']) { case 'checkbox': return $value ? __('Yes', 'bdfg-advanced-cart') : __('No', 'bdfg-advanced-cart'); case 'select': case 'radio': $options = $this->parse_field_options($field['options']); return isset($options[$value]) ? esc_html($options[$value]) : esc_html($value); default: return esc_html($value); } } /** * Display custom fields in order emails * * @param WC_Order $order * @param bool $sent_to_admin * @param bool $plain_text * @param WC_Email $email */ public function display_custom_fields_in_email($order, $sent_to_admin, $plain_text, $email) { $has_fields = false; foreach ($this->custom_fields as $field) { $field_id = sanitize_title($field['name']); $value = get_post_meta($order->get_id(), '_' . $field_id, true); if ($value) { if (!$has_fields) { echo '<h2>' . __('Additional Information', 'bdfg-advanced-cart') . '</h2>'; $has_fields = true; } echo '<p><strong>' . esc_html($field['label']) . ':</strong> ' . $this->format_field_value($value, $field) . '</p>'; } } } /** * Add a new custom field * * @param array $field_data * @return bool|WP_Error */ public function add_custom_field($field_data) { if (empty($field_data['name']) || empty($field_data['label'])) { return new WP_Error( 'invalid_field', __('Field name and label are required.', 'bdfg-advanced-cart') ); } $field_id = sanitize_title($field_data['name']); // Check for duplicate field names foreach ($this->custom_fields as $field) { if (sanitize_title($field['name']) === $field_id) { return new WP_Error( 'duplicate_field', __('A field with this name already exists.', 'bdfg-advanced-cart') ); } } $this->custom_fields[] = wp_parse_args($field_data, array( 'type' => 'text', 'placeholder' => '', 'required' => false, 'class' => array('form-row-wide'), 'priority' => 100, 'validation' => '', 'section' => 'billing' )); return $this->save_fields(); } /** * Save fields configuration * * @return bool */ private function save_fields() { return update_option('bdfg_custom_checkout_fields', $this->custom_fields); } }
includes/class-bdfg-mini-cart.php
<?php /** * Mini Cart Class * * Handles mini cart and popup cart functionality with enhanced features * and responsive design. * * @package BDFG_Advanced_Cart * @author beiduofengou * @link https://beiduofengou.net * @since 2.2.1 */ if (!defined('ABSPATH')) { exit; // Exit if accessed directly } class BDFG_Mini_Cart { /** * Cart settings * * @var array */ private $settings; /** * Constructor */ public function __construct() { $this->settings = array( 'position' => get_option('bdfg_mini_cart_position', 'right'), 'template' => get_option('bdfg_popup_cart_template', 'default'), 'auto_open' => get_option('bdfg_auto_open_cart', 'yes'), 'cart_threshold' => absint(get_option('bdfg_cart_threshold', 0)), 'enable_cart_notes' => get_option('bdfg_enable_cart_notes', 'yes') ); $this->init_hooks(); } /** * Initialize hooks */ private function init_hooks() { // Cart display add_action('wp_footer', array($this, 'render_mini_cart')); add_filter('woocommerce_add_to_cart_fragments', array($this, 'cart_fragments')); // AJAX handlers add_action('wp_ajax_bdfg_get_mini_cart', array($this, 'get_mini_cart_content')); add_action('wp_ajax_nopriv_bdfg_get_mini_cart', array($this, 'get_mini_cart_content')); add_action('wp_ajax_bdfg_update_cart_note', array($this, 'update_cart_note')); // Assets add_action('wp_enqueue_scripts', array($this, 'enqueue_assets')); // Cart modifications add_filter('woocommerce_cart_item_name', array($this, 'add_product_image'), 10, 3); add_action('woocommerce_after_mini_cart', array($this, 'add_cart_notes')); } /** * Enqueue required assets */ public function enqueue_assets() { if (is_cart() || is_checkout()) { return; } wp_enqueue_style( 'bdfg-mini-cart', BDFG_CART_URL . 'assets/css/mini-cart.min.css', array(), BDFG_CART_VERSION ); wp_enqueue_script( 'bdfg-mini-cart', BDFG_CART_URL . 'assets/js/mini-cart.min.js', array('jquery', 'wc-cart-fragments'), BDFG_CART_VERSION, true ); wp_localize_script('bdfg-mini-cart', 'bdfgMiniCart', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('bdfg-mini-cart'), 'position' => $this->settings['position'], 'autoOpen' => $this->settings['auto_open'], 'cartThreshold' => $this->settings['cart_threshold'], 'i18n' => array( 'empty' => __('Your cart is empty', 'bdfg-advanced-cart'), 'countdown' => __('Checkout within %s minutes to secure your items', 'bdfg-advanced-cart'), 'noteUpdated' => __('Cart note updated', 'bdfg-advanced-cart') ) )); } /** * Render mini cart in footer */ public function render_mini_cart() { if (is_cart() || is_checkout()) { return; } $template = 'mini-cart-' . $this->settings['template'] . '.php'; $template_path = $this->locate_template($template); if ($template_path) { include $template_path; } } /** * Locate template file * * @param string $template_name * @return string */ private function locate_template($template_name) { $template_path = locate_template('bdfg-advanced-cart/' . $template_name); if (!$template_path) { $template_path = BDFG_CART_PATH . 'templates/' . $template_name; } return file_exists($template_path) ? $template_path : false; } /** * Update cart fragments via AJAX * * @param array $fragments * @return array */ public function cart_fragments($fragments) { ob_start(); $this->get_cart_content_html(); $fragments['.bdfg-mini-cart-content'] = ob_get_clean(); ob_start(); ?> <div class="bdfg-cart-count"> <?php echo WC()->cart->get_cart_contents_count(); ?> </div> <?php $fragments['.bdfg-cart-count'] = ob_get_clean(); ob_start(); ?> <div class="bdfg-cart-total"> <?php echo WC()->cart->get_cart_total(); ?> </div> <?php $fragments['.bdfg-cart-total'] = ob_get_clean(); return $fragments; } /** * Get mini cart content via AJAX */ public function get_mini_cart_content() { check_ajax_referer('bdfg-mini-cart', 'nonce'); ob_start(); $this->get_cart_content_html(); $cart_html = ob_get_clean(); wp_send_json_success(array( 'html' => $cart_html, 'count' => WC()->cart->get_cart_contents_count(), 'total' => WC()->cart->get_cart_total() )); } /** * Generate cart content HTML */ private function get_cart_content_html() { $cart_items = WC()->cart->get_cart(); if (empty($cart_items)) { ?> <div class="bdfg-mini-cart-empty"> <p><?php _e('Your cart is empty', 'bdfg-advanced-cart'); ?></p> <a href="<?php echo esc_url(wc_get_page_permalink('shop')); ?>" class="button"> <?php _e('Continue Shopping', 'bdfg-advanced-cart'); ?> </a> </div> <?php return; } foreach ($cart_items as $cart_item_key => $cart_item) { $_product = apply_filters('woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key); if (!$_product || !$_product->exists() || $cart_item['quantity'] == 0) { continue; } ?> <div class="bdfg-mini-cart-item" data-key="<?php echo esc_attr($cart_item_key); ?>"> <div class="bdfg-cart-item-image"> <?php echo $_product->get_image(); ?> </div> <div class="bdfg-cart-item-details"> <h4><?php echo wp_kses_post($_product->get_name()); ?></h4> <div class="bdfg-cart-item-price"> <?php echo WC()->cart->get_product_price($_product); ?> </div> <div class="bdfg-cart-item-quantity"> <input type="number" min="0" max="<?php echo esc_attr($_product->get_max_purchase_quantity()); ?>" value="<?php echo esc_attr($cart_item['quantity']); ?>" class="bdfg-qty-input"> </div> </div> <a href="#" class="bdfg-remove-item" data-key="<?php echo esc_attr($cart_item_key); ?>"> <span class="dashicons dashicons-no-alt"></span> </a> </div> <?php } // Cart subtotal and buttons ?> <div class="bdfg-mini-cart-footer"> <div class="bdfg-cart-subtotal"> <strong><?php _e('Subtotal:', 'bdfg-advanced-cart'); ?></strong> <?php echo WC()->cart->get_cart_subtotal(); ?> </div> <div class="bdfg-cart-buttons"> <a href="<?php echo esc_url(wc_get_cart_url()); ?>" class="button"> <?php _e('View Cart', 'bdfg-advanced-cart'); ?> </a> <a href="<?php echo esc_url(wc_get_checkout_url()); ?>" class="button checkout"> <?php _e('Checkout', 'bdfg-advanced-cart'); ?> </a> </div> </div> <?php } /** * Add product image to cart item name * * @param string $name * @param array $cart_item * @param string $cart_item_key * @return string */ public function add_product_image($name, $cart_item, $cart_item_key) { if (!is_cart() && !is_checkout()) { $_product = apply_filters('woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key); if ($_product) { $thumbnail = $_product->get_image(array(50, 50)); $name = '<div class="bdfg-cart-item-thumb">' . $thumbnail . '</div>' . $name; } } return $name; } /** * Add cart notes section */ public function add_cart_notes() { if ('yes' !== $this->settings['enable_cart_notes']) { return; } $note = WC()->session->get('cart_note'); ?> <div class="bdfg-cart-notes"> <textarea placeholder="<?php esc_attr_e('Add a note about your order', 'bdfg-advanced-cart'); ?>" class="bdfg-cart-note"><?php echo esc_textarea($note); ?></textarea> </div> <?php } /** * Update cart note via AJAX */ public function update_cart_note() { check_ajax_referer('bdfg-mini-cart', 'nonce'); $note = isset($_POST['note']) ? sanitize_textarea_field($_POST['note']) : ''; WC()->session->set('cart_note', $note); wp_send_json_success(__('Cart note updated', 'bdfg-advanced-cart')); } }
includes/class-bdfg-cart-analytics.php
<?php /** * Cart Analytics Class * * Handles advanced cart analytics, tracking, and reporting functionality. * * @package BDFG_Advanced_Cart * @author beiduofengou * @link https://beiduofengou.net * @since 2.2.1 */ if (!defined('ABSPATH')) { exit; // Exit if accessed directly } class BDFG_Cart_Analytics { /** * Database table name * * @var string */ private $table_name; /** * Constructor */ public function __construct() { global $wpdb; $this->table_name = $wpdb->prefix . 'bdfg_cart_analytics'; $this->init_hooks(); } /** * Initialize hooks */ private function init_hooks() { // Track cart actions add_action('woocommerce_add_to_cart', array($this, 'track_add_to_cart'), 10, 6); add_action('woocommerce_remove_cart_item', array($this, 'track_remove_from_cart'), 10, 2); add_action('woocommerce_cart_item_restored', array($this, 'track_cart_restore'), 10, 2); add_action('woocommerce_cart_updated', array($this, 'track_cart_update')); // Track cart abandonment add_action('wp_login', array($this, 'track_user_return'), 10, 2); add_action('woocommerce_cart_emptied', array($this, 'track_cart_cleared')); // Admin reports add_action('admin_menu', array($this, 'add_analytics_menu')); // Cleanup add_action('bdfg_daily_cleanup', array($this, 'cleanup_old_records')); } /** * Create analytics table */ public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NULL, session_id varchar(32) NOT NULL, action varchar(50) NOT NULL, cart_total decimal(10,2) NOT NULL, items_count int(11) NOT NULL, product_id bigint(20) NULL, variation_id bigint(20) NULL, quantity int(11) NULL, timestamp datetime NOT NULL, meta longtext NULL, PRIMARY KEY (id), KEY user_id (user_id), KEY session_id (session_id), KEY action (action), KEY timestamp (timestamp) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } /** * Track item added to cart */ public function track_add_to_cart($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data) { $this->log_action('add_to_cart', array( 'product_id' => $product_id, 'variation_id' => $variation_id, 'quantity' => $quantity, 'cart_item_data' => $cart_item_data )); } /** * Track item removed from cart */ public function track_remove_from_cart($cart_item_key, $cart) { $cart_item = $cart->get_cart_item($cart_item_key); if ($cart_item) { $this->log_action('remove_from_cart', array( 'product_id' => $cart_item['product_id'], 'variation_id' => $cart_item['variation_id'], 'quantity' => $cart_item['quantity'] )); } } /** * Log cart action */ private function log_action($action, $meta = array()) { global $wpdb; $user_id = get_current_user_id(); $session_id = WC()->session->get_customer_id(); $data = array( 'user_id' => $user_id ? $user_id : null, 'session_id' => $session_id, 'action' => $action, 'cart_total' => WC()->cart->get_cart_contents_total(), 'items_count' => WC()->cart->get_cart_contents_count(), 'timestamp' => current_time('mysql'), 'meta' => wp_json_encode($meta) ); if (isset($meta['product_id'])) { $data['product_id'] = $meta['product_id']; } if (isset($meta['variation_id'])) { $data['variation_id'] = $meta['variation_id']; } if (isset($meta['quantity'])) { $data['quantity'] = $meta['quantity']; } $wpdb->insert($this->table_name, $data); } /** * Get cart abandonment rate */ public function get_cart_abandonment_rate($days = 30) { global $wpdb; $start_date = date('Y-m-d H:i:s', strtotime("-{$days} days")); $total_carts = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(DISTINCT session_id) FROM {$this->table_name} WHERE timestamp >= %s AND action IN ('add_to_cart', 'remove_from_cart')", $start_date )); $completed_carts = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(DISTINCT session_id) FROM {$this->table_name} t JOIN {$wpdb->prefix}wc_order_stats o ON t.session_id = o.customer_id WHERE t.timestamp >= %s", $start_date )); if ($total_carts == 0) { return 0; } return (($total_carts - $completed_carts) / $total_carts) * 100; } /** * Get popular abandoned products */ public function get_popular_abandoned_products($limit = 10) { global $wpdb; return $wpdb->get_results($wpdb->prepare( "SELECT p.ID, p.post_title, COUNT(DISTINCT t.session_id) as abandonment_count, SUM(t.quantity) as total_quantity, AVG(t.cart_total) as average_cart_total FROM {$this->table_name} t JOIN {$wpdb->posts} p ON t.product_id = p.ID WHERE t.action = 'add_to_cart' AND NOT EXISTS ( SELECT 1 FROM {$wpdb->prefix}wc_order_stats o WHERE t.session_id = o.customer_id ) GROUP BY p.ID ORDER BY abandonment_count DESC, total_quantity DESC LIMIT %d", $limit )); } /** * Get cart conversion data * * @param int $days Number of days to analyze * @return array */ public function get_cart_conversion_data($days = 30) { global $wpdb; $start_date = date('Y-m-d H:i:s', strtotime("-{$days} days")); return array( 'total_carts' => $this->get_total_carts($start_date), 'converted_carts' => $this->get_converted_carts($start_date), 'average_value' => $this->get_average_cart_value($start_date), 'peak_hours' => $this->get_peak_shopping_hours($start_date), 'top_products' => $this->get_top_cart_products($start_date) ); } /** * Get total number of unique carts * * @param string $start_date * @return int */ private function get_total_carts($start_date) { global $wpdb; return (int) $wpdb->get_var($wpdb->prepare( "SELECT COUNT(DISTINCT session_id) FROM {$this->table_name} WHERE timestamp >= %s", $start_date )); } /** * Get number of converted carts * * @param string $start_date * @return int */ private function get_converted_carts($start_date) { global $wpdb; return (int) $wpdb->get_var($wpdb->prepare( "SELECT COUNT(DISTINCT t.session_id) FROM {$this->table_name} t JOIN {$wpdb->prefix}wc_order_stats o ON t.session_id = o.customer_id WHERE t.timestamp >= %s", $start_date )); } /** * Get average cart value * * @param string $start_date * @return float */ private function get_average_cart_value($start_date) { global $wpdb; return (float) $wpdb->get_var($wpdb->prepare( "SELECT AVG(cart_total) FROM {$this->table_name} WHERE timestamp >= %s AND action = 'add_to_cart'", $start_date )); } /** * Get peak shopping hours * * @param string $start_date * @return array */ private function get_peak_shopping_hours($start_date) { global $wpdb; return $wpdb->get_results($wpdb->prepare( "SELECT HOUR(timestamp) as hour, COUNT(*) as count FROM {$this->table_name} WHERE timestamp >= %s AND action = 'add_to_cart' GROUP BY HOUR(timestamp) ORDER BY count DESC LIMIT 5", $start_date )); } /** * Get top products added to carts * * @param string $start_date * @return array */ private function get_top_cart_products($start_date) { global $wpdb; return $wpdb->get_results($wpdb->prepare( "SELECT p.ID, p.post_title, COUNT(*) as add_count, SUM(t.quantity) as total_quantity FROM {$this->table_name} t JOIN {$wpdb->posts} p ON t.product_id = p.ID WHERE t.timestamp >= %s AND t.action = 'add_to_cart' GROUP BY p.ID ORDER BY add_count DESC LIMIT 10", $start_date )); } /** * Add analytics menu to admin */ public function add_analytics_menu() { add_submenu_page( 'woocommerce', __('Cart Analytics', 'bdfg-advanced-cart'), __('Cart Analytics', 'bdfg-advanced-cart'), 'manage_woocommerce', 'bdfg-cart-analytics', array($this, 'render_analytics_page') ); } /** * Render analytics admin page */ public function render_analytics_page() { // Verify user permissions if (!current_user_can('manage_woocommerce')) { wp_die(__('You do not have sufficient permissions to access this page.', 'bdfg-advanced-cart')); } // Get time period from request $period = isset($_GET['period']) ? absint($_GET['period']) : 30; $valid_periods = array(7, 30, 90, 365); $period = in_array($period, $valid_periods) ? $period : 30; // Get analytics data $analytics_data = $this->get_cart_conversion_data($period); // Include template include BDFG_CART_PATH . 'admin/views/analytics.php'; } /** * Cleanup old records */ public function cleanup_old_records() { global $wpdb; // Keep records for 1 year by default $retention_days = apply_filters('bdfg_analytics_retention_days', 365); $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$retention_days} days")); $wpdb->query($wpdb->prepare( "DELETE FROM {$this->table_name} WHERE timestamp < %s", $cutoff_date )); do_action('bdfg_analytics_cleanup_completed'); } /** * Export analytics data to CSV */ public function export_analytics_data() { if (!current_user_can('manage_woocommerce')) { wp_die(__('You do not have sufficient permissions to export data.', 'bdfg-advanced-cart')); } $start_date = isset($_POST['start_date']) ? sanitize_text_field($_POST['start_date']) : date('Y-m-d', strtotime('-30 days')); $end_date = isset($_POST['end_date']) ? sanitize_text_field($_POST['end_date']) : date('Y-m-d'); global $wpdb; $results = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE DATE(timestamp) BETWEEN %s AND %s ORDER BY timestamp DESC", $start_date, $end_date )); if (!empty($results)) { $filename = 'bdfg-cart-analytics-' . date('Y-m-d') . '.csv'; header('Content-Type: text/csv'); header('Content-Disposition: attachment; filename="' . $filename . '"'); $output = fopen('php://output', 'w'); // Add headers fputcsv($output, array( 'ID', 'User ID', 'Session ID', 'Action', 'Cart Total', 'Items Count', 'Product ID', 'Variation ID', 'Quantity', 'Timestamp', 'Meta' )); // Add data foreach ($results as $row) { fputcsv($output, (array) $row); } fclose($output); exit; } } /** * Get real-time cart activity * * @param int $minutes Minutes to look back * @return array */ public function get_realtime_activity($minutes = 15) { global $wpdb; $cutoff = date('Y-m-d H:i:s', strtotime("-{$minutes} minutes")); return $wpdb->get_results($wpdb->prepare( "SELECT t.*, p.post_title as product_name FROM {$this->table_name} t LEFT JOIN {$wpdb->posts} p ON t.product_id = p.ID WHERE t.timestamp >= %s ORDER BY t.timestamp DESC LIMIT 50", $cutoff )); } }
admin/views/analytics.php
<?php /** * Admin Analytics View * * @package BDFG_Advanced_Cart * @author beiduofengou * @since 2.2.1 */ if (!defined('ABSPATH')) { exit; } ?> <div class="wrap bdfg-analytics-wrap"> <h1><?php _e('BDFG Cart Analytics', 'bdfg-advanced-cart'); ?></h1> <div class="bdfg-period-selector"> <form method="get"> <input type="hidden" name="page" value="bdfg-cart-analytics"> <select name="period" onchange="this.form.submit()"> <option value="7" <?php selected($period, 7); ?>><?php _e('Last 7 days', 'bdfg-advanced-cart'); ?></option> <option value="30" <?php selected($period, 30); ?>><?php _e('Last 30 days', 'bdfg-advanced-cart'); ?></option> <option value="90" <?php selected($period, 90); ?>><?php _e('Last 90 days', 'bdfg-advanced-cart'); ?></option> <option value="365" <?php selected($period, 365); ?>><?php _e('Last year', 'bdfg-advanced-cart'); ?></option> </select> </form> </div> <div class="bdfg-analytics-grid"> <!-- Summary Cards --> <div class="bdfg-card"> <h3><?php _e('Cart Conversion Rate', 'bdfg-advanced-cart'); ?></h3> <div class="bdfg-card-value"> <?php $conversion_rate = $analytics_data['converted_carts'] / $analytics_data['total_carts'] * 100; echo number_format($conversion_rate, 2) . '%'; ?> </div> </div> <div class="bdfg-card"> <h3><?php _e('Average Cart Value', 'bdfg-advanced-cart'); ?></h3> <div class="bdfg-card-value"> <?php echo wc_price($analytics_data['average_value']); ?> </div> </div> <div class="bdfg-card"> <h3><?php _e('Total Carts', 'bdfg-advanced-cart'); ?></h3> <div class="bdfg-card-value"> <?php echo number_format($analytics_data['total_carts']); ?> </div> </div> <!-- Peak Hours Chart --> <div class="bdfg-chart-container"> <h3><?php _e('Peak Shopping Hours', 'bdfg-advanced-cart'); ?></h3> <canvas id="peakHoursChart"></canvas> </div> <!-- Top Products Table --> <div class="bdfg-table-container"> <h3><?php _e('Top Products Added to Cart', 'bdfg-advanced-cart'); ?></h3> <table class="widefat"> <thead> <tr> <th><?php _e('Product', 'bdfg-advanced-cart'); ?></th> <th><?php _e('Add Count', 'bdfg-advanced-cart'); ?></th> <th><?php _e('Total Quantity', 'bdfg-advanced-cart'); ?></th> </tr> </thead> <tbody> <?php foreach ($analytics_data['top_products'] as $product): ?> <tr> <td> <a href="<?php echo get_edit_post_link($product->ID); ?>"> <?php echo esc_html($product->post_title); ?> </a> </td> <td><?php echo number_format($product->add_count); ?></td> <td><?php echo number_format($product->total_quantity); ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> <!-- Export Section --> <div class="bdfg-export-section"> <h3><?php _e('Export Data', 'bdfg-advanced-cart'); ?></h3> <form method="post" action="<?php echo admin_url('admin-post.php'); ?>"> <input type="hidden" name="action" value="bdfg_export_analytics"> <?php wp_nonce_field('bdfg_export_analytics', 'bdfg_export_nonce'); ?> <div class="date-range"> <label> <?php _e('Start Date:', 'bdfg-advanced-cart'); ?> <input type="date" name="start_date" value="<?php echo date('Y-m-d', strtotime('-30 days')); ?>"> </label> <label> <?php _e('End Date:', 'bdfg-advanced-cart'); ?> <input type="date" name="end_date" value="<?php echo date('Y-m-d'); ?>"> </label> </div> <button type="submit" class="button button-primary"> <?php _e('Export to CSV', 'bdfg-advanced-cart'); ?> </button> </form> </div> </div>
templates/mini-cart-default.php
<?php /** * Mini Cart Template - Default Style * * @package BDFG_Advanced_Cart * @author beiduofengou * @since 2.2.1 */ if (!defined('ABSPATH')) { exit; } ?> <div class="bdfg-mini-cart-wrapper <?php echo esc_attr($this->settings['position']); ?>"> <div class="bdfg-mini-cart-trigger"> <span class="bdfg-cart-count"><?php echo WC()->cart->get_cart_contents_count(); ?></span> <span class="dashicons dashicons-cart"></span> </div> <div class="bdfg-mini-cart-content"> <div class="bdfg-mini-cart-header"> <h3><?php _e('Your Cart', 'bdfg-advanced-cart'); ?></h3> <button class="bdfg-close-cart"> <span class="dashicons dashicons-no-alt"></span> </button> </div> <div class="bdfg-mini-cart-items"> <?php $this->get_cart_content_html(); ?> </div> <?php if ('yes' === $this->settings['enable_cart_notes']): ?> <div class="bdfg-cart-notes"> <textarea placeholder="<?php esc_attr_e('Add a note about your order', 'bdfg-advanced-cart'); ?>" class="bdfg-cart-note"><?php echo esc_textarea(WC()->session->get('cart_note')); ?></textarea> </div> <?php endif; ?> </div> </div>
assets/js/frontend.min.js
/* BDFG Advanced Cart Frontend Scripts */ (function($) { 'use strict'; const BDFG_Cart = { init: function() { this.miniCart = $('.bdfg-mini-cart-wrapper'); this.trigger = $('.bdfg-mini-cart-trigger'); this.content = $('.bdfg-mini-cart-content'); this.closeBtn = $('.bdfg-close-cart'); this.qtyInputs = $('.bdfg-qty-input'); this.removeButtons = $('.bdfg-remove-item'); this.cartNote = $('.bdfg-cart-note'); this.bindEvents(); this.initAutoOpen(); }, bindEvents: function() { // Toggle mini cart this.trigger.on('click', this.toggleCart.bind(this)); this.closeBtn.on('click', this.closeCart.bind(this)); // Update quantity this.qtyInputs.on('change', this.updateQuantity.bind(this)); // Remove item this.removeButtons.on('click', this.removeItem.bind(this)); // Cart note this.cartNote.on('change', this.updateNote.bind(this)); // Close on outside click $(document).on('click', (e) => { if (!$(e.target).closest('.bdfg-mini-cart-wrapper').length) { this.closeCart(); } }); }, toggleCart: function(e) { e.preventDefault(); this.miniCart.toggleClass('active'); }, closeCart: function() { this.miniCart.removeClass('active'); }, updateQuantity: function(e) { const input = $(e.target); const qty = input.val(); const key = input.closest('.bdfg-mini-cart-item').data('key'); this.blockCart(); $.ajax({ url: bdfgMiniCart.ajaxUrl, type: 'POST', data: { action: 'bdfg_update_quantity', nonce: bdfgMiniCart.nonce, key: key, qty: qty }, success: (response) => { if (response.success) { this.refreshCart(); } }, complete: () => { this.unblockCart(); } }); }, removeItem: function(e) { e.preventDefault(); const button = $(e.target).closest('.bdfg-remove-item'); const key = button.data('key'); this.blockCart(); $.ajax({ url: bdfgMiniCart.ajaxUrl, type: 'POST', data: { action: 'bdfg_remove_item', nonce: bdfgMiniCart.nonce, key: key }, success: (response) => { if (response.success) { this.refreshCart(); } }, complete: () => { this.unblockCart(); } }); }, updateNote: function(e) { const note = $(e.target).val(); $.ajax({ url: bdfgMiniCart.ajaxUrl, type: 'POST', data: { action: 'bdfg_update_cart_note', nonce: bdfgMiniCart.nonce, note: note }, success: (response) => { if (response.success) { this.showMessage(bdfgMiniCart.i18n.noteUpdated); } } }); }, refreshCart: function() { $.ajax({ url: bdfgMiniCart.ajaxUrl, type: 'POST', data: { action: 'bdfg_get_mini_cart', nonce: bdfgMiniCart.nonce }, success: (response) => { if (response.success) { this.content.find('.bdfg-mini-cart-items').html(response.data.html); this.updateCartCount(response.data.count); } } }); }, blockCart: function() { this.content.addClass('loading'); }, unblockCart: function() { this.content.removeClass('loading'); }, showMessage: function(message) { const messageEl = $('<div class="bdfg-message"></div>') .text(message) .appendTo(this.content); setTimeout(() => { messageEl.fadeOut(() => messageEl.remove()); }, 3000); }, updateCartCount: function(count) { this.trigger.find('.bdfg-cart-count').text(count); }, initAutoOpen: function() { if (bdfgMiniCart.autoOpen === 'yes' && WC()->cart.cart_contents_count >= bdfgMiniCart.cartThreshold) { setTimeout(() => { this.miniCart.addClass('active'); }, 1000); } } }; $(document).ready(() => BDFG_Cart.init()); })(jQuery);
assets/css/frontend.min.css
/* BDFG Advanced Cart Styles */ .bdfg-mini-cart-wrapper { position: fixed; z-index: 999999; } .bdfg-mini-cart-wrapper.right { right: 20px; top: 50%; transform: translateY(-50%); } .bdfg-mini-cart-wrapper.left { left: 20px; top: 50%; transform: translateY(-50%); } .bdfg-mini-cart-trigger { background: #2c2d33; color: #fff; width: 50px; height: 50px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; position: relative; transition: all 0.3s ease; } .bdfg-mini-cart-trigger:hover { background: #0073aa; } .bdfg-cart-count { position: absolute; top: -8px; right: -8px; background: #e94b35; color: #fff; font-size: 11px; padding: 2px 6px; border-radius: 10px; min-width: 18px; text-align: center; } .bdfg-mini-cart-content { position: absolute; top: 0; background: #fff; width: 320px; padding: 20px; box-shadow: 0 0 20px rgba(0,0,0,0.15); border-radius: 8px; display: none; max-height: 80vh; overflow-y: auto; } .right .bdfg-mini-cart-content { right: 70px; } .left .bdfg-mini-cart-content { left: 70px; } .bdfg-mini-cart-wrapper.active .bdfg-mini-cart-content { display: block; } .bdfg-mini-cart-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #eee; } .bdfg-mini-cart-header h3 { margin: 0; font-size: 18px; } .bdfg-close-cart { background: none; border: none; padding: 0; cursor: pointer; color: #666; } .bdfg-mini-cart-item { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; } .bdfg-cart-item-image { width: 60px; margin-right: 15px; } .bdfg-cart-item-image img { width: 100%; height: auto; border-radius: 4px; } .bdfg-cart-item-details { flex: 1; } .bdfg-cart-item-details h4 { margin: 0 0 5px; font-size: 14px; font-weight: 500; } .bdfg-cart-item-price { color: #0073aa; font-weight: 600; margin-bottom: 5px; } .bdfg-cart-item-quantity { display: flex; align-items: center; } .bdfg-qty-input { width: 50px; text-align: center; padding: 5px; border: 1px solid #ddd; border-radius: 4px; } .bdfg-remove-item { color: #e94b35; text-decoration: none; margin-left: 10px; } .bdfg-mini-cart-footer { margin-top: 20px; padding-top: 15px; border-top: 1px solid #eee; } .bdfg-cart-subtotal { display: flex; justify-content: space-between; margin-bottom: 15px; font-weight: 600; } .bdfg-cart-buttons { display: flex; gap: 10px; } .bdfg-cart-buttons .button { flex: 1; text-align: center; padding: 12px; border-radius: 4px; text-decoration: none; font-weight: 600; } .bdfg-cart-buttons .button:not(.checkout) { background: #f5f5f5; color: #333; } .bdfg-cart-buttons .checkout { background: #0073aa; color: #fff; } .bdfg-cart-notes { margin-top: 20px; padding-top: 15px; border-top: 1px solid #eee; } .bdfg-cart-note { width: 100%; min-height: 60px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; } .bdfg-message { position: fixed; bottom: 20px; right: 20px; background: #333; color: #fff; padding: 10px 20px; border-radius: 4px; z-index: 999999; } /* Loading State */ .bdfg-mini-cart-content.loading { opacity: 0.7; pointer-events: none; } /* Empty Cart State */ .bdfg-mini-cart-empty { text-align: center; padding: 30px 0; } .bdfg-mini-cart-empty p { color: #666; margin-bottom: 15px; }
assets/css/admin.min.css
/* BDFG Advanced Cart Admin Styles */ .bdfg-analytics-wrap { margin: 20px; } .bdfg-period-selector { margin-bottom: 30px; } .bdfg-analytics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 30px; } .bdfg-card { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .bdfg-card h3 { margin: 0 0 15px; color: #23282d; font-size: 16px; } .bdfg-card-value { font-size: 24px; font-weight: 600; color: #0073aa; } .bdfg-chart-container { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); grid-column: 1 / -1; } .bdfg-table-container { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); grid-column: 1 / -1; } .bdfg-export-section { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-top: 20px; } .bdfg-export-section .date-range { display: flex; gap: 20px; margin-bottom: 15px; } .bdfg-export-section label { display: block; margin-bottom: 5px; }
admin/class-bdfg-admin.php
<?php /** * Admin Class * * Handles all admin-related functionality * * @package BDFG_Advanced_Cart * @author beiduofengou * @since 2.2.1 */ if (!defined('ABSPATH')) { exit; } class BDFG_Admin { /** * Constructor */ public function __construct() { add_action('admin_menu', array($this, 'add_menu_items')); add_action('admin_init', array($this, 'register_settings')); add_filter('plugin_action_links_' . plugin_basename(BDFG_CART_FILE), array($this, 'add_plugin_links')); } /** * Add menu items */ public function add_menu_items() { add_submenu_page( 'woocommerce', __('BDFG Cart Settings', 'bdfg-advanced-cart'), __('BDFG Cart', 'bdfg-advanced-cart'), 'manage_woocommerce', 'bdfg-settings', array($this, 'render_settings_page') ); } /** * Register plugin settings */ public function register_settings() { register_setting('bdfg_cart_settings', 'bdfg_mini_cart_position'); register_setting('bdfg_cart_settings', 'bdfg_cart_expiry_days'); register_setting('bdfg_cart_settings', 'bdfg_enable_cart_sharing'); register_setting('bdfg_cart_settings', 'bdfg_popup_cart_template'); register_setting('bdfg_cart_settings', 'bdfg_analytics_enabled'); register_setting('bdfg_cart_settings', 'bdfg_cart_threshold'); register_setting('bdfg_cart_settings', 'bdfg_enable_cart_notes'); } /** * Render settings page */ public function render_settings_page() { // Check user capabilities if (!current_user_can('manage_woocommerce')) { return; } // Save settings if posted if (isset($_POST['bdfg_save_settings']) && check_admin_referer('bdfg_cart_settings')) { $this->save_settings(); } include BDFG_CART_PATH . 'admin/views/settings.php'; } /** * Save settings */ private function save_settings() { $fields = array( 'bdfg_mini_cart_position', 'bdfg_cart_expiry_days', 'bdfg_enable_cart_sharing', 'bdfg_popup_cart_template', 'bdfg_analytics_enabled', 'bdfg_cart_threshold', 'bdfg_enable_cart_notes' ); foreach ($fields as $field) { if (isset($_POST[$field])) { update_option($field, sanitize_text_field($_POST[$field])); } } WC_Admin_Settings::add_message(__('Settings saved successfully.', 'bdfg-advanced-cart')); } /** * Add plugin action links * * @param array $links * @return array */ public function add_plugin_links($links) { $plugin_links = array( '<a href="' . admin_url('admin.php?page=bdfg-settings') . '">' . __('Settings', 'bdfg-advanced-cart') . '</a>', '<a href="https://beiduofengou.net/docs/bdfg-advanced-cart" target="_blank">' . __('Documentation', 'bdfg-advanced-cart') . '</a>' ); return array_merge($plugin_links, $links); } }
admin/views/settings.php
<?php /** * Admin Settings View * * @package BDFG_Advanced_Cart * @author beiduofengou * @since 2.2.1 */ if (!defined('ABSPATH')) { exit; } ?> <div class="wrap"> <h1><?php _e('BDFG Advanced Cart Settings', 'bdfg-advanced-cart'); ?></h1> <form method="post" action=""> <?php wp_nonce_field('bdfg_cart_settings'); ?> <table class="form-table"> <tr> <th scope="row"> <label for="bdfg_mini_cart_position"> <?php _e('Mini Cart Position', 'bdfg-advanced-cart'); ?> </label> </th> <td> <select name="bdfg_mini_cart_position" id="bdfg_mini_cart_position"> <option value="right" <?php selected(get_option('bdfg_mini_cart_position'), 'right'); ?>> <?php _e('Right', 'bdfg-advanced-cart'); ?> </option> <option value="left" <?php selected(get_option('bdfg_mini_cart_position'), 'left'); ?>> <?php _e('Left', 'bdfg-advanced-cart'); ?> </option> </select> </td> </tr> <tr> <th scope="row"> <label for="bdfg_cart_expiry_days"> <?php _e('Cart Expiry Days', 'bdfg-advanced-cart'); ?> </label> </th> <td> <input type="number" name="bdfg_cart_expiry_days" id="bdfg_cart_expiry_days" value="<?php echo esc_attr(get_option('bdfg_cart_expiry_days', 30)); ?>" min="1" max="365"> <p class="description"> <?php _e('Number of days before saved carts expire', 'bdfg-advanced-cart'); ?> </p> </td> </tr> <tr> <th scope="row"> <label for="bdfg_enable_cart_sharing"> <?php _e('Enable Cart Sharing', 'bdfg-advanced-cart'); ?> </label> </th> <td> <select name="bdfg_enable_cart_sharing" id="bdfg_enable_cart_sharing"> <option value="yes" <?php selected(get_option('bdfg_enable_cart_sharing'), 'yes'); ?>> <?php _e('Yes', 'bdfg-advanced-cart'); ?> </option> <option value="no" <?php selected(get_option('bdfg_enable_cart_sharing'), 'no'); ?>> <?php _e('No', 'bdfg-advanced-cart'); ?> </option> </select> </td> </tr> <tr> <th scope="row"> <label for="bdfg_popup_cart_template"> <?php _e('Popup Cart Template', 'bdfg-advanced-cart'); ?> </label> </th> <td> <select name="bdfg_popup_cart_template" id="bdfg_popup_cart_template"> <option value="default" <?php selected(get_option('bdfg_popup_cart_template'), 'default'); ?>> <?php _e('Default', 'bdfg-advanced-cart'); ?> </option> <option value="modern" <?php selected(get_option('bdfg_popup_cart_template'), 'modern'); ?>> <?php _e('Modern', 'bdfg-advanced-cart'); ?> </option> <option value="minimal" <?php selected(get_option('bdfg_popup_cart_template'), 'minimal'); ?>> <?php _e('Minimal', 'bdfg-advanced-cart'); ?> </option> </select> </td> </tr> <tr> <th scope="row"> <label for="bdfg_analytics_enabled"> <?php _e('Enable Analytics', 'bdfg-advanced-cart'); ?> </label> </th> <td> <select name="bdfg_analytics_enabled" id="bdfg_analytics_enabled"> <option value="yes" <?php selected(get_option('bdfg_analytics_enabled'), 'yes'); ?>> <?php _e('Yes', 'bdfg-advanced-cart'); ?> </option> <option value="no" <?php selected(get_option('bdfg_analytics_enabled'), 'no'); ?>> <?php _e('No', 'bdfg-advanced-cart'); ?> </option> </select> </td> </tr> <tr> <th scope="row"> <label for="bdfg_cart_threshold"> <?php _e('Auto-open Cart Threshold', 'bdfg-advanced-cart'); ?> </label> </th> <td> <input type="number" name="bdfg_cart_threshold" id="bdfg_cart_threshold" value="<?php echo esc_attr(get_option('bdfg_cart_threshold', 0)); ?>" min="0"> <p class="description"> <?php _e('Number of items in cart before auto-opening the mini cart (0 to disable)', 'bdfg-advanced-cart'); ?> </p> </td> </tr> <tr> <th scope="row"> <label for="bdfg_enable_cart_notes"> <?php _e('Enable Cart Notes', 'bdfg-advanced-cart'); ?> </label> </th> <td> <select name="bdfg_enable_cart_notes" id="bdfg_enable_cart_notes"> <option value="yes" <?php selected(get_option('bdfg_enable_cart_notes'), 'yes'); ?>> <?php _e('Yes', 'bdfg-advanced-cart'); ?> </option> <option value="no" <?php selected(get_option('bdfg_enable_cart_notes'), 'no'); ?>> <?php _e('No', 'bdfg-advanced-cart'); ?> </option> </select> <p class="description"> <?php _e('Allow customers to add notes to their cart', 'bdfg-advanced-cart'); ?> </p> </td> </tr> </table> <p class="submit"> <input type="submit" name="bdfg_save_settings" class="button button-primary" value="<?php _e('Save Changes', 'bdfg-advanced-cart'); ?>"> </p> </form> <div class="bdfg-admin-footer"> <p> <?php printf( __('BDFG Advanced Cart v%s by %s', 'bdfg-advanced-cart'), BDFG_CART_VERSION, '<a href="https://beiduofengou.net" target="_blank">beiduofengou</a>' ); ?> </p> </div> </div>
templates/emails/shared-cart.php
<?php /** * Shared Cart Email Template * * @package BDFG_Advanced_Cart * @author beiduofengou * @since 2.2.1 */ if (!defined('ABSPATH')) { exit; } ?> <!DOCTYPE html> <html <?php language_attributes(); ?>> <head> <meta http-equiv="Content-Type" content="text/html; charset=<?php bloginfo('charset'); ?>"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?php echo get_bloginfo('name'); ?></title> </head> <body style="background-color: #f7f7f7; margin: 0; padding: 0; font-family: Arial, sans-serif;"> <table border="0" cellpadding="0" cellspacing="0" width="100%" style="padding: 40px 0;"> <tr> <td align="center"> <table border="0" cellpadding="0" cellspacing="0" width="600" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"> <tr> <td style="padding: 40px;"> <h1 style="color: #333333; margin: 0 0 20px;"> <?php _e('Shared Shopping Cart', 'bdfg-advanced-cart'); ?> </h1> <p style="color: #666666; font-size: 16px; line-height: 1.5; margin-bottom: 30px;"> <?php _e('Someone has shared their shopping cart with you! Click the button below to view and restore the shared cart.', 'bdfg-advanced-cart'); ?> </p> <table border="0" cellpadding="0" cellspacing="0" width="100%" style="margin-bottom: 30px;"> <tr> <td align="center"> <a href="<?php echo esc_url($share_url); ?>" style="background-color: #0073aa; color: #ffffff; display: inline-block; padding: 15px 30px; text-decoration: none; border-radius: 4px; font-weight: bold;"> <?php _e('View Shared Cart', 'bdfg-advanced-cart'); ?> </a> </td> </tr> </table> <p style="color: #666666; font-size: 14px; margin: 0;"> <?php printf( __('This cart will expire in %d days.', 'bdfg-advanced-cart'), get_option('bdfg_cart_expiry_days', 30) ); ?> </p> </td> </tr> </table> </td> </tr> </table> </body> </html>
languages/bdfg-advanced-cart.pot
# Copyright (C) 2025 beiduofengou # This file is distributed under the GPL v2 or later. msgid "" msgstr "" "Project-Id-Version: BDFG Advanced Cart 2.2.1\n" "Report-Msgid-Bugs-To: https://beiduofengou.net/support\n" "POT-Creation-Date: 2024-03-15 10:39:22+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2024-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <[email protected]>\n" #: admin/class-bdfg-admin.php:31 msgid "BDFG Cart Settings" msgstr "" #: admin/class-bdfg-admin.php:32 msgid "BDFG Cart" msgstr "" #: admin/class-bdfg-admin.php:89 msgid "Settings saved successfully." msgstr "" #: admin/views/settings.php:38 msgid "Mini Cart Position" msgstr "" #: admin/views/settings.php:43 msgid "Right" msgstr "" #: admin/views/settings.php:46 msgid "Left" msgstr "" #: admin/views/settings.php:54 msgid "Cart Expiry Days" msgstr "" #: admin/views/settings.php:65 msgid "Number of days before saved carts expire" msgstr "" #: admin/views/settings.php:72 msgid "Enable Cart Sharing" msgstr "" #: admin/views/settings.php:77 admin/views/settings.php:116 msgid "Yes" msgstr "" #: admin/views/settings.php:80 admin/views/settings.php:119 msgid "No" msgstr "" #: includes/class-bdfg-cart-manager.php:156 msgid "Cart saved successfully!" msgstr "" #: includes/class-bdfg-cart-manager.php:189 msgid "Cart restored successfully!" msgstr "" #: includes/class-bdfg-mini-cart.php:98 msgid "Your cart is empty" msgstr "" #: includes/class-bdfg-mini-cart.php:99 msgid "Continue Shopping" msgstr "" #: includes/class-bdfg-checkout-fields.php:167 msgid "%s is a required field." msgstr "" #: templates/emails/shared-cart.php:34 msgid "Shared Shopping Cart" msgstr "" #: templates/emails/shared-cart.php:38 msgid "Someone has shared their shopping cart with you! Click the button below to view and restore the shared cart." msgstr "" #: templates/emails/shared-cart.php:46 msgid "View Shared Cart" msgstr "" #: templates/emails/shared-cart.php:53 msgid "This cart will expire in %d days." msgstr ""
==常见问题==
=此插件是否需要WooCommerce? =
是的,BDFG高级购物车需要WooCommerce 5.0或更高的安装和激活。
=我可以自定义迷你购物车设计吗? =
是的,您可以从多个预构建的模板中进行选择,也可以通过将模板文件复制到主题来创建自己的。