about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/admin/settings_controller.rb81
-rw-r--r--app/javascript/styles/mastodon/forms.scss36
-rw-r--r--app/models/form/admin_settings.rb149
-rw-r--r--app/models/user.rb12
-rw-r--r--app/presenters/instance_presenter.rb1
-rw-r--r--app/services/vote_service.rb4
-rw-r--r--app/validators/existing_username_validator.rb20
-rw-r--r--app/validators/html_validator.rb14
-rw-r--r--app/views/about/_registration.html.haml29
-rw-r--r--app/views/admin/settings/edit.html.haml1
10 files changed, 209 insertions, 138 deletions
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index a64e98868..dc1c79b7f 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -2,94 +2,29 @@
 
 module Admin
   class SettingsController < BaseController
-    ADMIN_SETTINGS = %w(
-      site_contact_username
-      site_contact_email
-      site_title
-      site_short_description
-      site_description
-      site_extended_description
-      site_terms
-      registrations_mode
-      closed_registrations_message
-      open_deletion
-      timeline_preview
-      show_staff_badge
-      bootstrap_timeline_accounts
-      flavour
-      skin
-      flavour_and_skin
-      thumbnail
-      hero
-      mascot
-      min_invite_role
-      activity_api_enabled
-      peers_api_enabled
-      show_known_fediverse_at_about_page
-      preview_sensitive_media
-      custom_css
-      profile_directory
-      hide_followers_count
-    ).freeze
-
-    BOOLEAN_SETTINGS = %w(
-      open_deletion
-      timeline_preview
-      show_staff_badge
-      activity_api_enabled
-      peers_api_enabled
-      show_known_fediverse_at_about_page
-      preview_sensitive_media
-      profile_directory
-      hide_followers_count
-    ).freeze
-
-    UPLOAD_SETTINGS = %w(
-      thumbnail
-      hero
-      mascot
-    ).freeze
-
     def edit
       authorize :settings, :show?
+
       @admin_settings = Form::AdminSettings.new
     end
 
     def update
       authorize :settings, :update?
 
-      settings = settings_params
-      flavours_and_skin = settings.delete('flavour_and_skin')
-      if flavours_and_skin
-        settings['flavour'], settings['skin'] = flavours_and_skin.split('/', 2)
-      end
+      @admin_settings = Form::AdminSettings.new(settings_params)
 
-      settings.each do |key, value|
-        if UPLOAD_SETTINGS.include?(key)
-          upload = SiteUpload.where(var: key).first_or_initialize(var: key)
-          upload.update(file: value)
-        else
-          setting = Setting.where(var: key).first_or_initialize(var: key)
-          setting.update(value: value_for_update(key, value))
-        end
+      if @admin_settings.save
+        flash[:notice] = I18n.t('generic.changes_saved_msg')
+        redirect_to edit_admin_settings_path
+      else
+        render :edit
       end
-
-      flash[:notice] = I18n.t('generic.changes_saved_msg')
-      redirect_to edit_admin_settings_path
     end
 
     private
 
     def settings_params
-      params.require(:form_admin_settings).permit(ADMIN_SETTINGS)
-    end
-
-    def value_for_update(key, value)
-      if BOOLEAN_SETTINGS.include?(key)
-        value == '1'
-      else
-        value
-      end
+      params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
     end
   end
 end
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 9ef45e425..3ea104786 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -475,6 +475,42 @@ code {
       }
     }
   }
