Add per-user profile comment visibility settings.

Honor user blocks in ProfileReplyEditSubmit.
Reduce boilerplate.

Update account_privacy_profile_comments phrase.
Add account_privacy_profile_comments_public phrase.
Add account_privacy_profile_comments_registered phrase.
Add account_privacy_profile_comments_self phrase.
Add account_privacy_enable_embeds phrase.

Add profile_comments column to users table.
Add who_can_convo column to users table.

You will need to run the updater / patcher for this commit.
This commit is contained in:
Azareal 2020-07-15 07:50:29 +10:00
parent 4bdc528744
commit b1af963916
50 changed files with 919 additions and 767 deletions

View File

@ -5,12 +5,16 @@ import qgen "github.com/Azareal/Gosora/query_gen"
var mysqlPre = "utf8mb4"
var mysqlCol = "utf8mb4_general_ci"
var tables []string
type tblColumn = qgen.DBTableColumn
type tC = tblColumn
type tblKey = qgen.DBTableKey
func createTables(a qgen.Adapter) error {
tables = nil
f := func(table, charset, collation string, cols []tC, keys []tblKey) error {
tables = append(tables, table)
return qgen.Install.CreateTable(table, charset, collation, cols, keys)
}
return createTables2(a, f)
@ -23,30 +27,65 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
}
err = f(table, charset, collation, cols, keys)
}
bcol := func(col string, val bool) qgen.DBTableColumn {
if val {
return tC{col, "boolean", 0, false, false, "1"}
}
return tC{col, "boolean", 0, false, false, "0"}
}
ccol := func(col string, size int, sdefault string) qgen.DBTableColumn {
return tC{col, "varchar", size, false, false, sdefault}
}
text := func(params ...string) qgen.DBTableColumn {
if len(params) == 0 {
return tC{"", "text", 0, false, false, ""}
}
col, sdefault := params[0], ""
if len(params) > 1 {
sdefault = params[1]
if sdefault == "" {
sdefault = "''"
}
}
return tC{col, "text", 0, false, false, sdefault}
}
createdAt := func(coll ...string) qgen.DBTableColumn {
var col string
if len(coll) > 0 {
col = coll[0]
}
if col == "" {
col = "createdAt"
}
return tC{col, "createdAt", 0, false, false, ""}
}
createTable("users", mysqlPre, mysqlCol,
[]tC{
tC{"uid", "int", 0, false, true, ""},
tC{"name", "varchar", 100, false, false, ""},
tC{"password", "varchar", 100, false, false, ""},
ccol("name", 100, ""),
ccol("password", 100, ""),
tC{"salt", "varchar", 80, false, false, "''"},
ccol("salt", 80, "''"),
tC{"group", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"active", "boolean", 0, false, false, "0"},
tC{"is_super_admin", "boolean", 0, false, false, "0"},
tC{"createdAt", "createdAt", 0, false, false, ""},
bcol("active", false),
bcol("is_super_admin", false),
createdAt(),
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, "''"},
ccol("session", 200, "''"),
//ccol("authToken", 200, "''"),
ccol("last_ip", 200, "''"),
tC{"profile_comments", "int", 0, false, false, "0"},
tC{"who_can_convo", "int", 0, false, false, "0"},
tC{"enable_embeds", "int", 0, false, false, "-1"},
tC{"email", "varchar", 200, false, false, "''"},
tC{"avatar", "varchar", 100, false, false, "''"},
tC{"message", "text", 0, false, false, "''"},
ccol("email", 200, "''"),
ccol("avatar", 100, "''"),
text("message"),
// TODO: Drop these columns?
tC{"url_prefix", "varchar", 20, false, false, "''"},
tC{"url_name", "varchar", 100, false, false, "''"},
//tC{"pub_key", "text", 0, false, false, "''"},
ccol("url_prefix", 20, "''"),
ccol("url_name", 100, "''"),
//text("pub_key"),
tC{"level", "smallint", 0, false, false, "0"},
tC{"score", "int", 0, false, false, "0"},
@ -63,27 +102,27 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
//tC{"penalty_count","int",0,false,false,"0"},
tC{"temp_group", "int", 0, false, false, "0"}, // For temporary groups, set this to zero when a temporary group isn't in effect
},
[]tblKey{
tblKey{"uid", "primary", "", false},
tblKey{"name", "unique", "", false},
[]tK{
tK{"uid", "primary", "", false},
tK{"name", "unique", "", false},
},
)
createTable("users_groups", mysqlPre, mysqlCol,
[]tC{
tC{"gid", "int", 0, false, true, ""},
tC{"name", "varchar", 100, false, false, ""},
tC{"permissions", "text", 0, false, false, ""},
tC{"plugin_perms", "text", 0, false, false, ""},
tC{"is_mod", "boolean", 0, false, false, "0"},
tC{"is_admin", "boolean", 0, false, false, "0"},
tC{"is_banned", "boolean", 0, false, false, "0"},
ccol("name", 100, ""),
text("permissions"),
text("plugin_perms"),
bcol("is_mod", false),
bcol("is_admin", false),
bcol("is_banned", false),
tC{"user_count", "int", 0, false, false, "0"}, // TODO: Implement this
tC{"tag", "varchar", 50, false, false, "''"},
ccol("tag", 50, "''"),
},
[]tblKey{
tblKey{"gid", "primary", "", false},
[]tK{
tK{"gid", "primary", "", false},
},
)
@ -92,7 +131,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
tC{"pid", "int", 0, false, true, ""},
tC{"from_gid", "int", 0, false, false, ""},
tC{"to_gid", "int", 0, false, false, ""},
tC{"two_way", "boolean", 0, false, false, "0"}, // If a user no longer meets the requirements for this promotion then they will be demoted if this flag is set
bcol("two_way", false), // If a user no longer meets the requirements for this promotion then they will be demoted if this flag is set
// Requirements
tC{"level", "int", 0, false, false, ""},
@ -100,8 +139,8 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
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},
[]tK{
tK{"pid", "primary", "", false},
},
)
@ -122,15 +161,15 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("users_2fa_keys", mysqlPre, mysqlCol,
[]tC{
tC{"uid", "int", 0, false, false, ""},
tC{"secret", "varchar", 100, false, false, ""},
tC{"scratch1", "varchar", 50, false, false, ""},
tC{"scratch2", "varchar", 50, false, false, ""},
tC{"scratch3", "varchar", 50, false, false, ""},
tC{"scratch4", "varchar", 50, false, false, ""},
tC{"scratch5", "varchar", 50, false, false, ""},
tC{"scratch6", "varchar", 50, false, false, ""},
tC{"scratch7", "varchar", 50, false, false, ""},
tC{"scratch8", "varchar", 50, false, false, ""},
ccol("secret", 100, ""),
ccol("scratch1", 50, ""),
ccol("scratch2", 50, ""),
ccol("scratch3", 50, ""),
ccol("scratch4", 50, ""),
ccol("scratch5", 50, ""),
ccol("scratch6", 50, ""),
ccol("scratch7", 50, ""),
ccol("scratch8", 50, ""),
tC{"createdAt", "createdAt", 0, false, false, ""},
},
[]tblKey{
@ -148,12 +187,12 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"uid","int",0,false,false,""},
tC{"element_id","int",0,false,false,""},
tC{"element_type","varchar",50,false,false,""}, //forum, profile?, and social_group. Leave blank for global.
tC{"overrides","text",0,false,false,"{}"},
ccol("element_type",50,""), //forum, profile?, and social_group. Leave blank for global.
text("overrides","{}"),
tC{"mod_queue","boolean",0,false,false,"0"},
tC{"shadow_ban","boolean",0,false,false,"0"},
tC{"no_avatar","boolean",0,false,false,"0"}, // Coming Soon. Should this be a perm override instead?
bcol("mod_queue",false),
bcol("shadow_ban",false),
bcol("no_avatar",false), // Coming Soon. Should this be a perm override instead?
// Do we *really* need rate-limit penalty types? Are we going to be allowing bots or something?
//tC{"posts_per_hour","int",0,false,false,"0"},
@ -163,7 +202,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
//tC{"last_hour","int",0,false,false,"0"}, // UNIX Time, as we don't need to do anything too fancy here. When an hour has elapsed since that time, reset the hourly penalty counters.
tC{"issued_by","int",0,false,false,""},
tC{"issued_at","createdAt",0,false,false,""},
createdAt("issued_at"),
tC{"expires_at","datetime",0,false,false,""},
}, nil,
)*/
@ -174,7 +213,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
tC{"set_group", "int", 0, false, false, ""},
tC{"issued_by", "int", 0, false, false, ""},
tC{"issued_at", "createdAt", 0, false, false, ""},
createdAt("issued_at"),
tC{"revert_at", "datetime", 0, false, false, ""},
tC{"temporary", "boolean", 0, false, false, ""}, // special case for permanent bans to do the necessary bookkeeping, might be removed in the future
},
@ -197,10 +236,10 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
// TODO: Add an autoincrement key?
createTable("emails", "", "",
[]tC{
tC{"email", "varchar", 200, false, false, ""},
ccol("email", 200, ""),
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"validated", "boolean", 0, false, false, "0"},
tC{"token", "varchar", 200, false, false, "''"},
bcol("validated", false),
ccol("token", 200, "''"),
}, nil,
)
@ -208,11 +247,11 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
/*
createTable("email_domain_blacklist", "", "",
[]tC{
tC{"domain", "varchar", 200, false, false, ""},
tC{"gtld", "boolean", 0, false, false, "0"},
ccol("domain", 200, ""),
bcol("gtld", false),
},
[]tblKey{
tblKey{"domain", "primary"},
[]tK{
tK{"domain", "primary"},
},
)
*/
@ -220,26 +259,26 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
// TODO: Implement password resets
createTable("password_resets", "", "",
[]tC{
tC{"email", "varchar", 200, false, false, ""},
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"validated", "varchar", 200, false, false, ""}, // Token given once the one-use token is consumed, used to prevent multiple people consuming the same one-use token
tC{"token", "varchar", 200, false, false, ""},
tC{"createdAt", "createdAt", 0, false, false, ""},
ccol("email", 200, ""),
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
ccol("validated", 200, ""), // Token given once the one-use token is consumed, used to prevent multiple people consuming the same one-use token
ccol("token", 200, ""),
createdAt(),
}, nil,
)
createTable("forums", mysqlPre, mysqlCol,
[]tC{
tC{"fid", "int", 0, false, true, ""},
tC{"name", "varchar", 100, false, false, ""},
tC{"desc", "varchar", 200, false, false, ""},
tC{"tmpl", "varchar", 200, false, false, "''"},
tC{"active", "boolean", 0, false, false, "1"},
ccol("name", 100, ""),
ccol("desc", 200, ""),
ccol("tmpl", 200, "''"),
bcol("active", true),
tC{"order", "int", 0, false, false, "0"},
tC{"topicCount", "int", 0, false, false, "0"},
tC{"preset", "varchar", 100, false, false, "''"},
ccol("preset", 100, "''"),
tC{"parentID", "int", 0, false, false, "0"},
tC{"parentType", "varchar", 50, false, false, "''"},
ccol("parentType", 50, "''"),
tC{"lastTopicID", "int", 0, false, false, "0"},
tC{"lastReplyerID", "int", 0, false, false, "0"},
},
@ -252,8 +291,8 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"fid", "int", 0, false, false, ""},
tC{"gid", "int", 0, false, false, ""},
tC{"preset", "varchar", 100, false, false, "''"},
tC{"permissions", "text", 0, false, false, ""},
ccol("preset", 100, "''"),
text("permissions", "{}"),
},
[]tblKey{
// TODO: Test to see that the compound primary key works
@ -264,19 +303,19 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("topics", mysqlPre, mysqlCol,
[]tC{
tC{"tid", "int", 0, false, true, ""},
tC{"title", "varchar", 100, false, false, ""}, // TODO: Increase the max length to 200?
tC{"content", "text", 0, false, false, ""},
tC{"parsed_content", "text", 0, false, false, ""},
tC{"createdAt", "createdAt", 0, false, false, ""},
ccol("title", 100, ""), // TODO: Increase the max length to 200?
text("content"),
text("parsed_content"),
createdAt(),
tC{"lastReplyAt", "datetime", 0, false, false, ""},
tC{"lastReplyBy", "int", 0, false, false, ""},
tC{"lastReplyID", "int", 0, false, false, "0"},
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"is_closed", "boolean", 0, false, false, "0"},
tC{"sticky", "boolean", 0, false, false, "0"},
bcol("is_closed", false),
bcol("sticky", false),
// TODO: Add an index for this
tC{"parentID", "int", 0, false, false, "2"},
tC{"ip", "varchar", 200, false, false, "''"},
ccol("ip", 200, "''"),
tC{"postCount", "int", 0, false, false, "1"},
tC{"likeCount", "int", 0, false, false, "0"},
tC{"attachCount", "int", 0, false, false, "0"},
@ -288,14 +327,14 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
// ? - A little hacky, maybe we could do something less likely to bite us with huge numbers of topics?
// TODO: Add an index for this?
//tC{"lastMonth", "datetime", 0, false, false, ""},
tC{"css_class", "varchar", 100, false, false, "''"},
ccol("css_class", 100, "''"),
tC{"poll", "int", 0, false, false, "0"},
tC{"data", "varchar", 200, false, false, "''"},
ccol("data", 200, "''"),
},
[]tblKey{
tblKey{"tid", "primary", "", false},
tblKey{"title", "fulltext", "", false},
tblKey{"content", "fulltext", "", false},
[]tK{
tK{"tid", "primary", "", false},
tK{"title", "fulltext", "", false},
tK{"content", "fulltext", "", false},
},
)
@ -303,23 +342,23 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"rid", "int", 0, false, true, ""}, // TODO: Rename to replyID?
tC{"tid", "int", 0, false, false, ""}, // TODO: Rename to topicID?
tC{"content", "text", 0, false, false, ""},
tC{"parsed_content", "text", 0, false, false, ""},
tC{"createdAt", "createdAt", 0, false, false, ""},
text("content"),
text("parsed_content"),
createdAt(),
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"lastEdit", "int", 0, false, false, "0"},
tC{"lastEditBy", "int", 0, false, false, "0"},
tC{"lastUpdated", "datetime", 0, false, false, ""},
tC{"ip", "varchar", 200, false, false, "''"},
ccol("ip", 200, "''"),
tC{"likeCount", "int", 0, false, false, "0"},
tC{"attachCount", "int", 0, false, false, "0"},
tC{"words", "int", 0, false, false, "1"}, // ? - replies has a default of 1 and topics has 0? why?
tC{"actionType", "varchar", 20, false, false, "''"},
ccol("actionType", 20, "''"),
tC{"poll", "int", 0, false, false, "0"},
},
[]tblKey{
tblKey{"rid", "primary", "", false},
tblKey{"content", "fulltext", "", false},
[]tK{
tK{"rid", "primary", "", false},
tK{"content", "fulltext", "", false},
},
)
@ -327,12 +366,12 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"attachID", "int", 0, false, true, ""},
tC{"sectionID", "int", 0, false, false, "0"},
tC{"sectionTable", "varchar", 200, false, false, "forums"},
ccol("sectionTable", 200, "forums"),
tC{"originID", "int", 0, false, false, ""},
tC{"originTable", "varchar", 200, false, false, "replies"},
ccol("originTable", 200, "replies"),
tC{"uploadedBy", "int", 0, false, false, ""}, // TODO; Make this a foreign key
tC{"path", "varchar", 200, false, false, ""},
tC{"extra", "varchar", 200, false, false, ""},
ccol("path", 200, ""),
ccol("extra", 200, ""),
},
[]tblKey{
tblKey{"attachID", "primary", "", false},
@ -342,10 +381,10 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("revisions", mysqlPre, mysqlCol,
[]tC{
tC{"reviseID", "int", 0, false, true, ""},
tC{"content", "text", 0, false, false, ""},
text("content"),
tC{"contentID", "int", 0, false, false, ""},
tC{"contentType", "varchar", 100, false, false, "replies"},
tC{"createdAt", "createdAt", 0, false, false, ""},
ccol("contentType", 100, "replies"),
createdAt(),
// TODO: Add a createdBy column?
},
[]tblKey{
@ -357,7 +396,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"pollID", "int", 0, false, true, ""},
tC{"parentID", "int", 0, false, false, "0"},
tC{"parentTable", "varchar", 100, false, false, "topics"}, // topics, replies
ccol("parentTable", 100, "topics"), // topics, replies
tC{"type", "int", 0, false, false, "0"},
tC{"options", "json", 0, false, false, ""},
tC{"votes", "int", 0, false, false, "0"},
@ -380,8 +419,8 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
tC{"pollID", "int", 0, false, false, ""},
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"option", "int", 0, false, false, "0"},
tC{"castAt", "createdAt", 0, false, false, ""},
tC{"ip", "varchar", 200, false, false, "''"},
createdAt("castAt"),
ccol("ip", 200, "''"),
}, nil,
)
@ -389,13 +428,13 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"rid", "int", 0, false, true, ""},
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"content", "text", 0, false, false, ""},
tC{"parsed_content", "text", 0, false, false, ""},
tC{"createdAt", "createdAt", 0, false, false, ""},
text("content"),
text("parsed_content"),
createdAt(),
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"lastEdit", "int", 0, false, false, "0"},
tC{"lastEditBy", "int", 0, false, false, "0"},
tC{"ip", "varchar", 200, false, false, "''"},
ccol("ip", 200, "''"),
},
[]tblKey{
tblKey{"rid", "primary", "", false},
@ -406,9 +445,9 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"weight", "tinyint", 0, false, false, "1"},
tC{"targetItem", "int", 0, false, false, ""},
tC{"targetType", "varchar", 50, false, false, "replies"},
ccol("targetType", 50, "replies"),
tC{"sentBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"createdAt", "createdAt", 0, false, false, ""},
createdAt(),
tC{"recalc", "tinyint", 0, false, false, "0"},
}, nil,
)
@ -418,7 +457,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"cid", "int", 0, false, true, ""},
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"createdAt", "createdAt", 0, false, false, ""},
createdAt(),
tC{"lastReplyAt", "datetime", 0, false, false, ""},
tC{"lastReplyBy", "int", 0, false, false, ""},
},
@ -432,8 +471,8 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
tC{"pid", "int", 0, false, true, ""},
tC{"cid", "int", 0, false, false, ""},
tC{"createdBy", "int", 0, false, false, ""},
tC{"body", "varchar", 50, false, false, ""},
tC{"post", "varchar", 50, false, false, "''"},
ccol("body", 50, ""),
ccol("post", 50, "''"),
},
[]tblKey{
tblKey{"pid", "primary", "", false},
@ -482,35 +521,39 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("activity_stream", "", "",
[]tC{
tC{"asid", "int", 0, false, true, ""},
tC{"actor", "int", 0, false, false, ""}, /* the one doing the act */ // TODO: Make this a foreign key
tC{"targetUser", "int", 0, false, false, ""}, /* the user who created the item the actor is acting on, some items like forums may lack a targetUser field */
tC{"event", "varchar", 50, false, false, ""}, /* mention, like, reply (as in the act of replying to an item, not the reply item type, you can "reply" to a forum by making a topic in it), friend_invite */
tC{"elementType", "varchar", 50, false, false, ""}, /* topic, post (calling it post here to differentiate it from the 'reply' event), forum, user */
tC{"elementID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */
tC{"createdAt", "createdAt", 0, false, false, ""},
tC{"extra", "varchar", 200, false, false, "''"},
tC{"actor", "int", 0, false, false, ""}, /* the one doing the act */ // TODO: Make this a foreign key
tC{"targetUser", "int", 0, false, false, ""}, /* the user who created the item the actor is acting on, some items like forums may lack a targetUser field */
ccol("event", 50, ""), /* mention, like, reply (as in the act of replying to an item, not the reply item type, you can "reply" to a forum by making a topic in it), friend_invite */
ccol("elementType", 50, ""), /* topic, post (calling it post here to differentiate it from the 'reply' event), forum, user */
// replacement for elementType
tC{"elementTable", "int", 0, false, false, "0"},
tC{"elementID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */
createdAt(),
ccol("extra", 200, "''"),
},
[]tblKey{
tblKey{"asid", "primary", "", false},
[]tK{
tK{"asid", "primary", "", false},
},
)
createTable("activity_subscriptions", "", "",
[]tC{
tC{"user", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"targetID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */
tC{"targetType", "varchar", 50, false, false, ""}, /* topic, post (calling it post here to differentiate it from the 'reply' event), forum, user */
tC{"level", "int", 0, false, false, "0"}, /* 0: Mentions (aka the global default for any post), 1: Replies To You, 2: All Replies*/
tC{"user", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"targetID", "int", 0, false, false, ""}, /* the ID of the element being acted upon */
ccol("targetType", 50, ""), /* topic, post (calling it post here to differentiate it from the 'reply' event), forum, user */
tC{"level", "int", 0, false, false, "0"}, /* 0: Mentions (aka the global default for any post), 1: Replies To You, 2: All Replies*/
}, nil,
)
/* Due to MySQL's design, we have to drop the unique keys for table settings, plugins, and themes down from 200 to 180 or it will error */
createTable("settings", "", "",
[]tC{
tC{"name", "varchar", 180, false, false, ""},
tC{"content", "varchar", 250, false, false, ""},
tC{"type", "varchar", 50, false, false, ""},
tC{"constraints", "varchar", 200, false, false, "''"},
ccol("name", 180, ""),
ccol("content", 250, ""),
ccol("type", 50, ""),
ccol("constraints", 200, "''"),
},
[]tblKey{
tblKey{"name", "unique", "", false},
@ -520,8 +563,8 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("word_filters", "", "",
[]tC{
tC{"wfid", "int", 0, false, true, ""},
tC{"find", "varchar", 200, false, false, ""},
tC{"replacement", "varchar", 200, false, false, ""},
ccol("find", 200, ""),
ccol("replacement", 200, ""),
},
[]tblKey{
tblKey{"wfid", "primary", "", false},
@ -530,9 +573,9 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("plugins", "", "",
[]tC{
tC{"uname", "varchar", 180, false, false, ""},
tC{"active", "boolean", 0, false, false, "0"},
tC{"installed", "boolean", 0, false, false, "0"},
ccol("uname", 180, ""),
bcol("active", false),
bcol("installed", false),
},
[]tblKey{
tblKey{"uname", "unique", "", false},
@ -541,9 +584,9 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("themes", "", "",
[]tC{
tC{"uname", "varchar", 180, false, false, ""},
tC{"default", "boolean", 0, false, false, "0"},
//tC{"profileUserVars", "text", 0, false, false, "''"},
ccol("uname", 180, ""),
bcol("default", false),
//text("profileUserVars"),
},
[]tblKey{
tblKey{"uname", "unique", "", false},
@ -554,11 +597,11 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"wid", "int", 0, false, true, ""},
tC{"position", "int", 0, false, false, ""},
tC{"side", "varchar", 100, false, false, ""},
tC{"type", "varchar", 100, false, false, ""},
tC{"active", "boolean", 0, false, false, "0"},
tC{"location", "varchar", 100, false, false, ""},
tC{"data", "text", 0, false, false, "''"},
ccol("side", 100, ""),
ccol("type", 100, ""),
bcol("active", false),
ccol("location", 100, ""),
text("data"),
},
[]tblKey{
tblKey{"wid", "primary", "", false},
@ -578,51 +621,51 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"miid", "int", 0, false, true, ""},
tC{"mid", "int", 0, false, false, ""},
tC{"name", "varchar", 200, false, false, "''"},
tC{"htmlID", "varchar", 200, false, false, "''"},
tC{"cssClass", "varchar", 200, false, false, "''"},
tC{"position", "varchar", 100, false, false, ""},
tC{"path", "varchar", 200, false, false, "''"},
tC{"aria", "varchar", 200, false, false, "''"},
tC{"tooltip", "varchar", 200, false, false, "''"},
tC{"tmplName", "varchar", 200, false, false, "''"},
ccol("name", 200, "''"),
ccol("htmlID", 200, "''"),
ccol("cssClass", 200, "''"),
ccol("position", 100, ""),
ccol("path", 200, "''"),
ccol("aria", 200, "''"),
ccol("tooltip", 200, "''"),
ccol("tmplName", 200, "''"),
tC{"order", "int", 0, false, false, "0"},
tC{"guestOnly", "boolean", 0, false, false, "0"},
tC{"memberOnly", "boolean", 0, false, false, "0"},
tC{"staffOnly", "boolean", 0, false, false, "0"},
tC{"adminOnly", "boolean", 0, false, false, "0"},
bcol("guestOnly", false),
bcol("memberOnly", false),
bcol("staffOnly", false),
bcol("adminOnly", false),
},
[]tblKey{
tblKey{"miid", "primary", "", false},
[]tK{
tK{"miid", "primary", "", false},
},
)
createTable("pages", mysqlPre, mysqlCol,
[]tC{
tC{"pid", "int", 0, false, true, ""},
//tC{"path", "varchar", 200, false, false, ""},
tC{"name", "varchar", 200, false, false, ""},
tC{"title", "varchar", 200, false, false, ""},
tC{"body", "text", 0, false, false, ""},
//ccol("path", 200, ""),
ccol("name", 200, ""),
ccol("title", 200, ""),
text("body"),
// TODO: Make this a table?
tC{"allowedGroups", "text", 0, false, false, ""},
text("allowedGroups"),
tC{"menuID", "int", 0, false, false, "-1"}, // simple sidebar menu
},
[]tblKey{
tblKey{"pid", "primary", "", false},
[]tK{
tK{"pid", "primary", "", false},
},
)
createTable("registration_logs", "", "",
[]tC{
tC{"rlid", "int", 0, false, true, ""},
tC{"username", "varchar", 100, false, false, ""},
ccol("username", 100, ""),
tC{"email", "varchar", 100, false, false, ""},
tC{"failureReason", "varchar", 100, false, false, ""},
tC{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
tC{"ipaddress", "varchar", 200, false, false, ""},
tC{"doneAt", "createdAt", 0, false, false, ""},
ccol("failureReason", 100, ""),
bcol("success", false), // Did this attempt succeed?
ccol("ipaddress", 200, ""),
createdAt("doneAt"),
},
[]tblKey{
tblKey{"rlid", "primary", "", false},
@ -633,9 +676,9 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"lid", "int", 0, false, true, ""},
tC{"uid", "int", 0, false, false, ""},
tC{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
tC{"ipaddress", "varchar", 200, false, false, ""},
tC{"doneAt", "createdAt", 0, false, false, ""},
bcol("success", false), // Did this attempt succeed?
ccol("ipaddress", 200, ""),
createdAt("doneAt"),
},
[]tblKey{
tblKey{"lid", "primary", "", false},
@ -644,25 +687,25 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("moderation_logs", "", "",
[]tC{
tC{"action", "varchar", 100, false, false, ""},
ccol("action", 100, ""),
tC{"elementID", "int", 0, false, false, ""},
tC{"elementType", "varchar", 100, false, false, ""},
tC{"ipaddress", "varchar", 200, false, false, ""},
ccol("elementType", 100, ""),
ccol("ipaddress", 200, ""),
tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"doneAt", "datetime", 0, false, false, ""},
tC{"extra", "text", 0, false, false, ""},
text("extra"),
}, nil,
)
createTable("administration_logs", "", "",
[]tC{
tC{"action", "varchar", 100, false, false, ""},
ccol("action", 100, ""),
tC{"elementID", "int", 0, false, false, ""},
tC{"elementType", "varchar", 100, false, false, ""},
tC{"ipaddress", "varchar", 200, false, false, ""},
ccol("elementType", 100, ""),
ccol("ipaddress", 200, ""),
tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"doneAt", "datetime", 0, false, false, ""},
tC{"extra", "text", 0, false, false, ""},
text("extra"),
}, nil,
)
@ -671,7 +714,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
tC{"count", "int", 0, false, false, "0"},
tC{"avg", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""},
tC{"route", "varchar", 200, false, false, ""}, // todo: set a default empty here
ccol("route", 200, ""), // TODO: set a default empty here
}, nil,
)
@ -679,8 +722,8 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""},
tC{"browser", "varchar", 200, false, false, ""}, // googlebot, firefox, opera, etc.
//tC{"version","varchar",0,false,false,""}, // the version of the browser or bot
ccol("browser", 200, ""), // googlebot, firefox, opera, etc.
//ccol("version",0,""), // the version of the browser or bot
}, nil,
)
@ -688,7 +731,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""},
tC{"system", "varchar", 200, false, false, ""}, // windows, android, unknown, etc.
ccol("system", 200, ""), // windows, android, unknown, etc.
}, nil,
)
@ -696,7 +739,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""},
tC{"lang", "varchar", 200, false, false, ""}, // en, ru, etc.
ccol("lang", 200, ""), // en, ru, etc.
}, nil,
)
@ -704,7 +747,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{
tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""},
tC{"domain", "varchar", 200, false, false, ""},
ccol("domain", 200, ""),
}, nil,
)
@ -764,18 +807,19 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("meta", "", "",
[]tC{
tC{"name", "varchar", 200, false, false, ""},
tC{"value", "varchar", 200, false, false, ""},
ccol("name", 200, ""),
ccol("value", 200, ""),
}, nil,
)
/*createTable("tables", mysqlPre, mysqlCol,
/*createTable("tables", "", "",
[]tC{
tC{"id", "int", 0, false, true, ""},
tC{"name", "varchar", 200, false, false, ""},
ccol("name", 200, ""),
},
[]tblKey{
tblKey{"id", "primary", "", false},
[]tK{
tK{"id", "primary", "", false},
tK{"name", "unique", "", false},
},
)*/

