WP simple points system 简单的积分系统

<?php /**
 * Simple Points System for WordPress
 * Replace myCRED with a lightweight, atomic points system
 * 
 * Installation:
 * 1. Add this code using code snippets plugin
 * 2. Visit the WordPress admin to see the Points menu
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

class WP_Simple_Points {
    
    private static $instance = null;
    private $table_name;
    private $hooks_option = 'wp_points_hooks';
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    private function __construct() {
        global $wpdb;
        $this->table_name = $wpdb->prefix . 'user_points_transactions';
        
        // Core hooks
        add_action('init', array($this, 'init'));
        add_action('admin_menu', array($this, 'add_admin_menu'));
        add_action('show_user_profile', array($this, 'show_user_points_field'));
        add_action('edit_user_profile', array($this, 'show_user_points_field'));
        add_action('personal_options_update', array($this, 'save_user_points_field'));
        add_action('edit_user_profile_update', array($this, 'save_user_points_field'));
        
        // Points earning hooks
        add_action('user_register', array($this, 'points_on_register'));
        add_action('wp_login', array($this, 'points_on_login'), 10, 2);
        
        // AJAX handlers
        add_action('wp_ajax_update_user_points', array($this, 'ajax_update_user_points'));
        
        // Activation hook
        register_activation_hook(__FILE__, array($this, 'create_tables'));
    }
    
    public function init() {
        // Create tables if they don't exist
        $this->create_tables();
        
        // Register shortcode
        add_shortcode('user_points', array($this, 'points_shortcode'));
    }
    
    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) NOT NULL,
            amount decimal(10,2) NOT NULL,
            balance_after decimal(10,2) NOT NULL,
            description text NOT NULL,
            transaction_type varchar(50) DEFAULT 'manual',
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY user_id (user_id),
            KEY created_at (created_at)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
    }
    
    // Core Functions
    
    /**
     * Get user balance
     */
    public function get_balance($user_id) {
        global $wpdb;
        
        // Get from user meta (cached value)
        $balance = get_user_meta($user_id, 'points_balance', true);
        
        if ($balance === '') {
            // If no cached value, calculate from transactions
            $balance = $wpdb->get_var($wpdb->prepare(
                "SELECT balance_after FROM {$this->table_name} 
                WHERE user_id = %d 
                ORDER BY id DESC 
                LIMIT 1",
                $user_id
            ));
            
            $balance = $balance ? floatval($balance) : 0;
            update_user_meta($user_id, 'points_balance', $balance);
        }
        
        return floatval($balance);
    }
    
    /**
     * Add or subtract points (atomic operation)
     */
    public function update_points($user_id, $amount, $description = '', $type = 'manual') {
        global $wpdb;
        
        // Start transaction for atomicity
        $wpdb->query('START TRANSACTION');
        
        try {
            // Lock the user's transactions
            $current_balance = $wpdb->get_var($wpdb->prepare(
                "SELECT balance_after FROM {$this->table_name} 
                WHERE user_id = %d 
                ORDER BY id DESC 
                LIMIT 1 
                FOR UPDATE",
                $user_id
            ));
            
            if ($current_balance === null) {
                $current_balance = 0;
            }
            
            $new_balance = floatval($current_balance) + floatval($amount);
            
            // Prevent negative balance
            if ($new_balance < 0) {
                $wpdb->query('ROLLBACK');
                return new WP_Error('insufficient_balance', 'Insufficient balance');
            }
            
            // Insert transaction
            $result = $wpdb->insert(
                $this->table_name,
                array(
                    'user_id' => $user_id,
                    'amount' => $amount,
                    'balance_after' => $new_balance,
                    'description' => $description,
                    'transaction_type' => $type,
                    'created_at' => current_time('mysql')
                ),
                array('%d', '%f', '%f', '%s', '%s', '%s')
            );
            
            if ($result === false) {
                $wpdb->query('ROLLBACK');
                return new WP_Error('db_error', 'Database error');
            }
            
            // Update cached balance
            update_user_meta($user_id, 'points_balance', $new_balance);
            
            // Commit transaction
            $wpdb->query('COMMIT');
            
            // Trigger action for other plugins
            do_action('wp_points_balance_updated', $user_id, $amount, $new_balance, $description);
            
            return $new_balance;
            
        } catch (Exception $e) {
            $wpdb->query('ROLLBACK');
            return new WP_Error('transaction_error', $e->getMessage());
        }
    }
    
    /**
     * Add points
     */
    public function add_points($user_id, $amount, $description = '', $type = 'manual') {
        return $this->update_points($user_id, abs($amount), $description, $type);
    }
    
    /**
     * Subtract points
     */
    public function subtract_points($user_id, $amount, $description = '', $type = 'manual') {
        return $this->update_points($user_id, -abs($amount), $description, $type);
    }
    
    // Admin UI
    
    public function add_admin_menu() {
        add_menu_page(
            'Points System',
            'Points',
            'manage_options',
            'wp-points',
            array($this, 'admin_page'),
            'dashicons-awards',
            30
        );
        
        add_submenu_page(
            'wp-points',
            'Transactions',
            'Transactions',
            'manage_options',
            'wp-points',
            array($this, 'admin_page')
        );
        
        add_submenu_page(
            'wp-points',
            'Settings',
            'Settings',
            'manage_options',
            'wp-points-settings',
            array($this, 'settings_page')
        );
    }
    
    public function admin_page() {
        global $wpdb;
        
        // Handle bulk actions
        if (isset($_POST['action']) && $_POST['action'] === 'bulk_update' && check_admin_referer('points_bulk_action')) {
            $this->handle_bulk_update();
        }
        
        // Get filter parameters
        $user_id = isset($_GET['user_id']) ? intval($_GET['user_id']) : 0;
        $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
        $per_page = 50;
        $offset = ($page - 1) * $per_page;
        
        // Build query
        $where = array('1=1');
        $where_values = array();
        
        if ($user_id > 0) {
            $where[] = 'user_id = %d';
            $where_values[] = $user_id;
        }
        
        $where_clause = implode(' AND ', $where);
        
        // Get total count
        $total_items = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$this->table_name} WHERE $where_clause",
            ...$where_values
        ));
        
        // Get transactions
        $transactions = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$this->table_name} 
            WHERE $where_clause 
            ORDER BY id DESC 
            LIMIT %d OFFSET %d",
            array_merge($where_values, array($per_page, $offset))
        ));
        
        // Get user balances for summary
        $total_users = $wpdb->get_var(
            "SELECT COUNT(DISTINCT user_id) FROM {$this->table_name}"
        );
        
        $total_points = $wpdb->get_var(
            "SELECT SUM(balance_after) FROM (
                SELECT user_id, balance_after 
                FROM {$this->table_name} t1 
                WHERE id = (
                    SELECT MAX(id) 
                    FROM {$this->table_name} t2 
                    WHERE t2.user_id = t1.user_id
                )
            ) as latest_balances"
        );
        
        // Get recent activity stats
        $today_transactions = $wpdb->get_var(
            "SELECT COUNT(*) FROM {$this->table_name} WHERE DATE(created_at) = CURDATE()"
        );
        
        $active_users_today = $wpdb->get_var(
            "SELECT COUNT(DISTINCT user_id) FROM {$this->table_name} WHERE DATE(created_at) = CURDATE()"
        );
        
        ?>
        <style>
            .wrap { margin: 0; }
            .points-wrap { background: #f0f0f1; min-height: 100vh; margin: 0; padding: 0; }
            .points-header { background: #fff; padding: 20px 32px; margin: 0; box-shadow: 0 1px 3px rgba(0,0,0,0.1); position: relative; z-index: 10; }
            .points-header h1 { margin: 0; font-size: 24px; font-weight: 600; color: #1d2327; line-height: 1.3; }
            .points-content { padding: 32px 20px; }
            .points-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 20px; margin-bottom: 32px; }
            .stat-card { background: #fff; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); transition: transform 0.2s, box-shadow 0.2s; }
            .stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
            .stat-card h3 { margin: 0 0 8px; font-size: 13px; font-weight: 500; color: #50575e; text-transform: uppercase; letter-spacing: 0.5px; }
            .stat-card .value { font-size: 32px; font-weight: 600; color: #1d2327; line-height: 1; }
            .stat-card .meta { font-size: 13px; color: #8c8f94; margin-top: 8px; }
            .bulk-update-card { background: #fff; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 24px; }
            .bulk-update-card h3 { margin: 0 0 20px; font-size: 16px; font-weight: 600; color: #1d2327; }
            .bulk-form { display: grid; grid-template-columns: 2fr 1fr 1fr 2fr auto; gap: 16px; align-items: end; }
            .form-group { display: flex; flex-direction: column; }
            .form-group label { font-size: 13px; font-weight: 500; color: #1d2327; margin-bottom: 6px; }
            .form-group input, .form-group select { padding: 8px 12px; border: 1px solid #dcdcde; border-radius: 4px; font-size: 14px; width: 100%; box-sizing: border-box; }
            .form-group input:focus, .form-group select:focus { border-color: #2271b1; outline: none; box-shadow: 0 0 0 1px #2271b1; }
            .btn-update { background: #2271b1; color: #fff; border: none; padding: 8px 24px; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; transition: background 0.2s; white-space: nowrap; }
            .btn-update:hover { background: #135e96; }
            .filter-bar { background: #fff; border-radius: 8px; padding: 16px 24px; margin-bottom: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
            .filter-bar form { display: flex; align-items: center; gap: 12px; margin: 0; flex-wrap: wrap; }
            .transactions-table { background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
            .transactions-table table { margin: 0; border: none; }
            .transactions-table th { background: #f6f7f7; font-weight: 600; color: #1d2327; padding: 16px; text-align: left; border-bottom: 1px solid #e1e1e1; }
            .transactions-table td { padding: 16px; vertical-align: middle; }
            .transactions-table tr:hover td { background: #f6f7f7; }
            .user-info { display: flex; align-items: center; gap: 12px; }
            .user-avatar { width: 40px; height: 40px; border-radius: 50%; background: #f0f0f1; display: flex; align-items: center; justify-content: center; font-weight: 600; color: #50575e; flex-shrink: 0; }
            .user-details a { color: #2271b1; text-decoration: none; font-weight: 500; }
            .user-details a:hover { color: #135e96; }
            .user-details small { display: block; color: #8c8f94; font-size: 12px; margin-top: 2px; }
            .amount { font-weight: 600; font-size: 15px; }
            .amount.positive { color: #00a32a; }
            .amount.negative { color: #d63638; }
            .badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; }
            .badge-manual { background: #e7f2fd; color: #135e96; }
            .badge-registration { background: #edfaef; color: #00a32a; }
            .badge-daily_login { background: #fcf0e4; color: #996800; }
            .badge-admin_adjustment { background: #fef1f1; color: #d63638; }
            .badge-bulk_update { background: #f0f0f1; color: #50575e; }
            .pagination-wrap { margin-top: 24px; text-align: center; }
            .pagination-wrap .page-numbers { padding: 6px 12px; margin: 0 2px; border: 1px solid #dcdcde; border-radius: 4px; text-decoration: none; color: #50575e; transition: all 0.2s; display: inline-block; }
            .pagination-wrap .page-numbers:hover { border-color: #2271b1; color: #2271b1; }
            .pagination-wrap .page-numbers.current { background: #2271b1; color: #fff; border-color: #2271b1; }
            
            /* Responsive Design */
            @media (max-width: 1200px) { 
                .bulk-form { grid-template-columns: 1fr 1fr; }
                .bulk-form .btn-update { grid-column: 1 / -1; }
            }
            @media (max-width: 768px) {
                .points-header { padding: 16px 20px; }
                .points-header h1 { font-size: 20px; }
                .points-content { padding: 20px 16px; }
                .points-stats { grid-template-columns: 1fr; gap: 16px; }
                .stat-card { padding: 20px; }
                .stat-card .value { font-size: 28px; }
                .bulk-form { grid-template-columns: 1fr; }
                .transactions-table { overflow-x: auto; }
                .transactions-table table { min-width: 600px; }
                .filter-bar form { flex-direction: column; align-items: stretch; }
                .filter-bar input, .filter-bar button { width: 100%; }
            }
            @media (max-width: 480px) {
                .user-info { flex-direction: column; align-items: flex-start; gap: 8px; }
                .user-avatar { width: 32px; height: 32px; font-size: 14px; }
                .transactions-table th, .transactions-table td { padding: 12px; font-size: 13px; }
                .amount { font-size: 14px; }
            }
        </style>
        
        <div class="wrap">
            <div class="points-wrap">
                <div class="points-header">
                    <h1>🎯 Points System</h1>
                </div>
                
                <div class="points-content">
                    <!-- Summary Cards -->
                    <div class="points-stats">
                        <div class="stat-card">
                            <h3>Total Users</h3>
                            <div class="value"><?php echo number_format($total_users); ?></div>
                            <div class="meta">Active in points system</div>
                        </div>
                        <div class="stat-card">
                            <h3>Total Points</h3>
                            <div class="value"><?php echo number_format($total_points, 0); ?></div>
                            <div class="meta">Across all users</div>
                        </div>
                        <div class="stat-card">
                            <h3>Today's Activity</h3>
                            <div class="value"><?php echo number_format($today_transactions); ?></div>
                            <div class="meta"><?php echo number_format($active_users_today); ?> active users</div>
                        </div>
                        <div class="stat-card">
                            <h3>Average Balance</h3>
                            <div class="value"><?php echo $total_users > 0 ? number_format($total_points / $total_users, 0) : '0'; ?></div>
                            <div class="meta">Points per user</div>
                        </div>
                    </div>
                    
                    <!-- Bulk Update Form -->
                    <div class="bulk-update-card">
                        <h3>Bulk Update Points</h3>
                        <form method="post" class="bulk-form">
                            <?php wp_nonce_field('points_bulk_action'); ?>
                            <input type="hidden" name="action" value="bulk_update">
                            <div class="form-group">
                                <label>User(s)</label>
                                <input type="text" name="user_ids" placeholder="User IDs (comma separated) or 'all'">
                            </div>
                            <div class="form-group">
                                <label>Action</label>
                                <select name="operation">
                                    <option value="add">Add Points</option>
                                    <option value="subtract">Subtract Points</option>
                                    <option value="set">Set Balance</option>
                                </select>
                            </div>
                            <div class="form-group">
                                <label>Amount</label>
                                <input type="number" name="amount" step="0.01" required placeholder="0.00">
                            </div>
                            <div class="form-group">
                                <label>Description</label>
                                <input type="text" name="description" required placeholder="Reason for update">
                            </div>
                            <button type="submit" class="btn-update">Update Points</button>
                        </form>
                    </div>
                    
                    <!-- Filters -->
                    <div class="filter-bar">
                        <form method="get">
                            <input type="hidden" name="page" value="wp-points">
                            <label style="margin: 0; font-weight: 500;">Filter by User:</label>
                            <input type="number" name="user_id" value="<?php echo $user_id; ?>" placeholder="User ID" style="width: 120px; padding: 6px 12px; border: 1px solid #dcdcde; border-radius: 4px;">
                            <button type="submit" class="button button-primary">Filter</button>
                            <?php if ($user_id): ?>
                                <a href="<?php echo admin_url('admin.php?page=wp-points'); ?>" class="button">Clear Filter</a>
                            <?php endif; ?>
                        </form>
                    </div>
                    
                    <!-- Transactions Table -->
                    <div class="transactions-table">
                        <table class="wp-list-table widefat fixed striped">
                            <thead>
                                <tr>
                                    <th style="width: 60px;">ID</th>
                                    <th>User</th>
                                    <th style="width: 120px;">Amount</th>
                                    <th style="width: 120px;">Balance After</th>
                                    <th>Description</th>
                                    <th style="width: 120px;">Type</th>
                                    <th style="width: 160px;">Date</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php if (empty($transactions)): ?>
                                    <tr>
                                        <td colspan="7" style="text-align: center; padding: 40px;">
                                            No transactions found. Start by adding points to users.
                                        </td>
                                    </tr>
                                <?php else: ?>
                                    <?php foreach ($transactions as $transaction): ?>
                                        <?php $user = get_userdata($transaction->user_id); ?>
                                        <tr>
                                            <td style="color: #8c8f94;">#<?php echo $transaction->id; ?></td>
                                            <td>
                                                <div class="user-info">
                                                    <div class="user-avatar">
                                                        <?php echo $user ? strtoupper(substr($user->display_name, 0, 1)) : '?'; ?>
                                                    </div>
                                                    <div class="user-details">
                                                        <?php if ($user): ?>
                                                            <a href="<?php echo get_edit_user_link($transaction->user_id); ?>">
                                                                <?php echo esc_html($user->display_name); ?>
                                                            </a>
                                                            <small><?php echo esc_html($user->user_email); ?></small>
                                                        <?php else: ?>
                                                            <span style="color: #8c8f94;">User #<?php echo $transaction->user_id; ?> (deleted)</span>
                                                        <?php endif; ?>
                                                    </div>
                                                </div>
                                            </td>
                                            <td>
                                                <span class="amount <?php echo $transaction->amount >= 0 ? 'positive' : 'negative'; ?>">
                                                    <?php echo $transaction->amount >= 0 ? '+' : ''; ?><?php echo number_format($transaction->amount, 2); ?>
                                                </span>
                                            </td>
                                            <td style="font-weight: 500;"><?php echo number_format($transaction->balance_after, 2); ?></td>
                                            <td><?php echo esc_html($transaction->description); ?></td>
                                            <td>
                                                <span class="badge badge-<?php echo esc_attr($transaction->transaction_type); ?>">
                                                    <?php echo str_replace('_', ' ', esc_html($transaction->transaction_type)); ?>
                                                </span>
                                            </td>
                                            <td style="color: #50575e;">
                                                <?php echo date('M d, Y', strtotime($transaction->created_at)); ?><br>
                                                <small style="color: #8c8f94;"><?php echo date('g:i A', strtotime($transaction->created_at)); ?></small>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                <?php endif; ?>
                            </tbody>
                        </table>
                    </div>
                    
                    <!-- Pagination -->
                    <?php
                    $total_pages = ceil($total_items / $per_page);
                    if ($total_pages > 1):
                        $args = array(
                            'base' => add_query_arg('paged', '%#%'),
                            'format' => '',
                            'prev_text' => '← Previous',
                            'next_text' => 'Next →',
                            'total' => $total_pages,
                            'current' => $page
                        );
                        ?>
                        <div class="pagination-wrap">
                            <?php echo paginate_links($args); ?>
                        </div>
                    <?php endif; ?>
                </div>
            </div>
        </div>
        <?php
    }
    
    public function settings_page() {
        if (isset($_POST['submit']) && check_admin_referer('points_settings')) {
            $hooks = array(
                'register' => isset($_POST['hooks']['register']),
                'register_points' => intval($_POST['register_points']),
                'daily_login' => isset($_POST['hooks']['daily_login']),
                'daily_login_points' => intval($_POST['daily_login_points'])
            );
            update_option($this->hooks_option, $hooks);
            echo '<div class="notice notice-success is-dismissible" style="margin: 20px 20px 0;"><p>✅ Settings saved successfully!</p></div>';
        }
        
        $hooks = get_option($this->hooks_option, array(
            'register' => true,
            'register_points' => 100,
            'daily_login' => true,
            'daily_login_points' => 10
        ));
        ?>
        <style>
            .wrap { margin: 0; }
            .settings-wrap { background: #f0f0f1; min-height: 100vh; margin: 0; padding: 0; }
            .settings-header { background: #fff; padding: 20px 32px; margin: 0; box-shadow: 0 1px 3px rgba(0,0,0,0.1); position: relative; z-index: 10; }
            .settings-header h1 { margin: 0; font-size: 24px; font-weight: 600; color: #1d2327; line-height: 1.3; }
            .settings-content { padding: 32px 20px; }
            .settings-card { background: #fff; border-radius: 8px; padding: 32px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 24px; }
            .settings-card h2 { margin: 0 0 24px; font-size: 18px; font-weight: 600; color: #1d2327; padding-bottom: 16px; border-bottom: 1px solid #e1e1e1; }
            .settings-row { display: flex; align-items: start; padding: 20px 0; border-bottom: 1px solid #f0f0f1; }
            .settings-row:last-child { border-bottom: none; }
            .settings-label { flex: 0 0 300px; padding-right: 32px; }
            .settings-label h3 { margin: 0 0 4px; font-size: 15px; font-weight: 600; color: #1d2327; }
            .settings-label p { margin: 0; font-size: 13px; color: #50575e; line-height: 1.5; }
            .settings-control { flex: 1; }
            .toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; }
            .toggle-switch input { opacity: 0; width: 0; height: 0; }
            .toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccd0d4; transition: .3s; border-radius: 24px; }
            .toggle-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; }
            input:checked + .toggle-slider { background-color: #2271b1; }
            input:checked + .toggle-slider:before { transform: translateX(20px); }
            .points-input { margin-top: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
            .points-input input { width: 80px; padding: 8px 12px; border: 1px solid #dcdcde; border-radius: 4px; font-size: 14px; }
            .points-input input:focus { border-color: #2271b1; outline: none; box-shadow: 0 0 0 1px #2271b1; }
            .usage-examples { background: #f6f7f7; border-radius: 8px; padding: 24px; margin-top: 16px; }
            .usage-examples h3 { margin: 0 0 16px; font-size: 16px; font-weight: 600; color: #1d2327; }
            .code-example { background: #1d2327; color: #fff; padding: 16px 20px; border-radius: 4px; margin-bottom: 16px; font-family: 'Monaco', 'Consolas', monospace; font-size: 13px; line-height: 1.6; overflow-x: auto; }
            .code-example strong { color: #72aee6; font-weight: normal; }
            .code-comment { color: #8b8b8b; }
            .submit-wrap { margin-top: 32px; padding-top: 24px; border-top: 1px solid #e1e1e1; }
            .button-primary { background: #2271b1; border-color: #2271b1; color: #fff; padding: 8px 24px; font-size: 14px; font-weight: 500; border-radius: 4px; transition: background 0.2s; }
            .button-primary:hover { background: #135e96; border-color: #135e96; }
            .api-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; }
            .api-card { background: #fff; border: 1px solid #e1e1e1; border-radius: 4px; padding: 16px; }
            .api-card h4 { margin: 0 0 8px; font-size: 14px; font-weight: 600; color: #1d2327; }
            .api-card code { background: #f0f0f1; padding: 2px 4px; border-radius: 2px; font-size: 12px; }
            
            /* Responsive Design */
            @media (max-width: 768px) {
                .settings-header { padding: 16px 20px; }
                .settings-header h1 { font-size: 20px; }
                .settings-content { padding: 20px 16px; }
                .settings-card { padding: 20px; }
                .settings-card h2 { font-size: 16px; margin-bottom: 16px; padding-bottom: 12px; }
                .settings-row { flex-direction: column; padding: 16px 0; }
                .settings-label { flex: none; padding-right: 0; margin-bottom: 12px; }
                .points-input { flex-direction: column; align-items: flex-start; }
                .points-input input { width: 100px; }
                .api-grid { grid-template-columns: 1fr; }
                .usage-examples { padding: 16px; }
                .code-example { padding: 12px 16px; font-size: 12px; }
            }
        </style>
        
        <div class="wrap">
            <div class="settings-wrap">
                <div class="settings-header">
                    <h1>⚙️ Points Settings</h1>
                </div>
                
                <div class="settings-content">
                    <form method="post">
                        <?php wp_nonce_field('points_settings'); ?>
                        
                        <div class="settings-card">
                            <h2>Automatic Points Rewards</h2>
                            
                            <div class="settings-row">
                                <div class="settings-label">
                                    <h3>Registration Bonus</h3>
                                    <p>Award points automatically when new users sign up</p>
                                </div>
                                <div class="settings-control">
                                    <label class="toggle-switch">
                                        <input type="checkbox" name="hooks[register]" value="1" <?php checked($hooks['register']); ?>>
                                        <span class="toggle-slider"></span>
                                    </label>
                                    <div class="points-input">
                                        <span>Award</span>
                                        <input type="number" name="register_points" value="<?php echo $hooks['register_points']; ?>" min="0">
                                        <span>points on registration</span>
                                    </div>
                                </div>
                            </div>
                            
                            <div class="settings-row">
                                <div class="settings-label">
                                    <h3>Daily Login Reward</h3>
                                    <p>Give points to users who log in each day (once per day)</p>
                                </div>
                                <div class="settings-control">
                                    <label class="toggle-switch">
                                        <input type="checkbox" name="hooks[daily_login]" value="1" <?php checked($hooks['daily_login']); ?>>
                                        <span class="toggle-slider"></span>
                                    </label>
                                    <div class="points-input">
                                        <span>Award</span>
                                        <input type="number" name="daily_login_points" value="<?php echo $hooks['daily_login_points']; ?>" min="0">
                                        <span>points per daily login</span>
                                    </div>
                                </div>
                            </div>
                            
                            <div class="submit-wrap">
                                <?php submit_button('Save Settings', 'primary', 'submit', false); ?>
                            </div>
                        </div>
                    </form>
                    
                    <div class="settings-card">
                        <h2>🚀 Developer Documentation</h2>
                        
                        <div class="usage-examples">
                            <h3>Quick Start Guide</h3>
                            
                            <div class="code-example">
<strong>// Get user's current balance</strong>
$balance = wp_points()->get_balance($user_id);
<span class="code-comment">// or use the helper function</span>
$balance = get_user_points($user_id);
                            </div>
                            
                            <div class="code-example">
<strong>// Add points to user</strong>
wp_points()->add_points($user_id, 50, 'Completed profile');
<span class="code-comment">// or use the helper function</span>
add_user_points($user_id, 50, 'Completed profile');
                            </div>
                            
                            <div class="code-example">
<strong>// Subtract points from user</strong>
wp_points()->subtract_points($user_id, 20, 'Premium feature usage');
<span class="code-comment">// or use the helper function</span>
subtract_user_points($user_id, 20, 'Premium feature usage');
                            </div>
                            
                            <div class="code-example">
<strong>// Check balance before action</strong>
if (get_user_points($user_id) >= 100) {
    <span class="code-comment">// User has enough points, proceed with action</span>
    subtract_user_points($user_id, 100, 'Unlocked premium content');
    <span class="code-comment">// Grant access to premium content</span>
} else {
    <span class="code-comment">// Show insufficient balance message</span>
}
                            </div>
                            
                            <div class="code-example">
<strong>// Display user balance with shortcode</strong>
[user_points]
<span class="code-comment">// or for specific user</span>
[user_points user_id="123"]
                            </div>
                            
                            <div class="code-example">
<strong>// Hook into balance updates</strong>
add_action('wp_points_balance_updated', function($user_id, $amount, $new_balance, $description) {
    <span class="code-comment">// Send email notification for large transactions</span>
    if (abs($amount) >= 1000) {
        wp_mail(get_userdata($user_id)->user_email, 'Points Update', 
            "Your balance changed by {$amount} points. New balance: {$new_balance}");
    }
}, 10, 4);
                            </div>
                        </div>
                        
                        <h3 style="margin: 24px 0 16px; font-size: 16px; font-weight: 600;">Available Functions</h3>
                        
                        <div class="api-grid">
                            <div class="api-card">
                                <h4>wp_points()</h4>
                                <p style="margin: 0; font-size: 13px; color: #50575e;">Returns the main points system instance</p>
                            </div>
                            <div class="api-card">
                                <h4>get_user_points($user_id)</h4>
                                <p style="margin: 0; font-size: 13px; color: #50575e;">Get user's current balance</p>
                            </div>
                            <div class="api-card">
                                <h4>add_user_points($user_id, $amount, $description)</h4>
                                <p style="margin: 0; font-size: 13px; color: #50575e;">Add points to user balance</p>
                            </div>
                            <div class="api-card">
                                <h4>subtract_user_points($user_id, $amount, $description)</h4>
                                <p style="margin: 0; font-size: 13px; color: #50575e;">Subtract points from user balance</p>
                            </div>
                        </div>
                        
                        <h3 style="margin: 24px 0 16px; font-size: 16px; font-weight: 600;">Available Hooks</h3>
                        
                        <div class="api-grid">
                            <div class="api-card">
                                <h4>wp_points_balance_updated</h4>
                                <p style="margin: 0; font-size: 13px; color: #50575e;">Fired after any balance change<br>
                                <code>$user_id, $amount, $new_balance, $description</code></p>
                            </div>
                            <div class="api-card">
                                <h4>wp_points_after_registration</h4>
                                <p style="margin: 0; font-size: 13px; color: #50575e;">Add custom logic after user registration<br>
                                <code>$user_id</code></p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <?php
    }
    
    // User Profile Integration
    
    public function show_user_points_field($user) {
        if (!current_user_can('edit_users')) {
            return;
        }
        
        $balance = $this->get_balance($user->ID);
        ?>
        <style>
            .points-section { margin-top: 40px; }
            .points-section h3 { font-size: 16px; font-weight: 600; margin-bottom: 20px; }
            .points-balance-box { background: #f6f7f7; border: 1px solid #dcdcde; border-radius: 8px; padding: 20px; margin-bottom: 20px; }
            .balance-display { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; }
            .balance-value { font-size: 32px; font-weight: 600; color: #1d2327; }
            .balance-label { font-size: 14px; color: #50575e; }
            .balance-input-wrap { margin-top: 16px; }
            .balance-input-wrap input { width: 200px; padding: 8px 12px; border: 1px solid #dcdcde; border-radius: 4px; }
            .points-transactions { margin-top: 30px; }
            .points-transactions h4 { font-size: 15px; font-weight: 600; margin-bottom: 16px; }
            .points-table { border: 1px solid #dcdcde; border-radius: 8px; overflow: hidden; background: #fff; }
            .points-table table { margin: 0; border: none; }
            .points-table th { background: #f6f7f7; font-weight: 500; font-size: 13px; color: #50575e; padding: 12px 16px; text-align: left; border-bottom: 1px solid #e1e1e1; }
            .points-table td { padding: 12px 16px; font-size: 13px; }
            .points-table tr:nth-child(even) td { background: #f9f9f9; }
            .points-amount { font-weight: 600; }
            .points-amount.positive { color: #00a32a; }
            .points-amount.negative { color: #d63638; }
            .view-all-link { display: inline-block; margin-top: 12px; color: #2271b1; text-decoration: none; font-size: 14px; }
            .view-all-link:hover { color: #135e96; text-decoration: underline; }
            .no-transactions { text-align: center; padding: 40px; color: #8c8f94; }
        </style>
        
        <div class="points-section">
            <h3>💰 Points Balance</h3>
            
            <div class="points-balance-box">
                <div class="balance-display">
                    <span class="balance-value"><?php echo number_format($balance, 2); ?></span>
                    <span class="balance-label">points</span>
                </div>
                
                <div class="balance-input-wrap">
                    <label for="points_balance" style="display: block; margin-bottom: 8px; font-weight: 500;">Adjust Balance</label>
                    <input type="number" name="points_balance" id="points_balance" value="<?php echo $balance; ?>" step="0.01" />
                    <p class="description" style="margin-top: 8px;">Changing this value will create a manual adjustment transaction.</p>
                </div>
            </div>
            
            <div class="points-transactions">
                <h4>Recent Transactions</h4>
                
                <?php
                global $wpdb;
                $transactions = $wpdb->get_results($wpdb->prepare(
                    "SELECT * FROM {$this->table_name} WHERE user_id = %d ORDER BY id DESC LIMIT 10",
                    $user->ID
                ));
                
                if ($transactions): ?>
                    <div class="points-table">
                        <table class="widefat">
                            <thead>
                                <tr>
                                    <th>Date</th>
                                    <th>Amount</th>
                                    <th>Balance</th>
                                    <th>Description</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php foreach ($transactions as $t): ?>
                                    <tr>
                                        <td><?php echo date('M d, Y g:i A', strtotime($t->created_at)); ?></td>
                                        <td>
                                            <span class="points-amount <?php echo $t->amount >= 0 ? 'positive' : 'negative'; ?>">
                                                <?php echo $t->amount >= 0 ? '+' : ''; ?><?php echo number_format($t->amount, 2); ?>
                                            </span>
                                        </td>
                                        <td><?php echo number_format($t->balance_after, 2); ?></td>
                                        <td><?php echo esc_html($t->description); ?></td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>
                    <a href="<?php echo admin_url('admin.php?page=wp-points&user_id=' . $user->ID); ?>" class="view-all-link">
                        View all transactions →
                    </a>
                <?php else: ?>
                    <div class="points-table">
                        <div class="no-transactions">
                            No transactions found for this user yet.
                        </div>
                    </div>
                <?php endif; ?>
            </div>
        </div>
        <?php
    }
    
    public function save_user_points_field($user_id) {
        if (!current_user_can('edit_users')) {
            return;
        }
        
        if (isset($_POST['points_balance'])) {
            $new_balance = floatval($_POST['points_balance']);
            $current_balance = $this->get_balance($user_id);
            $difference = $new_balance - $current_balance;
            
            if ($difference != 0) {
                $this->update_points(
                    $user_id, 
                    $difference, 
                    'Manual adjustment by ' . wp_get_current_user()->display_name,
                    'admin_adjustment'
                );
            }
        }
    }
    
    // Points Earning Hooks
    
    public function points_on_register($user_id) {
        $hooks = get_option($this->hooks_option, array());
        if (!empty($hooks['register'])) {
            $points = isset($hooks['register_points']) ? intval($hooks['register_points']) : 100;
            $this->add_points($user_id, $points, 'Welcome bonus', 'registration');
        }
    }
    
    public function points_on_login($user_login, $user) {
        $hooks = get_option($this->hooks_option, array());
        if (!empty($hooks['daily_login'])) {
            // Check if user already got points today
            $last_login_bonus = get_user_meta($user->ID, 'last_login_bonus_date', true);
            $today = date('Y-m-d');
            
            if ($last_login_bonus !== $today) {
                $points = isset($hooks['daily_login_points']) ? intval($hooks['daily_login_points']) : 10;
                $this->add_points($user->ID, $points, 'Daily login bonus', 'daily_login');
                update_user_meta($user->ID, 'last_login_bonus_date', $today);
            }
        }
    }
    
    // Shortcode
    
    public function points_shortcode($atts) {
        $atts = shortcode_atts(array(
            'user_id' => get_current_user_id()
        ), $atts);
        
        if (!$atts['user_id']) {
            return '';
        }
        
        $balance = $this->get_balance($atts['user_id']);
        return '<span class="user-points-balance">' . number_format($balance, 2) . '</span>';
    }
    
    // Helper Functions
    
    private function handle_bulk_update() {
        $user_ids = sanitize_text_field($_POST['user_ids']);
        $operation = sanitize_text_field($_POST['operation']);
        $amount = floatval($_POST['amount']);
        $description = sanitize_text_field($_POST['description']);
        
        if ($user_ids === 'all') {
            $users = get_users(array('fields' => 'ID'));
        } else {
            $users = array_map('intval', explode(',', $user_ids));
        }
        
        $success = 0;
        foreach ($users as $user_id) {
            if ($operation === 'add') {
                $result = $this->add_points($user_id, $amount, $description, 'bulk_update');
            } elseif ($operation === 'subtract') {
                $result = $this->subtract_points($user_id, $amount, $description, 'bulk_update');
            } elseif ($operation === 'set') {
                $current = $this->get_balance($user_id);
                $diff = $amount - $current;
                $result = $this->update_points($user_id, $diff, $description, 'bulk_update');
            }
            
            if (!is_wp_error($result)) {
                $success++;
            }
        }
        
        echo '<div class="notice notice-success is-dismissible" style="margin: 20px 20px 0; padding: 12px 20px; border-left: 4px solid #00a32a; background: #fff;">
                <p style="margin: 0; font-size: 14px;">
                    <strong>✅ Success!</strong> Updated points for ' . $success . ' user' . ($success !== 1 ? 's' : '') . '.
                </p>
              </div>';
    }
    
    // AJAX Handler
    public function ajax_update_user_points() {
        check_ajax_referer('wp_points_nonce', 'nonce');
        
        if (!current_user_can('edit_users')) {
            wp_die('Unauthorized');
        }
        
        $user_id = intval($_POST['user_id']);
        $amount = floatval($_POST['amount']);
        $description = sanitize_text_field($_POST['description']);
        $operation = sanitize_text_field($_POST['operation']);
        
        if ($operation === 'add') {
            $result = $this->add_points($user_id, $amount, $description);
        } else {
            $result = $this->subtract_points($user_id, $amount, $description);
        }
        
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
        } else {
            wp_send_json_success(array('new_balance' => $result));
        }
    }
}

// Initialize the points system
function wp_points() {
    return WP_Simple_Points::get_instance();
}

// Initialize on plugins_loaded
add_action('plugins_loaded', 'wp_points');

// Global helper functions for easy access
function get_user_points($user_id) {
    return wp_points()->get_balance($user_id);
}

function add_user_points($user_id, $amount, $description = '') {
    return wp_points()->add_points($user_id, $amount, $description);
}

function subtract_user_points($user_id, $amount, $description = '') {
    return wp_points()->subtract_points($user_id, $amount, $description);
}

// Custom hooks for developers
add_action('wp_points_after_registration', function($user_id) {
    // Example: Add extra points based on referral
    if (isset($_COOKIE['referral_code'])) {
        add_user_points($user_id, 50, 'Referral bonus');
    }
}, 10, 1);

// Example usage in your code:
/*
// Get balance
$balance = get_user_points($user_id);

// Add points
add_user_points($user_id, 100, 'Purchased premium plan');

// Subtract points
subtract_user_points($user_id, 50, 'Used feature X');

// Check if user has enough points
if (get_user_points($user_id) >= 100) {
    // Allow action
    subtract_user_points($user_id, 100, 'Unlocked feature');
}
*/
×
WP simple points system 简单的积分系统

WP simple points system 简单的积分系统

<?php /** * Simpl...