Add registered time as a parameter for group promotions.
Run group promotions on group change. Run group promotions on registration. Load the CreatedAt field when users are loaded. Set the default for last_ip properly. Fix the default values in the group promotion form. Add initial group promotion tests. Add panel_group_promotion_registered_for phrase. Add the registeredFor column to the users_groups_promotions table. You will need to run the updater / patcher for this commit.
This commit is contained in:
parent
e37c98eaa1
commit
b6931fe16a
|
@ -30,7 +30,7 @@ func createTables(adapter qgen.Adapter) (err error) {
|
|||
tC{"lastActiveAt", "datetime", 0, false, false, ""},
|
||||
tC{"session", "varchar", 200, false, false, "''"},
|
||||
//tC{"authToken", "varchar", 200, false, false, "''"},
|
||||
tC{"last_ip", "varchar", 200, false, false, "0.0.0.0.0"},
|
||||
tC{"last_ip", "varchar", 200, false, false, "''"},
|
||||
tC{"enable_embeds", "int", 0, false, false, "-1"},
|
||||
tC{"email", "varchar", 200, false, false, "''"},
|
||||
tC{"avatar", "varchar", 100, false, false, "''"},
|
||||
|
@ -91,12 +91,27 @@ func createTables(adapter qgen.Adapter) (err error) {
|
|||
tC{"level", "int", 0, false, false, ""},
|
||||
tC{"posts", "int", 0, false, false, "0"},
|
||||
tC{"minTime", "int", 0, false, false, ""}, // How long someone needs to have been in their current group before being promoted
|
||||
tC{"registeredFor", "int", 0, false, false, "0"}, // minutes
|
||||
},
|
||||
[]tblKey{
|
||||
tblKey{"pid", "primary", "", false},
|
||||
},
|
||||
)
|
||||
|
||||
/*
|
||||
createTable("users_groups_promotions_scheduled","","",
|
||||
[]tC{
|
||||
tC{"prid","int",0,false,false,""},
|
||||
tC{"uid","int",0,false,false,""},
|
||||
tC{"runAt","datetime",0,false,false,""},
|
||||
},
|
||||
[]tblKey{
|
||||
// TODO: Test to see that the compound primary key works
|
||||
tblKey{"prid,uid", "primary", "", false},
|
||||
},
|
||||
)
|
||||
*/
|
||||
|
||||
createTable("users_2fa_keys", mysqlPre, mysqlCol,
|
||||
[]tC{
|
||||
tC{"uid", "int", 0, false, false, ""},
|
||||
|
|
|
@ -2,6 +2,8 @@ package common
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
//"log"
|
||||
"time"
|
||||
|
||||
qgen "github.com/Azareal/Gosora/query_gen"
|
||||
)
|
||||
|
@ -17,14 +19,15 @@ type GroupPromotion struct {
|
|||
Level int
|
||||
Posts int
|
||||
MinTime int
|
||||
RegisteredFor int
|
||||
}
|
||||
|
||||
type GroupPromotionStore interface {
|
||||
GetByGroup(gid int) (gps []*GroupPromotion, err error)
|
||||
Get(id int) (*GroupPromotion, error)
|
||||
PromoteIfEligible(u *User, level int, posts int) error
|
||||
PromoteIfEligible(u *User, level, posts int, registeredAt time.Time) error
|
||||
Delete(id int) error
|
||||
Create(from int, to int, twoWay bool, level int, posts int) (int, error)
|
||||
Create(from, to int, twoWay bool, level, posts, registeredFor int) (int, error)
|
||||
}
|
||||
|
||||
type DefaultGroupPromotionStore struct {
|
||||
|
@ -34,20 +37,32 @@ type DefaultGroupPromotionStore struct {
|
|||
create *sql.Stmt
|
||||
|
||||
getByUser *sql.Stmt
|
||||
getByUserMins *sql.Stmt
|
||||
updateUser *sql.Stmt
|
||||
updateGeneric *sql.Stmt
|
||||
}
|
||||
|
||||
func NewDefaultGroupPromotionStore(acc *qgen.Accumulator) (*DefaultGroupPromotionStore, error) {
|
||||
ugp := "users_groups_promotions"
|
||||
return &DefaultGroupPromotionStore{
|
||||
getByGroup: acc.Select(ugp).Columns("pid, from_gid, to_gid, two_way, level, posts, minTime").Where("from_gid=? OR to_gid=?").Prepare(),
|
||||
get: acc.Select(ugp).Columns("from_gid, to_gid, two_way, level, posts, minTime").Where("pid = ?").Prepare(),
|
||||
delete: acc.Delete(ugp).Where("pid = ?").Prepare(),
|
||||
create: acc.Insert(ugp).Columns("from_gid, to_gid, two_way, level, posts, minTime").Fields("?,?,?,?,?,?").Prepare(),
|
||||
prs := &DefaultGroupPromotionStore{
|
||||
getByGroup: acc.Select(ugp).Columns("pid, from_gid, to_gid, two_way, level, posts, minTime, registeredFor").Where("from_gid=? OR to_gid=?").Prepare(),
|
||||
get: acc.Select(ugp).Columns("from_gid, to_gid, two_way, level, posts, minTime, registeredFor").Where("pid=?").Prepare(),
|
||||
delete: acc.Delete(ugp).Where("pid=?").Prepare(),
|
||||
create: acc.Insert(ugp).Columns("from_gid, to_gid, two_way, level, posts, minTime, registeredFor").Fields("?,?,?,?,?,?,?").Prepare(),
|
||||
|
||||
getByUser: acc.Select(ugp).Columns("pid, to_gid, two_way, level, posts, minTime").Where("from_gid=? AND level<=? AND posts<=?").Orderby("level DESC").Limit("1").Prepare(),
|
||||
updateUser: acc.Update("users").Set("group = ?").Where("level >= ? AND posts >= ?").Prepare(),
|
||||
}, acc.FirstError()
|
||||
//err := s.getByUser.QueryRow(u.Group, level, posts, mins).Scan(&g.ID, &g.To, &g.TwoWay, &g.Level, &g.Posts, &g.MinTime, &g.RegisteredFor)
|
||||
//getByUserMins: acc.Select(ugp).Columns("pid, to_gid, two_way, level, posts, minTime, registeredFor").Where("from_gid=? AND level<=? AND posts<=?").DateOlderThanQ("registeredFor", "minute").Orderby("level DESC").Limit("1").Prepare(),
|
||||
getByUserMins: acc.Select(ugp).Columns("pid, to_gid, two_way, level, posts, minTime, registeredFor").Where("from_gid=? AND level<=? AND posts<=? AND registeredFor<=?").Orderby("level DESC").Limit("1").Prepare(),
|
||||
getByUser: acc.Select(ugp).Columns("pid, to_gid, two_way, level, posts, minTime, registeredFor").Where("from_gid=? AND level<=? AND posts<=?").Orderby("level DESC").Limit("1").Prepare(),
|
||||
updateUser: acc.Update("users").Set("group=?").Where("group=? AND uid=?").Prepare(),
|
||||
updateGeneric: acc.Update("users").Set("group=?").Where("group=? AND level>=? AND posts>=?").Prepare(),
|
||||
}
|
||||
AddScheduledFifteenMinuteTask(prs.Tick)
|
||||
return prs, acc.FirstError()
|
||||
}
|
||||
|
||||
func (s *DefaultGroupPromotionStore) Tick() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DefaultGroupPromotionStore) GetByGroup(gid int) (gps []*GroupPromotion, err error) {
|
||||
|
@ -59,7 +74,7 @@ func (s *DefaultGroupPromotionStore) GetByGroup(gid int) (gps []*GroupPromotion,
|
|||
|
||||
for rows.Next() {
|
||||
g := &GroupPromotion{}
|
||||
err := rows.Scan(&g.ID, &g.From, &g.To, &g.TwoWay, &g.Level, &g.Posts, &g.MinTime)
|
||||
err := rows.Scan(&g.ID, &g.From, &g.To, &g.TwoWay, &g.Level, &g.Posts, &g.MinTime, &g.RegisteredFor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -76,22 +91,31 @@ func (s *DefaultGroupPromotionStore) Get(id int) (*GroupPromotion, error) {
|
|||
}*/
|
||||
|
||||
g := &GroupPromotion{ID: id}
|
||||
err := s.get.QueryRow(id).Scan(&g.From, &g.To, &g.TwoWay, &g.Level, &g.Posts, &g.MinTime)
|
||||
err := s.get.QueryRow(id).Scan(&g.From, &g.To, &g.TwoWay, &g.Level, &g.Posts, &g.MinTime, &g.RegisteredFor)
|
||||
if err == nil {
|
||||
//s.cache.Set(u)
|
||||
}
|
||||
return g, err
|
||||
}
|
||||
|
||||
func (s *DefaultGroupPromotionStore) PromoteIfEligible(u *User, level int, posts int) error {
|
||||
// TODO: Optimise this to avoid the query
|
||||
func (s *DefaultGroupPromotionStore) PromoteIfEligible(u *User, level, posts int, registeredAt time.Time) error {
|
||||
mins := time.Since(registeredAt).Minutes()
|
||||
g := &GroupPromotion{From: u.Group}
|
||||
err := s.getByUser.QueryRow(u.Group, level, posts).Scan(&g.ID, &g.To, &g.TwoWay, &g.Level, &g.Posts, &g.MinTime)
|
||||
//log.Printf("pre getByUserMins: %+v\n", u)
|
||||
err := s.getByUserMins.QueryRow(u.Group, level, posts, mins).Scan(&g.ID, &g.To, &g.TwoWay, &g.Level, &g.Posts, &g.MinTime, &g.RegisteredFor)
|
||||
if err == sql.ErrNoRows {
|
||||
//log.Print("no matches found")
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.updateUser.Exec(g.To, g.Level, g.Posts)
|
||||
//log.Printf("g: %+v\n", g)
|
||||
if g.RegisteredFor == 0 {
|
||||
_, err = s.updateGeneric.Exec(g.To, g.From, g.Level, g.Posts)
|
||||
} else {
|
||||
_, err = s.updateUser.Exec(g.To, g.From, u.ID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -100,8 +124,8 @@ func (s *DefaultGroupPromotionStore) Delete(id int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *DefaultGroupPromotionStore) Create(from int, to int, twoWay bool, level int, posts int) (int, error) {
|
||||
res, err := s.create.Exec(from, to, twoWay, level, posts, 0)
|
||||
func (s *DefaultGroupPromotionStore) Create(from, to int, twoWay bool, level, posts, registeredFor int) (int, error) {
|
||||
res, err := s.create.Exec(from, to, twoWay, level, posts, 0, registeredFor)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
@ -93,14 +93,14 @@ var Template_account_handle = genIntTmpl("account")
|
|||
|
||||
func tmplInitUsers() (User, User, User) {
|
||||
avatar, microAvatar := BuildAvatar(62, "")
|
||||
user := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", avatar, microAvatar, "", "", 0, 0, 0, 0, "0.0.0.0.0", "", 0, nil}
|
||||
user := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", avatar, microAvatar, "", "", 0, 0, 0, 0, StartTime,"0.0.0.0.0", "", 0, nil}
|
||||
|
||||
// TODO: Do a more accurate level calculation for this?
|
||||
avatar, microAvatar = BuildAvatar(1, "")
|
||||
user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", 58, 1000, 0, 1000, "127.0.0.1", "", 0, nil}
|
||||
user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", 58, 1000, 0, 1000, StartTime, "127.0.0.1", "", 0, nil}
|
||||
|
||||
avatar, microAvatar = BuildAvatar(2, "")
|
||||
user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", 42, 900, 0, 900, "::1", "", 0, nil}
|
||||
user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", 42, 900, 0, 900, StartTime, "::1", "", 0, nil}
|
||||
return user, user2, user3
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ var BanGroup = 4
|
|||
|
||||
// TODO: Use something else as the guest avatar, maybe a question mark of some sort?
|
||||
// GuestUser is an instance of user which holds guest data to avoid having to initialise a guest every time
|
||||
var GuestUser = User{ID: 0, Name: "Guest", Link: "#", Group: 6, Perms: GuestPerms} // BuildAvatar is done in site.go to make sure it's done after init
|
||||
var GuestUser = User{ID: 0, Name: "Guest", Link: "#", Group: 6, Perms: GuestPerms, CreatedAt: StartTime} // BuildAvatar is done in site.go to make sure it's done after init
|
||||
var ErrNoTempGroup = errors.New("We couldn't find a temporary group for this user")
|
||||
|
||||
type User struct {
|
||||
|
@ -56,6 +56,7 @@ type User struct {
|
|||
Score int
|
||||
Posts int
|
||||
Liked int
|
||||
CreatedAt time.Time
|
||||
LastIP string // ! This part of the UserCache data might fall out of date
|
||||
LastAgent string // ! Temporary hack, don't use
|
||||
TempGroup int
|
||||
|
@ -592,7 +593,7 @@ func (u *User) IncreasePostStats(wcount int, topic bool) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = GroupPromotions.PromoteIfEligible(u, level, u.Posts+1)
|
||||
err = GroupPromotions.PromoteIfEligible(u, level, u.Posts+1, u.CreatedAt)
|
||||
u.CacheRemove()
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -53,13 +53,14 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
|
|||
cache = NewNullUserCache()
|
||||
}
|
||||
u := "users"
|
||||
allCols := "uid, name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, createdAt, enable_embeds"
|
||||
// TODO: Add an admin version of registerStmt with more flexibility?
|
||||
return &DefaultUserStore{
|
||||
cache: cache,
|
||||
get: acc.Select(u).Columns("name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Where("uid=?").Prepare(),
|
||||
getByName: acc.Select(u).Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Where("name = ?").Prepare(),
|
||||
getOffset: acc.Select(u).Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Orderby("uid ASC").Limit("?,?").Prepare(),
|
||||
getAll: acc.Select(u).Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, enable_embeds").Prepare(),
|
||||
get: acc.Select(u).Columns("name, group, active, is_super_admin, session, email, avatar, message, level, score, posts, liked, last_ip, temp_group, createdAt, enable_embeds").Where("uid=?").Prepare(),
|
||||
getByName: acc.Select(u).Columns(allCols).Where("name=?").Prepare(),
|
||||
getOffset: acc.Select(u).Columns(allCols).Orderby("uid ASC").Limit("?,?").Prepare(),
|
||||
getAll: acc.Select(u).Columns(allCols).Prepare(),
|
||||
exists: acc.Exists(u, "uid").Prepare(),
|
||||
register: acc.Insert(u).Columns("name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt").Fields("?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(), // TODO: Implement user_count on users_groups here
|
||||
nameExists: acc.Exists(u, "name").Prepare(),
|
||||
|
@ -91,7 +92,7 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
|
|||
|
||||
u = &User{ID: id, Loggedin: true}
|
||||
var embeds int
|
||||
err = s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds)
|
||||
err = s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &u.CreatedAt, &embeds)
|
||||
if err == nil {
|
||||
if embeds != -1 {
|
||||
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
||||
|
@ -108,7 +109,7 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
|
|||
func (s *DefaultUserStore) GetByName(name string) (*User, error) {
|
||||
u := &User{Loggedin: true}
|
||||
var embeds int
|
||||
err := s.getByName.QueryRow(name).Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds)
|
||||
err := s.getByName.QueryRow(name).Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &u.CreatedAt, &embeds)
|
||||
if err == nil {
|
||||
if embeds != -1 {
|
||||
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
||||
|
@ -132,7 +133,7 @@ func (s *DefaultUserStore) GetOffset(offset, perPage int) (users []*User, err er
|
|||
var embeds int
|
||||
for rows.Next() {
|
||||
u := &User{Loggedin: true}
|
||||
err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds)
|
||||
err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &u.CreatedAt, &embeds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -155,7 +156,7 @@ func (s *DefaultUserStore) Each(f func(*User) error) error {
|
|||
var embeds int
|
||||
for rows.Next() {
|
||||
u := new(User)
|
||||
if err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds); err != nil {
|
||||
if err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &u.CreatedAt, &embeds); err != nil {
|
||||
return err
|
||||
}
|
||||
if embeds != -1 {
|
||||
|
@ -213,7 +214,7 @@ func (s *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error)
|
|||
}
|
||||
q = q[0 : len(q)-1]
|
||||
|
||||
rows, err := qgen.NewAcc().Select("users").Columns("uid,name,group,active,is_super_admin,session,email,avatar,message,level,score,posts,liked,last_ip,temp_group,enable_embeds").Where("uid IN(" + q + ")").Query(idList...)
|
||||
rows, err := qgen.NewAcc().Select("users").Columns("uid,name,group,active,is_super_admin,session,email,avatar,message,level,score,posts,liked,last_ip,temp_group,createdAt,enable_embeds").Where("uid IN(" + q + ")").Query(idList...)
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
@ -222,7 +223,7 @@ func (s *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error)
|
|||
var embeds int
|
||||
for rows.Next() {
|
||||
u := &User{Loggedin: true}
|
||||
err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds)
|
||||
err := rows.Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &u.CreatedAt, &embeds)
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
@ -259,7 +260,7 @@ func (s *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error)
|
|||
func (s *DefaultUserStore) BypassGet(id int) (*User, error) {
|
||||
u := &User{ID: id, Loggedin: true}
|
||||
var embeds int
|
||||
err := s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &embeds)
|
||||
err := s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &u.CreatedAt, &embeds)
|
||||
if err == nil {
|
||||
if embeds != -1 {
|
||||
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
||||
|
|
|
@ -31,9 +31,9 @@ func init() {
|
|||
c.Plugins.Add(&c.Plugin{UName: "skeleton", Name: "Skeleton", Author: "Azareal", Init: initSkeleton, Activate: activateSkeleton, Deactivate: deactivateSkeleton})
|
||||
}
|
||||
|
||||
func initSkeleton(plugin *c.Plugin) error { return nil }
|
||||
func initSkeleton(pl *c.Plugin) error { return nil }
|
||||
|
||||
// Any errors encountered while trying to activate the plugin are reported back to the admin and the activation is aborted
|
||||
func activateSkeleton(plugin *c.Plugin) error { return nil }
|
||||
func activateSkeleton(pl *c.Plugin) error { return nil }
|
||||
|
||||
func deactivateSkeleton(plugin *c.Plugin) {}
|
||||
func deactivateSkeleton(pl *c.Plugin) {}
|
||||
|
|
|
@ -932,6 +932,7 @@
|
|||
"panel_group_promotions_two_way":"Two Way",
|
||||
"panel_group_promotions_level":"Level",
|
||||
"panel_group_promotions_posts":"Posts",
|
||||
"panel_group_promotion_registered_for":"Registered For",
|
||||
"panel_group_promotions_create_button":"Add Promotion",
|
||||
|
||||
"panel_word_filters_head":"Word Filters",
|
||||
|
|
224
misc_test.go
224
misc_test.go
|
@ -499,7 +499,7 @@ func topicStoreTest(t *testing.T, newID int, ip string) {
|
|||
return ""
|
||||
}
|
||||
|
||||
testTopic := func(tid int, title string, content string, createdBy int, ip string, parentID int, isClosed bool, sticky bool) {
|
||||
testTopic := func(tid int, title, content string, createdBy int, ip string, parentID int, isClosed, sticky bool) {
|
||||
topic, err = c.Topics.Get(tid)
|
||||
recordMustExist(t, err, fmt.Sprintf("Couldn't find TID #%d", tid))
|
||||
expect(t, topic.ID == tid, fmt.Sprintf("topic.ID does not match the requested TID. Got '%d' instead.", topic.ID))
|
||||
|
@ -734,7 +734,7 @@ func TestForumPermsStore(t *testing.T) {
|
|||
c.InitPlugins()
|
||||
}
|
||||
|
||||
f := func(fid int, gid int, msg string, inv ...bool) {
|
||||
f := func(fid, gid int, msg string, inv ...bool) {
|
||||
fp, err := c.FPStore.Get(fid, gid)
|
||||
expectNilErr(t, err)
|
||||
vt := fp.ViewTopic
|
||||
|
@ -900,6 +900,64 @@ func TestGroupStore(t *testing.T) {
|
|||
// TODO: Test group cache set
|
||||
}
|
||||
|
||||
func TestGroupPromotions(t *testing.T) {
|
||||
miscinit(t)
|
||||
if !c.PluginsInited {
|
||||
c.InitPlugins()
|
||||
}
|
||||
|
||||
_, err := c.GroupPromotions.Get(-1)
|
||||
recordMustNotExist(t, err, "GP #-1 shouldn't exist")
|
||||
_, err = c.GroupPromotions.Get(0)
|
||||
recordMustNotExist(t, err, "GP #0 shouldn't exist")
|
||||
_, err = c.GroupPromotions.Get(1)
|
||||
recordMustNotExist(t, err, "GP #1 shouldn't exist")
|
||||
expectNilErr(t, c.GroupPromotions.Delete(1))
|
||||
|
||||
//GetByGroup(gid int) (gps []*GroupPromotion, err error)
|
||||
|
||||
testPromo := func(exid, from, to, level, posts, registeredFor int, shouldFail bool) {
|
||||
gpid, err := c.GroupPromotions.Create(from, to, false, level, posts, registeredFor)
|
||||
expect(t, gpid == exid, fmt.Sprintf("gpid should be %d not %d", exid, gpid))
|
||||
//fmt.Println("gpid:", gpid)
|
||||
gp, err := c.GroupPromotions.Get(gpid)
|
||||
expectNilErr(t, err)
|
||||
expect(t, gp.ID == gpid, fmt.Sprintf("gp.ID should be %d not %d", gpid, gp.ID))
|
||||
expect(t, gp.From == from, fmt.Sprintf("gp.From should be %d not %d", from, gp.From))
|
||||
expect(t, gp.To == to, fmt.Sprintf("gp.To should be %d not %d", to, gp.To))
|
||||
expect(t, !gp.TwoWay, "gp.TwoWay should be false not true")
|
||||
expect(t, gp.Level == level, fmt.Sprintf("gp.Level should be %d not %d", level, gp.Level))
|
||||
expect(t, gp.Posts == posts, fmt.Sprintf("gp.Posts should be %d not %d", posts, gp.Posts))
|
||||
expect(t, gp.MinTime == 0, fmt.Sprintf("gp.MinTime should be %d not %d", 0, gp.MinTime))
|
||||
expect(t, gp.RegisteredFor == registeredFor, fmt.Sprintf("gp.RegisteredFor should be %d not %d", registeredFor, gp.RegisteredFor))
|
||||
|
||||
uid, err := c.Users.Create("Lord_"+strconv.Itoa(gpid), "I_Rule", "", from, false)
|
||||
expectNilErr(t, err)
|
||||
u, err := c.Users.Get(uid)
|
||||
expectNilErr(t, err)
|
||||
expect(t, u.ID == uid, fmt.Sprintf("u.ID should be %d not %d", uid, u.ID))
|
||||
expect(t, u.Group == from, fmt.Sprintf("u.Group should be %d not %d", from, u.Group))
|
||||
err = c.GroupPromotions.PromoteIfEligible(u, u.Level, u.Posts, u.CreatedAt)
|
||||
expectNilErr(t, err)
|
||||
u.CacheRemove()
|
||||
u, err = c.Users.Get(uid)
|
||||
expectNilErr(t, err)
|
||||
expect(t, u.ID == uid, fmt.Sprintf("u.ID should be %d not %d", uid, u.ID))
|
||||
if shouldFail {
|
||||
expect(t, u.Group == from, fmt.Sprintf("u.Group should be (from-group) %d not %d", from, u.Group))
|
||||
} else {
|
||||
expect(t, u.Group == to, fmt.Sprintf("u.Group should be (to-group)%d not %d", to, u.Group))
|
||||
}
|
||||
|
||||
expectNilErr(t, c.GroupPromotions.Delete(gpid))
|
||||
_, err = c.GroupPromotions.Get(gpid)
|
||||
recordMustNotExist(t, err, fmt.Sprintf("GP #%d should no longer exist", gpid))
|
||||
}
|
||||
testPromo(1, 1, 2, 0, 0, 0, false)
|
||||
testPromo(2, 1, 2, 5, 5, 0, true)
|
||||
testPromo(3, 1, 2, 0, 0, 1, true)
|
||||
}
|
||||
|
||||
func TestReplyStore(t *testing.T) {
|
||||
miscinit(t)
|
||||
if !c.PluginsInited {
|
||||
|
@ -1073,21 +1131,21 @@ func TestActivityStream(t *testing.T) {
|
|||
|
||||
func TestLogs(t *testing.T) {
|
||||
miscinit(t)
|
||||
gTests := func(store c.LogStore, phrase string) {
|
||||
expect(t, store.Count() == 0, "There shouldn't be any "+phrase)
|
||||
logs, err := store.GetOffset(0, 25)
|
||||
gTests := func(s c.LogStore, phrase string) {
|
||||
expect(t, s.Count() == 0, "There shouldn't be any "+phrase)
|
||||
logs, err := s.GetOffset(0, 25)
|
||||
expectNilErr(t, err)
|
||||
expect(t, len(logs) == 0, "The log slice should be empty")
|
||||
}
|
||||
gTests(c.ModLogs, "modlogs")
|
||||
gTests(c.AdminLogs, "adminlogs")
|
||||
|
||||
gTests2 := func(store c.LogStore, phrase string) {
|
||||
err := store.Create("something", 0, "bumblefly", "::1", 1)
|
||||
gTests2 := func(s c.LogStore, phrase string) {
|
||||
err := s.Create("something", 0, "bumblefly", "::1", 1)
|
||||
expectNilErr(t, err)
|
||||
count := store.Count()
|
||||
count := s.Count()
|
||||
expect(t, count == 1, fmt.Sprintf("store.Count should return one, not %d", count))
|
||||
logs, err := store.GetOffset(0, 25)
|
||||
logs, err := s.GetOffset(0, 25)
|
||||
recordMustExist(t, err, "We should have at-least one "+phrase)
|
||||
expect(t, len(logs) == 1, "The length of the log slice should be one")
|
||||
|
||||
|
@ -1113,125 +1171,125 @@ func TestPluginManager(t *testing.T) {
|
|||
|
||||
_, ok := c.Plugins["fairy-dust"]
|
||||
expect(t, !ok, "Plugin fairy-dust shouldn't exist")
|
||||
plugin, ok := c.Plugins["bbcode"]
|
||||
pl, ok := c.Plugins["bbcode"]
|
||||
expect(t, ok, "Plugin bbcode should exist")
|
||||
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err := plugin.BypassActive()
|
||||
expect(t, !pl.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !pl.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, !pl.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err := pl.BypassActive()
|
||||
expectNilErr(t, err)
|
||||
expect(t, !active, "Plugin bbcode shouldn't be active in the database either")
|
||||
hasPlugin, err := plugin.InDatabase()
|
||||
hasPlugin, err := pl.InDatabase()
|
||||
expectNilErr(t, err)
|
||||
expect(t, !hasPlugin, "Plugin bbcode shouldn't exist in the database")
|
||||
// TODO: Add some test cases for SetActive and SetInstalled before calling AddToDatabase
|
||||
|
||||
expectNilErr(t, plugin.AddToDatabase(true, false))
|
||||
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, plugin.Active, "Plugin bbcode should be active")
|
||||
active, err = plugin.BypassActive()
|
||||
expectNilErr(t, pl.AddToDatabase(true, false))
|
||||
expect(t, !pl.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !pl.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, pl.Active, "Plugin bbcode should be active")
|
||||
active, err = pl.BypassActive()
|
||||
expectNilErr(t, err)
|
||||
expect(t, active, "Plugin bbcode should be active in the database too")
|
||||
hasPlugin, err = plugin.InDatabase()
|
||||
hasPlugin, err = pl.InDatabase()
|
||||
expectNilErr(t, err)
|
||||
expect(t, hasPlugin, "Plugin bbcode should exist in the database")
|
||||
expect(t, plugin.Init != nil, "Plugin bbcode should have an init function")
|
||||
expectNilErr(t, plugin.Init(plugin))
|
||||
expect(t, pl.Init != nil, "Plugin bbcode should have an init function")
|
||||
expectNilErr(t, pl.Init(pl))
|
||||
|
||||
expectNilErr(t, plugin.SetActive(true))
|
||||
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, plugin.Active, "Plugin bbcode should still be active")
|
||||
active, err = plugin.BypassActive()
|
||||
expectNilErr(t, pl.SetActive(true))
|
||||
expect(t, !pl.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !pl.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, pl.Active, "Plugin bbcode should still be active")
|
||||
active, err = pl.BypassActive()
|
||||
expectNilErr(t, err)
|
||||
expect(t, active, "Plugin bbcode should still be active in the database too")
|
||||
hasPlugin, err = plugin.InDatabase()
|
||||
hasPlugin, err = pl.InDatabase()
|
||||
expectNilErr(t, err)
|
||||
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
|
||||
|
||||
expectNilErr(t, plugin.SetActive(false))
|
||||
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err = plugin.BypassActive()
|
||||
expectNilErr(t, pl.SetActive(false))
|
||||
expect(t, !pl.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !pl.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, !pl.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err = pl.BypassActive()
|
||||
expectNilErr(t, err)
|
||||
expect(t, !active, "Plugin bbcode shouldn't be active in the database")
|
||||
hasPlugin, err = plugin.InDatabase()
|
||||
hasPlugin, err = pl.InDatabase()
|
||||
expectNilErr(t, err)
|
||||
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
|
||||
expect(t, plugin.Deactivate != nil, "Plugin bbcode should have an init function")
|
||||
plugin.Deactivate(plugin) // Returns nothing
|
||||
expect(t, pl.Deactivate != nil, "Plugin bbcode should have an init function")
|
||||
pl.Deactivate(pl) // Returns nothing
|
||||
|
||||
// Not installable, should not be mutated
|
||||
expect(t, plugin.SetInstalled(true) == c.ErrPluginNotInstallable, "Plugin was set as installed despite not being installable")
|
||||
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err = plugin.BypassActive()
|
||||
expect(t, pl.SetInstalled(true) == c.ErrPluginNotInstallable, "Plugin was set as installed despite not being installable")
|
||||
expect(t, !pl.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !pl.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, !pl.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err = pl.BypassActive()
|
||||
expectNilErr(t, err)
|
||||
expect(t, !active, "Plugin bbcode shouldn't be active in the database either")
|
||||
hasPlugin, err = plugin.InDatabase()
|
||||
hasPlugin, err = pl.InDatabase()
|
||||
expectNilErr(t, err)
|
||||
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
|
||||
|
||||
expect(t, plugin.SetInstalled(false) == c.ErrPluginNotInstallable, "Plugin was set as not installed despite not being installable")
|
||||
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err = plugin.BypassActive()
|
||||
expect(t, pl.SetInstalled(false) == c.ErrPluginNotInstallable, "Plugin was set as not installed despite not being installable")
|
||||
expect(t, !pl.Installable, "Plugin bbcode shouldn't be installable")
|
||||
expect(t, !pl.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, !pl.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err = pl.BypassActive()
|
||||
expectNilErr(t, err)
|
||||
expect(t, !active, "Plugin bbcode shouldn't be active in the database either")
|
||||
hasPlugin, err = plugin.InDatabase()
|
||||
hasPlugin, err = pl.InDatabase()
|
||||
expectNilErr(t, err)
|
||||
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
|
||||
|
||||
// This isn't really installable, but we want to get a few tests done before getting plugins which are stateful
|
||||
plugin.Installable = true
|
||||
expectNilErr(t, plugin.SetInstalled(true))
|
||||
expect(t, plugin.Installable, "Plugin bbcode should be installable")
|
||||
expect(t, plugin.Installed, "Plugin bbcode should be 'installed'")
|
||||
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err = plugin.BypassActive()
|
||||
pl.Installable = true
|
||||
expectNilErr(t, pl.SetInstalled(true))
|
||||
expect(t, pl.Installable, "Plugin bbcode should be installable")
|
||||
expect(t, pl.Installed, "Plugin bbcode should be 'installed'")
|
||||
expect(t, !pl.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err = pl.BypassActive()
|
||||
expectNilErr(t, err)
|
||||
expect(t, !active, "Plugin bbcode shouldn't be active in the database either")
|
||||
hasPlugin, err = plugin.InDatabase()
|
||||
hasPlugin, err = pl.InDatabase()
|
||||
expectNilErr(t, err)
|
||||
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
|
||||
|
||||
expectNilErr(t, plugin.SetInstalled(false))
|
||||
expect(t, plugin.Installable, "Plugin bbcode should be installable")
|
||||
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err = plugin.BypassActive()
|
||||
expectNilErr(t, pl.SetInstalled(false))
|
||||
expect(t, pl.Installable, "Plugin bbcode should be installable")
|
||||
expect(t, !pl.Installed, "Plugin bbcode shouldn't be 'installed'")
|
||||
expect(t, !pl.Active, "Plugin bbcode shouldn't be active")
|
||||
active, err = pl.BypassActive()
|
||||
expectNilErr(t, err)
|
||||
expect(t, !active, "Plugin bbcode shouldn't be active in the database either")
|
||||
hasPlugin, err = plugin.InDatabase()
|
||||
hasPlugin, err = pl.InDatabase()
|
||||
expectNilErr(t, err)
|
||||
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
|
||||
|
||||
// Bugs sometimes arise when we try to delete a hook when there are multiple, so test for that
|
||||
// TODO: Do a finer grained test for that case...? A bigger test might catch more odd cases with multiple plugins
|
||||
plugin2, ok := c.Plugins["markdown"]
|
||||
pl2, ok := c.Plugins["markdown"]
|
||||
expect(t, ok, "Plugin markdown should exist")
|
||||
expect(t, !plugin2.Installable, "Plugin markdown shouldn't be installable")
|
||||
expect(t, !plugin2.Installed, "Plugin markdown shouldn't be 'installed'")
|
||||
expect(t, !plugin2.Active, "Plugin markdown shouldn't be active")
|
||||
active, err = plugin2.BypassActive()
|
||||
expect(t, !pl2.Installable, "Plugin markdown shouldn't be installable")
|
||||
expect(t, !pl2.Installed, "Plugin markdown shouldn't be 'installed'")
|
||||
expect(t, !pl2.Active, "Plugin markdown shouldn't be active")
|
||||
active, err = pl2.BypassActive()
|
||||
expectNilErr(t, err)
|
||||
expect(t, !active, "Plugin markdown shouldn't be active in the database either")
|
||||
hasPlugin, err = plugin2.InDatabase()
|
||||
hasPlugin, err = pl2.InDatabase()
|
||||
expectNilErr(t, err)
|
||||
expect(t, !hasPlugin, "Plugin markdown shouldn't exist in the database")
|
||||
|
||||
expectNilErr(t, plugin2.AddToDatabase(true, false))
|
||||
expectNilErr(t, plugin2.Init(plugin2))
|
||||
expectNilErr(t, plugin.SetActive(true))
|
||||
expectNilErr(t, plugin.Init(plugin))
|
||||
plugin2.Deactivate(plugin2)
|
||||
expectNilErr(t, plugin2.SetActive(false))
|
||||
plugin.Deactivate(plugin)
|
||||
expectNilErr(t, plugin.SetActive(false))
|
||||
expectNilErr(t, pl2.AddToDatabase(true, false))
|
||||
expectNilErr(t, pl2.Init(pl2))
|
||||
expectNilErr(t, pl.SetActive(true))
|
||||
expectNilErr(t, pl.Init(pl))
|
||||
pl2.Deactivate(pl2)
|
||||
expectNilErr(t, pl2.SetActive(false))
|
||||
pl.Deactivate(pl)
|
||||
expectNilErr(t, pl.SetActive(false))
|
||||
|
||||
// Hook tests
|
||||
ht := func() *c.HookTable {
|
||||
|
@ -1241,18 +1299,18 @@ func TestPluginManager(t *testing.T) {
|
|||
handle := func(in string) (out string) {
|
||||
return in + "hi"
|
||||
}
|
||||
plugin.AddHook("haha", handle)
|
||||
pl.AddHook("haha", handle)
|
||||
expect(t, ht().Sshook("haha", "ho") == "hohi", "Sshook didn't give hohi")
|
||||
plugin.RemoveHook("haha", handle)
|
||||
pl.RemoveHook("haha", handle)
|
||||
expect(t, ht().Sshook("haha", "ho") == "ho", "Sshook shouldn't have anything bound to it anymore")
|
||||
|
||||
expect(t, ht().Hook("haha", "ho") == "ho", "Hook shouldn't have anything bound to it yet")
|
||||
handle2 := func(inI interface{}) (out interface{}) {
|
||||
return inI.(string) + "hi"
|
||||
}
|
||||
plugin.AddHook("hehe", handle2)
|
||||
pl.AddHook("hehe", handle2)
|
||||
expect(t, ht().Hook("hehe", "ho").(string) == "hohi", "Hook didn't give hohi")
|
||||
plugin.RemoveHook("hehe", handle2)
|
||||
pl.RemoveHook("hehe", handle2)
|
||||
expect(t, ht().Hook("hehe", "ho").(string) == "ho", "Hook shouldn't have anything bound to it anymore")
|
||||
|
||||
// TODO: Add tests for more hook types
|
||||
|
@ -1260,7 +1318,7 @@ func TestPluginManager(t *testing.T) {
|
|||
|
||||
func TestPhrases(t *testing.T) {
|
||||
getPhrase := phrases.GetPermPhrase
|
||||
tp := func(name string, expects string) {
|
||||
tp := func(name, expects string) {
|
||||
res := getPhrase(name)
|
||||
expect(t, res == expects, "Not the expected phrase, got '"+res+"' instead")
|
||||
}
|
||||
|
@ -1572,7 +1630,7 @@ func TestAuth(t *testing.T) {
|
|||
}
|
||||
|
||||
// TODO: Vary the salts? Keep in mind that some algorithms store the salt in the hash therefore the salt string may be blank
|
||||
func passwordTest(t *testing.T, realPassword string, hashedPassword string) {
|
||||
func passwordTest(t *testing.T, realPassword, hashedPassword string) {
|
||||
if len(hashedPassword) < 10 {
|
||||
t.Error("Hash too short")
|
||||
}
|
||||
|
@ -1638,7 +1696,7 @@ type CountTestList struct {
|
|||
Items []CountTest
|
||||
}
|
||||
|
||||
func (l *CountTestList) Add(name string, msg string, expects int) {
|
||||
func (l *CountTestList) Add(name, msg string, expects int) {
|
||||
l.Items = append(l.Items, CountTest{name, msg, expects})
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ func init() {
|
|||
addPatch(27, patch27)
|
||||
addPatch(28, patch28)
|
||||
addPatch(29, patch29)
|
||||
addPatch(30, patch30)
|
||||
}
|
||||
|
||||
func patch0(scanner *bufio.Scanner) (err error) {
|
||||
|
@ -833,10 +834,6 @@ func patch29(scanner *bufio.Scanner) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = execStmt(qgen.Builder.SetDefaultColumn("users", "last_ip", "varchar", ""))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = execStmt(qgen.Builder.SetDefaultColumn("replies", "lastEdit", "int", "0"))
|
||||
if err != nil {
|
||||
|
@ -858,3 +855,11 @@ func patch29(scanner *bufio.Scanner) error {
|
|||
return execStmt(qgen.Builder.AddColumn("activity_stream", tC{"extra", "varchar", 200, false, false, "''"}, nil))
|
||||
|
||||
}
|
||||
|
||||
func patch30(scanner *bufio.Scanner) error {
|
||||
err := execStmt(qgen.Builder.AddColumn("users_groups_promotions", tC{"registeredFor", "int", 0, false, false, "0"}, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return execStmt(qgen.Builder.SetDefaultColumn("users", "last_ip", "varchar", ""))
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ func deleteAttachment(w http.ResponseWriter, r *http.Request, user c.User, aid i
|
|||
// TODO: Stop duplicating this code
|
||||
// TODO: Use a transaction here
|
||||
// TODO: Move this function to neutral ground
|
||||
func uploadAttachment(w http.ResponseWriter, r *http.Request, user c.User, sid int, sectionTable string, oid int, originTable string, extra string) (pathMap map[string]string, rerr c.RouteError) {
|
||||
func uploadAttachment(w http.ResponseWriter, r *http.Request, user c.User, sid int, sectionTable string, oid int, originTable, extra string) (pathMap map[string]string, rerr c.RouteError) {
|
||||
pathMap = make(map[string]string)
|
||||
files, rerr := uploadFilesWithHash(w, r, user, "./attachs/")
|
||||
if rerr != nil {
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"github.com/Azareal/Gosora/common/phrases"
|
||||
)
|
||||
|
||||
func IPSearch(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError {
|
||||
header.Title = phrases.GetTitlePhrase("ip_search")
|
||||
func IPSearch(w http.ResponseWriter, r *http.Request, user c.User, h *c.Header) c.RouteError {
|
||||
h.Title = phrases.GetTitlePhrase("ip_search")
|
||||
// TODO: How should we handle the permissions if we extend this into an alt detector of sorts?
|
||||
if !user.Perms.ViewIPs {
|
||||
return c.NoPermissions(w, r, user)
|
||||
|
@ -26,5 +26,5 @@ func IPSearch(w http.ResponseWriter, r *http.Request, user c.User, header *c.Hea
|
|||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
return renderTemplate("ip_search", w, r, header, c.IPSearchPage{header, userList, ip})
|
||||
return renderTemplate("ip_search", w, r, h, c.IPSearchPage{h, userList, ip})
|
||||
}
|
||||
|
|
|
@ -29,8 +29,7 @@ func Groups(w http.ResponseWriter, r *http.Request, u c.User) c.RouteError {
|
|||
if count == perPage {
|
||||
break
|
||||
}
|
||||
var rank string
|
||||
var rankClass string
|
||||
var rank, rankClass string
|
||||
canDelete := false
|
||||
|
||||
// TODO: Localise this
|
||||
|
@ -215,6 +214,20 @@ func GroupsPromotionsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
|
|||
return c.LocalError("posts must be integer", w, r, user)
|
||||
}
|
||||
|
||||
registeredHours, err := strconv.Atoi(r.FormValue("registered_hours"))
|
||||
if err != nil {
|
||||
return c.LocalError("registered_hours must be integer", w, r, user)
|
||||
}
|
||||
registeredDays, err := strconv.Atoi(r.FormValue("registered_days"))
|
||||
if err != nil {
|
||||
return c.LocalError("registered_days must be integer", w, r, user)
|
||||
}
|
||||
registeredMonths, err := strconv.Atoi(r.FormValue("registered_months"))
|
||||
if err != nil {
|
||||
return c.LocalError("registered_months must be integer", w, r, user)
|
||||
}
|
||||
registeredMinutes := (registeredHours * 60) + (registeredDays * 24 * 60) + (registeredMonths * 30 * 24 * 60)
|
||||
|
||||
g, err := c.Groups.Get(from)
|
||||
ferr := groupCheck(w, r, user, g, err)
|
||||
if err != nil {
|
||||
|
@ -225,7 +238,7 @@ func GroupsPromotionsCreateSubmit(w http.ResponseWriter, r *http.Request, user c
|
|||
if err != nil {
|
||||
return ferr
|
||||
}
|
||||
pid, err := c.GroupPromotions.Create(from, to, twoWay, level, posts)
|
||||
pid, err := c.GroupPromotions.Create(from, to, twoWay, level, posts, registeredMinutes)
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
|
|
@ -99,34 +99,34 @@ func UsersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid s
|
|||
return c.LocalError("Only administrators can edit the account of other administrators.", w, r, user)
|
||||
}
|
||||
|
||||
newname := c.SanitiseSingleLine(r.PostFormValue("name"))
|
||||
if newname == "" {
|
||||
newName := c.SanitiseSingleLine(r.PostFormValue("name"))
|
||||
if newName == "" {
|
||||
return c.LocalError("You didn't put in a name.", w, r, user)
|
||||
}
|
||||
|
||||
// TODO: How should activation factor into admin set emails?
|
||||
// TODO: How should we handle secondary emails? Do we even have secondary emails implemented?
|
||||
newemail := c.SanitiseSingleLine(r.PostFormValue("email"))
|
||||
if newemail == "" && targetUser.Email != "" {
|
||||
newEmail := c.SanitiseSingleLine(r.PostFormValue("email"))
|
||||
if newEmail == "" && targetUser.Email != "" {
|
||||
return c.LocalError("You didn't put in an email address.", w, r, user)
|
||||
}
|
||||
if newemail == "-1" {
|
||||
newemail = targetUser.Email
|
||||
if newEmail == "-1" {
|
||||
newEmail = targetUser.Email
|
||||
}
|
||||
if (newemail != targetUser.Email) && !user.Perms.EditUserEmail {
|
||||
if (newEmail != targetUser.Email) && !user.Perms.EditUserEmail {
|
||||
return c.LocalError("You need the EditUserEmail permission to edit the email address of a user.", w, r, user)
|
||||
}
|
||||
|
||||
newpassword := r.PostFormValue("password")
|
||||
if newpassword != "" && !user.Perms.EditUserPassword {
|
||||
newPassword := r.PostFormValue("password")
|
||||
if newPassword != "" && !user.Perms.EditUserPassword {
|
||||
return c.LocalError("You need the EditUserPassword permission to edit the password of a user.", w, r, user)
|
||||
}
|
||||
|
||||
newgroup, err := strconv.Atoi(r.PostFormValue("group"))
|
||||
newGroup, err := strconv.Atoi(r.PostFormValue("group"))
|
||||
if err != nil {
|
||||
return c.LocalError("You need to provide a whole number for the group ID", w, r, user)
|
||||
}
|
||||
group, err := c.Groups.Get(newgroup)
|
||||
group, err := c.Groups.Get(newGroup)
|
||||
if err == sql.ErrNoRows {
|
||||
return c.LocalError("The group you're trying to place this user in doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
|
@ -139,20 +139,32 @@ func UsersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, suid s
|
|||
return c.LocalError("You need the EditUserGroupSuperMod permission to assign someone to a super mod group.", w, r, user)
|
||||
}
|
||||
|
||||
err = targetUser.Update(newname, newemail, newgroup)
|
||||
err = targetUser.Update(newName, newEmail, newGroup)
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
red := false
|
||||
if newpassword != "" {
|
||||
c.SetPassword(targetUser.ID, newpassword)
|
||||
if newPassword != "" {
|
||||
c.SetPassword(targetUser.ID, newPassword)
|
||||
// Log the user out as a safety precaution
|
||||
c.Auth.ForceLogout(targetUser.ID)
|
||||
red = true
|
||||
}
|
||||
targetUser.CacheRemove()
|
||||
|
||||
targetUser, err = c.Users.Get(uid)
|
||||
if err == sql.ErrNoRows {
|
||||
return c.LocalError("The user you're trying to edit doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
err = c.GroupPromotions.PromoteIfEligible(targetUser, targetUser.Level, targetUser.Posts, targetUser.CreatedAt)
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
targetUser.CacheRemove()
|
||||
|
||||
err = c.AdminLogs.Create("edit", targetUser.ID, "user", user.GetIP(), user.ID)
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
|
|
|
@ -15,7 +15,6 @@ func PollVote(w http.ResponseWriter, r *http.Request, user c.User, sPollID strin
|
|||
if err != nil {
|
||||
return c.PreError("The provided PollID is not a valid number.", w, r)
|
||||
}
|
||||
|
||||
poll, err := c.Polls.Get(pollID)
|
||||
if err == sql.ErrNoRows {
|
||||
return c.PreError("The poll you tried to vote for doesn't exist.", w, r)
|
||||
|
@ -72,7 +71,6 @@ func PollResults(w http.ResponseWriter, r *http.Request, user c.User, sPollID st
|
|||
if err != nil {
|
||||
return c.PreError("The provided PollID is not a valid number.", w, r)
|
||||
}
|
||||
|
||||
poll, err := c.Polls.Get(pollID)
|
||||
if err == sql.ErrNoRows {
|
||||
return c.PreError("The poll you tried to vote for doesn't exist.", w, r)
|
||||
|
@ -81,7 +79,7 @@ func PollResults(w http.ResponseWriter, r *http.Request, user c.User, sPollID st
|
|||
}
|
||||
|
||||
// TODO: Abstract this
|
||||
rows, err := qgen.NewAcc().Select("polls_options").Columns("votes").Where("pollID = ?").Orderby("option ASC").Query(poll.ID)
|
||||
rows, err := qgen.NewAcc().Select("polls_options").Columns("votes").Where("pollID=?").Orderby("option ASC").Query(poll.ID)
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
|
|
@ -9,14 +9,14 @@ import (
|
|||
"github.com/Azareal/Gosora/common/counters"
|
||||
)
|
||||
|
||||
func ReportSubmit(w http.ResponseWriter, r *http.Request, user c.User, sitemID string) c.RouteError {
|
||||
func ReportSubmit(w http.ResponseWriter, r *http.Request, user c.User, sItemID string) c.RouteError {
|
||||
headerLite, ferr := c.SimpleUserCheck(w, r, &user)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
js := r.PostFormValue("js") == "1"
|
||||
|
||||
itemID, err := strconv.Atoi(sitemID)
|
||||
itemID, err := strconv.Atoi(sItemID)
|
||||
if err != nil {
|
||||
return c.LocalError("Bad ID", w, r, user)
|
||||
}
|
||||
|
|
|
@ -39,9 +39,9 @@ var topicStmts TopicStmts
|
|||
func init() {
|
||||
c.DbInits.Add(func(acc *qgen.Accumulator) error {
|
||||
topicStmts = TopicStmts{
|
||||
getLikedTopic: acc.Select("likes").Columns("targetItem").Where("sentBy = ? && targetItem = ? && targetType = 'topics'").Prepare(),
|
||||
getLikedTopic: acc.Select("likes").Columns("targetItem").Where("sentBy=? && targetItem=? && targetType='topics'").Prepare(),
|
||||
// TODO: Less race-y attachment count updates
|
||||
updateAttachs: acc.Update("topics").Set("attachCount = ?").Where("tid = ?").Prepare(),
|
||||
updateAttachs: acc.Update("topics").Set("attachCount=?").Where("tid=?").Prepare(),
|
||||
}
|
||||
return acc.FirstError()
|
||||
})
|
||||
|
@ -327,9 +327,9 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, user c.User, header *c.
|
|||
}
|
||||
|
||||
// Do a bulk forum fetch, just in case it's the SqlForumStore?
|
||||
forum := c.Forums.DirtyGet(ffid)
|
||||
if forum.Name != "" && forum.Active {
|
||||
fcopy := forum.Copy()
|
||||
f := c.Forums.DirtyGet(ffid)
|
||||
if f.Name != "" && f.Active {
|
||||
fcopy := f.Copy()
|
||||
// TODO: Abstract this
|
||||
if header.Hooks.HookSkippable("topic_create_frow_assign", &fcopy) {
|
||||
continue
|
||||
|
@ -707,7 +707,7 @@ func StickTopicSubmit(w http.ResponseWriter, r *http.Request, user c.User, stid
|
|||
return topicActionPost(topic.Stick(), "stick", w, r, lite, topic, user)
|
||||
}
|
||||
|
||||
func topicActionPre(stid string, action string, w http.ResponseWriter, r *http.Request, user c.User) (*c.Topic, *c.HeaderLite, c.RouteError) {
|
||||
func topicActionPre(stid, action string, w http.ResponseWriter, r *http.Request, user c.User) (*c.Topic, *c.HeaderLite, c.RouteError) {
|
||||
tid, err := strconv.Atoi(stid)
|
||||
if err != nil {
|
||||
return nil, nil, c.PreError(phrases.GetErrorPhrase("id_must_be_integer"), w, r)
|
||||
|
|
|
@ -32,11 +32,11 @@ func TopicListMostViewed(w http.ResponseWriter, r *http.Request, user c.User, h
|
|||
}
|
||||
|
||||
// TODO: Implement search
|
||||
func TopicListCommon(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header, torder string, tsorder string) c.RouteError {
|
||||
header.Title = phrases.GetTitlePhrase("topics")
|
||||
header.Zone = "topics"
|
||||
header.Path = "/topics/"
|
||||
header.MetaDesc = header.Settings["meta_desc"].(string)
|
||||
func TopicListCommon(w http.ResponseWriter, r *http.Request, user c.User, h *c.Header, torder, tsorder string) c.RouteError {
|
||||
h.Title = phrases.GetTitlePhrase("topics")
|
||||
h.Zone = "topics"
|
||||
h.Path = "/topics/"
|
||||
h.MetaDesc = h.Settings["meta_desc"].(string)
|
||||
|
||||
group, err := c.Groups.Get(user.Group)
|
||||
if err != nil {
|
||||
|
@ -61,8 +61,8 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user c.User, header
|
|||
if err != nil {
|
||||
return c.LocalError("Invalid fid forum", w, r, user)
|
||||
}
|
||||
header.Title = forum.Name
|
||||
header.ZoneID = forum.ID
|
||||
h.Title = forum.Name
|
||||
h.ZoneID = forum.ID
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,8 +95,8 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user c.User, header
|
|||
}
|
||||
for _, fid := range fids {
|
||||
if inSlice(canSee, fid) {
|
||||
forum := c.Forums.DirtyGet(fid)
|
||||
if forum.Name != "" && forum.Active && (forum.ParentType == "" || forum.ParentType == "forum") {
|
||||
f := c.Forums.DirtyGet(fid)
|
||||
if f.Name != "" && f.Active && (f.ParentType == "" || f.ParentType == "forum") {
|
||||
// TODO: Add a hook here for plugin_guilds?
|
||||
cfids = append(cfids, fid)
|
||||
}
|
||||
|
@ -118,10 +118,10 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user c.User, header
|
|||
return c.InternalError(err, w, r)
|
||||
}
|
||||
reqUserList := make(map[int]bool)
|
||||
for _, topic := range tMap {
|
||||
reqUserList[topic.CreatedBy] = true
|
||||
reqUserList[topic.LastReplyBy] = true
|
||||
topicList = append(topicList, topic.TopicsRow())
|
||||
for _, t := range tMap {
|
||||
reqUserList[t.CreatedBy] = true
|
||||
reqUserList[t.LastReplyBy] = true
|
||||
topicList = append(topicList, t.TopicsRow())
|
||||
}
|
||||
//fmt.Printf("reqUserList %+v\n", reqUserList)
|
||||
|
||||
|
@ -141,18 +141,18 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user c.User, header
|
|||
}
|
||||
|
||||
// TODO: De-dupe this logic in common/topic_list.go?
|
||||
for _, topic := range topicList {
|
||||
topic.Link = c.BuildTopicURL(c.NameToSlug(topic.Title), topic.ID)
|
||||
for _, t := range topicList {
|
||||
t.Link = c.BuildTopicURL(c.NameToSlug(t.Title), t.ID)
|
||||
// TODO: Pass forum to something like topic.Forum and use that instead of these two properties? Could be more flexible.
|
||||
forum := c.Forums.DirtyGet(topic.ParentID)
|
||||
topic.ForumName = forum.Name
|
||||
topic.ForumLink = forum.Link
|
||||
forum := c.Forums.DirtyGet(t.ParentID)
|
||||
t.ForumName = forum.Name
|
||||
t.ForumLink = forum.Link
|
||||
|
||||
// TODO: Create a specialised function with a bit less overhead for getting the last page for a post count
|
||||
_, _, lastPage := c.PageOffset(topic.PostCount, 1, c.Config.ItemsPerPage)
|
||||
topic.LastPage = lastPage
|
||||
topic.Creator = userList[topic.CreatedBy]
|
||||
topic.LastUser = userList[topic.LastReplyBy]
|
||||
_, _, lastPage := c.PageOffset(t.PostCount, 1, c.Config.ItemsPerPage)
|
||||
t.LastPage = lastPage
|
||||
t.Creator = userList[t.CreatedBy]
|
||||
t.LastUser = userList[t.LastReplyBy]
|
||||
}
|
||||
|
||||
// TODO: Reduce the amount of boilerplate here
|
||||
|
@ -165,9 +165,9 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user c.User, header
|
|||
return nil
|
||||
}
|
||||
|
||||
header.Title = phrases.GetTitlePhrase("topics_search")
|
||||
pi := c.TopicListPage{header, topicList, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, paginator}
|
||||
return renderTemplate("topics", w, r, header, pi)
|
||||
h.Title = phrases.GetTitlePhrase("topics_search")
|
||||
pi := c.TopicListPage{h, topicList, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, paginator}
|
||||
return renderTemplate("topics", w, r, h, pi)
|
||||
}
|
||||
|
||||
// TODO: Pass a struct back rather than passing back so many variables
|
||||
|
@ -190,6 +190,6 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user c.User, header
|
|||
return nil
|
||||
}
|
||||
|
||||
pi := c.TopicListPage{header, topicList, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, paginator}
|
||||
return renderTemplate("topics", w, r, header, pi)
|
||||
pi := c.TopicListPage{h, topicList, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, paginator}
|
||||
return renderTemplate("topics", w, r, h, pi)
|
||||
}
|
||||
|
|
|
@ -171,6 +171,18 @@ func ActivateUser(w http.ResponseWriter, r *http.Request, user c.User, suid stri
|
|||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
targetUser, err = c.Users.Get(uid)
|
||||
if err == sql.ErrNoRows {
|
||||
return c.LocalError("The account you're trying to activate no longer exists.", w, r, user)
|
||||
} else if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
err = c.GroupPromotions.PromoteIfEligible(targetUser, targetUser.Level, targetUser.Posts, targetUser.CreatedAt)
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
targetUser.CacheRemove()
|
||||
|
||||
err = c.ModLogs.Create("activate", targetUser.ID, "user", user.GetIP(), user.ID)
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
|
|
|
@ -9,7 +9,7 @@ CREATE TABLE [users] (
|
|||
[createdAt] datetime not null,
|
||||
[lastActiveAt] datetime not null,
|
||||
[session] nvarchar (200) DEFAULT '' not null,
|
||||
[last_ip] nvarchar (200) DEFAULT '0.0.0.0.0' not null,
|
||||
[last_ip] nvarchar (200) DEFAULT '' not null,
|
||||
[enable_embeds] int DEFAULT -1 not null,
|
||||
[email] nvarchar (200) DEFAULT '' not null,
|
||||
[avatar] nvarchar (100) DEFAULT '' not null,
|
||||
|
|
|
@ -6,5 +6,6 @@ CREATE TABLE [users_groups_promotions] (
|
|||
[level] int not null,
|
||||
[posts] int DEFAULT 0 not null,
|
||||
[minTime] int not null,
|
||||
[registeredFor] int DEFAULT 0 not null,
|
||||
primary key([pid])
|
||||
);
|
|
@ -9,7 +9,7 @@ CREATE TABLE `users` (
|
|||
`createdAt` datetime not null,
|
||||
`lastActiveAt` datetime not null,
|
||||
`session` varchar(200) DEFAULT '' not null,
|
||||
`last_ip` varchar(200) DEFAULT '0.0.0.0.0' not null,
|
||||
`last_ip` varchar(200) DEFAULT '' not null,
|
||||
`enable_embeds` int DEFAULT -1 not null,
|
||||
`email` varchar(200) DEFAULT '' not null,
|
||||
`avatar` varchar(100) DEFAULT '' not null,
|
||||
|
|
|
@ -6,5 +6,6 @@ CREATE TABLE `users_groups_promotions` (
|
|||
`level` int not null,
|
||||
`posts` int DEFAULT 0 not null,
|
||||
`minTime` int not null,
|
||||
`registeredFor` int DEFAULT 0 not null,
|
||||
primary key(`pid`)
|
||||
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
|
|
@ -9,7 +9,7 @@ CREATE TABLE "users" (
|
|||
`createdAt` timestamp not null,
|
||||
`lastActiveAt` timestamp not null,
|
||||
`session` varchar (200) DEFAULT '' not null,
|
||||
`last_ip` varchar (200) DEFAULT '0.0.0.0.0' not null,
|
||||
`last_ip` varchar (200) DEFAULT '' not null,
|
||||
`enable_embeds` int DEFAULT -1 not null,
|
||||
`email` varchar (200) DEFAULT '' not null,
|
||||
`avatar` varchar (100) DEFAULT '' not null,
|
||||
|
|
|
@ -6,5 +6,6 @@ CREATE TABLE "users_groups_promotions" (
|
|||
`level` int not null,
|
||||
`posts` int DEFAULT 0 not null,
|
||||
`minTime` int not null,
|
||||
`registeredFor` int DEFAULT 0 not null,
|
||||
primary key(`pid`)
|
||||
);
|
|
@ -13,6 +13,7 @@
|
|||
<a href="#p-{{.ID}}">{{.FromGroup.Name}} -> {{.ToGroup.Name}}{{if .TwoWay}} (two way){{end}}</a>
|
||||
{{if .Level}}<span> - {{lang "panel_group_promotions_level_prefix"}}{{.Level}}</span>{{end}}
|
||||
{{if .Posts}}<span> - {{lang "panel_group_promotions_posts_prefix"}}{{.Posts}}</span>{{end}}
|
||||
{{if .RegisteredFor}}<span> - registered for {{.RegisteredFor}} minutes</span>{{end}}
|
||||
<div class="to_right">
|
||||
<a href="/panel/groups/promotions/delete/submit/{{$.ID}}-{{.ID}}?s={{$.CurrentUser.Session}}"><button form="nn">{{lang "panel_group_promotions_delete_button"}}</button></a>
|
||||
</div>
|
||||
|
@ -56,11 +57,19 @@
|
|||
</div>
|
||||
<div class="formrow">
|
||||
<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 class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_group_promotions_posts"}}</a></div>
|
||||
<div class="formitem"><input name="posts" type="number" value=0 /></div>
|
||||
<div class="formitem"><input name="posts" type="number" value="0"/></div>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_group_promotion_registered_for"}}</a></div>
|
||||
<div class="formitem">
|
||||
<input name="registered_months" type="number" value="0"/> months<br>
|
||||
<input name="registered_days" type="number" value="0"/> days<br>
|
||||
<input name="registered_hours" type="number" value="0"/> hours
|
||||
</div>
|
||||
</div>
|
||||
<div class="formrow form_button_row">
|
||||
<div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_group_promotions_create_button"}}</button></div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="search widget_search">
|
||||
<input class="widget_search_input" name="widget_search" placeholder="Search" type="search" />
|
||||
<input class="widget_search_input" name="widget_search" placeholder="Search" type="search"/>
|
||||
</div>
|
||||
<div class="rowblock filter_list widget_filter">
|
||||
{{range .Forums}} <div class="rowitem filter_item{{if .Selected}} filter_selected{{end}}" data-fid={{.ID}}><a href="/topics/?fids={{.ID}}" rel="nofollow">{{.Name}} ({{.TopicCount}})</a></div>
|
||||
|
|
Loading…
Reference in New Issue