| @@ -32,9 +32,9 @@ | ||
| 32 | 32 | |
| 33 | 33 | /** |
| 34 | 34 | * Private variable to hold the upgrade version info. |
| 35 | 35 | * |
| 36 | - * @var null Version info. | |
| 36 | + * @var \stdClass|null Version info. | |
| 37 | 37 | */ |
| 38 | 38 | private $upgradeVersionInfo = null; |
| 39 | 39 | |
| 40 | 40 | /** |
| @@ -1023,18 +1023,167 @@ | ||
| 1023 | 1023 | * |
| 1024 | 1024 | * @uses \MainWP\Dashboard\MainWP_API_Handler::check_exts_upgrade() |
| 1025 | 1025 | * @uses \MainWP\Dashboard\MainWP_Utility::update_option() |
| 1026 | 1026 | */ |
| 1027 | - private function check_upgrade() { | |
| 1028 | - $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 | + | |
| 1029 | 1031 | if ( null === $this->upgradeVersionInfo ) { |
| 1030 | 1032 | $this->upgradeVersionInfo = new \stdClass(); |
| 1031 | 1033 | } |
| 1034 | + | |
| 1032 | 1035 | $this->upgradeVersionInfo->updated = time(); |
| 1033 | - if ( ! empty( $result ) ) { | |
| 1034 | - $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; | |
| 1035 | 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 | + | |
| 1036 | 1071 | MainWP_Utility::update_option( 'mainwp_upgradeVersionInfo', $this->upgradeVersionInfo ); |
| 1072 | + return ! $failed; | |
| 1073 | + } | |
| 1074 | + | |
| 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; | |
| 1037 | 1186 | } |
| 1038 | 1187 | |
| 1039 | 1188 | /** |
| 1040 | 1189 | * Method pre_check_update_custom() |