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 +220 -81 2.3.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,17 +202,21 @@
163 202 null;
164 203
165 204 $contents = ob_get_clean();
166 205
167 - $update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' );
206 + $install_or_update_button_id_attribute_pos = strpos( $contents, 'id="plugin_install_from_iframe"' );
168 207
169 - if ( false !== $update_button_id_attribute_pos ) {
170 - $update_button_start_pos = strrpos(
171 - substr( $contents, 0, $update_button_id_attribute_pos ),
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 ),
172 215 '<a'
173 216 );
174 217
175 - $update_button_end_pos = ( strpos( $contents, '</a>', $update_button_id_attribute_pos ) + strlen( '</a>' ) );
218 + $install_or_update_button_end_pos = ( strpos( $contents, '</a>', $install_or_update_button_id_attribute_pos ) + strlen( '</a>' ) );
176 219
177 220 /**
178 221 * The part of the contents without the update button.
179 222 *
@@ -179,11 +222,11 @@
179 222 *
180 223 * @author Leo Fajardo (@leorw)
181 224 * @since 2.2.5
182 225 */
183 - $modified_contents = substr( $contents, 0, $update_button_start_pos );
226 + $modified_contents = substr( $contents, 0, $install_or_update_button_start_pos );
184 227
185 - $update_button = substr( $contents, $update_button_start_pos, ( $update_button_end_pos - $update_button_start_pos ) );
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 ) );
186 229
187 230 /**
188 231 * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
189 232 * the text will be "Renew license" and will link to the checkout page with the license's billing cycle
@@ -188,13 +231,13 @@
188 231 * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
189 232 * the text will be "Renew license" and will link to the checkout page with the license's billing cycle
190 233 * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page.
191 234 */
192 - $update_button = preg_replace(
193 - '/(\<a.+)(id="plugin_update_from_iframe")(.+href=")([^\s]+)(".*\>)(.+)(\<\/a>)/is',
235 + $install_or_update_button = preg_replace(
236 + '/(\<a.+)(id="plugin_(install|update)_from_iframe")(.+href=")([^\s]+)(".*\>)(.+)(\<\/a>)/is',
194 237 is_object( $license ) ?
195 238 sprintf(
196 - '$1$3%s$5%s$7',
239 + '$1$4%s$6%s$8',
197 240 $this->_fs->checkout_url(
198 241 is_object( $subscription ) ?
199 242 ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) :
200 243 WP_FS__PERIOD_LIFETIME,
@@ -203,13 +246,13 @@
203 246 ),
204 247 fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() )
205 248 ) :
206 249 sprintf(
207 - '$1$3%s$5%s$7',
250 + '$1$4%s$6%s$8',
208 251 $this->_fs->pricing_url(),
209 252 fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() )
210 253 ),
211 - $update_button
254 + $install_or_update_button
212 255 );
213 256
214 257 /**
215 258 * Append the modified button.
@@ -216,9 +259,9 @@
216 259 *
217 260 * @author Leo Fajardo (@leorw)
218 261 * @since 2.2.5
219 262 */
220 - $modified_contents .= $update_button;
263 + $modified_contents .= $install_or_update_button;
221 264
222 265 /**
223 266 * Append the remaining part of the contents after the update button.
224 267 *
@@ -224,9 +267,9 @@
224 267 *
225 268 * @author Leo Fajardo (@leorw)
226 269 * @since 2.2.5
227 270 */
228 - $modified_contents .= substr( $contents, $update_button_end_pos );
271 + $modified_contents .= substr( $contents, $install_or_update_button_end_pos );
229 272
230 273 $contents = $modified_contents;
231 274 }
232 275
@@ -237,8 +280,18 @@
237 280 * @author Vova Feldman (@svovaf)
238 281 * @since 2.0.0
239 282 */
240 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 +
241 294 add_filter( 'pre_set_site_transient_update_plugins', array(
242 295 &$this,
243 296 'pre_set_site_transient_update_plugins_filter'
244 297 ) );
@@ -404,9 +457,9 @@
404 457 }
405 458
406 459 $themes_update = get_site_transient( 'update_themes' );
407 460 if ( ! isset( $themes_update->response[ $theme_basename ] ) ||
408 - empty( $themes_update->response[ $theme_basename ]['package'] )
461 + empty( $themes_update->response[ $theme_basename ]['package'] )
409 462 ) {
410 463 return $prepared_themes;
411 464 }
412 465
@@ -486,16 +539,40 @@
486 539 ) {
487 540 return $transient_data;
488 541 }
489 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 +
490 572 if ( ! isset( $this->_update_details ) ) {
491 573 // Get plugin's newest update.
492 - $new_version = $this->_fs->get_update(
493 - false,
494 - fs_request_get_bool( 'force-check' ),
495 - WP_FS__TIME_24_HOURS_IN_SEC / 24,
496 - $this->_fs->get_plugin_version()
497 - );
574 + $new_version = $this->_fs->get_update( false, fs_request_get_bool( 'force-check' ) );
498 575
499 576 $this->_update_details = false;
500 577
501 578 if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) {
@@ -512,37 +589,75 @@
512 589 }
513 590 }
514 591
515 592 if ( is_object( $this->_update_details ) ) {
593 + if ( isset( $transient_data->no_update ) ) {
594 + unset( $transient_data->no_update[ $basename ] );
595 + }
596 +
516 597 if ( ! isset( $transient_data->response ) ) {
517 598 $transient_data->response = array();
518 599 }
519 600
520 601 // Add plugin to transient data.
521 - $transient_data->response[ $this->_fs->premium_plugin_basename() ] = $this->_fs->is_plugin() ?
602 + $transient_data->response[ $basename ] = $this->_fs->is_plugin() ?
522 603 $this->_update_details :
523 604 (array) $this->_update_details;
524 - } else if ( isset( $transient_data->response ) ) {
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 +
525 620 /**
526 - * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version.
621 + * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI.
527 622 *
528 - * @author Leo Fajardo (@leorw)
529 - * @since 2.3.0
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/
530 625 */
531 - unset( $transient_data->response[ $this->_fs->premium_plugin_basename() ] );
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 + );
532 649 }
533 650
534 651 $slug = $this->_fs->get_slug();
535 652
536 - if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) {
653 + if ( $this->can_fetch_data_from_wp_org() ) {
537 654 if ( ! isset( $this->_translation_updates ) ) {
538 655 $this->_translation_updates = array();
539 656
540 - if ( current_user_can( 'update_languages' ) ) {
541 - $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
542 - if ( ! empty( $translation_updates ) ) {
543 - $this->_translation_updates = $translation_updates;
544 - }
657 + $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
658 + if ( ! empty( $translation_updates ) ) {
659 + $this->_translation_updates = $translation_updates;
545 660 }
546 661 }
547 662
548 663 if ( ! empty( $this->_translation_updates ) ) {
@@ -560,9 +675,9 @@
560 675
561 676 foreach ( $this->_translation_updates as $translation_update ) {
562 677 $lang = $translation_update['language'];
563 678 if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) ||
564 - 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'], '>' )
565 680 ) {
566 681 $current_plugin_translation_updates_map[ $lang ] = $translation_update;
567 682 }
568 683 }
@@ -574,9 +689,9 @@
574 689 return $transient_data;
575 690 }
576 691
577 692 /**
578 - * Get module's required data for the updates mechanism.
693 + * Get module's required data for the updates' mechanism.
579 694 *
580 695 * @author Vova Feldman (@svovaf)
581 696 * @since 2.0.0
582 697 *
@@ -584,15 +699,16 @@
584 699 *
585 700 * @return object
586 701 */
587 702 function get_update_details( FS_Plugin_Tag $new_version ) {
588 - $update = new stdClass();
589 - $update->slug = $this->_fs->get_slug();
590 - $update->new_version = $new_version->version;
591 - $update->url = WP_FS__ADDRESS;
592 - $update->package = $new_version->url;
593 - $update->tested = $new_version->tested_up_to_version;
594 - $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;
595 711
596 712 $icon = $this->_fs->get_local_icon_url();
597 713
598 714 if ( ! empty( $icon ) ) {
@@ -602,18 +718,10 @@
602 718 'default' => $icon,
603 719 );
604 720 }
605 721
606 - if ( $this->_fs->is_premium() ) {
607 - $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
608 -
609 - if (
610 - isset( $latest_tag->readme ) &&
611 - isset( $latest_tag->readme->upgrade_notice ) &&
612 - ! empty( $latest_tag->readme->upgrade_notice )
613 - ) {
614 - $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
615 - }
722 + if ( $this->_fs->is_premium() && ! empty( $new_version->upgrade_notice ) ) {
723 + $update->upgrade_notice = $new_version->upgrade_notice;
616 724 }
617 725
618 726 $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
619 727
@@ -628,15 +736,10 @@
628 736 *
629 737 * @return bool
630 738 */
631 739 private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
632 - $query_str = parse_url( $new_version->url, PHP_URL_QUERY );
633 - if ( empty( $query_str ) ) {
634 - return false;
635 - }
740 + $params = fs_parse_url_params( $new_version->url );
636 741
637 - parse_str( $query_str, $params );
638 -
639 742 return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
640 743 }
641 744
642 745 /**
@@ -732,11 +835,11 @@
732 835 // Alias
733 836 $basename = $this->_fs->get_plugin_basename();
734 837
735 838 if ( ! is_object( $transient_data ) ||
736 - ! isset( $transient_data->response ) ||
839 + ! isset( $transient_data->response ) ||
737 840 ! is_array( $transient_data->response ) ||
738 - empty( $transient_data->response[ $basename ] )
841 + empty( $transient_data->response[ $basename ] )
739 842 ) {
740 843 return;
741 844 }
742 845
@@ -761,30 +864,36 @@
761 864 *
762 865 * @return bool|mixed
763 866 */
764 867 static function _fetch_plugin_info_from_repository( $action, $args ) {
765 - $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/';
766 - 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' ) ) ) {
767 878 $url = set_url_scheme( $url, 'https' );
768 879 }
769 880
770 - $args = array(
771 - 'timeout' => 15,
772 - 'body' => array(
773 - 'action' => $action,
774 - 'request' => serialize( $args )
775 - )
776 - );
881 + // The new endpoint version serves only GET requests.
882 + $request = wp_remote_get( $url, array( 'timeout' => 15 ) );
777 883
778 - $request = wp_remote_post( $url, $args );
779 -
780 884 if ( is_wp_error( $request ) ) {
781 885 return false;
782 886 }
783 887
784 - $res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
888 + $res = json_decode( wp_remote_retrieve_body( $request ), true );
785 889
786 - 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 ) ) {
787 896 return false;
788 897 }
789 898
790 899 return $res;
@@ -790,8 +899,18 @@
790 899 return $res;
791 900 }
792 901
793 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 + /**
794 913 * Fetches module translation updates from wordpress.org.
795 914 *
796 915 * @author Leo Fajardo (@leorw)
797 916 * @since 2.1.2
@@ -973,9 +1092,9 @@
973 1092 $is_addon = true;
974 1093 }
975 1094
976 1095 $plugin_in_repo = false;
977 - if ( ! $is_addon ) {
1096 + if ( ! $is_addon && $this->can_fetch_data_from_wp_org() ) {
978 1097 // Try to fetch info from .org repository.
979 1098 $data = self::_fetch_plugin_info_from_repository( $action, $args );
980 1099
981 1100 $plugin_in_repo = ( false !== $data );
@@ -1019,8 +1138,9 @@
1019 1138
1020 1139 if ( ! $plugin_in_repo ) {
1021 1140 $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
1022 1141 $data->requires = $new_version->requires_platform_version;
1142 + $data->requires_php = $new_version->requires_programming_language_version;
1023 1143 $data->tested = $new_version->tested_up_to_version;
1024 1144 }
1025 1145
1026 1146 $data->version = $new_version->version;
@@ -1072,12 +1192,31 @@
1072 1192 $data = $new_version_readme_data;
1073 1193 }
1074 1194 }
1075 1195
1196 + if ( ! empty( $data->tested ) ) {
1197 + $data->tested = self::get_tested_wp_version( $data->tested );
1198 + }
1199 +
1076 1200 return $data;
1077 1201 }
1078 1202
1079 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 + /**
1080 1219 * @author Vova Feldman (@svovaf)
1081 1220 * @since 1.2.1.7
1082 1221 *
1083 1222 * @param number|bool $addon_id
@@ -1086,9 +1225,9 @@
1086 1225 *
1087 1226 * @return object
1088 1227 */
1089 1228 private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
1090 - 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 );
1091 1230 }
1092 1231
1093 1232 /**
1094 1233 * Checks if a given basename has a matching folder name