PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.1.8
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.1.8
6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 5.2.2 5.3 All 153 releases
mainwp / class / class-mainwp-utility.php

class-mainwp-utility.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.1.8, at class/class-mainwp-utility.php

2,338 lines 73.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP Utility Helper.
4 *
5 * @package MainWP/Dashboard
6 */
7
8 namespace MainWP\Dashboard;
9
10 // Exit if accessed directly.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 // phpcs:disable WordPress.DB.RestrictedFunctions, WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors, Generic.Metrics.CyclomaticComplexity -- Using cURL functions.
16
17 /**
18 * Class MainWP_Utility
19 *
20 * @package MainWP\Dashboard
21 */
22 class MainWP_Utility { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
23
24 /**
25 * Yoast SEO is enabled return true else return null.
26 *
27 * @static
28 * @var boolean $enabled_wp_seo If Yoast SEO is enabled return true else return null.
29 */
30 public static $enabled_wp_seo = null;
31
32 /**
33 * Private static variable.
34 *
35 * @static
36 *
37 * @var mixed Default null
38 */
39 public static $last_deactivated_alerts = null;
40
41 /**
42 * Private static variable to hold the single instance of the class.
43 *
44 * @static
45 *
46 * @var mixed Default null
47 */
48 private static $instance = null;
49
50 /**
51 * Store the disabled php functions.
52 *
53 * @static
54 * @var string $disabled_functions disabled php functions.
55 */
56 public static $disabled_functions = null;
57
58 /**
59 * Method get_class_name()
60 *
61 * Get Class Name.
62 *
63 * @return object __CLASS__
64 */
65 public static function get_class_name() {
66 return __CLASS__;
67 }
68
69 /**
70 * Method instance()
71 *
72 * Create public static instance.
73 *
74 * @static
75 * @return MainWP_Utility
76 */
77 public static function instance() {
78 if ( null === static::$instance ) {
79 static::$instance = new self();
80 }
81
82 return static::$instance;
83 }
84
85 /**
86 * Method starts_with()
87 *
88 * Start of Stack Trace.
89 *
90 * @param mixed $haystack The full stack.
91 * @param mixed $needle The function that is throwing the error.
92 *
93 * @return mixed Needle in the Haystack.
94 */
95 public static function starts_with( $haystack, $needle ) {
96 return ! strncmp( $haystack, $needle, strlen( $needle ) );
97 }
98
99 /**
100 * Method ends_with()
101 *
102 * End of Stack Trace.
103 *
104 * @param mixed $haystack Haystack parameter.
105 * @param mixed $needle Needle parameter.
106 *
107 * @return boolean
108 */
109 public static function ends_with( $haystack, $needle ) {
110 $length = strlen( $needle );
111 if ( 0 === $length ) {
112 return true;
113 }
114
115 return substr( $haystack, - $length ) === $needle;
116 }
117
118 /**
119 * Method get_nice_url()
120 *
121 * Grab url.
122 *
123 * @param string $pUrl Website URL.
124 * @param bool $showHttp Show HTTP.
125 *
126 * @return string $url.
127 */
128 public static function get_nice_url( $pUrl, $showHttp = false ) {
129 $url = $pUrl;
130
131 if ( static::starts_with( $url, 'http://' ) ) {
132 if ( ! $showHttp ) {
133 $url = substr( $url, 7 );
134 }
135 } elseif ( static::starts_with( $pUrl, 'https://' ) ) {
136 if ( ! $showHttp ) {
137 $url = substr( $url, 8 );
138 }
139 } elseif ( $showHttp ) {
140 $url = 'http://' . $url;
141 }
142
143 if ( static::ends_with( $url, '/' ) ) {
144 if ( ! $showHttp ) {
145 $url = substr( $url, 0, strlen( $url ) - 1 );
146 }
147 } else {
148 $url = $url . '/';
149 }
150
151 return $url;
152 }
153
154 /**
155 * Method is_domain_valid()
156 *
157 * Check $url against FILTER_VALIDATE_URL.
158 *
159 * @param mixed $url Domain to check.
160 *
161 * @return boolean True|False.
162 */
163 public static function is_domain_valid( $url ) {
164 return filter_var( $url, FILTER_VALIDATE_URL );
165 }
166
167 /**
168 * Method ctype_digit()
169 *
170 * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
171 *
172 * @param mixed $str String to check.
173 *
174 * @return boolean Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
175 */
176 public static function ctype_digit( $str ) {
177 return ( is_string( $str ) || is_int( $str ) || is_float( $str ) ) && preg_match( '/^\d+\z/', $str );
178 }
179
180 /**
181 * Method sortmulti()
182 *
183 * Sort the given array, Acending, Decending or by Natural Order.
184 *
185 * @param mixed $arr Array to sort.
186 * @param mixed $index Index of array.
187 * @param mixed $order Acending or Decending order.
188 * @param bool $natsort Sort an array using a "natural order" algorithm. Default: false.
189 * @param bool $case_sensitive If case sensitive return true else return false. Default: false.
190 *
191 * @return array $sorted Return the sorted array.
192 */
193 public static function sortmulti( $arr, $index, $order, $natsort = false, $case_sensitive = false ) { // phpcs:ignore -- NOSONAR - complex.
194 $sorted = array();
195 if ( is_array( $arr ) && ! empty( $arr ) ) {
196 foreach ( array_keys( $arr ) as $key ) {
197 $temp[ $key ] = $arr[ $key ][ $index ];
198 }
199 if ( ! $natsort ) {
200 if ( 'asc' === $order ) {
201 asort( $temp );
202 } else {
203 arsort( $temp );
204 }
205 } else {
206 if ( true === $case_sensitive ) {
207 natsort( $temp );
208 } else {
209 natcasesort( $temp );
210 }
211 if ( 'asc' !== $order ) {
212 $temp = array_reverse( $temp, true );
213 }
214 }
215 foreach ( array_keys( $temp ) as $key ) {
216 if ( is_numeric( $key ) ) {
217 $sorted[] = $arr[ $key ];
218 } else {
219 $sorted[ $key ] = $arr[ $key ];
220 }
221 }
222
223 return $sorted;
224 }
225
226 return $sorted;
227 }
228
229 /**
230 * Method get_sub_array_having()
231 *
232 * Get sub array.
233 *
234 * @param mixed $arr Array to traverse.
235 * @param mixed $index Index of array.
236 * @param mixed $value Array values.
237 *
238 * void array $output Sub array.
239 */
240 public static function get_sub_array_having( $arr, $index, $value ) {
241 $output = array();
242 if ( is_array( $arr ) && ! empty( $arr ) ) {
243 foreach ( $arr as $arrvalue ) {
244 $existed = isset( $arrvalue[ $index ] ) ? $arrvalue[ $index ] : null;
245 if ( $existed === $value ) {
246 $output[] = $arrvalue;
247 }
248 }
249 }
250
251 return $output;
252 }
253
254 /**
255 * Method get_sub_array_with_limit()
256 *
257 * Get sub array.
258 *
259 * @param mixed $arr Array to traverse.
260 * @param mixed $start start index of array.
261 * @param mixed $count count values.
262 *
263 * void array $output Sub array.
264 */
265 public static function get_sub_array_with_limit( $arr, $start, $count ) {
266 $output = array();
267 if ( is_array( $arr ) && ! empty( $arr ) ) {
268 if ( $start > count( $arr ) ) {
269 return array();
270 }
271 $i = 0;
272 foreach ( $arr as $value ) {
273 if ( $i >= $start && $i < $start + $count ) {
274 $output[] = $value;
275 }
276 ++$i;
277 if ( $i > $start + $count ) {
278 break;
279 }
280 }
281 }
282 return $output;
283 }
284
285
286 /**
287 * Method trim_slashes()
288 *
289 * Trim stashes from element.
290 *
291 * @param mixed $elem Element to trim.
292 *
293 * @return string Return string with no slashes.
294 */
295 public static function trim_slashes( $elem ) {
296 return trim( $elem, '/' );
297 }
298
299 /**
300 * Method sanitize()
301 *
302 * Sanitize given string.
303 *
304 * @param mixed $str String to sanitize.
305 *
306 * @return string Sanitized string.
307 */
308 public static function sanitize( $str ) {
309 return preg_replace( '/[\\\\\/\:"\*\?\<\>\|]+/', '', $str );
310 }
311
312 /**
313 * Method sanitize_alphanumeric()
314 *
315 * Sanitize given string.
316 *
317 * @param mixed $str String to sanitize.
318 *
319 * @return string Sanitized string.
320 */
321 public static function sanitize_attr_slug( $str ) {
322 $str = strtolower( $str );
323 $str = str_replace( array( '=', '?', '/' ), '-', $str );
324 $str = preg_replace( '/[^A-Za-z0-9^\-]/', '', $str );
325 return $str;
326 }
327
328 /**
329 * Method end_session()
330 *
331 * End a session.
332 *
333 * @return void
334 */
335 public static function end_session() {
336
337 if ( defined( 'WP_CLI' ) && WP_CLI ) {
338 return;
339 }
340
341 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
342 return;
343 }
344
345 session_write_close();
346 if ( 0 < ob_get_length() ) {
347 ob_end_flush();
348 }
349 }
350
351 /**
352 * Method get_timestamp()
353 *
354 * Get time stamp in gmt_offset.
355 *
356 * @param mixed $timestamp Time stamp to convert.
357 *
358 * @return string Time stamp in general mountain time offset.
359 */
360 public static function get_timestamp( $timestamp = false ) {
361 if ( false === $timestamp ) {
362 $timestamp = time();
363 }
364 $gmtOffset = get_option( 'gmt_offset' );
365
366 return $gmtOffset ? ( $gmtOffset * HOUR_IN_SECONDS ) + $timestamp : $timestamp;
367 }
368
369 /**
370 * Method date()
371 *
372 * Show date in given format.
373 *
374 * @param mixed $format Format to display date in.
375 *
376 * @return string Date.
377 */
378 public static function date( $format ) {
379 // phpcs:ignore -- use local date function.
380 return date( $format, static::get_timestamp() );
381 }
382
383 /**
384 * Method format_timestamp()
385 *
386 * Format the given timestamp.
387 *
388 * @param mixed $timestamp Timestamp to format.
389 *
390 * @return string Formatted timestamp.
391 */
392 public static function format_timestamp( $timestamp ) {
393 return date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $timestamp );
394 }
395
396 /**
397 * Method format_timezone()
398 *
399 * Format the given timestamp.
400 *
401 * @param mixed $timestamp Timestamp to format.
402 * @param mixed $with_tz_info Return date time with timezone infor.
403 * @param mixed $use_tzformat Input tz format, to support display tz child site format.
404 *
405 * @return string Formatted timestamp.
406 */
407 public static function format_timezone( $timestamp, $with_tz_info = false, $use_tzformat = false ) { // phpcs:ignore -- NOSONAR - complex.
408 $tzinfo = '';
409 if ( false !== $use_tzformat ) {
410 if ( is_array( $use_tzformat ) && ( isset( $use_tzformat['timezone_string'] ) || isset( $use_tzformat['gmt_offset'] ) || isset( $use_tzformat['date_format'] ) || isset( $use_tzformat['time_format'] ) ) ) {
411 $wp_timezone = ! empty( $use_tzformat['timezone_string'] ) ? $use_tzformat['timezone_string'] : '';
412
413 $format = '';
414 if ( ! empty( $use_tzformat['date_format'] ) ) {
415 $format .= $use_tzformat['date_format'] . ' ';
416 }
417 if ( ! empty( $use_tzformat['time_format'] ) ) {
418 $format .= $use_tzformat['time_format'] . ' ';
419 }
420 $format = rtrim( $format );
421
422 if ( empty( $wp_timezone ) ) {
423 $gmt = ! empty( $use_tzformat['gmt_offset'] ) ? $use_tzformat['gmt_offset'] : 0;
424 return date_i18n( $format, $timestamp, $gmt );
425 }
426
427 $datetime = new \DateTime( '@' . $timestamp );
428 $datetime->setTimezone( new \DateTimeZone( $wp_timezone ) );
429
430 return $datetime->format( $format );
431 }
432 return '';
433 }
434
435 $wp_timezone = static::clean_wp_timezone_string( get_option( 'timezone_string' ) );
436
437 if ( ! $wp_timezone ) {
438 if ( $with_tz_info ) {
439 $tzinfo = ' ( UTC ' . get_option( 'gmt_offset' ) . ' )';
440 }
441 return static::format_timestamp( static::get_timestamp( $timestamp ) ) . $tzinfo;
442 }
443
444 if ( $with_tz_info ) {
445 $tzinfo = ' ( ' . $wp_timezone . ' )';
446 }
447
448 $datetime = new \DateTime( '@' . $timestamp );
449 $datetime->setTimezone( new \DateTimeZone( $wp_timezone ) );
450
451 $format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) . $tzinfo;
452 return $datetime->format( $format );
453 }
454
455 /**
456 * Method clean_wp_timezone_string().
457 *
458 * @param mixed $raw Raw tz string to clean.
459 * @return string Clean tz string.
460 */
461 public static function clean_wp_timezone_string( $raw ) {
462 // If it's already a valid timezone, just return it.
463 if ( in_array( $raw, timezone_identifiers_list(), true ) ) {
464 return $raw;
465 }
466 // Try to find a valid timezone inside the messy string.
467 foreach ( timezone_identifiers_list() as $tz ) {
468 if ( strpos( $raw, $tz ) !== false ) {
469 return $tz;
470 }
471 }
472 return '';
473 }
474
475 /**
476 * Method format_timestamp()
477 *
478 * Format the given timestamp.
479 *
480 * @param mixed $timestamp Timestamp to format.
481 *
482 * @return string Formatted timestamp.
483 */
484 public static function format_date( $timestamp ) {
485 return date_i18n( get_option( 'date_format' ), $timestamp );
486 }
487
488 /**
489 * Method format_time()
490 *
491 * Format the given timestamp.
492 *
493 * @param mixed $timestamp Timestamp to format.
494 *
495 * @return string Formatted timestamp.
496 */
497 public static function format_time( $timestamp ) {
498 return date_i18n( get_option( 'time_format' ), $timestamp );
499 }
500
501 /**
502 * Get last sync information.
503 *
504 * Retrieves synchronization timestamp for global view or specific site.
505 * For global view, checks all sites and returns the most recent sync.
506 * For specific site, returns that site's last sync timestamp.
507 *
508 * @param int|null $site_id Optional. Site ID to get sync info for. Null for global view.
509 * @return array {
510 * Last sync information.
511 *
512 * @type int $timestamp Unix timestamp of last sync (0 if never synced).
513 * @type string $formatted Formatted date/time string using WordPress date/time format.
514 * @type string $status Sync status (only for global view): 'all_synced', 'not_synced', or false.
515 * @type string $message Human-readable sync message.
516 * }
517 */
518 public static function get_last_sync_info( $site_id = null ) {
519 $timestamp = 0;
520 $status = false;
521
522 if ( null === $site_id ) {
523 $result = MainWP_DB_Common::instance()->get_last_sync_status();
524 $status = $result['sync_status'];
525 $timestamp = $result['last_sync'];
526
527 if ( 'all_synced' === $status ) {
528 $timestamp = get_option( 'mainwp_last_synced_all_sites', $timestamp );
529 }
530 } else {
531 $site_id = absint( $site_id );
532 if ( $site_id > 0 ) {
533 $website = MainWP_DB::instance()->get_website_by_id( $site_id );
534 if ( $website && ! empty( $website->dtsSync ) ) {
535 $timestamp = $website->dtsSync;
536 }
537 }
538 }
539
540 $formatted = '';
541 $message = '';
542
543 if ( $timestamp ) {
544 $formatted = static::format_timestamp( static::get_timestamp( $timestamp ) );
545 /* translators: %s: formatted date/time */
546 $message = sprintf( esc_html__( 'Last synchronization completed on: %s', 'mainwp' ), $formatted );
547 } else {
548 $message = esc_html__( 'Not yet synchronized', 'mainwp' );
549 }
550
551 return array(
552 'timestamp' => $timestamp,
553 'formatted' => $formatted,
554 'status' => $status,
555 'message' => $message,
556 );
557 }
558
559 /**
560 * Format duration time to show.
561 *
562 * @param float $time timestamp.
563 * @return mixed result.
564 */
565 public static function format_duration_time( $time ) {
566
567 $original_sec = absint( $time );
568 $dura_sec = $original_sec;
569 $days = floor( $dura_sec / 86400 );
570 $dura_sec -= $days * 86400;
571 $dura_hour_sec = $dura_sec;
572 $dura_hours = floor( $dura_sec / 3600 );
573
574 if ( $days > 0 ) {
575 $formatted_dura = ( $days * 24 + $dura_hours ) . gmdate( 'i\m s\s', $dura_hour_sec );
576 } else {
577 $formatted_dura = gmdate( 'H\h i\m s\s', $original_sec );
578 }
579 return '<bdi>' . esc_html( $formatted_dura ) . '</bdi>';
580 }
581
582 /**
583 * Get UTC timestamp by date string.
584 *
585 * @param string $dt_str date.
586 * @param int $add_days Add days.
587 *
588 * @return mixed Local timestamp.
589 */
590 public static function get_utc_timestamp_by_date( $dt_str, $add_days = 0 ) {
591
592 $tz = wp_timezone(); // site timezone.
593 $day = new \DateTimeImmutable( $dt_str, $tz );
594
595 if ( is_numeric( $add_days ) && $add_days > 0 ) {
596 $day = $day->modify( '+' . $add_days . ' day' );
597 }
598
599 return $day->setTimezone( new \DateTimeZone( 'UTC' ) )->getTimestamp();
600 }
601
602
603 /**
604 * Converts a UTC timestamp (integer or float) to a local date string
605 * using the site's timezone (handles DST automatically).
606 *
607 * Supports:
608 * - Integer seconds timestamps (e.g. 1696930123)
609 * - Float seconds with microseconds (e.g. 1696930123.123456)
610 *
611 * Uses WordPress `wp_timezone()` to determine the local timezone.
612 *
613 * @since 6.0.0
614 *
615 * @param int|float|string $utc_time UTC timestamp in seconds. Can be integer (seconds)
616 * or float (seconds with microseconds).
617 * @param string $format_str PHP date format string. Default 'Y-m-d'.
618 * @param int $add_days Optional. Number of days to add (can be negative). Default 0.
619 *
620 * @return string Formatted local date string, or empty string on invalid input.
621 *
622 * @example
623 * // From plain timestamp:
624 * echo MyClass::get_local_date_by_utc_timestamp(1696930123, 'Y-m-d H:i:s');
625 * // → "2023-10-10 15:35:23" (depending on site timezone)
626 *
627 * @example
628 * // From microtime (float seconds):
629 * echo MyClass::get_local_date_by_utc_timestamp(1696930123.123456, 'Y-m-d H:i:s.v');
630 * // → "2023-10-10 15:35:23.123" (microseconds preserved)
631 */
632 public static function get_local_date_by_utc_timestamp( $utc_time, $format_str = 'Y-m-d', $add_days = 0 ) {
633 if ( ! is_numeric( $utc_time ) ) {
634 return '';
635 }
636
637 $tz = wp_timezone();
638 $utc_zone = new \DateTimeZone( 'UTC' );
639
640 // Detect float (has fractional seconds).
641 if ( is_float( $utc_time ) || strpos( (string) $utc_time, '.' ) !== false ) {
642 $dt_utc = \DateTimeImmutable::createFromFormat( 'U.u', sprintf( '%.6F', $utc_time ), $utc_zone );
643 } else {
644 $dt_utc = ( new \DateTimeImmutable( '@' . $utc_time ) )->setTimezone( $utc_zone );
645 }
646
647 if ( $add_days ) {
648 $dt_utc = $dt_utc->modify( sprintf( '%+d day', $add_days ) );
649 }
650
651 return $dt_utc->setTimezone( $tz )->format( $format_str );
652 }
653
654
655 /**
656 * Compute day/offset values and UTC datetime string for a local input time.
657 *
658 * - Automatically uses WordPress site timezone if none provided.
659 * - Converts the local datetime to UTC for MySQL compatibility.
660 * - Returns microsecond constants for daily grouping and offset adjustments.
661 *
662 * @param string $from_date_local 'Y-m-d H:i:s' in local timezone.
663 * @param string|null $local_timezone Optional. PHP timezone ID (e.g. 'Asia/Ho_Chi_Minh').
664 * @return array {
665 * @type int $day_micros Microseconds in one day (86400000000).
666 * @type int $offset_micro Timezone offset in microseconds (e.g. +25200000000).
667 * @type string $from_date_utc UTC datetime string ('Y-m-d H:i:s') for MySQL.
668 * @type string $local_timezone The timezone actually used.
669 * }
670 */
671 public static function get_time_context( $from_date_local, $local_timezone = null ) {
672 if ( empty( $local_timezone ) ) {
673 if ( function_exists( 'wp_timezone' ) ) {
674 $tz_obj = wp_timezone();
675 $local_timezone = $tz_obj->getName();
676 } else {
677 $local_timezone = get_option( 'timezone_string' ) ? get_option( 'timezone_string' ) : 'UTC';
678 }
679 }
680
681 $MICRO = 1000000;
682 $SECONDS_PER_DAY = 86400;
683 $day_micros = $SECONDS_PER_DAY * $MICRO;
684
685 $tz = new \DateTimeZone( $local_timezone );
686
687 // 👇 End of local day instead of start.
688 $dt = new \DateTimeImmutable( $from_date_local . ' 23:59:59', $tz );
689
690 $offset_seconds = $tz->getOffset( $dt );
691 $offset_micro = $offset_seconds * $MICRO;
692
693 $from_date_utc = $dt->setTimezone( new \DateTimeZone( 'UTC' ) )->format( 'Y-m-d H:i:s' );
694
695 return array(
696 'day_micros' => $day_micros,
697 'offset_micro' => $offset_micro,
698 'from_date_utc' => $from_date_utc,
699 'local_timezone' => $local_timezone,
700 );
701 }
702
703
704 /**
705 * Method human_filesize()
706 *
707 * Convert to human readable file size format,
708 * (B|kB|MB|GB|TB|PB|EB|ZB|YB).
709 *
710 * @param mixed $bytes File in bytes.
711 * @param integer $decimals Number of decimals to output.
712 *
713 * @return string Human readable file size.
714 */
715 public static function human_filesize( $bytes, $decimals = 2 ) {
716 $size = array( 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' );
717 $factor = floor( ( strlen( $bytes ) - 1 ) / 3 );
718
719 return sprintf( "%.{$decimals}f", $bytes / pow( 1024, $factor ) ) . @$size[ $factor ];
720 }
721
722 /**
723 * Method map_fields()
724 *
725 * Map Site.
726 *
727 * @param mixed $data data to map.
728 * @param mixed $keys Keys to map.
729 * @param bool $object_output Output format array|object.
730 *
731 * @return mixed Mapped data.
732 */
733 public static function map_fields( &$data, $keys, $object_output = true ) {
734 return static::map_site( $data, $keys, $object_output );
735 }
736
737 /**
738 * Method map_site()
739 *
740 * Map Site.
741 *
742 * @param mixed $website Website to map.
743 * @param mixed $keys Keys to map.
744 * @param bool $object_output Output format array|object.
745 *
746 * @return mixed $outputSite Mapped site.
747 */
748 public static function map_site( &$website, $keys, $object_output = true ) { // phpcs:ignore -- NOSONAR - complex.
749 if ( $object_output ) {
750 $outputSite = new \stdClass();
751 if ( ! empty( $website ) ) {
752 if ( is_object( $website ) ) {
753 foreach ( $keys as $key ) {
754 if ( property_exists( $website, $key ) ) {
755 $outputSite->{$key} = $website->$key;
756 } else {
757 $outputSite->{$key} = '';
758 }
759 }
760 } elseif ( is_array( $website ) ) {
761 foreach ( $keys as $key ) {
762 if ( isset( $website[ $key ] ) ) {
763 $outputSite->{$key} = $website[ $key ];
764 } else {
765 $outputSite->{$key} = '';
766 }
767 }
768 }
769 }
770 } else {
771 $outputSite = array();
772 if ( ! empty( $website ) ) {
773 if ( is_object( $website ) ) {
774 foreach ( $keys as $key ) {
775 if ( property_exists( $website, $key ) ) {
776 $outputSite[ $key ] = $website->$key;
777 } else {
778 $outputSite[ $key ] = '';
779 }
780 }
781 } elseif ( is_array( $website ) ) {
782 foreach ( $keys as $key ) {
783 if ( isset( $website[ $key ] ) ) {
784 $outputSite[ $key ] = $website[ $key ];
785 } else {
786 $outputSite[ $key ] = '';
787 }
788 }
789 }
790 }
791 }
792 return $outputSite;
793 }
794
795 /**
796 * Method array_merge()
797 *
798 * Merge two given arrays into one.
799 *
800 * @param mixed $arr1 First array.
801 * @param mixed $arr2 Second array.
802 *
803 * @return array Merged Array.
804 */
805 public static function array_merge( $arr1, $arr2 ) {
806 if ( ! is_array( $arr1 ) && ! is_array( $arr2 ) ) {
807 return array();
808 }
809 if ( ! is_array( $arr1 ) ) {
810 return $arr2;
811 }
812 if ( ! is_array( $arr2 ) ) {
813 return $arr1;
814 }
815
816 $output = array();
817 foreach ( $arr1 as $el ) {
818 $output[] = $el;
819 }
820 foreach ( $arr2 as $el ) {
821 $output[] = $el;
822 }
823
824 return $output;
825 }
826
827 /**
828 * Method update_option()
829 *
830 * Update option.
831 *
832 * @param mixed $option_name Option name.
833 * @param mixed $option_value Option value.
834 *
835 * @return (boolean) False if value was not updated and true if value was updated.
836 */
837 public static function update_option( $option_name, $option_value ) {
838 $success = add_option( $option_name, $option_value, '', 'no' );
839
840 if ( ! $success ) {
841 $success = update_option( $option_name, $option_value );
842 }
843
844 return $success;
845 }
846
847 /**
848 * Method update_user_option()
849 *
850 * Update option.
851 *
852 * @param mixed $option_name Option name.
853 * @param mixed $option_value Option value.
854 *
855 * @return (boolean) False if value was not updated and true if value was updated.
856 */
857 public static function update_user_option( $option_name, $option_value ) {
858 $user = wp_get_current_user();
859 if ( $user ) {
860 return update_user_option( $user->ID, $option_name, $option_value );
861 }
862 return false;
863 }
864
865 /**
866 * Method remove_preslash_spaces()
867 *
868 * Remove spaces before slashes.
869 *
870 * @param string $text String to strip.
871 *
872 * @return string $text Cleaned string.
873 */
874 public static function remove_preslash_spaces( $text ) {
875 while ( stristr( $text, ' /' ) ) {
876 $text = str_replace( ' /', '/', $text );
877 }
878
879 return $text;
880 }
881
882 /**
883 * Method remove_http_prefix()
884 *
885 * Remove http prefixes from given url.
886 *
887 * @param mixed $pUrl Given URL.
888 * @param bool $pTrimSlashes Whether or not to trim slashes. Default is false.
889 *
890 * @return string Trimmed URL.
891 */
892 public static function remove_http_prefix( $pUrl, $pTrimSlashes = false ) {
893 return str_replace( array( 'http:' . ( $pTrimSlashes ? '//' : '' ), 'https:' . ( $pTrimSlashes ? '//' : '' ) ), array( '', '' ), $pUrl );
894 }
895
896 /**
897 * Method remove_http_www_prefix()
898 *
899 * Remove 'www.' from given URL.
900 *
901 * @param mixed $pUrl Given URL.
902 *
903 * @return string Cleaned URL.
904 */
905 public static function remove_http_www_prefix( $pUrl ) {
906 $pUrl = static::remove_http_prefix( $pUrl, true );
907 if ( static::starts_with( strtolower( $pUrl ), 'www.' ) ) {
908 $pUrl = substr( $pUrl, 4 );
909 }
910 return $pUrl;
911 }
912
913 /**
914 * Method sanitize_file_name()
915 *
916 * Sanitize file names.
917 *
918 * @param mixed $filename File name to sanitize.
919 *
920 * @return string Sanitized filename.
921 */
922 public static function sanitize_file_name( $filename ) {
923 $filename = str_replace( array( '|', '/', '\\', ' ', ':' ), array( '-', '-', '-', '-', '-' ), $filename );
924 return sanitize_file_name( $filename );
925 }
926
927
928
929 /**
930 * Method esc_content()
931 *
932 * Escape content,
933 * allowed content (a,href,title,br,em,strong,p,hr,ul,ol,li,h1,h2 ... ).
934 *
935 * @param mixed $content Content to escape.
936 * @param string $type Type of content. Default = note.
937 * @param mixed $more_allowed input allowed tags - options.
938 *
939 * @return string Filtered content containing only the allowed HTML.
940 */
941 public static function esc_content( $content, $type = 'note', $more_allowed = array() ) {
942 if ( ! is_string( $content ) ) {
943 return $content;
944 }
945
946 if ( 'note' === $type ) {
947
948 $allowed_html = array(
949 'a' => array(
950 'href' => array(),
951 'title' => array(),
952 ),
953 'br' => array(),
954 'em' => array(),
955 'strong' => array(),
956 'p' => array(),
957 'hr' => array(),
958 'ul' => array(),
959 'ol' => array(),
960 'li' => array(),
961 'h1' => array(),
962 'h2' => array(),
963 );
964
965 if ( is_array( $more_allowed ) && ! empty( $more_allowed ) ) {
966 $allowed_html = array_merge( $allowed_html, $more_allowed );
967 }
968
969 $content = wp_kses( $content, $allowed_html );
970
971 } elseif ( 'mixed' === $type ) {
972
973 $allowed_html = array(
974 'a' => array(
975 'href' => array(),
976 'title' => array(),
977 'class' => array(),
978 'onclick' => array(),
979 ),
980 'img' => array(
981 'src' => array(),
982 'title' => array(),
983 'class' => array(),
984 'onclick' => array(),
985 'alt' => array(),
986 'width' => array(),
987 'height' => array(),
988 'sizes' => array(),
989 'srcset' => array(),
990 'usemap' => array(),
991 ),
992 'br' => array(),
993 'em' => array(),
994 'strong' => array(),
995 'p' => array(),
996 'hr' => array(),
997 'ul' => array(
998 'style' => array(),
999 ),
1000 'ol' => array(),
1001 'li' => array(),
1002 'h1' => array(),
1003 'h2' => array(),
1004 'head' => array(),
1005 'html' => array(
1006 'lang' => array(),
1007 ),
1008 'meta' => array(
1009 'name' => array(),
1010 'http-equiv' => array(),
1011 'content' => array(),
1012 'charset' => array(),
1013 ),
1014 'title' => array(),
1015 'body' => array(
1016 'style' => array(),
1017 ),
1018 'span' => array(
1019 'id' => array(),
1020 'style' => array(),
1021 'class' => array(),
1022 ),
1023 'form' => array(
1024 'id' => array(),
1025 'method' => array(),
1026 'action' => array(),
1027 'onsubmit' => array(),
1028 ),
1029 'table' => array(
1030 'class' => array(),
1031 ),
1032 'thead' => array(
1033 'class' => array(),
1034 ),
1035 'tbody' => array(
1036 'class' => array(),
1037 ),
1038 'tr' => array(
1039 'id' => array(),
1040 ),
1041 'td' => array(
1042 'class' => array(),
1043 ),
1044 'div' => array(
1045 'id' => array(),
1046 'style' => array(),
1047 'class' => array(),
1048 ),
1049 'input' => array(
1050 'type' => array(),
1051 'name' => array(),
1052 'class' => array(),
1053 'value' => array(),
1054 'onclick' => array(),
1055 ),
1056 'button' => array(
1057 'type' => array(),
1058 'name' => array(),
1059 'value' => array(),
1060 'class' => array(),
1061 'title' => array(),
1062 'onclick' => array(),
1063 ),
1064 );
1065
1066 if ( is_array( $more_allowed ) && ! empty( $more_allowed ) ) {
1067 $allowed_html = array_merge( $allowed_html, $more_allowed );
1068 }
1069
1070 $content = wp_kses( $content, $allowed_html );
1071 } else {
1072 $content = wp_kses_post( $content );
1073 }
1074
1075 return $content;
1076 }
1077
1078 /**
1079 * Method esc_mixed_content()
1080 *
1081 * Escape mixed content,
1082 * allowed content (a,href,title,br,em,strong,p,hr,ul,ol,li,h1,h2 ... ).
1083 *
1084 * @param mixed $data data to escape.
1085 * @param string $depth Maximum depth to walk through $data. Must be greater than 0.
1086 * @param mixed $more_allowed input allowed tags - options.
1087 *
1088 * @throws \MainWP_Exception Excetpion message.
1089 *
1090 * @return string Filtered content containing only the allowed HTML.
1091 */
1092 public static function esc_mixed_content( $data, $depth, $more_allowed = array() ) { // phpcs:ignore -- NOSONAR - complex.
1093 if ( $depth < 0 ) {
1094 throw new MainWP_Exception( 'Reached depth limit' );
1095 }
1096
1097 if ( is_array( $data ) ) {
1098 $output = array();
1099 foreach ( $data as $id => $el ) {
1100 // Don't forget to sanitize the ID!
1101 if ( is_string( $id ) ) {
1102 $clean_id = static::esc_content( $id, 'mixed', $more_allowed );
1103 } else {
1104 $clean_id = $id;
1105 }
1106
1107 // Check the element type, so that we're only recursing if we really have to.
1108 if ( is_array( $el ) || is_object( $el ) ) {
1109 $output[ $clean_id ] = static::esc_mixed_content( $el, $depth - 1 );
1110 } elseif ( is_string( $el ) ) {
1111 $output[ $clean_id ] = static::esc_content( $el, 'mixed', $more_allowed );
1112 } else {
1113 $output[ $clean_id ] = $el;
1114 }
1115 }
1116 } elseif ( is_object( $data ) ) {
1117 $output = new stdClass();
1118 foreach ( $data as $id => $el ) {
1119 if ( is_string( $id ) ) {
1120 $clean_id = static::esc_content( $id, 'mixed', $more_allowed );
1121 } else {
1122 $clean_id = $id;
1123 }
1124
1125 if ( is_array( $el ) || is_object( $el ) ) {
1126 $output->$clean_id = static::esc_mixed_content( $el, $depth - 1, $more_allowed );
1127 } elseif ( is_string( $el ) ) {
1128 $output->$clean_id = static::esc_content( $el, 'mixed', $more_allowed );
1129 } else {
1130 $output->$clean_id = $el;
1131 }
1132 }
1133 } elseif ( is_string( $data ) ) {
1134 return static::esc_content( $data, 'mixed', $more_allowed );
1135 } else {
1136 return $data;
1137 }
1138
1139 return $output;
1140 }
1141
1142 /**
1143 * Method parse_html_error_message()
1144 *
1145 * @param string $error_msg Error message.
1146 *
1147 * @return mixed array|string.
1148 */
1149 public static function parse_html_error_message( $error_msg ) {
1150 // pasing error message that included link html.
1151 preg_match( '/([^\<]*)(<a[^\>]*>)([^\<]*)(<[^\>]*>)(.*)/', $error_msg, $output_array );
1152 if ( is_array( $output_array ) && 6 === count( $output_array ) ) {
1153 preg_match( '/<a href="([^\"]*)"(.*)/', $output_array[2], $link_array );
1154 $link = '';
1155 if ( is_array( $link_array ) && 3 === count( $link_array ) ) {
1156 $link = $link_array[1];
1157 }
1158 if ( ! empty( $link ) ) {
1159 return array(
1160 'el_before' => esc_html( $output_array[1] ),
1161 'el_link' => esc_html( $link ),
1162 'el_text' => esc_html( $output_array[3] ),
1163 'el_after' => esc_html( $output_array[5] ),
1164 );
1165 }
1166 }
1167 return $error_msg;
1168 }
1169
1170 /**
1171 * Method show_mainwp_message()
1172 *
1173 * Check whenther or not to show the MainWP Message.
1174 *
1175 * @param mixed $type Type of message.
1176 * @param mixed $notice_id Notice ID.
1177 *
1178 * @return boolean true|false.
1179 */
1180 public static function show_mainwp_message( $type, $notice_id ) {
1181 unset( $type );
1182 $status = get_user_option( 'mainwp_notice_saved_status' );
1183 if ( ! is_array( $status ) ) {
1184 $status = array();
1185 }
1186 if ( isset( $status[ $notice_id ] ) ) {
1187 return false;
1188 }
1189 return true;
1190 }
1191
1192 /**
1193 * Method get_hide_notice_status()
1194 *
1195 * Check whenther or not to show the MainWP Message.
1196 *
1197 * @param mixed $notice_id Notice ID.
1198 *
1199 * @return mixed true|false|time.
1200 */
1201 public static function get_hide_notice_status( $notice_id ) {
1202 $notices = get_user_option( 'mainwp_notice_saved_status' );
1203 if ( ! is_array( $notices ) ) {
1204 $notices = array();
1205 }
1206 if ( isset( $notices[ $notice_id ] ) ) {
1207 return $notices[ $notice_id ];
1208 }
1209 return false;
1210 }
1211
1212 /**
1213 * Method get_flash_message()
1214 *
1215 * Get saved flash Message.
1216 *
1217 * @param mixed $message_id Notice ID.
1218 * @param bool $delete True to delete the message after get it.
1219 *
1220 * @return boolean true|false.
1221 */
1222 public static function get_flash_message( $message_id, $delete = true ) {
1223 $flash_messages = get_user_option( 'mainwp_flash_messages' );
1224 if ( ! is_array( $flash_messages ) ) {
1225 $flash_messages = array();
1226 }
1227 if ( ! isset( $flash_messages[ $message_id ] ) ) {
1228 return false;
1229 }
1230 $content = $flash_messages[ $message_id ];
1231 if ( $delete ) {
1232 unset( $flash_messages[ $message_id ] );
1233 static::update_user_option( 'mainwp_flash_messages', $flash_messages );
1234 }
1235 return $content;
1236 }
1237
1238 /**
1239 * Method update_flash_message()
1240 *
1241 * Check whenther or not to show the MainWP Message.
1242 *
1243 * @param mixed $message_id Notice ID.
1244 * @param mixed $content Content of message.
1245 *
1246 * @return boolean true|false.
1247 */
1248 public static function update_flash_message( $message_id, $content ) {
1249 $flash_messages = get_user_option( 'mainwp_flash_messages' );
1250 if ( ! is_array( $flash_messages ) ) {
1251 $flash_messages = array();
1252 }
1253 $current = isset( $flash_messages[ $message_id ] ) ? $flash_messages[ $message_id ] : '';
1254 if ( empty( $current ) ) {
1255 $current = $content;
1256 } else {
1257 $current .= '|' . $content;
1258 }
1259 $flash_messages[ $message_id ] = $current;
1260 return static::update_user_option( 'mainwp_flash_messages', $flash_messages );
1261 }
1262
1263 /**
1264 * Method array_sort()
1265 *
1266 * Sort given array by given flags.
1267 *
1268 * @param mixed $arr Array to sort.
1269 * @param mixed $key Array key.
1270 * @param string $sort_flag Flags to sort by. Default = SORT_STRING.
1271 */
1272 public static function array_sort( &$arr, $key, $sort_flag = SORT_STRING ) {
1273 $sorter = array();
1274 $ret = array();
1275 reset( $arr );
1276 foreach ( $arr as $ii => $val ) {
1277 if ( isset( $val[ $key ] ) ) {
1278 $sorter[ $ii ] = $val[ $key ];
1279 } elseif ( SORT_NUMERIC === $sort_flag ) {
1280 $sorter[ $ii ] = count( $sorter );
1281 }
1282 }
1283 asort( $sorter, $sort_flag );
1284 foreach ( $sorter as $ii => $val ) {
1285 $ret[ $ii ] = $arr[ $ii ];
1286 }
1287 $arr = $ret;
1288 }
1289
1290 /**
1291 * Method array_sort_existed_keys()
1292 *
1293 * Sort given array by given flags.
1294 *
1295 * @param mixed $arr Array to sort.
1296 * @param mixed $key Array key.
1297 * @param string $sort_flag Flags to sort by. Default = SORT_STRING.
1298 */
1299 public static function array_sort_existed_keys( &$arr, $key, $sort_flag = SORT_STRING ) {
1300 $sorter = array();
1301 $ret = array();
1302 reset( $arr );
1303
1304 // get items with $key to sort.
1305 foreach ( $arr as $ii => $val ) {
1306 if ( isset( $val[ $key ] ) ) {
1307 $sorter[ $ii ] = $val[ $key ];
1308 }
1309 }
1310 asort( $sorter, $sort_flag );
1311
1312 foreach ( $sorter as $ii => $val ) {
1313 $ret[ $ii ] = $arr[ $ii ];
1314 }
1315
1316 // asign other items (without $keys).
1317 foreach ( $arr as $ii => $val ) {
1318 if ( ! isset( $val[ $key ] ) ) {
1319 $ret[ $ii ] = $val;
1320 }
1321 }
1322
1323 $arr = $ret;
1324 }
1325
1326 /**
1327 * Method numeric_filter()
1328 *
1329 * Filter given numeric.
1330 *
1331 * @param int $int_num Int number.
1332 * @return array $arr_ints Array filtered.
1333 */
1334 public static function numeric_filter( $int_num ) {
1335 return ( (string) (int) $int_num === (string) $int_num && 0 < $int_num ) ? $int_num : false;
1336 }
1337
1338 /**
1339 * Method array_numeric_filter()
1340 *
1341 * Filter given numeric array.
1342 *
1343 * @param array $arr_ints Array to filter.
1344 * @return array $arr_ints Array filtered.
1345 */
1346 public static function array_numeric_filter( $arr_ints ) {
1347 $arr_ints = array_filter(
1348 $arr_ints,
1349 function ( $e ) {
1350 return ( (string) (int) $e === (string) $e && 0 < $e ) ? true : false;
1351 }
1352 );
1353 return $arr_ints;
1354 }
1355
1356 /**
1357 * Method enabled_wp_seo()
1358 *
1359 * Check if Yoast SEO is enabled.
1360 *
1361 * @return boolean true|false.
1362 */
1363 public static function enabled_wp_seo() {
1364 if ( null === static::$enabled_wp_seo ) {
1365 static::$enabled_wp_seo = is_plugin_active( 'wordpress-seo-extension/wordpress-seo-extension.php' );
1366 }
1367 return static::$enabled_wp_seo;
1368 }
1369
1370 /**
1371 * Method value_to_string()
1372 *
1373 * Value to string.
1374 *
1375 * @param mixed $var_value Value to convert to string.
1376 *
1377 * @return string Value that has been converted into a string.
1378 */
1379 public static function value_to_string( $var_value ) {
1380 if ( is_array( $var_value ) || is_object( $var_value ) ) {
1381 //phpcs:ignore -- for debug only
1382 return print_r( $var_value, true );
1383 } elseif ( is_string( $var_value ) ) {
1384 return $var_value;
1385 }
1386 return '';
1387 }
1388
1389 /**
1390 * Get Health Site value.
1391 *
1392 * @param mixed $issue_counts Health site issues.
1393 *
1394 * @return array Health status value.
1395 */
1396 public static function get_site_health( $issue_counts ) {
1397
1398 // Coerce non-array/empty/partial input to a full set of counts so a scalar
1399 // (e.g. a JSON-decoded string) or a missing key never fatals or warns.
1400 $issue_counts = array_merge(
1401 array(
1402 'good' => 0,
1403 'recommended' => 0,
1404 'critical' => 0,
1405 ),
1406 is_array( $issue_counts ) ? $issue_counts : array()
1407 );
1408
1409 // Normalize each counter to a non-negative integer before arithmetic so a
1410 // non-numeric or negative stored value cannot fatal or skew the score.
1411 // is_numeric() first: intval() maps a non-empty array to 1, which would
1412 // count a malformed nested value as a real issue.
1413 $good = is_numeric( $issue_counts['good'] ) ? max( 0, intval( $issue_counts['good'] ) ) : 0;
1414 $recommended = is_numeric( $issue_counts['recommended'] ) ? max( 0, intval( $issue_counts['recommended'] ) ) : 0;
1415 $critical = is_numeric( $issue_counts['critical'] ) ? max( 0, intval( $issue_counts['critical'] ) ) : 0;
1416
1417 $totalTests = $good + $recommended + $critical * 1.5;
1418 $failedTests = $recommended * 0.5 + $critical * 1.5;
1419
1420 if ( empty( $totalTests ) ) {
1421 $val = 100;
1422 } else {
1423 $val = 100 - ceil( ( $failedTests / $totalTests ) * 100 );
1424 }
1425
1426 if ( 0 > $val ) {
1427 $val = 0;
1428 }
1429
1430 if ( 100 < $val ) {
1431 $val = 100;
1432 }
1433
1434 return array(
1435 'val' => $val,
1436 'critical' => $critical,
1437 );
1438 }
1439
1440
1441 /**
1442 * Get HTTP code.
1443 *
1444 * @param int $code HTTP code.
1445 *
1446 * @return array $http_codes HTTP code.
1447 */
1448 public static function get_http_codes( $code = false ) {
1449
1450 $http_codes = array(
1451 100 => 'Continue',
1452 101 => 'Switching Protocols',
1453 200 => 'OK',
1454 201 => 'Created',
1455 202 => 'Accepted',
1456 203 => 'Non-Authoritative Information',
1457 204 => 'No Content',
1458 205 => 'Reset Content',
1459 206 => 'Partial Content',
1460 300 => 'Multiple Choices',
1461 301 => 'Moved Permanently',
1462 302 => 'Found',
1463 303 => 'See Other',
1464 304 => 'Not Modified',
1465 305 => 'Use Proxy',
1466 306 => '(Unused)',
1467 307 => 'Temporary Redirect',
1468 400 => 'Bad Request',
1469 401 => 'Unauthorized',
1470 402 => 'Payment Required',
1471 403 => 'Forbidden',
1472 404 => 'Not Found',
1473 405 => 'Method Not Allowed',
1474 406 => 'Not Acceptable',
1475 407 => 'Proxy Authentication Required',
1476 408 => 'Request Timeout',
1477 409 => 'Conflict',
1478 410 => 'Gone',
1479 411 => 'Length Required',
1480 412 => 'Precondition Failed',
1481 413 => 'Request Entity Too Large',
1482 414 => 'Request-URI Too Long',
1483 415 => 'Unsupported Media Type',
1484 416 => 'Requested Range Not Satisfiable',
1485 417 => 'Expectation Failed',
1486 500 => 'Internal Server Error',
1487 501 => 'Not Implemented',
1488 502 => 'Bad Gateway',
1489 503 => 'Service Unavailable',
1490 504 => 'Gateway Timeout',
1491 505 => 'HTTP Version Not Supported',
1492 );
1493
1494 if ( false === $code ) {
1495 return $http_codes;
1496 }
1497
1498 return isset( $http_codes[ $code ] ) ? $http_codes[ $code ] : '';
1499 }
1500
1501 /**
1502 * Method valid_input_emails().
1503 *
1504 * @param string $emails Input emails string.
1505 *
1506 * @return string $valid_emails Valid emails string.
1507 */
1508 public static function valid_input_emails( $emails ) {
1509
1510 if ( is_string( $emails ) ) {
1511 $emails = array_filter( explode( ',', $emails ) );
1512 }
1513
1514 $valid_emails = array();
1515 if ( is_array( $emails ) ) {
1516 foreach ( $emails as $email ) {
1517 $email = esc_html( trim( $email ) );
1518 if ( ! empty( $email ) && ! in_array( $email, $valid_emails, true ) ) {
1519 $valid_emails[] = $email;
1520 }
1521 }
1522 }
1523 $valid_emails = implode( ',', $valid_emails );
1524 return $valid_emails;
1525 }
1526
1527 /**
1528 * Method check_image_file_name()
1529 *
1530 * Check if the file image.
1531 *
1532 * @param string $filename Contains image (file) name.
1533 *
1534 * @return true|false valid name or not.
1535 */
1536 public static function check_image_file_name( $filename ) {
1537 if ( validate_file( $filename ) ) {
1538 return false;
1539 }
1540
1541 $allowed_files = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'webp', 'heic' );
1542 $file_ext = array_values( array_slice( explode( '.', $filename ), -1 ) )[0];
1543 $file_ext = strtolower( $file_ext );
1544 if ( ! in_array( $file_ext, $allowed_files ) ) {
1545 return false;
1546 }
1547
1548 return true;
1549 }
1550
1551 /**
1552 * Method check_abandoned()
1553 *
1554 * Get site's icon.
1555 *
1556 * @param mixed $siteId site's id.
1557 * @param string $which to check plugin/theme.
1558 *
1559 * @return array result error or success
1560 * @throws \MainWP_Exception Error message.
1561 */
1562 public static function check_abandoned( $siteId = null, $which = '' ) { // phpcs:ignore -- NOSONAR - complex.
1563 if ( static::ctype_digit( $siteId ) ) {
1564 $website = MainWP_DB::instance()->get_website_by_id( $siteId );
1565 if ( MainWP_System_Utility::can_edit_website( $website ) ) {
1566 $error = '';
1567 try {
1568 $information = MainWP_Connect::fetch_url_authed( $website, 'check_abandoned', array( 'which' => $which ) );
1569 } catch ( MainWP_Exception $e ) {
1570 $error = $e->getMessage();
1571 }
1572
1573 if ( '' !== $error ) {
1574 return array( 'error' => $error );
1575 } elseif ( isset( $information['success'] ) && ! empty( $information['success'] ) ) {
1576 return array( 'result' => 'success' );
1577 } else {
1578 return array( 'undefined_error' => true );
1579 }
1580 }
1581 }
1582 return array( 'result' => 'NOSITE' );
1583 }
1584
1585 /**
1586 * Get directory or slug of plugin.
1587 *
1588 * @param string $slug Plugin slug.
1589 *
1590 * @return string $value directory or slug of plugin.
1591 */
1592 public static function get_dir_slug( $slug ) {
1593 $value = '';
1594 if ( false === strpos( $slug, '/' ) ) {
1595 if ( false !== strpos( $slug, '.' ) ) {
1596 $value = substr( $slug, 0, strpos( $slug, '.' ) );
1597 }
1598 } else {
1599 $value = dirname( $slug );
1600 }
1601 if ( empty( $value ) ) {
1602 return $slug;
1603 }
1604 return $value;
1605 }
1606
1607 /**
1608 * Metho get_siteview_mode().
1609 *
1610 * Get site view mode.
1611 *
1612 * @return string $viewmode Site view mode.
1613 */
1614 public static function get_siteview_mode() {
1615 $viewmode = get_user_option( 'mainwp_sitesviewmode' );
1616 if ( 'grid' !== $viewmode && 'table' !== $viewmode ) {
1617 $viewmode = 'table';
1618 }
1619 return $viewmode;
1620 }
1621
1622
1623 /**
1624 * Metho delete_file().
1625 *
1626 * Delete file.
1627 *
1628 * @param string $file_path File path.
1629 *
1630 * @return bool true|false.
1631 */
1632 public static function delete_file( $file_path ) {
1633
1634 global $wp_filesystem;
1635
1636 if ( ! empty( $file_path ) ) {
1637 if ( $wp_filesystem ) {
1638 if ( $wp_filesystem->exists( $file_path ) ) {
1639 $wp_filesystem->delete( $file_path );
1640 }
1641 } elseif ( file_exists( $file_path ) ) {
1642 wp_delete_file( $file_path );
1643 }
1644 return true;
1645 }
1646
1647 return false;
1648 }
1649
1650 /**
1651 * Method get_disable_functions()
1652 *
1653 * Get disable functions.
1654 *
1655 * @return string
1656 */
1657 public function get_disable_functions() {
1658 if ( null === static::$disabled_functions ) {
1659 static::$disabled_functions = ini_get( 'disable_functions' );
1660 }
1661 return static::$disabled_functions;
1662 }
1663
1664 /**
1665 * Method is_disable_functions()
1666 *
1667 * Check if it is disabled functions.
1668 *
1669 * @param string $func Function name to check.
1670 *
1671 * @return string
1672 */
1673 public function is_disabled_functions( $func ) {
1674 $dis_funcs = $this->get_disable_functions();
1675
1676 if ( ! empty( $dis_funcs ) && ( false !== stristr( $dis_funcs, $func ) ) ) {
1677 return true;
1678 }
1679 return false;
1680 }
1681
1682 /**
1683 * Method hook_verify_ping_nonce()
1684 *
1685 * Verify nonce without session and user id.
1686 *
1687 * @param bool $input_value Boolean value, it should always be FALSE.
1688 * @param string $nonce Nonce to verify.
1689 * @param mixed $siteid Site ID.
1690 *
1691 * @return mixed If verified return 1 or 2, if not return false.
1692 */
1693 public static function hook_verify_ping_nonce( $input_value, $nonce = '', $siteid = false ) {
1694 unset( $input_value );
1695 $action = 'pingnonce';
1696 return static::verify_site_nonce( $nonce, $action, $siteid );
1697 }
1698
1699 /**
1700 * Method create_site_nonce()
1701 *
1702 * Create action nonce for site.
1703 *
1704 * @param mixed $action Action to perform.
1705 * @param mixed $siteid Site ID.
1706 *
1707 * @return string Custom nonce.
1708 */
1709 public static function create_site_nonce( $action = - 1, $siteid = false ) {
1710 if ( empty( $action ) || empty( $siteid || ! is_numeric( $siteid ) ) ) {
1711 return false;
1712 }
1713 return substr( wp_hash( 'site|' . $siteid . '|' . $action, 'nonce' ), - 12, 10 );
1714 }
1715
1716 /**
1717 * Method verify_site_nonce()
1718 *
1719 * Verify nonce without session and user id.
1720 *
1721 * @param string $nonce Nonce to verify.
1722 * @param mixed $action Action to perform.
1723 * @param mixed $siteid Site ID.
1724 *
1725 * @return mixed If verified return 1 or 2, if not return false.
1726 */
1727 public static function verify_site_nonce( $nonce, $action = - 1, $siteid = 0 ) {
1728 $nonce = (string) $nonce;
1729 if ( empty( $nonce ) || empty( $siteid || ! is_numeric( $siteid ) ) ) {
1730 return false;
1731 }
1732
1733 $expected = substr( wp_hash( 'site|' . $siteid . '|' . $action, 'nonce' ), - 12, 10 );
1734 if ( hash_equals( $expected, $nonce ) ) {
1735 return 1;
1736 }
1737 return false;
1738 }
1739
1740
1741 /**
1742 * Find for multi keywords.
1743 *
1744 * @param string $name_str string find on.
1745 * @param array $words Array string input.
1746 * @return bool True|False.
1747 */
1748 public static function multi_find_keywords( $name_str, $words = array() ) {
1749 if ( ! is_array( $words ) ) {
1750 return false;
1751 }
1752 foreach ( $words as $word ) {
1753 if ( stristr( $name_str, $word ) ) {
1754 return true;
1755
1756 }
1757 }
1758 return false;
1759 }
1760
1761 /**
1762 * Merge values from right array to left array.
1763 *
1764 * @param array $left_array left array.
1765 * @param array $right_array right array.
1766 *
1767 * @return array $result result array.
1768 */
1769 public static function right_array_merge( $left_array, $right_array ) {
1770 if ( ! is_array( $left_array ) || ! is_array( $right_array ) ) {
1771 return array();
1772 }
1773 $result = array_intersect_key( $right_array, $left_array );
1774 return array_merge( $left_array, $result );
1775 }
1776
1777
1778 /**
1779 * Method get_set_deactivated_licenses_alerted().
1780 *
1781 * @param string $slug Extension slug.
1782 * @param bool $time_value Time value.
1783 * @param string $act get/set value.
1784 *
1785 * @return array $result result array.
1786 */
1787 public function get_set_deactivated_licenses_alerted( $slug, $time_value = false, $act = 'get' ) {
1788 if ( null === $this->last_deactivated_alerts ) {
1789 $this->last_deactivated_alerts = get_option( 'mainwp_cron_licenses_deactivated_alerted', array() );
1790 if ( ! is_array( $this->last_deactivated_alerts ) ) {
1791 $this->last_deactivated_alerts = array();
1792 }
1793 }
1794 if ( 'get' === $act ) {
1795 return isset( $this->last_deactivated_alerts[ $slug ] ) ? $this->last_deactivated_alerts[ $slug ] : 0;
1796 } elseif ( 'set' === $act ) {
1797 $this->last_deactivated_alerts[ $slug ] = intval( $time_value );
1798 get_option( 'mainwp_cron_licenses_deactivated_alerted', $this->last_deactivated_alerts );
1799 }
1800 }
1801
1802 /**
1803 * Method get_remote_favicon().
1804 *
1805 * @param string $url Url.
1806 * @param string $favi favicon file name.
1807 * @param int $item_id item id.
1808 * @param string $file_prefix favicon file prefix name.
1809 *
1810 * @return mixed result.
1811 */
1812 public static function get_remote_favicon( $url, $favi = '', $item_id = false, $file_prefix = '' ) { // phpcs:ignore -- NOSONAR - complex.
1813
1814 if ( empty( $favi ) ) {
1815 $favi = 'favicon.ico';
1816 }
1817
1818 if ( '/' !== substr( $url, - 1 ) ) {
1819 $url .= '/';
1820 }
1821
1822 $favi_url = $url . $favi;
1823
1824 $content = MainWP_Connect::get_file_content( $favi_url );
1825
1826 if ( empty( $content ) && 'favicon.ico' === $favi ) {
1827 $favi_url = $url . 'favicon.png';
1828 $content = MainWP_Connect::get_file_content( $favi_url ); // try other file.
1829 }
1830
1831 if ( ! empty( $content ) ) {
1832
1833 MainWP_System_Utility::get_wp_file_system();
1834
1835 global $wp_filesystem;
1836
1837 $dirs = MainWP_System_Utility::get_mainwp_dir( 'icons', true );
1838 $iconsDir = $dirs[0];
1839 if ( $favi ) {
1840
1841 $tmp = explode( '.', $favi );
1842 if ( 2 !== count( $tmp ) ) {
1843 return false;
1844 }
1845
1846 $favi_ext = $tmp[1];
1847
1848 if ( empty( $item_id ) ) {
1849 $item_id = time() . '-' . wp_rand( 100, 999 );
1850 }
1851 if ( ! empty( $file_prefix ) ) {
1852 $filename = $file_prefix . $item_id . '.' . $favi_ext;
1853 } else {
1854 $filename = 'favi-' . $item_id . '.' . $favi_ext;
1855 }
1856
1857 $size = $wp_filesystem->put_contents( $iconsDir . $filename, $content ); // phpcs:ignore --
1858 if ( $size ) {
1859 MainWP_Logger::instance()->debug( 'Icon Cost Product size :: ' . $size );
1860 return array(
1861 'result' => 'success',
1862 'file' => $filename,
1863 'dir' => $iconsDir,
1864 );
1865 } else {
1866 return array( 'error' => 'Save icon file failed.' );
1867 }
1868 }
1869 return false;
1870 } else {
1871 return array( 'error' => esc_html__( 'Download icon file failed', 'mainwp' ) );
1872 }
1873 }
1874
1875 /**
1876 * Method get_saved_favicon_url()
1877 *
1878 * @param string $favi Favicon file name.
1879 *
1880 * @return mixed $faviurl Favicon URL.
1881 */
1882 public static function get_saved_favicon_url( $favi ) {
1883 $faviurl = '';
1884 if ( ! empty( $favi ) ) {
1885 $dirs = MainWP_System_Utility::get_icons_dir();
1886 if ( file_exists( $dirs[0] . $favi ) ) {
1887 $faviurl = $dirs[1] . $favi;
1888 } else {
1889 $faviurl = '';
1890 }
1891 }
1892 return $faviurl;
1893 }
1894
1895 /**
1896 * Method delete_saved_favicon()
1897 *
1898 * @param string $favi Favicon file name.
1899 *
1900 * @return bool Success result.
1901 */
1902 public static function delete_saved_favicon( $favi ) {
1903 if ( ! empty( $favi ) ) {
1904 $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
1905 global $wp_filesystem;
1906 $dirs = MainWP_System_Utility::get_icons_dir();
1907 if ( $hasWPFileSystem && $wp_filesystem->exists( $dirs[0] . $favi ) ) {
1908 $wp_filesystem->delete( $dirs[0] . $favi );
1909 return true;
1910 }
1911 }
1912 return false;
1913 }
1914
1915 /**
1916 * Delete icon file.
1917 *
1918 * @param string $sub_dir Sub dir file icon.
1919 * @param string $cost_icon file icon.
1920 */
1921 public function delete_uploaded_icon_file( $sub_dir, $cost_icon ) {
1922 $valid_file = 0 === validate_file( $cost_icon ) ? true : false;
1923 if ( $valid_file ) {
1924 $dirs = MainWP_System_Utility::get_mainwp_dir( $sub_dir, true );
1925 $f = $dirs[0] . $cost_icon;
1926 if ( file_exists( $f ) ) {
1927 wp_delete_file( $f );
1928 }
1929 }
1930 }
1931
1932 /**
1933 * Method get_table_orders().
1934 *
1935 * @param array $data table data.
1936 */
1937 public function get_table_orders( $data ) {
1938
1939 $values = array(
1940 'orderby' => null,
1941 'order' => null,
1942 );
1943
1944 if ( isset( $data['order'] ) ) {
1945 $columns = isset( $data['columns'] ) ? wp_unslash( $data['columns'] ) : array();
1946 $ord_col = isset( $data['order'][0]['column'] ) ? sanitize_text_field( wp_unslash( $data['order'][0]['column'] ) ) : '';
1947 if ( isset( $columns[ $ord_col ] ) ) {
1948 $values = array(
1949 'orderby' => isset( $columns[ $ord_col ]['data'] ) ? sanitize_text_field( wp_unslash( $columns[ $ord_col ]['data'] ) ) : '',
1950 'order' => isset( $data['order'][0]['dir'] ) ? sanitize_text_field( wp_unslash( $data['order'][0]['dir'] ) ) : '',
1951 );
1952 }
1953 }
1954
1955 return $values;
1956 }
1957
1958 /**
1959 * Method valid_file_check().
1960 *
1961 * @param string $path file path.
1962 * @param bool $readable readable.
1963 *
1964 * @return bool is valid.
1965 */
1966 public static function valid_file_check( $path, $readable = true ) {
1967 $valid = is_string( $path ) && ! stristr( $path, '..' );
1968 if ( $valid && $readable ) {
1969 $valid = is_readable( $path );
1970 }
1971 return $valid;
1972 }
1973
1974
1975 /**
1976 * Handle sanitize POST data.
1977 *
1978 * @param array $data input data.
1979 *
1980 * @return array
1981 */
1982 public function sanitize_data( $data ) {
1983 if ( ! is_array( $data ) ) {
1984 return array();
1985 }
1986
1987 // Sanitize all record values.
1988 return array_map(
1989 function ( $value ) {
1990 if ( ! is_array( $value ) ) {
1991 return wp_strip_all_tags( $value );
1992 }
1993
1994 return $value;
1995 },
1996 $data
1997 );
1998 }
1999
2000 /**
2001 * String ends by.
2002 *
2003 * @param mixed $str str.
2004 * @param mixed $ends ends.
2005 * @return bool value value.
2006 */
2007 public static function string_ends_by( $str, $ends ) {
2008 if ( function_exists( '\str_ends_with' ) ) {
2009 return \str_ends_with( $str, $ends );
2010 } else {
2011 $ends_len = strlen( $ends );
2012 if ( $ends_len > strlen( $str ) ) {
2013 return false;
2014 }
2015 return substr( $str, -$ends_len ) === $ends;
2016 }
2017 }
2018 /**
2019 * Returns number in shorter format.
2020 *
2021 * @param int $number str.
2022 * @return string $number Shorer number.
2023 */
2024 public static function short_number_format( $number ) {
2025 if ( $number > 999 && $number < 1000000 ) {
2026 // Anything between 1000 and 1000000.
2027 $number = number_format( $number / 1000, 1 ) . 'K';
2028 } elseif ( $number >= 1000000000 ) {
2029 // 1000000 or higher.
2030 $number = number_format( $number / 1000000, 2 ) . 'M';
2031 }
2032 return $number;
2033 }
2034
2035 /**
2036 * Returns date in time ago format
2037 *
2038 * @param mixed $ptime Date stamp.
2039 * @return string $string Time elapsed string.
2040 */
2041 public static function time_elapsed_string( $ptime ) {
2042 $etime = time() - $ptime;
2043
2044 if ( $etime < 1 ) {
2045 return '0 seconds';
2046 }
2047
2048 $a = array(
2049 365 * 24 * 60 * 60 => 'year',
2050 30 * 24 * 60 * 60 => 'month',
2051 24 * 60 * 60 => 'day',
2052 60 * 60 => 'hour',
2053 60 => 'minute',
2054 1 => 'second',
2055 );
2056 $a_plural = array(
2057 'year' => 'years',
2058 'month' => 'months',
2059 'day' => 'days',
2060 'hour' => 'hours',
2061 'minute' => 'minutes',
2062 'second' => 'seconds',
2063 );
2064
2065 foreach ( $a as $secs => $str ) {
2066 $d = $etime / $secs;
2067 if ( $d >= 1 ) {
2068 $r = round( $d );
2069 return $r . ' ' . ( $r > 1 ? $a_plural[ $str ] : $str ) . ' ago';
2070 }
2071 }
2072 }
2073
2074 /**
2075 * Returns language as flag.
2076 *
2077 * @param string $language Language code.
2078 * @return void
2079 */
2080 public static function get_language_code_as_flag( $language ) {
2081 // For flag extraction, remove trailing _formal or _informal if present.
2082 $flag_language = preg_replace( '/_(formal|informal)$/', '', $language );
2083 // Get the last 2 characters of the flag language code.
2084 $last_two_chars = ! empty( $flag_language ) ? substr( $flag_language, -2 ) : '';
2085 // Convert to lowercase.
2086 $lowercase_last_two_chars = strtolower( $last_two_chars );
2087 $lowercase_flag_language = strtolower( $flag_language );
2088
2089 // Get display name using the original language string.
2090 $display_language = function_exists( 'locale_get_display_name' ) ? locale_get_display_name( $language ) : $language;
2091
2092 // Adjust special country codes.
2093 if ( 'et' === $lowercase_last_two_chars ) {
2094 $lowercase_last_two_chars = 'ee';
2095 }
2096 if ( 'sq' === $lowercase_last_two_chars ) {
2097 $lowercase_last_two_chars = 'al';
2098 }
2099 if ( 'ab' === $lowercase_last_two_chars ) {
2100 $lowercase_last_two_chars = 'dz';
2101 }
2102
2103 $stacked_flags = array(
2104 'ca' => array(
2105 'primary' => 'es',
2106 'secondary' => 'ad',
2107 ),
2108 );
2109 // Only stack flags for explicit Catalan locales to avoid affecting Canada.
2110 $stacked_flag_locales = array(
2111 'ca' => array(
2112 'ca',
2113 ),
2114 );
2115
2116 if ( isset( $stacked_flags[ $lowercase_last_two_chars ] ) ) {
2117 $should_stack = true;
2118 if ( isset( $stacked_flag_locales[ $lowercase_last_two_chars ] ) ) {
2119 $should_stack = in_array( $lowercase_flag_language, $stacked_flag_locales[ $lowercase_last_two_chars ], true );
2120 }
2121
2122 if ( $should_stack ) {
2123 $primary_flag = $stacked_flags[ $lowercase_last_two_chars ]['primary'];
2124 $secondary_flag = $stacked_flags[ $lowercase_last_two_chars ]['secondary'];
2125
2126 echo '<span data-tooltip="' . esc_html__( 'Site Language: ', 'mainwp' ) . esc_attr( $display_language ) . '" data-position="left center" data-inverted="">';
2127 echo '<span class="mainwp-flag-stack">';
2128 echo '<i class="small ' . esc_attr( $primary_flag ) . ' flag mainwp-flag-stack__flag mainwp-flag-stack__flag--primary"></i>';
2129 echo '<i class="small ' . esc_attr( $secondary_flag ) . ' flag mainwp-flag-stack__flag mainwp-flag-stack__flag--secondary"></i>';
2130 echo '</span>';
2131 echo '</span>';
2132 return;
2133 }
2134 }
2135
2136 echo '<span data-tooltip="' . esc_html__( 'Site Language: ', 'mainwp' ) . esc_attr( $display_language ) . '" data-position="left center" data-inverted=""><i class="small ' . esc_attr( $lowercase_last_two_chars ) . ' flag"></i></span>';
2137 }
2138
2139 /**
2140 * Returns icon for the site indexability status.
2141 *
2142 * @param int $status Status, 1 or 0.
2143 * @return void.
2144 */
2145 public static function get_site_index_option_icon( $status ) {
2146 $icon = '';
2147 $tooltip = '';
2148 if ( isset( $status ) && '' !== $status ) {
2149 if ( 1 === intval( $status ) ) {
2150 $icon = 'green dot circle outline';
2151 $tooltip = 'Search engines can index this site.';
2152 } elseif ( 0 === intval( $status ) ) {
2153 $icon = 'red ban';
2154 $tooltip = 'This site is blocking search engines.';
2155 }
2156 } else {
2157 $icon = 'grey circle';
2158 $tooltip = 'Indexing status unknown. Resync the site or check manually in WordPress Settings > Reading.';
2159 }
2160 echo '<span data-tooltip="' . $tooltip . '" data-position="left center" data-inverted=""><i class="' . $icon . ' icon"></i></span>'; //phpcs:ignore -- ok.
2161 }
2162
2163 /**
2164 * Returns the appropriate Fomantic UI color class based on number of updates
2165 *
2166 * @param int $update_count Number of available updates.
2167 *
2168 * @return string CSS class for the element
2169 */
2170 public static function mainwp_get_update_count_class( $update_count ) {
2171 // Convert to integer using intval().
2172 $update_count = intval( $update_count );
2173
2174 // Ensure count is not negative.
2175 if ( 0 > $update_count ) {
2176 $update_count = 0;
2177 }
2178
2179 if ( 0 === $update_count ) {
2180 return 'grey';
2181 } elseif ( $update_count >= 1 && $update_count <= 3 ) {
2182 return 'yellow';
2183 } elseif ( $update_count >= 4 && $update_count <= 5 ) {
2184 return 'orange';
2185 } else {
2186 return 'red';
2187 }
2188 }
2189
2190 /**
2191 * Display site name and URL with optional WP Admin link.
2192 *
2193 * @param object|int $site Site object or Site ID.
2194 * @param bool $wp_admin Whether to show WP Admin link.
2195 * @param bool $print_content Whether to print or return the content.
2196 * @return string HTML markup for site display.
2197 */
2198 public static function mainwp_display_site( $site = '', $wp_admin = true, $print_content = false ) {
2199 if ( empty( $site ) ) {
2200 return '';
2201 }
2202
2203 if ( static::ctype_digit( $site ) ) {
2204 $website = MainWP_DB::instance()->get_website_by_id( $site );
2205 } elseif ( is_object( $site ) && isset( $site->id ) ) {
2206 $website = $site;
2207 } else {
2208 return '';
2209 }
2210
2211 if ( ! $website ) {
2212 return '';
2213 }
2214
2215 $site_name = esc_html( stripslashes( $website->name ) );
2216 $site_url = esc_url( $website->url );
2217 $nice_url = esc_html( static::get_nice_url( $website->url ) );
2218
2219 $html = '<div class="mainwp-site-display">';
2220
2221 // WP Admin link (if enabled and user has permission).
2222 if ( $wp_admin && \mainwp_current_user_can( 'dashboard', 'access_wpadmin_on_child_sites' ) ) {
2223 $admin_url = MainWP_Site_Open::get_open_site_url( $website->id, '', false );
2224 $html .= '<a href="' . esc_url( $admin_url ) . '" class="open_newwindow_wpadmin" target="_blank" data-tooltip="' . esc_attr__( 'Go to WP Admin', 'mainwp' ) . '" data-position="top left" data-inverted=""><i class="sign in icon"></i></a> ';
2225 } elseif ( $wp_admin ) {
2226 $html .= '<i class="sign in icon"></i> ';
2227 }
2228
2229 // Site name with dashboard link.
2230 $html .= '<a href="' . esc_url( admin_url( 'admin.php?page=managesites&dashboard=' . intval( $website->id ) ) ) . '">' . $site_name . '</a>';
2231
2232 // Site URL.
2233 $html .= '<div><span class="ui small text">';
2234 $html .= '<a href="' . $site_url . '" class="mainwp-may-hide-referrer open_site_url ui grey text" target="_blank">' . $nice_url . '</a>';
2235 $html .= '</span></div>';
2236
2237 $html .= '</div>';
2238
2239 if ( $print_content ) {
2240 echo $html; // phpcs:ignore -- ok.
2241 return '';
2242 }
2243
2244 return $html;
2245 }
2246
2247 /**
2248 * Generate a sortable BIGINT for WordPress versions.
2249 * Newer versions produce larger numbers.
2250 * Ordering matches WP core version_compare().
2251 *
2252 * @param string $version Version value.
2253 */
2254 public function wp_versions_order_num( $version ) {
2255
2256 $v = strtolower( trim( $version ) );
2257
2258 // 1) Normalize WordPress aliases.
2259 $v = str_replace(
2260 array( '-alpha', '-beta', '-rc' ),
2261 array( '-a', '-b', '-rc' ),
2262 $v
2263 );
2264
2265 // nightly / dev / trunk → dev.
2266 if ( preg_match( '/-(nightly|dev|trunk)/', $v ) ) {
2267 $v = preg_replace( '/-.+$/', '-dev', $v );
2268 }
2269
2270 // unknown tags → dev.
2271 if ( preg_match( '/-[a-z]+/', $v ) && ! preg_match( '/-(a|b|rc|dev)/', $v ) ) {
2272 $v = preg_replace( '/-.+$/', '-dev', $v );
2273 }
2274
2275 // 2) Extract numeric base version.
2276 $base = explode( '-', $v, 2 )[0];
2277 $parts = array_map( 'intval', explode( '.', $base ) );
2278
2279 if ( count( $parts ) < 4 ) {
2280 $parts = array_pad( $parts, 4, 0 );
2281 }
2282
2283 $versionNum =
2284 ( $parts[0] << 24 ) |
2285 ( $parts[1] << 16 ) |
2286 ( $parts[2] << 8 ) |
2287 $parts[3];
2288
2289 // 3) Release channel rank (lower = newer).
2290 $rank = 1; // stable.
2291 if ( strpos( $v, '-rc' ) !== false ) {
2292 $rank = 2;
2293 } elseif ( strpos( $v, '-b' ) !== false ) {
2294 $rank = 3;
2295 } elseif ( strpos( $v, '-a' ) !== false ) {
2296 $rank = 4;
2297 } elseif ( strpos( $v, '-dev' ) !== false ) {
2298 $rank = 5;
2299 }
2300
2301 // 4) Prerelease / build number.
2302 $build = 0;
2303 if ( preg_match( '/rc(\d+)/', $v, $m ) ) {
2304 $build = (int) $m[1];
2305 } elseif ( preg_match( '/b(\d+)/', $v, $m ) ) { // NOSONAR - same above ok.
2306 $build = (int) $m[1];
2307 } elseif ( preg_match( '/a(\d+)/', $v, $m ) ) { // NOSONAR - same above ok.
2308 $build = (int) $m[1];
2309 }
2310
2311 // 5) Compose sortable BIGINT.
2312 return ( $versionNum << 32 )
2313 | ( $rank << 29 )
2314 | min( $build, ( 1 << 29 ) - 1 );
2315 }
2316
2317 /**
2318 * Method handle gen_rand_id().
2319 *
2320 * @param string $str Input string.
2321 * @param bool $more_entropy More entropy False if a robust prefix is required, default false.
2322 * @return string Random ID 8 characters.
2323 */
2324 public static function gen_rand_id( $str = '', $more_entropy = false ) {
2325 return hash( 'crc32b', uniqid( $str, $more_entropy ? true : false ) );
2326 }
2327
2328 /**
2329 * Decode and validate JSON array.
2330 *
2331 * @param mixed $data Data to decode.
2332 */
2333 public static function get_decoded_array( $data ) {
2334 $decoded = ! empty( $data ) ? json_decode( $data, true ) : array();
2335 return is_array( $decoded ) ? $decoded : array();
2336 }
2337 }
2338