Class: Cloudinary::Utils

Inherits:
Object show all
Defined in:
lib/cloudinary/utils.rb

Constant Summary collapse

MODE_DOWNLOAD =
"download"
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION =
{:width => :auto, :crop => :limit}
CONDITIONAL_OPERATORS =
{
  "=" => 'eq',
  "!=" => 'ne',
  "<" => 'lt',
  ">" => 'gt',
  "<=" => 'lte',
  ">=" => 'gte',
  "&&" => 'and',
  "||" => 'or',
  "*" => 'mul',
  "/" => 'div',
  "+" => 'add',
  "-" => 'sub',
  "^" => 'pow'
}
PREDEFINED_VARS =
{
  "aspect_ratio"         => "ar",
  "aspectRatio"          => "ar",
  "current_page"         => "cp",
  "currentPage"          => "cp",
  "face_count"           => "fc",
  "faceCount"            => "fc",
  "height"               => "h",
  "initial_aspect_ratio" => "iar",
  "initialAspectRatio"   => "iar",
  "trimmed_aspect_ratio" => "tar",
  "trimmedAspectRatio"   => "tar",
  "initial_height"       => "ih",
  "initialHeight"        => "ih",
  "initial_width"        => "iw",
  "initialWidth"         => "iw",
  "page_count"           => "pc",
  "pageCount"            => "pc",
  "page_x"               => "px",
  "pageX"                => "px",
  "page_y"               => "py",
  "pageY"                => "py",
  "tags"                 => "tags",
  "initial_duration"     => "idu",
  "initialDuration"      => "idu",
  "duration"             => "du",
  "width"                => "w",
  "illustration_score"   => "ils",
  "illustrationScore"    => "ils",
  "context"              => "ctx"
}
SIMPLE_TRANSFORMATION_PARAMS =
{
  :ac => :audio_codec,
  :af => :audio_frequency,
  :br => :bit_rate,
  :cs => :color_space,
  :d  => :default_image,
  :dl => :delay,
  :dn => :density,
  :du => :duration,
  :eo => :end_offset,
  :f  => :fetch_format,
  :g  => :gravity,
  :ki => :keyframe_interval,
  :p  => :prefix,
  :pg => :page,
  :so => :start_offset,
  :sp => :streaming_profile,
  :vc => :video_codec,
  :vs => :video_sampling
}.freeze
URL_KEYS =
%w[
    api_secret
    auth_token
    cdn_subdomain
    cloud_name
    cname
    format
    private_cdn
    resource_type
    secure
    secure_cdn_subdomain
    secure_distribution
    shorten
    sign_url
    type
    url_suffix
    use_root_path
    version
].map(&:to_sym)
TRANSFORMATION_PARAMS =
%w[
    angle
    aspect_ratio
    audio_codec
    audio_frequency
    background
    bit_rate
    border
    color
    color_space
    crop
    custom_function
    default_image
    delay
    density
    dpr
    duration
    effect
    end_offset
    fetch_format
    flags
    fps
    gravity
    height
    if
    keyframe_interval
    offset
    opacity
    overlay
    page
    prefix
    quality
    radius
    raw_transformation
    responsive_width
    size
    start_offset
    streaming_profile
    transformation
    underlay
    variables
    video_codec
    video_sampling
    width
    x
    y
    zoom
].map(&:to_sym)
REMOTE_URL_REGEX =
%r(^ftp:|^https?:|^s3:|^gs:|^data:([\w-]+\/[\w-]+(\.[\w-]+)*(\+[\w-]+)?)?(;[\w-]+=[\w-]+)*;base64,([a-zA-Z0-9\/+\n=]+)$)
LONG_URL_SIGNATURE_LENGTH =
32
SHORT_URL_SIGNATURE_LENGTH =
8
UPLOAD_PREFIX =
'https://api.cloudinary.com'
ALGO_SHA1 =
:sha1
ALGO_SHA256 =
:sha256
ALGORITHM_SIGNATURE =
{
  ALGO_SHA1 => Digest::SHA1,
  ALGO_SHA256 => Digest::SHA256,
}
EXP_REGEXP =
Regexp.new('(\$_*[^_ ]+)|(?<![\$:])('+PREDEFINED_VARS.keys.join("|")+')'+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
EXP_REPLACEMENT =
PREDEFINED_VARS.merge(CONDITIONAL_OPERATORS)
LAYER_KEYWORD_PARAMS =
[
  [:font_weight     ,"normal"],
  [:font_style      ,"normal"],
  [:text_decoration ,"none"],
  [:text_align      ,nil],
  [:stroke          ,"none"],
]
IMAGE_FORMATS =
%w(ai bmp bpg djvu eps eps3 flif gif hdp hpx ico j2k jp2 jpc jpe jpeg jpg miff pdf png psd svg tif tiff wdp webp zip )
AUDIO_FORMATS =
%w(aac aifc aiff flac m4a mp3 ogg wav)
VIDEO_FORMATS =
%w(3g2 3gp asf avi flv h264 m2t m2v m3u8 mka mov mp4 mpeg ogv ts webm wmv )
@@json_decode =
false

Class Method Summary collapse

Class Method Details

.api_sign_request(params_to_sign, api_secret, signature_algorithm = nil) ⇒ Object



476
477
478
479
# File 'lib/cloudinary/utils.rb', line 476

def self.api_sign_request(params_to_sign, api_secret, signature_algorithm = nil)
  to_sign = api_string_to_sign(params_to_sign)
  hash("#{to_sign}#{api_secret}", signature_algorithm, :hexdigest)
end

.api_string_to_sign(params_to_sign) ⇒ Object



472
473
474
# File 'lib/cloudinary/utils.rb', line 472

def self.api_string_to_sign(params_to_sign)
  params_to_sign.map{|k,v| [k.to_s, v.is_a?(Array) ? v.join(",") : v]}.reject{|k,v| v.nil? || v == ""}.sort_by(&:first).map{|k,v| "#{k}=#{v}"}.join("&")
end

.archive_params(options = {}) ⇒ Object

Returns a Hash of parameters used to create an archive

Parameters:

  • options (Hash) (defaults to: {})


1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
# File 'lib/cloudinary/utils.rb', line 1046

def self.archive_params(options = {})
  options = Cloudinary::Utils.symbolize_keys options
  {
    :timestamp=>(options[:timestamp] || Time.now.to_i),
    :type=>options[:type],
    :mode => options[:mode],
    :target_format => options[:target_format],
    :target_public_id=> options[:target_public_id],
    :flatten_folders=>Cloudinary::Utils.as_safe_bool(options[:flatten_folders]),
    :flatten_transformations=>Cloudinary::Utils.as_safe_bool(options[:flatten_transformations]),
    :use_original_filename=>Cloudinary::Utils.as_safe_bool(options[:use_original_filename]),
    :async=>Cloudinary::Utils.as_safe_bool(options[:async]),
    :notification_url=>options[:notification_url],
    :target_tags=>options[:target_tags] && Cloudinary::Utils.build_array(options[:target_tags]),
    :keep_derived=>Cloudinary::Utils.as_safe_bool(options[:keep_derived]),
    :tags=>options[:tags] && Cloudinary::Utils.build_array(options[:tags]),
    :public_ids=>options[:public_ids] && Cloudinary::Utils.build_array(options[:public_ids]),
    :fully_qualified_public_ids=>options[:fully_qualified_public_ids] && Cloudinary::Utils.build_array(options[:fully_qualified_public_ids]),
    :prefixes=>options[:prefixes] && Cloudinary::Utils.build_array(options[:prefixes]),
    :expires_at=>options[:expires_at],
    :transformations => build_eager(options[:transformations]),
    :skip_transformation_name=>Cloudinary::Utils.as_safe_bool(options[:skip_transformation_name]),
    :allow_missing=>Cloudinary::Utils.as_safe_bool(options[:allow_missing])
  }
end

.as_bool(value) ⇒ Object



980
981
982
983
984
985
986
987
988
989
990
991
# File 'lib/cloudinary/utils.rb', line 980

def self.as_bool(value)
  case value
  when nil then nil
  when String then value.downcase == "true" || value == "1"
  when TrueClass then true
  when FalseClass then false
  when Integer then value != 0
  when Symbol then value == :true
  else
    raise "Invalid boolean value #{value} of type #{value.class}"
  end
end

.as_safe_bool(value) ⇒ Object



993
994
995
996
997
998
999
# File 'lib/cloudinary/utils.rb', line 993

def self.as_safe_bool(value)
  case as_bool(value)
  when nil then nil
  when TrueClass then 1
  when FalseClass then 0
  end
end

.asset_file_name(path) ⇒ Object



854
855
856
857
858
859
860
# File 'lib/cloudinary/utils.rb', line 854

def self.asset_file_name(path)
  data = Cloudinary.app_root.join(path).read(:mode=>"rb")
  ext = path.extname
  md5 = Digest::MD5.hexdigest(data)
  public_id = "#{path.basename(ext)}-#{md5}"
  "#{public_id}#{ext}"
end

.base_api_url(path, options = {}) ⇒ String

Creates a base URL for the cloudinary api

Parameters:

  • path (Object)

    Resource name

  • options (Hash) (defaults to: {})

    Additional options

Returns:



731
732
733
734
735
736
737
# File 'lib/cloudinary/utils.rb', line 731

def self.base_api_url(path, options = {})
  cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || UPLOAD_PREFIX
  cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise(CloudinaryException, 'Must supply cloud_name')
  api_version = options[:api_version] || Cloudinary.config.api_version || 'v1_1'

  [cloudinary, api_version, cloud_name, path].join('/')
end

.build_array(array) ⇒ Object



900
901
902
903
904
905
906
# File 'lib/cloudinary/utils.rb', line 900

def self.build_array(array)
  case array
    when Array then array
    when nil then []
    else [array]
  end
end

.build_distribution_domain(options = {}) ⇒ Object



711
712
713
714
715
716
717
718
719
720
721
722
723
# File 'lib/cloudinary/utils.rb', line 711

def self.build_distribution_domain(options = {})
  cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")

  source = options.delete(:source)
  secure = config_option_consume(options, :secure, true)
  private_cdn = config_option_consume(options, :private_cdn)
  secure_distribution = config_option_consume(options, :secure_distribution)
  cname = config_option_consume(options, :cname)
  cdn_subdomain = config_option_consume(options, :cdn_subdomain)
  secure_cdn_subdomain = config_option_consume(options, :secure_cdn_subdomain)

  unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
end

.build_eager(eager) ⇒ Object

Parameters:

  • eager (String|Hash|Array)

    an transformation as a string or hash, with or without a format. The parameter also accepts an array of eager transformations.



1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
# File 'lib/cloudinary/utils.rb', line 1075

def self.build_eager(eager)
  return nil if eager.nil?
  Cloudinary::Utils.build_array(eager).map do
  |transformation, format|
    unless transformation.is_a? String
      transformation = transformation.clone
      if transformation.respond_to?(:delete)
        format = transformation.delete(:format) || format
      end
      transformation = Cloudinary::Utils.generate_transformation_string(transformation, true)
    end
    [transformation, format].compact.join("/")
  end.join("|")
end

.chain_transformation(options, *transformation) ⇒ Object



179
180
181
182
183
184
# File 'lib/cloudinary/utils.rb', line 179

def self.chain_transformation(options, *transformation)
  base_options = extract_config_params(options)
  transformation = transformation.reject(&:nil?)
  base_options[:transformation] = build_array(extract_transformation_params(options)).concat(transformation)
  base_options
end

.cloudinary_api_url(action = 'upload', options = {}) ⇒ Object



739
740
741
742
743
# File 'lib/cloudinary/utils.rb', line 739

def self.cloudinary_api_url(action = 'upload', options = {})
  resource_type = options[:resource_type] || 'image'

  base_api_url([resource_type, action], options)
end

.cloudinary_url(source, options = {}) ⇒ Object

Warning: options are being destructively updated!



514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
# File 'lib/cloudinary/utils.rb', line 514

def self.cloudinary_url(source, options = {})

  patch_fetch_format(options)
  type = options.delete(:type)

  transformation = self.generate_transformation_string(options)

  resource_type = options.delete(:resource_type)
  version = options.delete(:version)
  force_version = config_option_consume(options, :force_version, true)
  format = options.delete(:format)

  shorten = config_option_consume(options, :shorten)
  force_remote = options.delete(:force_remote)

  sign_url = config_option_consume(options, :sign_url)
  secret = config_option_consume(options, :api_secret)
  url_suffix = options.delete(:url_suffix)
  use_root_path = config_option_consume(options, :use_root_path)
  auth_token = config_option_consume(options, :auth_token)
  long_url_signature = config_option_consume(options, :long_url_signature)
  signature_algorithm = config_option_consume(options, :signature_algorithm)
  unless auth_token == false
    auth_token = Cloudinary::AuthToken.merge_auth_token(Cloudinary.config.auth_token, auth_token)
  end

  original_source = source
  return original_source if source.blank?
  if defined?(CarrierWave::Uploader::Base) && source.is_a?(CarrierWave::Uploader::Base)
    resource_type ||= source.resource_type
    type ||= source.storage_type
    source = format.blank? ? source.filename : source.full_public_id
  end
  type = type.to_s unless type.nil?
  resource_type ||= "image"
  source = source.to_s
  unless force_remote
    static_support = Cloudinary.config.static_file_support || Cloudinary.config.static_image_support
    return original_source if !static_support && type == "asset"
    return original_source if (type.nil? || type == "asset") && source.match(%r(^https?:/)i)
    return original_source if source.match(%r(^/(?!images/).*)) # starts with / but not /images/

    source = source.sub(%r(^/images/), '') # remove /images/ prefix  - backwards compatibility
    if type == "asset"
      source, resource_type = Cloudinary::Static.public_id_and_resource_type_from_path(source)
      return original_source unless source # asset not found in Static
      source += File.extname(original_source) unless format
    end
  end

  resource_type, type = finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
  source, source_to_sign = finalize_source(source, format, url_suffix)

  if version.nil? && force_version &&
       source_to_sign.include?("/") &&
       !source_to_sign.match(/^v[0-9]+/) &&
       !source_to_sign.match(/^https?:\//)
    version = 1
  end
  version &&= "v#{version}"

  transformation = transformation.gsub(%r(([^:])//), '\1/')
  if sign_url && ( !auth_token || auth_token.empty?)
    raise(CloudinaryException, "Must supply api_secret") if (secret.nil? || secret.empty?)
    to_sign = [transformation, source_to_sign].reject(&:blank?).join("/")
    to_sign = fully_unescape(to_sign)
    signature_algorithm = long_url_signature ? ALGO_SHA256 : signature_algorithm
    hash = hash("#{to_sign}#{secret}", signature_algorithm)
    signature = Base64.urlsafe_encode64(hash)
    signature = "s--#{signature[0, long_url_signature ? LONG_URL_SIGNATURE_LENGTH : SHORT_URL_SIGNATURE_LENGTH ]}--"
  end

  options[:source] = source
  prefix = build_distribution_domain(options)

  source = [prefix, resource_type, type, signature, transformation, version, source].reject(&:blank?).join("/")

  token = nil
  if sign_url && auth_token && !auth_token.empty?
    auth_token[:url] = URI.parse(source).path
    token = Cloudinary::AuthToken.generate auth_token
  end

  analytics = config_option_consume(options, :analytics, true)
  analytics_token = nil
  if analytics && ! original_source.include?("?") # Disable analytics for public IDs containing query params.
    analytics_token = Cloudinary::Analytics.sdk_analytics_query_param
  end

  query_params = [token, analytics_token].compact.join("&")

  source += "?#{query_params}" unless query_params.empty?
  source
end

.config_option_consume(options, option_name, default_value = nil) ⇒ Object



968
969
970
971
972
# File 'lib/cloudinary/utils.rb', line 968

def self.config_option_consume(options, option_name, default_value = nil)
  return options.delete(option_name) if options.include?(option_name)
  option_value = Cloudinary.config.send(option_name)
  option_value.nil? ? default_value : option_value
end

.config_option_fetch(options, option_name, default_value = nil) ⇒ Object



974
975
976
977
978
# File 'lib/cloudinary/utils.rb', line 974

def self.config_option_fetch(options, option_name, default_value = nil)
  return options.fetch(option_name) if options.include?(option_name)
  option_value = Cloudinary.config.send(option_name)
  option_value.nil? ? default_value : option_value
end

.deep_symbolize_keys(object) ⇒ Object



1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
# File 'lib/cloudinary/utils.rb', line 1027

def self.deep_symbolize_keys(object)
  case object
  when Hash
    result = {}
    object.each do |key, value|
      key = key.to_sym rescue key
      result[key] = deep_symbolize_keys(value)
    end
    result
  when Array
    object.map{|e| deep_symbolize_keys(e)}
  else
    object
  end
end

.download_archive_url(options = {}) ⇒ String

Returns a URL that when invokes creates an archive and returns it.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :resource_type (String|Symbol)

    The resource type of files to include in the archive. Must be one of :image | :video | :raw

  • :type (String|Symbol) — default: :upload

    The specific file type of resources: :upload|:private|:authenticated

  • :tags (String|Symbol|Array) — default: nil

    list of tags to include in the archive

  • :public_ids (String|Array<String>) — default: nil

    list of public_ids to include in the archive

  • :prefixes (String|Array<String>) — default: nil

    Optional list of prefixes of public IDs (e.g., folders).

  • :transformations (String|Array<String>)

    Optional list of transformations. The derived images of the given transformations are included in the archive. Using the string representation of multiple chained transformations as we use for the ‘eager’ upload parameter.

  • :mode (String|Symbol) — default: :create

    return the generated archive file or to store it as a raw resource and return a JSON with URLs for accessing the archive. Possible values: :download, :create

  • :target_format (String|Symbol) — default: :zip
  • :target_public_id (String)

    Optional public ID of the generated raw resource. Relevant only for the create mode. If not specified, random public ID is generated.

  • :flatten_folders (boolean) — default: false

    If true, flatten public IDs with folders to be in the root of the archive. Add numeric counter to the file name in case of a name conflict.

  • :flatten_transformations (boolean) — default: false

    If true, and multiple transformations are given, flatten the folder structure of derived images and store the transformation details on the file name instead.

  • :use_original_filename (boolean)

    Use the original file name of included images (if available) instead of the public ID.

  • :async (boolean) — default: false

    If true, return immediately and perform the archive creation in the background. Relevant only for the create mode.

  • :notification_url (String)

    Optional URL to send an HTTP post request (webhook) when the archive creation is completed.

  • String|Array (String|Array<String] :target_tags Optional array. Allows assigning one or more tag to the generated archive file (for later housekeeping via the admin API).)

    :target_tags Optional array. Allows assigning one or more tag to the generated archive file (for later housekeeping via the admin API).

  • :keep_derived (String) — default: false

    keep the derived images used for generating the archive

Returns:



831
832
833
834
# File 'lib/cloudinary/utils.rb', line 831

def self.download_archive_url(options = {})
  params = Cloudinary::Utils.archive_params(options)
  cloudinary_api_download_url("generate_archive", params, options)
end

.download_folder(folder_path, options = {}) ⇒ String

Creates and returns a URL that when invoked creates an archive of a folder.

Parameters:

  • folder_path (Object)

    Full path (from the root) of the folder to download.

  • options (Hash) (defaults to: {})

    Additional options.

Returns:



848
849
850
851
852
# File 'lib/cloudinary/utils.rb', line 848

def self.download_folder(folder_path, options = {})
  resource_type = options[:resource_type] || "all"

  download_archive_url(options.merge(:resource_type => resource_type, :prefixes => folder_path))
end

.download_generated_sprite(tag, options = {}) ⇒ String

Return a signed URL to the ‘generate_sprite’ endpoint with ‘mode=download’.

Parameters:

  • tag (String|Hash)

    Treated as additional options when hash is passed, otherwise as a tag

  • options (Hash) (defaults to: {})

    Additional options. Should be omitted when tag_or_options is a Hash

Returns:

  • (String)

    The signed URL to download sprite



776
777
778
779
# File 'lib/cloudinary/utils.rb', line 776

def self.download_generated_sprite(tag, options = {})
  params = build_multi_and_sprite_params(tag, options)
  cloudinary_api_download_url("sprite", params, options)
end

.download_multi(tag, options = {}) ⇒ String

Return a signed URL to the ‘multi’ endpoint with ‘mode=download’.

Parameters:

  • tag (String|Hash)

    Treated as additional options when hash is passed, otherwise as a tag

  • options (Hash) (defaults to: {})

    Additional options. Should be omitted when tag_or_options is a Hash

Returns:

  • (String)

    The signed URL to download multi



787
788
789
790
# File 'lib/cloudinary/utils.rb', line 787

def self.download_multi(tag, options = {})
  params = build_multi_and_sprite_params(tag, options)
  cloudinary_api_download_url("multi", params, options)
end

.download_zip_url(options = {}) ⇒ Object

Returns a URL that when invokes creates an zip archive and returns it.



838
839
840
# File 'lib/cloudinary/utils.rb', line 838

def self.download_zip_url(options = {})
  download_archive_url(options.merge(:target_format => "zip"))
end

.encode_context(hash) ⇒ String

Same like encode_hash, with additional escaping of | and = characters

Returns:

  • (String)

    a joined string of all keys and values properly escaped and separated by a pipe character



924
925
926
927
928
929
930
# File 'lib/cloudinary/utils.rb', line 924

def self.encode_context(hash)
  case hash
    when Hash then hash.map{|k,v| "#{k}=#{v.to_s.gsub(/([=|])/, '\\\\\1')}"}.join("|")
    when nil then ""
    else hash
  end
end

.encode_double_array(array) ⇒ Object



932
933
934
935
936
937
938
939
# File 'lib/cloudinary/utils.rb', line 932

def self.encode_double_array(array)
  array = build_array(array)
  if array.length > 0 && array[0].is_a?(Array)
    return array.map{|a| build_array(a).join(",")}.join("|")
  else
    return array.join(",")
  end
end

.encode_hash(hash) ⇒ String

encodes a hash into pipe-delimited key-value pairs string

Returns:

  • (String)

    a joined string of all keys and values separated by a pipe character



912
913
914
915
916
917
918
# File 'lib/cloudinary/utils.rb', line 912

def self.encode_hash(hash)
  case hash
    when Hash then hash.map{|k,v| "#{k}=#{v}"}.join("|")
    when nil then ""
    else hash
  end
end

.extract_config_params(options) ⇒ Object



171
172
173
# File 'lib/cloudinary/utils.rb', line 171

def self.extract_config_params(options)
    options.select{|k,v| URL_KEYS.include?(k)}
end

.extract_transformation_params(options) ⇒ Object



175
176
177
# File 'lib/cloudinary/utils.rb', line 175

def self.extract_transformation_params(options)
  options.select{|k,v| TRANSFORMATION_PARAMS.include?(k)}
end

.finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten) ⇒ Object



629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
# File 'lib/cloudinary/utils.rb', line 629

def self.finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
  type ||= :upload
  if !url_suffix.blank?
    case
    when resource_type.to_s == "image" && type.to_s == "upload"
      resource_type = "images"
      type = nil
    when resource_type.to_s == "image" && type.to_s == "private"
      resource_type = "private_images"
      type = nil
    when resource_type.to_s == "image" && type.to_s == "authenticated"
      resource_type = "authenticated_images"
      type = nil
    when resource_type.to_s == "raw" && type.to_s == "upload"
      resource_type = "files"
      type = nil
    when resource_type.to_s == "video" && type.to_s == "upload"
      resource_type = "videos"
      type = nil
    else
      raise(CloudinaryException, "URL Suffix only supported for image/upload, image/private, image/authenticated, video/upload and raw/upload")
    end
  end
  if use_root_path
    if (resource_type.to_s == "image" && type.to_s == "upload") || (resource_type.to_s == "images" && type.blank?)
      resource_type = nil
      type = nil
    else
      raise(CloudinaryException, "Root path only supported for image/upload")
    end
  end
  if shorten && resource_type.to_s == "image" && type.to_s == "upload"
    resource_type = "iu"
    type = nil
  end
  [resource_type, type]
end

.finalize_source(source, format, url_suffix) ⇒ Object



609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
# File 'lib/cloudinary/utils.rb', line 609

def self.finalize_source(source, format, url_suffix)
  source = source.gsub(%r(([^:])//), '\1/')
  if source.match(%r(^https?:/)i)
    source = smart_escape(source)
    source_to_sign = source
  else
    source = smart_escape(smart_unescape(source))
    source_to_sign = source
    unless url_suffix.blank?
      raise(CloudinaryException, "url_suffix should not include . or /") if url_suffix.match(%r([\./]))
      source = "#{source}/#{url_suffix}"
    end
    if !format.blank?
      source = "#{source}.#{format}"
      source_to_sign = "#{source_to_sign}.#{format}"
    end
  end
  [source, source_to_sign]
end

.generate_auth_token(options) ⇒ Object



1090
1091
1092
1093
1094
# File 'lib/cloudinary/utils.rb', line 1090

def self.generate_auth_token(options)
  options = Cloudinary::AuthToken.merge_auth_token Cloudinary.config.auth_token, options
  Cloudinary::AuthToken.generate options

end

.generate_responsive_breakpoints_string(breakpoints) ⇒ Object



495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/cloudinary/utils.rb', line 495

def self.generate_responsive_breakpoints_string(breakpoints)
  return nil if breakpoints.nil?
  breakpoints = build_array(breakpoints)

  breakpoints.map do |breakpoint_settings|
    unless breakpoint_settings.nil?
      breakpoint_settings = breakpoint_settings.clone
      transformation =  breakpoint_settings.delete(:transformation) || breakpoint_settings.delete("transformation")
      format =  breakpoint_settings.delete(:format) || breakpoint_settings.delete("format")
      if transformation
        transformation = Cloudinary::Utils.generate_transformation_string(transformation.clone, true)
      end
      breakpoint_settings[:transformation] = [transformation, format].compact.join("/")
    end
    breakpoint_settings
  end.to_json
end

.generate_transformation_string(options = {}, allow_implicit_crop_mode = false) ⇒ Object

Warning: options are being destructively updated!



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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
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
# File 'lib/cloudinary/utils.rb', line 188

def self.generate_transformation_string(options={}, allow_implicit_crop_mode = false)
  # allow_implicit_crop_mode was added to support height and width parameters without specifying a crop mode.
  # This only apply to this (cloudinary_gem) SDK

  if options.is_a?(Array)
    return options.map{|base_transformation| generate_transformation_string(base_transformation.clone, allow_implicit_crop_mode)}.reject(&:blank?).join("/")
  end

  symbolize_keys!(options)

  responsive_width = config_option_consume(options, :responsive_width)
  size = options.delete(:size)
  options[:width], options[:height] = size.split("x") if size
  width = options[:width]
  width = width.to_s if width.is_a?(Symbol)
  height = options[:height]
  has_layer = options[:overlay].present? || options[:underlay].present?

  crop = options.delete(:crop)
  angle = build_array(options.delete(:angle)).join(".")

  no_html_sizes = has_layer || angle.present? || crop.to_s == "fit" || crop.to_s == "limit" || crop.to_s == "lfill"
  options.delete(:width) if width && (width.to_f < 1 || no_html_sizes || width.to_s.start_with?("auto") || responsive_width)
  options.delete(:height) if height && (height.to_f < 1 || no_html_sizes || responsive_width)

  width=height=nil if crop.nil? && !has_layer && !width.to_s.start_with?("auto") && !allow_implicit_crop_mode

  background = options.delete(:background)
  background = background.sub(/^#/, 'rgb:') if background

  color = options.delete(:color)
  color = color.sub(/^#/, 'rgb:') if color

  base_transformations = build_array(options.delete(:transformation))
  if base_transformations.any?{|base_transformation| base_transformation.is_a?(Hash)}
    base_transformations = base_transformations.map do
      |base_transformation|
      base_transformation.is_a?(Hash) ? generate_transformation_string(base_transformation.clone, allow_implicit_crop_mode) : generate_transformation_string({:transformation=>base_transformation}, allow_implicit_crop_mode)
    end
  else
    named_transformation = base_transformations.join(".")
    base_transformations = []
  end

  effect = options.delete(:effect)
  effect = Array(effect).flatten.join(":") if effect.is_a?(Array) || effect.is_a?(Hash)

  border = options.delete(:border)
  if border.is_a?(Hash)
    border = "#{border[:width] || 2}px_solid_#{(border[:color] || "black").sub(/^#/, 'rgb:')}"
  elsif border.to_s =~ /^\d+$/ # fallback to html border attribute
    options[:border] = border
    border = nil
  end
  flags = build_array(options.delete(:flags)).join(".")
  dpr = config_option_consume(options, :dpr)

  if options.include? :offset
    options[:start_offset], options[:end_offset] = split_range options.delete(:offset)
  end

  fps = options.delete(:fps)
  fps = fps.join('-') if fps.is_a? Array

  overlay = process_layer(options.delete(:overlay))
  underlay = process_layer(options.delete(:underlay))
  ifValue = process_if(options.delete(:if))
  custom_function = process_custom_function(options.delete(:custom_function))
  custom_pre_function = process_custom_pre_function(options.delete(:custom_pre_function))

  params = {
    :a   => normalize_expression(angle),
    :ar => normalize_expression(options.delete(:aspect_ratio)),
    :b   => background,
    :bo  => border,
    :c   => crop,
    :co  => color,
    :dpr => normalize_expression(dpr),
    :e   => normalize_expression(effect),
    :fl  => flags,
    :fn  => custom_function || custom_pre_function,
    :fps => fps,
    :h   => normalize_expression(height),
    :l  => overlay,
    :o => normalize_expression(options.delete(:opacity)),
    :q => normalize_expression(options.delete(:quality)),
    :r => process_radius(options.delete(:radius)),
    :t   => named_transformation,
    :u  => underlay,
    :w   => normalize_expression(width),
    :x => normalize_expression(options.delete(:x)),
    :y => normalize_expression(options.delete(:y)),
    :z => normalize_expression(options.delete(:zoom))
  }
  SIMPLE_TRANSFORMATION_PARAMS.each do
    |param, option|
    params[param] = options.delete(option)
  end

  params[:vc] = process_video_params params[:vc] if params[:vc].present?
  [:so, :eo, :du].each do |range_value|
    params[range_value] = norm_range_value params[range_value] if params[range_value].present?
  end

  variables = options.delete(:variables)
  var_params = []
  options.each_pair do |key, value|
    if key =~ /^\$/
      var_params.push "#{key}_#{normalize_expression(value.to_s)}"
    end
  end
  var_params.sort!
  unless variables.nil? || variables.empty?
    for name, value in variables
      var_params.push "#{name}_#{normalize_expression(value.to_s)}"
    end
  end
  variables = var_params.join(',')

  raw_transformation = options.delete(:raw_transformation)
  transformation = params.reject{|_k,v| v.blank?}.map{|k,v| "#{k}_#{v}"}.sort
  transformation = transformation.join(",")
  transformation = [ifValue, variables, transformation, raw_transformation].reject(&:blank?).join(",")

  transformations = base_transformations << transformation
  if responsive_width
    responsive_width_transformation = Cloudinary.config.responsive_width_transformation || DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION
    transformations << generate_transformation_string(responsive_width_transformation.clone, allow_implicit_crop_mode)
  end

  if width.to_s.start_with?( "auto") || responsive_width
    options[:responsive] = true
  end
  if dpr.to_s == "auto"
    options[:hidpi] = true
  end

  transformations.reject(&:blank?).join("/")
end

.json_array_param(data) ⇒ String|nil

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 JSON array as String. Yields the array before it is converted to JSON format

Parameters:

Returns:

  • (String|nil)

    a JSON array string or ‘nil` if data is `nil`



486
487
488
489
490
491
492
493
# File 'lib/cloudinary/utils.rb', line 486

def self.json_array_param(data)
  return nil if data.nil?

  data = JSON.parse(data) if data.is_a?(String)
  data = [data] unless data.is_a?(Array)
  data = yield data if block_given?
  JSON.generate(data)
end

.json_decode(str) ⇒ Object



884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
# File 'lib/cloudinary/utils.rb', line 884

def self.json_decode(str)
  if !@@json_decode
    @@json_decode = true
    begin
      require 'json'
    rescue LoadError
      begin
        require 'active_support/json'
      rescue LoadError
        raise LoadError, "Please add the json gem or active_support to your Gemfile"
      end
    end
  end
  defined?(JSON) ? JSON.parse(str) : ActiveSupport::JSON.decode(str)
end

.normalize_expression(expression) ⇒ Object



339
340
341
342
343
344
345
346
347
# File 'lib/cloudinary/utils.rb', line 339

def self.normalize_expression(expression)
  if expression.nil?
    nil
  elsif expression.is_a?( String) && expression =~ /^!.+!$/ # quoted string
    expression
  else
    expression.to_s.gsub(EXP_REGEXP) { |match| EXP_REPLACEMENT[match] || match }.gsub(/[ _]+/, "_")
  end
end

.private_download_url(public_id, format, options = {}) ⇒ Object



792
793
794
795
796
797
798
799
800
801
802
803
# File 'lib/cloudinary/utils.rb', line 792

def self.private_download_url(public_id, format, options = {})
  cloudinary_params = sign_request({
      :timestamp=>Time.now.to_i,
      :public_id=>public_id,
      :format=>format,
      :type=>options[:type],
      :attachment=>options[:attachment],
      :expires_at=>options[:expires_at] && options[:expires_at].to_i
    }, options)

  return Cloudinary::Utils.cloudinary_api_url("download", options) + "?" + hash_query_params(cloudinary_params)
end

.process_if(if_value) ⇒ string

Parse “if” parameter Translates the condition if provided.

Returns:

  • (string)

    “if_” + ifValue



332
333
334
# File 'lib/cloudinary/utils.rb', line 332

def self.process_if(if_value)
  "if_" + normalize_expression(if_value) unless if_value.to_s.empty?
end

.random_public_idObject



874
875
876
877
# File 'lib/cloudinary/utils.rb', line 874

def self.random_public_id
  sr = defined?(ActiveSupport::SecureRandom) ? ActiveSupport::SecureRandom : SecureRandom
  sr.base64(20).downcase.gsub(/[^a-z0-9]/, "").sub(/^[0-9]+/, '')[0,20]
end

.resource_type_for_format(format) ⇒ Object



957
958
959
960
961
962
963
964
965
966
# File 'lib/cloudinary/utils.rb', line 957

def self.resource_type_for_format(format)
  case
  when self.supported_format?(format, IMAGE_FORMATS)
    'image'
  when self.supported_format?(format, VIDEO_FORMATS), self.supported_format?(format, AUDIO_FORMATS)
    'video'
  else
    'raw'
  end
end

.safe_blank?(value) ⇒ Boolean

Returns:

  • (Boolean)


1001
1002
1003
# File 'lib/cloudinary/utils.rb', line 1001

def self.safe_blank?(value)
  value.nil? || value == "" || value == []
end

.sign_request(params, options = {}) ⇒ Object



745
746
747
748
749
750
751
752
753
# File 'lib/cloudinary/utils.rb', line 745

def self.sign_request(params, options={})
  api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
  api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
  signature_algorithm = options[:signature_algorithm]
  params = params.reject{|k, v| self.safe_blank?(v)}
  params[:signature] = api_sign_request(params, api_secret, signature_algorithm)
  params[:api_key] = api_key
  params
end

.signed_preloaded_image(result) ⇒ Object



879
880
881
# File 'lib/cloudinary/utils.rb', line 879

def self.signed_preloaded_image(result)
  "#{result["resource_type"]}/#{result["type"] || "upload"}/v#{result["version"]}/#{[result["public_id"], result["format"]].reject(&:blank?).join(".")}##{result["signature"]}"
end

.smart_escape(string, unsafe = /([^a-zA-Z0-9_.\-\/:]+)/) ⇒ Object

Based on CGI::escape. In addition does not escape / :



863
864
865
866
867
# File 'lib/cloudinary/utils.rb', line 863

def self.smart_escape(string, unsafe = /([^a-zA-Z0-9_.\-\/:]+)/)
  string.gsub(unsafe) do |m|
    '%' + m.unpack('H2' * m.bytesize).join('%').upcase
  end
end

.smart_unescape(string) ⇒ Object

Based on CGI::unescape. In addition keeps ‘+’ character as is



870
871
872
# File 'lib/cloudinary/utils.rb', line 870

def self.smart_unescape(string)
  CGI.unescape(string.gsub('+', '%2B'))
end

.supported_format?(format, formats) ⇒ Boolean

Returns:

  • (Boolean)


951
952
953
954
955
# File 'lib/cloudinary/utils.rb', line 951

def self.supported_format?( format, formats)
  format = format.to_s.downcase
  extension = format =~ /\./ ? format.split('.').last : format
  formats.include?(extension)
end

.supported_image_format?(format) ⇒ Boolean

Returns:

  • (Boolean)


947
948
949
# File 'lib/cloudinary/utils.rb', line 947

def self.supported_image_format?(format)
  supported_format? format, IMAGE_FORMATS
end

.symbolize_keys(h) ⇒ Object



1005
1006
1007
1008
1009
1010
1011
1012
1013
# File 'lib/cloudinary/utils.rb', line 1005

def self.symbolize_keys(h)
  new_h = Hash.new
  if (h.respond_to? :keys)
    h.keys.each do |key|
      new_h[(key.to_sym rescue key)] = h[key]
    end
  end
  new_h
end

.symbolize_keys!(h) ⇒ Object



1016
1017
1018
1019
1020
1021
1022
1023
1024
# File 'lib/cloudinary/utils.rb', line 1016

def self.symbolize_keys!(h)
  if (h.respond_to? :keys) && (h.respond_to? :delete)
    h.keys.each do |key|
      value = h.delete(key)
      h[(key.to_sym rescue key)] = value
    end
  end
  h
end

.text_style(layer) ⇒ Object



445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/cloudinary/utils.rb', line 445

def self.text_style(layer)
  return layer[:text_style] if layer[:text_style].present?

  font_family = layer[:font_family]
  font_size   = layer[:font_size]
  keywords    = []
  LAYER_KEYWORD_PARAMS.each do |attr, default_value|
    attr_value = layer[attr] || default_value
    keywords.push(attr_value) unless attr_value == default_value
  end
  letter_spacing = layer[:letter_spacing]
  keywords.push("letter_spacing_#{letter_spacing}") unless letter_spacing.blank?
  line_spacing = layer[:line_spacing]
  keywords.push("line_spacing_#{line_spacing}") unless line_spacing.blank?
  font_antialiasing = layer[:font_antialiasing]
  keywords.push("antialias_#{font_antialiasing}") unless font_antialiasing.blank?
  font_hinting = layer[:font_hinting]
  keywords.push("hinting_#{font_hinting}") unless font_hinting.blank?
  if !font_size.blank? || !font_family.blank? || !keywords.empty?
    raise(CloudinaryException, "Must supply font_family for text in overlay/underlay") if font_family.blank?
    raise(CloudinaryException, "Must supply font_size for text in overlay/underlay") if font_size.blank?
    keywords.unshift(font_size)
    keywords.unshift(font_family)
    keywords.reject(&:blank?).join("_")
  end
end

.unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution) ⇒ Object

Creates the URL prefix for the cloudinary resource URL

cdn_subdomain and secure_cdn_subdomain

  1. Customers in shared distribution (e.g. res.cloudinary.com)

    if cdn_domain is true uses res-[1-5 ].cloudinary.com for both http and https. Setting secure_cdn_subdomain to false disables this for https.

  2. Customers with private cdn

    if cdn_domain is true uses cloudname-res-[1-5 ].cloudinary.com for http

    if secure_cdn_domain is true uses cloudname-res-[1-5 ].cloudinary.com for https (please contact support if you require this)

  3. Customers with cname

    if cdn_domain is true uses a.cname for http. For https, uses the same naming scheme as 1 for shared distribution and as 2 for private distribution.



682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
# File 'lib/cloudinary/utils.rb', line 682

def self.unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
  return "/res#{cloud_name}" if cloud_name.start_with?("/") # For development

  shared_domain = !private_cdn

  if secure
    if secure_distribution.nil?
      secure_distribution = private_cdn ? "#{cloud_name}-res.cloudinary.com" : Cloudinary::SHARED_CDN
    end
    shared_domain ||= secure_distribution == Cloudinary::SHARED_CDN
    secure_cdn_subdomain = cdn_subdomain if secure_cdn_subdomain.nil? && shared_domain

    if secure_cdn_subdomain
      secure_distribution = secure_distribution.gsub('res.cloudinary.com', "res-#{(Zlib::crc32(source) % 5) + 1}.cloudinary.com")
    end

    prefix = "https://#{secure_distribution}"
  elsif cname
    subdomain = cdn_subdomain ? "a#{(Zlib::crc32(source) % 5) + 1}." : ""
    prefix = "http://#{subdomain}#{cname}"
  else
    host = [private_cdn ? "#{cloud_name}-" : "", "res", cdn_subdomain ? "-#{(Zlib::crc32(source) % 5) + 1}" : "", ".cloudinary.com"].join
    prefix = "http://#{host}"
  end
  prefix += "/#{cloud_name}" if shared_domain

  prefix
end