settings.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package form // import "miniflux.app/v2/internal/ui/form"
  4. import (
  5. "net/http"
  6. "strconv"
  7. "miniflux.app/v2/internal/config"
  8. "miniflux.app/v2/internal/locale"
  9. "miniflux.app/v2/internal/model"
  10. "miniflux.app/v2/internal/validator"
  11. )
  12. // MarkReadBehavior list all possible behaviors for automatically marking an entry as read
  13. type MarkReadBehavior string
  14. const (
  15. NoAutoMarkAsRead MarkReadBehavior = "no-auto"
  16. MarkAsReadOnView MarkReadBehavior = "on-view"
  17. MarkAsReadOnViewButWaitForPlayerCompletion MarkReadBehavior = "on-view-but-wait-for-player-completion"
  18. MarkAsReadOnlyOnPlayerCompletion MarkReadBehavior = "on-player-completion"
  19. )
  20. // SettingsForm represents the settings form.
  21. type SettingsForm struct {
  22. Username string
  23. Password string
  24. Confirmation string
  25. Theme string
  26. Language string
  27. Timezone string
  28. EntryDirection string
  29. EntryOrder string
  30. EntriesPerPage int
  31. KeyboardShortcuts bool
  32. ShowReadingTime bool
  33. CustomCSS string
  34. CustomJS string
  35. ExternalFontHosts string
  36. EntrySwipe bool
  37. GestureNav string
  38. DisplayMode string
  39. DefaultReadingSpeed int
  40. CJKReadingSpeed int
  41. DefaultHomePage string
  42. CategoriesSortingOrder string
  43. MarkReadOnView bool
  44. // MarkReadBehavior is a string representation of the MarkReadOnView and MarkReadOnMediaPlayerCompletion fields together
  45. MarkReadBehavior MarkReadBehavior
  46. MediaPlaybackRate float64
  47. BlockFilterEntryRules string
  48. KeepFilterEntryRules string
  49. AlwaysOpenExternalLinks bool
  50. }
  51. // MarkAsReadBehavior returns the MarkReadBehavior from the given MarkReadOnView and MarkReadOnMediaPlayerCompletion values.
  52. // Useful to convert the values from the User model to the form
  53. func MarkAsReadBehavior(markReadOnView, markReadOnMediaPlayerCompletion bool) MarkReadBehavior {
  54. switch {
  55. case markReadOnView && !markReadOnMediaPlayerCompletion:
  56. return MarkAsReadOnView
  57. case markReadOnView && markReadOnMediaPlayerCompletion:
  58. return MarkAsReadOnViewButWaitForPlayerCompletion
  59. case !markReadOnView && markReadOnMediaPlayerCompletion:
  60. return MarkAsReadOnlyOnPlayerCompletion
  61. case !markReadOnView && !markReadOnMediaPlayerCompletion:
  62. fallthrough // Explicit defaulting
  63. default:
  64. return NoAutoMarkAsRead
  65. }
  66. }
  67. // ExtractMarkAsReadBehavior returns the MarkReadOnView and MarkReadOnMediaPlayerCompletion values from the given MarkReadBehavior.
  68. // Useful to extract the values from the form to the User model
  69. func ExtractMarkAsReadBehavior(behavior MarkReadBehavior) (markReadOnView, markReadOnMediaPlayerCompletion bool) {
  70. switch behavior {
  71. case MarkAsReadOnView:
  72. return true, false
  73. case MarkAsReadOnViewButWaitForPlayerCompletion:
  74. return true, true
  75. case MarkAsReadOnlyOnPlayerCompletion:
  76. return false, true
  77. case NoAutoMarkAsRead:
  78. fallthrough // Explicit defaulting
  79. default:
  80. return false, false
  81. }
  82. }
  83. // Merge updates the fields of the given user.
  84. func (s *SettingsForm) Merge(user *model.User) *model.User {
  85. if !config.Opts.DisableLocalAuth() {
  86. user.Username = s.Username
  87. }
  88. user.Theme = s.Theme
  89. user.Language = s.Language
  90. user.Timezone = s.Timezone
  91. user.EntryDirection = s.EntryDirection
  92. user.EntryOrder = s.EntryOrder
  93. user.EntriesPerPage = s.EntriesPerPage
  94. user.KeyboardShortcuts = s.KeyboardShortcuts
  95. user.ShowReadingTime = s.ShowReadingTime
  96. user.Stylesheet = s.CustomCSS
  97. user.CustomJS = s.CustomJS
  98. user.ExternalFontHosts = s.ExternalFontHosts
  99. user.EntrySwipe = s.EntrySwipe
  100. user.GestureNav = s.GestureNav
  101. user.DisplayMode = s.DisplayMode
  102. user.CJKReadingSpeed = s.CJKReadingSpeed
  103. user.DefaultReadingSpeed = s.DefaultReadingSpeed
  104. user.DefaultHomePage = s.DefaultHomePage
  105. user.CategoriesSortingOrder = s.CategoriesSortingOrder
  106. user.MediaPlaybackRate = s.MediaPlaybackRate
  107. user.BlockFilterEntryRules = s.BlockFilterEntryRules
  108. user.KeepFilterEntryRules = s.KeepFilterEntryRules
  109. user.AlwaysOpenExternalLinks = s.AlwaysOpenExternalLinks
  110. MarkReadOnView, MarkReadOnMediaPlayerCompletion := ExtractMarkAsReadBehavior(s.MarkReadBehavior)
  111. user.MarkReadOnView = MarkReadOnView
  112. user.MarkReadOnMediaPlayerCompletion = MarkReadOnMediaPlayerCompletion
  113. if s.Password != "" {
  114. user.Password = s.Password
  115. }
  116. return user
  117. }
  118. // Validate makes sure the form values are valid.
  119. func (s *SettingsForm) Validate() *locale.LocalizedError {
  120. if (s.Username == "" && !config.Opts.DisableLocalAuth()) || s.Theme == "" || s.Language == "" || s.Timezone == "" || s.EntryDirection == "" || s.DisplayMode == "" || s.DefaultHomePage == "" {
  121. return locale.NewLocalizedError("error.settings_mandatory_fields")
  122. }
  123. if s.CJKReadingSpeed <= 0 || s.DefaultReadingSpeed <= 0 {
  124. return locale.NewLocalizedError("error.settings_reading_speed_is_positive")
  125. }
  126. if s.Confirmation == "" {
  127. // Firefox insists on auto-completing the password field.
  128. // If the confirmation field is blank, the user probably
  129. // didn't intend to change their password.
  130. s.Password = ""
  131. } else if s.Password != "" {
  132. if s.Password != s.Confirmation {
  133. return locale.NewLocalizedError("error.different_passwords")
  134. }
  135. }
  136. if s.MediaPlaybackRate < 0.25 || s.MediaPlaybackRate > 4 {
  137. return locale.NewLocalizedError("error.settings_media_playback_rate_range")
  138. }
  139. if s.ExternalFontHosts != "" {
  140. if !validator.IsValidDomainList(s.ExternalFontHosts) {
  141. return locale.NewLocalizedError("error.settings_invalid_domain_list")
  142. }
  143. }
  144. return nil
  145. }
  146. // NewSettingsForm returns a new SettingsForm.
  147. func NewSettingsForm(r *http.Request) *SettingsForm {
  148. entriesPerPage, err := strconv.ParseInt(r.FormValue("entries_per_page"), 10, 0)
  149. if err != nil {
  150. entriesPerPage = 0
  151. }
  152. defaultReadingSpeed, err := strconv.ParseInt(r.FormValue("default_reading_speed"), 10, 0)
  153. if err != nil {
  154. defaultReadingSpeed = 0
  155. }
  156. cjkReadingSpeed, err := strconv.ParseInt(r.FormValue("cjk_reading_speed"), 10, 0)
  157. if err != nil {
  158. cjkReadingSpeed = 0
  159. }
  160. mediaPlaybackRate, err := strconv.ParseFloat(r.FormValue("media_playback_rate"), 64)
  161. if err != nil {
  162. mediaPlaybackRate = 1
  163. }
  164. return &SettingsForm{
  165. Username: r.FormValue("username"),
  166. Password: r.FormValue("password"),
  167. Confirmation: r.FormValue("confirmation"),
  168. Theme: r.FormValue("theme"),
  169. Language: r.FormValue("language"),
  170. Timezone: r.FormValue("timezone"),
  171. EntryDirection: r.FormValue("entry_direction"),
  172. EntryOrder: r.FormValue("entry_order"),
  173. EntriesPerPage: int(entriesPerPage),
  174. KeyboardShortcuts: r.FormValue("keyboard_shortcuts") == "1",
  175. ShowReadingTime: r.FormValue("show_reading_time") == "1",
  176. CustomCSS: r.FormValue("custom_css"),
  177. CustomJS: r.FormValue("custom_js"),
  178. ExternalFontHosts: r.FormValue("external_font_hosts"),
  179. EntrySwipe: r.FormValue("entry_swipe") == "1",
  180. GestureNav: r.FormValue("gesture_nav"),
  181. DisplayMode: r.FormValue("display_mode"),
  182. DefaultReadingSpeed: int(defaultReadingSpeed),
  183. CJKReadingSpeed: int(cjkReadingSpeed),
  184. DefaultHomePage: r.FormValue("default_home_page"),
  185. CategoriesSortingOrder: r.FormValue("categories_sorting_order"),
  186. MarkReadOnView: r.FormValue("mark_read_on_view") == "1",
  187. MarkReadBehavior: MarkReadBehavior(r.FormValue("mark_read_behavior")),
  188. MediaPlaybackRate: mediaPlaybackRate,
  189. BlockFilterEntryRules: r.FormValue("block_filter_entry_rules"),
  190. KeepFilterEntryRules: r.FormValue("keep_filter_entry_rules"),
  191. AlwaysOpenExternalLinks: r.FormValue("always_open_external_links") == "1",
  192. }
  193. }