WooCommerce 灵活定价插件

通过强大的定价谈判系统赋予您的WooCommerce商店。让客户在您控制的边界内提出自己的价格。自动或手动批准提案,以创造更具吸引力的购物体验。

**关键功能:**
*允许客户提出自己的产品价格
*设置最低和最大折扣阈值
*可接受范围内的自动宣传提案
*管理仪表板管理所有价格建议
*新建议的电子邮件通知
*有关提案活动的详细统计数据
*与WooCommerce Cart无缝集成并结帐

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

<?php
/**
* Plugin Name: BDFG Flexible Pricing
* Plugin URI: https://beiduofengou.net/2024/12/25/bdfg-flexible-pricing/
* Description: Allows customers to propose their own prices for products, with admin-defined rules for approval.
* Version: 2.5.1
* Author: beiduofengou
* Author URI: https://beiduofengou.net
* Text Domain: bdfg-flexible-pricing
* Domain Path: /languages
* Requires at least: 5.6
* Requires PHP: 7.2
* WC requires at least: 5.0
* WC tested up to: 8.0
* License: GPL v2 or later
*/

// Prevent direct access
defined('ABSPATH') || exit;

// Define plugin constants
define('BDFG_FLEXIBLE_PRICING_VERSION', '2.5.1');
define('BDFG_FLEXIBLE_PRICING_FILE', __FILE__);
define('BDFG_FLEXIBLE_PRICING_PATH', plugin_dir_path(__FILE__));
define('BDFG_FLEXIBLE_PRICING_URL', plugin_dir_url(__FILE__));
define('BDFG_FLEXIBLE_PRICING_BASENAME', plugin_basename(__FILE__));

/**
* Check if WooCommerce is active
*
* @return bool True if WooCommerce is active, false otherwise
*/
function bdfg_flexible_pricing_check_wc() {
if (!class_exists('WooCommerce')) {
add_action('admin_notices', 'bdfg_flexible_pricing_wc_missing_notice');
return false;
}
return true;
}

/**
* Display notice when WooCommerce is missing
*/
function bdfg_flexible_pricing_wc_missing_notice() {
?>
<div class="error">
<p><?php _e('BDFG Flexible Pricing requires WooCommerce to be installed and active.', 'bdfg-flexible-pricing'); ?></p>
</div>
<?php
}

/**
* Plugin initialization
*/
function bdfg_flexible_pricing_init() {
if (!bdfg_flexible_pricing_check_wc()) {
return;
}

// Load language files
load_plugin_textdomain('bdfg-flexible-pricing', false, dirname(plugin_basename(__FILE__)) . '/languages');

// Include required files
require_once BDFG_FLEXIBLE_PRICING_PATH . 'includes/class-bdfg-flexible-pricing.php';

// Initialize the plugin
BDFG_Flexible_Pricing::get_instance();
}

// Register activation hook
register_activation_hook(__FILE__, 'bdfg_flexible_pricing_activate');
function bdfg_flexible_pricing_activate() {
// Create required database tables
if (class_exists('BDFG_Flexible_Pricing')) {
BDFG_Flexible_Pricing::get_instance()->create_tables();
}

// Create required directories
$upload_dir = wp_upload_dir();
$bdfg_dir = $upload_dir['basedir'] . '/bdfg-flexible-pricing';

if (!file_exists($bdfg_dir)) {
wp_mkdir_p($bdfg_dir);
}

// Add default settings
add_option('bdfg_min_discount', 0);
add_option('bdfg_max_discount', 50);
add_option('bdfg_allow_auto_approval', 'yes');
add_option('bdfg_admin_email', get_option('admin_email'));
}

// Start the plugin
add_action('plugins_loaded', 'bdfg_flexible_pricing_init');

// Register deactivation hook
register_deactivation_hook(__FILE__, 'bdfg_flexible_pricing_deactivate');

/**
* Plugin deactivation actions
*/
function bdfg_flexible_pricing_deactivate() {
// Clear scheduled hooks
wp_clear_scheduled_hook('bdfg_clean_expired_proposals');

// Keep database tables intact for future reactivation
// You may want to add an option to delete data upon uninstall
}

/**
* Add settings link on plugin page
*/
function bdfg_flexible_pricing_settings_link($links) {
$settings_link = '<a href="admin.php?page=bdfg-flexible-pricing&tab=settings">' . __('Settings', 'bdfg-flexible-pricing') . '</a>';
array_unshift($links, $settings_link);
return $links;
}
add_filter('plugin_action_links_' . BDFG_FLEXIBLE_PRICING_BASENAME, 'bdfg_flexible_pricing_settings_link');

includes/class-bdfg-flexible-pricing.php

<?php
/**
* BDFG Flexible Pricing Main Class
*
* @package BDFG_Flexible_Pricing
* @since 2.0.0
*/

// Prevent direct access
defined('ABSPATH') || exit;

/**
* Main plugin class
*/
class BDFG_Flexible_Pricing {
/**
* Single instance of the class
*
* @var BDFG_Flexible_Pricing
*/
private static $instance = null;

/**
* Minimum discount percentage allowed
*
* @var int
*/
private $min_discount = 0;

/**
* Maximum discount percentage allowed
*
* @var int
*/
private $max_discount = 50;

/**
* Whether to allow automatic approval for proposals within discount range
*
* @var bool
*/
private $allow_auto_approval = true;

/**
* Email to send notifications to
*
* @var string
*/
private $admin_email = '';

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

/**
* Class constructor
*/
private function __construct() {
// Initialize settings
$this->init_settings();

// Frontend hooks
add_action('woocommerce_single_product_summary', array($this, 'display_price_proposal_form'), 25);
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
add_action('wp_ajax_bdfg_submit_price_proposal', array($this, 'handle_price_proposal'));
add_action('wp_ajax_nopriv_bdfg_submit_price_proposal', array($this, 'handle_price_proposal'));

// Admin hooks
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_init', array($this, 'register_settings'));
add_action('admin_enqueue_scripts', array($this, 'admin_scripts'));

// Proposal management
add_action('wp_ajax_bdfg_approve_proposal', array($this, 'approve_proposal'));
add_action('wp_ajax_bdfg_reject_proposal', array($this, 'reject_proposal'));

// Cart and checkout hooks
add_action('woocommerce_add_to_cart', array($this, 'check_approved_price'), 10, 6);
add_filter('woocommerce_add_cart_item_data', array($this, 'add_price_proposal_to_cart_item'), 10, 3);
add_filter('woocommerce_get_cart_item_from_session', array($this, 'get_cart_item_proposal_from_session'), 10, 2);
add_filter('woocommerce_cart_item_price', array($this, 'show_proposed_price_in_cart'), 10, 3);
add_filter('woocommerce_cart_item_subtotal', array($this, 'show_proposed_subtotal_in_cart'), 10, 3);
add_action('woocommerce_before_calculate_totals', array($this, 'calculate_custom_price'), 10, 1);

// Order hooks
add_action('woocommerce_checkout_create_order_line_item', array($this, 'add_proposal_data_to_order_items'), 10, 4);

// Custom order statuses
add_action('init', array($this, 'register_custom_order_statuses'));
add_filter('wc_order_statuses', array($this, 'add_custom_order_statuses'));

// Schedule cleanup task
if (!wp_next_scheduled('bdfg_clean_expired_proposals')) {
wp_schedule_event(time(), 'daily', 'bdfg_clean_expired_proposals');
}
add_action('bdfg_clean_expired_proposals', array($this, 'clean_expired_proposals'));
}

/**
* Initialize settings
*/
public function init_settings() {
$this->min_discount = get_option('bdfg_min_discount', 0);
$this->max_discount = get_option('bdfg_max_discount', 50);
$this->allow_auto_approval = get_option('bdfg_allow_auto_approval', 'yes') === 'yes';
$this->admin_email = get_option('bdfg_admin_email', get_option('admin_email'));
}

/**
* Create required database tables
*/
public function create_tables() {
global $wpdb;

$charset_collate = $wpdb->get_charset_collate();
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

// Only create table if it doesn't exist
if ($wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") != $table_name) {
$sql = "CREATE TABLE {$table_name} (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
product_id bigint(20) NOT NULL,
original_price decimal(10,2) NOT NULL,
proposed_price decimal(10,2) NOT NULL,
status varchar(20) NOT NULL DEFAULT 'pending',
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT NULL,
notes text DEFAULT NULL,
token varchar(64) DEFAULT NULL,
PRIMARY KEY (id),
KEY user_product (user_id,product_id),
KEY status (status)
) $charset_collate;";

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

// Log table creation
error_log('BDFG Flexible Pricing: Created ' . $table_name);
}
}

/**
* Enqueue frontend scripts and styles
*/
public function enqueue_scripts() {
if (is_product()) {
wp_enqueue_style(
'bdfg-flexible-pricing',
BDFG_FLEXIBLE_PRICING_URL . 'assets/css/bdfg-flexible-pricing.css',
array(),
BDFG_FLEXIBLE_PRICING_VERSION
);

wp_enqueue_script(
'bdfg-flexible-pricing',
BDFG_FLEXIBLE_PRICING_URL . 'assets/js/bdfg-flexible-pricing.js',
array('jquery'),
BDFG_FLEXIBLE_PRICING_VERSION,
true
);

// Pass variables to JS
wp_localize_script('bdfg-flexible-pricing', 'bdfg_pricing_data', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bdfg_price_proposal'),
'currency_symbol' => get_woocommerce_currency_symbol(),
'min_discount' => $this->min_discount,
'max_discount' => $this->max_discount,
'i18n' => array(
'submit' => __('Submit Proposal', 'bdfg-flexible-pricing'),
'proposing' => __('Submitting...', 'bdfg-flexible-pricing'),
'success' => __('Price proposal submitted successfully!', 'bdfg-flexible-pricing'),
'error' => __('Error submitting proposal. Please try again.', 'bdfg-flexible-pricing'),
'invalid_price' => __('Please enter a valid price.', 'bdfg-flexible-pricing'),
'price_too_low' => __('The price is too low. Please increase your offer.', 'bdfg-flexible-pricing'),
'price_too_high' => __('The price is too high. Original price is better.', 'bdfg-flexible-pricing'),
)
));
}
}