View File

@ -98,7 +98,7 @@ func NewDefaultAuth() (*DefaultAuth, error) {
// Authenticate checks if a specific username and password is valid and returns the UID for the corresponding user, if so. Otherwise, a user safe error.
// IF MFA is enabled, then pass it back a flag telling the caller that authentication isn't complete yet
// TODO: Find a better way of handling errors we don't want to reach the user
func (auth *DefaultAuth) Authenticate(name string, password string) (uid int, err error, requiresExtraAuth bool) {
func (auth *DefaultAuth) Authenticate(name, password string) (uid int, err error, requiresExtraAuth bool) {
var realPassword, salt string
err = auth.login.QueryRow(name).Scan(&uid, &realPassword, &salt)
if err == ErrNoRows {
@ -211,7 +211,7 @@ func (auth *DefaultAuth) SetCookies(w http.ResponseWriter, uid int, session stri
// TODO: Set the cookie domain
// SetProvisionalCookies sets the two cookies required for guests to be recognised as having passed the initial login but not having passed the additional checks (e.g. multi-factor authentication)
func (auth *DefaultAuth) SetProvisionalCookies(w http.ResponseWriter, uid int, provSession string, signedSession string) {
func (auth *DefaultAuth) SetProvisionalCookies(w http.ResponseWriter, uid int, provSession, signedSession string) {
cookie := http.Cookie{Name: "uid", Value: strconv.Itoa(uid), Path: "/", MaxAge: int(Year)}
setCookie(w, &cookie, "lax")
cookie = http.Cookie{Name: "provSession", Value: provSession, Path: "/", MaxAge: int(Year)}
@ -282,7 +282,7 @@ func (auth *DefaultAuth) CreateSession(uid int) (session string, err error) {
return session, nil
}
func (auth *DefaultAuth) CreateProvisionalSession(uid int) (provSession string, signedSession string, err error) {
func (auth *DefaultAuth) CreateProvisionalSession(uid int) (provSession, signedSession string, err error) {
provSession, err = GenerateSafeString(SessionLength)
if err != nil {
return "", "", err

View File

@ -232,6 +232,7 @@ type ProfilePage struct {
Blocked bool
CanMessage bool
CanComment bool
ShowComments bool
}
type CreateTopicPage struct {
@ -249,7 +250,7 @@ type IPSearchPage struct {
type RegisterPage struct {
*Header
RequireEmail bool
Token string
Token string
}
type Account struct {
@ -278,8 +279,8 @@ type AccountBlocksPage struct {
type AccountPrivacyPage struct {
*Header
ProfileComments bool
ReceiveConvos bool
ProfileComments int
ReceiveConvos int
EnableEmbeds bool
}

View File

@ -95,14 +95,14 @@ var Template_account_handle = genIntTmpl("account")
func tmplInitUsers() (*User, *User, *User) {
avatar, microAvatar := BuildAvatar(62, "")
u := 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, 0, nil}
u := 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, 0, nil, UserPrivacy{}}
// TODO: Do a more accurate level calculation for this?
avatar, microAvatar = BuildAvatar(1, "")
u2 := 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, 0, nil}
u2 := 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, 0, nil, UserPrivacy{}}
avatar, microAvatar = BuildAvatar(2, "")
u3 := 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, 0, nil}
u3 := 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, 0, nil, UserPrivacy{}}
return &u, &u2, &u3
}
@ -306,7 +306,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
return err
}
ppage := ProfilePage{htitle("User 526"), replyList, *user, 0, 0, false, false, false} // TODO: Use the score from user to generate the currentScore and nextScore
ppage := ProfilePage{htitle("User 526"), replyList, *user, 0, 0, false, false, false, false} // TODO: Use the score from user to generate the currentScore and nextScore
t.Add("profile", "c.ProfilePage", ppage)
var topicsList []*TopicsRow
@ -349,7 +349,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
}
t.AddStd("login", "c.Page", Page{htitle("Login Page"), tList, nil})
t.AddStd("register", "c.RegisterPage", RegisterPage{htitle("Registration Page"), false,""})
t.AddStd("register", "c.RegisterPage", RegisterPage{htitle("Registration Page"), false, ""})
t.AddStd("error", "c.ErrorPage", ErrorPage{htitle("Error"), "A problem has occurred in the system."})
ipSearchPage := IPSearchPage{htitle("IP Search"), map[int]*User{1: user2}, "::1"}

View File

@ -62,6 +62,12 @@ type User struct {
TempGroup int
ParseSettings *ParseSettings
Privacy UserPrivacy
}
type UserPrivacy struct {
ShowComments int // 0 = default, 1 = public, 2 = registered, 3 = friends, 4 = mods / self
AllowMessage int // 0 = default, 1 = registered, 2 = friends, 3 = mods / self
}
func (u *User) WebSockets() *WsJSONUser {
@ -191,7 +197,7 @@ func init() {
decLiked: acc.Update(u).Set("liked=liked-?").Where(w).Prepare(),
//recalcLastLiked: acc...
updateLastIP: acc.SimpleUpdate(u, "last_ip=?", w),
updatePrivacy: acc.Update(u).Set("enable_embeds=?").Where(w).Prepare(),
updatePrivacy: acc.Update(u).Set("profile_comments=?,enable_embeds=?").Where(w).Prepare(),
setPassword: acc.Update(u).Set("password=?,salt=?").Where(w).Prepare(),
@ -211,6 +217,10 @@ func init() {
}
func (u *User) Init() {
// TODO: Let admins configure the minimum default?
if u.Privacy.ShowComments < 1 {
u.Privacy.ShowComments = 1
}
u.Avatar, u.MicroAvatar = BuildAvatar(u.ID, u.RawAvatar)
u.Link = BuildProfileURL(NameToSlug(u.Name), u.ID)
u.Tag = Groups.DirtyGet(u.Group).Tag
@ -554,12 +564,27 @@ func (u *User) UpdateIP(ip string) error {
return err
}
func (u *User) UpdatePrivacy(enableEmbeds int) error {
_, err := userStmts.updatePrivacy.Exec(enableEmbeds, u.ID)
//var ErrMalformedInteger = errors.New("malformed integer")
var ErrProfileCommentsOutOfBounds = errors.New("profile_comments must be an integer between -1 and 4")
var ErrEnableEmbedsOutOfBounds = errors.New("enable_embeds must be -1, 0 or 1")
/*func (u *User) UpdatePrivacyS(sProfileComments, sEnableEmbeds string) error {
return u.UpdatePrivacy(profileComments, enableEmbeds)
}*/
func (u *User) UpdatePrivacy(profileComments, enableEmbeds int) error {
if profileComments < -1 || profileComments > 4 {
return ErrProfileCommentsOutOfBounds
}
if enableEmbeds < -1 || enableEmbeds > 1 {
return ErrEnableEmbedsOutOfBounds
}
_, e := userStmts.updatePrivacy.Exec(profileComments, enableEmbeds, u.ID)
if uc := Users.GetCache(); uc != nil {
uc.Remove(u.ID)
}
return err
return e
}
func (u *User) Update(name, email string, group int) (err error) {
@ -790,3 +815,17 @@ func BuildProfileURL(slug string, uid int) string {
}
return "/user/" + slug + "." + strconv.Itoa(uid)
}
func BuildProfileURLSb(sb *strings.Builder, slug string, uid int) {
if slug == "" || !Config.BuildSlugs {
sb.Grow(6 + 1)
sb.WriteString("/user/")
sb.WriteString(strconv.Itoa(uid))
return
}
sb.Grow(7 + 1 + len(slug))
sb.WriteString("/user/")
sb.WriteString(slug)
sb.WriteRune('.')
sb.WriteString(strconv.Itoa(uid))
}

View File

@ -54,16 +54,16 @@ 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"
allCols := "uid,name,group,active,is_super_admin,session,email,avatar,message,level,score,posts,liked,last_ip,temp_group,createdAt,enable_embeds,profile_comments,who_can_convo"
// 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, createdAt, enable_embeds").Where("uid=?").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,profile_comments,who_can_convo").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
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(),
count: acc.Count(u).Prepare(),
}, acc.FirstError()
@ -93,16 +93,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, &u.CreatedAt, &embeds)
/*if err != nil {
return nil, err
}
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()
u.ParseSettings.NoEmbed = embeds == 0
}
u.Init()
s.cache.Set(u)*/
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, &u.Privacy.ShowComments, &u.Privacy.AllowMessage)
if err == nil {
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()
@ -122,7 +113,7 @@ func (s *DefaultUserStore) Getn(id int) *User {
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, &u.CreatedAt, &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, &u.Privacy.ShowComments, &u.Privacy.AllowMessage)
if err != nil {
return nil
}
@ -140,7 +131,7 @@ func (s *DefaultUserStore) Getn(id int) *User {
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, &u.CreatedAt, &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, &u.Privacy.ShowComments, &u.Privacy.AllowMessage)
if err != nil {
return nil, err
}
@ -165,7 +156,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, &u.CreatedAt, &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, &u.Privacy.ShowComments, &u.Privacy.AllowMessage)
if err != nil {
return nil, err
}
@ -180,24 +171,24 @@ func (s *DefaultUserStore) GetOffset(offset, perPage int) (users []*User, err er
return users, rows.Err()
}
func (s *DefaultUserStore) Each(f func(*User) error) error {
rows, err := s.getAll.Query()
if err != nil {
return err
rows, e := s.getAll.Query()
if e != nil {
return e
}
defer rows.Close()
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, &u.CreatedAt, &embeds); err != nil {
return err
if e := 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, &u.Privacy.ShowComments, &u.Privacy.AllowMessage); e != nil {
return e
}
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()
u.ParseSettings.NoEmbed = embeds == 0
}
u.Init()
if err := f(u); err != nil {
return err
if e := f(u); e != nil {
return e
}
}
return rows.Err()
@ -246,7 +237,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,createdAt,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,profile_comments,who_can_convo").Where("uid IN(" + q + ")").Query(idList...)
if err != nil {
return list, err
}
@ -255,7 +246,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, &u.CreatedAt, &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, &u.Privacy.ShowComments, &u.Privacy.AllowMessage)
if err != nil {
return list, err
}
@ -292,7 +283,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, &u.CreatedAt, &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, &u.Privacy.ShowComments, &u.Privacy.AllowMessage)
if err == nil {
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()

View File

@ -577,7 +577,11 @@
"account_password_update_button":"Update",
"account_privacy_head":"Privacy",
"account_privacy_profile_comments":"Enable Profile Comments",
"account_privacy_profile_comments":"Profile Comment Visibility",
"account_privacy_profile_comments_public":"Anyone",
"account_privacy_profile_comments_registered":"Registered Users",
"account_privacy_profile_comments_self":"Only Me",
"account_privacy_enable_embeds":"Enable Embeds",
"account_privacy_button":"Update",
"account_mfa_head":"Manage 2FA",

View File

@ -51,18 +51,21 @@ func init() {
addPatch(31, patch31)
addPatch(32, patch32)
addPatch(33, patch33)
addPatch(34, patch34)
//addPatch(35, patch35)
}
func bcol(col string, val bool) qgen.DBTableColumn {
if val {
return tC{col, "boolean", 0, false, false, "1"}
}
return tC{col, "boolean", 0, false, false, "0"}
}
func ccol(col string, size int, sdefault string) qgen.DBTableColumn {
return tC{col, "varchar", size, false, false, sdefault}
}
func patch0(scanner *bufio.Scanner) (err error) {
err = execStmt(qgen.Builder.DropTable("menus"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.DropTable("menu_items"))
if err != nil {
return err
}
err = createTable("menus", "", "",
[]tC{
tC{"mid", "int", 0, false, true, ""},
@ -79,20 +82,20 @@ func patch0(scanner *bufio.Scanner) (err error) {
[]tC{
tC{"miid", "int", 0, false, true, ""},
tC{"mid", "int", 0, false, false, ""},
tC{"name", "varchar", 200, false, false, ""},
tC{"htmlID", "varchar", 200, false, false, "''"},
tC{"cssClass", "varchar", 200, false, false, "''"},
tC{"position", "varchar", 100, false, false, ""},
tC{"path", "varchar", 200, false, false, "''"},
tC{"aria", "varchar", 200, false, false, "''"},
tC{"tooltip", "varchar", 200, false, false, "''"},
tC{"tmplName", "varchar", 200, false, false, "''"},
ccol("name", 200, ""),
ccol("htmlID", 200, "''"),
ccol("cssClass", 200, "''"),
ccol("position", 100, ""),
ccol("path", 200, "''"),
ccol("aria", 200, "''"),
ccol("tooltip", 200, "''"),
ccol("tmplName", 200, "''"),
tC{"order", "int", 0, false, false, "0"},
tC{"guestOnly", "boolean", 0, false, false, "0"},
tC{"memberOnly", "boolean", 0, false, false, "0"},
tC{"staffOnly", "boolean", 0, false, false, "0"},
tC{"adminOnly", "boolean", 0, false, false, "0"},
bcol("guestOnly", false),
bcol("memberOnly", false),
bcol("staffOnly", false),
bcol("adminOnly", false),
},
[]tK{
tK{"miid", "primary", "", false},
@ -194,11 +197,11 @@ func patch3(scanner *bufio.Scanner) error {
return createTable("registration_logs", "", "",
[]tC{
tC{"rlid", "int", 0, false, true, ""},
tC{"username", "varchar", 100, false, false, ""},
tC{"email", "varchar", 100, false, false, ""},
tC{"failureReason", "varchar", 100, false, false, ""},
tC{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
tC{"ipaddress", "varchar", 200, false, false, ""},
ccol("username", 100, ""),
ccol("email", 100, ""),
ccol("failureReason", 100, ""),
bcol("success", false), // Did this attempt succeed?
ccol("ipaddress", 200, ""),
tC{"doneAt", "createdAt", 0, false, false, ""},
},
[]tK{
@ -258,8 +261,8 @@ func patch4(scanner *bufio.Scanner) error {
err = createTable("pages", "utf8mb4", "utf8mb4_general_ci",
[]tC{
tC{"pid", "int", 0, false, true, ""},
tC{"name", "varchar", 200, false, false, ""},
tC{"title", "varchar", 200, false, false, ""},
ccol("name", 200, ""),
ccol("title", 200, ""),
tC{"body", "text", 0, false, false, ""},
tC{"allowedGroups", "text", 0, false, false, ""},
tC{"menuID", "int", 0, false, false, "-1"},
@ -293,29 +296,24 @@ func patch5(scanner *bufio.Scanner) error {
return err
}
err = createTable("users_2fa_keys", "utf8mb4", "utf8mb4_general_ci",
return createTable("users_2fa_keys", "utf8mb4", "utf8mb4_general_ci",
[]tC{
tC{"uid", "int", 0, false, false, ""},
tC{"secret", "varchar", 100, false, false, ""},
tC{"scratch1", "varchar", 50, false, false, ""},
tC{"scratch2", "varchar", 50, false, false, ""},
tC{"scratch3", "varchar", 50, false, false, ""},
tC{"scratch4", "varchar", 50, false, false, ""},
tC{"scratch5", "varchar", 50, false, false, ""},
tC{"scratch6", "varchar", 50, false, false, ""},
tC{"scratch7", "varchar", 50, false, false, ""},
tC{"scratch8", "varchar", 50, false, false, ""},
ccol("secret", 100, ""),
ccol("scratch1", 50, ""),
ccol("scratch2", 50, ""),
ccol("scratch3", 50, ""),
ccol("scratch4", 50, ""),
ccol("scratch5", 50, ""),
ccol("scratch6", 50, ""),
ccol("scratch7", 50, ""),
ccol("scratch8", 50, ""),
tC{"createdAt", "createdAt", 0, false, false, ""},
},
[]tK{
tK{"uid", "primary", "", false},
},
)
if err != nil {
return err
}
return nil
}
func patch6(scanner *bufio.Scanner) error {
@ -382,10 +380,6 @@ func patch8(scanner *bufio.Scanner) error {
return err
}
err = execStmt(qgen.Builder.DropTable("updates"))
if err != nil {
return err
}
return createTable("updates", "", "",
[]tC{
tC{"dbVersion", "int", 0, false, false, "0"},
@ -404,8 +398,8 @@ func patch9(scanner *bufio.Scanner) error {
[]tC{
tC{"lid", "int", 0, false, true, ""},
tC{"uid", "int", 0, false, false, ""},
tC{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
tC{"ipaddress", "varchar", 200, false, false, ""},
bcol("success", false), // Did this attempt succeed?
ccol("ipaddress", 200, ""),
tC{"doneAt", "createdAt", 0, false, false, ""},
},
[]tK{
@ -463,7 +457,7 @@ func patch11(scanner *bufio.Scanner) error {
}
// Attachments for replies got the topicID rather than the replyID for a while in error, so we want to separate these out
_, err = acc().Update("attachments").Set("originTable = 'freplies'").Where("originTable = 'replies'").Exec()
_, err = acc().Update("attachments").Set("originTable='freplies'").Where("originTable='replies'").Exec()
if err != nil {
return err
}
@ -491,39 +485,26 @@ func patch11(scanner *bufio.Scanner) error {
}
func patch12(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddIndex("topics", "parentID", "parentID"))
if err != nil {
return err
var e error
addIndex := func(tbl, iname, colname string) {
if e != nil {
return
}
/*e = execStmt(qgen.Builder.RemoveIndex(tbl, iname))
if e != nil {
return
}*/
e = execStmt(qgen.Builder.AddIndex(tbl, iname, colname))
}
err = execStmt(qgen.Builder.AddIndex("replies", "tid", "tid"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddIndex("polls", "parentID", "parentID"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddIndex("likes", "targetItem", "targetItem"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddIndex("emails", "uid", "uid"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddIndex("attachments", "originID", "originID"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddIndex("attachments", "path", "path"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddIndex("activity_stream_matches", "watcher", "watcher"))
if err != nil {
return err
}
return nil
addIndex("topics", "parentID", "parentID")
addIndex("replies", "tid", "tid")
addIndex("polls", "parentID", "parentID")
addIndex("likes", "targetItem", "targetItem")
addIndex("emails", "uid", "uid")
addIndex("attachments", "originID", "originID")
addIndex("attachments", "path", "path")
addIndex("activity_stream_matches", "watcher", "watcher")
return e
}
func patch13(scanner *bufio.Scanner) error {
@ -554,17 +535,17 @@ func patch15(scanner *bufio.Scanner) error {
func patch16(scanner *bufio.Scanner) error {
return createTable("password_resets", "", "",
[]tC{
tC{"email", "varchar", 200, false, false, ""},
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"validated", "varchar", 200, false, false, ""}, // Token given once the one-use token is consumed, used to prevent multiple people consuming the same one-use token
tC{"token", "varchar", 200, false, false, ""},
ccol("email", 200, ""),
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
ccol("validated", 200, ""), // Token given once the one-use token is consumed, used to prevent multiple people consuming the same one-use token
ccol("token", 200, ""),
tC{"createdAt", "createdAt", 0, false, false, ""},
}, nil,
)
}
func patch17(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("attachments", tC{"extra", "varchar", 200, false, false, ""}, nil))
err := execStmt(qgen.Builder.AddColumn("attachments", ccol("extra", 200, ""), nil))
if err != nil {
return err
}
@ -575,7 +556,7 @@ func patch17(scanner *bufio.Scanner) error {
if err != nil {
return err
}
_, err = acc().Update("attachments").Set("sectionID=?").Where("originTable = 'topics' AND originID = ?").Exec(parentID, tid)
_, err = acc().Update("attachments").Set("sectionID=?").Where("originTable='topics' AND originID=?").Exec(parentID, tid)
return err
})
if err != nil {
@ -592,7 +573,7 @@ func patch17(scanner *bufio.Scanner) error {
if err != nil {
return err
}
_, err = acc().Update("attachments").Set("sectionID=?, extra=?").Where("originTable = 'replies' AND originID = ?").Exec(sectionID, tid, rid)
_, err = acc().Update("attachments").Set("sectionID=?, extra=?").Where("originTable='replies' AND originID=?").Exec(sectionID, tid, rid)
return err
})
}
@ -613,16 +594,15 @@ func patch19(scanner *bufio.Scanner) error {
func patch20(scanner *bufio.Scanner) error {
err := acc().Select("activity_stream_matches").Cols("asid").Each(func(rows *sql.Rows) error {
var asid int
err := rows.Scan(&asid)
if err != nil {
return err
if e := rows.Scan(&asid); e != nil {
return e
}
err = acc().Select("activity_stream").Cols("asid").Where("asid = ?").QueryRow(asid).Scan(&asid)
if err != sql.ErrNoRows {
return err
e := acc().Select("activity_stream").Cols("asid").Where("asid=?").QueryRow(asid).Scan(&asid)
if e != sql.ErrNoRows {
return e
}
_, err = acc().Delete("activity_stream_matches").Where("asid = ?").Run(asid)
return err
_, e = acc().Delete("activity_stream_matches").Where("asid=?").Run(asid)
return e
})
if err != nil {
return err
@ -644,8 +624,8 @@ func patch21(scanner *bufio.Scanner) error {
err = createTable("meta", "", "",
[]tC{
tC{"name", "varchar", 200, false, false, ""},
tC{"value", "varchar", 200, false, false, ""},
ccol("name", 200, ""),
ccol("value", 200, ""),
}, nil,
)
if err != nil {
@ -656,15 +636,11 @@ func patch21(scanner *bufio.Scanner) error {
}
func patch22(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.AddColumn("forums", tC{"tmpl", "varchar", 200, false, false, "''"}, nil))
return execStmt(qgen.Builder.AddColumn("forums", ccol("tmpl", 200, "''"), nil))
}
func patch23(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.DropTable("conversations"))
if err != nil {
return err
}
err = createTable("conversations", "", "",
err := createTable("conversations", "", "",
[]tC{
tC{"cid", "int", 0, false, true, ""},
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
@ -680,17 +656,13 @@ func patch23(scanner *bufio.Scanner) error {
return err
}
err = execStmt(qgen.Builder.DropTable("conversations_posts"))
if err != nil {
return err
}
err = createTable("conversations_posts", "", "",
[]tC{
tC{"pid", "int", 0, false, true, ""},
tC{"cid", "int", 0, false, false, ""},
tC{"createdBy", "int", 0, false, false, ""},
tC{"body", "varchar", 50, false, false, ""},
tC{"post", "varchar", 50, false, false, "''"},
ccol("body", 50, ""),
ccol("post", 50, "''"),
},
[]tK{
tK{"pid", "primary", "", false},
@ -700,10 +672,6 @@ func patch23(scanner *bufio.Scanner) error {
return err
}
err = execStmt(qgen.Builder.DropTable("conversations_participants"))
if err != nil {
return err
}
return createTable("conversations_participants", "", "",
[]tC{
tC{"uid", "int", 0, false, false, ""},
@ -713,16 +681,12 @@ func patch23(scanner *bufio.Scanner) error {
}
func patch24(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.DropTable("users_groups_promotions"))
if err != nil {
return err
}
return createTable("users_groups_promotions", "", "",
[]tC{
tC{"pid", "int", 0, false, true, ""},
tC{"from_gid", "int", 0, false, false, ""},
tC{"to_gid", "int", 0, false, false, ""},
tC{"two_way", "boolean", 0, false, false, "0"}, // If a user no longer meets the requirements for this promotion then they will be demoted if this flag is set
bcol("two_way", false), // If a user no longer meets the requirements for this promotion then they will be demoted if this flag is set
// Requirements
tC{"level", "int", 0, false, false, ""},
@ -812,28 +776,21 @@ func patch29(scanner *bufio.Scanner) error {
return err
}
fixCol := func(tbl string) error {
//err := execStmt(qgen.Builder.RenameColumn(tbl, "ipaddress","ip"))
err := execStmt(qgen.Builder.ChangeColumn(tbl, "ipaddress", tC{"ip", "varchar", 200, false, false, "''"}))
if err != nil {
return err
fixCols := func(tbls ...string) error {
for _, tbl := range tbls {
//err := execStmt(qgen.Builder.RenameColumn(tbl, "ipaddress","ip"))
err := execStmt(qgen.Builder.ChangeColumn(tbl, "ipaddress", ccol("ip", 200, "''")))
if err != nil {
return err
}
err = execStmt(qgen.Builder.SetDefaultColumn(tbl, "ip", "varchar", ""))
if err != nil {
return err
}
}
return execStmt(qgen.Builder.SetDefaultColumn(tbl, "ip", "varchar", ""))
return nil
}
err = fixCol("topics")
if err != nil {
return err
}
err = fixCol("replies")
if err != nil {
return err
}
err = fixCol("polls_votes")
if err != nil {
return err
}
err = fixCol("users_replies")
err = fixCols("topics", "replies", "polls_votes", "users_replies")
if err != nil {
return err
}
@ -867,36 +824,31 @@ func patch30(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.SetDefaultColumn("users", "last_ip", "varchar", ""))
}
func patch31(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.RemoveIndex("topics", "title"))
func patch31(scanner *bufio.Scanner) (e error) {
addKey := func(tbl, col string, tk qgen.DBTableKey) error {
/*err := execStmt(qgen.Builder.RemoveIndex(tbl, col))
if err != nil {
return err
}*/
return execStmt(qgen.Builder.AddKey(tbl, col, tk))
}
err := addKey("topics", "title", tK{"title", "fulltext", "", false})
if err != nil {
return err
}
err = execStmt(qgen.Builder.RemoveIndex("topics", "content"))
err = addKey("topics", "content", tK{"content", "fulltext", "", false})
if err != nil {
return err
}
err = execStmt(qgen.Builder.RemoveIndex("replies", "content"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddKey("topics", "title", tK{"title", "fulltext", "", false}))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddKey("topics", "content", tK{"content", "fulltext", "", false}))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddKey("replies", "content", tK{"content", "fulltext", "", false}))
if err != nil {
return err
}
return nil
return addKey("replies", "content", tK{"content", "fulltext", "", false})
}
func createTable(table, charset, collation string, cols []tC, keys []tK) error {
return execStmt(qgen.Builder.CreateTable(table, charset, collation, cols, keys))
func createTable(tbl, charset, collation string, cols []tC, keys []tK) error {
err := execStmt(qgen.Builder.DropTable(tbl))
if err != nil {
return err
}
return execStmt(qgen.Builder.CreateTable(tbl, charset, collation, cols, keys))
}
func patch32(scanner *bufio.Scanner) error {
@ -913,3 +865,75 @@ func patch32(scanner *bufio.Scanner) error {
func patch33(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.AddColumn("viewchunks", tC{"avg", "int", 0, false, false, "0"}, nil))
}
func patch34(scanner *bufio.Scanner) error {
/*err := createTable("tables", "", "",
[]tC{
tC{"id", "int", 0, false, true, ""},
ccol("name", 200, ""),
},
[]tK{
tK{"id", "primary", "", false},
tK{"name", "unique", "", false},
},
)
if err != nil {
return err
}
insert := func(tbl, cols, fields string) {
if err != nil {
return
}
err = execStmt(qgen.Builder.SimpleInsert(tbl, cols, fields))
}
insert("tables", "name", "forums")
insert("tables", "name", "topics")
insert("tables", "name", "replies")
// ! Hold onto freplies for a while longer
insert("tables", "name", "freplies")*/
/*err := execStmt(qgen.Builder.AddColumn("topics", tC{"attachCount", "int", 0, false, false, "0"}, nil))
if err != nil {
return err
}*/
overwriteColumn := func(tbl, col string, tc qgen.DBTableColumn) error {
/*e := execStmt(qgen.Builder.DropColumn(tbl, col))
if e != nil {
return e
}*/
return execStmt(qgen.Builder.AddColumn(tbl, tc, nil))
}
err := overwriteColumn("users", "profile_comments", tC{"profile_comments", "int", 0, false, false, "0"})
if err != nil {
return err
}
err = overwriteColumn("users", "who_can_convo", tC{"who_can_convo", "int", 0, false, false, "0"})
if err != nil {
return err
}
setDefault := func(tbl, col, typ, val string) {
if err != nil {
return
}
err = execStmt(qgen.Builder.SetDefaultColumn(tbl, col, typ, val))
}
setDefault("users_groups", "permissions", "text", "{}")
setDefault("users_groups", "plugin_perms", "text", "{}")
setDefault("forums_permissions", "permissions", "text", "{}")
setDefault("topics", "content", "text", "")
setDefault("topics", "parsed_content", "text", "")
setDefault("replies", "content", "text", "")
setDefault("replies", "parsed_content", "text", "")
//setDefault("revisions", "content", "text", "")
setDefault("users_replies", "content", "text", "")
setDefault("users_replies", "parsed_content", "text", "")
setDefault("pages", "body", "text", "")
setDefault("pages", "allowedGroups", "text", "")
setDefault("moderation_logs", "extra", "text", "")
setDefault("administration_logs", "extra", "text", "")
if err != nil {
return err
}
return nil
}

View File

@ -126,7 +126,7 @@ type DBStmt struct {
Type string // create-table, insert, update, delete
}
// TODO: Add the DropTable, TableExists, AddColumn, ColumnExists, and RemoveColumn methods
// TODO: Add the TableExists and ColumnExists methods
type Adapter interface {
GetName() string
BuildConn(config map[string]string) (*sql.DB, error)

View File

@ -614,8 +614,8 @@ func AccountEditMFADisableSubmit(w http.ResponseWriter, r *http.Request, u *c.Us
func AccountEditPrivacy(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError {
accountEditHead("account_privacy", w, r, u, h)
profileComments := false
receiveConvos := false
profileComments := u.Privacy.ShowComments
receiveConvos := u.Privacy.AllowMessage
enableEmbeds := !c.DefaultParseSettings.NoEmbed
if u.ParseSettings != nil {
enableEmbeds = !u.ParseSettings.NoEmbed
@ -626,14 +626,21 @@ func AccountEditPrivacy(w http.ResponseWriter, r *http.Request, u *c.User, h *c.
func AccountEditPrivacySubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
//headerLite, _ := c.SimpleUserCheck(w, r, u)
sProfileComments := r.FormValue("profile_comments")
sEnableEmbeds := r.FormValue("enable_embeds")
enableEmbeds, e := strconv.Atoi(sEnableEmbeds)
if e != nil {
return c.LocalError("enable_embeds must be 0 or 1", w, r, u)
}
if sEnableEmbeds != r.FormValue("o_enable_embeds") {
if e = u.UpdatePrivacy(enableEmbeds); e != nil {
oProfileComments := r.FormValue("o_profile_comments")
oEnableEmbeds := r.FormValue("o_enable_embeds")
if sProfileComments != oProfileComments || sEnableEmbeds != oEnableEmbeds {
profileComments, e := strconv.Atoi(sProfileComments)
enableEmbeds, e2 := strconv.Atoi(sEnableEmbeds)
if e != nil || e2 != nil {
return c.LocalError("malformed integer", w, r, u)
}
e = u.UpdatePrivacy(profileComments, enableEmbeds)
if e == c.ErrProfileCommentsOutOfBounds || e == c.ErrEnableEmbedsOutOfBounds {
return c.LocalError(e.Error(), w, r, u)
} else if e != nil {
return c.InternalError(e, w, r)
}
}

View File

@ -399,24 +399,18 @@ func ConvosEditReplySubmit(w http.ResponseWriter, r *http.Request, user *c.User,
if err != nil {
return c.InternalError(err, w, r)
}
if !js {
http.Redirect(w, r, "/user/convo/"+strconv.Itoa(post.CID), http.StatusSeeOther)
} else {
w.Write(successJSONBytes)
}
return nil
return actionSuccess(w, r, "/user/convo/"+strconv.Itoa(post.CID), js)
}
func RelationsBlockCreate(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header, spid string) c.RouteError {
func RelationsBlockCreate(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header, spid string) c.RouteError {
h.Title = p.GetTitlePhrase("create_block")
pid, err := strconv.Atoi(spid)
if err != nil {
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, u)
}
puser, err := c.Users.Get(pid)
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to block doesn't exist.", w, r, user)
return c.LocalError("The user you're trying to block doesn't exist.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
@ -425,22 +419,22 @@ func RelationsBlockCreate(w http.ResponseWriter, r *http.Request, user *c.User,
return renderTemplate("are_you_sure", w, r, h, pi)
}
func RelationsBlockCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.User, spid string) c.RouteError {
func RelationsBlockCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User, spid string) c.RouteError {
pid, err := strconv.Atoi(spid)
if err != nil {
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, u)
}
puser, err := c.Users.Get(pid)
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to block doesn't exist.", w, r, user)
return c.LocalError("The user you're trying to block doesn't exist.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
if user.ID == puser.ID {
return c.LocalError("You can't block yourself.", w, r, user)
if u.ID == puser.ID {
return c.LocalError("You can't block yourself.", w, r, u)
}
err = c.UserBlocks.Add(user.ID, puser.ID)
err = c.UserBlocks.Add(u.ID, puser.ID)
if err != nil {
return c.InternalError(err, w, r)
}
@ -449,15 +443,15 @@ func RelationsBlockCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.
return nil
}
func RelationsBlockRemove(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header, spid string) c.RouteError {
func RelationsBlockRemove(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header, spid string) c.RouteError {
h.Title = p.GetTitlePhrase("remove_block")
pid, err := strconv.Atoi(spid)
if err != nil {
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, u)
}
puser, err := c.Users.Get(pid)
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to block doesn't exist.", w, r, user)
return c.LocalError("The user you're trying to block doesn't exist.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
@ -466,19 +460,19 @@ func RelationsBlockRemove(w http.ResponseWriter, r *http.Request, user *c.User,
return renderTemplate("are_you_sure", w, r, h, pi)
}
func RelationsBlockRemoveSubmit(w http.ResponseWriter, r *http.Request, user *c.User, spid string) c.RouteError {
func RelationsBlockRemoveSubmit(w http.ResponseWriter, r *http.Request, u *c.User, spid string) c.RouteError {
pid, err := strconv.Atoi(spid)
if err != nil {
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, u)
}
puser, err := c.Users.Get(pid)
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to unblock doesn't exist.", w, r, user)
return c.LocalError("The user you're trying to unblock doesn't exist.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
err = c.UserBlocks.Remove(user.ID, puser.ID)
err = c.UserBlocks.Remove(u.ID, puser.ID)
if err != nil {
return c.InternalError(err, w, r)
}

View File

@ -35,23 +35,23 @@ func Pages(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_page_list", "", "panel_pages", &pi})
}
func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
name := c.SanitiseSingleLine(r.PostFormValue("name"))
if name == "" {
return c.LocalError("No name was provided for this page", w, r, user)
return c.LocalError("No name was provided for this page", w, r, u)
}
title := c.SanitiseSingleLine(r.PostFormValue("title"))
if title == "" {
return c.LocalError("No title was provided for this page", w, r, user)
return c.LocalError("No title was provided for this page", w, r, u)
}
body := r.PostFormValue("body")
if body == "" {
return c.LocalError("No body was provided for this page", w, r, user)
return c.LocalError("No body was provided for this page", w, r, u)
}
page := c.BlankCustomPage()
@ -62,7 +62,7 @@ func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.R
if err != nil {
return c.InternalError(err, w, r)
}
err = c.AdminLogs.Create("create", pid, "page", user.GetIP(), user.ID)
err = c.AdminLogs.Create("create", pid, "page", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
@ -95,27 +95,27 @@ func PagesEdit(w http.ResponseWriter, r *http.Request, u *c.User, spid string) c
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_page_edit", "", "panel_pages_edit", &pi})
}
func PagesEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, spid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func PagesEditSubmit(w http.ResponseWriter, r *http.Request, u *c.User, spid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
pid, err := strconv.Atoi(spid)
if err != nil {
return c.LocalError("Page ID needs to be an integer", w, r, user)
return c.LocalError("Page ID needs to be an integer", w, r, u)
}
name := c.SanitiseSingleLine(r.PostFormValue("name"))
if name == "" {
return c.LocalError("No name was provided for this page", w, r, user)
return c.LocalError("No name was provided for this page", w, r, u)
}
title := c.SanitiseSingleLine(r.PostFormValue("title"))
if title == "" {
return c.LocalError("No title was provided for this page", w, r, user)
return c.LocalError("No title was provided for this page", w, r, u)
}
body := r.PostFormValue("body")
if body == "" {
return c.LocalError("No body was provided for this page", w, r, user)
return c.LocalError("No body was provided for this page", w, r, u)
}
page, err := c.Pages.Get(pid)
@ -129,7 +129,7 @@ func PagesEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, spid
if err != nil {
return c.InternalError(err, w, r)
}
err = c.AdminLogs.Create("edit", pid, "page", user.GetIP(), user.ID)
err = c.AdminLogs.Create("edit", pid, "page", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}

View File

@ -26,54 +26,54 @@ func Plugins(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
}
// TODO: Abstract more of the plugin activation / installation / deactivation logic, so we can test all that more reliably and easily
func PluginsActivate(w http.ResponseWriter, r *http.Request, user *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func PluginsActivate(w http.ResponseWriter, r *http.Request, u *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return c.NoPermissions(w, r, user)
if !u.Perms.ManagePlugins {
return c.NoPermissions(w, r, u)
}
plugin, ok := c.Plugins[uname]
pl, ok := c.Plugins[uname]
if !ok {
return c.LocalError("The plugin isn't registered in the system", w, r, user)
return c.LocalError("The plugin isn't registered in the system", w, r, u)
}
if plugin.Installable && !plugin.Installed {
return c.LocalError("You can't activate this plugin without installing it first", w, r, user)
if pl.Installable && !pl.Installed {
return c.LocalError("You can't activate this plugin without installing it first", w, r, u)
}
active, err := plugin.BypassActive()
hasPlugin, err2 := plugin.InDatabase()
active, err := pl.BypassActive()
hasPlugin, err2 := pl.InDatabase()
if err != nil || err2 != nil {
return c.InternalError(err, w, r)
}
if plugin.Activate != nil {
err = plugin.Activate(plugin)
if pl.Activate != nil {
err = pl.Activate(pl)
if err != nil {
return c.LocalError(err.Error(), w, r, user)
return c.LocalError(err.Error(), w, r, u)
}
}
if hasPlugin {
if active {
return c.LocalError("The plugin is already active", w, r, user)
return c.LocalError("The plugin is already active", w, r, u)
}
err = plugin.SetActive(true)
err = pl.SetActive(true)
} else {
err = plugin.AddToDatabase(true, false)
err = pl.AddToDatabase(true, false)
}
if err != nil {
return c.InternalError(err, w, r)
}
log.Printf("Activating plugin '%s'", plugin.Name)
err = plugin.Init(plugin)
log.Printf("Activating plugin '%s'", pl.Name)
err = pl.Init(pl)
if err != nil {
return c.LocalError(err.Error(), w, r, user)
return c.LocalError(err.Error(), w, r, u)
}
err = c.AdminLogs.CreateExtra("activate", 0, "plugin", user.GetIP(), user.ID, c.SanitiseSingleLine(plugin.Name))
err = c.AdminLogs.CreateExtra("activate", 0, "plugin", u.GetIP(), u.ID, c.SanitiseSingleLine(pl.Name))
if err != nil {
return c.InternalError(err, w, r)
}
@ -82,36 +82,36 @@ func PluginsActivate(w http.ResponseWriter, r *http.Request, user *c.User, uname
return nil
}
func PluginsDeactivate(w http.ResponseWriter, r *http.Request, user *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func PluginsDeactivate(w http.ResponseWriter, r *http.Request, u *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return c.NoPermissions(w, r, user)
if !u.Perms.ManagePlugins {
return c.NoPermissions(w, r, u)
}
plugin, ok := c.Plugins[uname]
pl, ok := c.Plugins[uname]
if !ok {
return c.LocalError("The plugin isn't registered in the system", w, r, user)
return c.LocalError("The plugin isn't registered in the system", w, r, u)
}
log.Printf("plugin: %+v\n", plugin)
log.Printf("plugin: %+v\n", pl)
active, err := plugin.BypassActive()
active, err := pl.BypassActive()
if err != nil {
return c.InternalError(err, w, r)
} else if !active {
return c.LocalError("The plugin you're trying to deactivate isn't active", w, r, user)
return c.LocalError("The plugin you're trying to deactivate isn't active", w, r, u)
}
err = plugin.SetActive(false)
err = pl.SetActive(false)
if err != nil {
return c.InternalError(err, w, r)
}
if plugin.Deactivate != nil {
plugin.Deactivate(plugin)
if pl.Deactivate != nil {
pl.Deactivate(pl)
}
err = c.AdminLogs.CreateExtra("deactivate", 0, "plugin", user.GetIP(), user.ID, c.SanitiseSingleLine(plugin.Name))
err = c.AdminLogs.CreateExtra("deactivate", 0, "plugin", u.GetIP(), u.ID, c.SanitiseSingleLine(pl.Name))
if err != nil {
return c.InternalError(err, w, r)
}
@ -120,28 +120,28 @@ func PluginsDeactivate(w http.ResponseWriter, r *http.Request, user *c.User, una
return nil
}
func PluginsInstall(w http.ResponseWriter, r *http.Request, user *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func PluginsInstall(w http.ResponseWriter, r *http.Request, u *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return c.NoPermissions(w, r, user)
if !u.Perms.ManagePlugins {
return c.NoPermissions(w, r, u)
}
plugin, ok := c.Plugins[uname]
pl, ok := c.Plugins[uname]
if !ok {
return c.LocalError("The plugin isn't registered in the system", w, r, user)
return c.LocalError("The plugin isn't registered in the system", w, r, u)
}
if !plugin.Installable {
return c.LocalError("This plugin is not installable", w, r, user)
if !pl.Installable {
return c.LocalError("This plugin is not installable", w, r, u)
}
if plugin.Installed {
return c.LocalError("This plugin has already been installed", w, r, user)
if pl.Installed {
return c.LocalError("This plugin has already been installed", w, r, u)
}
active, err := plugin.BypassActive()
hasPlugin, err2 := plugin.InDatabase()
active, err := pl.BypassActive()
hasPlugin, err2 := pl.InDatabase()
if err != nil || err2 != nil {
return c.InternalError(err, w, r)
}
@ -149,39 +149,39 @@ func PluginsInstall(w http.ResponseWriter, r *http.Request, user *c.User, uname
return c.InternalError(errors.New("An uninstalled plugin is still active"), w, r)
}
if plugin.Install != nil {
err = plugin.Install(plugin)
if pl.Install != nil {
err = pl.Install(pl)
if err != nil {
return c.LocalError(err.Error(), w, r, user)
return c.LocalError(err.Error(), w, r, u)
}
}
if plugin.Activate != nil {
err = plugin.Activate(plugin)
if pl.Activate != nil {
err = pl.Activate(pl)
if err != nil {
return c.LocalError(err.Error(), w, r, user)
return c.LocalError(err.Error(), w, r, u)
}
}
if hasPlugin {
err = plugin.SetInstalled(true)
err = pl.SetInstalled(true)
if err != nil {
return c.InternalError(err, w, r)
}
err = plugin.SetActive(true)
err = pl.SetActive(true)
} else {
err = plugin.AddToDatabase(true, true)
err = pl.AddToDatabase(true, true)
}
if err != nil {
return c.InternalError(err, w, r)
}
log.Printf("Installing plugin '%s'", plugin.Name)
err = plugin.Init(plugin)
log.Printf("Installing plugin '%s'", pl.Name)
err = pl.Init(pl)
if err != nil {
return c.LocalError(err.Error(), w, r, user)
return c.LocalError(err.Error(), w, r, u)
}
err = c.AdminLogs.CreateExtra("install", 0, "plugin", user.GetIP(), user.ID, c.SanitiseSingleLine(plugin.Name))
err = c.AdminLogs.CreateExtra("install", 0, "plugin", u.GetIP(), u.ID, c.SanitiseSingleLine(pl.Name))
if err != nil {
return c.InternalError(err, w, r)
}

View File

@ -12,13 +12,13 @@ import (
p "github.com/Azareal/Gosora/common/phrases"
)
func Themes(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "themes", "themes")
func Themes(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, u, "themes", "themes")
if ferr != nil {
return ferr
}
if !user.Perms.ManageThemes {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, u)
}
var pThemeList, vThemeList []*c.Theme
@ -37,28 +37,28 @@ func Themes(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_themes", "", "panel_themes", &pi})
}
func ThemesSetDefault(w http.ResponseWriter, r *http.Request, user *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ThemesSetDefault(w http.ResponseWriter, r *http.Request, u *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
if !user.Perms.ManageThemes {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, u)
}
theme, ok := c.Themes[uname]
if !ok {
return c.LocalError("The theme isn't registered in the system", w, r, user)
return c.LocalError("The theme isn't registered in the system", w, r, u)
}
if theme.Disabled {
return c.LocalError("You must not enable this theme", w, r, user)
return c.LocalError("You must not enable this theme", w, r, u)
}
err := c.UpdateDefaultTheme(theme)
if err != nil {
return c.InternalError(err, w, r)
}
err = c.AdminLogs.CreateExtra("set_default", 0, "theme", user.GetIP(), user.ID, c.SanitiseSingleLine(theme.Name))
err = c.AdminLogs.CreateExtra("set_default", 0, "theme", u.GetIP(), u.ID, c.SanitiseSingleLine(theme.Name))
if err != nil {
return c.InternalError(err, w, r)
}
@ -67,13 +67,13 @@ func ThemesSetDefault(w http.ResponseWriter, r *http.Request, user *c.User, unam
return nil
}
func ThemesMenus(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "themes_menus", "themes")
func ThemesMenus(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, u, "themes_menus", "themes")
if ferr != nil {
return ferr
}
if !user.Perms.ManageThemes {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, u)
}
var menuList []c.PanelMenuListItem
@ -92,21 +92,21 @@ func ThemesMenus(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteEr
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_themes_menus", &c.PanelMenuListPage{basePage, menuList}})
}
func ThemesMenusEdit(w http.ResponseWriter, r *http.Request, user *c.User, smid string) c.RouteError {
func ThemesMenusEdit(w http.ResponseWriter, r *http.Request, u *c.User, smid string) c.RouteError {
// TODO: Something like Menu #1 for the title?
basePage, ferr := buildBasePage(w, r, user, "themes_menus_edit", "themes")
basePage, ferr := buildBasePage(w, r, u, "themes_menus_edit", "themes")
if ferr != nil {
return ferr
}
if !user.Perms.ManageThemes {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, u)
}
basePage.Header.AddScript("Sortable-1.4.0/Sortable.min.js")
basePage.Header.AddScriptAsync("panel_menu_items.js")
mid, err := strconv.Atoi(smid)
if err != nil {
return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, user)
return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, u)
}
menuHold, err := c.Menus.Get(mid)
if err == sql.ErrNoRows {
@ -138,19 +138,19 @@ func ThemesMenusEdit(w http.ResponseWriter, r *http.Request, user *c.User, smid
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_themes_menus_items", &c.PanelMenuPage{basePage, mid, menuList}})
}
func ThemesMenuItemEdit(w http.ResponseWriter, r *http.Request, user *c.User, sitemID string) c.RouteError {
func ThemesMenuItemEdit(w http.ResponseWriter, r *http.Request, u *c.User, sitemID string) c.RouteError {
// TODO: Something like Menu #1 for the title?
basePage, ferr := buildBasePage(w, r, user, "themes_menus_edit", "themes")
basePage, ferr := buildBasePage(w, r, u, "themes_menus_edit", "themes")
if ferr != nil {
return ferr
}
if !user.Perms.ManageThemes {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, u)
}
itemID, err := strconv.Atoi(sitemID)
if err != nil {
return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, user)
return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, u)
}
menuItem, err := c.Menus.ItemStore().Get(itemID)
if err == sql.ErrNoRows {
@ -205,23 +205,23 @@ func themesMenuItemSetters(r *http.Request, i c.MenuItem) c.MenuItem {
return i
}
func ThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sitemID string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sitemID string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, u, js)
}
itemID, err := strconv.Atoi(sitemID)
if err != nil {
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, user, js)
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, u, js)
}
menuItem, err := c.Menus.ItemStore().Get(itemID)
if err == sql.ErrNoRows {
return c.LocalErrorJSQ("This item doesn't exist.", w, r, user, js)
return c.LocalErrorJSQ("This item doesn't exist.", w, r, u, js)
} else if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
@ -232,7 +232,7 @@ func ThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user *c.Us
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("edit", menuItem.ID, "menu_item", user.GetIP(), user.ID)
err = c.AdminLogs.Create("edit", menuItem.ID, "menu_item", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
@ -240,23 +240,23 @@ func ThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user *c.Us
return successRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, js)
}
func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, u, js)
}
smenuID := r.PostFormValue("mid")
if smenuID == "" {
return c.LocalErrorJSQ("No menuID provided", w, r, user, js)
return c.LocalErrorJSQ("No menuID provided", w, r, u, js)
}
menuID, err := strconv.Atoi(smenuID)
if err != nil {
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, user, js)
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, u, js)
}
menuItem := c.MenuItem{MenuID: menuID}
@ -265,7 +265,7 @@ func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("create", itemID, "menu_item", user.GetIP(), user.ID)
err = c.AdminLogs.Create("create", itemID, "menu_item", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
@ -273,23 +273,23 @@ func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.
return successRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, js)
}
func ThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sitemID string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sitemID string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, u, js)
}
itemID, err := strconv.Atoi(sitemID)
if err != nil {
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, user, js)
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, u, js)
}
menuItem, err := c.Menus.ItemStore().Get(itemID)
if err == sql.ErrNoRows {
return c.LocalErrorJSQ("This item doesn't exist.", w, r, user, js)
return c.LocalErrorJSQ("This item doesn't exist.", w, r, u, js)
} else if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
@ -299,7 +299,7 @@ func ThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("delete", menuItem.ID, "menu_item", user.GetIP(), user.ID)
err = c.AdminLogs.Create("delete", menuItem.ID, "menu_item", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
@ -307,23 +307,23 @@ func ThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.
return successRedirect("/panel/themes/menus/", w, r, js)
}
func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, user *c.User, smid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, u *c.User, smid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, u, js)
}
mid, err := strconv.Atoi(smid)
if err != nil {
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, user, js)
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, u, js)
}
menuHold, err := c.Menus.Get(mid)
if err == sql.ErrNoRows {
return c.LocalErrorJSQ("Can't find menu", w, r, user, js)
return c.LocalErrorJSQ("Can't find menu", w, r, u, js)
} else if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
@ -335,13 +335,13 @@ func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, user *c.U
for index, smiid := range strings.Split(sitems, ",") {
miid, err := strconv.Atoi(smiid)
if err != nil {
return c.LocalErrorJSQ("Invalid integer in menu item list", w, r, user, js)
return c.LocalErrorJSQ("Invalid integer in menu item list", w, r, u, js)
}
updateMap[miid] = index
}
menuHold.UpdateOrder(updateMap)
err = c.AdminLogs.Create("suborder", menuHold.MenuID, "menu", user.GetIP(), user.ID)
err = c.AdminLogs.Create("suborder", menuHold.MenuID, "menu", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
@ -349,13 +349,13 @@ func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, user *c.U
return successRedirect("/panel/themes/menus/edit/"+strconv.Itoa(mid), w, r, js)
}
func ThemesWidgets(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "themes_widgets", "themes")
func ThemesWidgets(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, u, "themes_widgets", "themes")
if ferr != nil {
return ferr
}
if !user.Perms.ManageThemes {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, u)
}
basePage.Header.AddScript("widgets.js")
@ -414,20 +414,20 @@ func widgetsParseInputs(r *http.Request, widget *c.Widget) (*c.WidgetEdit, error
}
// ThemesWidgetsEditSubmit is an action which is triggered when someone sends an update request for a widget
func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, swid string) c.RouteError {
func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, u *c.User, swid string) c.RouteError {
//fmt.Println("in ThemesWidgetsEditSubmit")
_, ferr := c.SimplePanelUserCheck(w, r, user)
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, u, js)
}
wid, err := strconv.Atoi(swid)
if err != nil {
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, user, js)
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, u, js)
}
widget, err := c.Widgets.Get(wid)
if err == sql.ErrNoRows {
@ -438,14 +438,14 @@ func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user *c.Use
ewidget, err := widgetsParseInputs(r, widget.Copy())
if err != nil {
return c.LocalErrorJSQ(err.Error(), w, r, user, js)
return c.LocalErrorJSQ(err.Error(), w, r, u, js)
}
err = ewidget.Commit()
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("edit", widget.ID, "widget", user.GetIP(), user.ID)
err = c.AdminLogs.Create("edit", widget.ID, "widget", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
@ -454,25 +454,25 @@ func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user *c.Use
}
// ThemesWidgetsCreateSubmit is an action which is triggered when someone sends a create request for a widget
func ThemesWidgetsCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
func ThemesWidgetsCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
js := r.PostFormValue("js") == "1"
_, ferr := c.SimplePanelUserCheck(w, r, user)
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
if !user.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, u, js)
}
ewidget, err := widgetsParseInputs(r, &c.Widget{})
if err != nil {
return c.LocalErrorJSQ(err.Error(), w, r, user, js)
return c.LocalErrorJSQ(err.Error(), w, r, u, js)
}
wid, err := ewidget.Create()
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("create", wid, "widget", user.GetIP(), user.ID)
err = c.AdminLogs.Create("create", wid, "widget", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
@ -480,19 +480,19 @@ func ThemesWidgetsCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.U
return successRedirect("/panel/themes/widgets/", w, r, js)
}
func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, swid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, u *c.User, swid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, u, js)
}
wid, err := strconv.Atoi(swid)
if err != nil {
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, user, js)
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, u, js)
}
widget, err := c.Widgets.Get(wid)
if err == sql.ErrNoRows {
@ -504,7 +504,7 @@ func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.U
if err != nil {
return c.InternalError(err, w, r)
}
err = c.AdminLogs.Create("delete", widget.ID, "widget", user.GetIP(), user.ID)
err = c.AdminLogs.Create("delete", widget.ID, "widget", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}

View File

@ -110,9 +110,49 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Head
return c.InternalError(err, w, r)
}
}
canMessage := (!blockedInv && user.Perms.UseConvos) || (!blockedInv && puser.IsSuperMod && user.Perms.UseConvosOnlyWithMod) || user.IsSuperMod
canComment := !blockedInv && user.Perms.CreateProfileReply
showComments := profileCommentsShow(puser, user)
if !showComments {
canComment = false
}
if !profileAllowMessage(puser, user) {
canMessage = false
}
ppage := c.ProfilePage{h, reList, *puser, currentScore, nextScore, blocked, canMessage, canComment}
ppage := c.ProfilePage{h, reList, *puser, currentScore, nextScore, blocked, canMessage, canComment, showComments}
return renderTemplate("profile", w, r, h, ppage)
}
func profileAllowMessage(pu, u *c.User) (canMsg bool) {
switch pu.Privacy.AllowMessage {
case 4: // Unused
canMsg = false
case 3: // mods / self
canMsg = u.IsSuperMod
//case 2: // friends
case 1: // registered
canMsg = true
default: // 0
canMsg = true
}
return canMsg
}
func profileCommentsShow(pu, u *c.User) (showComments bool) {
switch pu.Privacy.ShowComments {
case 5: // Unused
showComments = false
case 4: // Self
showComments = u.ID == pu.ID
//case 3: // friends
case 2: // registered
showComments = u.Loggedin
case 1: // public
showComments = true
default: // 0
showComments = true
}
return showComments
}

View File

@ -17,7 +17,6 @@ func ProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.Us
if err != nil {
return c.LocalError("Invalid UID", w, r, user)
}
profileOwner, err := c.Users.Get(uid)
if err == sql.ErrNoRows {
return c.LocalError("The profile you're trying to post on doesn't exist.", w, r, user)
@ -30,7 +29,7 @@ func ProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.Us
return c.InternalError(err, w, r)
}
// Supermods can bypass blocks so they can tell people off when they do something stupid or have to convey important information
if blocked && !user.IsSuperMod {
if (blocked || !profileCommentsShow(profileOwner, user)) && !user.IsSuperMod {
return c.LocalError("You don't have permission to send messages to one of these users.", w, r, user)
}
@ -56,13 +55,12 @@ func ProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.Us
return nil
}
func ProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid string) c.RouteError {
func ProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, u *c.User, srid string) c.RouteError {
js := r.PostFormValue("js") == "1"
rid, err := strconv.Atoi(srid)
if err != nil {
return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, js)
return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, u, js)
}
reply, err := c.Prstore.Get(rid)
if err == sql.ErrNoRows {
return c.PreErrorJSQ("The target reply doesn't exist.", w, r, js)
@ -74,34 +72,41 @@ 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)
if !u.Perms.CreateProfileReply {
return c.NoPermissionsJSQ(w, r, u, 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)
if u.ID != creator.ID && !u.Perms.EditReply {
return c.NoPermissionsJSQ(w, r, u, js)
}
// TODO: Stop blocked users from modifying profile replies?
profileOwner, err := c.Users.Get(reply.ParentID)
if err == sql.ErrNoRows {
return c.LocalError("The profile you're trying to edit a post on doesn't exist.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
blocked, err := c.UserBlocks.IsBlockedBy(profileOwner.ID, u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
// Supermods can bypass blocks so they can tell people off when they do something stupid or have to convey important information
if (blocked || !profileCommentsShow(profileOwner, u)) && !u.IsSuperMod {
return c.NoPermissionsJSQ(w, r, u, js)
}
err = reply.SetBody(r.PostFormValue("edit_item"))
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
if !js {
http.Redirect(w, r, "/user/"+strconv.Itoa(creator.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther)
} else {
w.Write(successJSONBytes)
}
return nil
return actionSuccess(w, r, "/user/"+strconv.Itoa(creator.ID)+"#reply-"+strconv.Itoa(rid), js)
}
func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid string) c.RouteError {
func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, u *c.User, srid string) c.RouteError {
js := r.PostFormValue("js") == "1"
rid, err := strconv.Atoi(srid)
if err != nil {
return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, js)
return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, u, js)
}
reply, err := c.Prstore.Get(rid)
@ -115,15 +120,15 @@ func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.Us
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
if user.ID != creator.ID && !user.Perms.DeleteReply {
return c.NoPermissionsJSQ(w, r, user, js)
if u.ID != creator.ID && !u.Perms.DeleteReply {
return c.NoPermissionsJSQ(w, r, u, js)
}
err = reply.Delete()
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
//log.Printf("The profile post '%d' was deleted by c.User #%d", reply.ID, user.ID)
//log.Printf("The profile post '%d' was deleted by c.User #%d", reply.ID, u.ID)
if !js {
//http.Redirect(w,r, "/user/" + strconv.Itoa(creator.ID), http.StatusSeeOther)
@ -131,7 +136,7 @@ func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.Us
w.Write(successJSONBytes)
}
err = c.ModLogs.Create("delete", reply.ParentID, "profile-reply", user.GetIP(), user.ID)
err = c.ModLogs.Create("delete", reply.ParentID, "profile-reply", u.GetIP(), u.ID)
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}

View File

@ -9,46 +9,46 @@ import (
c "github.com/Azareal/Gosora/common"
)
func BanUserSubmit(w http.ResponseWriter, r *http.Request, user *c.User, suid string) c.RouteError {
if !user.Perms.BanUsers {
return c.NoPermissions(w, r, user)
func BanUserSubmit(w http.ResponseWriter, r *http.Request, u *c.User, suid string) c.RouteError {
if !u.Perms.BanUsers {
return c.NoPermissions(w, r, u)
}
uid, err := strconv.Atoi(suid)
if err != nil {
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
return c.LocalError("The provided UserID is not a valid number.", w, r, u)
}
if uid == -2 {
return c.LocalError("Why don't you like Merlin?", w, r, user)
return c.LocalError("Why don't you like Merlin?", w, r, u)
}
targetUser, err := c.Users.Get(uid)
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to ban no longer exists.", w, r, user)
return c.LocalError("The user you're trying to ban no longer exists.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
// TODO: Is there a difference between IsMod and IsSuperMod? Should we delete the redundant one?
if targetUser.IsMod {
return c.LocalError("You may not ban another staff member.", w, r, user)
return c.LocalError("You may not ban another staff member.", w, r, u)
}
if uid == user.ID {
return c.LocalError("Why are you trying to ban yourself? Stop that.", w, r, user)
if uid == u.ID {
return c.LocalError("Why are you trying to ban yourself? Stop that.", w, r, u)
}
if targetUser.IsBanned {
return c.LocalError("The user you're trying to unban is already banned.", w, r, user)
return c.LocalError("The user you're trying to unban is already banned.", w, r, u)
}
durDays, err := strconv.Atoi(r.FormValue("dur-days"))
if err != nil {
return c.LocalError("You can only use whole numbers for the number of days", w, r, user)
return c.LocalError("You can only use whole numbers for the number of days", w, r, u)
}
durWeeks, err := strconv.Atoi(r.FormValue("dur-weeks"))
if err != nil {
return c.LocalError("You can only use whole numbers for the number of weeks", w, r, user)
return c.LocalError("You can only use whole numbers for the number of weeks", w, r, u)
}
durMonths, err := strconv.Atoi(r.FormValue("dur-months"))
if err != nil {
return c.LocalError("You can only use whole numbers for the number of months", w, r, user)
return c.LocalError("You can only use whole numbers for the number of months", w, r, u)
}
deletePosts := false
switch r.FormValue("delete-posts") {
@ -67,13 +67,13 @@ func BanUserSubmit(w http.ResponseWriter, r *http.Request, user *c.User, suid st
dur, _ = time.ParseDuration(strconv.Itoa(secs) + "s")
}
err = targetUser.Ban(dur, user.ID)
err = targetUser.Ban(dur, u.ID)
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to ban no longer exists.", w, r, user)
return c.LocalError("The user you're trying to ban no longer exists.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
err = c.ModLogs.Create("ban", uid, "user", user.GetIP(), user.ID)
err = c.ModLogs.Create("ban", uid, "user", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
@ -81,11 +81,11 @@ func BanUserSubmit(w http.ResponseWriter, r *http.Request, user *c.User, suid st
if deletePosts {
err = targetUser.DeletePosts()
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to ban no longer exists.", w, r, user)
return c.LocalError("The user you're trying to ban no longer exists.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
err = c.ModLogs.Create("delete-posts", uid, "user", user.GetIP(), user.ID)
err = c.ModLogs.Create("delete-posts", uid, "user", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
@ -93,7 +93,7 @@ func BanUserSubmit(w http.ResponseWriter, r *http.Request, user *c.User, suid st
// TODO: Trickle the hookTable down from the router
hTbl := c.GetHookTable()
skip, rerr := hTbl.VhookSkippable("action_end_ban_user", targetUser.ID, user)
skip, rerr := hTbl.VhookSkippable("action_end_ban_user", targetUser.ID, u)
if skip || rerr != nil {
return rerr
}
@ -102,42 +102,42 @@ func BanUserSubmit(w http.ResponseWriter, r *http.Request, user *c.User, suid st
return nil
}
func UnbanUser(w http.ResponseWriter, r *http.Request, user *c.User, suid string) c.RouteError {
if !user.Perms.BanUsers {
return c.NoPermissions(w, r, user)
func UnbanUser(w http.ResponseWriter, r *http.Request, u *c.User, suid string) c.RouteError {
if !u.Perms.BanUsers {
return c.NoPermissions(w, r, u)
}
uid, err := strconv.Atoi(suid)
if err != nil {
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
return c.LocalError("The provided UserID is not a valid number.", w, r, u)
}
targetUser, err := c.Users.Get(uid)
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to unban no longer exists.", w, r, user)
return c.LocalError("The user you're trying to unban no longer exists.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
if !targetUser.IsBanned {
return c.LocalError("The user you're trying to unban isn't banned.", w, r, user)
return c.LocalError("The user you're trying to unban isn't banned.", w, r, u)
}
err = targetUser.Unban()
if err == c.ErrNoTempGroup {
return c.LocalError("The user you're trying to unban is not banned", w, r, user)
return c.LocalError("The user you're trying to unban is not banned", w, r, u)
} else if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to unban no longer exists.", w, r, user)
return c.LocalError("The user you're trying to unban no longer exists.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
err = c.ModLogs.Create("unban", uid, "user", user.GetIP(), user.ID)
err = c.ModLogs.Create("unban", uid, "user", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
// TODO: Trickle the hookTable down from the router
hTbl := c.GetHookTable()
skip, rerr := hTbl.VhookSkippable("action_end_unban_user", targetUser.ID, user)
skip, rerr := hTbl.VhookSkippable("action_end_unban_user", targetUser.ID, u)
if skip || rerr != nil {
return rerr
}
@ -146,23 +146,23 @@ func UnbanUser(w http.ResponseWriter, r *http.Request, user *c.User, suid string
return nil
}
func ActivateUser(w http.ResponseWriter, r *http.Request, user *c.User, suid string) c.RouteError {
if !user.Perms.ActivateUsers {
return c.NoPermissions(w, r, user)
func ActivateUser(w http.ResponseWriter, r *http.Request, u *c.User, suid string) c.RouteError {
if !u.Perms.ActivateUsers {
return c.NoPermissions(w, r, u)
}
uid, err := strconv.Atoi(suid)
if err != nil {
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
return c.LocalError("The provided UserID is not a valid number.", w, r, u)
}
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)
return c.LocalError("The account you're trying to activate no longer exists.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
if targetUser.Active {
return c.LocalError("The account you're trying to activate has already been activated.", w, r, user)
return c.LocalError("The account you're trying to activate has already been activated.", w, r, u)
}
err = targetUser.Activate()
if err != nil {
@ -171,7 +171,7 @@ func ActivateUser(w http.ResponseWriter, r *http.Request, user *c.User, suid str
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)
return c.LocalError("The account you're trying to activate no longer exists.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
@ -181,14 +181,14 @@ func ActivateUser(w http.ResponseWriter, r *http.Request, user *c.User, suid str
}
targetUser.CacheRemove()
err = c.ModLogs.Create("activate", targetUser.ID, "user", user.GetIP(), user.ID)
err = c.ModLogs.Create("activate", targetUser.ID, "user", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
// TODO: Trickle the hookTable down from the router
hTbl := c.GetHookTable()
skip, rerr := hTbl.VhookSkippable("action_end_activate_user", targetUser.ID, user)
skip, rerr := hTbl.VhookSkippable("action_end_activate_user", targetUser.ID, u)
if skip || rerr != nil {
return rerr
}
@ -197,40 +197,40 @@ func ActivateUser(w http.ResponseWriter, r *http.Request, user *c.User, suid str
return nil
}
func DeletePostsSubmit(w http.ResponseWriter, r *http.Request, user *c.User, suid string) c.RouteError {
if !user.Perms.BanUsers {
return c.NoPermissions(w, r, user)
func DeletePostsSubmit(w http.ResponseWriter, r *http.Request, u *c.User, suid string) c.RouteError {
if !u.Perms.BanUsers {
return c.NoPermissions(w, r, u)
}
uid, err := strconv.Atoi(suid)
if err != nil {
return c.LocalError("The provided UserID is not a valid number.", w, r, user)
return c.LocalError("The provided UserID is not a valid number.", w, r, u)
}
targetUser, err := c.Users.Get(uid)
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to purge posts of no longer exists.", w, r, user)
return c.LocalError("The user you're trying to purge posts of no longer exists.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
// TODO: Is there a difference between IsMod and IsSuperMod? Should we delete the redundant one?
if targetUser.IsMod {
return c.LocalError("You may not purge the posts of another staff member.", w, r, user)
return c.LocalError("You may not purge the posts of another staff member.", w, r, u)
}
err = targetUser.DeletePosts()
if err == sql.ErrNoRows {
return c.LocalError("The user you're trying to purge posts of no longer exists.", w, r, user)
return c.LocalError("The user you're trying to purge posts of no longer exists.", w, r, u)
} else if err != nil {
return c.InternalError(err, w, r)
}
err = c.ModLogs.Create("delete-posts", uid, "user", user.GetIP(), user.ID)
err = c.ModLogs.Create("delete-posts", uid, "user", u.GetIP(), u.ID)
if err != nil {
return c.InternalError(err, w, r)
}
// TODO: Trickle the hookTable down from the router
hTbl := c.GetHookTable()
skip, rerr := hTbl.VhookSkippable("action_end_delete_posts", targetUser.ID, user)
skip, rerr := hTbl.VhookSkippable("action_end_delete_posts", targetUser.ID, u)
if skip || rerr != nil {
return rerr
}

View File

@ -1,7 +1,7 @@
CREATE TABLE `login_logs` (
`lid` int not null AUTO_INCREMENT,
`uid` int not null,
`success` bool DEFAULT 0 not null,
`success` boolean DEFAULT 0 not null,
`ipaddress` varchar(200) not null,
`doneAt` datetime not null,
primary key(`lid`)

View File

@ -3,7 +3,7 @@ CREATE TABLE `registration_logs` (
`username` varchar(100) not null,
`email` varchar(100) not null,
`failureReason` varchar(100) not null,
`success` bool DEFAULT 0 not null,
`success` boolean DEFAULT 0 not null,
`ipaddress` varchar(200) not null,
`doneAt` datetime not null,
primary key(`rlid`)

View File

@ -10,6 +10,8 @@ CREATE TABLE `users` (
`lastActiveAt` datetime not null,
`session` varchar(200) DEFAULT '' not null,
`last_ip` varchar(200) DEFAULT '' not null,
`profile_comments` int DEFAULT 0 not null,
`who_can_convo` int DEFAULT 0 not null,
`enable_embeds` int DEFAULT -1 not null,
`email` varchar(200) DEFAULT '' not null,
`avatar` varchar(100) DEFAULT '' not null,

View File

@ -2,6 +2,6 @@ CREATE TABLE "forums_permissions" (
`fid` int not null,
`gid` int not null,
`preset` varchar (100) DEFAULT '' not null,
`permissions` text not null,
`permissions` text DEFAULT '{}' not null,
primary key(`fid`,`gid`)
);

View File

@ -1,7 +1,7 @@
CREATE TABLE "login_logs" (
`lid` serial not null,
`uid` int not null,
`success` bool DEFAULT 0 not null,
`success` boolean DEFAULT 0 not null,
`ipaddress` varchar (200) not null,
`doneAt` timestamp not null,
primary key(`lid`)

View File

@ -3,7 +3,7 @@ CREATE TABLE "registration_logs" (
`username` varchar (100) not null,
`email` varchar (100) not null,
`failureReason` varchar (100) not null,
`success` bool DEFAULT 0 not null,
`success` boolean DEFAULT 0 not null,
`ipaddress` varchar (200) not null,
`doneAt` timestamp not null,
primary key(`rlid`)

View File

@ -3,17 +3,18 @@
</div>
<div class="colstack_item the_form">
<form action="/user/edit/privacy/submit/?s={{.CurrentUser.Session}}" method="post">
<!--<input name="o_profile_comments" value="{{if .ProfileComments}}1{{else}}0{{end}}" type="hidden">
<input name="o_receive_convos" value="{{if .ReceiveConvos}}1{{else}}0{{end}}" type="hidden">-->
<input name="o_profile_comments" value="{{.ProfileComments}}" type="hidden">
<!--<input name="o_receive_convos" value="{{if .ReceiveConvos}}1{{else}}0{{end}}" type="hidden">-->
<input name="o_enable_embeds" value="{{if .EnableEmbeds}}1{{else}}0{{end}}" type="hidden">
<!--<div class="formrow real_first_child">
<div class="formrow real_first_child">
<div class="formitem formlabel">{{lang "account_privacy_profile_comments"}}</div>
<div class="formitem"><select name="profile_comments">
<option{{if .ProfileComments}} selected{{end}} value=1>{{lang "option_yes"}}</option>
<option{{if not .ProfileComments}} selected{{end}} value=0>{{lang "option_no"}}</option>
<option{{if eq .ProfileComments 1}} selected{{end}} value=1>{{lang "account_privacy_profile_comments_public"}}</option>
<option{{if eq .ProfileComments 2}} selected{{end}} value=2>{{lang "account_privacy_profile_comments_registered"}}</option>
<option{{if eq .ProfileComments 4}} selected{{end}} value=4>{{lang "account_privacy_profile_comments_self"}}</option>
</select></div>
</div>
<div class="formrow">
<!--<div class="formrow">
<div class="formitem formlabel"><a>Receive Conversations</a></div>
<div class="formitem"><select name="receive_convos">
<option{{if .ReceiveConvos}} selected{{end}} value=1>{{lang "option_yes"}}</option>
@ -21,7 +22,7 @@
</select></div>
</div>-->
<div class="formrow">
<div class="formitem formlabel"><a>Enable Embeds</a></div>
<div class="formitem formlabel"><a>{{lang "account_privacy_enable_embeds"}}</a></div>
<div class="formitem"><select name="enable_embeds">
<option{{if .EnableEmbeds}} selected{{end}} value=1>{{lang "option_yes"}}</option>
<option{{if not .EnableEmbeds}} selected{{end}} value=0>{{lang "option_no"}}</option>

View File

@ -1,8 +1,8 @@
{{template "header.html" . }}
<main id="forumItemList" itemscope itemtype="http://schema.org/ItemList">
<link rel="canonical" href="//{{.Site.URL}}{{.Forum.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}"/>
<main id="forumItemList"itemscope itemtype="http://schema.org/ItemList">
<link rel="canonical"href="//{{.Site.URL}}{{.Forum.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}">
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block{{if .CurrentUser.Loggedin}} has_opt{{end}}">
<div id="forum_head_block"class="rowblock rowhead topic_list_title_block{{if .CurrentUser.Loggedin}} has_opt{{end}}">
<div class="rowitem forum_title">
<h1 itemprop="name">{{.Title}}</h1>
</div>

View File

@ -1,25 +1,25 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_active_memory_head"}}</h1>
<select form="timeRangeForm" class="typeSelector to_right autoSubmitRedirect" name="mtype">
<option value="0"{{if eq .MemType 0}} selected{{end}}>{{lang "panel_stats_memory_type_total"}}</option>
<option value="1"{{if eq .MemType 1}} selected{{end}}>{{lang "panel_stats_memory_type_stack"}}</option>
<option value="2"{{if eq .MemType 2}} selected{{end}}>{{lang "panel_stats_memory_type_heap"}}</option>
<select form="timeRangeForm"class="typeSelector to_right autoSubmitRedirect"name="mtype">
<option value="0"{{if eq .MemType 0}}selected{{end}}>{{lang "panel_stats_memory_type_total"}}</option>
<option value="1"{{if eq .MemType 1}}selected{{end}}>{{lang "panel_stats_memory_type_stack"}}</option>
<option value="2"{{if eq .MemType 2}}selected{{end}}>{{lang "panel_stats_memory_type_heap"}}</option>
</select>
<noscript><input form="timeRangeForm" type="submit"></noscript>
<noscript><input form="timeRangeForm"type="submit"></noscript>
{{template "panel_analytics_time_range_month.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/active-memory/" method="get"></form>
<div id="panel_analytics_memory" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_memory_chart_aria"}}"></div>
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/active-memory/"method="get"></form>
<div id="panel_analytics_memory"class="colstack_graph_holder">
<div class="ct_chart"aria-label="{{lang "panel_stats_memory_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_memory_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_memory_table_aria"}}">
<div id="panel_analytics_memory_table"class="colstack_item rowlist"aria-label="{{lang "panel_stats_memory_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_to_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}24_hour_time{{else}}date{{end}}">{{.Time}}</a>

View File

@ -4,16 +4,16 @@
{{template "panel_analytics_time_range_month.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/memory/" method="get"></form>
<div id="panel_analytics_memory" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_memory_chart_aria"}}"></div>
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/memory/"method="get"></form>
<div id="panel_analytics_memory"class="colstack_graph_holder">
<div class="ct_chart"aria-label="{{lang "panel_stats_memory_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_memory_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_memory_table_aria"}}">
<div id="panel_analytics_memory_table"class="colstack_item rowlist"aria-label="{{lang "panel_stats_memory_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}to_24_hour_time{{else}}to_date{{end}}">{{.Time}}</a>

View File

@ -1,25 +1,25 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_perf_head"}}</h1>
<select form="timeRangeForm" class="typeSelector to_right autoSubmitRedirect" name="type">
<option value="0"{{if eq .PerfType 0}} selected{{end}}>{{lang "panel_stats_perf_low"}}</option>
<option value="1"{{if eq .PerfType 1}} selected{{end}}>{{lang "panel_stats_perf_high"}}</option>
<option value="2"{{if eq .PerfType 2}} selected{{end}}>{{lang "panel_stats_perf_avg"}}</option>
<select form="timeRangeForm"class="typeSelector to_right autoSubmitRedirect" name="type">
<option value="0"{{if eq .PerfType 0}}selected{{end}}>{{lang "panel_stats_perf_low"}}</option>
<option value="1"{{if eq .PerfType 1}}selected{{end}}>{{lang "panel_stats_perf_high"}}</option>
<option value="2"{{if eq .PerfType 2}}selected{{end}}>{{lang "panel_stats_perf_avg"}}</option>
</select>
<noscript><input form="timeRangeForm" type="submit"></noscript>
<noscript><input form="timeRangeForm"type="submit"></noscript>
{{template "panel_analytics_time_range_month.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/perf/" method="get"></form>
<div id="panel_analytics_memory" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_perf_chart_aria"}}"></div>
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/perf/" method="get"></form>
<div id="panel_analytics_memory"class="colstack_graph_holder">
<div class="ct_chart"aria-label="{{lang "panel_stats_perf_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_perf_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_perf_table_aria"}}">
<div id="panel_analytics_perf_table"class="colstack_item rowlist"aria-label="{{lang "panel_stats_perf_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_to_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}24_hour_time{{else}}date{{end}}">{{.Time}}</a>

View File

@ -4,16 +4,16 @@
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/posts/" method="get"></form>
<div id="panel_analytics_posts" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_post_counts_chart_aria"}}"></div>
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/posts/"method="get"></form>
<div id="panel_analytics_posts"class="colstack_graph_holder">
<div class="ct_chart"aria-label="{{lang "panel_stats_post_counts_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_posts_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_post_counts_table_aria"}}">
<div id="panel_analytics_posts_table"class="colstack_item rowlist"aria-label="{{lang "panel_stats_post_counts_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}to_24_hour_time{{else}}to_date{{end}}">{{.Time}}</a>

View File

@ -4,8 +4,8 @@
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrer/{{.Agent}}" method="get"></form>
<div id="panel_analytics_referrers" class="colstack_graph_holder">
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/referrer/{{.Agent}}"method="get"></form>
<div id="panel_analytics_referrers"class="colstack_graph_holder">
<div class="ct_chart"></div>
</div>
{{template "panel_analytics_script.html" . }}

View File

@ -1,19 +1,19 @@
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_referrers_head"}}</h1>
<select form="timeRangeForm" class="spamSelector to_right autoSubmitRedirect" name="spam">
<option value="0"{{if not .ShowSpam}} selected{{end}}>{{lang "panel_stats_spam_hide"}}</option>
<option value="1"{{if .ShowSpam}} selected{{end}}>{{lang "panel_stats_spam_show"}}</option>
<select form="timeRangeForm"class="spamSelector to_right autoSubmitRedirect"name="spam">
<option value="0"{{if not .ShowSpam}}selected{{end}}>{{lang "panel_stats_spam_hide"}}</option>
<option value="1"{{if .ShowSpam}}selected{{end}}>{{lang "panel_stats_spam_show"}}</option>
</select>
<noscript><input form="timeRangeForm" type="submit"></noscript>
<noscript><input form="timeRangeForm"type="submit"></noscript>
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrers/" method="get"></form>
<div id="panel_analytics_referrers" class="colstack_item rowlist">
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/referrers/"method="get"></form>
<div id="panel_analytics_referrers"class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/referrer/{{.Agent}}" class="panel_upshift">{{.Agent}}</a>
<a href="/panel/analytics/referrer/{{.Agent}}"class="panel_upshift">{{.Agent}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_referrers_no_referrers"}}</div>{{end}}

View File

@ -4,14 +4,14 @@
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/route/{{.Route}}" method="get"></form>
<div id="panel_analytics_views" class="colstack_graph_holder">
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/route/{{.Route}}"method="get"></form>
<div id="panel_analytics_views"class="colstack_graph_holder">
<div class="ct_chart"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem"><a>{{lang "panel_stats_details_head"}}</a></div>
</div>
<div id="panel_analytics_views_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_route_views_table_aria"}}">
<div id="panel_analytics_views_table"class="colstack_item rowlist"aria-label="{{lang "panel_stats_route_views_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_to_24_hour_time">{{.Time}}</a>

View File

@ -4,14 +4,14 @@
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/routes/" method="get"></form>
<div id="panel_analytics_routes_chart" class="colstack_graph_holder">
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/routes/"method="get"></form>
<div id="panel_analytics_routes_chart"class="colstack_graph_holder">
<div class="ct_chart"></div>
</div>
<div id="panel_analytics_routes" class="colstack_item rowlist">
<div id="panel_analytics_routes"class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/route/{{.Route}}" class="panel_upshift">{{.Route}}</a>
<a href="/panel/analytics/route/{{.Route}}"class="panel_upshift">{{.Route}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_routes_no_routes"}}</div>{{end}}

View File

@ -4,14 +4,14 @@
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/routes-perf/" method="get"></form>
<div id="panel_analytics_routes_chart" class="colstack_graph_holder">
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/routes-perf/"method="get"></form>
<div id="panel_analytics_routes_chart"class="colstack_graph_holder">
<div class="ct_chart"></div>
</div>
<div id="panel_analytics_routes" class="colstack_item rowlist">
<div id="panel_analytics_routes"class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/route/{{.Route}}" class="panel_upshift">{{.Route}}</a>
<a href="/panel/analytics/route/{{.Route}}"class="panel_upshift">{{.Route}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{.Unit}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_routes_no_routes"}}</div>{{end}}

View File

@ -13,7 +13,7 @@ let legendNames = [{{range .Graph.Legends}}
addInitHook("after_phrases", () => {
addInitHook("end_init", () => {
addInitHook("analytics_loaded", () => {
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames);
buildStatsChart(rawLabels,seriesData,"{{.TimeRange}}",legendNames);
});
});
});

View File

@ -13,8 +13,8 @@ let legendNames = [{{range .Graph.Legends}}
addInitHook("after_phrases", () => {
addInitHook("end_init", () => {
addInitHook("analytics_loaded", () => {
memStuff(window, document, Chartist);
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames,1);
memStuff(window,document,Chartist);
buildStatsChart(rawLabels,seriesData,"{{.TimeRange}}",legendNames,1);
});
});
});

View File

@ -13,8 +13,8 @@ let legendNames = [{{range .Graph.Legends}}
addInitHook("after_phrases", () => {
addInitHook("end_init", () => {
addInitHook("analytics_loaded", () => {
perfStuff(window, document, Chartist);
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames,2);
perfStuff(window,document,Chartist);
buildStatsChart(rawLabels,seriesData,"{{.TimeRange}}",legendNames,2);
});
});
});

View File

@ -4,8 +4,8 @@
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/system/{{.Agent}}" method="get"></form>
<div id="panel_analytics_systems" class="colstack_graph_holder">
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/system/{{.Agent}}"method="get"></form>
<div id="panel_analytics_systems"class="colstack_graph_holder">
<div class="ct_chart"></div>
</div>
{{template "panel_analytics_script.html" . }}

View File

@ -4,14 +4,14 @@
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/systems/" method="get"></form>
<div id="panel_analytics_systems_chart" class="colstack_graph_holder">
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/systems/"method="get"></form>
<div id="panel_analytics_systems_chart"class="colstack_graph_holder">
<div class="ct_chart"></div>
</div>
<div id="panel_analytics_systems" class="colstack_item rowlist">
<div id="panel_analytics_systems"class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/system/{{.Agent}}" class="panel_upshift">{{.FriendlyAgent}}</a>
<a href="/panel/analytics/system/{{.Agent}}"class="panel_upshift">{{.FriendlyAgent}}</a>
<span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_operating_systems_no_operating_systems"}}</div>{{end}}

View File

@ -1,9 +1,9 @@
<select form="timeRangeForm" class="timeRangeSelector to_right autoSubmitRedirect" name="timeRange">
<option value="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>{{lang "panel_stats_time_range_one_month"}}</option>
<option value="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>{{lang "panel_stats_time_range_one_week"}}</option>
<option value="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>{{lang "panel_stats_time_range_two_days"}}</option>
<option value="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>{{lang "panel_stats_time_range_one_day"}}</option>
<option value="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>{{lang "panel_stats_time_range_twelve_hours"}}</option>
<option value="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>{{lang "panel_stats_time_range_six_hours"}}</option>
<select form="timeRangeForm"class="timeRangeSelector to_right autoSubmitRedirect"sname="timeRange">
<option value="one-month"{{if eq .TimeRange "one-month"}}selected{{end}}>{{lang "panel_stats_time_range_one_month"}}</option>
<option value="one-week"{{if eq .TimeRange "one-week"}}selected{{end}}>{{lang "panel_stats_time_range_one_week"}}</option>
<option value="two-days"{{if eq .TimeRange "two-days"}}selected{{end}}>{{lang "panel_stats_time_range_two_days"}}</option>
<option value="one-day"{{if eq .TimeRange "one-day"}}selected{{end}}>{{lang "panel_stats_time_range_one_day"}}</option>
<option value="twelve-hours"{{if eq .TimeRange "twelve-hours"}}selected{{end}}>{{lang "panel_stats_time_range_twelve_hours"}}</option>
<option value="six-hours"{{if eq .TimeRange "six-hours"}}selected{{end}}>{{lang "panel_stats_time_range_six_hours"}}</option>
</select>
<noscript><input form="timeRangeForm" type="submit"></noscript>
<noscript><input form="timeRangeForm"type="submit"></noscript>

View File

@ -4,16 +4,16 @@
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/topics/" method="get"></form>
<div id="panel_analytics_topics" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_topic_counts_chart_aria"}}"></div>
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/topics/"method="get"></form>
<div id="panel_analytics_topics"class="colstack_graph_holder">
<div class="ct_chart"aria-label="{{lang "panel_stats_topic_counts_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_topics_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_topic_counts_table_aria"}}">
<div id="panel_analytics_topics_table"class="colstack_item rowlist"aria-label="{{lang "panel_stats_topic_counts_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_to_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}24_hour_time{{else}}date{{end}}">{{.Time}}</a>

View File

@ -4,16 +4,16 @@
{{template "panel_analytics_time_range.html" . }}
</div>
</div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get"></form>
<div id="panel_analytics_views" class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_requests_chart_aria"}}"></div>
<form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/views/"method="get"></form>
<div id="panel_analytics_views"class="colstack_graph_holder">
<div class="ct_chart"aria-label="{{lang "panel_stats_requests_chart_aria"}}"></div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1>
</div>
</div>
<div id="panel_analytics_views_table" class="colstack_item rowlist" aria-label="{{lang "panel_stats_requests_table_aria"}}">
<div id="panel_analytics_views_table"class="colstack_item rowlist"aria-label="{{lang "panel_stats_requests_table_aria"}}">
{{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_to_{{if or (or (or (eq $.TimeRange "six-hours") (eq $.TimeRange "twelve-hours")) (eq $.TimeRange "one-day")) (eq $.TimeRange "two-days")}}24_hour_time{{else}}date{{end}}">{{.Time}}</a>

View File

@ -1,12 +1,12 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_backups_head"}}</h1></div>
</div>
<div id="panel_backups" class="colstack_item rowlist">
<div id="panel_backups"class="colstack_item rowlist">
{{range .Backups}}
<div class="rowitem panel_compactrow">
<span>{{.SQLURL}}</span>
<span class="panel_floater">
<a href="/panel/backups/{{.SQLURL}}" class="panel_tag panel_right_button">{{lang "panel_backups_download"}}</a>
<a href="/panel/backups/{{.SQLURL}}"class="panel_tag panel_right_button">{{lang "panel_backups_download"}}</a>
</span>
</div>
{{else}}<div class="rowitem rowmsg">{{lang "panel_backups_no_backups"}}</div>{{end}}

View File

@ -1,15 +1,15 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div>
</div>
<div id="panel_forum" class="colstack_item the_form">
<div id="panel_forum"class="colstack_item the_form">
<form action="/panel/forums/edit/submit/{{.ID}}?s={{.CurrentUser.Session}}" method="post">
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_name"}}</a></div>
<div class="formitem"><input name="forum_name" type="text" value="{{.Name}}" placeholder="{{lang "panel_forum_name_placeholder"}}"></div>
<div class="formitem"><input name="forum_name"type="text"value="{{.Name}}"placeholder="{{lang "panel_forum_name_placeholder"}}"></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_description"}}</a></div>
<div class="formitem"><input name="forum_desc" type="text" value="{{.Desc}}" placeholder="{{lang "panel_forum_description_placeholder"}}"></div>
<div class="formitem"><input name="forum_desc"type="text"value="{{.Desc}}"placeholder="{{lang "panel_forum_description_placeholder"}}"></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_active"}}</a></div>
@ -23,7 +23,7 @@
<div class="formitem">
<select name="forum_preset">
<option{{if eq .Preset "all"}} selected{{end}} value="all">{{lang "panel_preset_everyone"}}</option>
<option{{if eq .Preset "announce"}} selected{{end}} value="announce">{{lang "panel_preset_announcements"}}</option>
<option{{if eq .Preset "announce"}} selected{{end}} value="announce">{{lang "panel_preset_announcements"}}</option>
<option{{if eq .Preset "members"}} selected{{end}} value="members">{{lang "panel_preset_member_only"}}</option>
<option{{if eq .Preset "staff"}} selected{{end}} value="staff">{{lang "panel_preset_staff_only"}}</option>
<option{{if eq .Preset "admins"}} selected{{end}} value="admins">{{lang "panel_preset_admin_only"}}</option>
@ -33,7 +33,7 @@
</div>
</div>
<div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton form_middle_button">{{lang "panel_forum_update_button"}}</button></div>
<div class="formitem"><button name="panel-button"class="formbutton form_middle_button">{{lang "panel_forum_update_button"}}</button></div>
</div>
</form>
</div>
@ -42,17 +42,17 @@
<h1>{{lang "panel_forum_permissions_head"}}</h1>
</div>
</div>
<div id="forum_quick_perms" class="colstack_item rowlist formlist the_form">
<div id="forum_quick_perms"class="colstack_item rowlist formlist the_form">
{{range .Groups}}
<div class="formrow">
<div class="formitem editable_parent">
<a>{{.Group.Name}}</a>
<input name="gid" value="{{.Group.ID}}" type="hidden" class="editable_block" data-field="gid" data-type="hidden" data-value="{{.Group.ID}}">
<input name="gid"value="{{.Group.ID}}"type="hidden"class="editable_block"data-field="gid"data-type="hidden"data-value="{{.Group.ID}}">
<span class="edit_fields hide_on_edit rowsmall">{{lang "panel_forum_edit_button"}}</span>
<div class="panel_floater">
<span data-field="perm_preset" data-type="list" data-value="{{.Preset}}" class="editable_block perm_preset perm_preset_{{.Preset}}"></span>
<a class="panel_right_button has_inner_button show_on_edit" href="/panel/forums/edit/perms/submit/{{$.ID}}"><button class='panel_tag submit_edit' type='submit'>{{lang "panel_forum_short_update_button"}}</button></a>
<a class="panel_right_button has_inner_button show_on_edit" href="/panel/forums/edit/perms/{{$.ID}}-{{.Group.ID}}"><button class='panel_tag' type='submit'>{{lang "panel_forum_full_edit_button"}}</button></a>
<span data-field="perm_preset"data-type="list"data-value="{{.Preset}}" class="editable_block perm_preset perm_preset_{{.Preset}}"></span>
<a class="panel_right_button has_inner_button show_on_edit"href="/panel/forums/edit/perms/submit/{{$.ID}}"><button class='panel_tag submit_edit'type='submit'>{{lang "panel_forum_short_update_button"}}</button></a>
<a class="panel_right_button has_inner_button show_on_edit"href="/panel/forums/edit/perms/{{$.ID}}-{{.Group.ID}}"><button class='panel_tag'type='submit'>{{lang "panel_forum_full_edit_button"}}</button></a>
</div>
</div>
</div>

View File

@ -1,7 +1,7 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div>
</div>
<form action="/panel/forums/edit/perms/adv/submit/{{.ForumID}}-{{.GroupID}}?s={{.CurrentUser.Session}}" method="post">
<form action="/panel/forums/edit/perms/adv/submit/{{.ForumID}}-{{.GroupID}}?s={{.CurrentUser.Session}}"method="post">
<div class="colstack_item rowlist formlist the_form panel_forum_perms">
{{range .Perms}}
<div class="formrow">
@ -16,7 +16,7 @@
</div>
</div>{{end}}
<div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton form_middle_button">{{lang "panel_forum_update_button"}}</button></div>
<div class="formitem"><button name="panel-button"class="formbutton form_middle_button">{{lang "panel_forum_update_button"}}</button></div>
</div>
</div>
</form>

View File

@ -1,7 +1,7 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_logs_mod_head"}}</h1></div>
</div>
<div id="panel_modlogs" class="colstack_item rowlist loglist">
<div id="panel_modlogs"class="colstack_item rowlist loglist">
{{range .Logs}}
<div class="rowitem panel_compactrow">
<span class="to_left">

View File

@ -1,15 +1,15 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_pages_edit_head"}}</h1></div>
</div>
<form action="/panel/pages/edit/submit/{{.Page.ID}}?s={{.CurrentUser.Session}}" method="post">
<div id="panel_page_edit_item" class="colstack_item the_form">
<form action="/panel/pages/edit/submit/{{.Page.ID}}?s={{.CurrentUser.Session}}"method="post">
<div id="panel_page_edit_item"class="colstack_item the_form">
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_pages_name"}}</a></div>
<div class="formitem"><input name="name" type="text" value="{{.Page.Name}}"></div>
<div class="formitem"><input name="name"type="text"value="{{.Page.Name}}"></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_pages_title"}}</a></div>
<div class="formitem"><input name="title" type="text" value="{{.Page.Title}}"></div>
<div class="formitem"><input name="title"type="text"value="{{.Page.Title}}"></div>
</div>
<div class="formrow">
<div class="formitem">
@ -17,7 +17,7 @@
</div>
</div>
<div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton">{{lang "panel_pages_edit_update_button"}}</button></div>
<div class="formitem"><button name="panel-button"class="formbutton">{{lang "panel_pages_edit_update_button"}}</button></div>
</div>
</div>
</form>

View File

@ -1,20 +1,20 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_plugins_head"}}</h1></div>
</div>
<div id="panel_plugins" class="colstack_item complex_rowlist">
<div id="panel_plugins"class="colstack_item complex_rowlist">
{{range .ItemList}}
<div class="rowitem editable_parent">
<span class="panel_plugin_meta">
<a {{if .URL}}href="{{.URL}}"{{end}}class="editable_block"class="panel_upshift">{{.Name}}</a><br>
<a{{if .URL}} href="{{.URL}}"{{end}}class="editable_block"class="panel_upshift">{{.Name}}</a><br>
<small style="margin-left:2px;">{{lang "panel_plugins_author_prefix"}}{{.Author}}</small>
</span>
<span class="to_right">
{{if .Settings}}<a href="/panel/settings/" class="panel_tag panel_plugin_settings panel_right_button">{{lang "panel_plugins_settings"}}</a>{{end}}
{{if .Active}}<a href="/panel/plugins/deactivate/{{.UName}}?s={{$.CurrentUser.Session}}" class="panel_tag panel_plugin_deactivate panel_right_button">{{lang "panel_plugins_deactivate"}}</a>
{{if .Settings}}<a href="/panel/settings/"class="panel_tag panel_plugin_settings panel_right_button">{{lang "panel_plugins_settings"}}</a>{{end}}
{{if .Active}}<a href="/panel/plugins/deactivate/{{.UName}}?s={{$.CurrentUser.Session}}"class="panel_tag panel_plugin_deactivate panel_right_button">{{lang "panel_plugins_deactivate"}}</a>
{{else if .Installable}}
{{/** TODO: Write a custom template interpreter to fix this nonsense **/}}
{{if .Installed}}<a href="/panel/plugins/activate/{{.UName}}?s={{$.CurrentUser.Session}}" class="panel_tag panel_plugin_activate panel_right_button">{{lang "panel_plugins_activate"}}</a>{{else}}<a href="/panel/plugins/install/{{.UName}}?s={{$.CurrentUser.Session}}" class="panel_tag panel_plugin_install panel_right_button">{{lang "panel_plugins_install"}}</a>{{end}}
{{else}}<a href="/panel/plugins/activate/{{.UName}}?s={{$.CurrentUser.Session}}" class="panel_tag panel_plugin_activate panel_right_button">{{lang "panel_plugins_activate"}}</a>{{end}}
{{if .Installed}}<a href="/panel/plugins/activate/{{.UName}}?s={{$.CurrentUser.Session}}"class="panel_tag panel_plugin_activate panel_right_button">{{lang "panel_plugins_activate"}}</a>{{else}}<a href="/panel/plugins/install/{{.UName}}?s={{$.CurrentUser.Session}}"class="panel_tag panel_plugin_install panel_right_button">{{lang "panel_plugins_install"}}</a>{{end}}
{{else}}<a href="/panel/plugins/activate/{{.UName}}?s={{$.CurrentUser.Session}}"class="panel_tag panel_plugin_activate panel_right_button">{{lang "panel_plugins_activate"}}</a>{{end}}
</span>
</div>
{{end}}

View File

@ -4,14 +4,14 @@
<h2 class="hguide">{{lang "panel_hints_reorder"}}</h2>
</div>
</div>
<div id="panel_menu_item_holder" class="colstack_item rowlist">
<div id="panel_menu_item_holder"class="colstack_item rowlist">
{{range .ItemList}}
<div class="panel_menu_item rowitem panel_compactrow editable_parent" data-miid="{{.ID}}">
<div class="panel_menu_item rowitem panel_compactrow editable_parent"data-miid="{{.ID}}">
<span class="grip"></span>
<a href="/panel/themes/menus/item/edit/{{.ID}}" class="editable_block panel_upshift">{{.Name}}</a>
<a href="/panel/themes/menus/item/edit/{{.ID}}"class="editable_block panel_upshift">{{.Name}}</a>
<span class="panel_buttons">
<a href="/panel/themes/menus/item/edit/{{.ID}}" class="panel_tag panel_right_button edit_button" aria-label="{{lang "panel_themes_menus_items_edit_button_aria"}}"></a>
<a href="/panel/themes/menus/item/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="panel_tag panel_right_button delete_button" aria-label="{{lang "panel_themes_menus_items_delete_button_aria"}}"></a>
<a href="/panel/themes/menus/item/edit/{{.ID}}"class="panel_tag panel_right_button edit_button"aria-label="{{lang "panel_themes_menus_items_edit_button_aria"}}"></a>
<a href="/panel/themes/menus/item/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}"class="panel_tag panel_right_button delete_button"aria-label="{{lang "panel_themes_menus_items_delete_button_aria"}}"></a>
</span>
</div>{{end}}
</div>
@ -21,9 +21,9 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_create_head"}}</h1></div>
</div>
<form action="/panel/themes/menus/item/create/submit/?s={{.CurrentUser.Session}}" method="post">
<input name="mid" value="{{.MenuID}}" type="hidden">
<div id="panel_themes_menu_item_create" class="colstack_item the_form">
<form action="/panel/themes/menus/item/create/submit/?s={{.CurrentUser.Session}}"method="post">
<input name="mid"value="{{.MenuID}}"type="hidden">
<div id="panel_themes_menu_item_create"class="colstack_item the_form">
{{/** TODO: Let an admin move a menu item from one menu to another? **/}}
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_name"}}</a></div>

View File

@ -58,7 +58,7 @@
{{if .CurrentUser.Loggedin}}
{{if .CurrentUser.Perms.BanUsers}}
<!-- TODO: Inline the display:none; CSS -->
<div id="ban_user_head"class="colstack_item colstack_head hash_hide ban_user_hash" style="display:none;">
<div id="ban_user_head"class="colstack_item colstack_head hash_hide ban_user_hash"style="display:none;">
<div class="rowitem"><h1><a>{{lang "profile.ban_user_head"}}</a></h1></div>
</div>
<form id="ban_user_form"class="hash_hide ban_user_hash"action="/users/ban/submit/{{.ProfileOwner.ID}}?s={{.CurrentUser.Session}}"method="post"style="display:none;">
@ -120,8 +120,8 @@
<div id="profile_comments_head"class="colstack_item colstack_head hash_hide">
<div class="rowitem"><h1><a>{{lang "profile.comments_head"}}</a></h1></div>
</div>
<div id="profile_comments"class="colstack_item hash_hide">{{template "profile_comments_row.html" . }}</div>
</div>{{if .ShowComments}}
<div id="profile_comments"class="colstack_item hash_hide">{{template "profile_comments_row.html" . }}</div>{{end}}
{{if .CurrentUser.Loggedin}}
{{if .CanComment}}