首页 / 应用软件 / 手把手教学,为你的网站添加在线协同文档编辑与版本管理功能

手把手教学,为你的网站添加在线协同文档编辑与版本管理功能

手把手教学:为WordPress网站添加在线协同文档编辑与版本管理功能

引言:为什么网站需要协同编辑功能?

在当今数字化工作环境中,协同办公已成为企业和团队提高效率的关键。根据Statista的数据,2023年全球协同软件市场规模已达到138亿美元,预计到2027年将增长至287亿美元。对于拥有WordPress网站的企业、教育机构或内容创作团队而言,集成在线协同文档编辑功能可以显著提升团队协作效率,减少邮件往来,实现实时内容共创。

传统的WordPress内容编辑方式存在明显局限:单用户编辑锁定机制导致多人协作困难;版本管理功能有限,难以追踪每次修改;缺乏实时协同编辑体验,团队成员无法同时处理同一文档。本教程将指导您通过代码二次开发,为WordPress网站添加类似Google Docs的协同编辑与版本管理功能,而无需依赖昂贵的第三方服务。

第一部分:项目规划与技术选型

1.1 功能需求分析

在开始编码前,我们需要明确要实现的协同编辑系统应包含以下核心功能:

  1. 实时协同编辑:支持多用户同时编辑同一文档,实时显示他人光标位置和编辑内容
  2. 版本控制系统:完整记录文档修改历史,支持版本对比与回滚
  3. 用户权限管理:基于WordPress用户角色设置文档访问和编辑权限
  4. 冲突解决机制:智能处理编辑冲突,确保数据一致性
  5. 评论与批注系统:支持文档内评论和批注功能
  6. 导出与分享:支持多种格式导出和链接分享功能

1.2 技术架构设计

我们将采用以下技术栈实现协同编辑功能:

  • 前端编辑器:使用开源协同编辑器框架如Y.js或ShareDB
  • 实时通信:WebSocket协议实现实时数据同步
  • 后端框架:基于WordPress REST API扩展
  • 数据存储:MySQL数据库存储文档内容和版本历史
  • 版本控制:自定义版本管理算法或集成Git原理

1.3 开发环境准备

确保您的开发环境满足以下要求:

  1. WordPress 5.8或更高版本
  2. PHP 7.4+(建议使用PHP 8.0+以获得更好性能)
  3. MySQL 5.7+或MariaDB 10.3+
  4. Node.js环境(用于构建前端资源)
  5. WebSocket服务器(如Socket.io服务器)

第二部分:构建协同编辑核心系统

2.1 创建自定义文章类型

首先,我们需要创建一个新的自定义文章类型来存储协同文档:

// 在主题的functions.php或自定义插件中添加
function register_collaborative_doc_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' => 'collab-doc'),
        'capability_type'    => 'post',
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => 5,
        'supports'           => array('title', 'author'),
        'show_in_rest'       => true, // 启用REST API支持
    );
    
    register_post_type('collab_doc', $args);
}
add_action('init', 'register_collaborative_doc_type');

2.2 设置WebSocket服务器

为了实现实时协同编辑,我们需要设置WebSocket服务器处理实时通信:

// websocket-server.js - Node.js WebSocket服务器
const WebSocket = require('ws');
const http = require('http');
const server = http.createServer();
const wss = new WebSocket.Server({ server });

// 存储文档状态和连接
const documents = new Map();

wss.on('connection', (ws, request) => {
    const params = new URLSearchParams(request.url.split('?')[1]);
    const docId = params.get('docId');
    const userId = params.get('userId');
    
    if (!docId || !userId) {
        ws.close();
        return;
    }
    
    // 初始化文档状态
    if (!documents.has(docId)) {
        documents.set(docId, {
            content: '',
            users: new Map(),
            version: 0
        });
    }
    
    const doc = documents.get(docId);
    
    // 存储用户连接
    doc.users.set(userId, {
        ws,
        cursorPosition: 0,
        lastActive: Date.now()
    });
    
    // 发送当前文档状态给新用户
    ws.send(JSON.stringify({
        type: 'init',
        content: doc.content,
        version: doc.version,
        activeUsers: Array.from(doc.users.keys()).filter(id => id !== userId)
    }));
    
    // 广播新用户加入
    broadcastToOthers(docId, userId, {
        type: 'user_joined',
        userId
    });
    
    // 处理客户端消息
    ws.on('message', (message) => {
        try {
            const data = JSON.parse(message);
            handleClientMessage(docId, userId, data);
        } catch (error) {
            console.error('消息解析错误:', error);
        }
    });
    
    // 处理连接关闭
    ws.on('close', () => {
        if (doc.users.has(userId)) {
            doc.users.delete(userId);
            broadcastToOthers(docId, userId, {
                type: 'user_left',
                userId
            });
        }
    });
});

