这个完整版本的表单生成器插件包含了以下关键改进:
-
完善的错误处理机制:使用数据库事务确保数据一致性,任何步骤失败都会回滚操作,避免部分数据入库。
-
增强的安全性:
- 所有用户输入都经过严格清洗和验证
- 使用
wp_verify_nonce
进行安全验证,替代了可能导致问题的check_admin_referrer
- 明确指定数据库字段格式,防止 SQL 注入风险
-
用户体验优化:
- 详细的错误提示信息,帮助用户快速定位问题
- 表单验证失败时保留已输入的数据,避免重复填写
- 清晰的操作指引和提示信息
-
功能完整性:
- 支持多种表单字段类型和验证规则
- 表单数据管理和导出功能
- 邮件通知系统
- 短代码嵌入功能
安装方法与之前相同,将代码保存为
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
}
没有回复内容