PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
← All changes | libraries/freemius/includes/class-fs-plugin-updater.php +119 -76 2.0.4 → 3.4 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
@@ -498,38 +539,40 @@
498 539 ) {
499 540 return $transient_data;
500 541 }
501 542
543 + // Alias.
544 + $basename = $this->_fs->premium_plugin_basename();
545 +
502 546 global $wp_current_filter;
503 547
504 - $current_plugin_version = $this->_fs->get_plugin_version();
505 -
506 - if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) {
507 - if (
508 - is_null( $this->_update_details ) ||
509 - ( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version )
510 - ) {
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 ) && (
511 557 /**
512 - * After an update, clear the stored update details and reparse the plugin's main file in order to get
513 - * the updated version's information and prevent the previous update information from showing up on the
514 - * updates page.
515 - *
516 - * @author Leo Fajardo (@leorw)
517 - * @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.
518 560 */
519 - $this->_update_details = null;
520 - $current_plugin_version = $this->_fs->get_plugin_version( true );
521 - }
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;
522 570 }
523 571
524 572 if ( ! isset( $this->_update_details ) ) {
525 573 // Get plugin's newest update.
526 - $new_version = $this->_fs->get_update(
527 - false,
528 - fs_request_get_bool( 'force-check' ),
529 - WP_FS__TIME_24_HOURS_IN_SEC / 24,
530 - $current_plugin_version
531 - );
574 + $new_version = $this->_fs->get_update( false, fs_request_get_bool( 'force-check' ) );
532 575
533 576 $this->_update_details = false;
534 577
535 578 if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) {
@@ -545,11 +588,8 @@
545 588 $this->_update_details = $this->get_update_details( $new_version );
546 589 }
547 590 }
548 591
549 - // Alias.
550 - $basename = $this->_fs->premium_plugin_basename();
551 -
552 592 if ( is_object( $this->_update_details ) ) {
553 593 if ( isset( $transient_data->no_update ) ) {
554 594 unset( $transient_data->no_update[ $basename ] );
555 595 }
@@ -609,9 +649,9 @@
609 649 }
610 650
611 651 $slug = $this->_fs->get_slug();
612 652
613 - if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) {
653 + if ( $this->can_fetch_data_from_wp_org() ) {
614 654 if ( ! isset( $this->_translation_updates ) ) {
615 655 $this->_translation_updates = array();
616 656
617 657 $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
@@ -678,18 +718,10 @@
678 718 'default' => $icon,
679 719 );
680 720 }
681 721
682 - if ( $this->_fs->is_premium() ) {
683 - $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
684 -
685 - if (
686 - isset( $latest_tag->readme ) &&
687 - isset( $latest_tag->readme->upgrade_notice ) &&
688 - ! empty( $latest_tag->readme->upgrade_notice )
689 - ) {
690 - $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
691 - }
722 + if ( $this->_fs->is_premium() && ! empty( $new_version->upgrade_notice ) ) {
723 + $update->upgrade_notice = $new_version->upgrade_notice;
692 724 }
693 725
694 726 $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
695 727
@@ -704,15 +736,10 @@
704 736 *
705 737 * @return bool
706 738 */
707 739 private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
708 - $query_str = parse_url( $new_version->url, PHP_URL_QUERY );
709 - if ( empty( $query_str ) ) {
710 - return false;
711 - }
740 + $params = fs_parse_url_params( $new_version->url );
712 741
713 - parse_str( $query_str, $params );
714 -
715 742 return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
716 743 }
717 744
718 745 /**
@@ -837,30 +864,36 @@
837 864 *
838 865 * @return bool|mixed
839 866 */
840 867 static function _fetch_plugin_info_from_repository( $action, $args ) {
841 - $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/';
842 - 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' ) ) ) {
843 878 $url = set_url_scheme( $url, 'https' );
844 879 }
845 880
846 - $args = array(
847 - 'timeout' => 15,
848 - 'body' => array(
849 - 'action' => $action,
850 - 'request' => serialize( $args )
851 - )
852 - );
881 + // The new endpoint version serves only GET requests.
882 + $request = wp_remote_get( $url, array( 'timeout' => 15 ) );
853 883
854 - $request = wp_remote_post( $url, $args );
855 -
856 884 if ( is_wp_error( $request ) ) {
857 885 return false;
858 886 }
859 887
860 - $res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
888 + $res = json_decode( wp_remote_retrieve_body( $request ), true );
861 889
862 - 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 ) ) {
863 896 return false;
864 897 }
865 898
866 899 return $res;
@@ -866,8 +899,18 @@
866 899 return $res;
867 900 }
868 901
869 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 + /**
870 913 * Fetches module translation updates from wordpress.org.
871 914 *
872 915 * @author Leo Fajardo (@leorw)
873 916 * @since 2.1.2
@@ -1049,9 +1092,9 @@
1049 1092 $is_addon = true;
1050 1093 }
1051 1094
1052 1095 $plugin_in_repo = false;
1053 - if ( ! $is_addon ) {
1096 + if ( ! $is_addon && $this->can_fetch_data_from_wp_org() ) {
1054 1097 // Try to fetch info from .org repository.
1055 1098 $data = self::_fetch_plugin_info_from_repository( $action, $args );
1056 1099
1057 1100 $plugin_in_repo = ( false !== $data );
@@ -1182,9 +1225,9 @@
1182 1225 *
1183 1226 * @return object
1184 1227 */
1185 1228 private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
1186 - 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 );
1187 1230 }
1188 1231
1189 1232 /**
1190 1233 * Checks if a given basename has a matching folder name