function handleClientMessage(docId, userId, data) {
    const doc = documents.get(docId);
    if (!doc) return;
    
    switch (data.type) {
        case 'edit':
            // 应用编辑操作
            applyEdit(doc, data.operation);
            doc.version++;
            
            // 广播编辑给其他用户
            broadcastToOthers(docId, userId, {
                type: 'update',
                operation: data.operation,
                version: doc.version,
                userId
            });
            break;
            
        case 'cursor_move':
            // 广播光标移动
            broadcastToOthers(docId, userId, {
                type: 'cursor_move',
                userId,
                position: data.position
            });
            break;
            
        case 'selection_change':
            // 广播选择变化
            broadcastToOthers(docId, userId, {
                type: 'selection_change',
                userId,
                selection: data.selection
            });
            break;
    }
}

function applyEdit(doc, operation) {
    // 根据操作类型更新文档内容
    // 这里需要实现操作转换(OT)或冲突无关数据类型(CRDT)逻辑
    // 简化示例:直接替换内容
    if (operation.type === 'insert') {
        doc.content = doc.content.slice(0, operation.position) + 
                     operation.text + 
                     doc.content.slice(operation.position);
    } else if (operation.type === 'delete') {
        doc.content = doc.content.slice(0, operation.position) + 
                     doc.content.slice(operation.position + operation.length);
    }
}

function broadcastToOthers(docId, excludeUserId, message) {
    const doc = documents.get(docId);
    if (!doc) return;
    
    doc.users.forEach((user, userId) => {
        if (userId !== excludeUserId && user.ws.readyState === WebSocket.OPEN) {
            user.ws.send(JSON.stringify(message));
        }
    });
}

// 启动服务器
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
    console.log(`WebSocket服务器运行在端口 ${PORT}`);
});

2.3 集成前端协同编辑器

创建前端编辑器界面,使用Y.js库实现协同编辑:

<!-- collaborative-editor.php - 编辑器模板文件 -->
<div id="collaborative-editor-container">
    <div class="editor-header">
        <h1 id="doc-title" contenteditable="true"><?php echo get_the_title(); ?></h1>
        <div class="editor-toolbar">
            <button class="format-btn" data-format="bold">粗体</button>
            <button class="format-btn" data-format="italic">斜体</button>
            <button class="format-btn" data-format="underline">下划线</button>
            <select id="font-size">
                <option value="12px">12px</option>
                <option value="14px">14px</option>
                <option value="16px" selected>16px</option>
                <option value="18px">18px</option>
                <option value="24px">24px</option>
            </select>
            <button id="save-version">保存版本</button>
            <button id="export-pdf">导出PDF</button>
        </div>
        <div class="user-presence">
            <span>在线用户: </span>
            <div id="active-users"></div>
        </div>
    </div>
    
    <div class="editor-content">
        <div id="editor" contenteditable="true"></div>
    </div>
    
    <div class="editor-sidebar">
        <div class="version-history">
            <h3>版本历史</h3>
            <ul id="version-list"></ul>
        </div>
        <div class="comments-section">
            <h3>评论</h3>
            <div id="comments-container"></div>
            <textarea id="new-comment" placeholder="添加评论..."></textarea>
            <button id="add-comment">提交评论</button>
        </div>
    </div>
</div>

