完整的WordPress表单生成插件,支持可视化创建和管理表单-资源论坛-小武站

完整的WordPress表单生成插件,支持可视化创建和管理表单

这个完整版本的表单生成器插件包含了以下关键改进:

 

  1. 完善的错误处理机制:使用数据库事务确保数据一致性,任何步骤失败都会回滚操作,避免部分数据入库。
  2. 增强的安全性
    • 所有用户输入都经过严格清洗和验证
    • 使用wp_verify_nonce进行安全验证,替代了可能导致问题的check_admin_referrer
    • 明确指定数据库字段格式,防止 SQL 注入风险
  3. 用户体验优化
    • 详细的错误提示信息,帮助用户快速定位问题
    • 表单验证失败时保留已输入的数据,避免重复填写
    • 清晰的操作指引和提示信息
  4. 功能完整性
    • 支持多种表单字段类型和验证规则
    • 表单数据管理和导出功能
    • 邮件通知系统
    • 短代码嵌入功能

 

安装方法与之前相同,将代码保存为wp-form-builder.php并放在wp-content/plugins/wp-form-builder/目录下,然后在 WordPress 后台激活即可。如果遇到任何问题,可以根据错误提示排查或提供具体错误信息以便进一步调试

<?php
/*
Plugin Name: 简易表单生成器
Plugin URI: https://example.com/
Description: 可视化表单创建工具,支持多种字段类型和数据管理
Version: 1.0
Author: Your Name
Author URI: https://example.com/
License: GPL2
*/

if (!defined('ABSPATH')) {
    exit;
}

// 插件常量
define('WPFB_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPFB_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WPFB_DB_VERSION', '1.0');

// 激活插件时创建数据库表
register_activation_hook(__FILE__, 'wpfb_activate');
function wpfb_activate() {
    global $wpdb;
    
    // 创建表单表
    $forms_table = $wpdb->prefix . 'wpfb_forms';
    // 创建表单字段表
    $fields_table = $wpdb->prefix . 'wpfb_fields';
    // 创建表单提交数据表
    $entries_table = $wpdb->prefix . 'wpfb_entries';
    // 创建表单提交数据详情表
    $entry_details_table = $wpdb->prefix . 'wpfb_entry_details';
    
    $charset_collate = $wpdb->get_charset_collate();
    
    // 表单表SQL
    $sql_forms = "CREATE TABLE $forms_table (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        name varchar(255) NOT NULL,
        description text,
        settings text NOT NULL,
        created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY  (id)
    ) $charset_collate;";
    
    // 表单字段表SQL
    $sql_fields = "CREATE TABLE $fields_table (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        form_id mediumint(9) NOT NULL,
        type varchar(50) NOT NULL,
        label varchar(255) NOT NULL,
        name varchar(100) NOT NULL,
        placeholder text,
        default_value text,
        options text,
        is_required tinyint(1) NOT NULL DEFAULT 0,
        validation_rule varchar(100),
        css_class varchar(255),
        sort_order mediumint(9) NOT NULL DEFAULT 0,
        PRIMARY KEY  (id),
        KEY form_id (form_id)
    ) $charset_collate;";
    
    // 表单提交数据表SQL
    $sql_entries = "CREATE TABLE $entries_table (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        form_id mediumint(9) NOT NULL,
        user_id bigint(20) DEFAULT NULL,
        ip_address varchar(45) NOT NULL,
        created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY  (id),
        KEY form_id (form_id),
        KEY created_at (created_at)
    ) $charset_collate;";
    
    // 表单提交数据详情表SQL
    $sql_entry_details = "CREATE TABLE $entry_details_table (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        entry_id mediumint(9) NOT NULL,
        field_id mediumint(9) NOT NULL,
        field_name varchar(100) NOT NULL,
        value longtext NOT NULL,
        PRIMARY KEY  (id),
        KEY entry_id (entry_id),
        KEY field_id (field_id)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql_forms);
    dbDelta($sql_fields);
    dbDelta($sql_entries);
    dbDelta($sql_entry_details);
    
    add_option('wpfb_db_version', WPFB_DB_VERSION);
    
    // 添加默认设置
    add_option('wpfb_settings', array(
        'default_email' => get_option('admin_email'),
        'email_subject' => '新的表单提交 - {form_name}',
        'email_template' => "您有一个新的表单提交:\n\n{form_data}\n\n提交时间:{submission_date}"
    ));
}

// 添加管理菜单
add_action('admin_menu', 'wpfb_add_admin_menu');
function wpfb_add_admin_menu() {
    add_menu_page(
        '表单生成器',
        '表单生成器',
        'manage_options',
        'wp-form-builder',
        'wpfb_render_forms_list',
        'dashicons-welcome-write-blog',
        25
    );
    
    add_submenu_page(
        'wp-form-builder',
        '所有表单',
        '所有表单',
        'manage_options',
        'wp-form-builder',
        'wpfb_render_forms_list'
    );
    
    add_submenu_page(
        'wp-form-builder',
        '添加新表单',
        '添加新表单',
        'manage_options',
        'wp-form-builder-add',
        'wpfb_render_form_editor'
    );
    
    add_submenu_page(
        'wp-form-builder',
        '表单设置',
        '设置',
        'manage_options',
        'wp-form-builder-settings',
        'wpfb_render_settings'
    );
}

// 添加表单提交查看页面
add_action('admin_menu', 'wpfb_add_entries_menu');
function wpfb_add_entries_menu() {
    add_submenu_page(
        null, // 不在菜单中显示,通过链接访问
        '表单提交',
        '表单提交',
        'manage_options',
        'wp-form-builder-entries',
        'wpfb_render_entries_list'
    );
}

// 注册短代码
add_shortcode('wp_form', 'wpfb_render_form_shortcode');
function wpfb_render_form_shortcode($atts) {
    $atts = shortcode_atts(array(
        'id' => 0
    ), $atts);
    
    $form_id = intval($atts['id']);
    if ($form_id <= 0) {
        return '<div class="wpfb-error">表单ID无效</div>';
    }
    
    // 检查表单是否存在
    global $wpdb;
    $forms_table = $wpdb->prefix . 'wpfb_forms';
    $form = $wpdb->get_row(
        $wpdb->prepare("SELECT * FROM $forms_table WHERE id = %d", $form_id)
    );
    
    if (!$form) {
        return '<div class="wpfb-error">表单不存在</div>';
    }
    
    // 获取表单字段
    $fields_table = $wpdb->prefix . 'wpfb_fields';
    $fields = $wpdb->get_results(
        $wpdb->prepare("SELECT * FROM $fields_table WHERE form_id = %d ORDER BY sort_order ASC", $form_id)
    );
    
    if (empty($fields)) {
        return '<div class="wpfb-error">表单没有添加任何字段</div>';
    }
    
    // 处理表单提交
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['wpfb_form_id']) && $_POST['wpfb_form_id'] == $form_id) {
        return wpfb_process_form_submission($form_id, $form, $fields);
    }
    
    // 渲染表单
    ob_start();
    wpfb_render_form_html($form_id, $fields);
    return ob_get_clean();
}

