PluginProbe
WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation / trunk
WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation vtrunk
1.4.48 1.4.47 1.4.46 1.4.45 1.4.44 1.4.43 1.4.42 1.4.41 1.4.40 1.4.39 1.4.38 1.4.37 1.4.36 1.1.2 1.2.0 1.2.1 1.2.2 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 All 64 releases
optin / includes / utils / class-utils.php

class-utils.php in WowOptin: Next-Gen Popup Maker – Create Stunning Popups and Optins for Lead Generation trunk, at includes/utils/class-utils.php

891 lines 22.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore
2
3 namespace OPTN\Includes\Utils;
4
5 use OPTN\Includes\Dto\Option;
6 use WP_Error;
7
8 /**
9 * Utils class for public
10 *
11 * @since 1.0.1
12 *
13 * @package optin
14 * @subpackage optin/public
15 */
16 class Utils {
17
18
19 /**
20 * Class constructor
21 */
22 private function __construct() {}
23
24 /**
25 * Logs via Query Monitor
26 *
27 * @param mixed $output Output to log.
28 * @return void
29 */
30 public static function log( $output ) {
31 if ( class_exists( '\QM' ) && wp_get_environment_type() === 'local' ) {
32 \QM::debug( $output );
33 }
34 }
35
36 /**
37 * Checks if in debug mode. For Development purposes only.
38 *
39 * @since 1.0.3
40 * @return bool
41 */
42 public static function is_debug_mode() {
43 return wp_get_environment_type() === 'local' &&
44 defined( 'WP_DEBUG' ) &&
45 WP_DEBUG === true;
46 }
47
48
49 /**
50 * Logs via debug.log
51 *
52 * @param mixed $output Output to log.
53 * @return void
54 */
55 public static function log_to_file( $output ) {
56 if ( wp_get_environment_type() === 'local' &&
57 defined( 'WP_DEBUG' ) &&
58 defined( 'WP_DEBUG_LOG' ) &&
59 WP_DEBUG === true &&
60 WP_DEBUG_LOG === true
61 ) {
62 error_log( print_r( $output, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r
63 }
64 }
65
66 /**
67 * Logs via debug.log
68 *
69 * @param mixed $output Output to log.
70 * @return void
71 */
72 public static function log_error( $output ) {
73 self::log_to_file( '[OPTN_ERROR] ' . $output );
74 }
75
76 /**
77 * Checks if prefix exists
78 *
79 * @param string $value value to check.
80 * @param string $prefix prefix.
81 * @param boolean $remove Removes the prefix if found.
82 * @return boolean
83 */
84 public static function check_prefix( &$value, $prefix, $remove = false ) {
85 $i = strpos( $value, $prefix );
86
87 if ( false !== $i ) {
88 if ( $remove ) {
89 $value = substr( $value, strlen( $prefix ), strlen( $value ) );
90 }
91 return true;
92 }
93
94 return false;
95 }
96
97
98 /**
99 * Converts a binary array to decimal. Array's last element will be LSB
100 *
101 * @param array Binary array
102 * @return int
103 */
104 public static function bin_array_to_dec( $arr ) {
105 $bin = '';
106 foreach ( $arr as $a ) {
107 $bin .= $a ? '1' : '0';
108 }
109 return bindec( $bin );
110 }
111
112 /**
113 * Get percentage difference between two values.
114 *
115 * @param int|float $old_value old value.
116 * @param int|float $new_value new value.
117 * @return int|float
118 */
119 public static function get_diff_pct( $old_value, $new_value ) {
120 if ( 0 == $old_value ) { // phpcs:ignore
121 return 0 != $new_value ? 100 : 0; // phpcs:ignore
122 }
123 return ceil( ( abs( $new_value - $old_value ) / abs( $old_value ) ) * 100 );
124 }
125
126 /**
127 * Format an already-scaled percentage value for display.
128 *
129 * Rounds *up* at the last kept decimal, so a rate is never shown lower than
130 * it actually is ( 1.341 => "1.35", 1.445 => "1.45" ), and always renders
131 * the full number of decimals ( 1.5 => "1.50" ).
132 *
133 * The inner round() absorbs binary floating point error before the ceil():
134 * without it 1.11 * 100 lands on 111.00000000000001 and ceils up to 1.12.
135 *
136 * @param int|float $value Percentage value, already scaled to 0-100.
137 * @param int $decimals Decimal places to keep.
138 * @return string
139 */
140 public static function format_percentage( $value, $decimals = 2 ) {
141
142 if ( ! is_numeric( $value ) ) {
143 return number_format( 0, $decimals, '.', '' );
144 }
145
146 $factor = pow( 10, $decimals );
147 $rounded = ceil( round( floatval( $value ) * $factor, 6 ) ) / $factor;
148
149 return number_format( $rounded, $decimals, '.', '' );
150 }
151
152 public static function format_number( $num ) {
153
154 if ( ! is_numeric( $num ) ) {
155 return $num ?? '';
156 }
157
158 $units = array( '', 'K', 'M', 'B', 'T' );
159 for ( $i = 0; $num >= 1000; $i++ ) {
160 $num /= 1000;
161 }
162 return round( $num, 1 ) . $units[ $i ];
163 }
164
165 public static function get_revenue( $order_type, $order_id ) {
166
167 if ( $order_type == 0 ) {
168 if ( function_exists( 'wc_get_order' ) ) {
169 $order = wc_get_order( $order_id );
170 if ( $order && method_exists( $order, 'get_subtotal' ) ) {
171 return $order->get_subtotal() ?? 0;
172 }
173 return 0;
174 }
175 return 0;
176 } elseif ( $order_type == 1 ) {
177 if ( class_exists( 'EDD_Payment' ) ) {
178 $payment = new \EDD_Payment( $order_id );
179 return $payment->subtotal ?? 0;
180 }
181 return 0;
182 }
183
184 return 0;
185 }
186
187 public static function get_past_dates( $days = 7 ) {
188 $dates = array();
189 $today = new \DateTime();
190
191 for ( $i = 0; $i < $days; $i++ ) {
192 $date = clone $today;
193 $date->modify( "-$i day" );
194 $dates[] = $date->format( 'Y-m-d' );
195 }
196
197 return $dates;
198 }
199
200 public static function is_valid_json( $string ) {
201 json_decode( $string );
202 return json_last_error() === JSON_ERROR_NONE;
203 }
204
205 public static function get_license_info() {
206 $lic_data = get_option( 'edd_optin_license_data', array() );
207
208 $expiration = '';
209 $type = '';
210
211 if ( isset( $lic_data['expires'] ) ) {
212 $raw_expiration = $lic_data['expires'];
213
214 if ( strtotime( $raw_expiration ) ) {
215 $expiration = date( 'F j, Y, g:i A', strtotime( $raw_expiration ) ); // phpcs:ignore
216 } else {
217 $expiration = ucfirst( $raw_expiration );
218 }
219 }
220
221 if ( isset( $lic_data['license_limit'] ) ) {
222 $raw_limit = intval( $lic_data['license_limit'] );
223
224 if ( 0 === $raw_limit ) {
225 $type = 'Unlimited';
226 } else {
227 $type = $raw_limit . ' Site(s)';
228 }
229 }
230
231 $data = array(
232 'is_active' => defined( 'OPTN_PRO_VERSION' ) && isset( $lic_data['license'] ) && 'valid' === $lic_data['license'],
233 'expiration' => $expiration,
234 'type' => $type,
235 );
236
237 return $data;
238 }
239
240 public static function get_license_key() {
241 if ( ! defined( 'OPTN_PRO_VERSION' ) ) {
242 return null;
243 }
244
245 return get_option( 'edd_optin_license_key', null );
246 }
247
248 /**
249 * Check if Pro user
250 *
251 * @return boolean
252 */
253 public static function is_pro() {
254 if ( ! defined( 'OPTN_PRO_VERSION' ) ) {
255 return false;
256 }
257 $lic_data = get_option( 'edd_optin_license_data', array() );
258
259 return isset( $lic_data['license'] ) && 'valid' === $lic_data['license'];
260 }
261
262 public static function get_sanitized_query_parameters() {
263 if ( isset( $_GET ) && is_array( $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
264
265 $ret = array();
266
267 foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
268 $ret[ $key ] = $value;
269 }
270
271 return $ret;
272 }
273
274 return array();
275 }
276
277
278 public static function get_terms_by_id( $id ) {
279 $terms = array();
280 $pt = get_post_type( $id );
281 if ( $pt ) {
282 $taxes = get_object_taxonomies( $pt, 'objects' );
283 if ( ! empty( $taxes ) ) {
284 foreach ( $taxes as $tax ) {
285 $the_terms = get_the_terms( $id, $tax->name );
286 if ( ! empty( $the_terms ) ) {
287 foreach ( $the_terms as $term ) {
288 $terms[] = $term->term_id;
289 }
290 }
291 }
292 }
293 }
294 return $terms;
295 }
296
297
298 public static function get_visitor_types() {
299 $roles = array();
300 $roles[] = is_user_logged_in() ? 'logged_in' : 'logged_out';
301 $roles[] = self::detect_new_or_returning_visitor();
302 return $roles;
303 }
304
305 private static function detect_new_or_returning_visitor() {
306 $cookie_name = 'optn_visitor_type';
307 $is_new_visitor = false;
308
309 if ( ! isset( $_COOKIE[ $cookie_name ] ) ) {
310 $is_new_visitor = true;
311 setcookie( $cookie_name, 'returning', intval( time() + YEAR_IN_SECONDS ), '/' );
312 }
313
314 return $is_new_visitor ? 'new' : 'returning';
315 }
316
317 public static function check_math( $curr_value, $rule_value, $cond ) {
318 switch ( $cond ) {
319 case 'eq':
320 return $curr_value == $rule_value;
321 case 'gt':
322 return $curr_value > $rule_value;
323 case 'lt':
324 return $curr_value < $rule_value;
325 default:
326 return false;
327 }
328 }
329
330 public static function check_values( $curr_values, $rule_values, $cond, $all_count = 0 ) {
331 if ( isset( $rule_values[0] ) && str_starts_with( $rule_values[0], 'all' ) ) {
332
333 if ( is_array( $curr_values ) ) {
334 switch ( $cond ) {
335 case 'any':
336 return count( $curr_values ) > 0;
337 case 'all':
338 return $all_count <= count( $curr_values );
339 case 'not_all':
340 return $all_count > count( $curr_values );
341 case 'not_any':
342 default:
343 return count( $curr_values ) < 1;
344 }
345 }
346
347 switch ( $cond ) {
348 case 'any':
349 return true;
350 case 'not_any':
351 case 'all':
352 case 'not_all':
353 default:
354 return false;
355 }
356 }
357
358 if ( is_array( $curr_values ) ) {
359 $intersect = array_intersect( $curr_values, self::clean_values( $rule_values ) );
360
361 switch ( $cond ) {
362 case 'any':
363 return ! empty( $intersect );
364 case 'all':
365 return count( $intersect ) === count( $rule_values );
366 case 'not_all':
367 return count( $intersect ) !== count( $rule_values );
368 case 'not_any':
369 return empty( $intersect );
370 default:
371 return false;
372 }
373 }
374
375 $inc = in_array( $curr_values, self::clean_values( $rule_values ) );
376
377 if ( 'any' === $cond || 'all' === $cond ) {
378 return $inc;
379 }
380
381 if ( 'not_any' === $cond || 'not_all' === $cond ) {
382 return ! $inc;
383 }
384
385 return false;
386 }
387
388 public static function clean_values( $values ) {
389 if ( is_array( $values ) ) {
390 return array_map(
391 function ( $value ) {
392 if ( strpos( $value, '###' ) !== false ) {
393 return explode( '###', $value )[0];
394 }
395 return strval( $value );
396 },
397 $values
398 );
399 }
400
401 if ( ! is_string( $values ) ) {
402 return strval( $values );
403 }
404 }
405
406 public static function maybe_remove_prefix( &$string, $prefix ) {
407 if ( strpos( $string, $prefix ) === 0 ) {
408 $string = substr( $string, strlen( $prefix ) );
409 return true;
410 }
411 return false;
412 }
413
414 /**
415 * Validates user parameters against a string of key-value pairs
416 *
417 * @param string $values Query string format of key-value pairs to validate
418 * @param array $params Current params from $_GET
419 * @return bool Returns true if all parameters match, false otherwise
420 */
421 public static function validate_query_params( string $values, array $params ): bool {
422 if ( empty( $values ) || ! is_string( $values ) ) {
423 return false;
424 }
425
426 $param_pairs = explode( '&', $values );
427
428 foreach ( $param_pairs as $pair ) {
429 $parts = explode( '=', $pair );
430
431 // Skip invalid pairs
432 if ( count( $parts ) !== 2 || empty( $parts[0] ) || empty( $parts[1] ) ) {
433 return false;
434 }
435
436 [$key, $value] = $parts;
437
438 // Check if parameter exists and matches
439 if ( ! isset( $params[ $key ] ) || $params[ $key ] !== $value ) {
440 return false;
441 }
442 }
443
444 return true;
445 }
446
447 public static function get_user_info() {
448
449 $user_info = array(
450 'ip' => null,
451 'country' => null,
452 );
453
454 if ( isset( $_COOKIE['optn_user_info'] ) ) {
455 $info = json_decode( stripslashes( $_COOKIE['optn_user_info'] ) ); // phpcs:ignore
456 $user_info['ip'] = isset( $info->ip ) ? sanitize_text_field( $info->ip ) : null;
457 $user_info['country'] = isset( $info->country ) ? sanitize_text_field( $info->country ) : null;
458 } else {
459 $user_info['ip'] = self::get_ip_from_request();
460 }
461
462 return $user_info;
463 }
464
465 public static function get_ip_from_request() {
466 foreach ( array( 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR' ) as $key ) {
467 if ( array_key_exists( $key, $_SERVER ) === true ) {
468 foreach ( array_map( 'trim', explode( ',', $_SERVER[ $key ] ) ) as $ip ) { // phpcs:ignore
469 if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
470 return $ip;
471 }
472 }
473 }
474 }
475
476 return null;
477 }
478
479 public static function get_user_ip() {
480 $info = self::get_user_info();
481 return $info['ip'];
482 }
483
484 public static function get_embed_hook_and_pos( $opt ) {
485 $res = array( 'pos' => null );
486
487 if ( self::maybe_remove_prefix( $opt, 'before_' ) ) {
488 $res['pos'] = 'before';
489 } elseif ( self::maybe_remove_prefix( $opt, 'after_' ) ) {
490 $res['pos'] = 'after';
491 } elseif ( self::maybe_remove_prefix( $opt, 'custom###' ) ) {
492
493 if ( self::maybe_remove_prefix( $opt, 'before###' ) ) {
494 $res['pos'] = 'before';
495 $res['hook'] = $opt;
496 } elseif ( self::maybe_remove_prefix( $opt, 'after###' ) ) {
497 $res['pos'] = 'after';
498 $res['hook'] = $opt;
499 }
500
501 return $res;
502 }
503
504 switch ( $opt ) {
505 case 'body':
506 $res['hook'] = 'wp_body_open';
507 $res['single'] = false;
508 break;
509 case 'title':
510 $res['hook'] = 'the_title';
511 $res['single'] = true;
512 break;
513 case 'content':
514 $res['hook'] = 'the_content';
515 $res['single'] = true;
516 break;
517 case 'comment_form':
518 if ( 'before' === $res['pos'] ) {
519 $res['hook'] = 'comment_form_before';
520 $res['single'] = true;
521 } elseif ( 'after' === $res['pos'] ) {
522 $res['hook'] = 'comment_form_after';
523 $res['single'] = true;
524 }
525 break;
526 case 'footer':
527 $res['hook'] = 'the_footer';
528 $res['single'] = false;
529 break;
530 default:
531 $res['hook'] = null;
532 }
533
534 return $res;
535 }
536
537 /**
538 * Sanitizes a JSON array for safe use in WordPress
539 *
540 * @param string|array $input Raw JSON string or PHP array to sanitize.
541 * @param array $allowed_types Array of allowed data types ('string', 'int', 'float', 'bool', 'array').
542 * @param bool $strict_checking Whether to error on unexpected fields/types.
543 * @return array|WP_Error
544 */
545 public static function sanitize_json_array( $input, $allowed_types = array( 'string', 'int', 'float', 'bool', 'array', 'NULL' ), $strict_checking = false ) {
546 // If input is JSON string, decode it first.
547 if ( is_string( $input ) ) {
548 $decoded = json_decode( $input, true );
549 if ( json_last_error() !== JSON_ERROR_NONE ) {
550 return new \WP_Error(
551 'invalid_json',
552 'Invalid JSON format: ' . json_last_error_msg()
553 );
554 }
555 $input = $decoded;
556 }
557
558 // Verify we have an array.
559 if ( ! is_array( $input ) ) {
560 return new \WP_Error(
561 'invalid_input',
562 'Input must be a JSON string or array'
563 );
564 }
565
566 // Recursive function to sanitize array values.
567 $sanitize_value = function ( $value, $key = '' ) use ( &$sanitize_value, $allowed_types, $strict_checking ) {
568 $type = gettype( $value );
569
570 // Handle nested arrays recursively.
571 if ( is_array( $value ) ) {
572 if ( ! in_array( 'array', $allowed_types, true ) ) {
573 return new \WP_Error(
574 'invalid_type',
575 sprintf( 'Array type not allowed for key "%s"', $key )
576 );
577 }
578 $sanitized = array();
579 foreach ( $value as $k => $v ) {
580 // Sanitize array keys.
581 // $k = sanitize_key( $k );
582
583 $result = $sanitize_value( $v, $k );
584 if ( is_wp_error( $result ) ) {
585 return $result;
586 }
587
588 if ( null !== $result ) {
589 $sanitized[ $k ] = $result;
590 }
591 }
592 return $sanitized;
593 }
594
595 // Handle scalar values.
596 switch ( $type ) {
597 case 'string':
598 if ( ! in_array( 'string', $allowed_types, true ) ) {
599 return new \WP_Error(
600 'invalid_type',
601 sprintf( 'String type not allowed for key "%s"', $key )
602 );
603 }
604 return sanitize_text_field( $value );
605
606 case 'integer':
607 if ( ! in_array( 'int', $allowed_types, true ) ) {
608 return new \WP_Error(
609 'invalid_type',
610 sprintf( 'Integer type not allowed for key "%s"', $key )
611 );
612 }
613 return intval( $value );
614
615 case 'double':
616 if ( ! in_array( 'float', $allowed_types, true ) ) {
617 return new \WP_Error(
618 'invalid_type',
619 sprintf( 'Float type not allowed for key "%s"', $key )
620 );
621 }
622 return floatval( $value );
623
624 case 'boolean':
625 if ( ! in_array( 'bool', $allowed_types, true ) ) {
626 return new \WP_Error(
627 'invalid_type',
628 sprintf( 'Boolean type not allowed for key "%s"', $key )
629 );
630 }
631 return (bool) $value;
632 case 'NULL':
633 if ( ! in_array( 'NULL', $allowed_types, true ) ) {
634 return new \WP_Error(
635 'invalid_type',
636 sprintf( 'Null type not allowed for key "%s"', $key )
637 );
638 }
639 return null;
640
641 default:
642 if ( $strict_checking ) {
643 return new \WP_Error(
644 'invalid_type',
645 sprintf( 'Unsupported type "%s" for key "%s"', $type, $key )
646 );
647 }
648 // In non-strict mode, convert to sanitized string.
649 return sanitize_text_field( (string) $value );
650 }
651 };
652
653 $result = $sanitize_value( $input );
654
655 if ( is_wp_error( $result ) ) {
656 self::log_to_file( $result );
657 return null;
658 }
659
660 return $result;
661 }
662
663 /**
664 * Get the user hash
665 *
666 * @return string
667 */
668 public static function get_user_hash() {
669 if ( isset( $_COOKIE['optn_analytics_id'] ) ) {
670 return sanitize_text_field( wp_unslash( $_COOKIE['optn_analytics_id'] ) );
671 }
672
673 return null;
674 }
675
676 /**
677 * Get preview id
678 *
679 * @return int|null
680 */
681 public static function get_optin_preview_id() {
682 if ( isset( $_GET['optn_preview'] ) && current_user_can( OPTN_MIN_CAPABILITY ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
683 return intval( $_GET['optn_preview'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
684 }
685
686 return null;
687 }
688
689
690 /**
691 * Build attribute string for HTML Elements. Expected to be escaped.
692 *
693 * @param array $attr attributes array.
694 * @return string
695 */
696 public static function build_attr_str( $attr ) {
697 $res = '';
698
699 foreach ( $attr as $key => $value ) {
700 if ( false === $value ) {
701 continue;
702 }
703 $res .= " {$key}";
704 $v = is_string( $value ) ? $value : strval( $value );
705 if ( '' !== $v ) {
706 $res .= '="' . $v . '" ';
707 }
708 }
709
710 return $res;
711 }
712
713 /**
714 * Get template preview id
715 *
716 * @return int|null
717 */
718 public static function get_template_preview_id() {
719 if ( isset( $_GET['optn_template_preview'] ) && current_user_can( OPTN_MIN_CAPABILITY ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
720 return intval( $_GET['optn_template_preview'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
721 }
722
723 return null;
724 }
725
726 /**
727 * Is preview mode
728 *
729 * @return bool
730 */
731 public static function is_preview_mode() {
732 return (
733 isset( $_POST['et_fb_preview'] ) || // phpcs:ignore
734 isset( $_GET['optn_template_preview'] ) ||
735 isset( $_GET['optn_preview'] ) ) &&
736 current_user_can( OPTN_MIN_CAPABILITY ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
737 }
738
739 /**
740 * Get Audio files
741 *
742 * @return array
743 */
744 public static function get_audio_urls() {
745 $res = array();
746 $dir = OPTN_DIR . '/assets/sounds/';
747
748 $files = scandir( $dir );
749
750 foreach ( $files as $file ) {
751 if ( '.' !== $file && '..' !== $file ) {
752 $filename = pathinfo( $file, PATHINFO_FILENAME );
753 $res[ $filename ] = OPTN_URL . '/assets/sounds/' . $file;
754 }
755 }
756
757 return $res;
758 }
759
760 /**
761 * Is license page visible
762 *
763 * @return boolean
764 */
765 public static function is_show_license_page() {
766 return defined( 'OPTN_PRO_VERSION' );
767 }
768
769 /**
770 * Get currency symbol.
771 *
772 * @return string
773 */
774 public static function get_currency_symbol() {
775 if ( function_exists( 'get_woocommerce_currency_symbol' ) ) {
776 return trim( html_entity_decode( get_woocommerce_currency_symbol() ) );
777 }
778
779 if ( function_exists( 'edd_currency_symbol ' ) ) {
780 return edd_currency_symbol();
781 }
782
783 return '$';
784 }
785
786
787 /**
788 * Process REST response with error checking
789 *
790 * @param array|WP_Error $resp Remote response.
791 * @param array $status_codes HTTP status codes to consider as success.
792 * @return \OPTN\Includes\Dto\Option
793 */
794 public static function get_remote_req_body( $resp, $status_codes = array( 200 ) ) {
795 $option = new Option();
796
797 if ( is_wp_error( $resp ) ) {
798
799 $option->data = null;
800 $option->error = $resp->get_error_message();
801
802 return $option;
803 }
804
805 if ( ! in_array( wp_remote_retrieve_response_code( $resp ), $status_codes ) ) { // phpcs:ignore
806
807 $option->data = null;
808 $option->error = wp_remote_retrieve_response_message( $resp );
809
810 return $option;
811 }
812
813 $body = wp_remote_retrieve_body( $resp );
814 $body = json_decode( $body, true );
815
816 if ( json_last_error() !== JSON_ERROR_NONE ) {
817
818 $option->data = null;
819 $option->error = json_last_error_msg();
820
821 return $option;
822 }
823
824 $option->data = $body;
825 $option->error = null;
826
827 return $option;
828 }
829
830 /**
831 * Checks if given string is a valid version number
832 *
833 * @param string $version version string.
834 * @return boolean
835 */
836 public static function is_valid_version_number( $version ) {
837 if ( ! is_string( $version ) ) {
838 return false;
839 }
840
841 $regex = '/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/';
842
843 $is_match = preg_match( $regex, $version );
844
845 return boolval( $is_match );
846 }
847
848 /**
849 * Extracts name from array.
850 *
851 * @param array $fields Array of fields.
852 * @return string
853 */
854 public static function extract_name( $fields ) {
855 $res = '';
856
857 $common_keys = array(
858 'name',
859 array( 'first_name', 'last_name' ),
860 array( 'firstname', 'lastname' ),
861 array( 'fname', 'lname' ),
862 array( 'f_name', 'l_name' ),
863 );
864
865 $std_fields = array();
866 foreach ( $fields as $key => $value ) {
867 $std_fields[ strtolower( $key ) ] = $value;
868 }
869
870 foreach ( $std_fields as $key => $value ) {
871 foreach ( $common_keys as $common_key ) {
872 if ( is_array( $common_key ) ) {
873 if ( in_array( $key, $common_key, true ) ) {
874 $res = ( $fields[ $common_key[0] ] ?? '' ) . ' ' . ( $fields[ $common_key[1] ] ?? '' );
875 break;
876 }
877 } elseif ( $key === $common_key ) {
878 $res = $value;
879 break;
880 }
881 }
882
883 if ( ! empty( $res ) ) {
884 break;
885 }
886 }
887
888 return trim( $res );
889 }
890 }
891