diff --git a/app/assets/javascripts/discourse/app/components/fullname-input.gjs b/app/assets/javascripts/discourse/app/components/fullname-input.gjs
index acf8a1c0bf4..6c41c9b1aa8 100644
--- a/app/assets/javascripts/discourse/app/components/fullname-input.gjs
+++ b/app/assets/javascripts/discourse/app/components/fullname-input.gjs
@@ -6,15 +6,9 @@ import TextField from "discourse/components/text-field";
import valueEntered from "discourse/helpers/value-entered";
import { i18n } from "discourse-i18n";
-export default class SidebarEditNavigationMenuTagsModal extends Component {
+export default class FullnameInput extends Component {
@service siteSettings;
- get showFullname() {
- return (
- this.siteSettings.full_name_required || this.siteSettings.enable_names
- );
- }
-
get showFullnameInstructions() {
return (
this.siteSettings.show_signup_form_full_name_instructions &&
diff --git a/app/assets/javascripts/discourse/app/components/modal/create-account.hbs b/app/assets/javascripts/discourse/app/components/modal/create-account.hbs
index 7666fa6b341..4d52dfeea30 100644
--- a/app/assets/javascripts/discourse/app/components/modal/create-account.hbs
+++ b/app/assets/javascripts/discourse/app/components/modal/create-account.hbs
@@ -99,7 +99,7 @@
{{/if}}
- {{#if this.fullnameRequired}}
+ {{#if (and this.showFullname this.fullnameRequired)}}
- {{#if this.fullnameRequired}}
+ {{#if (and this.showFullname this.fullnameRequired)}}
- {{#if this.fullnameRequired}}
+ {{#if (and this.showFullname this.fullnameRequired)}}
{
+ assert.step("request");
+ const data = parsePostData(request.requestBody);
+ assert.strictEqual(data.password, "cool password bro");
+ assert.strictEqual(data.email, "z@z.co");
+ assert.strictEqual(data.username, "good-tuna");
+ return response({ success: true });
+ });
+
+ await click(".d-modal__footer .btn-primary");
+ assert
+ .dom(".d-modal__footer .btn-primary")
+ .isDisabled("create account is disabled");
+
+ assert.verifySteps(["request"]);
+ });
+
+ test("full name optional at signup", async function (assert) {
+ const site = Site.current();
+ site.set("full_name_required_for_signup", false);
+ site.set("full_name_visible_in_signup", true);
+
+ await visit("/");
+ await click("header .sign-up-button");
+
+ assert.dom("#new-account-name").exists();
+
+ await fillIn("#new-account-email", "z@z.co");
+ await fillIn("#new-account-username", "good-tuna");
+ await fillIn("#new-account-password", "cool password bro");
+
+ pretender.post("/u", (request) => {
+ assert.step("request");
+ const data = parsePostData(request.requestBody);
+ assert.strictEqual(data.password, "cool password bro");
+ assert.strictEqual(data.email, "z@z.co");
+ assert.strictEqual(data.username, "good-tuna");
+ return response({ success: true });
+ });
+
+ await click(".d-modal__footer .btn-primary");
+ assert
+ .dom(".d-modal__footer .btn-primary")
+ .isDisabled("create account is disabled");
+
+ assert.verifySteps(["request"]);
+ });
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js b/app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js
index 78ccc2b0231..ae260fa7d52 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js
@@ -1,6 +1,7 @@
import { click, currentURL, fillIn, visit } from "@ember/test-helpers";
import { test } from "qunit";
import PreloadStore from "discourse/lib/preload-store";
+import Site from "discourse/models/site";
import pretender, { response } from "discourse/tests/helpers/create-pretender";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import { i18n } from "discourse-i18n";
@@ -50,9 +51,7 @@ function preloadInvite({
PreloadStore.store("invite_info", info);
}
-acceptance("Invite accept", function (needs) {
- needs.settings({ full_name_required: true });
-
+acceptance("Invite accept", function () {
test("email invite link", async function (assert) {
PreloadStore.store("invite_info", {
invited_by: {
@@ -164,12 +163,38 @@ acceptance("Invite accept", function (needs) {
assert.dom(".invites-show .btn-primary").isEnabled("submit is enabled");
});
- test("invite name is required only if full name is required", async function (assert) {
+ test("invite name optional", async function (assert) {
+ const site = Site.current();
+ site.set("full_name_required_for_signup", false);
+ site.set("full_name_visible_in_signup", true);
+
preloadInvite();
await visit("/invites/my-valid-invite-token");
+ assert.dom("#new-account-name").exists();
assert
- .dom(".name-input .required")
- .doesNotExist("Full name is implicitly required");
+ .dom(".name-input.name-required")
+ .doesNotExist("full name is not required");
+ });
+
+ test("invite name hidden", async function (assert) {
+ const site = Site.current();
+ site.set("full_name_required_for_signup", false);
+ site.set("full_name_visible_in_signup", false);
+
+ preloadInvite();
+ await visit("/invites/my-valid-invite-token");
+ assert.dom("#new-account-name").doesNotExist();
+ });
+
+ test("invite name required", async function (assert) {
+ const site = Site.current();
+ site.set("full_name_required_for_signup", true);
+ site.set("full_name_visible_in_signup", true);
+
+ preloadInvite();
+ await visit("/invites/my-valid-invite-token");
+ assert.dom("#new-account-name").exists();
+ assert.dom(".name-input.name-required").exists("full name is required");
});
});
diff --git a/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js
index 7cb703a8062..db4a6bc51ed 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/site-fixtures.js
@@ -60,6 +60,8 @@ export default {
anonymous_top_menu_items: ["latest", "hot", "categories"],
uncategorized_category_id: 17,
is_readonly: false,
+ full_name_required_for_signup: false,
+ full_name_visible_in_signup: true,
categories: [
{
id: 3,
diff --git a/app/models/full_name_requirement.rb b/app/models/full_name_requirement.rb
new file mode 100644
index 00000000000..97e4268c6d7
--- /dev/null
+++ b/app/models/full_name_requirement.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require "enum_site_setting"
+
+class FullNameRequirement < EnumSiteSetting
+ def self.valid_value?(val)
+ values.any? { |v| v[:value] == val }
+ end
+
+ def self.values
+ @values ||= [
+ { name: "full_name_requirement.required_at_signup", value: "required_at_signup" },
+ { name: "full_name_requirement.optional_at_signup", value: "optional_at_signup" },
+ { name: "full_name_requirement.hidden_at_signup", value: "hidden_at_signup" },
+ ]
+ end
+
+ def self.translate_names?
+ true
+ end
+end
diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb
index 87089538b93..dbfc60c5715 100644
--- a/app/serializers/site_serializer.rb
+++ b/app/serializers/site_serializer.rb
@@ -48,6 +48,8 @@ class SiteSerializer < ApplicationSerializer
:system_user_avatar_template,
:lazy_load_categories,
:valid_flag_applies_to_types,
+ :full_name_required_for_signup,
+ :full_name_visible_in_signup,
)
has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer
@@ -381,6 +383,14 @@ class SiteSerializer < ApplicationSerializer
scope.is_admin?
end
+ def full_name_required_for_signup
+ SiteSetting.full_name_requirement == "required_at_signup"
+ end
+
+ def full_name_visible_in_signup
+ SiteSetting.full_name_requirement != "hidden_at_signup"
+ end
+
private
def ordered_flags(flags)
diff --git a/app/services/user_anonymizer.rb b/app/services/user_anonymizer.rb
index 21c02cd1448..1026230f171 100644
--- a/app/services/user_anonymizer.rb
+++ b/app/services/user_anonymizer.rb
@@ -29,7 +29,7 @@ class UserAnonymizer
@user.reload
@user.password = SecureRandom.hex
- @user.name = SiteSetting.full_name_required ? @user.username : nil
+ @user.name = SiteSetting.full_name_requirement == "required_at_signup" ? @user.username : nil
@user.date_of_birth = nil
@user.title = nil
@user.uploaded_avatar_id = nil
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 23c5f8ea854..f5517257e16 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -2539,6 +2539,10 @@ en:
categories_boxes: "Boxes with Subcategories"
categories_boxes_with_topics: "Boxes with Featured Topics"
subcategories_with_featured_topics: "Subcategories with Featured Topics"
+ full_name_requirement:
+ required_at_signup: "Required at signup"
+ optional_at_signup: "Optional at signup"
+ hidden_at_signup: "Hidden at signup"
shortcut_modifier_key:
shift: "Shift"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 7880daace7b..0ecbec292f0 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -2517,7 +2517,7 @@ en:
svg_icon_subset: "Add additional FontAwesome icons that you would like to include in your assets. Use prefix 'fa-' for solid icons, 'far-' for regular icons and 'fab-' for brand icons."
max_prints_per_hour_per_user: "Maximum number of /print page impressions (set to 0 to disable printing)"
- full_name_required: "Full name is a required field of a user's profile."
+ full_name_requirement: "Make the full name field a required, optional, or optional but hidden field in the signup form."
enable_names: "Show the user's full name on their profile, user card, and emails. Disable to hide full name everywhere."
display_name_on_posts: "Show a user's full name on their posts in addition to their @username."
show_time_gap_days: "If two posts are made this many days apart, display the time gap in the topic."
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 8f70a0c0333..98f36030477 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -707,9 +707,10 @@ users:
logout_redirect:
client: true
default: ""
- full_name_required:
- client: true
- default: false
+ full_name_requirement:
+ type: enum
+ default: hidden_at_signup
+ enum: "FullNameRequirement"
enable_names:
client: true
default: true
diff --git a/db/migrate/20241224191732_change_full_name_required_setting.rb b/db/migrate/20241224191732_change_full_name_required_setting.rb
new file mode 100644
index 00000000000..3475032d468
--- /dev/null
+++ b/db/migrate/20241224191732_change_full_name_required_setting.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class ChangeFullNameRequiredSetting < ActiveRecord::Migration[7.2]
+ def up
+ old_setting = DB.query_single(<<~SQL).first
+ SELECT value
+ FROM site_settings
+ WHERE name = 'full_name_required'
+ SQL
+
+ new_setting = nil
+ if old_setting
+ new_setting = old_setting == "t" ? "required_at_signup" : "optional_at_signup"
+ elsif Migration::Helpers.existing_site?
+ new_setting = "optional_at_signup"
+ end
+
+ DB.exec(<<~SQL)
+ DELETE FROM site_settings WHERE name = 'full_name_required'
+ SQL
+
+ DB.exec(<<~SQL, value: new_setting) if new_setting
+ INSERT INTO site_settings
+ (name, data_type, value, created_at, updated_at)
+ VALUES
+ ('full_name_requirement', 7, :value, NOW(), NOW())
+ SQL
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/lib/discourse_dev/config.rb b/lib/discourse_dev/config.rb
index c0c8ec22c0a..726f17ef2b0 100644
--- a/lib/discourse_dev/config.rb
+++ b/lib/discourse_dev/config.rb
@@ -116,7 +116,8 @@ module DiscourseDev
puts "Once site is running use https://localhost:9292/user/#{username}/become to access the account in development"
end
- admin.name = ask("Full name: ") if SiteSetting.full_name_required
+ admin.name = ask("Full name: ") if SiteSetting.full_name_requirement ==
+ "required_at_signup"
saved = admin.save
if saved
diff --git a/lib/tasks/admin.rake b/lib/tasks/admin.rake
index 35e6cc1b9db..a5fa3266af6 100644
--- a/lib/tasks/admin.rake
+++ b/lib/tasks/admin.rake
@@ -81,7 +81,8 @@ task "admin:create" => :environment do
admin.password = password
end
- admin.name = ask("Full name: ") if SiteSetting.full_name_required && admin.name.blank?
+ admin.name = ask("Full name: ") if SiteSetting.full_name_requirement == "required_at_signup" &&
+ admin.name.blank?
# save/update user account
saved = admin.save
diff --git a/lib/validators/user_full_name_validator.rb b/lib/validators/user_full_name_validator.rb
index 04e1f557c32..5e8a0ee9e36 100644
--- a/lib/validators/user_full_name_validator.rb
+++ b/lib/validators/user_full_name_validator.rb
@@ -2,6 +2,8 @@
class UserFullNameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
- record.errors.add(attribute, :blank) if SiteSetting.full_name_required && !record.name.present?
+ if SiteSetting.full_name_requirement == "required_at_signup" && !record.name.present?
+ record.errors.add(attribute, :blank)
+ end
end
end
diff --git a/spec/lib/validators/user_full_name_validator_spec.rb b/spec/lib/validators/user_full_name_validator_spec.rb
index 9fcbb551ec1..90fdb26328b 100644
--- a/spec/lib/validators/user_full_name_validator_spec.rb
+++ b/spec/lib/validators/user_full_name_validator_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe UserFullNameValidator do
let(:record) { Fabricate.build(:user, name: @name) }
context "when name is not required" do
- before { SiteSetting.full_name_required = false }
+ before { SiteSetting.full_name_requirement = "optional_at_signup" }
it "allows no name" do
@name = nil
@@ -23,7 +23,7 @@ RSpec.describe UserFullNameValidator do
end
context "when name is required" do
- before { SiteSetting.full_name_required = true }
+ before { SiteSetting.full_name_requirement = "required_at_signup" }
it "adds error for nil name" do
@name = nil
diff --git a/spec/requests/api/schemas/json/site_response.json b/spec/requests/api/schemas/json/site_response.json
index a91ea86bb52..cfaab22910e 100644
--- a/spec/requests/api/schemas/json/site_response.json
+++ b/spec/requests/api/schemas/json/site_response.json
@@ -861,6 +861,12 @@
},
"navigation_menu_site_top_tags": {
"type": "array"
+ },
+ "full_name_required_for_signup": {
+ "type": "boolean"
+ },
+ "full_name_visible_in_signup": {
+ "type": "boolean"
}
},
"required": [
@@ -893,6 +899,8 @@
"categories",
"archetypes",
"user_fields",
- "auth_providers"
+ "auth_providers",
+ "full_name_required_for_signup",
+ "full_name_visible_in_signup"
]
}
diff --git a/spec/services/anonymous_shadow_creator_spec.rb b/spec/services/anonymous_shadow_creator_spec.rb
index 75e82fd55e3..362e3262fe7 100644
--- a/spec/services/anonymous_shadow_creator_spec.rb
+++ b/spec/services/anonymous_shadow_creator_spec.rb
@@ -68,7 +68,7 @@ RSpec.describe AnonymousShadowCreator do
end
it "works even when names are required" do
- SiteSetting.full_name_required = true
+ SiteSetting.full_name_requirement = "required_at_signup"
expect { AnonymousShadowCreator.get(user) }.to_not raise_error
end
diff --git a/spec/services/user_anonymizer_spec.rb b/spec/services/user_anonymizer_spec.rb
index ad5ede53563..f2f91f326a3 100644
--- a/spec/services/user_anonymizer_spec.rb
+++ b/spec/services/user_anonymizer_spec.rb
@@ -76,7 +76,7 @@ RSpec.describe UserAnonymizer do
end
context "when Site Settings do not require full name" do
- before { SiteSetting.full_name_required = false }
+ before { SiteSetting.full_name_requirement = "optional_at_signup" }
it "resets profile to default values" do
user.update!(name: "Bibi", date_of_birth: 19.years.ago, title: "Super Star")
@@ -127,7 +127,7 @@ RSpec.describe UserAnonymizer do
end
context "when Site Settings require full name" do
- before { SiteSetting.full_name_required = true }
+ before { SiteSetting.full_name_requirement = "required_at_signup" }
it "changes name to anonymized username" do
prev_username = user.username
diff --git a/spec/system/social_authentication_spec.rb b/spec/system/social_authentication_spec.rb
index 94ca12d6b16..908726e9647 100644
--- a/spec/system/social_authentication_spec.rb
+++ b/spec/system/social_authentication_spec.rb
@@ -335,6 +335,8 @@ shared_examples "social authentication scenarios" do |signup_page_object, login_
end
describe "Social authentication", type: :system do
+ before { SiteSetting.full_name_requirement = "optional_at_signup" }
+
context "when desktop" do
include_examples "social authentication scenarios",
PageObjects::Modals::Signup.new,