File: //proc/self/cwd/wp-content/plugins/quick-message/quick-message.php
<?php
/**
* Plugin Name: 快速留言管理
* Description: 接收前台快速留言表单提交,并在后台提供管理菜单查看记录
* Version: 1.0.0
* Author: Dev
*/
if (!defined('ABSPATH')) exit;
define('QM_VERSION', '1.0.0');
define('QM_TABLE', 'quick_messages');
register_activation_hook(__FILE__, 'qm_create_table');
function qm_create_table() {
global $wpdb;
$table = $wpdb->prefix . QM_TABLE;
$charset = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL DEFAULT '',
contact_type varchar(20) NOT NULL DEFAULT '',
contact_number varchar(200) NOT NULL DEFAULT '',
message text NOT NULL,
url varchar(500) NOT NULL DEFAULT '',
sys_area_id varchar(100) NOT NULL DEFAULT '',
customer_service_id varchar(100) NOT NULL DEFAULT '',
office_id varchar(100) NOT NULL DEFAULT '',
status tinyint(1) NOT NULL DEFAULT 0,
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_status (status),
KEY idx_created (created_at)
) $charset;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
}
add_action('rest_api_init', 'qm_register_routes');
function qm_register_routes() {
register_rest_route('quick-message/v1', '/submit', array(
'methods' => 'POST',
'callback' => 'qm_handle_submit',
'permission_callback' => '__return_true',
));
}
function qm_handle_submit(WP_REST_Request $request) {
$params = $request->get_json_params();
if (!$params) {
$params = $request->get_body_params();
}
$name = sanitize_text_field($params['name'] ?? '');
$contact_type = sanitize_text_field($params['contactType'] ?? '');
$contact_number = sanitize_text_field($params['number'] ?? '');
$message = sanitize_textarea_field($params['messageContents'] ?? '');
$url = esc_url_raw($params['url'] ?? '');
$sys_area_id = sanitize_text_field($params['sysAreaId'] ?? '');
$customer_service_id = sanitize_text_field($params['customerServiceId'] ?? '');
$office_id = sanitize_text_field($params['officeId'] ?? '');
if (empty($name) || empty($contact_type) || empty($contact_number)) {
return new WP_REST_Response(array('success' => false, 'message' => '必填欄位不能為空'), 400);
}
global $wpdb;
$table = $wpdb->prefix . QM_TABLE;
$inserted = $wpdb->insert($table, array(
'name' => $name,
'contact_type' => $contact_type,
'contact_number' => $contact_number,
'message' => $message,
'url' => $url,
'sys_area_id' => $sys_area_id,
'customer_service_id' => $customer_service_id,
'office_id' => $office_id,
'status' => 0,
'created_at' => current_time('mysql'),
), array('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s'));
if ($inserted === false) {
return new WP_REST_Response(array('success' => false, 'message' => '提交失敗,請稍後重試'), 500);
}
return new WP_REST_Response(array('success' => true, 'message' => '提交成功'), 200);
}
add_action('admin_menu', 'qm_admin_menu');
function qm_admin_menu() {
add_menu_page(
'快速留言',
'快速留言',
'manage_options',
'quick-messages',
'qm_admin_page',
'dashicons-feedback',
26
);
}
function qm_admin_page() {
global $wpdb;
$table = $wpdb->prefix . QM_TABLE;
$action = isset($_GET['action']) ? sanitize_text_field($_GET['action']) : 'list';
$msg_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
if ($action === 'delete' && $msg_id > 0 && isset($_GET['_wpnonce']) && wp_verify_nonce($_GET['_wpnonce'], 'qm_delete_' . $msg_id)) {
$wpdb->delete($table, array('id' => $msg_id), array('%d'));
wp_redirect(admin_url('admin.php?page=quick-messages&qm_deleted=1'));
exit;
}
if ($action === 'mark_read' && $msg_id > 0 && isset($_GET['_wpnonce']) && wp_verify_nonce($_GET['_wpnonce'], 'qm_read_' . $msg_id)) {
$wpdb->update($table, array('status' => 1), array('id' => $msg_id), array('%d'), array('%d'));
wp_redirect(admin_url('admin.php?page=quick-messages&qm_updated=1'));
exit;
}
if ($action === 'mark_unread' && $msg_id > 0 && isset($_GET['_wpnonce']) && wp_verify_nonce($_GET['_wpnonce'], 'qm_unread_' . $msg_id)) {
$wpdb->update($table, array('status' => 0), array('id' => $msg_id), array('%d'), array('%d'));
wp_redirect(admin_url('admin.php?page=quick-messages&qm_updated=1'));
exit;
}
$contact_type_map = array('1' => 'WhatsApp', '2' => 'Line', '3' => 'WeChat', '4' => '電話');
$paged = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$per_page = 20;
$offset = ($paged - 1) * $per_page;
$filter_status = isset($_GET['filter_status']) && $_GET['filter_status'] !== '' ? intval($_GET['filter_status']) : null;
$search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
$where = '1=1';
if ($filter_status !== null) {
$where .= $wpdb->prepare(' AND status = %d', $filter_status);
}
if ($search !== '') {
$where .= $wpdb->prepare(' AND (name LIKE %s OR contact_number LIKE %s OR message LIKE %s)', '%' . $wpdb->esc_like($search) . '%', '%' . $wpdb->esc_like($search) . '%', '%' . $wpdb->esc_like($search) . '%');
}
$total = $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE $where");
$total_pages = ceil($total / $per_page);
$rows = $wpdb->get_results($wpdb->prepare("SELECT * FROM $table WHERE $where ORDER BY created_at DESC LIMIT %d OFFSET %d", $per_page, $offset));
$unread_count = $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status = 0");
if (isset($_GET['qm_deleted'])) {
echo '<div class="notice notice-success is-dismissible"><p>留言已刪除。</p></div>';
}
if (isset($_GET['qm_updated'])) {
echo '<div class="notice notice-success is-dismissible"><p>狀態已更新。</p></div>';
}
?>
<div class="wrap">
<h1 class="wp-heading-inline">快速留言 <?php if ($unread_count > 0) echo '<span class="update-plugins count-' . $unread_count . '"><span class="update-count">' . $unread_count . '</span></span>'; ?></h1>
<form method="get" style="margin-top:10px;">
<input type="hidden" name="page" value="quick-messages" />
<select name="filter_status">
<option value="">全部狀態</option>
<option value="0" <?php selected($filter_status, 0); ?>>未讀</option>
<option value="1" <?php selected($filter_status, 1); ?>>已讀</option>
</select>
<input type="search" name="s" value="<?php echo esc_attr($search); ?>" placeholder="搜尋姓名、聯絡方式、留言內容" />
<button class="button" type="submit">篩選</button>
</form>
<table class="wp-list-table widefat fixed striped" style="margin-top:10px;">
<thead>
<tr>
<th style="width:50px;">ID</th>
<th style="width:90px;">稱呼</th>
<th style="width:90px;">聯絡方式</th>
<th style="width:180px;">號碼/鏈接</th>
<th>留言內容</th>
<th style="width:100px;">來源頁面</th>
<th style="width:70px;">狀態</th>
<th style="width:140px;">提交時間</th>
<th style="width:180px;">操作</th>
</tr>
</thead>
<tbody>
<?php if (empty($rows)): ?>
<tr><td colspan="9" style="text-align:center;padding:20px;">暫無留言記錄</td></tr>
<?php else: ?>
<?php foreach ($rows as $row): ?>
<tr<?php if ($row->status == 0) echo ' style="background:#fff8e1;font-weight:600;"'; ?>>
<td><?php echo $row->id; ?></td>
<td><?php echo esc_html($row->name); ?></td>
<td><?php echo esc_html($contact_type_map[$row->contact_type] ?? $row->contact_type); ?></td>
<td><?php echo esc_html($row->contact_number); ?></td>
<td><?php echo esc_html(mb_substr($row->message, 0, 80)) . (mb_strlen($row->message) > 80 ? '...' : ''); ?></td>
<td><a href="<?php echo esc_url($row->url); ?>" target="_blank" title="<?php echo esc_attr($row->url); ?>"><?php echo esc_html(mb_substr(basename($row->url), 0, 20)); ?></a></td>
<td><?php echo $row->status == 1 ? '<span style="color:#888;">已讀</span>' : '<span style="color:#e65100;">未讀</span>'; ?></td>
<td><?php echo esc_html($row->created_at); ?></td>
<td>
<?php if ($row->status == 0): ?>
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=quick-messages&action=mark_read&id=' . $row->id), 'qm_read_' . $row->id); ?>" class="button button-small">標記已讀</a>
<?php else: ?>
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=quick-messages&action=mark_unread&id=' . $row->id), 'qm_unread_' . $row->id); ?>" class="button button-small">標記未讀</a>
<?php endif; ?>
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=quick-messages&action=delete&id=' . $row->id), 'qm_delete_' . $row->id); ?>" class="button button-small" onclick="return confirm('確定要刪除這條留言嗎?');">刪除</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php if ($total_pages > 1): ?>
<div class="tablenav bottom">
<div class="tablenav-pages">
<span class="displaying-num">共 <?php echo $total; ?> 條</span>
<span class="pagination-links">
<?php
if ($paged > 1) {
echo '<a class="button" href="' . esc_url(add_query_arg(array('paged' => $paged - 1, 'filter_status' => $filter_status, 's' => $search))) . '">« 上一頁</a> ';
}
echo '<span class="paging-input">' . $paged . ' / ' . $total_pages . '</span>';
if ($paged < $total_pages) {
echo ' <a class="button" href="' . esc_url(add_query_arg(array('paged' => $paged + 1, 'filter_status' => $filter_status, 's' => $search))) . '">下一頁 »</a>';
}
?>
</span>
</div>
</div>
<?php endif; ?>
</div>
<?php
}