PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.0.12
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.0.12
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.0.12, at class/class-mainwp-utility.php

2,327 lines 72.5 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 if ( empty( $issue_counts ) ) {
1399 $issue_counts = array(
1400 'good' => 0,
1401 'recommended' => 0,
1402 'critical' => 0,
1403 );
1404 }
1405
1406 $totalTests = intval( $issue_counts['good'] ) + intval( $issue_counts['recommended'] ) + intval( $issue_counts['critical'] ) * 1.5;
1407 $failedTests = intval( $issue_counts['recommended'] ) * 0.5 + $issue_counts['critical'] * 1.5;
1408
1409 if ( empty( $totalTests ) ) {
1410 $val = 100;
1411 } else {
1412 $val = 100 - ceil( ( $failedTests / $totalTests ) * 100 );
1413 }
1414
1415 if ( 0 > $val ) {
1416 $val = 0;
1417 }
1418
1419 if ( 100 < $val ) {
1420 $val = 100;
1421 }
1422
1423 return array(
1424 'val' => $val,
1425 'critical' => $issue_counts['critical'],
1426 );
1427 }
1428
1429
1430 /**
1431 * Get HTTP code.
1432 *
1433 * @param int $code HTTP code.
1434 *
1435 * @return array $http_codes HTTP code.
1436 */
1437 public static function get_http_codes( $code = false ) {
1438
1439 $http_codes = array(
1440 100 => 'Continue',
1441 101 => 'Switching Protocols',
1442 200 => 'OK',
1443 201 => 'Created',
1444 202 => 'Accepted',
1445 203 => 'Non-Authoritative Information',
1446 204 => 'No Content',
1447 205 => 'Reset Content',
1448 206 => 'Partial Content',
1449 300 => 'Multiple Choices',
1450 301 => 'Moved Permanently',
1451 302 => 'Found',
1452 303 => 'See Other',
1453 304 => 'Not Modified',
1454 305 => 'Use Proxy',
1455 306 => '(Unused)',
1456 307 => 'Temporary Redirect',
1457 400 => 'Bad Request',
1458 401 => 'Unauthorized',
1459 402 => 'Payment Required',
1460 403 => 'Forbidden',
1461 404 => 'Not Found',
1462 405 => 'Method Not Allowed',
1463 406 => 'Not Acceptable',
1464 407 => 'Proxy Authentication Required',
1465 408 => 'Request Timeout',
1466 409 => 'Conflict',
1467 410 => 'Gone',
1468 411 => 'Length Required',
1469 412 => 'Precondition Failed',
1470 413 => 'Request Entity Too Large',
1471 414 => 'Request-URI Too Long',
1472 415 => 'Unsupported Media Type',
1473 416 => 'Requested Range Not Satisfiable',
1474 417 => 'Expectation Failed',
1475 500 => 'Internal Server Error',
1476 501 => 'Not Implemented',
1477 502 => 'Bad Gateway',
1478 503 => 'Service Unavailable',
1479 504 => 'Gateway Timeout',
1480 505 => 'HTTP Version Not Supported',
1481 );
1482
1483 if ( false === $code ) {
1484 return $http_codes;
1485 }
1486
1487 return isset( $http_codes[ $code ] ) ? $http_codes[ $code ] : '';
1488 }
1489
1490 /**
1491 * Method valid_input_emails().
1492 *
1493 * @param string $emails Input emails string.
1494 *
1495 * @return string $valid_emails Valid emails string.
1496 */
1497 public static function valid_input_emails( $emails ) {
1498
1499 if ( is_string( $emails ) ) {
1500 $emails = array_filter( explode( ',', $emails ) );
1501 }
1502
1503 $valid_emails = array();
1504 if ( is_array( $emails ) ) {
1505 foreach ( $emails as $email ) {
1506 $email = esc_html( trim( $email ) );
1507 if ( ! empty( $email ) && ! in_array( $email, $valid_emails, true ) ) {
1508 $valid_emails[] = $email;
1509 }
1510 }
1511 }
1512 $valid_emails = implode( ',', $valid_emails );
1513 return $valid_emails;
1514 }
1515
1516 /**
1517 * Method check_image_file_name()
1518 *
1519 * Check if the file image.
1520 *
1521 * @param string $filename Contains image (file) name.
1522 *
1523 * @return true|false valid name or not.
1524 */
1525 public static function check_image_file_name( $filename ) {
1526 if ( validate_file( $filename ) ) {
1527 return false;
1528 }
1529
1530 $allowed_files = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'webp', 'heic' );
1531 $file_ext = array_values( array_slice( explode( '.', $filename ), -1 ) )[0];
1532 $file_ext = strtolower( $file_ext );
1533 if ( ! in_array( $file_ext, $allowed_files ) ) {
1534 return false;
1535 }
1536
1537 return true;
1538 }
1539
1540 /**
1541 * Method check_abandoned()
1542 *
1543 * Get site's icon.
1544 *
1545 * @param mixed $siteId site's id.
1546 * @param string $which to check plugin/theme.
1547 *
1548 * @return array result error or success
1549 * @throws \MainWP_Exception Error message.
1550 */
1551 public static function check_abandoned( $siteId = null, $which = '' ) { // phpcs:ignore -- NOSONAR - complex.
1552 if ( static::ctype_digit( $siteId ) ) {
1553 $website = MainWP_DB::instance()->get_website_by_id( $siteId );
1554 if ( MainWP_System_Utility::can_edit_website( $website ) ) {
1555 $error = '';
1556 try {
1557 $information = MainWP_Connect::fetch_url_authed( $website, 'check_abandoned', array( 'which' => $which ) );
1558 } catch ( MainWP_Exception $e ) {
1559 $error = $e->getMessage();
1560 }
1561
1562 if ( '' !== $error ) {
1563 return array( 'error' => $error );
1564 } elseif ( isset( $information['success'] ) && ! empty( $information['success'] ) ) {
1565 return array( 'result' => 'success' );
1566 } else {
1567 return array( 'undefined_error' => true );
1568 }
1569 }
1570 }
1571 return array( 'result' => 'NOSITE' );
1572 }
1573
1574 /**
1575 * Get directory or slug of plugin.
1576 *
1577 * @param string $slug Plugin slug.
1578 *
1579 * @return string $value directory or slug of plugin.
1580 */
1581 public static function get_dir_slug( $slug ) {
1582 $value = '';
1583 if ( false === strpos( $slug, '/' ) ) {
1584 if ( false !== strpos( $slug, '.' ) ) {
1585 $value = substr( $slug, 0, strpos( $slug, '.' ) );
1586 }
1587 } else {
1588 $value = dirname( $slug );
1589 }
1590 if ( empty( $value ) ) {
1591 return $slug;
1592 }
1593 return $value;
1594 }
1595
1596 /**
1597 * Metho get_siteview_mode().
1598 *
1599 * Get site view mode.
1600 *
1601 * @return string $viewmode Site view mode.
1602 */
1603 public static function get_siteview_mode() {
1604 $viewmode = get_user_option( 'mainwp_sitesviewmode' );
1605 if ( 'grid' !== $viewmode && 'table' !== $viewmode ) {
1606 $viewmode = 'table';
1607 }
1608 return $viewmode;
1609 }
1610
1611
1612 /**
1613 * Metho delete_file().
1614 *
1615 * Delete file.
1616 *
1617 * @param string $file_path File path.
1618 *
1619 * @return bool true|false.
1620 */
1621 public static function delete_file( $file_path ) {
1622
1623 global $wp_filesystem;
1624
1625 if ( ! empty( $file_path ) ) {
1626 if ( $wp_filesystem ) {
1627 if ( $wp_filesystem->exists( $file_path ) ) {
1628 $wp_filesystem->delete( $file_path );
1629 }
1630 } elseif ( file_exists( $file_path ) ) {
1631 wp_delete_file( $file_path );
1632 }
1633 return true;
1634 }
1635
1636 return false;
1637 }
1638
1639 /**
1640 * Method get_disable_functions()
1641 *
1642 * Get disable functions.
1643 *
1644 * @return string
1645 */
1646 public function get_disable_functions() {
1647 if ( null === static::$disabled_functions ) {
1648 static::$disabled_functions = ini_get( 'disable_functions' );
1649 }
1650 return static::$disabled_functions;
1651 }
1652
1653 /**
1654 * Method is_disable_functions()
1655 *
1656 * Check if it is disabled functions.
1657 *
1658 * @param string $func Function name to check.
1659 *
1660 * @return string
1661 */
1662 public function is_disabled_functions( $func ) {
1663 $dis_funcs = $this->get_disable_functions();
1664
1665 if ( ! empty( $dis_funcs ) && ( false !== stristr( $dis_funcs, $func ) ) ) {
1666 return true;
1667 }
1668 return false;
1669 }
1670
1671 /**
1672 * Method hook_verify_ping_nonce()
1673 *
1674 * Verify nonce without session and user id.
1675 *
1676 * @param bool $input_value Boolean value, it should always be FALSE.
1677 * @param string $nonce Nonce to verify.
1678 * @param mixed $siteid Site ID.
1679 *
1680 * @return mixed If verified return 1 or 2, if not return false.
1681 */
1682 public static function hook_verify_ping_nonce( $input_value, $nonce = '', $siteid = false ) {
1683 unset( $input_value );
1684 $action = 'pingnonce';
1685 return static::verify_site_nonce( $nonce, $action, $siteid );
1686 }
1687
1688 /**
1689 * Method create_site_nonce()
1690 *
1691 * Create action nonce for site.
1692 *
1693 * @param mixed $action Action to perform.
1694 * @param mixed $siteid Site ID.
1695 *
1696 * @return string Custom nonce.
1697 */
1698 public static function create_site_nonce( $action = - 1, $siteid = false ) {
1699 if ( empty( $action ) || empty( $siteid || ! is_numeric( $siteid ) ) ) {
1700 return false;
1701 }
1702 return substr( wp_hash( 'site|' . $siteid . '|' . $action, 'nonce' ), - 12, 10 );
1703 }
1704
1705 /**
1706 * Method verify_site_nonce()
1707 *
1708 * Verify nonce without session and user id.
1709 *
1710 * @param string $nonce Nonce to verify.
1711 * @param mixed $action Action to perform.
1712 * @param mixed $siteid Site ID.
1713 *
1714 * @return mixed If verified return 1 or 2, if not return false.
1715 */
1716 public static function verify_site_nonce( $nonce, $action = - 1, $siteid = 0 ) {
1717 $nonce = (string) $nonce;
1718 if ( empty( $nonce ) || empty( $siteid || ! is_numeric( $siteid ) ) ) {
1719 return false;
1720 }
1721
1722 $expected = substr( wp_hash( 'site|' . $siteid . '|' . $action, 'nonce' ), - 12, 10 );
1723 if ( hash_equals( $expected, $nonce ) ) {
1724 return 1;
1725 }
1726 return false;
1727 }
1728
1729
1730 /**
1731 * Find for multi keywords.
1732 *
1733 * @param string $name_str string find on.
1734 * @param array $words Array string input.
1735 * @return bool True|False.
1736 */
1737 public static function multi_find_keywords( $name_str, $words = array() ) {
1738 if ( ! is_array( $words ) ) {
1739 return false;
1740 }
1741 foreach ( $words as $word ) {
1742 if ( stristr( $name_str, $word ) ) {
1743 return true;
1744
1745 }
1746 }
1747 return false;
1748 }
1749
1750 /**
1751 * Merge values from right array to left array.
1752 *
1753 * @param array $left_array left array.
1754 * @param array $right_array right array.
1755 *
1756 * @return array $result result array.
1757 */
1758 public static function right_array_merge( $left_array, $right_array ) {
1759 if ( ! is_array( $left_array ) || ! is_array( $right_array ) ) {
1760 return array();
1761 }
1762 $result = array_intersect_key( $right_array, $left_array );
1763 return array_merge( $left_array, $result );
1764 }
1765
1766
1767 /**
1768 * Method get_set_deactivated_licenses_alerted().
1769 *
1770 * @param string $slug Extension slug.
1771 * @param bool $time_value Time value.
1772 * @param string $act get/set value.
1773 *
1774 * @return array $result result array.
1775 */
1776 public function get_set_deactivated_licenses_alerted( $slug, $time_value = false, $act = 'get' ) {
1777 if ( null === $this->last_deactivated_alerts ) {
1778 $this->last_deactivated_alerts = get_option( 'mainwp_cron_licenses_deactivated_alerted', array() );
1779 if ( ! is_array( $this->last_deactivated_alerts ) ) {
1780 $this->last_deactivated_alerts = array();
1781 }
1782 }
1783 if ( 'get' === $act ) {
1784 return isset( $this->last_deactivated_alerts[ $slug ] ) ? $this->last_deactivated_alerts[ $slug ] : 0;
1785 } elseif ( 'set' === $act ) {
1786 $this->last_deactivated_alerts[ $slug ] = intval( $time_value );
1787 get_option( 'mainwp_cron_licenses_deactivated_alerted', $this->last_deactivated_alerts );
1788 }
1789 }
1790
1791 /**
1792 * Method get_remote_favicon().
1793 *
1794 * @param string $url Url.
1795 * @param string $favi favicon file name.
1796 * @param int $item_id item id.
1797 * @param string $file_prefix favicon file prefix name.
1798 *
1799 * @return mixed result.
1800 */
1801 public static function get_remote_favicon( $url, $favi = '', $item_id = false, $file_prefix = '' ) { // phpcs:ignore -- NOSONAR - complex.
1802
1803 if ( empty( $favi ) ) {
1804 $favi = 'favicon.ico';
1805 }
1806
1807 if ( '/' !== substr( $url, - 1 ) ) {
1808 $url .= '/';
1809 }
1810
1811 $favi_url = $url . $favi;
1812
1813 $content = MainWP_Connect::get_file_content( $favi_url );
1814
1815 if ( empty( $content ) && 'favicon.ico' === $favi ) {
1816 $favi_url = $url . 'favicon.png';
1817 $content = MainWP_Connect::get_file_content( $favi_url ); // try other file.
1818 }
1819
1820 if ( ! empty( $content ) ) {
1821
1822 MainWP_System_Utility::get_wp_file_system();
1823
1824 global $wp_filesystem;
1825
1826 $dirs = MainWP_System_Utility::get_mainwp_dir( 'icons', true );
1827 $iconsDir = $dirs[0];
1828 if ( $favi ) {
1829
1830 $tmp = explode( '.', $favi );
1831 if ( 2 !== count( $tmp ) ) {
1832 return false;
1833 }
1834
1835 $favi_ext = $tmp[1];
1836
1837 if ( empty( $item_id ) ) {
1838 $item_id = time() . '-' . wp_rand( 100, 999 );
1839 }
1840 if ( ! empty( $file_prefix ) ) {
1841 $filename = $file_prefix . $item_id . '.' . $favi_ext;
1842 } else {
1843 $filename = 'favi-' . $item_id . '.' . $favi_ext;
1844 }
1845
1846 $size = $wp_filesystem->put_contents( $iconsDir . $filename, $content ); // phpcs:ignore --
1847 if ( $size ) {
1848 MainWP_Logger::instance()->debug( 'Icon Cost Product size :: ' . $size );
1849 return array(
1850 'result' => 'success',
1851 'file' => $filename,
1852 'dir' => $iconsDir,
1853 );
1854 } else {
1855 return array( 'error' => 'Save icon file failed.' );
1856 }
1857 }
1858 return false;
1859 } else {
1860 return array( 'error' => esc_html__( 'Download icon file failed', 'mainwp' ) );
1861 }
1862 }
1863
1864 /**
1865 * Method get_saved_favicon_url()
1866 *
1867 * @param string $favi Favicon file name.
1868 *
1869 * @return mixed $faviurl Favicon URL.
1870 */
1871 public static function get_saved_favicon_url( $favi ) {
1872 $faviurl = '';
1873 if ( ! empty( $favi ) ) {
1874 $dirs = MainWP_System_Utility::get_icons_dir();
1875 if ( file_exists( $dirs[0] . $favi ) ) {
1876 $faviurl = $dirs[1] . $favi;
1877 } else {
1878 $faviurl = '';
1879 }
1880 }
1881 return $faviurl;
1882 }
1883
1884 /**
1885 * Method delete_saved_favicon()
1886 *
1887 * @param string $favi Favicon file name.
1888 *
1889 * @return bool Success result.
1890 */
1891 public static function delete_saved_favicon( $favi ) {
1892 if ( ! empty( $favi ) ) {
1893 $hasWPFileSystem = MainWP_System_Utility::get_wp_file_system();
1894 global $wp_filesystem;
1895 $dirs = MainWP_System_Utility::get_icons_dir();
1896 if ( $hasWPFileSystem && $wp_filesystem->exists( $dirs[0] . $favi ) ) {
1897 $wp_filesystem->delete( $dirs[0] . $favi );
1898 return true;
1899 }
1900 }
1901 return false;
1902 }
1903
1904 /**
1905 * Delete icon file.
1906 *
1907 * @param string $sub_dir Sub dir file icon.
1908 * @param string $cost_icon file icon.
1909 */
1910 public function delete_uploaded_icon_file( $sub_dir, $cost_icon ) {
1911 $valid_file = 0 === validate_file( $cost_icon ) ? true : false;
1912 if ( $valid_file ) {
1913 $dirs = MainWP_System_Utility::get_mainwp_dir( $sub_dir, true );
1914 $f = $dirs[0] . $cost_icon;
1915 if ( file_exists( $f ) ) {
1916 wp_delete_file( $f );
1917 }
1918 }
1919 }
1920
1921 /**
1922 * Method get_table_orders().
1923 *
1924 * @param array $data table data.
1925 */
1926 public function get_table_orders( $data ) {
1927
1928 $values = array(
1929 'orderby' => null,
1930 'order' => null,
1931 );
1932
1933 if ( isset( $data['order'] ) ) {
1934 $columns = isset( $data['columns'] ) ? wp_unslash( $data['columns'] ) : array();
1935 $ord_col = isset( $data['order'][0]['column'] ) ? sanitize_text_field( wp_unslash( $data['order'][0]['column'] ) ) : '';
1936 if ( isset( $columns[ $ord_col ] ) ) {
1937 $values = array(
1938 'orderby' => isset( $columns[ $ord_col ]['data'] ) ? sanitize_text_field( wp_unslash( $columns[ $ord_col ]['data'] ) ) : '',
1939 'order' => isset( $data['order'][0]['dir'] ) ? sanitize_text_field( wp_unslash( $data['order'][0]['dir'] ) ) : '',
1940 );
1941 }
1942 }
1943
1944 return $values;
1945 }
1946
1947 /**
1948 * Method valid_file_check().
1949 *
1950 * @param string $path file path.
1951 * @param bool $readable readable.
1952 *
1953 * @return bool is valid.
1954 */
1955 public static function valid_file_check( $path, $readable = true ) {
1956 $valid = is_string( $path ) && ! stristr( $path, '..' );
1957 if ( $valid && $readable ) {
1958 $valid = is_readable( $path );
1959 }
1960 return $valid;
1961 }
1962
1963
1964 /**
1965 * Handle sanitize POST data.
1966 *
1967 * @param array $data input data.
1968 *
1969 * @return array
1970 */
1971 public function sanitize_data( $data ) {
1972 if ( ! is_array( $data ) ) {
1973 return array();
1974 }
1975
1976 // Sanitize all record values.
1977 return array_map(
1978 function ( $value ) {
1979 if ( ! is_array( $value ) ) {
1980 return wp_strip_all_tags( $value );
1981 }
1982
1983 return $value;
1984 },
1985 $data
1986 );
1987 }
1988
1989 /**
1990 * String ends by.
1991 *
1992 * @param mixed $str str.
1993 * @param mixed $ends ends.
1994 * @return bool value value.
1995 */
1996 public static function string_ends_by( $str, $ends ) {
1997 if ( function_exists( '\str_ends_with' ) ) {
1998 return \str_ends_with( $str, $ends );
1999 } else {
2000 $ends_len = strlen( $ends );
2001 if ( $ends_len > strlen( $str ) ) {
2002 return false;
2003 }
2004 return substr( $str, -$ends_len ) === $ends;
2005 }
2006 }
2007 /**
2008 * Returns number in shorter format.
2009 *
2010 * @param int $number str.
2011 * @return string $number Shorer number.
2012 */
2013 public static function short_number_format( $number ) {
2014 if ( $number > 999 && $number < 1000000 ) {
2015 // Anything between 1000 and 1000000.
2016 $number = number_format( $number / 1000, 1 ) . 'K';
2017 } elseif ( $number >= 1000000000 ) {
2018 // 1000000 or higher.
2019 $number = number_format( $number / 1000000, 2 ) . 'M';
2020 }
2021 return $number;
2022 }
2023
2024 /**
2025 * Returns date in time ago format
2026 *
2027 * @param mixed $ptime Date stamp.
2028 * @return string $string Time elapsed string.
2029 */
2030 public static function time_elapsed_string( $ptime ) {
2031 $etime = time() - $ptime;
2032
2033 if ( $etime < 1 ) {
2034 return '0 seconds';
2035 }
2036
2037 $a = array(
2038 365 * 24 * 60 * 60 => 'year',
2039 30 * 24 * 60 * 60 => 'month',
2040 24 * 60 * 60 => 'day',
2041 60 * 60 => 'hour',
2042 60 => 'minute',
2043 1 => 'second',
2044 );
2045 $a_plural = array(
2046 'year' => 'years',
2047 'month' => 'months',
2048 'day' => 'days',
2049 'hour' => 'hours',
2050 'minute' => 'minutes',
2051 'second' => 'seconds',
2052 );
2053
2054 foreach ( $a as $secs => $str ) {
2055 $d = $etime / $secs;
2056 if ( $d >= 1 ) {
2057 $r = round( $d );
2058 return $r . ' ' . ( $r > 1 ? $a_plural[ $str ] : $str ) . ' ago';
2059 }
2060 }
2061 }
2062
2063 /**
2064 * Returns language as flag.
2065 *
2066 * @param string $language Language code.
2067 * @return void
2068 */
2069 public static function get_language_code_as_flag( $language ) {
2070 // For flag extraction, remove trailing _formal or _informal if present.
2071 $flag_language = preg_replace( '/_(formal|informal)$/', '', $language );
2072 // Get the last 2 characters of the flag language code.
2073 $last_two_chars = ! empty( $flag_language ) ? substr( $flag_language, -2 ) : '';
2074 // Convert to lowercase.
2075 $lowercase_last_two_chars = strtolower( $last_two_chars );
2076 $lowercase_flag_language = strtolower( $flag_language );
2077
2078 // Get display name using the original language string.
2079 $display_language = function_exists( 'locale_get_display_name' ) ? locale_get_display_name( $language ) : $language;
2080
2081 // Adjust special country codes.
2082 if ( 'et' === $lowercase_last_two_chars ) {
2083 $lowercase_last_two_chars = 'ee';
2084 }
2085 if ( 'sq' === $lowercase_last_two_chars ) {
2086 $lowercase_last_two_chars = 'al';
2087 }
2088 if ( 'ab' === $lowercase_last_two_chars ) {
2089 $lowercase_last_two_chars = 'dz';
2090 }
2091
2092 $stacked_flags = array(
2093 'ca' => array(
2094 'primary' => 'es',
2095 'secondary' => 'ad',
2096 ),
2097 );
2098 // Only stack flags for explicit Catalan locales to avoid affecting Canada.
2099 $stacked_flag_locales = array(
2100 'ca' => array(
2101 'ca',
2102 ),
2103 );
2104
2105 if ( isset( $stacked_flags[ $lowercase_last_two_chars ] ) ) {
2106 $should_stack = true;
2107 if ( isset( $stacked_flag_locales[ $lowercase_last_two_chars ] ) ) {
2108 $should_stack = in_array( $lowercase_flag_language, $stacked_flag_locales[ $lowercase_last_two_chars ], true );
2109 }
2110
2111 if ( $should_stack ) {
2112 $primary_flag = $stacked_flags[ $lowercase_last_two_chars ]['primary'];
2113 $secondary_flag = $stacked_flags[ $lowercase_last_two_chars ]['secondary'];
2114
2115 echo '<span data-tooltip="' . esc_html__( 'Site Language: ', 'mainwp' ) . esc_attr( $display_language ) . '" data-position="left center" data-inverted="">';
2116 echo '<span class="mainwp-flag-stack">';
2117 echo '<i class="small ' . esc_attr( $primary_flag ) . ' flag mainwp-flag-stack__flag mainwp-flag-stack__flag--primary"></i>';
2118 echo '<i class="small ' . esc_attr( $secondary_flag ) . ' flag mainwp-flag-stack__flag mainwp-flag-stack__flag--secondary"></i>';
2119 echo '</span>';
2120 echo '</span>';
2121 return;
2122 }
2123 }
2124
2125 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>';
2126 }
2127
2128 /**
2129 * Returns icon for the site indexability status.
2130 *
2131 * @param int $status Status, 1 or 0.
2132 * @return void.
2133 */
2134 public static function get_site_index_option_icon( $status ) {
2135 $icon = '';
2136 $tooltip = '';
2137 if ( isset( $status ) && '' !== $status ) {
2138 if ( 1 === intval( $status ) ) {
2139 $icon = 'green dot circle outline';
2140 $tooltip = 'Search engines can index this site.';
2141 } elseif ( 0 === intval( $status ) ) {
2142 $icon = 'red ban';
2143 $tooltip = 'This site is blocking search engines.';
2144 }
2145 } else {
2146 $icon = 'grey circle';
2147 $tooltip = 'Indexing status unknown. Resync the site or check manually in WordPress Settings > Reading.';
2148 }
2149 echo '<span data-tooltip="' . $tooltip . '" data-position="left center" data-inverted=""><i class="' . $icon . ' icon"></i></span>'; //phpcs:ignore -- ok.
2150 }
2151
2152 /**
2153 * Returns the appropriate Fomantic UI color class based on number of updates
2154 *
2155 * @param int $update_count Number of available updates.
2156 *
2157 * @return string CSS class for the element
2158 */
2159 public static function mainwp_get_update_count_class( $update_count ) {
2160 // Convert to integer using intval().
2161 $update_count = intval( $update_count );
2162
2163 // Ensure count is not negative.
2164 if ( 0 > $update_count ) {
2165 $update_count = 0;
2166 }
2167
2168 if ( 0 === $update_count ) {
2169 return 'grey';
2170 } elseif ( $update_count >= 1 && $update_count <= 3 ) {
2171 return 'yellow';
2172 } elseif ( $update_count >= 4 && $update_count <= 5 ) {
2173 return 'orange';
2174 } else {
2175 return 'red';
2176 }
2177 }
2178
2179 /**
2180 * Display site name and URL with optional WP Admin link.
2181 *
2182 * @param object|int $site Site object or Site ID.
2183 * @param bool $wp_admin Whether to show WP Admin link.
2184 * @param bool $print_content Whether to print or return the content.
2185 * @return string HTML markup for site display.
2186 */
2187 public static function mainwp_display_site( $site = '', $wp_admin = true, $print_content = false ) {
2188 if ( empty( $site ) ) {
2189 return '';
2190 }
2191
2192 if ( static::ctype_digit( $site ) ) {
2193 $website = MainWP_DB::instance()->get_website_by_id( $site );
2194 } elseif ( is_object( $site ) && isset( $site->id ) ) {
2195 $website = $site;
2196 } else {
2197 return '';
2198 }
2199
2200 if ( ! $website ) {
2201 return '';
2202 }
2203
2204 $site_name = esc_html( stripslashes( $website->name ) );
2205 $site_url = esc_url( $website->url );
2206 $nice_url = esc_html( static::get_nice_url( $website->url ) );
2207
2208 $html = '<div class="mainwp-site-display">';
2209
2210 // WP Admin link (if enabled and user has permission).
2211 if ( $wp_admin && \mainwp_current_user_can( 'dashboard', 'access_wpadmin_on_child_sites' ) ) {
2212 $admin_url = MainWP_Site_Open::get_open_site_url( $website->id, '', false );
2213 $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> ';
2214 } elseif ( $wp_admin ) {
2215 $html .= '<i class="sign in icon"></i> ';
2216 }
2217
2218 // Site name with dashboard link.
2219 $html .= '<a href="' . esc_url( admin_url( 'admin.php?page=managesites&dashboard=' . intval( $website->id ) ) ) . '">' . $site_name . '</a>';
2220
2221 // Site URL.
2222 $html .= '<div><span class="ui small text">';
2223 $html .= '<a href="' . $site_url . '" class="mainwp-may-hide-referrer open_site_url ui grey text" target="_blank">' . $nice_url . '</a>';
2224 $html .= '</span></div>';
2225
2226 $html .= '</div>';
2227
2228 if ( $print_content ) {
2229 echo $html; // phpcs:ignore -- ok.
2230 return '';
2231 }
2232
2233 return $html;
2234 }
2235
2236 /**
2237 * Generate a sortable BIGINT for WordPress versions.
2238 * Newer versions produce larger numbers.
2239 * Ordering matches WP core version_compare().
2240 *
2241 * @param string $version Version value.
2242 */
2243 public function wp_versions_order_num( $version ) {
2244
2245 $v = strtolower( trim( $version ) );
2246
2247 // 1) Normalize WordPress aliases.
2248 $v = str_replace(
2249 array( '-alpha', '-beta', '-rc' ),
2250 array( '-a', '-b', '-rc' ),
2251 $v
2252 );
2253
2254 // nightly / dev / trunk → dev.
2255 if ( preg_match( '/-(nightly|dev|trunk)/', $v ) ) {
2256 $v = preg_replace( '/-.+$/', '-dev', $v );
2257 }
2258
2259 // unknown tags → dev.
2260 if ( preg_match( '/-[a-z]+/', $v ) && ! preg_match( '/-(a|b|rc|dev)/', $v ) ) {
2261 $v = preg_replace( '/-.+$/', '-dev', $v );
2262 }
2263
2264 // 2) Extract numeric base version.
2265 $base = explode( '-', $v, 2 )[0];
2266 $parts = array_map( 'intval', explode( '.', $base ) );
2267
2268 if ( count( $parts ) < 4 ) {
2269 $parts = array_pad( $parts, 4, 0 );
2270 }
2271
2272 $versionNum =
2273 ( $parts[0] << 24 ) |
2274 ( $parts[1] << 16 ) |
2275 ( $parts[2] << 8 ) |
2276 $parts[3];
2277
2278 // 3) Release channel rank (lower = newer).
2279 $rank = 1; // stable.
2280 if ( strpos( $v, '-rc' ) !== false ) {
2281 $rank = 2;
2282 } elseif ( strpos( $v, '-b' ) !== false ) {
2283 $rank = 3;
2284 } elseif ( strpos( $v, '-a' ) !== false ) {
2285 $rank = 4;
2286 } elseif ( strpos( $v, '-dev' ) !== false ) {
2287 $rank = 5;
2288 }
2289
2290 // 4) Prerelease / build number.
2291 $build = 0;
2292 if ( preg_match( '/rc(\d+)/', $v, $m ) ) {
2293 $build = (int) $m[1];
2294 } elseif ( preg_match( '/b(\d+)/', $v, $m ) ) { // NOSONAR - same above ok.
2295 $build = (int) $m[1];
2296 } elseif ( preg_match( '/a(\d+)/', $v, $m ) ) { // NOSONAR - same above ok.
2297 $build = (int) $m[1];
2298 }
2299
2300 // 5) Compose sortable BIGINT.
2301 return ( $versionNum << 32 )
2302 | ( $rank << 29 )
2303 | min( $build, ( 1 << 29 ) - 1 );
2304 }
2305
2306 /**
2307 * Method handle gen_rand_id().
2308 *
2309 * @param string $str Input string.
2310 * @param bool $more_entropy More entropy False if a robust prefix is required, default false.
2311 * @return string Random ID 8 characters.
2312 */
2313 public static function gen_rand_id( $str = '', $more_entropy = false ) {
2314 return hash( 'crc32b', uniqid( $str, $more_entropy ? true : false ) );
2315 }
2316
2317 /**
2318 * Decode and validate JSON array.
2319 *
2320 * @param mixed $data Data to decode.
2321 */
2322 public static function get_decoded_array( $data ) {
2323 $decoded = ! empty( $data ) ? json_decode( $data, true ) : array();
2324 return is_array( $decoded ) ? $decoded : array();
2325 }
2326 }
2327