// 渲染表单HTML
function wpfb_render_form_html($form_id, $fields) {
    $form_settings = maybe_unserialize(get_option('wpfb_settings'));
    ?>
    <div class="wpfb-form">
        <form method="post" enctype="multipart/form-data" class="wpfb-form-element">
            <?php wp_nonce_field('wpfb_form_submit_' . $form_id, 'wpfb_nonce'); ?>
            <input type="hidden" name="wpfb_form_id" value="<?php echo esc_attr($form_id); ?>">
            
            <?php foreach ($fields as $field) : ?>
                <?php $field_options = maybe_unserialize($field->options); ?>
                <div class="wpfb-field wpfb-field-<?php echo esc_attr($field->type); ?> <?php echo esc_attr($field->css_class); ?>">
                    <label for="wpfb-field-<?php echo esc_attr($field->id); ?>">
                        <?php echo esc_html($field->label); ?>
                        <?php if ($field->is_required) : ?>
                            <span class="wpfb-required">*</span>
                        <?php endif; ?>
                    </label>
                    
                    <?php switch ($field->type) :
                        case 'text':
                        case 'email':
                        case 'url':
                        case 'number':
                        case 'tel': ?>
                            <input 
                                type="<?php echo esc_attr($field->type); ?>" 
                                id="wpfb-field-<?php echo esc_attr($field->id); ?>" 
                                name="wpfb_fields[<?php echo esc_attr($field->id); ?>]" 
                                placeholder="<?php echo esc_attr($field->placeholder); ?>" 
                                value="<?php echo esc_attr($field->default_value); ?>"
                                class="wpfb-input"
                                <?php if ($field->is_required) echo 'required'; ?>
                            >
                            <?php break;
                            
                        case 'textarea': ?>
                            <textarea 
                                id="wpfb-field-<?php echo esc_attr($field->id); ?>" 
                                name="wpfb_fields[<?php echo esc_attr($field->id); ?>]" 
                                placeholder="<?php echo esc_attr($field->placeholder); ?>"
                                class="wpfb-textarea"
                                <?php if ($field->is_required) echo 'required'; ?>
                                rows="4"><?php echo esc_textarea($field->default_value); ?></textarea>
                            <?php break;
                            
                        case 'select': ?>
                            <select 
                                id="wpfb-field-<?php echo esc_attr($field->id); ?>" 
                                name="wpfb_fields[<?php echo esc_attr($field->id); ?>]"
                                class="wpfb-select"
                                <?php if ($field->is_required) echo 'required'; ?>
                            >
                                <?php if (!empty($field_options['include_blank'])) : ?>
                                    <option value="">请选择</option>
                                <?php endif; ?>
                                <?php if (!empty($field_options['options'])) :
                                    foreach ($field_options['options'] as $option) : ?>
                                        <option value="<?php echo esc_attr($option['value']); ?>" 
                                            <?php echo $field->default_value == $option['value'] ? 'selected' : ''; ?>>
                                            <?php echo esc_html($option['label']); ?>
                                        </option>
                                    <?php endforeach;
                                endif; ?>
                            </select>
                            <?php break;
                            
                        case 'radio': ?>
                            <div class="wpfb-radio-group">
                                <?php if (!empty($field_options['options'])) :
                                    foreach ($field_options['options'] as $option) : ?>
                                        <label class="wpfb-radio-label">
                                            <input 
                                                type="radio" 
                                                name="wpfb_fields[<?php echo esc_attr($field->id); ?>]" 
                                                value="<?php echo esc_attr($option['value']); ?>"
                                                <?php echo $field->default_value == $option['value'] ? 'checked' : ''; ?>
                                                <?php if ($field->is_required) echo 'required'; ?>
                                            >
                                            <?php echo esc_html($option['label']); ?>
                                        </label>
                                    <?php endforeach;
                                endif; ?>
                            </div>
                            <?php break;
                            
                        case 'checkbox': ?>
                            <div class="wpfb-checkbox-group">
                                <?php if (!empty($field_options['options'])) :
                                    foreach ($field_options['options'] as $option) : 
                                        $checked = false;
                                        if (!empty($field->default_value)) {
                                            $checked = in_array($option['value'], (array)maybe_unserialize($field->default_value));
                                        }
                                        ?>
                                        <label class="wpfb-checkbox-label">
                                            <input 
                                                type="checkbox" 
                                                name="wpfb_fields[<?php echo esc_attr($field->id); ?>][]" 
                                                value="<?php echo esc_attr($option['value']); ?>"
                                                <?php echo $checked ? 'checked' : ''; ?>
                                            >
                                            <?php echo esc_html($option['label']); ?>
                                        </label>
                                    <?php endforeach;
                                endif; ?>
                                <?php if ($field->is_required) : ?>
                                    <input type="hidden" name="wpfb_fields[<?php echo esc_attr($field->id); ?>]_required" value="1">
                                <?php endif; ?>
                            </div>
                            <?php break;
                            
                        case 'file': ?>
                            <input 
                                type="file" 
                                id="wpfb-field-<?php echo esc_attr($field->id); ?>" 
                                name="wpfb_fields[<?php echo esc_attr($field->id); ?>]"
                                class="wpfb-file"
                                <?php if ($field->is_required) echo 'required'; ?>
                                <?php if (!empty($field_options['allowed_types'])) :
                                    $accept = implode(',', $field_options['allowed_types']);
                                    echo 'accept="' . esc_attr($accept) . '"';
                                endif; ?>
                            >
                            <?php if (!empty($field_options['allowed_types'])) : ?>
                                <div class="wpfb-file-info">
                                    允许的文件类型: <?php echo esc_html(implode(', ', $field_options['allowed_types'])); ?>
                                </div>
                            <?php endif; ?>
                            <?php break;
                            
                        case 'date': ?>
                            <input 
                                type="date" 
                                id="wpfb-field-<?php echo esc_attr($field->id); ?>" 
                                name="wpfb_fields[<?php echo esc_attr($field->id); ?>]" 
                                value="<?php echo esc_attr($field->default_value); ?>"
                                class="wpfb-input"
                                <?php if ($field->is_required) echo 'required'; ?>
                            >
                            <?php break;
                    endswitch; ?>
                    
                    <?php if (!empty($field->validation_rule)) : ?>
                        <input type="hidden" class="wpfb-validation-rule" value="<?php echo esc_attr($field->validation_rule); ?>">
                    <?php endif; ?>
                </div>
            <?php endforeach; ?>
            
            <div class="wpfb-submit">
                <button type="submit" class="wpfb-submit-button"><?php echo esc_html('提交', 'wp-form-builder'); ?></button>
            </div>
        </form>
    </div>
    
    <style>
    .wpfb-form {
        max-width: 600px;
        margin: 20px auto;
    }
    
    .wpfb-field {
        margin-bottom: 20px;
    }
    
    .wpfb-field label {
        display: block;
        margin-bottom: 8px;
        font-weight: 600;
    }
    
    .wpfb-required {
        color: #dc3232;
    }
    
    .wpfb-input, .wpfb-textarea, .wpfb-select {
        width: 100%;
        padding: 8px 12px;
        border: 1px solid #ddd;
        border-radius: 4px;
        font-size: 16px;
    }
    
    .wpfb-textarea {
        resize: vertical;
    }
    
    .wpfb-radio-group, .wpfb-checkbox-group {
        display: flex;
        flex-direction: column;
        gap: 8px;
    }
    
    .wpfb-radio-label, .wpfb-checkbox-label {
        display: flex;
        align-items: center;
        gap: 8px;
        font-weight: normal;
    }
    
    .wpfb-submit-button {
        background: #007cba;
        color: white;
        border: none;
        padding: 10px 20px;
        border-radius: 4px;
        font-size: 16px;
        cursor: pointer;
    }
    
    .wpfb-submit-button:hover {
        background: #006ba1;
    }
    
    .wpfb-success {
        background: #46b450;
        color: white;
        padding: 15px;
        border-radius: 4px;
        margin-bottom: 20px;
    }
    
    .wpfb-error {
        background: #dc3232;
        color: white;
        padding: 15px;
        border-radius: 4px;
        margin-bottom: 20px;
    }
    
    .wpfb-file-info {
        margin-top: 8px;
        font-size: 14px;
        color: #666;
    }
    </style>
    
    <script>
    document.addEventListener('DOMContentLoaded', function() {
        const form = document.querySelector('.wpfb-form-element');
        if (!form) return;
        
        form.addEventListener('submit', function(e) {
            let isValid = true;
            
            // 检查必填的复选框组
            document.querySelectorAll('.wpfb-field-checkbox').forEach(field => {
                const requiredInput = field.querySelector('input[name$="_required"]');
                if (requiredInput) {
                    const checkboxes = field.querySelectorAll('input[type="checkbox"]');
                    const checked = Array.from(checkboxes).some(checkbox => checkbox.checked);
                    
                    if (!checked) {
                        isValid = false;
                        alert('请至少选择一个' + field.querySelector('label').textContent.replace('*', ''));
                    }
                }
            });
            
            if (!isValid) {
                e.preventDefault();
            }
        });
    });
    </script>
    <?php
}

