From 23deb43432fea55803b7fd519531b75f002147c0 Mon Sep 17 00:00:00 2001 From: AymanTerra Date: Mon, 1 Mar 2021 21:52:50 +0200 Subject: [PATCH 1/2] Embeddable mode #213 --- lib/plausible/site/schema.ex | 34 ++++++++++++++++++ .../controllers/site_controller.ex | 35 ++++++++++++++++++ .../controllers/stats_controller.ex | 17 ++++++--- .../plugs/shared_link_theme_selection_plug.ex | 2 +- lib/plausible_web/router.ex | 5 +++ .../templates/layout/app.html.eex | 2 +- .../site/settings_visibility.html.eex | 30 ++++++++++++++++ .../templates/stats/stats.html.eex | 36 +++++++++++++++---- ...210219100539_add_external_css_to_sites.exs | 9 +++++ 9 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 priv/repo/migrations/20210219100539_add_external_css_to_sites.exs diff --git a/lib/plausible/site/schema.ex b/lib/plausible/site/schema.ex index 59ceac9a3766..d06a4b037945 100644 --- a/lib/plausible/site/schema.ex +++ b/lib/plausible/site/schema.ex @@ -9,6 +9,7 @@ defmodule Plausible.Site do field :timezone, :string field :public, :boolean field :embeddable, :boolean + field :external_css, :string many_to_many :members, User, join_through: Plausible.Site.Membership has_one :google_auth, GoogleAuth @@ -47,6 +48,20 @@ defmodule Plausible.Site do change(site, embeddable: false) end + def add_external_css(site, external_css) do + site + |> cast(%{external_css: external_css}, [:external_css]) + |> validate_required(:external_css) + |> validate_format(:external_css, ~r/^https:\/\/.*$/, + message: "The style sheet must be served as https." + ) + |> validate_url(:external_css) + end + + def delete_external_css(site) do + change(site, external_css: nil) + end + defp clean_domain(changeset) do clean_domain = (get_field(changeset, :domain) || "") @@ -61,4 +76,23 @@ defmodule Plausible.Site do domain: clean_domain }) end + + # https://gist.github.com/atomkirk/74b39b5b09c7d0f21763dd55b877f998 + defp validate_url(changeset, field, opts \\ []) do + validate_change changeset, field, fn _, value -> + case URI.parse(value) do + %URI{scheme: nil} -> "is missing a scheme (e.g. https)" + %URI{host: nil} -> "is missing a host" + %URI{host: host} -> + case :inet.gethostbyname(Kernel.to_charlist host) do + {:ok, _} -> nil + {:error, _} -> "invalid host" + end + end + |> case do + error when is_binary(error) -> [{field, Keyword.get(opts, :message, error)}] + _ -> [] + end + end + end end diff --git a/lib/plausible_web/controllers/site_controller.ex b/lib/plausible_web/controllers/site_controller.ex index 68e2f4935e18..d9371dc3ed3a 100644 --- a/lib/plausible_web/controllers/site_controller.ex +++ b/lib/plausible_web/controllers/site_controller.ex @@ -163,11 +163,13 @@ defmodule PlausibleWeb.SiteController do def settings_visibility(conn, %{"website" => website}) do site = Sites.get_for_user!(conn.assigns[:current_user].id, website) shared_links = Repo.all(from l in Plausible.Site.SharedLink, where: l.site_id == ^site.id) + changeset = Plausible.Site.changeset(site) conn |> assign(:skip_plausible_tracking, true) |> render("settings_visibility.html", site: site, + changeset: changeset, shared_links: shared_links, layout: {PlausibleWeb.LayoutView, "site_settings.html"} ) @@ -656,4 +658,37 @@ defmodule PlausibleWeb.SiteController do end) |> Repo.transaction() end + + def add_external_css(conn, %{"website" => website, "site" => site_changes}) do + site = Sites.get_for_user!(conn.assigns[:current_user].id, website) + changeset = Plausible.Site.add_external_css(site, site_changes["external_css"]) + + case Repo.update(changeset) do + {:ok, _site} -> + redirect(conn, to: "/#{URI.encode_www_form(site.domain)}/settings/visibility") + + {:error, changeset} -> + shared_links = Repo.all(from l in Plausible.Site.SharedLink, where: l.site_id == ^site.id) + + conn + |> assign(:skip_plausible_tracking, true) + |> render("settings_visibility.html", + site: site, + changeset: changeset, + shared_links: shared_links, + layout: {PlausibleWeb.LayoutView, "site_settings.html"} + ) + end + end + + def delete_external_css(conn, %{"website" => website}) do + site = + Sites.get_for_user!(conn.assigns[:current_user].id, website) + |> Plausible.Site.delete_external_css() + |> Repo.update!() + + conn + |> put_flash(:success, "External CSS for #{site.domain} deleted successfully.") + |> redirect(to: "/#{URI.encode_www_form(site.domain)}/settings/visibility") + end end diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex index e1db517cb05d..b765e87ef7b1 100644 --- a/lib/plausible_web/controllers/stats_controller.ex +++ b/lib/plausible_web/controllers/stats_controller.ex @@ -84,13 +84,21 @@ defmodule PlausibleWeb.StatsController do Repo.get_by(Plausible.Site.SharedLink, slug: slug) |> Repo.preload(:site) - embed_mode = Enum.at(conn.path_info, 1) == "embed" - - embed_link = if(embed_mode, do: "/embed", else: "") - if shared_link do + embed_mode = Enum.at(conn.path_info, 1) == "embed" + embed_link = if(embed_mode, do: "/embed", else: "") + + theme_index = Enum.find_index(conn.path_info, fn x -> x == "theme" end) + admin_selected_theme = + if( + theme_index, + do: Enum.at(conn.path_info, theme_index + 1), + else: nil + ) + if shared_link.password_hash do conn + |> put_session("admin_selected_theme", admin_selected_theme) |> put_session("embed_mode", embed_mode) |> assign(:skip_plausible_tracking, true) |> render("shared_link_password.html", @@ -100,6 +108,7 @@ defmodule PlausibleWeb.StatsController do ) else conn + |> put_session("admin_selected_theme", admin_selected_theme) |> put_session("embed_mode", embed_mode) |> shared_link_auth_success(shared_link) end diff --git a/lib/plausible_web/plugs/shared_link_theme_selection_plug.ex b/lib/plausible_web/plugs/shared_link_theme_selection_plug.ex index bc600c6139d4..094ad57b7ffd 100644 --- a/lib/plausible_web/plugs/shared_link_theme_selection_plug.ex +++ b/lib/plausible_web/plugs/shared_link_theme_selection_plug.ex @@ -22,7 +22,7 @@ defmodule PlausibleWeb.SharedLinkThemeSelectionPlug do if( theme_index, do: Enum.at(conn.path_info, theme_index + 1), - else: get_session(conn, "selected_theme") + else: nil ) conn diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex index 9396599bf7a3..02c9d60f9518 100644 --- a/lib/plausible_web/router.ex +++ b/lib/plausible_web/router.ex @@ -152,6 +152,9 @@ defmodule PlausibleWeb.Router do post "/sites/:website/shared-links", SiteController, :create_shared_link delete "/sites/:website/shared-links/:slug", SiteController, :delete_shared_link + put "/sites/:website/external-css", SiteController, :add_external_css + delete "/sites/:website/external-css", SiteController, :delete_external_css + get "/sites/:website/custom-domains/new", SiteController, :new_custom_domain get "/sites/:website/custom-domains/dns-setup", SiteController, :custom_domain_dns_setup get "/sites/:website/custom-domains/snippet", SiteController, :custom_domain_snippet @@ -179,7 +182,9 @@ defmodule PlausibleWeb.Router do delete "/:website", SiteController, :delete_site delete "/:website/stats", SiteController, :reset_stats + get "/share/embed/:slug/theme/:theme_slug", StatsController, :shared_link get "/share/embed/:slug", StatsController, :shared_link + get "/share/:slug/theme/:theme_slug", StatsController, :shared_link get "/share/:slug", StatsController, :shared_link post "/share/embed/:slug/authenticate", StatsController, :authenticate_shared_link post "/share/:slug/authenticate", StatsController, :authenticate_shared_link diff --git a/lib/plausible_web/templates/layout/app.html.eex b/lib/plausible_web/templates/layout/app.html.eex index 15333c573e83..9b3953946896 100644 --- a/lib/plausible_web/templates/layout/app.html.eex +++ b/lib/plausible_web/templates/layout/app.html.eex @@ -10,7 +10,7 @@ <%= assigns[:title] || "Plausible · Simple, privacy-friendly alternative to Google Analytics" %> "/> <%= render("_tracking.html", assigns) %> - + <%= if !(@conn.assigns[:embed_mode] && @conn.assigns.site.embeddable) do %> diff --git a/lib/plausible_web/templates/site/settings_visibility.html.eex b/lib/plausible_web/templates/site/settings_visibility.html.eex index 378293a42b3a..eed3bc0d0cfa 100644 --- a/lib/plausible_web/templates/site/settings_visibility.html.eex +++ b/lib/plausible_web/templates/site/settings_visibility.html.eex @@ -80,3 +80,33 @@ +
+
+

