Class: Imagekitio::Helper

Inherits:
Object
  • Object
show all
Defined in:
lib/imagekitio/helpers/helper.rb

Instance Method Summary collapse

Constructor Details

#initialize(client:) ⇒ Helper

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Helper.

Parameters:



336
337
338
# File 'lib/imagekitio/helpers/helper.rb', line 336

def initialize(client:)
  @client = client
end

Instance Method Details

#build_transformation_string(transformations) ⇒ String

Generates transformation string from transformation objects

Parameters:

Returns:

  • (String)

    The transformation string (e.g., “w-400,h-300”)



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/imagekitio/helpers/helper.rb', line 134

def build_transformation_string(transformations)
  return "" unless transformations.is_a?(Array)

  parsed_transforms = []

  # rubocop:disable Metrics/BlockLength
  transformations.each do |transform|
    next unless transform

    # Convert model to hash - all transformation inputs are expected to be BaseModel objects
    current_transform = transform.to_h

    parsed_transform_step = []

    # rubocop:disable Metrics/BlockLength
    current_transform.each do |key, value|
      next if value.nil? || value.to_s.empty?

      # Handle overlay separately
      if key.to_s == "overlay" && value
        # Pass model object directly to process_overlay
        raw_string = process_overlay(value)
        if raw_string && !raw_string.strip.empty?
          parsed_transform_step << raw_string
        end
        next
      end

      transform_key = Imagekitio::TransformationUtils.get_transform_key(key)
      transform_key = key.to_s if transform_key.empty?

      next if transform_key.empty?

      # Handle special boolean effects
      boolean_effects = %w[e-grayscale e-contrast e-removedotbg e-bgremove e-upscale e-retouch e-genvar]
      if boolean_effects.include?(transform_key)
        if value == true || value == "-" || value == "true"
          parsed_transform_step << transform_key
        end
        next
      end

      # Handle effects that can be boolean or have values
      value_effects = %w[e-sharpen e-shadow e-gradient e-usm e-dropshadow]
      if value_effects.include?(transform_key) &&
         (value.to_s.strip.empty? || value == true || value == "true")
        parsed_transform_step << transform_key
        next
      end

      # Handle raw parameter
      if key.to_s == "raw"
        parsed_transform_step << value.to_s
        next
      end

      # Handle special cases for di and ff (need special encoding)
      if %w[di ff].include?(transform_key)
        processed_value = remove_leading_slash(remove_trailing_slash(value.to_s))
        processed_value = processed_value.gsub("/", "@@")
        value = processed_value
      end

      # Handle streaming resolutions array
      if transform_key == "sr" && value.is_a?(Array)
        value = value.join("_")
      end

      # Special case for trim with empty string
      if transform_key == "t" && value.to_s.strip.empty?
        value = "true"
      end

      # Convert numeric values to integers if they're whole numbers
      if value.is_a?(Numeric)
        value = value.to_i if value == value.to_i
      end

      parsed_transform_step << "#{transform_key}#{Imagekitio::TransformationUtils.get_transform_key_value_delimiter}#{value}"
    end
    # rubocop:enable Metrics/BlockLength

    unless parsed_transform_step.empty?
      parsed_transforms << parsed_transform_step.join(Imagekitio::TransformationUtils.get_transform_delimiter)
    end
  end
  # rubocop:enable Metrics/BlockLength

  parsed_transforms.join(Imagekitio::TransformationUtils.get_chain_transform_delimiter)
end

#build_url(options) ⇒ String

Builds a URL with transformations applied

Parameters:

Returns:

  • (String)

    The built URL with transformations



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/imagekitio/helpers/helper.rb', line 27