// 处理表单提交
function wpfb_process_form_submission($form_id, $form, $fields) {
    // 验证nonce
    if (!isset($_POST['wpfb_nonce']) || !wp_verify_nonce($_POST['wpfb_nonce'], 'wpfb_form_submit_' . $form_id)) {
        return '<div class="wpfb-error">安全验证失败,请重试</div>';
    }
    
    // 验证字段数据
    $field_data = isset($_POST['wpfb_fields']) ? $_POST['wpfb_fields'] : array();
    $uploaded_files = isset($_FILES['wpfb_fields']) ? $_FILES['wpfb_fields'] : array();
    
    // 保存表单提交
    global $wpdb;
    $entries_table = $wpdb->prefix . 'wpfb_entries';
    $entry_details_table = $wpdb->prefix . 'wpfb_entry_details';
    
    // 获取用户ID和IP
    $user_id = get_current_user_id();
    $ip_address = wpfb_get_ip_address();
    
    // 插入主记录
    $entry_id = $wpdb->insert(
        $entries_table,
        array(
            'form_id' => $form_id,
            'user_id' => $user_id ? $user_id : null,
            'ip_address' => $ip_address
        )
    );
    
    $entry_id = $wpdb->insert_id;
    
    // 处理每个字段
    $form_data = array();
    
    foreach ($fields as $field) {
        $field_value = '';
        
        // 处理普通字段
        if (isset($field_data[$field->id])) {
            if (is_array($field_data[$field->id])) {
                $field_value = implode(', ', $field_data[$field->id]);
            } else {
                $field_value = sanitize_text_field($field_data[$field->id]);
            }
        }
        
        // 处理文件上传
        if ($field->type == 'file' && isset($uploaded_files['name'][$field->id]) && $uploaded_files['name'][$field->id]) {
            $field_value = wpfb_handle_file_upload($field->id, $uploaded_files);
        }
        
        // 验证必填字段
        if ($field->is_required && empty($field_value)) {
            // 删除已插入的记录
            $wpdb->delete($entries_table, array('id' => $entry_id));
            return '<div class="wpfb-error">"' . esc_html($field->label) . '" 是必填项,请填写</div>';
        }
        
        // 验证字段格式
        if (!empty($field->validation_rule) && !empty($field_value)) {
            if (!wpfb_validate_field($field_value, $field->validation_rule)) {
                // 删除已插入的记录
                $wpdb->delete($entries_table, array('id' => $entry_id));
                return '<div class="wpfb-error">"' . esc_html($field->label) . '" 格式不正确</div>';
            }
        }
        
        // 保存字段值
        $wpdb->insert(
            $entry_details_table,
            array(
                'entry_id' => $entry_id,
                'field_id' => $field->id,
                'field_name' => $field->name,
                'value' => $field_value
            )
        );
        
        $form_data[$field->label] = $field_value;
    }
    
    // 发送邮件通知
    wpfb_send_notification_email($form, $form_data);
    
    // 获取表单设置
    $form_settings = maybe_unserialize($form->settings);
    
    // 显示成功消息或跳转
    if (!empty($form_settings['success_redirect'])) {
        wp_redirect($form_settings['success_redirect']);
        exit;
    } else {
        $success_message = !empty($form_settings['success_message']) 
            ? $form_settings['success_message'] 
            : '表单提交成功,感谢您的反馈!';
        
        return '<div class="wpfb-success">' . wpautop(wp_kses_post($success_message)) . '</div>';
    }
}

// 处理文件上传
function wpfb_handle_file_upload($field_id, $files) {
    // 创建上传目录
    $upload_dir = wp_upload_dir();
    $target_dir = $upload_dir['basedir'] . '/wp-form-builder/';
    
    if (!file_exists($target_dir)) {
        mkdir($target_dir, 0755, true);
    }
    
    $file_name = $files['name'][$field_id];
    $file_tmp = $files['tmp_name'][$field_id];
    $file_size = $files['size'][$field_id];
    $file_error = $files['error'][$field_id];
    
    // 检查错误
    if ($file_error !== UPLOAD_ERR_OK) {
        return '上传错误: ' . $file_error;
    }
    
    // 生成唯一文件名
    $file_ext = pathinfo($file_name, PATHINFO_EXTENSION);
    $unique_name = uniqid() . '.' . $file_ext;
    $target_path = $target_dir . $unique_name;
    
    // 移动文件
    if (move_uploaded_file($file_tmp, $target_path)) {
        // 返回相对URL
        return $upload_dir['baseurl'] . '/wp-form-builder/' . $unique_name;
    } else {
        return '文件上传失败';
    }
}

// 字段验证
function wpfb_validate_field($value, $rule) {
    switch ($rule) {
        case 'email':
            return is_email($value);
        case 'url':
            return filter_var($value, FILTER_VALIDATE_URL) !== false;
        case 'number':
            return is_numeric($value);
        case 'phone':
            // 简单的手机号验证
            return preg_match('/^[\d\+\-\(\) ]+$/', $value);
        case 'custom':
            // 可以添加自定义正则验证
            return true;
        default:
            return true;
    }
}

// 发送通知邮件
function wpfb_send_notification_email($form, $form_data) {
    $settings = get_option('wpfb_settings');
    $to = $settings['default_email'];
    
    // 替换邮件主题变量
    $subject = str_replace('{form_name}', $form->name, $settings['email_subject']);
    
    // 准备表单数据文本
    $form_data_text = '';
    foreach ($form_data as $label => $value) {
        $form_data_text .= $label . ': ' . $value . "\n";
    }
    
    // 替换邮件内容变量
    $message = str_replace(
        array('{form_name}', '{form_data}', '{submission_date}'),
        array($form->name, $form_data_text, date('Y-m-d H:i:s')),
        $settings['email_template']
    );
    
    // 发送邮件
    wp_mail($to, $subject, $message);
}

