| 1 |
<?php |
| 2 |
/** |
| 3 |
* CMB2 Utilities |
| 4 |
* |
| 5 |
* @since 1.1.0 |
| 6 |
* |
| 7 |
* @category WordPress_Plugin |
| 8 |
* @package CMB2 |
| 9 |
* @author CMB2 team |
| 10 |
* @license GPL-2.0+ |
| 11 |
* @link https://cmb2.io |
| 12 |
*/ |
| 13 |
class CMB2_Utils { |
| 14 |
|
| 15 |
/** |
| 16 |
* The WordPress ABSPATH constant. |
| 17 |
* |
| 18 |
* @var string |
| 19 |
* @since 2.2.3 |
| 20 |
*/ |
| 21 |
protected static $ABSPATH = ABSPATH; |
| 22 |
|
| 23 |
/** |
| 24 |
* The url which is used to load local resources. |
| 25 |
* |
| 26 |
* @var string |
| 27 |
* @since 2.0.0 |
| 28 |
*/ |
| 29 |
protected static $url = ''; |
| 30 |
|
| 31 |
/** |
| 32 |
* Utility method that attempts to get an attachment's ID by it's url |
| 33 |
* |
| 34 |
* @since 1.0.0 |
| 35 |
* @param string $img_url Attachment url. |
| 36 |
* @return int|false Attachment ID or false |
| 37 |
*/ |
| 38 |
public static function image_id_from_url( $img_url ) { |
| 39 |
$attachment_id = 0; |
| 40 |
$dir = wp_upload_dir(); |
| 41 |
|
| 42 |
// Is URL in uploads directory? |
| 43 |
if ( false === strpos( $img_url, $dir['baseurl'] . '/' ) ) { |
| 44 |
return false; |
| 45 |
} |
| 46 |
|
| 47 |
$file = basename( $img_url ); |
| 48 |
|
| 49 |
$query_args = array( |
| 50 |
'post_type' => 'attachment', |
| 51 |
'post_status' => 'inherit', |
| 52 |
'fields' => 'ids', |
| 53 |
'meta_query' => array( |
| 54 |
array( |
| 55 |
'value' => $file, |
| 56 |
'compare' => 'LIKE', |
| 57 |
'key' => '_wp_attachment_metadata', |
| 58 |
), |
| 59 |
), |
| 60 |
); |
| 61 |
|
| 62 |
$query = new WP_Query( $query_args ); |
| 63 |
|
| 64 |
if ( $query->have_posts() ) { |
| 65 |
|
| 66 |
foreach ( $query->posts as $post_id ) { |
| 67 |
$meta = wp_get_attachment_metadata( $post_id ); |
| 68 |
$original_file = basename( $meta['file'] ); |
| 69 |
$cropped_image_files = isset( $meta['sizes'] ) ? wp_list_pluck( $meta['sizes'], 'file' ) : array(); |
| 70 |
if ( $original_file === $file || in_array( $file, $cropped_image_files ) ) { |
| 71 |
$attachment_id = $post_id; |
| 72 |
break; |
| 73 |
} |
| 74 |
} |
| 75 |
} |
| 76 |
|
| 77 |
return 0 === $attachment_id ? false : $attachment_id; |
| 78 |
} |
| 79 |
|
| 80 |
/** |
| 81 |
* Utility method to get a combined list of default and custom registered image sizes |
| 82 |
* |
| 83 |
* @since 2.2.4 |
| 84 |
* @link http://core.trac.wordpress.org/ticket/18947 |
| 85 |
* @global array $_wp_additional_image_sizes |
| 86 |
* @return array The image sizes |
| 87 |
*/ |
| 88 |
public static function get_available_image_sizes() { |
| 89 |
global $_wp_additional_image_sizes; |
| 90 |
|
| 91 |
$default_image_sizes = array( 'thumbnail', 'medium', 'large' ); |
| 92 |
foreach ( $default_image_sizes as $size ) { |
| 93 |
$image_sizes[ $size ] = array( |
| 94 |
'height' => intval( get_option( "{$size}_size_h" ) ), |
| 95 |
'width' => intval( get_option( "{$size}_size_w" ) ), |
| 96 |
'crop' => get_option( "{$size}_crop" ) ? get_option( "{$size}_crop" ) : false, |
| 97 |
); |
| 98 |
} |
| 99 |
|
| 100 |
if ( isset( $_wp_additional_image_sizes ) && count( $_wp_additional_image_sizes ) ) { |
| 101 |
$image_sizes = array_merge( $image_sizes, $_wp_additional_image_sizes ); |
| 102 |
} |
| 103 |
|
| 104 |
return $image_sizes; |
| 105 |
} |
| 106 |
|
| 107 |
/** |
| 108 |
* Utility method to return the closest named size from an array of values |
| 109 |
* |
| 110 |
* Based off of WordPress's image_get_intermediate_size() |
| 111 |
* If the size matches an existing size then it will be used. If there is no |
| 112 |
* direct match, then the nearest image size larger than the specified size |
| 113 |
* will be used. If nothing is found, then the function will return false. |
| 114 |
* Uses get_available_image_sizes() to get all available sizes. |
| 115 |
* |
| 116 |
* @since 2.2.4 |
| 117 |
* @param array|string $size Image size. Accepts an array of width and height (in that order). |
| 118 |
* @return false|string Named image size e.g. 'thumbnail' |
| 119 |
*/ |
| 120 |
public static function get_named_size( $size ) { |
| 121 |
$data = array(); |
| 122 |
|
| 123 |
// Find the best match when '$size' is an array. |
| 124 |
if ( is_array( $size ) ) { |
| 125 |
$image_sizes = self::get_available_image_sizes(); |
| 126 |
$candidates = array(); |
| 127 |
|
| 128 |
foreach ( $image_sizes as $_size => $data ) { |
| 129 |
|
| 130 |
// If there's an exact match to an existing image size, short circuit. |
| 131 |
if ( $data['width'] == $size[0] && $data['height'] == $size[1] ) { |
| 132 |
$candidates[ $data['width'] * $data['height'] ] = array( $_size, $data ); |
| 133 |
break; |
| 134 |
} |
| 135 |
|
| 136 |
// If it's not an exact match, consider larger sizes with the same aspect ratio. |
| 137 |
if ( $data['width'] >= $size[0] && $data['height'] >= $size[1] ) { |
| 138 |
|
| 139 |
/** |
| 140 |
* To test for varying crops, we constrain the dimensions of the larger image |
| 141 |
* to the dimensions of the smaller image and see if they match. |
| 142 |
*/ |
| 143 |
if ( $data['width'] > $size[0] ) { |
| 144 |
$constrained_size = wp_constrain_dimensions( $data['width'], $data['height'], $size[0] ); |
| 145 |
$expected_size = array( $size[0], $size[1] ); |
| 146 |
} else { |
| 147 |
$constrained_size = wp_constrain_dimensions( $size[0], $size[1], $data['width'] ); |
| 148 |
$expected_size = array( $data['width'], $data['height'] ); |
| 149 |
} |
| 150 |
|
| 151 |
// If the image dimensions are within 1px of the expected size, we consider it a match. |
| 152 |
$matched = ( abs( $constrained_size[0] - $expected_size[0] ) <= 1 && abs( $constrained_size[1] - $expected_size[1] ) <= 1 ); |
| 153 |
|
| 154 |
if ( $matched ) { |
| 155 |
$candidates[ $data['width'] * $data['height'] ] = array( $_size, $data ); |
| 156 |
} |
| 157 |
} |
| 158 |
} |
| 159 |
|
| 160 |
if ( ! empty( $candidates ) ) { |
| 161 |
// Sort the array by size if we have more than one candidate. |
| 162 |
if ( 1 < count( $candidates ) ) { |
| 163 |
ksort( $candidates ); |
| 164 |
} |
| 165 |
|
| 166 |
$data = array_shift( $candidates ); |
| 167 |
$data = $data[0]; |
| 168 |
} elseif ( ! empty( $image_sizes['thumbnail'] ) && $image_sizes['thumbnail']['width'] >= $size[0] && $image_sizes['thumbnail']['width'] >= $size[1] ) { |
| 169 |
/* |
| 170 |
* When the size requested is smaller than the thumbnail dimensions, we |
| 171 |
* fall back to the thumbnail size. |
| 172 |
*/ |
| 173 |
$data = 'thumbnail'; |
| 174 |
} else { |
| 175 |
return false; |
| 176 |
} |
| 177 |
} elseif ( ! empty( $image_sizes[ $size ] ) ) { |
| 178 |
$data = $size; |
| 179 |
}// End if. |
| 180 |
|
| 181 |
// If we still don't have a match at this point, return false. |
| 182 |
if ( empty( $data ) ) { |
| 183 |
return false; |
| 184 |
} |
| 185 |
|
| 186 |
return $data; |
| 187 |
} |
| 188 |
|
| 189 |
/** |
| 190 |
* Utility method that returns time string offset by timezone |
| 191 |
* |
| 192 |
* @since 1.0.0 |
| 193 |
* @param string $tzstring Time string. |
| 194 |
* @return string Offset time string |
| 195 |
*/ |
| 196 |
public static function timezone_offset( $tzstring ) { |
| 197 |
$tz_offset = 0; |
| 198 |
|
| 199 |
if ( ! empty( $tzstring ) && is_string( $tzstring ) ) { |
| 200 |
if ( 'UTC' === substr( $tzstring, 0, 3 ) ) { |
| 201 |
$tzstring = str_replace( array( ':15', ':30', ':45' ), array( '.25', '.5', '.75' ), $tzstring ); |
| 202 |
return intval( floatval( substr( $tzstring, 3 ) ) * HOUR_IN_SECONDS ); |
| 203 |
} |
| 204 |
|
| 205 |
try { |
| 206 |
$date_time_zone_selected = new DateTimeZone( $tzstring ); |
| 207 |
$tz_offset = timezone_offset_get( $date_time_zone_selected, date_create() ); |
| 208 |
} catch ( Exception $e ) { |
| 209 |
self::log_if_debug( __METHOD__, __LINE__, $e->getMessage() ); |
| 210 |
} |
| 211 |
} |
| 212 |
|
| 213 |
return $tz_offset; |
| 214 |
} |
| 215 |
|
| 216 |
/** |
| 217 |
* Utility method that returns a timezone string representing the default timezone for the site. |
| 218 |
* |
| 219 |
* Roughly copied from WordPress, as get_option('timezone_string') will return |
| 220 |
* an empty string if no value has been set on the options page. |
| 221 |
* A timezone string is required by the wp_timezone_choice() used by the |
| 222 |
* select_timezone field. |
| 223 |
* |
| 224 |
* @since 1.0.0 |
| 225 |
* @return string Timezone string |
| 226 |
*/ |
| 227 |
public static function timezone_string() { |
| 228 |
$current_offset = get_option( 'gmt_offset' ); |
| 229 |
$tzstring = get_option( 'timezone_string' ); |
| 230 |
|
| 231 |
// Remove old Etc mappings. Fallback to gmt_offset. |
| 232 |
if ( false !== strpos( $tzstring, 'Etc/GMT' ) ) { |
| 233 |
$tzstring = ''; |
| 234 |
} |
| 235 |
|
| 236 |
if ( empty( $tzstring ) ) { // Create a UTC+- zone if no timezone string exists. |
| 237 |
if ( 0 == $current_offset ) { |
| 238 |
$tzstring = 'UTC+0'; |
| 239 |
} elseif ( $current_offset < 0 ) { |
| 240 |
$tzstring = 'UTC' . $current_offset; |
| 241 |
} else { |
| 242 |
$tzstring = 'UTC+' . $current_offset; |
| 243 |
} |
| 244 |
} |
| 245 |
|
| 246 |
return $tzstring; |
| 247 |
} |
| 248 |
|
| 249 |
/** |
| 250 |
* Returns a timestamp, first checking if value already is a timestamp. |
| 251 |
* |
| 252 |
* @since 2.0.0 |
| 253 |
* @param string|int $string Possible timestamp string. |
| 254 |
* @return int Time stamp. |
| 255 |
*/ |
| 256 |
public static function make_valid_time_stamp( $string ) { |
| 257 |
if ( ! $string ) { |
| 258 |
return 0; |
| 259 |
} |
| 260 |
|
| 261 |
return self::is_valid_time_stamp( $string ) |
| 262 |
? (int) $string : |
| 263 |
strtotime( (string) $string ); |
| 264 |
} |
| 265 |
|
| 266 |
/** |
| 267 |
* Determine if a value is a valid timestamp |
| 268 |
* |
| 269 |
* @since 2.0.0 |
| 270 |
* @param mixed $timestamp Value to check. |
| 271 |
* @return boolean Whether value is a valid timestamp |
| 272 |
*/ |
| 273 |
public static function is_valid_time_stamp( $timestamp ) { |
| 274 |
return (string) (int) $timestamp === (string) $timestamp |
| 275 |
&& $timestamp <= PHP_INT_MAX |
| 276 |
&& $timestamp >= ~PHP_INT_MAX; |
| 277 |
} |
| 278 |
|
| 279 |
/** |
| 280 |
* Checks if a value is 'empty'. Still accepts 0. |
| 281 |
* |
| 282 |
* @since 2.0.0 |
| 283 |
* @param mixed $value Value to check. |
| 284 |
* @return bool True or false |
| 285 |
*/ |
| 286 |
public static function isempty( $value ) { |
| 287 |
return null === $value || '' === $value || false === $value || array() === $value; |
| 288 |
} |
| 289 |
|
| 290 |
/** |
| 291 |
* Checks if a value is not 'empty'. 0 doesn't count as empty. |
| 292 |
* |
| 293 |
* @since 2.2.2 |
| 294 |
* @param mixed $value Value to check. |
| 295 |
* @return bool True or false |
| 296 |
*/ |
| 297 |
public static function notempty( $value ) { |
| 298 |
return null !== $value && '' !== $value && false !== $value && array() !== $value; |
| 299 |
} |
| 300 |
|
| 301 |
/** |
| 302 |
* Filters out empty values (not including 0). |
| 303 |
* |
| 304 |
* @since 2.2.2 |
| 305 |
* @param mixed $value Value to check. |
| 306 |
* @return array True or false. |
| 307 |
*/ |
| 308 |
public static function filter_empty( $value ) { |
| 309 |
return array_filter( $value, array( __CLASS__, 'notempty' ) ); |
| 310 |
} |
| 311 |
|
| 312 |
/** |
| 313 |
* Insert a single array item inside another array at a set position |
| 314 |
* |
| 315 |
* @since 2.0.2 |
| 316 |
* @param array $array Array to modify. Is passed by reference, and no return is needed. Passed by reference. |
| 317 |
* @param array $new New array to insert. |
| 318 |
* @param int $position Position in the main array to insert the new array. |
| 319 |
*/ |
| 320 |
public static function array_insert( &$array, $new, $position ) { |
| 321 |
$before = array_slice( $array, 0, $position - 1 ); |
| 322 |
$after = array_diff_key( $array, $before ); |
| 323 |
$array = array_merge( $before, $new, $after ); |
| 324 |
} |
| 325 |
|
| 326 |
/** |
| 327 |
* Defines the url which is used to load local resources. |
| 328 |
* This may need to be filtered for local Window installations. |
| 329 |
* If resources do not load, please check the wiki for details. |
| 330 |
* |
| 331 |
* @since 1.0.1 |
| 332 |
* |
| 333 |
* @param string $path URL path. |
| 334 |
* @return string URL to CMB2 resources |
| 335 |
*/ |
| 336 |
public static function url( $path = '' ) { |
| 337 |
if ( self::$url ) { |
| 338 |
return self::$url . $path; |
| 339 |
} |
| 340 |
|
| 341 |
$cmb2_url = self::get_url_from_dir( cmb2_dir() ); |
| 342 |
|
| 343 |
/** |
| 344 |
* Filter the CMB location url. |
| 345 |
* |
| 346 |
* @param string $cmb2_url Currently registered url. |
| 347 |
*/ |
| 348 |
self::$url = trailingslashit( apply_filters( 'cmb2_meta_box_url', $cmb2_url, CMB2_VERSION ) ); |
| 349 |
|
| 350 |
return self::$url . $path; |
| 351 |
} |
| 352 |
|
| 353 |
/** |
| 354 |
* Converts a system path to a URL |
| 355 |
* |
| 356 |
* @since 2.2.2 |
| 357 |
* @param string $dir Directory path to convert. |
| 358 |
* @return string Converted URL. |
| 359 |
*/ |
| 360 |
public static function get_url_from_dir( $dir ) { |
| 361 |
$dir = self::normalize_path( $dir ); |
| 362 |
|
| 363 |
// Let's test if We are in the plugins or mu-plugins dir. |
| 364 |
$test_dir = trailingslashit( $dir ) . 'unneeded.php'; |
| 365 |
if ( |
| 366 |
0 === strpos( $test_dir, self::normalize_path( WPMU_PLUGIN_DIR ) ) |
| 367 |
|| 0 === strpos( $test_dir, self::normalize_path( WP_PLUGIN_DIR ) ) |
| 368 |
) { |
| 369 |
// Ok, then use plugins_url, as it is more reliable. |
| 370 |
return trailingslashit( plugins_url( '', $test_dir ) ); |
| 371 |
} |
| 372 |
|
| 373 |
// Ok, now let's test if we are in the theme dir. |
| 374 |
$theme_root = self::normalize_path( get_theme_root() ); |
| 375 |
if ( 0 === strpos( $dir, $theme_root ) ) { |
| 376 |
// Ok, then use get_theme_root_uri. |
| 377 |
return set_url_scheme( |
| 378 |
trailingslashit( |
| 379 |
str_replace( |
| 380 |
untrailingslashit( $theme_root ), |
| 381 |
untrailingslashit( get_theme_root_uri() ), |
| 382 |
$dir |
| 383 |
) |
| 384 |
) |
| 385 |
); |
| 386 |
} |
| 387 |
|
| 388 |
// Check to see if it's anywhere in the root directory. |
| 389 |
$site_dir = self::get_normalized_abspath(); |
| 390 |
$site_url = trailingslashit( is_multisite() ? network_site_url() : site_url() ); |
| 391 |
|
| 392 |
$url = str_replace( |
| 393 |
array( $site_dir, WP_PLUGIN_DIR ), |
| 394 |
array( $site_url, WP_PLUGIN_URL ), |
| 395 |
$dir |
| 396 |
); |
| 397 |
|
| 398 |
return set_url_scheme( $url ); |
| 399 |
} |
| 400 |
|
| 401 |
/** |
| 402 |
* Get the normalized absolute path defined by WordPress. |
| 403 |
* |
| 404 |
* @since 2.2.6 |
| 405 |
* |
| 406 |
* @return string Normalized absolute path. |
| 407 |
*/ |
| 408 |
protected static function get_normalized_abspath() { |
| 409 |
return self::normalize_path( self::$ABSPATH ); |
| 410 |
} |
| 411 |
|
| 412 |
/** |
| 413 |
* `wp_normalize_path` wrapper for back-compat. Normalize a filesystem path. |
| 414 |
* |
| 415 |
* On windows systems, replaces backslashes with forward slashes |
| 416 |
* and forces upper-case drive letters. |
| 417 |
* Allows for two leading slashes for Windows network shares, but |
| 418 |
* ensures that all other duplicate slashes are reduced to a single. |
| 419 |
* |
| 420 |
* @since 2.2.0 |
| 421 |
* |
| 422 |
* @param string $path Path to normalize. |
| 423 |
* @return string Normalized path. |
| 424 |
*/ |
| 425 |
protected static function normalize_path( $path ) { |
| 426 |
if ( function_exists( 'wp_normalize_path' ) ) { |
| 427 |
return wp_normalize_path( $path ); |
| 428 |
} |
| 429 |
|
| 430 |
// Replace newer WP's version of wp_normalize_path. |
| 431 |
$path = str_replace( '\\', '/', $path ); |
| 432 |
$path = preg_replace( '|(?<=.)/+|', '/', $path ); |
| 433 |
if ( ':' === substr( $path, 1, 1 ) ) { |
| 434 |
$path = ucfirst( $path ); |
| 435 |
} |
| 436 |
|
| 437 |
return $path; |
| 438 |
} |
| 439 |
|
| 440 |
/** |
| 441 |
* Get timestamp from text date |
| 442 |
* |
| 443 |
* @since 2.2.0 |
| 444 |
* @param string $value Date value. |
| 445 |
* @param string $date_format Expected date format. |
| 446 |
* @return mixed Unix timestamp representing the date. |
| 447 |
*/ |
| 448 |
public static function get_timestamp_from_value( $value, $date_format ) { |
| 449 |
$date_object = date_create_from_format( $date_format, $value ); |
| 450 |
return $date_object ? $date_object->setTime( 0, 0, 0 )->getTimeStamp() : strtotime( $value ); |
| 451 |
} |
| 452 |
|
| 453 |
/** |
| 454 |
* Takes a php date() format string and returns a string formatted to suit for the date/time pickers |
| 455 |
* It will work only with the following subset of date() options: |
| 456 |
* |
| 457 |
* Formats: d, l, j, z, m, F, n, y, and Y. |
| 458 |
* |
| 459 |
* A slight effort is made to deal with escaped characters. |
| 460 |
* |
| 461 |
* Other options are ignored, because they would either bring compatibility problems between PHP and JS, or |
| 462 |
* bring even more translation troubles. |
| 463 |
* |
| 464 |
* @since 2.2.0 |
| 465 |
* @param string $format PHP date format. |
| 466 |
* @return string reformatted string |
| 467 |
*/ |
| 468 |
public static function php_to_js_dateformat( $format ) { |
| 469 |
|
| 470 |
// order is relevant here, since the replacement will be done sequentially. |
| 471 |
$supported_options = array( |
| 472 |
'd' => 'dd', // Day, leading 0. |
| 473 |
'j' => 'd', // Day, no 0. |
| 474 |
'z' => 'o', // Day of the year, no leading zeroes. |
| 475 |
// 'D' => 'D', // Day name short, not sure how it'll work with translations. |
| 476 |
'l ' => 'DD ', // Day name full, idem before. |
| 477 |
'l, ' => 'DD, ', // Day name full, idem before. |
| 478 |
'm' => 'mm', // Month of the year, leading 0. |
| 479 |
'n' => 'm', // Month of the year, no leading 0. |
| 480 |
// 'M' => 'M', // Month, Short name. |
| 481 |
'F ' => 'MM ', // Month, full name. |
| 482 |
'F, ' => 'MM, ', // Month, full name. |
| 483 |
'y' => 'y', // Year, two digit. |
| 484 |
'Y' => 'yy', // Year, full. |
| 485 |
'H' => 'HH', // Hour with leading 0 (24 hour). |
| 486 |
'G' => 'H', // Hour with no leading 0 (24 hour). |
| 487 |
'h' => 'hh', // Hour with leading 0 (12 hour). |
| 488 |
'g' => 'h', // Hour with no leading 0 (12 hour). |
| 489 |
'i' => 'mm', // Minute with leading 0. |
| 490 |
's' => 'ss', // Second with leading 0. |
| 491 |
'a' => 'tt', // am/pm. |
| 492 |
'A' => 'TT', // AM/PM. |
| 493 |
); |
| 494 |
|
| 495 |
foreach ( $supported_options as $php => $js ) { |
| 496 |
// replaces every instance of a supported option, but skips escaped characters. |
| 497 |
$format = preg_replace( "~(?<!\\\\)$php~", $js, $format ); |
| 498 |
} |
| 499 |
|
| 500 |
$supported_options = array( |
| 501 |
'l' => 'DD', // Day name full, idem before. |
| 502 |
'F' => 'MM', // Month, full name. |
| 503 |
); |
| 504 |
|
| 505 |
if ( isset( $supported_options[ $format ] ) ) { |
| 506 |
$format = $supported_options[ $format ]; |
| 507 |
} |
| 508 |
|
| 509 |
$format = preg_replace_callback( '~(?:\\\.)+~', array( __CLASS__, 'wrap_escaped_chars' ), $format ); |
| 510 |
|
| 511 |
return $format; |
| 512 |
} |
| 513 |
|
| 514 |
/** |
| 515 |
* Helper function for CMB_Utils::php_to_js_dateformat(). |
| 516 |
* |
| 517 |
* @since 2.2.0 |
| 518 |
* @param string $value Value to wrap/escape. |
| 519 |
* @return string Modified value |
| 520 |
*/ |
| 521 |
public static function wrap_escaped_chars( $value ) { |
| 522 |
return ''' . str_replace( '\\', '', $value[0] ) . '''; |
| 523 |
} |
| 524 |
|
| 525 |
/** |
| 526 |
* Send to debug.log if WP_DEBUG is defined and true |
| 527 |
* |
| 528 |
* @since 2.2.0 |
| 529 |
* |
| 530 |
* @param string $function Function name. |
| 531 |
* @param int $line Line number. |
| 532 |
* @param mixed $msg Message to output. |
| 533 |
* @param mixed $debug Variable to print_r. |
| 534 |
*/ |
| 535 |
public static function log_if_debug( $function, $line, $msg, $debug = null ) { |
| 536 |
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { |
| 537 |
error_log( "In $function, $line:" . print_r( $msg, true ) . ( $debug ? print_r( $debug, true ) : '' ) ); |
| 538 |
} |
| 539 |
} |
| 540 |
|
| 541 |
/** |
| 542 |
* Determine a file's extension |
| 543 |
* |
| 544 |
* @since 1.0.0 |
| 545 |
* @param string $file File url. |
| 546 |
* @return string|false File extension or false |
| 547 |
*/ |
| 548 |
public static function get_file_ext( $file ) { |
| 549 |
$parsed = parse_url( $file, PHP_URL_PATH ); |
| 550 |
return $parsed ? strtolower( pathinfo( $parsed, PATHINFO_EXTENSION ) ) : false; |
| 551 |
} |
| 552 |
|
| 553 |
/** |
| 554 |
* Get the file name from a url |
| 555 |
* |
| 556 |
* @since 2.0.0 |
| 557 |
* @param string $value File url or path. |
| 558 |
* @return string File name |
| 559 |
*/ |
| 560 |
public static function get_file_name_from_path( $value ) { |
| 561 |
$parts = explode( '/', $value ); |
| 562 |
return is_array( $parts ) ? end( $parts ) : $value; |
| 563 |
} |
| 564 |
|
| 565 |
/** |
| 566 |
* Check if WP version is at least $version. |
| 567 |
* |
| 568 |
* @since 2.2.2 |
| 569 |
* @param string $version WP version string to compare. |
| 570 |
* @return bool Result of comparison check. |
| 571 |
*/ |
| 572 |
public static function wp_at_least( $version ) { |
| 573 |
return version_compare( get_bloginfo( 'version' ), $version, '>=' ); |
| 574 |
} |
| 575 |
|
| 576 |
/** |
| 577 |
* Combines attributes into a string for a form element. |
| 578 |
* |
| 579 |
* @since 1.1.0 |
| 580 |
* @param array $attrs Attributes to concatenate. |
| 581 |
* @param array $attr_exclude Attributes that should NOT be concatenated. |
| 582 |
* @return string String of attributes for form element. |
| 583 |
*/ |
| 584 |
public static function concat_attrs( $attrs, $attr_exclude = array() ) { |
| 585 |
$attr_exclude[] = 'rendered'; |
| 586 |
$attr_exclude[] = 'js_dependencies'; |
| 587 |
|
| 588 |
$attributes = ''; |
| 589 |
foreach ( $attrs as $attr => $val ) { |
| 590 |
$excluded = in_array( $attr, (array) $attr_exclude, true ); |
| 591 |
$empty = false === $val && 'value' !== $attr; |
| 592 |
if ( ! $excluded && ! $empty ) { |
| 593 |
// if data attribute, use single quote wraps, else double. |
| 594 |
$quotes = self::is_data_attribute( $attr, 'data-' ) ? "'" : '"'; |
| 595 |
$attributes .= sprintf( ' %1$s=%3$s%2$s%3$s', $attr, $val, $quotes ); |
| 596 |
} |
| 597 |
} |
| 598 |
return $attributes; |
| 599 |
} |
| 600 |
|
| 601 |
/** |
| 602 |
* Check if given attribute is a data attribute. |
| 603 |
* |
| 604 |
* @since 2.2.5 |
| 605 |
* |
| 606 |
* @param string $att HTML attribute. |
| 607 |
* @return boolean |
| 608 |
*/ |
| 609 |
public static function is_data_attribute( $att ) { |
| 610 |
return 0 === stripos( $att, 'data-' ); |
| 611 |
} |
| 612 |
|
| 613 |
/** |
| 614 |
* Ensures value is an array. |
| 615 |
* |
| 616 |
* @since 2.2.3 |
| 617 |
* |
| 618 |
* @param mixed $value Value to ensure is array. |
| 619 |
* @param array $default Default array. Defaults to empty array. |
| 620 |
* |
| 621 |
* @return array The array. |
| 622 |
*/ |
| 623 |
public static function ensure_array( $value, $default = array() ) { |
| 624 |
if ( empty( $value ) ) { |
| 625 |
return $default; |
| 626 |
} |
| 627 |
|
| 628 |
if ( is_array( $value ) || is_object( $value ) ) { |
| 629 |
return (array) $value; |
| 630 |
} |
| 631 |
|
| 632 |
// Not sure anything would be non-scalar that is not an array or object? |
| 633 |
if ( ! is_scalar( $value ) ) { |
| 634 |
return $default; |
| 635 |
} |
| 636 |
|
| 637 |
return (array) $value; |
| 638 |
} |
| 639 |
|
| 640 |
/** |
| 641 |
* If number is numeric, normalize it with floatval or intval, depending on if decimal is found. |
| 642 |
* |
| 643 |
* @since 2.2.6 |
| 644 |
* |
| 645 |
* @param mixed $value Value to normalize (if numeric). |
| 646 |
* @return mixed Possibly normalized value. |
| 647 |
*/ |
| 648 |
public static function normalize_if_numeric( $value ) { |
| 649 |
if ( is_numeric( $value ) ) { |
| 650 |
$value = false !== strpos( $value, '.' ) ? floatval( $value ) : intval( $value ); |
| 651 |
} |
| 652 |
|
| 653 |
return $value; |
| 654 |
} |
| 655 |
|
| 656 |
/** |
| 657 |
* Generates a 12 character unique hash from a string. |
| 658 |
* |
| 659 |
* @since 2.4.0 |
| 660 |
* |
| 661 |
* @param string $string String to create a hash from. |
| 662 |
* |
| 663 |
* @return string |
| 664 |
*/ |
| 665 |
public static function generate_hash( $string ) { |
| 666 |
return substr( base_convert( md5( $string ), 16, 32 ), 0, 12 ); |
| 667 |
} |
| 668 |
|
| 669 |
} |
| 670 |
|