settings.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. var (
  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. }
  50. // MarkAsReadBehavior returns the MarkReadBehavior from the given MarkReadOnView and MarkReadOnMediaPlayerCompletion values.
  51. // Useful to convert the values from the User model to the form
  52. func MarkAsReadBehavior(markReadOnView, markReadOnMediaPlayerCompletion bool) MarkReadBehavior {
  53. switch {
  54. case markReadOnView && !markReadOnMediaPlayerCompletion:
  55. return MarkAsReadOnView
  56. case markReadOnView && markReadOnMediaPlayerCompletion:
  57. return MarkAsReadOnViewButWaitForPlayerCompletion
  58. case !markReadOnView && markReadOnMediaPlayerCompletion:
  59. return MarkAsReadOnlyOnPlayerCompletion
  60. case !markReadOnView && !markReadOnMediaPlayerCompletion:
  61. fallthrough // Explicit defaulting
  62. default:
  63. return NoAutoMarkAsRead
  64. }
  65. }
  66. // ExtractMarkAsReadBehavior returns the MarkReadOnView and MarkReadOnMediaPlayerCompletion values from the given MarkReadBehavior.
  67. // Useful to extract the values from the form to the User model
  68. func ExtractMarkAsReadBehavior(behavior MarkReadBehavior) (markReadOnView, markReadOnMediaPlayerCompletion bool) {
  69. switch behavior {
  70. case MarkAsReadOnView:
  71. return true, false
  72. case MarkAsReadOnViewButWaitForPlayerCompletion:
  73. return true, true
  74. case MarkAsReadOnlyOnPlayerCompletion:
  75. return false, true
  76. case NoAutoMarkAsRead:
  77. fallthrough // Explicit defaulting
  78. default:
  79. return false, false
  80. }
  81. }
  82. // Merge updates the fields of the given user.
  83. func (s *SettingsForm) Merge(user *model.User) *model.User {
  84. if !config.Opts.DisableLocalAuth() {
  85. user.Username = s.Username
  86. }
  87. user.Theme = s.Theme
  88. user.Language = s.Language
  89. user.Timezone = s.Timezone
  90. user.EntryDirection = s.EntryDirection
  91. user.EntryOrder = s.EntryOrder
  92. user.EntriesPerPage = s.EntriesPerPage
  93. user.KeyboardShortcuts = s.KeyboardShortcuts
  94. user.ShowReadingTime = s.ShowReadingTime
  95. user.Stylesheet = s.CustomCSS
  96. user.CustomJS = s.CustomJS
  97. user.ExternalFontHosts = s.ExternalFontHosts
  98. user.EntrySwipe = s.EntrySwipe
  99. user.GestureNav = s.GestureNav
  100. user.DisplayMode = s.DisplayMode
  101. user.CJKReadingSpeed = s.CJKReadingSpeed
  102. user.DefaultReadingSpeed = s.DefaultReadingSpeed
  103. user.DefaultHomePage = s.DefaultHomePage
  104. user.CategoriesSortingOrder = s.CategoriesSortingOrder
  105. user.MediaPlaybackRate = s.MediaPlaybackRate
  106. user.BlockFilterEntryRules = s.BlockFilterEntryRules
  107. user.KeepFilterEntryRules = s.KeepFilterEntryRules
  108. MarkReadOnView, MarkReadOnMediaPlayerCompletion := ExtractMarkAsReadBehavior(s.MarkReadBehavior)
  109. user.MarkReadOnView = MarkReadOnView
  110. user.MarkReadOnMediaPlayerCompletion = MarkReadOnMediaPlayerCompletion
  111. if s.Password != "" {
  112. user.Password = s.Password
  113. }
  114. return user
  115. }
  116. // Validate makes sure the form values are valid.
  117. func (s *SettingsForm) Validate() *locale.LocalizedError {
  118. if (s.Username == "" && !config.Opts.DisableLocalAuth()) || s.Theme == "" || s.Language == "" || s.Timezone == "" || s.EntryDirection == "" || s.DisplayMode == "" || s.DefaultHomePage == "" {
  119. return locale.NewLocalizedError("error.settings_mandatory_fields")
  120. }
  121. if s.CJKReadingSpeed <= 0 || s.DefaultReadingSpeed <= 0 {
  122. return locale.NewLocalizedError("error.settings_reading_speed_is_positive")
  123. }
  124. if s.Confirmation == "" {
  125. // Firefox insists on auto-completing the password field.
  126. // If the confirmation field is blank, the user probably
  127. // didn't intend to change their password.
  128. s.Password = ""
  129. } else if s.Password != "" {
  130. if s.Password != s.Confirmation {
  131. return locale.NewLocalizedError("error.different_passwords")
  132. }
  133. }
  134. if s.MediaPlaybackRate < 0.25 || s.MediaPlaybackRate > 4 {
  135. return locale.NewLocalizedError("error.settings_media_playback_rate_range")
  136. }
  137. if s.ExternalFontHosts != "" {
  138. if !validator.IsValidDomainList(s.ExternalFontHosts) {
  139. return locale.NewLocalizedError("error.settings_invalid_domain_list")
  140. }
  141. }
  142. return nil
  143. }
  144. // NewSettingsForm returns a new SettingsForm.
  145. func NewSettingsForm(r *http.Request) *SettingsForm {
  146. entriesPerPage, err := strconv.ParseInt(r.FormValue("entries_per_page"), 10, 0)
  147. if err != nil {
  148. entriesPerPage = 0
  149. }
  150. defaultReadingSpeed, err := strconv.ParseInt(r.FormValue("default_reading_speed"), 10, 0)
  151. if err != nil {
  152. defaultReadingSpeed = 0
  153. }
  154. cjkReadingSpeed, err := strconv.ParseInt(r.FormValue("cjk_reading_speed"), 10, 0)
  155. if err != nil {
  156. cjkReadingSpeed = 0
  157. }
  158. mediaPlaybackRate, err := strconv.ParseFloat(r.FormValue("media_playback_rate"), 64)
  159. if err != nil {
  160. mediaPlaybackRate = 1
  161. }
  162. return &SettingsForm{
  163. Username: r.FormValue("username"),
  164. Password: r.FormValue("password"),
  165. Confirmation: r.FormValue("confirmation"),
  166. Theme: r.FormValue("theme"),
  167. Language: r.FormValue("language"),
  168. Timezone: r.FormValue("timezone"),
  169. EntryDirection: r.FormValue("entry_direction"),
  170. EntryOrder: r.FormValue("entry_order"),
  171. EntriesPerPage: int(entriesPerPage),
  172. KeyboardShortcuts: r.FormValue("keyboard_shortcuts") == "1",
  173. ShowReadingTime: r.FormValue("show_reading_time") == "1",
  174. CustomCSS: r.FormValue("custom_css"),
  175. CustomJS: r.FormValue("custom_js"),
  176. ExternalFontHosts: r.FormValue("external_font_hosts"),
  177. EntrySwipe: r.FormValue("entry_swipe") == "1",
  178. GestureNav: r.FormValue("gesture_nav"),
  179. DisplayMode: r.FormValue("display_mode"),
  180. DefaultReadingSpeed: int(defaultReadingSpeed),
  181. CJKReadingSpeed: int(cjkReadingSpeed),
  182. DefaultHomePage: r.FormValue("default_home_page"),
  183. CategoriesSortingOrder: r.FormValue("categories_sorting_order"),
  184. MarkReadOnView: r.FormValue("mark_read_on_view") == "1",
  185. MarkReadBehavior: MarkReadBehavior(r.FormValue("mark_read_behavior")),
  186. MediaPlaybackRate: mediaPlaybackRate,
  187. BlockFilterEntryRules: r.FormValue("block_filter_entry_rules"),
  188. KeepFilterEntryRules: r.FormValue("keep_filter_entry_rules"),
  189. }
  190. }