WordPress高级教程:开发内部知识库与文档协作中心 引言:WordPress的无限可能性 WordPress作为全球最流行的内容管理系统,早已超越了简单的博客平台定位。根据W3Techs的数据,截至2023年,WordPress占据了全球网站市场份额的43%,其中超过三分之一的顶级网站使用WordPress。这一成功不仅源于其用户友好的界面和丰富的插件生态,更得益于其高度可扩展的架构设计。 本教程将深入探讨如何通过WordPress的二次开发,构建一个功能完善的内部知识库与文档协作中心,并集成常用互联网小工具功能。我们将从基础架构设计开始,逐步深入到高级功能实现,最终打造一个集知识管理、团队协作和工具集成为一体的企业级平台。 第一部分:知识库系统架构设计 1.1 知识库内容模型设计 一个高效的知识库系统需要精心设计的内容架构。在WordPress中,我们可以通过自定义文章类型(Custom Post Types)和分类法(Taxonomies)来构建专业的知识库结构。 // 注册知识库自定义文章类型 function register_knowledgebase_post_type() { $labels = array( 'name' => '知识库文章', 'singular_name' => '知识库文章', 'menu_name' => '知识库', 'add_new' => '添加新文章', 'add_new_item' => '添加新知识库文章', 'edit_item' => '编辑知识库文章', 'new_item' => '新知识库文章', 'view_item' => '查看知识库文章', 'search_items' => '搜索知识库文章', 'not_found' => '未找到知识库文章', 'not_found_in_trash' => '回收站中未找到知识库文章' ); $args = array( 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => array('slug' => 'knowledgebase'), 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => 5, 'menu_icon' => 'dashicons-book', 'supports' => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments', 'revisions', 'custom-fields'), 'show_in_rest' => true, // 启用Gutenberg编辑器支持 ); register_post_type('knowledgebase', $args); } add_action('init', 'register_knowledgebase_post_type'); // 注册知识库分类法 function register_knowledgebase_taxonomies() { // 主分类 register_taxonomy( 'kb_category', 'knowledgebase', array( 'label' => '知识分类', 'rewrite' => array('slug' => 'kb-category'), 'hierarchical' => true, 'show_admin_column' => true, 'show_in_rest' => true, ) ); // 标签 register_taxonomy( 'kb_tag', 'knowledgebase', array( 'label' => '知识标签', 'rewrite' => array('slug' => 'kb-tag'), 'hierarchical' => false, 'show_admin_column' => true, 'show_in_rest' => true, ) ); } add_action('init', 'register_knowledgebase_taxonomies'); 1.2 高级权限管理系统 内部知识库需要精细的权限控制。我们可以扩展WordPress的角色和权限系统,实现基于团队、部门和用户级别的访问控制。 // 添加自定义用户角色和权限 function add_knowledgebase_roles() { // 知识库编辑者角色 add_role('kb_editor', '知识库编辑者', array( 'read' => true, 'edit_knowledgebase' => true, 'edit_published_knowledgebase' => true, 'publish_knowledgebase' => true, 'delete_knowledgebase' => true, 'delete_published_knowledgebase' => true, 'upload_files' => true, 'read_knowledgebase' => true, )); // 知识库审核者角色 add_role('kb_reviewer', '知识库审核者', array( 'read' => true, 'edit_knowledgebase' => true, 'edit_others_knowledgebase' => true, 'edit_published_knowledgebase' => true, 'publish_knowledgebase' => true, 'read_private_knowledgebase' => true, 'delete_knowledgebase' => true, 'delete_published_knowledgebase' => true, 'delete_private_knowledgebase' => true, 'delete_others_knowledgebase' => true, 'manage_knowledgebase_categories' => true, )); } add_action('init', 'add_knowledgebase_roles'); // 扩展用户权限 function add_knowledgebase_capabilities() { $roles = array('administrator', 'editor', 'kb_editor', 'kb_reviewer'); foreach ($roles as $role_name) { $role = get_role($role_name); if ($role) { $role->add_cap('read_knowledgebase'); $role->add_cap('edit_knowledgebase'); $role->add_cap('edit_knowledgebases'); $role->add_cap('edit_others_knowledgebases'); $role->add_cap('publish_knowledgebases'); $role->add_cap('read_private_knowledgebases'); } } } add_action('admin_init', 'add_knowledgebase_capabilities'); 第二部分:文档协作功能实现 2.1 实时协作编辑器集成 现代文档协作中心需要支持多人实时编辑。我们可以集成开源协作编辑器,如CKEditor 5或Quill,实现类似Google Docs的协作体验。 // 集成协作编辑器 function enqueue_collaborative_editor() { global $post; // 仅在知识库文章编辑页面加载 if (is_admin() && isset($post) && $post->post_type === 'knowledgebase') { // 加载CKEditor 5 wp_enqueue_script('ckeditor5', 'https://cdn.ckeditor.com/ckeditor5/36.0.1/super-build/ckeditor.js', array(), '36.0.1', true); // 加载协作插件 wp_enqueue_script('collab-editor', get_template_directory_uri() . '/js/collab-editor.js', array('ckeditor5', 'jquery'), '1.0.0', true); // 传递必要参数 wp_localize_script('collab-editor', 'collabConfig', array( 'postId' => $post->ID, 'userId' => get_current_user_id(), 'userName' => wp_get_current_user()->display_name, 'nonce' => wp_create_nonce('collab_editor_nonce'), 'apiUrl' => rest_url('collab/v1/'), )); } } add_action('admin_enqueue_scripts', 'enqueue_collaborative_editor'); // 创建REST API端点处理实时协作 function register_collaboration_api() { register_rest_route('collab/v1', '/update/(?P<id>d+)', array( 'methods' => 'POST', 'callback' => 'handle_collaborative_update', 'permission_callback' => function() { return current_user_can('edit_knowledgebase'); }, 'args' => array( 'id' => array( 'validate_callback' => function($param, $request, $key) { return is_numeric($param); } ), ), )); register_rest_route('collab/v1', '/presence/(?P<id>d+)', array( 'methods' => 'GET', 'callback' => 'get_active_users', 'permission_callback' => function() { return current_user_can('read_knowledgebase'); }, )); } add_action('rest_api_init', 'register_collaboration_api'); function handle_collaborative_update(WP_REST_Request $request) { $post_id = $request->get_param('id'); $content = $request->get_param('content'); $user_id = get_current_user_id(); // 验证权限 if (!current_user_can('edit_post', $post_id)) { return new WP_Error('rest_forbidden', '您没有编辑此文章的权限', array('status' => 403)); } // 更新文章内容 wp_update_post(array( 'ID' => $post_id, 'post_content' => wp_kses_post($content) )); // 记录协作历史 add_post_meta($post_id, '_collab_history', array( 'user_id' => $user_id, 'timestamp' => current_time('mysql'), 'action' => 'edit' )); return new WP_REST_Response(array( 'success' => true, 'message' => '内容已更新' ), 200); } 2.2 版本控制与历史记录 完善的文档协作需要完整的版本控制功能。我们可以扩展WordPress的修订版本系统,提供更强大的版本管理。 // 增强版本控制功能 class Enhanced_Revision_Manager { private $max_revisions = 50; public function __construct() { // 设置最大修订版本数 add_filter('wp_revisions_to_keep', array($this, 'set_max_revisions'), 10, 2); // 添加版本比较功能 add_action('add_meta_boxes', array($this, 'add_revision_comparison_meta_box')); // 添加版本标记功能 add_action('post_submitbox_misc_actions', array($this, 'add_version_note_field')); add_action('save_post', array($this, 'save_version_note')); } public function set_max_revisions($num, $post) { if ($post->post_type === 'knowledgebase') { return $this->max_revisions; } return $num; } public function add_revision_comparison_meta_box() { add_meta_box( 'revision-comparison', '版本比较', array($this, 'render_revision_comparison'), 'knowledgebase', 'side', 'default' ); } public function render_revision_comparison($post) { $revisions = wp_get_post_revisions($post->ID); if (count($revisions) > 1) { echo '<div class="revision-comparison">'; echo '<select id="revision-from">'; echo '<option value="">选择旧版本</option>'; foreach ($revisions as $revision) { $author = get_userdata($revision->post_author); $date = date_i18n('Y-m-d H:i', strtotime($revision->post_modified)); echo sprintf( '<option value="%d">%s - %s</option>', $revision->ID, $date, $author->display_name ); } echo '</select>'; echo '<select id="revision-to">'; echo '<option value="">选择新版本</option>'; foreach ($revisions as $revision) { $author = get_userdata($revision->post_author); $date = date_i18n('Y-m-d H:i', strtotime($revision->post_modified)); echo sprintf( '<option value="%d">%s - %s</option>', $revision->ID, $date, $author->display_name ); } echo '</select>'; echo '<button id="compare-revisions" class="button">比较版本</button>'; echo '<div id="revision-diff-result" style="margin-top:10px;"></div>'; echo '</div>'; // 添加比较脚本 wp_enqueue_script('revision-diff', get_template_directory_uri() . '/js/revision-diff.js', array('jquery'), '1.0.0', true); } else { echo '<p>暂无历史版本</p>'; } } public function add_version_note_field($post) { if ($post->post_type !== 'knowledgebase') return; echo '<div class="misc-pub-section">'; echo '<label for="version-note">版本说明:</label>'; echo '<input type="text" id="version-note" name="version_note" value="" placeholder="简要说明本次修改内容" style="width:100%;margin-top:5px;">'; echo '</div>'; } public function save_version_note($post_id) { if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return; if (!current_user_can('edit_post', $post_id)) return; if (!isset($_POST['version_note'])) return; $version_note = sanitize_text_field($_POST['version_note']); if (!empty($version_note)) { // 获取最新修订版本 $revisions = wp_get_post_revisions($post_id); $latest_revision = reset($revisions); if ($latest_revision) { update_metadata('post', $latest_revision->ID, '_version_note', $version_note); } } } } new Enhanced_Revision_Manager(); 第三部分:常用互联网小工具集成 3.1 代码片段管理器 对于技术团队,代码片段管理是知识库的重要功能。我们可以创建一个专门的代码片段管理器。 // 代码片段管理器 class Code_Snippet_Manager { public function __construct() { // 注册代码片段文章类型 add_action('init', array($this, 'register_code_snippet_post_type')); // 添加上传代码文件支持 add_filter('upload_mimes', array($this, 'add_code_mime_types')); // 添加代码高亮 add_action('wp_enqueue_scripts', array($this, 'enqueue_code_highlight')); add_action('admin_enqueue_scripts', array($this, 'enqueue_code_highlight')); // 添加快捷码 add_shortcode('code_snippet', array($this, 'code_snippet_shortcode')); } public function register_code_snippet_post_type() { $args = array( 'public' => true, 'label' => '代码片段', 'menu_icon' => 'dashicons-editor-code', 'supports' => array('title', 'editor', 'author', 'custom-fields'), 'show_in_rest' => true, 'taxonomies' => array('post_tag', 'category'), 'has_archive' => true, ); register_post_type('code_snippet', $args); } public function add_code_mime_types($mimes) { $mimes['js'] = 'text/javascript'; $mimes['php'] = 'text/php'; $mimes['py'] = 'text/x-python'; $mimes['java'] = 'text/x-java'; $mimes['cpp'] = 'text/x-c++'; $mimes['c'] = 'text/x-c'; $mimes['sql'] = 'text/x-sql'; $mimes['json'] = 'application/json'; $mimes['xml'] = 'application/xml'; return $mimes; } public function enqueue_code_highlight() { // 加载Prism.js代码高亮库 wp_enqueue_style('prism-css', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css'); wp_enqueue_script('prism-js', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js', array(), '1.29.0', true); // 加载语言支持 $languages = array('php', 'javascript', 'python', 'java', 'cpp', 'sql', 'json', 'bash'); foreach ($languages as $lang) { wp_enqueue_script("prism-$lang", "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-$lang.min.js", array('prism-js'), '1.29.0', true); } } public function code_snippet_shortcode($atts) { $atts = shortcode_atts(array( 'id' => 0, 'language' => 'php', 'title' => '', 'linenumbers' => 'true', 'download' => 'false' ), $atts); $snippet_id = intval($atts['id']); if ($snippet_id <= 0) { return '<p>错误:未指定代码片段ID</p>'; } $snippet = get_post($snippet_id); if (!$snippet || $snippet->post_type !== 'code_snippet') { return '<p>错误:代码片段不存在</p>'; } $language = sanitize_text_field($atts['language']); $title = sanitize_text_field($atts['title']); $show_line_numbers = $atts['linenumbers'] === 'true'; $show_download = $atts['download'] === 'true'; $output = '<div class="code-snippet-container">'; if (!empty($title)) { $output .= '<div class="code-snippet-header">'; $output .= '<h4>' . esc_html($title) . '</h4>'; if ($show_download) { $output .= '<a href="' . get_permalink($snippet_id) . '?download=1" class="download-snippet" download>下载代码</a>'; } $output .= '</div>'; } $output .= '<pre class="' . ($show_line_numbers ? 'line-numbers' : '') . '">'; $output .= '<code class="language-' . esc_attr($language) . '">'; $output .= htmlspecialchars($snippet->post_content); $output .= '</code></pre>'; // 添加代码信息 $output .= '<div class="code-snippet-meta">'; $output .= '<span>语言: ' . esc_html($language) . '</span>'; $output .= '<span>作者: ' . get_the_author_meta('display_name', $snippet->post_author) . '</span>'; $output .= '<span>更新: ' . get_the_modified_date('', $snippet_id) . '</span>'; $output .= '</div>'; $output .= '</div>'; return $output; } } new Code_Snippet_Manager(); // 代码片段下载处理function handle_code_snippet_download() { if (isset($_GET['download']) && is_singular('code_snippet')) { $post_id = get_the_ID(); $post = get_post($post_id); if ($post && $post->post_type === 'code_snippet') { $filename = sanitize_title($post->post_title) . '.txt'; $content = $post->post_content; header('Content-Type: text/plain'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Length: ' . strlen($content)); echo $content; exit; } } }add_action('template_redirect', 'handle_code_snippet_download'); ### 3.2 API测试工具集成 为开发团队集成API测试工具,方便在知识库中直接测试和调试API接口。 // API测试工具class API_Test_Tool { public function __construct() { // 注册API测试文章类型 add_action('init', array($this, 'register_api_test_post_type')); // 添加快捷码 add_shortcode('api_tester', array($this, 'api_tester_shortcode')); // 添加REST API端点处理代理请求 add_action('rest_api_init', array($this, 'register_api_proxy_endpoint')); // 加载前端资源 add_action('wp_enqueue_scripts', array($this, 'enqueue_api_tester_assets')); } public function register_api_test_post_type() { $args = array( 'public' => true, 'label' => 'API文档', 'menu_icon' => 'dashicons-rest-api', 'supports' => array('title', 'editor', 'custom-fields'), 'show_in_rest' => true, 'has_archive' => true, ); register_post_type('api_doc', $args); } public function api_tester_shortcode($atts) { $atts = shortcode_atts(array( 'id' => 0, 'title' => 'API测试工具', 'default_url' => '', 'default_method' => 'GET', 'default_headers' => '{"Content-Type": "application/json"}', 'default_body' => '{}' ), $atts); $api_doc_id = intval($atts['id']); $api_data = array(); if ($api_doc_id > 0) { $api_doc = get_post($api_doc_id); if ($api_doc && $api_doc->post_type === 'api_doc') { $api_data = array( 'endpoint' => get_post_meta($api_doc_id, '_api_endpoint', true), 'method' => get_post_meta($api_doc_id, '_api_method', true) ?: 'GET', 'headers' => get_post_meta($api_doc_id, '_api_headers', true) ?: '{"Content-Type": "application/json"}', 'body' => get_post_meta($api_doc_id, '_api_body', true) ?: '{}', 'description' => $api_doc->post_content ); } } ob_start(); ?> <div class="api-tester-container" data-doc-id="<?php echo esc_attr($api_doc_id); ?>"> <div class="api-tester-header"> <h3><?php echo esc_html($atts['title']); ?></h3> <div class="api-tester-actions"> <button class="btn-run-api">发送请求</button> <button class="btn-save-api">保存配置</button> <button class="btn-clear-api">清空</button> </div> </div> <div class="api-tester-form"> <div class="form-group"> <label>请求方法:</label> <select class="api-method"> <option value="GET" <?php selected($api_data['method'] ?? $atts['default_method'], 'GET'); ?>>GET</option> <option value="POST" <?php selected($api_data['method'] ?? $atts['default_method'], 'POST'); ?>>POST</option> <option value="PUT" <?php selected($api_data['method'] ?? $atts['default_method'], 'PUT'); ?>>PUT</option> <option value="DELETE" <?php selected($api_data['method'] ?? $atts['default_method'], 'DELETE'); ?>>DELETE</option> <option value="PATCH" <?php selected($api_data['method'] ?? $atts['default_method'], 'PATCH'); ?>>PATCH</option> </select> <label class="url-label">请求URL:</label> <input type="text" class="api-url" value="<?php echo esc_attr($api_data['endpoint'] ?? $atts['default_url']); ?>" placeholder="https://api.example.com/endpoint"> </div> <div class="form-group"> <label>请求头:</label> <textarea class="api-headers" rows="4"><?php echo esc_textarea($api_data['headers'] ?? $atts['default_headers']); ?></textarea> </div> <div class="form-group"> <label>请求体:</label> <textarea class="api-body" rows="8"><?php echo esc_textarea($api_data['body'] ?? $atts['default_body']); ?></textarea> </div> <?php if (!empty($api_data['description'])): ?> <div class="api-description"> <h4>API说明:</h4> <div><?php echo wp_kses_post($api_data['description']); ?></div> </div> <?php endif; ?> </div> <div class="api-tester-response"> <div class="response-header"> <h4>响应结果</h4> <div class="response-status"> <span class="status-label">状态:</span> <span class="status-code">-</span> <span class="status-time">时间: <span class="time-value">-</span>ms</span> </div> </div> <div class="response-body"> <pre><code class="language-json">等待请求...</code></pre> </div> </div> </div> <?php return ob_get_clean(); } public function register_api_proxy_endpoint() { register_rest_route('api-tester/v1', '/proxy', array( 'methods' => 'POST', 'callback' => array($this, 'handle_api_proxy_request'), 'permission_callback' => function() { // 仅限登录用户使用 return is_user_logged_in(); }, )); } public function handle_api_proxy_request(WP_REST_Request $request) { $url = sanitize_url($request->get_param('url')); $method = sanitize_text_field($request->get_param('method')); $headers = $request->get_param('headers'); $body = $request->get_param('body'); // 验证URL if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) { return new WP_Error('invalid_url', '无效的URL地址', array('status' => 400)); } // 解析headers $headers_array = array(); if (!empty($headers)) { $headers_data = json_decode($headers, true); if (is_array($headers_data)) { foreach ($headers_data as $key => $value) { $headers_array[sanitize_text_field($key)] = sanitize_text_field($value); } } } // 发送请求 $start_time = microtime(true); $args = array( 'method' => $method, 'headers' => $headers_array, 'timeout' => 30, 'sslverify' => false, ); if (in_array($method, array('POST', 'PUT', 'PATCH')) && !empty($body)) { $args['body'] = $body; } $response = wp_remote_request($url, $args); $end_time = microtime(true); $response_time = round(($end_time - $start_time) * 1000, 2); if (is_wp_error($response)) { return array( 'success' => false, 'error' => $response->get_error_message(), 'response_time' => $response_time ); } $response_code = wp_remote_retrieve_response_code($response); $response_body = wp_remote_retrieve_body($response); $response_headers = wp_remote_retrieve_headers($response); // 尝试解析JSON响应 $json_response = json_decode($response_body); if (json_last_error() === JSON_ERROR_NONE) { $formatted_body = json_encode($json_response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); } else { $formatted_body = $response_body; } return array( 'success' => true, 'status_code' => $response_code, 'headers' => $response_headers, 'body' => $formatted_body, 'response_time' => $response_time, 'size' => strlen($response_body) ); } public function enqueue_api_tester_assets() { wp_enqueue_style('api-tester-css', get_template_directory_uri() . '/css/api-tester.css'); wp_enqueue_script('api-tester-js', get_template_directory_uri() . '/js/api-tester.js', array('jquery'), '1.0.0', true); wp_localize_script('api-tester-js', 'apiTesterConfig', array( 'ajax_url' => admin_url('admin-ajax.php'), 'rest_url' => rest_url('api-tester/v1/'), 'nonce' => wp_create_nonce('api_tester_nonce') )); } } new API_Test_Tool(); ### 3.3 数据可视化工具 集成数据可视化工具,支持在知识库中直接创建和展示图表。 // 数据可视化工具class Data_Visualization_Tool { public function __construct() { // 注册图表文章类型 add_action('init', array($this, 'register_chart_post_type')); // 添加快捷码 add_shortcode('chart', array($this, 'chart_shortcode')); // 添加图表编辑器元框 add_action('add_meta_boxes', array($this, 'add_chart_editor_meta_box')); add_action('save_post_chart', array($this, 'save_chart_data')); // 加载图表库 add_action('wp_enqueue_scripts', array($this, 'enqueue_chart_library')); } public function register_chart_post_type() { $args = array( 'public' => true, 'label' => '数据图表', 'menu_icon' => 'dashicons-chart-line', 'supports' => array('title', 'author'), 'show_in_rest' => true, 'has_archive' => true, ); register_post_type('chart', $args); } public function add_chart_editor_meta_box() { add_meta_box( 'chart-editor', '图表编辑器', array($this, 'render_chart_editor'), 'chart', 'normal', 'high' ); } public function render_chart_editor($post) { wp_nonce_field('save_chart_data', 'chart_data_nonce'); $chart_type = get_post_meta($post->ID, '_chart_type', true) ?: 'line'; $chart_data = get_post_meta($post->ID, '_chart_data', true) ?: '{}'; $chart_options = get_post_meta($post->ID, '_chart_options', true) ?: '{}'; ?> <div class="chart-editor-container"> <div class="chart-settings"> <div class="setting-group"> <label for="chart-type">图表类型:</label> <select id="chart-type" name="chart_type"> <option value="line" <?php selected($chart_type, 'line'); ?>>折线图</option> <option value="bar" <?php selected($chart_type, 'bar'); ?>>柱状图</option> <option value="pie" <?php selected($chart_type, 'pie'); ?>>饼图</option> <option value="doughnut" <?php selected($chart_type, 'doughnut'); ?>>环形图</option> <option value="radar" <?php selected($chart_type, 'radar'); ?>>雷达图</option> <option value="scatter" <?php selected($chart_type, 'scatter'); ?>>散点图</option> </select> </div> <div class="setting-group"> <label for="chart-data">图表数据 (JSON格式):</label> <textarea id="chart-data" name="chart_data" rows="10"><?php echo esc_textarea($chart_data); ?></textarea> <p class="description">示例: {"labels": ["一月", "二月", "三月"], "datasets": [{"label": "销售额", "data": [65, 59, 80]}]}</p> </div> <div class="setting-group"> <label for="chart-options">图表选项 (JSON格式):</label> <textarea id="chart-options" name="chart_options" rows="10"><?php echo esc_textarea($chart_options); ?></textarea> <p class="description">配置图表显示选项,如颜色、标题等</p> </div> </div> <div class="chart-preview"> <h4>预览:</h4> <canvas id="chart-preview-canvas" width="400" height="300"></canvas> </div> </div> <style> .chart-editor-container { display: flex; gap: 20px; } .chart-settings { flex: 1; } .chart-preview { flex: 1; } .setting-group { margin-bottom: 15px; } .setting-group label { display: block; margin-bottom: 5px; font-weight: bold; } .setting-group textarea, .setting-group select { width: 100%; padding: 8px; } </style> <script> jQuery(document).ready(function($) { function updateChartPreview() { var type = $('#chart-type').val(); var data = $('#chart-data').val(); var options = $('#chart-options').val(); try { var chartData = JSON.parse(data); var chartOptions = JSON.parse(options); var ctx = document.getElementById('chart-preview-canvas').getContext('2d'); // 销毁现有图表 if (window.previewChart) { window.previewChart.destroy(); } // 创建新图表 window.previewChart = new Chart(ctx, { type: type, data: chartData, options: chartOptions }); } catch (e) { console.error('图表数据解析错误:', e); } } // 监听输入变化 $('#chart-type, #chart-data, #chart-options').on('change keyup', function() { updateChartPreview(); }); // 初始预览 updateChartPreview(); }); </script> <?php } public function save_chart_data($post_id) { if (!isset($_POST['chart_data_nonce']) || !wp_verify_nonce($_POST['chart_data_nonce'], 'save_chart_data')) { return; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return; if (!current_user_can('edit_post', $post_id)) return; if (isset($_POST['chart_type'])) { update_post_meta($post_id, '_chart_type', sanitize_text_field($_POST['chart_type'])); } if (isset($_POST['chart_data'])) { // 验证JSON格式 $chart_data = stripslashes($_POST['chart_data']); json_decode($chart_data); if (json_last_error() === JSON_ERROR_NONE) { update_post_meta($
发表评论分类: 应用软件
手把手教程:实现网站内容的多平台一键分发与推广,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:内容分发的挑战与机遇 在当今数字时代,内容创作者和网站管理员面临着一个共同的挑战:如何高效地将优质内容分发到多个平台,同时保持品牌一致性和用户参与度。每天,数以百万计的文章、视频和图片在互联网上发布,但只有少数能够真正触达目标受众。传统的内容分发方式需要手动将内容复制粘贴到各个平台,这不仅耗时耗力,还容易导致格式错乱和品牌信息不一致。 与此同时,WordPress作为全球最流行的内容管理系统,占据了互联网上超过40%的网站份额。它强大的可扩展性和开源特性使其成为实现自动化内容分发的理想平台。通过代码二次开发,我们可以在WordPress中集成各种互联网小工具,实现内容的一键多平台分发,大大提高工作效率。 本教程将带领您一步步实现这一目标,从基础概念到实际代码实现,最终打造一个功能完善的自动化内容分发系统。 第一部分:理解多平台内容分发的基本原理 1.1 内容分发的技术架构 多平台内容分发的核心在于API(应用程序编程接口)的使用。大多数主流社交平台和内容聚合网站都提供了API,允许开发者以编程方式发布内容、管理账户和获取分析数据。 典型的内容分发流程包括: 内容创建与格式化 目标平台身份验证 内容转换与适配 API调用与发布 发布结果跟踪与记录 1.2 WordPress作为分发中心的优势 WordPress本身已经是一个强大的内容管理系统,通过二次开发可以将其转变为内容分发中心: 内置的内容创建和编辑工具 丰富的插件生态系统 灵活的用户权限管理 强大的数据库支持 活跃的开发者社区 1.3 常用互联网小工具功能概览 在内容分发过程中,以下小工具功能将大大提高效率: 社交媒体分享按钮 内容自动摘要生成 多平台发布计划 发布效果分析面板 内容格式自动转换器 第二部分:搭建WordPress开发环境 2.1 本地开发环境配置 在开始代码开发之前,我们需要搭建一个合适的WordPress开发环境: // 示例:本地WordPress环境配置要点 // 1. 安装本地服务器环境(如XAMPP、MAMP或Local by Flywheel) // 2. 下载最新版WordPress并解压到服务器目录 // 3. 创建数据库并配置wp-config.php文件 // 4. 安装WordPress并设置管理员账户 // wp-config.php数据库配置示例 define('DB_NAME', 'your_database_name'); define('DB_USER', 'your_database_user'); define('DB_PASSWORD', 'your_database_password'); define('DB_HOST', 'localhost'); define('DB_CHARSET', 'utf8mb4'); 2.2 开发工具与资源准备 代码编辑器:VS Code、PHPStorm或Sublime Text 版本控制:Git和GitHub/GitLab账户 调试工具:Query Monitor、Debug Bar插件 API测试工具:Postman或Insomnia 2.3 创建自定义插件框架 我们将创建一个独立插件来实现所有功能,避免修改主题文件: <?php /** * Plugin Name: 多平台内容分发系统 * Plugin URI: https://yourwebsite.com/ * Description: 实现WordPress内容一键分发到多个平台 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('MPCD_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('MPCD_PLUGIN_URL', plugin_dir_url(__FILE__)); define('MPCD_VERSION', '1.0.0'); // 初始化插件 class MultiPlatformContentDistributor { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->init_hooks(); } private function init_hooks() { // 插件激活时执行 register_activation_hook(__FILE__, array($this, 'activate')); // 插件停用时执行 register_deactivation_hook(__FILE__, array($this, 'deactivate')); // 初始化 add_action('init', array($this, 'init')); // 管理界面 add_action('admin_menu', array($this, 'add_admin_menu')); // 文章发布钩子 add_action('publish_post', array($this, 'on_post_publish'), 10, 2); } public function activate() { // 创建必要的数据库表 $this->create_database_tables(); // 设置默认选项 $this->set_default_options(); // 刷新重写规则 flush_rewrite_rules(); } public function deactivate() { // 清理临时数据 // 注意:不删除用户设置和发布记录 flush_rewrite_rules(); } public function init() { // 加载文本域 load_plugin_textdomain('mpcd', false, dirname(plugin_basename(__FILE__)) . '/languages'); } // 其他方法将在后续章节实现 } // 启动插件 MultiPlatformContentDistributor::get_instance(); ?> 第三部分:实现多平台API连接 3.1 平台API配置管理 我们需要创建一个管理界面来配置各个平台的API密钥和设置: // 在MultiPlatformContentDistributor类中添加以下方法 public function add_admin_menu() { add_menu_page( '多平台分发设置', '内容分发', 'manage_options', 'mpcd-settings', array($this, 'render_settings_page'), 'dashicons-share', 30 ); add_submenu_page( 'mpcd-settings', '平台配置', '平台配置', 'manage_options', 'mpcd-platforms', array($this, 'render_platforms_page') ); add_submenu_page( 'mpcd-settings', '发布记录', '发布记录', 'manage_options', 'mpcd-logs', array($this, 'render_logs_page') ); } public function render_settings_page() { ?> <div class="wrap"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> <div class="mpcd-dashboard"> <div class="mpcd-stats"> <h2>分发统计</h2> <div class="stats-grid"> <div class="stat-card"> <h3>今日发布</h3> <p class="stat-number"><?php echo $this->get_today_posts_count(); ?></p> </div> <div class="stat-card"> <h3>总发布数</h3> <p class="stat-number"><?php echo $this->get_total_posts_count(); ?></p> </div> <div class="stat-card"> <h3>平台覆盖</h3> <p class="stat-number"><?php echo $this->get_platforms_count(); ?></p> </div> </div> </div> <div class="mpcd-quick-actions"> <h2>快速操作</h2> <button class="button button-primary" id="mpcd-test-all">测试所有平台连接</button> <button class="button" id="mpcd-clear-logs">清理旧日志</button> </div> </div> </div> <style> .mpcd-stats .stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin: 20px 0; } .stat-card { background: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .stat-number { font-size: 2em; font-weight: bold; color: #2271b1; margin: 10px 0 0; } </style> <script> jQuery(document).ready(function($) { $('#mpcd-test-all').on('click', function() { // AJAX测试所有平台连接 }); }); </script> <?php } 3.2 主流平台API集成示例 以下是几个主流平台的API集成示例: // 平台API基类 abstract class MPCD_Platform { protected $platform_name; protected $api_key; protected $api_secret; protected $access_token; abstract public function authenticate(); abstract public function publish($content); abstract public function test_connection(); public function __construct($config) { $this->api_key = $config['api_key'] ?? ''; $this->api_secret = $config['api_secret'] ?? ''; $this->access_token = $config['access_token'] ?? ''; } } // 微信公众号平台实现 class MPCD_WeChat extends MPCD_Platform { protected $platform_name = 'wechat'; public function authenticate() { // 微信公众号OAuth认证流程 $auth_url = 'https://api.weixin.qq.com/cgi-bin/token'; $params = array( 'grant_type' => 'client_credential', 'appid' => $this->api_key, 'secret' => $this->api_secret ); $response = wp_remote_get(add_query_arg($params, $auth_url)); if (is_wp_error($response)) { return false; } $body = json_decode(wp_remote_retrieve_body($response), true); if (isset($body['access_token'])) { $this->access_token = $body['access_token']; update_option('mpcd_wechat_token', array( 'token' => $this->access_token, 'expires' => time() + 7000 // 微信token有效期为7200秒 )); return true; } return false; } public function publish($content) { if (!$this->access_token) { if (!$this->authenticate()) { return array('success' => false, 'message' => '认证失败'); } } $api_url = 'https://api.weixin.qq.com/cgi-bin/material/add_news?access_token=' . $this->access_token; $wechat_content = array( 'articles' => array( array( 'title' => $content['title'], 'thumb_media_id' => $this->upload_image($content['featured_image']), 'author' => $content['author'], 'digest' => wp_trim_words(strip_tags($content['content']), 100), 'show_cover_pic' => 1, 'content' => $this->format_content_for_wechat($content['content']), 'content_source_url' => $content['url'] ) ) ); $response = wp_remote_post($api_url, array( 'body' => json_encode($wechat_content, JSON_UNESCAPED_UNICODE), 'headers' => array('Content-Type' => 'application/json') )); // 处理响应 return $this->handle_response($response); } private function format_content_for_wechat($content) { // 转换WordPress内容为微信公众号格式 $content = preg_replace('/<img[^>]+>/i', '', $content); // 移除img标签 $content = strip_tags($content, '<p><br><strong><em><h1><h2><h3><h4><h5><h6>'); return $content; } } // 微博平台实现 class MPCD_Weibo extends MPCD_Platform { protected $platform_name = 'weibo'; public function publish($content) { $api_url = 'https://api.weibo.com/2/statuses/share.json'; // 微博有字数限制,需要处理 $weibo_content = $content['title'] . ' ' . $content['url']; if (mb_strlen($weibo_content) > 140) { $weibo_content = mb_substr($weibo_content, 0, 137) . '...'; } $params = array( 'access_token' => $this->access_token, 'status' => $weibo_content ); // 如果有特色图片,上传图片 if (!empty($content['featured_image'])) { $pic_id = $this->upload_pic($content['featured_image']); if ($pic_id) { $params['pic_id'] = $pic_id; } } $response = wp_remote_post($api_url, array( 'body' => $params )); return $this->handle_response($response); } } 第四部分:内容一键分发功能实现 4.1 文章发布钩子与自动分发 // 在MultiPlatformContentDistributor类中添加文章发布处理 public function on_post_publish($post_id, $post) { // 检查是否自动分发 $auto_distribute = get_option('mpcd_auto_distribute', true); if (!$auto_distribute || wp_is_post_revision($post_id)) { return; } // 检查文章类型 $allowed_types = apply_filters('mpcd_allowed_post_types', array('post')); if (!in_array($post->post_type, $allowed_types)) { return; } // 准备分发内容 $content = $this->prepare_content_for_distribution($post); // 获取启用的平台 $enabled_platforms = $this->get_enabled_platforms(); // 分发到各个平台 $results = array(); foreach ($enabled_platforms as $platform) { $platform_instance = $this->get_platform_instance($platform); if ($platform_instance) { $result = $platform_instance->publish($content); $results[$platform] = $result; // 记录日志 $this->log_distribution($post_id, $platform, $result); } } // 保存分发结果到文章meta update_post_meta($post_id, '_mpcd_distribution_results', $results); update_post_meta($post_id, '_mpcd_distributed_at', current_time('mysql')); } private function prepare_content_for_distribution($post) { $content = array( 'title' => get_the_title($post), 'content' => apply_filters('the_content', $post->post_content), 'excerpt' => $post->post_excerpt ?: wp_trim_words(strip_tags($post->post_content), 55), 'url' => get_permalink($post), 'author' => get_the_author_meta('display_name', $post->post_author), 'categories' => wp_get_post_categories($post->ID, array('fields' => 'names')), 'tags' => wp_get_post_tags($post->ID, array('fields' => 'names')), 'featured_image' => get_the_post_thumbnail_url($post, 'full') ); // 应用过滤器允许修改内容 return apply_filters('mpcd_prepared_content', $content, $post); } private function get_enabled_platforms() { $platforms = get_option('mpcd_enabled_platforms', array()); return is_array($platforms) ? $platforms : array(); } 4.2 手动分发与批量处理 // 添加文章编辑页面的分发按钮 public function add_post_distribution_meta_box() { add_meta_box( 'mpcd-distribution-box', '多平台分发', array($this, 'render_distribution_meta_box'), 'post', 'side', 'high' ); } public function render_distribution_meta_box($post) { $distributed = get_post_meta($post->ID, '_mpcd_distributed_at', true); $results = get_post_meta($post->ID, '_mpcd_distribution_results', true); ?> <div class="mpcd-distribution-controls"> <?php if ($distributed): ?> <p>已分发于: <?php echo date('Y-m-d H:i', strtotime($distributed)); ?></p> <button type="button" class="button" id="mpcd-redistribute">重新分发</button> <button type="button" class="button" id="mpcd-view-results">查看结果</button> <?php else: ?> <button type="button" class="button button-primary" id="mpcd-distribute-now">立即分发</button> <?php endif; ?> <div class="platform-selection" style="margin-top: 15px;"> <p><strong>选择平台:</strong></p> <?php $platforms = $this->get_available_platforms(); foreach ($platforms as $platform_id => $platform_name): $enabled = in_array($platform_id, $this->get_enabled_platforms()); ?> <label style="display: block; margin-bottom: 5px;"> <input type="checkbox" name="mpcd_platforms[]" value="<?php echo esc_attr($platform_id); ?>" <?php checked($enabled); ?>> <?php echo esc_html($platform_name); ?> </label> <?php endforeach; ?> </div> </div> <script> ready(function($) { $('#mpcd-distribute-now').on('click', function() { var platforms = []; $('input[name="mpcd_platforms[]"]:checked').each(function() { platforms.push($(this).val()); }); if (platforms.length === 0) { alert('请至少选择一个平台'); return; } $(this).prop('disabled', true).text('分发中...'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'mpcd_distribute_post', post_id: <?php echo $post->ID; ?>, platforms: platforms, nonce: '<?php echo wp_create_nonce("mpcd_distribute_{$post->ID}"); ?>' }, success: function(response) { if (response.success) { alert('分发成功!'); location.reload(); } else { alert('分发失败: ' + response.data); } }, error: function() { alert('请求失败,请检查网络连接'); } }); }); }); </script> <?php } // AJAX处理分发请求public function handle_ajax_distribution() { check_ajax_referer('mpcd_distribute_' . $_POST['post_id'], 'nonce'); $post_id = intval($_POST['post_id']); $platforms = isset($_POST['platforms']) ? (array)$_POST['platforms'] : array(); if (!current_user_can('edit_post', $post_id)) { wp_die('权限不足'); } $post = get_post($post_id); if (!$post) { wp_die('文章不存在'); } $content = $this->prepare_content_for_distribution($post); $results = array(); foreach ($platforms as $platform_id) { $platform_instance = $this->get_platform_instance($platform_id); if ($platform_instance) { $result = $platform_instance->publish($content); $results[$platform_id] = $result; $this->log_distribution($post_id, $platform_id, $result); } } update_post_meta($post_id, '_mpcd_distribution_results', $results); update_post_meta($post_id, '_mpcd_distributed_at', current_time('mysql')); wp_send_json_success('分发完成'); } ## 第五部分:集成常用互联网小工具 ### 5.1 智能内容摘要生成器 class MPCD_Content_Summarizer { public static function generate_summary($content, $max_length = 200) { // 移除HTML标签 $plain_text = wp_strip_all_tags($content); // 如果内容已经足够短,直接返回 if (mb_strlen($plain_text) <= $max_length) { return $plain_text; } // 尝试查找文章摘要段落 $summary = self::extract_summary_paragraph($plain_text, $max_length); // 如果没有找到合适的段落,使用智能截取 if (empty($summary)) { $summary = self::smart_truncate($plain_text, $max_length); } return apply_filters('mpcd_generated_summary', $summary, $content, $max_length); } private static function extract_summary_paragraph($text, $max_length) { // 分割成段落 $paragraphs = preg_split('/ns*n/', $text); foreach ($paragraphs as $paragraph) { $paragraph = trim($paragraph); $length = mb_strlen($paragraph); // 寻找长度合适的段落(在最大长度的60%-100%之间) if ($length >= $max_length * 0.6 && $length <= $max_length) { return $paragraph; } } // 如果没有合适长度的段落,找第一个较长的段落并截取 foreach ($paragraphs as $paragraph) { $paragraph = trim($paragraph); if (mb_strlen($paragraph) > 50) { return self::smart_truncate($paragraph, $max_length); } } return ''; } private static function smart_truncate($text, $max_length) { // 在句子边界处截断 $sentences = preg_split('/(?<=[。!?.!?])s+/u', $text); $result = ''; foreach ($sentences as $sentence) { if (mb_strlen($result . $sentence) > $max_length) { break; } $result .= $sentence; } // 如果没有完整的句子,在词边界处截断 if (empty($result)) { $result = mb_substr($text, 0, $max_length - 3); // 避免截断在单词中间 $last_space = mb_strrpos($result, ' '); if ($last_space > $max_length * 0.7) { $result = mb_substr($result, 0, $last_space); } $result .= '...'; } return $result; } } // 集成到分发系统中add_filter('mpcd_prepared_content', function($content, $post) { if (empty($content['excerpt']) || strlen($content['excerpt']) < 50) { $content['excerpt'] = MPCD_Content_Summarizer::generate_summary( $content['content'], 150 ); } return $content; }, 10, 2); ### 5.2 多平台发布计划器 class MPCD_Scheduler { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action('mpcd_scheduled_distribution', array($this, 'process_scheduled_distribution'), 10, 2); add_action('init', array($this, 'schedule_cron_jobs')); } public function schedule_cron_jobs() { if (!wp_next_scheduled('mpcd_hourly_distribution_check')) { wp_schedule_event(time(), 'hourly', 'mpcd_hourly_distribution_check'); } add_action('mpcd_hourly_distribution_check', array($this, 'check_scheduled_distributions')); } public function check_scheduled_distributions() { global $wpdb; $table_name = $wpdb->prefix . 'mpcd_schedules'; $scheduled = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table_name} WHERE scheduled_time <= %s AND status = 'pending' LIMIT 10", current_time('mysql') ) ); foreach ($scheduled as $schedule) { $this->process_schedule($schedule); } } public function process_schedule($schedule) { global $wpdb; $table_name = $wpdb->prefix . 'mpcd_schedules'; // 更新状态为处理中 $wpdb->update( $table_name, array('status' => 'processing'), array('id' => $schedule->id) ); $post = get_post($schedule->post_id); if (!$post) { $wpdb->update( $table_name, array( 'status' => 'failed', 'error_message' => '文章不存在' ), array('id' => $schedule->id) ); return; } // 准备内容 $distributor = MultiPlatformContentDistributor::get_instance(); $content = $distributor->prepare_content_for_distribution($post); // 分发到指定平台 $platforms = maybe_unserialize($schedule->platforms); $results = array(); foreach ($platforms as $platform_id) { $platform_instance = $distributor->get_platform_instance($platform_id); if ($platform_instance) { $result = $platform_instance->publish($content); $results[$platform_id] = $result; $distributor->log_distribution($schedule->post_id, $platform_id, $result); } } // 更新状态 $wpdb->update( $table_name, array( 'status' => 'completed', 'completed_at' => current_time('mysql'), 'results' => maybe_serialize($results) ), array('id' => $schedule->id) ); // 更新文章meta update_post_meta($schedule->post_id, '_mpcd_scheduled_distribution', $results); } public function create_schedule($post_id, $platforms, $scheduled_time) { global $wpdb; $table_name = $wpdb->prefix . 'mpcd_schedules'; return $wpdb->insert( $table_name, array( 'post_id' => $post_id, 'platforms' => maybe_serialize($platforms), 'scheduled_time' => $scheduled_time, 'status' => 'pending', 'created_at' => current_time('mysql') ) ); } public function get_schedules($args = array()) { global $wpdb; $defaults = array( 'post_id' => null, 'status' => null, 'limit' => 50, 'offset' => 0 ); $args = wp_parse_args($args, $defaults); $table_name = $wpdb->prefix . 'mpcd_schedules'; $where = array('1=1'); $params = array(); if ($args['post_id']) { $where[] = 'post_id = %d'; $params[] = $args['post_id']; } if ($args['status']) { $where[] = 'status = %s'; $params[] = $args['status']; } $where_clause = implode(' AND ', $where); $query = "SELECT * FROM {$table_name} WHERE {$where_clause} ORDER BY scheduled_time DESC LIMIT %d OFFSET %d"; $params[] = $args['limit']; $params[] = $args['offset']; if (!empty($params)) { $query = $wpdb->prepare($query, $params); } return $wpdb->get_results($query); } } ### 5.3 发布效果分析面板 class MPCD_Analytics { public static function get_distribution_stats($period = '7days') { global $wpdb; $logs_table = $wpdb->prefix . 'mpcd_distribution_logs'; // 根据时间段设置日期范围 $date_range = self::get_date_range($period); $stats = array( 'total_posts' => 0, 'successful' => 0, 'failed' => 0, 'by_platform' => array(), 'by_day' => array() ); // 获取总体统计 $overall_stats = $wpdb->get_row( $wpdb->prepare( "SELECT COUNT(*) as total, SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful, SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as failed FROM {$logs_table} WHERE distributed_at >= %s AND distributed_at <= %s", $date_range['start'], $date_range['end'] ) ); if ($overall_stats) { $stats['total_posts'] = intval($overall_stats->total); $stats['successful'] = intval($overall_stats->successful); $stats['failed'] = intval($overall_stats->failed); } // 按平台统计 $platform_stats = $wpdb->get_results( $wpdb->prepare( "SELECT platform, COUNT(*) as total, SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful FROM {$logs_table} WHERE distributed_at >= %s AND distributed_at <= %s GROUP BY platform ORDER BY total DESC", $date_range['start'], $date_range['end'] ) ); foreach ($platform_stats as $platform_stat) { $stats['by_platform'][$platform_stat->platform] = array( 'total' => intval($platform_stat->total), 'successful' => intval($platform_stat->successful), 'success_rate' => $platform_stat->total > 0 ? round($platform_stat->successful / $platform_stat->total * 100, 1) : 0 ); } // 按日期统计 $daily_stats = $wpdb->get_results( $wpdb->prepare( "SELECT DATE(distributed_at) as date, COUNT(*) as total, SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful FROM {$logs_table} WHERE distributed_at >= %s AND distributed_at <= %s GROUP BY DATE(distributed_at) ORDER BY date", $date_range['start'], $date_range['end'] ) ); foreach ($daily_stats as $daily_stat) { $stats['by_day'][$daily_stat->date] = array( 'total' => intval($daily_stat->total), 'successful' => intval($daily_stat->successful) ); } return $stats; } public static function get_top_performing_posts($limit = 10) { global $wpdb; $logs_table = $wpdb->prefix . 'mpcd_distribution_logs'; $query = $wpdb->prepare( "SELECT post_id, COUNT(*) as total_distributions, SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful_distributions FROM {$logs_table} WHERE post_id IS NOT NULL GROUP BY post_id ORDER BY total_distributions DESC LIMIT %d", $limit ); $results = $wpdb->get_results($query); $posts = array(); foreach ($results as $result) { $post = get_post($result->post_id); if ($post) { $posts[] = array( 'post' => $post, 'stats' => array( 'total' => intval($result->total_distributions), 'successful' => intval($result->successful_distributions), 'success_rate' => $result->total_distributions > 0 ? round($result->successful_distributions / $result->total_distributions * 100, 1) : 0 ) ); } } return $posts; } private static function get_date_range($period) { $end_date = current_time('mysql'); switch ($period) { case 'today': $start_date = date('Y-m-d 00:00:00', strtotime($end_date)); break; case 'yesterday': $start_date = date('Y-m-d 00:00:00', strtotime('-1 day', strtotime($end_date))); $end_date = date('Y-m-d 23:59:59', strtotime('-1 day', strtotime($end_date))); break; case '7days': $start_date = date('Y-m-d 00:00:00', strtotime('-7 days', strtotime($end_date))); break; case '30days': $start_date = date('Y-m-d 00:00:00', strtotime('-30 days', strtotime($end_date))); break; default: $start_date = date('Y-m-d 00:00:00', strtotime('-7 days', strtotime($end_date))); } return array( 'start' => $start_date, 'end' => $end_date ); } } ## 第六部分:高级功能与优化 ### 6.1 内容格式自动转换器 class MPCD_Content_Transformer { public static function transform_for_platform($content, $platform) { $transformers = array( 'wechat' => array('self', 'transform_for_wechat'), 'weibo' => array('self', 'transform_for_weibo'), 'zhihu' => array('self', 'transform_for_zhihu'), 'toutiao' => array('self', 'transform_for_toutiao'), 'default' => array('self', 'transform_default') ); $transformer = isset($transformers[$platform]) ? $transformers[$platform] : $transformers['default']; return call_user_func($transformer, $content); } private static function transform_for_wechat($content) { // 微信公众号特殊处理 $transformed = $content; // 移除所有外部链接(微信不允许) $transformed['content'] = preg_replace( '/<as[^>]*href=["'](?!https?://mp.weixin.qq.com)[^"']*["'][^>]*>(.*?)</a>/i', '$1', $transformed['content'] ); // 转换图片为微信格式 $transformed['content'] = preg_replace_callback( '/<imgs[^>]*src=["']([^"']+)["'][^>]*>/i', array('self', 'convert_image_for_wechat'), $transformed['content'] ); //
发表评论详细教程:为WordPress网站集成电子签名与合同管理工具 引言:为什么WordPress网站需要电子签名功能 在数字化时代,电子签名已成为商业活动中不可或缺的一环。无论是自由职业者与客户签订服务协议,还是企业与员工签署劳动合同,电子签名都能显著提高效率、降低成本并确保法律合规性。对于WordPress网站所有者而言,集成电子签名与合同管理功能可以: 提升专业形象:为客户提供完整的在线签约体验 简化工作流程:自动化合同创建、发送、签署和存档过程 增强安全性:确保合同文件的完整性和不可否认性 节省成本:减少纸质合同打印、邮寄和存储费用 提高转化率:减少客户签约过程中的摩擦点 本教程将指导您通过WordPress代码二次开发,为您的网站集成完整的电子签名与合同管理系统,而无需依赖昂贵的第三方插件。 第一部分:准备工作与环境配置 1.1 系统要求与工具准备 在开始开发之前,请确保您的环境满足以下要求: WordPress 5.0或更高版本 PHP 7.4或更高版本(推荐8.0+) MySQL 5.6或更高版本 文本编辑器(如VS Code、Sublime Text等) FTP客户端或服务器文件管理器访问权限 基本的PHP、JavaScript和WordPress开发知识 1.2 创建开发子主题 为了避免直接修改主题文件导致更新时丢失更改,我们首先创建一个子主题: 在wp-content/themes/目录下创建新文件夹my-esignature-theme 创建style.css文件,添加以下内容: /* Theme Name: My eSignature Theme Template: your-parent-theme-folder-name Version: 1.0.0 Description: 子主题,用于集成电子签名功能 */ 创建functions.php文件,添加以下代码: <?php // 子主题functions.php add_action('wp_enqueue_scripts', 'my_esignature_theme_enqueue_styles'); function my_esignature_theme_enqueue_styles() { wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css'); } ?> 在WordPress后台激活此子主题 1.3 创建插件目录结构 我们将创建一个独立的插件来管理电子签名功能: wp-content/plugins/my-esignature-system/ ├── my-esignature-system.php ├── includes/ │ ├── class-database.php │ ├── class-contract-manager.php │ ├── class-signature-handler.php │ └── class-pdf-generator.php ├── assets/ │ ├── css/ │ ├── js/ │ └── images/ ├── templates/ │ ├── contract-form.php │ ├── signature-pad.php │ └── contract-view.php └── vendor/ (第三方库) 第二部分:数据库设计与合同存储 2.1 创建数据库表 在includes/class-database.php中,我们将创建必要的数据库表: <?php class ESIGN_Database { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action('init', array($this, 'create_tables')); } public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_prefix = $wpdb->prefix . 'esign_'; // 合同表 $contracts_table = $table_prefix . 'contracts'; $sql1 = "CREATE TABLE IF NOT EXISTS $contracts_table ( id INT(11) NOT NULL AUTO_INCREMENT, contract_id VARCHAR(50) NOT NULL UNIQUE, title VARCHAR(255) NOT NULL, content LONGTEXT NOT NULL, status ENUM('draft', 'sent', 'signed', 'expired', 'cancelled') DEFAULT 'draft', created_by INT(11) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, expires_at DATETIME, PRIMARY KEY (id), INDEX (contract_id), INDEX (status), INDEX (created_by) ) $charset_collate;"; // 签署方表 $parties_table = $table_prefix . 'parties'; $sql2 = "CREATE TABLE IF NOT EXISTS $parties_table ( id INT(11) NOT NULL AUTO_INCREMENT, contract_id VARCHAR(50) NOT NULL, user_id INT(11), email VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, role ENUM('sender', 'signer', 'witness', 'approver') DEFAULT 'signer', signing_order INT(3) DEFAULT 1, signature_data TEXT, signed_at DATETIME, ip_address VARCHAR(45), user_agent TEXT, PRIMARY KEY (id), INDEX (contract_id), INDEX (email), FOREIGN KEY (contract_id) REFERENCES $contracts_table(contract_id) ON DELETE CASCADE ) $charset_collate;"; // 审计日志表 $audit_table = $table_prefix . 'audit_log'; $sql3 = "CREATE TABLE IF NOT EXISTS $audit_log_table ( id INT(11) NOT NULL AUTO_INCREMENT, contract_id VARCHAR(50) NOT NULL, action VARCHAR(100) NOT NULL, details TEXT, performed_by INT(11), performed_at DATETIME DEFAULT CURRENT_TIMESTAMP, ip_address VARCHAR(45), PRIMARY KEY (id), INDEX (contract_id), INDEX (performed_at) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql1); dbDelta($sql2); dbDelta($sql3); } // 生成唯一合同ID public function generate_contract_id() { return 'CONTRACT-' . date('Ymd') . '-' . strtoupper(wp_generate_password(8, false)); } } ?> 2.2 合同数据模型 在includes/class-contract-manager.php中,创建合同管理类: <?php class ESIGN_Contract_Manager { private $db; public function __construct() { $this->db = ESIGN_Database::get_instance(); } // 创建新合同 public function create_contract($data) { global $wpdb; $contract_id = $this->db->generate_contract_id(); $current_user_id = get_current_user_id(); $contract_data = array( 'contract_id' => $contract_id, 'title' => sanitize_text_field($data['title']), 'content' => wp_kses_post($data['content']), 'created_by' => $current_user_id, 'status' => 'draft', 'expires_at' => !empty($data['expires_at']) ? $data['expires_at'] : null ); $table_name = $wpdb->prefix . 'esign_contracts'; $result = $wpdb->insert($table_name, $contract_data); if ($result) { // 添加签署方 if (!empty($data['parties'])) { $this->add_parties($contract_id, $data['parties']); } // 记录审计日志 $this->log_audit($contract_id, 'contract_created', '合同创建'); return $contract_id; } return false; } // 添加签署方 private function add_parties($contract_id, $parties) { global $wpdb; $table_name = $wpdb->prefix . 'esign_parties'; foreach ($parties as $index => $party) { $party_data = array( 'contract_id' => $contract_id, 'email' => sanitize_email($party['email']), 'name' => sanitize_text_field($party['name']), 'role' => sanitize_text_field($party['role']), 'signing_order' => $index + 1 ); $wpdb->insert($table_name, $party_data); } } // 获取合同详情 public function get_contract($contract_id) { global $wpdb; $contracts_table = $wpdb->prefix . 'esign_contracts'; $parties_table = $wpdb->prefix . 'esign_parties'; $contract = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $contracts_table WHERE contract_id = %s", $contract_id )); if ($contract) { $contract->parties = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $parties_table WHERE contract_id = %s ORDER BY signing_order ASC", $contract_id )); } return $contract; } // 记录审计日志 private function log_audit($contract_id, $action, $details = '') { global $wpdb; $table_name = $wpdb->prefix . 'esign_audit_log'; $log_data = array( 'contract_id' => $contract_id, 'action' => $action, 'details' => $details, 'performed_by' => get_current_user_id(), 'ip_address' => $this->get_client_ip() ); $wpdb->insert($table_name, $log_data); } // 获取客户端IP private function get_client_ip() { $ipaddress = ''; if (isset($_SERVER['HTTP_CLIENT_IP'])) $ipaddress = $_SERVER['HTTP_CLIENT_IP']; else if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR']; else if(isset($_SERVER['HTTP_X_FORWARDED'])) $ipaddress = $_SERVER['HTTP_X_FORWARDED']; else if(isset($_SERVER['HTTP_FORWARDED_FOR'])) $ipaddress = $_SERVER['HTTP_FORWARDED_FOR']; else if(isset($_SERVER['HTTP_FORWARDED'])) $ipaddress = $_SERVER['HTTP_FORWARDED']; else if(isset($_SERVER['REMOTE_ADDR'])) $ipaddress = $_SERVER['REMOTE_ADDR']; else $ipaddress = 'UNKNOWN'; return $ipaddress; } } ?> 第三部分:电子签名功能实现 3.1 签名画板实现 创建templates/signature-pad.php模板文件: <div class="esignature-container"> <div class="signature-pad-wrapper"> <h3>请在下方签署您的名字</h3> <div class="signature-pad-container"> <canvas id="signature-pad" width="600" height="200"></canvas> </div> <div class="signature-actions"> <button type="button" id="clear-signature" class="button button-secondary"> <span class="dashicons dashicons-trash"></span> 清除 </button> <button type="button" id="undo-signature" class="button button-secondary"> <span class="dashicons dashicons-undo"></span> 撤销 </button> <button type="button" id="save-signature" class="button button-primary"> <span class="dashicons dashicons-yes-alt"></span> 确认签名 </button> </div> <div class="signature-preview" style="display:none;"> <h4>签名预览:</h4> <img id="signature-preview" src="" alt="签名预览" /> </div> <div class="signature-consent"> <label> <input type="checkbox" id="signature-consent" required> 我确认此电子签名具有法律约束力,并同意电子签名条款 </label> </div> </div> </div> 3.2 签名画板JavaScript实现 创建assets/js/signature-pad.js: (function($) { 'use strict'; class SignaturePad { constructor(canvasId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.signatureData = null; this.isDrawing = false; this.lastX = 0; this.lastY = 0; this.init(); } init() { // 设置画布样式 this.ctx.strokeStyle = '#000'; this.ctx.lineWidth = 2; this.ctx.lineCap = 'round'; this.ctx.lineJoin = 'round'; // 绑定事件 this.bindEvents(); // 清除画布 this.clear(); } bindEvents() { const canvas = this.canvas; // 鼠标事件 canvas.addEventListener('mousedown', (e) => this.startDrawing(e)); canvas.addEventListener('mousemove', (e) => this.draw(e)); canvas.addEventListener('mouseup', () => this.stopDrawing()); canvas.addEventListener('mouseout', () => this.stopDrawing()); // 触摸事件(移动设备支持) canvas.addEventListener('touchstart', (e) => { e.preventDefault(); const touch = e.touches[0]; const mouseEvent = new MouseEvent('mousedown', { clientX: touch.clientX, clientY: touch.clientY }); canvas.dispatchEvent(mouseEvent); }); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); const touch = e.touches[0]; const mouseEvent = new MouseEvent('mousemove', { clientX: touch.clientX, clientY: touch.clientY }); canvas.dispatchEvent(mouseEvent); }); canvas.addEventListener('touchend', (e) => { e.preventDefault(); const mouseEvent = new MouseEvent('mouseup', {}); canvas.dispatchEvent(mouseEvent); }); } startDrawing(e) { this.isDrawing = true; const rect = this.canvas.getBoundingClientRect(); this.lastX = e.clientX - rect.left; this.lastY = e.clientY - rect.top; } draw(e) { if (!this.isDrawing) return; const rect = this.canvas.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; this.ctx.beginPath(); this.ctx.moveTo(this.lastX, this.lastY); this.ctx.lineTo(currentX, currentY); this.ctx.stroke(); this.lastX = currentX; this.lastY = currentY; } stopDrawing() { this.isDrawing = false; this.saveSignature(); } saveSignature() { this.signatureData = this.canvas.toDataURL('image/png'); } clear() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.fillStyle = '#f8f9fa'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); this.signatureData = null; } undo() { // 简单实现:清除整个画布 this.clear(); } getSignatureData() { return this.signatureData; } setSignatureData(data) { const img = new Image(); img.onload = () => { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.drawImage(img, 0, 0); this.signatureData = data; }; img.src = data; } } // 初始化签名板 $(document).ready(function() { if ($('#signature-pad').length) { window.signaturePad = new SignaturePad('signature-pad'); // 绑定按钮事件 $('#clear-signature').on('click', function() { signaturePad.clear(); $('#signature-preview').hide(); }); $('#undo-signature').on('click', function() { signaturePad.undo(); }); $('#save-signature').on('click', function() { const signatureData = signaturePad.getSignatureData(); if (signatureData) { $('#signature-preview').attr('src', signatureData).show(); $('#signature-data').val(signatureData); // 显示成功消息 $('.signature-preview').show(); alert('签名已保存!'); } else { alert('请先绘制您的签名'); } }); // 表单提交验证 $('form.esignature-form').on('submit', function(e) { if (!$('#signature-consent').is(':checked')) { e.preventDefault(); alert('请同意电子签名条款'); return false; } if (!signaturePad.getSignatureData()) { e.preventDefault(); alert('请提供您的签名'); return false; } }); } }); })(jQuery); 3.3 签名处理类 创建includes/class-signature-handler.php: <?php class ESIGN_Signature_Handler { public function __construct() { add_action('wp_ajax_submit_signature', array($this, 'handle_signature_submission')); ature_submission')); } // 处理签名提交 public function handle_signature_submission() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'esignature_nonce')) { wp_die('安全验证失败'); } $contract_id = sanitize_text_field($_POST['contract_id']); $signature_data = $_POST['signature_data']; $party_email = sanitize_email($_POST['party_email']); // 验证签名数据 if (!$this->validate_signature_data($signature_data)) { wp_send_json_error('无效的签名数据'); } // 保存签名 $result = $this->save_signature($contract_id, $party_email, $signature_data); if ($result) { // 更新合同状态 $this->update_contract_status($contract_id); // 发送通知邮件 $this->send_notification_email($contract_id, $party_email); wp_send_json_success('签名提交成功'); } else { wp_send_json_error('签名保存失败'); } } // 验证签名数据 private function validate_signature_data($signature_data) { // 检查是否为有效的base64图像数据 if (preg_match('/^data:image/(png|jpeg);base64,/', $signature_data)) { return true; } return false; } // 保存签名到数据库 private function save_signature($contract_id, $party_email, $signature_data) { global $wpdb; $table_name = $wpdb->prefix . 'esign_parties'; $result = $wpdb->update( $table_name, array( 'signature_data' => $signature_data, 'signed_at' => current_time('mysql'), 'ip_address' => $this->get_client_ip(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ), array( 'contract_id' => $contract_id, 'email' => $party_email ) ); return $result !== false; } // 更新合同状态 private function update_contract_status($contract_id) { global $wpdb; $contracts_table = $wpdb->prefix . 'esign_contracts'; $parties_table = $wpdb->prefix . 'esign_parties'; // 检查是否所有签署方都已签名 $unsigned_parties = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $parties_table WHERE contract_id = %s AND signature_data IS NULL", $contract_id )); $new_status = ($unsigned_parties == 0) ? 'signed' : 'sent'; $wpdb->update( $contracts_table, array('status' => $new_status), array('contract_id' => $contract_id) ); } // 发送通知邮件 private function send_notification_email($contract_id, $party_email) { $contract = ESIGN_Contract_Manager::get_instance()->get_contract($contract_id); $to = $party_email; $subject = '合同签署通知 - ' . $contract->title; $message = " <html> <body> <h2>合同签署通知</h2> <p>您好,</p> <p>您已成功签署合同:<strong>{$contract->title}</strong></p> <p>合同ID:{$contract_id}</p> <p>签署时间:" . date('Y-m-d H:i:s') . "</p> <p>您可以通过以下链接查看合同详情:</p> <p><a href='" . home_url("/contract-view/{$contract_id}") . "'>查看合同</a></p> <hr> <p>此邮件为系统自动发送,请勿回复。</p> </body> </html> "; $headers = array('Content-Type: text/html; charset=UTF-8'); wp_mail($to, $subject, $message, $headers); } }?> ## 第四部分:PDF生成与合同管理 ### 4.1 集成TCPDF库生成PDF 创建`includes/class-pdf-generator.php`: <?phprequire_once(plugin_dir_path(__FILE__) . '../vendor/tcpdf/tcpdf.php'); class ESIGN_PDF_Generator extends TCPDF { private $contract_data; public function __construct($contract_data) { parent::__construct(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); $this->contract_data = $contract_data; $this->init_pdf(); } private function init_pdf() { // 设置文档信息 $this->SetCreator('WordPress eSignature System'); $this->SetAuthor(get_bloginfo('name')); $this->SetTitle($this->contract_data->title); $this->SetSubject('电子合同'); // 设置默认字体 $this->SetFont('stsongstdlight', '', 12); // 设置边距 $this->SetMargins(15, 15, 15); $this->SetHeaderMargin(5); $this->SetFooterMargin(10); // 自动分页 $this->SetAutoPageBreak(TRUE, 15); // 设置图像比例因子 $this->setImageScale(PDF_IMAGE_SCALE_RATIO); } // 生成合同PDF public function generate_contract_pdf() { $this->AddPage(); // 添加合同标题 $this->SetFont('stsongstdlight', 'B', 16); $this->Cell(0, 10, $this->contract_data->title, 0, 1, 'C'); $this->Ln(10); // 添加合同信息 $this->SetFont('stsongstdlight', '', 10); $this->Cell(0, 6, '合同编号:' . $this->contract_data->contract_id, 0, 1); $this->Cell(0, 6, '创建日期:' . $this->contract_data->created_at, 0, 1); $this->Cell(0, 6, '状态:' . $this->get_status_text($this->contract_data->status), 0, 1); $this->Ln(10); // 添加合同内容 $this->SetFont('stsongstdlight', '', 12); $this->writeHTML($this->contract_data->content, true, false, true, false, ''); $this->Ln(20); // 添加签署方信息 $this->add_signature_section(); // 添加审计信息 $this->add_audit_info(); return $this; } // 添加签署方部分 private function add_signature_section() { $this->SetFont('stsongstdlight', 'B', 14); $this->Cell(0, 10, '签署方信息', 0, 1); $this->Ln(5); $this->SetFont('stsongstdlight', '', 11); foreach ($this->contract_data->parties as $index => $party) { $this->Cell(0, 6, '签署方 ' . ($index + 1) . ':', 0, 1); $this->Cell(40, 6, '姓名:', 0, 0); $this->Cell(0, 6, $party->name, 0, 1); $this->Cell(40, 6, '邮箱:', 0, 0); $this->Cell(0, 6, $party->email, 0, 1); $this->Cell(40, 6, '角色:', 0, 0); $this->Cell(0, 6, $this->get_role_text($party->role), 0, 1); if ($party->signature_data) { $this->Cell(40, 6, '签署时间:', 0, 0); $this->Cell(0, 6, $party->signed_at, 0, 1); // 添加签名图像 $signature_data = $party->signature_data; $temp_file = $this->save_signature_image($signature_data); if ($temp_file) { $this->Image($temp_file, 50, $this->GetY(), 40, 20); $this->Ln(25); unlink($temp_file); // 删除临时文件 } } $this->Ln(10); } } // 保存签名图像到临时文件 private function save_signature_image($signature_data) { $upload_dir = wp_upload_dir(); $temp_dir = $upload_dir['basedir'] . '/esignature_temp/'; if (!file_exists($temp_dir)) { wp_mkdir_p($temp_dir); } $temp_file = $temp_dir . uniqid('sig_') . '.png'; // 移除base64前缀 $signature_data = preg_replace('/^data:image/w+;base64,/', '', $signature_data); $signature_data = base64_decode($signature_data); if (file_put_contents($temp_file, $signature_data)) { return $temp_file; } return false; } // 添加审计信息 private function add_audit_info() { global $wpdb; $audit_table = $wpdb->prefix . 'esign_audit_log'; $audit_logs = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $audit_table WHERE contract_id = %s ORDER BY performed_at DESC LIMIT 10", $this->contract_data->contract_id )); if ($audit_logs) { $this->SetFont('stsongstdlight', 'B', 12); $this->Cell(0, 10, '操作记录', 0, 1); $this->Ln(5); $this->SetFont('stsongstdlight', '', 9); foreach ($audit_logs as $log) { $user = get_userdata($log->performed_by); $username = $user ? $user->display_name : '系统'; $this->Cell(0, 5, date('Y-m-d H:i:s', strtotime($log->performed_at)) . ' - ' . $username . ' - ' . $log->action . ' - ' . $log->details, 0, 1); } } } // 获取状态文本 private function get_status_text($status) { $status_map = array( 'draft' => '草稿', 'sent' => '已发送', 'signed' => '已签署', 'expired' => '已过期', 'cancelled' => '已取消' ); return isset($status_map[$status]) ? $status_map[$status] : $status; } // 获取角色文本 private function get_role_text($role) { $role_map = array( 'sender' => '发起方', 'signer' => '签署方', 'witness' => '见证方', 'approver' => '审批方' ); return isset($role_map[$role]) ? $role_map[$role] : $role; } // 输出PDF到浏览器 public function output_pdf($filename = 'contract.pdf') { $this->Output($filename, 'I'); } // 保存PDF到服务器 public function save_pdf($filename) { $this->Output($filename, 'F'); return file_exists($filename); } }?> ### 4.2 合同管理界面 创建`templates/contract-manager.php`: <?php/** 合同管理界面模板 */ if (!defined('ABSPATH')) { exit; } global $wpdb;$current_user_id = get_current_user_id(); // 获取用户相关的合同$contracts_table = $wpdb->prefix . 'esign_contracts';$parties_table = $wpdb->prefix . 'esign_parties'; $contracts = $wpdb->get_results($wpdb->prepare( "SELECT c.* FROM $contracts_table c LEFT JOIN $parties_table p ON c.contract_id = p.contract_id WHERE c.created_by = %d OR p.email = %s GROUP BY c.id ORDER BY c.created_at DESC", $current_user_id, wp_get_current_user()->user_email ));?> <div class="esignature-manager-container"> <div class="esignature-header"> <h1>合同管理</h1> <button id="create-new-contract" class="button button-primary"> <span class="dashicons dashicons-plus"></span> 创建新合同 </button> </div> <!-- 合同筛选 --> <div class="contract-filters"> <select id="status-filter"> <option value="">所有状态</option> <option value="draft">草稿</option> <option value="sent">已发送</option> <option value="signed">已签署</option> <option value="expired">已过期</option> </select> <input type="text" id="search-contracts" placeholder="搜索合同标题或ID..."> <button id="apply-filters" class="button">筛选</button> <button id="reset-filters" class="button button-secondary">重置</button> </div> <!-- 合同列表 --> <div class="contracts-list"> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>合同标题</th> <th>合同ID</th> <th>状态</th> <th>创建时间</th> <th>过期时间</th> <th>操作</th> </tr> </thead> <tbody> <?php if ($contracts): ?> <?php foreach ($contracts as $contract): ?> <?php $parties = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $parties_table WHERE contract_id = %s", $contract->contract_id )); ?> <tr data-contract-id="<?php echo esc_attr($contract->contract_id); ?>"> <td> <strong><?php echo esc_html($contract->title); ?></strong> <div class="row-actions"> <span class="view"> <a href="#" class="view-contract" data-id="<?php echo esc_attr($contract->contract_id); ?>">查看</a> | </span> <span class="edit"> <a href="#" class="edit-contract" data-id="<?php echo esc_attr($contract->contract_id); ?>">编辑</a> | </span> <span class="delete"> <a href="#" class="delete-contract" data-id="<?php echo esc_attr($contract->contract_id); ?>">删除</a> </span> </div> </td> <td><?php echo esc_html($contract->contract_id); ?></td> <td> <span class="contract-status status-<?php echo esc_attr($contract->status); ?>"> <?php echo $this->get_status_badge($contract->status); ?> </span> </td> <td><?php echo date('Y-m-d H:i', strtotime($contract->created_at)); ?></td> <td> <?php echo $contract->expires_at ? date('Y-m-d H:i', strtotime($contract->expires_at)) : '无'; ?> </td> <td> <div class="contract-actions"> <?php if ($contract->status == 'draft'): ?> <button class="button button-small send-contract" data-id="<?php echo esc_attr($contract->contract_id); ?>"> 发送签署 </button> <?php endif; ?> <?php if ($contract->status == 'sent'): ?> <?php $user_can_sign = false; foreach ($parties as $party) { if ($party->email == wp_get_current_user()->user_email && !$party->signature_data) { $user_can_sign = true; break; } } ?> <?php if ($user_can_sign): ?> <a href="<?php echo home_url("/sign-contract/{$contract->contract_id}"); ?>" class="button button-small button-primary"> 签署合同 </a> <?php endif; ?> <?php endif; ?> <button class="button button-small download-pdf" data-id="<?php echo esc_attr($contract->contract_id); ?>"> 下载PDF </button> <button class="button button-small view-audit" data-id="<?php echo esc_attr($contract->contract_id); ?>"> 查看记录 </button> </div> </td> </tr> <?php endforeach; ?> <?php else: ?> <tr> <td colspan="6" class="no-contracts"> 暂无合同记录。点击"创建新合同"按钮开始创建。 </td> </tr> <?php endif; ?> </tbody> </table> </div> <!-- 分页 --> <div class="tablenav bottom"> <
发表评论开发指南:打造网站内嵌的在线代码编辑与运行环境 摘要 在当今数字化时代,网站功能多样化已成为吸引用户的关键因素之一。本文将详细介绍如何通过WordPress程序的二次开发,打造一个内嵌的在线代码编辑与运行环境,并实现常用互联网小工具功能。我们将从技术选型、环境搭建、核心功能实现到安全优化等方面进行全面解析,帮助开发者构建功能强大且用户友好的代码编辑平台。 一、引言:在线代码编辑环境的价值与意义 1.1 在线代码编辑器的兴起 随着云计算和Web技术的发展,在线代码编辑器逐渐成为开发者工具箱中的重要组成部分。从简单的代码片段分享到完整的集成开发环境(IDE),在线编辑器为用户提供了无需本地安装即可编写、运行和调试代码的便利。对于技术博客、教育平台或开发者社区而言,内嵌代码编辑器能极大提升用户体验和参与度。 1.2 WordPress作为开发平台的优势 WordPress作为全球最流行的内容管理系统,拥有庞大的插件生态系统和灵活的扩展能力。通过二次开发,我们可以在WordPress平台上构建专业级的代码编辑环境,同时利用其用户管理、内容发布和社区功能,打造一体化的开发者服务平台。 1.3 本文目标与结构 本文将指导读者完成以下目标: 在WordPress中集成在线代码编辑器 实现代码运行环境(支持多种编程语言) 开发常用互联网小工具(如JSON格式化、加密解密等) 确保系统的安全性与性能优化 二、技术架构与工具选型 2.1 核心编辑器选择 2.1.1 Monaco Editor Monaco Editor是Visual Studio Code的底层编辑器,功能强大且高度可定制。它支持语法高亮、代码补全、错误检查等高级功能,是构建专业代码编辑器的理想选择。 2.1.2 CodeMirror CodeMirror是一个轻量级的代码编辑器,易于集成且资源消耗较小。对于不需要完整IDE功能的场景,CodeMirror是一个优秀的选择。 2.1.3 Ace Editor Ace Editor是另一个流行的在线代码编辑器,性能优异且支持多种语言模式。 推荐方案:对于需要接近桌面IDE体验的场景,我们选择Monaco Editor;对于轻量级需求,可选择CodeMirror。 2.2 代码执行环境 2.2.1 服务器端执行方案 Docker容器:为每种语言创建独立的Docker容器,确保环境隔离和安全 语言特定沙箱:如PySandbox for Python, JS-Sandbox for JavaScript 第三方API:利用如JDoodle、Runnable等在线编译API 2.2.2 客户端执行方案 WebAssembly:将语言运行时编译为WebAssembly,在浏览器中直接执行 JavaScript解释器:对于JavaScript代码,可直接在浏览器中运行 推荐方案:结合使用客户端执行(适合简单JavaScript代码)和服务器端Docker容器(支持多种语言,安全性高)。 2.3 WordPress开发框架 2.3.1 插件开发基础 使用标准的WordPress插件结构 遵循WordPress编码标准 利用WordPress的REST API进行前后端通信 2.3.2 前端技术栈 React或Vue.js用于构建交互式UI组件 Webpack或Vite进行模块打包 Sass/Less用于样式管理 三、环境搭建与基础配置 3.1 开发环境准备 // WordPress插件基础结构 /* Plugin Name: 在线代码编辑器与工具集 Plugin URI: https://yourwebsite.com/ Description: 在WordPress中嵌入在线代码编辑器和常用开发工具 Version: 1.0.0 Author: Your Name */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('CODE_EDITOR_VERSION', '1.0.0'); define('CODE_EDITOR_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('CODE_EDITOR_PLUGIN_URL', plugin_dir_url(__FILE__)); 3.2 Monaco Editor集成 // 前端初始化Monaco Editor import * as monaco from 'monaco-editor'; class CodeEditor { constructor(containerId, language = 'javascript') { this.container = document.getElementById(containerId); this.language = language; this.editor = null; this.init(); } init() { this.editor = monaco.editor.create(this.container, { value: this.getDefaultCode(), language: this.language, theme: 'vs-dark', fontSize: 14, minimap: { enabled: true }, automaticLayout: true, scrollBeyondLastLine: false, wordWrap: 'on' }); } getDefaultCode() { const defaults = { 'javascript': 'console.log("Hello, World!");', 'python': 'print("Hello, World!")', 'html': '<!DOCTYPE html>n<html>n<head>n<title>示例</title>n</head>n<body>n<h1>Hello World</h1>n</body>n</html>', 'css': 'body {n font-family: Arial, sans-serif;n}', 'php': '<?phpnecho "Hello, World!";n?>' }; return defaults[this.language] || '// 输入你的代码'; } getCode() { return this.editor.getValue(); } setCode(code) { this.editor.setValue(code); } changeLanguage(language) { const model = this.editor.getModel(); monaco.editor.setModelLanguage(model, language); this.language = language; } } 3.3 Docker执行环境配置 # docker-compose.yml 配置示例 version: '3.8' services: code-executor: build: ./executor container_name: code_executor restart: unless-stopped networks: - code_network volumes: - ./code-temp:/tmp/code environment: - MAX_EXECUTION_TIME=10 - MAX_MEMORY=256m # 不同语言的执行器 python-executor: image: python:3.9-slim container_name: python_executor command: tail -f /dev/null networks: - code_network node-executor: image: node:16-alpine container_name: node_executor command: tail -f /dev/null networks: - code_network networks: code_network: driver: bridge 四、核心功能实现 4.1 多语言代码编辑器 // WordPress短代码实现编辑器嵌入 add_shortcode('code_editor', 'code_editor_shortcode'); function code_editor_shortcode($atts) { $atts = shortcode_atts(array( 'language' => 'javascript', 'height' => '400px', 'theme' => 'vs-dark', 'readonly' => false ), $atts); // 生成唯一ID $editor_id = 'code-editor-' . uniqid(); // 加载必要资源 wp_enqueue_script('monaco-editor', 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js'); wp_enqueue_style('code-editor-style', CODE_EDITOR_PLUGIN_URL . 'assets/css/editor.css'); ob_start(); ?> <div class="code-editor-container"> <div class="editor-toolbar"> <select class="language-selector"> <option value="javascript" <?php selected($atts['language'], 'javascript'); ?>>JavaScript</option> <option value="python" <?php selected($atts['language'], 'python'); ?>>Python</option> <option value="html" <?php selected($atts['language'], 'html'); ?>>HTML</option> <option value="css" <?php selected($atts['language'], 'css'); ?>>CSS</option> <option value="php" <?php selected($atts['language'], 'php'); ?>>PHP</option> <option value="java" <?php selected($atts['language'], 'java'); ?>>Java</option> </select> <button class="run-code-btn">运行代码</button> <button class="save-code-btn">保存代码</button> </div> <div id="<?php echo esc_attr($editor_id); ?>" class="code-editor" data-language="<?php echo esc_attr($atts['language']); ?>" data-theme="<?php echo esc_attr($atts['theme']); ?>" style="height: <?php echo esc_attr($atts['height']); ?>;"> </div> <div class="execution-result"> <h4>运行结果:</h4> <div class="result-output"></div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // 初始化编辑器 const editorId = '<?php echo $editor_id; ?>'; const initLanguage = '<?php echo $atts["language"]; ?>'; // 配置Monaco Editor require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs' } }); require(['vs/editor/editor.main'], function() { const editorContainer = document.getElementById(editorId); const language = editorContainer.dataset.language; const theme = editorContainer.dataset.theme; // 创建编辑器实例 const editor = monaco.editor.create(editorContainer, { value: getDefaultCode(language), language: language, theme: theme, fontSize: 14, minimap: { enabled: true }, automaticLayout: true }); // 存储编辑器实例供后续使用 window.codeEditors = window.codeEditors || {}; window.codeEditors[editorId] = editor; // 语言切换功能 const languageSelector = editorContainer.parentElement.querySelector('.language-selector'); languageSelector.addEventListener('change', function(e) { const newLanguage = e.target.value; const model = editor.getModel(); monaco.editor.setModelLanguage(model, newLanguage); editorContainer.dataset.language = newLanguage; }); // 运行代码功能 const runButton = editorContainer.parentElement.querySelector('.run-code-btn'); runButton.addEventListener('click', function() { const code = editor.getValue(); const language = editorContainer.dataset.language; runCode(code, language, editorContainer); }); }); function getDefaultCode(language) { const defaults = { 'javascript': 'console.log("Hello, World!");n// 尝试修改并运行这段代码', 'python': 'print("Hello, World!")n# 尝试修改并运行这段代码', 'html': '<!DOCTYPE html>n<html>n<head>nt<title>示例页面</title>n</head>n<body>nt<h1>Hello World!</h1>nt<p>尝试修改并运行这段HTML代码</p>n</body>n</html>', 'css': 'body {ntfont-family: Arial, sans-serif;ntbackground-color: #f0f0f0;n}nnh1 {ntcolor: #333;n}', 'php': '<?phpntecho "Hello, World!\n";nt// 尝试修改并运行这段PHP代码n?>', 'java': 'public class Main {ntpublic static void main(String[] args) {nttSystem.out.println("Hello, World!");nt}n}' }; return defaults[language] || '// 输入你的代码'; } function runCode(code, language, container) { const outputArea = container.parentElement.querySelector('.result-output'); outputArea.innerHTML = '<div class="loading">执行中...</div>'; // 发送AJAX请求到WordPress后端 fetch('<?php echo admin_url("admin-ajax.php"); ?>', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ 'action': 'execute_code', 'code': code, 'language': language, 'nonce': '<?php echo wp_create_nonce("code_execution_nonce"); ?>' }) }) .then(response => response.json()) .then(data => { if (data.success) { outputArea.innerHTML = `<pre class="success-output">${data.data.output}</pre>`; } else { outputArea.innerHTML = `<pre class="error-output">错误: ${data.data.error}</pre>`; } }) .catch(error => { outputArea.innerHTML = `<pre class="error-output">请求失败: ${error.message}</pre>`; }); } }); </script> <?php return ob_get_clean(); } 4.2 代码执行后端处理 // WordPress AJAX处理代码执行 add_action('wp_ajax_execute_code', 'handle_code_execution'); add_action('wp_ajax_nopriv_execute_code', 'handle_code_execution'); function handle_code_execution() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'code_execution_nonce')) { wp_send_json_error(array('error' => '安全验证失败')); return; } // 获取参数 $code = isset($_POST['code']) ? stripslashes($_POST['code']) : ''; $language = isset($_POST['language']) ? sanitize_text_field($_POST['language']) : 'javascript'; if (empty($code)) { wp_send_json_error(array('error' => '代码不能为空')); return; } // 根据语言选择执行方式 $result = execute_code_safely($code, $language); if ($result['success']) { wp_send_json_success(array('output' => $result['output'])); } else { wp_send_json_error(array('error' => $result['error'])); } } function execute_code_safely($code, $language) { // 安全检查:防止危险代码 $blacklisted_patterns = array( '/systems*(/i', '/execs*(/i', '/shell_execs*(/i', '/passthrus*(/i', '/proc_opens*(/i', '/popens*(/i', '/`.*`/', '/evals*(/i', '/includes*(/i', '/requires*(/i' ); foreach ($blacklisted_patterns as $pattern) { if (preg_match($pattern, $code)) { return array( 'success' => false, 'error' => '代码包含不允许的操作' ); } } // 根据语言执行代码 switch ($language) { case 'javascript': return execute_javascript($code); case 'python': return execute_python($code); case 'php': return execute_php($code); case 'html': return execute_html($code); default: return array( 'success' => false, 'error' => '暂不支持该语言' ); } } function execute_javascript($code) { // 对于简单JavaScript,可以在服务端使用V8JS(如果安装) // 或者返回给客户端执行 // 这里我们使用一个简化的方法:返回给客户端执行的指令 return array( 'success' => true, 'output' => 'JavaScript代码将在客户端执行。注意:出于安全考虑,某些API可能受限。' ); } function execute_python($code) { // 使用Docker执行Python代码 $temp_file = tempnam(sys_get_temp_dir(), 'py_'); file_put_contents($temp_file, $code); // 使用Docker运行代码(需要Docker环境) $command = "docker run --rm -v " . dirname($temp_file) . ":/code python:3.9-slim python /code/" . basename($temp_file) . " 2>&1"; $output = shell_exec("timeout 10 " . $command); // 清理临时文件 unlink($temp_file); if ($output === null) { return array( 'success' => false, 'error' => '执行超时或出错' ); } return array( 'success' => true, 'output' => htmlspecialchars($output) ); } function execute_html($code) { // 对于HTML,我们可以返回一个可预览的iframe $html_content = $code; // 如果代码中没有完整的HTML结构,添加基本结构 if (!preg_match('/<!DOCTYPEs+html>/i', $html_content) && !preg_match('/<html>/i', $html_content)) { $html_content = '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>运行结果</title></head><body>' . $html_content . '</body></html>'; } // 编码HTML内容以便安全传输 $encoded_html = base64_encode($html_content); return array( 'success' => true, 'output' => '<iframe srcdoc="' . htmlspecialchars($html_content) . '" style="width:100%; height:300px; border:1px solid #ddd;"></iframe>' ); } 4.3 常用互联网小工具集成 // 小工具集成的短代码 add_shortcode('web_tools', 'web_tools_shortcode'); function web_tools_shortcode() { ob_start(); ?><div class="web-tools-container"> <div class="tools-tabs"> <button class="tool-tab active" data-tool="json">JSON格式化</button> <button class="tool-tab" data-tool="encrypt">加密/解密</button> <button class="tool-tab" data-tool="timestamp">时间戳转换</button> <button class="tool-tab" data-tool="color">颜色转换</button> <button class="tool-tab" data-tool="regex">正则测试</button> <button class="tool-tab" data-tool="qrcode">二维码生成</button> </div> <div class="tool-content"> <!-- JSON格式化工具 --> <div class="tool-pane active" id="json-tool"> <div class="tool-description"> <h3>JSON格式化工具</h3> <p>格式化、验证和压缩JSON数据,支持JSON与XML互转</p> </div> <div class="tool-interface"> <div class="input-area"> <textarea id="json-input" placeholder="输入JSON字符串...">{"name": "示例", "value": 123, "items": [1, 2, 3]}</textarea> <div class="button-group"> <button id="json-format">格式化</button> <button id="json-minify">压缩</button> <button id="json-validate">验证</button> <button id="json-clear">清空</button> </div> </div> <div class="output-area"> <textarea id="json-output" readonly placeholder="格式化结果..."></textarea> <div class="conversion-options"> <button id="json-to-xml">JSON转XML</button> <button id="xml-to-json">XML转JSON</button> </div> </div> </div> </div> <!-- 加密解密工具 --> <div class="tool-pane" id="encrypt-tool"> <div class="tool-description"> <h3>加密/解密工具</h3> <p>支持Base64、MD5、SHA、AES、DES等多种加密算法</p> </div> <div class="tool-interface"> <div class="algorithm-selector"> <select id="encrypt-algorithm"> <option value="base64">Base64编码/解码</option> <option value="md5">MD5哈希</option> <option value="sha1">SHA-1哈希</option> <option value="sha256">SHA-256哈希</option> <option value="aes">AES加密/解密</option> <option value="des">DES加密/解密</option> </select> <input type="text" id="encrypt-key" placeholder="加密密钥(如适用)"> </div> <div class="input-area"> <textarea id="encrypt-input" placeholder="输入要加密/解密的文本...">Hello, World!</textarea> <div class="button-group"> <button id="encrypt-action">加密</button> <button id="decrypt-action">解密</button> </div> </div> <div class="output-area"> <textarea id="encrypt-output" readonly placeholder="加密/解密结果..."></textarea> </div> </div> </div> <!-- 时间戳转换工具 --> <div class="tool-pane" id="timestamp-tool"> <div class="tool-description"> <h3>时间戳转换工具</h3> <p>Unix时间戳与可读日期时间相互转换,支持多种格式</p> </div> <div class="tool-interface"> <div class="timestamp-input"> <div class="input-group"> <label>Unix时间戳:</label> <input type="text" id="timestamp-input" value="<?php echo time(); ?>"> <button id="timestamp-to-date">转换为日期</button> </div> <div class="input-group"> <label>日期时间:</label> <input type="datetime-local" id="date-input" value="<?php echo date('Y-m-dTH:i'); ?>"> <button id="date-to-timestamp">转换为时间戳</button> </div> </div> <div class="output-area"> <div id="timestamp-result"> <p><strong>当前时间戳:</strong> <span id="current-timestamp"><?php echo time(); ?></span></p> <p><strong>北京时间:</strong> <span id="beijing-time"><?php echo date('Y-m-d H:i:s'); ?></span></p> <p><strong>UTC时间:</strong> <span id="utc-time"><?php echo gmdate('Y-m-d H:i:s'); ?></span></p> </div> </div> </div> </div> <!-- 其他工具界面类似,此处省略详细HTML --> </div> </div> <script>document.addEventListener('DOMContentLoaded', function() { // 工具标签切换 const toolTabs = document.querySelectorAll('.tool-tab'); const toolPanes = document.querySelectorAll('.tool-pane'); toolTabs.forEach(tab => { tab.addEventListener('click', function() { const toolName = this.dataset.tool; // 更新标签状态 toolTabs.forEach(t => t.classList.remove('active')); this.classList.add('active'); // 显示对应工具 toolPanes.forEach(pane => { pane.classList.remove('active'); if (pane.id === toolName + '-tool') { pane.classList.add('active'); } }); }); }); // JSON格式化功能 const jsonInput = document.getElementById('json-input'); const jsonOutput = document.getElementById('json-output'); document.getElementById('json-format').addEventListener('click', function() { try { const jsonObj = JSON.parse(jsonInput.value); jsonOutput.value = JSON.stringify(jsonObj, null, 2); } catch (e) { jsonOutput.value = 'JSON格式错误: ' + e.message; } }); document.getElementById('json-minify').addEventListener('click', function() { try { const jsonObj = JSON.parse(jsonInput.value); jsonOutput.value = JSON.stringify(jsonObj); } catch (e) { jsonOutput.value = 'JSON格式错误: ' + e.message; } }); document.getElementById('json-validate').addEventListener('click', function() { try { JSON.parse(jsonInput.value); jsonOutput.value = '✓ JSON格式正确'; } catch (e) { jsonOutput.value = '✗ JSON格式错误: ' + e.message; } }); // 加密解密功能 const encryptInput = document.getElementById('encrypt-input'); const encryptOutput = document.getElementById('encrypt-output'); const encryptAlgorithm = document.getElementById('encrypt-algorithm'); document.getElementById('encrypt-action').addEventListener('click', function() { const text = encryptInput.value; const algorithm = encryptAlgorithm.value; const key = document.getElementById('encrypt-key').value; // 调用WordPress后端处理加密 fetch('<?php echo admin_url("admin-ajax.php"); ?>', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ 'action': 'process_encryption', 'text': text, 'algorithm': algorithm, 'key': key, 'mode': 'encrypt', 'nonce': '<?php echo wp_create_nonce("encryption_nonce"); ?>' }) }) .then(response => response.json()) .then(data => { if (data.success) { encryptOutput.value = data.data.result; } else { encryptOutput.value = '错误: ' + data.data.error; } }) .catch(error => { encryptOutput.value = '请求失败: ' + error.message; }); }); // 时间戳转换功能 document.getElementById('timestamp-to-date').addEventListener('click', function() { const timestamp = parseInt(document.getElementById('timestamp-input').value); if (!isNaN(timestamp)) { const date = new Date(timestamp * 1000); document.getElementById('date-input').value = date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0') + 'T' + String(date.getHours()).padStart(2, '0') + ':' + String(date.getMinutes()).padStart(2, '0'); } }); // 更新时间显示 function updateTimeDisplay() { const now = new Date(); document.getElementById('current-timestamp').textContent = Math.floor(now.getTime() / 1000); document.getElementById('beijing-time').textContent = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') + ':' + String(now.getSeconds()).padStart(2, '0'); const utcNow = new Date(now.toUTCString()); document.getElementById('utc-time').textContent = utcNow.getFullYear() + '-' + String(utcNow.getMonth() + 1).padStart(2, '0') + '-' + String(utcNow.getDate()).padStart(2, '0') + ' ' + String(utcNow.getHours()).padStart(2, '0') + ':' + String(utcNow.getMinutes()).padStart(2, '0') + ':' + String(utcNow.getSeconds()).padStart(2, '0'); } setInterval(updateTimeDisplay, 1000); });</script> <style>.web-tools-container { background: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin: 20px 0; overflow: hidden; } .tools-tabs { display: flex; background: #f5f5f5; border-bottom: 1px solid #ddd; flex-wrap: wrap; } .tool-tab { padding: 12px 20px; background: none; border: none; border-right: 1px solid #ddd; cursor: pointer; font-size: 14px; transition: all 0.3s; } .tool-tab:hover { background: #e9e9e9; } .tool-tab.active { background: #0073aa; color: white; } .tool-pane { display: none; padding: 20px; } .tool-pane.active { display: block; } .tool-description { margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid #eee; } .tool-description h3 { margin: 0 0 5px 0; color: #333; } .tool-description p { margin: 0; color: #666; font-size: 14px; } .tool-interface { display: flex; flex-direction: column; gap: 20px; } .input-area, .output-area { display: flex; flex-direction: column; gap: 10px; } textarea { width: 100%; min-height: 150px; padding: 12px; border: 1px solid #ddd; border-radius: 4px; font-family: 'Consolas', 'Monaco', monospace; font-size: 14px; resize: vertical; } .button-group { display: flex; gap: 10px; flex-wrap: wrap; } button { padding: 8px 16px; background: #0073aa; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background 0.3s; } button:hover { background: #005a87; } .algorithm-selector { display: flex; gap: 10px; margin-bottom: 15px; } select, input[type="text"] { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .timestamp-input { display: flex; flex-direction: column; gap: 15px; } .input-group { display: flex; align-items: center; gap: 10px; } .input-group label { min-width: 120px; font-weight: bold; } timestamp-result { background: #f9f9f9; padding: 15px; border-radius: 4px; border: 1px solid #eee; } timestamp-result p { margin: 8px 0; }</style><?php return ob_get_clean(); } // 加密解密处理函数add_action('wp_ajax_process_encryption', 'handle_encryption_request');add_action('wp_ajax_nopriv_process_encryption', 'handle_encryption_request'); function handle_encryption_request() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'encryption_nonce')) { wp_send_json_error(array('error' => '安全验证失败')); return; } $text = isset($_POST['text']) ? $_POST['text'] : ''; $algorithm = isset($_POST['algorithm']) ? sanitize_text_field($_POST['algorithm']) : 'base64'; $key = isset($_POST['key']) ? $_POST['key'] : ''; $mode = isset($_POST['mode']) ? sanitize_text_field($_POST['mode']) : 'encrypt'; if (empty($text)) { wp_send_json_error(array('error' => '输入文本不能为空')); return; } $result = process_encryption($text, $algorithm, $key, $mode); if ($result['success']) { wp_send_json_success(array('result' => $result['output'])); } else { wp_send_json_error(array('error' => $result['error'])); } } function process_encryption($text, $algorithm, $key, $mode) { try { switch ($algorithm) { case 'base64': if ($mode === 'encrypt') { $output = base64_encode($text); } else { $output = base64_decode($text); if ($output === false) { throw new Exception('Base64解码失败,请检查输入'); } } break; case 'md5': $output = md5($text); break; case 'sha1': $output = sha1($text); break; case 'sha256': $output = hash('sha256', $text); break; case 'aes': // 简化的AES示例(实际应用中需要更安全的实现) if (empty($key)) { throw new Exception('AES加密需要提供密钥'); } if ($mode === 'encrypt') { $output = openssl_encrypt($text, 'AES-128-ECB', $key, 0); } else { $output = openssl_decrypt($text, 'AES-128-ECB', $key, 0); } if ($output === false) { throw new Exception('AES处理失败'); } break; default: throw new Exception('不支持的算法'); } return array( 'success' => true, 'output' => $output ); } catch (Exception $e) { return array( 'success' => false, 'error' => $e->getMessage() ); } } --- ## 五、安全性与性能优化 ### 5.1 安全防护措施 // 安全增强功能class CodeExecutionSecurity { // 代码执行时间限制 const MAX_EXECUTION_TIME = 10; // 秒 // 内存限制 const MAX_MEMORY_LIMIT = '256M'; // 危险函数黑名单 private static $dangerous_functions = [ 'system', 'exec', 'shell_exec', 'passthru', 'proc_open', 'popen', 'eval', 'create_function', 'include', 'require', 'include_once', 'require_once', 'fopen', 'file_get_contents', 'file_put_contents', 'unlink', 'rmdir', 'mkdir' ]; // 危险模式黑名单 private static $dangerous_patterns = [ '/`.*`/', // 反引号执行 '/$_(GET|POST|REQUEST|COOKIE|SERVER)/', // 超全局变量 '/phar:///', // PHAR反序列化 '/expect:///', // Expect包装器 '/php://(filter|input)/', // PHP包装器 ]; /** * 检查代码安全性 */ public static function check_code_safety($code, $language) { // 检查代码长度 if (strlen($code) > 10000) { return array( 'safe' => false, 'reason' => '代码过长(超过10000字符)' ); } // 语言特定的安全检查 switch ($language) { case 'php': return self::check_php_code($code);
发表评论WordPress集成教程:连接项目管理软件并展示项目状态 引言:WordPress的无限可能性 在当今数字化时代,企业网站已不再仅仅是展示公司信息的静态页面,而是逐渐演变为功能丰富的业务平台。WordPress作为全球最受欢迎的内容管理系统,其真正的力量不仅在于创建博客或简单网站,更在于通过代码二次开发实现各种复杂功能。本教程将深入探讨如何将WordPress与项目管理软件集成,实时展示项目状态,并实现一系列常用互联网小工具功能。 传统的企业网站往往与内部业务系统脱节,导致信息更新滞后、数据不一致等问题。通过将WordPress与项目管理工具(如Jira、Asana、Trello、Monday.com等)集成,我们可以创建一个动态的、实时更新的项目状态展示平台,让客户、团队成员和利益相关者随时了解项目进展。 第一部分:准备工作与环境搭建 1.1 选择合适的WordPress环境 在开始集成之前,确保你的WordPress环境满足以下要求: WordPress 5.0或更高版本 PHP 7.4或更高版本(推荐PHP 8.0+) 支持HTTPS的域名 适当的服务器资源(至少1GB RAM,建议2GB以上) 1.2 安装必要插件 虽然本教程主要关注代码开发,但一些基础插件能极大提高开发效率: Advanced Custom Fields (ACF) - 用于创建自定义字段和管理数据 Custom Post Type UI - 简化自定义文章类型的创建 Query Monitor - 调试工具,监控数据库查询和性能 WP REST API Controller - 管理REST API端点 1.3 设置子主题 为避免主题更新覆盖自定义代码,强烈建议创建子主题: 在wp-content/themes/目录下创建新文件夹,如my-custom-theme 创建style.css文件,添加主题信息: /* Theme Name: My Custom Theme Template: parent-theme-folder-name Version: 1.0 */ 创建functions.php文件,用于添加自定义功能 第二部分:连接项目管理软件API 2.1 选择项目管理软件并获取API凭证 不同的项目管理软件提供不同的API接口。以Jira为例: 登录Jira管理员账户 进入"设置" > "系统" > "API令牌" 创建新令牌并妥善保存 获取你的Jira实例URL(如https://yourcompany.atlassian.net) 2.2 创建API连接类 在WordPress中创建专门的类来处理API通信: <?php /** * 项目管理软件API连接类 */ class ProjectManagementAPI { private $api_url; private $api_token; private $username; public function __construct($url, $username, $token) { $this->api_url = $url; $this->username = $username; $this->api_token = $token; } /** * 发送API请求 */ private function make_request($endpoint, $method = 'GET', $data = []) { $url = $this->api_url . $endpoint; $args = [ 'method' => $method, 'headers' => [ 'Authorization' => 'Basic ' . base64_encode($this->username . ':' . $this->api_token), 'Content-Type' => 'application/json', 'Accept' => 'application/json' ], 'timeout' => 30 ]; if (!empty($data)) { $args['body'] = json_encode($data); } $response = wp_remote_request($url, $args); if (is_wp_error($response)) { return [ 'success' => false, 'error' => $response->get_error_message() ]; } $body = wp_remote_retrieve_body($response); $status_code = wp_remote_retrieve_response_code($response); return [ 'success' => $status_code >= 200 && $status_code < 300, 'status' => $status_code, 'data' => json_decode($body, true), 'raw_body' => $body ]; } /** * 获取项目列表 */ public function get_projects() { $endpoint = '/rest/api/3/project'; return $this->make_request($endpoint); } /** * 获取特定项目的问题/任务 */ public function get_project_issues($project_key, $max_results = 50) { $endpoint = '/rest/api/3/search'; $jql = "project = " . $project_key; $data = [ 'jql' => $jql, 'maxResults' => $max_results, 'fields' => ['summary', 'status', 'assignee', 'created', 'updated'] ]; return $this->make_request($endpoint, 'POST', $data); } /** * 获取项目状态概览 */ public function get_project_status($project_key) { $issues_response = $this->get_project_issues($project_key, 100); if (!$issues_response['success']) { return $issues_response; } $issues = $issues_response['data']['issues'] ?? []; $status_summary = [ 'total' => 0, 'by_status' => [], 'by_assignee' => [] ]; foreach ($issues as $issue) { $status_summary['total']++; // 按状态统计 $status_name = $issue['fields']['status']['name'] ?? '未知'; if (!isset($status_summary['by_status'][$status_name])) { $status_summary['by_status'][$status_name] = 0; } $status_summary['by_status'][$status_name]++; // 按负责人统计 $assignee_name = $issue['fields']['assignee']['displayName'] ?? '未分配'; if (!isset($status_summary['by_assignee'][$assignee_name])) { $status_summary['by_assignee'][$assignee_name] = 0; } $status_summary['by_assignee'][$assignee_name]++; } return [ 'success' => true, 'data' => $status_summary ]; } } ?> 2.3 安全存储API凭证 永远不要在代码中硬编码API凭证。使用WordPress选项API安全存储: <?php /** * 保存API设置 */ function save_pm_api_settings() { if (isset($_POST['pm_api_nonce']) && wp_verify_nonce($_POST['pm_api_nonce'], 'save_pm_api_settings')) { $api_settings = [ 'api_url' => sanitize_text_field($_POST['api_url']), 'username' => sanitize_text_field($_POST['username']), 'api_token' => $_POST['api_token'] // 注意:令牌需要特殊处理 ]; // 加密存储令牌 if (!empty($api_settings['api_token'])) { require_once(ABSPATH . 'wp-includes/class-phpass.php'); $hasher = new PasswordHash(8, true); $api_settings['api_token'] = $hasher->HashPassword($api_settings['api_token']); } else { // 如果令牌为空,保留现有令牌 $existing_settings = get_option('pm_api_settings', []); if (isset($existing_settings['api_token'])) { $api_settings['api_token'] = $existing_settings['api_token']; } } update_option('pm_api_settings', $api_settings); wp_redirect(add_query_arg('settings-updated', 'true', wp_get_referer())); exit; } } add_action('admin_post_save_pm_api_settings', 'save_pm_api_settings'); /** * 获取API设置 */ function get_pm_api_settings() { $settings = get_option('pm_api_settings', []); // 解密令牌(在实际使用时) if (isset($settings['api_token']) && !empty($settings['api_token'])) { // 注意:实际解密逻辑需要根据加密方式实现 // 这里只是示例结构 } return $settings; } ?> 第三部分:在WordPress中展示项目状态 3.1 创建自定义文章类型和分类 为了更好地组织项目数据,我们创建自定义文章类型: <?php /** * 注册项目自定义文章类型 */ function register_project_post_type() { $labels = [ 'name' => '项目', 'singular_name' => '项目', 'menu_name' => '项目管理', 'add_new' => '添加新项目', 'add_new_item' => '添加新项目', 'edit_item' => '编辑项目', 'new_item' => '新项目', 'view_item' => '查看项目', 'search_items' => '搜索项目', 'not_found' => '未找到项目', 'not_found_in_trash' => '回收站中无项目' ]; $args = [ 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => ['slug' => 'project'], 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => 20, 'menu_icon' => 'dashicons-portfolio', 'supports' => ['title', 'editor', 'thumbnail', 'excerpt'], 'show_in_rest' => true, // 启用Gutenberg编辑器和REST API ]; register_post_type('project', $args); // 注册项目分类 register_taxonomy( 'project_category', 'project', [ 'label' => '项目分类', 'rewrite' => ['slug' => 'project-category'], 'hierarchical' => true, 'show_in_rest' => true ] ); } add_action('init', 'register_project_post_type'); ?> 3.2 使用Advanced Custom Fields添加项目元数据 通过ACF添加项目与外部项目管理软件的关联字段: <?php /** * 添加项目元字段 */ function add_project_meta_fields() { if (function_exists('acf_add_local_field_group')) { acf_add_local_field_group([ 'key' => 'group_project_meta', 'title' => '项目信息', 'fields' => [ [ 'key' => 'field_project_external_id', 'label' => '外部项目ID', 'name' => 'project_external_id', 'type' => 'text', 'instructions' => '在项目管理软件中的项目标识符', 'required' => 0, ], [ 'key' => 'field_project_key', 'label' => '项目键', 'name' => 'project_key', 'type' => 'text', 'instructions' => '项目键(如Jira中的项目键)', 'required' => 0, ], [ 'key' => 'field_project_status', 'label' => '项目状态', 'name' => 'project_status', 'type' => 'select', 'choices' => [ 'planning' => '规划中', 'active' => '进行中', 'on_hold' => '暂停', 'completed' => '已完成', 'cancelled' => '已取消' ], 'default_value' => 'planning', ], [ 'key' => 'field_project_start_date', 'label' => '开始日期', 'name' => 'project_start_date', 'type' => 'date_picker', ], [ 'key' => 'field_project_end_date', 'label' => '结束日期', 'name' => 'project_end_date', 'type' => 'date_picker', ], [ 'key' => 'field_project_progress', 'label' => '进度', 'name' => 'project_progress', 'type' => 'range', 'instructions' => '项目完成百分比', 'min' => 0, 'max' => 100, 'step' => 5, 'default_value' => 0, ] ], 'location' => [ [ [ 'param' => 'post_type', 'operator' => '==', 'value' => 'project', ], ], ], ]); } } add_action('acf/init', 'add_project_meta_fields'); ?> 3.3 创建项目状态展示短代码 创建短代码以便在文章或页面中插入项目状态: <?php /** * 项目状态展示短代码 */ function project_status_shortcode($atts) { // 解析短代码属性 $atts = shortcode_atts([ 'project_id' => '', // WordPress项目ID 'project_key' => '', // 外部项目键 'show_tasks' => 'true', // 是否显示任务 'max_tasks' => 10, // 最大任务显示数量 'refresh' => 30, // 自动刷新时间(秒),0表示不自动刷新 ], $atts); // 获取项目信息 $project_data = []; if (!empty($atts['project_id'])) { $project_post = get_post($atts['project_id']); if ($project_post && $project_post->post_type === 'project') { $project_data['title'] = $project_post->post_title; $project_data['description'] = $project_post->post_excerpt; $project_data['progress'] = get_field('project_progress', $atts['project_id']); $project_data['status'] = get_field('project_status', $atts['project_id']); $project_key = get_field('project_key', $atts['project_id']); } } // 如果直接提供了project_key,使用它 if (!empty($atts['project_key'])) { $project_key = $atts['project_key']; } // 如果没有项目键,返回错误 if (empty($project_key)) { return '<div class="project-status-error">未指定项目</div>'; } // 获取API设置 $api_settings = get_pm_api_settings(); // 初始化API连接 $api = new ProjectManagementAPI( $api_settings['api_url'] ?? '', $api_settings['username'] ?? '', $api_settings['api_token'] ?? '' ); // 获取项目状态 $status_response = $api->get_project_status($project_key); // 准备输出 ob_start(); // 添加自动刷新脚本 if ($atts['refresh'] > 0) { ?> <script> document.addEventListener('DOMContentLoaded', function() { setTimeout(function() { location.reload(); }, <?php echo $atts['refresh'] * 1000; ?>); }); </script> <?php } // 输出项目状态 ?> <div class="project-status-container" data-project-key="<?php echo esc_attr($project_key); ?>"> <div class="project-header"> <h3 class="project-title"><?php echo esc_html($project_data['title'] ?? '项目状态'); ?></h3> <?php if (!empty($project_data['description'])): ?> <p class="project-description"><?php echo esc_html($project_data['description']); ?></p> <?php endif; ?> </div> <?php if ($status_response['success']): $status_data = $status_response['data']; ?> <div class="project-stats"> <div class="stat-card total-tasks"> <div class="stat-value"><?php echo $status_data['total']; ?></div> <div class="stat-label">总任务数</div> </div> <?php foreach ($status_data['by_status'] as $status_name => $count): ?> <div class="stat-card status-<?php echo sanitize_title($status_name); ?>"> <div class="stat-value"><?php echo $count; ?></div> <div class="stat-label"><?php echo esc_html($status_name); ?></div> </div> <?php endforeach; ?> </div> <?php if ($atts['show_tasks'] === 'true'): $tasks_response = $api->get_project_issues($project_key, $atts['max_tasks']); if ($tasks_response['success'] && !empty($tasks_response['data']['issues'])): ?> <div class="project-tasks"> <h4>最近任务</h4> <table class="tasks-table"> <thead> <tr> <th>任务</th> <th>状态</th> <th>负责人</th> <th>更新时间</th> </tr> </thead> <tbody> <?php foreach ($tasks_response['data']['issues'] as $issue): $fields = $issue['fields'] ?? []; ?> <tr> <td> <a href="#" class="task-link" data-task-key="<?php echo esc_attr($issue['key']); ?>"> <?php echo esc_html($fields['summary'] ?? '无标题'); ?> </a> </td> <td> <span class="task-status status-<?php echo sanitize_title($fields['status']['name'] ?? '未知'); ?>"> <?php echo esc_html($fields['status']['name'] ?? '未知'); ?> </span> </td> displayName'] ?? '未分配'); ?></td> <td><?php $updated = $fields['updated'] ?? ''; if (!empty($updated)) { echo date('Y-m-d H:i', strtotime($updated)); } else { echo '未知'; } ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> <?php endif; endif; ?> <?php if (!empty($status_data['by_assignee'])): ?> <div class="assignee-distribution"> <h4>任务分配情况</h4> <div class="assignee-chart"> <?php foreach ($status_data['by_assignee'] as $assignee => $count): $percentage = $status_data['total'] > 0 ? ($count / $status_data['total']) * 100 : 0; ?> <div class="assignee-item"> <div class="assignee-name"><?php echo esc_html($assignee); ?></div> <div class="assignee-bar"> <div class="assignee-bar-fill" style="width: <?php echo $percentage; ?>%"></div> </div> <div class="assignee-count"><?php echo $count; ?> 任务</div> </div> <?php endforeach; ?> </div> </div> <?php endif; ?> <?php else: ?> <div class="project-status-error"> <p>无法获取项目状态数据</p> <?php if (current_user_can('manage_options')): ?> <p class="error-detail">错误: <?php echo esc_html($status_response['error'] ?? '未知错误'); ?></p> <?php endif; ?> </div> <?php endif; ?> <div class="project-status-footer"> <p class="update-time">最后更新: <?php echo current_time('Y-m-d H:i:s'); ?></p> <?php if ($atts['refresh'] > 0): ?> <p class="auto-refresh">自动刷新: 每 <?php echo $atts['refresh']; ?> 秒</p> <?php endif; ?> </div> </div> <style> .project-status-container { border: 1px solid #e0e0e0; border-radius: 8px; padding: 20px; margin: 20px 0; background: #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } .project-header { margin-bottom: 25px; padding-bottom: 15px; border-bottom: 2px solid #f0f0f0; } .project-title { margin: 0 0 10px 0; color: #333; } .project-description { margin: 0; color: #666; font-size: 14px; } .project-stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; margin-bottom: 25px; } .stat-card { background: #f8f9fa; border-radius: 6px; padding: 15px; text-align: center; border-left: 4px solid #0073aa; } .stat-card.total-tasks { border-left-color: #0073aa; } .stat-card.status-已完成 { border-left-color: #46b450; } .stat-card.status-进行中 { border-left-color: #00a0d2; } .stat-card.status-待处理 { border-left-color: #ffb900; } .stat-value { font-size: 24px; font-weight: bold; color: #333; margin-bottom: 5px; } .stat-label { font-size: 12px; color: #666; text-transform: uppercase; letter-spacing: 0.5px; } .project-tasks { margin-bottom: 25px; } .project-tasks h4 { margin-bottom: 15px; color: #444; } .tasks-table { width: 100%; border-collapse: collapse; font-size: 14px; } .tasks-table th { background: #f8f9fa; padding: 12px 15px; text-align: left; font-weight: 600; color: #555; border-bottom: 2px solid #e0e0e0; } .tasks-table td { padding: 12px 15px; border-bottom: 1px solid #eee; } .tasks-table tr:hover { background: #f9f9f9; } .task-link { color: #0073aa; text-decoration: none; } .task-link:hover { text-decoration: underline; } .task-status { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; } .status-已完成 { background: #d1f0d9; color: #1e7c1e; } .status-进行中 { background: #d1e8f0; color: #0a6a8c; } .status-待处理 { background: #fff0d1; color: #b36b00; } .assignee-distribution { margin-bottom: 20px; } .assignee-distribution h4 { margin-bottom: 15px; color: #444; } .assignee-item { display: flex; align-items: center; margin-bottom: 10px; } .assignee-name { width: 150px; font-size: 14px; color: #555; } .assignee-bar { flex-grow: 1; height: 20px; background: #f0f0f0; border-radius: 10px; overflow: hidden; margin: 0 15px; } .assignee-bar-fill { height: 100%; background: linear-gradient(90deg, #0073aa, #00a0d2); border-radius: 10px; transition: width 0.5s ease; } .assignee-count { width: 80px; text-align: right; font-size: 14px; color: #666; } .project-status-footer { margin-top: 20px; padding-top: 15px; border-top: 1px solid #eee; font-size: 12px; color: #888; display: flex; justify-content: space-between; } .project-status-error { padding: 20px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; color: #721c24; } .error-detail { font-size: 12px; margin-top: 10px; color: #856404; } @media (max-width: 768px) { .project-stats { grid-template-columns: repeat(2, 1fr); } .assignee-item { flex-direction: column; align-items: flex-start; } .assignee-name { width: 100%; margin-bottom: 5px; } .assignee-bar { width: 100%; margin: 5px 0; } .assignee-count { width: 100%; text-align: left; } .tasks-table { display: block; overflow-x: auto; } } </style> <?php return ob_get_clean(); }add_shortcode('project_status', 'project_status_shortcode');?> ## 第四部分:实现常用互联网小工具功能 ### 4.1 实时数据仪表板小工具 创建一个WordPress小工具,显示多个项目的实时状态: <?php/** 项目状态仪表板小工具 */ class ProjectDashboardWidget extends WP_Widget { public function __construct() { parent::__construct( 'project_dashboard_widget', '项目状态仪表板', ['description' => '显示多个项目的实时状态'] ); } public function widget($args, $instance) { echo $args['before_widget']; $title = apply_filters('widget_title', $instance['title']); if (!empty($title)) { echo $args['before_title'] . $title . $args['after_title']; } // 获取配置的项目键 $project_keys = !empty($instance['project_keys']) ? explode(',', $instance['project_keys']) : []; if (empty($project_keys)) { echo '<p>请配置要显示的项目</p>'; echo $args['after_widget']; return; } // 获取API设置 $api_settings = get_pm_api_settings(); $api = new ProjectManagementAPI( $api_settings['api_url'] ?? '', $api_settings['username'] ?? '', $api_settings['api_token'] ?? '' ); echo '<div class="project-dashboard-widget">'; foreach ($project_keys as $project_key) { $project_key = trim($project_key); if (empty($project_key)) continue; $status_response = $api->get_project_status($project_key); echo '<div class="dashboard-project-item">'; echo '<h4>' . esc_html($project_key) . '</h4>'; if ($status_response['success']) { $status_data = $status_response['data']; // 计算完成率 $completed = $status_data['by_status']['已完成'] ?? 0; $completion_rate = $status_data['total'] > 0 ? round(($completed / $status_data['total']) * 100) : 0; echo '<div class="completion-bar">'; echo '<div class="completion-fill" style="width: ' . $completion_rate . '%"></div>'; echo '</div>'; echo '<div class="project-metrics">'; echo '<span class="metric">总任务: ' . $status_data['total'] . '</span>'; echo '<span class="metric">完成率: ' . $completion_rate . '%</span>'; echo '</div>'; } else { echo '<p class="error">无法获取数据</p>'; } echo '</div>'; } echo '</div>'; // 添加样式 ?> <style> .project-dashboard-widget { font-size: 14px; } .dashboard-project-item { margin-bottom: 15px; padding: 10px; background: #f8f9fa; border-radius: 4px; border-left: 3px solid #0073aa; } .dashboard-project-item h4 { margin: 0 0 8px 0; font-size: 14px; color: #333; } .completion-bar { height: 6px; background: #e0e0e0; border-radius: 3px; overflow: hidden; margin-bottom: 8px; } .completion-fill { height: 100%; background: linear-gradient(90deg, #0073aa, #00a0d2); border-radius: 3px; transition: width 0.5s ease; } .project-metrics { display: flex; justify-content: space-between; font-size: 12px; color: #666; } .metric { display: inline-block; } .error { color: #dc3232; font-size: 12px; margin: 0; } </style> <?php echo $args['after_widget']; } public function form($instance) { $title = $instance['title'] ?? '项目状态'; $project_keys = $instance['project_keys'] ?? ''; ?> <p> <label for="<?php echo $this->get_field_id('title'); ?>">标题:</label> <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>"> </p> <p> <label for="<?php echo $this->get_field_id('project_keys'); ?>">项目键(用逗号分隔):</label> <input class="widefat" id="<?php echo $this->get_field_id('project_keys'); ?>" name="<?php echo $this->get_field_name('project_keys'); ?>" type="text" value="<?php echo esc_attr($project_keys); ?>"> <small>例如: PROJ1, PROJ2, PROJ3</small> </p> <?php } public function update($new_instance, $old_instance) { $instance = []; $instance['title'] = !empty($new_instance['title']) ? sanitize_text_field($new_instance['title']) : ''; $instance['project_keys'] = !empty($new_instance['project_keys']) ? sanitize_text_field($new_instance['project_keys']) : ''; return $instance; } } // 注册小工具function register_project_dashboard_widget() { register_widget('ProjectDashboardWidget'); }add_action('widgets_init', 'register_project_dashboard_widget');?> ### 4.2 项目时间线小工具 创建一个可视化项目时间线的小工具: <?php/** 项目时间线小工具 */ function project_timeline_shortcode($atts) { $atts = shortcode_atts([ 'project_id' => '', 'height' => '400px', 'show_milestones' => 'true', ], $atts); if (empty($atts['project_id'])) { return '<p>请指定项目ID</p>'; } // 获取项目信息 $project_post = get_post($atts['project_id']); if (!$project_post || $project_post->post_type !== 'project') { return '<p>项目不存在</p>'; } // 获取项目时间线数据 $timeline_data = get_project_timeline_data($atts['project_id']); ob_start(); ?> <div class="project-timeline-container" style="height: <?php echo esc_attr($atts['height']); ?>"> <div class="timeline-header"> <h3><?php echo esc_html($project_post->post_title); ?> 时间线</h3> </div> <div class="timeline-wrapper"> <div class="timeline"> <?php foreach ($timeline_data as $item): ?> <div class="timeline-item <?php echo esc_attr($item['type']); ?>" data-date="<?php echo esc_attr($item['date']); ?>"> <div class="timeline-marker"></div> <div class="timeline-content"> <div class="timeline-date"><?php echo esc_html($item['display_date']); ?></div> <h4 class="timeline-title"><?php echo esc_html($item['title']); ?></h4> <?php if (!empty($item['description'])): ?> <p class="timeline-description"><?php echo esc_html($item['description']); ?></p> <?php endif; ?> <?php if (!empty($item['status'])): ?> <span class="timeline-status status-<?php echo esc_attr($item['status']); ?>"> <?php echo esc_html($item['status']); ?> </span> <?php endif; ?> </div> </div> <?php endforeach; ?> </div> </div> </div> <style> .project-timeline-container { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; background: #fff; } .timeline-header { padding: 15px 20px; background: #f8f9fa; border-bottom: 1px solid #e0e0e0; } .timeline-header h3 { margin: 0; font-size: 18px; color: #333; } .timeline-wrapper { padding: 20px; height: calc(100% - 60px); overflow-y: auto; } .timeline { position: relative; padding-left: 30px; } .timeline::before { content: ''; position: absolute; left: 10px; top: 0; bottom: 0; width: 2px; background: #0073aa; } .timeline-item { position: relative; margin-bottom: 25px; } .timeline-marker { position: absolute; left: -25px; top: 5px; width: 12px; height: 12px; border-radius: 50%; background: #fff; border: 3px solid #0073aa; z-index: 1; } .timeline-item.milestone .timeline-marker { border-color:
发表评论一步步教你,为WordPress添加网站性能监控与告警功能 引言:为什么WordPress网站需要性能监控与告警 在当今数字化时代,网站性能直接影响用户体验、搜索引擎排名和业务转化率。根据Google的研究,页面加载时间每增加1秒,移动端跳出率就会增加20%。对于基于WordPress构建的网站而言,随着插件、主题和内容的不断增加,性能问题往往悄然而至。 许多WordPress站长依赖第三方监控服务,但这些服务往往价格昂贵,且无法深度集成到WordPress管理后台。通过代码二次开发,我们可以为WordPress添加自定义的性能监控与告警功能,不仅能节省成本,还能根据具体需求定制监控指标和告警规则。 本文将详细指导您如何通过WordPress代码二次开发,实现一个完整的网站性能监控与告警系统,涵盖从基础监控到高级告警功能的完整实现过程。 第一部分:准备工作与环境配置 1.1 理解WordPress钩子机制 WordPress的强大之处在于其完善的钩子(Hooks)系统,包括动作(Actions)和过滤器(Filters)。我们的性能监控系统将大量使用这些钩子来插入监控代码。 // 示例:WordPress钩子的基本使用 add_action('init', 'my_monitoring_init'); function my_monitoring_init() { // 初始化监控系统 } add_filter('the_content', 'my_content_monitor'); function my_content_monitor($content) { // 监控内容加载 return $content; } 1.2 创建专用插件目录结构 为了保持代码的整洁和可维护性,我们创建一个独立的插件来实现监控功能: wp-performance-monitor/ ├── performance-monitor.php # 主插件文件 ├── includes/ │ ├── class-monitor-core.php # 监控核心类 │ ├── class-alert-system.php # 告警系统类 │ ├── class-dashboard-widget.php # 仪表板小工具 │ └── class-data-storage.php # 数据存储类 ├── assets/ │ ├── css/ │ │ └── admin-styles.css # 管理界面样式 │ └── js/ │ └── admin-scripts.js # 管理界面脚本 ├── templates/ │ └── admin-dashboard.php # 管理面板模板 └── vendor/ # 第三方库(如果需要) 1.3 主插件文件配置 创建主插件文件 performance-monitor.php: <?php /** * Plugin Name: WordPress性能监控与告警系统 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress添加网站性能监控与告警功能 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: wp-performance-monitor */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('WPPM_VERSION', '1.0.0'); define('WPPM_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('WPPM_PLUGIN_URL', plugin_dir_url(__FILE__)); define('WPPM_CAPABILITY', 'manage_options'); // 自动加载类文件 spl_autoload_register(function ($class_name) { $prefix = 'WPPM_'; $base_dir = WPPM_PLUGIN_DIR . 'includes/'; if (strpos($class_name, $prefix) !== 0) { return; } $relative_class = substr($class_name, strlen($prefix)); $file = $base_dir . 'class-' . str_replace('_', '-', strtolower($relative_class)) . '.php'; if (file_exists($file)) { require_once $file; } }); // 初始化插件 function wppm_init_plugin() { // 检查WordPress版本 if (version_compare(get_bloginfo('version'), '5.0', '<')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>'; echo __('WordPress性能监控插件需要WordPress 5.0或更高版本。', 'wp-performance-monitor'); echo '</p></div>'; }); return; } // 初始化核心组件 WPPM_Monitor_Core::get_instance(); WPPM_Alert_System::get_instance(); // 如果是管理后台,初始化管理界面 if (is_admin()) { WPPM_Dashboard_Widget::get_instance(); } } add_action('plugins_loaded', 'wppm_init_plugin'); // 插件激活钩子 register_activation_hook(__FILE__, 'wppm_activate_plugin'); function wppm_activate_plugin() { // 创建必要的数据库表 wppm_create_database_tables(); // 设置默认选项 $default_options = array( 'monitoring_enabled' => true, 'alert_enabled' => true, 'performance_threshold' => 3, // 3秒 'uptime_monitoring' => true, 'data_retention_days' => 30, ); add_option('wppm_settings', $default_options); // 添加定时任务 if (!wp_next_scheduled('wppm_daily_maintenance')) { wp_schedule_event(time(), 'daily', 'wppm_daily_maintenance'); } } // 插件停用钩子 register_deactivation_hook(__FILE__, 'wppm_deactivate_plugin'); function wppm_deactivate_plugin() { // 清除定时任务 wp_clear_scheduled_hook('wppm_daily_maintenance'); } 第二部分:核心监控功能实现 2.1 页面加载时间监控 页面加载时间是衡量网站性能的关键指标。我们将通过测量WordPress核心加载时间、主题初始化和页面渲染时间来实现全面监控。 // includes/class-monitor-core.php class WPPM_Monitor_Core { private static $instance = null; private $start_time; private $memory_start; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->start_time = microtime(true); $this->memory_start = memory_get_usage(); // 在WordPress加载的不同阶段添加监控点 add_action('init', array($this, 'track_init_time')); add_action('wp_loaded', array($this, 'track_wp_loaded_time')); add_action('wp', array($this, 'track_wp_time')); add_action('shutdown', array($this, 'track_shutdown_time')); // 监控数据库查询 add_filter('query', array($this, 'track_database_query')); // 监控内存使用 add_action('shutdown', array($this, 'track_memory_usage')); } public function track_init_time() { $this->add_performance_point('init', 'WordPress初始化完成'); } public function track_wp_loaded_time() { $this->add_performance_point('wp_loaded', 'WordPress完全加载'); } public function track_wp_time() { $this->add_performance_point('wp', '主查询已设置'); } public function track_shutdown_time() { $total_time = microtime(true) - $this->start_time; $this->add_performance_point('shutdown', '页面完全渲染', $total_time); // 保存性能数据 $this->save_performance_data($total_time); } private function add_performance_point($stage, $description, $custom_time = null) { $time = $custom_time ?: (microtime(true) - $this->start_time); // 存储到全局变量中,供后续使用 global $wppm_performance_data; if (!isset($wppm_performance_data)) { $wppm_performance_data = array(); } $wppm_performance_data[$stage] = array( 'time' => round($time, 4), 'description' => $description, 'timestamp' => current_time('mysql') ); } public function track_database_query($query) { if (defined('SAVEQUERIES') && SAVEQUERIES) { // WordPress已启用查询保存,我们可以直接使用 return $query; } // 简单查询监控(不记录查询内容以保护隐私) global $wppm_db_queries; if (!isset($wppm_db_queries)) { $wppm_db_queries = 0; } $wppm_db_queries++; return $query; } public function track_memory_usage() { $memory_end = memory_get_usage(); $memory_peak = memory_get_peak_usage(); $memory_used = $memory_end - $this->memory_start; global $wppm_performance_data; if (!isset($wppm_performance_data)) { $wppm_performance_data = array(); } $wppm_performance_data['memory'] = array( 'used' => $this->format_bytes($memory_used), 'peak' => $this->format_bytes($memory_peak), 'limit' => ini_get('memory_limit') ); } private function save_performance_data($total_time) { global $wppm_performance_data, $wppm_db_queries; // 获取当前页面信息 $current_url = home_url($_SERVER['REQUEST_URI']); $is_admin = is_admin(); $user_role = 'guest'; if (is_user_logged_in()) { $user = wp_get_current_user(); $user_role = implode(',', $user->roles); } // 准备性能数据 $performance_data = array( 'url' => $current_url, 'total_time' => $total_time, 'is_admin' => $is_admin, 'user_role' => $user_role, 'performance_points' => $wppm_performance_data, 'db_queries' => $wppm_db_queries ?? 0, 'timestamp' => current_time('mysql'), 'server_load' => function_exists('sys_getloadavg') ? sys_getloadavg()[0] : 0 ); // 保存到数据库 $this->store_performance_record($performance_data); // 检查是否触发告警 $this->check_performance_alerts($performance_data); } private function store_performance_record($data) { global $wpdb; $table_name = $wpdb->prefix . 'wppm_performance_logs'; $wpdb->insert( $table_name, array( 'url' => substr($data['url'], 0, 500), 'total_time' => $data['total_time'], 'is_admin' => $data['is_admin'] ? 1 : 0, 'user_role' => $data['user_role'], 'performance_data' => maybe_serialize($data['performance_points']), 'db_queries' => $data['db_queries'], 'server_load' => $data['server_load'], 'created_at' => $data['timestamp'] ), array('%s', '%f', '%d', '%s', '%s', '%d', '%f', '%s') ); } private function format_bytes($bytes, $precision = 2) { $units = array('B', 'KB', 'MB', 'GB', 'TB'); $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= pow(1024, $pow); return round($bytes, $precision) . ' ' . $units[$pow]; } } 2.2 数据库性能监控 数据库是WordPress网站性能的关键瓶颈之一。我们需要监控查询数量、慢查询和数据库连接时间。 // 扩展监控核心类,添加数据库监控 class WPPM_DB_Monitor { private $queries = array(); private $slow_query_threshold = 0.1; // 100毫秒 public function __construct() { // 如果SAVEQUERIES未启用,我们需要自己监控 if (!defined('SAVEQUERIES') || !SAVEQUERIES) { add_filter('query', array($this, 'capture_query')); } add_action('shutdown', array($this, 'analyze_queries')); } public function capture_query($query) { $start_time = microtime(true); // 执行查询并计算时间 $result = $this->execute_query($query); $end_time = microtime(true); $execution_time = $end_time - $start_time; // 记录查询信息 $this->queries[] = array( 'query' => $this->sanitize_query($query), 'time' => $execution_time, 'trace' => $this->get_query_trace(), 'timestamp' => microtime(true) ); return $result; } public function analyze_queries() { if (empty($this->queries)) { return; } $total_query_time = 0; $slow_queries = array(); foreach ($this->queries as $query) { $total_query_time += $query['time']; if ($query['time'] > $this->slow_query_threshold) { $slow_queries[] = $query; } } // 保存分析结果 $this->save_query_analysis($total_query_time, $slow_queries); } private function save_query_analysis($total_time, $slow_queries) { global $wpdb; $table_name = $wpdb->prefix . 'wppm_query_logs'; $wpdb->insert( $table_name, array( 'total_queries' => count($this->queries), 'total_query_time' => $total_time, 'slow_queries' => maybe_serialize($slow_queries), 'average_query_time' => count($this->queries) > 0 ? $total_time / count($this->queries) : 0, 'created_at' => current_time('mysql') ), array('%d', '%f', '%s', '%f', '%s') ); } private function sanitize_query($query) { // 移除敏感数据(如密码) $query = preg_replace('/passwords*=s*'.*?'/i', "password = '***'", $query); $query = preg_replace('/passwords*=s*".*?"/i', 'password = "***"', $query); return substr($query, 0, 1000); // 限制长度 } private function get_query_trace() { $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); $simplified_trace = array(); foreach ($trace as $item) { if (isset($item['file']) && strpos($item['file'], 'wp-content') !== false) { $simplified_trace[] = array( 'file' => str_replace(ABSPATH, '', $item['file']), 'line' => $item['line'] ?? 0, 'function' => $item['function'] ?? '' ); } } return $simplified_trace; } } 2.3 服务器资源监控 监控服务器资源使用情况,包括CPU、内存和磁盘空间。 // includes/class-server-monitor.php class WPPM_Server_Monitor { public static function get_server_stats() { $stats = array( 'cpu' => self::get_cpu_usage(), 'memory' => self::get_memory_usage(), 'disk' => self::get_disk_usage(), 'php' => self::get_php_stats(), 'mysql' => self::get_mysql_stats(), 'timestamp' => current_time('mysql') ); return $stats; } private static function get_cpu_usage() { if (!function_exists('sys_getloadavg')) { return array('error' => '无法获取CPU负载信息'); } $load = sys_getloadavg(); return array( '1min' => $load[0], '5min' => $load[1], '15min' => $load[2], 'cores' => self::get_cpu_cores() ); } private static function get_cpu_cores() { if (is_readable('/proc/cpuinfo')) { $cpuinfo = file_get_contents('/proc/cpuinfo'); preg_match_all('/^processor/m', $cpuinfo, $matches); return count($matches[0]); } // 备用方法 $cores = intval(shell_exec('nproc 2>/dev/null')); return $cores > 0 ? $cores : 1; } private static function get_memory_usage() { if (!is_readable('/proc/meminfo')) { return array('error' => '无法获取内存信息'); } $meminfo = file_get_contents('/proc/meminfo'); $memory = array(); preg_match('/MemTotal:s+(d+)/', $meminfo, $matches); $memory['total'] = $matches[1] ?? 0; preg_match('/MemFree:s+(d+)/', $meminfo, $matches); $memory['free'] = $matches[1] ?? 0; $memory['available'] = $matches[1] ?? 0; preg_match('/Cached:s+(d+)/', $meminfo, $matches); $memory['cached'] = $matches[1] ?? 0; // 计算使用率 if ($memory['total'] > 0) { $memory['used'] = $memory['total'] - $memory['available']; $memory['usage_percent'] = round(($memory['used'] / $memory['total']) * 100, 2); } return $memory; } private static function get_disk_usage() { $disk_total = disk_total_space(ABSPATH); $disk_free = disk_free_space(ABSPATH); if ($disk_total === false || $disk_free === false) { return array('error' => '无法获取磁盘信息'); } $disk_used = $disk_total - $disk_free; $usage_percent = ($disk_total > 0) ? round(($disk_used / $disk_total) * 100, 2) : 0; return array( 'total' => $disk_total, 'free' => $disk_free, 'used' => $disk_used, 'usage_percent' => $usage_percent, 'total_human' => self::format_bytes($disk_total), 'free_human' => self::format_bytes($disk_free), 'used_human' => self::format_bytes($disk_used) ); } private static function get_php_stats() { return array( 'version' => PHP_VERSION, 'memory_limit' => ini_get('memory_limit'), 'max_execution_time' => ini_get('max_execution_time'), 'upload_max_filesize' => ini_get('upload_max_filesize'), 'post_max_size' => ini_get('post_max_size'), 'extensions' => get_loaded_extensions() ); } private static function get_mysql_stats() { global $wpdb; $stats = array(); // 获取MySQL版本 $version = $wpdb->get_var("SELECT VERSION()"); $stats['version'] = $version; // 获取数据库大小 $db_name = DB_NAME; $size_query = $wpdb->get_row( "SELECT SUM(data_length + index_length) as size FROM information_schema.TABLES WHERE table_schema = '$db_name' GROUP BY table_schema" ); $stats['database_size'] = $size_query ? $size_query->size : 0; $stats['database_size_human'] = self::format_bytes($stats['database_size']); // 获取表状态 $tables = $wpdb->get_results("SHOW TABLE STATUS"); $stats['tables'] = count($tables); return $stats; } private static function format_bytes($bytes, $precision = 2) { $units = array('B', 'KB', 'MB', 'GB', 'TB'); $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= pow(1024, $pow); return round($bytes, $precision) . ' ' . $units[$pow]; } } ## 第三部分:告警系统实现 ### 3.1 告警规则配置与管理 // includes/class-alert-system.phpclass WPPM_Alert_System { private static $instance = null; private $alert_rules = array(); public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->load_alert_rules(); // 添加定时检查任务 add_action('wppm_check_alerts', array($this, 'check_all_alerts')); // 注册定时任务 if (!wp_next_scheduled('wppm_check_alerts')) { wp_schedule_event(time(), 'hourly', 'wppm_check_alerts'); } // 性能数据检查 add_action('shutdown', array($this, 'check_realtime_alerts')); } private function load_alert_rules() { $default_rules = array( 'performance' => array( 'enabled' => true, 'threshold' => 3, // 秒 'consecutive_failures' => 3, 'notification_channels' => array('email', 'dashboard') ), 'uptime' => array( 'enabled' => true, 'check_interval' => 300, // 5分钟 'timeout' => 10, 'notification_channels' => array('email', 'sms') ), 'server' => array( 'enabled' => true, 'cpu_threshold' => 80, // 百分比 'memory_threshold' => 85, 'disk_threshold' => 90, 'notification_channels' => array('email') ), 'errors' => array( 'enabled' => true, 'php_errors' => true, 'http_errors' => array(500, 502, 503, 504), 'notification_channels' => array('email', 'dashboard') ) ); $saved_rules = get_option('wppm_alert_rules', array()); $this->alert_rules = wp_parse_args($saved_rules, $default_rules); } public function check_realtime_alerts() { global $wppm_performance_data; if (empty($wppm_performance_data) || !isset($wppm_performance_data['shutdown'])) { return; } $total_time = $wppm_performance_data['shutdown']['time']; $threshold = $this->alert_rules['performance']['threshold']; if ($total_time > $threshold) { $this->trigger_alert('performance', array( 'page_url' => home_url($_SERVER['REQUEST_URI']), 'load_time' => $total_time, 'threshold' => $threshold, 'timestamp' => current_time('mysql') )); } } public function check_all_alerts() { $this->check_server_alerts(); $this->check_uptime_alerts(); $this->check_error_alerts(); $this->check_scheduled_performance_alerts(); } private function check_server_alerts() { if (!$this->alert_rules['server']['enabled']) { return; } $stats = WPPM_Server_Monitor::get_server_stats(); // 检查CPU使用率 if (isset($stats['cpu']['1min']) && $stats['cpu']['1min'] > $this->alert_rules['server']['cpu_threshold']) { $this->trigger_alert('server_cpu', array( 'current_usage' => $stats['cpu']['1min'], 'threshold' => $this->alert_rules['server']['cpu_threshold'], 'timestamp' => $stats['timestamp'] )); } // 检查内存使用率 if (isset($stats['memory']['usage_percent']) && $stats['memory']['usage_percent'] > $this->alert_rules['server']['memory_threshold']) { $this->trigger_alert('server_memory', array( 'current_usage' => $stats['memory']['usage_percent'], 'threshold' => $this->alert_rules['server']['memory_threshold'], 'total_memory' => $stats['memory']['total_human'] ?? 'N/A', 'timestamp' => $stats['timestamp'] )); } // 检查磁盘使用率 if (isset($stats['disk']['usage_percent']) && $stats['disk']['usage_percent'] > $this->alert_rules['server']['disk_threshold']) { $this->trigger_alert('server_disk', array( 'current_usage' => $stats['disk']['usage_percent'], 'threshold' => $this->alert_rules['server']['disk_threshold'], 'free_space' => $stats['disk']['free_human'] ?? 'N/A', 'timestamp' => $stats['timestamp'] )); } } private function check_uptime_alerts() { if (!$this->alert_rules['uptime']['enabled']) { return; } $site_url = home_url(); $timeout = $this->alert_rules['uptime']['timeout']; // 使用wp_remote_get检查网站可访问性 $response = wp_remote_get($site_url, array( 'timeout' => $timeout, 'sslverify' => false )); if (is_wp_error($response)) { $error_message = $response->get_error_message(); $this->trigger_alert('uptime', array( 'site_url' => $site_url, 'error' => $error_message, 'timestamp' => current_time('mysql') )); // 记录宕机时间 $this->log_downtime($site_url, $error_message); } else { $response_code = wp_remote_retrieve_response_code($response); if ($response_code >= 400) { $this->trigger_alert('http_error', array( 'site_url' => $site_url, 'status_code' => $response_code, 'timestamp' => current_time('mysql') )); } } } private function check_error_alerts() { if (!$this->alert_rules['errors']['enabled']) { return; } global $wpdb; // 检查最近的PHP错误 if ($this->alert_rules['errors']['php_errors']) { $error_log_path = ini_get('error_log'); if ($error_log_path && file_exists($error_log_path)) { $recent_errors = $this->get_recent_php_errors($error_log_path); if (!empty($recent_errors)) { $this->trigger_alert('php_errors', array( 'error_count' => count($recent_errors), 'recent_errors' => array_slice($recent_errors, 0, 5), 'timestamp' => current_time('mysql') )); } } } } private function check_scheduled_performance_alerts() { global $wpdb; $table_name = $wpdb->prefix . 'wppm_performance_logs'; $threshold = $this->alert_rules['performance']['threshold']; $consecutive = $this->alert_rules['performance']['consecutive_failures']; // 检查过去一小时内的性能数据 $one_hour_ago = date('Y-m-d H:i:s', strtotime('-1 hour')); $slow_pages = $wpdb->get_results($wpdb->prepare( "SELECT url, COUNT(*) as slow_count, AVG(total_time) as avg_time FROM $table_name WHERE created_at > %s AND total_time > %f GROUP BY url HAVING slow_count >= %d", $one_hour_ago, $threshold, $consecutive )); foreach ($slow_pages as $page) { $this->trigger_alert('performance_trend', array( 'page_url' => $page->url, 'slow_count' => $page->slow_count, 'average_time' => round($page->avg_time, 2), 'threshold' => $threshold, 'time_period' => '过去1小时', 'timestamp' => current_time('mysql') )); } } private function trigger_alert($alert_type, $data) { // 检查是否已经发送过相同类型的告警(防止告警风暴) if ($this->is_alert_cooldown($alert_type, $data)) { return; } // 获取告警配置 $alert_config = $this->get_alert_config($alert_type); if (!$alert_config || !$alert_config['enabled']) { return; } // 准备告警消息 $message = $this->format_alert_message($alert_type, $data); $subject = $this->format_alert_subject($alert_type, $data); // 通过配置的渠道发送告警 foreach ($alert_config['notification_channels'] as $channel) { switch ($channel) { case 'email': $this->send_email_alert($subject, $message, $alert_type); break; case 'dashboard': $this->add_dashboard_notification($subject, $message, $alert_type); break; case 'sms': $this->send_sms_alert($message, $alert_type); break; case 'webhook': $this->send_webhook_alert($alert_type, $data); break; } } // 记录告警 $this->log_alert($alert_type, $data, $message); // 设置冷却时间 $this->set_alert_cooldown($alert_type, $data); } private function format_alert_message($alert_type, $data) { $messages = array( 'performance' => "网站性能告警nn页面加载时间超过阈值n页面: {page_url}n加载时间: {load_time}秒n阈值: {threshold}秒n时间: {timestamp}", 'server_cpu' => "服务器CPU告警nnCPU使用率超过阈值n当前使用率: {current_usage}%n阈值: {threshold}%n时间: {timestamp}", 'server_memory' => "服务器内存告警nn内存使用率超过阈值n当前使用率: {current_usage}%n总内存: {total_memory}n阈值: {threshold}%n时间: {timestamp}", 'uptime' => "网站可用性告警nn网站无法访问n网址: {site_url}n错误: {error}n时间: {timestamp}", 'php_errors' => "PHP错误告警nn检测到新的PHP错误n错误数量: {error_count}n时间: {timestamp}" ); $template = $messages[$alert_type] ?? "系统告警nn类型: {$alert_type}n数据: " . json_encode($data, JSON_PRETTY_PRINT); // 替换模板变量 foreach ($data as $key => $value) { if (is_array($value)) { $value = json_encode($value, JSON_PRETTY_PRINT); } $template = str_replace('{' . $key . '}', $value, $template); } return $template; } private function send_email_alert($subject, $message, $alert_type) { $admin_email = get_option('admin_email'); $site_name = get_bloginfo('name'); $headers = array('Content-Type: text/plain; charset=UTF-8'); // 添加紧急标记 if (in_array($alert_type, array('uptime', 'server_cpu', 'server_memory'))) { $subject = '[紧急] ' . $subject; } wp_mail($admin_email, $subject, $message, $headers); } private function log_alert($alert_type, $data, $message) { global $wpdb; $table_name = $wpdb->prefix . 'wppm_alert_logs'; $wpdb->insert( $table_name, array( 'alert_type' => $alert_type, 'alert_data' => maybe_serialize($data), 'message' => $message, 'sent_via' => maybe_serialize($this->get_alert_config($alert_type)['notification_channels'] ?? array()), 'created_at' => current_time('mysql') ), array('%s', '%s', '%s', '%s', '%s') ); } } ### 3.2 告警冷却与防风暴机制 // 在WPPM_Alert_System类中添加以下方法private function is_alert_cooldown($alert_type, $data) { $cooldown_key = 'wppm_alert_cooldown_' . $alert_type . '_' . md5(json_encode($data)); $cooldown_time = get_transient($cooldown_key); return $cooldown_time !== false; } private function set_alert_cooldown($alert_type, $data) { $cooldown_key = 'wppm_alert_cooldown_' . $alert_type . '_' . md5(json_encode($data)); // 根据告警类型设置不同的冷却时间 $cooldown_periods = array( 'performance' => HOUR_IN_SECONDS, // 1小时 'server_cpu' => 30 * MINUTE_IN_SECONDS, // 30分钟 'server_memory' => 30 * MINUTE_IN_SECONDS, 'uptime' => 15 * MINUTE_IN_SECONDS, // 15分钟 'php_errors' => 2 * HOUR_IN_SECONDS // 2小时 ); $cooldown = $cooldown_periods[$alert_type] ?? HOUR_IN_SECONDS; set_transient($cooldown_key, time(), $cooldown); } private function get_alert_config($alert_type) { $config_map = array( 'performance' => $this->alert_rules['performance'], 'performance_trend' => $this->alert_rules['performance'], 'server_cpu' => $this->alert_rules['server'], 'server_memory' => $this->alert_rules['server'], 'server_d
发表评论实战教程:在WordPress网站中集成智能客服机器人与常见问题解答库 引言:为什么现代网站需要智能客服与FAQ系统 在当今数字化时代,网站已不仅仅是信息展示平台,更是企业与用户互动的重要渠道。随着人工智能技术的快速发展,智能客服机器人已成为提升用户体验、降低运营成本的关键工具。据统计,集成智能客服的网站用户满意度平均提升40%,客服成本降低30%以上。 WordPress作为全球最流行的内容管理系统,占据了互联网上超过43%的网站份额。其强大的扩展性和开放性使其成为实现各种功能的理想平台。本教程将详细指导您如何通过代码二次开发,在WordPress网站中集成智能客服机器人与常见问题解答库,同时实现一些实用的互联网小工具功能。 第一部分:环境准备与基础配置 1.1 WordPress开发环境搭建 在开始之前,我们需要准备一个适合开发的WordPress环境: // 检查WordPress环境要求 if (version_compare(PHP_VERSION, '7.4', '<')) { die('需要PHP 7.4或更高版本'); } // 启用WordPress调试模式(仅限开发环境) define('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false); 1.2 创建自定义插件框架 我们将创建一个独立的插件来管理所有功能,避免直接修改主题文件: /* Plugin Name: 智能客服与工具套件 Plugin URI: https://yourwebsite.com/ Description: 集成智能客服机器人、FAQ系统及实用小工具 Version: 1.0.0 Author: 您的名称 License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('AI_CHATBOT_VERSION', '1.0.0'); define('AI_CHATBOT_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('AI_CHATBOT_PLUGIN_URL', plugin_dir_url(__FILE__)); 第二部分:构建智能客服机器人系统 2.1 设计客服机器人数据库结构 我们需要创建数据库表来存储对话记录和机器人知识库: // 安装时创建数据库表 function ai_chatbot_install_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_prefix = $wpdb->prefix . 'ai_chatbot_'; // 对话记录表 $conversations_table = $table_prefix . 'conversations'; $sql1 = "CREATE TABLE IF NOT EXISTS $conversations_table ( id bigint(20) NOT NULL AUTO_INCREMENT, session_id varchar(100) NOT NULL, user_message text NOT NULL, bot_response text NOT NULL, intent varchar(100) DEFAULT NULL, confidence decimal(4,3) DEFAULT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY session_id (session_id), KEY intent (intent) ) $charset_collate;"; // 知识库表 $knowledge_table = $table_prefix . 'knowledge_base'; $sql2 = "CREATE TABLE IF NOT EXISTS $knowledge_table ( id bigint(20) NOT NULL AUTO_INCREMENT, question text NOT NULL, answer text NOT NULL, category varchar(100) DEFAULT 'general', tags text DEFAULT NULL, priority int(11) DEFAULT 1, is_active tinyint(1) DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), FULLTEXT KEY question_answer (question, answer) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql1); dbDelta($sql2); } register_activation_hook(__FILE__, 'ai_chatbot_install_tables'); 2.2 实现自然语言处理基础功能 虽然完整的NLP系统很复杂,但我们可以实现一个基础的意图识别系统: class SimpleNLPProcessor { private $patterns = array(); public function __construct() { $this->initialize_patterns(); } private function initialize_patterns() { // 问候语识别 $this->patterns['greeting'] = array( 'patterns' => array('/你好/', '/您好/', '/hello/', '/hi/', '/早上好/', '/下午好/'), 'response' => '您好!我是智能客服,很高兴为您服务。' ); // 价格查询 $this->patterns['price'] = array( 'patterns' => array('/价格/', '/多少钱/', '/收费/', '/cost/', '/price/', '/多少钱/'), 'response' => '我们的产品有不同的价格方案,具体取决于您选择的套餐。' ); // 联系信息 $this->patterns['contact'] = array( 'patterns' => array('/电话/', '/联系方式/', '/联系你们/', '/contact/', '/phone/'), 'response' => '我们的客服电话是:400-123-4567,工作时间:周一至周五 9:00-18:00' ); } public function process_message($message) { $message = strtolower(trim($message)); foreach ($this->patterns as $intent => $data) { foreach ($data['patterns'] as $pattern) { if (preg_match($pattern, $message)) { return array( 'intent' => $intent, 'confidence' => 0.8, 'response' => $data['response'] ); } } } // 如果没有匹配到模式,尝试从知识库查找 return $this->search_knowledge_base($message); } private function search_knowledge_base($message) { global $wpdb; $table_name = $wpdb->prefix . 'ai_chatbot_knowledge_base'; // 简单的关键词匹配 $keywords = explode(' ', $message); $search_conditions = array(); foreach ($keywords as $keyword) { if (strlen($keyword) > 2) { // 忽略太短的词 $search_conditions[] = $wpdb->prepare("(question LIKE %s OR answer LIKE %s)", '%' . $wpdb->esc_like($keyword) . '%', '%' . $wpdb->esc_like($keyword) . '%' ); } } if (empty($search_conditions)) { return array( 'intent' => 'unknown', 'confidence' => 0.1, 'response' => '抱歉,我没有理解您的问题。您可以尝试换一种方式提问,或联系人工客服。' ); } $sql = "SELECT * FROM $table_name WHERE is_active = 1 AND (" . implode(' OR ', $search_conditions) . ") ORDER BY priority DESC LIMIT 1"; $result = $wpdb->get_row($sql); if ($result) { return array( 'intent' => 'knowledge_base', 'confidence' => 0.7, 'response' => $result->answer, 'source_id' => $result->id ); } return array( 'intent' => 'unknown', 'confidence' => 0.1, 'response' => '抱歉,我暂时无法回答这个问题。已记录您的问题,我们的客服人员会尽快回复。' ); } } 2.3 创建前端聊天界面 设计一个美观的聊天界面组件: class ChatbotFrontend { public function __construct() { add_action('wp_footer', array($this, 'render_chat_widget')); add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts')); add_action('wp_ajax_chatbot_send_message', array($this, 'handle_message')); add_action('wp_ajax_nopriv_chatbot_send_message', array($this, 'handle_message')); } public function enqueue_scripts() { wp_enqueue_style('ai-chatbot-style', AI_CHATBOT_PLUGIN_URL . 'assets/css/chatbot.css', array(), AI_CHATBOT_VERSION ); wp_enqueue_script('ai-chatbot-script', AI_CHATBOT_PLUGIN_URL . 'assets/js/chatbot.js', array('jquery'), AI_CHATBOT_VERSION, true ); wp_localize_script('ai-chatbot-script', 'chatbot_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('chatbot_nonce') )); } public function render_chat_widget() { ?> <div id="ai-chatbot-widget" class="ai-chatbot-hidden"> <div class="chatbot-header"> <h3>智能客服</h3> <button class="chatbot-close">×</button> </div> <div class="chatbot-messages" id="chatbot-messages"> <div class="message bot-message"> <div class="message-content"> 您好!我是智能客服,有什么可以帮您的吗? </div> <div class="message-time"><?php echo date('H:i'); ?></div> </div> </div> <div class="chatbot-input-area"> <input type="text" id="chatbot-input" placeholder="请输入您的问题..." maxlength="500"> <button id="chatbot-send">发送</button> </div> <div class="chatbot-quick-questions"> <span>常见问题:</span> <button class="quick-question" data-question="你们的产品价格是多少?">产品价格</button> <button class="quick-question" data-question="怎么联系客服?">联系客服</button> <button class="quick-question" data-question="退货政策是什么?">退货政策</button> </div> </div> <button id="ai-chatbot-toggle" class="chatbot-toggle-button"> <span class="chat-icon">💬</span> <span class="unread-count" id="unread-count" style="display:none">0</span> </button> <?php } public function handle_message() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'chatbot_nonce')) { wp_die('安全验证失败'); } $message = sanitize_text_field($_POST['message']); $session_id = sanitize_text_field($_POST['session_id']); // 处理消息 $nlp = new SimpleNLPProcessor(); $response = $nlp->process_message($message); // 保存对话记录 $this->save_conversation($session_id, $message, $response); wp_send_json_success(array( 'response' => $response['response'], 'intent' => $response['intent'] )); } private function save_conversation($session_id, $user_message, $bot_response) { global $wpdb; $table_name = $wpdb->prefix . 'ai_chatbot_conversations'; $wpdb->insert($table_name, array( 'session_id' => $session_id, 'user_message' => $user_message, 'bot_response' => $bot_response['response'], 'intent' => $bot_response['intent'], 'confidence' => $bot_response['confidence'], 'created_at' => current_time('mysql') )); } } 第三部分:构建FAQ常见问题解答系统 3.1 创建FAQ自定义文章类型 class FAQSystem { public function __construct() { add_action('init', array($this, 'register_faq_post_type')); add_action('add_meta_boxes', array($this, 'add_faq_meta_boxes')); add_action('save_post_faq', array($this, 'save_faq_meta')); add_shortcode('faq_list', array($this, 'faq_list_shortcode')); add_action('wp_ajax_search_faq', array($this, 'ajax_search_faq')); add_action('wp_ajax_nopriv_search_faq', array($this, 'ajax_search_faq')); } public function register_faq_post_type() { $labels = array( 'name' => '常见问题', 'singular_name' => '常见问题', 'menu_name' => 'FAQ管理', 'add_new' => '添加问题', 'add_new_item' => '添加新问题', 'edit_item' => '编辑问题', 'new_item' => '新问题', 'view_item' => '查看问题', 'search_items' => '搜索问题', 'not_found' => '未找到问题', 'not_found_in_trash' => '回收站中未找到问题' ); $args = array( 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => array('slug' => 'faq'), 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => 25, 'menu_icon' => 'dashicons-editor-help', 'supports' => array('title', 'editor', 'thumbnail', 'excerpt'), 'show_in_rest' => true ); register_post_type('faq', $args); // 注册分类 register_taxonomy('faq_category', 'faq', array( 'label' => '问题分类', 'rewrite' => array('slug' => 'faq-category'), 'hierarchical' => true, 'show_admin_column' => true )); } public function add_faq_meta_boxes() { add_meta_box( 'faq_additional_info', '附加信息', array($this, 'render_faq_meta_box'), 'faq', 'side', 'default' ); } public function render_faq_meta_box($post) { wp_nonce_field('faq_meta_box', 'faq_meta_box_nonce'); $priority = get_post_meta($post->ID, '_faq_priority', true); $keywords = get_post_meta($post->ID, '_faq_keywords', true); $views = get_post_meta($post->ID, '_faq_views', true) ?: 0; ?> <p> <label for="faq_priority">显示优先级:</label> <select id="faq_priority" name="faq_priority"> <option value="1" <?php selected($priority, 1); ?>>低</option> <option value="2" <?php selected($priority, 2); ?>>中</option> <option value="3" <?php selected($priority, 3); ?>>高</option> </select> </p> <p> <label for="faq_keywords">关键词:</label> <input type="text" id="faq_keywords" name="faq_keywords" value="<?php echo esc_attr($keywords); ?>" placeholder="用逗号分隔" style="width:100%"> </p> <p>浏览次数:<?php echo intval($views); ?></p> <?php } public function save_faq_meta($post_id) { if (!isset($_POST['faq_meta_box_nonce']) || !wp_verify_nonce($_POST['faq_meta_box_nonce'], 'faq_meta_box')) { return; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (!current_user_can('edit_post', $post_id)) { return; } if (isset($_POST['faq_priority'])) { update_post_meta($post_id, '_faq_priority', sanitize_text_field($_POST['faq_priority'])); } if (isset($_POST['faq_keywords'])) { update_post_meta($post_id, '_faq_keywords', sanitize_text_field($_POST['faq_keywords'])); } // 同步到机器人知识库 $this->sync_to_knowledge_base($post_id); } private function sync_to_knowledge_base($post_id) { global $wpdb; $table_name = $wpdb->prefix . 'ai_chatbot_knowledge_base'; $post = get_post($post_id); if ($post->post_type !== 'faq' || $post->post_status !== 'publish') { // 如果FAQ被删除或未发布,从知识库移除 $wpdb->delete($table_name, array('source_post_id' => $post_id)); return; } $question = $post->post_title; $answer = wp_strip_all_tags($post->post_content); // 检查是否已存在 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_name WHERE source_post_id = %d", $post_id )); if ($existing) { // 更新 $wpdb->update($table_name, array( 'question' => $question, 'answer' => $answer, 'updated_at' => current_time('mysql') ), array('id' => $existing) ); } else { // 插入 // 插入新记录 $wpdb->insert($table_name, array( 'question' => $question, 'answer' => $answer, 'category' => 'faq', 'source_post_id' => $post_id, 'priority' => get_post_meta($post_id, '_faq_priority', true) ?: 1, 'created_at' => current_time('mysql'), 'updated_at' => current_time('mysql') ) ); } } public function faq_list_shortcode($atts) { $atts = shortcode_atts(array( 'category' => '', 'limit' => 10, 'show_search' => true, 'collapse' => true ), $atts); ob_start(); ?> <div class="faq-container"> <?php if ($atts['show_search']): ?> <div class="faq-search-box"> <input type="text" id="faq-search-input" placeholder="搜索常见问题..."> <button id="faq-search-button">搜索</button> <div id="faq-search-results" class="search-results"></div> </div> <?php endif; ?> <div class="faq-categories"> <?php $categories = get_terms(array( 'taxonomy' => 'faq_category', 'hide_empty' => true )); foreach ($categories as $category): if ($atts['category'] && $category->slug !== $atts['category']) continue; ?> <div class="faq-category-section"> <h3 class="faq-category-title"><?php echo esc_html($category->name); ?></h3> <div class="faq-questions"> <?php $args = array( 'post_type' => 'faq', 'posts_per_page' => $atts['limit'], 'tax_query' => array( array( 'taxonomy' => 'faq_category', 'field' => 'term_id', 'terms' => $category->term_id ) ), 'meta_key' => '_faq_priority', 'orderby' => array('meta_value_num' => 'DESC', 'date' => 'DESC') ); $faq_query = new WP_Query($args); if ($faq_query->have_posts()): while ($faq_query->have_posts()): $faq_query->the_post(); $this->render_faq_item(get_the_ID(), $atts['collapse']); endwhile; else: echo '<p>暂无相关问题</p>'; endif; wp_reset_postdata(); ?> </div> </div> <?php endforeach; ?> </div> </div> <script> jQuery(document).ready(function($) { // FAQ搜索功能 $('#faq-search-button').on('click', function() { performFaqSearch(); }); $('#faq-search-input').on('keyup', function(e) { if (e.keyCode === 13) { performFaqSearch(); } }); function performFaqSearch() { var searchTerm = $('#faq-search-input').val(); if (searchTerm.length < 2) { $('#faq-search-results').html('<p>请输入至少2个字符</p>'); return; } $.ajax({ url: '<?php echo admin_url("admin-ajax.php"); ?>', type: 'POST', data: { action: 'search_faq', search: searchTerm, nonce: '<?php echo wp_create_nonce("faq_search_nonce"); ?>' }, beforeSend: function() { $('#faq-search-results').html('<p>搜索中...</p>'); }, success: function(response) { if (response.success) { $('#faq-search-results').html(response.data.html); } else { $('#faq-search-results').html('<p>搜索失败</p>'); } } }); } // 折叠/展开功能 $('.faq-question-title').on('click', function() { if (<?php echo $atts['collapse'] ? 'true' : 'false'; ?>) { $(this).toggleClass('active'); $(this).next('.faq-answer').slideToggle(); } }); }); </script> <?php return ob_get_clean(); } private function render_faq_item($post_id, $collapsible = true) { $post = get_post($post_id); $views = get_post_meta($post_id, '_faq_views', true) ?: 0; // 更新浏览次数 update_post_meta($post_id, '_faq_views', $views + 1); ?> <div class="faq-item"> <div class="faq-question-title <?php echo $collapsible ? 'collapsible' : ''; ?>"> <span class="faq-q">Q:</span> <span class="faq-question-text"><?php echo esc_html($post->post_title); ?></span> <?php if ($collapsible): ?> <span class="faq-toggle-icon">▼</span> <?php endif; ?> <span class="faq-views"><?php echo $views + 1; ?>次浏览</span> </div> <div class="faq-answer" <?php echo $collapsible ? 'style="display:none;"' : ''; ?>> <span class="faq-a">A:</span> <div class="faq-answer-content"> <?php echo apply_filters('the_content', $post->post_content); ?> </div> <div class="faq-meta"> <span class="faq-date">更新日期: <?php echo get_the_modified_date('', $post_id); ?></span> <button class="faq-helpful-btn" data-faq-id="<?php echo $post_id; ?>"> 有帮助 (<span class="helpful-count">0</span>) </button> </div> </div> </div> <?php } public function ajax_search_faq() { if (!wp_verify_nonce($_POST['nonce'], 'faq_search_nonce')) { wp_die('安全验证失败'); } $search_term = sanitize_text_field($_POST['search']); $args = array( 'post_type' => 'faq', 'posts_per_page' => 10, 's' => $search_term, 'meta_query' => array( 'relation' => 'OR', array( 'key' => '_faq_keywords', 'value' => $search_term, 'compare' => 'LIKE' ) ) ); $search_query = new WP_Query($args); ob_start(); if ($search_query->have_posts()): echo '<div class="faq-search-results-list">'; echo '<h4>找到 ' . $search_query->found_posts . ' 个相关结果</h4>'; while ($search_query->have_posts()): $search_query->the_post(); $this->render_faq_item(get_the_ID(), false); endwhile; echo '</div>'; else: echo '<p>未找到相关问题。建议您:</p>'; echo '<ul>'; echo '<li>检查拼写是否正确</li>'; echo '<li>尝试使用其他关键词</li>'; echo '<li>联系人工客服获取帮助</li>'; echo '</ul>'; endif; wp_reset_postdata(); wp_send_json_success(array( 'html' => ob_get_clean(), 'count' => $search_query->found_posts )); } } 第四部分:集成实用互联网小工具 4.1 创建多功能工具集 class WebToolsCollection { public function __construct() { add_shortcode('web_tools', array($this, 'web_tools_shortcode')); add_action('wp_ajax_calculate_tool', array($this, 'handle_tool_calculation')); add_action('wp_ajax_nopriv_calculate_tool', array($this, 'handle_tool_calculation')); } public function web_tools_shortcode($atts) { $atts = shortcode_atts(array( 'tools' => 'all', // all,calculator,converter,generator 'layout' => 'tabs' // tabs,accordion,grid ), $atts); ob_start(); ?> <div class="web-tools-container layout-<?php echo esc_attr($atts['layout']); ?>"> <h2 class="tools-title">实用在线工具</h2> <?php if ($atts['layout'] === 'tabs'): ?> <div class="tools-tabs"> <div class="tab-headers"> <button class="tab-header active" data-tab="calculator">计算器</button> <button class="tab-header" data-tab="converter">单位转换</button> <button class="tab-header" data-tab="generator">生成器</button> <button class="tab-header" data-tab="text-tools">文本工具</button> </div> <div class="tab-contents"> <div class="tab-content active" id="calculator-tab"> <?php $this->render_calculator_tools(); ?> </div> <div class="tab-content" id="converter-tab"> <?php $this->render_converter_tools(); ?> </div> <div class="tab-content" id="generator-tab"> <?php $this->render_generator_tools(); ?> </div> <div class="tab-content" id="text-tools-tab"> <?php $this->render_text_tools(); ?> </div> </div> </div> <?php endif; ?> <div class="tools-history" id="tools-history"> <h4>计算历史</h4> <ul id="history-list"></ul> <button id="clear-history" class="btn-secondary">清空历史</button> </div> </div> <script> jQuery(document).ready(function($) { // 标签页切换 $('.tab-header').on('click', function() { var tabId = $(this).data('tab'); $('.tab-header').removeClass('active'); $(this).addClass('active'); $('.tab-content').removeClass('active'); $('#' + tabId + '-tab').addClass('active'); }); // 计算器功能 $('.calculate-btn').on('click', function() { var toolType = $(this).data('tool'); var formData = {}; // 收集表单数据 $(this).closest('.tool-form').find('input, select').each(function() { if ($(this).attr('name')) { formData[$(this).attr('name')] = $(this).val(); } }); $.ajax({ url: '<?php echo admin_url("admin-ajax.php"); ?>', type: 'POST', data: { action: 'calculate_tool', tool_type: toolType, data: formData, nonce: '<?php echo wp_create_nonce("tools_calculation_nonce"); ?>' }, success: function(response) { if (response.success) { // 显示结果 $('#result-' + toolType).html(response.data.result); // 添加到历史记录 addToHistory(toolType, formData, response.data.result); } } }); }); // 历史记录功能 function addToHistory(toolType, input, result) { var historyItem = { tool: toolType, input: input, result: result, time: new Date().toLocaleTimeString() }; // 获取现有历史 var history = JSON.parse(localStorage.getItem('tools_history') || '[]'); // 添加到历史记录 history.unshift(historyItem); // 只保留最近10条记录 if (history.length > 10) { history = history.slice(0, 10); } // 保存到本地存储 localStorage.setItem('tools_history', JSON.stringify(history)); // 更新显示 updateHistoryDisplay(); } function updateHistoryDisplay() { var history = JSON.parse(localStorage.getItem('tools_history') || '[]'); var html = ''; history.forEach(function(item) { html += '<li>'; html += '<strong>' + getToolName(item.tool) + '</strong><br>'; html += '输入: ' + JSON.stringify(item.input) + '<br>'; html += '结果: ' + item.result + '<br>'; html += '<small>' + item.time + '</small>'; html += '</li>'; }); $('#history-list').html(html || '<li>暂无历史记录</li>'); } function getToolName(toolType) { var names = { 'loan_calculator': '贷款计算器', 'currency_converter': '货币转换', 'password_generator': '密码生成器', 'text_counter': '字数统计' }; return names[toolType] || toolType; } // 清空历史记录 $('#clear-history').on('click', function() { localStorage.removeItem('tools_history'); updateHistoryDisplay(); }); // 初始化时显示历史记录 updateHistoryDisplay(); }); </script> <?php return ob_get_clean(); } private function render_calculator_tools() { ?> <div class="tool-section"> <h3>贷款计算器</h3> <div class="tool-form"> <div class="form-group"> <label>贷款金额(元):</label> <input type="number" name="loan_amount" value="100000" min="1000" step="1000"> </div> <div class="form-group"> <label>贷款期限(月):</label> <input type="number" name="loan_term" value="12" min="1" max="360"> </div> <div class="form-group"> <label>年利率(%):</label> <input type="number" name="interest_rate" value="5.0" min="0.1" step="0.1" max="50"> </div> <button class="calculate-btn" data-tool="loan_calculator">计算</button> <div class="tool-result" id="result-loan_calculator"> 结果将显示在这里 </div> </div> </div> <div class="tool-section"> <h3>投资收益计算器</h3> <div class="tool-form"> <div class="form-group"> <label>投资本金(元):</label> <input type="number" name="principal" value="10000" min="100"> </div> <div class="form-group"> <label>年化收益率(%):</label> <input type="number" name="annual_return" value="8" min="0.1" step="0.1"> </div> <div class="form-group"> <label>投资年限:</label> <input type="number" name="years" value="5" min="1" max="50"> </div> <button class="calculate-btn" data-tool="investment_calculator">计算</button> <div class="tool-result" id="result-investment_calculator"> 结果将显示在这里 </div> </div> </div> <?php } private function render_converter_tools() { ?> <div class="tool-section"> <h3>货币转换器</h3> <div class="tool-form"> <div class="form-group"> <label>金额:</label> <input type="number" name="amount" value="100" min="0.01" step="0.01"> </div> <div class="form-group"> <label>从:</label> <select name="from_currency"> <option value="USD">美元 (USD)</option> <option value="CNY" selected>人民币 (CNY)</option> <option value="EUR">欧元 (EUR)</option> <option value="GBP">英镑 (GBP)</option> <option value="JPY">日元 (JPY)</option> </select> </div> <div class="form-group"> <label>到:</label> <select name="to_currency"> <option value="USD">美元 (USD)</option> <option value="CNY">人民币 (CNY)</option> <option value="EUR" selected>欧元 (EUR)</option> <option value="GBP">英镑 (GBP)</option> <option value="JPY">日元 (JPY)</option> </select> </div> <button class="calculate-btn" data-tool="currency_converter">转换</button> <div class="tool-result" id="result-currency_converter"> 结果将显示在这里 </div> </div> </div> <div class="tool-section"> <h3>单位转换器</h3> <div class="tool-form"> <div class="form-group"> <label>数值:</label> <input type="number" name="value" value="1" step="any"> </div> <div class="form-group"> <label>从:</label> <select name="from_unit"> <option value="km">千米</option>
发表评论详细指南:为WordPress开发自定义表单与工作流自动化工具 引言:为什么WordPress需要自定义表单与工作流自动化 在当今数字化时代,网站不仅仅是展示信息的平台,更是与用户互动、收集数据、自动化业务流程的重要工具。WordPress作为全球最流行的内容管理系统,拥有超过40%的网站市场份额,其强大的可扩展性使其成为开发各种互联网小工具的理想平台。 然而,许多WordPress用户发现,现有的表单插件和工作流工具要么功能过于复杂,要么无法完全满足特定业务需求。通过WordPress代码二次开发,我们可以创建完全符合需求的自定义表单和工作流自动化工具,将常用互联网小工具功能无缝集成到网站中。 本指南将详细介绍如何通过WordPress程序代码二次开发,实现自定义表单与工作流自动化工具,帮助您提升网站功能性和用户体验。 第一部分:理解WordPress开发基础 1.1 WordPress架构概述 在开始开发之前,我们需要理解WordPress的基本架构。WordPress采用MVC(模型-视图-控制器)的变体架构,主要包括: 主题系统:控制网站外观和前端展示 插件系统:扩展WordPress功能 核心文件:WordPress基础功能 数据库:存储所有网站数据 1.2 开发环境搭建 为了进行WordPress二次开发,您需要准备以下环境: 本地开发环境:使用XAMPP、MAMP或Local by Flywheel 代码编辑器:推荐VS Code、PHPStorm或Sublime Text 版本控制系统:Git用于代码管理 调试工具:Query Monitor、Debug Bar等WordPress调试插件 1.3 WordPress钩子系统 WordPress的钩子(Hooks)系统是扩展功能的核心机制,分为两种类型: 动作(Actions):在特定时间点执行自定义代码 过滤器(Filters):修改数据后再返回 理解并熟练使用钩子系统是开发自定义表单和工作流工具的关键。 第二部分:创建自定义表单系统 2.1 表单需求分析与设计 在开始编码之前,首先明确表单需求: 表单类型:联系表单、注册表单、调查问卷、订单表单等 字段类型:文本、邮箱、电话、下拉选择、文件上传等 验证需求:前端和后端验证规则 提交处理:数据存储、邮件通知、重定向等 2.2 创建基础表单插件 让我们从创建一个简单的表单插件开始: <?php /** * Plugin Name: 自定义表单系统 * Description: 为WordPress创建自定义表单和工作流自动化 * Version: 1.0.0 * Author: 您的名称 */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('CUSTOM_FORM_VERSION', '1.0.0'); define('CUSTOM_FORM_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('CUSTOM_FORM_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 class Custom_Form_Init { public function __construct() { // 注册激活和停用钩子 register_activation_hook(__FILE__, array($this, 'activate')); register_deactivation_hook(__FILE__, array($this, 'deactivate')); // 初始化插件功能 add_action('init', array($this, 'init')); } public function activate() { // 创建必要的数据库表 $this->create_tables(); // 设置默认选项 update_option('custom_form_version', CUSTOM_FORM_VERSION); } public function deactivate() { // 清理临时数据 } public function init() { // 加载文本域 load_plugin_textdomain('custom-form', false, dirname(plugin_basename(__FILE__)) . '/languages'); // 注册短代码 add_shortcode('custom_form', array($this, 'form_shortcode')); // 处理表单提交 add_action('admin_post_nopriv_custom_form_submit', array($this, 'handle_form_submit')); add_action('admin_post_custom_form_submit', array($this, 'handle_form_submit')); } private function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'custom_form_submissions'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, form_id varchar(50) NOT NULL, form_data longtext NOT NULL, submitted_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, ip_address varchar(45), user_agent text, status varchar(20) DEFAULT 'pending', PRIMARY KEY (id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } public function form_shortcode($atts) { // 解析短代码属性 $atts = shortcode_atts(array( 'id' => 'contact', 'title' => '联系我们' ), $atts, 'custom_form'); // 输出表单HTML ob_start(); include(CUSTOM_FORM_PLUGIN_DIR . 'templates/form-template.php'); return ob_get_clean(); } public function handle_form_submit() { // 验证nonce if (!isset($_POST['custom_form_nonce']) || !wp_verify_nonce($_POST['custom_form_nonce'], 'custom_form_action')) { wp_die('安全验证失败'); } // 处理表单数据 $form_data = array(); foreach ($_POST as $key => $value) { if ($key !== 'custom_form_nonce' && $key !== '_wp_http_referer' && $key !== 'action') { $form_data[sanitize_text_field($key)] = sanitize_text_field($value); } } // 保存到数据库 $this->save_submission($form_data); // 发送邮件通知 $this->send_notification($form_data); // 重定向用户 $redirect_url = isset($_POST['_wp_http_referer']) ? $_POST['_wp_http_referer'] : home_url(); wp_redirect(add_query_arg('form_status', 'success', $redirect_url)); exit; } private function save_submission($data) { global $wpdb; $table_name = $wpdb->prefix . 'custom_form_submissions'; $wpdb->insert( $table_name, array( 'form_id' => 'contact', 'form_data' => json_encode($data), 'ip_address' => $this->get_user_ip(), 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '' ), array('%s', '%s', '%s', '%s') ); } private function send_notification($data) { $to = get_option('admin_email'); $subject = '新的表单提交 - ' . get_bloginfo('name'); $message = "您收到一个新的表单提交:nn"; foreach ($data as $key => $value) { $message .= ucfirst($key) . ": " . $value . "n"; } wp_mail($to, $subject, $message); } private function get_user_ip() { if (!empty($_SERVER['HTTP_CLIENT_IP'])) { return $_SERVER['HTTP_CLIENT_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { return $_SERVER['HTTP_X_FORWARDED_FOR']; } else { return $_SERVER['REMOTE_ADDR']; } } } // 初始化插件 new Custom_Form_Init(); 2.3 创建表单模板 在插件目录中创建templates/form-template.php: <div class="custom-form-container"> <h3><?php echo esc_html($atts['title']); ?></h3> <?php if (isset($_GET['form_status']) && $_GET['form_status'] === 'success'): ?> <div class="form-success"> <p>表单提交成功!我们会尽快与您联系。</p> </div> <?php endif; ?> <form method="post" action="<?php echo admin_url('admin-post.php'); ?>" class="custom-form"> <input type="hidden" name="action" value="custom_form_submit"> <?php wp_nonce_field('custom_form_action', 'custom_form_nonce'); ?> <div class="form-group"> <label for="name">姓名 *</label> <input type="text" id="name" name="name" required> </div> <div class="form-group"> <label for="email">邮箱 *</label> <input type="email" id="email" name="email" required> </div> <div class="form-group"> <label for="phone">电话</label> <input type="tel" id="phone" name="phone"> </div> <div class="form-group"> <label for="message">留言 *</label> <textarea id="message" name="message" rows="5" required></textarea> </div> <div class="form-group"> <button type="submit">提交</button> </div> </form> </div> <style> .custom-form-container { max-width: 600px; margin: 0 auto; padding: 20px; background: #f9f9f9; border-radius: 8px; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; font-weight: bold; } .form-group input, .form-group textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } .form-group button { background: #0073aa; color: white; border: none; padding: 12px 24px; border-radius: 4px; cursor: pointer; font-size: 16px; } .form-group button:hover { background: #005a87; } .form-success { background: #d4edda; color: #155724; padding: 10px; border-radius: 4px; margin-bottom: 20px; } </style> 2.4 添加表单字段验证 为了确保数据质量,我们需要添加前端和后端验证: // 在表单模板中添加JavaScript验证 <script> document.addEventListener('DOMContentLoaded', function() { const form = document.querySelector('.custom-form'); if (form) { form.addEventListener('submit', function(e) { let isValid = true; const emailField = document.getElementById('email'); const phoneField = document.getElementById('phone'); // 邮箱验证 const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/; if (!emailRegex.test(emailField.value)) { alert('请输入有效的邮箱地址'); emailField.focus(); isValid = false; } // 电话验证(如果填写) if (phoneField.value) { const phoneRegex = /^[ds-+()]{10,}$/; if (!phoneRegex.test(phoneField.value.replace(/s/g, ''))) { alert('请输入有效的电话号码'); phoneField.focus(); isValid = false; } } if (!isValid) { e.preventDefault(); } }); } }); </script> // 在后端处理中添加验证 public function handle_form_submit() { // ... 之前的代码 ... // 验证必填字段 $required_fields = array('name', 'email', 'message'); foreach ($required_fields as $field) { if (empty($_POST[$field])) { wp_die('请填写所有必填字段'); } } // 验证邮箱格式 if (!is_email($_POST['email'])) { wp_die('请输入有效的邮箱地址'); } // ... 后续处理代码 ... } 第三部分:构建工作流自动化系统 3.1 工作流概念与设计 工作流自动化是指根据预设规则自动执行一系列操作的过程。在WordPress中,我们可以创建以下类型的工作流: 表单提交后的自动响应 数据验证与处理流程 多步骤审批流程 定时任务与提醒系统 与其他系统的集成工作流 3.2 创建基础工作流引擎 // 在工作流类中 class Custom_Workflow_Engine { private $workflows = array(); public function __construct() { add_action('init', array($this, 'register_workflows')); add_action('custom_form_submitted', array($this, 'trigger_workflows'), 10, 2); } public function register_workflows() { // 注册默认工作流 $this->workflows['new_contact_form'] = array( 'name' => '新联系表单工作流', 'trigger' => 'custom_form_submitted', 'conditions' => array( array( 'field' => 'form_id', 'operator' => 'equals', 'value' => 'contact' ) ), 'actions' => array( array( 'type' => 'email', 'recipient' => 'admin', 'subject' => '新的联系表单提交', 'template' => 'default_contact_notification' ), array( 'type' => 'email', 'recipient' => 'user', 'field' => 'email', 'subject' => '感谢您的联系', 'template' => 'user_auto_reply' ), array( 'type' => 'database', 'action' => 'update_status', 'status' => 'processed' ) ) ); // 允许其他插件添加工作流 $this->workflows = apply_filters('custom_workflow_registry', $this->workflows); } public function trigger_workflows($form_id, $form_data) { foreach ($this->workflows as $workflow_id => $workflow) { if ($this->check_conditions($workflow['conditions'], $form_id, $form_data)) { $this->execute_actions($workflow['actions'], $form_data); } } } private function check_conditions($conditions, $form_id, $form_data) { foreach ($conditions as $condition) { $field_value = ''; if ($condition['field'] === 'form_id') { $field_value = $form_id; } elseif (isset($form_data[$condition['field']])) { $field_value = $form_data[$condition['field']]; } switch ($condition['operator']) { case 'equals': if ($field_value != $condition['value']) return false; break; case 'contains': if (strpos($field_value, $condition['value']) === false) return false; break; case 'greater_than': if (!is_numeric($field_value) || $field_value <= $condition['value']) return false; break; // 添加更多条件运算符 } } return true; } private function execute_actions($actions, $form_data) { foreach ($actions as $action) { switch ($action['type']) { case 'email': $this->execute_email_action($action, $form_data); break; case 'database': $this->execute_database_action($action, $form_data); break; case 'webhook': $this->execute_webhook_action($action, $form_data); break; // 添加更多动作类型 } } } private function execute_email_action($action, $form_data) { $recipient = ''; if ($action['recipient'] === 'admin') { $recipient = get_option('admin_email'); } elseif ($action['recipient'] === 'user' && isset($action['field'])) { $recipient = isset($form_data[$action['field']]) ? $form_data[$action['field']] : ''; } elseif (filter_var($action['recipient'], FILTER_VALIDATE_EMAIL)) { $recipient = $action['recipient']; } if (empty($recipient)) return; $subject = $this->replace_placeholders($action['subject'], $form_data); $message = $this->get_email_template($action['template'], $form_data); wp_mail($recipient, $subject, $message); } private function replace_placeholders($text, $form_data) { foreach ($form_data as $key => $value) { $text = str_replace('{' . $key . '}', $value, $text); } $text = str_replace('{site_name}', get_bloginfo('name'), $text); $text = str_replace('{site_url}', home_url(), $text); $text = str_replace('{current_date}', date_i18n(get_option('date_format')), $text); return $text; } private function get_email_template($template_name, $form_data) { $templates = array( 'default_contact_notification' => "您收到一个新的联系表单提交:nn{form_data}nn提交时间:{current_date}", message}nn此致,n{site_name}团队" ); $template = isset($templates[$template_name]) ? $templates[$template_name] : $templates['default_contact_notification']; // 格式化表单数据 $form_data_text = ""; foreach ($form_data as $key => $value) { $form_data_text .= ucfirst($key) . ": " . $value . "n"; } $template = str_replace('{form_data}', $form_data_text, $template); return $this->replace_placeholders($template, $form_data); } private function execute_database_action($action, $form_data) { global $wpdb; switch ($action['action']) { case 'update_status': $table_name = $wpdb->prefix . 'custom_form_submissions'; $wpdb->update( $table_name, array('status' => $action['status']), array('id' => $form_data['submission_id']), array('%s'), array('%d') ); break; case 'create_post': $post_data = array( 'post_title' => $this->replace_placeholders($action['title'], $form_data), 'post_content' => $this->replace_placeholders($action['content'], $form_data), 'post_status' => isset($action['status']) ? $action['status'] : 'draft', 'post_type' => isset($action['post_type']) ? $action['post_type'] : 'post', 'post_author' => isset($action['author_id']) ? $action['author_id'] : 1 ); $post_id = wp_insert_post($post_data); // 添加自定义字段 if ($post_id && isset($action['meta_fields'])) { foreach ($action['meta_fields'] as $meta_key => $meta_value) { update_post_meta($post_id, $meta_key, $this->replace_placeholders($meta_value, $form_data)); } } break; } } private function execute_webhook_action($action, $form_data) { if (!isset($action['url']) || empty($action['url'])) { return; } $payload = array( 'form_data' => $form_data, 'site' => array( 'name' => get_bloginfo('name'), 'url' => home_url() ), 'timestamp' => current_time('mysql') ); // 添加自定义数据 if (isset($action['custom_data'])) { $payload['custom'] = $action['custom_data']; } $args = array( 'body' => json_encode($payload), 'headers' => array('Content-Type' => 'application/json'), 'timeout' => 30, 'redirection' => 5, 'blocking' => isset($action['blocking']) ? $action['blocking'] : false, 'sslverify' => false ); // 添加自定义请求头 if (isset($action['headers'])) { $args['headers'] = array_merge($args['headers'], $action['headers']); } wp_remote_post($action['url'], $args); } } // 初始化工作流引擎new Custom_Workflow_Engine(); ### 3.3 创建工作流管理界面 为了让用户能够配置工作流,我们需要创建一个管理界面: // 添加管理菜单class Custom_Workflow_Admin { public function __construct() { add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); } public function add_admin_menu() { add_menu_page( '工作流自动化', '工作流', 'manage_options', 'custom-workflows', array($this, 'workflows_admin_page'), 'dashicons-randomize', 30 ); add_submenu_page( 'custom-workflows', '添加新工作流', '添加新工作流', 'manage_options', 'custom-workflow-add', array($this, 'add_workflow_page') ); add_submenu_page( 'custom-workflows', '工作流日志', '日志', 'manage_options', 'custom-workflow-logs', array($this, 'workflow_logs_page') ); } public function workflows_admin_page() { ?> <div class="wrap"> <h1>工作流自动化</h1> <div class="workflow-list-container"> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>工作流名称</th> <th>触发器</th> <th>条件</th> <th>动作</th> <th>状态</th> <th>操作</th> </tr> </thead> <tbody> <?php $workflows = get_option('custom_workflows', array()); if (empty($workflows)) { echo '<tr><td colspan="6">暂无工作流。 <a href="' . admin_url('admin.php?page=custom-workflow-add') . '">创建第一个工作流</a></td></tr>'; } else { foreach ($workflows as $id => $workflow) { ?> <tr> <td><?php echo esc_html($workflow['name']); ?></td> <td><?php echo esc_html($workflow['trigger']); ?></td> <td><?php echo count($workflow['conditions']); ?> 个条件</td> <td><?php echo count($workflow['actions']); ?> 个动作</td> <td> <span class="workflow-status <?php echo $workflow['active'] ? 'active' : 'inactive'; ?>"> <?php echo $workflow['active'] ? '启用' : '禁用'; ?> </span> </td> <td> <a href="<?php echo admin_url('admin.php?page=custom-workflow-add&edit=' . $id); ?>" class="button button-small">编辑</a> <button class="button button-small toggle-workflow" data-id="<?php echo $id; ?>"> <?php echo $workflow['active'] ? '禁用' : '启用'; ?> </button> <button class="button button-small button-link-delete delete-workflow" data-id="<?php echo $id; ?>">删除</button> </td> </tr> <?php } } ?> </tbody> </table> </div> </div> <style> .workflow-status { padding: 3px 8px; border-radius: 3px; font-size: 12px; font-weight: bold; } .workflow-status.active { background: #d4edda; color: #155724; } .workflow-status.inactive { background: #f8d7da; color: #721c24; } </style> <script> jQuery(document).ready(function($) { $('.toggle-workflow').on('click', function() { var workflowId = $(this).data('id'); var button = $(this); $.post(ajaxurl, { action: 'toggle_workflow', workflow_id: workflowId, nonce: '<?php echo wp_create_nonce('workflow_toggle'); ?>' }, function(response) { if (response.success) { location.reload(); } else { alert('操作失败: ' + response.data); } }); }); $('.delete-workflow').on('click', function() { if (!confirm('确定要删除这个工作流吗?此操作不可撤销。')) { return; } var workflowId = $(this).data('id'); var button = $(this); $.post(ajaxurl, { action: 'delete_workflow', workflow_id: workflowId, nonce: '<?php echo wp_create_nonce('workflow_delete'); ?>' }, function(response) { if (response.success) { location.reload(); } else { alert('删除失败: ' + response.data); } }); }); }); </script> <?php } public function add_workflow_page() { $editing = isset($_GET['edit']) ? sanitize_text_field($_GET['edit']) : false; $workflow = $editing ? $this->get_workflow($editing) : false; ?> <div class="wrap"> <h1><?php echo $editing ? '编辑工作流' : '添加新工作流'; ?></h1> <form id="workflow-form" method="post" action="<?php echo admin_url('admin-post.php'); ?>"> <input type="hidden" name="action" value="save_workflow"> <input type="hidden" name="workflow_id" value="<?php echo $editing ? esc_attr($editing) : ''; ?>"> <?php wp_nonce_field('save_workflow_action', 'workflow_nonce'); ?> <div class="workflow-form-section"> <h2>基本信息</h2> <table class="form-table"> <tr> <th><label for="workflow_name">工作流名称</label></th> <td> <input type="text" id="workflow_name" name="workflow_name" value="<?php echo $workflow ? esc_attr($workflow['name']) : ''; ?>" class="regular-text" required> <p class="description">用于识别此工作流的名称</p> </td> </tr> <tr> <th><label for="workflow_trigger">触发器</label></th> <td> <select id="workflow_trigger" name="workflow_trigger" required> <option value="">选择触发器...</option> <option value="custom_form_submitted" <?php selected($workflow && $workflow['trigger'] === 'custom_form_submitted'); ?>> 表单提交 </option> <option value="user_registered" <?php selected($workflow && $workflow['trigger'] === 'user_registered'); ?>> 用户注册 </option> <option value="post_published" <?php selected($workflow && $workflow['trigger'] === 'post_published'); ?>> 文章发布 </option> <option value="scheduled" <?php selected($workflow && $workflow['trigger'] === 'scheduled'); ?>> 定时任务 </option> </select> <p class="description">触发此工作流执行的事件</p> </td> </tr> <tr> <th><label for="workflow_active">状态</label></th> <td> <label> <input type="checkbox" id="workflow_active" name="workflow_active" value="1" <?php checked(!$workflow || $workflow['active']); ?>> 启用此工作流 </label> </td> </tr> </table> </div> <div class="workflow-form-section"> <h2>条件</h2> <div id="conditions-container"> <?php if ($workflow && !empty($workflow['conditions'])) { foreach ($workflow['conditions'] as $index => $condition) { $this->render_condition_row($index, $condition); } } else { $this->render_condition_row(0); } ?> </div> <button type="button" id="add-condition" class="button">添加条件</button> </div> <div class="workflow-form-section"> <h2>动作</h2> <div id="actions-container"> <?php if ($workflow && !empty($workflow['actions'])) { foreach ($workflow['actions'] as $index => $action) { $this->render_action_row($index, $action); } } else { $this->render_action_row(0); } ?> </div> <button type="button" id="add-action" class="button">添加动作</button> </div> <p class="submit"> <button type="submit" class="button button-primary">保存工作流</button> <a href="<?php echo admin_url('admin.php?page=custom-workflows'); ?>" class="button">取消</a> </p> </form> </div> <script> jQuery(document).ready(function($) { var conditionIndex = <?php echo $workflow && !empty($workflow['conditions']) ? count($workflow['conditions']) : 1; ?>; var actionIndex = <?php echo $workflow && !empty($workflow['actions']) ? count($workflow['actions']) : 1; ?>; // 添加条件 $('#add-condition').on('click', function() { $.get('<?php echo admin_url('admin-ajax.php'); ?>', { action: 'get_condition_row', index: conditionIndex, nonce: '<?php echo wp_create_nonce('get_condition_row'); ?>' }, function(response) { if (response.success) { $('#conditions-container').append(response.data); conditionIndex++; } }); }); // 添加动作 $('#add-action').on('click', function() { $.get('<?php echo admin_url('admin-ajax.php'); ?>', { action: 'get_action_row', index: actionIndex, nonce: '<?php echo wp_create_nonce('get_action_row'); ?>' }, function(response) { if (response.success) { $('#actions-container').append(response.data); actionIndex++; } }); }); // 删除行 $(document).on('click', '.remove-row', function() { $(this).closest('.condition-row, .action-row').remove(); }); // 动态显示/隐藏字段 $(document).on('change', '.condition-field, .action-type', function() { var row = $(this).closest('.condition-row, .action-row'); // 根据选择显示/隐藏相关字段 // 这里可以添加更多逻辑 }); }); </script> <style> .workflow-form-section { background: white; padding: 20px; margin-bottom: 20px; border: 1px solid #ccd0d4; border-radius: 4px; } .condition-row, .action-row { background: #f9f9f9; padding: 15px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px; position: relative; } .remove-row { position: absolute; top: 10px; right: 10px; color: #dc3232; text-decoration: none; } .condition-row select, .condition-row input, .action-row select, .action-row input { margin-right: 10px; margin-bottom: 5px; } </style> <?php } private function render_condition_row($index, $condition = array()) { ?> <div class="condition-row"> <a href="#" class="remove-row">删除</a> <select name="conditions[<?php echo $index; ?>][field]" class="condition-field"> <option value="form_id" <?php selected(isset($condition['field']) && $condition['field'] === 'form_id'); ?>>表单ID</option> <option value="email" <?php selected(isset($condition['field']) && $condition['field'] === 'email'); ?>>邮箱地址</option> <option value="custom_field" <?php selected(isset($condition['field']) && $condition['field'] === 'custom_field'); ?>>自定义字段</option> </select> <select name="conditions[<?php echo $index; ?>][operator]"> <option value="equals" <?php selected(isset($condition['operator']) && $condition['operator'] === 'equals'); ?>>等于</option> <option value="contains" <?php selected(isset($condition['operator']) && $condition['operator'] === 'contains'); ?>>包含</option> <option value="not_equals" <?php selected(isset($condition['operator']) && $condition['operator'] === 'not_equals'); ?>>不等于</option> <option value="greater_than" <?php selected(isset($condition['operator']) && $condition['operator'] === 'greater_than'); ?>>大于</option> <option value="less_than" <?php selected(isset($condition['operator']) && $condition['operator'] === 'less_than'); ?>>小于</option> </select> <input type="text" name="conditions[<?php echo $index; ?>][value]" value="<?php echo isset($condition['value']) ? esc_attr($condition['value']) : ''; ?>" placeholder="值" style="width: 200px;"> <?php if (isset($condition['field']) && $condition['field'] === 'custom_field'): ?> <input type="text" name="conditions[<?php echo $index; ?>][custom_field]" value="<?php echo isset($condition['custom_field']) ? esc_attr($condition['custom_field']) : ''; ?>" placeholder="字段名"> <?php endif; ?> </div> <?php } private function render_action_row($index, $action = array()) { $action_type = isset($action['type']) ? $action['type'] : 'email'; ?> <div class="
发表评论手把手教学:集成在线视频会议与屏幕共享工具到WordPress网站 引言:为什么网站需要集成视频会议功能? 在当今数字化时代,线上沟通已成为商业运营、教育、医疗咨询和社交互动的重要组成部分。随着远程工作和在线协作的普及,网站不再仅仅是信息展示的平台,更是连接用户、提供实时服务的门户。集成视频会议与屏幕共享功能到您的WordPress网站,可以显著提升用户体验,增加用户粘性,并为您的业务创造新的价值。 传统的视频会议解决方案通常要求用户离开网站,跳转到第三方平台,这种体验割裂且不够专业。通过将视频会议功能直接集成到您的WordPress网站中,您可以: 提供无缝的用户体验 增强品牌一致性 降低用户使用门槛 收集有价值的用户互动数据 创造新的收入机会(如付费咨询、在线课程等) 本文将详细介绍如何通过代码二次开发,在WordPress网站中集成在线视频会议与屏幕共享功能,让您的网站从静态展示平台转变为动态互动空间。 第一部分:准备工作与环境搭建 1.1 选择合适的视频会议API 在开始集成之前,您需要选择一个合适的视频会议API。市场上有多种选择,每种都有其优缺点: 主流视频会议API对比: API提供商 免费额度 最大参与者 屏幕共享 录制功能 集成难度 Zoom API 40分钟/会议 100人 支持 额外付费 中等 Jitsi Meet 完全开源免费 无限制 支持 支持 较简单 Daily.co 每月2000分钟免费 无限制 支持 支持 简单 Agora.io 每月10000分钟免费 无限制 支持 支持 中等 Twilio Video 免费试用额度 50人 支持 支持 中等 推荐选择:对于大多数WordPress网站,我们推荐使用Jitsi Meet或Daily.co,原因如下: 免费或提供慷慨的免费额度 无需用户注册即可加入会议 提供完善的文档和社区支持 支持屏幕共享和录制功能 1.2 开发环境准备 在开始编码之前,请确保您的开发环境满足以下要求: WordPress环境:WordPress 5.0+,PHP 7.2+,MySQL 5.6+ 代码编辑器:VS Code、Sublime Text或PHPStorm 本地开发环境:XAMPP、MAMP或Local by Flywheel 版本控制:Git(可选但推荐) 浏览器开发者工具:熟悉Chrome或Firefox的开发者工具 1.3 创建WordPress插件框架 我们将创建一个独立的WordPress插件来实现视频会议功能,这样可以确保代码的可维护性和可移植性。 创建插件基本结构: wp-content/plugins/video-meeting-integration/ ├── video-meeting-integration.php # 主插件文件 ├── includes/ │ ├── class-api-handler.php # API处理类 │ ├── class-shortcodes.php # 短代码类 │ └── class-admin-settings.php # 管理设置类 ├── assets/ │ ├── css/ │ │ └── style.css # 前端样式 │ └── js/ │ └── script.js # 前端脚本 ├── templates/ │ └── meeting-room.php # 会议室模板 └── uninstall.php # 卸载脚本 主插件文件基础代码: <?php /** * Plugin Name: 视频会议与屏幕共享集成 * Plugin URI: https://yourwebsite.com/ * Description: 在WordPress网站中集成在线视频会议与屏幕共享功能 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: video-meeting-integration */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('VMI_VERSION', '1.0.0'); define('VMI_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('VMI_PLUGIN_URL', plugin_dir_url(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class) { $prefix = 'VMI_'; $base_dir = VMI_PLUGIN_DIR . 'includes/'; $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) { return; } $relative_class = substr($class, $len); $file = $base_dir . 'class-' . strtolower(str_replace('_', '-', $relative_class)) . '.php'; if (file_exists($file)) { require $file; } }); // 初始化插件 function vmi_init() { // 检查依赖 if (!function_exists('curl_init')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>视频会议插件需要cURL扩展支持,请联系主机提供商启用cURL。</p></div>'; }); return; } // 初始化各个组件 VMI_API_Handler::init(); VMI_Shortcodes::init(); VMI_Admin_Settings::init(); } add_action('plugins_loaded', 'vmi_init'); // 激活插件时的操作 register_activation_hook(__FILE__, function() { // 创建必要的数据库表 global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'vmi_meetings'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, meeting_id varchar(100) NOT NULL, meeting_name varchar(255) NOT NULL, host_id bigint(20) NOT NULL, start_time datetime DEFAULT CURRENT_TIMESTAMP, end_time datetime, max_participants int(11) DEFAULT 10, recording_enabled tinyint(1) DEFAULT 0, status varchar(20) DEFAULT 'scheduled', PRIMARY KEY (id), UNIQUE KEY meeting_id (meeting_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 添加默认选项 add_option('vmi_api_provider', 'jitsi'); add_option('vmi_default_max_participants', 10); add_option('vmi_enable_recording', 0); }); 第二部分:集成Jitsi Meet视频会议 2.1 Jitsi Meet API基础集成 Jitsi Meet是一个开源的视频会议解决方案,可以自托管或使用其云服务。我们将使用Jitsi Meet API在WordPress中创建视频会议房间。 创建API处理类: <?php // includes/class-api-handler.php class VMI_API_Handler { private static $instance = null; private $api_provider; private $api_settings; public static function init() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->api_provider = get_option('vmi_api_provider', 'jitsi'); $this->api_settings = get_option('vmi_api_settings', array()); add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts')); } // 加载前端资源 public function enqueue_scripts() { if (is_singular() && has_shortcode(get_post()->post_content, 'video_meeting')) { // Jitsi Meet API脚本 wp_enqueue_script( 'jitsi-external-api', 'https://meet.jit.si/external_api.js', array(), null, true ); // 自定义脚本 wp_enqueue_script( 'vmi-frontend', VMI_PLUGIN_URL . 'assets/js/script.js', array('jquery', 'jitsi-external-api'), VMI_VERSION, true ); // 样式 wp_enqueue_style( 'vmi-frontend-style', VMI_PLUGIN_URL . 'assets/css/style.css', array(), VMI_VERSION ); // 本地化脚本 wp_localize_script('vmi-frontend', 'vmi_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('vmi_nonce') )); } } // 创建会议房间 public function create_meeting($meeting_name, $options = array()) { $defaults = array( 'room_name' => sanitize_title($meeting_name) . '-' . uniqid(), 'user_display_name' => '主持人', 'max_participants' => get_option('vmi_default_max_participants', 10), 'enable_recording' => get_option('vmi_enable_recording', 0), 'start_with_audio_muted' => true, 'start_with_video_muted' => false ); $options = wp_parse_args($options, $defaults); // 生成会议ID $meeting_id = $this->generate_meeting_id($options['room_name']); // 保存到数据库 $this->save_meeting_to_db($meeting_id, $meeting_name, $options); return array( 'meeting_id' => $meeting_id, 'room_name' => $options['room_name'], 'join_url' => $this->get_join_url($options['room_name'], $options) ); } // 生成会议ID private function generate_meeting_id($room_name) { return md5($room_name . time() . wp_rand(1000, 9999)); } // 保存会议到数据库 private function save_meeting_to_db($meeting_id, $meeting_name, $options) { global $wpdb; $table_name = $wpdb->prefix . 'vmi_meetings'; $data = array( 'meeting_id' => $meeting_id, 'meeting_name' => $meeting_name, 'host_id' => get_current_user_id(), 'max_participants' => $options['max_participants'], 'recording_enabled' => $options['enable_recording'] ? 1 : 0, 'status' => 'scheduled' ); $wpdb->insert($table_name, $data); } // 获取加入URL private function get_join_url($room_name, $options) { $base_url = 'https://meet.jit.si/'; $params = array( 'config.startWithAudioMuted' => $options['start_with_audio_muted'] ? 'true' : 'false', 'config.startWithVideoMuted' => $options['start_with_video_muted'] ? 'true' : 'false', 'config.prejoinPageEnabled' => 'false', 'userInfo.displayName' => urlencode($options['user_display_name']) ); if ($options['enable_recording']) { $params['config.recordingService'] = 'enabled'; } $query_string = http_build_query($params); return $base_url . $room_name . '?' . $query_string; } // 获取会议信息 public function get_meeting_info($meeting_id) { global $wpdb; $table_name = $wpdb->prefix . 'vmi_meetings'; return $wpdb->get_row( $wpdb->prepare("SELECT * FROM $table_name WHERE meeting_id = %s", $meeting_id) ); } } 2.2 创建视频会议短代码 短代码是WordPress中集成功能的最简单方式,允许用户通过简单的标签将视频会议嵌入到任何页面或文章中。 <?php // includes/class-shortcodes.php class VMI_Shortcodes { private static $instance = null; public static function init() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { // 注册短代码 add_shortcode('video_meeting', array($this, 'video_meeting_shortcode')); add_shortcode('create_meeting', array($this, 'create_meeting_shortcode')); add_shortcode('meeting_list', array($this, 'meeting_list_shortcode')); // AJAX处理 add_action('wp_ajax_vmi_create_meeting', array($this, 'ajax_create_meeting')); add_action('wp_ajax_nopriv_vmi_create_meeting', array($this, 'ajax_create_meeting')); add_action('wp_ajax_vmi_join_meeting', array($this, 'ajax_join_meeting')); add_action('wp_ajax_nopriv_vmi_join_meeting', array($this, 'ajax_join_meeting')); } // 视频会议短代码 public function video_meeting_shortcode($atts) { $atts = shortcode_atts(array( 'id' => '', 'width' => '100%', 'height' => '600px', 'prejoin' => 'true' ), $atts, 'video_meeting'); if (empty($atts['id'])) { return '<div class="vmi-error">请提供会议ID</div>'; } // 获取会议信息 $api_handler = VMI_API_Handler::init(); $meeting_info = $api_handler->get_meeting_info($atts['id']); if (!$meeting_info) { return '<div class="vmi-error">会议不存在或已结束</div>'; } // 检查用户权限 $can_join = $this->check_meeting_access($meeting_info); if (!$can_join) { return '<div class="vmi-error">您没有权限加入此会议</div>'; } // 输出会议容器 ob_start(); ?> <div class="vmeeting-container"> <div id="vmeeting-<?php echo esc_attr($atts['id']); ?>" class="vmeeting-room" data-meeting-id="<?php echo esc_attr($atts['id']); ?>" data-room-name="<?php echo esc_attr($meeting_info->meeting_name); ?>" style="width: <?php echo esc_attr($atts['width']); ?>; height: <?php echo esc_attr($atts['height']); ?>;"> <?php if ($atts['prejoin'] === 'true') : ?> <div class="vmeeting-prejoin"> <h3><?php echo esc_html($meeting_info->meeting_name); ?></h3> <div class="prejoin-form"> <div class="form-group"> <label for="display-name">您的显示名称</label> <input type="text" id="display-name" class="form-control" value="<?php echo esc_attr(wp_get_current_user()->display_name); ?>"> </div> <div class="form-group"> <label for="audio-option"> <input type="checkbox" id="audio-option" checked> 加入时静音 </label> </div> <div class="form-group"> <label for="video-option"> <input type="checkbox" id="video-option"> 开启摄像头 </label> </div> <button class="btn btn-primary btn-join-meeting">加入会议</button> </div> </div> <?php endif; ?> <div class="vmeeting-interface" style="display: none;"> <div id="meeting-container"></div> <div class="meeting-controls"> <button class="btn-control btn-toggle-audio" title="静音/取消静音"> <i class="dashicons dashicons-microphone"></i> </button> <button class="btn-control btn-toggle-video" title="开启/关闭视频"> <i class="dashicons dashicons-video-alt3"></i> </button> <button class="btn-control btn-screen-share" title="共享屏幕"> <i class="dashicons dashicons-desktop"></i> </button> <button class="btn-control btn-toggle-chat" title="聊天"> <i class="dashicons dashicons-format-chat"></i> </button> <button class="btn-control btn-leave-meeting btn-danger" title="离开会议"> <i class="dashicons dashicons-no"></i> </button> </div> </div> </div> </div> <?php return ob_get_clean(); } // 创建会议短代码 public function create_meeting_shortcode($atts) { // 仅限登录用户 if (!is_user_logged_in()) { return '<div class="vmi-login-required">请先登录以创建会议</div>'; } ob_start(); ?> <div class="vmi-create-meeting-form"> <h3>创建新会议</h3> <form id="vmi-meeting-form"> <div class="form-group"> <label for="meeting-name">会议名称 *</label> meeting-name" name="meeting_name" required class="form-control"> </div> <div class="form-group"> <label for="max-participants">最大参与人数</label> <input type="number" id="max-participants" name="max_participants" min="2" max="100" value="10" class="form-control"> </div> <div class="form-group"> <label for="enable-recording"> <input type="checkbox" id="enable-recording" name="enable_recording"> 启用录制 </label> </div> <div class="form-group"> <label for="meeting-password">会议密码(可选)</label> <input type="password" id="meeting-password" name="meeting_password" class="form-control"> </div> <div class="form-group"> <label for="meeting-duration">会议时长(分钟)</label> <select id="meeting-duration" name="meeting_duration" class="form-control"> <option value="30">30分钟</option> <option value="60" selected>60分钟</option> <option value="120">2小时</option> <option value="240">4小时</option> <option value="0">无限制</option> </select> </div> <input type="hidden" name="action" value="vmi_create_meeting"> <?php wp_nonce_field('vmi_create_meeting_nonce', 'vmi_nonce'); ?> <button type="submit" class="btn btn-primary">创建会议</button> </form> <div id="vmi-meeting-result" style="display: none; margin-top: 20px;"> <h4>会议创建成功!</h4> <p>会议ID: <span id="result-meeting-id" class="meeting-info"></span></p> <p>会议链接: <a id="result-meeting-link" href="#" target="_blank"></a></p> <p>邀请链接: <input type="text" id="result-invite-link" readonly class="form-control"></p> <button class="btn btn-secondary" onclick="copyInviteLink()">复制邀请链接</button> </div> </div> <script> function copyInviteLink() { var copyText = document.getElementById("result-invite-link"); copyText.select(); copyText.setSelectionRange(0, 99999); document.execCommand("copy"); alert("邀请链接已复制到剪贴板"); } </script> <?php return ob_get_clean(); } // 会议列表短代码 public function meeting_list_shortcode($atts) { global $wpdb; $table_name = $wpdb->prefix . 'vmi_meetings'; $atts = shortcode_atts(array( 'limit' => 10, 'status' => 'scheduled', 'show_past' => false ), $atts, 'meeting_list'); $user_id = get_current_user_id(); $where_clause = "WHERE host_id = %d"; $where_args = array($user_id); if ($atts['status'] !== 'all') { $where_clause .= " AND status = %s"; $where_args[] = $atts['status']; } if (!$atts['show_past']) { $where_clause .= " AND (end_time IS NULL OR end_time > NOW())"; } $meetings = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name $where_clause ORDER BY start_time DESC LIMIT %d", array_merge($where_args, array($atts['limit'])) ) ); if (empty($meetings)) { return '<div class="vmi-no-meetings">没有找到会议</div>'; } ob_start(); ?> <div class="vmi-meeting-list"> <h3>我的会议</h3> <table class="vmi-meetings-table"> <thead> <tr> <th>会议名称</th> <th>会议ID</th> <th>开始时间</th> <th>状态</th> <th>操作</th> </tr> </thead> <tbody> <?php foreach ($meetings as $meeting): ?> <tr> <td><?php echo esc_html($meeting->meeting_name); ?></td> <td><code><?php echo esc_html($meeting->meeting_id); ?></code></td> <td><?php echo date('Y-m-d H:i', strtotime($meeting->start_time)); ?></td> <td> <span class="meeting-status status-<?php echo esc_attr($meeting->status); ?>"> <?php echo $this->get_status_label($meeting->status); ?> </span> </td> <td> <?php if ($meeting->status === 'scheduled' || $meeting->status === 'active'): ?> <a href="<?php echo $this->get_meeting_join_url($meeting->meeting_id); ?>" class="btn btn-small btn-primary" target="_blank">加入会议</a> <?php endif; ?> <?php if ($meeting->status === 'ended' && $meeting->recording_enabled): ?> <button class="btn btn-small btn-secondary" onclick="vmiViewRecording('<?php echo esc_js($meeting->meeting_id); ?>')"> 查看录制 </button> <?php endif; ?> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> <?php return ob_get_clean(); } // AJAX创建会议 public function ajax_create_meeting() { // 验证nonce if (!wp_verify_nonce($_POST['vmi_nonce'], 'vmi_create_meeting_nonce')) { wp_die('安全验证失败'); } // 验证用户权限 if (!is_user_logged_in()) { wp_send_json_error('请先登录'); } // 获取表单数据 $meeting_name = sanitize_text_field($_POST['meeting_name']); $max_participants = intval($_POST['max_participants']); $enable_recording = isset($_POST['enable_recording']) ? 1 : 0; $meeting_password = sanitize_text_field($_POST['meeting_password']); $meeting_duration = intval($_POST['meeting_duration']); if (empty($meeting_name)) { wp_send_json_error('会议名称不能为空'); } // 创建会议 $api_handler = VMI_API_Handler::init(); $options = array( 'max_participants' => $max_participants, 'enable_recording' => $enable_recording, 'meeting_password' => $meeting_password, 'meeting_duration' => $meeting_duration ); $result = $api_handler->create_meeting($meeting_name, $options); // 生成邀请链接 $invite_link = add_query_arg( array('meeting_id' => $result['meeting_id']), get_permalink() ); wp_send_json_success(array( 'meeting_id' => $result['meeting_id'], 'meeting_link' => $result['join_url'], 'invite_link' => $invite_link, 'message' => '会议创建成功' )); } // 辅助方法:检查会议访问权限 private function check_meeting_access($meeting_info) { // 如果是公开会议 if (get_option('vmi_meeting_access', 'public') === 'public') { return true; } // 如果是私有会议,检查用户权限 $user_id = get_current_user_id(); // 主持人可以访问自己的会议 if ($user_id == $meeting_info->host_id) { return true; } // 检查用户是否被邀请 global $wpdb; $invites_table = $wpdb->prefix . 'vmi_meeting_invites'; $invited = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $invites_table WHERE meeting_id = %s AND user_id = %d", $meeting_info->meeting_id, $user_id ) ); return $invited > 0; } // 辅助方法:获取状态标签 private function get_status_label($status) { $labels = array( 'scheduled' => '已安排', 'active' => '进行中', 'ended' => '已结束', 'cancelled' => '已取消' ); return isset($labels[$status]) ? $labels[$status] : $status; } // 辅助方法:获取会议加入URL private function get_meeting_join_url($meeting_id) { return add_query_arg( array('meeting_id' => $meeting_id, 'action' => 'join'), get_permalink() ); } } ## 第三部分:实现屏幕共享功能 ### 3.1 屏幕共享API集成 屏幕共享是现代视频会议的核心功能之一。我们将扩展Jitsi Meet的集成,添加屏幕共享功能。 **扩展前端JavaScript:** // assets/js/script.js (function($) { 'use strict'; var VMI_Meeting = { api: null, isAudioMuted: true, isVideoMuted: false, isScreenSharing: false, currentMeetingId: null, init: function() { this.bindEvents(); // 如果有会议ID参数,自动加入会议 var urlParams = new URLSearchParams(window.location.search); var meetingId = urlParams.get('meeting_id'); var action = urlParams.get('action'); if (meetingId && action === 'join') { this.joinMeetingById(meetingId); } }, bindEvents: function() { // 加入会议按钮 $(document).on('click', '.btn-join-meeting', function(e) { e.preventDefault(); VMI_Meeting.joinMeeting($(this).closest('.vmeeting-room')); }); // 会议控制按钮 $(document).on('click', '.btn-toggle-audio', function() { VMI_Meeting.toggleAudio(); }); $(document).on('click', '.btn-toggle-video', function() { VMI_Meeting.toggleVideo(); }); $(document).on('click', '.btn-screen-share', function() { VMI_Meeting.toggleScreenShare(); }); $(document).on('click', '.btn-leave-meeting', function() { VMI_Meeting.leaveMeeting(); }); // 创建会议表单 $(document).on('submit', '#vmi-meeting-form', function(e) { e.preventDefault(); VMI_Meeting.createMeeting($(this)); }); }, joinMeeting: function($container) { var meetingId = $container.data('meeting-id'); var roomName = $container.data('room-name'); var displayName = $container.find('#display-name').val(); var startAudioMuted = $container.find('#audio-option').is(':checked'); var startVideoMuted = !$container.find('#video-option').is(':checked'); this.currentMeetingId = meetingId; // 隐藏预加入界面 $container.find('.vmeeting-prejoin').hide(); $container.find('.vmeeting-interface').show(); // 初始化Jitsi Meet API const domain = 'meet.jit.si'; const options = { roomName: roomName, width: '100%', height: '100%', parentNode: $container.find('#meeting-container')[0], configOverwrite: { startWithAudioMuted: startAudioMuted, startWithVideoMuted: startVideoMuted, enableWelcomePage: false, prejoinPageEnabled: false, disableSimulcast: false }, interfaceConfigOverwrite: { TOOLBAR_BUTTONS: [ 'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup', 'profile', 'chat', 'recording', 'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts', 'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'security' ], SETTINGS_SECTIONS: ['devices', 'language', 'moderator', 'profile', 'calendar'], SHOW_CHROME_EXTENSION_BANNER: false }, userInfo: { displayName: displayName } }; this.api = new JitsiMeetExternalAPI(domain, options); // 添加事件监听器 this.api.addEventListeners({ readyToClose: this.onMeetingEnd.bind(this), participantJoined: this.onParticipantJoined.bind(this), participantLeft: this.onParticipantLeft.bind(this), videoConferenceJoined: this.onConferenceJoined.bind(this), videoConferenceLeft: this.onConferenceLeft.bind(this), screenSharingStatusChanged: this.onScreenSharingChanged.bind(this) }); // 更新会议状态为活跃 this.updateMeetingStatus(meetingId, 'active'); }, joinMeetingById: function(meetingId) { $.ajax({ url: vmi_ajax.ajax_url, type: 'POST', data: { action: 'vmi_join_meeting', meeting_id: meetingId, nonce: vmi_ajax.nonce }, success: function(response) { if (response.success) { // 重定向到会议页面 window.location.href = response.data.join_url; } else { alert('加入会议失败: ' + response.data); } } }); }, createMeeting: function($form) { var formData = $form.serialize(); $.ajax({ url: vmi_ajax.ajax_url, type: 'POST', data: formData, success: function(response) { if (response.success) { $('#vmi-meeting-result').show(); $('#result-meeting-id').text(response.data.meeting_id); $('#result-meeting-link').attr('href', response.data.meeting_link) .text(response.data.meeting_link); $('#result-invite-link').val(response.data.invite_link); $form.hide(); } else { alert('创建会议失败: ' + response.data); } } }); }, toggleAudio: function() { if (this.api) { this.api.executeCommand('toggleAudio'); this.isAudioMuted = !this.isAudioMuted; $('.btn-toggle-audio').toggleClass('muted', this.isAudioMuted); } }, toggleVideo: function() { if (this.api) { this.api.executeCommand('toggleVideo'); this.isVideoMuted = !this.isVideoMuted; $('.btn-toggle-video').toggleClass('muted', this.isVideoMuted); } }, toggleScreenShare: function() { if (this.api) { if (this.isScreenSharing) { this.api.executeCommand('toggleShareScreen'); } else { // 检查浏览器是否支持屏幕共享 if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) { alert('您的浏览器不支持屏幕共享功能。请使用最新版本的Chrome、Firefox或Edge浏览器。'); return; } // 请求屏幕共享权限 navigator.mediaDevices.getDisplayMedia({ video: true }) .then(() => { this.api.executeCommand('toggleShareScreen'); }) .catch(err => { console.error('屏幕共享失败:', err); if (err.name !== 'NotAllowedError') { alert('屏幕共享失败: ' + err.message); } }); } this.isScreenSharing = !this.isScreenSharing; $('.btn-screen-share').toggleClass('active', this.isScreenSharing); } }, leaveMeeting: function() { if (this.api) { this.api.executeCommand('hangup'); this.api.dispose(); // 显示离开消息 $('#meeting-container').html( '<div class="meeting-ended">' + '<h3>会议已结束</h3>' + '<p>您已离开会议。</p>' + '<button class="btn btn-primary" onclick="window.location.reload()">返回</button>' + '</div>' ); // 更新会议状态 if (this.currentMeetingId) { this.updateMeetingStatus(this.currentMeetingId, 'ended'); } } }, // 事件处理函数 onMeetingEnd: function() { console.log('会议结束'); }, onParticipantJoined: function(data) { console.log('参与者加入:', data); this.updateParticipantCount(); }, onParticipantLeft: function(data) { console.log('参与者离开:', data); this.updateParticipantCount(); }, onConferenceJoined: function(data) { console.log('已加入会议:', data); // 发送加入通知到服务器 this.sendJoinNotification(); }, onConferenceLeft: function(data) { console.log('已离开
发表评论WordPress插件开发教程:实现网站内容自动同步到社交媒体 引言:为什么需要内容自动同步功能 在当今数字化时代,社交媒体已成为网站流量和用户互动的重要来源。根据最新统计,超过70%的网络用户通过社交媒体发现新内容,而拥有自动同步功能的网站其社交媒体互动率平均提高45%。对于WordPress网站管理员而言,每次发布新内容后手动分享到各个社交平台不仅耗时耗力,而且容易遗漏,影响内容传播效果。 本教程将详细讲解如何开发一个WordPress插件,实现网站内容自动同步到主流社交媒体平台(如Twitter、Facebook、LinkedIn等)。通过这个实战项目,您不仅能掌握WordPress插件开发的核心技术,还能学习如何通过代码二次开发实现实用的互联网小工具功能。 第一章:WordPress插件开发基础 1.1 WordPress插件架构概述 WordPress插件本质上是一组PHP文件,遵循特定的结构和命名规范,用于扩展WordPress核心功能。一个标准的WordPress插件至少包含: 主文件:包含插件元信息的PHP文件 功能文件:实现插件核心逻辑的PHP文件 资源文件:CSS、JavaScript和图像等资源 语言文件:用于国际化的翻译文件 1.2 创建插件基本结构 首先,在WordPress的wp-content/plugins/目录下创建一个新文件夹,命名为auto-social-sync。在该文件夹中创建以下文件结构: auto-social-sync/ ├── auto-social-sync.php # 插件主文件 ├── includes/ │ ├── class-social-api.php # 社交媒体API处理类 │ ├── class-post-handler.php # 文章处理类 │ └── class-admin-settings.php # 管理设置类 ├── admin/ │ ├── css/ │ │ └── admin-style.css # 管理界面样式 │ └── js/ │ └── admin-script.js # 管理界面脚本 ├── public/ │ ├── css/ │ │ └── public-style.css # 前端样式 │ └── js/ │ └── public-script.js # 前端脚本 └── languages/ └── auto-social-sync.pot # 翻译模板文件 1.3 编写插件主文件 插件主文件是插件的入口点,需要包含标准的插件头部信息: <?php /** * Plugin Name: Auto Social Sync * Plugin URI: https://yourwebsite.com/auto-social-sync * Description: 自动将WordPress内容同步到社交媒体平台 * Version: 1.0.0 * Author: 您的姓名 * Author URI: https://yourwebsite.com * License: GPL v2 or later * Text Domain: auto-social-sync * Domain Path: /languages */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('ASS_VERSION', '1.0.0'); define('ASS_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('ASS_PLUGIN_URL', plugin_dir_url(__FILE__)); define('ASS_PLUGIN_BASENAME', plugin_basename(__FILE__)); // 加载必要文件 require_once ASS_PLUGIN_DIR . 'includes/class-social-api.php'; require_once ASS_PLUGIN_DIR . 'includes/class-post-handler.php'; require_once ASS_PLUGIN_DIR . 'includes/class-admin-settings.php'; // 初始化插件 function ass_init_plugin() { // 检查WordPress版本 if (version_compare(get_bloginfo('version'), '5.0', '<')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>'; echo __('Auto Social Sync需要WordPress 5.0或更高版本。', 'auto-social-sync'); echo '</p></div>'; }); return; } // 初始化各个组件 $social_api = new ASS_Social_API(); $post_handler = new ASS_Post_Handler($social_api); $admin_settings = new ASS_Admin_Settings($social_api); // 注册激活和停用钩子 register_activation_hook(__FILE__, array($admin_settings, 'activate_plugin')); register_deactivation_hook(__FILE__, array($admin_settings, 'deactivate_plugin')); } add_action('plugins_loaded', 'ass_init_plugin'); 第二章:社交媒体API集成 2.1 设计API抽象层 为了支持多个社交媒体平台,我们需要设计一个灵活的API抽象层。首先创建社交媒体API基类: <?php // includes/class-social-api.php class ASS_Social_API { protected $platforms = array(); protected $options = array(); public function __construct() { $this->options = get_option('ass_settings', array()); $this->register_platforms(); } // 注册支持的社交媒体平台 protected function register_platforms() { // 默认支持的平台 $this->platforms = array( 'twitter' => array( 'name' => 'Twitter', 'class' => 'ASS_Twitter_API', 'enabled' => false, 'auth_url' => '' ), 'facebook' => array( 'name' => 'Facebook', 'class' => 'ASS_Facebook_API', 'enabled' => false, 'auth_url' => '' ), 'linkedin' => array( 'name' => 'LinkedIn', 'class' => 'ASS_LinkedIn_API', 'enabled' => false, 'auth_url' => '' ) ); // 允许其他插件添加更多平台 $this->platforms = apply_filters('ass_register_platforms', $this->platforms); } // 获取所有平台 public function get_platforms() { return $this->platforms; } // 获取特定平台 public function get_platform($platform_id) { return isset($this->platforms[$platform_id]) ? $this->platforms[$platform_id] : false; } // 发布内容到所有已启用的平台 public function publish_to_all($post_id, $message = '') { $results = array(); foreach ($this->platforms as $platform_id => $platform) { if ($this->is_platform_enabled($platform_id)) { $result = $this->publish_to_platform($platform_id, $post_id, $message); $results[$platform_id] = $result; } } return $results; } // 发布内容到特定平台 public function publish_to_platform($platform_id, $post_id, $message = '') { $platform = $this->get_platform($platform_id); if (!$platform || !$this->is_platform_enabled($platform_id)) { return new WP_Error('platform_disabled', sprintf(__('%s平台未启用或未配置', 'auto-social-sync'), $platform['name'])); } // 动态加载平台类 $class_name = $platform['class']; if (!class_exists($class_name)) { $file_path = ASS_PLUGIN_DIR . 'includes/platforms/class-' . strtolower(str_replace('_', '-', $platform_id)) . '-api.php'; if (file_exists($file_path)) { require_once $file_path; } } if (class_exists($class_name)) { $api_instance = new $class_name($this->options); return $api_instance->publish($post_id, $message); } return new WP_Error('class_not_found', sprintf(__('找不到%s平台的API类', 'auto-social-sync'), $platform['name'])); } // 检查平台是否启用 public function is_platform_enabled($platform_id) { $platform = $this->get_platform($platform_id); if (!$platform) { return false; } // 检查全局设置 if (isset($this->options['platforms'][$platform_id]['enabled'])) { return (bool) $this->options['platforms'][$platform_id]['enabled']; } return $platform['enabled']; } } 2.2 实现Twitter API集成 接下来,我们实现Twitter API的具体集成。首先需要在Twitter开发者平台创建应用并获取API密钥。 <?php // includes/platforms/class-twitter-api.php class ASS_Twitter_API { private $consumer_key; private $consumer_secret; private $access_token; private $access_token_secret; private $api_url = 'https://api.twitter.com/2/'; public function __construct($options) { $twitter_settings = isset($options['twitter']) ? $options['twitter'] : array(); $this->consumer_key = isset($twitter_settings['consumer_key']) ? $twitter_settings['consumer_key'] : ''; $this->consumer_secret = isset($twitter_settings['consumer_secret']) ? $twitter_settings['consumer_secret'] : ''; $this->access_token = isset($twitter_settings['access_token']) ? $twitter_settings['access_token'] : ''; $this->access_token_secret = isset($twitter_settings['access_token_secret']) ? $twitter_settings['access_token_secret'] : ''; } // 发布推文 public function publish($post_id, $message = '') { // 验证配置 if (!$this->validate_config()) { return new WP_Error('twitter_config_error', __('Twitter API配置不完整', 'auto-social-sync')); } // 获取文章信息 $post = get_post($post_id); if (!$post) { return new WP_Error('post_not_found', __('文章不存在', 'auto-social-sync')); } // 准备推文内容 $tweet_content = $this->prepare_tweet_content($post, $message); // 检查内容长度(Twitter限制为280字符) if (mb_strlen($tweet_content) > 280) { $tweet_content = mb_substr($tweet_content, 0, 277) . '...'; } // 获取文章特色图片 $media_id = $this->upload_media($post_id); // 发布推文 $result = $this->post_tweet($tweet_content, $media_id); if ($result && !is_wp_error($result)) { // 记录发布日志 $this->log_activity($post_id, 'twitter', $result->data->id, $tweet_content); return $result; } return $result; } // 准备推文内容 private function prepare_tweet_content($post, $custom_message = '') { if (!empty($custom_message)) { $content = $custom_message; } else { // 使用文章标题和链接 $title = get_the_title($post); $permalink = get_permalink($post); // 添加话题标签 $hashtags = $this->get_hashtags($post); $content = $title . ' ' . $permalink; if (!empty($hashtags)) { $content .= ' ' . $hashtags; } } return apply_filters('ass_twitter_content', $content, $post, $custom_message); } // 获取话题标签 private function get_hashtags($post) { $hashtags = array(); // 从文章标签中获取 $post_tags = get_the_tags($post->ID); if ($post_tags) { foreach ($post_tags as $tag) { $hashtags[] = '#' . str_replace(' ', '', $tag->name); } } // 限制标签数量 $hashtags = array_slice($hashtags, 0, 3); return implode(' ', $hashtags); } // 上传媒体文件 private function upload_media($post_id) { $image_url = get_the_post_thumbnail_url($post_id, 'medium'); if (!$image_url) { return null; } // 下载图片到临时文件 $tmp_file = download_url($image_url); if (is_wp_error($tmp_file)) { return null; } // 准备OAuth请求 $oauth = array( 'oauth_consumer_key' => $this->consumer_key, 'oauth_nonce' => wp_generate_password(32, false), 'oauth_signature_method' => 'HMAC-SHA1', 'oauth_timestamp' => time(), 'oauth_token' => $this->access_token, 'oauth_version' => '1.0' ); // 这里简化处理,实际需要完整实现Twitter媒体上传API // 由于篇幅限制,省略详细实现 // 清理临时文件 @unlink($tmp_file); return null; // 返回媒体ID,这里简化处理 } // 发布推文 private function post_tweet($content, $media_id = null) { $endpoint = $this->api_url . 'tweets'; $data = array( 'text' => $content ); if ($media_id) { $data['media'] = array('media_ids' => array($media_id)); } // 使用WordPress HTTP API发送请求 $args = array( 'method' => 'POST', 'headers' => array( 'Authorization' => $this->generate_oauth_header('POST', $endpoint), 'Content-Type' => 'application/json' ), 'body' => json_encode($data), 'timeout' => 30 ); $response = wp_remote_post($endpoint, $args); if (is_wp_error($response)) { return $response; } $body = json_decode(wp_remote_retrieve_body($response)); if (isset($body->errors)) { return new WP_Error('twitter_api_error', $body->errors[0]->message); } return $body; } // 生成OAuth签名 private function generate_oauth_header($method, $url, $params = array()) { // OAuth 1.0签名生成 // 由于篇幅限制,这里简化处理 // 实际开发中需要使用完整的OAuth 1.0实现 return 'OAuth oauth_consumer_key="' . $this->consumer_key . '", oauth_nonce="' . wp_generate_password(32, false) . '", oauth_signature="placeholder", oauth_signature_method="HMAC-SHA1", oauth_timestamp="' . time() . '", oauth_token="' . $this->access_token . '", oauth_version="1.0"'; } // 验证配置 private function validate_config() { return !empty($this->consumer_key) && !empty($this->consumer_secret) && !empty($this->access_token) && !empty($this->access_token_secret); } // 记录活动日志 private function log_activity($post_id, $platform, $platform_post_id, $content) { $logs = get_post_meta($post_id, '_ass_social_logs', true); if (!is_array($logs)) { $logs = array(); } $logs[] = array( 'platform' => $platform, 'platform_post_id' => $platform_post_id, 'content' => $content, 'timestamp' => current_time('mysql'), 'status' => 'success' ); update_post_meta($post_id, '_ass_social_logs', $logs); } } 第三章:文章发布处理机制 3.1 监听文章发布事件 WordPress提供了多种钩子(hooks)来监听文章状态变化。我们将利用这些钩子自动触发社交媒体同步。 <?php // includes/class-post-handler.php class ASS_Post_Handler { private $social_api; private $options; public function __construct($social_api) { $this->social_api = $social_api; $this->options = get_option('ass_settings', array()); $this->init_hooks(); } // 初始化钩子 private function init_hooks() { // 发布新文章时触发 add_action('publish_post', array($this, 'on_post_published'), 10, 2); // 更新已发布文章时触发 add_action('post_updated', array($this, 'on_post_updated'), 10, 3); // 定时发布文章时触发 add_action('future_to_publish', array($this, 'on_scheduled_publish'), 10, 1); // 文章状态变化时触发 add_action('transition_post_status', array($this, 'on_post_status_change'), 10, 3); // 添加快捷发布按钮 add_action('post_submitbox_misc_actions', array($this, 'add_publish_actions')); // 处理手动发布请求 add_action('admin_post_ass_manual_sync', array($this, 'handle_manual_sync')); } // 文章发布时处理 public function on_post_published($post_id, $post) { // 检查是否自动发布 $auto_publish = isset($this->options['auto_publish']) ? $this->options['auto_publish'] : true; if (!$auto_publish) { return; } // 检查文章类型 $allowed_types = isset($this->options['post_types']) ? $this->options['post_types'] : array('post'); if (!in_array($post->post_type, $allowed_types)) { return; } // 检查是否已发布过 if (get_post_meta($post_id, '_ass_published', true)) { return; } // 检查排除条件 if ($this->is_excluded($post_id)) { return; } // 准备发布内容 $message = $this->prepare_social_message($post); // 发布到社交媒体 $results = $this->social_api->publish_to_all($post_id, $message); // 标记为已发布 update_post_meta($post_id, '_ass_published', current_time('mysql')); // 保存发布结果 update_post_meta($post_id, '_ass_publish_results', $results); // 发送通知 $this->send_notification($post_id, $results); } // 文章更新时处理 public function on_post_updated($post_id, $post_after, $post_before) { // 检查是否启用更新同步 $sync_updates = isset($this->options['sync_updates']) ? $this->options['sync_updates'] : false; if (!$sync_updates || $post_after->post_status !== 'publish') { return; } // 检查文章类型 $allowed_types = isset($this->options['post_types']) ? $this->options['post_types'] : array('post'); if (!in_array($post_after->post_type, $allowed_types)) { return; } // 检查是否有重要更改 if (!$this->has_significant_changes($post_after, $post_before)) { return; } // 准备更新消息 $message = $this->prepare_update_message($post_after); // 发布更新 $results = $this->social_api->publish_to_all($post_id, $message); // 记录更新发布 update_post_meta($post_id, '_ass_updated', current_time('mysql')); update_post_meta($post_id, '_ass_update_results', $results); } // 定时发布处理 public function on_scheduled_publish($post) { $this->on_post_published($post->ID, $post); } // 文章状态变化处理 public function on_post_status_change($new_status, $old_status, $post) { // 从草稿或待审核状态发布时 if ($new_status === 'publish' && in_array($old_status, array('draft', 'pending'))) { // 添加短暂延迟,确保其他元数据已保存 wp_schedule_single_event(time() + 5, 'ass_delayed_publish', array($post->ID)); } } // 准备社交媒体消息 private function prepare_social_message($post) { $message_template = isset($this->options['message_template']) ? $this->options['message_template'] : '{title} {url} {hashtags}'; // 获取替换变量 $replacements = array( '{title}' => get_the_title($post), '{url}' => get_permalink($post), '{excerpt}' => $this->get_excerpt($post), '{author}' => get_the_author_meta('display_name', $post->post_author), '{site_name}' => get_bloginfo('name'), '{hashtags}' => $this->generate_hashtags($post), '{date}' => get_the_date('', $post), '{categories}' => $this->get_categories_list($post) ); // 应用过滤器允许自定义变量 $replacements = apply_filters('ass_message_replacements', $replacements, $post); // 执行替换 $message = str_replace( array_keys($replacements), array_values($replacements), $message_template ); // 清理多余空格 $message = preg_replace('/s+/', ' ', trim($message)); return apply_filters('ass_final_message', $message, $post); } // 获取文章摘要 private function get_excerpt($post, $length = 100) { if (!empty($post->post_excerpt)) { $excerpt = $post->post_excerpt; } else { $excerpt = strip_shortcodes($post->post_content); $excerpt = wp_strip_all_tags($excerpt); } if (mb_strlen($excerpt) > $length) { $excerpt = mb_substr($excerpt, 0, $length) . '...'; } return $excerpt; } // 生成话题标签 private function generate_hashtags($post) { $hashtags = array(); // 从文章标签获取 $post_tags = get_the_tags($post->ID); if ($post_tags) { foreach ($post_tags as $tag) { $hashtag = '#' . preg_replace('/s+/', '', $tag->name); if (mb_strlen($hashtag) <= 20) { // 避免过长的标签 $hashtags[] = $hashtag; } } } // 从分类获取 $categories = get_the_category($post->ID); if ($categories) { foreach ($categories as $category) { if ($category->parent == 0) { // 只使用顶级分类 $hashtag = '#' . preg_replace('/s+/', '', $category->name); if (!in_array($hashtag, $hashtags) && mb_strlen($hashtag) <= 20) { $hashtags[] = $hashtag; } } } } // 限制标签数量 $max_hashtags = isset($this->options['max_hashtags']) ? $this->options['max_hashtags'] : 3; $hashtags = array_slice($hashtags, 0, $max_hashtags); return implode(' ', $hashtags); } // 获取分类列表 private function get_categories_list($post) { $categories = get_the_category($post->ID); if (!$categories) { return ''; } $category_names = array(); foreach ($categories as $category) { $category_names[] = $category->name; } return implode(', ', $category_names); } // 检查是否有重要更改 private function has_significant_changes($post_after, $post_before) { // 检查标题是否更改 if ($post_after->post_title !== $post_before->post_title) { return true; } // 检查内容是否显著更改(变化超过20%) $similarity = 0; similar_text( strip_tags($post_after->post_content), strip_tags($post_before->post_content), $similarity ); if ($similarity < 80) { return true; } // 检查特色图片是否更改 $old_thumbnail = get_post_thumbnail_id($post_before->ID); $new_thumbnail = get_post_thumbnail_id($post_after->ID); if ($old_thumbnail !== $new_thumbnail) { return true; } return false; } // 准备更新消息 private function prepare_update_message($post) { $template = isset($this->options['update_template']) ? $this->options['update_template'] : '更新: {title} {url}'; $replacements = array( '{title}' => get_the_title($post), '{url}' => get_permalink($post) ); return str_replace( array_keys($replacements), array_values($replacements), $template ); } // 检查是否排除 private function is_excluded($post_id) { // 检查元数据排除标记 if (get_post_meta($post_id, '_ass_exclude', true)) { return true; } // 检查分类排除 $excluded_categories = isset($this->options['excluded_categories']) ? $this->options['excluded_categories'] : array(); if (!empty($excluded_categories)) { $post_categories = wp_get_post_categories($post_id, array('fields' => 'ids')); $intersection = array_intersect($post_categories, $excluded_categories); if (!empty($intersection)) { return true; } } // 检查标签排除 $excluded_tags = isset($this->options['excluded_tags']) ? $this->options['excluded_tags'] : array(); if (!empty($excluded_tags)) { $post_tags = wp_get_post_tags($post_id, array('fields' => 'ids')); $tag_ids = array(); foreach ($post_tags as $tag) { $tag_ids[] = $tag->term_id; } $intersection = array_intersect($tag_ids, $excluded_tags); if (!empty($intersection)) { return true; } } return false; } // 发送通知 private function send_notification($post_id, $results) { $send_notifications = isset($this->options['send_notifications']) ? $this->options['send_notifications'] : false; if (!$send_notifications) { return; } $admin_email = get_option('admin_email'); $post = get_post($post_id); $subject = sprintf(__('文章已同步到社交媒体: %s', 'auto-social-sync'), get_the_title($post)); $message = sprintf(__('文章 "%s" 已同步到以下社交媒体平台:', 'auto-social-sync'), get_the_title($post)) . "nn"; foreach ($results as $platform => $result) { if (is_wp_error($result)) { $message .= sprintf(__('%s: 失败 - %s', 'auto-social-sync'), ucfirst($platform), $result->get_error_message() ) . "n"; } else { $message .= sprintf(__('%s: 成功', 'auto-social-sync'), ucfirst($platform)) . "n"; } } $message .= "n" . sprintf(__('文章链接: %s', 'auto-social-sync'), get_permalink($post)); wp_mail($admin_email, $subject, $message); } // 添加快捷发布按钮 public function add_publish_actions() { global $post; if (!$post || $post->post_status !== 'publish') { return; } // 检查是否已发布 $already_published = get_post_meta($post->ID, '_ass_published', true); ?> <div class="misc-pub-section misc-pub-ass-social"> <span class="dashicons dashicons-share"></span> <?php _e('社交媒体同步:', 'auto-social-sync'); ?> <span id="ass-status"> <?php if ($already_published): ?> <span class="ass-published"><?php _e('已同步', 'auto-social-sync'); ?></span> <?php else: ?> <span class="ass-not-published"><?php _e('未同步', 'auto-social-sync'); ?></span> <?php endif; ?> </span> <a href="#" id="ass-manual-sync" class="button button-small" data-post-id="<?php echo $post->ID; ?>" data-nonce="<?php echo wp_create_nonce('ass_manual_sync_' . $post->ID); ?>"> <?php _e('手动同步', 'auto-social-sync'); ?> </a> <div id="ass-sync-results" style="display:none; margin-top:10px;"></div> </div> <script> jQuery(document).ready(function($) { $('#ass-manual-sync').on('click', function(e) { e.preventDefault(); var $button = $(this); var postId = $button.data('post-id'); var nonce = $button.data('nonce'); $button.prop('disabled', true).text('<?php _e('同步中...', 'auto-social-sync'); ?>'); $('#ass-sync-results').hide().empty(); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'ass_manual_sync', post_id: postId, nonce: nonce }, success: function(response) { if (response.success) { $('#ass-status').html('<span class="ass-published"><?php _e('已同步', 'auto-social-sync'); ?></span>'); $('#ass-sync-results').html('<div class="notice notice-success inline"><p>' + '<?php _e('同步成功!', 'auto-social-sync'); ?>' + '</p></div>').show(); } else { $('#ass-sync-results').html('<div class="notice notice-error inline"><p>' + response.data + '</p></div>').show(); } }, error: function() { $('#ass-sync-results').html('<div class="notice notice-error inline"><p>' + '<?php _e('请求失败,请重试', 'auto-social-sync'); ?>' + '</p></div>').show(); }, complete: function() { $button.prop('disabled', false).text('<?php _e('手动同步', 'auto-social-sync'); ?>'); } }); }); }); </script> <style> .ass-published { color: #46b450; font-weight: bold; } .ass-not-published { color: #f56e28; font-weight: bold; } .misc-pub-ass-social { border-top: 1px solid #eee; padding-top: 10px; } </style> <?php } // 处理手动同步请求 public function handle_manual_sync() { if (!isset($_POST['post_id']) || !isset($_POST['nonce'])) { wp_die(__('参数错误', 'auto-social-sync')); } $post_id = intval($_POST['post_id']); $nonce = sanitize_text_field($_POST['nonce']); if (!wp_verify_nonce($nonce, 'ass_manual_sync_' . $post_id)) { wp_die(__('安全验证失败', 'auto-social-sync')); } if (!current_user_can('edit_post', $post_id)) { wp_die(__('权限不足', 'auto-social-sync')); } $post = get_post($post_id); if (!$post || $post->post_status !== 'publish') { wp_die(__('文章不存在或未发布', 'auto-social-sync')); } // 准备消息 $message = $this->prepare_social_message($post); // 发布到社交媒体 $results = $this->social_api->publish_to_all($post_id, $message); // 标记为已发布 update_post_meta($post_id, '_ass_published', current_time('mysql')); update_post_meta($post_id, '_ass_publish_results', $results); // 检查结果 $has_error = false; foreach ($results as $result) { if (is_wp_error($result)) { $has_error = true; break; } } if ($has_error) { wp_send_json_error(__('部分平台同步失败', 'auto-social-sync')); } else { wp_send_json_success(__('同步成功', 'auto-social-sync')); } } } 第四章:管理界面开发 4.1 创建设置页面 管理界面是插件的重要组成部分,让用户可以配置插件功能。 <?php // includes/class-admin-settings.php class ASS_Admin_Settings { private $social_api; private $options; private $page_hook; public function __construct($social_api) { $this->social_api = $social_api; $this->options = get_option('ass_settings', array()); $this->init_hooks(); } // 初始化钩子 private function init_hooks() { // 添加管理菜单 add_action('admin_menu', array($this, 'add_admin_menu')); // 注册设置 add_action('admin_init', array($this, 'register_settings')); // 添加设置链接到插件页面 add_filter('plugin_action_links_' . ASS_PLUGIN_BASENAME, array($this, 'add_plugin_action_links')); // 加载管理脚本和样式 add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); // 添加文章列表列 add_filter('manage_post_posts_columns', array($this, 'add_social_columns')); add_action('manage_post_posts_custom_column', array($this, 'render_social_columns'), 10, 2); // 添加批量操作 add_filter('bulk_actions-edit-post', array($this, 'add_bulk_actions')); add_filter('handle_bulk_actions-edit-post', array($this, 'handle_bulk_actions'), 10, 3); // AJAX处理 add_action('wp_ajax_ass_test_connection', array($this, 'ajax_test_connection')); add_action('wp_ajax_ass_get_logs', array($this, 'ajax_get_logs')); } // 添加管理菜单 public function add_admin_menu() { $this->page_hook = add_menu_page(
发表评论