/**
* Enqueue admin scripts and styles
*/
public function admin_scripts($hook) {
if ('woocommerce_page_bdfg-flexible-pricing' !== $hook) {
return;
}

wp_enqueue_style(
'bdfg-admin-css',
BDFG_FLEXIBLE_PRICING_URL . 'assets/css/bdfg-admin.css',
array(),
BDFG_FLEXIBLE_PRICING_VERSION
);

wp_enqueue_script(
'bdfg-admin-js',
BDFG_FLEXIBLE_PRICING_URL . 'assets/js/bdfg-admin.js',
array('jquery'),
BDFG_FLEXIBLE_PRICING_VERSION,
true
);

wp_localize_script('bdfg-admin-js', 'bdfg_admin', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bdfg_admin_nonce'),
'i18n' => array(
'approve_confirm' => __('Are you sure you want to approve this price proposal?', 'bdfg-flexible-pricing'),
'reject_confirm' => __('Are you sure you want to reject this price proposal?', 'bdfg-flexible-pricing'),
'approving' => __('Approving...', 'bdfg-flexible-pricing'),
'rejecting' => __('Rejecting...', 'bdfg-flexible-pricing'),
'success' => __('Action completed successfully.', 'bdfg-flexible-pricing'),
'error' => __('An error occurred. Please try again.', 'bdfg-flexible-pricing'),
)
));
}

/**
* Display price proposal form on product page
*/
public function display_price_proposal_form() {
global $product;

// Only show for simple products
if (!$product || $product->get_type() !== 'simple') {
return;
}

// Get price and currency
$regular_price = $product->get_regular_price();
if (empty($regular_price) || $regular_price <= 0) {
return; // Don't display for free products
}

$currency = get_woocommerce_currency_symbol();

// Calculate minimum allowed price
$min_allowed = $regular_price * (1 - ($this->max_discount / 100));
$min_allowed = round($min_allowed, 2);

// Get user's existing proposal if any
$user_id = get_current_user_id();
$proposal = $this->get_user_proposal($user_id, $product->get_id());

// Load template
include(BDFG_FLEXIBLE_PRICING_PATH . 'templates/proposal-form.php');
}

/**
* Get user's proposal for a product
*
* @param int $user_id User ID
* @param int $product_id Product ID
* @return object|null Proposal data or null
*/
private function get_user_proposal($user_id, $product_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

// Get user's latest price proposal
if ($user_id > 0) {
$proposal = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table_name}
WHERE user_id = %d AND product_id = %d
ORDER BY created_at DESC LIMIT 1",
$user_id, $product_id
));
} else {
// Try to get guest proposal using cookie
if (isset($_COOKIE['bdfg_guest_id'])) {
$guest_id = sanitize_text_field($_COOKIE['bdfg_guest_id']);
$proposal = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table_name}
WHERE notes = %s AND product_id = %d
ORDER BY created_at DESC LIMIT 1",
$guest_id, $product_id
));
} else {
$proposal = null;
}
}

return $proposal;
}

/**
* Handle price proposal submission via AJAX
*/
public function handle_price_proposal() {
// Security check
if (!check_ajax_referer('bdfg_price_proposal', 'nonce', false)) {
wp_send_json_error(array('message' => __('Security check failed. Please refresh the page and try again.', 'bdfg-flexible-pricing')));
}

// Get parameters
$product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
$proposed_price = isset($_POST['price']) ? floatval($_POST['price']) : 0;

// Validate product ID
$product = wc_get_product($product_id);
if (!$product) {
wp_send_json_error(array('message' => __('Invalid product. Please try again.', 'bdfg-flexible-pricing')));
return;
}

// Get original price
$original_price = floatval($product->get_regular_price());

// Validate price
if ($proposed_price <= 0) {
wp_send_json_error(array('message' => __('Price must be greater than zero.', 'bdfg-flexible-pricing')));
return;
}

// Calculate discount percentage
$discount_percent = (($original_price - $proposed_price) / $original_price) * 100;

// Check if price is within allowed range
if ($discount_percent < $this->min_discount || $discount_percent > $this->max_discount) {
$message = sprintf(
__('Price must be between %s and %s (discount range: %s%% to %s%%).', 'bdfg-flexible-pricing'),
wc_price($original_price * (1 - $this->max_discount / 100)),
wc_price($original_price * (1 - $this->min_discount / 100)),
$this->min_discount,
$this->max_discount
);
wp_send_json_error(array('message' => $message));
return;
}

// Get user ID
$user_id = get_current_user_id();
$guest_id = '';

if (!$user_id) {
// Create temporary identifier for guest
if (!isset($_COOKIE['bdfg_guest_id'])) {
$guest_id = 'guest_' . md5(uniqid('', true));
setcookie('bdfg_guest_id', $guest_id, time() + 86400 * 30, COOKIEPATH, COOKIE_DOMAIN, is_ssl());
} else {
$guest_id = sanitize_text_field($_COOKIE['bdfg_guest_id']);
}
}

// Determine status
$status = 'pending';
if ($this->allow_auto_approval && $discount_percent <= $this->max_discount) {
$status = 'approved';
}

// Generate unique token
$token = md5(uniqid(($user_id ? $user_id : $guest_id) . '_' . $product_id, true));

// Save proposal
$result = $this->save_proposal($user_id, $product_id, $original_price, $proposed_price, $status, $token, $guest_id);

if (!$result) {
wp_send_json_error(array('message' => __('Failed to save proposal. Please try again.', 'bdfg-flexible-pricing')));
return;
}

// Send email notification to admin
if ($status === 'pending') {
$this->send_admin_notification($user_id, $product_id, $original_price, $proposed_price, $token, $guest_id);
}

// Return response
wp_send_json_success(array(
'message' => $status === 'approved'
? __('Your price proposal has been automatically approved! You can now add this product to your cart at your proposed price.', 'bdfg-flexible-pricing')
: __('Your price proposal has been submitted! We will review it shortly.', 'bdfg-flexible-pricing'),
'status' => $status,
'token' => $token
));
}

/**
* Save price proposal to database
*
* @param int $user_id User ID
* @param int $product_id Product ID
* @param float $original_price Original product price
* @param float $proposed_price Proposed price
* @param string $status Proposal status
* @param string $token Unique token
* @param string $guest_id Guest identifier
* @return bool True on success, false on failure
*/
private function save_proposal($user_id, $product_id, $original_price, $proposed_price, $status, $token, $guest_id = '') {
global $wpdb;

$table_name = $wpdb->prefix . 'bdfg_price_proposals';

// Insert new record
$result = $wpdb->insert(
$table_name,
array(
'user_id' => $user_id ? $user_id : 0,
'product_id' => $product_id,
'original_price' => $original_price,
'proposed_price' => $proposed_price,
'status' => $status,
'created_at' => current_time('mysql'),
'token' => $token,
'notes' => $guest_id ? $guest_id : ''
),
array('%d', '%d', '%f', '%f', '%s', '%s', '%s', '%s')
);

return $result !== false;
}

/**
* Send notification email to admin
*
* @param int $user_id User ID
* @param int $product_id Product ID
* @param float $original_price Original product price
* @param float $proposed_price Proposed price
* @param string $token Unique token
* @param string $guest_id Guest identifier
*/
private function send_admin_notification($user_id, $product_id, $original_price, $proposed_price, $token, $guest_id = '') {
$product = wc_get_product($product_id);
$product_name = $product ? $product->get_name() : sprintf(__('Product #%d', 'bdfg-flexible-pricing'), $product_id);

// Get user information
if ($user_id > 0) {
$user = get_user_by('id', $user_id);
$user_info = $user ? $user->user_email . ' (' . $user->display_name . ')' : sprintf(__('User #%d', 'bdfg-flexible-pricing'), $user_id);
} else {
$user_info = sprintf(__('Guest user (%s)', 'bdfg-flexible-pricing'), substr($guest_id, 0, 8) . '...');
}

// Build URLs for quick actions
$admin_url = admin_url('admin.php?page=bdfg-flexible-pricing');
$approval_url = add_query_arg(array(
'action' => 'approve',
'token' => $token,
'nonce' => wp_create_nonce('bdfg_approve_' . $token)
), $admin_url);

$rejection_url = add_query_arg(array(
'action' => 'reject',
'token' => $token,
'nonce' => wp_create_nonce('bdfg_reject_' . $token)
), $admin_url);

$discount_percent = round((($original_price - $proposed_price) / $original_price) * 100, 2);

$subject = sprintf(
__('[%s] New Price Proposal for %s', 'bdfg-flexible-pricing'),
get_bloginfo('name'),
$product_name
);

$message = sprintf(
__("Hello,

A new price proposal has been submitted:

Product: %s
Original Price: %s
Proposed Price: %s
Discount: %s%%
By: %s
Date: %s

To approve: %s
To reject: %s

Or you can manage all proposals from the admin panel: %s

Regards,
BDFG Flexible Pricing
%s", 'bdfg-flexible-pricing'),
$product_name,
wc_price($original_price),
wc_price($proposed_price),
$discount_percent,
$user_info,
current_time(get_option('date_format') . ' ' . get_option('time_format')),
$approval_url,
$rejection_url,
$admin_url,
home_url()
);

// Send email
$headers = array('Content-Type: text/plain; charset=UTF-8');
wp_mail($this->admin_email, $subject, $message, $headers);
}

/**
* Add admin menu
*/
public function add_admin_menu() {
add_submenu_page(
'woocommerce',
__('BDFG Flexible Pricing', 'bdfg-flexible-pricing'),
__('Flexible Pricing', 'bdfg-flexible-pricing'),
'manage_woocommerce',
'bdfg-flexible-pricing',
array($this, 'render_admin_page')
);
}

/**
* Register plugin settings
*/
public function register_settings() {
register_setting('bdfg_pricing_options', 'bdfg_min_discount', array(
'type' => 'integer',
'sanitize_callback' => array($this, 'sanitize_discount_value'),
'default' => 0,
));

register_setting('bdfg_pricing_options', 'bdfg_max_discount', array(
'type' => 'integer',
'sanitize_callback' => array($this, 'sanitize_discount_value'),
'default' => 50,
));

register_setting('bdfg_pricing_options', 'bdfg_allow_auto_approval', array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => 'yes',
));

register_setting('bdfg_pricing_options', 'bdfg_admin_email', array(
'type' => 'string',
'sanitize_callback' => 'sanitize_email',
'default' => get_option('admin_email'),
));

add_settings_section(
'bdfg_pricing_settings',
__('Flexible Pricing Settings', 'bdfg-flexible-pricing'),
array($this, 'settings_section_callback'),
'bdfg_pricing_options'
);

add_settings_field(
'bdfg_min_discount',
__('Minimum Discount (%)', 'bdfg-flexible-pricing'),
array($this, 'min_discount_callback'),
'bdfg_pricing_options',
'bdfg_pricing_settings'
);

add_settings_field(
'bdfg_max_discount',
__('Maximum Discount (%)', 'bdfg-flexible-pricing'),
array($this, 'max_discount_callback'),
'bdfg_pricing_options',
'bdfg_pricing_settings'
);

add_settings_field(
'bdfg_allow_auto_approval',
__('Auto-Approve Eligible Proposals', 'bdfg-flexible-pricing'),
array($this, 'auto_approval_callback'),
'bdfg_pricing_options',
'bdfg_pricing_settings'
);

add_settings_field(
'bdfg_admin_email',
__('Notification Email', 'bdfg-flexible-pricing'),
array($this, 'admin_email_callback'),
'bdfg_pricing_options',
'bdfg_pricing_settings'
);
}

