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