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
2020-01-31 10:48:55 +00:00
updateAttachs : acc . Update ( "replies" ) . Set ( "attachCount=?" ) . Where ( "rid=?" ) . Prepare ( ) ,
2018-12-28 07:12:14 +00:00
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-12-31 21:57:54 +00:00
rid , err := c . Rstore . Create ( topic , content , user . GetIP ( ) , 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 {
2019-10-19 10:33:59 +00:00
if ! strings . HasPrefix ( key , "pollinputitem[" ) {
continue
}
halves := strings . Split ( key , "[" )
if len ( halves ) != 2 {
return c . LocalErrorJSQ ( "Malformed pollinputitem" , w , r , user , js )
}
halves [ 1 ] = strings . TrimSuffix ( halves [ 1 ] , "]" )
2018-03-08 03:59:47 +00:00
2019-10-19 10:33:59 +00:00
index , err := strconv . Atoi ( halves [ 1 ] )
if err != nil {
return c . LocalErrorJSQ ( "Malformed pollinputitem" , w , r , user , js )
}
2018-03-08 03:59:47 +00:00
2019-10-19 10:33:59 +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 ]
// TODO: Should we use SanitiseBody instead to keep the newlines?
if ! exists && len ( c . SanitiseSingleLine ( value ) ) != 0 {
pollInputItems [ index ] = c . SanitiseSingleLine ( value )
if len ( pollInputItems ) >= maxPollOptions {
break
2018-03-08 03:59:47 +00:00
}
}
}
}
// 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
}
Cascade delete attachments properly.
Cascade delete replied to topic events for replies properly.
Cascade delete likes on topic posts properly.
Cascade delete replies and their children properly.
Recalculate user stats properly when items are deleted.
Users can now unlike topic opening posts.
Add a recalculator to fix abnormalities across upgrades.
Try fixing a last_ip daily update bug.
Add Existable interface.
Add Delete method to LikeStore.
Add Each, Exists, Create, CountUser, CountMegaUser and CountBigUser methods to ReplyStore.
Add CountUser, CountMegaUser, CountBigUser methods to TopicStore.
Add Each method to UserStore.
Add Add, Delete and DeleteResource methods to SubscriptionStore.
Add Delete, DeleteByParams, DeleteByParamsExtra and AidsByParamsExtra methods to ActivityStream.
Add Exists method to ProfileReplyStore.
Add DropColumn, RenameColumn and ChangeColumn to the database adapters.
Shorten ipaddress column names to ip.
- topics table.
- replies table
- users_replies table.
- polls_votes table.
Add extra column to activity_stream table.
Fix an issue upgrading sites to MariaDB 10.3 from older versions of Gosora. Please report any other issues you find.
You need to run the updater / patcher for this commit.
2020-01-31 07:22:08 +00:00
c . AddActivityAndNotifyAll ( c . Alert { ActorID : user . ID , TargetUserID : topic . CreatedBy , Event : "reply" , ElementType : "topic" , ElementID : tid , Extra : strconv . Itoa ( rid ) } )
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-10-19 10:33:59 +00:00
err = user . IncreasePostStats ( c . WordCount ( content ) , false )
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-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
2019-10-19 10:33:59 +00:00
if err := rows . Scan ( & rid ) ; 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 )
}
2019-10-19 10:33:59 +00:00
if err := rows . Err ( ) ; 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-12-08 03:40:56 +00:00
outBytes , err := json . Marshal ( JsonReply { c . ParseMessage ( reply . Content , topic . ParentID , "forums" , user . ParseSettings ) } )
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 {
2019-10-06 11:32:00 +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-12-08 03:40:56 +00:00
outBytes , err := json . Marshal ( JsonReply { c . ParseMessage ( reply . Content , topic . ParentID , "forums" , user . ParseSettings ) } )
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-10-06 11:32:00 +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
}
2019-10-06 11:32:00 +00:00
if reply . CreatedBy != user . ID {
if ! user . Perms . ViewTopic || ! user . Perms . DeleteReply {
return c . NoPermissionsJSQ ( w , r , user , js )
}
2018-01-20 06:50:29 +00:00
}
2019-10-19 10:33:59 +00:00
if err := reply . Delete ( ) ; 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...?
2020-01-18 07:24:51 +00:00
/ * creator , err := c . Users . Get ( reply . CreatedBy )
2018-01-20 06:50:29 +00:00
if err == nil {
2020-01-18 07:24:51 +00:00
err = creator . DecreasePostStats ( c . WordCount ( reply . Content ) , false )
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
}
} else if err != sql . ErrNoRows {
2019-09-30 10:15:50 +00:00
return c . InternalErrorJSQ ( err , w , r , js )
2020-01-15 04:16:10 +00:00
} * /
2018-01-20 06:50:29 +00:00
2019-12-31 21:57:54 +00:00
err = c . ModLogs . Create ( "delete" , reply . ParentID , "reply" , user . GetIP ( ) , 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-10-29 01:58:04 +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
}
2019-04-19 06:36:26 +00:00
func ReplyLikeSubmit ( w http . ResponseWriter , r * http . Request , user c . User , srid string ) c . RouteError {
2019-10-06 11:32:00 +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
}
2020-01-31 10:48:55 +00:00
func ReplyUnlikeSubmit ( w http . ResponseWriter , r * http . Request , user c . User , srid string ) c . RouteError {
js := r . PostFormValue ( "js" ) == "1"
rid , err := strconv . Atoi ( srid )
if err != nil {
return c . PreErrorJSQ ( "The provided Reply ID is not a valid number." , w , r , js )
}
reply , err := c . Rstore . Get ( rid )
if err == sql . ErrNoRows {
return c . PreErrorJSQ ( "You can't unlike something which doesn't exist!" , w , r , js )
} else if err != nil {
return c . InternalErrorJSQ ( err , w , r , js )
}
topic , err := c . Topics . Get ( reply . ParentID )
if err == sql . ErrNoRows {
return c . PreErrorJSQ ( "The parent topic doesn't exist." , w , r , js )
} else if err != nil {
return c . InternalErrorJSQ ( err , w , r , js )
}
// TODO: Add hooks to make use of headerLite
lite , ferr := c . SimpleForumUserCheck ( w , r , & user , topic . ParentID )
if ferr != nil {
return ferr
}
if ! user . Perms . ViewTopic || ! user . Perms . LikeItem {
return c . NoPermissionsJSQ ( w , r , user , js )
}
_ , err = c . Users . Get ( reply . CreatedBy )
if err != nil && err != sql . ErrNoRows {
return c . LocalErrorJSQ ( "The target user doesn't exist" , w , r , user , js )
} else if err != nil {
return c . InternalErrorJSQ ( err , w , r , js )
}
err = reply . Unlike ( user . ID )
if err != nil {
return c . InternalErrorJSQ ( err , w , r , js )
}
// TODO: Push dismiss-event alerts to the users.
err = c . Activity . DeleteByParams ( "like" , topic . ID , "reply" )
if err != nil {
return c . InternalErrorJSQ ( err , w , r , js )
}
skip , rerr := lite . Hooks . VhookSkippable ( "action_end_unlike_reply" , reply . ID , & user )
if skip || rerr != nil {
return rerr
}
if ! js {
http . Redirect ( w , r , "/topic/" + strconv . Itoa ( reply . ParentID ) , http . StatusSeeOther )
} else {
_ , _ = w . Write ( successJSONBytes )
}
return nil
}