+
+  &__overlay-area {
+    position: relative;
+
+    &__overlay {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      background: rgba($ui-base-color, 0.65);
+      backdrop-filter: blur(2px);
+      border-radius: 4px;
+
+      &__content {
+        text-align: center;
+
+        &.rich-formatting {
+          &,
+          p {
+            color: $primary-text-color;
+          }
+        }
+      }
+    }
+  }
+}
+
+.block-icon {
+  display: block;
+  margin: 0 auto;
+  margin-bottom: 10px;
+  font-size: 24px;
 }
 
 .flash-message {
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index 929c65793..0fcbd0605 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -3,57 +3,108 @@
 class Form::AdminSettings
   include ActiveModel::Model
 
-  delegate(
-    :site_contact_username,
-    :site_contact_username=,
-    :site_contact_email,
-    :site_contact_email=,
-    :site_title,
-    :site_title=,
-    :site_short_description,
-    :site_short_description=,
-    :site_description,
-    :site_description=,
-    :site_extended_description,
-    :site_extended_description=,
-    :site_terms,
-    :site_terms=,
-    :registrations_mode,
-    :registrations_mode=,
-    :closed_registrations_message,
-    :closed_registrations_message=,
-    :open_deletion,
-    :open_deletion=,
-    :timeline_preview,
-    :timeline_preview=,
-    :show_staff_badge,
-    :show_staff_badge=,
-    :bootstrap_timeline_accounts,
-    :bootstrap_timeline_accounts=,
-    :hide_followers_count,
-    :hide_followers_count=,
-    :flavour,
-    :flavour=,
-    :skin,
-    :skin=,
-    :min_invite_role,
-    :min_invite_role=,
-    :activity_api_enabled,
-    :activity_api_enabled=,
-    :peers_api_enabled,
-    :peers_api_enabled=,
-    :show_known_fediverse_at_about_page,
-    :show_known_fediverse_at_about_page=,
-    :preview_sensitive_media,
-    :preview_sensitive_media=,
-    :custom_css,
-    :custom_css=,
-    :profile_directory,
-    :profile_directory=,
-    to: Setting
-  )
+  KEYS = %i(
+    site_contact_username
+    site_contact_email
+    site_title
+    site_short_description
+    site_description
+    site_extended_description
+    site_terms
+    registrations_mode
+    closed_registrations_message
+    open_deletion
+    timeline_preview
+    show_staff_badge
+    bootstrap_timeline_accounts
+    flavour
+    skin
+    min_invite_role
+    activity_api_enabled
+    peers_api_enabled
+    show_known_fediverse_at_about_page
+    preview_sensitive_media
+    custom_css
+    profile_directory
+    hide_followers_count
+    flavour_and_skin
+  ).freeze
+
+  BOOLEAN_KEYS = %i(
+    open_deletion
+    timeline_preview
+    show_staff_badge
+    activity_api_enabled
+    peers_api_enabled
+    show_known_fediverse_at_about_page
+    preview_sensitive_media
+    profile_directory
+    hide_followers_count
+  ).freeze
+
+  UPLOAD_KEYS = %i(
+    thumbnail
+    hero
+    mascot
+  ).freeze
+
+  PSEUDO_KEYS = %i(
+    flavour_and_skin
+  ).freeze
+
+  attr_accessor(*KEYS)
+
+  validates :site_short_description, :site_description, :site_extended_description, :site_terms, :closed_registrations_message, html: true
+  validates :registrations_mode, inclusion: { in: %w(open approved none) }
+  validates :min_invite_role, inclusion: { in: %w(disabled user moderator admin) }
+  validates :site_contact_email, :site_contact_username, presence: true
+  validates :site_contact_username, existing_username: true
+  validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
+
+  def initialize(_attributes = {})
+    super
+    initialize_attributes
+  end
+
+  def save
+    return false unless valid?
+
+    KEYS.each do |key|
+      next if PSEUDO_KEYS.include?(key)
+      value = instance_variable_get("@#{key}")
+
+      if UPLOAD_KEYS.include?(key)
+        upload = SiteUpload.where(var: key).first_or_initialize(var: key)
+        upload.update(file: value)
+      else
+        setting = Setting.where(var: key).first_or_initialize(var: key)
+        setting.update(value: typecast_value(key, value))
+      end
+    end
+  end
 
   def flavour_and_skin
     "#{Setting.flavour}/#{Setting.skin}"
   end
+
+  def flavour_and_skin=(value)
+    @flavour, @skin = value.split('/', 2)
+  end
+
+  private
+
+  def initialize_attributes
+    KEYS.each do |key|
+      next if PSEUDO_KEYS.include?(key)
+      instance_variable_set("@#{key}", Setting.public_send(key)) if instance_variable_get("@#{key}").nil?
+    end
+  end
+
+  def typecast_value(key, value)
+    if BOOLEAN_KEYS.include?(key)
+      value == '1'
+    else
+      value
+    end
+  end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 47657a670..66c1543ff 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -124,7 +124,8 @@ class User < ApplicationRecord
   end
 
   def confirm
-    new_user = !confirmed?
+    new_user      = !confirmed?
+    self.approved = true if open_registrations?
 
     super
 
@@ -136,7 +137,8 @@ class User < ApplicationRecord
   end
 
   def confirm!
-    new_user = !confirmed?
+    new_user      = !confirmed?
+    self.approved = true if open_registrations?
 
     skip_confirmation!
     save!
@@ -264,7 +266,11 @@ class User < ApplicationRecord
   private
 
   def set_approved
-    self.approved = Setting.registrations_mode == 'open' || invited?
+    self.approved = open_registrations? || invited?
+  end
+
+  def open_registrations?
+    Setting.registrations_mode == 'open'
   end
 
   def sanitize_languages
diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb
index 94a2c1692..d234516e0 100644
--- a/app/presenters/instance_presenter.rb
+++ b/app/presenters/instance_presenter.rb
@@ -8,6 +8,7 @@ class InstancePresenter
     :site_description,
     :site_extended_description,
     :site_terms,
+    :closed_registrations_message,
     to: Setting
   )
 
