Skip to content
Merged
41 changes: 30 additions & 11 deletions src/Cloudinary.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,10 @@ public static function option_get($options, $option, $default = null)

public static function option_consume(&$options, $option, $default = null)
{
if (isset($options[$option])) {
$value = $options[$option];
unset($options[$option]);

return $value;
} else {
unset($options[$option]);
$value = self::option_get($options, $option, $default);
unset($options[$option]);

return $default;
}
return $value;
}

public static function build_array($value)
Expand Down Expand Up @@ -361,6 +355,32 @@ public static function encode_assoc_array($array)
}
}

/**
* 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;
}

private static function is_assoc($array)
{
if (!is_array($array)) {
Expand Down Expand Up @@ -403,7 +423,6 @@ public static function generate_transformation_string(&$options = array())

$width = Cloudinary::option_get($options, "width");
$height = Cloudinary::option_get($options, "height");
$streaming_profile = Cloudinary::option_get($options, "streaming_profile");

$has_layer = Cloudinary::option_get($options, "underlay") || Cloudinary::option_get($options, "overlay");
$angle = implode(Cloudinary::build_array(Cloudinary::option_consume($options, "angle")), ".");
Expand Down Expand Up @@ -488,7 +507,6 @@ public static function generate_transformation_string(&$options = array())
"q" => self::normalize_expression($quality),
"r" => self::normalize_expression($radius),
"so" => $start_offset,
"sp" => $streaming_profile,
"t" => $named_transformation,
"u" => $underlay,
"vc" => $video_codec,
Expand All @@ -510,6 +528,7 @@ public static function generate_transformation_string(&$options = array())
"g" => "gravity",
"p" => "prefix",
"pg" => "page",
"sp" => "streaming_profile",
"vs" => "video_sampling",
);

Expand Down
233 changes: 227 additions & 6 deletions src/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,212 @@ function cl_form_tag($callback_url, $options = array())
return $form;
}

// Examples
// cl_image_tag("israel.png", array("width"=>100, "height"=>100, "alt"=>"hello") # W/H are not sent to cloudinary
// cl_image_tag("israel.png", array("width"=>100, "height"=>100, "alt"=>"hello", "crop"=>"fit") # W/H are sent to cloudinary
function cl_image_tag($source, $options = array())

/**
* @internal Helper function. Gets or populates srcset breakpoints using provided parameters
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Update PHPDoc

*
* Either the breakpoints or min_width, max_width, max_images must be provided.
*
* @param array $srcset_data {
*
* @var array breakpoints An array of breakpoints.
* @var int min_width Minimal width of the srcset images.
* @var int max_width Maximal width of the srcset images.
* @var int max_images Number of srcset images to generate.
* }
*
* @return array Array of breakpoints
*
* @throws InvalidArgumentException In case of invalid or missing parameters
*/
function get_srcset_breakpoints($srcset_data)
{
$source = cloudinary_url_internal($source, $options);
$breakpoints = Cloudinary::option_get($srcset_data, "breakpoints", array());

if (!empty($breakpoints)) {
return $breakpoints;
}

foreach (array('min_width', 'max_width', 'max_images') as $arg) {
if (empty($srcset_data[$arg]) || !is_numeric($srcset_data[$arg]) || is_string($srcset_data[$arg])) {
throw new InvalidArgumentException('Either valid (min_width, max_width, max_images) ' .
'or breakpoints must be provided to the image srcset attribute');
}
}

$min_width = $srcset_data['min_width'];
$max_width = $srcset_data['max_width'];
$max_images = $srcset_data['max_images'];

if ($min_width > $max_width) {
throw new InvalidArgumentException('min_width must be less than max_width');
}

if ($max_images <= 0) {
throw new InvalidArgumentException('max_images must be a positive integer');
} elseif ($max_images == 1) {
// if user requested only 1 image in srcset, we return max_width one
$min_width = $max_width;
}

$step_size = ceil(($max_width - $min_width) / ($max_images > 1 ? $max_images - 1 : 1));

$curr_breakpoint = $min_width;

while ($curr_breakpoint < $max_width) {
array_push($breakpoints, $curr_breakpoint);
$curr_breakpoint += $step_size;
}

array_push($breakpoints, $max_width);

return $breakpoints;
}

/**
* @internal Helper function. Generates a single srcset item url
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Update PHPDoc

*
* @param string $public_id Public ID of the resource
* @param int $width Width in pixels of the srcset item
* @param array $options Additional options
*
* @return mixed|null|string|string[] Resulting URL of the item
*/
function generate_single_srcset_url($public_id, $width, $options)
{
$curr_options = Cloudinary::array_copy($options);
/*
The following line is used for the next purposes:
1. Generate raw transformation string
2. Cleanup transformation parameters from $curr_options.
We call it intentionally even when the user provided custom transformation in srcset
*/
$raw_transformation = Cloudinary::generate_transformation_string($curr_options);

if (!empty($options["srcset"]["transformation"])) {
$curr_options["transformation"] = $options["srcset"]["transformation"];
$raw_transformation = Cloudinary::generate_transformation_string($curr_options);
}

$curr_options["raw_transformation"] = $raw_transformation . "/c_scale,w_{$width}";

// We might still have width and height params left if they were provided.
// We don't want to use them for the second time
$unwanted_params = array('width', 'height');
foreach ($unwanted_params as $key) {
unset($curr_options[$key]);
}

return cloudinary_url_internal($public_id, $curr_options);
}

