| 1 |
<?php |
| 2 |
|
| 3 |
use Optimole\Sdk\Resource\ImageProperty\ResizeTypeProperty; |
| 4 |
use Optimole\Sdk\ValueObject\Position; |
| 5 |
|
| 6 |
/** |
| 7 |
* Normalization traits. |
| 8 |
* |
| 9 |
* @package \Optml\Inc\Traits |
| 10 |
* @author Optimole <friends@optimole.com> |
| 11 |
*/ |
| 12 |
trait Optml_Normalizer { |
| 13 |
|
| 14 |
/** |
| 15 |
* Static cache for dimension calculations |
| 16 |
* |
| 17 |
* @var array |
| 18 |
*/ |
| 19 |
private static $dimension_cache = []; |
| 20 |
|
| 21 |
/** |
| 22 |
* Normalize value to boolean. |
| 23 |
* |
| 24 |
* @param mixed $value Value to process. |
| 25 |
* |
| 26 |
* @return bool |
| 27 |
*/ |
| 28 |
public function to_boolean( $value ) { |
| 29 |
if ( in_array( $value, [ 'yes', 'enabled', 'true', '1' ], true ) ) { |
| 30 |
return true; |
| 31 |
} |
| 32 |
|
| 33 |
if ( in_array( $value, [ 'no', 'disabled', 'false', '0' ], true ) ) { |
| 34 |
return false; |
| 35 |
} |
| 36 |
|
| 37 |
return boolval( $value ); |
| 38 |
} |
| 39 |
|
| 40 |
/** |
| 41 |
* Get the unoptimized url from an Optimole url. |
| 42 |
* Works only on non-offloaded images. |
| 43 |
* |
| 44 |
* @param string $url The url to get the unoptimized url for. |
| 45 |
* |
| 46 |
* @return string The unoptimized url. |
| 47 |
*/ |
| 48 |
public function get_unoptimized_url( $url ) { |
| 49 |
|
| 50 |
// If the url is not an optimole url, return the url |
| 51 |
if ( strpos( $url, Optml_Config::$service_url ) === false ) { |
| 52 |
return $url; |
| 53 |
} |
| 54 |
// If the url is an uploaded image, return the url |
| 55 |
if ( Optml_Media_Offload::is_uploaded_image( $url ) ) { |
| 56 |
$pattern = '#/id:([^/]+)/((?:https?://|http://|directUpload/)\S+)#'; |
| 57 |
if ( preg_match( $pattern, $url, $matches ) ) { |
| 58 |
$url = $matches[0]; |
| 59 |
} |
| 60 |
return $url; |
| 61 |
} |
| 62 |
|
| 63 |
$url_parts = explode( 'http', $url ); |
| 64 |
if ( ! isset( $url_parts[2] ) ) { |
| 65 |
return $url; |
| 66 |
} |
| 67 |
return 'http' . $url_parts[2]; |
| 68 |
} |
| 69 |
/** |
| 70 |
* Return domain hash. |
| 71 |
* |
| 72 |
* @param string $domain Full url. |
| 73 |
* |
| 74 |
* @return string Domain hash. |
| 75 |
*/ |
| 76 |
public function to_domain_hash( $domain ) { |
| 77 |
$domain_parts = parse_url( $domain ); |
| 78 |
$domain = isset( $domain_parts['host'] ) ? $domain_parts['host'] : ''; |
| 79 |
$prefix = substr( $domain, 0, 4 ); |
| 80 |
if ( $prefix === 'www.' ) { |
| 81 |
$domain = substr( $domain, 4 ); |
| 82 |
} |
| 83 |
|
| 84 |
return base64_encode( $domain ); |
| 85 |
} |
| 86 |
/** |
| 87 |
* Strip slashes on unicode encoded strings. |
| 88 |
* |
| 89 |
* @param string $content Input string. |
| 90 |
* |
| 91 |
* @return string Decoded string. |
| 92 |
*/ |
| 93 |
public function strip_slashes( $content ) { |
| 94 |
return html_entity_decode( stripslashes( preg_replace( '/\\\u([\da-fA-F]{4})/', '&#x\1;', $content ) ) ); |
| 95 |
} |
| 96 |
|
| 97 |
/** |
| 98 |
* Normalize value to positive integer. |
| 99 |
* |
| 100 |
* @param mixed $value Value to process. |
| 101 |
* |
| 102 |
* @return integer |
| 103 |
*/ |
| 104 |
public function to_positive_integer( $value ) { |
| 105 |
$integer = (int) $value; |
| 106 |
|
| 107 |
return ( $integer > 0 ) ? $integer : 0; |
| 108 |
} |
| 109 |
|
| 110 |
/** |
| 111 |
* Normalize value to map. |
| 112 |
* |
| 113 |
* @param mixed $value Value to process. |
| 114 |
* @param array $map Associative list from witch to return. |
| 115 |
* @param mixed $initial Default. |
| 116 |
* |
| 117 |
* @return mixed |
| 118 |
*/ |
| 119 |
public function to_map_values( $value, $map, $initial ) { |
| 120 |
if ( in_array( $value, $map, true ) ) { |
| 121 |
return $value; |
| 122 |
} |
| 123 |
|
| 124 |
return $initial; |
| 125 |
} |
| 126 |
|
| 127 |
/** |
| 128 |
* Normalize value to an accepted quality. |
| 129 |
* |
| 130 |
* @param mixed $value Value to process. |
| 131 |
* |
| 132 |
* @return mixed |
| 133 |
*/ |
| 134 |
public function to_accepted_quality( $value ) { |
| 135 |
if ( is_numeric( $value ) ) { |
| 136 |
return intval( $value ); |
| 137 |
} |
| 138 |
$value = trim( $value ); |
| 139 |
|
| 140 |
$accepted_qualities = [ |
| 141 |
'eco' => 'eco', |
| 142 |
'auto' => 'auto', |
| 143 |
'mauto' => 'mauto', |
| 144 |
'high_c' => 55, |
| 145 |
'medium_c' => 75, |
| 146 |
'low_c' => 90, |
| 147 |
]; |
| 148 |
|
| 149 |
if ( array_key_exists( $value, $accepted_qualities ) ) { |
| 150 |
return $accepted_qualities[ $value ]; |
| 151 |
} |
| 152 |
|
| 153 |
// Legacy values. |
| 154 |
return 60; |
| 155 |
} |
| 156 |
|
| 157 |
/** |
| 158 |
* Normalize value to an accepted minify. |
| 159 |
* |
| 160 |
* @param mixed $value Value to process. |
| 161 |
* |
| 162 |
* @return mixed |
| 163 |
*/ |
| 164 |
public function to_accepted_minify( $value ) { |
| 165 |
if ( is_numeric( $value ) ) { |
| 166 |
return $this->to_bound_integer( $value, 0, 1 ); |
| 167 |
} |
| 168 |
|
| 169 |
return 'auto'; |
| 170 |
} |
| 171 |
|
| 172 |
/** |
| 173 |
* Normalize value to an integer within bounds. |
| 174 |
* |
| 175 |
* @param mixed $value Value to process. |
| 176 |
* @param integer $min Lower bound. |
| 177 |
* @param integer $max Upper bound. |
| 178 |
* |
| 179 |
* @return integer |
| 180 |
*/ |
| 181 |
public function to_bound_integer( $value, $min, $max ) { |
| 182 |
$integer = absint( $value ); |
| 183 |
if ( $integer < $min ) { |
| 184 |
$integer = $min; |
| 185 |
} |
| 186 |
if ( $integer > $max ) { |
| 187 |
$integer = $max; |
| 188 |
} |
| 189 |
|
| 190 |
return $integer; |
| 191 |
} |
| 192 |
|
| 193 |
/** |
| 194 |
* Convert image size to dimensions. |
| 195 |
* |
| 196 |
* This function takes an image size and its metadata, and returns the dimensions |
| 197 |
* of the image based on the specified size. It handles different cases such as |
| 198 |
* custom sizes, predefined sizes, and full size. |
| 199 |
* |
| 200 |
* @param mixed $size The size of the image. Can be an array of width and height, a predefined size, or 'full'. |
| 201 |
* @param array $image_meta Metadata of the image, including width and height. |
| 202 |
* @param int $attachment_id The ID of the attachment. |
| 203 |
* |
| 204 |
* @return array The dimensions of the image, including width, height, and optional resize parameters. |
| 205 |
*/ |
| 206 |
public function size_to_dimension( $size, $image_meta, $attachment_id = null ) { |
| 207 |
// default size |
| 208 |
$sizes = [ |
| 209 |
'width' => isset( $image_meta['width'] ) ? intval( $image_meta['width'] ) : false, |
| 210 |
'height' => isset( $image_meta['height'] ) ? intval( $image_meta['height'] ) : false, |
| 211 |
]; |
| 212 |
$image_args = Optml_App_Replacer::image_sizes(); |
| 213 |
$sizes2crop = Optml_App_Replacer::size_to_crop(); |
| 214 |
switch ( $size ) { |
| 215 |
case is_array( $size ): |
| 216 |
$width = isset( $size[0] ) ? (int) $size[0] : false; |
| 217 |
$height = isset( $size[1] ) ? (int) $size[1] : false; |
| 218 |
if ( ! $width || ! $height ) { |
| 219 |
break; |
| 220 |
} |
| 221 |
$cache_key = 'a' . $width . '_' . $height . '_' . $sizes['width'] . '_' . $sizes['height']; |
| 222 |
if ( isset( self::$dimension_cache[ $cache_key ] ) ) { |
| 223 |
return self::$dimension_cache[ $cache_key ]; |
| 224 |
} |
| 225 |
if ( $attachment_id ) { |
| 226 |
$intermediate = image_get_intermediate_size( $attachment_id, $size ); |
| 227 |
if ( $intermediate ) { |
| 228 |
$sizes['width'] = $intermediate['width']; |
| 229 |
$sizes['height'] = $intermediate['height']; |
| 230 |
} |
| 231 |
} |
| 232 |
|
| 233 |
list( $sizes['width'], $sizes['height'] ) = image_constrain_size_for_editor( $sizes['width'], $sizes['height'], $size ); |
| 234 |
$resize = apply_filters( 'optml_default_crop', [] ); |
| 235 |
if ( isset( $sizes2crop[ $sizes['width'] . $sizes['height'] ] ) ) { |
| 236 |
$resize = $this->to_optml_crop( $sizes2crop[ $sizes['width'] . $sizes['height'] ] ); |
| 237 |
} |
| 238 |
$sizes['resize'] = $resize; |
| 239 |
self::$dimension_cache[ $cache_key ] = $sizes; |
| 240 |
break; |
| 241 |
case 'full' !== $size && isset( $image_args[ $size ] ): |
| 242 |
$cache_key = 'b' . $size . '_' . $sizes['width'] . '_' . $sizes['height']; |
| 243 |
if ( isset( self::$dimension_cache[ $cache_key ] ) ) { |
| 244 |
return self::$dimension_cache[ $cache_key ]; |
| 245 |
} |
| 246 |
$image_resized = image_resize_dimensions( $sizes['width'], $sizes['height'], $image_args[ $size ]['width'], $image_args[ $size ]['height'], $image_args[ $size ]['crop'] ); |
| 247 |
|
| 248 |
if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image. |
| 249 |
$sizes['width'] = $image_resized[6]; |
| 250 |
$sizes['height'] = $image_resized[7]; |
| 251 |
} |
| 252 |
// There are cases when the image meta is missing and image size is non existent, see SVG image handling. |
| 253 |
if ( ! $sizes['width'] || ! $sizes['height'] ) { |
| 254 |
break; |
| 255 |
} |
| 256 |
list( $sizes['width'], $sizes['height'] ) = image_constrain_size_for_editor( $sizes['width'], $sizes['height'], $size, 'display' ); |
| 257 |
|
| 258 |
$sizes['resize'] = $this->to_optml_crop( $image_args[ $size ]['crop'] ); |
| 259 |
self::$dimension_cache[ $cache_key ] = $sizes; |
| 260 |
break; |
| 261 |
} |
| 262 |
return $sizes; |
| 263 |
} |
| 264 |
/** |
| 265 |
* Normalize arguments for crop. |
| 266 |
* |
| 267 |
* @param array|bool $crop_args Crop arguments. |
| 268 |
* |
| 269 |
* @return array |
| 270 |
*/ |
| 271 |
public function to_optml_crop( $crop_args = [] ) { |
| 272 |
|
| 273 |
$enlarge = false; |
| 274 |
if ( isset( $crop_args['enlarge'] ) ) { |
| 275 |
$enlarge = $crop_args['enlarge']; |
| 276 |
$crop_args = $crop_args['crop']; |
| 277 |
} |
| 278 |
if ( $crop_args === true ) { |
| 279 |
return [ |
| 280 |
'type' => ResizeTypeProperty::FILL, |
| 281 |
'enlarge' => $enlarge, |
| 282 |
'gravity' => Position::CENTER, |
| 283 |
]; |
| 284 |
} |
| 285 |
if ( $crop_args === false || ! is_array( $crop_args ) || count( $crop_args ) !== 2 ) { |
| 286 |
return []; |
| 287 |
} |
| 288 |
|
| 289 |
$allowed_x = [ |
| 290 |
'left' => true, |
| 291 |
'center' => true, |
| 292 |
'right' => true, |
| 293 |
]; |
| 294 |
$allowed_y = [ |
| 295 |
'top' => true, |
| 296 |
'center' => true, |
| 297 |
'bottom' => true, |
| 298 |
]; |
| 299 |
$allowed_gravities = [ |
| 300 |
'left' => Position::WEST, |
| 301 |
'right' => Position::EAST, |
| 302 |
'top' => Position::NORTH, |
| 303 |
'bottom' => Position::SOUTH, |
| 304 |
'lefttop' => Position::NORTH_WEST, |
| 305 |
'leftbottom' => Position::SOUTH_WEST, |
| 306 |
'righttop' => Position::NORTH_EAST, |
| 307 |
'rightbottom' => Position::SOUTH_EAST, |
| 308 |
'centertop' => [ 0.5, 0 ], |
| 309 |
'centerbottom' => [ 0.5, 1 ], |
| 310 |
'leftcenter' => [ 0, 0.5 ], |
| 311 |
'rightcenter' => [ 1, 0.5 ], |
| 312 |
]; |
| 313 |
|
| 314 |
$gravity = Position::CENTER; |
| 315 |
$key_search = ( $crop_args[0] === true ? '' : |
| 316 |
( isset( $allowed_x[ $crop_args[0] ] ) ? $crop_args[0] : '' ) ) . |
| 317 |
( $crop_args[1] === true ? '' : |
| 318 |
( isset( $allowed_y[ $crop_args[1] ] ) ? $crop_args[1] : '' ) ); |
| 319 |
|
| 320 |
if ( array_key_exists( $key_search, $allowed_gravities ) ) { |
| 321 |
$gravity = $allowed_gravities[ $key_search ]; |
| 322 |
} |
| 323 |
|
| 324 |
return [ |
| 325 |
'type' => ResizeTypeProperty::FILL, |
| 326 |
'enlarge' => $enlarge, |
| 327 |
'gravity' => $gravity, |
| 328 |
]; |
| 329 |
} |
| 330 |
|
| 331 |
/** |
| 332 |
* Normalize arguments for watermark. |
| 333 |
* |
| 334 |
* @param array $watermark_args Watermark arguments. |
| 335 |
* |
| 336 |
* @return array |
| 337 |
*/ |
| 338 |
public function to_optml_watermark( $watermark_args = [] ) { |
| 339 |
$allowed_gravities = [ |
| 340 |
'left' => Position::WEST, |
| 341 |
'right' => Position::EAST, |
| 342 |
'top' => Position::NORTH, |
| 343 |
'bottom' => Position::SOUTH, |
| 344 |
'left_top' => Position::NORTH_WEST, |
| 345 |
'left_bottom' => Position::SOUTH_WEST, |
| 346 |
'right_top' => Position::NORTH_EAST, |
| 347 |
'right_bottom' => Position::SOUTH_EAST, |
| 348 |
]; |
| 349 |
$gravity = Position::CENTER; |
| 350 |
if ( isset( $watermark_args['position'] ) && array_key_exists( $watermark_args['position'], $allowed_gravities ) ) { |
| 351 |
$gravity = $allowed_gravities[ $watermark_args['position'] ]; |
| 352 |
} |
| 353 |
|
| 354 |
return [ |
| 355 |
'opacity' => 1, |
| 356 |
'position' => $gravity, |
| 357 |
]; |
| 358 |
} |
| 359 |
/** |
| 360 |
* If missing, add schema to urls. |
| 361 |
* |
| 362 |
* @param string $url Url to check. |
| 363 |
* |
| 364 |
* @return string |
| 365 |
*/ |
| 366 |
public function add_schema( $url ) { |
| 367 |
$schema_url = $url; |
| 368 |
$should_add_schema = false; |
| 369 |
if ( function_exists( 'str_starts_with' ) ) { |
| 370 |
$should_add_schema = str_starts_with( $schema_url, '//' ); |
| 371 |
} else { |
| 372 |
$should_add_schema = substr( $schema_url, 0, strlen( '//' ) ) === '//'; |
| 373 |
} |
| 374 |
if ( $should_add_schema ) { |
| 375 |
$schema_url = ( is_ssl() ? 'https:' : 'http:' ) . $schema_url; |
| 376 |
} |
| 377 |
return $schema_url; |
| 378 |
} |
| 379 |
} |
| 380 |
|