discourse/app/services/site_setting/splash_screen_image_changed.rb
Kris 72a51e8943
DEV: clean up splash SVG on upload, not when rendering (#37681)
This moves the splash screen SVG processing (SMIL and script stripping)
to upload rather than render for the `splash_screen_image` setting.

While testing I also found a case where SVG dimensions could make splash
images very tiny, so I strip out the dimensions (as long as a viewbox is
present) so the SVG can scale to fit the wrapper better.

---------

Co-authored-by: Régis Hanol <regis@hanol.fr>
Co-authored-by: Martin Brennan <martin@discourse.org>
2026-02-16 13:14:35 +10:00

79 lines
1.9 KiB
Ruby

# frozen_string_literal: true
class SiteSetting::SplashScreenImageChanged
include Service::Base
params { attribute :upload_id, :integer }
model :upload
model :svg
model :cleaned_svg
step :save_cleaned_svg_upload
step :clear_cache
private
def fetch_upload(params:)
Upload.find_by(id: params.upload_id)
end
def fetch_svg(upload:)
content =
begin
upload.content
rescue StandardError
nil
end
return nil if content.blank?
Nokogiri.XML(content).at_css("svg")
end
def fetch_cleaned_svg(svg:)
# Disables SMIL animations which can cause performance issues,
# CSS animations are preferred.
svg.xpath(
".//*[local-name()='animate' or local-name()='animateTransform' or local-name()='animateMotion' or local-name()='set']",
).each(&:remove)
# Remove explicit dimensions so the SVG scales via viewBox
if svg["viewBox"].present?
svg.remove_attribute("width")
svg.remove_attribute("height")
end
svg.to_xml
end
def save_cleaned_svg_upload(cleaned_svg:, upload:)
return if cleaned_svg == upload.content
Tempfile.open(%w[splash_screen .svg]) do |tmp|
tmp.write(cleaned_svg)
tmp.rewind
new_sha1 = Upload.generate_digest(tmp.path)
existing = Upload.find_by(sha1: new_sha1)
if existing && existing.id != upload.id
SiteSetting.splash_screen_image = existing.id
else
old_path = Discourse.store.get_path_for_upload(upload)
old_url = upload.url
upload.tap do |u|
u.sha1 = new_sha1
u.filesize = tmp.size
u.url = Discourse.store.store_upload(tmp, u)
u.save!(validate: false)
end
Discourse.store.remove_file(old_url, old_path) if upload.url != old_url
end
end
end
def clear_cache(upload:)
Discourse.cache.delete("splash_screen_svg_#{upload.id}_#{upload.sha1}")
end
end