diff --git a/z.ai/notes/index.php b/z.ai/notes/index.php index 189725d..baa4ebd 100644 --- a/z.ai/notes/index.php +++ b/z.ai/notes/index.php @@ -170,17 +170,12 @@ function downloadAndSaveImage($url) { // 自动格式化链接 function autoFormatLinks($content) { - // 匹配非Markdown格式的URL - $pattern = '/(?exec(' + CREATE TABLE IF NOT EXISTS shares ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + note_id INTEGER NOT NULL, + share_token TEXT UNIQUE NOT NULL, + password TEXT, + view_limit INTEGER DEFAULT 0, + view_count INTEGER DEFAULT 0, + expires_at DATETIME, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (note_id) REFERENCES notes (id) ON DELETE CASCADE + ) + '); + // 创建索引 $db->exec('CREATE INDEX IF NOT EXISTS idx_notes_title ON notes(title)'); $db->exec('CREATE INDEX IF NOT EXISTS idx_notes_tags ON notes(tags)'); $db->exec('CREATE INDEX IF NOT EXISTS idx_notes_created ON notes(created_at)'); $db->exec('CREATE INDEX IF NOT EXISTS idx_attachments_note_id ON attachments(note_id)'); + $db->exec('CREATE INDEX IF NOT EXISTS idx_shares_token ON shares(share_token)'); + $db->exec('CREATE INDEX IF NOT EXISTS idx_shares_note_id ON shares(note_id)'); return $db; } catch (Exception $e) { @@ -569,6 +581,75 @@ function isLoggedIn() { return isset($_SESSION['user_id']); } +// 创建分享 +function createShare($noteId, $password = null, $viewLimit = 0, $expiresAt = null) { + $db = getDB(); + + // 生成唯一的分享令牌 + $shareToken = bin2hex(random_bytes(16)); + + $stmt = $db->prepare('INSERT INTO shares (note_id, share_token, password, view_limit, expires_at) VALUES (?, ?, ?, ?, ?)'); + $stmt->bindValue(1, $noteId, SQLITE3_INTEGER); + $stmt->bindValue(2, $shareToken, SQLITE3_TEXT); + $stmt->bindValue(3, $password ? password_hash($password, PASSWORD_DEFAULT) : null, SQLITE3_TEXT); + $stmt->bindValue(4, $viewLimit, SQLITE3_INTEGER); + $stmt->bindValue(5, $expiresAt, SQLITE3_TEXT); + $stmt->execute(); + + return $shareToken; +} + +// 获取分享信息 +function getShareByToken($token) { + $db = getDB(); + $stmt = $db->prepare('SELECT * FROM shares WHERE share_token = ?'); + $stmt->bindValue(1, $token, SQLITE3_TEXT); + $result = $stmt->execute(); + $share = $result->fetchArray(SQLITE3_ASSOC); + + if ($share) { + // 检查是否过期 + if ($share['expires_at'] && strtotime($share['expires_at']) < time()) { + return null; + } + + // 检查查看次数限制 + if ($share['view_limit'] > 0 && $share['view_count'] >= $share['view_limit']) { + return null; + } + + // 增加查看次数 + $updateStmt = $db->prepare('UPDATE shares SET view_count = view_count + 1 WHERE id = ?'); + $updateStmt->bindValue(1, $share['id'], SQLITE3_INTEGER); + $updateStmt->execute(); + } + + return $share; +} + +// 获取笔记的分享列表 +function getNoteShares($noteId) { + $db = getDB(); + $stmt = $db->prepare('SELECT * FROM shares WHERE note_id = ? ORDER BY created_at DESC'); + $stmt->bindValue(1, $noteId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $shares = []; + + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $shares[] = $row; + } + + return $shares; +} + +// 删除分享 +function deleteShare($id) { + $db = getDB(); + $stmt = $db->prepare('DELETE FROM shares WHERE id = ?'); + $stmt->bindValue(1, $id, SQLITE3_INTEGER); + return $stmt->execute(); +} + // 处理登录 if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { if ($_POST['action'] === 'init') { @@ -652,6 +733,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { } else { $error = '标题和内容不能为空'; } + } elseif ($_POST['action'] === 'create_share') { + if (!isLoggedIn()) { + header('Location: ?'); + exit; + } + + $noteId = intval($_POST['note_id'] ?? 0); + $password = trim($_POST['share_password'] ?? ''); + $viewLimit = intval($_POST['view_limit'] ?? 0); + $expiresIn = intval($_POST['expires_in'] ?? 0); + + // 计算过期时间 + $expiresAt = null; + if ($expiresIn > 0) { + $expiresAt = date('Y-m-d H:i:s', time() + $expiresIn * 3600); // 小时转换为秒 + } + + $shareToken = createShare($noteId, $password ?: null, $viewLimit, $expiresAt); + + if ($shareToken) { + $shareUrl = getBaseUrl() . '/' . getCurrentFile() . '?share=' . $shareToken; + $_SESSION['share_success'] = '分享链接已创建: ' . $shareUrl . ''; + } else { + $error = '创建分享失败'; + } + + header('Location: ?view=' . $noteId); + exit; } } @@ -671,6 +780,14 @@ if (isset($_GET['delete_attachment']) && isLoggedIn()) { exit; } +// 处理删除分享 +if (isset($_GET['delete_share']) && isLoggedIn()) { + $id = intval($_GET['delete_share']); + deleteShare($id); + header('Location: ' . $_SERVER['HTTP_REFERER']); + exit; +} + // 处理登出 if (isset($_GET['logout'])) { session_destroy(); @@ -686,6 +803,392 @@ if (isset($_GET['logout'])) { // 获取导航URL $homeUrl = getHomeUrl(); +// 处理分享访问 +if (isset($_GET['share'])) { + $shareToken = $_GET['share']; + $share = getShareByToken($shareToken); + + if (!$share) { + $shareError = '分享链接无效或已过期'; + } else { + $note = getNote($share['note_id']); + if (!$note) { + $shareError = '笔记不存在'; + } else { + // 检查是否需要密码 + if ($share['password'] && !isset($_SESSION['share_authenticated'][$shareToken])) { + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'verify_share_password') { + $password = $_POST['share_password'] ?? ''; + if (password_verify($password, $share['password'])) { + $_SESSION['share_authenticated'][$shareToken] = true; + } else { + $shareError = '密码错误'; + } + } + + if (!isset($_SESSION['share_authenticated'][$shareToken])) { + // 显示密码输入页面 + ?> + + +
+ + +此分享需要密码验证
+