External styles

+

You can use external css style sheet but should be hosted securely (https).

+ <%= link(to: "https://docs.plausible.io/external-css", target: "_blank") do %> + + <% end %> +
+ <%= if !@site.external_css do %> +
+ <%= form_for @changeset, "/sites/#{URI.encode_www_form(@site.domain)}/external-css", [class: "max-w-xl"], fn f -> %> +
+ <%= label f, :external_css, "External CSS URL", class: "block text-sm font-medium leading-5 text-gray-700 dark:text-gray-300" %> +
+ <%= text_input f, :external_css, class: "shadow-sm dark:bg-gray-900 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:bg-gray-800" %> + <%= error_tag f, :external_css %> +
+
+ <%= submit "Save", class: "button mt-4" %> + <% end %> +
+ <% else %> +
+ + <%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/external-css", method: :delete, class: "py-2 px-4 bg-gray-200 dark:bg-gray-850 text-red-600 dark:text-red-500 rounded-l-none hover:bg-gray-300 dark:hover:bg-gray-825", data: [confirm: "Are you sure you want to delete this external style sheet?"]) do %> + + <% end %> +
+ <% end %> +
diff --git a/lib/plausible_web/templates/stats/stats.html.eex b/lib/plausible_web/templates/stats/stats.html.eex index 5fc7df4cfe4e..e355439aa8d3 100644 --- a/lib/plausible_web/templates/stats/stats.html.eex +++ b/lib/plausible_web/templates/stats/stats.html.eex @@ -4,22 +4,41 @@ <%= link("Click here to enable weekly email reports →", to: "/#{URI.encode_www_form(@site.domain)}/settings#email-reports", class: "py-2 block") %> <% end %> - <%= if @valid_shared_link do %> -
- <%= if @selected_theme == "dark" do %> + <%= if @valid_shared_link && (Plug.Conn.get_session(@conn, "admin_selected_theme") == "user") do %> +
+ <%= if !@selected_theme do %>
+ System OFF - Light/Dark Mode + ON
<% else %> -
- - Light/Dark Mode + ON
+ <%= if @selected_theme == "light" do %> +
+ Dark + + Light +
+ <% else %> +
+ Dark + + Light +
+ <% end %> <% end %>
<% end %> @@ -50,4 +69,7 @@
<% end %> + <%= if (@site.external_css) && !(@conn.assigns[:current_user]) do %> + + <% end %> diff --git a/priv/repo/migrations/20210219100539_add_external_css_to_sites.exs b/priv/repo/migrations/20210219100539_add_external_css_to_sites.exs new file mode 100644 index 000000000000..78d00a720860 --- /dev/null +++ b/priv/repo/migrations/20210219100539_add_external_css_to_sites.exs @@ -0,0 +1,9 @@ +defmodule Plausible.Repo.Migrations.AddExternalCssToSites do + use Ecto.Migration + + def change do + alter table(:sites) do + add :external_css, :string, null: true + end + end +end From b93557e6fc6be41dbbfc9e6e3e5f45cc0c7fe212 Mon Sep 17 00:00:00 2001 From: AymanTerra Date: Mon, 1 Mar 2021 22:06:18 +0200 Subject: [PATCH 2/2] Embeddable mode #213 --- lib/plausible/site/schema.ex | 14 +++++++++----- lib/plausible_web/controllers/stats_controller.ex | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/plausible/site/schema.ex b/lib/plausible/site/schema.ex index d06a4b037945..78ae3e8bf7e8 100644 --- a/lib/plausible/site/schema.ex +++ b/lib/plausible/site/schema.ex @@ -79,12 +79,16 @@ defmodule Plausible.Site do # https://gist.github.com/atomkirk/74b39b5b09c7d0f21763dd55b877f998 defp validate_url(changeset, field, opts \\ []) do - validate_change changeset, field, fn _, value -> + validate_change(changeset, field, fn _, value -> case URI.parse(value) do - %URI{scheme: nil} -> "is missing a scheme (e.g. https)" - %URI{host: nil} -> "is missing a host" + %URI{scheme: nil} -> + "is missing a scheme (e.g. https)" + + %URI{host: nil} -> + "is missing a host" + %URI{host: host} -> - case :inet.gethostbyname(Kernel.to_charlist host) do + case :inet.gethostbyname(Kernel.to_charlist(host)) do {:ok, _} -> nil {:error, _} -> "invalid host" end @@ -93,6 +97,6 @@ defmodule Plausible.Site do error when is_binary(error) -> [{field, Keyword.get(opts, :message, error)}] _ -> [] end - end + end) end end diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex index b765e87ef7b1..8481c79ce915 100644 --- a/lib/plausible_web/controllers/stats_controller.ex +++ b/lib/plausible_web/controllers/stats_controller.ex @@ -89,6 +89,7 @@ defmodule PlausibleWeb.StatsController do embed_link = if(embed_mode, do: "/embed", else: "") theme_index = Enum.find_index(conn.path_info, fn x -> x == "theme" end) + admin_selected_theme = if( theme_index,