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

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

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