<script>
// 协同编辑器前端JavaScript
document.addEventListener('DOMContentLoaded', function() {
    const docId = <?php echo get_the_ID(); ?>;
    const userId = <?php echo get_current_user_id(); ?>;
    
    // 初始化Y.js协同编辑
    const ydoc = new Y.Doc();
    const ytext = ydoc.getText('content');
    const editor = document.getElementById('editor');
    
    // 连接WebSocket服务器
    const ws = new WebSocket(`ws://localhost:8080?docId=${docId}&userId=${userId}`);
    
    // 绑定Y.js到编辑器
    const binding = new Y.QuillBinding(ytext, editor);
    
    // 监听WebSocket消息
    ws.onmessage = function(event) {
        const data = JSON.parse(event.data);
        
        switch(data.type) {
            case 'init':
                // 初始化文档内容
                ytext.delete(0, ytext.length);
                ytext.insert(0, data.content);
                updateActiveUsers(data.activeUsers);
                break;
                
            case 'update':
                // 应用远程更新
                applyRemoteUpdate(data.operation);
                break;
                
            case 'user_joined':
                addActiveUser(data.userId);
                break;
                
            case 'user_left':
                removeActiveUser(data.userId);
                break;
                
            case 'cursor_move':
                showRemoteCursor(data.userId, data.position);
                break;
        }
    };
    
    // 发送本地编辑到服务器
    ydoc.on('update', function(update) {
        ws.send(JSON.stringify({
            type: 'edit',
            operation: extractOperationFromUpdate(update)
        }));
    });
    
    // 光标移动跟踪
    editor.addEventListener('keyup', function() {
        const selection = window.getSelection();
        const position = getCursorPosition(editor, selection);
        
        ws.send(JSON.stringify({
            type: 'cursor_move',
            position: position
        }));
    });
    
    // 初始化版本历史
    loadVersionHistory(docId);
    
    // 保存版本功能
    document.getElementById('save-version').addEventListener('click', function() {
        saveDocumentVersion(docId, ytext.toString());
    });
});

// 辅助函数
function updateActiveUsers(userIds) {
    const container = document.getElementById('active-users');
    container.innerHTML = '';
    
    userIds.forEach(userId => {
        const userElement = document.createElement('span');
        userElement.className = 'active-user';
        userElement.textContent = `用户${userId}`;
        userElement.style.backgroundColor = getUserColor(userId);
        container.appendChild(userElement);
    });
}

function getUserColor(userId) {
    // 根据用户ID生成一致的颜色
    const colors = ['#FF6B6B', '#4ECDC4', '#FFD166', '#06D6A0', '#118AB2'];
    return colors[userId % colors.length];
}

function loadVersionHistory(docId) {
    // 通过AJAX加载版本历史
    fetch(`/wp-json/collab/v1/document/${docId}/versions`)
        .then(response => response.json())
        .then(versions => {
            const list = document.getElementById('version-list');
            list.innerHTML = '';
            
            versions.forEach(version => {
                const li = document.createElement('li');
                li.innerHTML = `
                    <span>${version.date}</span>
                    <span>${version.author}</span>
                    <button onclick="restoreVersion(${version.id})">恢复</button>
                    <button onclick="compareVersion(${version.id})">对比</button>
                `;
                list.appendChild(li);
            });
        });
}

function saveDocumentVersion(docId, content) {
    fetch(`/wp-json/collab/v1/document/${docId}/version`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-WP-Nonce': collabVars.nonce
        },
        body: JSON.stringify({
            content: content,
            comment: document.getElementById('version-comment')?.value || '手动保存'
        })
    })
    .then(response => response.json())
    .then(data => {
        if(data.success) {
            alert('版本保存成功');
            loadVersionHistory(docId);
        }
    });
}
</script>

第三部分:实现版本管理系统

3.1 设计版本数据库结构

我们需要创建自定义数据库表来存储文档版本历史:

// 创建版本管理数据库表
function create_version_tables() {
    global $wpdb;
    $charset_collate = $wpdb->get_charset_collate();
    $table_name = $wpdb->prefix . 'collab_doc_versions';
    
    $sql = "CREATE TABLE IF NOT EXISTS $table_name (
        id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        doc_id bigint(20) UNSIGNED NOT NULL,
        version_number int(11) NOT NULL,
        content longtext NOT NULL,
        author_id bigint(20) UNSIGNED NOT NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        change_summary text,
        parent_version_id bigint(20) UNSIGNED DEFAULT NULL,
        PRIMARY KEY (id),
        KEY doc_id (doc_id),
        KEY author_id (author_id)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}
register_activation_hook(__FILE__, 'create_version_tables');

3.2 实现版本控制API

创建REST API端点处理版本管理操作:

