Add CreateProfileReply and AutoEmbed group permissions.

Log profile reply deletions in the moderator log.
Split the global permissions in the UI to make them easier to manage.
Experiment with showing group ID in group edit header.
Avoid loading groups multiple times for the same profile reply.

Initialise disabled IP log points to empty string rather than 0.

Add CreateProfileReply perm phrase.
Add AutoEmbed perm phrase.
Add panel_group_mod_permissions phrase.
Add panel_logs_mod_action_profile_reply_delete phrase.
This commit is contained in:
Azareal 2020-02-04 21:47:03 +10:00
parent 2178efb8f0
commit 0c1d6f0516
26 changed files with 215 additions and 135 deletions

View File

@ -118,6 +118,8 @@ func seedTables(a qgen.Adapter) error {
UploadFiles
UploadAvatars
UseConvos
CreateProfileReply
AutoEmbed
// CreateConvo ?
// CreateConvoReply ?
@ -142,7 +144,7 @@ func seedTables(a qgen.Adapter) error {
}
return string(jBytes)
}
addGroup := func(name string, perms c.Perms, mod bool, admin bool, banned bool, tag string) {
addGroup := func(name string, perms c.Perms, mod, admin, banned bool, tag string) {
mi, ai, bi := "0", "0", "0"
if mod {
mi = "1"
@ -161,10 +163,10 @@ func seedTables(a qgen.Adapter) error {
perms.EditGroupAdmin = false
addGroup("Administrator", perms, true, true, false, "Admin")
perms = c.Perms{BanUsers: true, ActivateUsers: true, EditUser: true, EditUserEmail: false, EditUserGroup: true, ViewIPs: true, UploadFiles: true, UploadAvatars: true, UseConvos: true, ViewTopic: true, LikeItem: true, CreateTopic: true, EditTopic: true, DeleteTopic: true, CreateReply: true, EditReply: true, DeleteReply: true, PinTopic: true, CloseTopic: true, MoveTopic: true}
perms = c.Perms{BanUsers: true, ActivateUsers: true, EditUser: true, EditUserEmail: false, EditUserGroup: true, ViewIPs: true, UploadFiles: true, UploadAvatars: true, UseConvos: true, CreateProfileReply: true, AutoEmbed: true, ViewTopic: true, LikeItem: true, CreateTopic: true, EditTopic: true, DeleteTopic: true, CreateReply: true, EditReply: true, DeleteReply: true, PinTopic: true, CloseTopic: true, MoveTopic: true}
addGroup("Moderator", perms, true, false, false, "Mod")
perms = c.Perms{UploadFiles: true, UploadAvatars: true, UseConvos: true, ViewTopic: true, LikeItem: true, CreateTopic: true, CreateReply: true}
perms = c.Perms{UploadFiles: true, UploadAvatars: true, UseConvos: true, CreateProfileReply: true, AutoEmbed: true, ViewTopic: true, LikeItem: true, CreateTopic: true, CreateReply: true}
addGroup("Member", perms, false, false, false, "")
perms = c.Perms{ViewTopic: true}

View File

@ -594,6 +594,7 @@ type PanelEditGroupPermsPage struct {
Name string
LocalPerms []NameLangToggle
GlobalPerms []NameLangToggle
ModPerms []NameLangToggle
}
type GroupPromotionExtend struct {

View File

@ -465,19 +465,25 @@ func (ps *ParseSettings) CopyPtr() *ParseSettings {
// TODO: Write a test for this
// TODO: We need a lot more hooks here. E.g. To add custom media types and handlers.
// TODO: Use templates to reduce the amount of boilerplate?
func ParseMessage(msg string, sectionID int, sectionType string, settings *ParseSettings /*, user User*/) string {
func ParseMessage(msg string, sectionID int, sectionType string, settings *ParseSettings, user *User) string {
if settings == nil {
settings = DefaultParseSettings
}
if user == nil {
user = &GuestUser
}
// TODO: Word boundary detection for these to avoid mangling code
msg = strings.Replace(msg, ":)", "😀", -1)
msg = strings.Replace(msg, ":(", "😞", -1)
msg = strings.Replace(msg, ":D", "😃", -1)
msg = strings.Replace(msg, ":P", "😛", -1)
msg = strings.Replace(msg, ":O", "😲", -1)
msg = strings.Replace(msg, ":p", "😛", -1)
msg = strings.Replace(msg, ":o", "😲", -1)
msg = strings.Replace(msg, ";)", "😉", -1)
rep := func(find, replace string) {
msg = strings.Replace(msg, find, replace, -1)
}
rep(":)", "😀")
rep(":(", "😞")
rep(":D", "😃")
rep(":P", "😛")
rep(":O", "😲")
rep(":p", "😛")
rep(":o", "😲")
rep(";)", "😉")
// Word filter list. E.g. Swear words and other things the admins don't like
wordFilters, err := WordFilters.GetAll()

View File

@ -40,6 +40,8 @@ var GlobalPermList = []string{
"UploadFiles",
"UploadAvatars",
"UseConvos",
"CreateProfileReply",
"AutoEmbed",
}
// Permission Structure: ActionComponent[Subcomponent]Flag
@ -69,6 +71,8 @@ type Perms struct {
UploadFiles bool `json:",omitempty"`
UploadAvatars bool `json:",omitempty"`
UseConvos bool `json:",omitempty"`
CreateProfileReply bool `json:",omitempty"`
AutoEmbed bool `json:",omitempty"`
// Forum permissions
ViewTopic bool `json:",omitempty"`
@ -125,6 +129,8 @@ func init() {
UploadFiles: true,
UploadAvatars: true,
UseConvos: true,
CreateProfileReply: true,
AutoEmbed: true,
ViewTopic: true,
LikeItem: true,

View File

@ -65,7 +65,7 @@ func (r *ProfileReply) Delete() error {
func (r *ProfileReply) SetBody(content string) error {
content = PreparseMessage(html.UnescapeString(content))
_, err := profileReplyStmts.edit.Exec(content, ParseMessage(content, 0, "", nil), r.ID)
_, err := profileReplyStmts.edit.Exec(content, ParseMessage(content, 0, "", nil, nil), r.ID)
return err
}

View File

@ -50,9 +50,9 @@ func (s *SQLProfileReplyStore) Exists(id int) bool {
func (s *SQLProfileReplyStore) Create(profileID int, content string, createdBy int, ip string) (id int, err error) {
if Config.DisablePostIP {
ip = "0"
ip = ""
}
res, err := s.create.Exec(profileID, content, ParseMessage(content, 0, "", nil), createdBy, ip)
res, err := s.create.Exec(profileID, content, ParseMessage(content, 0, "", nil, nil), createdBy, ip)
if err != nil {
return 0, err
}

View File

@ -199,7 +199,7 @@ func (r *Reply) SetPost(content string) error {
return err
}
content = PreparseMessage(html.UnescapeString(content))
parsedContent := ParseMessage(content, topic.ParentID, "forums", nil)
parsedContent := ParseMessage(content, topic.ParentID, "forums", nil, nil)
_, err = replyStmts.edit.Exec(content, parsedContent, r.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll
_ = Rstore.GetCache().Remove(r.ID)
return err

View File

@ -99,9 +99,9 @@ func (s *SQLReplyStore) Exists(id int) bool {
// TODO: Write a test for this
func (s *SQLReplyStore) Create(t *Topic, content, ip string, uid int) (rid int, err error) {
if Config.DisablePostIP {
ip = "0"
ip = ""
}
res, err := s.create.Exec(t.ID, content, ParseMessage(content, t.ParentID, "forums", nil), ip, WordCount(content), uid)
res, err := s.create.Exec(t.ID, content, ParseMessage(content, t.ParentID, "forums", nil, nil), ip, WordCount(content), uid)
if err != nil {
return 0, err
}

View File

@ -46,9 +46,9 @@ func (s *DefaultReportStore) Create(title, content string, u *User, itemType str
ip := u.GetIP()
if Config.DisablePostIP {
ip = "0"
ip = ""
}
res, err := s.create.Exec(title, content, ParseMessage(content, 0, "", nil), ip, u.ID, u.ID, itemType+"_"+strconv.Itoa(itemID), ReportForumID)
res, err := s.create.Exec(title, content, ParseMessage(content, 0, "", nil, nil), ip, u.ID, u.ID, itemType+"_"+strconv.Itoa(itemID), ReportForumID)
if err != nil {
return 0, err
}

View File

@ -400,7 +400,7 @@ func handleAttachments(stmt *sql.Stmt, id int) error {
}
// TODO: Only load a row per createdBy, maybe with group by?
func handleTopicReplies(umap map[int]struct{}, uid int, tid int) error {
func handleTopicReplies(umap map[int]struct{}, uid, tid int) error {
rows, err := userStmts.getRepliesOfTopic.Query(uid, tid)
if err != nil {
return err
@ -505,7 +505,7 @@ func (t *Topic) Update(name, content string) error {
}
content = PreparseMessage(html.UnescapeString(content))
parsedContent := ParseMessage(content, t.ParentID, "forums", nil)
parsedContent := ParseMessage(content, t.ParentID, "forums", nil, nil)
_, err := topicStmts.edit.Exec(name, content, parsedContent, t.ID)
t.cacheRemove()
return err
@ -518,9 +518,9 @@ func (t *Topic) SetPoll(pollID int) error {
}
// TODO: Have this go through the ReplyStore?
func (t *Topic) CreateActionReply(action string, ip string, uid int) (err error) {
func (t *Topic) CreateActionReply(action, ip string, uid int) (err error) {
if Config.DisablePostIP {
ip = "0"
ip = ""
}
res, err := topicStmts.createAction.Exec(t.ID, action, ip, uid)
if err != nil {
@ -566,13 +566,13 @@ var unlockai = "&#x1F513"
var stickai = "&#x1F4CC"
var unstickai = "&#x1F4CC" + aipost
func (ru *ReplyUser) Init() error {
func (ru *ReplyUser) Init() (group *Group, err error) {
ru.UserLink = BuildProfileURL(NameToSlug(ru.CreatedByName), ru.CreatedBy)
ru.ContentLines = strings.Count(ru.Content, "\n")
postGroup, err := Groups.Get(ru.Group)
if err != nil {
return err
return nil, err
}
if postGroup.IsMod {
ru.ClassName = Config.StaffCSS
@ -581,9 +581,6 @@ func (ru *ReplyUser) Init() error {
// TODO: Make a function for this? Build a more sophisticated noavatar handling system? Do bulk user loads and let the c.UserStore initialise this?
ru.Avatar, ru.MicroAvatar = BuildAvatar(ru.CreatedBy, ru.Avatar)
if ru.Tag == "" {
ru.Tag = postGroup.Tag
}
// We really shouldn't have inline HTML, we should do something about this...
if ru.ActionType != "" {
@ -604,18 +601,18 @@ func (ru *ReplyUser) Init() error {
forum, err := Forums.Get(fid)
if err == nil {
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_move_dest", forum.Link, forum.Name, ru.UserLink, ru.CreatedByName)
return nil
return postGroup, nil
}
}
default:
// TODO: Only fire this off if a corresponding phrase for the ActionType doesn't exist? Or maybe have some sort of action registry?
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_default", ru.ActionType)
return nil
return postGroup, nil
}
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_"+action, ru.UserLink, ru.CreatedByName)
}
return nil
return postGroup, nil
}
// TODO: Factor TopicUser into a *Topic and *User, as this starting to become overly complicated x.x
@ -648,12 +645,21 @@ func (t *TopicUser) Replies(offset, pFrag int, user *User) (rlist []*ReplyUser,
hTbl := GetHookTable()
rf := func(r *ReplyUser) error {
//log.Printf("before r: %+v\n", r)
err := r.Init()
group, err := r.Init()
if err != nil {
return err
}
//log.Printf("after r: %+v\n", r)
r.ContentHtml = ParseMessage(r.Content, t.ParentID, "forums", user.ParseSettings)
var parseSettings *ParseSettings
if !group.Perms.AutoEmbed && (user.ParseSettings == nil || !user.ParseSettings.NoEmbed) {
parseSettings = DefaultParseSettings.CopyPtr()
parseSettings.NoEmbed = true
} else {
parseSettings = user.ParseSettings
}
r.ContentHtml = ParseMessage(r.Content, t.ParentID, "forums", parseSettings, user)
// TODO: Do this more efficiently by avoiding the allocations entirely in ParseMessage, if there's nothing to do.
if r.ContentHtml == r.Content {
r.ContentHtml = r.Content

View File

@ -222,14 +222,14 @@ func (s *DefaultTopicStore) Create(fid int, name, content string, uid int, ip st
return 0, ErrLongTitle
}
parsedContent := strings.TrimSpace(ParseMessage(content, fid, "forums", nil))
parsedContent := strings.TrimSpace(ParseMessage(content, fid, "forums", nil, nil))
if parsedContent == "" {
return 0, ErrNoBody
}
// TODO: Move this statement into the topic store
if Config.DisablePostIP {
ip = "0"
ip = ""
}
res, err := s.create.Exec(fid, name, content, parsedContent, uid, ip, WordCount(content), uid)
if err != nil {

View File

@ -936,7 +936,7 @@ func BenchmarkParserSerial(b *testing.B) {
f := func(name, msg string) func(b *testing.B) {
return func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = c.ParseMessage(msg, 0, "", nil)
_ = c.ParseMessage(msg, 0, "", nil, nil)
}
}
}

View File

@ -31,6 +31,8 @@
"UploadFiles": "Can upload files",
"UploadAvatars": "Can upload avatars",
"UseConvos":"Can use conversations",
"CreateProfileReply": "Can create profile replies",
"AutoEmbed":"Automatically embed media they post",
"ViewTopic": "Can view topics",
"LikeItem": "Can like items",
@ -919,6 +921,7 @@
"panel_group_tag_placeholder":"VIP",
"panel_group_update_button":"Update Group",
"panel_group_extended_permissions":"Extended Permissions",
"panel_group_mod_permissions":"Moderator Permissions",
"panel_group_promotions_level_prefix":"level ",
"panel_group_promotions_posts_prefix":"posts ",
@ -1029,6 +1032,7 @@
"panel_logs_mod_action_topic_move_dest":"<a href='%s'>%s</a> was moved to <a href='%s'>%s</a> by <a href='%s'>%s</a>",
"panel_logs_mod_action_topic_unknown":"Unknown action '%s' on elementType '%s' by <a href='%s'>%s</a>",
"panel_logs_mod_action_reply_delete":"A reply in <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_mod_action_profile_reply_delete":"A reply on <a href='%s'>%s</a>'s profile was deleted by <a href='%s'>%s</a>",
"panel_logs_mod_action_user_ban":"<a href='%s'>%s</a> was banned by <a href='%s'>%s</a>",
"panel_logs_mod_action_user_unban":"<a href='%s'>%s</a> was unbanned by <a href='%s'>%s</a>",
"panel_logs_mod_action_user_delete-posts":"<a href='%s'>%s</a> had their posts purged by <a href='%s'>%s</a>",

View File

@ -59,17 +59,17 @@ func TestUserStore(t *testing.T) {
func userStoreTest(t *testing.T, newUserID int) {
ucache := c.Users.GetCache()
// Go doesn't have short-circuiting, so this'll allow us to do one liner tests
isCacheLengthZero := func(ucache c.UserCache) bool {
if ucache == nil {
isCacheLengthZero := func(uc c.UserCache) bool {
if uc == nil {
return true
}
return ucache.Length() == 0
return uc.Length() == 0
}
cacheLength := func(ucache c.UserCache) int {
if ucache == nil {
cacheLength := func(uc c.UserCache) int {
if uc == nil {
return 0
}
return ucache.Length()
return uc.Length()
}
expect(t, isCacheLengthZero(ucache), fmt.Sprintf("The initial ucache length should be zero, not %d", cacheLength(ucache)))
@ -84,7 +84,7 @@ func userStoreTest(t *testing.T, newUserID int) {
user, err := c.Users.Get(1)
recordMustExist(t, err, "Couldn't find UID #1")
expectW := func(cond bool, expec bool, prefix string, suffix string) {
expectW := func(cond, expec bool, prefix, suffix string) {
midfix := "should not be"
if expec {
midfix = "should be"
@ -93,7 +93,7 @@ func userStoreTest(t *testing.T, newUserID int) {
}
// TODO: Add email checks too? Do them separately?
expectUser := func(u *c.User, uid int, name string, group int, super bool, admin bool, mod bool, banned bool) {
expectUser := func(u *c.User, uid int, name string, group int, super, admin, mod, banned bool) {
expect(t, u.ID == uid, fmt.Sprintf("u.ID should be %d. Got '%d' instead.", uid, u.ID))
expect(t, u.Name == name, fmt.Sprintf("u.Name should be '%s', not '%s'", name, u.Name))
expectW(u.Group == group, true, u.Name, "in group"+strconv.Itoa(group))
@ -258,7 +258,7 @@ func userStoreTest(t *testing.T, newUserID int) {
dummyRequest2 := httptest.NewRequest("", "/forum/"+strconv.Itoa(generalForumID), bytesBuffer)
var user2 *c.User
changeGroupTest := func(oldGroup int, newGroup int) {
changeGroupTest := func(oldGroup, newGroup int) {
err = user.ChangeGroup(newGroup)
expectNilErr(t, err)
// ! I don't think ChangeGroup should be changing the value of user... Investigate this.
@ -270,7 +270,7 @@ func userStoreTest(t *testing.T, newUserID int) {
*user2 = *user
}
changeGroupTest2 := func(rank string, firstShouldBe bool, secondShouldBe bool) {
changeGroupTest2 := func(rank string, firstShouldBe, secondShouldBe bool) {
head, err := c.UserCheck(dummyResponseRecorder, dummyRequest1, user)
if err != nil {
t.Fatal(err)
@ -364,7 +364,7 @@ func expectNilErr(t *testing.T, item error) {
}
}
func expectIntToBeX(t *testing.T, item int, expect int, errmsg string) {
func expectIntToBeX(t *testing.T, item, expect int, errmsg string) {
if item != expect {
debug.PrintStack()
t.Fatalf(errmsg, item)
@ -452,14 +452,14 @@ func TestTopicStore(t *testing.T) {
c.Config.DisablePostIP = false
topicStoreTest(t, 2, "::1")
c.Config.DisablePostIP = true
topicStoreTest(t, 3, "0")
topicStoreTest(t, 3, "")
c.Topics, err = c.NewDefaultTopicStore(nil)
expectNilErr(t, err)
c.Config.DisablePostIP = false
topicStoreTest(t, 4, "::1")
c.Config.DisablePostIP = true
topicStoreTest(t, 5, "0")
topicStoreTest(t, 5, "")
}
func topicStoreTest(t *testing.T, newID int, ip string) {
var topic *c.Topic
@ -914,20 +914,20 @@ func TestReplyStore(t *testing.T) {
c.Config.DisablePostIP = false
testReplyStore(t, 2, 1, "::1")
c.Config.DisablePostIP = true
testReplyStore(t, 5, 3, "0")
testReplyStore(t, 5, 3, "")
}
func testReplyStore(t *testing.T, newID, newPostCount int, ip string) {
replyTest2 := func(reply *c.Reply, err error, rid int, parentID int, createdBy int, content string, ip string) {
replyTest2 := func(r *c.Reply, err error, rid, parentID, createdBy int, content, ip string) {
expectNilErr(t, err)
expect(t, reply.ID == rid, fmt.Sprintf("RID #%d has the wrong ID. It should be %d not %d", rid, rid, reply.ID))
expect(t, reply.ParentID == parentID, fmt.Sprintf("The parent topic of RID #%d should be %d not %d", rid, parentID, reply.ParentID))
expect(t, reply.CreatedBy == createdBy, fmt.Sprintf("The creator of RID #%d should be %d not %d", rid, createdBy, reply.CreatedBy))
expect(t, reply.Content == content, fmt.Sprintf("The contents of RID #%d should be '%s' not %s", rid, content, reply.Content))
expect(t, reply.IP == ip, fmt.Sprintf("The IP of RID#%d should be '%s' not %s", rid, ip, reply.IP))
expect(t, r.ID == rid, fmt.Sprintf("RID #%d has the wrong ID. It should be %d not %d", rid, rid, r.ID))
expect(t, r.ParentID == parentID, fmt.Sprintf("The parent topic of RID #%d should be %d not %d", rid, parentID, r.ParentID))
expect(t, r.CreatedBy == createdBy, fmt.Sprintf("The creator of RID #%d should be %d not %d", rid, createdBy, r.CreatedBy))
expect(t, r.Content == content, fmt.Sprintf("The contents of RID #%d should be '%s' not %s", rid, content, r.Content))
expect(t, r.IP == ip, fmt.Sprintf("The IP of RID#%d should be '%s' not %s", rid, ip, r.IP))
}
replyTest := func(rid int, parentID int, createdBy int, content string, ip string) {
replyTest := func(rid, parentID, createdBy int, content, ip string) {
reply, err := c.Rstore.Get(rid)
replyTest2(reply, err, rid, parentID, createdBy, content, ip)
reply, err = c.Rstore.GetCache().Get(rid)
@ -1015,7 +1015,7 @@ func TestProfileReplyStore(t *testing.T) {
c.Config.DisablePostIP = false
testProfileReplyStore(t, 1, "::1")
c.Config.DisablePostIP = true
testProfileReplyStore(t, 2, "0")
testProfileReplyStore(t, 2, "")
}
func testProfileReplyStore(t *testing.T, newID int, ip string) {
// ? - Commented this one out as strong constraints like this put an unreasonable load on the database, we only want errors if a delete which should succeed fails
@ -1466,22 +1466,22 @@ func TestWidgets(t *testing.T) {
expectNilErr(t, err)
expect(t, wid == 1, "wid should be 1")
wtest := func(w, w2 *c.Widget) {
expect(t, w.Position == w2.Position, "wrong position")
expect(t, w.Side == w2.Side, "wrong side")
expect(t, w.Type == w2.Type, "wrong type")
expect(t, w2.Enabled, "not enabled")
expect(t, w.Location == w2.Location, "wrong location")
}
// TODO: Do a test for the widget body
widget2, err := c.Widgets.Get(1)
expectNilErr(t, err)
expect(t, widget2.Position == widget.Position, "wrong position")
expect(t, widget2.Side == widget.Side, "wrong side")
expect(t, widget2.Type == widget.Type, "wrong type")
expect(t, widget2.Enabled, "not enabled")
expect(t, widget2.Location == widget.Location, "wrong location")
wtest(widget, widget2)
widgets = c.Docks.RightSidebar.Items
expect(t, len(widgets) == 1, fmt.Sprintf("RightSidebar should have 1 item, not %d", len(widgets)))
expect(t, widgets[0].Position == widget.Position, "wrong position")
expect(t, widgets[0].Side == widget.Side, "wrong side")
expect(t, widgets[0].Type == widget.Type, "wrong type")
expect(t, widgets[0].Enabled, "not enabled")
expect(t, widgets[0].Location == widget.Location, "wrong location")
wtest(widget, widgets[0])
widget2.Enabled = false
ewidget = &c.WidgetEdit{widget2, map[string]string{"Name": "Test", "Text": "Testing"}}
@ -1493,7 +1493,7 @@ func TestWidgets(t *testing.T) {
expect(t, widget2.Position == widget.Position, "wrong position")
expect(t, widget2.Side == widget.Side, "wrong side")
expect(t, widget2.Type == widget.Type, "wrong type")
expect(t, !widget2.Enabled, "not enabled")
expect(t, !widget2.Enabled, "should not be enabled")
expect(t, widget2.Location == widget.Location, "wrong location")
widgets = c.Docks.RightSidebar.Items
@ -1501,7 +1501,7 @@ func TestWidgets(t *testing.T) {
expect(t, widgets[0].Position == widget.Position, "wrong position")
expect(t, widgets[0].Side == widget.Side, "wrong side")
expect(t, widgets[0].Type == widget.Type, "wrong type")
expect(t, !widgets[0].Enabled, "not enabled")
expect(t, !widgets[0].Enabled, "should not be enabled")
expect(t, widgets[0].Location == widget.Location, "wrong location")
err = widget2.Delete()

View File

@ -300,7 +300,7 @@ func TestParser(t *testing.T) {
// TODO: Fix this hack and make the results a bit more reproducible, push the tests further in the process.
for _, item := range l.Items {
if res := c.ParseMessage(item.Msg, 1, "forums", nil); res != item.Expects {
if res := c.ParseMessage(item.Msg, 1, "forums", nil, nil); res != item.Expects {
if item.Name != "" {
t.Error("Name: ", item.Name)
}
@ -321,7 +321,7 @@ func TestParser(t *testing.T) {
l.Add("//"+c.Site.URL+"\n", "<a href='https://"+c.Site.URL+"'>"+c.Site.URL+"</a><br>")
l.Add("//"+c.Site.URL+"\n//"+c.Site.URL, "<a href='https://"+c.Site.URL+"'>"+c.Site.URL+"</a><br><a href='https://"+c.Site.URL+"'>"+c.Site.URL+"</a>")
for _, item := range l.Items {
if res := c.ParseMessage(item.Msg, 1, "forums", nil); res != item.Expects {
if res := c.ParseMessage(item.Msg, 1, "forums", nil, nil); res != item.Expects {
if item.Name != "" {
t.Error("Name: ", item.Name)
}
@ -345,7 +345,7 @@ func TestParser(t *testing.T) {
}
c.WriteURL(sb, c.BuildTopicURL("", tid), "#nnid-"+strconv.Itoa(tid))
})
res := c.ParseMessage("#nnid-1", 1, "forums", nil)
res := c.ParseMessage("#nnid-1", 1, "forums", nil, nil)
expect := "<a href='/topic/1'>#nnid-1</a>"
if res != expect {
t.Error("Bad output:", "'"+res+"'")
@ -363,7 +363,7 @@ func TestParser(t *testing.T) {
}
c.WriteURL(sb, c.BuildTopicURL("", tid), "#longidnameneedtooverflowhack-"+strconv.Itoa(tid))
})
res = c.ParseMessage("#longidnameneedtooverflowhack-1", 1, "forums", nil)
res = c.ParseMessage("#longidnameneedtooverflowhack-1", 1, "forums", nil,nil)
expect = "<a href='/topic/1'>#longidnameneedtooverflowhack-1</a>"
if res != expect {
t.Error("Bad output:", "'"+res+"'")

View File

@ -336,6 +336,17 @@ func GroupsEditPerms(w http.ResponseWriter, r *http.Request, user c.User, sgid s
globalPerms = append(globalPerms, c.NameLangToggle{permStr, p.GetPermPhrase(permStr), perm})
}
addPerm("UploadFiles", g.Perms.UploadFiles)
addPerm("UploadAvatars", g.Perms.UploadAvatars)
addPerm("UseConvos", g.Perms.UseConvos)
addPerm("CreateProfileReply", g.Perms.CreateProfileReply)
addPerm("AutoEmbed", g.Perms.AutoEmbed)
var modPerms []c.NameLangToggle
addPerm = func(permStr string, perm bool) {
modPerms = append(modPerms, c.NameLangToggle{permStr, p.GetPermPhrase(permStr), perm})
}
addPerm("BanUsers", g.Perms.BanUsers)
addPerm("ActivateUsers", g.Perms.ActivateUsers)
addPerm("EditUser", g.Perms.EditUser)
@ -355,11 +366,8 @@ func GroupsEditPerms(w http.ResponseWriter, r *http.Request, user c.User, sgid s
addPerm("ManagePlugins", g.Perms.ManagePlugins)
addPerm("ViewAdminLogs", g.Perms.ViewAdminLogs)
addPerm("ViewIPs", g.Perms.ViewIPs)
addPerm("UploadFiles", g.Perms.UploadFiles)
addPerm("UploadAvatars", g.Perms.UploadAvatars)
addPerm("UseConvos", g.Perms.UseConvos)
pi := c.PanelEditGroupPermsPage{basePage, g.ID, g.Name, localPerms, globalPerms}
pi := c.PanelEditGroupPermsPage{basePage, g.ID, g.Name, localPerms, globalPerms, modPerms}
return renderTemplate("panel_group_edit_perms", w, r, basePage.Header, pi)
}

View File

@ -39,21 +39,21 @@ func LogsRegs(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
// TODO: Log errors when something really screwy is going on?
// TODO: Base the slugs on the localised usernames?
func handleUnknownUser(user *c.User, err error) *c.User {
func handleUnknownUser(u *c.User, err error) *c.User {
if err != nil {
return &c.User{Name: p.GetTmplPhrase("user_unknown"), Link: c.BuildProfileURL("unknown", 0)}
}
return user
return u
}
func handleUnknownTopic(topic *c.Topic, err error) *c.Topic {
func handleUnknownTopic(t *c.Topic, err error) *c.Topic {
if err != nil {
return &c.Topic{Title: p.GetTmplPhrase("topic_unknown"), Link: c.BuildTopicURL("unknown", 0)}
}
return topic
return t
}
// TODO: Move the log building logic into /common/ and it's own abstraction
func topicElementTypeAction(action string, elementType string, elementID int, actor *c.User, topic *c.Topic) (out string) {
func topicElementTypeAction(action, elementType string, elementID int, actor *c.User, topic *c.Topic) (out string) {
if action == "delete" {
return p.GetTmplPhrasef("panel_logs_mod_action_topic_delete", elementID, actor.Link, actor.Name)
}
@ -80,7 +80,7 @@ func topicElementTypeAction(action string, elementType string, elementID int, ac
return fmt.Sprintf(out, topic.Link, topic.Title, actor.Link, actor.Name)
}
func modlogsElementType(action string, elementType string, elementID int, actor *c.User) (out string) {
func modlogsElementType(action, elementType string, elementID int, actor *c.User) (out string) {
switch elementType {
case "topic":
topic := handleUnknownTopic(c.Topics.Get(elementID))
@ -93,6 +93,18 @@ func modlogsElementType(action string, elementType string, elementID int, actor
topic := handleUnknownTopic(c.TopicByReplyID(elementID))
out = p.GetTmplPhrasef("panel_logs_mod_action_reply_delete", topic.Link, topic.Title, actor.Link, actor.Name)
}
case "profile-reply":
if action == "delete" {
// TODO: Optimise this
var profile *c.User
profileReply, err := c.Prstore.Get(elementID)
if err != nil {
profile = &c.User{Name: p.GetTmplPhrase("user_unknown"), Link: c.BuildProfileURL("unknown", 0)}
} else {
profile = handleUnknownUser(c.Users.Get(profileReply.ParentID))
}
out = p.GetTmplPhrasef("panel_logs_mod_action_profile_reply_delete", profile.Link, profile.Name, actor.Link, actor.Name)
}
}
if out == "" {
out = p.GetTmplPhrasef("panel_logs_mod_action_unknown", action, elementType, actor.Link, actor.Name)
@ -100,7 +112,7 @@ func modlogsElementType(action string, elementType string, elementID int, actor
return out
}
func adminlogsElementType(action string, elementType string, elementID int, actor *c.User, extra string) (out string) {
func adminlogsElementType(action, elementType string, elementID int, actor *c.User, extra string) (out string) {
switch elementType {
// TODO: Record more detail for this, e.g. which field/s was changed
case "user":

View File

@ -20,7 +20,7 @@ var profileStmts ProfileStmts
func init() {
c.DbInits.Add(func(acc *qgen.Accumulator) error {
profileStmts = ProfileStmts{
getReplies: acc.SimpleLeftJoin("users_replies", "users", "users_replies.rid, users_replies.content, users_replies.createdBy, users_replies.createdAt, users_replies.lastEdit, users_replies.lastEditBy, users.avatar, users.name, users.group", "users_replies.createdBy = users.uid", "users_replies.uid = ?", "", ""),
getReplies: acc.SimpleLeftJoin("users_replies", "users", "users_replies.rid, users_replies.content, users_replies.createdBy, users_replies.createdAt, users_replies.lastEdit, users_replies.lastEditBy, users.avatar, users.name, users.group", "users_replies.createdBy=users.uid", "users_replies.uid=?", "", ""),
}
return acc.FirstError()
})
@ -28,11 +28,10 @@ func init() {
// TODO: Remove the View part of the name?
func ViewProfile(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError {
var err error
var replyCreatedAt time.Time
var replyContent, replyCreatedByName, replyAvatar string
var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyGroup int
var replyList []*c.ReplyUser
var reCreatedAt time.Time
var reContent, reCreatedByName, reAvatar string
var rid, reCreatedBy, reLastEdit, reLastEditBy, reGroup int
var reList []*c.ReplyUser
// TODO: Do a 301 if it's the wrong username? Do a canonical too?
_, pid, err := ParseSEOURL(r.URL.Path[len("/user/"):])
@ -71,28 +70,24 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user c.User, header *c.
defer rows.Close()
for rows.Next() {
err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName, &replyGroup)
err := rows.Scan(&rid, &reContent, &reCreatedBy, &reCreatedAt, &reLastEdit, &reLastEditBy, &reAvatar, &reCreatedByName, &reGroup)
if err != nil {
return c.InternalError(err, w, r)
}
replyLiked := false
replyLikeCount := 0
ru := &c.ReplyUser{Reply: c.Reply{rid, puser.ID, replyContent, replyCreatedBy, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, 0, "", replyLiked, replyLikeCount, 0, ""}, ContentHtml: c.ParseMessage(replyContent, 0, "", user.ParseSettings), CreatedByName: replyCreatedByName, Avatar: replyAvatar, Level: 0}
ru.Init()
group, err := c.Groups.Get(ru.Group)
reLiked := false
reLikeCount := 0
ru := &c.ReplyUser{Reply: c.Reply{rid, puser.ID, reContent, reCreatedBy, reGroup, reCreatedAt, reLastEdit, reLastEditBy, 0, "", reLiked, reLikeCount, 0, ""}, ContentHtml: c.ParseMessage(reContent, 0, "", user.ParseSettings, &user), CreatedByName: reCreatedByName, Avatar: reAvatar, Level: 0}
_, err = ru.Init()
if err != nil {
return c.InternalError(err, w, r)
}
if group.Tag != "" {
ru.Tag = group.Tag
} else if puser.ID == ru.CreatedBy {
if puser.ID == ru.CreatedBy {
ru.Tag = phrases.GetTmplPhrase("profile.owner_tag")
}
// TODO: Add a hook here
replyList = append(replyList, ru)
reList = append(reList, ru)
}
if err := rows.Err(); err != nil {
return c.InternalError(err, w, r)
@ -114,8 +109,8 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user c.User, header *c.
}
}
canMessage := (!blockedInv && user.Perms.UseConvos) || user.IsSuperMod
canComment := !blockedInv && user.Perms.ViewTopic && user.Perms.CreateReply
canComment := !blockedInv && user.Perms.CreateProfileReply
ppage := c.ProfilePage{header, replyList, *puser, currentScore, nextScore, blocked, canMessage, canComment}
ppage := c.ProfilePage{header, reList, *puser, currentScore, nextScore, blocked, canMessage, canComment}
return renderTemplate("profile", w, r, header, ppage)
}

View File

@ -10,7 +10,7 @@ import (
)
func ProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
if !user.Perms.ViewTopic || !user.Perms.CreateReply {
if !user.Perms.CreateProfileReply {
return c.NoPermissions(w, r, user)
}
uid, err := strconv.Atoi(r.PostFormValue("uid"))
@ -74,6 +74,9 @@ func ProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user c.User,
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
if !user.Perms.CreateProfileReply {
return c.NoPermissionsJSQ(w, r, user, js)
}
// ? Does the admin understand that this group perm affects this?
if user.ID != creator.ID && !user.Perms.EditReply {
return c.NoPermissionsJSQ(w, r, user, js)
@ -127,5 +130,10 @@ func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.Use
} else {
w.Write(successJSONBytes)
}
err = c.ModLogs.Create("delete", reply.ParentID, "profile-reply", user.GetIP(), user.ID)
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
return nil
}

View File

@ -194,7 +194,7 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
prid, _ := strconv.Atoi(r.FormValue("prid"))
if js && (prid == 0 || rids[0] == prid) {
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(reply.Content, topic.ParentID, "forums", user.ParseSettings)})
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(reply.Content, topic.ParentID, "forums", user.ParseSettings, &user)})
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
@ -267,7 +267,7 @@ func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, srid s
if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(topic.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther)
} else {
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(reply.Content, topic.ParentID, "forums", user.ParseSettings)})
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(reply.Content, topic.ParentID, "forums", user.ParseSettings, &user)})
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}

View File

@ -72,14 +72,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user c.User, header *c.He
header.Title = topic.Title
header.Path = c.BuildTopicURL(c.NameToSlug(topic.Title), topic.ID)
// TODO: Cache ContentHTML when possible?
topic.ContentHTML = c.ParseMessage(topic.Content, topic.ParentID, "forums", user.ParseSettings)
// TODO: Do this more efficiently by avoiding the allocations entirely in ParseMessage, if there's nothing to do.
if topic.ContentHTML == topic.Content {
topic.ContentHTML = topic.Content
}
topic.ContentLines = strings.Count(topic.Content, "\n")
if len(topic.Content) > 200 {
header.OGDesc = topic.Content[:197] + "..."
} else {
@ -90,6 +83,22 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user c.User, header *c.He
if err != nil {
return c.InternalError(err, w, r)
}
var parseSettings *c.ParseSettings
if !postGroup.Perms.AutoEmbed && (user.ParseSettings == nil || !user.ParseSettings.NoEmbed) {
parseSettings = c.DefaultParseSettings.CopyPtr()
parseSettings.NoEmbed = true
} else {
parseSettings = user.ParseSettings
}
// TODO: Cache ContentHTML when possible?
topic.ContentHTML = c.ParseMessage(topic.Content, topic.ParentID, "forums", parseSettings, &user)
// TODO: Do this more efficiently by avoiding the allocations entirely in ParseMessage, if there's nothing to do.
if topic.ContentHTML == topic.Content {
topic.ContentHTML = topic.Content
}
topic.Tag = postGroup.Tag
if postGroup.IsMod {
topic.ClassName = c.Config.StaffCSS
@ -604,7 +613,7 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid s
if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
} else {
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(topic.Content, topic.ParentID, "forums", user.ParseSettings)})
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(topic.Content, topic.ParentID, "forums", user.ParseSettings, &user)})
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}

View File

@ -7,9 +7,9 @@ INSERT INTO [settings] ([name],[content],[type]) VALUES ('rapid_loading','1','bo
INSERT INTO [settings] ([name],[content],[type]) VALUES ('google_site_verify','','html-attribute');
INSERT INTO [themes] ([uname],[default]) VALUES ('cosora',1);
INSERT INTO [emails] ([email],[uid],[validated]) VALUES ('admin@localhost',1,1);
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[is_banned],[tag]) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,0,'Admin');
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[is_banned],[tag]) VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserGroup":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,0,0,'Mod');
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[is_banned],[tag]) VALUES ('Member','{"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}','{}',0,0,0,"");
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[is_banned],[tag]) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"CreateProfileReply":true,"AutoEmbed":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,0,'Admin');
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[is_banned],[tag]) VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserGroup":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"CreateProfileReply":true,"AutoEmbed":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,0,0,'Mod');
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[is_banned],[tag]) VALUES ('Member','{"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"CreateProfileReply":true,"AutoEmbed":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}','{}',0,0,0,"");
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[is_banned],[tag]) VALUES ('Banned','{"ViewTopic":true}','{}',0,0,1,"");
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[is_banned],[tag]) VALUES ('Awaiting Activation','{"ViewTopic":true}','{}',0,0,0,"");
INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[is_banned],[tag]) VALUES ('Not Loggedin','{"ViewTopic":true}','{}',0,0,0,'Guest');

View File

@ -15,9 +15,9 @@ INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('rapid_loading','1','boo
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('google_site_verify','','html-attribute');
INSERT INTO `themes`(`uname`,`default`) VALUES ('cosora',1);
INSERT INTO `emails`(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1);
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag`) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,0,'Admin');
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag`) VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserGroup":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,0,0,'Mod');
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag`) VALUES ('Member','{"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}','{}',0,0,0,"");
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag`) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"CreateProfileReply":true,"AutoEmbed":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,0,'Admin');
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag`) VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserGroup":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"CreateProfileReply":true,"AutoEmbed":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,0,0,'Mod');
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag`) VALUES ('Member','{"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"CreateProfileReply":true,"AutoEmbed":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}','{}',0,0,0,"");
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag`) VALUES ('Banned','{"ViewTopic":true}','{}',0,0,1,"");
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag`) VALUES ('Awaiting Activation','{"ViewTopic":true}','{}',0,0,0,"");
INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag`) VALUES ('Not Loggedin','{"ViewTopic":true}','{}',0,0,0,'Guest');

View File

@ -7,9 +7,9 @@ INSERT INTO "settings"("name","content","type") VALUES ('rapid_loading','1','boo
INSERT INTO "settings"("name","content","type") VALUES ('google_site_verify','','html-attribute');
INSERT INTO "themes"("uname","default") VALUES ('cosora',1);
INSERT INTO "emails"("email","uid","validated") VALUES ('admin@localhost',1,1);
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","is_banned","tag") VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,0,'Admin');
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","is_banned","tag") VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserGroup":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,0,0,'Mod');
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","is_banned","tag") VALUES ('Member','{"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}','{}',0,0,0,"");
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","is_banned","tag") VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"CreateProfileReply":true,"AutoEmbed":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,0,'Admin');
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","is_banned","tag") VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserGroup":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"CreateProfileReply":true,"AutoEmbed":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,0,0,'Mod');
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","is_banned","tag") VALUES ('Member','{"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"CreateProfileReply":true,"AutoEmbed":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}','{}',0,0,0,"");
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","is_banned","tag") VALUES ('Banned','{"ViewTopic":true}','{}',0,0,1,"");
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","is_banned","tag") VALUES ('Awaiting Activation','{"ViewTopic":true}','{}',0,0,0,"");
INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","is_banned","tag") VALUES ('Not Loggedin','{"ViewTopic":true}','{}',0,0,0,'Guest');

View File

@ -4,7 +4,7 @@
<main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div>
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}} - #{{.ID}}</h1></div>
</div>
<div id="panel_group" class="colstack_item the_form">
<form action="/panel/groups/edit/submit/{{.ID}}?s={{.CurrentUser.Session}}" method="post">

View File

@ -4,7 +4,7 @@
<main class="colstack_right">
{{template "panel_before_head.html" . }}
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div>
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}} - #{{.ID}}</h1></div>
</div>
<form action="/panel/groups/edit/perms/submit/{{.ID}}?s={{.CurrentUser.Session}}" method="post">
{{if .CurrentUser.Perms.EditGroupLocalPerms}}
@ -50,6 +50,29 @@
</div>
</div>
{{end}}
{{if .CurrentUser.Perms.EditGroupGlobalPerms}}
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_group_mod_permissions"}}</h1></div>
</div>
<div class="colstack_item rowlist formlist the_form panel_group_perms">
{{range .ModPerms}}
<div class="formrow">
<div class="formitem">
<a>{{.LangStr}}</a>
<div class="to_right">
<select name="perm-{{.Name}}">
<option{{if .Toggle}} selected{{end}} value=1>{{lang "option_yes"}}</option>
<option{{if not .Toggle}} selected{{end}} value=0>{{lang "option_no"}}</option>
</select>
</div>
</div>
</div>
{{end}}
<div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton form_middle_button">{{lang "panel_group_update_button"}}</button></div>
</div>
</div>
{{end}}
</form>
</main>
</div>