PluginProbe
WPIDE – File Manager & Code Editor / 3.5.9
WPIDE – File Manager & Code Editor v3.5.9
3.5.9 3.5.8 3.5.7 2.0.14 2.0.15 2.0.16 2.0.2 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1 2.2 2.3 2.3.1 2.3.2 2.4.0 2.5 2.6 3.0 3.1 3.2 3.3 All 55 releases
← All changes | freemius/includes/class-fs-plugin-updater.php +160 -94 3.23.5.9 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
@@ -103,8 +105,10 @@
103 105 ), 11, 2 );
104 106
105 107 if ( ! $this->_fs->has_any_active_valid_license() ) {
106 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' ) );
107 111 }
108 112
109 113 if ( ! WP_FS__IS_PRODUCTION_MODE ) {
110 114 add_filter( 'http_request_host_is_external', array(
@@ -128,15 +132,48 @@
128 132 }
129 133
130 134 /**
131 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)
132 172 * @since 2.1.4
133 173 */
134 174 function catch_plugin_information_dialog_contents() {
135 - if (
136 - 'plugin-information' !== fs_request_get( 'tab', false ) ||
137 - $this->_fs->get_slug() !== fs_request_get( 'plugin', false )
138 - ) {
175 + if ( ! $this->is_plugin_information_dialog_for_plugin() ) {
139 176 return;
140 177 }
141 178
142 179 add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 );
@@ -152,9 +189,9 @@
152 189 */
153 190 function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) {
154 191 if (
155 192 'plugin-information' !== fs_request_get( 'tab', false ) ||
156 - $this->_fs->get_slug() !== fs_request_get( 'plugin', false )
193 + $this->_fs->get_slug() !== fs_request_get_raw( 'plugin', false )
157 194 ) {
158 195 return;
159 196 }
160 197
@@ -165,17 +202,21 @@
165 202 null;
166 203
167 204 $contents = ob_get_clean();
168 205
169 - $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"' );
170 207
171 - if ( false !== $update_button_id_attribute_pos ) {
172 - $update_button_start_pos = strrpos(
173 - 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 ),
174 215 '<a'
175 216 );
176 217
177 - $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>' ) );
178 219
179 220 /**
180 221 * The part of the contents without the update button.
181 222 *
@@ -181,11 +222,11 @@
181 222 *
182 223 * @author Leo Fajardo (@leorw)
183 224 * @since 2.2.5
184 225 */
185 - $modified_contents = substr( $contents, 0, $update_button_start_pos );
226 + $modified_contents = substr( $contents, 0, $install_or_update_button_start_pos );
186 227
187 - $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 ) );
188 229
189 230 /**
190 231 * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
191 232 * the text will be "Renew license" and will link to the checkout page with the license's billing cycle
@@ -190,13 +231,13 @@
190 231 * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
191 232 * the text will be "Renew license" and will link to the checkout page with the license's billing cycle
192 233 * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page.
193 234 */
194 - $update_button = preg_replace(
195 - '/(\<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',
196 237 is_object( $license ) ?
197 238 sprintf(
198 - '$1$3%s$5%s$7',
239 + '$1$4%s$6%s$8',
199 240 $this->_fs->checkout_url(
200 241 is_object( $subscription ) ?
201 242 ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) :
202 243 WP_FS__PERIOD_LIFETIME,
@@ -205,13 +246,13 @@
205 246 ),
206 247 fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() )
207 248 ) :
208 249 sprintf(
209 - '$1$3%s$5%s$7',
250 + '$1$4%s$6%s$8',
210 251 $this->_fs->pricing_url(),
211 252 fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() )
212 253 ),
213 - $update_button
254 + $install_or_update_button
214 255 );
215 256
216 257 /**
217 258 * Append the modified button.
@@ -218,9 +259,9 @@
218 259 *
219 260 * @author Leo Fajardo (@leorw)
220 261 * @since 2.2.5
221 262 */
222 - $modified_contents .= $update_button;
263 + $modified_contents .= $install_or_update_button;
223 264
224 265 /**
225 266 * Append the remaining part of the contents after the update button.
226 267 *
@@ -226,9 +267,9 @@
226 267 *
227 268 * @author Leo Fajardo (@leorw)
228 269 * @since 2.2.5
229 270 */
230 - $modified_contents .= substr( $contents, $update_button_end_pos );
271 + $modified_contents .= substr( $contents, $install_or_update_button_end_pos );
231 272
232 273 $contents = $modified_contents;
233 274 }
234 275
@@ -239,9 +280,13 @@
239 280 * @author Vova Feldman (@svovaf)
240 281 * @since 2.0.0
241 282 */
242 283 private function add_transient_filters() {
243 - if ( $this->_fs->is_premium() && ! $this->_fs->is_tracking_allowed() ) {
284 + if (
285 + $this->_fs->is_premium() &&
286 + $this->_fs->is_registered() &&
287 + ! FS_Permission_Manager::instance( $this->_fs )->is_essentials_tracking_allowed()
288 + ) {
244 289 $this->_logger->log( 'Opted out sites cannot receive automatic software updates.' );
245 290
246 291 return;
247 292 }
@@ -412,9 +457,9 @@
412 457 }
413 458
414 459 $themes_update = get_site_transient( 'update_themes' );
415 460 if ( ! isset( $themes_update->response[ $theme_basename ] ) ||
416 - empty( $themes_update->response[ $theme_basename ]['package'] )
461 + empty( $themes_update->response[ $theme_basename ]['package'] )
417 462 ) {
418 463 return $prepared_themes;
419 464 }
420 465
@@ -494,38 +539,40 @@
494 539 ) {
495 540 return $transient_data;
496 541 }
497 542
543 + // Alias.
544 + $basename = $this->_fs->premium_plugin_basename();
545 +
498 546 global $wp_current_filter;
499 547
500 - $current_plugin_version = $this->_fs->get_plugin_version();
501 -
502 - if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) {
503 - if (
504 - is_null( $this->_update_details ) ||
505 - ( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version )
506 - ) {
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 ) && (
507 557 /**
508 - * After an update, clear the stored update details and reparse the plugin's main file in order to get
509 - * the updated version's information and prevent the previous update information from showing up on the
510 - * updates page.
511 - *
512 - * @author Leo Fajardo (@leorw)
513 - * @since 2.3.1
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.
514 560 */
515 - $this->_update_details = null;
516 - $current_plugin_version = $this->_fs->get_plugin_version( true );
517 - }
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;
518 570 }
519 571
520 572 if ( ! isset( $this->_update_details ) ) {
521 573 // Get plugin's newest update.
522 - $new_version = $this->_fs->get_update(
523 - false,
524 - fs_request_get_bool( 'force-check' ),
525 - WP_FS__TIME_24_HOURS_IN_SEC / 24,
526 - $current_plugin_version
527 - );
574 + $new_version = $this->_fs->get_update( false, fs_request_get_bool( 'force-check' ) );
528 575
529 576 $this->_update_details = false;
530 577
531 578 if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) {
@@ -541,11 +588,8 @@
541 588 $this->_update_details = $this->get_update_details( $new_version );
542 589 }
543 590 }
544 591
545 - // Alias.
546 - $basename = $this->_fs->premium_plugin_basename();
547 -
548 592 if ( is_object( $this->_update_details ) ) {
549 593 if ( isset( $transient_data->no_update ) ) {
550 594 unset( $transient_data->no_update[ $basename ] );
551 595 }
@@ -605,17 +649,15 @@
605 649 }
606 650
607 651 $slug = $this->_fs->get_slug();
608 652
609 - if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) {
653 + if ( $this->can_fetch_data_from_wp_org() ) {
610 654 if ( ! isset( $this->_translation_updates ) ) {
611 655 $this->_translation_updates = array();
612 656
613 - if ( current_user_can( 'update_languages' ) ) {
614 - $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
615 - if ( ! empty( $translation_updates ) ) {
616 - $this->_translation_updates = $translation_updates;
617 - }
657 + $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
658 + if ( ! empty( $translation_updates ) ) {
659 + $this->_translation_updates = $translation_updates;
618 660 }
619 661 }
620 662
621 663 if ( ! empty( $this->_translation_updates ) ) {
@@ -633,9 +675,9 @@
633 675
634 676 foreach ( $this->_translation_updates as $translation_update ) {
635 677 $lang = $translation_update['language'];
636 678 if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) ||
637 - 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'], '>' )
638 680 ) {
639 681 $current_plugin_translation_updates_map[ $lang ] = $translation_update;
640 682 }
641 683 }
@@ -647,9 +689,9 @@
647 689 return $transient_data;
648 690 }
649 691
650 692 /**
651 - * Get module's required data for the updates mechanism.
693 + * Get module's required data for the updates' mechanism.
652 694 *
653 695 * @author Vova Feldman (@svovaf)
654 696 * @since 2.0.0
655 697 *
@@ -657,15 +699,16 @@
657 699 *
658 700 * @return object
659 701 */
660 702 function get_update_details( FS_Plugin_Tag $new_version ) {
661 - $update = new stdClass();
662 - $update->slug = $this->_fs->get_slug();
663 - $update->new_version = $new_version->version;
664 - $update->url = WP_FS__ADDRESS;
665 - $update->package = $new_version->url;
666 - $update->tested = $new_version->tested_up_to_version;
667 - $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;
668 711
669 712 $icon = $this->_fs->get_local_icon_url();
670 713
671 714 if ( ! empty( $icon ) ) {
@@ -675,18 +718,10 @@
675 718 'default' => $icon,
676 719 );
677 720 }
678 721
679 - if ( $this->_fs->is_premium() ) {
680 - $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
681 -
682 - if (
683 - isset( $latest_tag->readme ) &&
684 - isset( $latest_tag->readme->upgrade_notice ) &&
685 - ! empty( $latest_tag->readme->upgrade_notice )
686 - ) {
687 - $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
688 - }
722 + if ( $this->_fs->is_premium() && ! empty( $new_version->upgrade_notice ) ) {
723 + $update->upgrade_notice = $new_version->upgrade_notice;
689 724 }
690 725
691 726 $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
692 727
@@ -701,15 +736,10 @@
701 736 *
702 737 * @return bool
703 738 */
704 739 private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
705 - $query_str = parse_url( $new_version->url, PHP_URL_QUERY );
706 - if ( empty( $query_str ) ) {
707 - return false;
708 - }
740 + $params = fs_parse_url_params( $new_version->url );
709 741
710 - parse_str( $query_str, $params );
711 -
712 742 return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
713 743 }
714 744
715 745 /**
@@ -805,11 +835,11 @@
805 835 // Alias
806 836 $basename = $this->_fs->get_plugin_basename();
807 837
808 838 if ( ! is_object( $transient_data ) ||
809 - ! isset( $transient_data->response ) ||
839 + ! isset( $transient_data->response ) ||
810 840 ! is_array( $transient_data->response ) ||
811 - empty( $transient_data->response[ $basename ] )
841 + empty( $transient_data->response[ $basename ] )
812 842 ) {
813 843 return;
814 844 }
815 845
@@ -834,30 +864,36 @@
834 864 *
835 865 * @return bool|mixed
836 866 */
837 867 static function _fetch_plugin_info_from_repository( $action, $args ) {
838 - $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/';
839 - 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' ) ) ) {
840 878 $url = set_url_scheme( $url, 'https' );
841 879 }
842 880
843 - $args = array(
844 - 'timeout' => 15,
845 - 'body' => array(
846 - 'action' => $action,
847 - 'request' => serialize( $args )
848 - )
849 - );
881 + // The new endpoint version serves only GET requests.
882 + $request = wp_remote_get( $url, array( 'timeout' => 15 ) );
850 883
851 - $request = wp_remote_post( $url, $args );
852 -
853 884 if ( is_wp_error( $request ) ) {
854 885 return false;
855 886 }
856 887
857 - $res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
888 + $res = json_decode( wp_remote_retrieve_body( $request ), true );
858 889
859 - 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 ) ) {
860 896 return false;
861 897 }
862 898
863 899 return $res;
@@ -863,8 +899,18 @@
863 899 return $res;
864 900 }
865 901
866 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 + /**
867 913 * Fetches module translation updates from wordpress.org.
868 914 *
869 915 * @author Leo Fajardo (@leorw)
870 916 * @since 2.1.2
@@ -1046,9 +1092,9 @@
1046 1092 $is_addon = true;
1047 1093 }
1048 1094
1049 1095 $plugin_in_repo = false;
1050 - if ( ! $is_addon ) {
1096 + if ( ! $is_addon && $this->can_fetch_data_from_wp_org() ) {
1051 1097 // Try to fetch info from .org repository.
1052 1098 $data = self::_fetch_plugin_info_from_repository( $action, $args );
1053 1099
1054 1100 $plugin_in_repo = ( false !== $data );
@@ -1092,8 +1138,9 @@
1092 1138
1093 1139 if ( ! $plugin_in_repo ) {
1094 1140 $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
1095 1141 $data->requires = $new_version->requires_platform_version;
1142 + $data->requires_php = $new_version->requires_programming_language_version;
1096 1143 $data->tested = $new_version->tested_up_to_version;
1097 1144 }
1098 1145
1099 1146 $data->version = $new_version->version;
@@ -1145,12 +1192,31 @@
1145 1192 $data = $new_version_readme_data;
1146 1193 }
1147 1194 }
1148 1195
1196 + if ( ! empty( $data->tested ) ) {
1197 + $data->tested = self::get_tested_wp_version( $data->tested );
1198 + }
1199 +
1149 1200 return $data;
1150 1201 }
1151 1202
1152 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 + /**
1153 1219 * @author Vova Feldman (@svovaf)
1154 1220 * @since 1.2.1.7
1155 1221 *
1156 1222 * @param number|bool $addon_id
@@ -1159,9 +1225,9 @@
1159 1225 *
1160 1226 * @return object
1161 1227 */
1162 1228 private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
1163 - 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 );
1164 1230 }
1165 1231
1166 1232 /**
1167 1233 * Checks if a given basename has a matching folder name