// 版本管理REST API
add_action('rest_api_init', function() {
    // 获取文档版本列表
    register_rest_route('collab/v1', '/document/(?P<id>d+)/versions', array(
        'methods' => 'GET',
        'callback' => 'get_document_versions',
        'permission_callback' => 'check_document_permission'
    ));
    
    // 创建新版本
    register_rest_route('collab/v1', '/document/(?P<id>d+)/version', array(
        'methods' => 'POST',
        'callback' => 'create_document_version',
        'permission_callback' => 'check_document_permission'
    ));
    
    // 恢复特定版本
    register_rest_route('collab/v1', '/version/(?P<id>d+)/restore', array(
        'methods' => 'POST',
        'callback' => 'restore_document_version',
        'permission_callback' => 'check_document_permission'
    ));
    
    // 比较两个版本
    register_rest_route('collab/v1', '/compare-versions', array(
        'methods' => 'GET',
        'callback' => 'compare_versions',
        'permission_callback' => 'check_document_permission'
    ));
});

function get_document_versions($request) {
    global $wpdb;
    $doc_id = $request['id'];
    $table_name = $wpdb->prefix . 'collab_doc_versions';
    
    $versions = $wpdb->get_results($wpdb->prepare(
        "SELECT v.*, u.user_login as author_name 
         FROM $table_name v 
         LEFT JOIN {$wpdb->users} u ON v.author_id = u.ID 
         WHERE v.doc_id = %d 
         ORDER BY v.created_at DESC 
         LIMIT 50",
        $doc_id
    ));
    
    // 格式化返回数据
    $formatted_versions = array();
    foreach ($versions as $version) {
        $formatted_versions[] = array(
            'id' => $version->id,
            'version_number' => $version->version_number,
            'created_at' => $version->created_at,
            'author' => $version->author_name,
            'change_summary' => $version->change_summary,
            'content_preview' => wp_trim_words($version->content, 20)
        );
    }
    
    return rest_ensure_response($formatted_versions);
}

function create_document_version($request) {
    global $wpdb;
    $doc_id = $request['id'];

current_user_id();

$content = sanitize_text_field($request['content']);
$comment = sanitize_text_field($request['comment']);

// 获取当前最高版本号
$table_name = $wpdb->prefix . 'collab_doc_versions';
$latest_version = $wpdb->get_var($wpdb->prepare(
    "SELECT MAX(version_number) FROM $table_name WHERE doc_id = %d",
    $doc_id
));

$new_version = $latest_version ? $latest_version + 1 : 1;

// 插入新版本
$result = $wpdb->insert(
    $table_name,
    array(
        'doc_id' => $doc_id,
        'version_number' => $new_version,
        'content' => $content,
        'author_id' => $user_id,
        'change_summary' => $comment,
        'parent_version_id' => $latest_version ? $wpdb->get_var($wpdb->prepare(
            "SELECT id FROM $table_name WHERE doc_id = %d AND version_number = %d",
            $doc_id, $latest_version
        )) : null
    ),
    array('%d', '%d', '%s', '%d', '%s', '%d')
);

if ($result) {
    return rest_ensure_response(array(
        'success' => true,
        'version_id' => $wpdb->insert_id,
        'version_number' => $new_version
    ));
}

return new WP_Error('version_creation_failed', '版本创建失败', array('status' => 500));

}

function restore_document_version($request) {

global $wpdb;
$version_id = $request['id'];

// 获取版本内容
$table_name = $wpdb->prefix . 'collab_doc_versions';
$version = $wpdb->get_row($wpdb->prepare(
    "SELECT * FROM $table_name WHERE id = %d",
    $version_id
));

if (!$version) {
    return new WP_Error('version_not_found', '版本不存在', array('status' => 404));
}

// 更新文档当前内容
update_post_meta($version->doc_id, '_current_content', $version->content);

// 创建恢复记录
$user_id = get_current_user_id();
$latest_version = $wpdb->get_var($wpdb->prepare(
    "SELECT MAX(version_number) FROM $table_name WHERE doc_id = %d",
    $version->doc_id
));

$wpdb->insert(
    $table_name,
    array(
        'doc_id' => $version->doc_id,
        'version_number' => $latest_version + 1,
        'content' => $version->content,
        'author_id' => $user_id,
        'change_summary' => sprintf('恢复到版本 %d', $version->version_number),
        'parent_version_id' => $version->id
    ),
    array('%d', '%d', '%s', '%d', '%s', '%d')
);

return rest_ensure_response(array(
    'success' => true,
    'message' => '版本恢复成功'
));

}

