1
0
mirror of https://github.com/go-gitea/gitea.git synced 2024-06-29 01:45:30 +00:00
This commit is contained in:
Rafael 2024-06-17 19:04:36 +01:00 committed by GitHub
commit a08b085c2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 406 additions and 61 deletions

View File

@ -156,6 +156,14 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
continue
}
// Filter users by their notification preference.
// At this point we exclude:
// user that don't have all notifications enabled or users only get notification on mention and this is one ...
if !(user.UINotificationsPreference == user_model.NotificationsEnabled ||
user.UINotificationsPreference == user_model.NotificationsOnMention) {
continue
}
if notificationExists(notifications, issue.ID, userID) {
if err = updateIssueNotification(ctx, userID, issue.ID, commentID, notificationAuthorID); err != nil {
return err

View File

@ -8,6 +8,7 @@
email: user1@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -45,6 +46,7 @@
email: user2@example.com
keep_email_private: true
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -82,6 +84,7 @@
email: org3@example.com
keep_email_private: false
email_notifications_preference: onmention
ui_notifications_preference: onmention
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -119,6 +122,7 @@
email: user4@example.com
keep_email_private: false
email_notifications_preference: onmention
ui_notifications_preference: onmention
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -156,6 +160,7 @@
email: user5@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -193,6 +198,7 @@
email: org6@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -230,6 +236,7 @@
email: org7@example.com
keep_email_private: false
email_notifications_preference: disabled
ui_notifications_preference: disabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -267,6 +274,7 @@
email: user8@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -304,6 +312,7 @@
email: user9@example.com
keep_email_private: false
email_notifications_preference: onmention
ui_notifications_preference: onmention
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -341,6 +350,7 @@
email: user10@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -378,6 +388,7 @@
email: user11@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -415,6 +426,7 @@
email: user12@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -452,6 +464,7 @@
email: user13@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -489,6 +502,7 @@
email: user14@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -526,6 +540,7 @@
email: user15@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -563,6 +578,7 @@
email: user16@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -600,6 +616,7 @@
email: org17@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -637,6 +654,7 @@
email: user18@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -674,6 +692,7 @@
email: org19@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -711,6 +730,7 @@
email: user20@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -748,6 +768,7 @@
email: user21@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -785,6 +806,7 @@
email: limited_org@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -822,6 +844,7 @@
email: privated_org@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -859,6 +882,7 @@
email: user24@example.com
keep_email_private: true
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -896,6 +920,7 @@
email: org25@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -933,6 +958,7 @@
email: org26@example.com
keep_email_private: false
email_notifications_preference: onmention
ui_notifications_preference: onmention
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -970,6 +996,7 @@
email: user27@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1007,6 +1034,7 @@
email: user28@example.com
keep_email_private: true
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1044,6 +1072,7 @@
email: user29@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1081,6 +1110,7 @@
email: user30@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1118,6 +1148,7 @@
email: user31@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1155,6 +1186,7 @@
email: user32@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:notpassword
passwd_hash_algo: dummy
must_change_password: false
@ -1192,6 +1224,7 @@
email: user33@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1230,6 +1263,7 @@
email: user34@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1267,6 +1301,7 @@
email: private_org35@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1304,6 +1339,7 @@
email: abcde@gitea.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1341,6 +1377,7 @@
email: user37@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1378,6 +1415,7 @@
email: user38@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1415,6 +1453,7 @@
email: user39@example.com
keep_email_private: false
email_notifications_preference: enabled
ui_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1452,6 +1491,7 @@
email: user40@example.com
keep_email_private: false
email_notifications_preference: onmention
ui_notifications_preference: onmention
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
@ -1489,6 +1529,7 @@
email: org41@example.com
keep_email_private: false
email_notifications_preference: onmention
ui_notifications_preference: onmention
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false

View File

@ -61,15 +61,16 @@ const (
UserTypeRemoteUser
)
// Constants used as user-setting for both Email and UI notifications.
const (
// EmailNotificationsEnabled indicates that the user would like to receive all email notifications except your own
EmailNotificationsEnabled = "enabled"
// EmailNotificationsOnMention indicates that the user would like to be notified via email when mentioned.
EmailNotificationsOnMention = "onmention"
// EmailNotificationsDisabled indicates that the user would not like to be notified via email.
EmailNotificationsDisabled = "disabled"
// EmailNotificationsAndYourOwn indicates that the user would like to receive all email notifications and your own
EmailNotificationsAndYourOwn = "andyourown"
// NotificationsEnabled indicates that the user would like to receive all notifications except your own
NotificationsEnabled = "enabled"
// NotificationsOnMention indicates that the user would like to be notified when mentioned.
NotificationsOnMention = "onmention"
// NotificationsDisabled indicates that the user would not like to be notified.
NotificationsDisabled = "disabled"
// NotificationsAndYourOwn indicates that the user would like to receive all notifications and their own
NotificationsAndYourOwn = "andyourown"
)
// User represents the object of individual and member of organization.
@ -82,6 +83,7 @@ type User struct {
Email string `xorm:"NOT NULL"`
KeepEmailPrivate bool
EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"`
UINotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"`
Passwd string `xorm:"NOT NULL"`
PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
@ -578,6 +580,7 @@ type CreateUserOverwriteOptions struct {
Visibility *structs.VisibleType
AllowCreateOrganization optional.Option[bool]
EmailNotificationsPreference *string
UINotificationsPreference *string
MaxRepoCreation *int
Theme *string
IsRestricted optional.Option[bool]
@ -605,6 +608,8 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa
u.Visibility = setting.Service.DefaultUserVisibilityMode
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
u.UINotificationsPreference = setting.Admin.DefaultUINotification
u.MaxRepoCreation = -1
u.Theme = setting.UI.DefaultTheme
u.IsRestricted = setting.Service.DefaultUserIsRestricted
@ -630,6 +635,9 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa
if overwrite.EmailNotificationsPreference != nil {
u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference
}
if overwrite.UINotificationsPreference != nil {
u.UINotificationsPreference = *overwrite.UINotificationsPreference
}
if overwrite.MaxRepoCreation != nil {
u.MaxRepoCreation = *overwrite.MaxRepoCreation
}
@ -924,7 +932,7 @@ func GetUserEmailsByNames(ctx context.Context, names []string) []string {
if err != nil {
continue
}
if u.IsMailable() && u.EmailNotificationsPreference != EmailNotificationsDisabled {
if u.IsMailable() && u.EmailNotificationsPreference != NotificationsDisabled {
mails = append(mails, u.Email)
}
}
@ -944,7 +952,7 @@ func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([
Where("`type` = ?", UserTypeIndividual).
And("`prohibit_login` = ?", false).
And("`is_active` = ?", true).
In("`email_notifications_preference`", EmailNotificationsEnabled, EmailNotificationsOnMention, EmailNotificationsAndYourOwn).
In("`email_notifications_preference`", NotificationsEnabled, NotificationsOnMention, NotificationsAndYourOwn).
Find(&ous)
}
@ -953,7 +961,7 @@ func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([
Where("`type` = ?", UserTypeIndividual).
And("`prohibit_login` = ?", false).
And("`is_active` = ?", true).
In("`email_notifications_preference`", EmailNotificationsEnabled, EmailNotificationsAndYourOwn).
In("`email_notifications_preference`", NotificationsEnabled, NotificationsAndYourOwn).
Find(&ous)
}

View File

@ -137,21 +137,43 @@ func TestEmailNotificationPreferences(t *testing.T) {
expected string
userID int64
}{
{user_model.EmailNotificationsEnabled, 1},
{user_model.EmailNotificationsEnabled, 2},
{user_model.EmailNotificationsOnMention, 3},
{user_model.EmailNotificationsOnMention, 4},
{user_model.EmailNotificationsEnabled, 5},
{user_model.EmailNotificationsEnabled, 6},
{user_model.EmailNotificationsDisabled, 7},
{user_model.EmailNotificationsEnabled, 8},
{user_model.EmailNotificationsOnMention, 9},
{user_model.NotificationsEnabled, 1},
{user_model.NotificationsEnabled, 2},
{user_model.NotificationsOnMention, 3},
{user_model.NotificationsOnMention, 4},
{user_model.NotificationsEnabled, 5},
{user_model.NotificationsEnabled, 6},
{user_model.NotificationsDisabled, 7},
{user_model.NotificationsEnabled, 8},
{user_model.NotificationsOnMention, 9},
} {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.userID})
assert.Equal(t, test.expected, user.EmailNotificationsPreference)
}
}
func TestUINotificationPreferences(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
for _, test := range []struct {
expected string
userID int64
}{
{user_model.NotificationsEnabled, 1},
{user_model.NotificationsEnabled, 2},
{user_model.NotificationsOnMention, 3},
{user_model.NotificationsOnMention, 4},
{user_model.NotificationsEnabled, 5},
{user_model.NotificationsEnabled, 6},
{user_model.NotificationsDisabled, 7},
{user_model.NotificationsEnabled, 8},
{user_model.NotificationsOnMention, 9},
} {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.userID})
assert.Equal(t, test.expected, user.UINotificationsPreference)
}
}
func TestHashPasswordDeterministic(t *testing.T) {
b := make([]byte, 16)
u := &user_model.User{}

View File

@ -11,6 +11,7 @@ import (
var Admin struct {
DisableRegularOrgCreation bool
DefaultEmailNotification string
DefaultUINotification string
UserDisabledFeatures container.Set[string]
ExternalUserDisableFeatures container.Set[string]
}
@ -19,6 +20,7 @@ func loadAdminFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("admin")
Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
Admin.DefaultUINotification = sec.Key("DEFAULT_UI_NOTIFICATIONS").MustString("enabled")
Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...)
}

View File

@ -680,6 +680,7 @@ block.list.none = You have not blocked any users.
profile = Profile
account = Account
appearance = Appearance
notifications = Notifications
password = Password
security = Security
avatar = Avatar
@ -694,6 +695,7 @@ account_link = Linked Accounts
organization = Organizations
uid = UID
webauthn = Two-Factor Authentication (Security Keys)
manage_notifications = Manage Notifications
public_profile = Public Profile
biography_placeholder = Tell us a little bit about yourself! (You can use Markdown)
@ -763,6 +765,8 @@ manage_emails = Manage Email Addresses
manage_themes = Select default theme
manage_openid = Manage OpenID Addresses
email_desc = Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations.
email_notifications_desc = Choose what type of Email notifications you want to receive.
ui_notifications_desc = Choose what type of UI notifications you want to receive.
theme_desc = This will be your default theme across the site.
theme_colorblindness_help = Colorblindness Theme Support
theme_colorblindness_prompt = Gitea just gets some themes with basic colorblindness support, which only have a few colors defined. The work is still in progress. More improvements could be done by defining more colors in the theme CSS files.
@ -789,6 +793,7 @@ add_openid = Add OpenID URI
add_email_confirmation_sent = A confirmation email has been sent to "%s". Please check your inbox within the next %s to confirm your email address.
add_email_success = The new email address has been added.
email_preference_set_success = Email preference has been set successfully.
ui_preference_set_success = UI preference has been set successfully.
add_openid_success = The new OpenID address has been added.
keep_email_private = Hide Email Address
keep_email_private_popup = This will hide your email address from your profile, as well as when you make a pull request or edit a file using the web interface. Pushed commits will not be modified. Use %s in commits to associate them with your account.
@ -976,11 +981,12 @@ confirm_delete_account = Confirm Deletion
delete_account_title = Delete User Account
delete_account_desc = Are you sure you want to permanently delete this user account?
email_notifications.enable = Enable Email Notifications
email_notifications.onmention = Only Email on Mention
email_notifications.disable = Disable Email Notifications
email_notifications.submit = Set Email Preference
email_notifications.andyourown = And Your Own Notifications
notifications.enable = Enable Notifications
notifications.onmention = Only Notify on Mention
notifications.andyourown = Receive All And Your Own
notifications.disable = Disable Notifications
notifications.submit_email = Set Email Preference
notifications.submit_ui = Set UI Preference
visibility = User visibility
visibility.public = Public

View File

@ -154,10 +154,10 @@ func EmailPost(ctx *context.Context) {
// Set Email Notification Preference
if ctx.FormString("_method") == "NOTIFICATION" {
preference := ctx.FormString("preference")
if !(preference == user_model.EmailNotificationsEnabled ||
preference == user_model.EmailNotificationsOnMention ||
preference == user_model.EmailNotificationsDisabled ||
preference == user_model.EmailNotificationsAndYourOwn) {
if !(preference == user_model.NotificationsEnabled ||
preference == user_model.NotificationsOnMention ||
preference == user_model.NotificationsDisabled ||
preference == user_model.NotificationsAndYourOwn) {
log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name)
ctx.ServerError("SetEmailPreference", errors.New("option unrecognized"))
return
@ -316,6 +316,8 @@ func loadAccountData(ctx *context.Context) {
}
ctx.Data["Emails"] = emails
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
ctx.Data["UINotificationsPreference"] = ctx.Doer.UINotificationsPreference
ctx.Data["ActivationsPending"] = pendingActivation
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)

View File

@ -0,0 +1,116 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"errors"
"net/http"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/user"
)
const (
tplSettingsNotifications base.TplName = "user/settings/notifications"
)
// Notifications render manage access token page
func Notifications(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings.notifications")
ctx.Data["PageIsSettingsNotifications"] = true
ctx.Data["Email"] = ctx.Doer.Email
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
loadNotificationsData(ctx)
ctx.HTML(http.StatusOK, tplSettingsNotifications)
}
// NotificationPost response for change user's notification preferences
func NotificationPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsNotifications"] = true
// Set Email Notification Preference
if ctx.FormString("_method") == "EMAIL" {
preference := ctx.FormString("preference")
if !(preference == user_model.NotificationsEnabled ||
preference == user_model.NotificationsOnMention ||
preference == user_model.NotificationsDisabled ||
preference == user_model.NotificationsAndYourOwn) {
log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name)
ctx.ServerError("SetEmailPreference", errors.New("option unrecognized"))
return
}
opts := &user.UpdateOptions{
EmailNotificationsPreference: optional.Some(preference),
}
if err := user.UpdateUser(ctx, ctx.Doer, opts); err != nil {
log.Error("Set Email Notifications failed: %v", err)
ctx.ServerError("UpdateUser", err)
return
}
log.Trace("Email notifications preference made %s: %s", preference, ctx.Doer.Name)
ctx.Flash.Success(ctx.Tr("settings.email_preference_set_success"))
ctx.Redirect(setting.AppSubURL + "/user/settings/notifications")
return
// Set UI Notification Preference
} else if ctx.FormString("_method") == "UI" {
preference := ctx.FormString("preference")
if !(preference == user_model.NotificationsEnabled ||
preference == user_model.NotificationsOnMention ||
preference == user_model.NotificationsDisabled ||
preference == user_model.NotificationsAndYourOwn) {
log.Error("UI notifications preference change returned unrecognized option %s: %s", preference, ctx.Doer.Name)
ctx.ServerError("SetUIPreference", errors.New("option unrecognized"))
return
}
opts := &user.UpdateOptions{
UINotificationsPreference: optional.Some(preference),
}
if err := user.UpdateUser(ctx, ctx.Doer, opts); err != nil {
log.Error("Set UI Notifications failed: %v", err)
ctx.ServerError("UpdateUser", err)
return
}
log.Trace("UI notifications preference made %s: %s", preference, ctx.Doer.Name)
ctx.Flash.Success(ctx.Tr("settings.ui_preference_set_success"))
ctx.Redirect(setting.AppSubURL + "/user/settings/notifications")
return
}
if ctx.HasError() {
loadAccountData(ctx)
ctx.HTML(http.StatusOK, tplSettingsAccount)
return
}
}
func loadNotificationsData(ctx *context.Context) {
emlist, err := user_model.GetEmailAddresses(ctx, ctx.Doer.ID)
if err != nil {
ctx.ServerError("GetEmailAddresses", err)
return
}
type UserEmail struct {
user_model.EmailAddress
}
emails := make([]*UserEmail, len(emlist))
for i, em := range emlist {
if !em.IsActivated {
continue
}
var email UserEmail
email.EmailAddress = *em
emails[i] = &email
}
ctx.Data["Emails"] = emails
ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference
ctx.Data["UINotificationsPreference"] = ctx.Doer.UINotificationsPreference
}

View File

@ -560,6 +560,11 @@ func registerRoutes(m *web.Route) {
m.Get("/change_password", auth.MustChangePassword)
m.Post("/change_password", web.Bind(forms.MustChangePasswordForm{}), auth.MustChangePasswordPost)
m.Post("/avatar", web.Bind(forms.AvatarForm{}), user_setting.AvatarPost)
m.Group("/notifications", func() {
m.Get("", user_setting.Notifications)
m.Post("", user_setting.NotificationPost)
})
m.Post("/avatar/delete", user_setting.DeleteAvatar)
m.Group("/account", func() {
m.Combo("").Get(user_setting.Account).Post(web.Bind(forms.ChangePasswordForm{}), user_setting.AccountPost)

View File

@ -170,11 +170,12 @@ func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) (
Email: email,
Language: cfg.DefaultLanguage,
}
emailNotificationPreference := user_model.EmailNotificationsDisabled
notificationPreference := user_model.NotificationsDisabled
overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: optional.Some(cfg.AutoActivateUsers),
KeepEmailPrivate: optional.Some(true),
EmailNotificationsPreference: &emailNotificationPreference,
EmailNotificationsPreference: &notificationPreference,
UINotificationsPreference: &notificationPreference,
}
if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
return nil, err

View File

@ -93,7 +93,7 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo
visited := make(container.Set[int64], len(unfiltered)+len(mentions)+1)
// Avoid mailing the doer
if ctx.Doer.EmailNotificationsPreference != user_model.EmailNotificationsAndYourOwn && !ctx.ForceDoerNotification {
if ctx.Doer.EmailNotificationsPreference != user_model.NotificationsAndYourOwn && !ctx.ForceDoerNotification {
visited.Add(ctx.Doer.ID)
}
@ -134,9 +134,9 @@ func mailIssueCommentBatch(ctx *mailCommentContext, users []*user_model.User, vi
}
// At this point we exclude:
// user that don't have all mails enabled or users only get mail on mention and this is one ...
if !(user.EmailNotificationsPreference == user_model.EmailNotificationsEnabled ||
user.EmailNotificationsPreference == user_model.EmailNotificationsAndYourOwn ||
fromMention && user.EmailNotificationsPreference == user_model.EmailNotificationsOnMention) {
if !(user.EmailNotificationsPreference == user_model.NotificationsEnabled ||
user.EmailNotificationsPreference == user_model.NotificationsAndYourOwn ||
fromMention && user.EmailNotificationsPreference == user_model.NotificationsOnMention) {
continue
}

View File

@ -114,7 +114,7 @@ func (m *mailNotifier) PullRequestCodeComment(ctx context.Context, pr *issues_mo
func (m *mailNotifier) IssueChangeAssignee(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, assignee *user_model.User, removed bool, comment *issues_model.Comment) {
// mail only sent to added assignees and not self-assignee
if !removed && doer.ID != assignee.ID && assignee.EmailNotificationsPreference != user_model.EmailNotificationsDisabled {
if !removed && doer.ID != assignee.ID && assignee.EmailNotificationsPreference != user_model.NotificationsDisabled {
ct := fmt.Sprintf("Assigned #%d.", issue.Index)
if err := SendIssueAssignedMail(ctx, issue, doer, ct, comment, []*user_model.User{assignee}); err != nil {
log.Error("Error in SendIssueAssignedMail for issue[%d] to assignee[%d]: %v", issue.ID, assignee.ID, err)
@ -123,7 +123,7 @@ func (m *mailNotifier) IssueChangeAssignee(ctx context.Context, doer *user_model
}
func (m *mailNotifier) PullRequestReviewRequest(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, reviewer *user_model.User, isRequest bool, comment *issues_model.Comment) {
if isRequest && doer.ID != reviewer.ID && reviewer.EmailNotificationsPreference != user_model.EmailNotificationsDisabled {
if isRequest && doer.ID != reviewer.ID && reviewer.EmailNotificationsPreference != user_model.NotificationsDisabled {
ct := fmt.Sprintf("Requested to review %s.", issue.HTMLURL())
if err := SendIssueAssignedMail(ctx, issue, doer, ct, comment, []*user_model.User{reviewer}); err != nil {
log.Error("Error in SendIssueAssignedMail for issue[%d] to reviewer[%d]: %v", issue.ID, reviewer.ID, err)

View File

@ -75,6 +75,11 @@ func (ns *notificationService) CreateIssueComment(ctx context.Context, doer *use
}
_ = ns.issueQueue.Push(opts)
for _, mention := range mentions {
// Avoid notifying users according to their setting.
if mention.UINotificationsPreference != user_model.NotificationsEnabled &&
mention.UINotificationsPreference != user_model.NotificationsOnMention {
continue
}
opts := issueNotificationOpts{
IssueID: issue.ID,
NotificationAuthorID: doer.ID,
@ -92,7 +97,13 @@ func (ns *notificationService) NewIssue(ctx context.Context, issue *issues_model
IssueID: issue.ID,
NotificationAuthorID: issue.Poster.ID,
})
for _, mention := range mentions {
// Avoid notifying users according to their setting.
if mention.UINotificationsPreference != user_model.NotificationsEnabled &&
mention.UINotificationsPreference != user_model.NotificationsOnMention {
continue
}
_ = ns.issueQueue.Push(issueNotificationOpts{
IssueID: issue.ID,
NotificationAuthorID: issue.Poster.ID,
@ -145,6 +156,15 @@ func (ns *notificationService) NewPullRequest(ctx context.Context, pr *issues_mo
return
}
for _, id := range repoWatchers {
// Exclude users based on their notification prefs.
user, err := user_model.GetUserByID(ctx, id)
if err != nil {
log.Error("GetUserByID: %v", err)
return
}
if user.UINotificationsPreference != user_model.NotificationsEnabled {
continue
}
toNotify.Add(id)
}
issueParticipants, err := issues_model.GetParticipantsIDsByIssueID(ctx, pr.IssueID)
@ -153,12 +173,30 @@ func (ns *notificationService) NewPullRequest(ctx context.Context, pr *issues_mo
return
}
for _, id := range issueParticipants {
// Exclude users based on their notification prefs.
user, err := user_model.GetUserByID(ctx, id)
if err != nil {
log.Error("GetUserByID: %v", err)
return
}
if user.UINotificationsPreference != user_model.NotificationsEnabled {
continue
}
toNotify.Add(id)
}
delete(toNotify, pr.Issue.PosterID)
// Check if user should not be mentioned on their own actions.
if pr.Issue.Poster.UINotificationsPreference != user_model.NotificationsAndYourOwn {
delete(toNotify, pr.Issue.PosterID)
}
for _, mention := range mentions {
// Exclude users based on their notification preferences.
if mention.UINotificationsPreference != user_model.NotificationsEnabled &&
mention.UINotificationsPreference != user_model.NotificationsOnMention {
continue
}
toNotify.Add(mention.ID)
}
// Exclude users based on their notification preferences.
for receiverID := range toNotify {
_ = ns.issueQueue.Push(issueNotificationOpts{
IssueID: pr.Issue.ID,
@ -178,6 +216,11 @@ func (ns *notificationService) PullRequestReview(ctx context.Context, pr *issues
}
_ = ns.issueQueue.Push(opts)
for _, mention := range mentions {
// Exclude users based on their notification preferences.
if mention.UINotificationsPreference != user_model.NotificationsEnabled &&
mention.UINotificationsPreference != user_model.NotificationsOnMention {
continue
}
opts := issueNotificationOpts{
IssueID: pr.Issue.ID,
NotificationAuthorID: r.Reviewer.ID,
@ -192,6 +235,11 @@ func (ns *notificationService) PullRequestReview(ctx context.Context, pr *issues
func (ns *notificationService) PullRequestCodeComment(ctx context.Context, pr *issues_model.PullRequest, c *issues_model.Comment, mentions []*user_model.User) {
for _, mention := range mentions {
// Exclude users based on their notification preferences.
if mention.UINotificationsPreference != user_model.NotificationsEnabled &&
mention.UINotificationsPreference != user_model.NotificationsOnMention {
continue
}
_ = ns.issueQueue.Push(issueNotificationOpts{
IssueID: pr.Issue.ID,
NotificationAuthorID: c.Poster.ID,

View File

@ -35,6 +35,7 @@ type UpdateOptions struct {
IsActive optional.Option[bool]
IsAdmin optional.Option[bool]
EmailNotificationsPreference optional.Option[string]
UINotificationsPreference optional.Option[string]
SetLastLogin bool
RepoAdminChangeTeamAccess optional.Option[bool]
}
@ -152,6 +153,12 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er
cols = append(cols, "email_notifications_preference")
}
if opts.UINotificationsPreference.Has() {
u.UINotificationsPreference = opts.UINotificationsPreference.Value()
cols = append(cols, "ui_notifications_preference")
}
if opts.SetLastLogin {
u.SetLastLogin()

View File

@ -46,6 +46,7 @@ func TestUpdateUser(t *testing.T) {
DiffViewStyle: optional.Some("split"),
AllowCreateOrganization: optional.Some(false),
EmailNotificationsPreference: optional.Some("disabled"),
UINotificationsPreference: optional.Some("disabled"),
SetLastLogin: true,
}
assert.NoError(t, UpdateUser(db.DefaultContext, user, opts))
@ -68,6 +69,7 @@ func TestUpdateUser(t *testing.T) {
assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle)
assert.Equal(t, opts.AllowCreateOrganization.Value(), user.AllowCreateOrganization)
assert.Equal(t, opts.EmailNotificationsPreference.Value(), user.EmailNotificationsPreference)
assert.Equal(t, opts.UINotificationsPreference.Value(), user.UINotificationsPreference)
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28})
assert.Equal(t, opts.KeepEmailPrivate.Value(), user.KeepEmailPrivate)
@ -88,6 +90,7 @@ func TestUpdateUser(t *testing.T) {
assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle)
assert.Equal(t, opts.AllowCreateOrganization.Value(), user.AllowCreateOrganization)
assert.Equal(t, opts.EmailNotificationsPreference.Value(), user.EmailNotificationsPreference)
assert.Equal(t, opts.UINotificationsPreference.Value(), user.UINotificationsPreference)
}
func TestUpdateAuth(t *testing.T) {

View File

@ -40,29 +40,6 @@
</h4>
<div class="ui attached segment">
<div class="ui list">
{{if $.EnableNotifyMail}}
<div class="item">
<div class="tw-mb-2">{{ctx.Locale.Tr "settings.email_desc"}}</div>
<form action="{{AppSubUrl}}/user/settings/account/email" class="ui form" method="post">
{{$.CsrfTokenHtml}}
<input name="_method" type="hidden" value="NOTIFICATION">
<div class="tw-flex tw-flex-wrap tw-gap-2">
<div class="ui selection dropdown">
<input name="preference" type="hidden" value="{{.EmailNotificationsPreference}}">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="text"></div>
<div class="menu">
<div data-value="enabled" class="{{if eq .EmailNotificationsPreference "enabled"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.enable"}}</div>
<div data-value="andyourown" class="{{if eq .EmailNotificationsPreference "andyourown"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.andyourown"}}</div>
<div data-value="onmention" class="{{if eq .EmailNotificationsPreference "onmention"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.onmention"}}</div>
<div data-value="disabled" class="{{if eq .EmailNotificationsPreference "disabled"}}active selected {{end}}item">{{ctx.Locale.Tr "settings.email_notifications.disable"}}</div>
</div>
</div>
<button class="ui primary button">{{ctx.Locale.Tr "settings.email_notifications.submit"}}</button>
</div>
</form>
</div>
{{end}}
{{range .Emails}}
<div class="item">
{{if not .IsPrimary}}

View File

@ -10,6 +10,9 @@
<a class="{{if .PageIsSettingsAppearance}}active {{end}}item" href="{{AppSubUrl}}/user/settings/appearance">
{{ctx.Locale.Tr "settings.appearance"}}
</a>
<a class="{{if .PageIsSettingsNotifications}}active {{end}}item" href="{{AppSubUrl}}/user/settings/notifications">
{{ctx.Locale.Tr "settings.notifications"}}
</a>
<a class="{{if .PageIsSettingsSecurity}}active {{end}}item" href="{{AppSubUrl}}/user/settings/security">
{{ctx.Locale.Tr "settings.security"}}
</a>

View File

@ -0,0 +1,96 @@
{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings notifications")}}
<div class="user-setting-content">
<h4 class="ui top attached header">
{{ctx.Locale.Tr "settings.manage_notifications"}}
</h4>
<div class="ui attached segment">
<div class="ui list">
{{if $.EnableNotifyMail}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "settings.email_notifications_desc"}}</h5>
<form action="{{AppSubUrl}}/user/settings/notifications" class="ui form" method="post">
{{$.CsrfTokenHtml}}
<input name="_method" type="hidden" value="EMAIL">
<div class="field">
<div class="ui radio checkbox">
<input name="preference" type="radio" value="disabled" class="toggle-target-disabled" {{if eq
.EmailNotificationsPreference "disabled"}}checked{{end}}>
<label>{{ctx.Locale.Tr "settings.notifications.disable"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="preference" type="radio" value="enabled" class="toggle-target-disabled" {{if eq
.EmailNotificationsPreference "enabled"}}checked{{end}}>
<label>{{ctx.Locale.Tr "settings.notifications.enable"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="preference" type="radio" value="andyourown" class="toggle-target-disabled" {{if eq
.EmailNotificationsPreference "andyourown"}}checked{{end}}>
<label>{{ctx.Locale.Tr "settings.notifications.andyourown"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="preference" type="radio" value="onmention" class="toggle-target-disabled" {{if eq
.EmailNotificationsPreference "onmention"}}checked{{end}}>
<label>{{ctx.Locale.Tr "settings.notifications.onmention"}}</label>
</div>
<br>
<br>
<button class="ui primary button">{{ctx.Locale.Tr "settings.notifications.submit_email"}}</button>
</form>
</div>
{{end}}
</div>
</div>
<div class="ui attached segment">
<div class="ui list">
<h5 class="ui dividing header">{{ctx.Locale.Tr "settings.ui_notifications_desc"}}</h5>
<form action="{{AppSubUrl}}/user/settings/notifications" class="ui form" method="post">
{{$.CsrfTokenHtml}}
<input name="_method" type="hidden" value="UI">
<div class="field">
<div class="ui radio checkbox">
<input name="preference" type="radio" value="disabled" class="toggle-target-disabled" {{if eq
.UINotificationsPreference "disabled"}}checked{{end}}>
<label>{{ctx.Locale.Tr "settings.notifications.disable"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="preference" type="radio" value="enabled" class="toggle-target-disabled" {{if eq
.UINotificationsPreference "enabled"}}checked{{end}}>
<label>{{ctx.Locale.Tr "settings.notifications.enable"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="preference" type="radio" value="andyourown" class="toggle-target-disabled" {{if eq
.UINotificationsPreference "andyourown"}}checked{{end}}>
<label>{{ctx.Locale.Tr "settings.notifications.andyourown"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="preference" type="radio" value="onmention" class="toggle-target-disabled" {{if eq
.UINotificationsPreference "onmention"}}checked{{end}}>
<label>{{ctx.Locale.Tr "settings.notifications.onmention"}}</label>
</div>
<br>
<br>
<button class="ui primary button">{{ctx.Locale.Tr "settings.notifications.submit_ui"}}</button>
</form>
</div>
</div>
</div>
{{template "user/settings/layout_footer" .}}