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