function compare_versions($request) {

$version1_id = $request->get_param('v1');
$version2_id = $request->get_param('v2');

global $wpdb;
$table_name = $wpdb->prefix . 'collab_doc_versions';

$version1 = $wpdb->get_row($wpdb->prepare(
    "SELECT content FROM $table_name WHERE id = %d",
    $version1_id
));

$version2 = $wpdb->get_row($wpdb->prepare(
    "SELECT content FROM $table_name WHERE id = %d",
    $version2_id
));

if (!$version1 || !$version2) {
    return new WP_Error('versions_not_found', '版本不存在', array('status' => 404));
}

// 使用文本差异算法比较版本
$diff = compute_text_diff($version1->content, $version2->content);

return rest_ensure_response(array(
    'diff' => $diff,
    'version1' => array('id' => $version1_id),
    'version2' => array('id' => $version2_id)
));

}

function compute_text_diff($text1, $text2) {

// 使用PHP内置的差异计算函数
require_once(ABSPATH . 'wp-admin/includes/diff.php');

$text1_lines = explode("n", $text1);
$text2_lines = explode("n", $text2);

$diff = new Text_Diff($text1_lines, $text2_lines);
$renderer = new Text_Diff_Renderer_inline();

return $renderer->render($diff);

}


### 3.3 自动版本保存机制

实现智能的自动版本保存功能:

// 自动版本保存机制
class AutoVersionSaver {

private $save_threshold = 30; // 每30秒检查一次
private $change_threshold = 10; // 至少10个字符变化才保存

public function __construct() {
    add_action('wp_ajax_save_auto_version', array($this, 'handle_auto_save'));
    add_action('wp_ajax_nopriv_save_auto_version', array($this, 'handle_auto_save'));
}

public function handle_auto_save() {
    $doc_id = intval($_POST['doc_id']);
    $content = wp_unslash($_POST['content']);
    $last_saved_version = isset($_POST['last_saved']) ? $_POST['last_saved'] : null;
    
    // 验证权限
    if (!current_user_can('edit_post', $doc_id)) {
        wp_die('权限不足');
    }
    
    // 获取上次保存的内容
    $last_content = $this->get_last_saved_content($doc_id, $last_saved_version);
    
    // 计算变化量
    $changes = $this->calculate_changes($last_content, $content);
    
    // 如果变化足够大,则保存新版本
    if ($changes['change_count'] >= $this->change_threshold) {
        $this->save_minor_version($doc_id, $content, $changes['summary']);
        
        wp_send_json_success(array(
            'saved' => true,
            'change_summary' => $changes['summary'],
            'timestamp' => current_time('mysql')
        ));
    } else {
        wp_send_json_success(array(
            'saved' => false,
            'message' => '变化太小,未保存版本'
        ));
    }
}

private function get_last_saved_content($doc_id, $last_saved_version) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'collab_doc_versions';
    
    if ($last_saved_version) {
        $content = $wpdb->get_var($wpdb->prepare(
            "SELECT content FROM $table_name WHERE id = %d",
            $last_saved_version
        ));
    } else {
        $content = $wpdb->get_var($wpdb->prepare(
            "SELECT content FROM $table_name 
             WHERE doc_id = %d 
             ORDER BY created_at DESC LIMIT 1",
            $doc_id
        ));
    }
    
    return $content ?: '';
}

private function calculate_changes($old_content, $new_content) {
    // 简单计算变化:字符差异
    $old_length = strlen($old_content);
    $new_length = strlen($new_content);
    $length_change = $new_length - $old_length;
    
    // 使用更高级的差异检测(可选)
    similar_text($old_content, $new_content, $similarity);
    $change_percentage = 100 - $similarity;
    
    // 生成变化摘要
    $summary = sprintf(
        '长度变化: %+d 字符, 相似度: %.1f%%',
        $length_change,
        $similarity
    );
    
    return array(
        'change_count' => abs($length_change),
        'summary' => $summary,
        'similarity' => $similarity
    );
}

