Aggiunge i tornei con hub, pagina pubblica e tabellone per semifinali e finali.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Tournaments
|
||||
class ComposeInviteEmail
|
||||
LINK_TOKEN = "{{link_invito}}"
|
||||
EXPIRY_TOKEN = "{{scadenza}}"
|
||||
MAX_HTML_BYTES = 100_000
|
||||
ALLOWED_TAGS = %w[p br strong b em i u ul ol li a img h2 h3 h4 span div blockquote].freeze
|
||||
ALLOWED_ATTR = %w[href src alt width height style target rel data-invite-note].freeze
|
||||
ALLOWED_STYLE = %w[
|
||||
width height max-width min-width margin margin-top margin-bottom margin-left margin-right
|
||||
padding padding-top padding-bottom padding-left padding-right
|
||||
float display text-align color background background-color
|
||||
font-weight font-size line-height text-decoration border-radius border
|
||||
].freeze
|
||||
|
||||
def self.call(html:, invite_url:, expires_on: nil)
|
||||
new(html: html, invite_url: invite_url, expires_on: expires_on).call
|
||||
end
|
||||
|
||||
def self.sanitize_html(html)
|
||||
new(html: html.to_s, invite_url: "").sanitize(html.to_s)
|
||||
end
|
||||
|
||||
def self.default_html(tournament:, invited_by:)
|
||||
intro = I18n.t(
|
||||
"mailers.tournament_invite.body_html",
|
||||
inviter: CGI.escapeHTML(invited_by.name.to_s),
|
||||
tournament: CGI.escapeHTML(tournament.name.to_s),
|
||||
club: CGI.escapeHTML(tournament.club.name.to_s),
|
||||
assignment: I18n.t("tournaments.hub.invite_email_scope_generic")
|
||||
)
|
||||
cta = CGI.escapeHTML(I18n.t("mailers.tournament_invite.cta"))
|
||||
<<~HTML
|
||||
<p>#{CGI.escapeHTML(I18n.t("mailers.tournament_invite.hello"))}</p>
|
||||
<p>#{intro}</p>
|
||||
<p><a href="#{LINK_TOKEN}" style="display:inline-block;background:#e53935;color:#ffffff;text-decoration:none;padding:12px 18px;border-radius:8px;font-weight:600;">#{cta}</a></p>
|
||||
<p>#{CGI.escapeHTML(I18n.t("mailers.tournament_invite.link_fallback"))}<br>#{LINK_TOKEN}</p>
|
||||
<p>#{CGI.escapeHTML(I18n.t("mailers.tournament_invite.steps"))}</p>
|
||||
<p>#{CGI.escapeHTML(I18n.t("mailers.tournament_invite.expiry", date: EXPIRY_TOKEN))}</p>
|
||||
<p>#{CGI.escapeHTML(I18n.t("mailers.tournament_invite.ignore"))}</p>
|
||||
HTML
|
||||
end
|
||||
|
||||
def initialize(html:, invite_url:, expires_on: nil)
|
||||
@html = html.to_s
|
||||
@invite_url = invite_url.to_s
|
||||
@expires_on = expires_on.to_s
|
||||
end
|
||||
|
||||
def call
|
||||
html = @html.dup
|
||||
html.gsub!(LINK_TOKEN, @invite_url)
|
||||
html.gsub!(EXPIRY_TOKEN, @expires_on) if @expires_on.present?
|
||||
sanitized = sanitize(html)
|
||||
sanitized += fallback_cta if @invite_url.present? && !sanitized.include?(@invite_url)
|
||||
sanitized
|
||||
end
|
||||
|
||||
def sanitize(html)
|
||||
html = html.bytesize > MAX_HTML_BYTES ? html.byteslice(0, MAX_HTML_BYTES) : html
|
||||
fragment = Loofah.fragment(html.to_s)
|
||||
fragment.css("[data-invite-note]").each do |el|
|
||||
el.remove if el.text.to_s.strip.blank?
|
||||
end
|
||||
scrubber = Rails::HTML::PermitScrubber.new
|
||||
scrubber.tags = ALLOWED_TAGS
|
||||
scrubber.attributes = ALLOWED_ATTR
|
||||
fragment.scrub!(scrubber)
|
||||
fragment.css("a").each { |node| scrub_url!(node, "href") }
|
||||
fragment.css("img").each do |node|
|
||||
scrub_url!(node, "src")
|
||||
end
|
||||
fragment.css("[style]").each { |node| scrub_style!(node) }
|
||||
fragment.css("a[target='_blank']").each do |node|
|
||||
node["rel"] = "noopener noreferrer"
|
||||
end
|
||||
fragment.to_s
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scrub_url!(node, attr)
|
||||
url = node[attr].to_s.strip
|
||||
return if url == LINK_TOKEN || url.include?(LINK_TOKEN)
|
||||
return if url.match?(/\Ahttps?:\/\//i) || url.start_with?("/rails/active_storage")
|
||||
|
||||
if attr == "src"
|
||||
node.remove
|
||||
else
|
||||
node.remove_attribute(attr)
|
||||
end
|
||||
end
|
||||
|
||||
def scrub_style!(node)
|
||||
decls = node["style"].to_s.split(";").map(&:strip).reject(&:blank?)
|
||||
kept = decls.select do |decl|
|
||||
prop, value = decl.split(":", 2).map { |part| part.to_s.strip }
|
||||
next false if prop.blank? || value.blank?
|
||||
next false unless ALLOWED_STYLE.include?(prop.downcase)
|
||||
next false if value.match?(/expression|javascript|url\s*\(/i)
|
||||
|
||||
true
|
||||
end
|
||||
if kept.any?
|
||||
node["style"] = kept.join("; ")
|
||||
else
|
||||
node.remove_attribute("style")
|
||||
end
|
||||
end
|
||||
|
||||
def fallback_cta
|
||||
label = CGI.escapeHTML(I18n.t("mailers.tournament_invite.cta"))
|
||||
url = CGI.escapeHTML(@invite_url)
|
||||
%(<p><a href="#{url}" style="display:inline-block;background:#e53935;color:#ffffff;text-decoration:none;padding:12px 18px;border-radius:8px;font-weight:600;">#{label}</a></p><p>#{url}</p>)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user