docbrown/app/services/images/optimizer.rb
Ben Halpern aab0a19011
Allow configuration to conditionally prefer Cloudflare over Cloudinary (#20539)
* Allow configuration to conditionally prefer Cloudflare over Cloudinary

* Update app/services/images/optimizer.rb

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Fix some lints

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-19 09:45:36 -05:00

141 lines
4.5 KiB
Ruby

module Images
module Optimizer
def self.call(img_src, **kwargs)
return img_src if img_src.blank? || img_src.starts_with?("/")
if imgproxy_enabled?
imgproxy(img_src, **kwargs)
elsif cloudinary_enabled? && !cloudflare_contextually_preferred?(img_src)
cloudinary(img_src, **kwargs)
elsif cloudflare_enabled?
cloudflare(img_src, **kwargs)
else
img_src
end
end
# Each service has different ways of describing image cropping.
# for the ideal croping we want.
# Cloudinary uses "fill" and "limit"
# Cloudflare uses "cover" and "scale-down" respectively
# imgproxy uses "fill" and "fit" respectively
DEFAULT_CL_OPTIONS = {
type: "fetch",
height: nil,
width: nil,
crop: "limit",
quality: "auto",
flags: "progressive",
fetch_format: "auto",
sign_url: true
}.freeze
def self.cloudflare(img_src, **kwargs)
template = Addressable::Template.new("https://{domain}/cdn-cgi/image/{options*}/{src}")
fit = kwargs[:crop] == "crop" ? "cover" : "scale-down"
template.expand(
domain: ApplicationConfig["CLOUDFLARE_IMAGES_DOMAIN"],
options: {
width: kwargs[:width],
height: kwargs[:height],
fit: fit,
gravity: "auto",
format: "auto"
},
src: extract_suffix_url(img_src),
).to_s
end
def self.cloudinary(img_src, **kwargs)
options = DEFAULT_CL_OPTIONS.merge(kwargs).compact_blank
imagga = kwargs[:crop] == "crop" && ApplicationConfig["CROP_WITH_IMAGGA_SCALE"].present? && !kwargs[:never_imagga]
options[:crop] = if imagga
"imagga_scale" # Legacy setting if admin imagga_scale set
elsif kwargs[:crop] == "crop"
"fill"
else
"limit"
end
if img_src&.include?(".gif")
options[:quality] = 66
end
ActionController::Base.helpers.cl_image_path(img_src, options)
end
DEFAULT_IMGPROXY_OPTIONS = {
height: nil,
width: nil,
max_bytes: 500_000, # Keep everything under half of one MB.
auto_rotate: true,
gravity: "sm",
resizing_type: "fit"
}.freeze
def self.imgproxy(img_src, **kwargs)
translated_options = translate_cloudinary_options(kwargs)
options = DEFAULT_IMGPROXY_OPTIONS.merge(translated_options).compact_blank
Imgproxy.config.endpoint ||= get_imgproxy_endpoint
Imgproxy.url_for(img_src, options)
end
def self.translate_cloudinary_options(options)
options[:resizing_type] = if options[:crop] == "crop"
"fill"
else
"fit"
end
options[:crop] = nil
options[:fetch_format] = nil
options[:never_imagga] = nil
options
end
def self.imgproxy_enabled?
Imgproxy.config.key.present? && Imgproxy.config.salt.present?
end
def self.cloudinary_enabled?
config = Cloudinary.config
config.cloud_name.present? && config.api_key.present? && config.api_secret.present?
end
def self.cloudflare_enabled?
ApplicationConfig["CLOUDFLARE_IMAGES_DOMAIN"].present?
end
def self.get_imgproxy_endpoint
if Rails.env.production?
# Use /images with the same domain on Production as
# our default configuration
URL.url("images")
# ie. https://forem.dev/images
else
# On other environments, rely on ApplicationConfig for a
# more flexible configuration
# ie. default imgproxy endpoint is localhost:8080
ApplicationConfig["IMGPROXY_ENDPOINT"] || "http://localhost:8080"
end
end
def self.extract_suffix_url(full_url)
prefix = "https://#{ApplicationConfig['CLOUDFLARE_IMAGES_DOMAIN']}/cdn-cgi/image"
return full_url unless full_url&.starts_with?(prefix)
uri = URI.parse(full_url)
match = uri.path.match(%r{https?.+})
CGI.unescape(match[0]) if match
end
# This is a feature-flagged Cloudflare preference for hosted images only — works specifically with S3-hosted image sources.
def self.cloudflare_contextually_preferred?(img_src)
return false unless cloudflare_enabled?
return false unless FeatureFlag.enabled?(:cloudflare_preferred_for_hosted_images)
img_src&.start_with?("https://#{ApplicationConfig['AWS_BUCKET_NAME']}.s3.amazonaws.com")
end
end
end