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-premium-update.php +246 -83 6.1.8 → 6.2.2 View file →
@@ -25,8 +25,18 @@
25 25 */
26 26 class MainWP_Premium_Update { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
27 27
28 28 /**
29 + * Raw response of the most recent premium request-update GET, if any.
30 + *
31 + * Captured so fetch_url_authed() can report the child's real result instead
32 + * of fabricating success (MWP-1660). Reset per maybe_request_premium_updates() call.
33 + *
34 + * @var array|\WP_Error|null
35 + */
36 + private static $last_request_response = null;
37 +
38 + /**
29 39 * Method get_class_name()
30 40 *
31 41 * Get Class Name.
32 42 *
@@ -40,9 +50,9 @@
40 50 * Method check_premium_updates()
41 51 *
42 52 * Check for Premium Plugin updates.
43 53 *
44 - * @param array $updates Array of updates.
54 + * @param array $updates Array of installed items reported by the child site.
45 55 * @param mixed $type Type of update.
46 56 *
47 57 * @return boolean true|false.
48 58 */
@@ -53,45 +63,9 @@
53 63 }
54 64
55 65 if ( 'plugin' === $type ) {
56 66
57 - $premiums = array(
58 - 'ithemes-security-pro/ithemes-security-pro.php',
59 - 'monarch/monarch.php',
60 - 'cornerstone/cornerstone.php',
61 - 'updraftplus/updraftplus.php',
62 - 'wp-all-import-pro/wp-all-import-pro.php',
63 - 'bbq-pro/bbq-pro.php',
64 - 'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php',
65 - 'oxygen/functions.php',
66 - 'elementor-pro/elementor-pro.php',
67 - 'bbpowerpack/bb-powerpack.php',
68 - 'bb-ultimate-addon/bb-ultimate-addon.php',
69 - 'webarx/webarx.php',
70 - 'leco-client-portal/leco-client-portal.php',
71 - 'elementor-extras/elementor-extras.php',
72 - 'wp-schema-pro/wp-schema-pro.php',
73 - 'convertpro/convertpro.php',
74 - 'astra-addon/astra-addon.php',
75 - 'astra-portfolio/astra-portfolio.php',
76 - 'astra-pro-sites/astra-pro-sites.php',
77 - 'custom-facebook-feed-pro/custom-facebook-feed.php',
78 - 'convertpro/convertpro.php',
79 - 'convertpro-addon/convertpro-addon.php',
80 - 'wp-schema-pro/wp-schema-pro.php',
81 - 'ultimate-elementor/ultimate-elementor.php',
82 - 'gp-premium/gp-premium.php',
83 - 'flying-press/flying-press.php',
84 - 'wp-rocket/wp-rocket.php',
85 - 'fluentformpro/fluentformpro.php',
86 - 'fluentform-signature/fluentform-signature.php',
87 - 'fluentcampaign-pro/fluentcampaign-pro.php',
88 - 'fluent-support-pro/fluent-support-pro.php',
89 - 'ninja-tables-pro/ninja-tables-pro.php',
90 - 'fluent-booking-pro/fluent-booking-pro.php',
91 - 'wp-social-ninja-pro/wp-social-ninja-pro.php',
92 - 'wp-payment-form-pro/wp-payment-form-pro.php',
93 - );
67 + $premiums = MainWP_Premium_Update_Registry::get_filter_defaults( 'plugin', 'detect' );
94 68
95 69 /**
96 70 * Filter: mainwp_detect_premiums_updates
97 71 *
@@ -109,11 +83,14 @@
109 83 * @since Unknown
110 84 */
111 85 $premiums = apply_filters( 'mainwp_detect_premium_plugins_update', $premiums );
112 86
113 - if ( is_array( $premiums ) && ! empty( $premiums ) ) {
114 - foreach ( $updates as $info ) {
115 - if ( isset( $info['slug'] ) && ( in_array( $info['slug'], $premiums ) || false !== strpos( $info['slug'], 'yith-' ) ) ) {
87 + $prefixes = MainWP_Premium_Update_Registry::get_prefixes( 'plugin', 'detect' );
88 +
89 + if ( ( is_array( $premiums ) && ! empty( $premiums ) ) || ! empty( $prefixes ) ) {
90 + foreach ( $updates as $inventory_key => $info ) {
91 + $identifier = MainWP_Premium_Update_Registry::get_inventory_identifier( $inventory_key, $info );
92 + if ( '' !== $identifier && MainWP_Premium_Update_Registry::slug_matches( $identifier, is_array( $premiums ) ? $premiums : array(), $prefixes ) ) {
116 93 return true;
117 94 }
118 95 }
119 96 }
@@ -118,9 +95,9 @@
118 95 }
119 96 }
120 97 } elseif ( 'theme' === $type ) {
121 98
122 - $premiums = array();
99 + $premiums = MainWP_Premium_Update_Registry::get_filter_defaults( 'theme', 'detect' );
123 100
124 101 /**
125 102 * Filter: mainwp_detect_premium_themes_update
126 103 *
@@ -129,11 +106,25 @@
129 106 * @since Unknown
130 107 */
131 108 $premiums = apply_filters( 'mainwp_detect_premium_themes_update', $premiums );
132 109
133 - if ( is_array( $premiums ) && ! empty( $premiums ) ) {
134 - foreach ( $updates as $info ) {
135 - if ( isset( $info['slug'] ) && in_array( $info['slug'], $premiums ) ) {
110 + $prefixes = MainWP_Premium_Update_Registry::get_prefixes( 'theme', 'detect' );
111 +
112 + if ( ( is_array( $premiums ) && ! empty( $premiums ) ) || ! empty( $prefixes ) ) {
113 + foreach ( $updates as $inventory_key => $info ) {
114 + $identifier = MainWP_Premium_Update_Registry::get_inventory_identifier( $inventory_key, $info );
115 + if ( '' === $identifier ) {
116 + continue;
117 + }
118 + // A theme updater only runs while its theme (or a child of it) is active,
119 + // so the extra checks are limited to the active theme and its parent.
120 + // Older child plugins that do not report activity flags keep the
121 + // previous match-any-installed behavior.
122 + if ( ( isset( $info['active'] ) || isset( $info['parent_active'] ) )
123 + && empty( $info['active'] ) && empty( $info['parent_active'] ) ) {
124 + continue;
125 + }
126 + if ( MainWP_Premium_Update_Registry::slug_matches( $identifier, is_array( $premiums ) ? $premiums : array(), $prefixes ) ) {
136 127 return true;
137 128 }
138 129 }
139 130 }
@@ -147,13 +138,15 @@
147 138 *
148 139 * @param mixed $website Child Site info.
149 140 * @param mixed $what stats|upgradeplugintheme What function to perform.
150 141 * @param mixed $params plugin|theme Update Type.
142 + * @param mixed $output Output result.
151 143 *
152 144 * @return mixed $request_update
153 145 */
154 - public static function maybe_request_premium_updates( $website, $what, $params ) { // phpcs:ignore -- NOSONAR -Current complexity is the only way to achieve desired results, pull request solutions appreciated.
155 - $request_update = false;
146 + public static function maybe_request_premium_updates( $website, $what, $params, &$output ) { // phpcs:ignore -- NOSONAR -Current complexity is the only way to achieve desired results, pull request solutions appreciated.
147 + self::$last_request_response = null;
148 +
156 149 if ( 'stats' === $what || ( 'upgradeplugintheme' === $what && isset( $params['type'] ) ) ) {
157 150
158 151 $update_type = '';
159 152
@@ -187,15 +180,15 @@
187 180 if ( static::check_premium_updates( $check_premi_themes, 'theme' ) ) {
188 181 static::try_to_detect_premiums_update( $website, 'theme' );
189 182 }
190 183
191 - if ( 'upgradeplugintheme' === $what && ( 'plugin' === $update_type || 'theme' === $update_type ) && static::check_request_update_premium( $params['list'], $update_type ) ) {
192 - static::request_premiums_update( $website, $update_type, $params['list'] );
193 - $request_update = true;
184 + if ( 'upgradeplugintheme' === $what && ( 'plugin' === $update_type || 'theme' === $update_type ) && ( static::check_request_update_premium( $params['list'], $update_type ) || static::check_premium_updates( explode( ',', $params['list'] ), $update_type ) ) ) {
185 + $output = static::request_premiums_update( $website, $update_type, $params['list'] );
186 + return true;
194 187 }
195 188 }
196 189
197 - return $request_update;
190 + return false;
198 191 }
199 192
200 193 /**
201 194 * Method check_request_update_premium()
@@ -214,17 +207,15 @@
214 207 if ( ! is_array( $updates ) || empty( $updates ) ) {
215 208 return false;
216 209 }
217 210
218 - if ( 1 < count( $updates ) ) {
211 + if ( 3 < count( $updates ) ) {
219 212 return false;
220 213 }
221 214
222 215 if ( 'plugin' === $type ) {
223 216
224 - $update_premiums = array(
225 - 'yith-woocommerce-request-a-quote-premium/init.php',
226 - );
217 + $update_premiums = MainWP_Premium_Update_Registry::get_filter_defaults( 'plugin', 'request' );
227 218
228 219 /**
229 220 * Filter: mainwp_request_update_premium_plugins
230 221 *
@@ -233,18 +224,18 @@
233 224 * @since Unknown
234 225 */
235 226 $update_premiums = apply_filters( 'mainwp_request_update_premium_plugins', $update_premiums );
236 227
237 - if ( is_array( $update_premiums ) && ! empty( $update_premiums ) ) {
238 - foreach ( $updates as $slug ) {
239 - if ( ! empty( $slug ) && in_array( $slug, $update_premiums ) ) {
240 - return true;
241 - }
228 + $prefixes = MainWP_Premium_Update_Registry::get_prefixes( 'plugin', 'request' );
229 +
230 + foreach ( $updates as $slug ) {
231 + if ( ! empty( $slug ) && MainWP_Premium_Update_Registry::slug_matches( $slug, is_array( $update_premiums ) ? $update_premiums : array(), $prefixes ) ) {
232 + return true;
242 233 }
243 234 }
244 235 } elseif ( 'theme' === $type ) {
245 236
246 - $update_premiums = array();
237 + $update_premiums = MainWP_Premium_Update_Registry::get_filter_defaults( 'theme', 'request' );
247 238
248 239 /**
249 240 * Filter: mainwp_request_update_premium_themes
250 241 *
@@ -252,13 +243,14 @@
252 243 *
253 244 * @since Unknown
254 245 */
255 246 $update_premiums = apply_filters( 'mainwp_request_update_premium_themes', $update_premiums );
256 - if ( is_array( $update_premiums ) && ! empty( $update_premiums ) ) {
257 - foreach ( $updates as $slug ) {
258 - if ( ! empty( $slug ) && in_array( $slug, $update_premiums ) ) {
259 - return true;
260 - }
247 +
248 + $prefixes = MainWP_Premium_Update_Registry::get_prefixes( 'theme', 'request' );
249 +
250 + foreach ( $updates as $slug ) {
251 + if ( ! empty( $slug ) && MainWP_Premium_Update_Registry::slug_matches( $slug, is_array( $update_premiums ) ? $update_premiums : array(), $prefixes ) ) {
252 + return true;
261 253 }
262 254 }
263 255 }
264 256 return false;
@@ -268,8 +260,10 @@
268 260 * Method redirect_request_site()
269 261 *
270 262 * Redirect to requested Site.
271 263 *
264 + * Deprecated, see function handle_premium_update_actions().
265 + *
272 266 * @param mixed $website Child Site.
273 267 * @param mixed $where_url page to redirerct to.
274 268 *
275 269 * @uses \MainWP\Dashboard\MainWP_Connect::get_get_data_authed()
@@ -284,8 +278,9 @@
284 278 $args = array(
285 279 'timeout' => 25,
286 280 'httpversion' => '1.1',
287 281 'User-Agent' => $agent,
282 + 'sslverify' => static::get_ssl_verify( $website ),
288 283 );
289 284
290 285 if ( ! empty( $website->http_user ) && ! empty( $website->http_pass ) ) {
291 286 // MWP-1548: decrypt before constructing the Basic Auth header.
@@ -297,13 +292,141 @@
297 292 );
298 293 }
299 294 }
300 295
301 - MainWP_Logger::instance()->debug( ' :: tryRequest :: [website=' . $website->url . ']' );
302 - return wp_remote_get( $request_url, $args );
296 + $ssl_version = isset( $website->ssl_version ) ? (int) $website->ssl_version : 0;
297 + $force_use_ipv4 = static::get_force_use_ipv4( $website );
298 +
299 + // MWP-1660: honor the site's connection profile, mirroring fetch_url().
300 + $curl_extra = function ( $handle ) use ( $ssl_version, $force_use_ipv4 ) {
301 + if ( ! empty( $ssl_version ) ) {
302 + curl_setopt( $handle, CURLOPT_SSLVERSION, $ssl_version );
303 + }
304 + if ( $force_use_ipv4 && defined( 'CURLOPT_IPRESOLVE' ) && defined( 'CURL_IPRESOLVE_V4' ) ) {
305 + curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
306 + }
307 + };
308 +
309 + add_action( 'http_api_curl', $curl_extra );
310 + try {
311 + MainWP_Logger::instance()->debug( ' :: tryRequest :: [website=' . $website->url . ']' );
312 + $response = wp_remote_get( $request_url, $args );
313 + } finally {
314 + remove_action( 'http_api_curl', $curl_extra );
315 + }
316 +
317 + static::log_request_outcome( $website, $where_url, $response );
318 +
319 + return $response;
303 320 }
304 321
322 +
305 323 /**
324 + * Method handle_premium_update_actions()
325 + *
326 + * Redirect to requested Site.
327 + *
328 + * @since 6.2
329 + *
330 + * @param mixed $website Child Site.
331 + * @param array $params Other params.
332 + *
333 + * @return mixed false|array information.
334 + */
335 + public static function handle_premium_update_actions( $website, $params = array() ) {
336 + if ( ! is_array( $params ) ) {
337 + return false;
338 + }
339 + MainWP_Logger::instance()->debug( 'Request premium update :: [siteid=' . $website->id . '] :: [type=' . $params['premium_type'] . '] :: [perform=' . $params['premium_perform'] . ']' );
340 + try {
341 + return MainWP_Connect::fetch_url_authed( $website, 'process_premium_updates', $params, false, false, true, null, true );
342 + } catch ( \Exception $e ) {
343 + // Just ignore.
344 + $err = $e->getMessage(); //phpcs:ignore -- NOSONAR -for debug.
345 + }
346 + return false;
347 + }
348 +
349 + /**
350 + * Resolve the sslverify argument for a website, mirroring fetch_url() semantics.
351 + *
352 + * @param mixed $website Child Site.
353 + *
354 + * @return bool
355 + */
356 + private static function get_ssl_verify( $website ) {
357 + $verify = isset( $website->verify_certificate ) ? (int) $website->verify_certificate : 2;
358 + if ( 1 === $verify ) {
359 + return true;
360 + }
361 + if ( 0 === $verify ) {
362 + return false;
363 + }
364 + return ( false === get_option( 'mainwp_sslVerifyCertificate' ) ) || ( 1 === (int) get_option( 'mainwp_sslVerifyCertificate' ) );
365 + }
366 +
367 + /**
368 + * Resolve the force-IPv4 flag for a website, mirroring fetch_url() semantics.
369 + *
370 + * @param mixed $website Child Site.
371 + *
372 + * @return bool
373 + */
374 + private static function get_force_use_ipv4( $website ) {
375 + $force = isset( $website->force_use_ipv4 ) && null !== $website->force_use_ipv4 ? (int) $website->force_use_ipv4 : null;
376 + if ( 1 === $force ) {
377 + return true;
378 + }
379 + if ( null === $force || 2 === $force ) {
380 + return 1 === (int) get_option( 'mainwp_forceUseIPv4' );
381 + }
382 + return false;
383 + }
384 +
385 + /**
386 + * Log the outcome of a premium detect/request GET and record failures per site.
387 + *
388 + * These requests previously discarded their responses entirely, leaving
389 + * support blind to sites where the premium checks silently fail (MWP-1660).
390 + *
391 + * @param mixed $website Child Site.
392 + * @param string $where_url Requested wp-admin location.
393 + * @param array|\WP_Error|mixed $response HTTP response.
394 + */
395 + private static function log_request_outcome( $website, $where_url, $response ) {
396 + $error = '';
397 + if ( is_wp_error( $response ) ) {
398 + $error = $response->get_error_message();
399 + } else {
400 + $code = (int) wp_remote_retrieve_response_code( $response );
401 + if ( $code < 200 || $code >= 400 ) {
402 + $error = 'HTTP ' . $code;
403 + }
404 + }
405 +
406 + if ( '' === $error ) {
407 + MainWP_Logger::instance()->debug_for_website( $website, 'premium_update', '[where=' . $where_url . '] :: ok' );
408 + return;
409 + }
410 +
411 + MainWP_Logger::instance()->warning_for_website( $website, 'premium_update', '[where=' . $where_url . '] :: ' . $error, false );
412 +
413 + $count = (int) MainWP_DB::instance()->get_website_option( $website, 'premium_updates_error_count' );
414 + MainWP_DB::instance()->update_website_option( $website, 'premium_updates_error_count', (string) ( $count + 1 ) );
415 + MainWP_DB::instance()->update_website_option(
416 + $website,
417 + 'premium_updates_last_error',
418 + wp_json_encode(
419 + array(
420 + 'time' => time(),
421 + 'where' => $where_url,
422 + 'error' => $error,
423 + )
424 + )
425 + );
426 + }
427 +
428 + /**
306 429 * Method request_premiums_update()
307 430 *
308 431 * Request to update plugin or theme.
309 432 *
@@ -310,23 +433,62 @@
310 433 * @param mixed $website Child Site to update.
311 434 * @param mixed $type Type of update, plugin|theme.
312 435 * @param mixed $list_items list of plugins & themes installed.
313 436 *
314 - * @return mixed null|true.
437 + * @return mixed
315 438 */
316 439 public static function request_premiums_update( $website, $type, $list_items ) {
317 - if ( 'plugin' === $type ) {
318 - $where_url = 'plugins.php?_request_update_premiums_type=plugin&list=' . $list_items;
319 - } elseif ( 'theme' === $type ) {
320 - $where_url = 'update-core.php?_request_update_premiums_type=theme&list=' . $list_items;
321 - } else {
440 + if ( ! in_array( $type, array( 'plugin', 'theme' ) ) ) {
441 + return false;
442 + }
443 + $params = array(
444 + 'premium_type' => $type,
445 + 'premium_perform' => 'premium_update',
446 + 'list' => $list_items,
447 + );
448 + return static::handle_premium_update_actions( $website, $params );
449 + }
450 +
451 + /**
452 + * Parse the child's `<mainwp>` result envelope out of an HTML response body.
453 + *
454 + * The premium request route runs the regular upgradeplugintheme callable
455 + * mid-render of a wp-admin page; the callable terminates via
456 + * MainWP_Helper::write(), so its result envelope is embedded in the page
457 + * output. Returns null when no valid envelope is present (e.g. timeout).
458 + *
459 + * @param string $body Response body.
460 + *
461 + * @return array|null Decoded information array or null.
462 + */
463 + public static function parse_mainwp_envelope( $body ) {
464 + if ( ! is_string( $body ) || '' === $body ) {
322 465 return null;
323 466 }
324 - static::redirect_request_site( $website, $where_url );
325 - return true;
467 + if ( ! preg_match( '/<mainwp>(.*)<\/mainwp>/', $body, $results ) ) {
468 + return null;
469 + }
470 + $information = MainWP_System_Utility::get_child_response( base64_decode( $results[1] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for backwards compatibility.
471 + return is_array( $information ) ? $information : null;
326 472 }
327 473
328 474 /**
475 + * Get the parsed result of the most recent premium request-update GET.
476 + *
477 + * Used by fetch_url_authed() to report the child's real result. Returns null
478 + * when the request timed out or produced no parsable envelope; callers keep
479 + * the previous optimistic behavior in that case (MWP-1660).
480 + *
481 + * @return array|null
482 + */
483 + public static function get_last_parsed_response() {
484 + if ( null === self::$last_request_response || is_wp_error( self::$last_request_response ) ) {
485 + return null;
486 + }
487 + return static::parse_mainwp_envelope( wp_remote_retrieve_body( self::$last_request_response ) );
488 + }
489 +
490 + /**
329 491 * Method try_to_detect_premiums_update()
330 492 *
331 493 * Try to detect if pugin and themes are premium.
332 494 *
@@ -335,14 +497,15 @@
335 497 *
336 498 * @return mixed false|static::redirect_request_site()
337 499 */
338 500 public static function try_to_detect_premiums_update( $website, $type ) {
339 - if ( 'plugin' === $type ) {
340 - $where_url = 'plugins.php?_detect_plugins_updates=yes';
341 - } elseif ( 'theme' === $type ) {
342 - $where_url = 'update-core.php?_detect_themes_updates=yes';
343 - } else {
344 - return false;
501 +
502 + if ( ! in_array( $type, array( 'plugin', 'theme' ), true ) ) {
503 + return null;
345 504 }
346 - static::redirect_request_site( $website, $where_url );
505 + $params = array(
506 + 'premium_type' => $type,
507 + 'premium_perform' => 'detect_update',
508 + );
509 + static::handle_premium_update_actions( $website, $params );
347 510 }
348 511 }