Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
1733 lines (1534 sloc) 62.4 KB
<?php
use Cloudinary\HttpClient;
class Cloudinary
{
const CF_SHARED_CDN = "d3jpl91pxevbkh.cloudfront.net";
const OLD_AKAMAI_SHARED_CDN = "cloudinary-a.akamaihd.net";
const AKAMAI_SHARED_CDN = "res.cloudinary.com";
const SHARED_CDN = "res.cloudinary.com";
const BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
const RANGE_VALUE_RE = '/^(?P<value>(\d+\.)?\d+)(?P<modifier>[%pP])?$/';
const RANGE_RE = '/^(\d+\.)?\d+[%pP]?\.\.(\d+\.)?\d+[%pP]?$/';
const VERSION = "1.14.0";
/**
* @internal
* @var array a list of keys used by the cloudinary_url function
*/
public static $URL_KEYS = array(
'api_secret',
'auth_token',
'cdn_subdomain',
'cloud_name',
'cname',
'format',
'private_cdn',
'resource_type',
'secure',
'secure_cdn_subdomain',
'secure_distribution',
'shorten',
'sign_url',
'ssl_detected',
'type',
'url_suffix',
'use_root_path',
'version'
);
/**
* Contains information about SDK user agent. Passed to the Cloudinary servers.
*
* Initialized on the first call to {@see self::userAgent()}
*
* Sample value: CloudinaryPHP/1.2.3 (PHP 5.6.7)
*
* @internal
* Do not change this value
*/
private static $USER_AGENT = "";
/**
* Additional information to be passed with the USER_AGENT, e.g. "CloudinaryMagento/1.0.1".
* This value is set in platform-specific
* implementations that use cloudinary_php.
*
* The format of the value should be <ProductName>/Version[ (comment)].
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
*
* @internal
* <b>Do not set this value in application code!</b>
*
* @var string
*/
public static $USER_PLATFORM = "";
public static $DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = array("width" => "auto", "crop" => "limit");
private static $config = null;
public static $JS_CONFIG_PARAMS = array(
"api_key",
"cloud_name",
"private_cdn",
"secure_distribution",
"cdn_subdomain",
);
/**
* Provides the {@see self::$USER_AGENT} string that is passed to the Cloudinary servers.
*
* Prepends {@see self::$USER_PLATFORM} if it is defined.
*
* @return string
*/
public static function userAgent()
{
if (empty(self::$USER_AGENT)) {
self::$USER_AGENT = 'CloudinaryPHP/' . self::VERSION . ' (PHP ' . PHP_VERSION. ')';
}
if (empty(self::$USER_PLATFORM)) {
return self::$USER_AGENT;
}
return self::$USER_PLATFORM . ' ' . self::$USER_AGENT;
}
public static function is_not_null($var)
{
return !is_null($var);
}
/**
* @internal
* When upload type is fetch, remove the format options.
* In addition, set the fetch_format options to the format value unless it was already set.
* Mutates the $options parameter!
* @param array $options URL and transformation options
*/
public static function patch_fetch_format(&$options)
{
$type = Cloudinary::option_get($options, "type", "upload");
if ($type != "fetch") return;
// format does not apply to fetch resources since they are identified by a URL
$format = Cloudinary::option_consume($options, "format");
if (!isset($options["fetch_format"])) {
$options["fetch_format"] = $format;
}
}
public static function config($values = null)
{
if (self::$config == null) {
self::reset_config();
}
if ($values != null) {
self::$config = array_merge(self::$config, $values);
}
return self::$config;
}
public static function reset_config()
{
self::config_from_url(getenv("CLOUDINARY_URL"));
}
public static function config_from_url($cloudinary_url)
{
self::$config = array();
if ($cloudinary_url) {
$uri = parse_url($cloudinary_url);
$q_params = array();
if (isset($uri["query"])) {
parse_str($uri["query"], $q_params);
}
$private_cdn = isset($uri["path"]) && $uri["path"] != "/";
$config = array_merge(
$q_params,
array(
"cloud_name" => $uri["host"],
"api_key" => $uri["user"],
"api_secret" => $uri["pass"],
"private_cdn" => $private_cdn,
)
);
if ($private_cdn) {
$config["secure_distribution"] = substr($uri["path"], 1);
}
self::$config = array_merge(self::$config, $config);
}
}
public static function config_get($option, $default = null)
{
return Cloudinary::option_get(self::config(), $option, $default);
}
public static function option_get($options, $option, $default = null)
{
if (isset($options[$option])) {
return $options[$option];
} else {
return $default;
}
}
public static function option_consume(&$options, $option, $default = null)
{
$value = self::option_get($options, $option, $default);
unset($options[$option]);
return $value;
}
public static function build_array($value)
{
if (is_null($value)) {
return array();
}
if (is_array($value) && !Cloudinary::is_assoc($value)) {
return $value;
}
return array($value);
}
/**
* Converts a value that can be presented as an array of associative arrays.
*
* In case top level item is not an array, it is wrapped with an array
*
* @param array|string $value The value to be converted
*
* Valid values examples:
* - Valid assoc array: array("k" => "v", "k2"=> "v2")
* - Array of assoc arrays: array(array("k" => "v"), array("k2" =>"v2"))
* - JSON decodable string: '{"k": "v"}', or '[{"k": "v"}]'
*
* Invalid values examples:
* - array("not", "an", "assoc", "array")
* - array(123, None),
* - array(array("another", "array"))
*
* @return array|mixed Converted(or original) array of associative arrays
*
* @throws InvalidArgumentException in case value cannot be converted to an array of associative arrays
*/
private static function build_array_of_assoc_arrays($value)
{
if (is_string($value)) {
$value = Cloudinary::json_decode_cb($value, 'Cloudinary::ensure_assoc');
if (is_null($value)) {
throw new InvalidArgumentException("Failed parsing JSON string value");
}
}
$value = Cloudinary::build_array($value);
if (!self::is_array_of_assoc($value)) {
throw new InvalidArgumentException("Expected an array of associative arrays");
}
return $value;
}
static function ensure_assoc($item)
{
if (is_string($item)) {
$item = json_decode($item, true);
if (is_null($item)) {
throw new InvalidArgumentException("Failed parsing JSON string item");
}
}
if (!Cloudinary::is_assoc($item)) {
throw new InvalidArgumentException("Expected an array of associative arrays");
}
return $item;
}
/**
* Encodes an array of associative arrays to JSON.
*
* This is a wrapper around json_encode with additional preprocessing (DateTime formatting, etc)
*
* @param array $array An array of associative arrays
*
* @return string Resulting JSON string
*
* @throws InvalidArgumentException in case the value is not an array of associative arrays
*/
private static function json_encode_array_of_assoc_arrays($array)
{
return self::json_encode_cb($array, 'Cloudinary::encode_dates');
}
static function encode_dates($value)
{
if ($value instanceof DateTime) {
$value = $value->format(DateTime::ISO8601);
}
return $value;
}
static function is_array_of_assoc($array)
{
if (!is_array($array)) {
return false;
}
foreach ($array as &$item) {
if (!Cloudinary::is_assoc($item)) {
return false;
}
}
return true;
}
/**
* Returns the JSON representation of a value
* @param mixed $array <p>
* The <i>value</i> being encoded. Can be any type except
* a resource.
* </p>
* @param callable $encoder [optional] <p>
* An encoder that will be invoked for each value.
* </p>
* @param int $options [optional] <p>
* Bitmask consisting of <b>JSON_HEX_QUOT</b>,
* <b>JSON_HEX_TAG</b>,
* <b>JSON_HEX_AMP</b>,
* <b>JSON_HEX_APOS</b>,
* <b>JSON_NUMERIC_CHECK</b>,
* <b>JSON_PRETTY_PRINT</b>,
* <b>JSON_UNESCAPED_SLASHES</b>,
* <b>JSON_FORCE_OBJECT</b>,
* <b>JSON_UNESCAPED_UNICODE</b>. The behaviour of these
* constants is described on
* the JSON constants page.
* </p>
* @param int $depth [optional] <p>
* Set the maximum depth. Must be greater than zero.
* </p>
* @return string a JSON encoded string on success or <b>FALSE</b> on failure.
*/
public static function json_encode_cb($array, $encoder = null, $options = 0, $depth = 512)
{
if (!is_array($array)) {
throw new InvalidArgumentException("Expected an array of associative arrays");
}
foreach ($array as &$item) {
$is_assoc = Cloudinary::is_assoc($item);
if (!$is_assoc) {
throw new InvalidArgumentException("Expected an array of associative arrays");
}
if (!is_null($encoder)) {
foreach ($item as $key => $value) {
$item[$key] = call_user_func($encoder, $value);
}
}
}
return \json_encode($array);
}
public static function json_decode_cb($json, $decoder)
{
if (!is_string($json)) {
throw new InvalidArgumentException("Expected an string");
}
$array = json_decode($json, true);
if (!is_null($decoder) && !is_null($array)) {
foreach ($array as $key => $value) {
try {
$array[$key] = call_user_func($decoder, $value);
} catch (Exception $e) {
}
}
}
return $array;
}
/**
* Wrapper for calling build_array_of_assoc_arrays and json_encode_array_of_assoc_arrays with null value handling.
*
* @see Cloudinary::json_encode_array_of_assoc_arrays
* @see Cloudinary::build_array_of_assoc_arrays
*
* @param array|string $value The value to be converted
*
* @return string Resulting JSON string
*
* @throws InvalidArgumentException in case value cannot be converted and encoded
*/
public static function encode_array_to_json($value)
{
if (is_null($value)) {
return null;
}
$array = Cloudinary::build_array_of_assoc_arrays($value);
return Cloudinary::json_encode_cb($array, 'Cloudinary::encode_dates');
}
public static function encode_array($array)
{
return implode(",", Cloudinary::build_array($array));
}
public static function encode_double_array($array)
{
$array = Cloudinary::build_array($array);
if (count($array) > 0 && !is_array($array[0])) {
return Cloudinary::encode_array($array);
} else {
$array = array_map('Cloudinary::encode_array', $array);
}
return implode("|", $array);
}
public static function encode_assoc_array($array)
{
if (Cloudinary::is_assoc($array)) {
$encoded = array();
foreach ($array as $key => $value) {
if (!empty($value)) {
$value = preg_replace('/([\|=])/', '\\\$1', $value);
}
array_push($encoded, $key . '=' . $value);
}
return implode("|", $encoded);
} else {
return $array;
}
}
/**
* Encodes data with URL safe base64
*
* @see https://tools.ietf.org/html/rfc4648#section-5
*
* @param mixed $data The data to encode.
*
* @return string The encoded data, as a string.
*/
private static function base64url_encode($data)
{
return strtr(base64_encode($data), '+/', '-_');
}
/**
* Helper function for making a recursive array copy while cloning objects on the way.
*
* @param array $array Source array
*
* @return array Recursive copy of the source array
*/
public static function array_copy($array)
{
if (!is_array($array)) {
return $array;
}
$result = array();
foreach ($array as $key => $val) {
if (is_array($val)) {
$result[$key] = self::array_copy($val);
} elseif (is_object($val)) {
$result[$key] = clone $val;
} else {
$result[$key] = $val;
}
}
return $result;
}
/**
* Returns subset of associative array specified by array of keys
*
* @param array $array Source associative array
* @param array $keys Simple array of keys
*
* @return array Resulting array
*/
public static function array_subset($array, $keys)
{
return array_intersect_key($array, array_flip($keys));
}
private static function is_assoc($array)
{
if (!is_array($array)) {
return false;
}
return $array != array_values($array);
}
/** @internal
* Prepends associative element to the beginning of an array
*
* @param array $arr The input array.
* @param mixed $key The prepended key
* @param mixed $val The prepended value
*
* @return array The resulting array
*/
public static function array_unshift_assoc(&$arr, $key, $val)
{
$arr = array_reverse($arr, true);
$arr[$key] = $val;
$arr = array_reverse($arr, true);
return $arr;
}
private static function generate_base_transformation($base_transformation)
{
$options = is_array(
$base_transformation
) ? $base_transformation : array("transformation" => $base_transformation);
return Cloudinary::generate_transformation_string($options);
}
// Warning: $options are being destructively updated!
public static function generate_transformation_string(&$options = array())
{
$generate_base_transformation = "Cloudinary::generate_base_transformation";
if (is_string($options)) {
return $options;
}
if ($options == array_values($options)) {
return implode("/", array_map($generate_base_transformation, $options));
}
$responsive_width = Cloudinary::option_consume(
$options,
"responsive_width",
Cloudinary::config_get("responsive_width")
);
$size = Cloudinary::option_consume($options, "size");
if ($size) {
list($options["width"], $options["height"]) = preg_split("/x/", $size);
}
$width = Cloudinary::option_get($options, "width");
$height = Cloudinary::option_get($options, "height");
$has_layer = Cloudinary::option_get($options, "underlay") || Cloudinary::option_get($options, "overlay");
$angle = implode(Cloudinary::build_array(Cloudinary::option_consume($options, "angle")), ".");
$crop = Cloudinary::option_consume($options, "crop");
$no_html_sizes = $has_layer || !empty($angle) || $crop == "fit" || $crop == "limit" || $responsive_width;
if (strlen($width) == 0 || $width &&
(substr($width, 0, 4) == "auto" || floatval($width) < 1 || $no_html_sizes)) {
unset($options["width"]);
}
if (strlen($height) == 0 || $height && (floatval($height) < 1 || $no_html_sizes)) {
unset($options["height"]);
}
$background = Cloudinary::option_consume($options, "background");
if ($background) {
$background = preg_replace("/^#/", 'rgb:', $background);
}
$color = Cloudinary::option_consume($options, "color");
if ($color) {
$color = preg_replace("/^#/", 'rgb:', $color);
}
$base_transformations = Cloudinary::build_array(Cloudinary::option_consume($options, "transformation"));
if (count(array_filter($base_transformations, "is_array")) > 0) {
$base_transformations = array_map($generate_base_transformation, $base_transformations);
$named_transformation = "";
} else {
$named_transformation = implode(".", $base_transformations);
$base_transformations = array();
}
$effect = Cloudinary::option_consume($options, "effect");
if (is_array($effect)) {
$effect = implode(":", $effect);
}
$border = Cloudinary::process_border(Cloudinary::option_consume($options, "border"));
$flags = implode(Cloudinary::build_array(Cloudinary::option_consume($options, "flags")), ".");
$dpr = Cloudinary::option_consume($options, "dpr", Cloudinary::config_get("dpr"));
$duration = Cloudinary::norm_range_value(Cloudinary::option_consume($options, "duration"));
$start_offset = Cloudinary::norm_auto_range_value(Cloudinary::option_consume($options, "start_offset"));
$end_offset = Cloudinary::norm_range_value(Cloudinary::option_consume($options, "end_offset"));
$offset = Cloudinary::split_range(Cloudinary::option_consume($options, "offset"));
if (!empty($offset)) {
$start_offset = Cloudinary::norm_auto_range_value($offset[0]);
$end_offset = Cloudinary::norm_range_value($offset[1]);
}
$video_codec = Cloudinary::process_video_codec_param(Cloudinary::option_consume($options, "video_codec"));
$fps = Cloudinary::process_fps(Cloudinary::option_consume($options, "fps"));
$keyframe_interval = Cloudinary::process_keyframe_interval(Cloudinary::option_consume($options, "keyframe_interval"));
$overlay = Cloudinary::process_layer(Cloudinary::option_consume($options, "overlay"), "overlay");
$underlay = Cloudinary::process_layer(Cloudinary::option_consume($options, "underlay"), "underlay");
$if = Cloudinary::process_if(Cloudinary::option_consume($options, "if"));
$custom_function = Cloudinary::process_custom_function(Cloudinary::option_consume($options, "custom_function"));
$custom_pre_function = Cloudinary::process_custom_pre_function(Cloudinary::option_consume($options, "custom_pre_function"));
$aspect_ratio = Cloudinary::option_consume($options, "aspect_ratio");
$opacity = Cloudinary::option_consume($options, "opacity");
$quality = Cloudinary::option_consume($options, "quality");
$radius = Cloudinary::process_radius(Cloudinary::option_consume($options, "radius"));
$x = Cloudinary::option_consume($options, "x");
$y = Cloudinary::option_consume($options, "y");
$zoom = Cloudinary::option_consume($options, "zoom");
$params = array(
"a" => self::normalize_expression($angle),
"ar" => self::normalize_expression($aspect_ratio),
"b" => $background,
"bo" => $border,
"c" => $crop,
"co" => $color,
"dpr" => self::normalize_expression($dpr),
"du" => $duration,
"e" => self::normalize_expression($effect),
"eo" => $end_offset,
"fl" => $flags,
"fn" => $custom_function ?: $custom_pre_function,
"fps" => $fps,
"ki" => $keyframe_interval,
"h" => self::normalize_expression($height),
"l" => $overlay,
"o" => self::normalize_expression($opacity),
"q" => self::normalize_expression($quality),
"r" => $radius,
"so" => $start_offset,
"t" => $named_transformation,
"u" => $underlay,
"vc" => $video_codec,
"w" => self::normalize_expression($width),
"x" => self::normalize_expression($x),
"y" => self::normalize_expression($y),
"z" => self::normalize_expression($zoom),
);
$simple_params = array(
"ac" => "audio_codec",
"af" => "audio_frequency",
"br" => "bit_rate",
"cs" => "color_space",
"d" => "default_image",
"dl" => "delay",
"dn" => "density",
"f" => "fetch_format",
"g" => "gravity",
"p" => "prefix",
"pg" => "page",
"sp" => "streaming_profile",
"vs" => "video_sampling",
);
foreach ($simple_params as $param => $option) {
$params[$param] = Cloudinary::option_consume($options, $option);
}
$variables = !empty($options["variables"]) ? $options["variables"] : [];
$var_params = [];
foreach ($options as $key => $value) {
if (preg_match('/^\$/', $key)) {
$var_params[] = $key . '_' . self::normalize_expression((string)$value);
}
}
sort($var_params);
if (!empty($variables)) {
foreach ($variables as $key => $value) {
$var_params[] = $key . '_' . self::normalize_expression((string)$value);
}
}
$variables = join(',', $var_params);
$param_filter = function ($value) {
return $value === 0 || $value === '0' || trim($value) == true;
};
$params = array_filter($params, $param_filter);
ksort($params);
if (isset($if)) {
$if = 'if_' . $if;
}
$join_pair = function ($key, $value) {
return $key . "_" . $value;
};
$transformation = implode(",", array_map($join_pair, array_keys($params), array_values($params)));
$raw_transformation = Cloudinary::option_consume($options, "raw_transformation");
$transformation = implode(",", array_filter(array($if, $variables, $transformation, $raw_transformation)));
array_push($base_transformations, $transformation);
if ($responsive_width) {
$responsive_width_transformation = Cloudinary::config_get(
"responsive_width_transformation",
Cloudinary::$DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION
);
array_push(
$base_transformations,
Cloudinary::generate_transformation_string($responsive_width_transformation)
);
}
if (substr($width, 0, 4) == "auto" || $responsive_width) {
$options["responsive"] = true;
}
if (substr($dpr, 0, 4) == "auto") {
$options["hidpi"] = true;
}
return implode("/", array_filter($base_transformations));
}
/**
* Helper function, allows chaining transformations to the end of transformations list
*
* The result of this function is an updated $options parameter
*
* @param array $options Original options
* @param array $transformations Transformations to chain at the end
*
* @return array Resulting options
*/
public static function chain_transformations($options, $transformations)
{
$transformations = \Cloudinary::build_array($transformations);
// preserve url options
$url_options = self::array_subset($options, self::$URL_KEYS);
array_unshift($transformations, $options);
$url_options["transformation"] = $transformations;
return $url_options;
}
private static $LAYER_KEYWORD_PARAMS = array(
"font_weight" => "normal",
"font_style" => "normal",
"text_decoration" => "none",
"text_align" => null,
"stroke" => "none",
);
private static function text_style($layer, $layer_parameter)
{
$font_family = Cloudinary::option_get($layer, "font_family");
$font_size = Cloudinary::option_get($layer, "font_size");
$keywords = array();
foreach (Cloudinary::$LAYER_KEYWORD_PARAMS as $attr => $default_value) {
$attr_value = Cloudinary::option_get($layer, $attr, $default_value);
if ($attr_value != $default_value) {
array_push($keywords, $attr_value);
}
}
$letter_spacing = Cloudinary::option_get($layer, "letter_spacing");
if ($letter_spacing != null) {
array_push($keywords, "letter_spacing_$letter_spacing");
}
$line_spacing = Cloudinary::option_get($layer, "line_spacing");
if ($line_spacing != null) {
array_push($keywords, "line_spacing_$line_spacing");
}
$font_antialiasing = Cloudinary::option_get($layer, "font_antialiasing");
if ($font_antialiasing != null) {
array_push($keywords, "antialias_$font_antialiasing");
}
$font_hinting = Cloudinary::option_get($layer, "font_hinting");
if ($font_hinting != null) {
array_push($keywords, "hinting_$font_hinting");
}
$has_text_options = $font_size != null || $font_family != null || !empty($keywords);
if (!$has_text_options) {
return null;
}
if ($font_family == null) {
throw new InvalidArgumentException("Must supply font_family for text in $layer_parameter");
}
if ($font_size == null) {
throw new InvalidArgumentException("Must supply font_size for text in $layer_parameter");
}
array_unshift($keywords, $font_size);
array_unshift($keywords, $font_family);
return implode("_", array_filter($keywords, 'Cloudinary::is_not_null'));
}
/**
* Handle overlays.
* Overlay properties can came as array or as string.
* @param $layer
* @param $layer_parameter
* @return string
*/
private static function process_layer($layer, $layer_parameter)
{
// When overlay is array.
if (is_array($layer)) {
$resource_type = Cloudinary::option_get($layer, "resource_type");
$type = Cloudinary::option_get($layer, "type");
$text = Cloudinary::option_get($layer, "text");
$fetch = Cloudinary::option_get($layer, "fetch");
$text_style = null;
$public_id = Cloudinary::option_get($layer, "public_id");
$format = Cloudinary::option_get($layer, "format");
$components = array();
if ($public_id != null) {
$public_id = str_replace("/", ":", $public_id);
if ($format != null) {
$public_id = $public_id . "." . $format;
}
}
// Fetch overlay.
if (!empty($fetch) || $resource_type === "fetch") {
$public_id = null;
$resource_type = "fetch";
$fetch = self::base64url_encode($fetch);
} // Text overlay.
elseif (!empty($text) || $resource_type === "text") {
$resource_type = "text";
$type = null; // type is ignored for text layers
$text_style = Cloudinary::text_style($layer, $layer_parameter); #FIXME duplicate
if ($text != null) {
if (!($public_id != null xor $text_style != null)) {
throw new InvalidArgumentException(
"Must supply either style parameters or a public_id when providing text parameter in a text $layer_parameter"
);
}
$escaped = Cloudinary::smart_escape($text);
$escaped = str_replace("%2C", "%252C", $escaped);
$escaped = str_replace("/", "%252F", $escaped);
# Don't encode interpolation expressions e.g. $(variable)
preg_match_all('/\$\([a-zA-Z]\w+\)/', $text, $matches);
foreach ($matches[0] as $match) {
$escaped_match = Cloudinary::smart_escape($match);
$escaped = str_replace($escaped_match, $match, $escaped);
}
$text = $escaped;
}
} else {
if ($public_id == null) {
throw new InvalidArgumentException("Must supply public_id for $resource_type $layer_parameter");
}
if ($resource_type == "subtitles") {
$text_style = Cloudinary::text_style($layer, $layer_parameter);
}
}
// Build a components array.
if ($resource_type != "image") {
array_push($components, $resource_type);
}
if ($type != "upload") {
array_push($components, $type);
}
array_push($components, $text_style);
array_push($components, $public_id);
array_push($components, $text);
array_push($components, $fetch);
// Build a valid overlay string.
$layer = implode(":", array_filter($components, 'Cloudinary::is_not_null'));
} // Handle fetch overlay from string definition.
elseif (substr($layer, 0, strlen('fetch:')) === 'fetch:') {
$url = substr($layer, strlen('fetch:'));
$b64 = self::base64url_encode($url);
$layer = 'fetch:' . $b64;
}
return $layer;
}
private static $CONDITIONAL_OPERATORS = array(
"=" => 'eq',
"!=" => 'ne',
"<" => 'lt',
">" => 'gt',
"<=" => 'lte',
">=" => 'gte',
"&&" => 'and',
"||" => 'or',
"*" => 'mul',
"/" => 'div',
"+" => 'add',
"-" => 'sub',
);
private static $PREDEFINED_VARS = array(
"aspect_ratio" => "ar",
"current_page" => "cp",
"face_count" => "fc",
"height" => "h",
"initial_aspect_ratio" => "iar",
"initial_height" => "ih",
"initial_width" => "iw",
"page_count" => "pc",
"page_x" => "px",
"page_y" => "py",
"tags" => "tags",
"width" => "w",
);
private static function translate_if($source)
{
if (isset(self::$CONDITIONAL_OPERATORS[$source[0]])) {
return self::$CONDITIONAL_OPERATORS[$source[0]];
} elseif (isset(self::$PREDEFINED_VARS[$source[0]])) {
return self::$PREDEFINED_VARS[$source[0]];
} else {
return $source[0];
}
}
private static $IF_REPLACE_RE;
private static function process_if($if)
{
$if = self::normalize_expression($if);
return $if;
}
private static function float_to_string($value) {
if (!is_float($value)) {
return $value;
}
$locale = localeconv();
$string = strval($value);
$string = str_replace($locale['decimal_point'], '.', $string);
return $string;
}
private static function normalize_expression($exp)
{
if (is_float($exp)) {
return self::float_to_string($exp);
}
if (preg_match('/^!.+!$/', $exp)) {
return $exp;
} else {
if (empty(self::$IF_REPLACE_RE)) {
self::$IF_REPLACE_RE = '/((\|\||>=|<=|&&|!=|>|=|<|\/|\-|\+|\*)(?=[ _])|'.
implode('|', array_keys(self::$PREDEFINED_VARS)) . ')/';
}
if (isset($exp)) {
$exp = preg_replace('/[ _]+/', '_', $exp);
$exp = preg_replace_callback(self::$IF_REPLACE_RE, array("Cloudinary", "translate_if"), $exp);
}
return $exp;
}
}
private static function process_border($border)
{
if (is_array($border)) {
$border_width = Cloudinary::option_get($border, "width", "2");
$border_color = preg_replace("/^#/", 'rgb:', Cloudinary::option_get($border, "color", "black"));
$border = $border_width . "px_solid_" . $border_color;
}
return $border;
}
private static function process_radius($radius)
{
if (!is_array($radius)) {
return $radius;
}
return implode(":", array_map("self::normalize_expression", $radius));
}
private static function process_custom_function($custom_function)
{
if (!is_array($custom_function)) {
return $custom_function;
}
$function_type = Cloudinary::option_get($custom_function, "function_type");
$source = Cloudinary::option_get($custom_function, "source");
if ($function_type == 'remote') {
$source = self::base64url_encode($source);
}
return implode(':', [$function_type, $source]);
}
private static function process_custom_pre_function($custom_function)
{
$value = self::process_custom_function($custom_function);
return $value ? 'pre:' . $value : null;
}
private static function split_range($range)
{
if (is_array($range) && count($range) >= 2) {
return array($range[0], end($range));
} else {
if (is_string($range) && preg_match(Cloudinary::RANGE_RE, $range) == 1) {
return explode("..", $range, 2);
} else {
return null;
}
}
}
private static function norm_range_value($value)
{
if (is_null($value)) {
return null;
}
// Ensure that trailing decimal(.0) part is not cropped when float is provided
// e.g. float 1.0 should be returned as "1.0" and not "1" as it happens by default
if (is_float($value) && $value - (int)$value == 0) {
$value = sprintf("%.1f", $value);
}
preg_match(Cloudinary::RANGE_VALUE_RE, $value, $matches);
if (empty($matches)) {
return null;
}
$modifier = '';
if (!empty($matches['modifier'])) {
$modifier = 'p';
}
return $matches['value'] . $modifier;
}
private static function norm_auto_range_value($value)
{
if ($value == 'auto') {
return $value;
}
return self::norm_range_value($value);
}
private static function process_video_codec_param($param)
{
$out_param = $param;
if (is_array($out_param)) {
$out_param = $param['codec'];
if (array_key_exists('profile', $param)) {
$out_param = $out_param . ':' . $param['profile'];
if (array_key_exists('level', $param)) {
$out_param = $out_param . ':' . $param['level'];
}
}
}
return $out_param;
}
/**
* Serializes fps transformation parameter
*
* @param mixed $fps A single number, an array of mixed type, a string, including open-ended and closed range values
* Examples: '24-29.97', 24, 24.973, '-24', [24, 29.97]
*
* @return string
*/
private static function process_fps($fps)
{
if (!is_array($fps)) {
return strval($fps);
}
return implode("-", array_map("self::normalize_expression", $fps));
}
/**
* Serializes keyframe_interval transformation parameter
*
* @param float|int|string $keyframe_interval A positive number or a string
*
* @return string
*/
private static function process_keyframe_interval($keyframe_interval)
{
if (is_string($keyframe_interval) || $keyframe_interval == null) {
return $keyframe_interval;
}
if (!is_numeric($keyframe_interval)) {
throw new InvalidArgumentException("Keyframe interval should be a number or a string");
}
if ($keyframe_interval < 0) {
throw new InvalidArgumentException("Keyframe interval should be greater than zero");
}
if (is_int($keyframe_interval)) {
return $keyframe_interval . ".0";
}
return $keyframe_interval;
}
// Warning: $options are being destructively updated!
public static function cloudinary_url($source, &$options = array())
{
$source = self::check_cloudinary_field($source, $options);
self::patch_fetch_format($options);
$type = Cloudinary::option_consume($options, "type", "upload");
$transformation = Cloudinary::generate_transformation_string($options);
$resource_type = Cloudinary::option_consume($options, "resource_type", "image");
$version = Cloudinary::option_consume($options, "version");
$force_version = Cloudinary::option_consume(
$options,
"force_version",
Cloudinary::config_get("force_version", true)
);
$format = Cloudinary::option_consume($options, "format");
$cloud_name = Cloudinary::option_consume($options, "cloud_name", Cloudinary::config_get("cloud_name"));
if (!$cloud_name) {
throw new InvalidArgumentException("Must supply cloud_name in tag or in configuration");
}
$secure = Cloudinary::option_consume($options, "secure", Cloudinary::config_get("secure"));
$private_cdn = Cloudinary::option_consume($options, "private_cdn", Cloudinary::config_get("private_cdn"));
$secure_distribution = Cloudinary::option_consume(
$options,
"secure_distribution",
Cloudinary::config_get("secure_distribution")
);
$cdn_subdomain = Cloudinary::option_consume(
$options,
"cdn_subdomain",
Cloudinary::config_get("cdn_subdomain")
);
$secure_cdn_subdomain = Cloudinary::option_consume(
$options,
"secure_cdn_subdomain",
Cloudinary::config_get("secure_cdn_subdomain")
);
$cname = Cloudinary::option_consume($options, "cname", Cloudinary::config_get("cname"));
$shorten = Cloudinary::option_consume($options, "shorten", Cloudinary::config_get("shorten"));
$sign_url = Cloudinary::option_consume($options, "sign_url", Cloudinary::config_get("sign_url"));
$api_secret = Cloudinary::option_consume($options, "api_secret", Cloudinary::config_get("api_secret"));
$url_suffix = Cloudinary::option_consume($options, "url_suffix", Cloudinary::config_get("url_suffix"));
$use_root_path = Cloudinary::option_consume($options, "use_root_path", Cloudinary::config_get("use_root_path"));
$auth_token = Cloudinary::option_consume($options, "auth_token");
if (is_array($auth_token)) {
$auth_token = array_merge(self::config_get("auth_token", array()), $auth_token);
} elseif (is_null($auth_token)) {
$auth_token = self::config_get("auth_token");
}
if (!$source) {
return $source;
}
if (preg_match("/^https?:\//i", $source)) {
if ($type == "upload") {
return $source;
}
}
$resource_type_and_type = Cloudinary::finalize_resource_type(
$resource_type,
$type,
$url_suffix,
$use_root_path,
$shorten
);
$sources = Cloudinary::finalize_source($source, $format, $url_suffix);
$source = $sources["source"];
$source_to_sign = $sources["source_to_sign"];
if (empty($version) && $force_version && strpos($source_to_sign, "/") &&
!preg_match("/^https?:\//", $source_to_sign) && !preg_match("/^v[0-9]+/", $source_to_sign)) {
$version = "1";
}
$version = $version ? "v" . $version : null;
$signature = null;
if ($sign_url && !$auth_token) {
$to_sign = implode("/", array_filter(array($transformation, $source_to_sign)));
$signature = self::base64url_encode(sha1($to_sign . $api_secret, true));
$signature = 's--' . substr($signature, 0, 8) . '--';
}
$prefix = Cloudinary::unsigned_download_url_prefix(
$source,
$cloud_name,
$private_cdn,
$cdn_subdomain,
$secure_cdn_subdomain,
$cname,
$secure,
$secure_distribution
);
$source = preg_replace(
"/([^:])\/+/",
"$1/",
implode(
"/",
array_filter(
array(
$prefix,
$resource_type_and_type,
$signature,
$transformation,
$version,
$source,
)
)
)
);
if ($sign_url && $auth_token) {
$path = parse_url($source, PHP_URL_PATH);
$token = \Cloudinary\AuthToken::generate(array_merge($auth_token, array("url" => $path)));
$source = $source . "?" . $token;
}
return $source;
}
private static function finalize_source($source, $format, $url_suffix)
{
$source = preg_replace('/([^:])\/\//', '$1/', $source);
if (preg_match('/^https?:\//i', $source)) {
$source = Cloudinary::smart_escape($source);
$source_to_sign = $source;
} else {
$source = Cloudinary::smart_escape(rawurldecode($source));
$source_to_sign = $source;
if (!empty($url_suffix)) {
if (preg_match('/[\.\/]/i', $url_suffix)) {
throw new InvalidArgumentException("url_suffix should not include . or /");
}
$source = $source . '/' . $url_suffix;
}
if (!empty($format)) {
$source = $source . '.' . $format;
$source_to_sign = $source_to_sign . '.' . $format;
}
}
return array("source" => $source, "source_to_sign" => $source_to_sign);
}
private static function finalize_resource_type($resource_type, $type, $url_suffix, $use_root_path, $shorten)
{
if (empty($type)) {
$type = "upload";
}
if (!empty($url_suffix)) {
if ($resource_type == "image" && $type == "upload") {
$resource_type = "images";
$type = null;
} elseif ($resource_type == "image" && $type == "private") {
$resource_type = "private_images";
$type = null;
} elseif ($resource_type == "image" && $type == "authenticated") {
$resource_type = "authenticated_images";
$type = null;
} elseif ($resource_type == "video" && $type == "upload") {
$resource_type = "videos";
$type = null;
} elseif ($resource_type == "raw" && $type == "upload") {
$resource_type = "files";
$type = null;
} else {
throw new InvalidArgumentException(
"URL Suffix only supported for image/upload, image/private, image/authenticated, " .
"video/upload and raw/upload"
);
}
}
if ($use_root_path) {
if (($resource_type == "image" && $type == "upload") || ($resource_type == "images" && empty($type))) {
$resource_type = null;
$type = null;
} else {
throw new InvalidArgumentException("Root path only supported for image/upload");
}
}
if ($shorten && $resource_type == "image" && $type == "upload") {
$resource_type = "iu";
$type = null;
}
$out = "";
if (!empty($resource_type)) {
$out = $resource_type;
}
if (!empty($type)) {
$out = $out.'/'.$type;
}
return $out;
}
// 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[1-5].cname for http. For https, uses the same naming scheme as 1 for shared distribution and as 2 for private distribution.
private static function unsigned_download_url_prefix(
$source,
$cloud_name,
$private_cdn,
$cdn_subdomain,
$secure_cdn_subdomain,
$cname,
$secure,
$secure_distribution
) {
$shared_domain = !$private_cdn;
$prefix = null;
if ($secure) {
if (empty($secure_distribution) || $secure_distribution == Cloudinary::OLD_AKAMAI_SHARED_CDN) {
$secure_distribution = $private_cdn ? $cloud_name . '-res.cloudinary.com' : Cloudinary::SHARED_CDN;
}
if (empty($shared_domain)) {
$shared_domain = ($secure_distribution == Cloudinary::SHARED_CDN);
}
if (is_null($secure_cdn_subdomain) && $shared_domain) {
$secure_cdn_subdomain = $cdn_subdomain;
}
if ($secure_cdn_subdomain) {
$secure_distribution = str_replace(
'res.cloudinary.com',
"res-" . Cloudinary::domain_shard($source) . ".cloudinary.com",
$secure_distribution
);
}
$prefix = "https://".$secure_distribution;
} else {
if ($cname) {
$subdomain = $cdn_subdomain ? "a" . Cloudinary::domain_shard($source) . '.' : "";
$prefix = "http://" . $subdomain . $cname;
} else {
$host = implode(
array(
$private_cdn ? $cloud_name."-" : "",
"res",
$cdn_subdomain ? "-".Cloudinary::domain_shard($source) : "",
".cloudinary.com",
)
);
$prefix = "http://".$host;
}
}
if ($shared_domain) {
$prefix = $prefix . '/' . $cloud_name;
}
return $prefix;
}
private static function domain_shard($source)
{
return (((crc32($source) % 5) + 5) % 5 + 1);
}
// Warning: $options are being destructively updated!
public static function check_cloudinary_field($source, &$options = array())
{
// [<resource_type>/][<image_type>/][v<version>/]<public_id>[.<format>][#<signature>]
$IDENTIFIER_RE = "~" .
"^" .
"(?:([^/]+)/)??" . // resource type
"(?:([^/]+)/)??" . // type
"(?:(?:v(\\d+)/)(?:([^#]+)/)?)?" . // version
"([^#/]+?)" . // public ID
"(?:\\.([^.#/]+))?" . //format
"(?:#([^/]+))?" . // signature
"$" .
"~";
if (!is_object($source) || !method_exists($source, 'identifier')) {
// $source doesn't look like a CloudinaryField, so just return it
return $source;
}
// $source is a CloudinaryField, parse its identifier
$matches = array();
$identifier = $source->identifier();
if (!$identifier || strstr(':', $identifier) !== false || !preg_match($IDENTIFIER_RE, $identifier, $matches)) {
return $source;
}
$optionNames = array('resource_type', 'type', 'version', 'folder', 'public_id', 'format');
foreach ($optionNames as $index => $optionName) {
if (@$matches[$index + 1]) {
$options[$optionName] = $matches[$index + 1];
}
}
return Cloudinary::option_consume($options, 'public_id');
}
// Based on http://stackoverflow.com/a/1734255/526985
private static function smart_escape($str)
{
$revert = array('%3A' => ':', '%2F' => '/');
return strtr(rawurlencode($str), $revert);
}
public static function cloudinary_api_url($action = 'upload', $options = array())
{
$cloudinary = Cloudinary::option_get(
$options,
"upload_prefix",
Cloudinary::config_get("upload_prefix", "https://api.cloudinary.com")
);
$cloud_name = Cloudinary::option_get($options, "cloud_name", Cloudinary::config_get("cloud_name"));
if (!$cloud_name) {
throw new InvalidArgumentException("Must supply cloud_name in options or in configuration");
}
$resource_type = Cloudinary::option_get($options, "resource_type", "image");
return implode("/", array($cloudinary, "v1_1", $cloud_name, $resource_type, $action));
}
public static function random_public_id()
{
return substr(sha1(uniqid(Cloudinary::config_get("api_secret", "") . mt_rand())), 0, 16);
}
public static function signed_preloaded_image($result)
{
return $result["resource_type"] . "/upload/v" . $result["version"] . "/" . $result["public_id"] .
(isset($result["format"]) ? "." . $result["format"] : "") . "#" . $result["signature"];
}
/**
* Generates a cloudinary url scaled to specified width.
*
* In case transformation parameter is provided, it is used instead of transformations specified in $options
*
* @param string $source Public ID of the resource
* @param int $width Width in pixels of the srcset item
* @param array|string $transformation Custom transformation that overrides transformations provided in $options
* @param array $options Additional options
*
* @return null|string
*/
public static function cloudinary_scaled_url($source, $width, $transformation, $options)
{
if (!empty($transformation)) {
// Replace transformation parameters in $options with those in $transformation
if(is_string($transformation)){
$transformation = array("raw_transformation"=> $transformation);
}
$options = self::array_subset($options, self::$URL_KEYS);
$options = array_merge($options, $transformation);
}
$scale_transformation = ["crop" => "scale", "width" => $width];
self::check_cloudinary_field($source, $options);
self::patch_fetch_format($options);
$options = self::chain_transformations($options, $scale_transformation);
return cloudinary_url_internal($source, $options);
}
# Utility method that uses the deprecated ZIP download API.
# @deprecated Replaced by {download_zip_url} that uses the more advanced and robust archive generation and download API
public static function zip_download_url($tag, $options = array())
{
$params = array(
"timestamp" => time(),
"tag" => $tag,
"transformation" => \Cloudinary::generate_transformation_string($options),
);
$params = Cloudinary::sign_request($params, $options);
return Cloudinary::cloudinary_api_url("download_tag.zip", $options) . "?" . http_build_query($params);
}
# Returns a URL that when invokes creates an archive and returns it.
# @param options [Hash]
# @option options [String] resource_type The resource type of files to include in the archive. Must be one of image | video | raw
# @option options [String] type (upload) The specific file type of resources upload|private|authenticated
# @option options [String|Array] tags (nil) list of tags to include in the archive
# @option options [String|Array<String>] public_ids (nil) list of public_ids to include in the archive
# @option options [String|Array<String>] prefixes (nil) Optional list of prefixes of public IDs (e.g., folders).
# @option options [String|Array<String>] transformations 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.
# @option options [String] mode (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
# @option options [String] target_format (zip)
# @option options [String] target_public_id Optional public ID of the generated raw resource.
# Relevant only for the create mode. If not specified, random public ID is generated.
# @option options [boolean] flatten_folders (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.
# @option options [boolean] flatten_transformations (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.
# @option options [boolean] use_original_filename Use the original file name of included images (if available) instead of the public ID.
# @option options [boolean] async (false) If true, return immediately and perform the archive creation in the background.
# Relevant only for the create mode.
# @option options [String] notification_url Optional URL to send an HTTP post request (webhook) when the archive creation is completed.
# @option options [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).
# @option options [String] keep_derived (false) keep the derived images used for generating the archive
# @return [String] archive url
public static function download_archive_url($options = array())
{
$options["mode"] = "download";
$params = Cloudinary::build_archive_params($options);
$params = Cloudinary::sign_request($params, $options);
return Cloudinary::cloudinary_api_url("generate_archive", $options) . "?" .
preg_replace("/%5B\d+%5D/", "%5B%5D", http_build_query($params));
}
# Returns a URL that when invokes creates an zip archive and returns it.
# @see download_archive_url
public static function download_zip_url($options = array())
{
$options["target_format"] = "zip";
return Cloudinary::download_archive_url($options);
}
/**
* Generate an authorization token.
* Options:
* string key - the secret key required to sign the token
* string ip - the IP address of the client
* number start_time - the start time of the token in seconds from epoch
* string expiration - the expiration time of the token in seconds from epoch
* string duration - the duration of the token (from start_time)
* string acl - the ACL for the token
* string url - the URL to authentication in case of a URL token
*
* @param array $options token configuration, merge with the global configuration "auth_token".
* @return string the authorization token
* @throws \Cloudinary\Error
*/
public static function generate_auth_token($options)
{
$token_options = array_merge(self::config_get("auth_token", array()), $options);
return \Cloudinary\AuthToken::generate($token_options);
}
/**
* @internal
*
* Returns an array of parameters used to create an archive
*
* @param array $options
*
* @return array
*/
public static function build_archive_params(&$options)
{
$params = array(
"allow_missing" => \Cloudinary::option_get($options, "allow_missing"),
"async" => \Cloudinary::option_get($options, "async"),
"expires_at" => \Cloudinary::option_get($options, "expires_at"),
"flatten_folders" => \Cloudinary::option_get($options, "flatten_folders"),
"flatten_transformations" => \Cloudinary::option_get($options, "flatten_transformations"),
"keep_derived" => \Cloudinary::option_get($options, "keep_derived"),
"mode" => \Cloudinary::option_get($options, "mode"),
"notification_url" => \Cloudinary::option_get($options, "notification_url"),
"phash" => \Cloudinary::option_get($options, "phash"),
"prefixes" => \Cloudinary::build_array(\Cloudinary::option_get($options, "prefixes")),
"public_ids" => \Cloudinary::build_array(\Cloudinary::option_get($options, "public_ids")),
"fully_qualified_public_ids" => \Cloudinary::build_array(
\Cloudinary::option_get($options, "fully_qualified_public_ids")
),
"skip_transformation_name" => \Cloudinary::option_get($options, "skip_transformation_name"),
"tags" => \Cloudinary::build_array(\Cloudinary::option_get($options, "tags")),
"target_format" => \Cloudinary::option_get($options, "target_format"),
"target_public_id" => \Cloudinary::option_get($options, "target_public_id"),
"target_tags" => \Cloudinary::build_array(\Cloudinary::option_get($options, "target_tags")),
"timestamp" => time(),
"transformations" => \Cloudinary::build_eager(\Cloudinary::option_get($options, "transformations")),
"type" => \Cloudinary::option_get($options, "type"),
"use_original_filename" => \Cloudinary::option_get($options, "use_original_filename"),
);
array_walk(
$params,
function (&$value, $key) {
$value = (is_bool($value) ? ($value ? "1" : "0") : $value);
}
);
return array_filter(
$params,
function ($v) {
return !is_null($v) && ($v !== "");
}
);
}
public static function build_eager($transformations)
{
$eager = array();
foreach (\Cloudinary::build_array($transformations) as $trans) {
$single_eager = \Cloudinary::build_single_eager($trans);
array_push($eager, $single_eager);
}
return implode("|", $eager);
}
/**
* Builds a single eager transformation which consists of transformation and (optionally) format joined by "/"
*
* @param array|string $options Options containing transformation parameters and (optionally) a "format" key
* format can be a string value (jpg, gif, etc) or can be set to "" (empty string).
* The latter leads to transformation ending with "/", which means "No extension, use original format"
* If format is not provided or set to null, only transformation is used (without the trailing "/")
*
* @return string
*/
public static function build_single_eager($options)
{
if (is_string($options)) {
return $options;
}
$trans_str = \Cloudinary::generate_transformation_string($options);
if (empty($trans_str)) {
return "";
}
$file_format_str = "";
if (isset($options["format"])) {
$file_format_str = "/" . $options["format"];
}
return $trans_str . $file_format_str;
}
public static function private_download_url($public_id, $format, $options = array())
{
$cloudinary_params = Cloudinary::sign_request(
array(
"timestamp" => time(),
"public_id" => $public_id,
"format" => $format,
"type" => Cloudinary::option_get($options, "type"),
"attachment" => Cloudinary::option_get($options, "attachment"),
"expires_at" => Cloudinary::option_get($options, "expires_at"),
),
$options
);
return Cloudinary::cloudinary_api_url("download", $options) . "?" . http_build_query($cloudinary_params);
}
public static function sign_request($params, &$options)
{
$api_key = Cloudinary::option_get($options, "api_key", Cloudinary::config_get("api_key"));
if (!$api_key) {
throw new \InvalidArgumentException("Must supply api_key");
}
$api_secret = Cloudinary::option_get($options, "api_secret", Cloudinary::config_get("api_secret"));
if (!$api_secret) {
throw new \InvalidArgumentException("Must supply api_secret");
}
# Remove blank parameters
$params = array_filter(
$params,
function ($v) {
return isset($v) && $v !== "";
}
);
$params["signature"] = Cloudinary::api_sign_request($params, $api_secret);
$params["api_key"] = $api_key;
return $params;
}
public static function api_sign_request($params_to_sign, $api_secret)
{
$params = array();
foreach ($params_to_sign as $param => $value) {
if (isset($value) && $value !== "") {
if (!is_array($value)) {
$params[$param] = $value;
} else {
if (count($value) > 0) {
$params[$param] = implode(",", $value);
}
}
}
}
ksort($params);
$join_pair = function ($key, $value) {
return $key . "=" . $value;
};
$to_sign = implode("&", array_map($join_pair, array_keys($params), array_values($params)));
return sha1($to_sign . $api_secret);
}
public static function html_attrs($options, $only = null)
{
$attrs = array();
foreach ($options as $k => $v) {
$key = $k;
$value = $v;
if (is_int($k)) {
$key = $v;
$value = "";
}
if (is_array($only) && array_search($key, $only) !== false || !is_array($only)) {
$attrs[$key] = $value;
}
}
ksort($attrs);
$join_pair = function ($key, $value) {
$out = $key;
if (!empty($value)) {
$out .= '=\'' . htmlspecialchars($value, ENT_QUOTES) . '\'';
}
return $out;
};
return implode(" ", array_map($join_pair, array_keys($attrs), array_values($attrs)));
}
}
You can’t perform that action at this time.