/**
* Sanitize discount values
*/
public function sanitize_discount_value($value) {
$value = intval($value);
return max(0, min(100, $value)); // Value between 0 and 100
}

/**
* Settings section description
*/
public function settings_section_callback() {
echo '<p>' . __('Configure how customer price proposals are handled. Set the discount range that customers can propose and whether proposals should be automatically approved.', 'bdfg-flexible-pricing') . '</p>';
}

/**
* Minimum discount field
*/
public function min_discount_callback() {
$min_discount = get_option('bdfg_min_discount', 0);
echo '<input type="number" min="0" max="99" name="bdfg_min_discount" value="' . esc_attr($min_discount) . '" />';
echo '<p class="description">' . __('Minimum discount allowed (0 = no discount, customers can offer full price or higher)', 'bdfg-flexible-pricing') . '</p>';
}

/**
* Maximum discount field
*/
public function max_discount_callback() {
$max_discount = get_option('bdfg_max_discount', 50);
echo '<input type="number" min="1" max="100" name="bdfg_max_discount" value="' . esc_attr($max_discount) . '" />';
echo '<p class="description">' . __('Maximum discount allowed (e.g., 50 means customers can offer up to 50% off)', 'bdfg-flexible-pricing') . '</p>';
}

/**
* Auto approval field
*/
public function auto_approval_callback() {
$allow_auto_approval = get_option('bdfg_allow_auto_approval', 'yes');
echo '<input type="checkbox" name="bdfg_allow_auto_approval" value="yes" ' . checked($allow_auto_approval, 'yes', false) . ' />';
echo '<p class="description">' . __('Automatically approve proposals that meet the discount criteria', 'bdfg-flexible-pricing') . '</p>';
}

/**
* Admin email field
*/
public function admin_email_callback() {
$admin_email = get_option('bdfg_admin_email', get_option('admin_email'));
echo '<input type="email" name="bdfg_admin_email" value="' . esc_attr($admin_email) . '" class="regular-text" />';
echo '<p class="description">' . __('Email address for price proposal notifications', 'bdfg-flexible-pricing') . '</p>';
}

/**
* Render admin page
*/
public function render_admin_page() {
// Handle direct approve/reject links
if (isset($_GET['action']) && isset($_GET['token']) && isset($_GET['nonce'])) {
$action = sanitize_text_field($_GET['action']);
$token = sanitize_text_field($_GET['token']);
$nonce = sanitize_text_field($_GET['nonce']);

if (($action === 'approve' && wp_verify_nonce($nonce, 'bdfg_approve_' . $token)) ||
($action === 'reject' && wp_verify_nonce($nonce, 'bdfg_reject_' . $token))) {

global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

$proposal = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table_name} WHERE token = %s",
$token
));

if ($proposal) {
$status = $action === 'approve' ? 'approved' : 'rejected';

$updated = $wpdb->update(
$table_name,
array(
'status' => $status,
'updated_at' => current_time('mysql')
),
array('id' => $proposal->id),
array('%s', '%s'),
array('%d')
);

if ($updated) {
// Also notify the customer if we have their email
if ($proposal->user_id > 0) {
$this->notify_customer_of_decision($proposal, $status);
}

echo '<div class="notice notice-success"><p>' .
sprintf(__('Proposal has been %s.', 'bdfg-flexible-pricing'), $status) .
'</p></div>';
} else {
echo '<div class="notice notice-error"><p>' .
__('Failed to update proposal status.', 'bdfg-flexible-pricing') .
'</p></div>';
}
}
}
}

// Display tabs
$active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'proposals';
?>
<div class="wrap bdfg-admin-wrap">
<h1><?php _e('BDFG Flexible Pricing', 'bdfg-flexible-pricing'); ?></h1>

<h2 class="nav-tab-wrapper">
<a href="?page=bdfg-flexible-pricing&tab=proposals" class="nav-tab <?php echo $active_tab === 'proposals' ? 'nav-tab-active' : ''; ?>">
<?php _e('Price Proposals', 'bdfg-flexible-pricing'); ?>
</a>
<a href="?page=bdfg-flexible-pricing&tab=settings" class="nav-tab <?php echo $active_tab === 'settings' ? 'nav-tab-active' : ''; ?>">
<?php _e('Settings', 'bdfg-flexible-pricing'); ?>
</a>
<a href="?page=bdfg-flexible-pricing&tab=stats" class="nav-tab <?php echo $active_tab === 'stats' ? 'nav-tab-active' : ''; ?>">
<?php _e('Statistics', 'bdfg-flexible-pricing'); ?>
</a>
</h2>

<div class="tab-content">
<?php
if ($active_tab === 'proposals') {
$this->render_proposals_tab();
} elseif ($active_tab === 'stats') {
$this->render_stats_tab();
} else {
$this->render_settings_tab();
}
?>
</div>

<div class="bdfg-footer">
<p>
<?php
printf(
__('BDFG Flexible Pricing v%s | Made with ❤️ by <a href="%s" target="_blank">beiduofengou</a>', 'bdfg-flexible-pricing'),
BDFG_FLEXIBLE_PRICING_VERSION,
'https://beiduofengou.net'
);
?>
</p>
</div>
</div>
<?php
}

/**
* Render proposals tab
*/
private function render_proposals_tab() {
// Create custom list table
require_once(BDFG_FLEXIBLE_PRICING_PATH . 'includes/class-bdfg-proposals-list-table.php');
$proposals_table = new BDFG_Proposals_List_Table();
$proposals_table->prepare_items();

echo '<div class="bdfg-proposals-container">';

// Add a filter form
echo '<div class="bdfg-filter-form">';
$proposals_table->views();
$proposals_table->display_tablenav('top');
echo '</div>';

$proposals_table->display();
echo '</div>';
}

/**
* Render settings tab
*/
private function render_settings_tab() {
?>
<div class="bdfg-settings-container">
<form method="post" action="options.php">
<?php
settings_fields('bdfg_pricing_options');
do_settings_sections('bdfg_pricing_options');
submit_button(__('Save Settings', 'bdfg-flexible-pricing'));
?>
</form>

<div class="bdfg-settings-sidebar">
<div class="bdfg-settings-box">
<h3><?php _e('Need Help?', 'bdfg-flexible-pricing'); ?></h3>
<p><?php _e('Check out our documentation for tips on how to get the most out of BDFG Flexible Pricing.', 'bdfg-flexible-pricing'); ?></p>
<a href="https://beiduofengou.net/docs/flexible-pricing/" class="button button-secondary" target="_blank">
<?php _e('View Documentation', 'bdfg-flexible-pricing'); ?>
</a>
</div>

<div class="bdfg-settings-box">
<h3><?php _e('Rate Our Plugin', 'bdfg-flexible-pricing'); ?></h3>
<p><?php _e('If you enjoy using BDFG Flexible Pricing, please take a moment to rate it. It helps us a lot!', 'bdfg-flexible-pricing'); ?></p>
<a href="https://wordpress.org/plugins/bdfg-flexible-pricing/reviews/" class="button button-secondary" target="_blank">
<?php _e('Leave a Review', 'bdfg-flexible-pricing'); ?>
</a>
</div>
</div>
</div>
<?php
}

/**
* Render statistics tab
*/
private function render_stats_tab() {
global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

// Get statistics
$total_proposals = $wpdb->get_var("SELECT COUNT(id) FROM $table_name");
$approved_proposals = $wpdb->get_var("SELECT COUNT(id) FROM $table_name WHERE status = 'approved'");
$rejected_proposals = $wpdb->get_var("SELECT COUNT(id) FROM $table_name WHERE status = 'rejected'");
$pending_proposals = $wpdb->get_var("SELECT COUNT(id) FROM $table_name WHERE status = 'pending'");
$used_proposals = $wpdb->get_var("SELECT COUNT(id) FROM $table_name WHERE status = 'used'");

// Get average discount
$avg_discount = $wpdb->get_var(
"SELECT AVG((original_price - proposed_price) / original_price * 100) FROM $table_name WHERE status IN ('approved', 'used')"
);

// Get most active products
$popular_products = $wpdb->get_results(
"SELECT product_id, COUNT(id) as proposal_count
FROM $table_name
GROUP BY product_id
ORDER BY proposal_count DESC
LIMIT 5"
);

// Date range for recent stats
$thirty_days_ago = date('Y-m-d H:i:s', strtotime('-30 days'));
$recent_proposals = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(id) FROM $table_name WHERE created_at >= %s",
$thirty_days_ago
));

?>
<div class="bdfg-stats-container">
<div class="bdfg-stats-summary">
<div class="bdfg-stat-box">
<h3><?php _e('Total Proposals', 'bdfg-flexible-pricing'); ?></h3>
<div class="bdfg-stat-number"><?php echo esc_html($total_proposals); ?></div>
</div>

<div class="bdfg-stat-box bdfg-stat-approved">
<h3><?php _e('Approved', 'bdfg-flexible-pricing'); ?></h3>
<div class="bdfg-stat-number"><?php echo esc_html($approved_proposals); ?></div>
</div>

<div class="bdfg-stat-box bdfg-stat-rejected">
<h3><?php _e('Rejected', 'bdfg-flexible-pricing'); ?></h3>
<div class="bdfg-stat-number"><?php echo esc_html($rejected_proposals); ?></div>
</div>

<div class="bdfg-stat-box bdfg-stat-pending">
<h3><?php _e('Pending', 'bdfg-flexible-pricing'); ?></h3>
<div class="bdfg-stat-number"><?php echo esc_html($pending_proposals); ?></div>
</div>

