diff --git a/.travis.yml b/.travis.yml
index ef80efb34..36959458d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -44,6 +44,8 @@ env:
- FOREM_OWNER_SECRET="secret" # test secret so e2e tests can run properly.
- DISABLE_SPRING=true
before_install:
+ - sudo apt-get update
+ - sudo apt-get -y install gsfonts
- >-
sudo sed -i
-e '/local.*peer/s/postgres/all/'
diff --git a/app/uploaders/base_uploader.rb b/app/uploaders/base_uploader.rb
index 52e2e7674..bd5a22661 100644
--- a/app/uploaders/base_uploader.rb
+++ b/app/uploaders/base_uploader.rb
@@ -26,6 +26,9 @@ class BaseUploader < CarrierWave::Uploader::Base
# strip EXIF (and GPS) data
def strip_exif
+ # There will be no exif data for an SVG
+ return if file.content_type.include?("svg")
+
manipulate! do |image|
image.strip unless image.frames.count > FRAME_STRIP_MAX
image = yield(image) if block_given?
diff --git a/app/uploaders/logo_svg_uploader.rb b/app/uploaders/logo_svg_uploader.rb
new file mode 100644
index 000000000..e119f749e
--- /dev/null
+++ b/app/uploaders/logo_svg_uploader.rb
@@ -0,0 +1,65 @@
+class LogoSvgUploader < BaseUploader
+ # This Uploader is being used by a Data Update Script
+ # lib/data_update_scripts/20211208074428_migrate_logo_svg_data.rb
+ # to convert logo_svg contents to a png file.
+
+ STORE_DIRECTORY = "uploads/logos/".freeze
+ EXTENSION_ALLOWLIST = %w[svg png].freeze
+ IMAGE_TYPE_ALLOWLIST = %i[svg png].freeze
+ CONTENT_TYPE_ALLOWLIST = %w[image/svg+xml image/png].freeze
+
+ process :convert_to_png
+
+ def store_dir
+ STORE_DIRECTORY
+ end
+
+ def extension_allowlist
+ EXTENSION_ALLOWLIST
+ end
+
+ def image_type_whitelist
+ # this is needed by CarrierWave::BombShelter
+ IMAGE_TYPE_ALLOWLIST
+ end
+
+ def content_type_allowlist
+ CONTENT_TYPE_ALLOWLIST
+ end
+
+ def filename
+ # random_string in the filename to avoid caching issues
+ "original_logo_#{random_string}.png"
+ end
+
+ def convert_to_png
+ temp_png_file = Tempfile.new ["output", ".png"]
+
+ begin
+ MiniMagick::Tool::Convert.new do |convert|
+ convert.background("none")
+ convert << file.path
+ convert << temp_png_file.path
+ end
+
+ # https://github.com/minimagick/minimagick/issues/332
+ file.instance_variable_set(:@file, temp_png_file)
+ file.instance_variable_set(:@content_type, "image/png")
+ ensure
+ temp_png_file.close
+ end
+ end
+
+ version :resized_logo do
+ process resize_to_limit: [nil, 80]
+ def full_filename(_for_file = file)
+ "resized_logo_#{random_string}.png"
+ end
+ end
+
+ private
+
+ def random_string
+ SecureRandom.alphanumeric(20)
+ end
+end
diff --git a/app/uploaders/logo_uploader.rb b/app/uploaders/logo_uploader.rb
index fd8e12410..5a8303c74 100644
--- a/app/uploaders/logo_uploader.rb
+++ b/app/uploaders/logo_uploader.rb
@@ -29,13 +29,13 @@ class LogoUploader < BaseUploader
def filename
# random_string in the filename to avoid caching issues
- "original_logo_#{random_string}.#{file.extension}" if original_filename
+ "original_logo_#{random_string}.#{file.extension}"
end
version :resized_logo do
process resize_to_limit: [nil, 80]
def full_filename(_for_file = file)
- "resized_logo_#{random_string}.#{file.extension}" if original_filename
+ "resized_logo_#{random_string}.#{file.extension}"
end
end
diff --git a/lib/data_update_scripts/20220105112823_migrate_logo_svg_data.rb b/lib/data_update_scripts/20220105112823_migrate_logo_svg_data.rb
new file mode 100644
index 000000000..008542d99
--- /dev/null
+++ b/lib/data_update_scripts/20220105112823_migrate_logo_svg_data.rb
@@ -0,0 +1,38 @@
+module DataUpdateScripts
+ class MigrateLogoSvgData
+ def run
+ return if ::Settings::General.try(:logo_svg).blank? ||
+ (::Settings::General.try(:original_logo).present? && ::Settings::General.try(:resized_logo).present?)
+
+ logo_svg = Settings::General.logo_svg
+
+ # We do our best effort here: These are some (possibly not all) of the css variables that we may see in Forem
+ # logos. Hence, we try and replace them before converting.
+ logo_svg.gsub! "currentColor", "#090909"
+ logo_svg.gsub! "var(--base-inverted)", "#fff"
+ logo_svg.gsub! "var(--link-color)", "#3d3d3d"
+
+ Tempfile.create(["logo", ".svg"]) do |file|
+ file.write(logo_svg)
+
+ logo_svg_uploader = LogoSvgUploader.new.tap do |uploader|
+ uploader.store!(file)
+ end
+
+ ::Settings::General.original_logo = logo_svg_uploader.url
+ ::Settings::General.resized_logo = logo_svg_uploader.resized_logo.url
+ end
+ rescue StandardError => e
+ # If we can't convert the logo, then alert ourselves so that we can track the Forems
+ # whose logos could not be converted.
+
+ Rails.logger.error("Could not convert logo_svg for #{Settings::Community.community_name} Forem to a PNG.")
+
+ Honeybadger.notify(e, context: {
+ community_name: Settings::Community.community_name,
+ app_domain: Settings::General.app_domain,
+ },
+ tags: "failed_svg_conversion")
+ end
+ end
+end
diff --git a/spec/lib/data_update_scripts/migrate_logo_svg_data_spec.rb b/spec/lib/data_update_scripts/migrate_logo_svg_data_spec.rb
new file mode 100644
index 000000000..b6ae63068
--- /dev/null
+++ b/spec/lib/data_update_scripts/migrate_logo_svg_data_spec.rb
@@ -0,0 +1,42 @@
+require "rails_helper"
+require Rails.root.join(
+ "lib/data_update_scripts/20220105112823_migrate_logo_svg_data.rb",
+)
+
+describe DataUpdateScripts::MigrateLogoSvgData do
+ # This simply tests the output of the script, however,
+ # the manner in which the uploader behaves is tested by logo_svg_uploader_spec.rb
+
+ context "when the svg is able to be processed" do
+ processable_svg_string = ''
+
+ it "returns the logo url with a png extension", :aggregate_failures do
+ allow(Settings::General).to receive(:logo_svg).and_return(processable_svg_string)
+ described_class.new.run
+
+ expect(::Settings::General.original_logo).to include(".png")
+ expect(::Settings::General.resized_logo).to include(".png")
+ end
+ end
+
+ context "when the svg is unable to be processed" do
+ # rubocop:disable Layout/LineLength
+ unprocessable_svg_string = ''
+ # rubocop:enable Layout/LineLength
+
+ it "logs an error" do
+ allow(Settings::General).to receive(:logo_svg).and_return(unprocessable_svg_string)
+ allow(Honeybadger).to receive(:notify)
+ allow(LogoSvgUploader).to receive(:new).and_raise(StandardError)
+
+ described_class.new.run
+ expect(Honeybadger).to have_received(:notify).once
+ expect(::Settings::General.original_logo).to be(nil)
+ expect(::Settings::General.resized_logo).to be(nil)
+ end
+ end
+end
diff --git a/spec/uploaders/logo_svg_uploader_spec.rb b/spec/uploaders/logo_svg_uploader_spec.rb
new file mode 100644
index 000000000..83cb8fcc0
--- /dev/null
+++ b/spec/uploaders/logo_svg_uploader_spec.rb
@@ -0,0 +1,56 @@
+require "rails_helper"
+require "carrierwave/test/matchers"
+require "exifr/jpeg"
+
+describe LogoSvgUploader, type: :uploader do
+ include CarrierWave::Test::Matchers
+
+ let(:image_svg) { fixture_file_upload("300x100.svg", "image/svg+xml") }
+ let(:image_webp) { fixture_file_upload("800x600.webp", "image/webp") }
+
+ # we need a new uploader before each test, and since the uploader is not a model
+ # we can recreate it quickly in memory with `let!`
+ let!(:uploader) { described_class.new }
+
+ before do
+ described_class.include CarrierWave::MiniMagick # needed for processing
+ described_class.enable_processing = true
+ end
+
+ after do
+ described_class.enable_processing = false
+ uploader.remove!
+ end
+
+ it "stores files in the correct directory" do
+ expect(uploader.store_dir).to eq("uploads/logos/")
+ end
+
+ describe "error handling" do
+ it "raises a CarrierWave error which can be parsed if MiniMagick timeout occurs" do
+ allow(MiniMagick::Image).to receive(:new).and_raise(Timeout::Error)
+
+ expect { uploader.store!(image_svg) }.to raise_error(CarrierWave::IntegrityError, /Image processing timed out/)
+ end
+ end
+
+ describe "processed images" do
+ it "creates versions of the image with different filenames", :aggregate_failures do
+ uploader.store!(image_svg)
+ expect(uploader.filename).to match(/original_logo/)
+ expect(uploader.resized_logo.file.filename).to match(/resized_logo/)
+ end
+
+ it "stores the processed logo's with a png file extension" do
+ uploader.store!(image_svg)
+ expect(uploader.filename).to match(/\.png\z/)
+ expect(uploader.resized_logo.file.filename).to match(/\.png\z/)
+ end
+
+ it "stores the processed logo as a png content type" do
+ uploader.store!(image_svg)
+ expect(uploader.content_type).to match(/png/)
+ expect(uploader.resized_logo.file.content_type).to match(/png/)
+ end
+ end
+end
diff --git a/spec/uploaders/logo_uploader_spec.rb b/spec/uploaders/logo_uploader_spec.rb
index 73ba1b99f..941bdfe79 100644
--- a/spec/uploaders/logo_uploader_spec.rb
+++ b/spec/uploaders/logo_uploader_spec.rb
@@ -5,7 +5,7 @@ require "exifr/jpeg"
describe LogoUploader, type: :uploader do
include CarrierWave::Test::Matchers
- let(:image_svg) { fixture_file_upload("300x100.svg") }
+ let(:image_svg) { fixture_file_upload("300x100.svg", "image/svg+xml") }
let(:image_jpg) { fixture_file_upload("800x600.jpg", "image/jpeg") }
let(:image_png) { fixture_file_upload("800x600.png", "image/png") }
let(:image_webp) { fixture_file_upload("800x600.webp", "image/webp") }