1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2// See LICENSE.txt for license information. 3 4package model 5 6import ( 7 "encoding/json" 8 "net/http" 9 "regexp" 10 "strings" 11 "unicode/utf8" 12) 13 14const ( 15 PreferenceCategoryDirectChannelShow = "direct_channel_show" 16 PreferenceCategoryGroupChannelShow = "group_channel_show" 17 PreferenceCategoryTutorialSteps = "tutorial_step" 18 PreferenceCategoryAdvancedSettings = "advanced_settings" 19 PreferenceCategoryFlaggedPost = "flagged_post" 20 PreferenceCategoryFavoriteChannel = "favorite_channel" 21 PreferenceCategorySidebarSettings = "sidebar_settings" 22 23 PreferenceCategoryDisplaySettings = "display_settings" 24 PreferenceNameCollapsedThreadsEnabled = "collapsed_reply_threads" 25 PreferenceNameChannelDisplayMode = "channel_display_mode" 26 PreferenceNameCollapseSetting = "collapse_previews" 27 PreferenceNameMessageDisplay = "message_display" 28 PreferenceNameNameFormat = "name_format" 29 PreferenceNameUseMilitaryTime = "use_military_time" 30 PreferenceRecommendedNextSteps = "recommended_next_steps" 31 32 PreferenceCategoryTheme = "theme" 33 // the name for theme props is the team id 34 35 PreferenceCategoryAuthorizedOAuthApp = "oauth_app" 36 // the name for oauth_app is the client_id and value is the current scope 37 38 PreferenceCategoryLast = "last" 39 PreferenceNameLastChannel = "channel" 40 PreferenceNameLastTeam = "team" 41 42 PreferenceCategoryCustomStatus = "custom_status" 43 PreferenceNameRecentCustomStatuses = "recent_custom_statuses" 44 PreferenceNameCustomStatusTutorialState = "custom_status_tutorial_state" 45 46 PreferenceCustomStatusModalViewed = "custom_status_modal_viewed" 47 48 PreferenceCategoryNotifications = "notifications" 49 PreferenceNameEmailInterval = "email_interval" 50 51 PreferenceEmailIntervalNoBatchingSeconds = "30" // the "immediate" setting is actually 30s 52 PreferenceEmailIntervalBatchingSeconds = "900" // fifteen minutes is 900 seconds 53 PreferenceEmailIntervalImmediately = "immediately" 54 PreferenceEmailIntervalFifteen = "fifteen" 55 PreferenceEmailIntervalFifteenAsSeconds = "900" 56 PreferenceEmailIntervalHour = "hour" 57 PreferenceEmailIntervalHourAsSeconds = "3600" 58) 59 60type Preference struct { 61 UserId string `json:"user_id"` 62 Category string `json:"category"` 63 Name string `json:"name"` 64 Value string `json:"value"` 65} 66 67type Preferences []Preference 68 69func (o *Preference) IsValid() *AppError { 70 if !IsValidId(o.UserId) { 71 return NewAppError("Preference.IsValid", "model.preference.is_valid.id.app_error", nil, "user_id="+o.UserId, http.StatusBadRequest) 72 } 73 74 if o.Category == "" || len(o.Category) > 32 { 75 return NewAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category, http.StatusBadRequest) 76 } 77 78 if len(o.Name) > 32 { 79 return NewAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name, http.StatusBadRequest) 80 } 81 82 if utf8.RuneCountInString(o.Value) > 2000 { 83 return NewAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value, http.StatusBadRequest) 84 } 85 86 if o.Category == PreferenceCategoryTheme { 87 var unused map[string]string 88 if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&unused); err != nil { 89 return NewAppError("Preference.IsValid", "model.preference.is_valid.theme.app_error", nil, "value="+o.Value, http.StatusBadRequest) 90 } 91 } 92 93 return nil 94} 95 96func (o *Preference) PreUpdate() { 97 if o.Category == PreferenceCategoryTheme { 98 // decode the value of theme (a map of strings to string) and eliminate any invalid values 99 var props map[string]string 100 if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&props); err != nil { 101 // just continue, the invalid preference value should get caught by IsValid before saving 102 return 103 } 104 105 colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`) 106 107 // blank out any invalid theme values 108 for name, value := range props { 109 if name == "image" || name == "type" || name == "codeTheme" { 110 continue 111 } 112 113 if !colorPattern.MatchString(value) { 114 props[name] = "#ffffff" 115 } 116 } 117 118 if b, err := json.Marshal(props); err == nil { 119 o.Value = string(b) 120 } 121 } 122} 123