<div class="bdfg-stat-box bdfg-stat-used">
<h3><?php _e('Used in Orders', 'bdfg-flexible-pricing'); ?></h3>
<div class="bdfg-stat-number"><?php echo esc_html($used_proposals); ?></div>
</div>

<div class="bdfg-stat-box bdfg-stat-discount">
<h3><?php _e('Avg. Discount', 'bdfg-flexible-pricing'); ?></h3>
<div class="bdfg-stat-number"><?php echo number_format($avg_discount, 2); ?>%</div>
</div>

<div class="bdfg-stat-box bdfg-stat-recent">
<h3><?php _e('Last 30 Days', 'bdfg-flexible-pricing'); ?></h3>
<div class="bdfg-stat-number"><?php echo esc_html($recent_proposals); ?></div>
</div>
</div>

<div class="bdfg-stats-details">
<div class="bdfg-popular-products">
<h3><?php _e('Most Popular Products', 'bdfg-flexible-pricing'); ?></h3>
<table class="widefat">
<thead>
<tr>
<th><?php _e('Product', 'bdfg-flexible-pricing'); ?></th>
<th><?php _e('Proposals', 'bdfg-flexible-pricing'); ?></th>
<th><?php _e('Actions', 'bdfg-flexible-pricing'); ?></th>
</tr>
</thead>
<tbody>
<?php if ($popular_products): ?>
<?php foreach ($popular_products as $item): ?>
<?php $product = wc_get_product($item->product_id); ?>
<tr>
<td>
<?php if ($product): ?>
<a href="<?php echo get_edit_post_link($item->product_id); ?>">
<?php echo esc_html($product->get_name()); ?>
</a>
<?php else: ?>
<?php printf(__('Product #%d (Deleted)', 'bdfg-flexible-pricing'), $item->product_id); ?>
<?php endif; ?>
</td>
<td><?php echo esc_html($item->proposal_count); ?></td>
<td>
<?php if ($product): ?>
<a href="<?php echo esc_url(admin_url('admin.php?page=bdfg-flexible-pricing&product_id=' . $item->product_id)); ?>" class="button button-small">
<?php _e('View Proposals', 'bdfg-flexible-pricing'); ?>
</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="3"><?php _e('No proposals yet.', 'bdfg-flexible-pricing'); ?></td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php
}

/**
* Notify customer of proposal decision
*
* @param object $proposal Proposal data
* @param string $status New status
*/
private function notify_customer_of_decision($proposal, $status) {
if ($proposal->user_id <= 0) {
return; // No user to notify
}

$user = get_user_by('id', $proposal->user_id);
if (!$user || !$user->user_email) {
return;
}

$product = wc_get_product($proposal->product_id);
$product_name = $product ? $product->get_name() : sprintf(__('Product #%d', 'bdfg-flexible-pricing'), $proposal->product_id);
$product_url = $product ? get_permalink($proposal->product_id) : '';

$subject = sprintf(
$status === 'approved' ?
__('[%s] Your Price Proposal for %s has been Approved', 'bdfg-flexible-pricing') :
__('[%s] Your Price Proposal for %s has been Declined', 'bdfg-flexible-pricing'),
get_bloginfo('name'),
$product_name
);

if ($status === 'approved') {
$message = sprintf(
__("Hello %s,

Good news! Your price proposal for %s has been approved.

Product: %s
Your Proposed Price: %s
Original Price: %s
Discount: %.2f%%

You can now purchase this product at your proposed price by adding it to your cart.

Product link: %s

Thank you for shopping with us!

Regards,
%s
%s", 'bdfg-flexible-pricing'),
$user->display_name,
$product_name,
$product_name,
wc_price($proposal->proposed_price),
wc_price($proposal->original_price),
(($proposal->original_price - $proposal->proposed_price) / $proposal->original_price) * 100,
$product_url,
get_bloginfo('name'),
home_url()
);
} else {
$message = sprintf(
__("Hello %s,

Thank you for your interest in our products. Unfortunately, we are unable to accept your price proposal for %s at this time.

Product: %s
Your Proposed Price: %s
Original Price: %s

You are welcome to submit a new proposal or purchase the product at the regular price.

Product link: %s

Thank you for your understanding.

Regards,
%s
%s", 'bdfg-flexible-pricing'),
$user->display_name,
$product_name,
$product_name,
wc_price($proposal->proposed_price),
wc_price($proposal->original_price),
$product_url,
get_bloginfo('name'),
home_url()
);
}

$headers = array('Content-Type: text/plain; charset=UTF-8');
wp_mail($user->user_email, $subject, $message, $headers);
}

/**
* Approve a price proposal via AJAX
*/
public function approve_proposal() {
// Check security
check_ajax_referer('bdfg_admin_nonce', 'nonce');

if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => __('You do not have permission to do this', 'bdfg-flexible-pricing')));
}

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

global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

// Get proposal details before updating
$proposal = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table_name} WHERE id = %d", $proposal_id));

if (!$proposal) {
wp_send_json_error(array('message' => __('Proposal not found', 'bdfg-flexible-pricing')));
return;
}

$result = $wpdb->update(
$table_name,
array(
'status' => 'approved',
'updated_at' => current_time('mysql')
),
array('id' => $proposal_id),
array('%s', '%s'),
array('%d')
);

if ($result) {
// Notify customer
$this->notify_customer_of_decision($proposal, 'approved');

// Add admin note
$this->add_admin_action_log('Approved price proposal #' . $proposal_id);

wp_send_json_success(array(
'message' => __('Proposal approved successfully', 'bdfg-flexible-pricing')
));
} else {
wp_send_json_error(array('message' => __('Failed to approve proposal', 'bdfg-flexible-pricing')));
}
}

/**
* Reject a price proposal via AJAX
*/
public function reject_proposal() {
// Check security
check_ajax_referer('bdfg_admin_nonce', 'nonce');

if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => __('You do not have permission to do this', 'bdfg-flexible-pricing')));
}

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

global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

// Get proposal details before updating
$proposal = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table_name} WHERE id = %d", $proposal_id));

if (!$proposal) {
wp_send_json_error(array('message' => __('Proposal not found', 'bdfg-flexible-pricing')));
return;
}

$result = $wpdb->update(
$table_name,
array(
'status' => 'rejected',
'updated_at' => current_time('mysql')
),
array('id' => $proposal_id),
array('%s', '%s'),
array('%d')
);

if ($result) {
// Notify customer
$this->notify_customer_of_decision($proposal, 'rejected');

// Add admin note
$this->add_admin_action_log('Rejected price proposal #' . $proposal_id);

wp_send_json_success(array(
'message' => __('Proposal rejected successfully', 'bdfg-flexible-pricing')
));
} else {
wp_send_json_error(array('message' => __('Failed to reject proposal', 'bdfg-flexible-pricing')));
}
}

/**
* Add proposal data to cart item
*/
public function add_price_proposal_to_cart_item($cart_item_data, $product_id, $variation_id) {
$user_id = get_current_user_id();

// Guest user
if (!$user_id && isset($_COOKIE['bdfg_guest_id'])) {
$guest_id = sanitize_text_field($_COOKIE['bdfg_guest_id']);
}

// Check for approved price proposal
if (isset($user_id) || isset($guest_id)) {
global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

// Get user's latest approved price proposal
if (isset($user_id) && $user_id > 0) {
$proposal = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table_name}
WHERE user_id = %d AND product_id = %d AND status = 'approved'
ORDER BY created_at DESC LIMIT 1",
$user_id, $product_id
));
} else {
$proposal = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table_name}
WHERE notes = %s AND product_id = %d AND status = 'approved'
ORDER BY created_at DESC LIMIT 1",
$guest_id, $product_id
));
}

if ($proposal) {
// Save proposal info to cart item
$cart_item_data['bdfg_proposal'] = array(
'id' => $proposal->id,
'original_price' => $proposal->original_price,
'custom_price' => $proposal->proposed_price
);
}
}

return $cart_item_data;
}

/**
* Get cart item proposal from session
*/
public function get_cart_item_proposal_from_session($cart_item, $values) {
if (isset($values['bdfg_proposal'])) {
$cart_item['bdfg_proposal'] = $values['bdfg_proposal'];
}
return $cart_item;
}

/**
* Show proposed price in cart
*/
public function show_proposed_price_in_cart($price, $cart_item, $cart_item_key) {
if (isset($cart_item['bdfg_proposal'])) {
$custom_price = $cart_item['bdfg_proposal']['custom_price'];
$original_price = $cart_item['bdfg_proposal']['original_price'];

// Show original and custom price
$formatted_price = '<del>' . wc_price($original_price) . '</del> <ins>' . wc_price($custom_price) . '</ins>';
return $formatted_price;
}
return $price;
}

/**
* Show proposed subtotal in cart
*/
public function show_proposed_subtotal_in_cart($subtotal, $cart_item, $cart_item_key) {
if (isset($cart_item['bdfg_proposal'])) {
$custom_price = $cart_item['bdfg_proposal']['custom_price'];
$custom_subtotal = $custom_price * $cart_item['quantity'];
return wc_price($custom_subtotal);
}
return $subtotal;
}

/**
* Calculate custom price for cart items
*/
public function calculate_custom_price($cart) {
if (is_admin() && !defined('DOING_AJAX')) {
return;
}

if (did_action('woocommerce_before_calculate_totals') >= 2) {
return;
}

foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
if (isset($cart_item['bdfg_proposal'])) {
$cart_item['data']->set_price($cart_item['bdfg_proposal']['custom_price']);
}
}
}

/**
* Add proposal data to order items
*/
public function add_proposal_data_to_order_items($item, $cart_item_key, $values, $order) {
if (isset($values['bdfg_proposal'])) {
$item->add_meta_data('_bdfg_original_price', $values['bdfg_proposal']['original_price'], true);
$item->add_meta_data('_bdfg_proposed_price', $values['bdfg_proposal']['custom_price'], true);
$item->add_meta_data('_bdfg_proposal_id', $values['bdfg_proposal']['id'], true);

// Calculate discount percentage
$original = $values['bdfg_proposal']['original_price'];
$custom = $values['bdfg_proposal']['custom_price'];
$discount = round((($original - $custom) / $original) * 100, 2);

$item->add_meta_data('_bdfg_discount_percent', $discount, true);

// Visible order item metadata
$item->add_meta_data(
__('Custom Price', 'bdfg-flexible-pricing'),
sprintf(__('Customer proposed price: %s (%.2f%% off)', 'bdfg-flexible-pricing'),
wc_price($custom), $discount),
true
);
}
}

