diff --git a/lib/mv_web/live/join_live.ex b/lib/mv_web/live/join_live.ex index b679127..2c37f3a 100644 --- a/lib/mv_web/live/join_live.ex +++ b/lib/mv_web/live/join_live.ex @@ -8,6 +8,7 @@ defmodule MvWeb.JoinLive do alias Ash.Resource.Info alias Mv.Membership alias Mv.Membership.CustomFieldLookup + alias MvWeb.Helpers.JoinDescriptionRenderer alias MvWeb.JoinRateLimit alias MvWeb.Translations.MemberFields @@ -96,14 +97,20 @@ defmodule MvWeb.JoinLive do class="checkbox checkbox-sm" /> - {field.label} + {render_field_label(field)} <% else %>
assign(:form, to_form(params, as: "join"))} end + # Renders a join field's label. When a custom field has a join_description it is + # rendered with auto-linked URLs/Markdown; otherwise the plain field label is used. + # Safe: join_description is admin-set settings content, never end-user input, and + # JoinDescriptionRenderer escapes all non-link text (only emits tags). + defp render_field_label(%{join_description: join_description}) + when is_binary(join_description) do + JoinDescriptionRenderer.render(join_description) + end + + defp render_field_label(%{label: label}), do: label + defp build_join_fields_with_labels(allowlist) do member_field_strings = Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1) custom_field_by_id = custom_field_map(allowlist, member_field_strings) @@ -249,20 +267,36 @@ defmodule MvWeb.JoinLive do defp build_join_field(id, required, member_field_strings, custom_field_by_id) do if id in member_field_strings do label = MemberFields.label(String.to_existing_atom(id)) - %{id: id, label: label, required: required, input_type: member_field_input_type(id)} + + %{ + id: id, + label: label, + required: required, + input_type: member_field_input_type(id), + join_description: nil + } else custom_field = Map.get(custom_field_by_id, id) label = if custom_field, do: custom_field.name, else: gettext("Field") input_type = custom_field_input_type(custom_field && custom_field.value_type) - %{id: id, label: label, required: required, input_type: input_type} + %{ + id: id, + label: label, + required: required, + input_type: input_type, + join_description: custom_field && custom_field.join_description + } end end defp custom_field_map(allowlist, _member_field_strings) do allowlist |> Enum.map(& &1.id) - |> CustomFieldLookup.fetch_map_by_ids(authorize?: false, select: [:id, :name, :value_type]) + |> CustomFieldLookup.fetch_map_by_ids( + authorize?: false, + select: [:id, :name, :value_type, :join_description] + ) end defp initial_form_params(join_fields) do diff --git a/test/mv_web/live/join_live_test.exs b/test/mv_web/live/join_live_test.exs index d0efee9..efe03bb 100644 --- a/test/mv_web/live/join_live_test.exs +++ b/test/mv_web/live/join_live_test.exs @@ -165,6 +165,34 @@ defmodule MvWeb.JoinLiveTest do custom_field.name ) end + + @tag role: :unauthenticated + test "renders join_description with rendered link as label when set", %{conn: conn} do + {:ok, settings} = Membership.get_settings() + system_actor = Mv.Helpers.SystemActor.get_system_actor() + + {:ok, custom_field} = + Membership.create_custom_field( + %{ + name: "DSGVO", + value_type: :boolean, + join_description: "Akzeptiere die [Datenschutzerklärung](https://example.com/dsgvo)" + }, + actor: system_actor + ) + + {:ok, _} = + Membership.update_settings(settings, %{ + join_form_enabled: true, + join_form_field_ids: ["email", custom_field.id], + join_form_field_required: %{"email" => true, custom_field.id => false} + }) + + {:ok, _view, html} = live(conn, "/join") + + assert html =~ ~s(Datenschutzerklärung) + assert html =~ "Akzeptiere die" + end end describe "join field input types" do