Tighten validation on group promotion delete.

Shorten some variable names.
Reduce the amount of duplication in the panel group routes.
Rename isJs to js here and there.
Localise group promotions.

Phrases:

Rename create_topic_create_topic_button to create_topic_create_button
Rename quick_topic.create_topic_button to quick_topic.create_button
Rename quick_topic.create_topic_button_short to quick_topic.create_button_short
Rename panel_groups_create_create_group_button to panel_groups_create_button
Rename panel_word_filters_create_create_word_filter_button to panel_word_filters_create_button
Rename panel_pages_create_submit_button to panel_pages_create_button
Add panel_group_promotions_create_head
Add panel_group_promotions_from
Add panel_group_promotions_to
Add panel_group_promotions_two_way
Add panel_group_promotions_level
Add panel_group_promotions_create_button
This commit is contained in:
Azareal 2019-09-30 20:15:50 +10:00
parent 69e1e8d364
commit 764b9904f1
15 changed files with 237 additions and 241 deletions

View File

@ -552,7 +552,7 @@
"create_topic_name":"Topic Name", "create_topic_name":"Topic Name",
"create_topic_content":"Content", "create_topic_content":"Content",
"create_topic_placeholder":"Insert content here", "create_topic_placeholder":"Insert content here",
"create_topic_create_topic_button":"Create Topic", "create_topic_create_button":"Create Topic",
"create_topic_add_file_button":"Add File", "create_topic_add_file_button":"Add File",
"quick_topic.aria":"Quick Topic Form", "quick_topic.aria":"Quick Topic Form",
@ -561,8 +561,8 @@
"quick_topic.whatsup":"What's up?", "quick_topic.whatsup":"What's up?",
"quick_topic.content_placeholder":"Insert post here", "quick_topic.content_placeholder":"Insert post here",
"quick_topic.add_poll_option_first":"Poll option #0", "quick_topic.add_poll_option_first":"Poll option #0",
"quick_topic.create_topic_button":"Create Topic", "quick_topic.create_button":"Create Topic",
"quick_topic.create_topic_button_short":"New Topic", "quick_topic.create_button_short":"New Topic",
"quick_topic.add_poll_button":"Add Poll", "quick_topic.add_poll_button":"Add Poll",
"quick_topic.add_file_button":"Add File", "quick_topic.add_file_button":"Add File",
"quick_topic.cancel_button":"Cancel", "quick_topic.cancel_button":"Cancel",
@ -870,7 +870,7 @@
"panel_groups_create_name_placeholder":"Administrator", "panel_groups_create_name_placeholder":"Administrator",
"panel_groups_create_type":"Type", "panel_groups_create_type":"Type",
"panel_groups_create_tag":"Tag", "panel_groups_create_tag":"Tag",
"panel_groups_create_create_group_button":"Add Group", "panel_groups_create_button":"Add Group",
"panel_group_menu_head":"Group Editor", "panel_group_menu_head":"Group Editor",
"panel_group_menu_general":"General", "panel_group_menu_general":"General",
@ -885,6 +885,13 @@
"panel_group_update_button":"Update Group", "panel_group_update_button":"Update Group",
"panel_group_extended_permissions":"Extended Permissions", "panel_group_extended_permissions":"Extended Permissions",
"panel_group_promotions_create_head":"Add Promotion",
"panel_group_promotions_from":"From",
"panel_group_promotions_to":"To",
"panel_group_promotions_two_way":"Two Way",
"panel_group_promotions_level":"Level",
"panel_group_promotions_create_button":"Add Promotion",
"panel_word_filters_head":"Word Filters", "panel_word_filters_head":"Word Filters",
"panel_word_filters_to":"to", "panel_word_filters_to":"to",
"panel_word_filters_edit_button_aria":"Edit Word Filter", "panel_word_filters_edit_button_aria":"Edit Word Filter",
@ -896,7 +903,7 @@
"panel_word_filters_create_find_placeholder":"fuck", "panel_word_filters_create_find_placeholder":"fuck",
"panel_word_filters_create_replacement":"Replacement", "panel_word_filters_create_replacement":"Replacement",
"panel_word_filters_create_replacement_placeholder":"fudge", "panel_word_filters_create_replacement_placeholder":"fudge",
"panel_word_filters_create_create_word_filter_button":"Add Filter", "panel_word_filters_create_button":"Add Filter",
"panel_pages_head":"Page Manager", "panel_pages_head":"Page Manager",
"panel_pages_edit_button_aria":"Edit Page", "panel_pages_edit_button_aria":"Edit Page",
@ -908,7 +915,7 @@
"panel_pages_create_title":"Title", "panel_pages_create_title":"Title",
"panel_pages_create_title_placeholder":"Frequently Asked Questions", "panel_pages_create_title_placeholder":"Frequently Asked Questions",
"panel_pages_create_body_placeholder":"We understand you have a lot of questions.", "panel_pages_create_body_placeholder":"We understand you have a lot of questions.",
"panel_pages_create_submit_button":"Create Page", "panel_pages_create_button":"Create Page",
"panel_pages_edit_head":"Page Editor", "panel_pages_edit_head":"Page Editor",
"panel_pages_name":"Name", "panel_pages_name":"Name",

View File

