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