vikunja/pkg/models/comment_quotes.go

125 lines
3.1 KiB
Go

// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package models
import (
"strconv"
"strings"
"code.vikunja.io/api/pkg/user"
"golang.org/x/net/html"
"xorm.io/xorm"
)
// extractQuotedCommentIDs parses HTML and returns the set of positive integer
// comment ids referenced by <blockquote data-comment-id="…"> nodes anywhere in
// the document. Malformed values and zero/negative ids are silently skipped.
func extractQuotedCommentIDs(htmlText string) []int64 {
if htmlText == "" {
return nil
}
doc, err := html.Parse(strings.NewReader(htmlText))
if err != nil {
return nil
}
ids := []int64{}
seen := make(map[int64]bool)
var traverse func(*html.Node)
traverse = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "blockquote" {
for _, attr := range n.Attr {
if attr.Key != "data-comment-id" {
continue
}
id, err := strconv.ParseInt(strings.TrimSpace(attr.Val), 10, 64)
if err == nil && id > 0 && !seen[id] {
ids = append(ids, id)
seen[id] = true
}
break
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
traverse(c)
}
}
traverse(doc)
return ids
}
// findQuotedCommentAuthors resolves the authors of comments referenced via
// <blockquote data-comment-id="…"> within text, restricted to comments on the
// given task and excluding the doer themselves. Missing or wrong-task
// references are silently skipped. Link-share authors (negative ids) are
// also skipped since they cannot receive notifications.
func findQuotedCommentAuthors(s *xorm.Session, taskID, doerID int64, text string) (map[int64]*user.User, error) {
ids := extractQuotedCommentIDs(text)
if len(ids) == 0 {
return nil, nil
}
rows := []*TaskComment{}
err := s.
In("id", ids).
And("task_id = ?", taskID).
Cols("id", "author_id", "task_id").
Find(&rows)
if err != nil {
return nil, err
}
if len(rows) == 0 {
return nil, nil
}
dedup := map[int64]bool{}
wanted := []int64{}
for _, r := range rows {
id := r.AuthorID
if id == doerID {
continue
}
if id <= 0 {
continue
}
if dedup[id] {
continue
}
dedup[id] = true
wanted = append(wanted, id)
}
if len(wanted) == 0 {
return nil, nil
}
list := []*user.User{}
err = s.In("id", wanted).Find(&list)
if err != nil {
return nil, err
}
users := make(map[int64]*user.User, len(list))
for _, u := range list {
users[u.ID] = u
}
return users, nil
}