/**
* @internal Helper function. Generates srcset attribute value of the HTML img tag
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Update PHPDoc

*
* @param array $srcset_data {
*
* @var array breakpoints An array of breakpoints.
* @var int min_width Minimal width of the srcset images.
* @var int max_width Maximal width of the srcset images.
* @var int max_images Number of srcset images to generate.
* }
*
* @param array $options Additional options.
*
* @return string Resulting srcset attribute value
*
* @throws InvalidArgumentException In case of invalid or missing parameters
*/
function generate_image_srcset_attribute($public_id, $srcset_data, $options = array())
{
if (empty($srcset_data)) {
return null;
}
if (is_string($srcset_data)) {
return $srcset_data;
}

$breakpoints = get_srcset_breakpoints($srcset_data);

// The code below is a part of `cloudinary_url` code that affects $options.
// We call it here, to make sure we get exactly the same behavior.
// TODO: Refactor this code, unify it with `cloudinary_url` or fix `cloudinary_url` and remove it
Cloudinary::check_cloudinary_field($public_id, $options);
$type = Cloudinary::option_get($options, "type", "upload");

if ($type == "fetch" && !isset($options["fetch_format"])) {
$options["fetch_format"] = Cloudinary::option_consume($options, "format");
}
//END OF TODO

$items = array();
foreach ($breakpoints as $breakpoint) {
array_push($items, generate_single_srcset_url($public_id, $breakpoint, $options) . " {$breakpoint}w");
}

return implode(", ", $items);
}

/**
* @internal Helper function. Generates sizes attribute value of the HTML img tag
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Update PHPDoc

*
* @param array $srcset_data {
*
* @var array breakpoints An array of breakpoints.
* @var int min_width Minimal width of the srcset images.
* @var int max_width Maximal width of the srcset images.
* @var int max_images Number of srcset images to generate.
* }
*
* @return string Resulting sizes attribute value
*
* @throws InvalidArgumentException In case of invalid or missing parameters
*/
function generate_image_sizes_attribute($srcset_data)
{
if (empty($srcset_data) or is_string($srcset_data)) {
return null;
}

$breakpoints = get_srcset_breakpoints($srcset_data);

$sizes_items = array();
foreach ($breakpoints as $breakpoint) {
array_push($sizes_items, "(max-width: {$breakpoint}px) {$breakpoint}px");
}

return implode(", ", $sizes_items);
}

/**
* Generates HTML img tag
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Update PHPDoc

*
* @param string $public_id Public ID of the resource
*
* @param array $options Additional options
*
* Examples:
*
* W/H are not sent to cloudinary
* cl_image_tag("israel.png", array("width"=>100, "height"=>100, "alt"=>"hello")
*
* W/H are sent to cloudinary
* cl_image_tag("israel.png", array("width"=>100, "height"=>100, "alt"=>"hello", "crop"=>"fit")
*
* @return string Resulting img tag
*
*/
function cl_image_tag($public_id, $options = array())
{
$original_options = null;

if (!empty($options['srcset'])) {
// Since cloudinary_url is destructive, we need to save a copy of original options passed to this function
$original_options = Cloudinary::array_copy($options);
}

$source = cloudinary_url_internal($public_id, $options);

if (isset($options["html_width"])) {
$options["width"] = Cloudinary::option_consume($options, "html_width");
}
Expand Down Expand Up @@ -128,10 +328,31 @@ function cl_image_tag($source, $options = array())
}
}
$html = "<img ";

if ($source) {
$html .= "src='" . htmlspecialchars($source, ENT_QUOTES) . "' ";
}
$html .= Cloudinary::html_attrs($options) . "/>";

if (!empty($options["srcset"])) {
$srcset_data = $options["srcset"];
$options["srcset"] = generate_image_srcset_attribute($public_id, $srcset_data, $original_options);

if (!empty($srcset_data["sizes"]) && $srcset_data["sizes"] === true) {
$options["sizes"] = generate_image_sizes_attribute($srcset_data);
}

// width and height attributes override srcset behavior, they should be removed from html attributes.
$unwanted_attributes = array('width', 'height');
foreach ($unwanted_attributes as $key) {
unset($options[$key]);
}
}

$attr_data = Cloudinary::option_consume($options, 'attributes', array());
// Explicitly provided attributes override options
$attributes = array_merge($options, $attr_data);

$html .= Cloudinary::html_attrs($attributes) . "/>";

return $html;
}
Expand Down
Loading