2018-02-10 15:07:21 +00:00
|
|
|
package routes
|
|
|
|
|
|
|
|
import (
|
2019-02-23 06:29:19 +00:00
|
|
|
"database/sql"
|
2018-02-10 15:07:21 +00:00
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
2019-02-10 05:52:26 +00:00
|
|
|
"strings"
|
2018-02-10 15:07:21 +00:00
|
|
|
|
2019-04-19 06:36:26 +00:00
|
|
|
c "github.com/Azareal/Gosora/common"
|
2018-11-01 06:43:56 +00:00
|
|
|
"github.com/Azareal/Gosora/common/phrases"
|
2018-02-10 15:07:21 +00:00
|
|
|
)
|
|
|
|
|
2019-04-19 06:36:26 +00:00
|
|
|
func wsTopicList(topicList []*c.TopicsRow, lastPage int) *c.WsTopicList {
|
|
|
|
wsTopicList := make([]*c.WsTopicsRow, len(topicList))
|
2019-02-10 05:52:26 +00:00
|
|
|
for i, topicRow := range topicList {
|
|
|
|
wsTopicList[i] = topicRow.WebSockets()
|
|
|
|
}
|
2019-05-09 06:58:55 +00:00
|
|
|
return &c.WsTopicList{wsTopicList, lastPage, 0}
|
2019-02-10 05:52:26 +00:00
|
|
|
}
|
|
|
|
|
2020-07-19 03:26:56 +00:00
|
|
|
func wsTopicList2(topicList []*c.TopicsRow, u *c.User, fps map[int]c.QuickTools, lastPage int) *c.WsTopicList {
|
|
|
|
wsTopicList := make([]*c.WsTopicsRow, len(topicList))
|
|
|
|
for i, t := range topicList {
|
|
|
|
var canMod bool
|
|
|
|
if fps == nil {
|
|
|
|
canMod = true
|
|
|
|
} else {
|
|
|
|
quickTools := fps[t.ParentID]
|
|
|
|
canMod = t.CreatedBy == u.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove
|
|
|
|
}
|
|
|
|
wsTopicList[i] = t.WebSockets2(canMod)
|
|
|
|
}
|
|
|
|
return &c.WsTopicList{wsTopicList, lastPage, 0}
|
|
|
|
}
|
|
|
|
|
2020-04-11 08:12:37 +00:00
|
|
|
func TopicList(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError {
|
2020-07-15 08:27:36 +00:00
|
|
|
/*skip, rerr := h.Hooks.VhookSkippable("route_topic_list_start", w, r, u, h)
|
|
|
|
if skip || rerr != nil {
|
|
|
|
return rerr
|
|
|
|
}*/
|
|
|
|
skip, rerr := c.H_route_topic_list_start_hook(h.Hooks, w, r, u, h)
|
2019-04-20 04:55:22 +00:00
|
|
|
if skip || rerr != nil {
|
|
|
|
return rerr
|
|
|
|
}
|
2020-04-11 08:12:37 +00:00
|
|
|
return TopicListCommon(w, r, u, h, "lastupdated", 0)
|
2019-02-23 06:29:19 +00:00
|
|
|
}
|
|
|
|
|
2020-04-11 08:12:37 +00:00
|
|
|
func TopicListMostViewed(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError {
|
2020-07-15 08:27:36 +00:00
|
|
|
skip, rerr := h.Hooks.VhookSkippable("route_topic_list_mostviewed_start", w, r, u, h)
|
|
|
|
if skip || rerr != nil {
|
|
|
|
return rerr
|
|
|
|
}
|
2020-04-11 08:12:37 +00:00
|
|
|
return TopicListCommon(w, r, u, h, "mostviewed", c.TopicListMostViewed)
|
2019-02-23 06:29:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Implement search
|
2020-03-18 09:21:34 +00:00
|
|
|
func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header, torder string, tsorder int) c.RouteError {
|
2020-02-09 10:00:08 +00:00
|
|
|
h.Title = phrases.GetTitlePhrase("topics")
|
|
|
|
h.Zone = "topics"
|
|
|
|
h.Path = "/topics/"
|
|
|
|
h.MetaDesc = h.Settings["meta_desc"].(string)
|
2018-09-26 07:46:30 +00:00
|
|
|
|
2019-04-19 06:36:26 +00:00
|
|
|
group, err := c.Groups.Get(user.Group)
|
2018-09-26 07:46:30 +00:00
|
|
|
if err != nil {
|
2019-04-19 06:36:26 +00:00
|
|
|
log.Printf("Group #%d doesn't exist despite being used by c.User #%d", user.Group, user.ID)
|
|
|
|
return c.LocalError("Something weird happened", w, r, user)
|
2018-09-26 07:46:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the current page
|
|
|
|
page, _ := strconv.Atoi(r.FormValue("page"))
|
2019-02-10 05:52:26 +00:00
|
|
|
sfids := r.FormValue("fids")
|
|
|
|
var fids []int
|
|
|
|
if sfids != "" {
|
|
|
|
for _, sfid := range strings.Split(sfids, ",") {
|
|
|
|
fid, err := strconv.Atoi(sfid)
|
|
|
|
if err != nil {
|
2019-04-19 06:36:26 +00:00
|
|
|
return c.LocalError("Invalid fid", w, r, user)
|
2019-02-10 05:52:26 +00:00
|
|
|
}
|
|
|
|
fids = append(fids, fid)
|
|
|
|
}
|
|
|
|
if len(fids) == 1 {
|
2019-04-19 06:36:26 +00:00
|
|
|
forum, err := c.Forums.Get(fids[0])
|
2019-02-10 05:52:26 +00:00
|
|
|
if err != nil {
|
2019-04-19 06:36:26 +00:00
|
|
|
return c.LocalError("Invalid fid forum", w, r, user)
|
2019-02-10 05:52:26 +00:00
|
|
|
}
|
2020-02-09 10:00:08 +00:00
|
|
|
h.Title = forum.Name
|
|
|
|
h.ZoneID = forum.ID
|
2019-02-10 05:52:26 +00:00
|
|
|
}
|
|
|
|
}
|
2018-09-26 07:46:30 +00:00
|
|
|
|
2019-02-23 06:29:19 +00:00
|
|
|
// TODO: Allow multiple forums in searches
|
|
|
|
// TODO: Simplify this block after initially landing search
|
2019-04-19 06:36:26 +00:00
|
|
|
var topicList []*c.TopicsRow
|
|
|
|
var forumList []c.Forum
|
2020-03-03 04:25:18 +00:00
|
|
|
var pagi c.Paginator
|
2020-07-19 03:26:56 +00:00
|
|
|
var canDelete, ccanDelete, canLock, ccanLock, canMove, ccanMove bool
|
2019-02-23 06:29:19 +00:00
|
|
|
q := r.FormValue("q")
|
2019-04-19 06:36:26 +00:00
|
|
|
if q != "" && c.RepliesSearch != nil {
|
2019-02-23 06:29:19 +00:00
|
|
|
var canSee []int
|
|
|
|
if user.IsSuperAdmin {
|
2019-04-19 06:36:26 +00:00
|
|
|
canSee, err = c.Forums.GetAllVisibleIDs()
|
2019-02-23 06:29:19 +00:00
|
|
|
if err != nil {
|
2019-04-19 06:36:26 +00:00
|
|
|
return c.InternalError(err, w, r)
|
2019-02-23 06:29:19 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
canSee = group.CanSee
|
|
|
|
}
|
|
|
|
|
|
|
|
var cfids []int
|
|
|
|
if len(fids) > 0 {
|
2019-10-06 00:34:09 +00:00
|
|
|
inSlice := func(haystack []int, needle int) bool {
|
2019-02-23 06:29:19 +00:00
|
|
|
for _, item := range haystack {
|
|
|
|
if needle == item {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for _, fid := range fids {
|
|
|
|
if inSlice(canSee, fid) {
|
2020-02-09 10:00:08 +00:00
|
|
|
f := c.Forums.DirtyGet(fid)
|
2020-03-03 04:25:18 +00:00
|
|
|
if f.Name != "" && f.Active && (f.ParentType == "" || f.ParentType == "forum") && f.TopicCount != 0 {
|
2019-02-23 06:29:19 +00:00
|
|
|
// TODO: Add a hook here for plugin_guilds?
|
|
|
|
cfids = append(cfids, fid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cfids = canSee
|
|
|
|
}
|
|
|
|
|
2019-04-19 06:36:26 +00:00
|
|
|
tids, err := c.RepliesSearch.Query(q, cfids)
|
2019-02-23 06:29:19 +00:00
|
|
|
if err != nil && err != sql.ErrNoRows {
|
2019-04-19 06:36:26 +00:00
|
|
|
return c.InternalError(err, w, r)
|
2019-02-23 06:29:19 +00:00
|
|
|
}
|
2019-04-20 04:55:22 +00:00
|
|
|
//log.Printf("tids %+v\n", tids)
|
2019-02-23 06:29:19 +00:00
|
|
|
// TODO: Handle the case where there aren't any items...
|
|
|
|
// TODO: Add a BulkGet method which returns a slice?
|
2019-04-19 06:36:26 +00:00
|
|
|
tMap, err := c.Topics.BulkGetMap(tids)
|
2019-02-23 06:29:19 +00:00
|
|
|
if err != nil {
|
2019-04-19 06:36:26 +00:00
|
|
|
return c.InternalError(err, w, r)
|
2019-02-23 06:29:19 +00:00
|
|
|
}
|
2020-07-15 08:27:36 +00:00
|
|
|
// TODO: Cache emptied map across requests with sync pool
|
2019-10-06 00:34:09 +00:00
|
|
|
reqUserList := make(map[int]bool)
|
2020-02-09 10:00:08 +00:00
|
|
|
for _, t := range tMap {
|
|
|
|
reqUserList[t.CreatedBy] = true
|
|
|
|
reqUserList[t.LastReplyBy] = true
|
|
|
|
topicList = append(topicList, t.TopicsRow())
|
2019-02-23 06:29:19 +00:00
|
|
|
}
|
|
|
|
//fmt.Printf("reqUserList %+v\n", reqUserList)
|
|
|
|
|
|
|
|
// Convert the user ID map to a slice, then bulk load the users
|
2019-10-06 00:34:09 +00:00
|
|
|
idSlice := make([]int, len(reqUserList))
|
2019-02-23 06:29:19 +00:00
|
|
|
var i int
|
|
|
|
for userID := range reqUserList {
|
|
|
|
idSlice[i] = userID
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: What if a user is deleted via the Control Panel?
|
|
|
|
//fmt.Printf("idSlice %+v\n", idSlice)
|
2019-04-19 06:36:26 +00:00
|
|
|
userList, err := c.Users.BulkGetMap(idSlice)
|
2019-02-23 06:29:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil // TODO: Implement this!
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: De-dupe this logic in common/topic_list.go?
|
2020-07-15 08:27:36 +00:00
|
|
|
//var sb strings.Builder
|
2020-07-19 03:26:56 +00:00
|
|
|
fps := make(map[int]c.QuickTools)
|
2020-02-09 10:00:08 +00:00
|
|
|
for _, t := range topicList {
|
2020-07-15 08:27:36 +00:00
|
|
|
//c.BuildTopicURLSb(&sb, c.NameToSlug(t.Title), t.ID)
|
|
|
|
//t.Link = sb.String()
|
|
|
|
//sb.Reset()
|
2020-02-09 10:00:08 +00:00
|
|
|
t.Link = c.BuildTopicURL(c.NameToSlug(t.Title), t.ID)
|
2020-03-03 04:25:18 +00:00
|
|
|
// TODO: Pass forum to something like t.Forum and use that instead of these two properties? Could be more flexible.
|
2020-07-19 03:26:56 +00:00
|
|
|
f := c.Forums.DirtyGet(t.ParentID)
|
|
|
|
t.ForumName = f.Name
|
|
|
|
t.ForumLink = f.Link
|
2019-02-23 06:29:19 +00:00
|
|
|
|
2020-07-19 03:26:56 +00:00
|
|
|
_, ok := fps[f.ID]
|
|
|
|
if !ok {
|
|
|
|
// TODO: Abstract this?
|
|
|
|
fp, err := c.FPStore.Get(f.ID, user.Group)
|
|
|
|
if err == c.ErrNoRows {
|
|
|
|
fp = c.BlankForumPerms()
|
|
|
|
} else if err != nil {
|
|
|
|
return c.InternalError(err, w, r)
|
|
|
|
}
|
|
|
|
if fp.Overrides && !user.IsSuperAdmin {
|
|
|
|
ccanDelete = fp.DeleteTopic
|
|
|
|
ccanLock = fp.CloseTopic
|
|
|
|
ccanMove = fp.MoveTopic
|
|
|
|
} else {
|
|
|
|
ccanDelete = user.Perms.DeleteTopic
|
|
|
|
ccanLock = user.Perms.CloseTopic
|
|
|
|
ccanMove = user.Perms.MoveTopic
|
|
|
|
}
|
|
|
|
if ccanDelete {
|
|
|
|
canDelete = true
|
|
|
|
}
|
|
|
|
if ccanLock {
|
|
|
|
canLock = true
|
|
|
|
}
|
|
|
|
if ccanMove {
|
|
|
|
canMove = true
|
|
|
|
}
|
|
|
|
fps[f.ID] = c.QuickTools{ccanDelete, ccanLock, ccanMove}
|
2020-07-15 08:27:36 +00:00
|
|
|
}
|
|
|
|
|
2019-02-23 06:29:19 +00:00
|
|
|
// TODO: Create a specialised function with a bit less overhead for getting the last page for a post count
|
2020-02-09 10:00:08 +00:00
|
|
|
_, _, lastPage := c.PageOffset(t.PostCount, 1, c.Config.ItemsPerPage)
|
|
|
|
t.LastPage = lastPage
|
2020-07-15 08:27:36 +00:00
|
|
|
// TODO: Avoid map if either is equal to the current user
|
2020-02-09 10:00:08 +00:00
|
|
|
t.Creator = userList[t.CreatedBy]
|
|
|
|
t.LastUser = userList[t.LastReplyBy]
|
2019-02-23 06:29:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Reduce the amount of boilerplate here
|
|
|
|
if r.FormValue("js") == "1" {
|
2020-07-19 03:26:56 +00:00
|
|
|
outBytes, err := wsTopicList2(topicList, user, fps, pagi.LastPage).MarshalJSON()
|
2019-02-23 06:29:19 +00:00
|
|
|
if err != nil {
|
2019-04-19 06:36:26 +00:00
|
|
|
return c.InternalError(err, w, r)
|
2019-02-23 06:29:19 +00:00
|
|
|
}
|
|
|
|
w.Write(outBytes)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-19 03:26:56 +00:00
|
|
|
topicList2 := make([]c.TopicsRowMut, len(topicList))
|
|
|
|
for i, t := range topicList {
|
|
|
|
var canMod bool
|
|
|
|
if fps == nil {
|
|
|
|
canMod = true
|
|
|
|
} else {
|
|
|
|
quickTools := fps[t.ParentID]
|
|
|
|
canMod = t.CreatedBy == user.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove
|
|
|
|
}
|
|
|
|
topicList2[i] = c.TopicsRowMut{t, canMod}
|
|
|
|
}
|
|
|
|
|
2020-02-09 10:00:08 +00:00
|
|
|
h.Title = phrases.GetTitlePhrase("topics_search")
|
2020-07-19 03:26:56 +00:00
|
|
|
pi := c.TopicListPage{h, topicList2, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, c.QuickTools{canDelete, canLock, canMove}, pagi}
|
2020-02-09 10:00:08 +00:00
|
|
|
return renderTemplate("topics", w, r, h, pi)
|
2019-02-23 06:29:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Pass a struct back rather than passing back so many variables
|
2020-07-19 03:26:56 +00:00
|
|
|
var fps map[int]c.QuickTools
|
2018-09-26 07:46:30 +00:00
|
|
|
if user.IsSuperAdmin {
|
2020-03-03 04:25:18 +00:00
|
|
|
topicList, forumList, pagi, err = c.TopicList.GetList(page, tsorder, fids)
|
2020-07-15 08:27:36 +00:00
|
|
|
canLock, canMove = true, true
|
2018-09-26 07:46:30 +00:00
|
|
|
} else {
|
2020-03-03 04:25:18 +00:00
|
|
|
topicList, forumList, pagi, err = c.TopicList.GetListByGroup(group, page, tsorder, fids)
|
2020-07-19 03:26:56 +00:00
|
|
|
fps = make(map[int]c.QuickTools)
|
|
|
|
for _, f := range forumList {
|
|
|
|
fp, err := c.FPStore.Get(f.ID, user.Group)
|
2020-07-15 08:27:36 +00:00
|
|
|
if err == c.ErrNoRows {
|
|
|
|
fp = c.BlankForumPerms()
|
|
|
|
} else if err != nil {
|
|
|
|
return c.InternalError(err, w, r)
|
|
|
|
}
|
|
|
|
if fp.Overrides {
|
2020-07-19 03:26:56 +00:00
|
|
|
ccanDelete = fp.DeleteTopic
|
2020-07-15 08:27:36 +00:00
|
|
|
ccanLock = fp.CloseTopic
|
|
|
|
ccanMove = fp.MoveTopic
|
|
|
|
} else {
|
2020-07-19 03:26:56 +00:00
|
|
|
ccanDelete = user.Perms.DeleteTopic
|
2020-07-15 08:27:36 +00:00
|
|
|
ccanLock = user.Perms.CloseTopic
|
|
|
|
ccanMove = user.Perms.MoveTopic
|
|
|
|
}
|
2020-07-19 03:26:56 +00:00
|
|
|
if ccanDelete {
|
|
|
|
canDelete = true
|
|
|
|
}
|
2020-07-15 08:27:36 +00:00
|
|
|
if ccanLock {
|
|
|
|
canLock = true
|
|
|
|
}
|
|
|
|
if ccanMove {
|
|
|
|
canMove = true
|
|
|
|
}
|
2020-07-19 03:26:56 +00:00
|
|
|
fps[f.ID] = c.QuickTools{ccanDelete, ccanLock, ccanMove}
|
2020-07-15 08:27:36 +00:00
|
|
|
}
|
2018-09-26 07:46:30 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
2019-04-19 06:36:26 +00:00
|
|
|
return c.InternalError(err, w, r)
|
2018-09-26 07:46:30 +00:00
|
|
|
}
|
|
|
|
|
2019-02-10 05:52:26 +00:00
|
|
|
// TODO: Reduce the amount of boilerplate here
|
|
|
|
if r.FormValue("js") == "1" {
|
2020-07-19 03:26:56 +00:00
|
|
|
outBytes, err := wsTopicList2(topicList, user, fps, pagi.LastPage).MarshalJSON()
|
2019-02-10 05:52:26 +00:00
|
|
|
if err != nil {
|
2019-04-19 06:36:26 +00:00
|
|
|
return c.InternalError(err, w, r)
|
2019-02-10 05:52:26 +00:00
|
|
|
}
|
|
|
|
w.Write(outBytes)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-19 03:26:56 +00:00
|
|
|
topicList2 := make([]c.TopicsRowMut, len(topicList))
|
|
|
|
for i, t := range topicList {
|
|
|
|
var canMod bool
|
|
|
|
if fps == nil {
|
|
|
|
canMod = true
|
|
|
|
} else {
|
|
|
|
quickTools := fps[t.ParentID]
|
|
|
|
canMod = t.CreatedBy == user.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove
|
|
|
|
}
|
|
|
|
topicList2[i] = c.TopicsRowMut{t, canMod}
|
|
|
|
}
|
|
|
|
|
|
|
|
pi := c.TopicListPage{h, topicList2, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, c.QuickTools{canDelete, canLock, canMove}, pagi}
|
2020-04-11 08:12:37 +00:00
|
|
|
if r.FormValue("i") == "1" {
|
|
|
|
return renderTemplate("topics_mini", w, r, h, pi)
|
|
|
|
}
|
2020-02-09 10:00:08 +00:00
|
|
|
return renderTemplate("topics", w, r, h, pi)
|
2018-09-26 07:46:30 +00:00
|
|
|
}
|