PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / trunk
MainWP Dashboard: Self-hosted WordPress Management for Agencies vtrunk
6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 5.2.2 5.3 All 153 releases
← All changes | class/class-mainwp-system-handler.php +333 -17 5.2trunk View file →
@@ -6,8 +6,13 @@
6 6 */
7 7
8 8 namespace MainWP\Dashboard;
9 9
10 +// Exit if accessed directly.
11 +if ( ! defined( 'ABSPATH' ) ) {
12 + exit;
13 +}
14 +
10 15 /**
11 16 * Class MainWP_System_Handler
12 17 *
13 18 * @package MainWP\Dashboard
@@ -27,9 +32,9 @@
27 32
28 33 /**
29 34 * Private variable to hold the upgrade version info.
30 35 *
31 - * @var null Version info.
36 + * @var \stdClass|null Version info.
32 37 */
33 38 private $upgradeVersionInfo = null;
34 39
35 40 /**
@@ -66,12 +71,12 @@
66 71 *
67 72 * @see \MainWP_Extensions::hook_get_sites
68 73 */
69 74 add_filter( 'mainwp-getsites', array( MainWP_Extensions_Handler::get_class_name(), 'hook_get_sites' ), 10, 5 ); // @deprecated Use 'mainwp_getsites' instead.
70 - add_filter( 'mainwp-getdbsites', array( MainWP_Extensions_Handler::get_class_name(), 'hook_get_db_sites' ), 10, 5 ); // @deprecated Use 'mainwp_getdbsites' instead.
75 + add_filter( 'mainwp-getdbsites', array( MainWP_Extensions_Handler::get_class_name(), 'hook_get_db_sites' ), 10, 6 ); // @deprecated Use 'mainwp_getdbsites' instead.
71 76
72 77 add_filter( 'mainwp_getsites', array( MainWP_Extensions_Handler::get_class_name(), 'hook_get_sites' ), 10, 5 );
73 - add_filter( 'mainwp_getdbsites', array( MainWP_Extensions_Handler::get_class_name(), 'hook_get_db_sites' ), 10, 5 );
78 + add_filter( 'mainwp_getdbsites', array( MainWP_Extensions_Handler::get_class_name(), 'hook_get_db_sites' ), 10, 6 );
74 79 add_filter( 'mainwp_get_db_websites', array( MainWP_Extensions_Handler::get_class_name(), 'hook_get_db_websites' ), 10, 5 );
75 80
76 81 /**
77 82 * This hook allows you to get a information about groups via the 'mainwp-getgroups' filter.
@@ -220,8 +225,10 @@
220 225 if ( ! MainWP_System_Utility::is_admin() ) {
221 226 return;
222 227 }
223 228
229 + $this->handle_quick_theme_change();
230 +
224 231 global $pagenow;
225 232
226 233 if ( 'plugins.php' === $pagenow && isset( $_GET['do'] ) && 'checkUpgrade' === $_GET['do'] && ( ( time() - $this->upgradeVersionInfo->updated ) > 30 ) ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
227 234 $this->check_upgrade();
@@ -230,10 +237,8 @@
230 237 exit;
231 238 }
232 239 }
233 240
234 -
235 -
236 241 /**
237 242 * Method hook_get_sql_websites_for_current_user()
238 243 *
239 244 * Get sql websites for current user.
@@ -249,8 +254,16 @@
249 254 unset( $input_value );
250 255 if ( ! MainWP_Extensions_Handler::hook_verify( $pluginFile, $key ) ) {
251 256 return false;
252 257 }
258 +
259 + // Prevent conflicts in wp_options query conditions.
260 + // If use_join_wp_options is not enabled,
261 + // force the use of a compatible subquery approach to ensure the query remains correct.
262 + if ( is_array( $params ) && empty( $params['use_join_wp_options'] ) ) {
263 + $params['use_compatible_subquery'] = 1;
264 + }
265 +
253 266 return MainWP_DB::instance()->get_sql_wp_for_current_user( $params );
254 267 }
255 268 /**
256 269 * Method handle_manage_sites_screen_settings()
@@ -324,13 +337,8 @@
324 337 update_user_option( $user->ID, 'mainwp_settings_show_monitoring_sites_columns', $show_cols, true );
325 338 update_option( 'mainwp_default_monitoring_sites_per_page', ( isset( $_POST['mainwp_default_monitoring_sites_per_page'] ) ? intval( $_POST['mainwp_default_monitoring_sites_per_page'] ) : 25 ) );
326 339 }
327 340
328 - update_option( 'mainwp_disableSitesChecking', ( ! isset( $_POST['mainwp_disableSitesChecking'] ) ? 1 : 0 ) );
329 -
330 - $val = isset( $_POST['mainwp_frequency_sitesChecking'] ) ? intval( $_POST['mainwp_frequency_sitesChecking'] ) : 1440;
331 - update_option( 'mainwp_frequencySitesChecking', $val );
332 -
333 341 update_option( 'mainwp_disableSitesHealthMonitoring', ( ! isset( $_POST['mainwp_disable_sitesHealthMonitoring'] ) ? 1 : 0 ) );
334 342
335 343 $val = isset( $_POST['mainwp_site_healthThreshold'] ) ? intval( $_POST['mainwp_site_healthThreshold'] ) : 80;
336 344 update_option( 'mainwp_sitehealthThreshold', $val );
@@ -367,18 +375,52 @@
367 375 }
368 376 }
369 377
370 378 /**
379 + * Method handle_insights_events_screen_settings()
380 + *
381 + * Handle manage insights events screen settings
382 + */
383 + public function handle_insights_events_screen_settings() { // phpcs:ignore -- NOSONAR - complex.
384 + if ( isset( $_POST['wp_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_nonce'] ), 'ManageEventsScrOptions' ) ) {
385 + $show_cols = array();
386 + foreach ( array_map( 'sanitize_text_field', wp_unslash( $_POST ) ) as $key => $val ) {
387 + if ( false !== strpos( $key, 'mainwp_show_column_' ) ) {
388 + $col = str_replace( 'mainwp_show_column_', '', $key );
389 + $show_cols[ $col ] = 1;
390 + }
391 + }
392 + if ( isset( $_POST['show_columns_name'] ) ) {
393 + foreach ( array_map( 'sanitize_text_field', wp_unslash( $_POST['show_columns_name'] ) ) as $col ) {
394 + if ( ! isset( $show_cols[ $col ] ) ) {
395 + $show_cols[ $col ] = 0; // uncheck, hide columns.
396 + }
397 + }
398 + }
399 +
400 + $user = wp_get_current_user();
401 + if ( $user ) {
402 + update_user_option( $user->ID, 'mainwp_settings_show_insights_events_columns', $show_cols, true );
403 + update_option( 'mainwp_default_manage_insights_events_per_page', ( isset( $_POST['mainwp_default_sites_per_page'] ) ? intval( $_POST['mainwp_default_sites_per_page'] ) : 25 ) );
404 + }
405 + wp_safe_redirect( admin_url( 'admin.php?page=InsightsManage&message=savedscreenopts' ) );
406 + exit();
407 + }
408 + }
409 +
410 + /**
371 411 * Method handle_clients_screen_settings()
372 412 *
373 413 * Handle manage clients screen settings
374 414 */
375 - public function handle_updates_screen_settings() {
415 + public function handle_updates_screen_settings() { //phpcs:ignore -- NOSONAR - complex.
376 416 if ( isset( $_POST['wp_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_nonce'] ), 'UpdatesScrOptions' ) ) {
377 417 $val = ( ! isset( $_POST['mainwp_pluginAutomaticDailyUpdate'] ) ? 0 : intval( $_POST['mainwp_pluginAutomaticDailyUpdate'] ) );
378 418 MainWP_Utility::update_option( 'mainwp_pluginAutomaticDailyUpdate', $val );
379 419 $val = ( ! isset( $_POST['mainwp_themeAutomaticDailyUpdate'] ) ? 0 : intval( $_POST['mainwp_themeAutomaticDailyUpdate'] ) );
380 420 MainWP_Utility::update_option( 'mainwp_themeAutomaticDailyUpdate', $val );
421 + $val = ( ! isset( $_POST['mainwp_transAutomaticDailyUpdate'] ) ? 0 : intval( $_POST['mainwp_transAutomaticDailyUpdate'] ) );
422 + MainWP_Utility::update_option( 'mainwp_transAutomaticDailyUpdate', $val );
381 423 $val = ( ! isset( $_POST['mainwp_automaticDailyUpdate'] ) ? 0 : intval( $_POST['mainwp_automaticDailyUpdate'] ) );
382 424 MainWP_Utility::update_option( 'mainwp_automaticDailyUpdate', $val );
383 425 $val = ( isset( $_POST['mainwp_delay_autoupdate'] ) ? intval( $_POST['mainwp_delay_autoupdate'] ) : 1 );
384 426 MainWP_Utility::update_option( 'mainwp_delay_autoupdate', $val );
@@ -388,9 +430,43 @@
388 430 MainWP_Utility::update_option( 'mainwp_disable_update_confirmations', $val );
389 431 }
390 432 }
391 433
434 + /**
435 + * Method handle_quick_theme_change()
436 + *
437 + * Handle quick theme switching via URL parameter.
438 + */
439 + public function handle_quick_theme_change() {
440 + if ( ! isset( $_GET['mainwp_quick_theme_change'] ) || ! isset( $_GET['_wpnonce'] ) ) {
441 + return;
442 + }
392 443
444 + if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'mainwp_quick_theme_change' ) ) {
445 + wp_die( esc_html__( 'Security check failed', 'mainwp' ) );
446 + }
447 +
448 + $user = wp_get_current_user();
449 +
450 + if ( ! $user || ! $user->ID || $user->ID <= 0 ) {
451 + wp_die( esc_html__( 'Authentication required', 'mainwp' ) );
452 + }
453 +
454 + $theme = sanitize_text_field( wp_unslash( $_GET['mainwp_quick_theme_change'] ) );
455 +
456 + $allowed_themes = array( 'default', 'default-dark' );
457 + if ( ! in_array( $theme, $allowed_themes, true ) ) {
458 + wp_die( esc_html__( 'Invalid theme selection', 'mainwp' ) );
459 + }
460 +
461 + update_user_option( $user->ID, 'mainwp_selected_theme', $theme );
462 + set_transient( 'mainwp_settings_saved', 1, 30 );
463 +
464 + $redirect_url = remove_query_arg( array( 'mainwp_quick_theme_change', '_wpnonce' ) );
465 + wp_safe_redirect( $redirect_url );
466 + exit;
467 + }
468 +
393 469 /**
394 470 * Method handle_mainwp_tools_settings()
395 471 *
396 472 * Handle mainwp tools settings.
@@ -402,10 +478,14 @@
402 478
403 479 $user = wp_get_current_user();
404 480
405 481 $update_selected_mainwp_themes = false;
482 + $should_redirect = false; // Redirect status.
406 483
407 484 if ( isset( $_GET['page'] ) && 'MainWPTools' === $_GET['page'] && isset( $_POST['wp_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_nonce'] ), 'MainWPTools' ) ) {
485 +
486 + $old_settings = MainWP_Settings::get_all_settings_values();
487 +
408 488 if ( isset( $_POST['mainwp_restore_info_messages'] ) && ! empty( $_POST['mainwp_restore_info_messages'] ) ) {
409 489 delete_user_option( $user->ID, 'mainwp_notice_saved_status' );
410 490 }
411 491 $enabled_tours = ! isset( $_POST['mainwp-guided-tours-option'] ) ? 0 : 1;
@@ -410,15 +490,39 @@
410 490 }
411 491 $enabled_tours = ! isset( $_POST['mainwp-guided-tours-option'] ) ? 0 : 1;
412 492 MainWP_Utility::update_option( 'mainwp_enable_guided_tours', $enabled_tours );
413 493
494 + $enabled1 = ! isset( $_POST['mainwp-guided-chatbase-option'] ) ? 0 : 1;
495 + MainWP_Utility::update_option( 'mainwp_enable_guided_chatbase', $enabled1 );
496 +
497 + $enabled2 = ! isset( $_POST['mainwp-guided-video-option'] ) ? 0 : 1;
498 + MainWP_Utility::update_option( 'mainwp_enable_guided_video', $enabled2 );
499 +
414 500 if ( isset( $_POST['mainwp_settings_custom_theme'] ) ) {
415 501 $update_selected_mainwp_themes = true;
416 502 }
503 +
504 + $new_settings = MainWP_Settings::get_all_settings_values();
505 +
506 + /**
507 + * Action: mainwp_after_save_settings
508 + *
509 + * Fires after save settings.
510 + *
511 + * @since 6.0
512 + *
513 + * @param array $new_settings The new settings.
514 + * @param array $old_settings The old settings.
515 + */
516 + do_action( 'mainwp_after_save_settings', $new_settings, $old_settings );
517 + $should_redirect = true;
518 + set_transient( 'mainwp_settings_saved', 1, 30 );
417 519 }
418 520
419 521 if ( isset( $_POST['wp_scr_options_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_scr_options_nonce'] ), 'MainWPSelectThemes' ) ) {
420 522 $update_selected_mainwp_themes = true;
523 + $should_redirect = true;
524 + set_transient( 'mainwp_settings_saved', 1, 30 );
421 525 }
422 526
423 527 if ( $update_selected_mainwp_themes && isset( $_POST['mainwp_settings_custom_theme'] ) ) {
424 528 $custom_theme = sanitize_text_field( wp_unslash( $_POST['mainwp_settings_custom_theme'] ) );
@@ -487,8 +591,9 @@
487 591
488 592 if ( isset( $_POST['reset_overview_settings'] ) && ! empty( $_POST['reset_overview_settings'] ) && isset( $_POST['reset_overview_which_settings'] ) && 'overview_settings' === $_POST['reset_overview_which_settings'] ) {
489 593 update_user_option( $user->ID, 'mainwp_widgets_sorted_toplevel_page_mainwp_tab', false, true );
490 594 update_user_option( $user->ID, 'mainwp_widgets_sorted_mainwp_page_managesites', false, true );
595 + MainWP_Cache_Warm_Helper::invalidate_manage_pages( array( 'mainwp_tab' ) );
491 596 }
492 597 }
493 598
494 599 $update_clients_screen_options = false;
@@ -521,8 +626,15 @@
521 626 if ( isset( $_POST['reset_client_overview_settings'] ) && ! empty( $_POST['reset_client_overview_settings'] ) ) {
522 627 update_user_option( $user->ID, 'mainwp_widgets_sorted_mainwp_page_manageclients', false, true );
523 628 }
524 629 }
630 +
631 + // Redirect after save settings and redirect to the same page.
632 + if ( $should_redirect ) {
633 + $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : 'MainWPTools';
634 + wp_safe_redirect( admin_url( 'admin.php?page=' . $page ) );
635 + exit();
636 + }
525 637 }
526 638
527 639 /**
528 640 * Method handle_settings_post()
@@ -543,8 +655,9 @@
543 655 $this->handle_manage_sites_screen_settings();
544 656 $this->handle_monitoring_sites_screen_settings();
545 657 $this->handle_clients_screen_settings();
546 658 $this->handle_updates_screen_settings();
659 + $this->handle_insights_events_screen_settings();
547 660 }
548 661
549 662 if ( isset( $_POST['select_mainwp_options_siteview'] ) ) {
550 663 $this->include_pluggable();
@@ -569,9 +682,12 @@
569 682 }
570 683
571 684 if ( isset( $_POST['submit'] ) && isset( $_POST['wp_nonce'] ) ) {
572 685 $this->include_pluggable();
573 - if ( wp_verify_nonce( sanitize_key( $_POST['wp_nonce'] ), 'Settings' ) ) {
686 + $nonce = sanitize_key( $_POST['wp_nonce'] );
687 + // Check for both 'Settings' or 'MonitoringSettings' nonces.
688 + $is_valid_nonce = wp_verify_nonce( $nonce, 'Settings' ) || wp_verify_nonce( $nonce, 'MonitoringSettings' );
689 + if ( $is_valid_nonce ) {
574 690 $updated = MainWP_Settings::handle_settings_post();
575 691 $updated |= MainWP_Backup_Handler::handle_settings_post();
576 692 $updated |= MainWP_Monitoring_Handler::handle_settings_post();
577 693 $msg = '';
@@ -577,9 +693,15 @@
577 693 $msg = '';
578 694 if ( $updated ) {
579 695 $msg = '&message=saved';
580 696 }
581 - wp_safe_redirect( admin_url( 'admin.php?page=Settings' . $msg ) );
697 +
698 + // Redirect to the correct page based on the nonce.
699 + $url_page = 'admin.php?page=Settings' . $msg;
700 + if ( wp_verify_nonce( $nonce, 'MonitoringSettings' ) ) {
701 + $url_page = 'admin.php?page=MonitoringSettings' . $msg;
702 + }
703 + wp_safe_redirect( admin_url( $url_page ) );
582 704 exit();
583 705 }
584 706 }
585 707
@@ -603,8 +725,22 @@
603 725 exit;
604 726 }
605 727 }
606 728 }
729 +
730 + // Redirect current page after reset and save overview settings.
731 + if ( isset( $_GET['page'] ) && isset( $_POST['reset_overview_settings'] ) && ( isset( $_POST['reset_overview_which_settings'] ) && 'overview_settings' === $_POST['reset_overview_which_settings'] ) ) {
732 + $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
733 + $url = 'admin.php?page=' . $page;
734 +
735 + $dashboard_id = isset( $_GET['dashboard'] ) ? absint( $_GET['dashboard'] ) : 0;
736 + if ( ! empty( $dashboard_id ) ) {
737 + $url .= '&dashboard=' . $dashboard_id;
738 + }
739 +
740 + wp_safe_redirect( admin_url( $url ) );
741 + exit;
742 + }
607 743 }
608 744
609 745 /**
610 746 * Method include_pluggable()
@@ -887,21 +1023,170 @@
887 1023 *
888 1024 * @uses \MainWP\Dashboard\MainWP_API_Handler::check_exts_upgrade()
889 1025 * @uses \MainWP\Dashboard\MainWP_Utility::update_option()
890 1026 */
891 - private function check_upgrade() {
892 - $result = MainWP_API_Handler::check_exts_upgrade();
1027 + private function check_upgrade() { // phpcs:ignore -- NOSONAR - validates and preserves per-extension update state.
1028 + $expected_extensions = $this->get_expected_extension_updates();
1029 + $has_full_scope = $this->has_full_extension_update_scope( $expected_extensions );
1030 +
893 1031 if ( null === $this->upgradeVersionInfo ) {
894 1032 $this->upgradeVersionInfo = new \stdClass();
895 1033 }
1034 +
896 1035 $this->upgradeVersionInfo->updated = time();
897 - if ( ! empty( $result ) ) {
898 - $this->upgradeVersionInfo->result = $result;
1036 +
1037 + if ( empty( $expected_extensions ) ) {
1038 + if ( $has_full_scope ) {
1039 + $this->clear_extension_update_failure();
1040 + }
1041 + MainWP_Utility::update_option( 'mainwp_upgradeVersionInfo', $this->upgradeVersionInfo );
1042 + return $has_full_scope;
899 1043 }
1044 +
1045 + $result = MainWP_API_Handler::check_exts_upgrade();
1046 + $previous_result = property_exists( $this->upgradeVersionInfo, 'result' ) && is_array( $this->upgradeVersionInfo->result ) ? $this->upgradeVersionInfo->result : array();
1047 + $fresh_result = array();
1048 + $failed = ! is_array( $result );
1049 +
1050 + foreach ( $expected_extensions as $api_slug => $installed_version ) {
1051 + $fresh = is_array( $result ) && isset( $result[ $api_slug ] ) ? $result[ $api_slug ] : null;
1052 + if ( $this->is_usable_extension_update_result( $fresh, $api_slug, $installed_version ) ) {
1053 + $fresh_result[ $api_slug ] = $fresh;
1054 + } else {
1055 + $failed = true;
1056 + }
1057 + }
1058 +
1059 + if ( ! $failed ) {
1060 + $this->upgradeVersionInfo->result = $has_full_scope ? $fresh_result : array_merge( $previous_result, $fresh_result );
1061 + }
1062 +
1063 + if ( $has_full_scope ) {
1064 + if ( $failed ) {
1065 + $this->record_extension_update_failure();
1066 + } else {
1067 + $this->clear_extension_update_failure();
1068 + }
1069 + }
1070 +
900 1071 MainWP_Utility::update_option( 'mainwp_upgradeVersionInfo', $this->upgradeVersionInfo );
1072 + return ! $failed;
901 1073 }
902 1074
903 1075 /**
1076 + * Get Add-ons expected in an update-check response.
1077 + *
1078 + * @return array API slugs keyed to installed versions.
1079 + */
1080 + private function get_expected_extension_updates() {
1081 + $expected = array();
1082 +
1083 + foreach ( MainWP_Extensions_Handler::get_extensions() as $extension ) {
1084 + if ( ! isset( $extension['activated_key'] ) || 'Activated' !== $extension['activated_key'] || empty( $extension['api'] ) ) {
1085 + continue;
1086 + }
1087 +
1088 + $expected[ $extension['api'] ] = isset( $extension['version'] ) ? (string) $extension['version'] : '';
1089 + }
1090 +
1091 + return $expected;
1092 + }
1093 +
1094 + /**
1095 + * Check whether visible Add-ons cover the shared update cache.
1096 + *
1097 + * @param array $expected Visible Add-ons expected in the response.
1098 + *
1099 + * @return bool Whether this request covers every activated Add-on.
1100 + */
1101 + private function has_full_extension_update_scope( $expected ) {
1102 + $all_expected = array();
1103 +
1104 + foreach ( get_option( 'mainwp_extensions', array() ) as $extension ) {
1105 + if ( isset( $extension['activated_key'], $extension['api'] ) && 'Activated' === $extension['activated_key'] && '' !== $extension['api'] ) {
1106 + $all_expected[] = $extension['api'];
1107 + }
1108 + }
1109 +
1110 + $visible = array_keys( $expected );
1111 + sort( $all_expected );
1112 + sort( $visible );
1113 +
1114 + return $all_expected === $visible;
1115 + }
1116 +
1117 + /**
1118 + * Check whether an Add-on update result is safe to persist.
1119 + *
1120 + * @param mixed $result Update result.
1121 + * @param string $api_slug Expected API slug.
1122 + * @param string $installed_version Installed Add-on version.
1123 + *
1124 + * @return bool Whether the result is usable.
1125 + */
1126 + private function is_usable_extension_update_result( $result, $api_slug, $installed_version ) {
1127 + if ( ! is_object( $result ) || ! isset( $result->slug ) || $api_slug !== $result->slug || ! property_exists( $result, 'new_version' ) || ! is_string( $result->new_version ) ) {
1128 + return false;
1129 + }
1130 +
1131 + $new_version = trim( $result->new_version );
1132 + if ( '' === $new_version || ( property_exists( $result, 'error' ) && ! empty( $result->error ) ) ) {
1133 + return false;
1134 + }
1135 +
1136 + if ( version_compare( $new_version, $installed_version, '>' ) ) {
1137 + return property_exists( $result, 'package' ) && is_string( $result->package ) && '' !== trim( $result->package );
1138 + }
1139 +
1140 + return true;
1141 + }
1142 +
1143 + /**
1144 + * Record a failed Add-on update-check attempt.
1145 + */
1146 + private function record_extension_update_failure() {
1147 + $failure_count = property_exists( $this->upgradeVersionInfo, 'extension_update_failure_count' ) ? (int) $this->upgradeVersionInfo->extension_update_failure_count : 0;
1148 +
1149 + if ( $failure_count < 1 || ! property_exists( $this->upgradeVersionInfo, 'extension_update_failure_episode' ) || empty( $this->upgradeVersionInfo->extension_update_failure_episode ) ) {
1150 + $this->upgradeVersionInfo->extension_update_failure_episode = wp_generate_uuid4();
1151 + }
1152 +
1153 + $this->upgradeVersionInfo->extension_update_failure_count = $failure_count + 1;
1154 + }
1155 +
1156 + /**
1157 + * Clear Add-on update-check failure state after a complete response.
1158 + */
1159 + private function clear_extension_update_failure() {
1160 + unset( $this->upgradeVersionInfo->extension_update_failure_count, $this->upgradeVersionInfo->extension_update_failure_episode );
1161 + }
1162 +
1163 + /**
1164 + * Get the active repeated-failure notice ID.
1165 + *
1166 + * @return string Notice ID, or an empty string before the second consecutive failure.
1167 + */
1168 + public function get_extension_update_failure_notice_id() {
1169 + if ( ! $this->has_full_extension_update_scope( $this->get_expected_extension_updates() ) || ! is_object( $this->upgradeVersionInfo ) || ! property_exists( $this->upgradeVersionInfo, 'extension_update_failure_count' ) || 2 > (int) $this->upgradeVersionInfo->extension_update_failure_count || ! property_exists( $this->upgradeVersionInfo, 'extension_update_failure_episode' ) ) {
1170 + return '';
1171 + }
1172 +
1173 + $episode = sanitize_key( $this->upgradeVersionInfo->extension_update_failure_episode );
1174 + return '' !== $episode ? 'mainwp-extension-update-check-failure-' . $episode : '';
1175 + }
1176 +
1177 + /**
1178 + * Retry the Add-on update check and refresh WordPress update data.
1179 + *
1180 + * @return bool Whether every expected Add-on returned a usable result.
1181 + */
1182 + public function retry_extension_update_check() {
1183 + $success = $this->check_upgrade();
1184 + delete_site_transient( 'update_plugins' );
1185 + return $success;
1186 + }
1187 +
1188 + /**
904 1189 * Method pre_check_update_custom()
905 1190 *
906 1191 * Pre-check for extension updates.
907 1192 *
@@ -916,8 +1201,14 @@
916 1201 if ( ! isset( $transient->checked ) ) {
917 1202 return $transient;
918 1203 }
919 1204
1205 + if ( defined( 'MAINWP_PLUGIN_UPGRADING' ) && true === MAINWP_PLUGIN_UPGRADING ) {
1206 + return $transient;
1207 + }
1208 +
1209 + MainWP_Logger::instance()->log_events( 'extension-updates-check', 'pre_set_site_transient_update_plugins' );
1210 +
920 1211 if ( ( null === $this->upgradeVersionInfo ) || ! property_exists( $this->upgradeVersionInfo, 'updated' ) || ( ( time() - $this->upgradeVersionInfo->updated ) > 60 ) ) {
921 1212 $this->check_upgrade();
922 1213 }
923 1214
@@ -936,8 +1227,33 @@
936 1227 }
937 1228
938 1229 return $transient;
939 1230 }
1231 +
1232 + /**
1233 + * Method hook_refresh_trusted_extension_package_url
1234 + *
1235 + * @param mixed $reply The reply from the upgrader.
1236 + * @param mixed $package The package URL.
1237 + * @param mixed $upgrader The upgrader instance.
1238 + * @param mixed $hook_extra Extra information about the upgrade, including the plugin being updated.
1239 + * @return void
1240 + */
1241 + public function hook_refresh_trusted_extension_package_url( $reply, $package, $upgrader, $hook_extra ) {
1242 + if ( isset( $hook_extra['plugin'] ) ) { // && isset( $_POST['mainwpsignature'] ) && isset( $_POST['function'] ) && 'upgradeplugintheme' === $_POST['function'] ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- NOSONAR - hook for plugin update action, it is safe to use $_POST here.
1243 + $ext_api_slug = dirname( $hook_extra['plugin'] );
1244 + if ( ! empty( $ext_api_slug ) ) {
1245 + $rslt_info = MainWP_API_Handler::get_upgrade_information( $ext_api_slug );
1246 + if ( ! empty( $rslt_info ) && is_object( $rslt_info ) && property_exists( $rslt_info, 'package' ) && ! empty( $rslt_info->package ) ) {
1247 + // Replace package URL.
1248 + $upgrader->skin->options['package'] = $rslt_info->package;
1249 + unset( $package );
1250 + }
1251 + }
1252 + }
1253 + return $reply;
1254 + }
1255 +
940 1256
941 1257 /**
942 1258 * Method upload_file()
943 1259 *