settings.go 8.0 KB

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