/**
* Register custom order statuses
*/
public function register_custom_order_statuses() {
register_post_status('wc-proposal-pending', array(
'label' => __('Proposal Pending', 'bdfg-flexible-pricing'),
'public' => true,
'show_in_admin_status_list' => true,
'show_in_admin_all_list' => true,
'exclude_from_search' => false,
'label_count' => _n_noop('Proposal Pending <span class="count">(%s)</span>',
'Proposal Pending <span class="count">(%s)</span>',
'bdfg-flexible-pricing')
));
}

/**
* Add custom order statuses
*/
public function add_custom_order_statuses($order_statuses) {
$new_statuses = array();

// Insert our custom status after processing
foreach ($order_statuses as $key => $status) {
$new_statuses[$key] = $status;

if ($key === 'wc-processing') {
$new_statuses['wc-proposal-pending'] = __('Proposal Pending', 'bdfg-flexible-pricing');
}
}

return $new_statuses;
}

/**
* Check approved price on add to cart
*/
public function check_approved_price($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data) {
// Check if added product has approved price proposal
if (isset($cart_item_data['bdfg_proposal'])) {
// Update price proposal status to used
global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

$wpdb->update(
$table_name,
array(
'status' => 'used',
'updated_at' => current_time('mysql'),
'notes' => sprintf(__('Used in cart item %s on %s', 'bdfg-flexible-pricing'),
$cart_item_key,
current_time(get_option('date_format') . ' ' . get_option('time_format')))
),
array('id' => $cart_item_data['bdfg_proposal']['id']),
array('%s', '%s', '%s'),
array('%d')
);
}
}

/**
* Clean expired proposals
*/
public function clean_expired_proposals() {
global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

// Clean up proposals older than 30 days that are pending
$thirty_days_ago = date('Y-m-d H:i:s', strtotime('-30 days'));

$wpdb->query($wpdb->prepare(
"UPDATE {$table_name}
SET status = 'expired', updated_at = %s
WHERE status = 'pending' AND created_at < %s",
current_time('mysql'), $thirty_days_ago
));

// Log cleanup
$this->add_admin_action_log('Cleaned expired price proposals');
}

/**
* Add admin action log
*/
private function add_admin_action_log($message) {
$current_user = wp_get_current_user();
$username = $current_user->exists() ? $current_user->user_login : 'system';

$log_message = sprintf(
'[%s] %s: %s',
current_time('Y-m-d H:i:s'),
$username,
$message
);

$log_file = WP_CONTENT_DIR . '/uploads/bdfg-flexible-pricing/admin-actions.log';

// Create directory if it doesn't exist
$log_dir = dirname($log_file);
if (!file_exists($log_dir)) {
wp_mkdir_p($log_dir);
}

// Append to log file
file_put_contents($log_file, $log_message . PHP_EOL, FILE_APPEND);
}
}

includes/class-bdfg-proposals-list-table.php

相关文章: WooCommerce 产品过滤器

<?php
/**
* BDFG Proposals List Table
*
* @package BDFG_Flexible_Pricing
* @since 2.0.0
*/

// Prevent direct access
defined('ABSPATH') || exit;

// Check for WP_List_Table class
if (!class_exists('WP_List_Table')) {
require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php');
}

/**
* Proposals List Table
*/
class BDFG_Proposals_List_Table extends WP_List_Table {

/**
* Constructor
*/
public function __construct() {
parent::__construct(array(
'singular' => 'proposal',
'plural' => 'proposals',
'ajax' => false
));
}

/**
* Get columns
*
* @return array
*/
public function get_columns() {
return array(
'cb' => '<input type="checkbox" />',
'id' => __('ID', 'bdfg-flexible-pricing'),
'product' => __('Product', 'bdfg-flexible-pricing'),
'user' => __('Customer', 'bdfg-flexible-pricing'),
'original_price' => __('Original Price', 'bdfg-flexible-pricing'),
'proposed_price' => __('Proposed Price', 'bdfg-flexible-pricing'),
'discount' => __('Discount %', 'bdfg-flexible-pricing'),
'status' => __('Status', 'bdfg-flexible-pricing'),
'date' => __('Date', 'bdfg-flexible-pricing'),
'actions' => __('Actions', 'bdfg-flexible-pricing')
);
}

/**
* Get sortable columns
*
* @return array
*/
public function get_sortable_columns() {
return array(
'id' => array('id', true),
'product' => array('product_id', false),
'original_price' => array('original_price', false),
'proposed_price' => array('proposed_price', false),
'discount' => array('discount', false),
'status' => array('status', false),
'date' => array('created_at', true)
);
}

/**
* Column default
*
* @param object $item Item data
* @param string $column_name Column name
* @return string
*/
public function column_default($item, $column_name) {
switch ($column_name) {
case 'id':
return $item->id;
case 'product':
$product = wc_get_product($item->product_id);
if ($product) {
$image = $product->get_image(array(32, 32));
return sprintf(
'<div class="bdfg-product-cell"><div class="bdfg-product-thumb">%s</div><div class="bdfg-product-info"><a href="%s">%s</a> <span class="bdfg-product-id">#%d</span></div></div>',
$image,
get_edit_post_link($item->product_id),
$product->get_name(),
$item->product_id
);
}
return sprintf('<span class="bdfg-deleted-product">' . __('Product #%d (Deleted)', 'bdfg-flexible-pricing') . '</span>', $item->product_id);
case 'user':
if (is_numeric($item->user_id) && $item->user_id > 0) {
$user = get_user_by('id', $item->user_id);
if ($user) {
return sprintf(
'<a href="%s">%s</a><br><span class="bdfg-user-email">%s</span>',
get_edit_user_link($item->user_id),
$user->display_name,
$user->user_email
);
}
return sprintf(__('User #%d', 'bdfg-flexible-pricing'), $item->user_id);
} elseif (!empty($item->notes) && strpos($item->notes, 'guest_') === 0) {
return sprintf('<span class="bdfg-guest-user">' . __('Guest (%s)', 'bdfg-flexible-pricing') . '</span>', substr($item->notes, 0, 15) . '...');
}
return '<span class="bdfg-guest-user">' . __('Guest', 'bdfg-flexible-pricing') . '</span>';
case 'original_price':
return wc_price($item->original_price);
case 'proposed_price':
return wc_price($item->proposed_price);
case 'discount':
$discount = (($item->original_price - $item->proposed_price) / $item->original_price) * 100;
$class = $discount >= 30 ? 'bdfg-high-discount' : ($discount >= 15 ? 'bdfg-medium-discount' : 'bdfg-low-discount');
return '<span class="' . $class . '">' . number_format($discount, 2) . '%</span>';
case 'status':
$statuses = array(
'pending' => __('Pending', 'bdfg-flexible-pricing'),
'approved' => __('Approved', 'bdfg-flexible-pricing'),
'rejected' => __('Rejected', 'bdfg-flexible-pricing'),
'used' => __('Used', 'bdfg-flexible-pricing'),
'expired' => __('Expired', 'bdfg-flexible-pricing')
);

$status_class = 'bdfg-status-' . $item->status;
$status_text = isset($statuses[$item->status]) ? $statuses[$item->status] : $item->status;

return sprintf('<span class="%s">%s</span>', $status_class, $status_text);
case 'date':
$date_created = date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($item->created_at));
$date_updated = !empty($item->updated_at) ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($item->updated_at)) : '';

$output = sprintf('<span class="bdfg-date-created">%s</span>', $date_created);

if ($date_updated && $item->status !== 'pending') {
$output .= sprintf('<br><span class="bdfg-date-updated">' . __('Updated: %s', 'bdfg-flexible-pricing') . '</span>', $date_updated);
}

return $output;
case 'actions':
$actions = array();

if ($item->status === 'pending') {
$actions['approve'] = sprintf(
'<a href="#" class="bdfg-approve button button-small" data-id="%d">%s</a>',
$item->id,
__('Approve', 'bdfg-flexible-pricing')
);

$actions['reject'] = sprintf(
'<a href="#" class="bdfg-reject button button-small button-link-delete" data-id="%d">%s</a>',
$item->id,
__('Reject', 'bdfg-flexible-pricing')
);
}

if ($item->status === 'approved') {
$actions['reject'] = sprintf(
'<a href="#" class="bdfg-reject button button-small button-link-delete" data-id="%d">%s</a>',
$item->id,
__('Reject', 'bdfg-flexible-pricing')
);
}

if ($item->status === 'rejected') {
$actions['approve'] = sprintf(
'<a href="#" class="bdfg-approve button button-small" data-id="%d">%s</a>',
$item->id,
__('Approve', 'bdfg-flexible-pricing')
);
}

return implode(' ', $actions);
default:
return print_r($item, true);
}
}

/**
* Column checkbox
*
* @param object $item Item data
* @return string
*/
public function column_cb($item) {
return sprintf(
'<input type="checkbox" name="proposals[]" value="%d" />',
$item->id
);
}

/**
* Get views
*
* @return array
*/
public function get_views() {
global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

$status_links = array();
$current_status = isset($_REQUEST['status']) ? sanitize_text_field($_REQUEST['status']) : '';

// All link
$all_count = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
$class = empty($current_status) ? 'current' : '';
$status_links['all'] = "<a href='" . admin_url('admin.php?page=bdfg-flexible-pricing') . "' class='$class'>" . __('All', 'bdfg-flexible-pricing') . " <span class='count'>($all_count)</span></a>";

// Status specific links
$statuses = array(
'pending' => __('Pending', 'bdfg-flexible-pricing'),
'approved' => __('Approved', 'bdfg-flexible-pricing'),
'rejected' => __('Rejected', 'bdfg-flexible-pricing'),
'used' => __('Used', 'bdfg-flexible-pricing'),
'expired' => __('Expired', 'bdfg-flexible-pricing')
);

foreach ($statuses as $status => $label) {
$count = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE status = %s", $status));
if ($count > 0) {
$class = ($current_status === $status) ? 'current' : '';
$status_links[$status] = "<a href='" . admin_url('admin.php?page=bdfg-flexible-pricing&status=' . $status) . "' class='$class'>" . $label . " <span class='count'>($count)</span></a>";
}
}

return $status_links;
}

