Add custom profile fields
authorEgor Kislitsyn <egor@kislitsyn.com>
Wed, 24 Jul 2019 12:26:35 +0000 (19:26 +0700)
committerEgor Kislitsyn <egor@kislitsyn.com>
Wed, 14 Aug 2019 07:52:54 +0000 (14:52 +0700)
config/config.exs
docs/config.md
lib/pleroma/user/info.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs

index 75866112058d1a61f1cd5ab1e54f0e11b02281e4..109cf6516cded5ddac9ec8b981a3116f1d8aa440 100644 (file)
@@ -255,6 +255,7 @@ config :pleroma, :instance,
   dynamic_configuration: false,
   user_bio_length: 5000,
   user_name_length: 100,
+  max_account_fields: 4,
   external_user_synchronization: true
 
 config :pleroma, :markup,
index 20311db541a75dfc9edb53f8ccd8125550dff29a..ca5da7db1f8e9c29cf0dddc8b2191f6faf6e7b07 100644 (file)
@@ -132,6 +132,7 @@ config :pleroma, Pleroma.Emails.Mailer,
 * `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
 * `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.
 * `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
+* `max_account_fields`: The maximum number of custom fields in the user profile (default: `4`)
 * `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
 
 
index 22eb9a1825e4d4edf384bd7e643565c064805ed6..fa57052fb3f39bd6ca072d2ed951c116c424127c 100644 (file)
@@ -49,6 +49,7 @@ defmodule Pleroma.User.Info do
     field(:mascot, :map, default: nil)
     field(:emoji, {:array, :map}, default: [])
     field(:pleroma_settings_store, :map, default: %{})
+    field(:fields, {:array, :map}, default: [])
 
     field(:notification_settings, :map,
       default: %{
@@ -286,10 +287,32 @@ defmodule Pleroma.User.Info do
       :background,
       :show_role,
       :skip_thread_containment,
+      :fields,
       :pleroma_settings_store
     ])
+    |> validate_fields()
   end
 
+  def validate_fields(changeset) do
+    limit = Pleroma.Config.get([:instance, :max_account_fields], 0)
+
+    changeset
+    |> validate_length(:fields, max: limit)
+    |> validate_change(:fields, fn :fields, fields ->
+      if Enum.all?(fields, &valid_field?/1) do
+        []
+      else
+        [fields: "invalid"]
+      end
+    end)
+  end
+
+  defp valid_field?(%{"name" => name, "value" => value}) do
+    is_binary(name) && is_binary(value)
+  end
+
+  defp valid_field?(_), do: false
+
   @spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
   def confirmation_changeset(info, opts) do
     need_confirmation? = Keyword.get(opts, :need_confirmation)
@@ -384,6 +407,14 @@ defmodule Pleroma.User.Info do
     cast(info, params, [:muted_reblogs])
   end
 
+  def fields(%{source_data: %{"attachment" => attachment}}) do
+    attachment
+    |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
+    |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
+  end
+
+  def fields(%{fields: fields}), do: fields
+
   def follow_information_update(info, params) do
     info
     |> cast(params, [
index 7ce2b5b0608d2c2059dd7179a67dea79d4cd4d35..e79a02caa6caff223c11be9a492a5a6b61163584 100644 (file)
@@ -156,6 +156,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         end)
       end)
       |> add_if_present(params, "default_scope", :default_scope)
+      |> add_if_present(params, "fields", :fields, fn fields ->
+        fields =
+          Enum.map(fields, fn field ->
+            %{
+              "name" => Formatter.html_escape(field["name"], "text/plain"),
+              "value" => Formatter.html_escape(field["value"], "text/plain")
+            }
+          end)
+
+        {:ok, fields}
+      end)
       |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
         {:ok, Map.merge(user.info.pleroma_settings_store, value)}
       end)
index 72c092f252e6e8c604b7eb3db956bd856f929a3f..d2f3986ff0476b4ec8f711dffe810c669f1d8817 100644 (file)
@@ -93,10 +93,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
         }
       end)
 
-    fields =
-      (user.info.source_data["attachment"] || [])
-      |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
-      |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
+    fields = User.Info.fields(user.info)
+    fields_html = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
 
     bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
 
@@ -119,11 +117,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       header: header,
       header_static: header,
       emojis: emojis,
-      fields: fields,
+      fields: fields_html,
       bot: bot,
       source: %{
         note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
         sensitive: false,
+        fields: fields,
         pleroma: %{}
       },
 
index 71d0c8af89c57f122ab58d123728a0dd0d257635..a3eadde16371f2e56b000014172d298d4c7758e1 100644 (file)
@@ -300,5 +300,44 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert user["display_name"] == name
       assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
     end
+
+    test "update fields", %{conn: conn} do
+      user = insert(:user)
+
+      fields = [
+        %{"name" => "<b>foo<b>", "value" => "<i>bar</i>"},
+        %{"name" => "link", "value" => "cofe.io"}
+      ]
+
+      account =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{"fields" => fields})
+        |> json_response(200)
+
+      assert account["fields"] == [
+               %{"name" => "&lt;b&gt;foo&lt;b&gt;", "value" => "&lt;i&gt;bar&lt;/i&gt;"},
+               %{"name" => "link", "value" => "<a href=\"http://cofe.io\">cofe.io</a>"}
+             ]
+
+      assert account["source"]["fields"] == [
+               %{"name" => "&lt;b&gt;foo&lt;b&gt;", "value" => "&lt;i&gt;bar&lt;/i&gt;"},
+               %{"name" => "link", "value" => "cofe.io"}
+             ]
+
+      Pleroma.Config.put([:instance, :max_account_fields], 1)
+
+      fields = [
+        %{"name" => "<b>foo<b>", "value" => "<i>bar</i>"},
+        %{"name" => "link", "value" => "cofe.io"}
+      ]
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{"fields" => fields})
+
+      assert %{"error" => "Invalid request"} == json_response(conn, 403)
+    end
   end
 end