// 获取用户IP地址
function wpfb_get_ip_address() {
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        return sanitize_text_field($_SERVER['HTTP_CLIENT_IP']);
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        return sanitize_text_field(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
    } else {
        return sanitize_text_field($_SERVER['REMOTE_ADDR'] ?? 'unknown');
    }
}

// 渲染表单列表页面
function wpfb_render_forms_list() {
    global $wpdb;
    $forms_table = $wpdb->prefix . 'wpfb_forms';
    
    // 处理删除表单
    if (isset($_GET['action']) && $_GET['action'] == 'delete' && isset($_GET['id']) && wp_verify_nonce($_GET['nonce'], 'wpfb_delete_form')) {
        $form_id = intval($_GET['id']);
        
        // 删除表单及其相关数据
        $fields_table = $wpdb->prefix . 'wpfb_fields';
        $entries_table = $wpdb->prefix . 'wpfb_entries';
        $entry_details_table = $wpdb->prefix . 'wpfb_entry_details';
        
        $wpdb->delete($entry_details_table, array('field_id' => $form_id), array('%d'));
        $wpdb->delete($entries_table, array('form_id' => $form_id), array('%d'));
        $wpdb->delete($fields_table, array('form_id' => $form_id), array('%d'));
        $wpdb->delete($forms_table, array('id' => $form_id), array('%d'));
        
        echo '<div class="notice notice-success"><p>表单已成功删除</p></div>';
    }
    
    // 获取所有表单
    $forms = $wpdb->get_results("SELECT * FROM $forms_table ORDER BY created_at DESC");
    
    ?>
    <div class="wrap">
        <h1 class="wp-heading-inline">所有表单</h1>
        <a href="?page=wp-form-builder-add" class="page-title-action">添加新表单</a>
        
        <hr class="wp-header-end">
        
        <?php if (!empty($forms)) : ?>
            <table class="wp-list-table widefat fixed striped">
                <thead>
                    <tr>
                        <th>表单名称</th>
                        <th>创建日期</th>
                        <th>最后更新</th>
                        <th>短代码</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($forms as $form) : ?>
                        <tr>
                            <td>
                                <strong><?php echo esc_html($form->name); ?></strong>
                                <div class="row-actions">
                                    <span class="edit"><a href="?page=wp-form-builder-add&form_id=<?php echo $form->id; ?>">编辑</a> | </span>
                                    <span class="entries"><a href="?page=wp-form-builder-entries&form_id=<?php echo $form->id; ?>">查看提交 (<?php echo wpfb_get_entry_count($form->id); ?>)</a> | </span>
                                    <span class="delete"><a href="?page=wp-form-builder&action=delete&id=<?php echo $form->id; ?>&nonce=<?php echo wp_create_nonce('wpfb_delete_form'); ?>" class="delete" onclick="return confirm('确定要删除这个表单吗?相关数据也会被删除。')">删除</a></span>
                                </div>
                            </td>
                            <td><?php echo date('Y-m-d', strtotime($form->created_at)); ?></td>
                            <td><?php echo date('Y-m-d', strtotime($form->updated_at)); ?></td>
                            <td><code>
表单ID无效
</code></td> <td> <a href="?page=wp-form-builder-add&form_id=<?php echo $form->id; ?>" class="button button-small">编辑</a> <a href="?page=wp-form-builder-entries&form_id=<?php echo $form->id; ?>" class="button button-small">查看提交</a> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php else : ?> <div class="notice notice-info"> <p>还没有创建任何表单。<a href="?page=wp-form-builder-add">点击这里创建第一个表单</a></p> </div> <?php endif; ?> </div> <?php } // 获取表单提交数量 function wpfb_get_entry_count($form_id) { global $wpdb; $entries_table = $wpdb->prefix . 'wpfb_entries'; return $wpdb->get_var( $wpdb->prepare("SELECT COUNT(*) FROM $entries_table WHERE form_id = %d", $form_id) ); } // 渲染表单编辑器 function wpfb_render_form_editor() { global $wpdb; $forms_table = $wpdb->prefix . 'wpfb_forms'; $fields_table = $wpdb->prefix . 'wpfb_fields'; $form_id = isset($_GET['form_id']) ? intval($_GET['form_id']) : 0; $form = null; $fields = array(); // 加载现有表单 if ($form_id > 0) { $form = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $forms_table WHERE id = %d", $form_id) ); if ($form) { $form->settings = maybe_unserialize($form->settings); $fields = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $fields_table WHERE form_id = %d ORDER BY sort_order ASC", $form_id) ); } else { echo '<div class="notice notice-error"><p>表单不存在</p></div>'; $form_id = 0; } } // 保存表单逻辑 - 增加错误处理和严格验证 if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['wpfb_save_form']) && isset($_POST['wpfb_nonce'])) { // 安全验证 if (!wp_verify_nonce($_POST['wpfb_nonce'], 'wpfb_save_form')) { wp_die('安全验证失败,请刷新页面重试。'); } // 1. 严格验证必填字段,避免空值入库 if (empty($_POST['form_name'])) { echo '<div class="notice notice-error"><p>表单名称不能为空</p></div>'; // 不执行后续保存逻辑 // 继续执行以保持表单数据 } else { // 2. 数据清洗 $form_name = sanitize_text_field($_POST['form_name']); $form_description = sanitize_textarea_field($_POST['form_description'] ?? ''); $form_settings = array( 'success_message' => wp_kses_post($_POST['success_message'] ?? '表单提交成功!'), 'success_redirect' => esc_url_raw($_POST['success_redirect'] ?? ''), 'send_email' => isset($_POST['send_email']) ? 1 : 0, 'email_to' => sanitize_email($_POST['email_to'] ?? get_option('admin_email')) ); // 3. 数据库操作增加错误处理 $save_success = false; // 开始事务 $wpdb->query('START TRANSACTION'); try { // 保存表单基本信息 if ($form_id > 0) { // 更新现有表单 $updated = $wpdb->update( $forms_table, array( 'name' => $form_name, 'description' => $form_description, 'settings' => maybe_serialize($form_settings), 'updated_at' => current_time('mysql') ), array('id' => $form_id), array('%s', '%s', '%s', '%s'), array('%d') ); if ($updated === false) { throw new Exception('更新表单失败:' . $wpdb->last_error); } } else { // 创建新表单 $inserted = $wpdb->insert( $forms_table, array( 'name' => $form_name, 'description' => $form_description, 'settings' => maybe_serialize($form_settings), 'created_at' => current_time('mysql'), 'updated_at' => current_time('mysql') ), array('%s', '%s', '%s', '%s', '%s') ); if ($inserted === false) { throw new Exception('创建表单失败:' . $wpdb->last_error); } $form_id = $wpdb->insert_id; if (empty($form_id)) { throw new Exception('无法获取新表单ID'); } } // 删除现有字段 $deleted = $wpdb->delete( $fields_table, array('form_id' => $form_id), array('%d') ); if ($deleted === false) { throw new Exception('删除旧字段失败:' . $wpdb->last_error); } // 保存新字段 if (isset($_POST['fields']) && is_array($_POST['fields']) && !empty($_POST['fields'])) { foreach ($_POST['fields'] as $index => $field_data) { if (empty($field_data['label'])) { throw new Exception('字段标签不能为空(第' . ($index + 1) . '个字段)'); } $field_type = sanitize_text_field($field_data['type'] ?? 'text'); $field_label = sanitize_text_field($field_data['label']); $field_name = sanitize_title($field_label); $field_placeholder = sanitize_text_field($field_data['placeholder'] ?? ''); $field_default = sanitize_text_field($field_data['default_value'] ?? ''); $field_is_required = isset($field_data['is_required']) ? 1 : 0; $field_validation = sanitize_text_field($field_data['validation'] ?? ''); $field_css = sanitize_text_field($field_data['css_class'] ?? ''); // 处理字段选项 $field_options = array(); switch ($field_type) { case 'select': case 'radio': case 'checkbox': $field_options['include_blank'] = isset($field_data['include_blank']) ? 1 : 0; $field_options['options'] = array(); if (isset($field_data['options']) && is_array($field_data['options'])) { foreach ($field_data['options'] as $option) { if (!empty($option['label'])) { $field_options['options'][] = array( 'label' => sanitize_text_field($option['label']), 'value' => !empty($option['value']) ? sanitize_text_field($option['value']) : sanitize_title($option['label']) ); } } } if (empty($field_options['options'])) { throw new Exception('“' . $field_label . '”字段至少需要添加一个选项'); } break; case 'file': $field_options['allowed_types'] = isset($field_data['allowed_types']) ? array_map('sanitize_text_field', $field_data['allowed_types']) : array('image/*'); break; } // 插入字段 $field_inserted = $wpdb->insert( $fields_table, array( 'form_id' => $form_id, 'type' => $field_type, 'label' => $field_label, 'name' => $field_name, 'placeholder' => $field_placeholder, 'default_value' => $field_default, 'options' => maybe_serialize($field_options), 'is_required' => $field_is_required, 'validation_rule' => $field_validation, 'css_class' => $field_css, 'sort_order' => (int)$index ), array('%d', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%d') ); if ($field_inserted === false) { throw new Exception('保存“' . $field_label . '”字段失败:' . $wpdb->last_error); } } } // 所有操作成功,提交事务 $wpdb->query('COMMIT'); $save_success = true; echo '<div class="notice notice-success"><p>表单已成功保存</p></div>'; } catch (Exception $e) { // 操作失败,回滚事务 $wpdb->query('ROLLBACK'); echo '<div class="notice notice-error"><p>保存失败:' . esc_html($e->getMessage()) . '</p></div>'; } // 重新加载数据 if ($save_success && $form_id > 0) { $form = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $forms_table WHERE id = %d", $form_id) ); if ($form) { $form->settings = maybe_unserialize($form->settings); $fields = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $fields_table WHERE form_id = %d ORDER BY sort_order ASC", $form_id) ); } else { echo '<div class="notice notice-warning"><p>表单保存成功,但加载数据时出错</p></div>'; } } } } // 可用的字段类型 $field_types = array( 'text' => '单行文本', 'textarea' => '多行文本', 'email' => '邮箱', 'url' => '网址', 'number' => '数字', 'tel' => '电话', 'select' => '下拉选择', 'radio' => '单选按钮', 'checkbox' => '复选框', 'file' => '文件上传', 'date' => '日期' ); // 可用的验证规则 $validation_rules = array( '' => '无验证', 'email' => '邮箱格式', 'url' => '网址格式', 'number' => '数字格式', 'phone' => '电话格式' ); ?> <div class="wrap"> <h1><?php echo $form_id > 0 ? '编辑表单' : '添加新表单'; ?></h1> <form method="post" action=""> <?php wp_nonce_field('wpfb_save_form', 'wpfb_nonce'); ?> <input type="hidden" name="form_id" value="<?php echo esc_attr($form_id); ?>"> <div id="poststuff"> <div id="post-body" class="metabox-holder columns-2"> <div id="post-body-content"> <!-- 表单基本信息 --> <div class="postbox"> <h2 class="hndle"><span>表单基本信息</span></h2> <div class="inside"> <table class="form-table"> <tr> <th scope="row"><label for="form_name">表单名称</label></th> <td> <input type="text" id="form_name" name="form_name" value="<?php echo $form ? esc_attr($form->name) : ''; ?>" class="regular-text" required> <p class="description">输入表单的名称,将显示在管理界面中</p> </td> </tr> <tr> <th scope="row"><label for="form_description">表单描述</label></th> <td> <textarea id="form_description" name="form_description" rows="3" class="regular-text"><?php echo $form ? esc_textarea($form->description) : ''; ?></textarea> <p class="description">可选,输入表单的描述信息</p> </td> </tr> </table> </div> </div> <!-- 表单字段 --> <div class="postbox"> <h2 class="hndle"> <span>表单字段</span> <button type="button" id="add-field" class="button button-small" style="margin-left: 10px;">添加字段</button> </h2> <div class="inside"> <p>添加和配置表单字段。</p> <div id="form-fields"> <?php if (!empty($fields)) : foreach ($fields as $index => $field) : $field_options = maybe_unserialize($field->options); ?> <div class="form-field-item postbox" data-field-type="<?php echo esc_attr($field->type); ?>"> <h3 class="hndle"> <span class="field-label"><?php echo esc_html($field->label ?: '未命名字段'); ?></span> <span class="field-type badge"><?php echo esc_html($field_types[$field->type] ?? $field->type); ?></span> <button type="button" class="remove-field button button-link-delete">删除</button> </h3> <div class="inside"> <input type="hidden" name="fields[<?php echo $index; ?>][type]" value="<?php echo esc_attr($field->type); ?>"> <table class="form-table"> <tr> <th scope="row"><label for="fields[<?php echo $index; ?>][label]">字段标签</label></th> <td> <input type="text" name="fields[<?php echo $index; ?>][label]" value="<?php echo esc_attr($field->label); ?>" class="regular-text field-label-input" required> </td> </tr> <tr> <th scope="row"><label for="fields[<?php echo $index; ?>][placeholder]">占位文本</label></th> <td> <input type="text" name="fields[<?php echo $index; ?>][placeholder]" value="<?php echo esc_attr($field->placeholder); ?>" class="regular-text"> </td> </tr> <tr> <th scope="row"><label for="fields[<?php echo $index; ?>][default_value]">默认值</label></th> <td> <input type="text" name="fields[<?php echo $index; ?>][default_value]" value="<?php echo esc_attr($field->default_value); ?>" class="regular-text"> <p class="description">复选框的默认值需要以逗号分隔</p> </td> </tr> <tr> <th scope="row">验证规则</th> <td> <select name="fields[<?php echo $index; ?>][validation]" class="regular-text"> <?php foreach ($validation_rules as $value => $label) : ?> <option value="<?php echo esc_attr($value); ?>" <?php echo $field->validation_rule == $value ? 'selected' : ''; ?>> <?php echo esc_html($label); ?> </option> <?php endforeach; ?> </select> </td> </tr> <tr> <th scope="row">CSS类</th> <td> <input type="text" name="fields[<?php echo $index; ?>][css_class]" value="<?php echo esc_attr($field->css_class); ?>" class="regular-text"> <p class="description">添加自定义CSS类,用于样式调整</p> </td> </tr> <tr> <th scope="row">必填项</th> <td> <label> <input type="checkbox" name="fields[<?php echo $index; ?>][is_required]" <?php echo $field->is_required ? 'checked' : ''; ?>> 设为必填项 </label> </td> </tr> <?php switch ($field->type) : case 'select': case 'radio': case 'checkbox': ?> <tr> <th scope="row">选项设置</th> <td> <label> <input type="checkbox" name="fields[<?php echo $index; ?>][include_blank]" <?php echo !empty($field_options['include_blank']) ? 'checked' : ''; ?>> 包含空选项(仅下拉选择) </label> <div class="field-options"> <p>选项</p> <div class="options-container"> <?php if (!empty($field_options['options'])) : foreach ($field_options['options'] as $option) : ?> <div class="option-row"> <input type="text" name="fields[<?php echo $index; ?>][options][][label]" value="<?php echo esc_attr($option['label']); ?>" placeholder="标签" class="regular-text"> <input type="text" name="fields[<?php echo $index; ?>][options][][value]" value="<?php echo esc_attr($option['value']); ?>" placeholder="值(可选)" class="regular-text"> <button type="button" class="remove-option button button-link-delete">删除</button> </div> <?php endforeach; else : ?> <div class="option-row"> <input type="text" name="fields[<?php echo $index; ?>][options][][label]" placeholder="标签" class="regular-text"> <input type="text" name="fields[<?php echo $index; ?>][options][][value]" placeholder="值(可选)" class="regular-text"> <button type="button" class="remove-option button button-link-delete">删除</button> </div> <?php endif; ?> </div> <button type="button" class="add-option button">添加选项</button> </div> </td> </tr> <?php break; case 'file': ?> <tr> <th scope="row">文件设置</th> <td> <p>允许的文件类型:</p> <div class="file-types"> <label> <input type="checkbox" name="fields[<?php echo $index; ?>][allowed_types][]" value="image/*" <?php echo empty($field_options['allowed_types']) || in_array('image/*', $field_options['allowed_types']) ? 'checked' : ''; ?>> 图片文件 </label> <label> <input type="checkbox" name="fields[<?php echo $index; ?>][allowed_types][]" value="application/pdf" <?php echo !empty($field_options['allowed_types']) && in_array('application/pdf', $field_options['allowed_types']) ? 'checked' : ''; ?>> PDF文件 </label> <label> <input type="checkbox" name="fields[<?php echo $index; ?>][allowed_types][]" value="application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document" <?php echo !empty($field_options['allowed_types']) && in_array('application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document', $field_options['allowed_types']) ? 'checked' : ''; ?>> Word文件 </label> </div> </td> </tr> <?php break; endswitch; ?> </table> </div> </div> <?php endforeach; else : ?> <div class="form-field-placeholder"> 点击"添加字段"按钮开始创建表单字段 </div> <?php endif; ?> </div> </div> </div> </div> <div id="postbox-container-1" class="postbox-container"> <!-- 表单设置 --> <div class="postbox"> <h2 class="hndle"><span>提交设置</span></h2> <div class="inside"> <table class="form-table"> <tr> <th scope="row"><label for="success_message">成功消息</label></th> <td> <textarea id="success_message" name="success_message" rows="3" class="regular-text"><?php echo $form && isset($form->settings['success_message']) ? esc_textarea($form->settings['success_message']) : '表单提交成功,感谢您的反馈!'; ?></textarea> <p class="description">用户提交表单后显示的消息</p> </td> </tr> <tr> <th scope="row"><label for="success_redirect">跳转URL</label></th> <td> <input type="url" id="success_redirect" name="success_redirect" value="<?php echo $form && isset($form->settings['success_redirect']) ? esc_attr($form->settings['success_redirect']) : ''; ?>" class="regular-text"> <p class="description">可选,提交后跳转的URL,留空则显示成功消息</p> </td> </tr> <tr> <th scope="row"><label for="send_email">邮件通知</label></th> <td> <label> <input type="checkbox" id="send_email" name="send_email" <?php echo $form && isset($form->settings['send_email']) && $form->settings['send_email'] ? 'checked' : 'checked'; ?>> 提交后发送邮件通知 </label> </td> </tr> <tr> <th scope="row"><label for="email_to">接收邮箱</label></th> <td> <input type="email" id="email_to" name="email_to" value="<?php echo $form && isset($form->settings['email_to']) ? esc_attr($form->settings['email_to']) : get_option('admin_email'); ?>" class="regular-text"> <p class="description">接收表单提交通知的邮箱地址</p> </td> </tr> </table> </div> </div> <!-- 发布区域 --> <div class="postbox"> <h2 class="hndle"><span>保存表单</span></h2> <div class="inside"> <p class="submit"> <button type="submit" name="wpfb_save_form" class="button button-primary button-large">保存表单</button> <?php if ($form_id > 0) : ?> <a href="?page=wp-form-builder" class="button button-secondary">返回列表</a> <?php endif; ?> </p> <?php if ($form_id > 0) : ?> <div class="shortcode-info"> <h4>表单短代码</h4> <p>使用以下短代码在文章或页面中显示表单:</p> <code>
表单ID无效
</code> </div> <?php endif; ?> </div> </div> </div> </div> </div> </form> </div> <style> .form-field-item { margin-bottom: 15px; } .form-field-item .hndle { display: flex; justify-content: space-between; align-items: center; } .form-field-placeholder { padding: 20px; text-align: center; color: #666; border: 1px dashed #ccc; margin: 10px 0; } .field-type { font-size: 11px; padding: 2px 8px; border-radius: 4px; background: #f0f0f1; color: #3c434a; margin-left: 10px; } .option-row { display: flex; gap: 10px; margin-bottom: 10px; align-items: center; } .option-row input:first-child { flex: 2; } .option-row input:last-child { flex: 2; } .file-types { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; } .shortcode-info { padding: 10px; background: #f0f0f1; border-radius: 4px; margin-top: 15px; } .shortcode-info code { display: block; padding: 8px; background: #fff; border: 1px solid #ddd; border-radius: 4px; margin-top: 5px; word-break: break-all; } </style> <script> document.addEventListener('DOMContentLoaded', function() { // 字段计数器 let fieldCounter = <?php echo count($fields); ?>; // 添加新字段 document.getElementById('add-field').addEventListener('click', function() { const fieldTypes = <?php echo json_encode($field_types); ?>; const validationRules = <?php echo json_encode($validation_rules); ?>; // 显示字段类型选择对话框 const fieldType = prompt('请选择字段类型:\n' + Object.values(fieldTypes).join('\n')); if (!fieldType) return; // 找到对应的字段类型值 let fieldTypeKey = ''; for (const key in fieldTypes) { if (fieldTypes[key] === fieldType) { fieldTypeKey = key; break; } } if (!fieldTypeKey) { alert('无效的字段类型'); return; } // 创建新字段HTML const fieldIndex = fieldCounter++; let fieldHtml = ` <div class="form-field-item postbox" data-field-type="${fieldTypeKey}"> <h3 class="hndle"> <span class="field-label">未命名字段</span> <span class="field-type badge">${fieldType}</span> <button type="button" class="remove-field button button-link-delete">删除</button> </h3> <div class="inside"> <input type="hidden" name="fields[${fieldIndex}][type]" value="${fieldTypeKey}"> <table class="form-table"> <tr> <th scope="row"><label for="fields[${fieldIndex}][label]">字段标签</label></th> <td> <input type="text" name="fields[${fieldIndex}][label]" value="" class="regular-text field-label-input" required> </td> </tr> <tr> <th scope="row"><label for="fields[${fieldIndex}][placeholder]">占位文本</label></th> <td> <input type="text" name="fields[${fieldIndex}][placeholder]" value="" class="regular-text"> </td> </tr> <tr> <th scope="row"><label for="fields[${fieldIndex}][default_value]">默认值</label></th> <td> <input type="text" name="fields[${fieldIndex}][default_value]" value="" class="regular-text"> <p class="description">复选框的默认值需要以逗号分隔</p> </td> </tr> <tr> <th scope="row">验证规则</th> <td> <select name="fields[${fieldIndex}][validation]" class="regular-text"> `; // 添加验证规则选项 for (const ruleValue in validationRules) { fieldHtml += `<option value="${ruleValue}">${validationRules[ruleValue]}</option>`; } fieldHtml += ` </select> </td> </tr> <tr> <th scope="row">CSS类</th> <td> <input type="text" name="fields[${fieldIndex}][css_class]" value="" class="regular-text"> <p class="description">添加自定义CSS类,用于样式调整</p> </td> </tr> <tr> <th scope="row">必填项</th> <td> <label> <input type="checkbox" name="fields[${fieldIndex}][is_required]"> 设为必填项 </label> </td> </tr> `; // 添加特定字段类型的设置 switch (fieldTypeKey) { case 'select': case 'radio': case 'checkbox': fieldHtml += ` <tr> <th scope="row">选项设置</th> <td> <label> <input type="checkbox" name="fields[${fieldIndex}][include_blank]" ${fieldTypeKey === 'select' ? 'checked' : 'disabled'}> 包含空选项(仅下拉选择) </label> <div class="field-options"> <p>选项</p> <div class="options-container"> <div class="option-row"> <input type="text" name="fields[${fieldIndex}][options][][label]" placeholder="标签" class="regular-text"> <input type="text" name="fields[${fieldIndex}][options][][value]" placeholder="值(可选)" class="regular-text"> <button type="button" class="remove-option button button-link-delete">删除</button> </div> </div> <button type="button" class="add-option button">添加选项</button> </div> </td> </tr> `; break; case 'file': fieldHtml += ` <tr> <th scope="row">文件设置</th> <td> <p>允许的文件类型:</p> <div class="file-types"> <label> <input type="checkbox" name="fields[${fieldIndex}][allowed_types][]" value="image/*" checked> 图片文件 </label> <label> <input type="checkbox" name="fields[${fieldIndex}][allowed_types][]" value="application/pdf"> PDF文件 </label> <label> <input type="checkbox" name="fields[${fieldIndex}][allowed_types][]" value="application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"> Word文件 </label> </div> </td> </tr> `; break; } fieldHtml += ` </table> </div> </div> `; // 添加到字段容器 const fieldsContainer = document.getElementById('form-fields'); const placeholder = fieldsContainer.querySelector('.form-field-placeholder'); if (placeholder) { fieldsContainer.innerHTML = fieldHtml; } else { fieldsContainer.insertAdjacentHTML('beforeend', fieldHtml); } // 绑定新添加元素的事件 bindFieldEvents(); }); // 绑定字段事件 function bindFieldEvents() { // 删除字段 document.querySelectorAll('.remove-field').forEach(button => { button.addEventListener('click', function() { if (confirm('确定要删除这个字段吗?')) { this.closest('.form-field-item').remove(); // 检查是否还有字段 checkFieldsPlaceholder(); } }); }); // 更新字段标签显示 document.querySelectorAll('.field-label-input').forEach(input => { input.addEventListener('input', function() { const label = this.value || '未命名字段'; this.closest('.form-field-item').querySelector('.field-label').textContent = label; }); }); // 添加选项 document.querySelectorAll('.add-option').forEach(button => { button.addEventListener('click', function() { const container = this.parentElement.querySelector('.options-container'); const fieldIndex = this.closest('.form-field-item').querySelector('input[name$="[type]"]').name.match(/fields\[(\d+)\]/)[1]; const optionHtml = ` <div class="option-row"> <input type="text" name="fields[${fieldIndex}][options][][label]" placeholder="标签" class="regular-text"> <input type="text" name="fields[${fieldIndex}][options][][value]" placeholder="值(可选)" class="regular-text"> <button type="button" class="remove-option button button-link-delete">删除</button> </div> `; container.insertAdjacentHTML('beforeend', optionHtml); // 绑定删除选项事件 container.lastElementChild.querySelector('.remove-option').addEventListener('click', function() { this.closest('.option-row').remove(); }); }); }); // 删除选项 document.querySelectorAll('.remove-option').forEach(button => { button.addEventListener('click', function() { // 至少保留一个选项 const container = this.closest('.options-container'); if (container.querySelectorAll('.option-row').length > 1) { this.closest('.option-row').remove(); } else { alert('至少需要保留一个选项'); } }); }); } // 检查是否需要显示占位符 function checkFieldsPlaceholder() { const fieldsContainer = document.getElementById('form-fields'); const fieldItems = fieldsContainer.querySelectorAll('.form-field-item'); if (fieldItems.length === 0) { fieldsContainer.innerHTML = '<div class="form-field-placeholder">点击"添加字段"按钮开始创建表单字段</div>'; } } // 初始化事件绑定 bindFieldEvents(); }); </script> <?php } // 渲染表单提交列表 function wpfb_render_entries_list() { global $wpdb; $entries_table = $wpdb->prefix . 'wpfb_entries'; $entry_details_table = $wpdb->prefix . 'wpfb_entry_details'; $forms_table = $wpdb->prefix . 'wpfb_forms'; $fields_table = $wpdb->prefix . 'wpfb_fields'; $form_id = isset($_GET['form_id']) ? intval($_GET['form_id']) : 0; if ($form_id <= 0) { echo '<div class="wrap"><div class="notice notice-error"><p>表单ID无效</p></div></div>'; return; } // 获取表单信息 $form = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $forms_table WHERE id = %d", $form_id) ); if (!$form) { echo '<div class="wrap"><div class="notice notice-error"><p>表单不存在</p></div></div>'; return; } // 获取表单字段 $fields = $wpdb->get_results( $wpdb->prepare("SELECT id, label, name FROM $fields_table WHERE form_id = %d ORDER BY sort_order ASC", $form_id) ); // 处理删除提交 if (isset($_GET['action']) && $_GET['action'] == 'delete_entry' && isset($_GET['entry_id']) && wp_verify_nonce($_GET['nonce'], 'wpfb_delete_entry')) { $entry_id = intval($_GET['entry_id']); // 删除提交记录 $wpdb->delete($entry_details_table, array('entry_id' => $entry_id), array('%d')); $wpdb->delete($entries_table, array('id' => $entry_id), array('%d')); echo '<div class="notice notice-success"><p>提交记录已成功删除</p></div>'; } // 处理批量删除 if (isset($_POST['wpfb_bulk_action']) && $_POST['wpfb_bulk_action'] == 'delete' && isset($_POST['entries']) && is_array($_POST['entries'])) { // 验证nonce if (!wp_verify_nonce($_POST['wpfb_nonce'], 'wpfb_bulk_entries')) { wp_die('安全验证失败,请刷新页面重试。'); } foreach ($_POST['entries'] as $entry_id) { $entry_id = intval($entry_id); $wpdb->delete($entry_details_table, array('entry_id' => $entry_id), array('%d')); $wpdb->delete($entries_table, array('id' => $entry_id), array('%d')); } echo '<div class="notice notice-success"><p>选中的提交记录已成功删除</p></div>'; } // 处理导出 if (isset($_GET['action']) && $_GET['action'] == 'export_entries' && wp_verify_nonce($_GET['nonce'], 'wpfb_export_entries')) { wpfb_export_entries($form_id, $form, $fields); exit; } // 获取提交记录 $entries = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $entries_table WHERE form_id = %d ORDER BY created_at DESC", $form_id) ); ?> <div class="wrap"> <h1 class="wp-heading-inline">表单提交: <?php echo esc_html($form->name); ?></h1> <a href="?page=wp-form-builder" class="page-title-action">返回表单列表</a> <a href="?page=wp-form-builder-add&form_id=<?php echo $form_id; ?>" class="page-title-action">编辑表单</a> <a href="?page=wp-form-builder-entries&form_id=<?php echo $form_id; ?>&action=export_entries&nonce=<?php echo wp_create_nonce('wpfb_export_entries'); ?>" class="page-title-action">导出CSV</a> <hr class="wp-header-end"> <?php if (!empty($entries)) : ?> <form method="post" action=""> <?php wp_nonce_field('wpfb_bulk_entries', 'wpfb_nonce'); ?> <div class="tablenav top"> <div class="alignleft actions bulkactions"> <select name="wpfb_bulk_action"> <option value="">批量操作</option> <option value="delete">删除</option> </select> <input type="submit" class="button action" value="应用"> </div> <br class="clear"> </div> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th scope="col" class="check-column"> <input type="checkbox" id="cb-select-all-1"> </th> <th>提交ID</th> <?php foreach ($fields as $field) : ?> <th><?php echo esc_html($field->label); ?></th> <?php endforeach; ?> <th>提交时间</th> <th>IP地址</th> <th>操作</th> </tr> </thead> <tbody> <?php foreach ($entries as $entry) : // 获取提交详情 $entry_details = $wpdb->get_results( $wpdb->prepare("SELECT field_id, value FROM $entry_details_table WHERE entry_id = %d", $entry->id) ); // 构建字段值数组 $field_values = array(); foreach ($entry_details as $detail) { $field_values[$detail->field_id] = $detail->value; } ?> <tr> <th scope="row" class="check-column"> <input type="checkbox" name="entries[]" value="<?php echo $entry->id; ?>"> </th> <td><?php echo $entry->id; ?></td> <?php foreach ($fields as $field) : ?> <td> <?php $value = isset($field_values[$field->id]) ? $field_values[$field->id] : ''; // 处理文件链接 if ($value && strpos($value, 'http') === 0) { echo '<a href="' . esc_url($value) . '" target="_blank">查看文件</a>'; } else { echo esc_html($value); } ?> </td> <?php endforeach; ?> <td><?php echo date('Y-m-d H:i:s', strtotime($entry->created_at)); ?></td> <td><?php echo esc_html($entry->ip_address); ?></td> <td> <a href="?page=wp-form-builder-entries&form_id=<?php echo $form_id; ?>&action=delete_entry&entry_id=<?php echo $entry->id; ?>&nonce=<?php echo wp_create_nonce('wpfb_delete_entry'); ?>" class="delete" onclick="return confirm('确定要删除这条记录吗?')">删除</a> </td> </tr> <?php endforeach; ?> </tbody> </table> <div class="tablenav bottom"> <div class="alignleft actions bulkactions"> <select name="wpfb_bulk_action2"> <option value="">批量操作</option> <option value="delete">删除</option> </select> <input type="submit" class="button action" value="应用"> </div> <br class="clear"> </div> </form> <?php else : ?> <div class="notice notice-info"> <p>这个表单还没有任何提交记录</p> </div> <?php endif; ?> </div> <?php } // 导出表单提交记录 function wpfb_export_entries($form_id, $form, $fields) { global $wpdb; $entries_table = $wpdb->prefix . 'wpfb_entries'; $entry_details_table = $wpdb->prefix . 'wpfb_entry_details'; // 获取所有提交记录 $entries = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $entries_table WHERE form_id = %d ORDER BY created_at DESC", $form_id) ); // 设置CSV头部 header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="form-entries-' . $form_id . '-' . date('Ymd') . '.csv"'); $output = fopen('php://output', 'w'); fprintf($output, chr(0xEF) . chr(0xBB) . chr(0xBF)); // UTF-8 BOM // 输出表头 $header = array('提交ID', '提交时间', 'IP地址'); foreach ($fields as $field) { $header[] = $field->label; } fputcsv($output, $header); // 输出每条记录 foreach ($entries as $entry) { $entry_details = $wpdb->get_results( $wpdb->prepare("SELECT field_id, value FROM $entry_details_table WHERE entry_id = %d", $entry->id) ); $field_values = array(); foreach ($entry_details as $detail) { $field_values[$detail->field_id] = $detail->value; } $row = array( $entry->id, $entry->created_at, $entry->ip_address ); foreach ($fields as $field) { $row[] = isset($field_values[$field->id]) ? $field_values[$field->id] : ''; } fputcsv($output, $row); } fclose($output); } // 渲染设置页面 function wpfb_render_settings() { // 保存设置 if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['wpfb_save_settings']) && isset($_POST['wpfb_nonce'])) { // 验证nonce if (!wp_verify_nonce($_POST['wpfb_nonce'], 'wpfb_save_settings')) { wp_die('安全验证失败,请刷新页面重试。'); } $settings = array( 'default_email' => sanitize_email($_POST['default_email']), 'email_subject' => sanitize_text_field($_POST['email_subject']), 'email_template' => sanitize_textarea_field($_POST['email_template']) ); update_option('wpfb_settings', $settings); echo '<div class="notice notice-success"><p>设置已成功保存</p></div>'; } $settings = get_option('wpfb_settings'); ?> <div class="wrap"> <h1>表单生成器设置</h1> <form method="post" action=""> <?php wp_nonce_field('wpfb_save_settings', 'wpfb_nonce'); ?> <table class="form-table"> <tr> <th scope="row"><label for="default_email">默认通知邮箱</label></th> <td> <input type="email" id="default_email" name="default_email" value="<?php echo esc_attr($settings['default_email']); ?>" class="regular-text" required> <p class="description">表单提交通知的默认接收邮箱</p> </td> </tr> <tr> <th scope="row"><label for="email_subject">邮件主题</label></th> <td> <input type="text" id="email_subject" name="email_subject" value="<?php echo esc_attr($settings['email_subject']); ?>" class="regular-text" required> <p class="description">可用变量: {form_name} - 表单名称</p> </td> </tr> <tr> <th scope="row"><label for="email_template">邮件内容模板</label></th> <td> <textarea id="email_template" name="email_template" rows="6" class="regular-text" required><?php echo esc_textarea($settings['email_template']); ?></textarea> <p class="description">可用变量: {form_name} - 表单名称, {form_data} - 表单数据, {submission_date} - 提交时间</p> </td> </tr> </table> <p class="submit"> <button type="submit" name="wpfb_save_settings" class="button button-primary">保存设置</button> </p> </form> </div> <?php }