@ -436,7 +436,7 @@ function mainInit(){
url: target, url: target,
type: "POST", type: "POST",
dataType: "json", dataType: "json",
data: { isJs: 1 }, data: { js: 1 },
error: ajaxError, error: ajaxError,
success: function (data, status, xhr) { success: function (data, status, xhr) {
if("success" in data && data["success"] == "1") return; if("success" in data && data["success"] == "1") return;
@ -729,7 +729,7 @@ function mainInit(){
type: "POST", type: "POST",
dataType: "json", dataType: "json",
error: ajaxError, error: ajaxError,
data: { isJs: "1", edit_item: newContent } data: { js: "1", edit_item: newContent }
}); });
}); });
}); });
@ -771,7 +771,7 @@ function mainInit(){
$(".submit_edit").click(function(event) { $(".submit_edit").click(function(event) {
event.preventDefault(); event.preventDefault();
var outData = {isJs: "1"} var outData = {js: "1"}
var blockParent = $(this).closest('.editable_parent'); var blockParent = $(this).closest('.editable_parent');
blockParent.find('.editable_block').each(function() { blockParent.find('.editable_block').each(function() {
var fieldName = this.getAttribute("data-field"); var fieldName = this.getAttribute("data-field");
@ -856,7 +856,7 @@ function mainInit(){
url: this.form.getAttribute("action") + "?s=" + me.User.S, url: this.form.getAttribute("action") + "?s=" + me.User.S,
type: "POST", type: "POST",
dataType: "json", dataType: "json",
data: { "newTheme": this.options[this.selectedIndex].getAttribute("val"), isJs: "1" }, data: { "newTheme": this.options[this.selectedIndex].getAttribute("val"), js: "1" },
error: ajaxError, error: ajaxError,
success: function (data, status, xhr) { success: function (data, status, xhr) {
console.log("Theme successfully switched"); console.log("Theme successfully switched");

View File

@ -83,19 +83,19 @@ func CustomPage(w http.ResponseWriter, r *http.Request, user c.User, header *c.H
// TODO: Set the cookie domain // TODO: Set the cookie domain
func ChangeTheme(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { func ChangeTheme(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
//headerLite, _ := SimpleUserCheck(w, r, &user) //headerLite, _ := SimpleUserCheck(w, r, &user)
// TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly? // TODO: Rename js to something else, just in case we rewrite the JS side in WebAssembly?
isJs := (r.PostFormValue("isJs") == "1") js := (r.PostFormValue("js") == "1")
newTheme := c.SanitiseSingleLine(r.PostFormValue("newTheme")) newTheme := c.SanitiseSingleLine(r.PostFormValue("newTheme"))
theme, ok := c.Themes[newTheme] theme, ok := c.Themes[newTheme]
if !ok || theme.HideFromThemes { if !ok || theme.HideFromThemes {
return c.LocalErrorJSQ("That theme doesn't exist", w, r, user, isJs) return c.LocalErrorJSQ("That theme doesn't exist", w, r, user, js)
} }
cookie := http.Cookie{Name: "current_theme", Value: newTheme, Path: "/", MaxAge: int(c.Year)} cookie := http.Cookie{Name: "current_theme", Value: newTheme, Path: "/", MaxAge: int(c.Year)}
http.SetCookie(w, &cookie) http.SetCookie(w, &cookie)
if !isJs { if !js {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} else { } else {
_, _ = w.Write(successJSONBytes) _, _ = w.Write(successJSONBytes)

View File

@ -1052,7 +1052,7 @@ func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user c.User) c.R
ovList := analyticsVMapToOVList(vMap) ovList := analyticsVMapToOVList(vMap)
ex := strings.Split(r.FormValue("ex"), ",") ex := strings.Split(r.FormValue("ex"), ",")
var inEx = func(name string) bool { inEx := func(name string) bool {
for _, e := range ex { for _, e := range ex {
if e == name { if e == name {
return true return true
@ -1134,7 +1134,7 @@ func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user c.User) c.R
} }
showSpam := r.FormValue("spam") == "1" showSpam := r.FormValue("spam") == "1"
var isSpammy = func(domain string) bool { isSpammy := func(domain string) bool {
for _, substr := range c.SpammyDomainBits { for _, substr := range c.SpammyDomainBits {
if strings.Contains(domain, substr) { if strings.Contains(domain, substr) {
return true return true

View File

@ -10,14 +10,14 @@ import (
p "github.com/Azareal/Gosora/common/phrases" p "github.com/Azareal/Gosora/common/phrases"
) )
func Groups(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { func Groups(w http.ResponseWriter, r *http.Request, u c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, &user, "groups", "groups") bPage, ferr := buildBasePage(w, r, &u, "groups", "groups")
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 15 perPage := 15
offset, page, lastPage := c.PageOffset(basePage.Stats.Groups, page, perPage) offset, page, lastPage := c.PageOffset(bPage.Stats.Groups, page, perPage)
// Skip the 'Unknown' group // Skip the 'Unknown' group
offset++ offset++
@ -25,7 +25,7 @@ func Groups(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
var count int var count int
var groupList []c.GroupAdmin var groupList []c.GroupAdmin
groups, _ := c.Groups.GetRange(offset, 0) groups, _ := c.Groups.GetRange(offset, 0)
for _, group := range groups { for _, g := range groups {
if count == perPage { if count == perPage {
break break
} }
@ -33,33 +33,33 @@ func Groups(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
var rankClass string var rankClass string
var canDelete = false var canDelete = false
// TODO: Use a switch for this
// TODO: Localise this // TODO: Localise this
if group.IsAdmin { switch {
case g.IsAdmin:
rank = "Admin" rank = "Admin"
rankClass = "admin" rankClass = "admin"
} else if group.IsMod { case g.IsMod:
rank = "Mod" rank = "Mod"
rankClass = "mod" rankClass = "mod"
} else if group.IsBanned { case g.IsBanned:
rank = "Banned" rank = "Banned"
rankClass = "banned" rankClass = "banned"
} else if group.ID == 6 { case g.ID == 6:
rank = "Guest" rank = "Guest"
rankClass = "guest" rankClass = "guest"
} else { default:
rank = "Member" rank = "Member"
rankClass = "member" rankClass = "member"
} }
canEdit := user.Perms.EditGroup && (!group.IsAdmin || user.Perms.EditGroupAdmin) && (!group.IsMod || user.Perms.EditGroupSuperMod) canEdit := u.Perms.EditGroup && (!g.IsAdmin || u.Perms.EditGroupAdmin) && (!g.IsMod || u.Perms.EditGroupSuperMod)
groupList = append(groupList, c.GroupAdmin{group.ID, group.Name, rank, rankClass, canEdit, canDelete}) groupList = append(groupList, c.GroupAdmin{g.ID, g.Name, rank, rankClass, canEdit, canDelete})
count++ count++
} }
pageList := c.Paginate(page, lastPage, 5) pageList := c.Paginate(page, lastPage, 5)
pi := c.PanelGroupPage{basePage, groupList, c.Paginator{pageList, page, lastPage}} pi := c.PanelGroupPage{bPage, groupList, c.Paginator{pageList, page, lastPage}}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage,"","","panel_groups",&pi}) return renderTemplate("panel", w, r, bPage.Header, c.Panel{bPage, "", "", "panel_groups", &pi})
} }
func GroupsEdit(w http.ResponseWriter, r *http.Request, user c.User, sgid string) c.RouteError { func GroupsEdit(w http.ResponseWriter, r *http.Request, user c.User, sgid string) c.RouteError {
@ -75,38 +75,32 @@ func GroupsEdit(w http.ResponseWriter, r *http.Request, user c.User, sgid string
if err != nil { if err != nil {
return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, user) return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, user)
} }
g, err := c.Groups.Get(gid)
group, err := c.Groups.Get(gid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
//log.Print("aaaaa monsters") //log.Print("aaaaa monsters")
return c.NotFound(w, r, basePage.Header) return c.NotFound(w, r, basePage.Header)
} else if err != nil {
return c.InternalError(err, w, r)
} }
ferr = groupCheck(w,r,user,g,err)
if group.IsAdmin && !user.Perms.EditGroupAdmin { if ferr != nil {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_admin"), w, r, user) return ferr
}
if group.IsMod && !user.Perms.EditGroupSuperMod {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_supermod"), w, r, user)
} }
var rank string var rank string
switch { switch {
case group.IsAdmin: case g.IsAdmin:
rank = "Admin" rank = "Admin"
case group.IsMod: case g.IsMod:
rank = "Mod" rank = "Mod"
case group.IsBanned: case g.IsBanned:
rank = "Banned" rank = "Banned"
case group.ID == 6: case g.ID == 6:
rank = "Guest" rank = "Guest"
default: default:
rank = "Member" rank = "Member"
} }
disableRank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6) disableRank := !user.Perms.EditGroupGlobalPerms || (g.ID == 6)
pi := c.PanelEditGroupPage{basePage, group.ID, group.Name, group.Tag, rank, disableRank} pi := c.PanelEditGroupPage{basePage, g.ID, g.Name, g.Tag, rank, disableRank}
return renderTemplate("panel_group_edit", w, r, basePage.Header, pi) return renderTemplate("panel_group_edit", w, r, basePage.Header, pi)
} }
@ -123,19 +117,14 @@ func GroupsEditPromotions(w http.ResponseWriter, r *http.Request, user c.User, s
if err != nil { if err != nil {
return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, user) return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, user)
} }
g, err := c.Groups.Get(gid) g, err := c.Groups.Get(gid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
//log.Print("aaaaa monsters") //log.Print("aaaaa monsters")
return c.NotFound(w, r, basePage.Header) return c.NotFound(w, r, basePage.Header)
} else if err != nil {
return c.InternalError(err, w, r)
} }
if g.IsAdmin && !user.Perms.EditGroupAdmin { ferr = groupCheck(w,r,user,g,err)
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_admin"), w, r, user) if ferr != nil {
} return ferr
if g.IsMod && !user.Perms.EditGroupSuperMod {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_supermod"), w, r, user)
} }
promotions, err := c.GroupPromotions.GetByGroup(g.ID) promotions, err := c.GroupPromotions.GetByGroup(g.ID)
@ -146,13 +135,13 @@ func GroupsEditPromotions(w http.ResponseWriter, r *http.Request, user c.User, s
for i, promote := range promotions { for i, promote := range promotions {
fg, err := c.Groups.Get(promote.From) fg, err := c.Groups.Get(promote.From)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
fg = &c.Group{Name:"Deleted Group"} fg = &c.Group{Name: "Deleted Group"}
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
tg, err := c.Groups.Get(promote.To) tg, err := c.Groups.Get(promote.To)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
tg = &c.Group{Name:"Deleted Group"} tg = &c.Group{Name: "Deleted Group"}
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -180,6 +169,21 @@ func GroupsEditPromotions(w http.ResponseWriter, r *http.Request, user c.User, s
return renderTemplate("panel_group_edit_promotions", w, r, basePage.Header, pi) return renderTemplate("panel_group_edit_promotions", w, r, basePage.Header, pi)
} }
func groupCheck(w http.ResponseWriter, r *http.Request, user c.User, g *c.Group, err error) c.RouteError {
if err == sql.ErrNoRows {
return c.LocalError("No such group.", w, r, user)
} else if err != nil {
return c.InternalError(err, w, r)
}
if g.IsAdmin && !user.Perms.EditGroupAdmin {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_admin"), w, r, user)
}
if g.IsMod && !user.Perms.EditGroupSuperMod {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_supermod"), w, r, user)
}
return nil
}
func GroupsPromotionsCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User, sgid string) c.RouteError { func GroupsPromotionsCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User, sgid string) c.RouteError {
if !user.Perms.EditGroup { if !user.Perms.EditGroup {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, user)
@ -206,36 +210,20 @@ func GroupsPromotionsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
} }
g, err := c.Groups.Get(from) g, err := c.Groups.Get(from)
if err == sql.ErrNoRows { ferr := groupCheck(w, r, user, g, err)
return c.LocalError("No such group.",w, r, user) if err != nil {
} else if err != nil { return ferr
return c.InternalError(err, w, r)
} }
if g.IsAdmin && !user.Perms.EditGroupAdmin {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_admin"), w, r, user)
}
if g.IsMod && !user.Perms.EditGroupSuperMod {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_supermod"), w, r, user)
}
g, err = c.Groups.Get(to) g, err = c.Groups.Get(to)
if err == sql.ErrNoRows { ferr = groupCheck(w, r, user, g, err)
return c.LocalError("No such group.",w, r, user) if err != nil {
} else if err != nil { return ferr
return c.InternalError(err, w, r)
} }
if g.IsAdmin && !user.Perms.EditGroupAdmin {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_admin"), w, r, user)
}
if g.IsMod && !user.Perms.EditGroupSuperMod {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_supermod"), w, r, user)
}
_, err = c.GroupPromotions.Create(from, to, twoWay, level) _, err = c.GroupPromotions.Create(from, to, twoWay, level)
if err != nil { if err != nil {
return c.InternalError(err,w,r) return c.InternalError(err, w, r)
} }
http.Redirect(w, r, "/panel/groups/edit/promotions/"+strconv.Itoa(gid), http.StatusSeeOther) http.Redirect(w, r, "/panel/groups/edit/promotions/"+strconv.Itoa(gid), http.StatusSeeOther)
return nil return nil
} }
@ -246,7 +234,7 @@ func GroupsPromotionsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c
} }
spl := strings.Split(sspl, "-") spl := strings.Split(sspl, "-")
if len(spl) < 2 { if len(spl) < 2 {
return c.LocalError("need two params",w,r,user) return c.LocalError("need two params", w, r, user)
} }
gid, err := strconv.Atoi(spl[0]) gid, err := strconv.Atoi(spl[0])
if err != nil { if err != nil {
@ -257,9 +245,26 @@ func GroupsPromotionsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c
return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, user) return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, user)
} }
pro, err := c.GroupPromotions.Get(pid)
if err == sql.ErrNoRows {
return c.LocalError("That group promotion doesn't exist", w, r, user)
} else if err != nil {
return c.InternalError(err, w, r)
}
g, err := c.Groups.Get(pro.From)
ferr := groupCheck(w, r, user, g, err)
if err != nil {
return ferr
}
g, err = c.Groups.Get(pro.To)
ferr = groupCheck(w, r, user, g, err)
if err != nil {
return ferr
}
err = c.GroupPromotions.Delete(pid) err = c.GroupPromotions.Delete(pid)
if err != nil { if err != nil {
return c.InternalError(err,w,r) return c.InternalError(err, w, r)
} }
http.Redirect(w, r, "/panel/groups/edit/promotions/"+strconv.Itoa(gid), http.StatusSeeOther) http.Redirect(w, r, "/panel/groups/edit/promotions/"+strconv.Itoa(gid), http.StatusSeeOther)
@ -360,14 +365,10 @@ func GroupsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, sgid
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
//log.Print("aaaaa monsters") //log.Print("aaaaa monsters")
return c.NotFound(w, r, nil) return c.NotFound(w, r, nil)
} else if err != nil {
return c.InternalError(err, w, r)
} }
if group.IsAdmin && !user.Perms.EditGroupAdmin { ferr = groupCheck(w, r, user, group, err)
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_admin"), w, r, user) if ferr != nil {
} return ferr
if group.IsMod && !user.Perms.EditGroupSuperMod {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_supermod"), w, r, user)
} }
gname := r.FormValue("group-name") gname := r.FormValue("group-name")
@ -447,31 +448,24 @@ func GroupsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user c.User,
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
//log.Print("aaaaa monsters o.o") //log.Print("aaaaa monsters o.o")
return c.NotFound(w, r, nil) return c.NotFound(w, r, nil)
} else if err != nil {
return c.InternalError(err, w, r)
} }
if group.IsAdmin && !user.Perms.EditGroupAdmin { ferr = groupCheck(w, r, user, group, err)
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_admin"), w, r, user) if ferr != nil {
} return ferr
if group.IsMod && !user.Perms.EditGroupSuperMod {
return c.LocalError(p.GetErrorPhrase("panel_groups_cannot_edit_supermod"), w, r, user)
} }
// TODO: Don't unset perms we don't have permission to set?
pmap := make(map[string]bool) pmap := make(map[string]bool)
pCheck := func(hasPerm bool, perms []string) {
if user.Perms.EditGroupLocalPerms { if hasPerm {
for _, perm := range c.LocalPermList { for _, perm := range perms {
pvalue := r.PostFormValue("group-perm-" + perm) pvalue := r.PostFormValue("group-perm-" + perm)
pmap[perm] = (pvalue == "1") pmap[perm] = (pvalue == "1")
} }
}
if user.Perms.EditGroupGlobalPerms {
for _, perm := range c.GlobalPermList {
pvalue := r.PostFormValue("group-perm-" + perm)
pmap[perm] = (pvalue == "1")
} }
} }
pCheck(user.Perms.EditGroupLocalPerms, c.LocalPermList)
pCheck(user.Perms.EditGroupGlobalPerms, c.GlobalPermList)
err = group.UpdatePerms(pmap) err = group.UpdatePerms(pmap)
if err != nil { if err != nil {
@ -499,19 +493,19 @@ func GroupsCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.R
var isAdmin, isMod, isBanned bool var isAdmin, isMod, isBanned bool
if user.Perms.EditGroupGlobalPerms { if user.Perms.EditGroupGlobalPerms {
groupType := r.PostFormValue("group-type") switch r.PostFormValue("group-type") {
if groupType == "Admin" { case "Admin":
if !user.Perms.EditGroupAdmin { if !user.Perms.EditGroupAdmin {
return c.LocalError(p.GetErrorPhrase("panel_groups_create_cannot_designate_admin"), w, r, user) return c.LocalError(p.GetErrorPhrase("panel_groups_create_cannot_designate_admin"), w, r, user)
} }
isAdmin = true isAdmin = true
isMod = true isMod = true
} else if groupType == "Mod" { case "Mod":
if !user.Perms.EditGroupSuperMod { if !user.Perms.EditGroupSuperMod {
return c.LocalError(p.GetErrorPhrase("panel_groups_create_cannot_designate_supermod"), w, r, user) return c.LocalError(p.GetErrorPhrase("panel_groups_create_cannot_designate_supermod"), w, r, user)
} }
isMod = true isMod = true
} else if groupType == "Banned" { case "Banned":
isBanned = true isBanned = true
} }
} }

View File

@ -25,7 +25,7 @@ func WordFilters(w http.ResponseWriter, r *http.Request, user c.User) c.RouteErr
} }
pi := c.PanelPage{basePage, tList, filterList} pi := c.PanelPage{basePage, tList, filterList}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage,"","","panel_word_filters",&pi}) return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_word_filters", &pi})
} }
func WordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { func WordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
@ -36,23 +36,23 @@ func WordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User
if !user.Perms.EditSettings { if !user.Perms.EditSettings {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, user)
} }
isJs := (r.PostFormValue("js") == "1") js := (r.PostFormValue("js") == "1")
// ? - We're not doing a full sanitise here, as it would be useful if admins were able to put down rules for replacing things with HTML, etc. // ? - We're not doing a full sanitise here, as it would be useful if admins were able to put down rules for replacing things with HTML, etc.
find := strings.TrimSpace(r.PostFormValue("find")) find := strings.TrimSpace(r.PostFormValue("find"))
if find == "" { if find == "" {
return c.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, isJs) return c.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, js)
} }
// Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement
replacement := strings.TrimSpace(r.PostFormValue("replacement")) replace := strings.TrimSpace(r.PostFormValue("replacement"))
err := c.WordFilters.Create(find, replacement) err := c.WordFilters.Create(find, replace)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
return successRedirect("/panel/settings/word-filters/", w, r, isJs) return successRedirect("/panel/settings/word-filters/", w, r, js)
} }
// TODO: Implement this as a non-JS fallback // TODO: Implement this as a non-JS fallback
@ -67,7 +67,7 @@ func WordFiltersEdit(w http.ResponseWriter, r *http.Request, user c.User, wfid s
_ = wfid _ = wfid
pi := c.PanelPage{basePage, tList, nil} pi := c.PanelPage{basePage, tList, nil}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage,"","","panel_word_filters_edit",&pi}) return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_word_filters_edit", &pi})
} }
func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, wfid string) c.RouteError { func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, wfid string) c.RouteError {
@ -75,28 +75,26 @@ func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User,
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
// TODO: Either call it isJs or js rather than flip-flopping back and forth across the routes x.x js := (r.PostFormValue("js") == "1")
isJs := (r.PostFormValue("isJs") == "1")
if !user.Perms.EditSettings { if !user.Perms.EditSettings {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
id, err := strconv.Atoi(wfid) id, err := strconv.Atoi(wfid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, isJs) return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, js)
} }
find := strings.TrimSpace(r.PostFormValue("find")) find := strings.TrimSpace(r.PostFormValue("find"))
if find == "" { if find == "" {
return c.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, isJs) return c.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, js)
} }
// Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement
replacement := strings.TrimSpace(r.PostFormValue("replacement")) replace := strings.TrimSpace(r.PostFormValue("replacement"))
err = c.WordFilters.Update(id, find, replacement) err = c.WordFilters.Update(id, find, replace)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther)
@ -109,19 +107,19 @@ func WordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User
return ferr return ferr
} }
isJs := (r.PostFormValue("isJs") == "1") js := (r.PostFormValue("js") == "1")
if !user.Perms.EditSettings { if !user.Perms.EditSettings {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
id, err := strconv.Atoi(wfid) id, err := strconv.Atoi(wfid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, isJs) return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, js)
} }
err = c.WordFilters.Delete(id) err = c.WordFilters.Delete(id)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalErrorJSQ("This word filter doesn't exist", w, r, user, isJs) return c.LocalErrorJSQ("This word filter doesn't exist", w, r, user, js)
} }
http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther)

