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.3 6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 All 156 releases
← All changes | class/class-mainwp-premium-update.php +261 -88 5.1.1 → 6.2.2 View file →
@@ -8,8 +8,13 @@
8 8 */
9 9
10 10 namespace MainWP\Dashboard;
11 11
12 +// Exit if accessed directly.
13 +if ( ! defined( 'ABSPATH' ) ) {
14 + exit;
15 +}
16 +
12 17 // phpcs:disable WordPress.DB.RestrictedFunctions, WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors -- Using cURL functions.
13 18
14 19 /**
15 20 * Class MainWP_Premium_Update
@@ -20,8 +25,18 @@
20 25 */
21 26 class MainWP_Premium_Update { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.
22 27
23 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 + /**
24 39 * Method get_class_name()
25 40 *
26 41 * Get Class Name.
27 42 *
@@ -35,9 +50,9 @@
35 50 * Method check_premium_updates()
36 51 *
37 52 * Check for Premium Plugin updates.
38 53 *
39 - * @param array $updates Array of updates.
54 + * @param array $updates Array of installed items reported by the child site.
40 55 * @param mixed $type Type of update.
41 56 *
42 57 * @return boolean true|false.
43 58 */
@@ -48,45 +63,9 @@
48 63 }
49 64
50 65 if ( 'plugin' === $type ) {
51 66
52 - $premiums = array(
53 - 'ithemes-security-pro/ithemes-security-pro.php',
54 - 'monarch/monarch.php',
55 - 'cornerstone/cornerstone.php',
56 - 'updraftplus/updraftplus.php',
57 - 'wp-all-import-pro/wp-all-import-pro.php',
58 - 'bbq-pro/bbq-pro.php',
59 - 'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php',
60 - 'oxygen/functions.php',
61 - 'elementor-pro/elementor-pro.php',
62 - 'bbpowerpack/bb-powerpack.php',
63 - 'bb-ultimate-addon/bb-ultimate-addon.php',
64 - 'webarx/webarx.php',
65 - 'leco-client-portal/leco-client-portal.php',
66 - 'elementor-extras/elementor-extras.php',
67 - 'wp-schema-pro/wp-schema-pro.php',
68 - 'convertpro/convertpro.php',
69 - 'astra-addon/astra-addon.php',
70 - 'astra-portfolio/astra-portfolio.php',
71 - 'astra-pro-sites/astra-pro-sites.php',
72 - 'custom-facebook-feed-pro/custom-facebook-feed.php',
73 - 'convertpro/convertpro.php',
74 - 'convertpro-addon/convertpro-addon.php',
75 - 'wp-schema-pro/wp-schema-pro.php',
76 - 'ultimate-elementor/ultimate-elementor.php',
77 - 'gp-premium/gp-premium.php',
78 - 'flying-press/flying-press.php',
79 - 'wp-rocket/wp-rocket.php',
80 - 'fluentformpro/fluentformpro.php',
81 - 'fluentform-signature/fluentform-signature.php',
82 - 'fluentcampaign-pro/fluentcampaign-pro.php',
83 - 'fluent-support-pro/fluent-support-pro.php',
84 - 'ninja-tables-pro/ninja-tables-pro.php',
85 - 'fluent-booking-pro/fluent-booking-pro.php',
86 - 'wp-social-ninja-pro/wp-social-ninja-pro.php',
87 - 'wp-payment-form-pro/wp-payment-form-pro.php',
88 - );
67 + $premiums = MainWP_Premium_Update_Registry::get_filter_defaults( 'plugin', 'detect' );
89 68
90 69 /**
91 70 * Filter: mainwp_detect_premiums_updates
92 71 *
@@ -98,17 +77,20 @@
98 77
99 78 /**
100 79 * Filter: mainwp_detect_premium_plugins_update
101 80 *
102 - * Filters supported premium plugins to fix compatiblity issues with detecting premium plugins updates.
81 + * Filters supported premium plugins to fix compatiblity issues with detecting premium plugin updates.
103 82 *
104 83 * @since Unknown
105 84 */
106 85 $premiums = apply_filters( 'mainwp_detect_premium_plugins_update', $premiums );
107 86
108 - if ( is_array( $premiums ) && ! empty( $premiums ) ) {
109 - foreach ( $updates as $info ) {
110 - 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 ) ) {
111 93 return true;
112 94 }
113 95 }
114 96 }
@@ -113,22 +95,36 @@
113 95 }
114 96 }
115 97 } elseif ( 'theme' === $type ) {
116 98
117 - $premiums = array();
99 + $premiums = MainWP_Premium_Update_Registry::get_filter_defaults( 'theme', 'detect' );
118 100
119 101 /**
120 102 * Filter: mainwp_detect_premium_themes_update
121 103 *
122 - * Filters supported premium themes to fix compatiblity issues with detecting premium themes updates.
104 + * Filters supported premium themes to fix compatiblity issues with detecting premium theme updates.
123 105 *
124 106 * @since Unknown
125 107 */
126 108 $premiums = apply_filters( 'mainwp_detect_premium_themes_update', $premiums );
127 109
128 - if ( is_array( $premiums ) && ! empty( $premiums ) ) {
129 - foreach ( $updates as $info ) {
130 - 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 ) ) {
131 127 return true;
132 128 }
133 129 }
134 130 }
@@ -142,13 +138,15 @@
142 138 *
143 139 * @param mixed $website Child Site info.
144 140 * @param mixed $what stats|upgradeplugintheme What function to perform.
145 141 * @param mixed $params plugin|theme Update Type.
142 + * @param mixed $output Output result.
146 143 *
147 144 * @return mixed $request_update
148 145 */
149 - 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.
150 - $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 +
151 149 if ( 'stats' === $what || ( 'upgradeplugintheme' === $what && isset( $params['type'] ) ) ) {
152 150
153 151 $update_type = '';
154 152
@@ -182,15 +180,15 @@
182 180 if ( static::check_premium_updates( $check_premi_themes, 'theme' ) ) {
183 181 static::try_to_detect_premiums_update( $website, 'theme' );
184 182 }
185 183
186 - if ( 'upgradeplugintheme' === $what && ( 'plugin' === $update_type || 'theme' === $update_type ) && static::check_request_update_premium( $params['list'], $update_type ) ) {
187 - static::request_premiums_update( $website, $update_type, $params['list'] );
188 - $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;
189 187 }
190 188 }
191 189
192 - return $request_update;
190 + return false;
193 191 }
194 192
195 193 /**
196 194 * Method check_request_update_premium()
@@ -209,17 +207,15 @@
209 207 if ( ! is_array( $updates ) || empty( $updates ) ) {
210 208 return false;
211 209 }
212 210
213 - if ( 1 < count( $updates ) ) {
211 + if ( 3 < count( $updates ) ) {
214 212 return false;
215 213 }
216 214
217 215 if ( 'plugin' === $type ) {
218 216
219 - $update_premiums = array(
220 - 'yith-woocommerce-request-a-quote-premium/init.php',
221 - );
217 + $update_premiums = MainWP_Premium_Update_Registry::get_filter_defaults( 'plugin', 'request' );
222 218
223 219 /**
224 220 * Filter: mainwp_request_update_premium_plugins
225 221 *
@@ -228,18 +224,18 @@
228 224 * @since Unknown
229 225 */
230 226 $update_premiums = apply_filters( 'mainwp_request_update_premium_plugins', $update_premiums );
231 227
232 - if ( is_array( $update_premiums ) && ! empty( $update_premiums ) ) {
233 - foreach ( $updates as $slug ) {
234 - if ( ! empty( $slug ) && in_array( $slug, $update_premiums ) ) {
235 - return true;
236 - }
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;
237 233 }
238 234 }
239 235 } elseif ( 'theme' === $type ) {
240 236
241 - $update_premiums = array();
237 + $update_premiums = MainWP_Premium_Update_Registry::get_filter_defaults( 'theme', 'request' );
242 238
243 239 /**
244 240 * Filter: mainwp_request_update_premium_themes
245 241 *
@@ -247,13 +243,14 @@
247 243 *
248 244 * @since Unknown
249 245 */
250 246 $update_premiums = apply_filters( 'mainwp_request_update_premium_themes', $update_premiums );
251 - if ( is_array( $update_premiums ) && ! empty( $update_premiums ) ) {
252 - foreach ( $updates as $slug ) {
253 - if ( ! empty( $slug ) && in_array( $slug, $update_premiums ) ) {
254 - return true;
255 - }
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;
256 253 }
257 254 }
258 255 }
259 256 return false;
@@ -263,8 +260,10 @@
263 260 * Method redirect_request_site()
264 261 *
265 262 * Redirect to requested Site.
266 263 *
264 + * Deprecated, see function handle_premium_update_actions().
265 + *
267 266 * @param mixed $website Child Site.
268 267 * @param mixed $where_url page to redirerct to.
269 268 *
270 269 * @uses \MainWP\Dashboard\MainWP_Connect::get_get_data_authed()
@@ -279,21 +278,155 @@
279 278 $args = array(
280 279 'timeout' => 25,
281 280 'httpversion' => '1.1',
282 281 'User-Agent' => $agent,
282 + 'sslverify' => static::get_ssl_verify( $website ),
283 283 );
284 284
285 285 if ( ! empty( $website->http_user ) && ! empty( $website->http_pass ) ) {
286 - $args['headers'] = array(
287 - 'Authorization' => 'Basic ' . base64_encode( $website->http_user . ':' . stripslashes( $website->http_pass ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
288 - );
286 + // MWP-1548: decrypt before constructing the Basic Auth header.
287 + $http_user_plain = MainWP_Credential_Storage::decrypt_credential( $website->http_user );
288 + $http_pass_plain = MainWP_Credential_Storage::decrypt_credential( $website->http_pass );
289 + if ( ! empty( $http_user_plain ) && ! empty( $http_pass_plain ) ) {
290 + $args['headers'] = array(
291 + 'Authorization' => 'Basic ' . base64_encode( $http_user_plain . ':' . stripslashes( $http_pass_plain ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- base64_encode used for http encoding compatible.
292 + );
293 + }
289 294 }
290 295
291 - MainWP_Logger::instance()->debug( ' :: tryRequest :: [website=' . $website->url . ']' );
292 - 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;
293 320 }
294 321
322 +
295 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 + /**
296 429 * Method request_premiums_update()
297 430 *
298 431 * Request to update plugin or theme.
299 432 *
@@ -300,23 +433,62 @@
300 433 * @param mixed $website Child Site to update.
301 434 * @param mixed $type Type of update, plugin|theme.
302 435 * @param mixed $list_items list of plugins & themes installed.
303 436 *
304 - * @return mixed null|true.
437 + * @return mixed
305 438 */
306 439 public static function request_premiums_update( $website, $type, $list_items ) {
307 - if ( 'plugin' === $type ) {
308 - $where_url = 'plugins.php?_request_update_premiums_type=plugin&list=' . $list_items;
309 - } elseif ( 'theme' === $type ) {
310 - $where_url = 'update-core.php?_request_update_premiums_type=theme&list=' . $list_items;
311 - } 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 ) {
312 465 return null;
313 466 }
314 - static::redirect_request_site( $website, $where_url );
315 - 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;
316 472 }
317 473
318 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 + /**
319 491 * Method try_to_detect_premiums_update()
320 492 *
321 493 * Try to detect if pugin and themes are premium.
322 494 *
@@ -325,14 +497,15 @@
325 497 *
326 498 * @return mixed false|static::redirect_request_site()
327 499 */
328 500 public static function try_to_detect_premiums_update( $website, $type ) {
329 - if ( 'plugin' === $type ) {
330 - $where_url = 'plugins.php?_detect_plugins_updates=yes';
331 - } elseif ( 'theme' === $type ) {
332 - $where_url = 'update-core.php?_detect_themes_updates=yes';
333 - } else {
334 - return false;
501 +
502 + if ( ! in_array( $type, array( 'plugin', 'theme' ), true ) ) {
503 + return null;
335 504 }
336 - 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 );
337 510 }
338 511 }