private function save_minor_version($doc_id, $content, $summary) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'collab_doc_versions';
    $user_id = get_current_user_id();
    
    // 获取当前版本号
    $latest_version = $wpdb->get_var($wpdb->prepare(
        "SELECT MAX(version_number) FROM $table_name WHERE doc_id = %d",
        $doc_id
    ));
    
    $new_version = $latest_version ? $latest_version + 1 : 1;
    
    $wpdb->insert(
        $table_name,
        array(
            'doc_id' => $doc_id,
            'version_number' => $new_version,
            'content' => $content,
            'author_id' => $user_id,
            'change_summary' => '自动保存: ' . $summary,
            'parent_version_id' => $latest_version ? $wpdb->get_var($wpdb->prepare(
                "SELECT id FROM $table_name 
                 WHERE doc_id = %d AND version_number = %d",
                $doc_id, $latest_version
            )) : null
        ),
        array('%d', '%d', '%s', '%d', '%s', '%d')
    );
}

}

new AutoVersionSaver();


## 第四部分:用户权限与访问控制

### 4.1 扩展WordPress权限系统

// 协同文档权限管理
class CollaborativeDocPermissions {


public function __construct() {
    add_filter('user_has_cap', array($this, 'add_collab_capabilities'), 10, 4);
    add_action('admin_init', array($this, 'setup_roles_and_capabilities'));
}

public function setup_roles_and_capabilities() {
    $roles = array('administrator', 'editor', 'author', 'contributor', 'subscriber');
    
    foreach ($roles as $role_name) {
        $role = get_role($role_name);
        
        if ($role) {
            // 基础权限
            $role->add_cap('read_collab_doc');
            
            // 根据角色分配不同权限
            switch ($role_name) {
                case 'administrator':
                case 'editor':
                    $role->add_cap('edit_collab_docs');
                    $role->add_cap('edit_others_collab_docs');
                    $role->add_cap('publish_collab_docs');
                    $role->add_cap('delete_collab_docs');
                    $role->add_cap('manage_collab_doc_settings');
                    break;
                    
                case 'author':
                    $role->add_cap('edit_collab_docs');
                    $role->add_cap('publish_collab_docs');
                    $role->add_cap('delete_collab_docs');
                    break;
                    
                case 'contributor':
                    $role->add_cap('edit_collab_docs');
                    break;
            }
        }
    }
}

public function add_collab_capabilities($allcaps, $caps, $args, $user) {
    $requested_capability = $args[0];
    $post_id = isset($args[2]) ? $args[2] : 0;
    
    // 处理协同文档特定权限
    if (strpos($requested_capability, 'collab_doc') !== false && $post_id) {
        $post_type = get_post_type($post_id);
        
        if ($post_type === 'collab_doc') {
            // 检查文档特定权限设置
            $doc_permissions = get_post_meta($post_id, '_collab_permissions', true);
            
            if (!empty($doc_permissions)) {
                $user_id = $user->ID;
                
                // 文档所有者有完全权限
                $post = get_post($post_id);
                if ($post && $post->post_author == $user_id) {
                    $allcaps['edit_collab_docs'] = true;
                    $allcaps['edit_others_collab_docs'] = true;
                    $allcaps['delete_collab_docs'] = true;
                    return $allcaps;
                }
                
                // 检查用户是否在允许列表中
                if (isset($doc_permissions['allowed_users'])) {
                    $allowed_users = $doc_permissions['allowed_users'];
                    
                    if (in_array($user_id, $allowed_users)) {
                        $permission_level = $doc_permissions['user_levels'][$user_id] ?? 'viewer';
                        
                        switch ($permission_level) {
                            case 'editor':
                                $allcaps['edit_collab_docs'] = true;
                                break;
                            case 'commenter':
                                $allcaps['comment_collab_docs'] = true;
                                break;
                            case 'viewer':
                                $allcaps['read_collab_doc'] = true;
                                break;
                        }
                    }
                }
                
                // 检查用户组权限
                if (isset($doc_permissions['allowed_roles'])) {
                    $user_roles = $user->roles;
                    
                    foreach ($user_roles as $role) {
                        if (in_array($role, $doc_permissions['allowed_roles'])) {
                            $allcaps['read_collab_doc'] = true;
                            
                            if (in_array($role, $doc_permissions['editor_roles'])) {
                                $allcaps['edit_collab_docs'] = true;
                            }
                            break;
                        }
                    }
                }
            }
        }
    }
    
    return $allcaps;
}

// 文档共享功能
public static function share_document($doc_id, $user_emails, $permission_level = 'viewer') {
    $user_ids = array();
    
    foreach ($user_emails as $email) {
        $user = get_user_by('email', $email);
        if ($user) {
            $user_ids[] = $user->ID;
            
            // 发送通知邮件
            self::send_sharing_notification($user, $doc_id, $permission_level);
        }
    }
    
    // 更新文档权限设置
    $permissions = get_post_meta($doc_id, '_collab_permissions', true) ?: array();
    
    if (!isset($permissions['allowed_users'])) {
        $permissions['allowed_users'] = array();
    }
    
    foreach ($user_ids as $user_id) {
        if (!in_array($user_id, $permissions['allowed_users'])) {
            $permissions['allowed_users'][] = $user_id;
        }
        
        $permissions['user_levels'][$user_id] = $permission_level;
    }
    
    update_post_meta($doc_id, '_collab_permissions', $permissions);
    
    return count($user_ids);
}

private static function send_sharing_notification($user, $doc_id, $permission_level) {
    $doc_title = get_the_title($doc_id);
    $doc_link = get_permalink($doc_id);
    $admin_email = get_option('admin_email');
    
    $subject = sprintf('您被邀请协作编辑文档: %s', $doc_title);
    
    $message = sprintf(
        "您好 %s,nn您被邀请%s文档《%s》。nn文档链接: %snn权限级别: %snn请点击链接开始协作。nn此邮件由系统自动发送,请勿回复。",
        $user->display_name,
        $permission_level === 'viewer' ? '查看' : ($permission_level === 'commenter' ? '评论' : '编辑'),
        $doc_title,
        $doc_link,
        $permission_level
    );
    
    wp_mail($user->user_email, $subject, $message, array(
        'From: ' . get_bloginfo('name') . ' <' . $admin_email . '>'
    ));
}

}