/**
* Prepare items
*/
public function prepare_items() {
global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

$per_page = 20;
$current_page = $this->get_pagenum();

// Process filter options
$where = '1=1';

// Filter by status if set
if (isset($_REQUEST['status']) && !empty($_REQUEST['status'])) {
$status = sanitize_text_field($_REQUEST['status']);
$where .= $wpdb->prepare(" AND status = %s", $status);
}

// Filter by product ID if set
if (isset($_REQUEST['product_id']) && !empty($_REQUEST['product_id'])) {
$product_id = intval($_REQUEST['product_id']);
$where .= $wpdb->prepare(" AND product_id = %d", $product_id);
}

// Filter by user ID if set
if (isset($_REQUEST['user_id']) && !empty($_REQUEST['user_id'])) {
$user_id = intval($_REQUEST['user_id']);
$where .= $wpdb->prepare(" AND user_id = %d", $user_id);
}

// Filter by date range if set
if (isset($_REQUEST['date_from']) && !empty($_REQUEST['date_from'])) {
$date_from = sanitize_text_field($_REQUEST['date_from']);
$where .= $wpdb->prepare(" AND created_at >= %s", $date_from);
}

if (isset($_REQUEST['date_to']) && !empty($_REQUEST['date_to'])) {
$date_to = sanitize_text_field($_REQUEST['date_to']);
$where .= $wpdb->prepare(" AND created_at <= %s", $date_to);
}

// Get total items count
$total_items = $wpdb->get_var("SELECT COUNT(id) FROM $table_name WHERE $where");

// Process ordering
$orderby = !empty($_REQUEST['orderby']) ? sanitize_text_field($_REQUEST['orderby']) : 'created_at';
$order = !empty($_REQUEST['order']) ? sanitize_text_field($_REQUEST['order']) : 'DESC';

// Handle special orderby cases
if ($orderby === 'discount') {
// Special handling for discount sorting
$orderby = '((original_price - proposed_price) / original_price * 100)';
}

// Prepare the SQL query
$sql = $wpdb->prepare(
"SELECT * FROM $table_name WHERE $where ORDER BY $orderby $order LIMIT %d OFFSET %d",
$per_page,
($current_page - 1) * $per_page
);

// Get the data
$this->items = $wpdb->get_results($sql);

// Set pagination arguments
$this->set_pagination_args(array(
'total_items' => $total_items,
'per_page' => $per_page,
'total_pages' => ceil($total_items / $per_page)
));

// Set column headers
$this->_column_headers = array(
$this->get_columns(),
array(),
$this->get_sortable_columns()
);
}

/**
* Get bulk actions
*
* @return array
*/
public function get_bulk_actions() {
return array(
'approve' => __('Approve', 'bdfg-flexible-pricing'),
'reject' => __('Reject', 'bdfg-flexible-pricing'),
'delete' => __('Delete', 'bdfg-flexible-pricing')
);
}

/**
* Process bulk actions
*/
public function process_bulk_action() {
// Security check
if (isset($_POST['_wpnonce']) && !empty($_POST['_wpnonce'])) {
$nonce = filter_input(INPUT_POST, '_wpnonce', FILTER_SANITIZE_STRING);
if (!wp_verify_nonce($nonce, 'bulk-' . $this->_args['plural'])) {
wp_die(__('Security check failed', 'bdfg-flexible-pricing'));
}
}

$action = $this->current_action();

if (!$action) {
return;
}

// Make sure we have proposals to process
if (!isset($_POST['proposals']) || empty($_POST['proposals'])) {
return;
}

global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

// Process each selected proposal
$proposals = array_map('intval', $_POST['proposals']);

switch ($action) {
case 'approve':
foreach ($proposals as $id) {
// Get proposal before updating to notify customer
$proposal = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $id));

if ($proposal && $proposal->status !== 'approved') {
$wpdb->update(
$table_name,
array(
'status' => 'approved',
'updated_at' => current_time('mysql')
),
array('id' => $id),
array('%s', '%s'),
array('%d')
);

// Notify customer if logged in user
if ($proposal->user_id > 0) {
$bdfg = BDFG_Flexible_Pricing::get_instance();
$bdfg->notify_customer_of_decision($proposal, 'approved');
}
}
}
break;

case 'reject':
foreach ($proposals as $id) {
// Get proposal before updating to notify customer
$proposal = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $id));

if ($proposal && $proposal->status !== 'rejected') {
$wpdb->update(
$table_name,
array(
'status' => 'rejected',
'updated_at' => current_time('mysql')
),
array('id' => $id),
array('%s', '%s'),
array('%d')
);

// Notify customer if logged in user
if ($proposal->user_id > 0) {
$bdfg = BDFG_Flexible_Pricing::get_instance();
$bdfg->notify_customer_of_decision($proposal, 'rejected');
}
}
}
break;

case 'delete':
foreach ($proposals as $id) {
$wpdb->delete(
$table_name,
array('id' => $id),
array('%d')
);
}
break;
}
}

/**
* Extra tablenav
*
* @param string $which Top or bottom
*/
public function extra_tablenav($which) {
if ($which !== 'top') {
return;
}

global $wpdb;
$table_name = $wpdb->prefix . 'bdfg_price_proposals';

$product_id = isset($_REQUEST['product_id']) ? intval($_REQUEST['product_id']) : 0;
$date_from = isset($_REQUEST['date_from']) ? sanitize_text_field($_REQUEST['date_from']) : '';
$date_to = isset($_REQUEST['date_to']) ? sanitize_text_field($_REQUEST['date_to']) : '';

echo '<div class="alignleft actions">';

// Product dropdown
echo '<select name="product_id" id="filter-by-product">';
echo '<option value="">' . __('All products', 'bdfg-flexible-pricing') . '</option>';

// Get products that have proposals
$products = $wpdb->get_results("SELECT DISTINCT product_id FROM $table_name ORDER BY product_id");

foreach ($products as $prod) {
$product = wc_get_product($prod->product_id);
$selected = selected($product_id, $prod->product_id, false);

$name = $product ? $product->get_name() : sprintf(__('Product #%d (Deleted)', 'bdfg-flexible-pricing'), $prod->product_id);
printf(
'<option value="%d" %s>%s</option>',
$prod->product_id,
$selected,
$name
);
}
echo '</select>';

// Date range
echo '<input type="date" name="date_from" value="' . esc_attr($date_from) . '" placeholder="' . __('From date', 'bdfg-flexible-pricing') . '" class="bdfg-date-input" />';
echo '<input type="date" name="date_to" value="' . esc_attr($date_to) . '" placeholder="' . __('To date', 'bdfg-flexible-pricing') . '" class="bdfg-date-input" />';

submit_button(__('Filter', 'bdfg-flexible-pricing'), '', 'filter_action', false);

// Add a reset filters button if any filter is active
if (!empty($product_id) || !empty($date_from) || !empty($date_to) || isset($_REQUEST['status'])) {
echo '<a href="' . esc_url(admin_url('admin.php?page=bdfg-flexible-pricing')) . '" class="button bdfg-reset-filters">' . __('Reset Filters', 'bdfg-flexible-pricing') . '</a>';
}

echo '</div>';
}

/**
* Display the list table
*/
public function display() {
// Process any bulk actions before displaying
$this->process_bulk_action();

// Display the table
parent::display();
}
}

templates/proposal-form.php

<?php
/**
* BDFG Flexible Pricing - Price Proposal Form
*
* @package BDFG_Flexible_Pricing
* @version 2.5.1
*/

defined('ABSPATH') || exit;
?>

<div class="bdfg-price-proposal-form">
<h4><?php _e('Make a Price Proposal', 'bdfg-flexible-pricing'); ?></h4>

<div class="bdfg-proposal-info">
<p><?php printf(__('Regular price: <span class="bdfg-original-price">%s</span>', 'bdfg-flexible-pricing'), wc_price($regular_price)); ?></p>

<?php if ($proposal): ?>
<?php if ($proposal->status === 'approved'): ?>
<div class="bdfg-proposal-approved">
<p class="bdfg-success-message">
<?php _e('Your price proposal has been approved!', 'bdfg-flexible-pricing'); ?>
</p>
<p class="bdfg-approved-price">
<?php printf(__('Approved price: <strong>%s</strong>', 'bdfg-flexible-pricing'), wc_price($proposal->proposed_price)); ?>
<span class="bdfg-discount-badge">
<?php
$discount = (($proposal->original_price - $proposal->proposed_price) / $proposal->original_price) * 100;
printf(__('%.2f%% off', 'bdfg-flexible-pricing'), $discount);
?>
</span>
</p>
<p class="bdfg-approved-note">
<?php _e('This price will be applied when you add this product to your cart.', 'bdfg-flexible-pricing'); ?>
</p>
</div>
<?php elseif ($proposal->status === 'pending'): ?>
<div class="bdfg-proposal-pending">
<p class="bdfg-info-message">
<?php _e('Your price proposal is being reviewed.', 'bdfg-flexible-pricing'); ?>
</p>
<p>
<?php printf(__('Proposed price: <strong>%s</strong>', 'bdfg-flexible-pricing'), wc_price($proposal->proposed_price)); ?>
</p>
<p class="bdfg-submission-date">
<?php printf(__('Submitted: %s', 'bdfg-flexible-pricing'), date_i18n(get_option('date_format'), strtotime($proposal->created_at))); ?>
</p>
</div>
<?php elseif ($proposal->status === 'rejected'): ?>
<div class="bdfg-proposal-rejected">
<p class="bdfg-error-message">
<?php _e('Your price proposal was not accepted.', 'bdfg-flexible-pricing'); ?>
</p>
<p>
<?php printf(__('You can submit a new proposal below.', 'bdfg-flexible-pricing')); ?>
</p>
<form class="bdfg-proposal-form">
<div class="bdfg-form-row">
<label for="bdfg-proposed-price"><?php _e('Your Offer:', 'bdfg-flexible-pricing'); ?></label>
<div class="bdfg-price-input">
<span class="bdfg-currency-symbol"><?php echo $currency; ?></span>
<input type="number" id="bdfg-proposed-price" name="proposed_price" min="<?php echo esc_attr($min_allowed); ?>" step="0.01" placeholder="<?php echo esc_attr($min_allowed); ?>">
</div>
</div>
<div class="bdfg-message-container"></div>
<div class="bdfg-form-row bdfg-form-actions">
<input type="hidden" name="product_id" value="<?php echo esc_attr($product->get_id()); ?>">
<button type="submit" class="button bdfg-submit-proposal"><?php _e('Submit Proposal', 'bdfg-flexible-pricing'); ?></button>
</div>
</form>
</div>
<?php else: ?>
<form class="bdfg-proposal-form">
<div class="bdfg-form-row">
<label for="bdfg-proposed-price"><?php _e('Your Offer:', 'bdfg-flexible-pricing'); ?></label>
<div class="bdfg-price-input">
<span class="bdfg-currency-symbol"><?php echo $currency; ?></span>
<input type="number" id="bdfg-proposed-price" name="proposed_price" min="<?php echo esc_attr($min_allowed); ?>" step="0.01" placeholder="<?php echo esc_attr($min_allowed); ?>">
</div>
</div>
<div class="bdfg-message-container"></div>
<div class="bdfg-form-row bdfg-form-actions">
<input type="hidden" name="product_id" value="<?php echo esc_attr($product->get_id()); ?>">
<button type="submit" class="button bdfg-submit-proposal"><?php _e('Submit Proposal', 'bdfg-flexible-pricing'); ?></button>
</div>
</form>
<?php endif; ?>
<?php else: ?>
<form class="bdfg-proposal-form">
<div class="bdfg-form-row">
<label for="bdfg-proposed-price"><?php _e('Your Offer:', 'bdfg-flexible-pricing'); ?></label>
<div class="bdfg-price-input">
<span class="bdfg-currency-symbol"><?php echo $currency; ?></span>
<input type="number" id="bdfg-proposed-price" name="proposed_price" min="<?php echo esc_attr($min_allowed); ?>" step="0.01" placeholder="<?php echo esc_attr($min_allowed); ?>">
</div>
</div>
<div class="bdfg-message-container"></div>
<div class="bdfg-form-row bdfg-form-actions">
<input type="hidden" name="product_id" value="<?php echo esc_attr($product->get_id()); ?>">
<button type="submit" class="button bdfg-submit-proposal"><?php _e('Submit Proposal', 'bdfg-flexible-pricing'); ?></button>
</div>
</form>
<?php endif; ?>
</div>

