| @@ -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; |