new CollaborativeDocPermissions();


### 4.2 实时用户状态显示

// 实时用户状态管理
class UserPresenceManager {

constructor(docId) {
    this.docId = docId;
    this.activeUsers = new Map();
    this.userColors = new Map();
    this.cursorPositions = new Map();
    
    this.initWebSocket();
    this.setupHeartbeat();
}

initWebSocket() {
    this.ws = new WebSocket(`ws://localhost:8080/presence?docId=${this.docId}&userId=${this.userId}`);
    
    this.ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        this.handlePresenceUpdate(data);
    };
    
    this.ws.onopen = () => {
        this.sendHeartbeat();
    };
}

handlePresenceUpdate(data) {
    switch(data.type) {
        case 'user_joined':
            this.addUser(data.userId, data.userInfo);
            break;
            
        case 'user_left':
            this.removeUser(data.userId);
            break;
            
        case 'user_activity':
            this.updateUserActivity(data.userId, data.activity);
            break;
            
        case 'cursor_update':
            this.updateCursor(data.userId, data.position, data.selection);
            break;
    }
}

addUser(userId, userInfo) {
    if (!this.activeUsers.has(userId)) {
        this.activeUsers.set(userId, {
            ...userInfo,
            lastActive: Date.now(),
            color: this.getUserColor(userId)
        });
        
        this.updateUI();
        
        // 显示通知
        this.showNotification(`${userInfo.name} 加入了文档`);
    }
}

removeUser(userId) {
    if (this.activeUsers.has(userId)) {
        const user = this.activeUsers.get(userId);
        this.activeUsers.delete(userId);
        
        this.updateUI();
        
        // 显示通知
        this.showNotification(`${user.name} 离开了文档`);
        
        // 移除光标显示
        this.removeCursor(userId);
    }
}

updateCursor(userId,
本文来自网络,不代表柔性供应链服务中心立场,转载请注明出处:https://mall.org.cn/5341.html

EXCHANGES®作者

上一篇
下一篇

为您推荐

发表回复

联系我们

联系我们

18559313275

在线咨询: QQ交谈

邮箱: vip@exchanges.center

工作时间:周一至周五,9:00-17:30,节假日休息
返回顶部