PluginProbe
Tiered Pricing Table for WooCommerce / 8.0.2
Tiered Pricing Table for WooCommerce v8.0.2
8.0.2 7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 2.3.3 All 91 releases
← All changes | freemius/includes/class-fs-plugin-updater.php +358 -91 2.2.2 → 8.0.2 View file →
@@ -36,8 +36,10 @@
36 36 private $_translation_updates;
37 37
38 38 private static $_upgrade_basename = null;
39 39
40 + const UPDATES_CHECK_CACHE_EXPIRATION = ( WP_FS__TIME_24_HOURS_IN_SEC / 24 );
41 +
40 42 #--------------------------------------------------------------------------------
41 43 #region Singleton
42 44 #--------------------------------------------------------------------------------
43 45
@@ -101,9 +103,13 @@
101 103 &$this,
102 104 'edit_and_echo_plugin_update_row'
103 105 ), 11, 2 );
104 106
105 - add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) );
107 + if ( ! $this->_fs->has_any_active_valid_license() ) {
108 + add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) );
109 + } else {
110 + add_action( 'admin_footer', array( &$this, '_add_fs_allow_updater_and_dialog_request_param' ) );
111 + }
106 112
107 113 if ( ! WP_FS__IS_PRODUCTION_MODE ) {
108 114 add_filter( 'http_request_host_is_external', array(
109 115 $this,
@@ -126,15 +132,48 @@
126 132 }
127 133
128 134 /**
129 135 * @author Leo Fajardo (@leorw)
136 + * @since 2.7.4
137 + */
138 + function _add_fs_allow_updater_and_dialog_request_param() {
139 + if ( ! $this->is_plugin_information_dialog_for_plugin() ) {
140 + return;
141 + }
142 + ?>
143 + <script type="text/javascript">
144 + if ( typeof jQuery !== 'undefined' ) {
145 + jQuery( document ).on( 'wp-plugin-updating', function( event, args ) {
146 + if ( typeof args === 'object' && args.slug && typeof args.slug === 'string' ) {
147 + if ( <?php echo json_encode( $this->_fs->get_slug() ) ?> === args.slug ) {
148 + args.fs_allow_updater_and_dialog = true;
149 + }
150 + }
151 + } );
152 + }
153 + </script>
154 + <?php
155 + }
156 +
157 + /**
158 + * @author Leo Fajardo (@leorw)
159 + * @since 2.7.4
160 + *
161 + * @return bool
162 + */
163 + private function is_plugin_information_dialog_for_plugin() {
164 + return (
165 + 'plugin-information' === fs_request_get( 'tab', false ) &&
166 + $this->_fs->get_slug() === fs_request_get_raw( 'plugin', false )
167 + );
168 + }
169 +
170 + /**
171 + * @author Leo Fajardo (@leorw)
130 172 * @since 2.1.4
131 173 */
132 174 function catch_plugin_information_dialog_contents() {
133 - if (
134 - 'plugin-information' !== fs_request_get( 'tab', false ) ||
135 - $this->_fs->get_slug() !== fs_request_get( 'plugin', false )
136 - ) {
175 + if ( ! $this->is_plugin_information_dialog_for_plugin() ) {
137 176 return;
138 177 }
139 178
140 179 add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 );
@@ -150,9 +189,9 @@
150 189 */
151 190 function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) {
152 191 if (
153 192 'plugin-information' !== fs_request_get( 'tab', false ) ||
154 - $this->_fs->get_slug() !== fs_request_get( 'plugin', false )
193 + $this->_fs->get_slug() !== fs_request_get_raw( 'plugin', false )
155 194 ) {
156 195 return;
157 196 }
158 197
@@ -163,35 +202,78 @@
163 202 null;
164 203
165 204 $contents = ob_get_clean();
166 205
167 - /**
168 - * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
169 - * the text will be "Renew license" and will link to the checkout page with the license's billing cycle
170 - * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page.
171 - */
172 - $contents = preg_replace(
173 - '/(.+\<a.+)(id="plugin_update_from_iframe")(.+href=")([^\s]+)(".+\>)(.+)(\<\/a.+)/is',
174 - is_object( $license ) ?
175 - sprintf(
176 - '$1$3%s$5%s$7',
177 - $this->_fs->checkout_url(
178 - is_object( $subscription ) ?
179 - ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) :
180 - WP_FS__PERIOD_LIFETIME,
181 - false,
182 - array( 'licenses' => $license->quota )
206 + $install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_install_from_iframe"' );
207 +
208 + if ( false === $install_or_update_button_id_attribute_pos ) {
209 + $install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' );
210 + }
211 +
212 + if ( false !== $install_or_update_button_id_attribute_pos ) {
213 + $install_or_update_button_start_pos = strrpos(
214 + substr( $contents, 0, $install_or_update_button_id_attribute_pos ),
215 + '<a'
216 + );
217 +
218 + $install_or_update_button_end_pos = ( strpos( $contents, '</a>', $install_or_update_button_id_attribute_pos ) + strlen( '</a>' ) );
219 +
220 + /**
221 + * The part of the contents without the update button.
222 + *
223 + * @author Leo Fajardo (@leorw)
224 + * @since 2.2.5
225 + */
226 + $modified_contents = substr( $contents, 0, $install_or_update_button_start_pos );
227 +
228 + $install_or_update_button = substr( $contents, $install_or_update_button_start_pos, ( $install_or_update_button_end_pos - $install_or_update_button_start_pos ) );
229 +
230 + /**
231 + * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
232 + * the text will be "Renew license" and will link to the checkout page with the license's billing cycle
233 + * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page.
234 + */
235 + $install_or_update_button = preg_replace(
236 + '/(\<a.+)(id="plugin_(install|update)_from_iframe")(.+href=")([^\s]+)(".*\>)(.+)(\<\/a>)/is',
237 + is_object( $license ) ?
238 + sprintf(
239 + '$1$4%s$6%s$8',
240 + $this->_fs->checkout_url(
241 + is_object( $subscription ) ?
242 + ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) :
243 + WP_FS__PERIOD_LIFETIME,
244 + false,
245 + array( 'licenses' => $license->quota )
246 + ),
247 + fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() )
248 + ) :
249 + sprintf(
250 + '$1$4%s$6%s$8',
251 + $this->_fs->pricing_url(),
252 + fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() )
183 253 ),
184 - fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() )
185 - ) :
186 - sprintf(
187 - '$1$3%s$5%s$7',
188 - $this->_fs->pricing_url(),
189 - fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() )
190 - ),
191 - $contents
192 - );
254 + $install_or_update_button
255 + );
193 256
257 + /**
258 + * Append the modified button.
259 + *
260 + * @author Leo Fajardo (@leorw)
261 + * @since 2.2.5
262 + */
263 + $modified_contents .= $install_or_update_button;
264 +
265 + /**
266 + * Append the remaining part of the contents after the update button.
267 + *
268 + * @author Leo Fajardo (@leorw)
269 + * @since 2.2.5
270 + */
271 + $modified_contents .= substr( $contents, $install_or_update_button_end_pos );
272 +
273 + $contents = $modified_contents;
274 + }
275 +
194 276 echo $contents;
195 277 }
196 278
197 279 /**
@@ -198,8 +280,18 @@
198 280 * @author Vova Feldman (@svovaf)
199 281 * @since 2.0.0
200 282 */
201 283 private function add_transient_filters() {
284 + if (
285 + $this->_fs->is_premium() &&
286 + $this->_fs->is_registered() &&
287 + ! FS_Permission_Manager::instance( $this->_fs )->is_essentials_tracking_allowed()
288 + ) {
289 + $this->_logger->log( 'Opted out sites cannot receive automatic software updates.' );
290 +
291 + return;
292 + }
293 +
202 294 add_filter( 'pre_set_site_transient_update_plugins', array(
203 295 &$this,
204 296 'pre_set_site_transient_update_plugins_filter'
205 297 ) );
@@ -256,9 +348,42 @@
256 348 }
257 349
258 350 $r = $current->response[ $file ];
259 351
260 - if ( ! $this->_fs->has_any_active_valid_license() ) {
352 + $has_beta_update = $this->_fs->has_beta_update();
353 +
354 + if ( $this->_fs->has_any_active_valid_license() ) {
355 + if ( $has_beta_update ) {
356 + /**
357 + * Turn the "new version" text into "new Beta version".
358 + *
359 + * Sample input:
360 + * There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
361 + * Output:
362 + * There is a new Beta version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
363 + *
364 + * @author Leo Fajardo (@leorw)
365 + * @since 2.3.0
366 + */
367 + $plugin_update_row = preg_replace(
368 + '/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
369 + (
370 + '$1' .
371 + sprintf(
372 + fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
373 + $has_beta_update ?
374 + fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
375 + fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ),
376 + $this->_fs->get_plugin_title()
377 + ) .
378 + ' ' .
379 + '$3' .
380 + '$6'
381 + ),
382 + $plugin_update_row
383 + );
384 + }
385 + } else {
261 386 /**
262 387 * Turn the "new version" text into a link that opens the plugin information dialog when clicked and
263 388 * make the "View version x details" text link to the checkout page instead of opening the plugin
264 389 * information dialog when clicked.
@@ -266,8 +391,10 @@
266 391 * Sample input:
267 392 * There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
268 393 * Output:
269 394 * There is a <a href="...>new version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
395 + * OR
396 + * There is a <a href="...>new Beta version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
270 397 *
271 398 * @author Leo Fajardo (@leorw)
272 399 */
273 400 $plugin_update_row = preg_replace(
@@ -278,9 +405,11 @@
278 405 fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
279 406 sprintf(
280 407 '<a href="$4"%s>%s</a>',
281 408 '$5',
282 - fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() )
409 + $has_beta_update ?
410 + fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
411 + fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() )
283 412 ),
284 413 $this->_fs->get_plugin_title()
285 414 ) .
286 415 ' ' .
@@ -298,9 +427,9 @@
298 427 ) {
299 428 $slug = $this->_fs->get_slug();
300 429
301 430 $upgrade_notice_html = sprintf(
302 - '<p class="notice fs-upgrade-notice fs-slug-%1s fs-type-%2s" data-slug="%1s" data-type="%2s"><strong>%3s</strong> %4s</p>',
431 + '<p class="notice fs-upgrade-notice fs-slug-%1$s fs-type-%2$s" data-slug="%1$s" data-type="%2$s"><strong>%3$s</strong> %4$s</p>',
303 432 $slug,
304 433 $this->_fs->get_module_type(),
305 434 fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ),
306 435 esc_html( $r->upgrade_notice )
@@ -328,9 +457,9 @@
328 457 }
329 458
330 459 $themes_update = get_site_transient( 'update_themes' );
331 460 if ( ! isset( $themes_update->response[ $theme_basename ] ) ||
332 - empty( $themes_update->response[ $theme_basename ]['package'] )
461 + empty( $themes_update->response[ $theme_basename ]['package'] )
333 462 ) {
334 463 return $prepared_themes;
335 464 }
336 465
@@ -410,20 +539,44 @@
410 539 ) {
411 540 return $transient_data;
412 541 }
413 542
543 + // Alias.
544 + $basename = $this->_fs->premium_plugin_basename();
545 +
546 + global $wp_current_filter;
547 +
548 + /**
549 + * During bulk updates, avoid re-injecting update data for the plugin itself once it has already been updated.
550 + *
551 + * If the custom package is re-added to the transient after the plugin update, WordPress may detect the package again and incorrectly report "The plugin is at the latest version" for a pending update, since the custom package version matches the currently installed version.
552 + *
553 + * Behavior differs depending on how the bulk update is triggered. Please refer to the inline comments for each flow below for details.
554 + */
555 + if (
556 + ! empty( $wp_current_filter ) && (
557 + /**
558 + * update-core.php and other upgrader pages:
559 + * The `upgrader_process_complete` action fires only once after all updates have finished. In this case, it is the current action (`$wp_current_filter[0]`), while `self::$_upgrade_basename` may contain any plugin basename.
560 + */
561 + 'upgrader_process_complete' === $wp_current_filter[0] ||
562 + /**
563 + * AJAX bulk updates (e.g., from the Plugins page):
564 + * The `upgrader_process_complete` action fires multiple times — once for each plugin after it finishes updating. In this flow, it is not the current action (`$wp_current_filter[0]`) because it is triggered from another action. Instead, we compare `self::$_upgrade_basename` with the basename of the plugin currently being updated, since the `upgrader_process_complete` action runs separately for each plugin.
565 + */
566 + ( in_array( 'upgrader_process_complete', $wp_current_filter ) && self::$_upgrade_basename === $basename )
567 + )
568 + ) {
569 + return $transient_data;
570 + }
571 +
414 572 if ( ! isset( $this->_update_details ) ) {
415 573 // Get plugin's newest update.
416 - $new_version = $this->_fs->get_update(
417 - false,
418 - fs_request_get_bool( 'force-check' ),
419 - WP_FS__TIME_24_HOURS_IN_SEC / 24,
420 - $this->_fs->get_plugin_version()
421 - );
574 + $new_version = $this->_fs->get_update( false, fs_request_get_bool( 'force-check' ) );
422 575
423 576 $this->_update_details = false;
424 577
425 - if ( is_object( $new_version ) ) {
578 + if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) {
426 579 $this->_logger->log( 'Found newer plugin version ' . $new_version->version );
427 580
428 581 /**
429 582 * Cache plugin details locally since set_site_transient( 'update_plugins' )
@@ -436,25 +589,75 @@
436 589 }
437 590 }
438 591
439 592 if ( is_object( $this->_update_details ) ) {
593 + if ( isset( $transient_data->no_update ) ) {
594 + unset( $transient_data->no_update[ $basename ] );
595 + }
596 +
597 + if ( ! isset( $transient_data->response ) ) {
598 + $transient_data->response = array();
599 + }
600 +
440 601 // Add plugin to transient data.
441 - $transient_data->response[ $this->_fs->get_plugin_basename() ] = $this->_fs->is_plugin() ?
602 + $transient_data->response[ $basename ] = $this->_fs->is_plugin() ?
442 603 $this->_update_details :
443 604 (array) $this->_update_details;
605 + } else {
606 + if ( isset( $transient_data->response ) ) {
607 + /**
608 + * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version.
609 + *
610 + * @author Leo Fajardo (@leorw)
611 + * @since 2.3.0
612 + */
613 + unset( $transient_data->response[ $basename ] );
614 + }
615 +
616 + if ( ! isset( $transient_data->no_update ) ) {
617 + $transient_data->no_update = array();
618 + }
619 +
620 + /**
621 + * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI.
622 + *
623 + * @since 2.4.1
624 + * @link https://make.wordpress.org/core/2020/07/30/recommended-usage-of-the-updates-api-to-support-the-auto-updates-ui-for-plugins-and-themes-in-wordpress-5-5/
625 + */
626 + $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ?
627 + (object) array(
628 + 'id' => $basename,
629 + 'slug' => $this->_fs->get_slug(),
630 + 'plugin' => $basename,
631 + 'new_version' => $this->_fs->get_plugin_version(),
632 + 'url' => '',
633 + 'package' => '',
634 + 'icons' => array(),
635 + 'banners' => array(),
636 + 'banners_rtl' => array(),
637 + 'tested' => '',
638 + 'requires_php' => '',
639 + 'compatibility' => new stdClass(),
640 + ) :
641 + array(
642 + 'theme' => $basename,
643 + 'new_version' => $this->_fs->get_plugin_version(),
644 + 'url' => '',
645 + 'package' => '',
646 + 'requires' => '',
647 + 'requires_php' => '',
648 + );
444 649 }
445 650
446 651 $slug = $this->_fs->get_slug();
447 652
448 - if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) {
653 + if ( $this->can_fetch_data_from_wp_org() ) {
449 654 if ( ! isset( $this->_translation_updates ) ) {
450 655 $this->_translation_updates = array();
451 656
452 - if ( current_user_can( 'update_languages' ) ) {
453 - $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
454 - if ( ! empty( $translation_updates ) ) {
455 - $this->_translation_updates = $translation_updates;
456 - }
657 + $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
658 + if ( ! empty( $translation_updates ) ) {
659 + $this->_translation_updates = $translation_updates;
457 660 }
458 661 }
459 662
460 663 if ( ! empty( $this->_translation_updates ) ) {
@@ -472,9 +675,9 @@
472 675
473 676 foreach ( $this->_translation_updates as $translation_update ) {
474 677 $lang = $translation_update['language'];
475 678 if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) ||
476 - version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' )
679 + version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' )
477 680 ) {
478 681 $current_plugin_translation_updates_map[ $lang ] = $translation_update;
479 682 }
480 683 }
@@ -486,9 +689,9 @@
486 689 return $transient_data;
487 690 }
488 691
489 692 /**
490 - * Get module's required data for the updates mechanism.
693 + * Get module's required data for the updates' mechanism.
491 694 *
492 695 * @author Vova Feldman (@svovaf)
493 696 * @since 2.0.0
494 697 *
@@ -496,15 +699,16 @@
496 699 *
497 700 * @return object
498 701 */
499 702 function get_update_details( FS_Plugin_Tag $new_version ) {
500 - $update = new stdClass();
501 - $update->slug = $this->_fs->get_slug();
502 - $update->new_version = $new_version->version;
503 - $update->url = WP_FS__ADDRESS;
504 - $update->package = $new_version->url;
505 - $update->tested = $new_version->tested_up_to_version;
506 - $update->requires = $new_version->requires_platform_version;
703 + $update = new stdClass();
704 + $update->slug = $this->_fs->get_slug();
705 + $update->new_version = $new_version->version;
706 + $update->url = WP_FS__ADDRESS;
707 + $update->package = $new_version->url;
708 + $update->tested = self::get_tested_wp_version( $new_version->tested_up_to_version );
709 + $update->requires = $new_version->requires_platform_version;
710 + $update->requires_php = $new_version->requires_programming_language_version;
507 711
508 712 $icon = $this->_fs->get_local_icon_url();
509 713
510 714 if ( ! empty( $icon ) ) {
@@ -514,18 +718,10 @@
514 718 'default' => $icon,
515 719 );
516 720 }
517 721
518 - if ( $this->_fs->is_premium() ) {
519 - $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
520 -
521 - if (
522 - isset( $latest_tag->readme ) &&
523 - isset( $latest_tag->readme->upgrade_notice ) &&
524 - ! empty( $latest_tag->readme->upgrade_notice )
525 - ) {
526 - $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
527 - }
722 + if ( $this->_fs->is_premium() && ! empty( $new_version->upgrade_notice ) ) {
723 + $update->upgrade_notice = $new_version->upgrade_notice;
528 724 }
529 725
530 726 $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
531 727
@@ -532,8 +728,22 @@
532 728 return $update;
533 729 }
534 730
535 731 /**
732 + * @author Leo Fajardo (@leorw)
733 + * @since 2.3.0
734 + *
735 + * @param FS_Plugin_Tag $new_version
736 + *
737 + * @return bool
738 + */
739 + private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
740 + $params = fs_parse_url_params( $new_version->url );
741 +
742 + return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
743 + }
744 +
745 + /**
536 746 * Update the updates transient with the module's update information.
537 747 *
538 748 * This method is required for multisite environment.
539 749 * If a module is site activated (not network) and not on the main site,
@@ -549,8 +759,12 @@
549 759 */
550 760 function set_update_data( FS_Plugin_Tag $new_version ) {
551 761 $this->_logger->entrance();
552 762
763 + if ( ! $this->is_new_version_premium( $new_version ) ) {
764 + return;
765 + }
766 +
553 767 $transient_key = "update_{$this->_fs->get_module_type()}s";
554 768
555 769 $transient_data = get_site_transient( $transient_key );
556 770
@@ -621,11 +835,11 @@
621 835 // Alias
622 836 $basename = $this->_fs->get_plugin_basename();
623 837
624 838 if ( ! is_object( $transient_data ) ||
625 - ! isset( $transient_data->response ) ||
839 + ! isset( $transient_data->response ) ||
626 840 ! is_array( $transient_data->response ) ||
627 - empty( $transient_data->response[ $basename ] )
841 + empty( $transient_data->response[ $basename ] )
628 842 ) {
629 843 return;
630 844 }
631 845
@@ -650,30 +864,36 @@
650 864 *
651 865 * @return bool|mixed
652 866 */
653 867 static function _fetch_plugin_info_from_repository( $action, $args ) {
654 - $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/';
655 - if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
868 + $url = $http_url = 'http://api.wordpress.org/plugins/info/1.2/';
869 + $url = add_query_arg(
870 + array(
871 + 'action' => $action,
872 + 'request' => $args,
873 + ),
874 + $url
875 + );
876 +
877 + if ( wp_http_supports( array( 'ssl' ) ) ) {
656 878 $url = set_url_scheme( $url, 'https' );
657 879 }
658 880
659 - $args = array(
660 - 'timeout' => 15,
661 - 'body' => array(
662 - 'action' => $action,
663 - 'request' => serialize( $args )
664 - )
665 - );
881 + // The new endpoint version serves only GET requests.
882 + $request = wp_remote_get( $url, array( 'timeout' => 15 ) );
666 883
667 - $request = wp_remote_post( $url, $args );
668 -
669 884 if ( is_wp_error( $request ) ) {
670 885 return false;
671 886 }
672 887
673 - $res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
888 + $res = json_decode( wp_remote_retrieve_body( $request ), true );
674 889
675 - if ( ! is_object( $res ) && ! is_array( $res ) ) {
890 + if ( is_array( $res ) ) {
891 + // Object casting is required in order to match the info/1.0 format. We are not decoding directly into an object as we need some fields to remain an array (e.g., $res->sections).
892 + $res = (object) $res;
893 + }
894 +
895 + if ( ! is_object( $res ) || isset( $res->error ) ) {
676 896 return false;
677 897 }
678 898
679 899 return $res;
@@ -679,8 +899,18 @@
679 899 return $res;
680 900 }
681 901
682 902 /**
903 + * Returns true if the product can fetch data from WordPress.org.
904 + *
905 + * @author Leo Fajardo (@leorw)
906 + * @since 2.7.4
907 + */
908 + private function can_fetch_data_from_wp_org() {
909 + return ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() );
910 + }
911 +
912 + /**
683 913 * Fetches module translation updates from wordpress.org.
684 914 *
685 915 * @author Leo Fajardo (@leorw)
686 916 * @since 2.1.2
@@ -833,10 +1063,11 @@
833 1063 ) {
834 1064 return $data;
835 1065 }
836 1066
837 - $addon = false;
838 - $is_addon = false;
1067 + $addon = false;
1068 + $is_addon = false;
1069 + $addon_version = false;
839 1070
840 1071 if ( $this->_fs->get_slug() !== $args->slug ) {
841 1072 $addon = $this->_fs->get_addon_by_slug( $args->slug );
842 1073
@@ -843,13 +1074,27 @@
843 1074 if ( ! is_object( $addon ) ) {
844 1075 return $data;
845 1076 }
846 1077
1078 + if ( $this->_fs->is_addon_activated( $addon->id ) ) {
1079 + $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version();
1080 + } else if ( $this->_fs->is_addon_installed( $addon->id ) ) {
1081 + $addon_plugin_data = get_plugin_data(
1082 + ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ),
1083 + false,
1084 + false
1085 + );
1086 +
1087 + if ( ! empty( $addon_plugin_data ) ) {
1088 + $addon_version = $addon_plugin_data['Version'];
1089 + }
1090 + }
1091 +
847 1092 $is_addon = true;
848 1093 }
849 1094
850 1095 $plugin_in_repo = false;
851 - if ( ! $is_addon ) {
1096 + if ( ! $is_addon && $this->can_fetch_data_from_wp_org() ) {
852 1097 // Try to fetch info from .org repository.
853 1098 $data = self::_fetch_plugin_info_from_repository( $action, $args );
854 1099
855 1100 $plugin_in_repo = ( false !== $data );
@@ -873,9 +1118,11 @@
873 1118 $data = $info;
874 1119 }*/
875 1120 }
876 1121
877 - $plugin_version = $this->_fs->get_plugin_version();
1122 + $plugin_version = $is_addon ?
1123 + $addon_version :
1124 + $this->_fs->get_plugin_version();
878 1125
879 1126 // Get plugin's newest update.
880 1127 $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version );
881 1128
@@ -891,8 +1138,9 @@
891 1138
892 1139 if ( ! $plugin_in_repo ) {
893 1140 $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
894 1141 $data->requires = $new_version->requires_platform_version;
1142 + $data->requires_php = $new_version->requires_programming_language_version;
895 1143 $data->tested = $new_version->tested_up_to_version;
896 1144 }
897 1145
898 1146 $data->version = $new_version->version;
@@ -944,12 +1192,31 @@
944 1192 $data = $new_version_readme_data;
945 1193 }
946 1194 }
947 1195
1196 + if ( ! empty( $data->tested ) ) {
1197 + $data->tested = self::get_tested_wp_version( $data->tested );
1198 + }
1199 +
948 1200 return $data;
949 1201 }
950 1202
951 1203 /**
1204 + * @since 2.5.3 If the current WordPress version is a patch of the tested version (e.g., 6.1.2 is a patch of 6.1), then override the tested version with the patch so developers won't need to release a new version just to bump the latest supported WP version.
1205 + *
1206 + * @param string|null $tested_up_to
1207 + *
1208 + * @return string|null
1209 + */
1210 + private static function get_tested_wp_version( $tested_up_to ) {
1211 + $current_wp_version = get_bloginfo( 'version' );
1212 +
1213 + return ( ! empty($tested_up_to) && fs_starts_with( $current_wp_version, $tested_up_to . '.' ) ) ?
1214 + $current_wp_version :
1215 + $tested_up_to;
1216 + }
1217 +
1218 + /**
952 1219 * @author Vova Feldman (@svovaf)
953 1220 * @since 1.2.1.7
954 1221 *
955 1222 * @param number|bool $addon_id
@@ -958,9 +1225,9 @@
958 1225 *
959 1226 * @return object
960 1227 */
961 1228 private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
962 - return $this->_fs->_fetch_latest_version( $addon_id, true, WP_FS__TIME_24_HOURS_IN_SEC, $newer_than, $fetch_readme );
1229 + return $this->_fs->_fetch_latest_version( $addon_id, true, FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION, $newer_than, $fetch_readme );
963 1230 }
964 1231
965 1232 /**
966 1233 * Checks if a given basename has a matching folder name
@@ -1001,10 +1268,10 @@
1001 1268 }
1002 1269
1003 1270 $active_plugins_basenames = get_option( 'active_plugins' );
1004 1271
1005 - for ( $i = 0, $len = count( $active_plugins_basenames ); $i < $len; $i ++ ) {
1006 - if ( $basename === $active_plugins_basenames[ $i ] ) {
1272 + foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) {
1273 + if ( $basename === $active_plugin_basename ) {
1007 1274 // Get filename including extension.
1008 1275 $filename = basename( $basename );
1009 1276
1010 1277 $new_basename = plugin_basename(
@@ -1014,9 +1281,9 @@
1014 1281
1015 1282 // Verify that the expected correct path exists.
1016 1283 if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) {
1017 1284 // Override active plugin name.
1018 - $active_plugins_basenames[ $i ] = $new_basename;
1285 + $active_plugins_basenames[ $key ] = $new_basename;
1019 1286 update_option( 'active_plugins', $active_plugins_basenames );
1020 1287 }
1021 1288
1022 1289 break;