def build_url(options)
  # Convert model to hash - all inputs are expected to be BaseModel objects
  opts = options.to_h

  # Set defaults
  opts[:url_endpoint] ||= ""
  opts[:src] ||= ""
  opts[:transformation_position] ||= :query

  return "" if opts[:src].nil? || opts[:src].empty?

  src = opts[:src].to_s
  is_absolute_url = src.start_with?("http://", "https://")

  begin
    if is_absolute_url
      url_obj = URI.parse(src)
      is_src_parameter_used_for_url = true
    else
      url_obj = URI.parse(opts[:url_endpoint].to_s)
    end
  rescue URI::InvalidURIError
    return ""
  end

  # Add query parameters
  query_params = opts[:query_parameters] || {}
  existing_params = CGI.parse(url_obj.query || "")
  query_params.each do |key, value|
    existing_params[key.to_s] = [value.to_s]
  end

  # Build transformation string
  transformation_string = build_transformation_string(opts[:transformation])

  add_as_query = Imagekitio::TransformationUtils.add_as_query_parameter?(opts) ||
                 is_src_parameter_used_for_url
  transformation_placeholder = "PLEASEREPLACEJUSTBEFORESIGN"

  unless is_absolute_url
    # For non-absolute URLs, construct the path
    endpoint_path = url_obj.path
    path_parts = []

    # Add endpoint path if it's not empty
    if !endpoint_path.empty? && endpoint_path != "/"
      path_parts << endpoint_path
    end

    if !transformation_string.empty? && !add_as_query
      path_parts << "#{TRANSFORMATION_PARAMETER}#{Imagekitio::TransformationUtils.get_chain_transform_delimiter}#{transformation_placeholder}"
    end

    path_parts << src
    url_obj.path = path_join(path_parts)
  end

  # Build query string
  unless existing_params.empty?
    url_obj.query = existing_params.map { |k, v| "#{CGI.escape(k)}=#{CGI.escape(v.first)}" }.join("&")
  end

  # Build final URL
  final_url = url_obj.to_s

  # Add transformation parameter manually to avoid URL encoding
  if !transformation_string.empty? && add_as_query
    separator = url_obj.query && !url_obj.query.empty? ? "&" : "?"
    final_url = "#{final_url}#{separator}#{TRANSFORMATION_PARAMETER}=#{transformation_placeholder}"
  end

  # Replace placeholder with actual transformation string
  unless transformation_string.empty?
    final_url = final_url.gsub(transformation_placeholder, transformation_string)
  end

  # Sign the URL if needed
  if opts[:signed] == true || (opts[:expires_in] && opts[:expires_in].to_i.positive?)
    expiry_timestamp = get_signature_timestamp(opts[:expires_in])

    url_signature = get_signature(
      private_key: @client.private_key,
      url: final_url,
      url_endpoint: opts[:url_endpoint].to_s,
      expiry_timestamp: expiry_timestamp
    )

    # Add signature parameters
    final_url_uri = URI.parse(final_url)
    has_existing_params = final_url_uri.query && !final_url_uri.query.empty?
    separator = has_existing_params ? "&" : "?"

    if expiry_timestamp && expiry_timestamp != DEFAULT_TIMESTAMP
      final_url = "#{final_url}#{separator}#{TIMESTAMP_PARAMETER}=#{expiry_timestamp}"
      final_url = "#{final_url}&#{SIGNATURE_PARAMETER}=#{url_signature}"
    else
      final_url = "#{final_url}#{separator}#{SIGNATURE_PARAMETER}=#{url_signature}"
    end
  end

  final_url
end

#get_authentication_parameters(token: nil, expire: nil) ⇒ Hash{Symbol => String, Integer}

Generates authentication parameters for client-side file uploads using ImageKit’s Upload API V1.

This method creates the required authentication signature that allows secure file uploads directly from the browser or mobile applications without exposing your private API key. The generated parameters include a unique token, expiration timestamp, and HMAC signature.

Parameters:

  • token (String, nil) (defaults to: nil)

    Custom token for the upload session. If not provided, a UUID v4 will be generated automatically.

  • expire (Integer, nil) (defaults to: nil)

    Expiration time in seconds from now. If not provided, defaults to 1800 seconds (30 minutes).

Returns:

  • (Hash{Symbol => String, Integer})

    Authentication parameters object containing:

    • :token (String): Unique identifier for this upload session

    • :expire (Integer): Unix timestamp when these parameters expire

    • :signature (String): HMAC-SHA1 signature for authenticating the upload



