PluginProbe
Passster – Password Protect Pages and Content / 4.3.17
Passster – Password Protect Pages and Content v4.3.17
4.3.17 4.3.16 4.3.15 4.3.14 4.3.12 4.3.13 4.3.11 4.3.10 4.3.9 4.3.8 4.3.7 4.3.6 4.3.5 trunk 3.5.4 3.5.5.2 3.5.5.8 3.5.5.9 4.0 4.1.4 4.2.10 4.2.11 4.2.12 4.2.13 4.2.14 All 49 releases
← All changes | inc/freemius/includes/class-fs-plugin-updater.php +151 -87 3.5.5.9 → 4.3.17 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
@@ -416,9 +457,9 @@
416 457 }
417 458
418 459 $themes_update = get_site_transient( 'update_themes' );
419 460 if ( ! isset( $themes_update->response[ $theme_basename ] ) ||
420 - empty( $themes_update->response[ $theme_basename ]['package'] )
461 + empty( $themes_update->response[ $theme_basename ]['package'] )
421 462 ) {
422 463 return $prepared_themes;
423 464 }
424 465
@@ -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 );
@@ -635,9 +675,9 @@
635 675
636 676 foreach ( $this->_translation_updates as $translation_update ) {
637 677 $lang = $translation_update['language'];
638 678 if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) ||
639 - 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'], '>' )
640 680 ) {
641 681 $current_plugin_translation_updates_map[ $lang ] = $translation_update;
642 682 }
643 683 }
@@ -659,15 +699,16 @@
659 699 *
660 700 * @return object
661 701 */
662 702 function get_update_details( FS_Plugin_Tag $new_version ) {
663 - $update = new stdClass();
664 - $update->slug = $this->_fs->get_slug();
665 - $update->new_version = $new_version->version;
666 - $update->url = WP_FS__ADDRESS;
667 - $update->package = $new_version->url;
668 - $update->tested = $new_version->tested_up_to_version;
669 - $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;
670 711
671 712 $icon = $this->_fs->get_local_icon_url();
672 713
673 714 if ( ! empty( $icon ) ) {
@@ -677,18 +718,10 @@
677 718 'default' => $icon,
678 719 );
679 720 }
680 721
681 - if ( $this->_fs->is_premium() ) {
682 - $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
683 -
684 - if (
685 - isset( $latest_tag->readme ) &&
686 - isset( $latest_tag->readme->upgrade_notice ) &&
687 - ! empty( $latest_tag->readme->upgrade_notice )
688 - ) {
689 - $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
690 - }
722 + if ( $this->_fs->is_premium() && ! empty( $new_version->upgrade_notice ) ) {
723 + $update->upgrade_notice = $new_version->upgrade_notice;
691 724 }
692 725
693 726 $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
694 727
@@ -703,15 +736,10 @@
703 736 *
704 737 * @return bool
705 738 */
706 739 private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
707 - $query_str = parse_url( $new_version->url, PHP_URL_QUERY );
708 - if ( empty( $query_str ) ) {
709 - return false;
710 - }
740 + $params = fs_parse_url_params( $new_version->url );
711 741
712 - parse_str( $query_str, $params );
713 -
714 742 return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
715 743 }
716 744
717 745 /**
@@ -807,11 +835,11 @@
807 835 // Alias
808 836 $basename = $this->_fs->get_plugin_basename();
809 837
810 838 if ( ! is_object( $transient_data ) ||
811 - ! isset( $transient_data->response ) ||
839 + ! isset( $transient_data->response ) ||
812 840 ! is_array( $transient_data->response ) ||
813 - empty( $transient_data->response[ $basename ] )
841 + empty( $transient_data->response[ $basename ] )
814 842 ) {
815 843 return;
816 844 }
817 845
@@ -836,30 +864,36 @@
836 864 *
837 865 * @return bool|mixed
838 866 */
839 867 static function _fetch_plugin_info_from_repository( $action, $args ) {
840 - $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/';
841 - 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' ) ) ) {
842 878 $url = set_url_scheme( $url, 'https' );
843 879 }
844 880
845 - $args = array(
846 - 'timeout' => 15,
847 - 'body' => array(
848 - 'action' => $action,
849 - 'request' => serialize( $args )
850 - )
851 - );
881 + // The new endpoint version serves only GET requests.
882 + $request = wp_remote_get( $url, array( 'timeout' => 15 ) );
852 883
853 - $request = wp_remote_post( $url, $args );
854 -
855 884 if ( is_wp_error( $request ) ) {
856 885 return false;
857 886 }
858 887
859 - $res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
888 + $res = json_decode( wp_remote_retrieve_body( $request ), true );
860 889
861 - 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 ) ) {
862 896 return false;
863 897 }
864 898
865 899 return $res;
@@ -865,8 +899,18 @@
865 899 return $res;
866 900 }
867 901
868 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 + /**
869 913 * Fetches module translation updates from wordpress.org.
870 914 *
871 915 * @author Leo Fajardo (@leorw)
872 916 * @since 2.1.2
@@ -1048,9 +1092,9 @@
1048 1092 $is_addon = true;
1049 1093 }
1050 1094
1051 1095 $plugin_in_repo = false;
1052 - if ( ! $is_addon ) {
1096 + if ( ! $is_addon && $this->can_fetch_data_from_wp_org() ) {
1053 1097 // Try to fetch info from .org repository.
1054 1098 $data = self::_fetch_plugin_info_from_repository( $action, $args );
1055 1099
1056 1100 $plugin_in_repo = ( false !== $data );
@@ -1094,8 +1138,9 @@
1094 1138
1095 1139 if ( ! $plugin_in_repo ) {
1096 1140 $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
1097 1141 $data->requires = $new_version->requires_platform_version;
1142 + $data->requires_php = $new_version->requires_programming_language_version;
1098 1143 $data->tested = $new_version->tested_up_to_version;
1099 1144 }
1100 1145
1101 1146 $data->version = $new_version->version;
@@ -1147,12 +1192,31 @@
1147 1192 $data = $new_version_readme_data;
1148 1193 }
1149 1194 }
1150 1195
1196 + if ( ! empty( $data->tested ) ) {
1197 + $data->tested = self::get_tested_wp_version( $data->tested );
1198 + }
1199 +
1151 1200 return $data;
1152 1201 }
1153 1202
1154 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 + /**
1155 1219 * @author Vova Feldman (@svovaf)
1156 1220 * @since 1.2.1.7
1157 1221 *
1158 1222 * @param number|bool $addon_id
@@ -1161,9 +1225,9 @@
1161 1225 *
1162 1226 * @return object
1163 1227 */
1164 1228 private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
1165 - 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 );
1166 1230 }
1167 1231
1168 1232 /**
1169 1233 * Checks if a given basename has a matching folder name