PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
← All changes | class/class-mainwp-connect.php +532 -151 6.1.4 → 6.2.2 View file →
@@ -412,20 +412,33 @@
412 412 *
413 413 * @param mixed $website Array of Child Site Info.
414 414 * @param mixed $what What we are posting.
415 415 * @param null $params Post parameters.
416 + * @param array $others Other data.
416 417 *
417 418 * @return mixed null|http_build_query()
418 419 */
419 - 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 +
420 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.
421 434 $data = array();
422 435 $data['user'] = $website->adminname;
423 436 $data['function'] = $what;
424 - $data['nonce'] = wp_rand( 0, 9999 );
437 + $data['nonce'] = bin2hex( random_bytes( 16 ) );
425 438 $data['mainwpver'] = MainWP_System::$version;
426 439
427 - $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 );
428 441 if ( is_array( $params_filter ) && ! empty( $params_filter ) ) {
429 442 $data = array_merge( $data, $params_filter );
430 443 }
431 444
@@ -436,48 +449,12 @@
436 449 $alg = false;
437 450 $sign_success = null;
438 451 $use_seclib = false;
439 452
440 - $data = apply_filters( 'mainwp_get_post_data_authed', $data, $website, $what, $params );
441 - if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
442 - $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.
443 - $use_seclib = true;
444 - } elseif ( function_exists( 'openssl_verify' ) ) {
445 - $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
446 - $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.
447 - if ( false !== $alg ) {
448 - $data['sign_algo'] = $alg;
449 - }
450 - }
453 + $data = apply_filters( 'mainwp_get_post_data_authed', $data, $website, $what, $params, $verify_signature );
451 454
452 - if ( $use_seclib ) {
453 - $data['verifylib'] = 1;
454 - }
455 + $alt_user = '';
455 456
456 - if ( null !== $sign_success && empty( $sign_success ) ) {
457 - $sign_error = '';
458 - while ( $msg = openssl_error_string() ) {
459 - if ( is_string( $msg ) ) {
460 - $sign_error .= $msg;
461 - }
462 - }
463 - $pk_info = ! empty( $website->privkey ) ? substr( $website->privkey, 0, 10 ) : '';
464 - 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 );
465 - }
466 -
467 - $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
468 -
469 - /** This filter is documented in ../widgets/widget-mainwp-recent-posts.php */
470 - $recent_number = apply_filters( 'mainwp_recent_posts_pages_number', 5 );
471 - if ( 5 !== $recent_number ) {
472 - $data['recent_number'] = $recent_number;
473 - }
474 -
475 - $scan_dir = apply_filters( 'mainwp_stats_scan_dir', false, $website );
476 - if ( ! empty( $scan_dir ) ) {
477 - $data['scan_dir'] = 1;
478 - }
479 -
480 457 /**
481 458 * Current user global.
482 459 *
483 460 * @global string
@@ -494,14 +471,125 @@
494 471 * @param int $current_user->ID User ID.
495 472 *
496 473 * @since Unknown
497 474 */
498 - $alter_user = apply_filters( 'mainwp_alter_login_user', false, $website->id, $current_user->ID );
499 - if ( ! empty( $alter_user ) ) {
500 - $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 + }
501 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';
502 508 }
503 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 + }
504 592 return http_build_query( $data, '', '&' );
505 593 }
506 594
507 595 return null;
@@ -513,20 +601,29 @@
513 601 * Get authorized $_POST data & build query for renew connection action only.
514 602 *
515 603 * @param mixed $website Array of Child Site Info.
516 604 * @param mixed $what What we are posting.
605 + * @param array $others Other data.
517 606 *
518 607 * @return mixed null|http_build_query()
519 608 */
520 - 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.
521 610
611 + if ( ! is_array( $others ) ) {
612 + $others = array();
613 + }
614 +
615 + $verify_signature = ! empty( $others['verify_signature'] ) ? true : false;
616 +
522 617 if ( $website && '' !== $what ) {
523 618 $compat_what = 'disconnect'; // to compatible, renew will call disconnect.
524 619 $data = array();
525 620 $data['user'] = $website->adminname;
526 621 $data['function'] = $compat_what;
527 - $data['nonce'] = wp_rand( 0, 9999 );
622 + $data['nonce'] = bin2hex( random_bytes( 16 ) );
528 623
624 + $sign_value = $compat_what . $data['nonce']; // compatible format.
625 +
529 626 $alg = false;
530 627 $sign_success = null;
531 628 $use_seclib = false;
532 629
@@ -531,17 +628,17 @@
531 628 $use_seclib = false;
532 629
533 630 if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
534 631 // to disconnect.
535 - $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.
536 633 $use_seclib = true;
537 634 } elseif ( function_exists( 'openssl_verify' ) ) {
538 635 $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
539 - $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.
540 637 if ( empty( $sign_success ) ) { // error from openssl, openssl_sign().
541 638 $alg = defined( 'OPENSSL_ALGO_SHA1' ) ? OPENSSL_ALGO_SHA1 : false; // to set default SHA1, to disconnect.
542 639 MainWP_Logger::instance()->debug_for_website( $website, 'get_renew_post_data_authed', '[' . $website->url . '] :: [openssl_sign:failed] :: Set sign_algo=SHA1' );
543 - $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.
544 641 }
545 642
546 643 if ( false !== $alg ) {
547 644 $data['sign_algo'] = $alg;
@@ -547,11 +644,9 @@
547 644 $data['sign_algo'] = $alg;
548 645 }
549 646 }
550 647
551 - if ( $use_seclib ) {
552 - $data['verifylib'] = 1;
553 - }
648 + $data['mainwpsignature'] = ! empty( $signature ) ? base64_encode( $signature ) : ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
554 649
555 650 if ( null !== $sign_success && empty( $sign_success ) ) {
556 651 $sign_error = '';
557 652 while ( $msg = openssl_error_string() ) {
@@ -561,10 +656,55 @@
561 656 }
562 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 );
563 658 }
564 659
565 - $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 );
566 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 +
567 707 return http_build_query( $data, '', '&' );
568 708 }
569 709 return null;
570 710 }
@@ -579,12 +719,16 @@
579 719 * @param mixed $paramValue OpenSSL parameter.
580 720 * @param string $paramName Parameter name.
581 721 * @param bool $asArray true|false Default is false.
582 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.
583 727 *
584 728 * @return string $url
585 729 */
586 - 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.
587 731 $params = array();
588 732 if ( $website && '' !== $paramValue ) {
589 733
590 734 $sign_success = null;
@@ -589,27 +733,51 @@
589 733
590 734 $sign_success = null;
591 735 $alg = false;
592 736 $use_seclib = false;
593 - $nonce = wp_rand( 0, 9999 );
594 - if ( MainWP_Connect_Lib::is_use_fallback_sec_lib( $website ) ) {
595 - $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.
596 - $use_seclib = true;
597 - } elseif ( function_exists( 'openssl_verify' ) ) {
598 - $alg = MainWP_System_Utility::get_connect_sign_algorithm( $website );
599 - $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 );
600 750 }
601 751
602 - $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'] );
603 753
604 - if ( null !== $sign_success && empty( $sign_success ) ) {
605 - $sign_error = '';
606 - while ( $msg = openssl_error_string() ) {
607 - if ( is_string( $msg ) ) {
608 - $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 + }
609 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 );
610 777 }
611 - 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.
612 780 }
613 781
614 782 $params = array(
615 783 'login_required' => 1,
@@ -618,8 +786,62 @@
618 786 'nonce' => $nonce,
619 787 $paramName => rawurlencode( $paramValue ),
620 788 );
621 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 +
622 844 if ( is_array( $other_params ) ) {
623 845 foreach ( $other_params as $name => $value ) {
624 846 if ( is_string( $name ) && ! empty( $name ) && is_scalar( $value ) ) {
625 847 $params[ sanitize_text_field( wp_unslash( $name ) ) ] = rawurlencode( sanitize_text_field( wp_unslash( $value ) ) );
@@ -639,30 +861,11 @@
639 861 }
640 862 }
641 863 }
642 864
643 - if ( false !== $alg ) {
644 - $params['sign_algo'] = $alg;
645 - }
646 -
647 865 if ( ! empty( $use_seclib ) ) {
648 866 $params['verifylib'] = 1;
649 867 }
650 -
651 - /**
652 - * Current user global.
653 - *
654 - * @global string
655 - */
656 - global $current_user;
657 -
658 - if ( ( ( ! defined( 'DOING_CRON' ) || false === DOING_CRON ) && ( ! defined( 'WP_CLI' ) || false === WP_CLI ) ) && $current_user && $current_user->ID ) {
659 - /** This filter is documented in ../class/class-mainwp-connect.php */
660 - $alter_user = apply_filters( 'mainwp_alter_login_user', false, $website->id, $current_user->ID );
661 - if ( ! empty( $alter_user ) ) {
662 - $params['alt_user'] = rawurlencode( $alter_user );
663 - }
664 - }
665 868 }
666 869
667 870 if ( $asArray ) {
668 871 return $params;
@@ -667,9 +870,13 @@
667 870 if ( $asArray ) {
668 871 return $params;
669 872 }
670 873
671 - $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 + }
672 879 $url .= ( substr( $url, - 1 ) !== '/' ? '/' : '' );
673 880 $url .= '?';
674 881
675 882 foreach ( $params as $key => $value ) {
@@ -737,8 +944,27 @@
737 944 return null;
738 945 }
739 946
740 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 + /**
741 967 * Method fetch_urls_authed()
742 968 *
743 969 * Fetches data from child sites if authenticated.
744 970 *
@@ -783,9 +1009,26 @@
783 1009
784 1010 $agent = 'Mozilla/5.0 (compatible; MainWP/' . MainWP_System::$version . '; +http://mainwp.com)';
785 1011 $mh = curl_multi_init();
786 1012
787 - $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 + }
788 1031
789 1032 $disabled_functions = ini_get( 'disable_functions' );
790 1033 $handleToWebsite = array();
791 1034 $requestUrls = array();
@@ -976,10 +1219,12 @@
976 1219 }
977 1220 }
978 1221
979 1222 if ( empty( $disabled_functions ) || ( false === stristr( $disabled_functions, 'curl_multi_exec' ) ) ) {
980 - $lastRun = 0;
1223 + $lastRun = 0;
1224 + $retry_added = false;
981 1225 do {
1226 + $retry_added = false;
982 1227 if ( 20 < time() - $lastRun ) {
983 1228 MainWP_System_Utility::set_time_limit( $timeout );
984 1229 $lastRun = time();
985 1230 }
@@ -995,24 +1240,25 @@
995 1240 if ( ! $contains && isset( $requestUrls[ $rid ] ) ) {
996 1241 curl_setopt( $info['handle'], CURLOPT_URL, $requestUrls[ $rid ] );
997 1242 curl_setopt( $info['handle'], CURLOPT_FRESH_CONNECT, true );
998 1243 curl_setopt( $info['handle'], CURLOPT_FORBID_REUSE, true );
999 - curl_multi_add_handle( $mh, $info['handle'] );
1000 - unset( $requestUrls[ $rid ] );
1001 - 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 + }
1002 1256 }
1003 1257
1004 1258 if ( ! $contains ) {
1005 - // Add useful debug log for unexpected response.
1006 - $log_data = (string) $data;
1007 - if ( is_string( $log_data ) && strlen( $log_data ) > 2000 ) {
1008 - $log_data = substr( $log_data, 0, 2000 ) . '...[truncated]';
1009 - }
1010 - $log_url = isset( $requestUrls[ $rid ] ) ? $requestUrls[ $rid ] : '';
1011 - if ( empty( $log_url ) ) {
1012 - $log_url = ( isset( $handleToWebsite[ $rid ] ) && is_object( $handleToWebsite[ $rid ] ) && property_exists( $handleToWebsite[ $rid ], 'url' ) ) ? $handleToWebsite[ $rid ]->url : 'Unknown';
1013 - }
1014 - MainWP_Logger::instance()->debug( 'curl_multi_getcontent :: unexpected response :: [data=' . $log_data . '] :: [url=' . $log_url . ']' );
1259 + $log_website = isset( $handleToWebsite[ $rid ] ) ? $handleToWebsite[ $rid ] : null;
1260 + MainWP_Logger::instance()->debug( static::format_unexpected_response_log( $log_website, $data ) );
1015 1261 }
1016 1262
1017 1263 if ( null !== $handler ) {
1018 1264 $site = &$handleToWebsite[ static::get_resource_id( $info['handle'] ) ];
@@ -1025,9 +1271,9 @@
1025 1271 }
1026 1272 unset( $info['handle'] );
1027 1273 }
1028 1274 usleep( 10000 );
1029 - } while ( $running > 0 );
1275 + } while ( $running > 0 || $retry_added );
1030 1276
1031 1277 if ( static::is_valid_curl_handle( $mh ) ) {
1032 1278 curl_multi_close( $mh );
1033 1279 }
@@ -1188,8 +1434,9 @@
1188 1434 * @param bool $checkConstraints Whether or not to check constraints.
1189 1435 * @param bool $pForceFetch Whether or not to force the fetch.
1190 1436 * @param bool $pRetryFailed Whether or not to retry the fetch process.
1191 1437 * @param null $rawResponse Raw response.
1438 + * @param bool $verify_signature Wether verify signature data.
1192 1439 *
1193 1440 * @return mixed $information
1194 1441 *
1195 1442 * @uses \MainWP\Dashboard\MainWP_Monitoring_Handler::handle_check_website()
@@ -1202,9 +1449,10 @@
1202 1449 $params = null,
1203 1450 $checkConstraints = false,
1204 1451 $pForceFetch = false,
1205 1452 $pRetryFailed = true,
1206 - $rawResponse = null
1453 + $rawResponse = null,
1454 + $verify_signature = false
1207 1455 ) {
1208 1456 unset( $pForceFetch );
1209 1457
1210 1458 // to support demo data.
@@ -1239,9 +1487,9 @@
1239 1487 * @return mixed Array to return early, false to proceed normally.
1240 1488 */
1241 1489 $is_phpunit_env = defined( 'WP_TESTS_DOMAIN' ) || defined( 'PHPUNIT_COMPOSER_INSTALL' ) || ( defined( 'WP_TESTS_DIR' ) && WP_TESTS_DIR );
1242 1490 if ( defined( 'MAINWP_TESTING_MODE' ) && MAINWP_TESTING_MODE && $is_phpunit_env ) {
1243 - $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 );
1244 1492 if ( false !== $pre_result ) {
1245 1493 return $pre_result;
1246 1494 }
1247 1495 }
@@ -1254,10 +1502,8 @@
1254 1502 'force_use_ipv4' => $website->force_use_ipv4,
1255 1503 'upgrade' => ( 'upgradeplugintheme' === $what || 'upgrade' === $what || 'upgradetranslation' === $what ),
1256 1504 );
1257 1505
1258 - $request_update = MainWP_Premium_Update::maybe_request_premium_updates( $website, $what, $params );
1259 -
1260 1506 if ( isset( $rawResponse ) && $rawResponse ) {
1261 1507 $others['raw_response'] = 'yes';
1262 1508 }
1263 1509
@@ -1265,11 +1511,17 @@
1265 1511
1266 1512 $updating_website = false;
1267 1513 $type = '';
1268 1514 $list = '';
1269 - 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 ) {
1270 1519 $updating_website = true;
1271 - if ( 'upgradeplugintheme' === $what || 'upgradetranslation' === $what ) {
1520 + if ( $premium_update ) {
1521 + $type = $params['premium_type'];
1522 + $list = $params['list'];
1523 + } elseif ( 'upgradeplugintheme' === $what || 'upgradetranslation' === $what ) {
1272 1524 $type = $params['type'];
1273 1525 $list = $params['list'];
1274 1526 } else {
1275 1527 $type = 'wp';
@@ -1291,52 +1543,80 @@
1291 1543 */
1292 1544 do_action( 'mainwp_website_before_updated', $website, $type, $list );
1293 1545 }
1294 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 +
1295 1564 if ( 'renew' === $what ) {
1296 - $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 + );
1297 1572 } else {
1298 - $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 + );
1299 1583
1300 1584 }
1585 +
1301 1586 $others['function'] = $what;
1302 1587
1303 - $information = array();
1304 - $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 );
1305 1589
1306 - if ( ! $request_update ) {
1307 - // MWP-1548: decrypt http_user / http_pass before they hit the
1308 - // outbound HTTP Basic Auth header. Legacy plaintext rows pass
1309 - // through unchanged.
1310 - $http_user_plain = MainWP_Credential_Storage::decrypt_credential( $website->http_user );
1311 - $http_pass_plain = MainWP_Credential_Storage::decrypt_credential( $website->http_pass );
1312 - $information = static::fetch_url( $website, $website->url, $postdata, $checkConstraints, $website->verify_certificate, $pRetryFailed, $http_user_plain, $http_pass_plain, $website->ssl_version, $others, $output );
1313 - if ( ! empty( $output ) ) {
1314 - if ( ! is_array( $information ) ) {
1315 - $information = array();
1316 - }
1317 - $information['fetch_url_output'] = $output;
1590 + if ( ! empty( $output ) ) {
1591 + if ( ! is_array( $information ) ) {
1592 + $information = array();
1318 1593 }
1319 - /**
1320 - * Fires immediately after fetch url action.
1321 - *
1322 - * @param object $website website.
1323 - * @param array $information information result data.
1324 - * @param string $what action.
1325 - * @param array $params params input array.
1326 - * @param array $others others input array.
1327 - *
1328 - * @since 4.5.1.1
1329 - */
1330 - do_action( 'mainwp_fetch_url_authed', $website, $information, $what, $params, $others );
1331 - } else {
1332 - $slug = $params['list'];
1333 - $information['upgrades'] = array( $slug => 1 );
1594 + $information['fetch_url_output'] = $output;
1334 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 );
1335 1608
1336 - if ( is_array( $information ) && isset( $information['sync'] ) && ! empty( $information['sync'] ) ) {
1337 - MainWP_Sync::sync_information_array( $website, $information['sync'] );
1338 - 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 + }
1339 1619 }
1340 1620
1341 1621 if ( $updating_website ) {
1342 1622 /**
@@ -1391,8 +1671,72 @@
1391 1671 $sslVersion = 0,
1392 1672 $others = array(),
1393 1673 &$output = array()
1394 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 +
1395 1739 unset( $pForceFetch );
1396 1740
1397 1741 if ( empty( $params ) ) {
1398 1742 $params = array();
@@ -1684,9 +2028,25 @@
1684 2028 if ( $force_use_ipv4 && defined( 'CURLOPT_IPRESOLVE' ) && defined( 'CURL_IPRESOLVE_V4' ) ) {
1685 2029 curl_setopt( $ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
1686 2030 }
1687 2031
1688 - $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 +
1689 2049 curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
1690 2050 MainWP_System_Utility::set_time_limit( $timeout );
1691 2051
1692 2052 MainWP_Utility::end_session();
@@ -1772,16 +2132,17 @@
1772 2132 MainWP_Logger::instance()->log_execution_time( 'fetch_url_site :: [url=' . $url . ']' );
1773 2133
1774 2134 $thr_error = null;
1775 2135
1776 - if ( isset( $others['function'] ) ) {
1777 - $what = $others['function'];
1778 - if ( in_array( $what, array( 'installplugintheme', 'upgradeplugintheme', 'upgradetranslation', 'upgrade', 'stats', 'renew', 'reconnect' ), true ) ) {
1779 - MainWP_Cache_Helper::invalidate_cache_group( MainWP_Cache_Helper::CGR_UPDATES );
1780 - MainWP_Cache_Warm_Helper::invalidate_pages_by_site_actions( $what );
1781 - }
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 );
1782 2139 }
1783 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 +
1784 2145 if ( ( false === $data ) && empty( $http_status ) ) {
1785 2146 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url', '[' . $url . '] HTTP Error: [status=0][' . $err . ']' );
1786 2147 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1787 2148 $output['error_category'] = 'http_error';
@@ -1803,8 +2164,27 @@
1803 2164 $error_code = is_array( $information ) && isset( $information['error_code'] ) ? sanitize_text_field( wp_unslash( $information['error_code'] ) ) : '';
1804 2165 if ( ! empty( $error_code ) ) {
1805 2166 $output['child_error_code'] = $error_code;
1806 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 +
1807 2187 return $information;
1808 2188 } elseif ( 200 === (int) $http_status && ! empty( $err ) ) {
1809 2189 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1810 2190 $output['error_category'] = 'http_error';
@@ -1809,8 +2189,9 @@
1809 2189 $thr_error = new MainWP_Exception( 'HTTPERROR', $err ); //phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
1810 2190 $output['error_category'] = 'http_error';
1811 2191 $output['error_code'] = 'http_request_failed';
1812 2192 $output['error_message'] = $err;
2193 + MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', '[' . $url . '] error [' . $err . ']' ); //phpcs:ignore -- ok.
1813 2194 } elseif ( $raw_response ) {
1814 2195 MainWP_Logger::instance()->debug_for_website( $website, 'fetch_url_site', 'Response: [RAW]' );
1815 2196 return $data;
1816 2197 } else {