<div class="bdfg-proposal-guidelines">
<p class="bdfg-guidelines-text">
<?php
if ($this->min_discount > 0) {
printf(
__('Offer between %s and %s (discount range: %s%% to %s%%).', 'bdfg-flexible-pricing'),
wc_price($regular_price * (1 - $this->max_discount / 100)),
wc_price($regular_price * (1 - $this->min_discount / 100)),
$this->min_discount,
$this->max_discount
);
} else {
printf(
__('Maximum discount allowed: %s%% (minimum price: %s).', 'bdfg-flexible-pricing'),
$this->max_discount,
wc_price($regular_price * (1 - $this->max_discount / 100))
);
}
?>
</p>
</div>
</div>

assets/js/bdfg-flexible-pricing.js

相关文章: WordPress 智能缓存管理

/**
* BDFG Flexible Pricing - Frontend Script
*
* @package BDFG_Flexible_Pricing
* @version 2.5.1
*/
(function($) {
'use strict';

// Initialize the plugin when the DOM is fully loaded
$(document).ready(function() {

// Form submission handling
$('.bdfg-proposal-form').on('submit', function(e) {
e.preventDefault();

var $form = $(this),
$submitButton = $form.find('.bdfg-submit-proposal'),
$messageContainer = $form.find('.bdfg-message-container'),
proposedPrice = $form.find('#bdfg-proposed-price').val(),
productId = $form.find('input[name="product_id"]').val();

// Basic validation
if (!proposedPrice || isNaN(parseFloat(proposedPrice)) || parseFloat(proposedPrice) <= 0) {
$messageContainer.html('<div class="bdfg-error">' + bdfg_pricing_data.i18n.invalid_price + '</div>');
return false;
}

// Show loading state
$submitButton.prop('disabled', true).text(bdfg_pricing_data.i18n.proposing);
$messageContainer.html('<div class="bdfg-info">Processing...</div>');

// Send AJAX request
$.ajax({
url: bdfg_pricing_data.ajax_url,
type: 'POST',
data: {
action: 'bdfg_submit_price_proposal',
nonce: bdfg_pricing_data.nonce,
product_id: productId,
price: proposedPrice
},
success: function(response) {
if (response.success) {
// Success
$messageContainer.html('<div class="bdfg-success">' + response.data.message + '</div>');

// If approved, reload the page after a delay to show the updated form
if (response.data.status === 'approved') {
setTimeout(function() {
window.location.reload();
}, 2000);
} else {
// Reset form after a delay
setTimeout(function() {
$form[0].reset();
$submitButton.prop('disabled', false).text(bdfg_pricing_data.i18n.submit);
}, 2000);
}
} else {
// Error
$messageContainer.html('<div class="bdfg-error">' + response.data.message + '</div>');
$submitButton.prop('disabled', false).text(bdfg_pricing_data.i18n.submit);
}
},
error: function() {
// AJAX error
$messageContainer.html('<div class="bdfg-error">' + bdfg_pricing_data.i18n.error + '</div>');
$submitButton.prop('disabled', false).text(bdfg_pricing_data.i18n.submit);
}
});
});

// Price input validation
$('#bdfg-proposed-price').on('input', function() {
var $input = $(this),
value = parseFloat($input.val());

// Ensure valid number
if (isNaN(value) || value <= 0) {
return;
}

// Validate against min price if available
var minPrice = parseFloat($input.attr('min'));
if (!isNaN(minPrice) && value < minPrice) {
$input.addClass('bdfg-invalid-price');
$input.parent().siblings('.bdfg-price-warning').remove();
$input.parent().after('<div class="bdfg-price-warning">' + bdfg_pricing_data.i18n.price_too_low + '</div>');
} else {
$input.removeClass('bdfg-invalid-price');
$input.parent().siblings('.bdfg-price-warning').remove();
}
});

// Highlight price input on focus
$('#bdfg-proposed-price').on('focus', function() {
$(this).parent().addClass('bdfg-input-focused');
}).on('blur', function() {
$(this).parent().removeClass('bdfg-input-focused');
});

});
})(jQuery);

assets/js/bdfg-admin.js

/**
* BDFG Flexible Pricing - Admin Script
*
* @package BDFG_Flexible_Pricing
* @version 2.5.1
*/
(function($) {
'use strict';

// Run when document is ready
$(document).ready(function() {

// Approve proposal
$('.bdfg-proposals-container').on('click', '.bdfg-approve', function(e) {
e.preventDefault();

if (!confirm(bdfg_admin.i18n.approve_confirm)) {
return false;
}

var $button = $(this),
proposalId = $button.data('id'),
$row = $button.closest('tr');

$button.text(bdfg_admin.i18n.approving).attr('disabled', true);

// Send AJAX request
$.ajax({
url: bdfg_admin.ajax_url,
type: 'POST',
data: {
action: 'bdfg_approve_proposal',
nonce: bdfg_admin.nonce,
proposal_id: proposalId
},
success: function(response) {
if (response.success) {
// Update the status cell
$row.find('td.column-status').html('<span class="bdfg-status-approved">Approved</span>');

// Update the actions cell
$row.find('td.column-actions').html('<a href="#" class="bdfg-reject button button-small button-link-delete" data-id="' + proposalId + '">' + 'Reject' + '</a>');

// Flash the row
$row.addClass('bdfg-updated').delay(1500).queue(function(next) {
$(this).removeClass('bdfg-updated');
next();
});
} else {
alert(response.data.message || bdfg_admin.i18n.error);
$button.text('Approve').attr('disabled', false);
}
},
error: function() {
alert(bdfg_admin.i18n.error);
$button.text('Approve').attr('disabled', false);
}
});
});

// Reject proposal
$('.bdfg-proposals-container').on('click', '.bdfg-reject', function(e) {
e.preventDefault();

if (!confirm(bdfg_admin.i18n.reject_confirm)) {
return false;
}

var $button = $(this),
proposalId = $button.data('id'),
$row = $button.closest('tr');

$button.text(bdfg_admin.i18n.rejecting).attr('disabled', true);

// Send AJAX request
$.ajax({
url: bdfg_admin.ajax_url,
type: 'POST',
data: {
action: 'bdfg_reject_proposal',
nonce: bdfg_admin.nonce,
proposal_id: proposalId
},
success: function(response) {
if (response.success) {
// Update the status cell
$row.find('td.column-status').html('<span class="bdfg-status-rejected">Rejected</span>');

// Update the actions cell
$row.find('td.column-actions').html('<a href="#" class="bdfg-approve button button-small" data-id="' + proposalId + '">' + 'Approve' + '</a>');

// Flash the row
$row.addClass('bdfg-updated').delay(1500).queue(function(next) {
$(this).removeClass('bdfg-updated');
next();
});
} else {
alert(response.data.message || bdfg_admin.i18n.error);
$button.text('Reject').attr('disabled', false);
}
},
error: function() {
alert(bdfg_admin.i18n.error);
$button.text('Reject').attr('disabled', false);
}
});
});

// Date range picker functionality
if ($.fn.datepicker) {
$('.bdfg-date-input').datepicker({
dateFormat: 'yy-mm-dd',
changeMonth: true,
changeYear: true
});
}

// Statistics tab - toggle details
$('.bdfg-stat-box').on('click', function() {
$(this).toggleClass('bdfg-stat-expanded');
});

// Settings tab - min discount should not be greater than max discount
$('input[name="bdfg_min_discount"], input[name="bdfg_max_discount"]').on('change', function() {
var minDiscount = parseInt($('input[name="bdfg_min_discount"]').val()) || 0,
maxDiscount = parseInt($('input[name="bdfg_max_discount"]').val()) || 0;

if (minDiscount > maxDiscount) {
alert('Minimum discount cannot be greater than maximum discount');
$(this).val(maxDiscount);
}

// Ensure values are between 0 and 100
if (minDiscount < 0) {
$('input[name="bdfg_min_discount"]').val(0);
} else if (minDiscount > 100) {
$('input[name="bdfg_min_discount"]').val(100);
}

if (maxDiscount < 0) {
$('input[name="bdfg_max_discount"]').val(0);
} else if (maxDiscount > 100) {
$('input[name="bdfg_max_discount"]').val(100);
}
});

// Log current admin
console.log('BDFG Flexible Pricing initialized by: beiduofengou on ' + getCurrentDateTime());
});

/**
* Get current datetime in YYYY-MM-DD HH:MM:SS format
*/
function getCurrentDateTime() {
var now = new Date();

var year = now.getUTCFullYear();
var month = ('0' + (now.getUTCMonth() + 1)).slice(-2);
var day = ('0' + now.getUTCDate()).slice(-2);
var hours = ('0' + now.getUTCHours()).slice(-2);
var minutes = ('0' + now.getUTCMinutes()).slice(-2);
var seconds = ('0' + now.getUTCSeconds()).slice(-2);

return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
}

})(jQuery);