View File

@ -118,7 +118,7 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
} }
// Make sure the indices are sequential to avoid out of bounds issues // Make sure the indices are sequential to avoid out of bounds issues
var seqPollInputItems = make(map[int]string) seqPollInputItems := make(map[int]string)
for i := 0; i < len(pollInputItems); i++ { for i := 0; i < len(pollInputItems); i++ {
seqPollInputItems[i] = pollInputItems[i] seqPollInputItems[i] = pollInputItems[i]
} }
@ -282,24 +282,24 @@ func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid s
// TODO: Refactor this // TODO: Refactor this
// TODO: Disable stat updates in posts handled by plugin_guilds // TODO: Disable stat updates in posts handled by plugin_guilds
func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid string) c.RouteError { func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid string) c.RouteError {
isJs := (r.PostFormValue("isJs") == "1") js := (r.PostFormValue("js") == "1")
rid, err := strconv.Atoi(srid) rid, err := strconv.Atoi(srid)
if err != nil { if err != nil {
return c.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, isJs) return c.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, js)
} }
reply, err := c.Rstore.Get(rid) reply, err := c.Rstore.Get(rid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The reply you tried to delete doesn't exist.", w, r, isJs) return c.PreErrorJSQ("The reply you tried to delete doesn't exist.", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
topic, err := c.Topics.Get(reply.ParentID) topic, err := c.Topics.Get(reply.ParentID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The parent topic doesn't exist.", w, r, isJs) return c.PreErrorJSQ("The parent topic doesn't exist.", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// TODO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
@ -308,12 +308,12 @@ func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid
return ferr return ferr
} }
if !user.Perms.ViewTopic || !user.Perms.DeleteReply { if !user.Perms.ViewTopic || !user.Perms.DeleteReply {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
err = reply.Delete() err = reply.Delete()
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
skip, rerr := lite.Hooks.VhookSkippable("action_end_delete_reply", reply.ID, &user) skip, rerr := lite.Hooks.VhookSkippable("action_end_delete_reply", reply.ID, &user)
@ -322,7 +322,7 @@ func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid
} }
//log.Printf("Reply #%d was deleted by c.User #%d", rid, user.ID) //log.Printf("Reply #%d was deleted by c.User #%d", rid, user.ID)
if !isJs { if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther)
} else { } else {
w.Write(successJSONBytes) w.Write(successJSONBytes)
@ -334,15 +334,15 @@ func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid
wcount := c.WordCount(reply.Content) wcount := c.WordCount(reply.Content)
err = replyCreator.DecreasePostStats(wcount, false) err = replyCreator.DecreasePostStats(wcount, false)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
} else if err != sql.ErrNoRows { } else if err != sql.ErrNoRows {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.ModLogs.Create("delete", reply.ParentID, "reply", user.LastIP, user.ID) err = c.ModLogs.Create("delete", reply.ParentID, "reply", user.LastIP, user.ID)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
return nil return nil
} }
@ -501,34 +501,34 @@ func ProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user c.Use
} }
func ProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid string) c.RouteError { func ProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid string) c.RouteError {
isJs := (r.PostFormValue("js") == "1") js := (r.PostFormValue("js") == "1")
rid, err := strconv.Atoi(srid) rid, err := strconv.Atoi(srid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, isJs) return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, js)
} }
reply, err := c.Prstore.Get(rid) reply, err := c.Prstore.Get(rid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The target reply doesn't exist.", w, r, isJs) return c.PreErrorJSQ("The target reply doesn't exist.", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
creator, err := c.Users.Get(reply.CreatedBy) creator, err := c.Users.Get(reply.CreatedBy)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// ? Does the admin understand that this group perm affects this? // ? Does the admin understand that this group perm affects this?
if user.ID != creator.ID && !user.Perms.EditReply { if user.ID != creator.ID && !user.Perms.EditReply {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
err = reply.SetBody(r.PostFormValue("edit_item")) err = reply.SetBody(r.PostFormValue("edit_item"))
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
if !isJs { if !js {
http.Redirect(w, r, "/user/"+strconv.Itoa(creator.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther) http.Redirect(w, r, "/user/"+strconv.Itoa(creator.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther)
} else { } else {
w.Write(successJSONBytes) w.Write(successJSONBytes)
@ -537,35 +537,34 @@ func ProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user c.User,
} }
func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid string) c.RouteError { func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid string) c.RouteError {
isJs := (r.PostFormValue("isJs") == "1") js := (r.PostFormValue("js") == "1")
rid, err := strconv.Atoi(srid) rid, err := strconv.Atoi(srid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, isJs) return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, js)
} }
reply, err := c.Prstore.Get(rid) reply, err := c.Prstore.Get(rid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The target reply doesn't exist.", w, r, isJs) return c.PreErrorJSQ("The target reply doesn't exist.", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
creator, err := c.Users.Get(reply.CreatedBy) creator, err := c.Users.Get(reply.CreatedBy)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
if user.ID != creator.ID && !user.Perms.DeleteReply { if user.ID != creator.ID && !user.Perms.DeleteReply {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
err = reply.Delete() err = reply.Delete()
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
//log.Printf("The profile post '%d' was deleted by c.User #%d", reply.ID, user.ID) //log.Printf("The profile post '%d' was deleted by c.User #%d", reply.ID, user.ID)
if !isJs { if !js {
//http.Redirect(w,r, "/user/" + strconv.Itoa(creator.ID), http.StatusSeeOther) //http.Redirect(w,r, "/user/" + strconv.Itoa(creator.ID), http.StatusSeeOther)
} else { } else {
w.Write(successJSONBytes) w.Write(successJSONBytes)
@ -574,25 +573,25 @@ func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.Use
} }
func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid string) c.RouteError { func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid string) c.RouteError {
isJs := (r.PostFormValue("isJs") == "1") js := (r.PostFormValue("js") == "1")
rid, err := strconv.Atoi(srid) rid, err := strconv.Atoi(srid)
if err != nil { if err != nil {
return c.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, isJs) return c.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, js)
} }
reply, err := c.Rstore.Get(rid) reply, err := c.Rstore.Get(rid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("You can't like something which doesn't exist!", w, r, isJs) return c.PreErrorJSQ("You can't like something which doesn't exist!", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
topic, err := c.Topics.Get(reply.ParentID) topic, err := c.Topics.Get(reply.ParentID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The parent topic doesn't exist.", w, r, isJs) return c.PreErrorJSQ("The parent topic doesn't exist.", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// TODO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
@ -601,31 +600,31 @@ func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid s
return ferr return ferr
} }
if !user.Perms.ViewTopic || !user.Perms.LikeItem { if !user.Perms.ViewTopic || !user.Perms.LikeItem {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
if reply.CreatedBy == user.ID { if reply.CreatedBy == user.ID {
return c.LocalErrorJSQ("You can't like your own replies", w, r, user, isJs) return c.LocalErrorJSQ("You can't like your own replies", w, r, user, js)
} }
_, err = c.Users.Get(reply.CreatedBy) _, err = c.Users.Get(reply.CreatedBy)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return c.LocalErrorJSQ("The target user doesn't exist", w, r, user, isJs) return c.LocalErrorJSQ("The target user doesn't exist", w, r, user, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
err = reply.Like(user.ID) err = reply.Like(user.ID)
if err == c.ErrAlreadyLiked { if err == c.ErrAlreadyLiked {
return c.LocalErrorJSQ("You've already liked this!", w, r, user, isJs) return c.LocalErrorJSQ("You've already liked this!", w, r, user, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// ! Be careful about leaking per-route permission state with &user // ! Be careful about leaking per-route permission state with &user
alert := c.Alert{ActorID: user.ID, TargetUserID: reply.CreatedBy, Event: "like", ElementType: "post", ElementID: rid, Actor: &user} alert := c.Alert{ActorID: user.ID, TargetUserID: reply.CreatedBy, Event: "like", ElementType: "post", ElementID: rid, Actor: &user}
err = c.AddActivityAndNotifyTarget(alert) err = c.AddActivityAndNotifyTarget(alert)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
skip, rerr := lite.Hooks.VhookSkippable("action_end_like_reply", reply.ID, &user) skip, rerr := lite.Hooks.VhookSkippable("action_end_like_reply", reply.ID, &user)
@ -633,7 +632,7 @@ func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid s
return rerr return rerr
} }
if !isJs { if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther)
} else { } else {
_, _ = w.Write(successJSONBytes) _, _ = w.Write(successJSONBytes)

View File

@ -14,7 +14,7 @@ func ReportSubmit(w http.ResponseWriter, r *http.Request, user c.User, sitemID s
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
isJs := (r.PostFormValue("isJs") == "1") js := (r.PostFormValue("js") == "1")
itemID, err := strconv.Atoi(sitemID) itemID, err := strconv.Atoi(sitemID)
if err != nil { if err != nil {
@ -104,7 +104,7 @@ func ReportSubmit(w http.ResponseWriter, r *http.Request, user c.User, sitemID s
} }
counters.PostCounter.Bump() counters.PostCounter.Bump()
if !isJs { if !js {
// TODO: Redirect back to where we came from // TODO: Redirect back to where we came from
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} else { } else {

View File

@ -508,17 +508,17 @@ func uploadFilesWithHash(w http.ResponseWriter, r *http.Request, user c.User, di
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes // TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
// TODO: Disable stat updates in posts handled by plugin_guilds // TODO: Disable stat updates in posts handled by plugin_guilds
func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid string) c.RouteError { func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid string) c.RouteError {
isJs := (r.PostFormValue("js") == "1") js := (r.PostFormValue("js") == "1")
tid, err := strconv.Atoi(stid) tid, err := strconv.Atoi(stid)
if err != nil { if err != nil {
return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, isJs) return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, js)
} }
topic, err := c.Topics.Get(tid) topic, err := c.Topics.Get(tid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The topic you tried to edit doesn't exist.", w, r, isJs) return c.PreErrorJSQ("The topic you tried to edit doesn't exist.", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// TODO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
@ -527,10 +527,10 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
return ferr return ferr
} }
if !user.Perms.ViewTopic || !user.Perms.EditTopic { if !user.Perms.ViewTopic || !user.Perms.EditTopic {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
if topic.IsClosed && !user.Perms.CloseTopic { if topic.IsClosed && !user.Perms.CloseTopic {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
err = topic.Update(r.PostFormValue("topic_name"), r.PostFormValue("topic_content")) err = topic.Update(r.PostFormValue("topic_name"), r.PostFormValue("topic_content"))
@ -538,26 +538,26 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
if err != nil { if err != nil {
switch err { switch err {
case c.ErrNoTitle: case c.ErrNoTitle:
return c.LocalErrorJSQ("This topic doesn't have a title", w, r, user, isJs) return c.LocalErrorJSQ("This topic doesn't have a title", w, r, user, js)
case c.ErrLongTitle: case c.ErrLongTitle:
return c.LocalErrorJSQ("The length of the title is too long, max: "+strconv.Itoa(c.Config.MaxTopicTitleLength), w, r, user, isJs) return c.LocalErrorJSQ("The length of the title is too long, max: "+strconv.Itoa(c.Config.MaxTopicTitleLength), w, r, user, js)
case c.ErrNoBody: case c.ErrNoBody:
return c.LocalErrorJSQ("This topic doesn't have a body", w, r, user, isJs) return c.LocalErrorJSQ("This topic doesn't have a body", w, r, user, js)
} }
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.Forums.UpdateLastTopic(topic.ID, user.ID, topic.ParentID) err = c.Forums.UpdateLastTopic(topic.ID, user.ID, topic.ParentID)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// TODO: Avoid the load to get this faster? // TODO: Avoid the load to get this faster?
topic, err = c.Topics.Get(topic.ID) topic, err = c.Topics.Get(topic.ID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The updated topic doesn't exist.", w, r, isJs) return c.PreErrorJSQ("The updated topic doesn't exist.", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
skip, rerr := lite.Hooks.VhookSkippable("action_end_edit_topic", topic.ID, &user) skip, rerr := lite.Hooks.VhookSkippable("action_end_edit_topic", topic.ID, &user)
@ -565,12 +565,12 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
return rerr return rerr
} }
if !isJs { if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
} else { } else {
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(topic.Content, topic.ParentID, "forums")}) outBytes, err := json.Marshal(JsonReply{c.ParseMessage(topic.Content, topic.ParentID, "forums")})
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
w.Write(outBytes) w.Write(outBytes)
} }
@ -582,7 +582,7 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
func DeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { func DeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
// TODO: Move this to some sort of middleware // TODO: Move this to some sort of middleware
var tids []int var tids []int
var isJs = false js := false
if c.ReqIsJson(r) { if c.ReqIsJson(r) {
if r.Body == nil { if r.Body == nil {
return c.PreErrorJS("No request body", w, r) return c.PreErrorJS("No request body", w, r)
@ -591,7 +591,7 @@ func DeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
if err != nil { if err != nil {
return c.PreErrorJS("We weren't able to parse your data", w, r) return c.PreErrorJS("We weren't able to parse your data", w, r)
} }
isJs = true js = true
} else { } else {
tid, err := strconv.Atoi(r.URL.Path[len("/topic/delete/submit/"):]) tid, err := strconv.Atoi(r.URL.Path[len("/topic/delete/submit/"):])
if err != nil { if err != nil {
@ -600,15 +600,15 @@ func DeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
tids = append(tids, tid) tids = append(tids, tid)
} }
if len(tids) == 0 { if len(tids) == 0 {
return c.LocalErrorJSQ("You haven't provided any IDs", w, r, user, isJs) return c.LocalErrorJSQ("You haven't provided any IDs", w, r, user, js)
} }
for _, tid := range tids { for _, tid := range tids {
topic, err := c.Topics.Get(tid) topic, err := c.Topics.Get(tid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The topic you tried to delete doesn't exist.", w, r, isJs) return c.PreErrorJSQ("The topic you tried to delete doesn't exist.", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// TODO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
@ -617,24 +617,24 @@ func DeleteTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
return ferr return ferr
} }
if !user.Perms.ViewTopic || !user.Perms.DeleteTopic { if !user.Perms.ViewTopic || !user.Perms.DeleteTopic {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
// We might be able to handle this err better // We might be able to handle this err better
err = topic.Delete() err = topic.Delete()
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.ModLogs.Create("delete", tid, "topic", user.LastIP, user.ID) err = c.ModLogs.Create("delete", tid, "topic", user.LastIP, user.ID)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// ? - We might need to add soft-delete before we can do an action reply for this // ? - We might need to add soft-delete before we can do an action reply for this
/*_, err = stmts.createActionReply.Exec(tid,"delete",ipaddress,user.ID) /*_, err = stmts.createActionReply.Exec(tid,"delete",ipaddress,user.ID)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err,w,r,isJs) return c.InternalErrorJSQ(err,w,r,js)
}*/ }*/
// TODO: Do a bulk delete action hook? // TODO: Do a bulk delete action hook?
@ -712,7 +712,7 @@ func UnstickTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, sti
func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
// TODO: Move this to some sort of middleware // TODO: Move this to some sort of middleware
var tids []int var tids []int
var isJs = false js := false
if c.ReqIsJson(r) { if c.ReqIsJson(r) {
if r.Body == nil { if r.Body == nil {
return c.PreErrorJS("No request body", w, r) return c.PreErrorJS("No request body", w, r)
@ -721,7 +721,7 @@ func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
if err != nil { if err != nil {
return c.PreErrorJS("We weren't able to parse your data", w, r) return c.PreErrorJS("We weren't able to parse your data", w, r)
} }
isJs = true js = true
} else { } else {
tid, err := strconv.Atoi(r.URL.Path[len("/topic/lock/submit/"):]) tid, err := strconv.Atoi(r.URL.Path[len("/topic/lock/submit/"):])
if err != nil { if err != nil {
@ -730,15 +730,15 @@ func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
tids = append(tids, tid) tids = append(tids, tid)
} }
if len(tids) == 0 { if len(tids) == 0 {
return c.LocalErrorJSQ("You haven't provided any IDs", w, r, user, isJs) return c.LocalErrorJSQ("You haven't provided any IDs", w, r, user, js)
} }
for _, tid := range tids { for _, tid := range tids {
topic, err := c.Topics.Get(tid) topic, err := c.Topics.Get(tid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The topic you tried to lock doesn't exist.", w, r, isJs) return c.PreErrorJSQ("The topic you tried to lock doesn't exist.", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// TODO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
@ -747,17 +747,17 @@ func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Rout
return ferr return ferr
} }
if !user.Perms.ViewTopic || !user.Perms.CloseTopic { if !user.Perms.ViewTopic || !user.Perms.CloseTopic {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
err = topic.Lock() err = topic.Lock()
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
err = addTopicAction("lock", topic, user) err = addTopicAction("lock", topic, user)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// TODO: Do a bulk lock action hook? // TODO: Do a bulk lock action hook?
@ -863,17 +863,17 @@ func addTopicAction(action string, topic *c.Topic, user c.User) error {
// TODO: Refactor this // TODO: Refactor this
func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid string) c.RouteError { func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid string) c.RouteError {
isJs := (r.PostFormValue("isJs") == "1") js := (r.PostFormValue("js") == "1")
tid, err := strconv.Atoi(stid) tid, err := strconv.Atoi(stid)
if err != nil { if err != nil {
return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, isJs) return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, js)
} }
topic, err := c.Topics.Get(tid) topic, err := c.Topics.Get(tid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The requested topic doesn't exist.", w, r, isJs) return c.PreErrorJSQ("The requested topic doesn't exist.", w, r, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// TODO: Add hooks to make use of headerLite // TODO: Add hooks to make use of headerLite
@ -882,32 +882,32 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
return ferr return ferr
} }
if !user.Perms.ViewTopic || !user.Perms.LikeItem { if !user.Perms.ViewTopic || !user.Perms.LikeItem {
return c.NoPermissionsJSQ(w, r, user, isJs) return c.NoPermissionsJSQ(w, r, user, js)
} }
if topic.CreatedBy == user.ID { if topic.CreatedBy == user.ID {
return c.LocalErrorJSQ("You can't like your own topics", w, r, user, isJs) return c.LocalErrorJSQ("You can't like your own topics", w, r, user, js)
} }
_, err = c.Users.Get(topic.CreatedBy) _, err = c.Users.Get(topic.CreatedBy)
if err != nil && err == sql.ErrNoRows { if err != nil && err == sql.ErrNoRows {
return c.LocalErrorJSQ("The target user doesn't exist", w, r, user, isJs) return c.LocalErrorJSQ("The target user doesn't exist", w, r, user, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
score := 1 score := 1
err = topic.Like(score, user.ID) err = topic.Like(score, user.ID)
if err == c.ErrAlreadyLiked { if err == c.ErrAlreadyLiked {
return c.LocalErrorJSQ("You already liked this", w, r, user, isJs) return c.LocalErrorJSQ("You already liked this", w, r, user, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
// ! Be careful about leaking per-route permission state with &user // ! Be careful about leaking per-route permission state with &user
alert := c.Alert{ActorID: user.ID, TargetUserID: topic.CreatedBy, Event: "like", ElementType: "topic", ElementID: tid, Actor: &user} alert := c.Alert{ActorID: user.ID, TargetUserID: topic.CreatedBy, Event: "like", ElementType: "topic", ElementID: tid, Actor: &user}
err = c.AddActivityAndNotifyTarget(alert) err = c.AddActivityAndNotifyTarget(alert)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, isJs) return c.InternalErrorJSQ(err, w, r, js)
} }
skip, rerr := lite.Hooks.VhookSkippable("action_end_like_topic", topic.ID, &user) skip, rerr := lite.Hooks.VhookSkippable("action_end_like_topic", topic.ID, &user)
@ -915,7 +915,7 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
return rerr return rerr
} }
if !isJs { if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
} else { } else {
_, _ = w.Write(successJSONBytes) _, _ = w.Write(successJSONBytes)

View File

@ -20,7 +20,7 @@
<div class="formitem"><textarea form="quick_post_form" class="large" id="topic_content" name="topic-content" placeholder="{{lang "create_topic_placeholder"}}" required></textarea></div> <div class="formitem"><textarea form="quick_post_form" class="large" id="topic_content" name="topic-content" placeholder="{{lang "create_topic_placeholder"}}" required></textarea></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<button form="quick_post_form" name="topic-button" class="formbutton">{{lang "create_topic_create_topic_button"}}</button> <button form="quick_post_form" name="topic-button" class="formbutton">{{lang "create_topic_create_button"}}</button>
{{if .CurrentUser.Perms.UploadFiles}} {{if .CurrentUser.Perms.UploadFiles}}
<input name="quick_topic_upload_files" form="quick_post_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" /> <input name="quick_topic_upload_files" form="quick_post_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" />
<label for="quick_topic_upload_files" class="formbutton add_file_button">{{lang "create_topic_add_file_button"}}</label>{{end}} <label for="quick_topic_upload_files" class="formbutton add_file_button">{{lang "create_topic_add_file_button"}}</label>{{end}}

View File

@ -1,22 +1,20 @@
</div> </div>
<aside class="midRight sidebar">{{dock "rightSidebar" .Header }}</aside> <aside class="midRight sidebar">{{dock "rightSidebar" .Header}}</aside>
</div> </div>
<div class="footBlock"> <div class="footBlock">
<div class="footLeft"></div> <div class="footLeft"></div>
<div class="footer"> <div class="footer">
{{dock "footer" .Header }} {{dock "footer" .Header}}
<div id="poweredByHolder" class="footerBit"> <div id="poweredByHolder" class="footerBit">
<div id="poweredBy"> <div id="poweredBy">
<a id="poweredByName" href="https://github.com/Azareal/Gosora">{{lang "footer_powered_by"}}</a><span id="poweredByDash"> - </span><span id="poweredByMaker">{{lang "footer_made_with_love"}}</span> <a id="poweredByName" href="https://github.com/Azareal/Gosora">{{lang "footer_powered_by"}}</a><span id="poweredByDash"> - </span><span id="poweredByMaker">{{lang "footer_made_with_love"}}</span>
</div> </div>
{{if .CurrentUser.IsAdmin}}<div title="start to before tmpl" class="elapsed">{{.Header.Elapsed1}}</div><div title="start to footer" class="elapsed">{{elapsed .Header.StartedAt}}</div>{{end}} {{/**{{if .CurrentUser.IsAdmin}}**/}}<div title="start to before tmpl" class="elapsed">{{.Header.Elapsed1}}</div><div title="start to footer" class="elapsed">{{elapsed .Header.StartedAt}}</div>{{/**{{end}}**/}}
<form action="/theme/" method="post"> <form action="/theme/" method="post">
<div id="themeSelector"> <div id="themeSelector">
<select id="themeSelectorSelect" name="themeSelector" aria-label="{{lang "footer_theme_selector_aria"}}"> <select id="themeSelectorSelect" name="themeSelector" aria-label="{{lang "footer_theme_selector_aria"}}">{{range .Header.Themes}}
{{range .Header.Themes}}
{{if not .HideFromThemes}}<option val="{{.Name}}"{{if eq $.Header.Theme.Name .Name}} selected{{end}}>{{.FriendlyName}}</option>{{end}} {{if not .HideFromThemes}}<option val="{{.Name}}"{{if eq $.Header.Theme.Name .Name}} selected{{end}}>{{.FriendlyName}}</option>{{end}}
{{end}} {{end}}</select>
</select>
<noscript><input type="submit" /></noscript> <noscript><input type="submit" /></noscript>
</div> </div>
</form> </form>

View File

@ -24,12 +24,12 @@
{{if .CurrentUser.Perms.EditGroup}} {{if .CurrentUser.Perms.EditGroup}}
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>Add Promotion</h1></div> <div class="rowitem"><h1>{{lang "panel_group_promotions_create_head"}}</h1></div>
</div> </div>
<div class="colstack_item the_form"> <div class="colstack_item the_form">
<form action="/panel/groups/promotions/create/submit/{{.ID}}?s={{.CurrentUser.Session}}" method="post"> <form action="/panel/groups/promotions/create/submit/{{.ID}}?s={{.CurrentUser.Session}}" method="post">
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>From</a></div> <div class="formitem formlabel"><a>{{lang "panel_group_promotions_from"}}</a></div>
<div class="formitem"> <div class="formitem">
<select name="from"> <select name="from">
{{range .Groups}}<option value="{{.ID}}">{{.Name}}</option>{{end}} {{range .Groups}}<option value="{{.ID}}">{{.Name}}</option>{{end}}
@ -37,7 +37,7 @@
</div> </div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>To</a></div> <div class="formitem formlabel"><a>{{lang "panel_group_promotions_to"}}</a></div>
<div class="formitem"> <div class="formitem">
<select name="to"> <select name="to">
{{range .Groups}}<option value="{{.ID}}">{{.Name}}</option>{{end}} {{range .Groups}}<option value="{{.ID}}">{{.Name}}</option>{{end}}
@ -45,7 +45,7 @@
</div> </div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>Two Way</a></div> <div class="formitem formlabel"><a>{{lang "panel_group_promotions_two_way"}}</a></div>
<div class="formitem"> <div class="formitem">
<select name="two-way" disabled> <select name="two-way" disabled>
<option value=1>{{lang "option_yes"}}</option> <option value=1>{{lang "option_yes"}}</option>
@ -54,11 +54,11 @@
</div> </div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>Level</a></div> <div class="formitem formlabel"><a>{{lang "panel_group_promotions_level"}}</a></div>
<div class="formitem"><input name="level" type="number" value=0 /></div> <div class="formitem"><input name="level" type="number" value=0 /></div>
</div> </div>
<div class="formrow form_button_row"> <div class="formrow form_button_row">
<div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_groups_create_create_group_button"}}</button></div> <div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_group_promotions_create_button"}}</button></div>
</div> </div>
</form> </form>
</div> </div>

View File

@ -34,7 +34,7 @@
<div class="formitem"><input name="replacement" type="text" placeholder="{{lang "panel_word_filters_create_replacement_placeholder"}}" /></div> <div class="formitem"><input name="replacement" type="text" placeholder="{{lang "panel_word_filters_create_replacement_placeholder"}}" /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton form_middle_button">{{lang "panel_word_filters_create_create_word_filter_button"}}</button></div> <div class="formitem"><button name="panel-button" class="formbutton form_middle_button">{{lang "panel_word_filters_create_button"}}</button></div>
</div> </div>
</form> </form>
</div> </div>

View File

@ -15,7 +15,7 @@
</div> </div>
<div class="formrow quick_button_row"> <div class="formrow quick_button_row">
<div class="formitem"> <div class="formitem">
<button form="quick_post_form" class="formbutton">{{lang "quick_topic.create_topic_button"}}</button> <button form="quick_post_form" class="formbutton">{{lang "quick_topic.create_button"}}</button>
<button form="quick_post_form" class="formbutton" id="add_poll_button">{{lang "quick_topic.add_poll_button"}}</button> <button form="quick_post_form" class="formbutton" id="add_poll_button">{{lang "quick_topic.add_poll_button"}}</button>
{{if .CurrentUser.Perms.UploadFiles}} {{if .CurrentUser.Perms.UploadFiles}}
<input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" class="auto_hide" /> <input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" class="auto_hide" />

View File

@ -486,7 +486,7 @@ h2 {
white-space: nowrap; white-space: nowrap;
} }
.topic_list_title_block .create_topic_opt a:before { .topic_list_title_block .create_topic_opt a:before {
content: "{{lang "quick_topic.create_topic_button" . }}"; content: "{{lang "quick_topic.create_button" . }}";
} }
.topic_list_title_block .mod_opt a:before { .topic_list_title_block .mod_opt a:before {
content: "{{lang "topic_list.moderate" . }}"; content: "{{lang "topic_list.moderate" . }}";
@ -1312,7 +1312,7 @@ input[type=checkbox]:checked + label .sel {
display: none; display: none;
} }
.topic_list_title_block .create_topic_opt a:before { .topic_list_title_block .create_topic_opt a:before {
content: "{{lang "quick_topic.create_topic_button_short" . }}"; content: "{{lang "quick_topic.create_button_short" . }}";
} }
.topic_list_title_block .mod_opt a:before { .topic_list_title_block .mod_opt a:before {
content: "{{lang "topic_list.moderate_short" . }}"; content: "{{lang "topic_list.moderate_short" . }}";