diff --git a/app/services/vote_service.rb b/app/services/vote_service.rb
index 0cace6c00..81af9ef3a 100644
--- a/app/services/vote_service.rb
+++ b/app/services/vote_service.rb
@@ -11,14 +11,14 @@ class VoteService < BaseService
     @choices = choices
     @votes   = []
 
-    return if @poll.expired?
-
     ApplicationRecord.transaction do
       @choices.each do |choice|
         @votes << @poll.votes.create!(account: @account, choice: choice)
       end
     end
 
+    ActivityTracker.increment('activity:interactions')
+
     if @poll.account.local?
       distribute_poll!
     else
diff --git a/app/validators/existing_username_validator.rb b/app/validators/existing_username_validator.rb
new file mode 100644
index 000000000..4388a0c98
--- /dev/null
+++ b/app/validators/existing_username_validator.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class ExistingUsernameValidator < ActiveModel::EachValidator
+  def validate_each(record, attribute, value)
+    return if value.blank?
+
+    if options[:multiple]
+      missing_usernames = value.split(',').map { |username| username unless Account.find_local(username) }.compact
+      record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: missing_usernames.join(', '))) if missing_usernames.any?
+    else
+      record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) unless Account.find_local(value)
+    end
+  end
+
+  private
+
+  def valid_html?(str)
+    Nokogiri::HTML.fragment(str).to_s == str
+  end
+end
diff --git a/app/validators/html_validator.rb b/app/validators/html_validator.rb
new file mode 100644
index 000000000..882c35d41
--- /dev/null
+++ b/app/validators/html_validator.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class HtmlValidator < ActiveModel::EachValidator
+  def validate_each(record, attribute, value)
+    return if value.blank?
+    record.errors.add(attribute, I18n.t('html_validator.invalid_markup')) unless valid_html?(value)
+  end
+
+  private
+
+  def valid_html?(str)
+    Nokogiri::HTML.fragment(str).to_s == str
+  end
+end
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
index 9cb4eb2bc..4823ff375 100644
--- a/app/views/about/_registration.html.haml
+++ b/app/views/about/_registration.html.haml
@@ -1,16 +1,23 @@
 = simple_form_for(new_user, url: user_registration_path) do |f|
-  %p.lead= t('about.federation_hint_html', instance: content_tag(:strong, site_hostname))
+  .simple_form__overlay-area
+    %p.lead= t('about.federation_hint_html', instance: content_tag(:strong, site_hostname))
 
-  .fields-group
-    = f.simple_fields_for :account do |account_fields|
-      = account_fields.input :username, wrapper: :with_label, autofocus: true, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username') }, append: "@#{site_hostname}", hint: false, disabled: closed_registrations?
+    .fields-group
+      = f.simple_fields_for :account do |account_fields|
+        = account_fields.input :username, wrapper: :with_label, autofocus: true, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username') }, append: "@#{site_hostname}", hint: false, disabled: closed_registrations?
 
-    = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
-    = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
-    = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
+      = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
+      = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
+      = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
 
-  .fields-group
-    = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), disabled: closed_registrations?
+    .fields-group
+      = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), disabled: closed_registrations?
 
-  .actions
-    = f.button :button, sign_up_message, type: :submit, class: 'button button-primary', disabled: closed_registrations?
+    .actions
+      = f.button :button, sign_up_message, type: :submit, class: 'button button-primary', disabled: closed_registrations?
+
+    - if closed_registrations? && @instance_presenter.closed_registrations_message.present?
+      .simple_form__overlay-area__overlay
+        .simple_form__overlay-area__overlay__content.rich-formatting
+          .block-icon= fa_icon 'warning'
+          = @instance_presenter.closed_registrations_message.html_safe
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index 9995e0b2a..475fb3a2f 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -2,6 +2,7 @@
   = t('admin.settings.title')
 
 = simple_form_for @admin_settings, url: admin_settings_path, html: { method: :patch } do |f|
+  = render 'shared/error_messages', object: @admin_settings
 
   .fields-group
     = f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title')