assets/css/bdfg-flexible-pricing.css

相关文章: WooCommerce 预定功能插件

/**
* BDFG Flexible Pricing - Frontend Styles
*
* @package BDFG_Flexible_Pricing
* @version 2.5.1
* @author beiduofengou
*/

/* Main container */
.bdfg-price-proposal-form {
margin: 1.5em 0;
padding: 1.2em;
border: 1px solid #eaeaea;
border-radius: 4px;
background-color: #f9f9f9;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}

.bdfg-price-proposal-form h4 {
margin: 0 0 1em;
padding-bottom: 0.5em;
border-bottom: 1px solid #eaeaea;
font-size: 1.2em;
font-weight: 600;
color: #333;
}

/* Form elements */
.bdfg-proposal-form {
margin-top: 1em;
}

.bdfg-form-row {
margin-bottom: 1em;
display: flex;
align-items: center;
}

.bdfg-form-row label {
width: 30%;
margin-right: 1em;
font-weight: 600;
}

.bdfg-price-input {
position: relative;
display: flex;
align-items: center;
width: 60%;
}

.bdfg-currency-symbol {
position: absolute;
left: 10px;
font-weight: bold;
color: #555;
z-index: 1;
}

#bdfg-proposed-price {
padding-left: 25px;
width: 100%;
border-radius: 3px;
border: 1px solid #ddd;
padding: 8px 8px 8px 25px;
font-size: 1em;
}

.bdfg-input-focused {
box-shadow: 0 0 0 1px rgba(30, 140, 190, 0.8);
}

.bdfg-form-actions {
margin-top: 1.5em;
}

.bdfg-submit-proposal {
background-color: #0073aa !important;
color: #fff !important;
border-color: #0073aa !important;
padding: 0.5em 1em !important;
cursor: pointer;
}

.bdfg-submit-proposal:hover {
background-color: #006799 !important;
}

.bdfg-submit-proposal:disabled {
opacity: 0.7;
cursor: not-allowed;
}

/* Messages */
.bdfg-message-container {
margin: 1em 0;
}

.bdfg-success {
padding: 0.75em 1em;
border-left: 4px solid #46b450;
background-color: #f7fff7;
color: #0a3622;
}

.bdfg-error {
padding: 0.75em 1em;
border-left: 4px solid #dc3232;
background-color: #fdf2f2;
color: #8a1f11;
}

.bdfg-info {
padding: 0.75em 1em;
border-left: 4px solid #00a0d2;
background-color: #f3f9fb;
color: #0a769d;
}

.bdfg-price-warning {
color: #d63638;
font-size: 0.9em;
margin-top: 0.5em;
padding-left: 30%;
}

.bdfg-invalid-price {
border-color: #d63638 !important;
}

/* Proposal statuses */
.bdfg-proposal-approved {
background-color: #f0f9e8;
border-left: 4px solid #46b450;
padding: 1em;
margin-bottom: 1.5em;
}

.bdfg-proposal-pending {
background-color: #f7fcfe;
border-left: 4px solid #00a0d2;
padding: 1em;
margin-bottom: 1.5em;
}

.bdfg-proposal-rejected {
background-color: #fef7f1;
border-left: 4px solid #ffb900;
padding: 1em;
margin-bottom: 1.5em;
}

.bdfg-success-message {
color: #46b450;
font-weight: 600;
margin-top: 0;
}

.bdfg-info-message {
color: #00a0d2;
font-weight: 600;
margin-top: 0;
}

.bdfg-error-message {
color: #dc3232;
font-weight: 600;
margin-top: 0;
}

.bdfg-discount-badge {
display: inline-block;
margin-left: 10px;
padding: 2px 8px;
border-radius: 3px;
background-color: #46b450;
color: white;
font-size: 0.85em;
font-weight: bold;
}

.bdfg-approved-price {
font-size: 1.1em;
}

.bdfg-approved-note {
color: #555;
font-style: italic;
margin-bottom: 0;
}

.bdfg-submission-date {
color: #777;
font-size: 0.9em;
font-style: italic;
margin-bottom: 0;
}

/* Guidelines */
.bdfg-proposal-guidelines {
margin-top: 1.5em;
padding-top: 1em;
border-top: 1px dashed #eaeaea;
font-size: 0.9em;
color: #666;
}

.bdfg-guidelines-text {
margin-bottom: 0;
}

.bdfg-original-price {
font-weight: bold;
color: #333;
}

/* Responsive */
@media screen and (max-width: 768px) {
.bdfg-form-row {
flex-direction: column;
align-items: flex-start;
}

.bdfg-form-row label {
width: 100%;
margin-bottom: 0.5em;
}

.bdfg-price-input {
width: 100%;
}

.bdfg-price-warning {
padding-left: 0;
}
}

/* BDFG Branding */
.bdfg-price-proposal-form::after {
content: "Powered by BDFG Flexible Pricing";
display: block;
text-align: right;
font-size: 0.75em;
color: #999;
margin-top: 1.5em;
font-style: italic;
}

assets/css/bdfg-admin.css

/**
* BDFG Flexible Pricing - Admin Styles
*
* @package BDFG_Flexible_Pricing
* @version 2.5.1
* @author beiduofengou
*/

/* Admin Container */
.bdfg-admin-wrap {
max-width: 100%;
margin-top: 20px;
}

/* Tabs Content */
.tab-content {
margin-top: 20px;
background: #fff;
border: 1px solid #ccd0d4;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
padding: 20px;
}

/* Settings Layout */
.bdfg-settings-container {
display: flex;
flex-wrap: wrap;
}

.bdfg-settings-container form {
flex: 1 1 65%;
min-width: 500px;
margin-right: 30px;
}

.bdfg-settings-sidebar {
flex: 1 1 25%;
min-width: 250px;
}

.bdfg-settings-box {
background: #f8f9fa;
border: 1px solid #e2e4e7;
border-radius: 3px;
padding: 15px;
margin-bottom: 20px;
}

.bdfg-settings-box h3 {
margin-top: 0;
border-bottom: 1px solid #e2e4e7;
padding-bottom: 10px;
color: #23282d;
}

/* Footer */
.bdfg-footer {
margin-top: 30px;
padding-top: 15px;
border-top: 1px solid #eee;
color: #777;
font-size: 13px;
text-align: right;
}

/* Proposals Table */
.bdfg-proposals-container {
position: relative;
}

.bdfg-filter-form {
margin-bottom: 20px;
}

.bdfg-date-input {
margin: 0 5px;
vertical-align: middle;
}

.bdfg-reset-filters {
margin-left: 5px !important;
}

/* Product Cell */
.bdfg-product-cell {
display: flex;
align-items: center;
}

.bdfg-product-thumb {
margin-right: 10px;
}

.bdfg-product-thumb img {
max-width: 32px;
height: auto;
vertical-align: middle;
border: 1px solid #eee;
border-radius: 2px;
}

.bdfg-product-info {
display: flex;
flex-direction: column;
}

.bdfg-product-id {
color: #777;
font-size: 12px;
}

/* Deleted Items */
.bdfg-deleted-product {
color: #a00;
font-style: italic;
}

/* User Cell */
.bdfg-user-email {
color: #777;
font-size: 12px;
}

.bdfg-guest-user {
color: #666;
font-style: italic;
}

/* Discount formatting */
.bdfg-high-discount {
color: #d63638;
font-weight: bold;
}

.bdfg-medium-discount {
color: #b26200;
font-weight: bold;
}

.bdfg-low-discount {
color: #007017;
}

/* Status labels */
.bdfg-status-pending {
background-color: #f1f1f1;
border: 1px solid #ccd0d4;
color: #222;
display: inline-block;
padding: 3px 7px;
border-radius: 3px;
}

.bdfg-status-approved {
background-color: #dff0d8;
border: 1px solid #c3e6cb;
color: #3c763d;
display: inline-block;
padding: 3px 7px;
border-radius: 3px;
}

.bdfg-status-rejected {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
display: inline-block;
padding: 3px 7px;
border-radius: 3px;
}

.bdfg-status-used {
background-color: #d1ecf1;
border: 1px solid #bee5eb;
color: #0c5460;
display: inline-block;
padding: 3px 7px;
border-radius: 3px;
}

.bdfg-status-expired {
background-color: #ffeeba;
border: 1px solid #ffeeba;
color: #856404;
display: inline-block;
padding: 3px 7px;
border-radius: 3px;
}

/* Row highlighting */
.bdfg-updated {
background-color: #fffbdd !important;
transition: background-color 1.5s ease-in-out;
}

/* Statistics Tab */
.bdfg-stats-container {
margin-top: 20px;
}

.bdfg-stats-summary {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 30px;
}

.bdfg-stat-box {
background: #fff;
border: 1px solid #e2e4e7;
border-radius: 5px;
padding: 15px;
flex: 1 1 calc(25% - 15px);
min-width: 150px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
text-align: center;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}

.bdfg-stat-box:hover {
transform: translateY(-2px);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
}

.bdfg-stat-box h3 {
margin: 0 0 10px 0;
color: #23282d;
font-size: 1em;
}

.bdfg-stat-number {
font-size: 2em;
font-weight: 600;
}

.bdfg-stat-approved .bdfg-stat-number {
color: #46b450;
}

.bdfg-stat-rejected .bdfg-stat-number {
color: #dc3232;
}

.bdfg-stat-pending .bdfg-stat-number {
color: #ffb900;
}

.bdfg-stat-discount .bdfg-stat-number {
color: #00a0d2;
}

.bdfg-stats-details {
background: #fff;
border: 1px solid #e2e4e7;
border-radius: 5px;
padding: 20px;
margin-top: 20px;
}

.bdfg-popular-products h3 {
margin-top: 0;
border-bottom: 1px solid #e2e4e7;
padding-bottom: 10px;
}

/* Custom WP Admin Branding */
#wpadminbar #wp-admin-bar-site-name>.ab-item:before {
content: "\f227";
top: 2px;
}

/* Version info */
.bdfg-admin-wrap::after {
content: "BDFG Flexible Pricing v2.5.1 - Last updated: 2025-03-05 16:04:03 by beiduofengou";
display: block;
text-align: right;
font-size: 11px;
color: #999;
margin-top: 30px;
}

 

Leave a Comment