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
mainwp / class / class-mainwp-system-utility.php

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

2,187 lines 72.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP System Utility Helper
4 *
5 * @package MainWP/Dashboard
6 */
7
8 namespace MainWP\Dashboard;
9
10 // Exit if accessed directly.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 // phpcs:disable WordPress.DB.RestrictedFunctions, WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors, Generic.Metrics.CyclomaticComplexity -- Using cURL functions.
16
17 /**
18 * Class MainWP_System_Utility
19 *
20 * @package MainWP\Dashboard
21 */
22 class MainWP_System_Utility { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
23
24 /**
25 * Private static variable to hold the single instance of the class.
26 *
27 * @static
28 *
29 * @var mixed Default null
30 */
31 private static $instance = null;
32
33 /**
34 * Request-local contrast results for tag backgrounds.
35 *
36 * Keyed by normalized #rrggbb. Invalid colors are not stored.
37 *
38 * @static
39 *
40 * @var array
41 */
42 private static $tag_contrast_cache = array();
43
44 /**
45 * Method instance()
46 *
47 * Create a public static instance.
48 *
49 * @static
50 * @return MainWP_Post_Handler
51 */
52 public static function instance() {
53 if ( null === static::$instance ) {
54 static::$instance = new self();
55 }
56 return static::$instance;
57 }
58
59 /**
60 * Constructor.
61 *
62 * Run each time the class is called.
63 *
64 * @return void
65 */
66 public function __construct() {
67 add_action( 'admin_init', array( &$this, 'admin_init' ) );
68 }
69
70 /**
71 * Method admin_init().
72 *
73 * @return void
74 */
75 public function admin_init() {
76 $current_month = gmdate( 'Y-m' );
77 if ( get_option( 'mainwp_last_short_term_notice_purge' ) !== $current_month ) { // Run once per month.
78 MainWP_Utility::purge_short_term_notices();
79 update_option(
80 'mainwp_last_short_term_notice_purge',
81 $current_month,
82 false
83 );
84 }
85 }
86
87 /**
88 * Method get_class_name()
89 *
90 * Get Class Name.
91 *
92 * @return object
93 */
94 public static function get_class_name() {
95 return __CLASS__;
96 }
97
98 /**
99 * Method is_admin()
100 *
101 * Check if current user is an administrator.
102 *
103 * @return boolean True|False.
104 */
105 public static function is_admin() {
106
107 /**
108 * Current user global.
109 *
110 * @global string
111 */
112 global $current_user;
113 if ( empty( $current_user->ID ) ) {
114 return false;
115 }
116
117 if ( ( property_exists( $current_user, 'wp_user_level' ) && 10 === (int) $current_user->wp_user_level ) || ( isset( $current_user->user_level ) && 10 === (int) $current_user->user_level ) || static::current_user_has_role( 'administrator' ) ) {
118 return true;
119 }
120
121 return false;
122 }
123
124 /**
125 * Method current_user_has_role()
126 *
127 * Check if the user has role.
128 *
129 * @param array|string $roles role or array of roles to check.
130 * @param object|null $user user check.
131 *
132 * @return bool true|false If the user is administrator (Level 10), return true, if not, return false.
133 */
134 public static function current_user_has_role( $roles, $user = null ) {
135
136 if ( null === $user ) {
137 $user = wp_get_current_user();
138 }
139
140 if ( empty( $user ) || empty( $user->ID ) ) {
141 return false;
142 }
143
144 if ( is_string( $roles ) ) {
145 $allowed_roles = array( $roles );
146 } elseif ( is_array( $roles ) ) {
147 $allowed_roles = $roles;
148 } else {
149 return false;
150 }
151
152 if ( array_intersect( $allowed_roles, $user->roles ) ) {
153 return true;
154 }
155
156 return false;
157 }
158
159 /**
160 * Method get_primary_backup()
161 *
162 * Check if using Legacy Backup Solution.
163 *
164 * @return mixed False|$enable_legacy_backup.
165 */
166 public static function get_primary_backup() {
167 $enable_legacy_backup = get_option( 'mainwp_enableLegacyBackupFeature' );
168 $global_method = false;
169 if ( ! $enable_legacy_backup ) {
170 $global_method = get_option( 'mainwp_primaryBackup', false );
171 }
172 return $global_method;
173 }
174
175 /**
176 * Method get_notification_email()
177 *
178 * Check if user wants to recieve MainWP Notification Emails.
179 *
180 * @return mixed null|User Email Address.
181 *
182 * @uses \MainWP\Dashboard\MainWP_DB_Common::get_user_extension()
183 */
184 public static function get_notification_email() {
185 return get_option( 'admin_email' );
186 }
187
188 /**
189 * Method get_base_dir()
190 *
191 * Get the base upload directory.
192 *
193 * @return string basedir/
194 */
195 public static function get_base_dir() {
196 $upload_dir = wp_upload_dir();
197
198 return $upload_dir['basedir'] . DIRECTORY_SEPARATOR;
199 }
200
201 /**
202 * Method get_icons_dir()
203 *
204 * Get MainWP icons directory,
205 * if it doesn't exist create it.
206 *
207 * @return array $dir, $url
208 */
209 public static function get_icons_dir() {
210 static::get_wp_file_system();
211
212 /**
213 * WordPress files system object.
214 *
215 * @global object
216 */
217 global $wp_filesystem;
218
219 $dirs = static::get_mainwp_dir();
220 $dir = $dirs[0] . 'icons' . DIRECTORY_SEPARATOR;
221 $url = $dirs[1] . 'icons/';
222 if ( ! $wp_filesystem->exists( $dir ) ) {
223 $wp_filesystem->mkdir( $dir, 0755 ); // MWP-1558: tightened from 0777 to WP convention; public-asset dir, blocks cross-tenant manipulation on shared hosting.
224 }
225 if ( ! $wp_filesystem->exists( $dir . 'index.php' ) ) {
226 $wp_filesystem->touch( $dir . 'index.php' );
227 }
228 return array( $dir, $url );
229 }
230
231 /**
232 * Method touch().
233 *
234 * If the file does not exist, it will be created.
235 *
236 * @param string $filename File name.
237 */
238 public static function touch( $filename ) {
239 $hasWPFileSystem = static::get_wp_file_system();
240 /**
241 * WordPress files system object.
242 *
243 * @global object
244 */
245 global $wp_filesystem;
246 if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
247 if ( ! $wp_filesystem->exists( $filename ) ) {
248 $wp_filesystem->touch( $filename );
249 }
250 } elseif ( ! file_exists( $filename ) ) { //phpcs:ignore -- ok.
251 touch( $filename ); //phpcs:ignore -- ok.
252 }
253 }
254
255 /**
256 * Method is_writable().
257 *
258 * @param string $file The file.
259 */
260 public static function is_writable( $file ) {
261 $hasWPFileSystem = static::get_wp_file_system();
262 /**
263 * WordPress files system object.
264 *
265 * @global object
266 */
267 global $wp_filesystem;
268
269 $is_writable = true;
270 if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
271 if ( ! $wp_filesystem->is_writable( $file ) ) {
272 $is_writable = false;
273 }
274 } elseif ( ! is_writable( $file ) ) { //phpcs:ignore -- ok.
275 $is_writable = false;
276 }
277 return $is_writable;
278 }
279
280 /**
281 * Method get_mainwp_dir()
282 *
283 * Get the MainWP directory,
284 * if it doesn't exist create it.
285 *
286 * @param string|null $subdir mainwp sub diectories.
287 * @param bool $direct_access Return true if Direct access file system. Default: false.
288 *
289 * @return array $dir, $url
290 */
291 public static function get_mainwp_dir( $subdir = null, $direct_access = false ) {
292 static::get_wp_file_system();
293
294 /**
295 * WordPress files system object.
296 *
297 * @global object
298 */
299 global $wp_filesystem;
300
301 $upload_dir = wp_upload_dir();
302
303 /**
304 * Allow filtering the upload directory array used by MainWP.
305 *
306 * @since 5.4.1.
307 *
308 * @param array $upload_dir Array of upload directory info (from wp_upload_dir()).
309 * @param string $subdir Optional. Sub Directory requested.
310 * @param bool $direct_access Optional. Direct access.
311 */
312 $upload_dir = apply_filters( 'mainwp_get_wp_upload_dir', $upload_dir, $subdir, $direct_access );
313
314 $dir = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'mainwp' . DIRECTORY_SEPARATOR;
315 $url = $upload_dir['baseurl'] . '/mainwp/';
316 if ( ! $wp_filesystem->exists( $dir ) ) {
317 $wp_filesystem->mkdir( $dir, 0755 ); // MWP-1558: tightened from 0777 to WP convention; closes cross-tenant pk/ substitution attack on shared hosting.
318 }
319 if ( ! $wp_filesystem->exists( $dir . 'index.php' ) ) {
320 $wp_filesystem->touch( $dir . 'index.php' );
321 }
322
323 if ( ! empty( $subdir ) && ! stristr( $subdir, '..' ) ) {
324 $newdir = $dir . $subdir . DIRECTORY_SEPARATOR;
325 $url = $url . $subdir . '/';
326
327 if ( ! $wp_filesystem->exists( $newdir ) ) {
328 // MWP-1558: 0750 for private (htaccess-protected) subdirs, 0755 for public-asset subdirs (icons, client-images, etc.).
329 $wp_filesystem->mkdir( $newdir, $direct_access ? 0755 : 0750 );
330 }
331
332 if ( $direct_access ) {
333 if ( ! $wp_filesystem->exists( trailingslashit( $newdir ) . 'index.php' ) ) {
334 $wp_filesystem->touch( trailingslashit( $newdir ) . 'index.php' );
335 }
336 if ( $wp_filesystem->exists( trailingslashit( $newdir ) . '.htaccess' ) ) {
337 $wp_filesystem->delete( trailingslashit( $newdir ) . '.htaccess' );
338 }
339 } elseif ( ! $wp_filesystem->exists( trailingslashit( $newdir ) . '.htaccess' ) ) {
340 $wp_filesystem->put_contents( trailingslashit( $newdir ) . '.htaccess', 'deny from all' );
341 }
342 return array( $newdir, $url );
343 }
344
345 return array( $dir, $url );
346 }
347
348 /**
349 * Method get_mainwp_sub_dir()
350 *
351 * Get the MainWP directory,
352 * if it doesn't exist create it.
353 *
354 * @param string|null $subdir mainwp sub diectories.
355 * @param bool $direct_access Return true if Direct access file system. Default: false.
356 *
357 * @return string $dir mainwp sub-directory.
358 */
359 public static function get_mainwp_sub_dir( $subdir = null, $direct_access = false ) {
360 $dirs = static::get_mainwp_dir( $subdir, $direct_access );
361 return $dirs[0];
362 }
363
364 /**
365 * Method get_download_dir()
366 *
367 * @param mixed $what What url.
368 * @param mixed $filename File Name.
369 *
370 * @return string Download URL.
371 */
372 public static function get_download_url( $what, $filename ) {
373 $specificDir = static::get_mainwp_specific_dir( $what );
374 $mwpDir = static::get_mainwp_dir();
375 $mwpDir = $mwpDir[0];
376 $fullFile = $specificDir . $filename;
377
378 return admin_url( '?sig=' . static::get_download_sig( $fullFile ) . '&mwpdl=' . rawurlencode( str_replace( $mwpDir, '', $fullFile ) ) );
379 }
380
381
382 /**
383 * Method get_download_sig()
384 *
385 * @param string $fullFile File Name.
386 *
387 * @return string Sig Download URL.
388 */
389 public static function get_download_sig( $fullFile ) {
390 $key_value = uniqid( 'sig_', true ) . filesize( $fullFile ) . time();
391 $secret_value = uniqid( 'sig_secret_', true ) . filesize( $fullFile ) . time();
392
393 $hashkey = wp_hash( $key_value );
394
395 $sig_values = array(
396 'sig' => md5( filesize( $fullFile ) ), // NOSONAR - safe for sig file size.
397 'key_value' => $key_value,
398 'hash_key' => $secret_value,
399 );
400
401 set_site_transient( 'mainwp_fdl_' . $hashkey, $secret_value, 3 * HOUR_IN_SECONDS );
402
403 $sig_values = wp_json_encode( $sig_values );
404 $sig_values = rawurlencode( $sig_values );
405 return $sig_values;
406 }
407
408
409 /**
410 * Method valid_download_sig()
411 *
412 * @param string $file File Name.
413 * @param string $sig download.
414 *
415 * @return bool true|false.
416 */
417 public static function valid_download_sig( $file, $sig ) {
418
419 $sig = rawurldecode( $sig );
420 $value = json_decode( $sig, true );
421
422 if ( ! is_array( $value ) || empty( $value['key_value'] ) || empty( $value['sig'] ) || md5( filesize( $file ) ) !== $value['sig'] ) { // NOSONAR - it's safe for size matching, file in uploads folder.
423 return false;
424 }
425
426 $hash_key = wp_hash( $value['key_value'] );
427 $secure_key = get_site_transient( 'mainwp_fdl_' . $hash_key );
428
429 if ( empty( $secure_key ) || empty( $value['hash_key'] ) || ! hash_equals( $secure_key, $value['hash_key'] ) ) {
430 return false;
431 }
432
433 return true;
434 }
435
436 /**
437 * Method get_mainwp_specific_dir()
438 *
439 * Get MainWP Specific directory,
440 * if it doesn't exist create it.
441 *
442 * Update .htaccess.
443 *
444 * @param null $dir Current MainWP directory.
445 *
446 * @return string $newdir
447 *
448 * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
449 */
450 public static function get_mainwp_specific_dir( $dir = null ) { // phpcs:ignore -- NOSONAR - complex.
451 if ( MainWP_System::instance()->is_single_user() ) {
452 $userid = 0;
453 } else {
454
455 /**
456 * Current user global.
457 *
458 * @global string
459 */
460 global $current_user;
461
462 $userid = $current_user->ID;
463 }
464
465 $hasWPFileSystem = static::get_wp_file_system();
466
467 global $wp_filesystem;
468
469 $dirs = static::get_mainwp_dir();
470
471 $userdir = $dirs[0] . $userid;
472 $newdir = $userdir;
473
474 if ( '/' === $dir || null === $dir ) {
475 $newdir .= DIRECTORY_SEPARATOR;
476 } else {
477 $newdir .= DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR;
478 }
479
480 if ( $hasWPFileSystem && ! empty( $wp_filesystem ) ) {
481
482 // need to check user dir first.
483 if ( ! $wp_filesystem->is_dir( $userdir ) ) {
484 $wp_filesystem->mkdir( $userdir, 0750 ); // MWP-1558: tightened from 0777; per-user backup root.
485 }
486
487 if ( ! $wp_filesystem->is_dir( $newdir ) ) {
488 $wp_filesystem->mkdir( $newdir, 0750 ); // MWP-1558: tightened from 0777; per-user backup subdir.
489 }
490
491 if ( ! empty( $dirs[0] ) . $userid && ! $wp_filesystem->exists( trailingslashit( $dirs[0] . $userid ) . '.htaccess' ) ) {
492 $file_htaccess = trailingslashit( $dirs[0] . $userid ) . '.htaccess';
493 $wp_filesystem->put_contents( $file_htaccess, 'deny from all' );
494 }
495 } else {
496
497 // need to check user dir first.
498 if ( ! file_exists( $userdir ) ) {
499 mkdir( $userdir, 0750, true ); // MWP-1558: tightened from 0777. NOSONAR - @newdir is valid.
500 }
501
502 if ( ! file_exists( $newdir ) ) {
503 mkdir( $newdir, 0750, true ); // MWP-1558: tightened from 0777. NOSONAR - @newdir is valid.
504 }
505
506 if ( ! empty( $dirs[0] ) . $userid && ! file_exists( trailingslashit( $dirs[0] . $userid ) . '.htaccess' ) ) {
507 $file = fopen( trailingslashit( $dirs[0] . $userid ) . '.htaccess', 'w+' );
508 fwrite( $file, 'deny from all' );
509 fclose( $file );
510 }
511 }
512
513 return $newdir;
514 }
515
516 /**
517 * Method get_mainwp_specific_url()
518 *
519 * Get MainWP specific URL.
520 *
521 * @param mixed $dir MainWP Directory.
522 *
523 * @return string MainWP URL.
524 *
525 * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
526 */
527 public static function get_mainwp_specific_url( $dir ) {
528 if ( MainWP_System::instance()->is_single_user() ) {
529 $userid = 0;
530 } else {
531
532 /**
533 * Current user global.
534 *
535 * @global string
536 */
537 global $current_user;
538
539 $userid = $current_user->ID;
540 }
541 $dirs = static::get_mainwp_dir();
542
543 return $dirs[1] . $userid . '/' . $dir . '/';
544 }
545
546 /**
547 * Method get_or_create_filename_secret()
548 *
549 * Return a stable per-install secret used to derive opaque private filenames
550 * for sensitive files under wp-content/uploads/mainwp/. Generated once per
551 * install and persisted as a plain wp_option (autoload off).
552 *
553 * Storage rationale: the secret is stored as a plain wp_option rather than
554 * via MainWP_Keys_Manager::update_key_value(). The Keys_Manager would store
555 * the key file inside the very pk/ directory whose filenames this secret
556 * protects, defeating the purpose. The plaintext-in-DB trade-off is
557 * acceptable because the threat model targets external URL guessing only;
558 * a database read primitive that retrieves this secret would also retrieve
559 * the encrypted privkey ciphertext it would be used to locate.
560 *
561 * @return string 64-character hex secret (256 bits of entropy).
562 */
563 public static function get_or_create_filename_secret() {
564 // Use site-option storage so the secret is network-wide on multisite (consistent across blogs)
565 // and falls through to get_option() on single-site. Matches MainWP_Install's pattern for network state.
566 $secret = get_site_option( 'mainwp_private_filename_secret' );
567 if ( ! is_string( $secret ) || ! preg_match( '/^[a-f0-9]{64}$/', $secret ) ) {
568 $candidate = bin2hex( random_bytes( 32 ) );
569 // add_site_option is atomic at the DB layer: only one concurrent writer wins.
570 // Losing requests re-read so all callers see the same secret. Prevents a race where
571 // two concurrent first-upgrade requests would both generate (and write) different
572 // secrets, breaking subsequent filename derivations done by the losing request.
573 if ( add_site_option( 'mainwp_private_filename_secret', $candidate ) ) {
574 $secret = $candidate;
575 } else {
576 $secret = get_site_option( 'mainwp_private_filename_secret' );
577 }
578 }
579 return $secret;
580 }
581
582 /**
583 * Method get_private_filename()
584 *
585 * Compute the opaque filename for a private file under a wp-content/uploads/mainwp/
586 * subdirectory. Replaces predictable patterns like 'mainwp_priv_encrypt_keys_<site_id>'
587 * with HMAC-SHA256 derivations that are not externally enumerable.
588 *
589 * Filenames are deterministic per install: the same (subdir, key, purpose)
590 * always yields the same output. Different subdir or purpose values produce
591 * independent filename spaces with no cross-collision.
592 *
593 * @param string $subdir Subdirectory under mainwp/ (e.g., 'pk', 'cookies').
594 * @param string $key Internal identifier (e.g., site_id, cookie salt).
595 * @param string $purpose Domain separator to prevent cross-subdir filename collisions.
596 *
597 * @return string 64-character hex filename.
598 */
599 public static function get_private_filename( $subdir, $key, $purpose ) {
600 $secret = static::get_or_create_filename_secret();
601 return hash_hmac( 'sha256', $subdir . ':' . $purpose . ':' . $key, $secret );
602 }
603
604 /**
605 * Method get_mainwp_dir_allow_access()
606 *
607 * Get MainWP specific sub folder allow access.
608 *
609 * @param mixed $sub_dir MainWP Sub Directory.
610 */
611 public static function get_mainwp_dir_allow_access( $sub_dir ) {
612 $dirs = static::get_mainwp_dir( $sub_dir, false );
613 if ( $dirs ) {
614 static::get_wp_file_system();
615 global $wp_filesystem;
616 if ( $wp_filesystem ) {
617 // to fix issue of do not allow access.
618 $newdir = $dirs[0];
619 $content = "Order allow,deny\r\nAllow from all";
620 // check if the htaccess is deny access all.
621 if ( $wp_filesystem->exists( trailingslashit( $newdir ) . '.htaccess' ) ) {
622 if ( $wp_filesystem->size( trailingslashit( $newdir ) . '.htaccess' ) < 25 ) { // 25 bytes: deny from all.
623 // update the htaccess file to allow direct access.
624 $wp_filesystem->put_contents( trailingslashit( $newdir ) . '.htaccess', $content );
625 }
626 } else {
627 // update the htaccess file to allow direct access.
628 $wp_filesystem->put_contents( trailingslashit( $newdir ) . '.htaccess', $content );
629 }
630 }
631 }
632 return $dirs;
633 }
634
635
636 /**
637 * Method get_wp_file_system()
638 *
639 * Get WP file system & define Global Variable FS_METHOD.
640 *
641 * @return boolean $init True.
642 */
643 public static function get_wp_file_system() {
644
645 /**
646 * WordPress files system object.
647 *
648 * @global object
649 */
650 global $wp_filesystem;
651
652 if ( empty( $wp_filesystem ) ) {
653 ob_start();
654 if ( file_exists( ABSPATH . '/wp-admin/includes/screen.php' ) ) {
655 include_once ABSPATH . '/wp-admin/includes/screen.php'; // NOSONAR - WP compatible.
656 }
657 if ( file_exists( ABSPATH . '/wp-admin/includes/template.php' ) ) {
658 include_once ABSPATH . '/wp-admin/includes/template.php'; // NOSONAR - WP compatible.
659 }
660 include_once ABSPATH . 'wp-admin/includes/file.php'; // NOSONAR - WP compatible.
661
662 if ( ! function_exists( 'wp_create_nonce' ) ) {
663 include_once ABSPATH . WPINC . '/pluggable.php'; // NOSONAR - WP compatible.
664 }
665
666 $creds = request_filesystem_credentials( 'test' );
667 ob_end_clean();
668 if ( empty( $creds ) ) {
669
670 /**
671 * Define WordPress File system.
672 *
673 * @const ( bool ) Default: true
674 * @source https://code-reference.mainwp.com/classes/MainWP.Dashboard.MainWP_System_Utility.html
675 */
676 define( 'FS_METHOD', 'direct' );
677 }
678 $init = \WP_Filesystem( $creds );
679 } else {
680 $init = true;
681 }
682
683 return $init;
684 }
685
686 /**
687 * Method can_edit_website()
688 *
689 * Check if current user can edit Child Site.
690 *
691 * @param mixed $website Child Site.
692 *
693 * @return mixed true|false|userid
694 *
695 * @uses \MainWP\Dashboard\MainWP_System::is_single_user()
696 */
697 public static function can_edit_website( &$website ) {
698 if ( empty( $website ) ) {
699 return false;
700 }
701
702 if ( MainWP_System::instance()->is_single_user() ) {
703 return true;
704 }
705
706 /**
707 * Current user global.
708 *
709 * @global string
710 */
711 global $current_user;
712
713 return $website->userid === $current_user->ID;
714 }
715
716 /**
717 * Gets site tags
718 *
719 * @param array $item Array containing child site data.
720 * @param bool $client_tag It is client tags or not.
721 *
722 * @return mixed Single Row Classes Item.
723 */
724 public static function get_site_tags( $item, $client_tag = false ) { // phpcs:ignore -- NOSONAR - complex.
725
726 if ( ! is_array( $item ) || ! isset( $item['wpgroups'] ) ) {
727 return '';
728 }
729
730 $href = 'admin.php?page=managesites&g=';
731 if ( $client_tag ) {
732 $href = 'admin.php?page=ManageClients&tags=';
733 }
734
735 $groups_colors = '';
736 if ( isset( $item['wpgroups_colors'] ) ) {
737 $groups_colors = explode( ',', $item['wpgroups_colors'] );
738 }
739
740 $tags = '';
741 $tags_labels = '';
742
743 if ( isset( $item['wpgroups'] ) && ! empty( $item['wpgroups'] ) ) {
744
745 if ( $client_tag ) {
746 $tags_filter = static::client_tags_filter( $item );
747 $tags = $tags_filter['wpgroups'];
748 $tags_ids = $tags_filter['wpgroupids'];
749 } else {
750 $tags = $item['wpgroups'];
751 $tags = explode( ',', $tags );
752 $tags_ids = $item['wpgroupids'];
753 $tags_ids = explode( ',', $tags_ids );
754 }
755
756 if ( is_array( $tags ) ) {
757 foreach ( $tags as $idx => $tag ) {
758 $tag = trim( $tag );
759 $tagc = '';
760
761 // to improved db query.
762 if ( is_array( $groups_colors ) && isset( $groups_colors[ $idx ] ) ) {
763 $tagc = $groups_colors[ $idx ];
764 } else {
765 $tagx = MainWP_DB_Common::instance()->get_group_by_name( $tag );
766 $tagc = is_object( $tagx ) && '' !== $tagx->color ? $tagx->color : '';
767 }
768
769 $tag_id = '';
770 if ( isset( $tags_ids[ $idx ] ) && ! empty( $tags_ids[ $idx ] ) ) {
771 $tag_id = $tags_ids[ $idx ];
772 }
773
774 $tags_labels .= self::get_site_tag_label( $tag, $tag_id, $href, $tagc );
775 }
776 }
777 }
778 return $tags_labels;
779 }
780
781 /**
782 * Gets site tags
783 *
784 * @param array $item Array containing child site data.
785 *
786 * @return mixed Single Row Classes Item.
787 */
788 public static function get_site_tags_belong( $item ) { // phpcs:ignore -- NOSONAR - complex.
789
790 if ( ! is_array( $item ) || ! isset( $item['wpgroups_belong'] ) ) {
791 return static::get_site_tags( $item );
792 }
793
794 $href = 'admin.php?page=managesites&g=';
795
796 $tags = '';
797 $tags_labels = '';
798
799 if ( isset( $item['wpgroups_belong'] ) && ! empty( $item['wpgroups_belong'] ) ) {
800
801 $tags = $item['wpgroups_belong'];
802 $tags = explode( ',', $tags );
803 $tags_ids = $item['wpgroupids_belong'];
804 $tags_ids = explode( ',', $tags_ids );
805
806 $tags_colors = explode( ',', $item['wpgroupcolors_belong'] );
807
808 if ( is_array( $tags ) ) {
809 foreach ( $tags as $idx => $tag ) {
810 $tag = trim( $tag );
811 $tagc = $tags_colors[ $idx ];
812
813 $tag_id = '';
814 if ( isset( $tags_ids[ $idx ] ) && ! empty( $tags_ids[ $idx ] ) ) {
815 $tag_id = $tags_ids[ $idx ];
816 }
817
818 $tags_labels .= self::get_site_tag_label( $tag, $tag_id, $href, $tagc );
819 }
820 }
821 }
822 return $tags_labels;
823 }
824
825 /**
826 * Method get_tag_contrast_color()
827 *
828 * Pick white or black text for a tag background.
829 * White is returned when its contrast is at least 4.5:1.
830 *
831 * @param string $background Stored tag color.
832 *
833 * @return string #ffffff, #000000, or an empty string when the color is unusable.
834 */
835 public static function get_tag_contrast_color( $background ) {
836 $normalized = self::normalize_tag_color( $background );
837 if ( '' === $normalized ) {
838 return '';
839 }
840
841 if ( isset( self::$tag_contrast_cache[ $normalized ] ) ) {
842 return self::$tag_contrast_cache[ $normalized ];
843 }
844
845 $white_contrast = 1.05 / ( self::tag_relative_luminance( $normalized ) + 0.05 );
846 if ( $white_contrast >= 4.5 ) {
847 $foreground = '#ffffff';
848 } else {
849 $foreground = '#000000';
850 }
851
852 self::$tag_contrast_cache[ $normalized ] = $foreground;
853 return $foreground;
854 }
855
856 /**
857 * Method normalize_tag_color()
858 *
859 * Reduce a stored tag color to lowercase #rrggbb.
860 *
861 * @param string $background Stored tag color.
862 *
863 * @return string Normalized color, or an empty string when it is not 3 or 6 digit hex.
864 */
865 private static function normalize_tag_color( $background ) {
866 if ( ! is_string( $background ) ) {
867 return '';
868 }
869
870 $background = trim( $background );
871 if ( ! preg_match( '/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/', $background ) ) {
872 return '';
873 }
874
875 $background = strtolower( $background );
876 if ( 4 === strlen( $background ) ) {
877 $background = '#' . $background[1] . $background[1] . $background[2] . $background[2] . $background[3] . $background[3];
878 }
879
880 return $background;
881 }
882
883 /**
884 * Method tag_relative_luminance()
885 *
886 * WCAG relative luminance for a normalized #rrggbb color.
887 *
888 * @param string $hex Normalized background.
889 *
890 * @return float Luminance between 0 and 1.
891 */
892 private static function tag_relative_luminance( $hex ) {
893 $red = self::tag_channel_linear( hexdec( substr( $hex, 1, 2 ) ) / 255 );
894 $green = self::tag_channel_linear( hexdec( substr( $hex, 3, 2 ) ) / 255 );
895 $blue = self::tag_channel_linear( hexdec( substr( $hex, 5, 2 ) ) / 255 );
896
897 return ( 0.2126 * $red ) + ( 0.7152 * $green ) + ( 0.0722 * $blue );
898 }
899
900 /**
901 * Method tag_channel_linear()
902 *
903 * Convert an sRGB channel to linear light.
904 *
905 * @param float $channel Channel from 0 to 1.
906 *
907 * @return float Linear channel.
908 */
909 private static function tag_channel_linear( $channel ) {
910 if ( $channel <= 0.04045 ) {
911 return $channel / 12.92;
912 }
913
914 return pow( ( $channel + 0.055 ) / 1.055, 2.4 );
915 }
916
917 /**
918 * Method get_site_tag_label()
919 *
920 * Build one tag chip for the linked and plain renderers.
921 *
922 * @param string $tag Tag name.
923 * @param string $tag_id Tag id. An empty id renders a plain chip.
924 * @param string $href Link prefix.
925 * @param string $background Stored background color.
926 *
927 * @return string Chip HTML.
928 */
929 private static function get_site_tag_label( $tag, $tag_id, $href, $background ) {
930 $foreground = static::get_tag_contrast_color( $background );
931 $tag_style = '';
932
933 if ( '' !== $foreground ) {
934 $normalized = self::normalize_tag_color( $background );
935 $tag_style = 'style="background-color:' . esc_attr( $normalized ) . ';color:' . esc_attr( $foreground ) . '"';
936 }
937
938 if ( ! empty( $tag_id ) ) {
939 $tag_a_style = 'style="opacity:1;"';
940 if ( '' !== $foreground ) {
941 $tag_a_style = 'style="color:' . esc_attr( $foreground ) . '!important;opacity:1;"';
942 }
943
944 return '<span ' . $tag_style . ' tag_id="' . esc_attr( $tag_id ) . '" class="ui tag mini label"><a ' . $tag_a_style . ' href="' . esc_url( $href . $tag_id ) . '">' . esc_html( $tag ) . '</a></span>';
945 }
946
947 return '<span ' . $tag_style . ' class="ui tag mini label">' . esc_html( $tag ) . '</span>';
948 }
949
950 /**
951 * Filter client tags
952 *
953 * @param array $item Array containing tags.
954 *
955 * @return mixed Single Row Classes Item.
956 */
957 public static function client_tags_filter( $item ) {
958 $tags = $item['wpgroups'];
959 $tags = explode( ',', $tags );
960 $tags = array_values( array_unique( $tags ) );
961
962 $tags_ids = $item['wpgroupids'];
963 $tags_ids = explode( ',', $tags_ids );
964 $tags_ids = array_values( array_unique( $tags_ids ) );
965
966 $return = array();
967
968 $return['wpgroups'] = $tags;
969 $return['wpgroupids'] = $tags_ids;
970 return $return;
971 }
972
973 /**
974 * Method is_suspended_site()
975 *
976 * Check if enable site.
977 *
978 * @param mixed $website The website.
979 */
980 public static function is_suspended_site( $website = false ) {
981 if ( empty( $website ) ) {
982 return true; // empty so return as suspended.
983 }
984 if ( is_array( $website ) ) {
985 return '1' === $website['suspended'];
986 } elseif ( is_object( $website ) ) {
987 if ( ! property_exists( $website, 'suspended' ) && property_exists( $website, 'id' ) ) {
988 $website = MainWP_DB::instance()->get_website_by_id( $website->id );
989 }
990 if ( property_exists( $website, 'suspended' ) ) {
991 return '1' === $website->suspended;
992 }
993 } elseif ( is_numeric( $website ) ) {
994 $siteId = $website;
995 $website = MainWP_DB::instance()->get_website_by_id( $siteId );
996 if ( $website ) {
997 return static::is_suspended_site( $website );
998 }
999 }
1000 return false;
1001 }
1002
1003 /**
1004 * Method get_current_wpid()
1005 *
1006 * Get current Child Site ID.
1007 *
1008 * @return string $current_user->current_site_id Current Child Site ID.
1009 */
1010 public static function get_current_wpid() {
1011
1012 /**
1013 * Current user global.
1014 *
1015 * @global string
1016 */
1017 global $current_user;
1018
1019 return $current_user->current_site_id;
1020 }
1021
1022 /**
1023 * Method set_current_wpid()
1024 *
1025 * Set the current Child Site ID.
1026 *
1027 * @param mixed $wpid Child Site ID.
1028 */
1029 public static function set_current_wpid( $wpid ) {
1030
1031 /**
1032 * Current user global.
1033 *
1034 * @global string
1035 */
1036 global $current_user;
1037
1038 $current_user->current_site_id = $wpid;
1039 }
1040
1041 /**
1042 * Method get_page_id()
1043 *
1044 * Get current Page ID.
1045 *
1046 * @param null $screen Current Screen ID.
1047 *
1048 * @return string $page Current page ID.
1049 */
1050 public static function get_page_id( $screen = null ) {
1051
1052 if ( empty( $screen ) ) {
1053 $screen = get_current_screen();
1054 } elseif ( is_string( $screen ) ) {
1055 $screen = convert_to_screen( $screen );
1056 }
1057
1058 if ( ! isset( $screen->id ) ) {
1059 return '';
1060 }
1061
1062 return $screen->id;
1063 }
1064
1065 /**
1066 * Check register error
1067 *
1068 * @param string $error Error message.
1069 *
1070 * @return string
1071 */
1072 public static function check_register_error( $error ) {
1073 if ( is_string( $error ) ) {
1074 $error_langs = array(
1075 'This site already contains a link. Please deactivate and reactivate the',
1076 'Diese Website enthält bereits diesen Link',
1077 'Este sitio ya contiene un enlace. Por favor, desactive y vuelva a activar el',
1078 'Ce site contient déjà un lien. Veuillez désactiver et réactiver l’extension',
1079 'Este site já contém um link. Desative e reative o plug-in',
1080 );
1081 foreach ( $error_langs as $err ) {
1082 if ( false !== stripos( $error, $err ) ) {
1083 return esc_html__( 'The child site already contains connection data. To proceed, you need to clear the connection data on the child site. Navigate to WP Admin > Settings > MainWP Child > MainWP Child Settings, and click the Clear Connection Data button. After clearing the data, try reconnecting the site.', 'mainwp' );
1084 }
1085 }
1086 }
1087 return $error;
1088 }
1089
1090 /**
1091 * Method get_child_response()
1092 *
1093 * Get response from Child Site.
1094 *
1095 * @param mixed $data Data to process.
1096 *
1097 * @return json $data|true.
1098 */
1099 public static function get_child_response( $data ) { // phpcs:ignore -- NOSONAR - complex.
1100 $resp = json_decode( $data, true );
1101
1102 if ( is_array( $resp ) ) {
1103 if ( isset( $resp['error'] ) ) {
1104 $resp['error'] = MainWP_Utility::esc_content( $resp['error'] );
1105 $resp['error'] = static::check_register_error( $resp['error'] );
1106 }
1107
1108 if ( isset( $resp['message'] ) && is_string( $resp['message'] ) ) {
1109 $resp['message'] = MainWP_Utility::esc_content( $resp['message'] );
1110 }
1111
1112 if ( isset( $resp['error_message'] ) ) {
1113 $resp['error_message'] = MainWP_Utility::esc_content( $resp['error_message'] );
1114 }
1115
1116 if ( isset( $resp['notices'] ) ) {
1117 if ( is_string( $resp['notices'] ) ) {
1118 $resp['notices'] = MainWP_Utility::esc_content( $resp['notices'] );
1119 } elseif ( is_array( $resp['notices'] ) ) {
1120 $notices = array();
1121 foreach ( $resp['notices'] as $noti ) {
1122 if ( ! empty( $noti ) && is_string( $noti ) ) {
1123 $notices[] = MainWP_Utility::esc_content( $noti );
1124 }
1125 }
1126 if ( ! empty( $notices ) ) {
1127 $resp['notices'] = implode( ' || ', $notices );
1128 }
1129 }
1130 }
1131 }
1132
1133 return $resp;
1134 }
1135
1136 /**
1137 * Method maybe_unserialyze()
1138 *
1139 * Check if $data is serialized,
1140 * if it isn't then base64_decode it.
1141 *
1142 * @param mixed $data Data to check.
1143 *
1144 * @return mixed $data.
1145 */
1146 public static function maybe_unserialyze( $data ) {
1147 if ( empty( $data ) || is_array( $data ) ) {
1148 return $data;
1149 } elseif ( is_serialized( $data ) ) {
1150 // phpcs:ignore -- for compatability.
1151 return maybe_unserialize( $data );
1152 } else {
1153 // phpcs:ignore -- for compatability.
1154 return maybe_unserialize( base64_decode( $data ) );
1155 }
1156 }
1157
1158 /**
1159 * Method get_openssl_conf()
1160 *
1161 * Get dashboard openssl configuration.
1162 */
1163 public static function get_openssl_conf() {
1164
1165 if ( defined( 'MAINWP_CRYPT_RSA_OPENSSL_CONFIG' ) ) {
1166 return MAINWP_CRYPT_RSA_OPENSSL_CONFIG;
1167 }
1168 $lib_loc = get_option( 'mainwp_opensslLibLocation' );
1169 return ! empty( $lib_loc ) ? $lib_loc : '';
1170 }
1171
1172 /**
1173 * Get tokens of site.
1174 *
1175 * @param object $site The website.
1176 * @param bool $monitor Get tokens for monitor.
1177 * @param mixed $website_status Object containing the child site status.
1178 *
1179 * @return array Array of tokens.
1180 *
1181 * @uses \MainWP\Dashboard\MainWP_DB::get_website_option()
1182 */
1183 public static function get_tokens_site_values( $site, $monitor = false, $website_status = false) { //phpcs:ignore -- NOSONAR -complex.
1184
1185 $tokens_values = array(
1186 '[site.name]' => $site->name,
1187 '[site.url]' => $site->url,
1188 );
1189
1190 $site_info = MainWP_DB::instance()->get_website_option( $site, 'site_info' );
1191 $site_info = ! empty( $site_info ) ? json_decode( $site_info, true ) : array();
1192
1193 if ( is_array( $site_info ) ) {
1194 $map_site_tokens = array(
1195 'client.site.version' => 'wpversion', // Displays the WP version of the child site.
1196 'client.site.theme' => 'themeactivated', // Displays the currently active theme for the child site.
1197 'client.site.php' => 'phpversion', // Displays the PHP version of the child site.
1198 'client.site.mysql' => 'mysql_version', // Displays the MySQL version of the child site.
1199 );
1200 foreach ( $map_site_tokens as $tok => $val ) {
1201 $tokens_values[ '[' . $tok . ']' ] = ( is_array( $site_info ) && isset( $site_info[ $val ] ) ) ? $site_info[ $val ] : '';
1202 }
1203 }
1204
1205 if ( $monitor ) {
1206 if ( is_object( $website_status ) && property_exists( $website_status, 'status' ) ) {
1207 $status = $website_status->status ? 'UP' : 'DOWN';
1208 } else {
1209 $active_monitor = 0;
1210 $primary_monitor = MainWP_DB_Uptime_Monitoring::instance()->get_monitor_by( $site->id, 'issub', 0 );
1211 if ( $primary_monitor ) {
1212 $global_settings = MainWP_Uptime_Monitoring_Handle::get_global_monitoring_settings();
1213 $active_monitor = MainWP_Uptime_Monitoring_Connect::get_apply_setting( 'active', (int) $primary_monitor->active, $global_settings, -1, 60 );
1214 }
1215 if ( $active_monitor ) {
1216 $status = 'PENDING';
1217 $last = MainWP_DB_Uptime_Monitoring::instance()->get_last_site_heartbeat( $site->id, false );
1218 if ( $last ) {
1219 $status = $last && $last->status ? 'UP' : 'DOWN';
1220 }
1221 } else {
1222 $status = 'DISABLED';
1223 }
1224 }
1225
1226 $tokens_values['[uptime.status]'] = $status;
1227 }
1228 return $tokens_values;
1229 }
1230
1231 /**
1232 *
1233 * Replace site tokens.
1234 *
1235 * @param string $str String data.
1236 * @param array $replace_tokens array of tokens.
1237 *
1238 * @return string content with replaced tokens.
1239 */
1240 public static function replace_tokens_values( $str, $replace_tokens ) {
1241 $tokens = array_keys( $replace_tokens );
1242 $values = array_values( $replace_tokens );
1243 return str_replace( $tokens, $values, $str );
1244 }
1245
1246 /**
1247 *
1248 * Set timeout limit.
1249 *
1250 * @param int $timeout timeout value.
1251 */
1252 public static function set_time_limit( $timeout = 0 ) {
1253 if ( false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) ) {
1254 set_time_limit( $timeout );
1255 }
1256 }
1257
1258 /**
1259 *
1260 * Method get_plugin_theme_info().
1261 *
1262 * Get WordPress plugin/theme info.
1263 *
1264 * @param string $what 'plugin' or 'theme'.
1265 * @param array $params Plugin/Theme info params.
1266 */
1267 public static function get_plugin_theme_info( $what, $params = array() ) {
1268
1269 if ( 'plugin' === $what ) {
1270 include_once ABSPATH . '/wp-admin/includes/plugin-install.php'; // NOSONAR - WP compatible.
1271 return plugins_api(
1272 'plugin_information',
1273 $params
1274 );
1275 } elseif ( 'theme' === $what ) {
1276 include_once ABSPATH . '/wp-admin/includes/theme-install.php'; // NOSONAR - WP compatible.
1277 return themes_api(
1278 'theme_information',
1279 $params
1280 );
1281
1282 }
1283
1284 return false;
1285 }
1286
1287 /**
1288 * Method save_cached_icons().
1289 *
1290 * Save cached icons
1291 *
1292 * @param string $icon The icon.
1293 * @param string $slug slug.
1294 * @param string $type Type: plugin|theme.
1295 */
1296 public static function save_cached_icons( $icon, $slug, $type ) {
1297 $file_extension = strtolower( pathinfo( $icon, PATHINFO_EXTENSION ) );
1298
1299 $file_exts = apply_filters(
1300 'mainwp_save_cached_icons_file_ext',
1301 array(
1302 'jpeg',
1303 'jpg',
1304 'gif',
1305 'ico',
1306 'png',
1307 )
1308 );
1309
1310 if ( ! in_array( $file_extension, $file_exts ) ) {
1311 $icon = '';
1312 }
1313 static::update_cached_icons( $icon, $slug, $type, false, true );
1314 }
1315
1316 /**
1317 * Method update_cached_icons().
1318 *
1319 * Update cached icons
1320 *
1321 * @param string $icon The icon.
1322 * @param string $slug slug.
1323 * @param string $type Type: plugin|theme.
1324 * @param bool $custom_icon Custom icon or not. Default: false.
1325 * @param bool $noexp No expire icon - that for sync icon.
1326 */
1327 public static function update_cached_icons( $icon, $slug, $type, $custom_icon = false, $noexp = false ) {
1328
1329 if ( 'plugin' === $type ) {
1330 $option_name = 'plugins_icons';
1331 } elseif ( 'theme' === $type ) {
1332 $option_name = 'themes_icons';
1333 } else {
1334 return false;
1335 }
1336
1337 $cached_icons = MainWP_DB::instance()->get_general_option( $option_name, 'array' );
1338
1339 $icon = apply_filters( 'mainwp_update_cached_icons', $icon, $slug, $type );
1340
1341 if ( isset( $cached_icons[ $slug ] ) ) {
1342 $value = $cached_icons[ $slug ];
1343 } else {
1344 $value = array(
1345 'lasttime_cached' => time(),
1346 'path_custom' => '',
1347 'path' => '',
1348 );
1349 }
1350
1351 $value['lasttime_cached'] = time();
1352
1353 if ( $custom_icon ) {
1354
1355 if ( isset( $value['path_custom'] ) && isset( $value['path'] ) && $value['path_custom'] === $icon && '' === $value['path'] ) {
1356 return true; // no change.
1357 }
1358
1359 $value['path_custom'] = $icon;
1360 $value['path'] = '';
1361 } else {
1362
1363 if ( isset( $value['path_custom'] ) && isset( $value['path'] ) && $value['path'] === $icon && '' === $value['path_custom'] ) {
1364 return true; // no change.
1365 }
1366
1367 $value['path'] = $icon;
1368 $value['path_custom'] = '';
1369 }
1370
1371 if ( $noexp ) {
1372 $value['noexpire'] = 1;
1373 } elseif ( isset( $value['noexpire'] ) ) {
1374 unset( $value['noexpire'] );
1375 }
1376
1377 // update cache.
1378 $cached_icons[ $slug ] = $value;
1379
1380 /**
1381 * @since 5.4.0.18
1382 */
1383 $cached_icons = apply_filters( 'mainwp_before_save_cached_icons', $cached_icons, $icon, $slug, $type, $custom_icon, $noexp );
1384
1385 MainWP_DB::instance()->update_general_option( $option_name, $cached_icons, 'array' );
1386 return true;
1387 }
1388
1389 /**
1390 * Private function Fetch a plugin|theme icon via API from WordPress.org
1391 *
1392 * @param string $slug Plugin|Theme slug.
1393 * @param string $type Plugin|Theme.
1394 */
1395 private static function fetch_wp_org_icons( $slug, $type ) { // phpcs:ignore -- NOSONAR - complex.
1396 if ( 'plugin' === $type ) {
1397 $fields = array(
1398 'tags' => false,
1399 'icons' => true,
1400 'sections' => false,
1401 'description' => false,
1402 'tested' => false,
1403 'requires' => false,
1404 'rating' => false,
1405 'downloaded' => false,
1406 'downloadlink' => false,
1407 'last_updated' => false,
1408 'homepage' => false,
1409 'compatibility' => false,
1410 'ratings' => false,
1411 'added' => false,
1412 'donate_link' => false,
1413 );
1414 } elseif ( 'theme' === $type ) {
1415 $fields = array(
1416 'screenshots' => true,
1417 'screenshot_count' => 5,
1418 'sections' => false,
1419 'rating' => false,
1420 'downloaded' => false,
1421 'download_link' => false,
1422 'last_updated' => false,
1423 'tags' => false,
1424 'template' => false,
1425 'parent' => false,
1426 'screenshot_url' => false,
1427 'homepage' => false,
1428 );
1429
1430 } else {
1431 return false;
1432 }
1433
1434 $icon = '';
1435 if ( 'theme' === $type ) {
1436 // with $fields empty to get screenshot_url of theme.
1437 $info = static::get_plugin_theme_info(
1438 $type,
1439 array(
1440 'slug' => $slug,
1441 'timeout' => 60,
1442 )
1443 );
1444 if ( is_object( $info ) && ! empty( $info->screenshot_url ) ) {
1445 $icon = $info->screenshot_url;
1446 }
1447 }
1448
1449 // if get screenshot_url of theme success.
1450 if ( ! empty( $icon ) ) {
1451 $option_name = 'themes_icons';
1452 } else {
1453 $info = static::get_plugin_theme_info(
1454 $type,
1455 array(
1456 'slug' => $slug,
1457 'fields' => $fields,
1458 'timeout' => 60,
1459 )
1460 );
1461 $option_name = 'plugins_icons';
1462 $icon = '';
1463 if ( 'plugin' === $type ) {
1464 if ( is_object( $info ) && property_exists( $info, 'icons' ) && isset( $info->icons['1x'] ) ) {
1465 $icon = $info->icons['1x'];
1466 }
1467 } else {
1468 if ( is_object( $info ) && property_exists( $info, 'screenshots' ) && isset( $info->screenshots[0] ) ) {
1469 $icon = $info->screenshots[0];
1470 }
1471 $option_name = 'themes_icons';
1472 }
1473 }
1474
1475 $fetched_icon = '';
1476 if ( '' !== $icon ) {
1477 $fetched_icon = rawurlencode( $icon );
1478 }
1479
1480 $cached_icons = MainWP_DB::instance()->get_general_option( $option_name, 'array' );
1481
1482 if ( isset( $cached_icons[ $slug ] ) && '' === $fetched_icon && ! empty( $cached_icons[ $slug ]['path'] ) ) {
1483 // if fetch icon empty then used caching icon.
1484 $fetched_icon = $cached_icons[ $slug ]['path'];
1485 $icon = rawurldecode( $fetched_icon );
1486 }
1487
1488 static::update_cached_icons( $fetched_icon, $slug, $type );
1489
1490 if ( '' !== $icon ) {
1491 return $icon;
1492 }
1493 return false;
1494 }
1495
1496 /**
1497 * Method handle_get_icon()
1498 *
1499 * @param string $slug Plugin slug.
1500 * @param string $type Type: theme|plugin.
1501 */
1502 public static function handle_get_icon( $slug, $type ) {
1503 if ( empty( $slug ) ) {
1504 return false;
1505 }
1506 if ( 'plugin' === $type || 'theme' === $type ) {
1507 return static::fetch_wp_org_icons( $slug, $type );
1508 }
1509 return '';
1510 }
1511
1512
1513 /**
1514 * Gets a plugin icon via API from WordPress.org
1515 *
1516 * @param string $slug Plugin slug.
1517 * @param bool $forced_get Forced get icon, default: false.
1518 */
1519 public static function get_plugin_icon( $slug, $forced_get = false ) { // phpcs:ignore -- NOSONAR - complex.
1520
1521 $icon = apply_filters( 'mainwp_get_plugin_theme_icon', '', $slug, 'plugin' );
1522
1523 if ( ! empty( $icon ) ) {
1524 return $icon;
1525 }
1526
1527 $forced_get = apply_filters( 'mainwp_forced_get_plugin_theme_icon', $forced_get, $slug, 'plugin' );
1528
1529 if ( $forced_get ) {
1530 $fet_icon = static::fetch_wp_org_icons( $slug, 'plugin' );
1531 if ( false !== $fet_icon ) {
1532 $scr = MainWP_Utility::remove_http_prefix( $fet_icon );
1533 return '<img style="display:inline-block" class="ui mini circular image" updated-icon="true" src="' . esc_attr( $scr ) . '" />';
1534 }
1535 return $icon;
1536 }
1537
1538 // checks expired.
1539 $cached_icons = MainWP_DB::instance()->get_general_option( 'plugins_icons', 'array' );
1540
1541 if ( ! empty( $cached_icons ) ) {
1542 $lasttime_clear_cached = MainWP_DB::instance()->get_general_option( 'lasttime_clear_cached_plugins_icon' );
1543 if ( time() > ( intval( $lasttime_clear_cached ) + MONTH_IN_SECONDS ) ) {
1544 $updated = false;
1545 $new_cached = array();
1546 foreach ( $cached_icons as $sl => $val ) {
1547 if ( empty( $val['noexpire'] ) && empty( $val['path_custom'] ) && time() < ( intval( $val['lasttime_cached'] ) + 12 * MONTH_IN_SECONDS ) ) {
1548 $new_cached[ $sl ] = $val; // unset.
1549 $updated = true;
1550 }
1551 }
1552 if ( $updated ) {
1553 MainWP_DB::instance()->update_general_option( 'plugins_icons', $new_cached, 'array' );
1554 }
1555 MainWP_DB::instance()->update_general_option( 'lasttime_clear_cached_plugins_icon', time() );
1556 }
1557 }
1558
1559 return static::get_plugin_theme_icon( $slug, 'plugin' );
1560 }
1561
1562 /**
1563 * Gets a theme icon via API from WordPress.org
1564 *
1565 * @param string $slug Theme slug.
1566 * @param bool $forced_get Forced get icon, default: false.
1567 */
1568 public static function get_theme_icon( $slug, $forced_get = false ) { // phpcs:ignore -- NOSONAR - complex.
1569
1570 $icon = apply_filters( 'mainwp_get_plugin_theme_icon', '', $slug, 'theme' );
1571
1572 if ( ! empty( $icon ) ) {
1573 return $icon;
1574 }
1575
1576 $forced_get = apply_filters( 'mainwp_forced_get_plugin_theme_icon', $forced_get, $slug, 'theme' );
1577
1578 if ( $forced_get ) {
1579 $fet_icon = static::fetch_wp_org_icons( $slug, 'theme' );
1580 if ( false !== $fet_icon ) {
1581 $scr = MainWP_Utility::remove_http_prefix( $fet_icon );
1582 $icon = '<img style="display:inline-block" class="ui mini circular image" updated-icon="true" src="' . esc_attr( $scr ) . '" />';
1583 }
1584 return $icon;
1585 }
1586
1587 // checks expired.
1588 $cached_icons = MainWP_DB::instance()->get_general_option( 'themes_icons', 'array' );
1589
1590 if ( ! empty( $cached_icons ) ) {
1591 $lasttime_clear_cached = MainWP_DB::instance()->get_general_option( 'lasttime_clear_cached_themes_icon' );
1592 if ( time() > ( intval( $lasttime_clear_cached ) + MONTH_IN_SECONDS ) ) {
1593 $updated = false;
1594 $new_cached = array();
1595 foreach ( $cached_icons as $sl => $val ) {
1596 if ( empty( $val['path_custom'] ) && time() < ( intval( $val['lasttime_cached'] ) + 12 * MONTH_IN_SECONDS ) ) {
1597 $new_cached[ $sl ] = $val;
1598 $updated = true;
1599 }
1600 }
1601 if ( $updated ) {
1602 MainWP_DB::instance()->update_general_option( 'themes_icons', $new_cached, 'array' );
1603 }
1604 MainWP_DB::instance()->update_general_option( 'lasttime_clear_cached_themes_icon', time() );
1605 }
1606 }
1607
1608 return static::get_plugin_theme_icon( $slug, 'theme' );
1609 }
1610
1611
1612 /**
1613 * Gets a plugin|theme icon to output.
1614 *
1615 * @param string $slug Plugin|Theme slug.
1616 * @param string $type Type icon, plugin|theme.
1617 */
1618 private static function get_plugin_theme_icon( $slug, $type ) { // phpcs:ignore -- NOSONAR -Current complexity is the only way to achieve desired results, pull request solutions appreciated.
1619
1620 $icon_placeholder_cls = '';
1621
1622 if ( 'plugin' === $type ) {
1623 $option_name = 'plugins_icons';
1624 $icon_placeholder_cls = 'plug circular inverted';
1625 } elseif ( 'theme' === $type ) {
1626 $option_name = 'themes_icons';
1627 $icon_placeholder_cls = 'tint circular inverted';
1628 } else {
1629 return '<i style="font-size: 17px" class="plug circular inverted icon" not-cached-path="true"></i>';
1630 }
1631
1632 $cached_icons = MainWP_DB::instance()->get_general_option( $option_name, 'array' );
1633
1634 if ( ! is_array( $cached_icons ) ) {
1635 $cached_icons = array();
1636 }
1637
1638 $cached_days = apply_filters( 'mainwp_plugin_theme_icon_cache_days', 15, $slug, $type ); // default 15 days.
1639
1640 $attr_slug = ' icon-type="' . esc_attr( $type ) . '" item-slug="' . esc_attr( $slug ) . '" ';
1641 $cls_expired = ' cached-icon-expired ';
1642 $cls_uploadable = ' cached-icon-customable ';
1643
1644 if ( empty( $slug ) ) {
1645 $cls_uploadable = '';
1646 }
1647
1648 $icon = '';
1649
1650 if ( isset( $cached_icons[ $slug ] ) ) {
1651 $scr = '';
1652 $is_custom_icon = false;
1653 if ( ! empty( $cached_icons[ $slug ]['path_custom'] ) ) {
1654 if ( 'plugin' === $type ) {
1655 $dirs = static::get_mainwp_dir( 'plugin-icons', true );
1656 } elseif ( 'theme' === $type ) {
1657 $dirs = static::get_mainwp_dir( 'theme-icons', true );
1658 }
1659 $scr = $dirs[1] . rawurldecode( $cached_icons[ $slug ]['path_custom'] );
1660 $is_custom_icon = true; // custom icons will not expired.
1661 } elseif ( ! empty( $cached_icons[ $slug ]['path'] ) ) {
1662 $scr = rawurldecode( $cached_icons[ $slug ]['path'] );
1663 $scr = MainWP_Utility::remove_http_prefix( $scr );
1664 }
1665
1666 $set_cached_expired = apply_filters( 'mainwp_cache_icon_expired', false, $slug, 'theme' );
1667 $set_expired = false;
1668
1669 if ( $set_cached_expired && time() > ( intval( $cached_icons[ $slug ]['lasttime_cached'] ) + 15 * MINUTE_IN_SECONDS ) ) {
1670 $set_expired = true;
1671 }
1672
1673 $forced_exprided = 1700238511;
1674 $lasttime_cached = isset( $cached_icons[ $slug ]['lasttime_cached'] ) ? intval( $cached_icons[ $slug ]['lasttime_cached'] ) : 0;
1675
1676 if ( time() > ( $lasttime_cached + $cached_days * DAY_IN_SECONDS ) || $lasttime_cached < $forced_exprided ) { // expired.
1677 if ( ! empty( $scr ) ) {
1678 $icon = '<img style="display:inline-block" class="ui mini circular image ' . ( $is_custom_icon ? $cls_uploadable : $cls_expired ) . '" ' . $attr_slug . 'src="' . esc_attr( $scr ) . '" alt="Icon"/>'; // to update expired icon.
1679 } else {
1680 $icon = '<i style="font-size: 17px" class="' . esc_attr( $icon_placeholder_cls ) . ' icon ' . $cls_expired . $cls_uploadable . '" ' . $attr_slug . '></i>'; // to update expired icon.
1681 }
1682 } elseif ( ! empty( $scr ) ) {
1683 $use_cls_expired = $set_expired ? $cls_expired : '';
1684 $icon = '<img style="display:inline-block" class="ui mini circular image ' . ( $is_custom_icon ? $cls_uploadable : $use_cls_expired ) . '" ' . $attr_slug . ' cached-path-icon="true" src="' . esc_attr( $scr ) . '" alt="Icon"/>';
1685 } else {
1686 $icon = '<i style="font-size: 17px" class="' . esc_attr( $icon_placeholder_cls ) . ' icon ' . ( $set_expired ? $cls_expired : '' ) . $cls_uploadable . '" ' . $attr_slug . ' cached-path-icon="true"></i>';
1687 }
1688 } elseif ( empty( $icon ) ) {
1689 $icon = '<i style="font-size: 17px" class="' . esc_attr( $icon_placeholder_cls ) . ' icon ' . $cls_expired . $cls_uploadable . '" ' . $attr_slug . ' not-cached-path="true"></i>'; // not upload when not existed in the cached.
1690 }
1691 return $icon;
1692 }
1693
1694
1695 /**
1696 * Method handle_upload_image().
1697 *
1698 * Handle upload icons.
1699 *
1700 * @param string $sub_folder The sub folder.
1701 * @param mixed $file_uploader The file uploader.
1702 * @param mixed $file_index The index of file uploader.
1703 * @param bool $file_subindex Is file with sub index.
1704 * @param int $max_width max image width.
1705 * @param int $max_height max image height.
1706 */
1707 public static function handle_upload_image( $sub_folder, $file_uploader, $file_index = 0, $file_subindex = false, $max_width = 300, $max_height = 300 ) { // phpcs:ignore -- NOSONAR - complex function. Current complexity is the only way to achieve desired results, pull request solutions appreciated.
1708
1709 $dirs = static::get_mainwp_dir( $sub_folder, true );
1710 $base_dir = $dirs[0];
1711 $base_url = $dirs[1];
1712
1713 /**
1714 * WordPress files system object.
1715 *
1716 * @global object
1717 */
1718 global $wp_filesystem;
1719
1720 $output = array();
1721 $filename = '';
1722 $filepath = '';
1723
1724 $file_types = array(
1725 'image/jpeg',
1726 'image/jpg',
1727 'image/gif',
1728 'image/x-icon',
1729 'image/png',
1730 );
1731
1732 $file_exts = array(
1733 'jpeg',
1734 'jpg',
1735 'gif',
1736 'ico',
1737 'png',
1738 );
1739
1740 $upload_ok = ( false === $file_subindex ) ? ( UPLOAD_ERR_OK === $file_uploader['error'][ $file_index ] ) : ( UPLOAD_ERR_OK === $file_uploader['error'][ $file_index ][ $file_subindex ] );
1741
1742 if ( $upload_ok ) {
1743 $tmp_file = ( false === $file_subindex ) ? ( $file_uploader['tmp_name'][ $file_index ] ) : ( $file_uploader['tmp_name'][ $file_index ][ $file_subindex ] );
1744
1745 if ( is_uploaded_file( $tmp_file ) ) {
1746 if ( false === $file_subindex ) {
1747 $file_size = $file_uploader['size'][ $file_index ];
1748 $file_type = $file_uploader['type'][ $file_index ];
1749 $file_name = $file_uploader['name'][ $file_index ];
1750 } else {
1751 $file_size = $file_uploader['size'][ $file_index ][ $file_subindex ];
1752 $file_type = $file_uploader['type'][ $file_index ][ $file_subindex ];
1753 $file_name = $file_uploader['name'][ $file_index ][ $file_subindex ];
1754 }
1755
1756 $file_extension = strtolower( pathinfo( $file_name, PATHINFO_EXTENSION ) );
1757
1758 if ( $file_size > 500 * 1025 ) {
1759 $output['error'][] = 3;
1760 } elseif ( ! in_array( $file_type, $file_types ) ) {
1761 $output['error'][] = 4;
1762 } elseif ( ! in_array( $file_extension, $file_exts ) ) {
1763 $output['error'][] = 5;
1764 } else {
1765
1766 $dest_file = $base_dir . '/' . $file_name;
1767 $dest_file = dirname( $dest_file ) . '/' . wp_unique_filename( dirname( $dest_file ), basename( $dest_file ) );
1768
1769 if ( $wp_filesystem->put_contents( $dest_file, $wp_filesystem->get_contents( $tmp_file ) ) ) {
1770 if ( file_exists( $dest_file ) ) {
1771 list( $width, $height ) = getimagesize( $dest_file );
1772 }
1773
1774 $resize = false;
1775 if ( $width > $max_width ) {
1776 $dst_width = $max_width;
1777 if ( $height > $max_height ) {
1778 $dst_height = $max_height;
1779 } else {
1780 $dst_height = $height;
1781 }
1782 $resize = true;
1783 } elseif ( $height > $max_height ) {
1784 $dst_width = $width;
1785 $dst_height = $max_height;
1786 $resize = true;
1787 }
1788
1789 if ( $resize ) {
1790 $src = $dest_file;
1791 $cropped_file = wp_crop_image( $src, 0, 0, $width, $height, $dst_width, $dst_height, false );
1792 if ( ! $cropped_file || is_wp_error( $cropped_file ) ) {
1793 $output['error'][] = 9;
1794 } else {
1795 wp_delete_file( $dest_file );
1796 $filename = basename( $cropped_file );
1797 $filepath = $cropped_file;
1798 }
1799 } else {
1800 $filename = basename( $dest_file );
1801 $filepath = $dest_file;
1802 }
1803 } else {
1804 $output['error'][] = 6;
1805 }
1806 }
1807 }
1808 }
1809 $output['fileurl'] = ! empty( $filename ) ? $base_url . '/' . $filename : '';
1810 $output['filepath'] = ! empty( $filepath ) ? $filepath : '';
1811 $output['filename'] = ! empty( $filename ) ? $filename : '';
1812
1813 return $output;
1814 }
1815
1816 /**
1817 * Method disabled_wpcore_update_by().
1818 *
1819 * Get disabled wpcore update by.
1820 *
1821 * @param string $website The website.
1822 */
1823 public static function disabled_wpcore_update_by( $website ) {
1824 $by = static::get_disabled_wpcore_update_host( $website );
1825 if ( 'flywheel' === $by ) {
1826 return esc_html__( 'FlyWheel disables WP core updates. For more information contact FlyWheel support.', 'mainwp' );
1827 } elseif ( 'pressable' === $by ) {
1828 return esc_html__( 'Pressable disables WP core updates. For more information contact Pressable support.', 'mainwp' );
1829 }
1830 return '';
1831 }
1832
1833
1834 /**
1835 * Method get_disabled_wpcore_update_host().
1836 *
1837 * Get wpcore update disabled for the websites on FlyWheel host or Pressable host.
1838 *
1839 * @param mixed $website data.
1840 */
1841 public static function get_disabled_wpcore_update_host( $website ) {
1842 if ( empty( $website ) ) {
1843 return '';
1844 }
1845 $wphost = MainWP_DB::instance()->get_website_option( $website, 'wphost' );
1846 if ( ! empty( $wphost ) && ( 'flywheel' !== $wphost && 'pressable' !== $wphost ) ) {
1847 $wphost = '';
1848 }
1849 return empty( $wphost ) ? '' : $wphost;
1850 }
1851
1852
1853 /**
1854 * Method get_connect_sign_algorithm().
1855 *
1856 * Get supported sign algorithms.
1857 *
1858 * @param mixed $website The Website object.
1859 *
1860 * @return mixed $alg Algorithm connect.
1861 */
1862 public static function get_connect_sign_algorithm( $website ) { // phpcs:ignore -- NOSONAR - complex.
1863 $alg = is_object( $website ) && property_exists( $website, 'signature_algo' ) && ! empty( $website->signature_algo ) ? $website->signature_algo : false;
1864
1865 // to fix.
1866 if ( is_numeric( $alg ) ) {
1867 $alg = intval( $alg );
1868 }
1869
1870 $default_alg = false;
1871 if ( defined( 'OPENSSL_ALGO_SHA256' ) ) {
1872 $default_alg = OPENSSL_ALGO_SHA256;
1873 }
1874
1875 if ( ! empty( $alg ) && 9999 === $alg ) {
1876 $alg = get_option( 'mainwp_connect_signature_algo', $default_alg );
1877 // to fix.
1878 if ( is_numeric( $alg ) ) {
1879 $alg = intval( $alg );
1880 }
1881 }
1882
1883 if ( empty( $alg ) ) {
1884 $site_info = MainWP_DB::instance()->get_website_option( $website, 'site_info' );
1885 $site_info = ! empty( $site_info ) ? json_decode( $site_info, true ) : array();
1886 if ( is_array( $site_info ) && ! empty( $site_info['child_version'] ) && version_compare( $site_info['child_version'], '4.5', '>=' ) ) {
1887 $alg = $default_alg;
1888 }
1889 }
1890
1891 if ( ! static::is_valid_supported_sign_alg( $alg ) ) {
1892 $alg = false;
1893 }
1894
1895 $alg = apply_filters( 'mainwp_connect_sign_algo', $alg, $website );
1896
1897 return $alg;
1898 }
1899
1900 /**
1901 * Method is_valid_supported_sign_alg()
1902 *
1903 * Check if is supported sign algorithms.
1904 *
1905 * @param int $alg The Sign Algo value.
1906 */
1907 public static function is_valid_supported_sign_alg( $alg ) {
1908 $valid = false;
1909 if ( ( defined( 'OPENSSL_ALGO_SHA1' ) && OPENSSL_ALGO_SHA1 === $alg ) || ( defined( 'OPENSSL_ALGO_SHA224' ) && OPENSSL_ALGO_SHA224 === $alg ) || ( defined( 'OPENSSL_ALGO_SHA256' ) && OPENSSL_ALGO_SHA256 === $alg ) || ( defined( 'OPENSSL_ALGO_SHA384' ) && OPENSSL_ALGO_SHA384 === $alg ) || ( defined( 'OPENSSL_ALGO_SHA512' ) && OPENSSL_ALGO_SHA512 === $alg ) ) {
1910 $valid = true;
1911 }
1912 return $valid;
1913 }
1914
1915 /**
1916 * Method get_signature_alg()
1917 *
1918 * Get custom signature algorithms.
1919 */
1920 public static function get_open_ssl_sign_algos() {
1921 $values = array();
1922
1923 if ( defined( 'OPENSSL_ALGO_SHA1' ) ) {
1924 $values[ OPENSSL_ALGO_SHA1 ] = 'OPENSSL_ALGO_SHA1';
1925 }
1926 if ( defined( 'OPENSSL_ALGO_SHA224' ) ) {
1927 $values[ OPENSSL_ALGO_SHA224 ] = 'OPENSSL_ALGO_SHA224';
1928 }
1929
1930 if ( defined( 'OPENSSL_ALGO_SHA256' ) ) {
1931 $values[ OPENSSL_ALGO_SHA256 ] = 'OPENSSL_ALGO_SHA256 ' . esc_html__( '(Default)', 'mainwp' );
1932 }
1933
1934 if ( defined( 'OPENSSL_ALGO_SHA384' ) ) {
1935 $values[ OPENSSL_ALGO_SHA384 ] = 'OPENSSL_ALGO_SHA384';
1936 }
1937
1938 if ( defined( 'OPENSSL_ALGO_SHA512' ) ) {
1939 $values[ OPENSSL_ALGO_SHA512 ] = 'OPENSSL_ALGO_SHA512';
1940 }
1941
1942 return $values;
1943 }
1944
1945 /**
1946 * Method get_default_map_site_fields()
1947 *
1948 * Get default map site fields.
1949 */
1950 public static function get_default_map_site_fields() {
1951 return array(
1952 'id',
1953 'url',
1954 'name',
1955 'adminname',
1956 'privkey',
1957 'http_user',
1958 'http_pass',
1959 'ssl_version',
1960 'sync_errors',
1961 'signature_algo',
1962 'verify_method',
1963 'suspended',
1964 );
1965 }
1966
1967 /**
1968 * Method get_select_staging_view_sites()
1969 *
1970 * Get staging options sites view for current users.
1971 *
1972 * @return string Site views.
1973 */
1974 public static function get_select_staging_view_sites() {
1975
1976 $view = get_user_option( 'mainwp_staging_options_updates_view' );
1977
1978 if ( empty( $view ) ) {
1979 $view = 'livesites';
1980 }
1981
1982 return apply_filters( 'mainwp_staging_current_user_sites_view', $view );
1983 }
1984
1985 /**
1986 * Method get_custom_nonce().
1987 *
1988 * @param string $type Type.
1989 * @param string $slug Slug to create nonce.
1990 * @return string
1991 */
1992 public static function get_custom_nonce( $type, $slug ) {
1993 global $current_user;
1994 if ( empty( $current_user ) || empty( $current_user->ID ) ) {
1995 return '';
1996 }
1997 return wp_create_nonce( $type . '-' . $current_user->ID . '-' . $slug );
1998 }
1999
2000 /**
2001 * Method is_valid_custom_nonce().
2002 *
2003 * @param string $type Type.
2004 * @param string $slug Slug to create nonce.
2005 * @param string $nonce Nonce value.
2006 * @return string
2007 */
2008 public static function is_valid_custom_nonce( $type, $slug, $nonce ) {
2009 global $current_user;
2010 if ( empty( $current_user ) || empty( $current_user->ID ) ) {
2011 return false;
2012 }
2013 return wp_verify_nonce( $nonce, $type . '-' . $current_user->ID . '-' . $slug );
2014 }
2015
2016
2017 /**
2018 * Method get_http_version_const_str().
2019 *
2020 * @param int $http_ver_int Version value.
2021 *
2022 * @return string Version const string.
2023 */
2024 public static function get_http_version_const_str( $http_ver_int ) {
2025 $const_names = array(
2026 'CURL_HTTP_VERSION_1_0',
2027 'CURL_HTTP_VERSION_1_1',
2028 'CURL_HTTP_VERSION_2',
2029 'CURL_HTTP_VERSION_2TLS',
2030 'CURL_HTTP_VERSION_2_0',
2031 'CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE',
2032 'CURL_HTTP_VERSION_3',
2033 'CURL_HTTP_VERSION_3ONLY',
2034 'CURL_HTTP_VERSION_NONE',
2035 );
2036 foreach ( $const_names as $const ) {
2037 if ( defined( $const ) && constant( $const ) === $http_ver_int ) {
2038 return $const;
2039 }
2040 }
2041 return $http_ver_int;
2042 }
2043
2044
2045 /**
2046 * Method test_self_connect()
2047 *
2048 * Server self-connect test.
2049 *
2050 * @param string $url Dashboard self-connect URL.
2051 *
2052 * @return array|WP_Error
2053 */
2054 public static function test_self_connect( $url ) {
2055
2056 $secret = static::get_self_connect_secret();
2057 $timestamp = time();
2058
2059 if ( empty( $secret ) ) {
2060 return new \WP_Error(
2061 'mainwp_self_connect_secret',
2062 __( 'Unable to generate self-connect secret.', 'mainwp' )
2063 );
2064 }
2065
2066 $message = 'mainwp_self_connect|' . $timestamp;
2067
2068 $signature = hash_hmac(
2069 'sha256',
2070 $message,
2071 $secret
2072 );
2073
2074 /**
2075 * Filter: https_local_ssl_verify
2076 *
2077 * Filters whether the server-self check shoul verify SSL Cert.
2078 *
2079 * @since Unknown
2080 */
2081 $args = array(
2082 'blocking' => true,
2083 'sslverify' => apply_filters( 'https_local_ssl_verify', true ),
2084 'timeout' => 15,
2085 'headers' => array(
2086 'X-MainWP-Self-Connect-Timestamp' => (string) $timestamp,
2087 'X-MainWP-Self-Connect-Signature' => $signature,
2088 ),
2089 'body' => array(
2090 'action' => 'mainwp_self_connect',
2091 'mainwp_run' => 'self_connect',
2092 ),
2093 );
2094 return wp_remote_post( $url, $args );
2095 }
2096
2097
2098 /**
2099 * Method handle_self_connect()
2100 *
2101 * Server self-connect response.
2102 *
2103 * @return void
2104 */
2105 public function handle_self_connect() {
2106
2107 if ( ! $this->verify_self_connect_request() ) {
2108 status_header( 403 );
2109 exit;
2110 }
2111
2112 status_header( 200 );
2113 header( 'Content-Type: text/plain; charset=utf-8' );
2114
2115 echo 'MainWP Self Connect OK';
2116 exit;
2117 }
2118
2119
2120 /**
2121 * Method verify_self_connect_request()
2122 *
2123 * Server self-connect test.
2124 *
2125 * @return bool
2126 */
2127 private function verify_self_connect_request() {
2128
2129 $signature = isset( $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_SIGNATURE'] )
2130 ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_SIGNATURE'] ) )
2131 : '';
2132
2133 $timestamp = isset( $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_TIMESTAMP'] )
2134 ? (int) $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_TIMESTAMP']
2135 : 0;
2136
2137 if ( 64 !== strlen( $signature ) || empty( $timestamp ) ) {
2138 return false;
2139 }
2140
2141 // Prevent replay attacks.
2142 if ( abs( time() - $timestamp ) > 300 ) {
2143 return false;
2144 }
2145
2146 $secret = static::get_self_connect_secret();
2147
2148 if ( empty( $secret ) ) {
2149 return false;
2150 }
2151
2152 $message = 'mainwp_self_connect|' . $timestamp;
2153
2154 $expected = hash_hmac(
2155 'sha256',
2156 $message,
2157 $secret
2158 );
2159
2160 return hash_equals( $expected, $signature );
2161 }
2162
2163
2164 /**
2165 * Method get_self_connect_secret()
2166 *
2167 * @return string Self connect secret.
2168 */
2169 public static function get_self_connect_secret() {
2170
2171 $option_name = 'mainwp_self_connect_secret';
2172 $secret = get_option( $option_name, '' );
2173
2174 if ( ! is_string( $secret ) || 64 !== strlen( $secret ) ) {
2175 try {
2176 $secret = bin2hex( random_bytes( 32 ) );
2177 } catch ( \Exception $e ) {
2178 return '';
2179 }
2180
2181 update_option( $option_name, $secret, false );
2182 }
2183
2184 return $secret;
2185 }
2186 }
2187