2017-09-03 04:50:31 +00:00
|
|
|
/*
|
|
|
|
*
|
2017-09-13 15:09:13 +00:00
|
|
|
* Gosora Route Handlers
|
|
|
|
* Copyright Azareal 2016 - 2018
|
2017-09-03 04:50:31 +00:00
|
|
|
*
|
|
|
|
*/
|
2016-12-02 07:38:54 +00:00
|
|
|
package main
|
|
|
|
|
2017-05-11 13:04:43 +00:00
|
|
|
import (
|
2017-09-03 04:50:31 +00:00
|
|
|
"html"
|
2017-12-24 07:38:46 +00:00
|
|
|
"log"
|
2017-05-11 13:04:43 +00:00
|
|
|
"net/http"
|
2017-09-03 04:50:31 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2017-06-19 08:06:54 +00:00
|
|
|
|
2017-11-10 03:33:11 +00:00
|
|
|
"./common"
|
2017-06-19 08:06:54 +00:00
|
|
|
"./query_gen/lib"
|
2017-05-11 13:04:43 +00:00
|
|
|
)
|
2017-04-05 14:05:37 +00:00
|
|
|
|
2016-12-02 07:38:54 +00:00
|
|
|
// A blank list to fill out that parameter in Page for routes which don't use it
|
2016-12-18 12:56:06 +00:00
|
|
|
var tList []interface{}
|
2017-09-03 04:50:31 +00:00
|
|
|
|
|
|
|
//var nList []string
|
|
|
|
var successJSONBytes = []byte(`{"success":"1"}`)
|
2016-12-02 07:38:54 +00:00
|
|
|
|
2017-09-18 17:03:52 +00:00
|
|
|
// HTTPSRedirect is a connection handler which redirects all HTTP requests to HTTPS
|
2017-09-10 16:57:22 +00:00
|
|
|
type HTTPSRedirect struct {
|
2017-08-13 11:22:34 +00:00
|
|
|
}
|
|
|
|
|
2017-09-10 16:57:22 +00:00
|
|
|
func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
2017-12-22 03:32:23 +00:00
|
|
|
w.Header().Set("Connection", "close")
|
2017-08-13 11:22:34 +00:00
|
|
|
dest := "https://" + req.Host + req.URL.Path
|
|
|
|
if len(req.URL.RawQuery) > 0 {
|
|
|
|
dest += "?" + req.URL.RawQuery
|
|
|
|
}
|
2017-09-03 04:50:31 +00:00
|
|
|
http.Redirect(w, req, dest, http.StatusTemporaryRedirect)
|
2017-08-13 11:22:34 +00:00
|
|
|
}
|
|
|
|
|
2017-12-24 22:08:35 +00:00
|
|
|
// Temporary stubs for view tracking
|
|
|
|
func routeDynamic() {
|
|
|
|
}
|
|
|
|
func routeUploads() {
|
|
|
|
}
|
2018-01-26 05:53:34 +00:00
|
|
|
func BadRoute() {
|
|
|
|
}
|
2017-12-24 22:08:35 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
|
|
|
headerVars, ferr := common.UserCheck(w, r, &user)
|
2017-10-30 09:57:08 +00:00
|
|
|
if ferr != nil {
|
|
|
|
return ferr
|
2016-12-16 10:37:42 +00:00
|
|
|
}
|
2017-11-29 02:34:02 +00:00
|
|
|
headerVars.Zone = "topics"
|
2017-12-25 06:12:19 +00:00
|
|
|
headerVars.MetaDesc = headerVars.Settings["meta_desc"].(string)
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-23 05:37:08 +00:00
|
|
|
group, err := common.Groups.Get(user.Group)
|
2017-09-15 22:20:01 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID)
|
|
|
|
return common.LocalError("Something weird happened", w, r, user)
|
2017-09-15 22:20:01 +00:00
|
|
|
}
|
|
|
|
|
2017-12-24 07:38:46 +00:00
|
|
|
// TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins?
|
2017-09-24 00:49:41 +00:00
|
|
|
var canSee []int
|
|
|
|
if user.IsSuperAdmin {
|
2017-11-23 05:37:08 +00:00
|
|
|
canSee, err = common.Forums.GetAllVisibleIDs()
|
2017-09-24 00:49:41 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-09-24 00:49:41 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
canSee = group.CanSee
|
|
|
|
}
|
|
|
|
|
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
|
|
|
// We need a list of the visible forums for Quick Topic
|
2017-12-30 06:45:29 +00:00
|
|
|
// ? - Would it be useful, if we could post in social groups from /topics/?
|
2017-11-11 04:06:16 +00:00
|
|
|
var forumList []common.Forum
|
2017-09-24 00:49:41 +00:00
|
|
|
for _, fid := range canSee {
|
2017-11-23 05:37:08 +00:00
|
|
|
forum := common.Forums.DirtyGet(fid)
|
2017-12-30 06:45:29 +00:00
|
|
|
if forum.Name != "" && forum.Active && (forum.ParentType == "" || forum.ParentType == "forum") {
|
|
|
|
fcopy := forum.Copy()
|
|
|
|
// TODO: Add a hook here for plugin_guilds
|
|
|
|
forumList = append(forumList, fcopy)
|
2017-02-05 14:41:53 +00:00
|
|
|
}
|
|
|
|
}
|
2017-09-24 00:49:41 +00:00
|
|
|
|
2017-12-26 07:17:26 +00:00
|
|
|
// ? - Should we be showing plugin_guilds posts on /topics/?
|
|
|
|
argList, qlist := common.ForumListToArgQ(forumList)
|
|
|
|
|
2017-09-24 00:49:41 +00:00
|
|
|
// ! Need an inline error not a page level error
|
|
|
|
if qlist == "" {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.NotFound(w, r)
|
2017-09-24 00:49:41 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-12-26 07:17:26 +00:00
|
|
|
topicCount, err := common.ArgQToTopicCount(argList, qlist)
|
2017-10-30 09:57:08 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-10-30 09:57:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the current page
|
|
|
|
page, _ := strconv.Atoi(r.FormValue("page"))
|
2018-01-20 06:50:29 +00:00
|
|
|
offset, page, lastPage := common.PageOffset(topicCount, page, common.Config.ItemsPerPage)
|
2017-10-30 09:57:08 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
var topicList []*common.TopicsRow
|
2017-10-30 09:57:08 +00:00
|
|
|
stmt, err := qgen.Builder.SimpleSelect("topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount", "parentID IN("+qlist+")", "sticky DESC, lastReplyAt DESC, createdBy DESC", "?,?")
|
2017-06-19 08:06:54 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-06-19 08:06:54 +00:00
|
|
|
}
|
2017-11-11 23:34:27 +00:00
|
|
|
defer stmt.Close()
|
2017-06-19 08:06:54 +00:00
|
|
|
|
2017-10-30 09:57:08 +00:00
|
|
|
argList = append(argList, offset)
|
2017-11-11 04:06:16 +00:00
|
|
|
argList = append(argList, common.Config.ItemsPerPage)
|
2017-10-30 09:57:08 +00:00
|
|
|
|
|
|
|
rows, err := stmt.Query(argList...)
|
2016-12-02 07:38:54 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2016-12-02 07:38:54 +00:00
|
|
|
}
|
2017-08-06 15:22:18 +00:00
|
|
|
defer rows.Close()
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-09-03 04:50:31 +00:00
|
|
|
var reqUserList = make(map[int]bool)
|
2016-12-02 07:38:54 +00:00
|
|
|
for rows.Next() {
|
2017-11-11 04:06:16 +00:00
|
|
|
topicItem := common.TopicsRow{ID: 0}
|
2017-09-03 04:50:31 +00:00
|
|
|
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount)
|
2016-12-02 07:38:54 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2016-12-02 07:38:54 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-23 05:37:08 +00:00
|
|
|
forum := common.Forums.DirtyGet(topicItem.ParentID)
|
2017-10-30 09:57:08 +00:00
|
|
|
topicItem.ForumName = forum.Name
|
|
|
|
topicItem.ForumLink = forum.Link
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
//topicItem.CreatedAt = common.RelativeTime(topicItem.CreatedAt)
|
|
|
|
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
if common.Vhooks["topics_topic_row_assign"] != nil {
|
|
|
|
common.RunVhook("topics_topic_row_assign", &topicItem, &forum)
|
2016-12-11 16:06:17 +00:00
|
|
|
}
|
2017-08-06 15:22:18 +00:00
|
|
|
topicList = append(topicList, &topicItem)
|
|
|
|
reqUserList[topicItem.CreatedBy] = true
|
|
|
|
reqUserList[topicItem.LastReplyBy] = true
|
2016-12-02 07:38:54 +00:00
|
|
|
}
|
|
|
|
err = rows.Err()
|
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2016-12-02 07:38:54 +00:00
|
|
|
}
|
2017-08-06 15:22:18 +00:00
|
|
|
|
|
|
|
// Convert the user ID map to a slice, then bulk load the users
|
2017-09-03 04:50:31 +00:00
|
|
|
var idSlice = make([]int, len(reqUserList))
|
2017-08-06 15:22:18 +00:00
|
|
|
var i int
|
2017-09-03 04:50:31 +00:00
|
|
|
for userID := range reqUserList {
|
2017-08-06 15:22:18 +00:00
|
|
|
idSlice[i] = userID
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
2017-09-10 16:57:22 +00:00
|
|
|
// TODO: What if a user is deleted via the Control Panel?
|
2017-11-11 04:06:16 +00:00
|
|
|
userList, err := common.Users.BulkGetMap(idSlice)
|
2017-08-06 15:22:18 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-08-06 15:22:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Second pass to the add the user data
|
2017-09-10 16:57:22 +00:00
|
|
|
// TODO: Use a pointer to TopicsRow instead of TopicsRow itself?
|
2017-08-06 15:22:18 +00:00
|
|
|
for _, topicItem := range topicList {
|
|
|
|
topicItem.Creator = userList[topicItem.CreatedBy]
|
|
|
|
topicItem.LastUser = userList[topicItem.LastReplyBy]
|
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2018-01-20 06:50:29 +00:00
|
|
|
pageList := common.Paginate(topicCount, common.Config.ItemsPerPage, 5)
|
|
|
|
pi := common.TopicsPage{common.GetTitlePhrase("topics"), user, headerVars, topicList, forumList, common.Config.DefaultForum, pageList, page, lastPage}
|
2017-11-11 04:06:16 +00:00
|
|
|
if common.PreRenderHooks["pre_render_topic_list"] != nil {
|
|
|
|
if common.RunPreRenderHook("pre_render_topic_list", w, r, &user, &pi) {
|
2017-10-30 09:57:08 +00:00
|
|
|
return nil
|
2017-07-12 11:05:18 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-23 05:37:08 +00:00
|
|
|
err = common.RunThemeTemplate(headerVars.Theme.Name, "topics", pi, w)
|
2017-10-30 09:57:08 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-10-30 09:57:08 +00:00
|
|
|
}
|
|
|
|
return nil
|
2016-12-02 07:38:54 +00:00
|
|
|
}
|
2016-12-03 13:45:08 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
|
2017-01-26 13:37:50 +00:00
|
|
|
page, _ := strconv.Atoi(r.FormValue("page"))
|
2017-06-25 09:56:39 +00:00
|
|
|
|
|
|
|
// SEO URLs...
|
2017-09-03 04:50:31 +00:00
|
|
|
halves := strings.Split(sfid, ".")
|
2017-06-25 09:56:39 +00:00
|
|
|
if len(halves) < 2 {
|
2017-09-03 04:50:31 +00:00
|
|
|
halves = append(halves, halves[0])
|
2017-06-25 09:56:39 +00:00
|
|
|
}
|
|
|
|
fid, err := strconv.Atoi(halves[1])
|
2016-12-03 13:45:08 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.PreError("The provided ForumID is not a valid number.", w, r)
|
2016-12-03 13:45:08 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
headerVars, ferr := common.ForumUserCheck(w, r, &user, fid)
|
2017-10-30 09:57:08 +00:00
|
|
|
if ferr != nil {
|
|
|
|
return ferr
|
2016-12-03 13:45:08 +00:00
|
|
|
}
|
2017-02-05 16:36:54 +00:00
|
|
|
if !user.Perms.ViewTopic {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.NoPermissions(w, r, user)
|
2016-12-21 02:30:32 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-09-10 16:57:22 +00:00
|
|
|
// TODO: Fix this double-check
|
2017-11-23 05:37:08 +00:00
|
|
|
forum, err := common.Forums.Get(fid)
|
2017-06-28 12:05:26 +00:00
|
|
|
if err == ErrNoRows {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.NotFound(w, r)
|
2017-06-28 12:05:26 +00:00
|
|
|
} else if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-06-28 12:05:26 +00:00
|
|
|
}
|
2017-11-29 02:34:02 +00:00
|
|
|
headerVars.Zone = "view_forum"
|
2017-06-28 12:05:26 +00:00
|
|
|
|
2018-01-21 11:17:43 +00:00
|
|
|
// TODO: Does forum.TopicCount take the deleted items into consideration for guests? We don't have soft-delete yet, only hard-delete
|
|
|
|
offset, page, lastPage := common.PageOffset(forum.TopicCount, page, common.Config.ItemsPerPage)
|
2017-10-21 00:27:47 +00:00
|
|
|
|
|
|
|
// TODO: Move this to *Forum
|
2017-11-11 04:06:16 +00:00
|
|
|
rows, err := stmts.getForumTopicsOffset.Query(fid, offset, common.Config.ItemsPerPage)
|
2016-12-03 13:45:08 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2016-12-03 13:45:08 +00:00
|
|
|
}
|
2017-08-06 15:22:18 +00:00
|
|
|
defer rows.Close()
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-09-10 16:57:22 +00:00
|
|
|
// TODO: Use something other than TopicsRow as we don't need to store the forum name and link on each and every topic item?
|
2017-11-11 04:06:16 +00:00
|
|
|
var topicList []*common.TopicsRow
|
2017-09-03 04:50:31 +00:00
|
|
|
var reqUserList = make(map[int]bool)
|
2016-12-03 13:45:08 +00:00
|
|
|
for rows.Next() {
|
2017-11-11 04:06:16 +00:00
|
|
|
var topicItem = common.TopicsRow{ID: 0}
|
2017-09-03 04:50:31 +00:00
|
|
|
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount)
|
2016-12-03 13:45:08 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2016-12-03 13:45:08 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
|
|
|
|
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
if common.Vhooks["forum_trow_assign"] != nil {
|
|
|
|
common.RunVhook("forum_trow_assign", &topicItem, &forum)
|
2016-12-11 16:06:17 +00:00
|
|
|
}
|
2017-08-06 15:22:18 +00:00
|
|
|
topicList = append(topicList, &topicItem)
|
|
|
|
reqUserList[topicItem.CreatedBy] = true
|
|
|
|
reqUserList[topicItem.LastReplyBy] = true
|
2016-12-03 13:45:08 +00:00
|
|
|
}
|
|
|
|
err = rows.Err()
|
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2016-12-03 13:45:08 +00:00
|
|
|
}
|
2017-08-06 15:22:18 +00:00
|
|
|
|
|
|
|
// Convert the user ID map to a slice, then bulk load the users
|
2017-09-03 04:50:31 +00:00
|
|
|
var idSlice = make([]int, len(reqUserList))
|
2017-08-06 15:22:18 +00:00
|
|
|
var i int
|
2017-09-03 04:50:31 +00:00
|
|
|
for userID := range reqUserList {
|
2017-08-06 15:22:18 +00:00
|
|
|
idSlice[i] = userID
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
2017-09-10 16:57:22 +00:00
|
|
|
// TODO: What if a user is deleted via the Control Panel?
|
2017-11-11 04:06:16 +00:00
|
|
|
userList, err := common.Users.BulkGetMap(idSlice)
|
2017-08-06 15:22:18 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-08-06 15:22:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Second pass to the add the user data
|
2017-09-10 16:57:22 +00:00
|
|
|
// TODO: Use a pointer to TopicsRow instead of TopicsRow itself?
|
2017-08-06 15:22:18 +00:00
|
|
|
for _, topicItem := range topicList {
|
|
|
|
topicItem.Creator = userList[topicItem.CreatedBy]
|
|
|
|
topicItem.LastUser = userList[topicItem.LastReplyBy]
|
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
pi := common.ForumPage{forum.Name, user, headerVars, topicList, forum, page, lastPage}
|
2018-01-21 11:17:43 +00:00
|
|
|
if common.PreRenderHooks["pre_render_forum"] != nil {
|
|
|
|
if common.RunPreRenderHook("pre_render_forum", w, r, &user, &pi) {
|
2017-10-30 09:57:08 +00:00
|
|
|
return nil
|
2017-07-12 11:05:18 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-23 05:37:08 +00:00
|
|
|
err = common.RunThemeTemplate(headerVars.Theme.Name, "forum", pi, w)
|
2017-10-30 09:57:08 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-10-30 09:57:08 +00:00
|
|
|
}
|
|
|
|
return nil
|
2016-12-03 13:45:08 +00:00
|
|
|
}
|
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
func routeForums(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
|
|
|
headerVars, ferr := common.UserCheck(w, r, &user)
|
2017-10-30 09:57:08 +00:00
|
|
|
if ferr != nil {
|
|
|
|
return ferr
|
2016-12-16 10:37:42 +00:00
|
|
|
}
|
2017-11-29 02:34:02 +00:00
|
|
|
headerVars.Zone = "forums"
|
2017-12-27 23:38:37 +00:00
|
|
|
headerVars.MetaDesc = headerVars.Settings["meta_desc"].(string)
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-02-06 04:52:19 +00:00
|
|
|
var err error
|
2017-11-11 04:06:16 +00:00
|
|
|
var forumList []common.Forum
|
2017-07-12 11:05:18 +00:00
|
|
|
var canSee []int
|
2017-09-03 04:50:31 +00:00
|
|
|
if user.IsSuperAdmin {
|
2017-11-23 05:37:08 +00:00
|
|
|
canSee, err = common.Forums.GetAllVisibleIDs()
|
2017-07-12 11:05:18 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-07-12 11:05:18 +00:00
|
|
|
}
|
|
|
|
} else {
|
2017-11-23 05:37:08 +00:00
|
|
|
group, err := common.Groups.Get(user.Group)
|
2017-09-15 22:20:01 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID)
|
|
|
|
return common.LocalError("Something weird happened", w, r, user)
|
2017-09-15 22:20:01 +00:00
|
|
|
}
|
2017-07-12 11:05:18 +00:00
|
|
|
canSee = group.CanSee
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, fid := range canSee {
|
2017-09-28 22:16:34 +00:00
|
|
|
// Avoid data races by copying the struct into something we can freely mold without worrying about breaking something somewhere else
|
2017-11-23 05:37:08 +00:00
|
|
|
var forum = common.Forums.DirtyGet(fid).Copy()
|
2017-09-24 00:49:41 +00:00
|
|
|
if forum.ParentID == 0 && forum.Name != "" && forum.Active {
|
2017-02-06 04:52:19 +00:00
|
|
|
if forum.LastTopicID != 0 {
|
2017-09-28 22:16:34 +00:00
|
|
|
if forum.LastTopic.ID != 0 && forum.LastReplyer.ID != 0 {
|
2017-11-11 04:06:16 +00:00
|
|
|
forum.LastTopicTime = common.RelativeTime(forum.LastTopic.LastReplyAt)
|
2017-09-28 22:16:34 +00:00
|
|
|
} else {
|
|
|
|
forum.LastTopicTime = ""
|
2017-02-06 04:52:19 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
forum.LastTopicTime = ""
|
|
|
|
}
|
2017-11-11 04:06:16 +00:00
|
|
|
if common.Hooks["forums_frow_assign"] != nil {
|
|
|
|
common.RunHook("forums_frow_assign", &forum)
|
2017-07-12 11:05:18 +00:00
|
|
|
}
|
2017-02-06 04:52:19 +00:00
|
|
|
forumList = append(forumList, forum)
|
2016-12-03 13:45:08 +00:00
|
|
|
}
|
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-23 05:37:08 +00:00
|
|
|
pi := common.ForumsPage{common.GetTitlePhrase("forums"), user, headerVars, forumList}
|
2017-11-11 04:06:16 +00:00
|
|
|
if common.PreRenderHooks["pre_render_forum_list"] != nil {
|
|
|
|
if common.RunPreRenderHook("pre_render_forum_list", w, r, &user, &pi) {
|
2017-10-30 09:57:08 +00:00
|
|
|
return nil
|
2017-07-12 11:05:18 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-23 05:37:08 +00:00
|
|
|
err = common.RunThemeTemplate(headerVars.Theme.Name, "forums", pi, w)
|
2017-10-30 09:57:08 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-10-30 09:57:08 +00:00
|
|
|
}
|
|
|
|
return nil
|
2016-12-03 13:45:08 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
func routeProfile(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
|
|
|
headerVars, ferr := common.UserCheck(w, r, &user)
|
2017-10-30 09:57:08 +00:00
|
|
|
if ferr != nil {
|
|
|
|
return ferr
|
2016-12-16 10:37:42 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-04-06 17:37:32 +00:00
|
|
|
var err error
|
2017-11-02 02:52:21 +00:00
|
|
|
var replyCreatedAt time.Time
|
|
|
|
var replyContent, replyCreatedByName, replyRelativeCreatedAt, replyAvatar, replyTag, replyClassName string
|
2017-04-06 17:37:32 +00:00
|
|
|
var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyLines, replyGroup int
|
2017-11-11 04:06:16 +00:00
|
|
|
var replyList []common.ReplyUser
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-06-28 12:05:26 +00:00
|
|
|
// SEO URLs...
|
2017-12-30 05:47:46 +00:00
|
|
|
// TODO: Do a 301 if it's the wrong username? Do a canonical too?
|
2017-09-03 04:50:31 +00:00
|
|
|
halves := strings.Split(r.URL.Path[len("/user/"):], ".")
|
2017-06-28 12:05:26 +00:00
|
|
|
if len(halves) < 2 {
|
2017-09-03 04:50:31 +00:00
|
|
|
halves = append(halves, halves[0])
|
2017-06-28 12:05:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pid, err := strconv.Atoi(halves[1])
|
2016-12-07 09:34:09 +00:00
|
|
|
if err != nil {
|
2017-12-25 02:41:10 +00:00
|
|
|
return common.LocalError("The provided UserID is not a valid number.", w, r, user)
|
2016-12-07 09:34:09 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
var puser *common.User
|
2017-02-15 10:49:30 +00:00
|
|
|
if pid == user.ID {
|
2017-09-03 04:50:31 +00:00
|
|
|
user.IsMod = true
|
2017-02-15 10:49:30 +00:00
|
|
|
puser = &user
|
2016-12-07 13:46:14 +00:00
|
|
|
} else {
|
|
|
|
// Fetch the user data
|
2017-11-23 05:37:08 +00:00
|
|
|
// TODO: Add a shared function for checking for ErrNoRows and internal erroring if it's not that case?
|
2017-11-11 04:06:16 +00:00
|
|
|
puser, err = common.Users.Get(pid)
|
2017-06-28 12:05:26 +00:00
|
|
|
if err == ErrNoRows {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.NotFound(w, r)
|
2016-12-07 13:46:14 +00:00
|
|
|
} else if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2016-12-07 13:46:14 +00:00
|
|
|
}
|
2016-12-07 09:34:09 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2016-12-07 09:34:09 +00:00
|
|
|
// Get the replies..
|
2017-11-05 09:55:34 +00:00
|
|
|
rows, err := stmts.getProfileReplies.Query(puser.ID)
|
2016-12-07 09:34:09 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2016-12-07 09:34:09 +00:00
|
|
|
}
|
|
|
|
defer rows.Close()
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2016-12-07 09:34:09 +00:00
|
|
|
for rows.Next() {
|
2017-02-11 14:51:16 +00:00
|
|
|
err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName, &replyGroup)
|
2016-12-07 09:34:09 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2016-12-07 09:34:09 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-23 05:37:08 +00:00
|
|
|
group, err := common.Groups.Get(replyGroup)
|
2017-09-15 22:20:01 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-09-15 22:20:01 +00:00
|
|
|
}
|
|
|
|
|
2017-09-03 04:50:31 +00:00
|
|
|
replyLines = strings.Count(replyContent, "\n")
|
2017-09-15 22:20:01 +00:00
|
|
|
if group.IsMod || group.IsAdmin {
|
2017-11-11 04:06:16 +00:00
|
|
|
replyClassName = common.Config.StaffCSS
|
2016-12-07 09:34:09 +00:00
|
|
|
} else {
|
2017-07-17 10:23:42 +00:00
|
|
|
replyClassName = ""
|
2016-12-07 09:34:09 +00:00
|
|
|
}
|
2018-02-03 05:47:14 +00:00
|
|
|
replyAvatar = common.BuildAvatar(replyCreatedBy, replyAvatar)
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-09-15 22:20:01 +00:00
|
|
|
if group.Tag != "" {
|
|
|
|
replyTag = group.Tag
|
2016-12-07 09:34:09 +00:00
|
|
|
} else if puser.ID == replyCreatedBy {
|
|
|
|
replyTag = "Profile Owner"
|
|
|
|
} else {
|
|
|
|
replyTag = ""
|
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-02-10 13:39:13 +00:00
|
|
|
replyLiked := false
|
|
|
|
replyLikeCount := 0
|
2017-11-11 04:06:16 +00:00
|
|
|
replyRelativeCreatedAt = common.RelativeTime(replyCreatedAt)
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-09-10 16:57:22 +00:00
|
|
|
// TODO: Add a hook here
|
2017-07-12 11:05:18 +00:00
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
replyList = append(replyList, common.ReplyUser{rid, puser.ID, replyContent, common.ParseMessage(replyContent, 0, ""), replyCreatedBy, common.BuildProfileURL(common.NameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyRelativeCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
|
2016-12-07 09:34:09 +00:00
|
|
|
}
|
|
|
|
err = rows.Err()
|
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2016-12-07 09:34:09 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-11-23 05:37:08 +00:00
|
|
|
// TODO: Add a phrase for this title
|
2017-11-11 04:06:16 +00:00
|
|
|
ppage := common.ProfilePage{puser.Name + "'s Profile", user, headerVars, replyList, *puser}
|
|
|
|
if common.PreRenderHooks["pre_render_profile"] != nil {
|
|
|
|
if common.RunPreRenderHook("pre_render_profile", w, r, &user, &ppage) {
|
2017-10-30 09:57:08 +00:00
|
|
|
return nil
|
2017-07-12 11:05:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-23 05:37:08 +00:00
|
|
|
err = common.RunThemeTemplate(headerVars.Theme.Name, "profile", ppage, w)
|
2017-10-30 09:57:08 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-10-30 09:57:08 +00:00
|
|
|
}
|
|
|
|
return nil
|
2016-12-07 09:34:09 +00:00
|
|
|
}
|
|
|
|
|
2017-09-10 16:57:22 +00:00
|
|
|
// TODO: Set the cookie domain
|
2017-11-11 04:06:16 +00:00
|
|
|
func routeChangeTheme(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
2017-09-10 17:39:16 +00:00
|
|
|
//headerLite, _ := SimpleUserCheck(w, r, &user)
|
2017-09-10 16:57:22 +00:00
|
|
|
// TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly?
|
|
|
|
isJs := (r.PostFormValue("isJs") == "1")
|
|
|
|
newTheme := html.EscapeString(r.PostFormValue("newTheme"))
|
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
theme, ok := common.Themes[newTheme]
|
2017-09-10 16:57:22 +00:00
|
|
|
if !ok || theme.HideFromThemes {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.LocalErrorJSQ("That theme doesn't exist", w, r, user, isJs)
|
2017-09-10 16:57:22 +00:00
|
|
|
}
|
|
|
|
|
2017-11-11 04:06:16 +00:00
|
|
|
cookie := http.Cookie{Name: "current_theme", Value: newTheme, Path: "/", MaxAge: common.Year}
|
2017-09-10 16:57:22 +00:00
|
|
|
http.SetCookie(w, &cookie)
|
|
|
|
|
|
|
|
if !isJs {
|
|
|
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
|
|
} else {
|
|
|
|
_, _ = w.Write(successJSONBytes)
|
|
|
|
}
|
2017-10-30 09:57:08 +00:00
|
|
|
return nil
|
2017-09-10 16:57:22 +00:00
|
|
|
}
|
|
|
|
|
2017-11-23 05:37:08 +00:00
|
|
|
// TODO: Refactor this
|
2017-09-10 16:57:22 +00:00
|
|
|
var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`)
|
2017-09-03 04:50:31 +00:00
|
|
|
|
2017-11-23 05:37:08 +00:00
|
|
|
// TODO: Refactor this endpoint
|
2017-11-11 04:06:16 +00:00
|
|
|
func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
2017-11-23 05:37:08 +00:00
|
|
|
// TODO: Don't make this too JSON dependent so that we can swap in newer more efficient formats
|
2017-09-03 04:50:31 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2017-02-28 09:27:28 +00:00
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.PreErrorJS("Bad Form", w, r)
|
2017-02-28 09:27:28 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-02-28 09:27:28 +00:00
|
|
|
action := r.FormValue("action")
|
|
|
|
if action != "get" && action != "set" {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.PreErrorJS("Invalid Action", w, r)
|
2017-02-28 09:27:28 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-02-28 09:27:28 +00:00
|
|
|
module := r.FormValue("module")
|
2017-09-03 04:50:31 +00:00
|
|
|
switch module {
|
|
|
|
case "dismiss-alert":
|
|
|
|
asid, err := strconv.Atoi(r.FormValue("asid"))
|
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.PreErrorJS("Invalid asid", w, r)
|
2017-09-03 04:50:31 +00:00
|
|
|
}
|
2017-11-05 09:55:34 +00:00
|
|
|
_, err = stmts.deleteActivityStreamMatch.Exec(user.ID, asid)
|
2017-09-03 04:50:31 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalError(err, w, r)
|
2017-09-03 04:50:31 +00:00
|
|
|
}
|
|
|
|
case "alerts": // A feed of events tailored for a specific user
|
|
|
|
if !user.Loggedin {
|
2017-09-10 16:57:22 +00:00
|
|
|
w.Write(phraseLoginAlerts)
|
2017-10-30 09:57:08 +00:00
|
|
|
return nil
|
2017-09-03 04:50:31 +00:00
|
|
|
}
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-09-03 04:50:31 +00:00
|
|
|
var msglist, event, elementType string
|
|
|
|
var asid, actorID, targetUserID, elementID int
|
|
|
|
var msgCount int
|
2017-06-10 07:58:15 +00:00
|
|
|
|
2017-11-05 09:55:34 +00:00
|
|
|
err = stmts.getActivityCountByWatcher.QueryRow(user.ID).Scan(&msgCount)
|
2017-09-03 04:50:31 +00:00
|
|
|
if err == ErrNoRows {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.PreErrorJS("Couldn't find the parent topic", w, r)
|
2017-09-03 04:50:31 +00:00
|
|
|
} else if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalErrorJS(err, w, r)
|
2017-09-03 04:50:31 +00:00
|
|
|
}
|
|
|
|
|
2017-11-05 09:55:34 +00:00
|
|
|
rows, err := stmts.getActivityFeedByWatcher.Query(user.ID)
|
2017-09-03 04:50:31 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalErrorJS(err, w, r)
|
2017-09-03 04:50:31 +00:00
|
|
|
}
|
|
|
|
defer rows.Close()
|
2017-05-29 14:52:37 +00:00
|
|
|
|
2017-09-03 04:50:31 +00:00
|
|
|
for rows.Next() {
|
|
|
|
err = rows.Scan(&asid, &actorID, &targetUserID, &event, &elementType, &elementID)
|
2017-02-28 09:27:28 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalErrorJS(err, w, r)
|
2017-02-28 09:27:28 +00:00
|
|
|
}
|
2017-09-03 04:50:31 +00:00
|
|
|
res, err := buildAlert(asid, event, elementType, actorID, targetUserID, elementID, user)
|
2017-02-28 09:27:28 +00:00
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.LocalErrorJS(err.Error(), w, r)
|
2017-02-28 09:27:28 +00:00
|
|
|
}
|
2017-09-03 04:50:31 +00:00
|
|
|
msglist += res + ","
|
|
|
|
}
|
|
|
|
err = rows.Err()
|
|
|
|
if err != nil {
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.InternalErrorJS(err, w, r)
|
2017-09-03 04:50:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(msglist) != 0 {
|
|
|
|
msglist = msglist[0 : len(msglist)-1]
|
|
|
|
}
|
|
|
|
_, _ = w.Write([]byte(`{"msgs":[` + msglist + `],"msgCount":` + strconv.Itoa(msgCount) + `}`))
|
|
|
|
default:
|
2017-11-11 04:06:16 +00:00
|
|
|
return common.PreErrorJS("Invalid Module", w, r)
|
2017-02-28 09:27:28 +00:00
|
|
|
}
|
2017-10-30 09:57:08 +00:00
|
|
|
return nil
|
2017-02-28 09:27:28 +00:00
|
|
|
}
|