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 mysqlPre = "utf8mb4"
var mysqlCol = "utf8mb4_general_ci" var mysqlCol = "utf8mb4_general_ci"
var tables []string
type tblColumn = qgen.DBTableColumn type tblColumn = qgen.DBTableColumn
type tC = tblColumn type tC = tblColumn
type tblKey = qgen.DBTableKey type tblKey = qgen.DBTableKey
func createTables(a qgen.Adapter) error { func createTables(a qgen.Adapter) error {
tables = nil
f := func(table, charset, collation string, cols []tC, keys []tblKey) error { 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 qgen.Install.CreateTable(table, charset, collation, cols, keys)
} }
return createTables2(a, f) 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) 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, createTable("users", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"uid", "int", 0, false, true, ""}, tC{"uid", "int", 0, false, true, ""},
tC{"name", "varchar", 100, false, false, ""}, ccol("name", 100, ""),
tC{"password", "varchar", 100, false, false, ""}, 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{"group", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"active", "boolean", 0, false, false, "0"}, bcol("active", false),
tC{"is_super_admin", "boolean", 0, false, false, "0"}, bcol("is_super_admin", false),
tC{"createdAt", "createdAt", 0, false, false, ""}, createdAt(),
tC{"lastActiveAt", "datetime", 0, false, false, ""}, tC{"lastActiveAt", "datetime", 0, false, false, ""},
tC{"session", "varchar", 200, false, false, "''"}, ccol("session", 200, "''"),
//tC{"authToken", "varchar", 200, false, false, "''"}, //ccol("authToken", 200, "''"),
tC{"last_ip", "varchar", 200, false, false, "''"}, 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{"enable_embeds", "int", 0, false, false, "-1"},
tC{"email", "varchar", 200, false, false, "''"}, ccol("email", 200, "''"),
tC{"avatar", "varchar", 100, false, false, "''"}, ccol("avatar", 100, "''"),
tC{"message", "text", 0, false, false, "''"}, text("message"),
// TODO: Drop these columns? // TODO: Drop these columns?
tC{"url_prefix", "varchar", 20, false, false, "''"}, ccol("url_prefix", 20, "''"),
tC{"url_name", "varchar", 100, false, false, "''"}, ccol("url_name", 100, "''"),
//tC{"pub_key", "text", 0, false, false, "''"}, //text("pub_key"),
tC{"level", "smallint", 0, false, false, "0"}, tC{"level", "smallint", 0, false, false, "0"},
tC{"score", "int", 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{"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 tC{"temp_group", "int", 0, false, false, "0"}, // For temporary groups, set this to zero when a temporary group isn't in effect
}, },
[]tblKey{ []tK{
tblKey{"uid", "primary", "", false}, tK{"uid", "primary", "", false},
tblKey{"name", "unique", "", false}, tK{"name", "unique", "", false},
}, },
) )
createTable("users_groups", mysqlPre, mysqlCol, createTable("users_groups", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"gid", "int", 0, false, true, ""}, tC{"gid", "int", 0, false, true, ""},
tC{"name", "varchar", 100, false, false, ""}, ccol("name", 100, ""),
tC{"permissions", "text", 0, false, false, ""}, text("permissions"),
tC{"plugin_perms", "text", 0, false, false, ""}, text("plugin_perms"),
tC{"is_mod", "boolean", 0, false, false, "0"}, bcol("is_mod", false),
tC{"is_admin", "boolean", 0, false, false, "0"}, bcol("is_admin", false),
tC{"is_banned", "boolean", 0, false, false, "0"}, bcol("is_banned", false),
tC{"user_count", "int", 0, false, false, "0"}, // TODO: Implement this tC{"user_count", "int", 0, false, false, "0"}, // TODO: Implement this
tC{"tag", "varchar", 50, false, false, "''"}, ccol("tag", 50, "''"),
}, },
[]tblKey{ []tK{
tblKey{"gid", "primary", "", false}, 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{"pid", "int", 0, false, true, ""},
tC{"from_gid", "int", 0, false, false, ""}, tC{"from_gid", "int", 0, false, false, ""},
tC{"to_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 // Requirements
tC{"level", "int", 0, false, false, ""}, 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{"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 tC{"registeredFor", "int", 0, false, false, "0"}, // minutes
}, },
[]tblKey{ []tK{
tblKey{"pid", "primary", "", false}, 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, createTable("users_2fa_keys", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
tC{"secret", "varchar", 100, false, false, ""}, ccol("secret", 100, ""),
tC{"scratch1", "varchar", 50, false, false, ""}, ccol("scratch1", 50, ""),
tC{"scratch2", "varchar", 50, false, false, ""}, ccol("scratch2", 50, ""),
tC{"scratch3", "varchar", 50, false, false, ""}, ccol("scratch3", 50, ""),
tC{"scratch4", "varchar", 50, false, false, ""}, ccol("scratch4", 50, ""),
tC{"scratch5", "varchar", 50, false, false, ""}, ccol("scratch5", 50, ""),
tC{"scratch6", "varchar", 50, false, false, ""}, ccol("scratch6", 50, ""),
tC{"scratch7", "varchar", 50, false, false, ""}, ccol("scratch7", 50, ""),
tC{"scratch8", "varchar", 50, false, false, ""}, ccol("scratch8", 50, ""),
tC{"createdAt", "createdAt", 0, false, false, ""}, tC{"createdAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tblKey{
@ -148,12 +187,12 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"uid","int",0,false,false,""}, tC{"uid","int",0,false,false,""},
tC{"element_id","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. ccol("element_type",50,""), //forum, profile?, and social_group. Leave blank for global.
tC{"overrides","text",0,false,false,"{}"}, text("overrides","{}"),
tC{"mod_queue","boolean",0,false,false,"0"}, bcol("mod_queue",false),
tC{"shadow_ban","boolean",0,false,false,"0"}, bcol("shadow_ban",false),
tC{"no_avatar","boolean",0,false,false,"0"}, // Coming Soon. Should this be a perm override instead? 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? // 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"}, //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{"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_by","int",0,false,false,""},
tC{"issued_at","createdAt",0,false,false,""}, createdAt("issued_at"),
tC{"expires_at","datetime",0,false,false,""}, tC{"expires_at","datetime",0,false,false,""},
}, nil, }, 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{"set_group", "int", 0, false, false, ""},
tC{"issued_by", "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{"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 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? // TODO: Add an autoincrement key?
createTable("emails", "", "", createTable("emails", "", "",
[]tC{ []tC{
tC{"email", "varchar", 200, false, false, ""}, ccol("email", 200, ""),
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"validated", "boolean", 0, false, false, "0"}, bcol("validated", false),
tC{"token", "varchar", 200, false, false, "''"}, ccol("token", 200, "''"),
}, nil, }, nil,
) )
@ -208,11 +247,11 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
/* /*
createTable("email_domain_blacklist", "", "", createTable("email_domain_blacklist", "", "",
[]tC{ []tC{
tC{"domain", "varchar", 200, false, false, ""}, ccol("domain", 200, ""),
tC{"gtld", "boolean", 0, false, false, "0"}, bcol("gtld", false),
}, },
[]tblKey{ []tK{
tblKey{"domain", "primary"}, tK{"domain", "primary"},
}, },
) )
*/ */
@ -220,26 +259,26 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
// TODO: Implement password resets // TODO: Implement password resets
createTable("password_resets", "", "", createTable("password_resets", "", "",
[]tC{ []tC{
tC{"email", "varchar", 200, false, false, ""}, ccol("email", 200, ""),
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key 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 ccol("validated", 200, ""), // 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("token", 200, ""),
tC{"createdAt", "createdAt", 0, false, false, ""}, createdAt(),
}, nil, }, nil,
) )
createTable("forums", mysqlPre, mysqlCol, createTable("forums", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"fid", "int", 0, false, true, ""}, tC{"fid", "int", 0, false, true, ""},
tC{"name", "varchar", 100, false, false, ""}, ccol("name", 100, ""),
tC{"desc", "varchar", 200, false, false, ""}, ccol("desc", 200, ""),
tC{"tmpl", "varchar", 200, false, false, "''"}, ccol("tmpl", 200, "''"),
tC{"active", "boolean", 0, false, false, "1"}, bcol("active", true),
tC{"order", "int", 0, false, false, "0"}, tC{"order", "int", 0, false, false, "0"},
tC{"topicCount", "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{"parentID", "int", 0, false, false, "0"},
tC{"parentType", "varchar", 50, false, false, "''"}, ccol("parentType", 50, "''"),
tC{"lastTopicID", "int", 0, false, false, "0"}, tC{"lastTopicID", "int", 0, false, false, "0"},
tC{"lastReplyerID", "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{
tC{"fid", "int", 0, false, false, ""}, tC{"fid", "int", 0, false, false, ""},
tC{"gid", "int", 0, false, false, ""}, tC{"gid", "int", 0, false, false, ""},
tC{"preset", "varchar", 100, false, false, "''"}, ccol("preset", 100, "''"),
tC{"permissions", "text", 0, false, false, ""}, text("permissions", "{}"),
}, },
[]tblKey{ []tblKey{
// TODO: Test to see that the compound primary key works // 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, createTable("topics", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"tid", "int", 0, false, true, ""}, tC{"tid", "int", 0, false, true, ""},
tC{"title", "varchar", 100, false, false, ""}, // TODO: Increase the max length to 200? ccol("title", 100, ""), // TODO: Increase the max length to 200?
tC{"content", "text", 0, false, false, ""}, text("content"),
tC{"parsed_content", "text", 0, false, false, ""}, text("parsed_content"),
tC{"createdAt", "createdAt", 0, false, false, ""}, createdAt(),
tC{"lastReplyAt", "datetime", 0, false, false, ""}, tC{"lastReplyAt", "datetime", 0, false, false, ""},
tC{"lastReplyBy", "int", 0, false, false, ""}, tC{"lastReplyBy", "int", 0, false, false, ""},
tC{"lastReplyID", "int", 0, false, false, "0"}, tC{"lastReplyID", "int", 0, false, false, "0"},
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"is_closed", "boolean", 0, false, false, "0"}, bcol("is_closed", false),
tC{"sticky", "boolean", 0, false, false, "0"}, bcol("sticky", false),
// TODO: Add an index for this // TODO: Add an index for this
tC{"parentID", "int", 0, false, false, "2"}, tC{"parentID", "int", 0, false, false, "2"},
tC{"ip", "varchar", 200, false, false, "''"}, ccol("ip", 200, "''"),
tC{"postCount", "int", 0, false, false, "1"}, tC{"postCount", "int", 0, false, false, "1"},
tC{"likeCount", "int", 0, false, false, "0"}, tC{"likeCount", "int", 0, false, false, "0"},
tC{"attachCount", "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? // ? - A little hacky, maybe we could do something less likely to bite us with huge numbers of topics?
// TODO: Add an index for this? // TODO: Add an index for this?
//tC{"lastMonth", "datetime", 0, false, false, ""}, //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{"poll", "int", 0, false, false, "0"},
tC{"data", "varchar", 200, false, false, "''"}, ccol("data", 200, "''"),
}, },
[]tblKey{ []tK{
tblKey{"tid", "primary", "", false}, tK{"tid", "primary", "", false},
tblKey{"title", "fulltext", "", false}, tK{"title", "fulltext", "", false},
tblKey{"content", "fulltext", "", false}, tK{"content", "fulltext", "", false},
}, },
) )
@ -303,23 +342,23 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"rid", "int", 0, false, true, ""}, // TODO: Rename to replyID? tC{"rid", "int", 0, false, true, ""}, // TODO: Rename to replyID?
tC{"tid", "int", 0, false, false, ""}, // TODO: Rename to topicID? tC{"tid", "int", 0, false, false, ""}, // TODO: Rename to topicID?
tC{"content", "text", 0, false, false, ""}, text("content"),
tC{"parsed_content", "text", 0, false, false, ""}, text("parsed_content"),
tC{"createdAt", "createdAt", 0, false, false, ""}, createdAt(),
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"lastEdit", "int", 0, false, false, "0"}, tC{"lastEdit", "int", 0, false, false, "0"},
tC{"lastEditBy", "int", 0, false, false, "0"}, tC{"lastEditBy", "int", 0, false, false, "0"},
tC{"lastUpdated", "datetime", 0, false, false, ""}, tC{"lastUpdated", "datetime", 0, false, false, ""},
tC{"ip", "varchar", 200, false, false, "''"}, ccol("ip", 200, "''"),
tC{"likeCount", "int", 0, false, false, "0"}, tC{"likeCount", "int", 0, false, false, "0"},
tC{"attachCount", "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{"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"}, tC{"poll", "int", 0, false, false, "0"},
}, },
[]tblKey{ []tK{
tblKey{"rid", "primary", "", false}, tK{"rid", "primary", "", false},
tblKey{"content", "fulltext", "", false}, tK{"content", "fulltext", "", false},
}, },
) )
@ -327,12 +366,12 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"attachID", "int", 0, false, true, ""}, tC{"attachID", "int", 0, false, true, ""},
tC{"sectionID", "int", 0, false, false, "0"}, 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{"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{"uploadedBy", "int", 0, false, false, ""}, // TODO; Make this a foreign key
tC{"path", "varchar", 200, false, false, ""}, ccol("path", 200, ""),
tC{"extra", "varchar", 200, false, false, ""}, ccol("extra", 200, ""),
}, },
[]tblKey{ []tblKey{
tblKey{"attachID", "primary", "", false}, tblKey{"attachID", "primary", "", false},
@ -342,10 +381,10 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("revisions", mysqlPre, mysqlCol, createTable("revisions", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"reviseID", "int", 0, false, true, ""}, tC{"reviseID", "int", 0, false, true, ""},
tC{"content", "text", 0, false, false, ""}, text("content"),
tC{"contentID", "int", 0, false, false, ""}, tC{"contentID", "int", 0, false, false, ""},
tC{"contentType", "varchar", 100, false, false, "replies"}, ccol("contentType", 100, "replies"),
tC{"createdAt", "createdAt", 0, false, false, ""}, createdAt(),
// TODO: Add a createdBy column? // TODO: Add a createdBy column?
}, },
[]tblKey{ []tblKey{
@ -357,7 +396,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"pollID", "int", 0, false, true, ""}, tC{"pollID", "int", 0, false, true, ""},
tC{"parentID", "int", 0, false, false, "0"}, 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{"type", "int", 0, false, false, "0"},
tC{"options", "json", 0, false, false, ""}, tC{"options", "json", 0, false, false, ""},
tC{"votes", "int", 0, false, false, "0"}, 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{"pollID", "int", 0, false, false, ""},
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"option", "int", 0, false, false, "0"}, tC{"option", "int", 0, false, false, "0"},
tC{"castAt", "createdAt", 0, false, false, ""}, createdAt("castAt"),
tC{"ip", "varchar", 200, false, false, "''"}, ccol("ip", 200, "''"),
}, nil, }, nil,
) )
@ -389,13 +428,13 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"rid", "int", 0, false, true, ""}, tC{"rid", "int", 0, false, true, ""},
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"content", "text", 0, false, false, ""}, text("content"),
tC{"parsed_content", "text", 0, false, false, ""}, text("parsed_content"),
tC{"createdAt", "createdAt", 0, false, false, ""}, createdAt(),
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"lastEdit", "int", 0, false, false, "0"}, tC{"lastEdit", "int", 0, false, false, "0"},
tC{"lastEditBy", "int", 0, false, false, "0"}, tC{"lastEditBy", "int", 0, false, false, "0"},
tC{"ip", "varchar", 200, false, false, "''"}, ccol("ip", 200, "''"),
}, },
[]tblKey{ []tblKey{
tblKey{"rid", "primary", "", false}, tblKey{"rid", "primary", "", false},
@ -406,9 +445,9 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"weight", "tinyint", 0, false, false, "1"}, tC{"weight", "tinyint", 0, false, false, "1"},
tC{"targetItem", "int", 0, false, false, ""}, 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{"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"}, tC{"recalc", "tinyint", 0, false, false, "0"},
}, nil, }, nil,
) )
@ -418,7 +457,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"cid", "int", 0, false, true, ""}, tC{"cid", "int", 0, false, true, ""},
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key 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{"lastReplyAt", "datetime", 0, false, false, ""},
tC{"lastReplyBy", "int", 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{"pid", "int", 0, false, true, ""},
tC{"cid", "int", 0, false, false, ""}, tC{"cid", "int", 0, false, false, ""},
tC{"createdBy", "int", 0, false, false, ""}, tC{"createdBy", "int", 0, false, false, ""},
tC{"body", "varchar", 50, false, false, ""}, ccol("body", 50, ""),
tC{"post", "varchar", 50, false, false, "''"}, ccol("post", 50, "''"),
}, },
[]tblKey{ []tblKey{
tblKey{"pid", "primary", "", false}, tblKey{"pid", "primary", "", false},
@ -482,35 +521,39 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("activity_stream", "", "", createTable("activity_stream", "", "",
[]tC{ []tC{
tC{"asid", "int", 0, false, true, ""}, tC{"asid", "int", 0, false, true, ""},
tC{"actor", "int", 0, false, false, ""}, /* the one doing the act */ // TODO: Make this a foreign key 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{"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 */ 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 */
tC{"elementType", "varchar", 50, false, false, ""}, /* topic, post (calling it post here to differentiate it from the 'reply' event), forum, user */ ccol("elementType", 50, ""), /* 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, ""}, // replacement for elementType
tC{"extra", "varchar", 200, false, false, "''"}, 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{ []tK{
tblKey{"asid", "primary", "", false}, tK{"asid", "primary", "", false},
}, },
) )
createTable("activity_subscriptions", "", "", createTable("activity_subscriptions", "", "",
[]tC{ []tC{
tC{"user", "int", 0, false, false, ""}, // TODO: Make this a foreign key 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{"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 */ 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*/ tC{"level", "int", 0, false, false, "0"}, /* 0: Mentions (aka the global default for any post), 1: Replies To You, 2: All Replies*/
}, nil, }, 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 */ /* 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", "", "", createTable("settings", "", "",
[]tC{ []tC{
tC{"name", "varchar", 180, false, false, ""}, ccol("name", 180, ""),
tC{"content", "varchar", 250, false, false, ""}, ccol("content", 250, ""),
tC{"type", "varchar", 50, false, false, ""}, ccol("type", 50, ""),
tC{"constraints", "varchar", 200, false, false, "''"}, ccol("constraints", 200, "''"),
}, },
[]tblKey{ []tblKey{
tblKey{"name", "unique", "", false}, tblKey{"name", "unique", "", false},
@ -520,8 +563,8 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("word_filters", "", "", createTable("word_filters", "", "",
[]tC{ []tC{
tC{"wfid", "int", 0, false, true, ""}, tC{"wfid", "int", 0, false, true, ""},
tC{"find", "varchar", 200, false, false, ""}, ccol("find", 200, ""),
tC{"replacement", "varchar", 200, false, false, ""}, ccol("replacement", 200, ""),
}, },
[]tblKey{ []tblKey{
tblKey{"wfid", "primary", "", false}, tblKey{"wfid", "primary", "", false},
@ -530,9 +573,9 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("plugins", "", "", createTable("plugins", "", "",
[]tC{ []tC{
tC{"uname", "varchar", 180, false, false, ""}, ccol("uname", 180, ""),
tC{"active", "boolean", 0, false, false, "0"}, bcol("active", false),
tC{"installed", "boolean", 0, false, false, "0"}, bcol("installed", false),
}, },
[]tblKey{ []tblKey{
tblKey{"uname", "unique", "", false}, tblKey{"uname", "unique", "", false},
@ -541,9 +584,9 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("themes", "", "", createTable("themes", "", "",
[]tC{ []tC{
tC{"uname", "varchar", 180, false, false, ""}, ccol("uname", 180, ""),
tC{"default", "boolean", 0, false, false, "0"}, bcol("default", false),
//tC{"profileUserVars", "text", 0, false, false, "''"}, //text("profileUserVars"),
}, },
[]tblKey{ []tblKey{
tblKey{"uname", "unique", "", false}, tblKey{"uname", "unique", "", false},
@ -554,11 +597,11 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"wid", "int", 0, false, true, ""}, tC{"wid", "int", 0, false, true, ""},
tC{"position", "int", 0, false, false, ""}, tC{"position", "int", 0, false, false, ""},
tC{"side", "varchar", 100, false, false, ""}, ccol("side", 100, ""),
tC{"type", "varchar", 100, false, false, ""}, ccol("type", 100, ""),
tC{"active", "boolean", 0, false, false, "0"}, bcol("active", false),
tC{"location", "varchar", 100, false, false, ""}, ccol("location", 100, ""),
tC{"data", "text", 0, false, false, "''"}, text("data"),
}, },
[]tblKey{ []tblKey{
tblKey{"wid", "primary", "", false}, tblKey{"wid", "primary", "", false},
@ -578,51 +621,51 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"miid", "int", 0, false, true, ""}, tC{"miid", "int", 0, false, true, ""},
tC{"mid", "int", 0, false, false, ""}, tC{"mid", "int", 0, false, false, ""},
tC{"name", "varchar", 200, false, false, "''"}, ccol("name", 200, "''"),
tC{"htmlID", "varchar", 200, false, false, "''"}, ccol("htmlID", 200, "''"),
tC{"cssClass", "varchar", 200, false, false, "''"}, ccol("cssClass", 200, "''"),
tC{"position", "varchar", 100, false, false, ""}, ccol("position", 100, ""),
tC{"path", "varchar", 200, false, false, "''"}, ccol("path", 200, "''"),
tC{"aria", "varchar", 200, false, false, "''"}, ccol("aria", 200, "''"),
tC{"tooltip", "varchar", 200, false, false, "''"}, ccol("tooltip", 200, "''"),
tC{"tmplName", "varchar", 200, false, false, "''"}, ccol("tmplName", 200, "''"),
tC{"order", "int", 0, false, false, "0"}, tC{"order", "int", 0, false, false, "0"},
tC{"guestOnly", "boolean", 0, false, false, "0"}, bcol("guestOnly", false),
tC{"memberOnly", "boolean", 0, false, false, "0"}, bcol("memberOnly", false),
tC{"staffOnly", "boolean", 0, false, false, "0"}, bcol("staffOnly", false),
tC{"adminOnly", "boolean", 0, false, false, "0"}, bcol("adminOnly", false),
}, },
[]tblKey{ []tK{
tblKey{"miid", "primary", "", false}, tK{"miid", "primary", "", false},
}, },
) )
createTable("pages", mysqlPre, mysqlCol, createTable("pages", mysqlPre, mysqlCol,
[]tC{ []tC{
tC{"pid", "int", 0, false, true, ""}, tC{"pid", "int", 0, false, true, ""},
//tC{"path", "varchar", 200, false, false, ""}, //ccol("path", 200, ""),
tC{"name", "varchar", 200, false, false, ""}, ccol("name", 200, ""),
tC{"title", "varchar", 200, false, false, ""}, ccol("title", 200, ""),
tC{"body", "text", 0, false, false, ""}, text("body"),
// TODO: Make this a table? // TODO: Make this a table?
tC{"allowedGroups", "text", 0, false, false, ""}, text("allowedGroups"),
tC{"menuID", "int", 0, false, false, "-1"}, // simple sidebar menu tC{"menuID", "int", 0, false, false, "-1"}, // simple sidebar menu
}, },
[]tblKey{ []tK{
tblKey{"pid", "primary", "", false}, tK{"pid", "primary", "", false},
}, },
) )
createTable("registration_logs", "", "", createTable("registration_logs", "", "",
[]tC{ []tC{
tC{"rlid", "int", 0, false, true, ""}, tC{"rlid", "int", 0, false, true, ""},
tC{"username", "varchar", 100, false, false, ""}, ccol("username", 100, ""),
tC{"email", "varchar", 100, false, false, ""}, tC{"email", "varchar", 100, false, false, ""},
tC{"failureReason", "varchar", 100, false, false, ""}, ccol("failureReason", 100, ""),
tC{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed? bcol("success", false), // Did this attempt succeed?
tC{"ipaddress", "varchar", 200, false, false, ""}, ccol("ipaddress", 200, ""),
tC{"doneAt", "createdAt", 0, false, false, ""}, createdAt("doneAt"),
}, },
[]tblKey{ []tblKey{
tblKey{"rlid", "primary", "", false}, tblKey{"rlid", "primary", "", false},
@ -633,9 +676,9 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"lid", "int", 0, false, true, ""}, tC{"lid", "int", 0, false, true, ""},
tC{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
tC{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed? bcol("success", false), // Did this attempt succeed?
tC{"ipaddress", "varchar", 200, false, false, ""}, ccol("ipaddress", 200, ""),
tC{"doneAt", "createdAt", 0, false, false, ""}, createdAt("doneAt"),
}, },
[]tblKey{ []tblKey{
tblKey{"lid", "primary", "", false}, tblKey{"lid", "primary", "", false},
@ -644,25 +687,25 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("moderation_logs", "", "", createTable("moderation_logs", "", "",
[]tC{ []tC{
tC{"action", "varchar", 100, false, false, ""}, ccol("action", 100, ""),
tC{"elementID", "int", 0, false, false, ""}, tC{"elementID", "int", 0, false, false, ""},
tC{"elementType", "varchar", 100, false, false, ""}, ccol("elementType", 100, ""),
tC{"ipaddress", "varchar", 200, false, false, ""}, ccol("ipaddress", 200, ""),
tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"doneAt", "datetime", 0, false, false, ""}, tC{"doneAt", "datetime", 0, false, false, ""},
tC{"extra", "text", 0, false, false, ""}, text("extra"),
}, nil, }, nil,
) )
createTable("administration_logs", "", "", createTable("administration_logs", "", "",
[]tC{ []tC{
tC{"action", "varchar", 100, false, false, ""}, ccol("action", 100, ""),
tC{"elementID", "int", 0, false, false, ""}, tC{"elementID", "int", 0, false, false, ""},
tC{"elementType", "varchar", 100, false, false, ""}, ccol("elementType", 100, ""),
tC{"ipaddress", "varchar", 200, false, false, ""}, ccol("ipaddress", 200, ""),
tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"doneAt", "datetime", 0, false, false, ""}, tC{"doneAt", "datetime", 0, false, false, ""},
tC{"extra", "text", 0, false, false, ""}, text("extra"),
}, nil, }, 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{"count", "int", 0, false, false, "0"},
tC{"avg", "int", 0, false, false, "0"}, tC{"avg", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, 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, }, nil,
) )
@ -679,8 +722,8 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
tC{"browser", "varchar", 200, false, false, ""}, // googlebot, firefox, opera, etc. ccol("browser", 200, ""), // googlebot, firefox, opera, etc.
//tC{"version","varchar",0,false,false,""}, // the version of the browser or bot //ccol("version",0,""), // the version of the browser or bot
}, nil, }, nil,
) )
@ -688,7 +731,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
tC{"system", "varchar", 200, false, false, ""}, // windows, android, unknown, etc. ccol("system", 200, ""), // windows, android, unknown, etc.
}, nil, }, nil,
) )
@ -696,7 +739,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
tC{"lang", "varchar", 200, false, false, ""}, // en, ru, etc. ccol("lang", 200, ""), // en, ru, etc.
}, nil, }, nil,
) )
@ -704,7 +747,7 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
[]tC{ []tC{
tC{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tC{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
tC{"domain", "varchar", 200, false, false, ""}, ccol("domain", 200, ""),
}, nil, }, nil,
) )
@ -764,18 +807,19 @@ func createTables2(a qgen.Adapter, f func(table, charset, collation string, colu
createTable("meta", "", "", createTable("meta", "", "",
[]tC{ []tC{
tC{"name", "varchar", 200, false, false, ""}, ccol("name", 200, ""),
tC{"value", "varchar", 200, false, false, ""}, ccol("value", 200, ""),
}, nil, }, nil,
) )
/*createTable("tables", mysqlPre, mysqlCol, /*createTable("tables", "", "",
[]tC{ []tC{
tC{"id", "int", 0, false, true, ""}, tC{"id", "int", 0, false, true, ""},
tC{"name", "varchar", 200, false, false, ""}, ccol("name", 200, ""),
}, },
[]tblKey{ []tK{
tblKey{"id", "primary", "", false}, 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. // 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 // 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 // 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 var realPassword, salt string
err = auth.login.QueryRow(name).Scan(&uid, &realPassword, &salt) err = auth.login.QueryRow(name).Scan(&uid, &realPassword, &salt)
if err == ErrNoRows { if err == ErrNoRows {
@ -211,7 +211,7 @@ func (auth *DefaultAuth) SetCookies(w http.ResponseWriter, uid int, session stri
// TODO: Set the cookie domain // 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) // 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)} cookie := http.Cookie{Name: "uid", Value: strconv.Itoa(uid), Path: "/", MaxAge: int(Year)}
setCookie(w, &cookie, "lax") setCookie(w, &cookie, "lax")
cookie = http.Cookie{Name: "provSession", Value: provSession, Path: "/", MaxAge: int(Year)} 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 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) provSession, err = GenerateSafeString(SessionLength)
if err != nil { if err != nil {
return "", "", err return "", "", err

View File

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

View File

@ -95,14 +95,14 @@ var Template_account_handle = genIntTmpl("account")
func tmplInitUsers() (*User, *User, *User) { func tmplInitUsers() (*User, *User, *User) {
avatar, microAvatar := BuildAvatar(62, "") 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? // TODO: Do a more accurate level calculation for this?
avatar, microAvatar = BuildAvatar(1, "") 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, "") 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 return &u, &u2, &u3
} }
@ -306,7 +306,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
return err 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) t.Add("profile", "c.ProfilePage", ppage)
var topicsList []*TopicsRow 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("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."}) 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"} ipSearchPage := IPSearchPage{htitle("IP Search"), map[int]*User{1: user2}, "::1"}

View File

@ -62,6 +62,12 @@ type User struct {
TempGroup int TempGroup int
ParseSettings *ParseSettings 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 { func (u *User) WebSockets() *WsJSONUser {
@ -191,7 +197,7 @@ func init() {
decLiked: acc.Update(u).Set("liked=liked-?").Where(w).Prepare(), decLiked: acc.Update(u).Set("liked=liked-?").Where(w).Prepare(),
//recalcLastLiked: acc... //recalcLastLiked: acc...
updateLastIP: acc.SimpleUpdate(u, "last_ip=?", w), 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(), setPassword: acc.Update(u).Set("password=?,salt=?").Where(w).Prepare(),
@ -211,6 +217,10 @@ func init() {
} }
func (u *User) 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.Avatar, u.MicroAvatar = BuildAvatar(u.ID, u.RawAvatar)
u.Link = BuildProfileURL(NameToSlug(u.Name), u.ID) u.Link = BuildProfileURL(NameToSlug(u.Name), u.ID)
u.Tag = Groups.DirtyGet(u.Group).Tag u.Tag = Groups.DirtyGet(u.Group).Tag
@ -554,12 +564,27 @@ func (u *User) UpdateIP(ip string) error {
return err return err
} }
func (u *User) UpdatePrivacy(enableEmbeds int) error { //var ErrMalformedInteger = errors.New("malformed integer")
_, err := userStmts.updatePrivacy.Exec(enableEmbeds, u.ID) 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 { if uc := Users.GetCache(); uc != nil {
uc.Remove(u.ID) uc.Remove(u.ID)
} }
return err return e
} }
func (u *User) Update(name, email string, group int) (err error) { 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) 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() cache = NewNullUserCache()
} }
u := "users" 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? // TODO: Add an admin version of registerStmt with more flexibility?
return &DefaultUserStore{ return &DefaultUserStore{
cache: cache, 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(), getByName: acc.Select(u).Columns(allCols).Where("name=?").Prepare(),
getOffset: acc.Select(u).Columns(allCols).Orderby("uid ASC").Limit("?,?").Prepare(), getOffset: acc.Select(u).Columns(allCols).Orderby("uid ASC").Limit("?,?").Prepare(),
getAll: acc.Select(u).Columns(allCols).Prepare(), getAll: acc.Select(u).Columns(allCols).Prepare(),
exists: acc.Exists(u, "uid").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(), nameExists: acc.Exists(u, "name").Prepare(),
count: acc.Count(u).Prepare(), count: acc.Count(u).Prepare(),
}, acc.FirstError() }, acc.FirstError()
@ -93,16 +93,7 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
u = &User{ID: id, Loggedin: true} u = &User{ID: id, Loggedin: true}
var embeds int 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, err
}
if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr()
u.ParseSettings.NoEmbed = embeds == 0
}
u.Init()
s.cache.Set(u)*/
if err == nil { if err == nil {
if embeds != -1 { if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr() u.ParseSettings = DefaultParseSettings.CopyPtr()
@ -122,7 +113,7 @@ func (s *DefaultUserStore) Getn(id int) *User {
u = &User{ID: id, Loggedin: true} u = &User{ID: id, Loggedin: true}
var embeds int 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 err != nil {
return nil return nil
} }
@ -140,7 +131,7 @@ func (s *DefaultUserStore) Getn(id int) *User {
func (s *DefaultUserStore) GetByName(name string) (*User, error) { func (s *DefaultUserStore) GetByName(name string) (*User, error) {
u := &User{Loggedin: true} u := &User{Loggedin: true}
var embeds int 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 { if err != nil {
return nil, err return nil, err
} }
@ -165,7 +156,7 @@ func (s *DefaultUserStore) GetOffset(offset, perPage int) (users []*User, err er
var embeds int var embeds int
for rows.Next() { for rows.Next() {
u := &User{Loggedin: true} 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 { if err != nil {
return nil, err return nil, err
} }
@ -180,24 +171,24 @@ func (s *DefaultUserStore) GetOffset(offset, perPage int) (users []*User, err er
return users, rows.Err() return users, rows.Err()
} }
func (s *DefaultUserStore) Each(f func(*User) error) error { func (s *DefaultUserStore) Each(f func(*User) error) error {
rows, err := s.getAll.Query() rows, e := s.getAll.Query()
if err != nil { if e != nil {
return err return e
} }
defer rows.Close() defer rows.Close()
var embeds int var embeds int
for rows.Next() { for rows.Next() {
u := new(User) 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 { 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 err return e
} }
if embeds != -1 { if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr() u.ParseSettings = DefaultParseSettings.CopyPtr()
u.ParseSettings.NoEmbed = embeds == 0 u.ParseSettings.NoEmbed = embeds == 0
} }
u.Init() u.Init()
if err := f(u); err != nil { if e := f(u); e != nil {
return err return e
} }
} }
return rows.Err() 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] 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 { if err != nil {
return list, err return list, err
} }
@ -255,7 +246,7 @@ func (s *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err error)
var embeds int var embeds int
for rows.Next() { for rows.Next() {
u := &User{Loggedin: true} 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 { if err != nil {
return list, err 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) { func (s *DefaultUserStore) BypassGet(id int) (*User, error) {
u := &User{ID: id, Loggedin: true} u := &User{ID: id, Loggedin: true}
var embeds int 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 err == nil {
if embeds != -1 { if embeds != -1 {
u.ParseSettings = DefaultParseSettings.CopyPtr() u.ParseSettings = DefaultParseSettings.CopyPtr()

View File

@ -577,7 +577,11 @@
"account_password_update_button":"Update", "account_password_update_button":"Update",
"account_privacy_head":"Privacy", "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_privacy_button":"Update",
"account_mfa_head":"Manage 2FA", "account_mfa_head":"Manage 2FA",

View File

@ -51,18 +51,21 @@ func init() {
addPatch(31, patch31) addPatch(31, patch31)
addPatch(32, patch32) addPatch(32, patch32)
addPatch(33, patch33) 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) { 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", "", "", err = createTable("menus", "", "",
[]tC{ []tC{
tC{"mid", "int", 0, false, true, ""}, tC{"mid", "int", 0, false, true, ""},
@ -79,20 +82,20 @@ func patch0(scanner *bufio.Scanner) (err error) {
[]tC{ []tC{
tC{"miid", "int", 0, false, true, ""}, tC{"miid", "int", 0, false, true, ""},
tC{"mid", "int", 0, false, false, ""}, tC{"mid", "int", 0, false, false, ""},
tC{"name", "varchar", 200, false, false, ""}, ccol("name", 200, ""),
tC{"htmlID", "varchar", 200, false, false, "''"}, ccol("htmlID", 200, "''"),
tC{"cssClass", "varchar", 200, false, false, "''"}, ccol("cssClass", 200, "''"),
tC{"position", "varchar", 100, false, false, ""}, ccol("position", 100, ""),
tC{"path", "varchar", 200, false, false, "''"}, ccol("path", 200, "''"),
tC{"aria", "varchar", 200, false, false, "''"}, ccol("aria", 200, "''"),
tC{"tooltip", "varchar", 200, false, false, "''"}, ccol("tooltip", 200, "''"),
tC{"tmplName", "varchar", 200, false, false, "''"}, ccol("tmplName", 200, "''"),
tC{"order", "int", 0, false, false, "0"}, tC{"order", "int", 0, false, false, "0"},
tC{"guestOnly", "boolean", 0, false, false, "0"}, bcol("guestOnly", false),
tC{"memberOnly", "boolean", 0, false, false, "0"}, bcol("memberOnly", false),
tC{"staffOnly", "boolean", 0, false, false, "0"}, bcol("staffOnly", false),
tC{"adminOnly", "boolean", 0, false, false, "0"}, bcol("adminOnly", false),
}, },
[]tK{ []tK{
tK{"miid", "primary", "", false}, tK{"miid", "primary", "", false},
@ -194,11 +197,11 @@ func patch3(scanner *bufio.Scanner) error {
return createTable("registration_logs", "", "", return createTable("registration_logs", "", "",
[]tC{ []tC{
tC{"rlid", "int", 0, false, true, ""}, tC{"rlid", "int", 0, false, true, ""},
tC{"username", "varchar", 100, false, false, ""}, ccol("username", 100, ""),
tC{"email", "varchar", 100, false, false, ""}, ccol("email", 100, ""),
tC{"failureReason", "varchar", 100, false, false, ""}, ccol("failureReason", 100, ""),
tC{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed? bcol("success", false), // Did this attempt succeed?
tC{"ipaddress", "varchar", 200, false, false, ""}, ccol("ipaddress", 200, ""),
tC{"doneAt", "createdAt", 0, false, false, ""}, tC{"doneAt", "createdAt", 0, false, false, ""},
}, },
[]tK{ []tK{
@ -258,8 +261,8 @@ func patch4(scanner *bufio.Scanner) error {
err = createTable("pages", "utf8mb4", "utf8mb4_general_ci", err = createTable("pages", "utf8mb4", "utf8mb4_general_ci",
[]tC{ []tC{
tC{"pid", "int", 0, false, true, ""}, tC{"pid", "int", 0, false, true, ""},
tC{"name", "varchar", 200, false, false, ""}, ccol("name", 200, ""),
tC{"title", "varchar", 200, false, false, ""}, ccol("title", 200, ""),
tC{"body", "text", 0, false, false, ""}, tC{"body", "text", 0, false, false, ""},
tC{"allowedGroups", "text", 0, false, false, ""}, tC{"allowedGroups", "text", 0, false, false, ""},
tC{"menuID", "int", 0, false, false, "-1"}, tC{"menuID", "int", 0, false, false, "-1"},
@ -293,29 +296,24 @@ func patch5(scanner *bufio.Scanner) error {
return err return err
} }
err = createTable("users_2fa_keys", "utf8mb4", "utf8mb4_general_ci", return createTable("users_2fa_keys", "utf8mb4", "utf8mb4_general_ci",
[]tC{ []tC{
tC{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
tC{"secret", "varchar", 100, false, false, ""}, ccol("secret", 100, ""),
tC{"scratch1", "varchar", 50, false, false, ""}, ccol("scratch1", 50, ""),
tC{"scratch2", "varchar", 50, false, false, ""}, ccol("scratch2", 50, ""),
tC{"scratch3", "varchar", 50, false, false, ""}, ccol("scratch3", 50, ""),
tC{"scratch4", "varchar", 50, false, false, ""}, ccol("scratch4", 50, ""),
tC{"scratch5", "varchar", 50, false, false, ""}, ccol("scratch5", 50, ""),
tC{"scratch6", "varchar", 50, false, false, ""}, ccol("scratch6", 50, ""),
tC{"scratch7", "varchar", 50, false, false, ""}, ccol("scratch7", 50, ""),
tC{"scratch8", "varchar", 50, false, false, ""}, ccol("scratch8", 50, ""),
tC{"createdAt", "createdAt", 0, false, false, ""}, tC{"createdAt", "createdAt", 0, false, false, ""},
}, },
[]tK{ []tK{
tK{"uid", "primary", "", false}, tK{"uid", "primary", "", false},
}, },
) )
if err != nil {
return err
}
return nil
} }
func patch6(scanner *bufio.Scanner) error { func patch6(scanner *bufio.Scanner) error {
@ -382,10 +380,6 @@ func patch8(scanner *bufio.Scanner) error {
return err return err
} }
err = execStmt(qgen.Builder.DropTable("updates"))
if err != nil {
return err
}
return createTable("updates", "", "", return createTable("updates", "", "",
[]tC{ []tC{
tC{"dbVersion", "int", 0, false, false, "0"}, tC{"dbVersion", "int", 0, false, false, "0"},
@ -404,8 +398,8 @@ func patch9(scanner *bufio.Scanner) error {
[]tC{ []tC{
tC{"lid", "int", 0, false, true, ""}, tC{"lid", "int", 0, false, true, ""},
tC{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
tC{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed? bcol("success", false), // Did this attempt succeed?
tC{"ipaddress", "varchar", 200, false, false, ""}, ccol("ipaddress", 200, ""),
tC{"doneAt", "createdAt", 0, false, false, ""}, tC{"doneAt", "createdAt", 0, false, false, ""},
}, },
[]tK{ []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 // 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 { if err != nil {
return err return err
} }
@ -491,39 +485,26 @@ func patch11(scanner *bufio.Scanner) error {
} }
func patch12(scanner *bufio.Scanner) error { func patch12(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddIndex("topics", "parentID", "parentID")) var e error
if err != nil { addIndex := func(tbl, iname, colname string) {
return err 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")) addIndex("topics", "parentID", "parentID")
if err != nil { addIndex("replies", "tid", "tid")
return err addIndex("polls", "parentID", "parentID")
} addIndex("likes", "targetItem", "targetItem")
err = execStmt(qgen.Builder.AddIndex("polls", "parentID", "parentID")) addIndex("emails", "uid", "uid")
if err != nil { addIndex("attachments", "originID", "originID")
return err addIndex("attachments", "path", "path")
} addIndex("activity_stream_matches", "watcher", "watcher")
err = execStmt(qgen.Builder.AddIndex("likes", "targetItem", "targetItem")) return e
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
} }
func patch13(scanner *bufio.Scanner) error { func patch13(scanner *bufio.Scanner) error {
@ -554,17 +535,17 @@ func patch15(scanner *bufio.Scanner) error {
func patch16(scanner *bufio.Scanner) error { func patch16(scanner *bufio.Scanner) error {
return createTable("password_resets", "", "", return createTable("password_resets", "", "",
[]tC{ []tC{
tC{"email", "varchar", 200, false, false, ""}, ccol("email", 200, ""),
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key 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 ccol("validated", 200, ""), // 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("token", 200, ""),
tC{"createdAt", "createdAt", 0, false, false, ""}, tC{"createdAt", "createdAt", 0, false, false, ""},
}, nil, }, nil,
) )
} }
func patch17(scanner *bufio.Scanner) error { 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 { if err != nil {
return err return err
} }
@ -575,7 +556,7 @@ func patch17(scanner *bufio.Scanner) error {
if err != nil { if err != nil {
return err 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 return err
}) })
if err != nil { if err != nil {
@ -592,7 +573,7 @@ func patch17(scanner *bufio.Scanner) error {
if err != nil { if err != nil {
return err 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 return err
}) })
} }
@ -613,16 +594,15 @@ func patch19(scanner *bufio.Scanner) error {
func patch20(scanner *bufio.Scanner) error { func patch20(scanner *bufio.Scanner) error {
err := acc().Select("activity_stream_matches").Cols("asid").Each(func(rows *sql.Rows) error { err := acc().Select("activity_stream_matches").Cols("asid").Each(func(rows *sql.Rows) error {
var asid int var asid int
err := rows.Scan(&asid) if e := rows.Scan(&asid); e != nil {
if err != nil { return e
return err
} }
err = acc().Select("activity_stream").Cols("asid").Where("asid = ?").QueryRow(asid).Scan(&asid) e := acc().Select("activity_stream").Cols("asid").Where("asid=?").QueryRow(asid).Scan(&asid)
if err != sql.ErrNoRows { if e != sql.ErrNoRows {
return err return e
} }
_, err = acc().Delete("activity_stream_matches").Where("asid = ?").Run(asid) _, e = acc().Delete("activity_stream_matches").Where("asid=?").Run(asid)
return err return e
}) })
if err != nil { if err != nil {
return err return err
@ -644,8 +624,8 @@ func patch21(scanner *bufio.Scanner) error {
err = createTable("meta", "", "", err = createTable("meta", "", "",
[]tC{ []tC{
tC{"name", "varchar", 200, false, false, ""}, ccol("name", 200, ""),
tC{"value", "varchar", 200, false, false, ""}, ccol("value", 200, ""),
}, nil, }, nil,
) )
if err != nil { if err != nil {
@ -656,15 +636,11 @@ func patch21(scanner *bufio.Scanner) error {
} }
func patch22(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 { func patch23(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.DropTable("conversations")) err := createTable("conversations", "", "",
if err != nil {
return err
}
err = createTable("conversations", "", "",
[]tC{ []tC{
tC{"cid", "int", 0, false, true, ""}, tC{"cid", "int", 0, false, true, ""},
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
@ -680,17 +656,13 @@ func patch23(scanner *bufio.Scanner) error {
return err return err
} }
err = execStmt(qgen.Builder.DropTable("conversations_posts"))
if err != nil {
return err
}
err = createTable("conversations_posts", "", "", err = createTable("conversations_posts", "", "",
[]tC{ []tC{
tC{"pid", "int", 0, false, true, ""}, tC{"pid", "int", 0, false, true, ""},
tC{"cid", "int", 0, false, false, ""}, tC{"cid", "int", 0, false, false, ""},
tC{"createdBy", "int", 0, false, false, ""}, tC{"createdBy", "int", 0, false, false, ""},
tC{"body", "varchar", 50, false, false, ""}, ccol("body", 50, ""),
tC{"post", "varchar", 50, false, false, "''"}, ccol("post", 50, "''"),
}, },
[]tK{ []tK{
tK{"pid", "primary", "", false}, tK{"pid", "primary", "", false},
@ -700,10 +672,6 @@ func patch23(scanner *bufio.Scanner) error {
return err return err
} }
err = execStmt(qgen.Builder.DropTable("conversations_participants"))
if err != nil {
return err
}
return createTable("conversations_participants", "", "", return createTable("conversations_participants", "", "",
[]tC{ []tC{
tC{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
@ -713,16 +681,12 @@ func patch23(scanner *bufio.Scanner) error {
} }
func patch24(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", "", "", return createTable("users_groups_promotions", "", "",
[]tC{ []tC{
tC{"pid", "int", 0, false, true, ""}, tC{"pid", "int", 0, false, true, ""},
tC{"from_gid", "int", 0, false, false, ""}, tC{"from_gid", "int", 0, false, false, ""},
tC{"to_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 // Requirements
tC{"level", "int", 0, false, false, ""}, tC{"level", "int", 0, false, false, ""},
@ -812,28 +776,21 @@ func patch29(scanner *bufio.Scanner) error {
return err return err
} }
fixCol := func(tbl string) error { fixCols := func(tbls ...string) error {
//err := execStmt(qgen.Builder.RenameColumn(tbl, "ipaddress","ip")) for _, tbl := range tbls {
err := execStmt(qgen.Builder.ChangeColumn(tbl, "ipaddress", tC{"ip", "varchar", 200, false, false, "''"})) //err := execStmt(qgen.Builder.RenameColumn(tbl, "ipaddress","ip"))
if err != nil { err := execStmt(qgen.Builder.ChangeColumn(tbl, "ipaddress", ccol("ip", 200, "''")))
return err 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 = fixCols("topics", "replies", "polls_votes", "users_replies")
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")
if err != nil { if err != nil {
return err return err
} }
@ -867,36 +824,31 @@ func patch30(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.SetDefaultColumn("users", "last_ip", "varchar", "")) return execStmt(qgen.Builder.SetDefaultColumn("users", "last_ip", "varchar", ""))
} }
func patch31(scanner *bufio.Scanner) error { func patch31(scanner *bufio.Scanner) (e error) {
err := execStmt(qgen.Builder.RemoveIndex("topics", "title")) 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 { if err != nil {
return err return err
} }
err = execStmt(qgen.Builder.RemoveIndex("topics", "content")) err = addKey("topics", "content", tK{"content", "fulltext", "", false})
if err != nil { if err != nil {
return err return err
} }
err = execStmt(qgen.Builder.RemoveIndex("replies", "content")) return addKey("replies", "content", tK{"content", "fulltext", "", false})
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
} }
func createTable(table, charset, collation string, cols []tC, keys []tK) error { func createTable(tbl, charset, collation string, cols []tC, keys []tK) error {
return execStmt(qgen.Builder.CreateTable(table, charset, collation, cols, keys)) 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 { func patch32(scanner *bufio.Scanner) error {
@ -913,3 +865,75 @@ func patch32(scanner *bufio.Scanner) error {
func patch33(scanner *bufio.Scanner) error { func patch33(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.AddColumn("viewchunks", tC{"avg", "int", 0, false, false, "0"}, nil)) 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 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 { type Adapter interface {
GetName() string GetName() string
BuildConn(config map[string]string) (*sql.DB, error) 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 { func AccountEditPrivacy(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError {
accountEditHead("account_privacy", w, r, u, h) accountEditHead("account_privacy", w, r, u, h)
profileComments := false profileComments := u.Privacy.ShowComments
receiveConvos := false receiveConvos := u.Privacy.AllowMessage
enableEmbeds := !c.DefaultParseSettings.NoEmbed enableEmbeds := !c.DefaultParseSettings.NoEmbed
if u.ParseSettings != nil { if u.ParseSettings != nil {
enableEmbeds = !u.ParseSettings.NoEmbed 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 { func AccountEditPrivacySubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
//headerLite, _ := c.SimpleUserCheck(w, r, u) //headerLite, _ := c.SimpleUserCheck(w, r, u)
sProfileComments := r.FormValue("profile_comments")
sEnableEmbeds := r.FormValue("enable_embeds") sEnableEmbeds := r.FormValue("enable_embeds")
enableEmbeds, e := strconv.Atoi(sEnableEmbeds) oProfileComments := r.FormValue("o_profile_comments")
if e != nil { oEnableEmbeds := r.FormValue("o_enable_embeds")
return c.LocalError("enable_embeds must be 0 or 1", w, r, u)
} if sProfileComments != oProfileComments || sEnableEmbeds != oEnableEmbeds {
if sEnableEmbeds != r.FormValue("o_enable_embeds") { profileComments, e := strconv.Atoi(sProfileComments)
if e = u.UpdatePrivacy(enableEmbeds); e != nil { 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) 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
return actionSuccess(w, r, "/user/convo/"+strconv.Itoa(post.CID), js)
if !js {
http.Redirect(w, r, "/user/convo/"+strconv.Itoa(post.CID), http.StatusSeeOther)
} else {
w.Write(successJSONBytes)
}
return nil
} }
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") h.Title = p.GetTitlePhrase("create_block")
pid, err := strconv.Atoi(spid) pid, err := strconv.Atoi(spid)
if err != nil { 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) puser, err := c.Users.Get(pid)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) 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) 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) pid, err := strconv.Atoi(spid)
if err != nil { 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) puser, err := c.Users.Get(pid)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
if user.ID == puser.ID { if u.ID == puser.ID {
return c.LocalError("You can't block yourself.", w, r, user) 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -449,15 +443,15 @@ func RelationsBlockCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.
return nil 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") h.Title = p.GetTitlePhrase("remove_block")
pid, err := strconv.Atoi(spid) pid, err := strconv.Atoi(spid)
if err != nil { 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) puser, err := c.Users.Get(pid)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) 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) 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) pid, err := strconv.Atoi(spid)
if err != nil { 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) puser, err := c.Users.Get(pid)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) 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 { if err != nil {
return c.InternalError(err, w, r) 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}) 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 { func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
name := c.SanitiseSingleLine(r.PostFormValue("name")) name := c.SanitiseSingleLine(r.PostFormValue("name"))
if 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")) title := c.SanitiseSingleLine(r.PostFormValue("title"))
if 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") body := r.PostFormValue("body")
if 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() page := c.BlankCustomPage()
@ -62,7 +62,7 @@ func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.R
if err != nil { if err != nil {
return c.InternalError(err, w, r) 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 { if err != nil {
return c.InternalError(err, w, r) 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}) 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 { func PagesEditSubmit(w http.ResponseWriter, r *http.Request, u *c.User, spid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
pid, err := strconv.Atoi(spid) pid, err := strconv.Atoi(spid)
if err != nil { 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")) name := c.SanitiseSingleLine(r.PostFormValue("name"))
if 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")) title := c.SanitiseSingleLine(r.PostFormValue("title"))
if 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") body := r.PostFormValue("body")
if 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) 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 { if err != nil {
return c.InternalError(err, w, r) 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 { if err != nil {
return c.InternalError(err, w, r) 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 // 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 { func PluginsActivate(w http.ResponseWriter, r *http.Request, u *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManagePlugins { if !u.Perms.ManagePlugins {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
plugin, ok := c.Plugins[uname] pl, ok := c.Plugins[uname]
if !ok { 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 { if pl.Installable && !pl.Installed {
return c.LocalError("You can't activate this plugin without installing it first", w, r, user) return c.LocalError("You can't activate this plugin without installing it first", w, r, u)
} }
active, err := plugin.BypassActive() active, err := pl.BypassActive()
hasPlugin, err2 := plugin.InDatabase() hasPlugin, err2 := pl.InDatabase()
if err != nil || err2 != nil { if err != nil || err2 != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
if plugin.Activate != nil { if pl.Activate != nil {
err = plugin.Activate(plugin) err = pl.Activate(pl)
if err != nil { if err != nil {
return c.LocalError(err.Error(), w, r, user) return c.LocalError(err.Error(), w, r, u)
} }
} }
if hasPlugin { if hasPlugin {
if active { 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 { } else {
err = plugin.AddToDatabase(true, false) err = pl.AddToDatabase(true, false)
} }
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
log.Printf("Activating plugin '%s'", plugin.Name) log.Printf("Activating plugin '%s'", pl.Name)
err = plugin.Init(plugin) err = pl.Init(pl)
if err != nil { 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -82,36 +82,36 @@ func PluginsActivate(w http.ResponseWriter, r *http.Request, user *c.User, uname
return nil return nil
} }
func PluginsDeactivate(w http.ResponseWriter, r *http.Request, user *c.User, uname string) c.RouteError { func PluginsDeactivate(w http.ResponseWriter, r *http.Request, u *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManagePlugins { if !u.Perms.ManagePlugins {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
plugin, ok := c.Plugins[uname] pl, ok := c.Plugins[uname]
if !ok { 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} else if !active { } 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
if plugin.Deactivate != nil { if pl.Deactivate != nil {
plugin.Deactivate(plugin) 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -120,28 +120,28 @@ func PluginsDeactivate(w http.ResponseWriter, r *http.Request, user *c.User, una
return nil return nil
} }
func PluginsInstall(w http.ResponseWriter, r *http.Request, user *c.User, uname string) c.RouteError { func PluginsInstall(w http.ResponseWriter, r *http.Request, u *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManagePlugins { if !u.Perms.ManagePlugins {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
plugin, ok := c.Plugins[uname] pl, ok := c.Plugins[uname]
if !ok { 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 { if !pl.Installable {
return c.LocalError("This plugin is not installable", w, r, user) return c.LocalError("This plugin is not installable", w, r, u)
} }
if plugin.Installed { if pl.Installed {
return c.LocalError("This plugin has already been installed", w, r, user) return c.LocalError("This plugin has already been installed", w, r, u)
} }
active, err := plugin.BypassActive() active, err := pl.BypassActive()
hasPlugin, err2 := plugin.InDatabase() hasPlugin, err2 := pl.InDatabase()
if err != nil || err2 != nil { if err != nil || err2 != nil {
return c.InternalError(err, w, r) 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) return c.InternalError(errors.New("An uninstalled plugin is still active"), w, r)
} }
if plugin.Install != nil { if pl.Install != nil {
err = plugin.Install(plugin) err = pl.Install(pl)
if err != nil { if err != nil {
return c.LocalError(err.Error(), w, r, user) return c.LocalError(err.Error(), w, r, u)
} }
} }
if plugin.Activate != nil { if pl.Activate != nil {
err = plugin.Activate(plugin) err = pl.Activate(pl)
if err != nil { if err != nil {
return c.LocalError(err.Error(), w, r, user) return c.LocalError(err.Error(), w, r, u)
} }
} }
if hasPlugin { if hasPlugin {
err = plugin.SetInstalled(true) err = pl.SetInstalled(true)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = plugin.SetActive(true) err = pl.SetActive(true)
} else { } else {
err = plugin.AddToDatabase(true, true) err = pl.AddToDatabase(true, true)
} }
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
log.Printf("Installing plugin '%s'", plugin.Name) log.Printf("Installing plugin '%s'", pl.Name)
err = plugin.Init(plugin) err = pl.Init(pl)
if err != nil { 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -12,13 +12,13 @@ import (
p "github.com/Azareal/Gosora/common/phrases" p "github.com/Azareal/Gosora/common/phrases"
) )
func Themes(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError { func Themes(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "themes", "themes") basePage, ferr := buildBasePage(w, r, u, "themes", "themes")
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
var pThemeList, vThemeList []*c.Theme 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}) 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 { func ThemesSetDefault(w http.ResponseWriter, r *http.Request, u *c.User, uname string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
theme, ok := c.Themes[uname] theme, ok := c.Themes[uname]
if !ok { 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 { 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) err := c.UpdateDefaultTheme(theme)
if err != nil { if err != nil {
return c.InternalError(err, w, r) 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -67,13 +67,13 @@ func ThemesSetDefault(w http.ResponseWriter, r *http.Request, user *c.User, unam
return nil return nil
} }
func ThemesMenus(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError { func ThemesMenus(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "themes_menus", "themes") basePage, ferr := buildBasePage(w, r, u, "themes_menus", "themes")
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
var menuList []c.PanelMenuListItem 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}}) 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? // 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 { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
basePage.Header.AddScript("Sortable-1.4.0/Sortable.min.js") basePage.Header.AddScript("Sortable-1.4.0/Sortable.min.js")
basePage.Header.AddScriptAsync("panel_menu_items.js") basePage.Header.AddScriptAsync("panel_menu_items.js")
mid, err := strconv.Atoi(smid) mid, err := strconv.Atoi(smid)
if err != nil { if err != nil {
return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, user) return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, u)
} }
menuHold, err := c.Menus.Get(mid) menuHold, err := c.Menus.Get(mid)
if err == sql.ErrNoRows { 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}}) 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? // 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 { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
itemID, err := strconv.Atoi(sitemID) itemID, err := strconv.Atoi(sitemID)
if err != nil { if err != nil {
return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, user) return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, u)
} }
menuItem, err := c.Menus.ItemStore().Get(itemID) menuItem, err := c.Menus.ItemStore().Get(itemID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@ -205,23 +205,23 @@ func themesMenuItemSetters(r *http.Request, i c.MenuItem) c.MenuItem {
return i return i
} }
func ThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sitemID string) c.RouteError { func ThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sitemID string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, u, js)
} }
itemID, err := strconv.Atoi(sitemID) itemID, err := strconv.Atoi(sitemID)
if err != nil { 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) menuItem, err := c.Menus.ItemStore().Get(itemID)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, js) 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 { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) 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 { if err != nil {
return c.InternalError(err, w, r) 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) 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 { func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, u, js)
} }
smenuID := r.PostFormValue("mid") smenuID := r.PostFormValue("mid")
if smenuID == "" { 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) menuID, err := strconv.Atoi(smenuID)
if err != nil { 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} menuItem := c.MenuItem{MenuID: menuID}
@ -265,7 +265,7 @@ func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) 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 { if err != nil {
return c.InternalError(err, w, r) 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) 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 { func ThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sitemID string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, u, js)
} }
itemID, err := strconv.Atoi(sitemID) itemID, err := strconv.Atoi(sitemID)
if err != nil { 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) menuItem, err := c.Menus.ItemStore().Get(itemID)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
@ -299,7 +299,7 @@ func ThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) 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 { if err != nil {
return c.InternalError(err, w, r) 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) return successRedirect("/panel/themes/menus/", w, r, js)
} }
func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, user *c.User, smid string) c.RouteError { func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, u *c.User, smid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, u, js)
} }
mid, err := strconv.Atoi(smid) mid, err := strconv.Atoi(smid)
if err != nil { 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) menuHold, err := c.Menus.Get(mid)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, js) 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, ",") { for index, smiid := range strings.Split(sitems, ",") {
miid, err := strconv.Atoi(smiid) miid, err := strconv.Atoi(smiid)
if err != nil { 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 updateMap[miid] = index
} }
menuHold.UpdateOrder(updateMap) 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 { if err != nil {
return c.InternalError(err, w, r) 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) 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 { func ThemesWidgets(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "themes_widgets", "themes") basePage, ferr := buildBasePage(w, r, u, "themes_widgets", "themes")
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
basePage.Header.AddScript("widgets.js") 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 // 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") //fmt.Println("in ThemesWidgetsEditSubmit")
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, u, js)
} }
wid, err := strconv.Atoi(swid) wid, err := strconv.Atoi(swid)
if err != nil { 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) widget, err := c.Widgets.Get(wid)
if err == sql.ErrNoRows { 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()) ewidget, err := widgetsParseInputs(r, widget.Copy())
if err != nil { if err != nil {
return c.LocalErrorJSQ(err.Error(), w, r, user, js) return c.LocalErrorJSQ(err.Error(), w, r, u, js)
} }
err = ewidget.Commit() err = ewidget.Commit()
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) 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 { if err != nil {
return c.InternalError(err, w, r) 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 // 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" js := r.PostFormValue("js") == "1"
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, u, js)
} }
ewidget, err := widgetsParseInputs(r, &c.Widget{}) ewidget, err := widgetsParseInputs(r, &c.Widget{})
if err != nil { 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() wid, err := ewidget.Create()
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) 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 { if err != nil {
return c.InternalError(err, w, r) 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) return successRedirect("/panel/themes/widgets/", w, r, js)
} }
func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, swid string) c.RouteError { func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, u *c.User, swid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes { if !u.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, u, js)
} }
wid, err := strconv.Atoi(swid) wid, err := strconv.Atoi(swid)
if err != nil { 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) widget, err := c.Widgets.Get(wid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@ -504,7 +504,7 @@ func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.U
if err != nil { if err != nil {
return c.InternalError(err, w, r) 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 { if err != nil {
return c.InternalError(err, w, r) 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) return c.InternalError(err, w, r)
} }
} }
canMessage := (!blockedInv && user.Perms.UseConvos) || (!blockedInv && puser.IsSuperMod && user.Perms.UseConvosOnlyWithMod) || user.IsSuperMod canMessage := (!blockedInv && user.Perms.UseConvos) || (!blockedInv && puser.IsSuperMod && user.Perms.UseConvosOnlyWithMod) || user.IsSuperMod
canComment := !blockedInv && user.Perms.CreateProfileReply 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) 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 { if err != nil {
return c.LocalError("Invalid UID", w, r, user) return c.LocalError("Invalid UID", w, r, user)
} }
profileOwner, err := c.Users.Get(uid) profileOwner, err := c.Users.Get(uid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("The profile you're trying to post on doesn't exist.", w, r, user) 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) 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 // 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) 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 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" js := r.PostFormValue("js") == "1"
rid, err := strconv.Atoi(srid) rid, err := strconv.Atoi(srid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, js) return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, u, js)
} }
reply, err := c.Prstore.Get(rid) reply, err := c.Prstore.Get(rid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.PreErrorJSQ("The target reply doesn't exist.", w, r, js) 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 { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
if !user.Perms.CreateProfileReply { if !u.Perms.CreateProfileReply {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, u, js)
} }
// ? Does the admin understand that this group perm affects this? // ? Does the admin understand that this group perm affects this?
if user.ID != creator.ID && !user.Perms.EditReply { if u.ID != creator.ID && !u.Perms.EditReply {
return c.NoPermissionsJSQ(w, r, user, js) 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")) err = reply.SetBody(r.PostFormValue("edit_item"))
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
return actionSuccess(w, r, "/user/"+strconv.Itoa(creator.ID)+"#reply-"+strconv.Itoa(rid), js)
if !js {
http.Redirect(w, r, "/user/"+strconv.Itoa(creator.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther)
} else {
w.Write(successJSONBytes)
}
return nil
} }
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" js := r.PostFormValue("js") == "1"
rid, err := strconv.Atoi(srid) rid, err := strconv.Atoi(srid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, user, js) return c.LocalErrorJSQ("The provided Reply ID is not a valid number.", w, r, u, js)
} }
reply, err := c.Prstore.Get(rid) reply, err := c.Prstore.Get(rid)
@ -115,15 +120,15 @@ func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.Us
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
if user.ID != creator.ID && !user.Perms.DeleteReply { if u.ID != creator.ID && !u.Perms.DeleteReply {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, u, js)
} }
err = reply.Delete() err = reply.Delete()
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) 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 { if !js {
//http.Redirect(w,r, "/user/" + strconv.Itoa(creator.ID), http.StatusSeeOther) //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) 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 { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }

View File

@ -9,46 +9,46 @@ import (
c "github.com/Azareal/Gosora/common" c "github.com/Azareal/Gosora/common"
) )
func BanUserSubmit(w http.ResponseWriter, r *http.Request, user *c.User, suid string) c.RouteError { func BanUserSubmit(w http.ResponseWriter, r *http.Request, u *c.User, suid string) c.RouteError {
if !user.Perms.BanUsers { if !u.Perms.BanUsers {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
uid, err := strconv.Atoi(suid) uid, err := strconv.Atoi(suid)
if err != nil { 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 { 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) targetUser, err := c.Users.Get(uid)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
// TODO: Is there a difference between IsMod and IsSuperMod? Should we delete the redundant one? // TODO: Is there a difference between IsMod and IsSuperMod? Should we delete the redundant one?
if targetUser.IsMod { 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 { if uid == u.ID {
return c.LocalError("Why are you trying to ban yourself? Stop that.", w, r, user) return c.LocalError("Why are you trying to ban yourself? Stop that.", w, r, u)
} }
if targetUser.IsBanned { 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")) durDays, err := strconv.Atoi(r.FormValue("dur-days"))
if err != nil { 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")) durWeeks, err := strconv.Atoi(r.FormValue("dur-weeks"))
if err != nil { 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")) durMonths, err := strconv.Atoi(r.FormValue("dur-months"))
if err != nil { 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 deletePosts := false
switch r.FormValue("delete-posts") { 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") dur, _ = time.ParseDuration(strconv.Itoa(secs) + "s")
} }
err = targetUser.Ban(dur, user.ID) err = targetUser.Ban(dur, u.ID)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) 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 { if err != nil {
return c.InternalError(err, w, r) 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 { if deletePosts {
err = targetUser.DeletePosts() err = targetUser.DeletePosts()
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) 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 { if err != nil {
return c.InternalError(err, w, r) 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 // TODO: Trickle the hookTable down from the router
hTbl := c.GetHookTable() 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 { if skip || rerr != nil {
return rerr return rerr
} }
@ -102,42 +102,42 @@ func BanUserSubmit(w http.ResponseWriter, r *http.Request, user *c.User, suid st
return nil return nil
} }
func UnbanUser(w http.ResponseWriter, r *http.Request, user *c.User, suid string) c.RouteError { func UnbanUser(w http.ResponseWriter, r *http.Request, u *c.User, suid string) c.RouteError {
if !user.Perms.BanUsers { if !u.Perms.BanUsers {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
uid, err := strconv.Atoi(suid) uid, err := strconv.Atoi(suid)
if err != nil { 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) targetUser, err := c.Users.Get(uid)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
if !targetUser.IsBanned { 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() err = targetUser.Unban()
if err == c.ErrNoTempGroup { 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 { } 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 { } else if err != nil {
return c.InternalError(err, w, r) 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
// TODO: Trickle the hookTable down from the router // TODO: Trickle the hookTable down from the router
hTbl := c.GetHookTable() 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 { if skip || rerr != nil {
return rerr return rerr
} }
@ -146,23 +146,23 @@ func UnbanUser(w http.ResponseWriter, r *http.Request, user *c.User, suid string
return nil return nil
} }
func ActivateUser(w http.ResponseWriter, r *http.Request, user *c.User, suid string) c.RouteError { func ActivateUser(w http.ResponseWriter, r *http.Request, u *c.User, suid string) c.RouteError {
if !user.Perms.ActivateUsers { if !u.Perms.ActivateUsers {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
uid, err := strconv.Atoi(suid) uid, err := strconv.Atoi(suid)
if err != nil { 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) targetUser, err := c.Users.Get(uid)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
if targetUser.Active { 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() err = targetUser.Activate()
if err != nil { 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) targetUser, err = c.Users.Get(uid)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) 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() 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
// TODO: Trickle the hookTable down from the router // TODO: Trickle the hookTable down from the router
hTbl := c.GetHookTable() 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 { if skip || rerr != nil {
return rerr return rerr
} }
@ -197,40 +197,40 @@ func ActivateUser(w http.ResponseWriter, r *http.Request, user *c.User, suid str
return nil return nil
} }
func DeletePostsSubmit(w http.ResponseWriter, r *http.Request, user *c.User, suid string) c.RouteError { func DeletePostsSubmit(w http.ResponseWriter, r *http.Request, u *c.User, suid string) c.RouteError {
if !user.Perms.BanUsers { if !u.Perms.BanUsers {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
uid, err := strconv.Atoi(suid) uid, err := strconv.Atoi(suid)
if err != nil { 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) targetUser, err := c.Users.Get(uid)
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
// TODO: Is there a difference between IsMod and IsSuperMod? Should we delete the redundant one? // TODO: Is there a difference between IsMod and IsSuperMod? Should we delete the redundant one?
if targetUser.IsMod { 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() err = targetUser.DeletePosts()
if err == sql.ErrNoRows { 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 { } else if err != nil {
return c.InternalError(err, w, r) 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 { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
// TODO: Trickle the hookTable down from the router // TODO: Trickle the hookTable down from the router
hTbl := c.GetHookTable() 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 { if skip || rerr != nil {
return rerr return rerr
} }

View File

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

View File

@ -3,7 +3,7 @@ CREATE TABLE `registration_logs` (
`username` varchar(100) not null, `username` varchar(100) not null,
`email` varchar(100) not null, `email` varchar(100) not null,
`failureReason` 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, `ipaddress` varchar(200) not null,
`doneAt` datetime not null, `doneAt` datetime not null,
primary key(`rlid`) primary key(`rlid`)

View File

@ -10,6 +10,8 @@ CREATE TABLE `users` (
`lastActiveAt` datetime not null, `lastActiveAt` datetime not null,
`session` varchar(200) DEFAULT '' not null, `session` varchar(200) DEFAULT '' not null,
`last_ip` 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, `enable_embeds` int DEFAULT -1 not null,
`email` varchar(200) DEFAULT '' not null, `email` varchar(200) DEFAULT '' not null,
`avatar` varchar(100) DEFAULT '' not null, `avatar` varchar(100) DEFAULT '' not null,

View File

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

View File

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

View File

@ -3,7 +3,7 @@ CREATE TABLE "registration_logs" (
`username` varchar (100) not null, `username` varchar (100) not null,
`email` varchar (100) not null, `email` varchar (100) not null,
`failureReason` 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, `ipaddress` varchar (200) not null,
`doneAt` timestamp not null, `doneAt` timestamp not null,
primary key(`rlid`) primary key(`rlid`)

View File

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

View File

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

View File

@ -1,25 +1,25 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "panel_stats_active_memory_head"}}</h1> <h1>{{lang "panel_stats_active_memory_head"}}</h1>
<select form="timeRangeForm" class="typeSelector to_right autoSubmitRedirect" name="mtype"> <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="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="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> <option value="2"{{if eq .MemType 2}}selected{{end}}>{{lang "panel_stats_memory_type_heap"}}</option>
</select> </select>
<noscript><input form="timeRangeForm" type="submit"></noscript> <noscript><input form="timeRangeForm"type="submit"></noscript>
{{template "panel_analytics_time_range_month.html" . }} {{template "panel_analytics_time_range_month.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/active-memory/" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/active-memory/"method="get"></form>
<div id="panel_analytics_memory" class="colstack_graph_holder"> <div id="panel_analytics_memory"class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_memory_chart_aria"}}"></div> <div class="ct_chart"aria-label="{{lang "panel_stats_memory_chart_aria"}}"></div>
</div> </div>
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1> <h1>{{lang "panel_stats_details_head"}}</h1>
</div> </div>
</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}} {{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent"> <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> <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" . }} {{template "panel_analytics_time_range_month.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/memory/" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/memory/"method="get"></form>
<div id="panel_analytics_memory" class="colstack_graph_holder"> <div id="panel_analytics_memory"class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_memory_chart_aria"}}"></div> <div class="ct_chart"aria-label="{{lang "panel_stats_memory_chart_aria"}}"></div>
</div> </div>
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1> <h1>{{lang "panel_stats_details_head"}}</h1>
</div> </div>
</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}} {{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent"> <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> <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="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "panel_stats_perf_head"}}</h1> <h1>{{lang "panel_stats_perf_head"}}</h1>
<select form="timeRangeForm" class="typeSelector to_right autoSubmitRedirect" name="type"> <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="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="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> <option value="2"{{if eq .PerfType 2}}selected{{end}}>{{lang "panel_stats_perf_avg"}}</option>
</select> </select>
<noscript><input form="timeRangeForm" type="submit"></noscript> <noscript><input form="timeRangeForm"type="submit"></noscript>
{{template "panel_analytics_time_range_month.html" . }} {{template "panel_analytics_time_range_month.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/perf/" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/perf/" method="get"></form>
<div id="panel_analytics_memory" class="colstack_graph_holder"> <div id="panel_analytics_memory"class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_perf_chart_aria"}}"></div> <div class="ct_chart"aria-label="{{lang "panel_stats_perf_chart_aria"}}"></div>
</div> </div>
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1> <h1>{{lang "panel_stats_details_head"}}</h1>
</div> </div>
</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}} {{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent"> <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> <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" . }} {{template "panel_analytics_time_range.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/posts/" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/posts/"method="get"></form>
<div id="panel_analytics_posts" class="colstack_graph_holder"> <div id="panel_analytics_posts"class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_post_counts_chart_aria"}}"></div> <div class="ct_chart"aria-label="{{lang "panel_stats_post_counts_chart_aria"}}"></div>
</div> </div>
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1> <h1>{{lang "panel_stats_details_head"}}</h1>
</div> </div>
</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}} {{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent"> <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> <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" . }} {{template "panel_analytics_time_range.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrer/{{.Agent}}" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/referrer/{{.Agent}}"method="get"></form>
<div id="panel_analytics_referrers" class="colstack_graph_holder"> <div id="panel_analytics_referrers"class="colstack_graph_holder">
<div class="ct_chart"></div> <div class="ct_chart"></div>
</div> </div>
{{template "panel_analytics_script.html" . }} {{template "panel_analytics_script.html" . }}

View File

@ -1,19 +1,19 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "panel_stats_referrers_head"}}</h1> <h1>{{lang "panel_stats_referrers_head"}}</h1>
<select form="timeRangeForm" class="spamSelector to_right autoSubmitRedirect" name="spam"> <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="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> <option value="1"{{if .ShowSpam}}selected{{end}}>{{lang "panel_stats_spam_show"}}</option>
</select> </select>
<noscript><input form="timeRangeForm" type="submit"></noscript> <noscript><input form="timeRangeForm"type="submit"></noscript>
{{template "panel_analytics_time_range.html" . }} {{template "panel_analytics_time_range.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrers/" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/referrers/"method="get"></form>
<div id="panel_analytics_referrers" class="colstack_item rowlist"> <div id="panel_analytics_referrers"class="colstack_item rowlist">
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent"> <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> <span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div> </div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_referrers_no_referrers"}}</div>{{end}} {{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" . }} {{template "panel_analytics_time_range.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/route/{{.Route}}" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/route/{{.Route}}"method="get"></form>
<div id="panel_analytics_views" class="colstack_graph_holder"> <div id="panel_analytics_views"class="colstack_graph_holder">
<div class="ct_chart"></div> <div class="ct_chart"></div>
</div> </div>
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a>{{lang "panel_stats_details_head"}}</a></div> <div class="rowitem"><a>{{lang "panel_stats_details_head"}}</a></div>
</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}} {{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent"> <div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift unix_to_24_hour_time">{{.Time}}</a> <a class="panel_upshift unix_to_24_hour_time">{{.Time}}</a>

View File

@ -4,14 +4,14 @@
{{template "panel_analytics_time_range.html" . }} {{template "panel_analytics_time_range.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/routes/" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/routes/"method="get"></form>
<div id="panel_analytics_routes_chart" class="colstack_graph_holder"> <div id="panel_analytics_routes_chart"class="colstack_graph_holder">
<div class="ct_chart"></div> <div class="ct_chart"></div>
</div> </div>
<div id="panel_analytics_routes" class="colstack_item rowlist"> <div id="panel_analytics_routes"class="colstack_item rowlist">
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent"> <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> <span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div> </div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_routes_no_routes"}}</div>{{end}} {{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" . }} {{template "panel_analytics_time_range.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/routes-perf/" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/routes-perf/"method="get"></form>
<div id="panel_analytics_routes_chart" class="colstack_graph_holder"> <div id="panel_analytics_routes_chart"class="colstack_graph_holder">
<div class="ct_chart"></div> <div class="ct_chart"></div>
</div> </div>
<div id="panel_analytics_routes" class="colstack_item rowlist"> <div id="panel_analytics_routes"class="colstack_item rowlist">
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent"> <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> <span class="panel_compacttext to_right">{{.Count}}{{.Unit}}</span>
</div> </div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_routes_no_routes"}}</div>{{end}} {{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("after_phrases", () => {
addInitHook("end_init", () => { addInitHook("end_init", () => {
addInitHook("analytics_loaded", () => { 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("after_phrases", () => {
addInitHook("end_init", () => { addInitHook("end_init", () => {
addInitHook("analytics_loaded", () => { addInitHook("analytics_loaded", () => {
memStuff(window, document, Chartist); memStuff(window,document,Chartist);
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}",legendNames,1); buildStatsChart(rawLabels,seriesData,"{{.TimeRange}}",legendNames,1);
}); });
}); });
}); });

View File

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

View File

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

View File

@ -4,14 +4,14 @@
{{template "panel_analytics_time_range.html" . }} {{template "panel_analytics_time_range.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/systems/" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/systems/"method="get"></form>
<div id="panel_analytics_systems_chart" class="colstack_graph_holder"> <div id="panel_analytics_systems_chart"class="colstack_graph_holder">
<div class="ct_chart"></div> <div class="ct_chart"></div>
</div> </div>
<div id="panel_analytics_systems" class="colstack_item rowlist"> <div id="panel_analytics_systems"class="colstack_item rowlist">
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent"> <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> <span class="panel_compacttext to_right">{{.Count}}{{lang "panel_stats_views_suffix"}}</span>
</div> </div>
{{else}}<div class="rowitem passive rowmsg">{{lang "panel_stats_operating_systems_no_operating_systems"}}</div>{{end}} {{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"> <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-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="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="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="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="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> <option value="six-hours"{{if eq .TimeRange "six-hours"}}selected{{end}}>{{lang "panel_stats_time_range_six_hours"}}</option>
</select> </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" . }} {{template "panel_analytics_time_range.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/topics/" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/topics/"method="get"></form>
<div id="panel_analytics_topics" class="colstack_graph_holder"> <div id="panel_analytics_topics"class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_topic_counts_chart_aria"}}"></div> <div class="ct_chart"aria-label="{{lang "panel_stats_topic_counts_chart_aria"}}"></div>
</div> </div>
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1> <h1>{{lang "panel_stats_details_head"}}</h1>
</div> </div>
</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}} {{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent"> <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> <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" . }} {{template "panel_analytics_time_range.html" . }}
</div> </div>
</div> </div>
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get"></form> <form id="timeRangeForm"name="timeRangeForm"action="/panel/analytics/views/"method="get"></form>
<div id="panel_analytics_views" class="colstack_graph_holder"> <div id="panel_analytics_views"class="colstack_graph_holder">
<div class="ct_chart" aria-label="{{lang "panel_stats_requests_chart_aria"}}"></div> <div class="ct_chart"aria-label="{{lang "panel_stats_requests_chart_aria"}}"></div>
</div> </div>
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "panel_stats_details_head"}}</h1> <h1>{{lang "panel_stats_details_head"}}</h1>
</div> </div>
</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}} {{range .ViewItems}}
<div class="rowitem panel_compactrow editable_parent"> <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> <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="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_backups_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_backups_head"}}</h1></div>
</div> </div>
<div id="panel_backups" class="colstack_item rowlist"> <div id="panel_backups"class="colstack_item rowlist">
{{range .Backups}} {{range .Backups}}
<div class="rowitem panel_compactrow"> <div class="rowitem panel_compactrow">
<span>{{.SQLURL}}</span> <span>{{.SQLURL}}</span>
<span class="panel_floater"> <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> </span>
</div> </div>
{{else}}<div class="rowitem rowmsg">{{lang "panel_backups_no_backups"}}</div>{{end}} {{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="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div> <div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div>
</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"> <form action="/panel/forums/edit/submit/{{.ID}}?s={{.CurrentUser.Session}}" method="post">
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_name"}}</a></div> <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>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_description"}}</a></div> <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>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_active"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_forum_active"}}</a></div>
@ -23,7 +23,7 @@
<div class="formitem"> <div class="formitem">
<select name="forum_preset"> <select name="forum_preset">
<option{{if eq .Preset "all"}} selected{{end}} value="all">{{lang "panel_preset_everyone"}}</option> <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 "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 "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> <option{{if eq .Preset "admins"}} selected{{end}} value="admins">{{lang "panel_preset_admin_only"}}</option>
@ -33,7 +33,7 @@
</div> </div>
</div> </div>
<div class="formrow"> <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> </form>
</div> </div>
@ -42,17 +42,17 @@
<h1>{{lang "panel_forum_permissions_head"}}</h1> <h1>{{lang "panel_forum_permissions_head"}}</h1>
</div> </div>
</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}} {{range .Groups}}
<div class="formrow"> <div class="formrow">
<div class="formitem editable_parent"> <div class="formitem editable_parent">
<a>{{.Group.Name}}</a> <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> <span class="edit_fields hide_on_edit rowsmall">{{lang "panel_forum_edit_button"}}</span>
<div class="panel_floater"> <div class="panel_floater">
<span data-field="perm_preset" data-type="list" data-value="{{.Preset}}" class="editable_block perm_preset perm_preset_{{.Preset}}"></span> <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/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> <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> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div> <div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div>
</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"> <div class="colstack_item rowlist formlist the_form panel_forum_perms">
{{range .Perms}} {{range .Perms}}
<div class="formrow"> <div class="formrow">
@ -16,7 +16,7 @@
</div> </div>
</div>{{end}} </div>{{end}}
<div class="formrow"> <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>
</div> </div>
</form> </form>

View File

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

View File

@ -1,15 +1,15 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_pages_edit_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_pages_edit_head"}}</h1></div>
</div> </div>
<form action="/panel/pages/edit/submit/{{.Page.ID}}?s={{.CurrentUser.Session}}" method="post"> <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 id="panel_page_edit_item"class="colstack_item the_form">
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_pages_name"}}</a></div> <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>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_pages_title"}}</a></div> <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>
<div class="formrow"> <div class="formrow">
<div class="formitem"> <div class="formitem">
@ -17,7 +17,7 @@
</div> </div>
</div> </div>
<div class="formrow"> <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>
</div> </div>
</form> </form>

View File

@ -1,20 +1,20 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_plugins_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_plugins_head"}}</h1></div>
</div> </div>
<div id="panel_plugins" class="colstack_item complex_rowlist"> <div id="panel_plugins"class="colstack_item complex_rowlist">
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem editable_parent"> <div class="rowitem editable_parent">
<span class="panel_plugin_meta"> <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> <small style="margin-left:2px;">{{lang "panel_plugins_author_prefix"}}{{.Author}}</small>
</span> </span>
<span class="to_right"> <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 .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 .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}} {{else if .Installable}}
{{/** TODO: Write a custom template interpreter to fix this nonsense **/}} {{/** 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}} {{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}} {{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> </span>
</div> </div>
{{end}} {{end}}

View File

@ -4,14 +4,14 @@
<h2 class="hguide">{{lang "panel_hints_reorder"}}</h2> <h2 class="hguide">{{lang "panel_hints_reorder"}}</h2>
</div> </div>
</div> </div>
<div id="panel_menu_item_holder" class="colstack_item rowlist"> <div id="panel_menu_item_holder"class="colstack_item rowlist">
{{range .ItemList}} {{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> <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"> <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/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/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> </span>
</div>{{end}} </div>{{end}}
</div> </div>
@ -21,9 +21,9 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_create_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_themes_menus_create_head"}}</h1></div>
</div> </div>
<form action="/panel/themes/menus/item/create/submit/?s={{.CurrentUser.Session}}" method="post"> <form action="/panel/themes/menus/item/create/submit/?s={{.CurrentUser.Session}}"method="post">
<input name="mid" value="{{.MenuID}}" type="hidden"> <input name="mid"value="{{.MenuID}}"type="hidden">
<div id="panel_themes_menu_item_create" class="colstack_item the_form"> <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? **/}} {{/** TODO: Let an admin move a menu item from one menu to another? **/}}
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_name"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_menus_name"}}</a></div>

View File

@ -58,7 +58,7 @@
{{if .CurrentUser.Loggedin}} {{if .CurrentUser.Loggedin}}
{{if .CurrentUser.Perms.BanUsers}} {{if .CurrentUser.Perms.BanUsers}}
<!-- TODO: Inline the display:none; CSS --> <!-- 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 class="rowitem"><h1><a>{{lang "profile.ban_user_head"}}</a></h1></div>
</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;"> <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 id="profile_comments_head"class="colstack_item colstack_head hash_hide">
<div class="rowitem"><h1><a>{{lang "profile.comments_head"}}</a></h1></div> <div class="rowitem"><h1><a>{{lang "profile.comments_head"}}</a></h1></div>
</div> </div>{{if .ShowComments}}
<div id="profile_comments"class="colstack_item hash_hide">{{template "profile_comments_row.html" . }}</div> <div id="profile_comments"class="colstack_item hash_hide">{{template "profile_comments_row.html" . }}</div>{{end}}
{{if .CurrentUser.Loggedin}} {{if .CurrentUser.Loggedin}}
{{if .CanComment}} {{if .CanComment}}