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.3 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 All 156 releases
← All changes | class/class-mainwp-connect.php +541 -142 6.1 → 6.2.1 View file →
@@ -205,10 +205,15 @@
205 205 $host = wp_parse_url( ( empty( $realurl ) ? $url : $realurl ), PHP_URL_HOST );
206 206 $ip = false;
207 207 $target = false;
208 208
209 + // Ask only for the record types read below. dns_get_record() defaults to DNS_ANY, which
210 + // most resolvers now refuse or answer with a stub (RFC 8482), so it buys retries and
211 + // timeouts instead of answers. Names that exist only in the hosts file are not resolved
212 + // here at all -- dns_get_record() never reads the hosts file -- they fall through to the
213 + // gethostbynamel() call below.
209 214 $found = false;
210 - $dnsRecord = @dns_get_record( $host );
215 + $dnsRecord = @dns_get_record( $host, DNS_A | DNS_AAAA | DNS_CNAME );
211 216 MainWP_Logger::instance()->debug( ' :: tryVisit :: [dnsRecord=' . MainWP_Utility::value_to_string( $dnsRecord, 1 ) . ']' );
212 217
213 218 if ( false !== $dnsRecord && is_array( $dnsRecord ) ) {
214 219 if ( ! isset( $dnsRecord['ip'] ) ) {
@@ -407,20 +412,33 @@
407 412 *
408 413 * @param mixed $website Array of Child Site Info.
409 414 * @param mixed $what What we are posting.
410 415 * @param null $params Post parameters.
416 + * @param array $others Other data.
411 417 *
412 418 * @return mixed null|http_build_query()
413 419 */
414 - public static function get_post_data_authed( &$website, $what, $params = null ) { //phpcs:ignore -- NOSONAR - complex method.
420 + public static function get_post_data_authed( &$website, $what, $params = null, $others = array() ) { //phpcs:ignore -- NOSONAR - complex method.
421 +
422 + if ( ! is_array( $others ) ) {
423 + $others = array();
424 + }
425 +
426 + $verify_signature = ! empty( $others['verify_signature'] ) ? true : false;
427 + $http_user_plain = ! empty( $others['http_user_plain'] ) ? $others['http_user_plain'] : '';
428 + $http_pass_plain = ! empty( $others['http_pass_plain'] ) ? $others['http_pass_plain'] : '';
429 +
415 430 if ( $website && '' !== $what ) {
431 + // The nonce must never repeat. The Child stores every legacy signature it accepts, forever,
432 + // and refuses a repeat as "This request has already been used"; signing is deterministic,
433 + // so the nonce is the only thing that makes two requests for the same action differ.
416 434 $data = array();
417 435 $data['user'] = $website->adminname;
418 436 $data['function'] = $what;
419 - $data['nonce'] = wp_rand( 0, 9999 );
437 + $data['nonce'] = bin2hex( random_bytes( 16 ) );
420 438 $data['mainwpver'] = MainWP_System::$version;
421 439
422 - $params_filter = apply_filters( 'mainwp_pre_fetch_authed_data', false, $params, $what, $website );
440 + $params_filter = apply_filters( 'mainwp_pre_fetch_authed_data', false, $params, $what, $website, $verify_signature );
423 441 if ( is_array( $params_filter ) && ! empty( $params_filter ) ) {
424 442 $data = array_merge( $data, $params_filter );
425 443 }
426 444
@@ -431,48 +449,12 @@
431 449 $alg = false;
432 450 $sign_success = null;
433 451 $use_seclib = false;
434 452
435 - $data = apply_filters( 'mainwp_get_post_data_authed', $data, $website, $what, $params );
436 - if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
437 - $sign_success = MainWP_Connect_Lib::connect_sign( $what . $data['nonce'], $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
438 - $use_seclib = true;
439 - } elseif ( function_exists( 'openssl_verify' ) ) {
440 - $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
441 - $sign_success = static::connect_sign( $what . $data['nonce'], $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
442 - if ( false !== $alg ) {
443 - $data['sign_algo'] = $alg;
444 - }
445 - }
453 + $data = apply_filters( 'mainwp_get_post_data_authed', $data, $website, $what, $params, $verify_signature );
446 454
447 - if ( $use_seclib ) {
448 - $data['verifylib'] = 1;
449 - }
455 + $alt_user = '';
450 456
451 - if ( null !== $sign_success && empty( $sign_success ) ) {
452 - $sign_error = '';
453 - while ( $msg = openssl_error_string() ) {
454 - if ( is_string( $msg ) ) {
455 - $sign_error .= $msg;
456 - }
457 - }
458 - $pk_info = ! empty( $website->privkey ) ? substr( $website->privkey, 0, 10 ) : '';
459 - MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT SIGN', 'FAILED :: [what=' . ( is_string( $what ) ? $what : '' ) . '] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . '] :: [pkey start =' . $pk_info . '...]', false );
460 - }
461 -
462 - $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
463 -
464 - /** This filter is documented in ../widgets/widget-mainwp-recent-posts.php */
465 - $recent_number = apply_filters( 'mainwp_recent_posts_pages_number', 5 );
466 - if ( 5 !== $recent_number ) {
467 - $data['recent_number'] = $recent_number;
468 - }
469 -
470 - $scan_dir = apply_filters( 'mainwp_stats_scan_dir', false, $website );
471 - if ( ! empty( $scan_dir ) ) {
472 - $data['scan_dir'] = 1;
473 - }
474 -
475 457 /**
476 458 * Current user global.
477 459 *
478 460 * @global string
@@ -489,14 +471,125 @@
489 471 * @param int $current_user->ID User ID.
490 472 *
491 473 * @since Unknown
492 474 */
493 - $alter_user = apply_filters( 'mainwp_alter_login_user', false, $website->id, $current_user->ID );
494 - if ( ! empty( $alter_user ) ) {
495 - $data['alt_user'] = rawurlencode( $alter_user );
475 + $alt_user = apply_filters( 'mainwp_alter_login_user', false, $website->id, $current_user->ID );
476 +
477 + }
478 +
479 + $child_support_adv_sign = 1 === (int) MainWP_DB::instance()->get_website_option( $website, 'support_advanced_sign' );
480 +
481 + $alg = false;
482 + $sign_value = $what . $data['nonce']; // Legacy signature data.
483 +
484 + if ( ! $verify_signature || ! $child_support_adv_sign ) {
485 + if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
486 + $sign_success = MainWP_Connect_Lib::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
487 + $use_seclib = true;
488 + } elseif ( function_exists( 'openssl_verify' ) ) {
489 + $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
490 + $sign_success = static::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
491 + if ( false !== $alg ) {
492 + $data['sign_algo'] = $alg;
493 + }
496 494 }
495 +
496 + if ( null !== $sign_success && empty( $sign_success ) ) {
497 + $sign_error = '';
498 + while ( $msg = openssl_error_string() ) {
499 + if ( is_string( $msg ) ) {
500 + $sign_error .= $msg;
501 + }
502 + }
503 + $pk_info = ! empty( $website->privkey ) ? substr( $website->privkey, 0, 10 ) : '';
504 + MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT SIGN', 'FAILED :: [what=' . ( is_string( $what ) ? $what : '' ) . '] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . '] :: [pkey start =' . $pk_info . '...]', false );
505 + }
506 + } else {
507 + $signature = 'useadvancedmainwpsignature';
497 508 }
498 509
510 + $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
511 +
512 + if ( $verify_signature ) {
513 + $ts = time();
514 + $data_sign_v2 = array(
515 + 'base_function' => $what,
516 + 'nonce' => $data['nonce'],
517 + 'expires' => $ts + 60,
518 + 'user' => $website->adminname,
519 + 'req_id' => wp_generate_uuid4(),
520 + );
521 +
522 + if ( ! empty( $alt_user ) ) {
523 + $data_sign_v2['alt_user'] = rawurlencode( $alt_user );
524 + }
525 +
526 + if ( 'process_premium_updates' === $what ) {
527 + $add_sign_params = array( 'premium_perform', 'premium_type', 'list' );
528 + foreach ( $add_sign_params as $_name ) {
529 + if ( isset( $params[ $_name ] ) ) {
530 + $data_sign_v2[ $_name ] = $params[ $_name ];
531 + }
532 + }
533 + }
534 +
535 + $sign_success_v2 = null;
536 +
537 + $sign_value_v2 = wp_json_encode( $data_sign_v2 );
538 +
539 + if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
540 + $use_seclib = true;
541 + $sign_success_v2 = MainWP_Connect_Lib::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
542 + } elseif ( function_exists( 'openssl_verify' ) ) {
543 + $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
544 + $sign_success_v2 = static::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
545 + if ( false !== $alg ) {
546 + $data['sign_algo'] = $alg;
547 + }
548 + }
549 +
550 + $data['data_signature'] = $sign_value_v2;
551 + $data['mainwpsignature_adv'] = ! empty( $signature_v2 ) ? base64_encode( $signature_v2 ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
552 +
553 + if ( null !== $sign_success_v2 && empty( $sign_success_v2 ) ) {
554 + $sign_error = '';
555 + while ( $msg = openssl_error_string() ) {
556 + if ( is_string( $msg ) ) {
557 + $sign_error .= $msg;
558 + }
559 + }
560 + $pk_info = ! empty( $website->privkey ) ? substr( $website->privkey, 0, 10 ) : '';
561 + MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT ADV SIGN', 'FAILED :: [what=' . ( is_string( $what ) ? $what : '' ) . '] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . '] :: [pkey start =' . $pk_info . '...]', false );
562 + }
563 + }
564 +
565 + if ( $use_seclib ) {
566 + $data['verifylib'] = 1;
567 + }
568 +
569 + if ( ! empty( $alt_user ) ) {
570 + $data['alt_user'] = rawurlencode( $alt_user );
571 + }
572 +
573 + /** This filter is documented in ../widgets/widget-mainwp-recent-posts.php */
574 + $recent_number = apply_filters( 'mainwp_recent_posts_pages_number', 5 );
575 + if ( 5 !== $recent_number ) {
576 + $data['recent_number'] = $recent_number;
577 + }
578 +
579 + $scan_dir = apply_filters( 'mainwp_stats_scan_dir', false, $website );
580 + if ( ! empty( $scan_dir ) ) {
581 + $data['scan_dir'] = 1;
582 + }
583 +
584 + if ( 'process_premium_updates' === $what ) {
585 + if ( ! empty( $http_user_plain ) && ! empty( $http_pass_plain ) ) {
586 + // For post data.
587 + $data['wp_http_user'] = $http_user_plain;
588 + $data['wp_http_pass'] = $http_pass_plain;
589 + }
590 + $data['sslVerify'] = $website->verify_certificate ? 1 : 0;
591 + }
499 592 return http_build_query( $data, '', '&' );
500 593 }
501 594
502 595 return null;
@@ -508,20 +601,29 @@
508 601 * Get authorized $_POST data & build query for renew connection action only.
509 602 *
510 603 * @param mixed $website Array of Child Site Info.
511 604 * @param mixed $what What we are posting.
605 + * @param array $others Other data.
512 606 *
513 607 * @return mixed null|http_build_query()
514 608 */
515 - private static function get_renew_post_data_authed( &$website, $what ) { // phpcs:ignore -- NOSONAR - complex.
609 + private static function get_renew_post_data_authed( &$website, $what, $others = array() ) { // phpcs:ignore -- NOSONAR - complex.
516 610
611 + if ( ! is_array( $others ) ) {
612 + $others = array();
613 + }
614 +
615 + $verify_signature = ! empty( $others['verify_signature'] ) ? true : false;
616 +
517 617 if ( $website && '' !== $what ) {
518 618 $compat_what = 'disconnect'; // to compatible, renew will call disconnect.
519 619 $data = array();
520 620 $data['user'] = $website->adminname;
521 621 $data['function'] = $compat_what;
522 - $data['nonce'] = wp_rand( 0, 9999 );
622 + $data['nonce'] = bin2hex( random_bytes( 16 ) );
523 623
624 + $sign_value = $compat_what . $data['nonce']; // compatible format.
625 +
524 626 $alg = false;
525 627 $sign_success = null;
526 628 $use_seclib = false;
527 629
@@ -526,17 +628,17 @@
526 628 $use_seclib = false;
527 629
528 630 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
529 631 // to disconnect.
530 - $sign_success = MainWP_Connect_Lib::connect_sign( $compat_what . $data['nonce'], $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
632 + $sign_success = MainWP_Connect_Lib::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
531 633 $use_seclib = true;
532 634 } elseif ( function_exists( 'openssl_verify' ) ) {
533 635 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
534 - $sign_success = static::connect_sign( $compat_what . $data['nonce'], $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
636 + $sign_success = static::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
535 637 if ( empty( $sign_success ) ) { // error from openssl, openssl_sign().
536 638 $alg = defined( 'OPENSSL_ALGO_SHA1' ) ? OPENSSL_ALGO_SHA1 : false; // to set default SHA1, to disconnect.
537 639 MainWP_Logger::instance()->debug_for_website( $website, 'get_renew_post_data_authed', '[' . $website->url . '] :: [openssl_sign:failed] :: Set sign_algo=SHA1' );
538 - $sign_success = static::connect_sign( $compat_what . $data['nonce'], $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
640 + $sign_success = static::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
539 641 }
540 642
541 643 if ( false !== $alg ) {
542 644 $data['sign_algo'] = $alg;
@@ -542,11 +644,9 @@
542 644 $data['sign_algo'] = $alg;
543 645 }
544 646 }
545 647
546 - if ( $use_seclib ) {
547 - $data['verifylib'] = 1;
548 - }
648 + $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
549 649
550 650 if ( null !== $sign_success && empty( $sign_success ) ) {
551 651 $sign_error = '';
552 652 while ( $msg = openssl_error_string() ) {
@@ -556,10 +656,55 @@
556 656 }
557 657 MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT SIGN', 'FAILED :: [what=' . ( is_string( $what ) ? $what : '' ) . '] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . ']', false );
558 658 }
559 659
560 - $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
660 + if ( $verify_signature ) {
661 + $ts = time();
662 + $data_sign_v2 = array(
663 + 'base_function' => $compat_what,
664 + 'nonce' => $data['nonce'],
665 + 'expires' => $ts + 60,
666 + 'user' => $website->adminname,
667 + 'req_id' => wp_generate_uuid4(),
668 + );
669 + $sign_value_v2 = wp_json_encode( $data_sign_v2 );
561 670
671 + $sign_success_v2 = null;
672 +
673 + if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
674 + // to disconnect.
675 + $sign_success_v2 = MainWP_Connect_Lib::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
676 + } elseif ( function_exists( 'openssl_verify' ) ) {
677 + $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
678 + $sign_success_v2 = static::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
679 + if ( empty( $sign_success_v2 ) ) { // error from openssl, openssl_sign().
680 + $alg = defined( 'OPENSSL_ALGO_SHA1' ) ? OPENSSL_ALGO_SHA1 : false; // to set default SHA1, to disconnect.
681 + MainWP_Logger::instance()->debug_for_website( $website, 'get_renew_post_data_authed', '[' . $website->url . '] :: [openssl_sign:failed] :: Set sign_algo=SHA1' );
682 + $sign_success_v2 = static::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for keys encoding.
683 + }
684 + if ( false !== $alg ) {
685 + $data['sign_algo'] = $alg;
686 + }
687 + }
688 +
689 + $data['data_signature'] = $sign_value_v2;
690 + $data['mainwpsignature_adv'] = ! empty( $signature_v2 ) ? base64_encode( $signature_v2 ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
691 +
692 + if ( null !== $sign_success_v2 && empty( $sign_success_v2 ) ) {
693 + $sign_error = '';
694 + while ( $msg = openssl_error_string() ) {
695 + if ( is_string( $msg ) ) {
696 + $sign_error .= $msg;
697 + }
698 + }
699 + MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT ADV SIGN', 'FAILED :: [what=' . ( is_string( $what ) ? $what : '' ) . '] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . ']', false );
700 + }
701 + }
702 +
703 + if ( $use_seclib ) {
704 + $data['verifylib'] = 1;
705 + }
706 +
562 707 return http_build_query( $data, '', '&' );
563 708 }
564 709 return null;
565 710 }
@@ -574,12 +719,16 @@
574 719 * @param mixed $paramValue OpenSSL parameter.
575 720 * @param string $paramName Parameter name.
576 721 * @param bool $asArray true|false Default is false.
577 722 * @param array $other_params other params.
723 + * @param string $custom_url Optional. Override the target base URL for browser-bound
724 + * requests ( see MainWP_Site_Url_Corrector::browser_target_url() ).
725 + * Default null keeps the stored URL � server-side callers
726 + * ( backups, premium updates ) must not pass this.
578 727 *
579 728 * @return string $url
580 729 */
581 - public static function get_get_data_authed( $website, $paramValue, $paramName = 'where', $asArray = false, $other_params = array() ) { //phpcs:ignore -- NOSONAR - complex method.
730 + public static function get_get_data_authed( $website, $paramValue, $paramName = 'where', $asArray = false, $other_params = array(), $custom_url = null ) { //phpcs:ignore -- NOSONAR - complex method.
582 731 $params = array();
583 732 if ( $website && '' !== $paramValue ) {
584 733
585 734 $sign_success = null;
@@ -584,27 +733,51 @@
584 733
585 734 $sign_success = null;
586 735 $alg = false;
587 736 $use_seclib = false;
588 - $nonce = wp_rand( 0, 9999 );
589 - if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
590 - $sign_success = MainWP_Connect_Lib::connect_sign( $paramValue . $nonce, $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
591 - $use_seclib = true;
592 - } elseif ( function_exists( 'openssl_verify' ) ) {
593 - $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
594 - $sign_success = static::connect_sign( $paramValue . $nonce, $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
737 + $nonce = bin2hex( random_bytes( 16 ) );
738 +
739 + /**
740 + * Current user global.
741 + *
742 + * @global string
743 + */
744 + global $current_user;
745 +
746 + $alt_user = '';
747 + if ( ( ( ! defined( 'DOING_CRON' ) || false === DOING_CRON ) && ( ! defined( 'WP_CLI' ) || false === WP_CLI ) ) && $current_user && $current_user->ID ) {
748 + /** This filter is documented in ../class/class-mainwp-connect.php */
749 + $alt_user = apply_filters( 'mainwp_alter_login_user', false, $website->id, $current_user->ID );
595 750 }
596 751
597 - $signature = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
752 + $verify_signature = is_array( $other_params ) && ! empty( $other_params['verify_signature'] );
598 753
599 - if ( null !== $sign_success && empty( $sign_success ) ) {
600 - $sign_error = '';
601 - while ( $msg = openssl_error_string() ) {
602 - if ( is_string( $msg ) ) {
603 - $sign_error .= $msg;
754 + $child_support_adv_sign = 1 === (int) MainWP_DB::instance()->get_website_option( $website, 'support_advanced_sign' );
755 +
756 + if ( ! $verify_signature || ! $child_support_adv_sign ) {
757 + $sign_value = $paramValue . $nonce; // compatible format.
758 +
759 + if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
760 + $sign_success = MainWP_Connect_Lib::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
761 + $use_seclib = true;
762 + } elseif ( function_exists( 'openssl_verify' ) ) {
763 + $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
764 + $sign_success = static::connect_sign( $sign_value, $signature, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
765 + }
766 +
767 + $signature = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
768 +
769 + if ( null !== $sign_success && empty( $sign_success ) ) {
770 + $sign_error = '';
771 + while ( $msg = openssl_error_string() ) {
772 + if ( is_string( $msg ) ) {
773 + $sign_error .= $msg;
774 + }
604 775 }
776 + MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT SIGN', 'FAILED :: [login_required=1] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . ']', false );
605 777 }
606 - MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT SIGN', 'FAILED :: [login_required=1] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . ']', false );
778 + } else {
779 + $signature = base64_encode( 'useadvancedmainwpsignature' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
607 780 }
608 781
609 782 $params = array(
610 783 'login_required' => 1,
@@ -613,8 +786,62 @@
613 786 'nonce' => $nonce,
614 787 $paramName => rawurlencode( $paramValue ),
615 788 );
616 789
790 + if ( false !== $alg ) {
791 + $params['sign_algo'] = $alg;
792 + }
793 +
794 + if ( $verify_signature ) {
795 + $ts = time();
796 + $data_sign_v2 = array(
797 + 'base_function' => $paramValue,
798 + 'where' => rawurlencode( $paramName ),
799 + 'nonce' => $nonce,
800 + 'expires' => $ts + 60,
801 + 'user' => $website->adminname,
802 + 'req_id' => wp_generate_uuid4(),
803 + );
804 +
805 + if ( ! empty( $alt_user ) ) {
806 + $data_sign_v2['alt_user'] = rawurlencode( $alt_user );
807 + }
808 +
809 + $sign_success_v2 = null;
810 +
811 + $sign_value_v2 = wp_json_encode( $data_sign_v2 );
812 +
813 + if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
814 + $sign_success_v2 = MainWP_Connect_Lib::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
815 + $use_seclib = true;
816 + } elseif ( function_exists( 'openssl_verify' ) ) {
817 + $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
818 + $sign_success_v2 = static::connect_sign( $sign_value_v2, $signature_v2, base64_decode( $website->privkey ), $alg, $website->id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
819 + if ( false !== $alg ) {
820 + $params['sign_algo'] = $alg;
821 + }
822 + }
823 +
824 + $signature_v2 = ! empty( $signature_v2 ) ? base64_encode( $signature_v2 ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
825 +
826 + $params['data_signature'] = rawurlencode( $sign_value_v2 );
827 + $params['mainwpsignature_adv'] = rawurlencode( $signature_v2 );
828 +
829 + if ( null !== $sign_success_v2 && empty( $sign_success_v2 ) ) {
830 + $sign_error = '';
831 + while ( $msg = openssl_error_string() ) {
832 + if ( is_string( $msg ) ) {
833 + $sign_error .= $msg;
834 + }
835 + }
836 + MainWP_Logger::instance()->warning_for_website( $website, 'CONNECT ADV SIGN', 'FAILED :: [login_required=1] :: [seclib=' . intval( $use_seclib ) . '] :: [algorithm=' . $alg . '] :: [openssl_sign error =' . $sign_error . ']', false );
837 + }
838 + }
839 +
840 + if ( ! empty( $alt_user ) ) {
841 + $params['alt_user'] = rawurlencode( $alt_user );
842 + }
843 +
617 844 if ( is_array( $other_params ) ) {
618 845 foreach ( $other_params as $name => $value ) {
619 846 if ( is_string( $name ) && ! empty( $name ) && is_scalar( $value ) ) {
620 847 $params[ sanitize_text_field( wp_unslash( $name ) ) ] = rawurlencode( sanitize_text_field( wp_unslash( $value ) ) );
@@ -634,30 +861,11 @@
634 861 }
635 862 }
636 863 }
637 864
638 - if ( false !== $alg ) {
639 - $params['sign_algo'] = $alg;
640 - }
641 -
642 865 if ( ! empty( $use_seclib ) ) {
643 866 $params['verifylib'] = 1;
644 867 }
645 -
646 - /**
647 - * Current user global.
648 - *
649 - * @global string
650 - */
651 - global $current_user;
652 -
653 - if ( ( ( ! defined( 'DOING_CRON' ) || false === DOING_CRON ) && ( ! defined( 'WP_CLI' ) || false === WP_CLI ) ) && $current_user && $current_user->ID ) {
654 - /** This filter is documented in ../class/class-mainwp-connect.php */
655 - $alter_user = apply_filters( 'mainwp_alter_login_user', false, $website->id, $current_user->ID );
656 - if ( ! empty( $alter_user ) ) {
657 - $params['alt_user'] = rawurlencode( $alter_user );
658 - }
659 - }
660 868 }
661 869
662 870 if ( $asArray ) {
663 871 return $params;
@@ -662,9 +870,13 @@
662 870 if ( $asArray ) {
663 871 return $params;
664 872 }
665 873
666 - $url = ( isset( $website->url ) && '' !== $website->url ? $website->url : $website->siteurl );
874 + if ( null !== $custom_url && '' !== $custom_url ) {
875 + $url = $custom_url;
876 + } else {
877 + $url = ( isset( $website->url ) && '' !== $website->url ? $website->url : $website->siteurl );
878 + }
667 879 $url .= ( substr( $url, - 1 ) !== '/' ? '/' : '' );
668 880 $url .= '?';
669 881
670 882 foreach ( $params as $key => $value ) {
@@ -732,8 +944,27 @@
732 944 return null;
733 945 }
734 946
735 947 /**
948 + * Format a privacy-safe diagnostic for an unexpected Child response.
949 + *
950 + * @param mixed $website Website information.
951 + * @param mixed $data Raw response data.
952 + *
953 + * @return string Redacted diagnostic message.
954 + */
955 + private static function format_unexpected_response_log( $website, $data ) {
956 + $site_id = 0;
957 + if ( is_object( $website ) && property_exists( $website, 'id' ) && 0 < (int) $website->id ) {
958 + $site_id = (int) $website->id;
959 + }
960 +
961 + $response_bytes = is_string( $data ) ? strlen( $data ) : 0;
962 +
963 + return 'curl_multi_getcontent :: unexpected response :: [siteid=' . $site_id . '] :: [response_bytes=' . $response_bytes . ']';
964 + }
965 +
966 + /**
736 967 * Method fetch_urls_authed()
737 968 *
738 969 * Fetches data from child sites if authenticated.
739 970 *
@@ -778,9 +1009,26 @@
778 1009
779 1010 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
780 1011 $mh = curl_multi_init();
781 1012
782 - $timeout = 20 * 60 * 60;
1013 + /**
1014 + * Filter: mainwp_fetch_url_site_timeout
1015 + *
1016 + * Filters the request timeout ( CURLOPT_TIMEOUT + PHP time limit ) used
1017 + * for child site requests. Defaults to 20 hours to accommodate the
1018 + * longest operations ( upgrades, backups ); short-lived callers such as
1019 + * the URL-correction verify probe bound it much tighter.
1020 + *
1021 + * @param int $timeout Timeout in seconds. Default 72000 ( 20 hours ).
1022 + * Values below 1 are ignored ( 0 would disable the
1023 + * cURL timeout entirely ) and fall back to the default.
1024 + *
1025 + * @since 6.2
1026 + */
1027 + $timeout = (int) apply_filters( 'mainwp_fetch_url_site_timeout', 20 * 60 * 60 );
1028 + if ( $timeout <= 0 ) {
1029 + $timeout = 20 * 60 * 60;
1030 + }
783 1031
784 1032 $disabled_functions = ini_get( 'disable_functions' );
785 1033 $handleToWebsite = array();
786 1034 $requestUrls = array();
@@ -971,10 +1219,12 @@
971 1219 }
972 1220 }
973 1221
974 1222 if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
975 - $lastRun = 0;
1223 + $lastRun = 0;
1224 + $retry_added = false;
976 1225 do {
1226 + $retry_added = false;
977 1227 if ( 20 < time() - $lastRun ) {
978 1228 MainWP_System_Utility::set_time_limit( $timeout );
979 1229 $lastRun = time();
980 1230 }
@@ -990,13 +1240,27 @@
990 1240 if ( ! $contains && isset( $requestUrls[ $rid ] ) ) {
991 1241 curl_setopt( $info['handle'], CURLOPT_URL, $requestUrls[ $rid ] );
992 1242 curl_setopt( $info['handle'], CURLOPT_FRESH_CONNECT, true );
993 1243 curl_setopt( $info['handle'], CURLOPT_FORBID_REUSE, true );
994 - curl_multi_add_handle( $mh, $info['handle'] );
995 - unset( $requestUrls[ $rid ] );
996 - continue; // libcurl updates $running automatically.
1244 + $add_retry = curl_multi_add_handle( $mh, $info['handle'] );
1245 + if ( CURLM_OK === $add_retry ) {
1246 + $mrc = curl_multi_exec( $mh, $running );
1247 +
1248 + if ( CURLM_OK === $mrc ) {
1249 + $retry_added = true;
1250 + unset( $requestUrls[ $rid ] );
1251 + continue; // libcurl updates $running automatically.
1252 + }
1253 +
1254 + curl_multi_remove_handle( $mh, $info['handle'] );
1255 + }
997 1256 }
998 1257
1258 + if ( ! $contains ) {
1259 + $log_website = isset( $handleToWebsite[ $rid ] ) ? $handleToWebsite[ $rid ] : null;
1260 + MainWP_Logger::instance()->debug( static::format_unexpected_response_log( $log_website, $data ) );
1261 + }
1262 +
999 1263 if ( null !== $handler ) {
1000 1264 $site = &$handleToWebsite[ static::get_resource_id( $info['handle'] ) ];
1001 1265 call_user_func_array( $handler, array( $data, $site, &$output, $params ) );
1002 1266 }
@@ -1007,9 +1271,9 @@
1007 1271 }
1008 1272 unset( $info['handle'] );
1009 1273 }
1010 1274 usleep( 10000 );
1011 - } while ( $running > 0 );
1275 + } while ( $running > 0 || $retry_added );
1012 1276
1013 1277 if ( static::is_valid_curl_handle( $mh ) ) {
1014 1278 curl_multi_close( $mh );
1015 1279 }
@@ -1170,8 +1434,9 @@
1170 1434 * @param bool $checkConstraints Whether or not to check constraints.
1171 1435 * @param bool $pForceFetch Whether or not to force the fetch.
1172 1436 * @param bool $pRetryFailed Whether or not to retry the fetch process.
1173 1437 * @param null $rawResponse Raw response.
1438 + * @param bool $verify_signature Wether verify signature data.
1174 1439 *
1175 1440 * @return mixed $information
1176 1441 *
1177 1442 * @uses \MainWP\Dashboard\MainWP_Monitoring_Handler::handle_check_website()
@@ -1184,9 +1449,10 @@
1184 1449 $params = null,
1185 1450 $checkConstraints = false,
1186 1451 $pForceFetch = false,
1187 1452 $pRetryFailed = true,
1188 - $rawResponse = null
1453 + $rawResponse = null,
1454 + $verify_signature = false
1189 1455 ) {
1190 1456 unset( $pForceFetch );
1191 1457
1192 1458 // to support demo data.
@@ -1221,9 +1487,9 @@
1221 1487 * @return mixed Array to return early, false to proceed normally.
1222 1488 */
1223 1489 $is_phpunit_env = defined( 'WP_TESTS_DOMAIN' ) || defined( 'PHPUNIT_COMPOSER_INSTALL' ) || ( defined( 'WP_TESTS_DIR' ) && WP_TESTS_DIR );
1224 1490 if ( defined( 'MAINWP_TESTING_MODE' ) && MAINWP_TESTING_MODE && $is_phpunit_env ) {
1225 - $pre_result = apply_filters( 'mainwp_fetch_url_authed_pre', false, $website, $what, $params );
1491 + $pre_result = apply_filters( 'mainwp_fetch_url_authed_pre', false, $website, $what, $params, $verify_signature );
1226 1492 if ( false !== $pre_result ) {
1227 1493 return $pre_result;
1228 1494 }
1229 1495 }
@@ -1236,10 +1502,8 @@
1236 1502 'force_use_ipv4' => $website->force_use_ipv4,
1237 1503 'upgrade' => ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what ),
1238 1504 );
1239 1505
1240 - $request_update = MainWP_Premium_Update::maybe_request_premium_updates( $website, $what, $params );
1241 -
1242 1506 if ( isset( $rawResponse ) && $rawResponse ) {
1243 1507 $others['raw_response'] = 'yes';
1244 1508 }
1245 1509
@@ -1247,11 +1511,17 @@
1247 1511
1248 1512 $updating_website = false;
1249 1513 $type = '';
1250 1514 $list = '';
1251 - if ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what ) {
1515 +
1516 + $premium_update = 'process_premium_updates' === $what && ! empty( $params['premium_perform'] ) && 'premium_update' === $params['premium_perform'] ? true : false;
1517 +
1518 + if ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what || $premium_update ) {
1252 1519 $updating_website = true;
1253 - if ( 'upgradeplugintheme' === $what || 'upgradetranslation' === $what ) {
1520 + if ( $premium_update ) {
1521 + $type = $params['premium_type'];
1522 + $list = $params['list'];
1523 + } elseif ( 'upgradeplugintheme' === $what || 'upgradetranslation' === $what ) {
1254 1524 $type = $params['type'];
1255 1525 $list = $params['list'];
1256 1526 } else {
1257 1527 $type = 'wp';
@@ -1273,52 +1543,80 @@
1273 1543 */
1274 1544 do_action( 'mainwp_website_before_updated', $website, $type, $list );
1275 1545 }
1276 1546
1547 + $information = array();
1548 + $output = array();
1549 +
1550 + if ( 'stats' === $what || ( 'upgradeplugintheme' === $what && isset( $params['type'] ) ) ) {
1551 + $request_update = MainWP_Premium_Update::maybe_request_premium_updates( $website, $what, $params, $output_result );
1552 + if ( $request_update ) {
1553 + // Return the information here.
1554 + return $output_result;
1555 + }
1556 + }
1557 +
1558 + // MWP-1548: decrypt http_user / http_pass before they hit the
1559 + // outbound HTTP Basic Auth header. Legacy plaintext rows pass
1560 + // through unchanged.
1561 + $http_user_plain = MainWP_Credential_Storage::decrypt_credential( $website->http_user );
1562 + $http_pass_plain = MainWP_Credential_Storage::decrypt_credential( $website->http_pass );
1563 +
1277 1564 if ( 'renew' === $what ) {
1278 - $postdata = static::get_renew_post_data_authed( $website, $what );
1565 + $postdata = static::get_renew_post_data_authed(
1566 + $website,
1567 + $what,
1568 + array(
1569 + 'verify_signature' => $verify_signature,
1570 + )
1571 + );
1279 1572 } else {
1280 - $postdata = static::get_post_data_authed( $website, $what, $params );
1573 + $postdata = static::get_post_data_authed(
1574 + $website,
1575 + $what,
1576 + $params,
1577 + array(
1578 + 'verify_signature' => $verify_signature,
1579 + 'http_user_plain' => $http_user_plain,
1580 + 'http_pass_plain' => $http_pass_plain,
1581 + )
1582 + );
1281 1583
1282 1584 }
1585 +
1283 1586 $others['function'] = $what;
1284 1587
1285 - $information = array();
1286 - $output = array();
1588 + $information = static::fetch_url( $website, $website->url, $postdata, $checkConstraints, $website->verify_certificate, $pRetryFailed, $http_user_plain, $http_pass_plain, $website->ssl_version, $others, $output );
1287 1589
1288 - if ( ! $request_update ) {
1289 - // MWP-1548: decrypt http_user / http_pass before they hit the
1290 - // outbound HTTP Basic Auth header. Legacy plaintext rows pass
1291 - // through unchanged.
1292 - $http_user_plain = MainWP_Credential_Storage::decrypt_credential( $website->http_user );
1293 - $http_pass_plain = MainWP_Credential_Storage::decrypt_credential( $website->http_pass );
1294 - $information = static::fetch_url( $website, $website->url, $postdata, $checkConstraints, $website->verify_certificate, $pRetryFailed, $http_user_plain, $http_pass_plain, $website->ssl_version, $others, $output );
1295 - if ( ! empty( $output ) ) {
1296 - if ( ! is_array( $information ) ) {
1297 - $information = array();
1298 - }
1299 - $information['fetch_url_output'] = $output;
1590 + if ( ! empty( $output ) ) {
1591 + if ( ! is_array( $information ) ) {
1592 + $information = array();
1300 1593 }
1301 - /**
1302 - * Fires immediately after fetch url action.
1303 - *
1304 - * @param object $website website.
1305 - * @param array $information information result data.
1306 - * @param string $what action.
1307 - * @param array $params params input array.
1308 - * @param array $others others input array.
1309 - *
1310 - * @since 4.5.1.1
1311 - */
1312 - do_action( 'mainwp_fetch_url_authed', $website, $information, $what, $params, $others );
1313 - } else {
1314 - $slug = $params['list'];
1315 - $information['upgrades'] = array( $slug => 1 );
1594 + $information['fetch_url_output'] = $output;
1316 1595 }
1596 + /**
1597 + * Fires immediately after fetch url action.
1598 + *
1599 + * @param object $website website.
1600 + * @param array $information information result data.
1601 + * @param string $what action.
1602 + * @param array $params params input array.
1603 + * @param array $others others input array.
1604 + *
1605 + * @since 4.5.1.1
1606 + */
1607 + do_action( 'mainwp_fetch_url_authed', $website, $information, $what, $params, $others );
1317 1608
1318 - if ( is_array( $information ) && isset( $information['sync'] ) && ! empty( $information['sync'] ) ) {
1319 - MainWP_Sync::sync_information_array( $website, $information['sync'] );
1320 - unset( $information['sync'] );
1609 + if ( is_array( $information ) ) {
1610 + if ( isset( $information['sync'] ) && ! empty( $information['sync'] ) ) {
1611 + MainWP_Sync::sync_information_array( $website, $information['sync'] );
1612 + unset( $information['sync'] );
1613 + }
1614 +
1615 + if ( isset( $information['sync_partial'] ) && is_array( $information['sync_partial'] ) && ! empty( $information['sync_partial'] ) ) {
1616 + MainWP_Sync::sync_partial_information( $website, $information['sync_partial'] );
1617 + unset( $information['sync_partial'] );
1618 + }
1321 1619 }
1322 1620
1323 1621 if ( $updating_website ) {
1324 1622 /**
@@ -1373,8 +1671,72 @@
1373 1671 $sslVersion = 0,
1374 1672 $others = array(),
1375 1673 &$output = array()
1376 1674 ) {
1675 +
1676 + /**
1677 + * Filter to mock the fetch_url_not_authed response before any HTTP request occurs.
1678 + *
1679 + * This filter fires early, before the HTTP request is performed, allowing
1680 + * tests to bypass child site communication entirely.
1681 + *
1682 + * SECURITY WARNING - TEST ONLY:
1683 + * This filter ONLY fires when ALL of the following conditions are met:
1684 + * 1. MAINWP_TESTING_MODE constant is defined and true.
1685 + * 2. A PHPUnit test harness constant is present (WP_TESTS_DOMAIN,
1686 + * PHPUNIT_COMPOSER_INSTALL, or WP_TESTS_DIR).
1687 + *
1688 + * These checks prevent malicious code from defining MAINWP_TESTING_MODE
1689 + * in production to spoof child site responses.
1690 + *
1691 + * IMPORTANT: MAINWP_TESTING_MODE must ONLY be defined in the PHPUnit bootstrap
1692 + * file (tests/bootstrap.php). Defining it in production code, wp-config.php,
1693 + * or plugin files would create a security vulnerability allowing response
1694 + * spoofing.
1695 + *
1696 + * @since 6.2.1
1697 + *
1698 + * @param mixed $pre_result Return non-false to short-circuit the request and return this value.
1699 + * @param string $url URL to fetch.
1700 + * @param string $admin Admin username or identifier.
1701 + * @param string $what Action being performed.
1702 + * @param array|null $params Request parameters.
1703 + * @param bool $pForceFetch Whether to force the fetch.
1704 + * @param bool|null $verifyCertificate Whether to verify the SSL certificate.
1705 + * @param string|null $http_user HTTP authentication username.
1706 + * @param string|null $http_pass HTTP authentication password.
1707 + * @param int $sslVersion SSL version.
1708 + * @param array $others Additional request options.
1709 + * @param array $output Output data passed by reference.
1710 + *
1711 + * @return mixed Array or other value to return early, false to proceed normally.
1712 + */
1713 + $is_phpunit_env = defined( 'WP_TESTS_DOMAIN' ) || defined( 'PHPUNIT_COMPOSER_INSTALL' ) || ( defined( 'WP_TESTS_DIR' ) && WP_TESTS_DIR );
1714 +
1715 + if ( defined( 'MAINWP_TESTING_MODE' ) && MAINWP_TESTING_MODE && $is_phpunit_env ) {
1716 + $pre_result = apply_filters_ref_array(
1717 + 'mainwp_fetch_url_not_authed_pre',
1718 + array(
1719 + false,
1720 + $url,
1721 + $admin,
1722 + $what,
1723 + $params,
1724 + $pForceFetch,
1725 + $verifyCertificate,
1726 + $http_user,
1727 + $http_pass,
1728 + $sslVersion,
1729 + $others,
1730 + &$output,
1731 + )
1732 + );
1733 +
1734 + if ( false !== $pre_result ) {
1735 + return $pre_result;
1736 + }
1737 + }
1738 +
1377 1739 unset( $pForceFetch );
1378 1740
1379 1741 if ( empty( $params ) ) {
1380 1742 $params = array();
@@ -1666,9 +2028,25 @@
1666 2028 if ( $force_use_ipv4 && defined( 'CURLOPT_IPRESOLVE' ) && defined( 'CURL_IPRESOLVE_V4' ) ) {
1667 2029 curl_setopt( $ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
1668 2030 }
1669 2031
1670 - $timeout = 20 * 60 * 60;
2032 + $what = '';
2033 + if ( is_array( $others ) && isset( $others['function'] ) ) {
2034 + $what = $others['function'];
2035 + }
2036 +
2037 + if ( 'deactivate' === $what ) {
2038 + $timeout = 120; // 2 minutes.
2039 + } else {
2040 + $timeout = 20 * 60 * 60;
2041 + }
2042 +
2043 + /** This filter is documented in class/class-mainwp-connect.php */
2044 + $timeout = (int) apply_filters( 'mainwp_fetch_url_site_timeout', $timeout, $what );
2045 + if ( $timeout <= 0 ) {
2046 + $timeout = 20 * 60 * 60; // values below 1 would disable the cURL timeout entirely.
2047 + }
2048 +
1671 2049 curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
1672 2050 MainWP_System_Utility::set_time_limit( $timeout );
1673 2051
1674 2052 MainWP_Utility::end_session();
@@ -1754,16 +2132,17 @@
1754 2132 MainWP_Logger::instance()->log_execution_time( 'fetch_url_site :: [url=' . $url . ']' );
1755 2133
1756 2134 $thr_error = null;
1757 2135
1758 - if ( isset( $others['function'] ) ) {
1759 - $what = $others['function'];
1760 - if ( in_array( $what, array( 'installplugintheme', 'upgradeplugintheme', 'upgradetranslation', 'upgrade', 'stats', 'renew', 'reconnect' ), true ) ) {
1761 - MainWP_Cache_Helper::invalidate_cache_group( MainWP_Cache_Helper::CGR_UPDATES );
1762 - MainWP_Cache_Warm_Helper::invalidate_pages_by_site_actions( $what );
1763 - }
2136 + if ( in_array( $what, array( 'installplugintheme', 'upgradeplugintheme', 'upgradetranslation', 'process_premium_updates', 'upgrade', 'stats', 'renew', 'reconnect' ), true ) ) {
2137 + MainWP_Cache_Helper::invalidate_cache_group( MainWP_Cache_Helper::CGR_UPDATES );
2138 + MainWP_Cache_Warm_Helper::invalidate_pages_by_site_actions( $what );
1764 2139 }
1765 2140
2141 + if ( 'process_premium_updates' === $what ) {
2142 + MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'function: [process_premium_updates] response data: [' . MainWP_Utility::value_to_string( $data ) . ']' );
2143 + }
2144 +
1766 2145 if ( ( false === $data ) && empty( $http_status ) ) {
1767 2146 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] HTTP Error: [status=0][' . $err . ']' );
1768 2147 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1769 2148 $output['error_category'] = 'http_error';
@@ -1785,8 +2164,27 @@
1785 2164 $error_code = is_array( $information ) && isset( $information['error_code'] ) ? sanitize_text_field( wp_unslash( $information['error_code'] ) ) : '';
1786 2165 if ( ! empty( $error_code ) ) {
1787 2166 $output['child_error_code'] = $error_code;
1788 2167 }
2168 +
2169 + if ( is_array( $information ) && $website ) {
2170 + // Process to ensure compatibility with old and new Child versions.
2171 + $new_sync = isset( $information['support_advanced_sign'] );
2172 + $old_sync = ! $new_sync && ( isset( $information['mainwpdir'], $information['uniqueId'] ) || isset( $information['version'], $information['wpversion'], $information['wpe'] ) );
2173 + if ( $new_sync || $old_sync ) {
2174 + $adv_sign_support = MainWP_DB::instance()->get_website_option( $website, 'support_advanced_sign' );
2175 + if ( ! empty( $information['support_advanced_sign'] ) && 0 === (int) $adv_sign_support ) {
2176 + MainWP_DB::instance()->update_website_option( $website, 'support_advanced_sign', 1 );
2177 + } elseif ( empty( $information['support_advanced_sign'] ) && 1 === (int) $adv_sign_support ) {
2178 + MainWP_DB::instance()->update_website_option( $website, 'support_advanced_sign', 0 );
2179 + }
2180 + }
2181 + }
2182 +
2183 + if ( 'process_premium_updates' === $what ) {
2184 + MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'function: [process_premium_updates] decoded data: [' . MainWP_Utility::value_to_string( $information ) . ']' );
2185 + }
2186 +
1789 2187 return $information;
1790 2188 } elseif ( 200 === (int) $http_status && ! empty( $err ) ) {
1791 2189 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1792 2190 $output['error_category'] = 'http_error';
@@ -1791,8 +2189,9 @@
1791 2189 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1792 2190 $output['error_category'] = 'http_error';
1793 2191 $output['error_code'] = 'http_request_failed';
1794 2192 $output['error_message'] = $err;
2193 + MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', '[' . $url . '] error [' . $err . ']' ); //phpcs:ignore -- ok.
1795 2194 } elseif ( $raw_response ) {
1796 2195 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Response: [RAW]' );
1797 2196 return $data;
1798 2197 } else {