2018-01-20 06:50:29 +00:00
package routes
import (
"database/sql"
2018-12-28 02:08:35 +00:00
"encoding/json"
2018-12-31 09:03:49 +00:00
"errors"
2018-01-20 06:50:29 +00:00
"net/http"
"strconv"
2018-03-08 03:59:47 +00:00
"strings"
2018-01-20 06:50:29 +00:00
2019-04-19 06:36:26 +00:00
c "github.com/Azareal/Gosora/common"
2018-10-27 03:21:02 +00:00
"github.com/Azareal/Gosora/common/counters"
2019-10-01 21:06:22 +00:00
p "github.com/Azareal/Gosora/common/phrases"
qgen "github.com/Azareal/Gosora/query_gen"
2018-01-20 06:50:29 +00:00
)
2018-12-27 09:12:30 +00:00
type ReplyStmts struct {
2018-12-28 07:12:14 +00:00
updateAttachs * sql . Stmt
createReplyPaging * sql . Stmt
2018-12-27 09:12:30 +00:00
}
var replyStmts ReplyStmts
// TODO: Move this statement somewhere else
func init ( ) {
2019-04-19 06:36:26 +00:00
c . DbInits . Add ( func ( acc * qgen . Accumulator ) error {
2018-12-27 09:12:30 +00:00
replyStmts = ReplyStmts {
// TODO: Less race-y attachment count updates
2018-12-28 07:12:14 +00:00
updateAttachs : acc . Update ( "replies" ) . Set ( "attachCount = ?" ) . Where ( "rid = ?" ) . Prepare ( ) ,
createReplyPaging : acc . Select ( "replies" ) . Cols ( "rid" ) . Where ( "rid >= ? - 1 AND tid = ?" ) . Orderby ( "rid ASC" ) . Prepare ( ) ,
2018-12-27 09:12:30 +00:00
}
return acc . FirstError ( )
} )
}
2018-12-28 02:08:35 +00:00
type JsonReply struct {
Content string
}
2019-04-19 06:36:26 +00:00
func CreateReplySubmit ( w http . ResponseWriter , r * http . Request , user c . User ) c . RouteError {
2018-12-28 02:08:35 +00:00
// TODO: Use this
js := r . FormValue ( "js" ) == "1"
2018-03-08 03:59:47 +00:00
tid , err := strconv . Atoi ( r . PostFormValue ( "tid" ) )
if err != nil {
2019-04-19 06:36:26 +00:00
return c . PreErrorJSQ ( "Failed to convert the Topic ID" , w , r , js )
2018-03-08 03:59:47 +00:00
}
2019-04-19 06:36:26 +00:00
topic , err := c . Topics . Get ( tid )
2018-03-08 03:59:47 +00:00
if err == sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . PreErrorJSQ ( "Couldn't find the parent topic" , w , r , js )
2018-03-08 03:59:47 +00:00
} else if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-03-08 03:59:47 +00:00
}
// TODO: Add hooks to make use of headerLite
2019-04-19 06:36:26 +00:00
lite , ferr := c . SimpleForumUserCheck ( w , r , & user , topic . ParentID )
2018-03-08 03:59:47 +00:00
if ferr != nil {
return ferr
}
if ! user . Perms . ViewTopic || ! user . Perms . CreateReply {
2019-04-19 06:36:26 +00:00
return c . NoPermissionsJSQ ( w , r , user , js )
2018-03-08 03:59:47 +00:00
}
2018-06-01 05:02:29 +00:00
if topic . IsClosed && ! user . Perms . CloseTopic {
2019-04-19 06:36:26 +00:00
return c . NoPermissionsJSQ ( w , r , user , js )
2018-06-01 05:02:29 +00:00
}
2018-03-08 03:59:47 +00:00
2019-07-28 10:42:30 +00:00
content := c . PreparseMessage ( r . PostFormValue ( "content" ) )
2018-03-08 03:59:47 +00:00
// TODO: Fully parse the post and put that in the parsed column
2019-04-19 06:36:26 +00:00
rid , err := c . Rstore . Create ( topic , content , user . LastIP , user . ID )
2018-03-08 03:59:47 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-03-08 03:59:47 +00:00
}
2019-04-19 06:36:26 +00:00
reply , err := c . Rstore . Get ( rid )
2018-03-08 03:59:47 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . LocalErrorJSQ ( "Unable to load the reply" , w , r , user , js )
2018-03-08 03:59:47 +00:00
}
2018-12-27 09:12:30 +00:00
// Handle the file attachments
// TODO: Stop duplicating this code
if user . Perms . UploadFiles {
2019-04-13 11:54:22 +00:00
_ , rerr := uploadAttachment ( w , r , user , topic . ParentID , "forums" , rid , "replies" , strconv . Itoa ( topic . ID ) )
2018-12-27 09:12:30 +00:00
if rerr != nil {
return rerr
}
}
2018-03-08 03:59:47 +00:00
if r . PostFormValue ( "has_poll" ) == "1" {
2019-07-28 10:42:30 +00:00
maxPollOptions := 10
pollInputItems := make ( map [ int ] string )
2018-03-08 03:59:47 +00:00
for key , values := range r . Form {
2019-04-19 06:36:26 +00:00
//c.DebugDetail("key: ", key)
//c.DebugDetailf("values: %+v\n", values)
2018-03-08 03:59:47 +00:00
for _ , value := range values {
if strings . HasPrefix ( key , "pollinputitem[" ) {
halves := strings . Split ( key , "[" )
if len ( halves ) != 2 {
2019-04-19 06:36:26 +00:00
return c . LocalErrorJSQ ( "Malformed pollinputitem" , w , r , user , js )
2018-03-08 03:59:47 +00:00
}
halves [ 1 ] = strings . TrimSuffix ( halves [ 1 ] , "]" )
index , err := strconv . Atoi ( halves [ 1 ] )
if err != nil {
2019-04-19 06:36:26 +00:00
return c . LocalErrorJSQ ( "Malformed pollinputitem" , w , r , user , js )
2018-03-08 03:59:47 +00:00
}
// If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack
_ , exists := pollInputItems [ index ]
2018-05-31 06:51:31 +00:00
// TODO: Should we use SanitiseBody instead to keep the newlines?
2019-04-19 06:36:26 +00:00
if ! exists && len ( c . SanitiseSingleLine ( value ) ) != 0 {
pollInputItems [ index ] = c . SanitiseSingleLine ( value )
2018-03-08 03:59:47 +00:00
if len ( pollInputItems ) >= maxPollOptions {
break
}
}
}
}
}
// Make sure the indices are sequential to avoid out of bounds issues
2019-09-30 10:15:50 +00:00
seqPollInputItems := make ( map [ int ] string )
2018-03-08 03:59:47 +00:00
for i := 0 ; i < len ( pollInputItems ) ; i ++ {
seqPollInputItems [ i ] = pollInputItems [ i ]
}
pollType := 0 // Basic single choice
2019-04-19 06:36:26 +00:00
_ , err := c . Polls . Create ( reply , pollType , seqPollInputItems )
2018-03-08 03:59:47 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . LocalErrorJSQ ( "Failed to add poll to reply" , w , r , user , js ) // TODO: Might need to be an internal error as it could leave phantom polls?
2018-03-08 03:59:47 +00:00
}
}
2019-04-19 06:36:26 +00:00
err = c . Forums . UpdateLastTopic ( tid , user . ID , topic . ParentID )
2018-03-08 03:59:47 +00:00
if err != nil && err != sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-03-08 03:59:47 +00:00
}
2019-04-19 06:36:26 +00:00
c . AddActivityAndNotifyAll ( user . ID , topic . CreatedBy , "reply" , "topic" , tid )
2018-03-08 03:59:47 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-03-08 03:59:47 +00:00
}
2019-04-19 06:36:26 +00:00
wcount := c . WordCount ( content )
2018-03-08 03:59:47 +00:00
err = user . IncreasePostStats ( wcount , false )
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-12-28 02:08:35 +00:00
}
2019-04-19 06:36:26 +00:00
nTopic , err := c . Topics . Get ( tid )
2018-12-28 07:12:14 +00:00
if err == sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . PreErrorJSQ ( "Couldn't find the parent topic" , w , r , js )
2018-12-28 07:12:14 +00:00
} else if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-12-28 07:12:14 +00:00
}
2019-04-19 06:36:26 +00:00
page := c . LastPage ( nTopic . PostCount , c . Config . ItemsPerPage )
2018-12-28 07:12:14 +00:00
rows , err := replyStmts . createReplyPaging . Query ( reply . ID , topic . ID )
if err != nil && err != sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-12-28 07:12:14 +00:00
}
defer rows . Close ( )
var rids [ ] int
for rows . Next ( ) {
var rid int
err := rows . Scan ( & rid )
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-12-28 07:12:14 +00:00
}
rids = append ( rids , rid )
}
err = rows . Err ( )
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-12-28 07:12:14 +00:00
}
if len ( rids ) == 0 {
2019-04-19 06:36:26 +00:00
return c . NotFoundJSQ ( w , r , nil , js )
2018-12-28 07:12:14 +00:00
}
if page > 1 {
var offset int
if rids [ 0 ] == reply . ID {
offset = 1
} else if len ( rids ) == 2 && rids [ 1 ] == reply . ID {
offset = 2
}
2019-04-19 06:36:26 +00:00
page = c . LastPage ( nTopic . PostCount - ( len ( rids ) + offset ) , c . Config . ItemsPerPage )
2018-12-28 07:12:14 +00:00
}
2019-04-06 01:08:49 +00:00
counters . PostCounter . Bump ( )
2019-04-19 01:02:33 +00:00
skip , rerr := lite . Hooks . VhookSkippable ( "action_end_create_reply" , reply . ID , & user )
2019-04-06 01:08:49 +00:00
if skip || rerr != nil {
return rerr
}
2018-12-28 07:12:14 +00:00
prid , _ := strconv . Atoi ( r . FormValue ( "prid" ) )
if js && ( prid == 0 || rids [ 0 ] == prid ) {
2019-04-19 06:36:26 +00:00
outBytes , err := json . Marshal ( JsonReply { c . ParseMessage ( reply . Content , topic . ParentID , "forums" ) } )
2018-12-28 02:08:35 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-12-28 02:08:35 +00:00
}
w . Write ( outBytes )
} else {
2018-12-28 07:12:14 +00:00
var spage string
if page > 1 {
spage = "?page=" + strconv . Itoa ( page )
}
http . Redirect ( w , r , "/topic/" + strconv . Itoa ( tid ) + spage + "#post-" + strconv . Itoa ( reply . ID ) , http . StatusSeeOther )
2018-03-08 03:59:47 +00:00
}
return nil
}
2018-01-20 06:50:29 +00:00
// TODO: Disable stat updates in posts handled by plugin_guilds
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
2019-04-19 06:36:26 +00:00
func ReplyEditSubmit ( w http . ResponseWriter , r * http . Request , user c . User , srid string ) c . RouteError {
2018-12-28 02:08:35 +00:00
js := ( r . PostFormValue ( "js" ) == "1" )
2018-01-20 06:50:29 +00:00
rid , err := strconv . Atoi ( srid )
if err != nil {
2019-04-19 06:36:26 +00:00
return c . PreErrorJSQ ( "The provided Reply ID is not a valid number." , w , r , js )
2018-01-20 06:50:29 +00:00
}
2019-04-19 06:36:26 +00:00
reply , err := c . Rstore . Get ( rid )
2018-01-20 06:50:29 +00:00
if err == sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . PreErrorJSQ ( "The target reply doesn't exist." , w , r , js )
2018-01-20 06:50:29 +00:00
} else if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
topic , err := reply . Topic ( )
if err == sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . PreErrorJSQ ( "The parent topic doesn't exist." , w , r , js )
2018-01-20 06:50:29 +00:00
} else if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
// TODO: Add hooks to make use of headerLite
2019-04-19 06:36:26 +00:00
lite , ferr := c . SimpleForumUserCheck ( w , r , & user , topic . ParentID )
2018-01-20 06:50:29 +00:00
if ferr != nil {
return ferr
}
if ! user . Perms . ViewTopic || ! user . Perms . EditReply {
2019-04-19 06:36:26 +00:00
return c . NoPermissionsJSQ ( w , r , user , js )
2018-01-20 06:50:29 +00:00
}
2018-06-01 05:02:29 +00:00
if topic . IsClosed && ! user . Perms . CloseTopic {
2019-04-19 06:36:26 +00:00
return c . NoPermissionsJSQ ( w , r , user , js )
2018-06-01 05:02:29 +00:00
}
2018-01-20 06:50:29 +00:00
2018-01-23 10:48:44 +00:00
err = reply . SetPost ( r . PostFormValue ( "edit_item" ) )
2018-01-20 06:50:29 +00:00
if err == sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . PreErrorJSQ ( "The parent topic doesn't exist." , w , r , js )
2018-01-20 06:50:29 +00:00
} else if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
2018-12-28 02:08:35 +00:00
// TODO: Avoid the load to get this faster?
2019-04-19 06:36:26 +00:00
reply , err = c . Rstore . Get ( rid )
2018-12-28 02:08:35 +00:00
if err == sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . PreErrorJSQ ( "The updated reply doesn't exist." , w , r , js )
2018-12-28 02:08:35 +00:00
} else if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-12-28 02:08:35 +00:00
}
2019-04-19 01:02:33 +00:00
skip , rerr := lite . Hooks . VhookSkippable ( "action_end_edit_reply" , reply . ID , & user )
2019-04-08 07:44:41 +00:00
if skip || rerr != nil {
return rerr
}
2018-12-28 02:08:35 +00:00
if ! js {
2018-01-20 06:50:29 +00:00
http . Redirect ( w , r , "/topic/" + strconv . Itoa ( topic . ID ) + "#reply-" + strconv . Itoa ( rid ) , http . StatusSeeOther )
} else {
2019-04-19 06:36:26 +00:00
outBytes , err := json . Marshal ( JsonReply { c . ParseMessage ( reply . Content , topic . ParentID , "forums" ) } )
2018-12-28 02:08:35 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-12-28 02:08:35 +00:00
}
w . Write ( outBytes )
2018-01-20 06:50:29 +00:00
}
2018-12-28 02:08:35 +00:00
2018-01-20 06:50:29 +00:00
return nil
}
// TODO: Refactor this
// TODO: Disable stat updates in posts handled by plugin_guilds
2019-04-19 06:36:26 +00:00
func ReplyDeleteSubmit ( w http . ResponseWriter , r * http . Request , user c . User , srid string ) c . RouteError {
2019-09-30 10:15:50 +00:00
js := ( r . PostFormValue ( "js" ) == "1" )
2018-01-20 06:50:29 +00:00
rid , err := strconv . Atoi ( srid )
if err != nil {
2019-09-30 10:15:50 +00:00
return c . PreErrorJSQ ( "The provided Reply ID is not a valid number." , w , r , js )
2018-01-20 06:50:29 +00:00
}
2019-04-19 06:36:26 +00:00
reply , err := c . Rstore . Get ( rid )
2018-01-20 06:50:29 +00:00
if err == sql . ErrNoRows {
2019-09-30 10:15:50 +00:00
return c . PreErrorJSQ ( "The reply you tried to delete doesn't exist." , w , r , js )
2018-01-20 06:50:29 +00:00
} else if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
2019-04-19 06:36:26 +00:00
topic , err := c . Topics . Get ( reply . ParentID )
2018-01-20 06:50:29 +00:00
if err == sql . ErrNoRows {
2019-09-30 10:15:50 +00:00
return c . PreErrorJSQ ( "The parent topic doesn't exist." , w , r , js )
2018-01-20 06:50:29 +00:00
} else if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
// TODO: Add hooks to make use of headerLite
2019-04-19 06:36:26 +00:00
lite , ferr := c . SimpleForumUserCheck ( w , r , & user , topic . ParentID )
2018-01-20 06:50:29 +00:00
if ferr != nil {
return ferr
}
if ! user . Perms . ViewTopic || ! user . Perms . DeleteReply {
2019-09-30 10:15:50 +00:00
return c . NoPermissionsJSQ ( w , r , user , js )
2018-01-20 06:50:29 +00:00
}
err = reply . Delete ( )
if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
2019-04-19 01:02:33 +00:00
skip , rerr := lite . Hooks . VhookSkippable ( "action_end_delete_reply" , reply . ID , & user )
2019-04-08 07:44:41 +00:00
if skip || rerr != nil {
return rerr
}
2019-04-19 06:36:26 +00:00
//log.Printf("Reply #%d was deleted by c.User #%d", rid, user.ID)
2019-09-30 10:15:50 +00:00
if ! js {
2018-01-20 06:50:29 +00:00
http . Redirect ( w , r , "/topic/" + strconv . Itoa ( reply . ParentID ) , http . StatusSeeOther )
} else {
w . Write ( successJSONBytes )
}
2019-04-08 07:44:41 +00:00
// ? - What happens if an error fires after a redirect...?
2019-04-19 06:36:26 +00:00
replyCreator , err := c . Users . Get ( reply . CreatedBy )
2018-01-20 06:50:29 +00:00
if err == nil {
2019-04-19 06:36:26 +00:00
wcount := c . WordCount ( reply . Content )
2018-01-20 06:50:29 +00:00
err = replyCreator . DecreasePostStats ( wcount , false )
if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
} else if err != sql . ErrNoRows {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
2019-04-19 06:36:26 +00:00
err = c . ModLogs . Create ( "delete" , reply . ParentID , "reply" , user . LastIP , user . ID )
2018-01-20 06:50:29 +00:00
if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
return nil
}
2018-12-31 09:03:49 +00:00
// TODO: Avoid uploading this again if the attachment already exists? They'll resolve to the same hash either way, but we could save on some IO / bandwidth here
// TODO: Enforce the max request limit on all of this topic's attachments
// TODO: Test this route
2019-04-19 06:36:26 +00:00
func AddAttachToReplySubmit ( w http . ResponseWriter , r * http . Request , user c . User , srid string ) c . RouteError {
2018-12-31 09:03:49 +00:00
rid , err := strconv . Atoi ( srid )
if err != nil {
2019-10-01 21:06:22 +00:00
return c . LocalErrorJS ( p . GetErrorPhrase ( "id_must_be_integer" ) , w , r )
2018-12-31 09:03:49 +00:00
}
2019-04-19 06:36:26 +00:00
reply , err := c . Rstore . Get ( rid )
2018-12-31 09:03:49 +00:00
if err == sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . PreErrorJS ( "You can't attach to something which doesn't exist!" , w , r )
2018-12-31 09:03:49 +00:00
} else if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJS ( err , w , r )
2018-12-31 09:03:49 +00:00
}
2019-04-19 06:36:26 +00:00
topic , err := c . Topics . Get ( reply . ParentID )
2018-12-31 09:03:49 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . NotFoundJS ( w , r )
2018-12-31 09:03:49 +00:00
}
2019-04-19 06:36:26 +00:00
lite , ferr := c . SimpleForumUserCheck ( w , r , & user , topic . ParentID )
2018-12-31 09:03:49 +00:00
if ferr != nil {
return ferr
}
if ! user . Perms . ViewTopic || ! user . Perms . EditReply || ! user . Perms . UploadFiles {
2019-04-19 06:36:26 +00:00
return c . NoPermissionsJS ( w , r , user )
2018-12-31 09:03:49 +00:00
}
if topic . IsClosed && ! user . Perms . CloseTopic {
2019-04-19 06:36:26 +00:00
return c . NoPermissionsJS ( w , r , user )
2018-12-31 09:03:49 +00:00
}
// Handle the file attachments
2019-04-13 11:54:22 +00:00
pathMap , rerr := uploadAttachment ( w , r , user , topic . ParentID , "forums" , rid , "replies" , strconv . Itoa ( topic . ID ) )
2018-12-31 09:03:49 +00:00
if rerr != nil {
// TODO: This needs to be a JS error...
return rerr
}
if len ( pathMap ) == 0 {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJS ( errors . New ( "no paths for attachment add" ) , w , r )
2018-12-31 09:03:49 +00:00
}
2019-04-19 01:02:33 +00:00
skip , rerr := lite . Hooks . VhookSkippable ( "action_end_add_attach_to_reply" , reply . ID , & user )
2019-04-11 04:22:03 +00:00
if skip || rerr != nil {
return rerr
}
2018-12-31 09:03:49 +00:00
var elemStr string
for path , aids := range pathMap {
elemStr += "\"" + path + "\":\"" + aids + "\","
}
if len ( elemStr ) > 1 {
elemStr = elemStr [ : len ( elemStr ) - 1 ]
}
2019-04-16 09:42:20 +00:00
w . Write ( [ ] byte ( ` { "success":"1","elems": { ` + elemStr + ` }} ` ) )
2018-12-31 09:03:49 +00:00
return nil
}
// TODO: Reduce the amount of duplication between this and RemoveAttachFromTopicSubmit
2019-04-19 06:36:26 +00:00
func RemoveAttachFromReplySubmit ( w http . ResponseWriter , r * http . Request , user c . User , srid string ) c . RouteError {
2018-12-31 09:03:49 +00:00
rid , err := strconv . Atoi ( srid )
if err != nil {
2019-10-01 21:06:22 +00:00
return c . LocalErrorJS ( p . GetErrorPhrase ( "id_must_be_integer" ) , w , r )
2018-12-31 09:03:49 +00:00
}
2019-04-19 06:36:26 +00:00
reply , err := c . Rstore . Get ( rid )
2018-12-31 09:03:49 +00:00
if err == sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . PreErrorJS ( "You can't attach from something which doesn't exist!" , w , r )
2018-12-31 09:03:49 +00:00
} else if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJS ( err , w , r )
2018-12-31 09:03:49 +00:00
}
2019-04-19 06:36:26 +00:00
topic , err := c . Topics . Get ( reply . ParentID )
2018-12-31 09:03:49 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . NotFoundJS ( w , r )
2018-12-31 09:03:49 +00:00
}
2019-04-19 06:36:26 +00:00
lite , ferr := c . SimpleForumUserCheck ( w , r , & user , topic . ParentID )
2018-12-31 09:03:49 +00:00
if ferr != nil {
return ferr
}
if ! user . Perms . ViewTopic || ! user . Perms . EditReply {
2019-04-19 06:36:26 +00:00
return c . NoPermissionsJS ( w , r , user )
2018-12-31 09:03:49 +00:00
}
if topic . IsClosed && ! user . Perms . CloseTopic {
2019-04-19 06:36:26 +00:00
return c . NoPermissionsJS ( w , r , user )
2018-12-31 09:03:49 +00:00
}
2019-04-16 09:42:20 +00:00
saids := strings . Split ( r . PostFormValue ( "aids" ) , "," )
if len ( saids ) == 0 {
2019-04-19 06:36:26 +00:00
return c . LocalErrorJS ( "No aids provided" , w , r )
2019-04-16 09:42:20 +00:00
}
for _ , said := range saids {
2018-12-31 09:03:49 +00:00
aid , err := strconv . Atoi ( said )
if err != nil {
2019-10-01 21:06:22 +00:00
return c . LocalErrorJS ( p . GetErrorPhrase ( "id_must_be_integer" ) , w , r )
2018-12-31 09:03:49 +00:00
}
rerr := deleteAttachment ( w , r , user , aid , true )
if rerr != nil {
// TODO: This needs to be a JS error...
return rerr
}
}
2019-04-19 01:02:33 +00:00
skip , rerr := lite . Hooks . VhookSkippable ( "action_end_remove_attach_from_reply" , reply . ID , & user )
2019-04-11 04:22:03 +00:00
if skip || rerr != nil {
return rerr
}
2018-12-31 09:03:49 +00:00
w . Write ( successJSONBytes )
return nil
}
2018-05-15 05:59:52 +00:00
// TODO: Move the profile reply routes to their own file?
2019-04-19 06:36:26 +00:00
func ProfileReplyCreateSubmit ( w http . ResponseWriter , r * http . Request , user c . User ) c . RouteError {
2018-05-15 05:59:52 +00:00
if ! user . Perms . ViewTopic || ! user . Perms . CreateReply {
2019-04-19 06:36:26 +00:00
return c . NoPermissions ( w , r , user )
2018-05-15 05:59:52 +00:00
}
uid , err := strconv . Atoi ( r . PostFormValue ( "uid" ) )
if err != nil {
2019-04-19 06:36:26 +00:00
return c . LocalError ( "Invalid UID" , w , r , user )
2018-05-15 05:59:52 +00:00
}
2019-04-19 06:36:26 +00:00
profileOwner , err := c . Users . Get ( uid )
2018-05-15 05:59:52 +00:00
if err == sql . ErrNoRows {
2019-04-19 06:36:26 +00:00
return c . LocalError ( "The profile you're trying to post on doesn't exist." , w , r , user )
2018-05-15 05:59:52 +00:00
} else if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalError ( err , w , r )
2018-05-15 05:59:52 +00:00
}
2019-07-28 10:42:30 +00:00
content := c . PreparseMessage ( r . PostFormValue ( "content" ) )
if len ( content ) == 0 {
return c . LocalError ( "You can't make a blank post" , w , r , user )
}
2018-05-15 05:59:52 +00:00
// TODO: Fully parse the post and store it in the parsed column
2019-04-19 06:36:26 +00:00
_ , err = c . Prstore . Create ( profileOwner . ID , content , user . ID , user . LastIP )
2018-05-15 05:59:52 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalError ( err , w , r )
2018-05-15 05:59:52 +00:00
}
2018-11-22 07:21:43 +00:00
// ! Be careful about leaking per-route permission state with &user
2019-05-11 23:07:24 +00:00
alert := c . Alert { ActorID : user . ID , TargetUserID : profileOwner . ID , Event : "reply" , ElementType : "user" , ElementID : profileOwner . ID , Actor : & user }
2019-04-19 06:36:26 +00:00
err = c . AddActivityAndNotifyTarget ( alert )
2018-05-15 05:59:52 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalError ( err , w , r )
2018-05-15 05:59:52 +00:00
}
counters . PostCounter . Bump ( )
http . Redirect ( w , r , "/user/" + strconv . Itoa ( uid ) , http . StatusSeeOther )
return nil
}
2019-04-19 06:36:26 +00:00
func ProfileReplyEditSubmit ( w http . ResponseWriter , r * http . Request , user c . User , srid string ) c . RouteError {
2019-09-30 10:15:50 +00:00
js := ( r . PostFormValue ( "js" ) == "1" )
2018-01-20 06:50:29 +00:00
rid , err := strconv . Atoi ( srid )
if err != nil {
2019-09-30 10:15:50 +00:00
return c . LocalErrorJSQ ( "The provided Reply ID is not a valid number." , w , r , user , js )
2018-01-20 06:50:29 +00:00
}
2019-04-19 06:36:26 +00:00
reply , err := c . Prstore . Get ( rid )
2018-01-20 06:50:29 +00:00
if err == sql . ErrNoRows {
2019-09-30 10:15:50 +00:00
return c . PreErrorJSQ ( "The target reply doesn't exist." , w , r , js )
2018-01-20 06:50:29 +00:00
} else if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
2019-04-19 06:36:26 +00:00
creator , err := c . Users . Get ( reply . CreatedBy )
2018-01-20 06:50:29 +00:00
if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
// ? Does the admin understand that this group perm affects this?
if user . ID != creator . ID && ! user . Perms . EditReply {
2019-09-30 10:15:50 +00:00
return c . NoPermissionsJSQ ( w , r , user , js )
2018-01-20 06:50:29 +00:00
}
err = reply . SetBody ( r . PostFormValue ( "edit_item" ) )
if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
2019-09-30 10:15:50 +00:00
if ! js {
2018-01-20 06:50:29 +00:00
http . Redirect ( w , r , "/user/" + strconv . Itoa ( creator . ID ) + "#reply-" + strconv . Itoa ( rid ) , http . StatusSeeOther )
} else {
w . Write ( successJSONBytes )
}
return nil
}
2019-04-19 06:36:26 +00:00
func ProfileReplyDeleteSubmit ( w http . ResponseWriter , r * http . Request , user c . User , srid string ) c . RouteError {
2019-09-30 10:15:50 +00:00
js := ( r . PostFormValue ( "js" ) == "1" )
2018-01-20 06:50:29 +00:00
rid , err := strconv . Atoi ( srid )
if err != nil {
2019-09-30 10:15:50 +00:00
return c . LocalErrorJSQ ( "The provided Reply ID is not a valid number." , w , r , user , js )
2018-01-20 06:50:29 +00:00
}
2019-04-19 06:36:26 +00:00
reply , err := c . Prstore . Get ( rid )
2018-01-20 06:50:29 +00:00
if err == sql . ErrNoRows {
2019-09-30 10:15:50 +00:00
return c . PreErrorJSQ ( "The target reply doesn't exist." , w , r , js )
2018-01-20 06:50:29 +00:00
} else if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
2019-04-19 06:36:26 +00:00
creator , err := c . Users . Get ( reply . CreatedBy )
2018-01-20 06:50:29 +00:00
if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
if user . ID != creator . ID && ! user . Perms . DeleteReply {
2019-09-30 10:15:50 +00:00
return c . NoPermissionsJSQ ( w , r , user , js )
2018-01-20 06:50:29 +00:00
}
err = reply . Delete ( )
if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-01-20 06:50:29 +00:00
}
2019-04-19 06:36:26 +00:00
//log.Printf("The profile post '%d' was deleted by c.User #%d", reply.ID, user.ID)
2018-01-20 06:50:29 +00:00
2019-09-30 10:15:50 +00:00
if ! js {
2018-01-20 06:50:29 +00:00
//http.Redirect(w,r, "/user/" + strconv.Itoa(creator.ID), http.StatusSeeOther)
} else {
w . Write ( successJSONBytes )
}
return nil
}
2018-05-15 05:59:52 +00:00
2019-04-19 06:36:26 +00:00
func ReplyLikeSubmit ( w http . ResponseWriter , r * http . Request , user c . User , srid string ) c . RouteError {
2019-09-30 10:15:50 +00:00
js := ( r . PostFormValue ( "js" ) == "1" )
2018-05-15 05:59:52 +00:00
rid , err := strconv . Atoi ( srid )
if err != nil {
2019-09-30 10:15:50 +00:00
return c . PreErrorJSQ ( "The provided Reply ID is not a valid number." , w , r , js )
2018-05-15 05:59:52 +00:00
}
2019-04-19 06:36:26 +00:00
reply , err := c . Rstore . Get ( rid )
2018-05-15 05:59:52 +00:00
if err == sql . ErrNoRows {
2019-09-30 10:15:50 +00:00
return c . PreErrorJSQ ( "You can't like something which doesn't exist!" , w , r , js )
2018-05-15 05:59:52 +00:00
} else if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-05-15 05:59:52 +00:00
}
2019-04-19 06:36:26 +00:00
topic , err := c . Topics . Get ( reply . ParentID )
2018-05-15 05:59:52 +00:00
if err == sql . ErrNoRows {
2019-09-30 10:15:50 +00:00
return c . PreErrorJSQ ( "The parent topic doesn't exist." , w , r , js )
2018-05-15 05:59:52 +00:00
} else if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-05-15 05:59:52 +00:00
}
// TODO: Add hooks to make use of headerLite
2019-04-19 06:36:26 +00:00
lite , ferr := c . SimpleForumUserCheck ( w , r , & user , topic . ParentID )
2018-05-15 05:59:52 +00:00
if ferr != nil {
return ferr
}
if ! user . Perms . ViewTopic || ! user . Perms . LikeItem {
2019-09-30 10:15:50 +00:00
return c . NoPermissionsJSQ ( w , r , user , js )
2018-05-15 05:59:52 +00:00
}
if reply . CreatedBy == user . ID {
2019-09-30 10:15:50 +00:00
return c . LocalErrorJSQ ( "You can't like your own replies" , w , r , user , js )
2018-05-15 05:59:52 +00:00
}
2019-04-19 06:36:26 +00:00
_ , err = c . Users . Get ( reply . CreatedBy )
2018-05-15 05:59:52 +00:00
if err != nil && err != sql . ErrNoRows {
2019-09-30 10:15:50 +00:00
return c . LocalErrorJSQ ( "The target user doesn't exist" , w , r , user , js )
2018-05-15 05:59:52 +00:00
} else if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-05-15 05:59:52 +00:00
}
err = reply . Like ( user . ID )
2019-04-19 06:36:26 +00:00
if err == c . ErrAlreadyLiked {
2019-09-30 10:15:50 +00:00
return c . LocalErrorJSQ ( "You've already liked this!" , w , r , user , js )
2018-05-15 05:59:52 +00:00
} else if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-05-15 05:59:52 +00:00
}
2018-11-22 07:21:43 +00:00
// ! Be careful about leaking per-route permission state with &user
2019-05-11 23:07:24 +00:00
alert := c . Alert { ActorID : user . ID , TargetUserID : reply . CreatedBy , Event : "like" , ElementType : "post" , ElementID : rid , Actor : & user }
2019-04-19 06:36:26 +00:00
err = c . AddActivityAndNotifyTarget ( alert )
2018-05-15 05:59:52 +00:00
if err != nil {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2018-05-15 05:59:52 +00:00
}
2019-04-19 01:02:33 +00:00
skip , rerr := lite . Hooks . VhookSkippable ( "action_end_like_reply" , reply . ID , & user )
if skip || rerr != nil {
return rerr
}
2019-09-30 10:15:50 +00:00
if ! js {
2018-05-15 05:59:52 +00:00
http . Redirect ( w , r , "/topic/" + strconv . Itoa ( reply . ParentID ) , http . StatusSeeOther )
} else {
_ , _ = w . Write ( successJSONBytes )
}
return nil
}