237
238
239
240
241
242
243
244
245
246
247
# File 'lib/imagekitio/helpers/helper.rb', line 237

def get_authentication_parameters(token: nil, expire: nil)
  default_time_diff = 60 * 30
  default_expire = Time.now.to_i + default_time_diff

  # Handle falsy values - empty string and nil should generate new token
  final_token = token.nil? || token.to_s.empty? ? generate_token : token
  # Handle falsy values - nil and 0 should use default expire
  final_expire = expire.nil? || expire.zero? ? default_expire : expire

  get_authentication_parameters_internal(final_token, final_expire, @client.private_key)
end

#get_responsive_image_attributes(options) ⇒ Imagekitio::Models::ResponsiveImageAttributes

Generates responsive image attributes for use in HTML <img> tags.

This method creates optimized srcset and sizes attributes for responsive images, enabling browsers to select the most appropriate image size based on the device’s screen width and resolution. Supports three strategies:

  • Width-based (w descriptors): When sizes attribute is provided

  • DPR-based (x descriptors): When width is provided without sizes

  • Fallback (w descriptors): Uses device breakpoints when neither is provided

Parameters:

Returns:



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/imagekitio/helpers/helper.rb', line 260

def get_responsive_image_attributes(options)
  # Convert model to hash for easier access
  opts = options.is_a?(Imagekitio::Internal::Type::BaseModel) ? options.to_h : options

  # Default breakpoint pools
  default_device_breakpoints = [640, 750, 828, 1080, 1200, 1920, 2048, 3840]
  default_image_breakpoints = [16, 32, 48, 64, 96, 128, 256, 384]

  # Extract options
  src = opts[:src]
  url_endpoint = opts[:url_endpoint]
  width = opts[:width]
  sizes = opts[:sizes]
  device_breakpoints = opts[:device_breakpoints] || default_device_breakpoints
  image_breakpoints = opts[:image_breakpoints] || default_image_breakpoints
  transformation = opts[:transformation] || []
  transformation_position = opts[:transformation_position]
  query_parameters = opts[:query_parameters]
  expires_in = opts[:expires_in]
  signed = opts[:signed]

  # Sort and merge breakpoints
  sorted_device_breakpoints = device_breakpoints.sort
  sorted_image_breakpoints = image_breakpoints.sort
  all_breakpoints = (sorted_image_breakpoints + sorted_device_breakpoints).sort.uniq

  # Compute candidate widths and descriptor kind
  result = compute_candidate_widths(
    all_breakpoints: all_breakpoints,
    device_breakpoints: sorted_device_breakpoints,
    explicit_width: width,
    sizes_attr: sizes
  )
  candidates = result[:candidates]
  descriptor_kind = result[:descriptor_kind]

  # Helper to build a single ImageKit URL
  build_url_fn = lambda do |w|
    build_url(
      Imagekitio::Models::SrcOptions.new(
        src: src,
        url_endpoint: url_endpoint,
        query_parameters: query_parameters,
        transformation_position: transformation_position,
        expires_in: expires_in,
        signed: signed,
        transformation: transformation + [
          Imagekitio::Models::Transformation.new(width: w, crop: "at_max") # never upscale beyond original
        ]
      )
    )
  end

  # Build srcset
  src_set_entries = candidates.map.with_index do |w, i|
    # Ensure width is an integer for proper descriptor format (e.g., "640w" not "640.0w")
    width_int = w.to_i
    descriptor = descriptor_kind == :w ? "#{width_int}w" : "#{i + 1}x"
    "#{build_url_fn.call(width_int)} #{descriptor}"
  end
  src_set = src_set_entries.empty? ? nil : src_set_entries.join(", ")

  final_sizes = sizes || (descriptor_kind == :w ? "100vw" : nil)

  # Build and return ResponsiveImageAttributes model
  Imagekitio::Models::ResponsiveImageAttributes.new(
    src: build_url_fn.call(candidates.last.to_i), # largest candidate as integer
    src_set: src_set,
    sizes: final_sizes,
    width: width
  )
end