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/fs-plugin-info-dialog.php +663 -115 2.1 → 8.0.2 View file →
@@ -30,8 +30,31 @@
30 30 * @var Freemius
31 31 */
32 32 private $_fs;
33 33
34 + /**
35 + * Collection of plugin installation, update, download, activation, and purchase actions. This is used in
36 + * populating the actions dropdown list when there are at least 2 actions. If there's only 1 action, a button
37 + * is used instead.
38 + *
39 + * @author Leo Fajardo (@leorw)
40 + * @since 2.3.0
41 + *
42 + * @var string[]
43 + */
44 + private $actions;
45 +
46 + /**
47 + * Contains plugin status information that is used to determine which actions should be part of the actions
48 + * dropdown list.
49 + *
50 + * @author Leo Fajardo (@leorw)
51 + * @since 2.3.0
52 + *
53 + * @var string[]
54 + */
55 + private $status;
56 +
34 57 function __construct( Freemius $fs ) {
35 58 $this->_fs = $fs;
36 59
37 60 $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
@@ -121,17 +144,38 @@
121 144 $has_free_plan = true;
122 145 }
123 146
124 147 if ( is_array( $pricing ) && 0 < count( $pricing ) ) {
125 - $has_paid_plan = true;
148 + $filtered_pricing = array();
126 149
127 - foreach ( $pricing as &$prices ) {
150 + foreach ( $pricing as $prices ) {
128 151 $prices = new FS_Pricing( $prices );
152 +
153 + if ( ! $prices->is_usd() ) {
154 + /**
155 + * Skip non-USD pricing.
156 + *
157 + * @author Leo Fajardo (@leorw)
158 + * @since 2.3.1
159 + */
160 + continue;
161 + }
162 +
163 + if ( ( $prices->has_monthly() && $prices->monthly_price > 1.0 ) ||
164 + ( $prices->has_annual() && $prices->annual_price > 1.0 ) ||
165 + ( $prices->has_lifetime() && $prices->lifetime_price > 1.0 )
166 + ) {
167 + $filtered_pricing[] = $prices;
168 + }
129 169 }
130 170
131 - $plan->pricing = $pricing;
171 + if ( ! empty( $filtered_pricing ) ) {
172 + $has_paid_plan = true;
132 173
133 - $has_pricing = true;
174 + $plan->pricing = $filtered_pricing;
175 +
176 + $has_pricing = true;
177 + }
134 178 }
135 179
136 180 if ( is_array( $features ) && 0 < count( $features ) ) {
137 181 $plan->features = $features;
@@ -169,13 +213,16 @@
169 213 }
170 214
171 215 $data->fs_missing = ( ! $has_free_plan || $data->wp_org_missing );
172 216 } else {
173 - $data->wp_org_missing = false;
217 + $data->has_purchased_license = false;
218 + $data->wp_org_missing = false;
174 219
220 + $fs_addon = null;
175 221 $current_addon_version = false;
176 222 if ( $this->_fs->is_addon_activated( $selected_addon->id ) ) {
177 - $current_addon_version = $this->_fs->get_addon_instance( $selected_addon->id )->get_plugin_version();
223 + $fs_addon = $this->_fs->get_addon_instance( $selected_addon->id );
224 + $current_addon_version = $fs_addon->get_plugin_version();
178 225 } else if ( $this->_fs->is_addon_installed( $selected_addon->id ) ) {
179 226 $addon_plugin_data = get_plugin_data(
180 227 ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $selected_addon->id ) ),
181 228 false,
@@ -190,22 +237,59 @@
190 237 // Fetch latest version from Freemius.
191 238 $latest = $this->_fs->_fetch_latest_version(
192 239 $selected_addon->id,
193 240 true,
194 - WP_FS__TIME_24_HOURS_IN_SEC,
241 + FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION,
195 242 $current_addon_version
196 243 );
197 244
198 245 if ( $has_paid_plan ) {
199 - $data->checkout_link = $this->_fs->checkout_url();
246 + $blog_id = fs_request_get( 'fs_blog_id' );
247 + $has_valid_blog_id = is_numeric( $blog_id );
248 +
249 + if ( $has_valid_blog_id ) {
250 + switch_to_blog( $blog_id );
251 + }
252 +
253 + $data->checkout_link = $this->_fs->checkout_url(
254 + WP_FS__PERIOD_ANNUALLY,
255 + false,
256 + array(),
257 + ( $has_valid_blog_id ? false : null )
258 + );
259 +
260 + if ( $has_valid_blog_id ) {
261 + restore_current_blog();
262 + }
200 263 }
201 - if ( $has_free_plan ) {
264 +
265 + /**
266 + * Check if there's a purchased license in case the add-on can only be installed/downloaded as part of a purchased bundle.
267 + *
268 + * @author Leo Fajardo (@leorw)
269 + * @since 2.4.1
270 + */
271 + if ( is_object( $fs_addon ) ) {
272 + $data->has_purchased_license = $fs_addon->has_active_valid_license();
273 + } else {
274 + $account_addons = $this->_fs->get_account_addons();
275 + if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) {
276 + $data->has_purchased_license = true;
277 + }
278 + }
279 +
280 + if ( $has_free_plan || $data->has_purchased_license ) {
202 281 $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id );
203 282 }
204 283
205 - $data->fs_missing = ( false === $latest );
284 + $data->fs_missing = (
285 + false === $latest &&
286 + (
287 + empty( $selected_addon->premium_releases_count ) ||
288 + ! ( $selected_addon->premium_releases_count > 0 )
289 + )
290 + );
206 291
207 -
208 292 // Fetch as much as possible info from local files.
209 293 $plugin_local_data = $this->_fs->get_plugin_data();
210 294 $data->author = $plugin_local_data['Author'];
211 295
@@ -226,9 +310,12 @@
226 310 if ( is_object( $latest ) ) {
227 311 $data->version = $latest->version;
228 312 $data->last_updated = $latest->created;
229 313 $data->requires = $latest->requires_platform_version;
314 + $data->requires_php = $latest->requires_programming_language_version;
230 315 $data->tested = $latest->tested_up_to_version;
316 + } else if ( ! empty( $current_addon_version ) ) {
317 + $data->version = $current_addon_version;
231 318 } else {
232 319 // Add dummy version.
233 320 $data->version = '1.0.0';
234 321
@@ -266,9 +353,15 @@
266 353 $data->has_free_plan = $has_free_plan;
267 354 $data->has_paid_plan = $has_paid_plan;
268 355 $data->is_paid = $has_paid_plan;
269 356 $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant;
357 + $data->premium_slug = $selected_addon->premium_slug;
358 + $data->addon_id = $selected_addon->id;
270 359
360 + if ( ! isset( $data->has_purchased_license ) ) {
361 + $data->has_purchased_license = false;
362 + }
363 +
271 364 return $data;
272 365 }
273 366
274 367 /**
@@ -332,8 +425,72 @@
332 425 return '$' . $price_tag;
333 426 }
334 427
335 428 /**
429 + * @author Leo Fajardo (@leorw)
430 + * @since 2.3.0
431 + *
432 + * @param object $api
433 + * @param FS_Plugin_Plan $plan
434 + *
435 + * @return string
436 + */
437 + private function get_actions_dropdown( $api, $plan = null ) {
438 + $this->actions = isset( $this->actions ) ?
439 + $this->actions :
440 + $this->get_plugin_actions( $api );
441 +
442 + $actions = $this->actions;
443 +
444 + $checkout_cta = $this->get_checkout_cta( $api, $plan );
445 + if ( ! empty( $checkout_cta ) ) {
446 + /**
447 + * If there's no license yet, make the checkout button the main CTA. Otherwise, make it the last item in
448 + * the actions dropdown.
449 + *
450 + * @author Leo Fajardo (@leorw)
451 + * @since 2.3.0
452 + */
453 + if ( ! $api->has_purchased_license ) {
454 + array_unshift( $actions, $checkout_cta );
455 + } else {
456 + $actions[] = $checkout_cta;
457 + }
458 + }
459 +
460 + if ( empty( $actions ) ) {
461 + return '';
462 + }
463 +
464 + $total_actions = count( $actions );
465 + if ( 1 === $total_actions ) {
466 + return $actions[0];
467 + }
468 +
469 + ob_start();
470 +
471 + ?>
472 + <div class="fs-cta fs-dropdown">
473 + <div class="button-group">
474 + <?php
475 + // This should NOT be sanitized as the $actions are HTML buttons already.
476 + echo $actions[0] ?>
477 + <div class="button button-primary fs-dropdown-arrow-button">
478 + <span class="fs-dropdown-arrow"></span>
479 + <ul class="fs-dropdown-list" style="display: none">
480 + <?php for ( $i = 1; $i < $total_actions; $i ++ ) : ?>
481 + <li><?php echo str_replace( 'button button-primary', '', $actions[ $i ] ) ?></li>
482 + <?php endfor ?>
483 + </ul>
484 + </div>
485 + </div>
486 + </div>
487 + <?php
488 +
489 + return ob_get_clean();
490 + }
491 +
492 + /**
336 493 * @author Vova Feldman (@svovaf)
337 494 * @since 1.1.7
338 495 *
339 496 * @param object $api
@@ -358,16 +515,34 @@
358 515 }
359 516 }
360 517 }
361 518
362 - return '<a class="button button-primary fs-checkout-button right" href="' . $this->_fs->addon_checkout_url(
519 + $blog_id = fs_request_get( 'fs_blog_id' );
520 + $has_valid_blog_id = is_numeric( $blog_id );
521 +
522 + if ( $has_valid_blog_id ) {
523 + switch_to_blog( $blog_id );
524 + }
525 +
526 + $addon_checkout_url = $this->_fs->addon_checkout_url(
363 527 $plan->plugin_id,
364 528 $plan->pricing[0]->id,
365 529 $this->get_billing_cycle( $plan ),
366 - $plan->has_trial()
367 - ) . '" target="_parent">' .
368 - ( ! $plan->has_trial() ?
369 - fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug ) :
530 + $plan->has_trial(),
531 + ( $has_valid_blog_id ? false : null )
532 + );
533 +
534 + if ( $has_valid_blog_id ) {
535 + restore_current_blog();
536 + }
537 +
538 + return '<a class="button button-primary fs-checkout-button right" href="' . $addon_checkout_url . '" target="_parent">' .
539 + esc_html( ! $plan->has_trial() ?
540 + (
541 + $api->has_purchased_license ?
542 + fs_text_inline( 'Purchase More', 'purchase-more', $api->slug ) :
543 + fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug )
544 + ) :
370 545 sprintf(
371 546 /* translators: %s: N-days trial */
372 547 fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ),
373 548 $this->get_trial_period( $plan )
@@ -376,97 +551,306 @@
376 551 '</a>';
377 552 }
378 553
379 554 /**
380 - * @author Vova Feldman (@svovaf)
381 - * @since 2.0.0
555 + * @author Leo Fajardo (@leorw)
556 + * @since 2.3.0
382 557 *
383 558 * @param object $api
384 - * @param bool $is_primary
385 559 *
386 - * @return string
560 + * @return string[]
387 561 */
388 - private function get_download_cta( $api, $is_primary = true ) {
389 - if ( empty( $api->download_link ) ) {
390 - return '';
562 + private function get_plugin_actions( $api ) {
563 + $this->status = isset( $this->status ) ?
564 + $this->status :
565 + install_plugin_install_status( $api );
566 +
567 + $is_update_available = ( 'update_available' === $this->status['status'] );
568 +
569 + if ( $is_update_available && empty( $this->status['url'] ) ) {
570 + return array();
391 571 }
392 572
393 - $status = install_plugin_install_status( $api );
573 + $blog_id = fs_request_get( 'fs_blog_id' );
394 574
395 - $has_paid_version = $api->has_paid_plan;
575 + $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $blog_id );
396 576
397 - // Hosted on WordPress.org.
398 - switch ( $status['status'] ) {
399 - case 'install':
400 - if ( $api->is_wp_org_compliant ||
401 - ! $this->_fs->is_org_repo_compliant() ||
402 - $this->_fs->is_premium()
403 - ) {
404 - /**
405 - * Allow immediate installation if one of the following:
406 - * 1. WordPress.org add-on.
407 - * 2. The core module is NOT wp.org compliant.
408 - * 3. The core module is running the premium version which is not wp.org compliant.
409 - */
410 - if ( $status['url'] ) {
411 - return $this->get_cta(
412 - ( $has_paid_version ?
413 - fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) :
414 - fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ),
415 - $is_primary,
416 - false,
417 - $status['url'],
418 - '_parent'
419 - );
577 + $actions = array();
578 +
579 + $is_addon_activated = $this->_fs->is_addon_activated( $api->slug );
580 + $fs_addon = null;
581 +
582 + $is_free_installed = null;
583 + $is_premium_installed = null;
584 +
585 + $has_installed_version = ( 'install' !== $this->status['status'] );
586 +
587 + if ( ! $api->has_paid_plan && ! $api->has_purchased_license ) {
588 + /**
589 + * Free-only add-on.
590 + *
591 + * @author Leo Fajardo (@leorw)
592 + * @since 2.3.0
593 + */
594 + $is_free_installed = $has_installed_version;
595 + $is_premium_installed = false;
596 + } else if ( ! $api->has_free_plan ) {
597 + /**
598 + * Premium-only add-on.
599 + *
600 + * @author Leo Fajardo (@leorw)
601 + * @since 2.3.0
602 + */
603 + $is_free_installed = false;
604 + $is_premium_installed = $has_installed_version;
605 + } else {
606 + /**
607 + * Freemium add-on.
608 + *
609 + * @author Leo Fajardo (@leorw)
610 + * @since 2.3.0
611 + */
612 + if ( ! $has_installed_version ) {
613 + $is_free_installed = false;
614 + $is_premium_installed = false;
615 + } else {
616 + $fs_addon = $is_addon_activated ?
617 + $this->_fs->get_addon_instance( $api->slug ) :
618 + null;
619 +
620 + if ( is_object( $fs_addon ) ) {
621 + if ( $fs_addon->is_premium() ) {
622 + $is_premium_installed = true;
623 + } else {
624 + $is_free_installed = true;
420 625 }
421 626 }
422 627
423 - return $this->get_cta(
424 - ( $has_paid_version ?
425 - fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) :
426 - fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ),
427 - $is_primary,
428 - false,
429 - esc_url( $api->download_link )
430 - );
431 - break;
432 - case 'update_available':
433 - if ( $status['url'] ) {
434 - return $this->get_cta(
435 - ( $has_paid_version ?
436 - fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) :
437 - fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ),
438 - $is_primary,
439 - false,
440 - $status['url'],
441 - '_parent'
442 - );
628 + if ( is_null( $is_free_installed ) ) {
629 + $is_free_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->slug}/{$api->slug}.php" ) );
630 + if ( ! $is_free_installed ) {
631 + /**
632 + * Check if there's a plugin installed in a directory named `$api->slug`.
633 + *
634 + * @author Leo Fajardo (@leorw)
635 + * @since 2.3.0
636 + */
637 + $installed_plugins = get_plugins( '/' . $api->slug );
638 + $is_free_installed = ( ! empty( $installed_plugins ) );
639 + }
443 640 }
444 - break;
445 - case 'newer_installed':
446 - return $this->get_cta(
447 - ( $has_paid_version ?
448 - esc_html( sprintf( fs_text_inline( 'Newer Free Version (%s) Installed', 'newer-free-installed', $api->slug ), $status['version'] ) ) :
449 - esc_html( sprintf( fs_text_inline( 'Newer Version (%s) Installed', 'newer-installed', $api->slug ), $status['version'] ) ) ),
450 - $is_primary,
451 - true
452 - );
453 - break;
454 - case 'latest_installed':
455 - return $this->get_cta(
456 - ( $has_paid_version ?
457 - fs_esc_html_inline( 'Latest Free Version Installed', 'latest-free-installed', $api->slug ) :
458 - fs_esc_html_inline( 'Latest Version Installed', 'latest-installed', $api->slug ) ),
459 - $is_primary,
460 - true
461 - );
462 - break;
641 +
642 + if ( is_null( $is_premium_installed ) ) {
643 + $is_premium_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->premium_slug}/{$api->slug}.php" ) );
644 + if ( ! $is_premium_installed ) {
645 + /**
646 + * Check if there's a plugin installed in a directory named `$api->premium_slug`.
647 + *
648 + * @author Leo Fajardo (@leorw)
649 + * @since 2.3.0
650 + */
651 + $installed_plugins = get_plugins( '/' . $api->premium_slug );
652 + $is_premium_installed = ( ! empty( $installed_plugins ) );
653 + }
654 + }
655 + }
656 +
657 + $has_installed_version = ( $is_free_installed || $is_premium_installed );
463 658 }
464 659
465 - return '';
660 + $this->status['is_free_installed'] = $is_free_installed;
661 + $this->status['is_premium_installed'] = $is_premium_installed;
662 +
663 + $can_install_free_version = false;
664 + $can_install_free_version_update = false;
665 + $can_download_free_version = false;
666 + $can_activate_free_version = false;
667 + $can_install_premium_version = false;
668 + $can_install_premium_version_update = false;
669 + $can_download_premium_version = false;
670 + $can_activate_premium_version = false;
671 +
672 + if ( ! $api->has_purchased_license ) {
673 + if ( $api->has_free_plan ) {
674 + if ( $has_installed_version ) {
675 + if ( $is_update_available ) {
676 + $can_install_free_version_update = true;
677 + } else if ( ! $is_premium_installed && ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) {
678 + $can_activate_free_version = true;
679 + }
680 + } else {
681 + if (
682 + $this->_fs->is_premium() ||
683 + ! $this->_fs->is_org_repo_compliant() ||
684 + $api->is_wp_org_compliant
685 + ) {
686 + $can_install_free_version = true;
687 + } else {
688 + $can_download_free_version = true;
689 + }
690 + }
691 + }
692 + } else {
693 + if ( ! is_object( $fs_addon ) && $is_addon_activated ) {
694 + $fs_addon = $this->_fs->get_addon_instance( $api->slug );
695 + }
696 +
697 + $can_download_premium_version = true;
698 +
699 + if ( ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) {
700 + if ( $is_premium_installed ) {
701 + $can_activate_premium_version = ( ! $is_addon_activated || ! $fs_addon->is_premium() );
702 + } else if ( $is_free_installed ) {
703 + $can_activate_free_version = ( ! $is_addon_activated );
704 + }
705 + }
706 +
707 + if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() ) {
708 + if ( $is_update_available ) {
709 + $can_install_premium_version_update = true;
710 + } else if ( ! $is_premium_installed ) {
711 + $can_install_premium_version = true;
712 + }
713 + }
714 + }
715 +
716 + if (
717 + $can_install_premium_version ||
718 + $can_install_premium_version_update
719 + ) {
720 + if ( is_numeric( $blog_id ) ) {
721 + /**
722 + * Replace the network status URL with a blog admin–based status URL if the `Add-Ons` page is loaded
723 + * from a specific blog admin page (when `fs_blog_id` is valid) in order for plugin installation/update
724 + * to work.
725 + *
726 + * @author Leo Fajardo (@leorw)
727 + * @since 2.3.0
728 + */
729 + $this->status['url'] = self::get_blog_status_url( $blog_id, $this->status['url'], $this->status['status'] );
730 + }
731 +
732 + /**
733 + * Add the `fs_allow_updater_and_dialog` param to the install/update URL so that the add-on can be
734 + * installed/updated.
735 + *
736 + * @author Leo Fajardo (@leorw)
737 + * @since 2.3.0
738 + */
739 + $this->status['url'] = str_replace( '?', '?fs_allow_updater_and_dialog=true&amp;', $this->status['url'] );
740 + }
741 +
742 + if ( $can_install_free_version_update || $can_install_premium_version_update ) {
743 + $actions[] = $this->get_cta(
744 + ( $can_install_free_version_update ?
745 + fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) :
746 + fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ),
747 + true,
748 + false,
749 + $this->status['url'],
750 + '_parent'
751 + );
752 + } else if ( $can_install_free_version || $can_install_premium_version ) {
753 + $actions[] = $this->get_cta(
754 + ( $can_install_free_version ?
755 + fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) :
756 + fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ),
757 + true,
758 + false,
759 + $this->status['url'],
760 + '_parent'
761 + );
762 + }
763 +
764 + $download_latest_action = '';
765 +
766 + if (
767 + ! empty( $api->download_link ) &&
768 + ( $can_download_free_version || $can_download_premium_version )
769 + ) {
770 + $download_latest_action = $this->get_cta(
771 + ( $can_download_free_version ?
772 + fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) :
773 + fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ),
774 + true,
775 + false,
776 + esc_url( $api->download_link )
777 + );
778 + }
779 +
780 + if ( ! $can_activate_free_version && ! $can_activate_premium_version ) {
781 + if ( ! empty( $download_latest_action ) ) {
782 + $actions[] = $download_latest_action;
783 + }
784 + } else {
785 + $activate_action = sprintf(
786 + '<a class="button button-primary edit" href="%s" title="%s" target="_parent">%s</a>',
787 + wp_nonce_url( ( is_numeric( $blog_id ) ? trailingslashit( get_admin_url( $blog_id ) ) : '' ) . 'plugins.php?action=activate&amp;plugin=' . $this->status['file'], 'activate-plugin_' . $this->status['file'] ),
788 + fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $api->slug ),
789 + $can_activate_free_version ?
790 + fs_text_inline( 'Activate Free Version', 'activate-free', $api->slug ) :
791 + fs_text_inline( 'Activate', 'activate', $api->slug )
792 + );
793 +
794 + if ( ! $can_download_premium_version && ! empty( $download_latest_action ) ) {
795 + $actions[] = $download_latest_action;
796 +
797 + $download_latest_action = '';
798 + }
799 +
800 + if ( $can_install_premium_version || $can_install_premium_version_update ) {
801 + if ( $can_download_premium_version && ! empty( $download_latest_action ) ) {
802 + $actions[] = $download_latest_action;
803 +
804 + $download_latest_action = '';
805 + }
806 +
807 + $actions[] = $activate_action;
808 + } else {
809 + array_unshift( $actions, $activate_action );
810 + }
811 +
812 + if ( ! empty ($download_latest_action ) ) {
813 + $actions[] = $download_latest_action;
814 + }
815 + }
816 +
817 + return $actions;
466 818 }
467 819
468 820 /**
821 + * Rebuilds the status URL based on the admin URL.
822 + *
823 + * @author Leo Fajardo (@leorw)
824 + * @since 2.3.0
825 + *
826 + * @param int $blog_id
827 + * @param string $network_status_url
828 + * @param string $status
829 + *
830 + * @return string
831 + */
832 + private static function get_blog_status_url( $blog_id, $network_status_url, $status ) {
833 + if ( ! in_array( $status, array( 'install', 'update_available' ) ) ) {
834 + return $network_status_url;
835 + }
836 +
837 + $action = ( 'install' === $status ) ?
838 + 'install-plugin' :
839 + 'upgrade-plugin';
840 +
841 + $url_params = fs_parse_url_params( $network_status_url, true );
842 +
843 + if ( empty( $url_params ) || ! isset( $url_params['plugin'] ) ) {
844 + return $network_status_url;
845 + }
846 +
847 + $plugin = $url_params['plugin'];
848 +
849 + return wp_nonce_url( get_admin_url( $blog_id,"update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}");
850 + }
851 +
852 + /**
469 853 * Helper method to get a CTA button HTML.
470 854 *
471 855 * @author Vova Feldman (@svovaf)
472 856 * @since 2.0.0
@@ -498,11 +882,13 @@
498 882 if ( $is_disabled ) {
499 883 $classes[] = 'disabled';
500 884 }
501 885
886 + $rel = ( '_blank' === $target ) ? ' rel="noopener noreferrer"' : '';
887 +
502 888 return sprintf(
503 889 '<a %s class="button %s">%s</a>',
504 - empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"',
890 + empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"' . $rel,
505 891 implode( ' ', $classes ),
506 892 $label
507 893 );
508 894 }
@@ -686,9 +1072,9 @@
686 1072 $class = ( $section_name === $section ) ? ' class="current"' : '';
687 1073 $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) );
688 1074 $href = esc_url( $href );
689 1075 $san_section = esc_attr( $section_name );
690 - echo "\t<a name='$san_section' href='$href' $class>$title</a>\n";
1076 + echo "\t<a name='$san_section' href='$href' $class>" . esc_html( $title ) . "</a>\n";
691 1077 }
692 1078
693 1079 echo "</div>\n";
694 1080
@@ -814,14 +1200,14 @@
814 1200 echo " + '&trial=true'";
815 1201 }?>;
816 1202 },
817 1203 _updateCtaUrl = function (plan, pricing, cycle) {
818 - $('.plugin-information-pricing .button, #plugin-information-footer .button.fs-checkout-button').attr('href', _checkoutUrl(plan, pricing, cycle));
1204 + $('.plugin-information-pricing .fs-checkout-button, #plugin-information-footer .fs-checkout-button').attr('href', _checkoutUrl(plan, pricing, cycle));
819 1205 };
820 1206
821 1207 $(document).ready(function () {
822 1208 var $plan = $('.plugin-information-pricing .fs-plan[data-plan-id=<?php echo $plan->id ?>]');
823 - $plan.find('input[type=radio]').live('click', function () {
1209 + $plan.find('input[type=radio]').on('click', function () {
824 1210 _updateCtaUrl(
825 1211 $plan.attr('data-plan-id'),
826 1212 $(this).val(),
827 1213 $plan.find('.nav-tab-active').attr('data-billing-cycle')
@@ -901,9 +1287,9 @@
901 1287 fs_esc_html_inline( 'Save %s', 'save-x', $api->slug ), $annual_discount . '%' ) ?></span>
902 1288 <?php endif ?>
903 1289 <ul class="fs-licenses">
904 1290 </ul>
905 - <?php echo $this->get_checkout_cta( $api, $plan, false ) ?>
1291 + <?php echo $this->get_actions_dropdown( $api, $plan ) ?>
906 1292 <div style="clear:both"></div>
907 1293 <?php if ( $plan->has_trial() ) : ?>
908 1294 <?php $trial_period = $this->get_trial_period( $plan ) ?>
909 1295 <ul class="fs-trial-terms">
@@ -916,10 +1302,10 @@
916 1302 </ul>
917 1303 <?php endif ?>
918 1304 </div>
919 1305 </div>
920 - </div>
921 1306 <?php endforeach ?>
1307 + </div>
922 1308 <?php endif ?>
923 1309 <?php endif ?>
924 1310 <div>
925 1311 <h3><?php fs_echo_inline( 'Details', 'details', $api->slug ) ?></h3>
@@ -954,9 +1340,12 @@
954 1340 if ( ! empty( $api->requires ) ) {
955 1341 ?>
956 1342 <li>
957 1343 <strong><?php fs_esc_html_echo_inline( 'Requires WordPress Version', 'requires-wordpress-version', $api->slug ) ?>
958 - :</strong> <?php echo esc_html( sprintf( fs_text_inline( '%s or higher', 'x-or-higher', $api->slug ), $api->requires ) ) ?>
1344 + :</strong> <?php echo esc_html( sprintf(
1345 + /* translators: %s: Version number. */
1346 + fs_text_inline( '%s or higher', 'x-or-higher', $api->slug ), $api->requires )
1347 + ) ?>
959 1348 </li>
960 1349 <?php
961 1350 }
962 1351 if ( ! empty( $api->tested ) ) {
@@ -966,8 +1355,21 @@
966 1355 :</strong> <?php echo $api->tested; ?>
967 1356 </li>
968 1357 <?php
969 1358 }
1359 + if ( ! empty( $api->requires_php ) ) {
1360 + ?>
1361 + <li>
1362 + <strong><?php fs_esc_html_echo_inline( 'Requires PHP Version', 'requires-php-version', $api->slug ); ?>:</strong>
1363 + <?php
1364 + echo esc_html( sprintf(
1365 + /* translators: %s: Version number. */
1366 + fs_text_inline( '%s or higher', 'x-or-higher', $api->slug ), $api->requires_php )
1367 + );
1368 + ?>
1369 + </li>
1370 + <?php
1371 + }
970 1372 if ( ! empty( $api->downloaded ) ) {
971 1373 ?>
972 1374 <li>
973 1375 <strong><?php fs_esc_html_echo_inline( 'Downloaded', 'downloaded', $api->slug ) ?>
@@ -985,8 +1387,9 @@
985 1387 }
986 1388 if ( ! empty( $api->slug ) && true == $api->is_wp_org_compliant ) {
987 1389 ?>
988 1390 <li><a target="_blank"
1391 + rel="noopener noreferrer"
989 1392 href="https://wordpress.org/plugins/<?php echo $api->slug; ?>/"><?php fs_esc_html_echo_inline( 'WordPress.org Plugin Page', 'wp-org-plugin-page', $api->slug ) ?>
990 1393 &#187;</a>
991 1394 </li>
992 1395 <?php
@@ -993,8 +1396,9 @@
993 1396 }
994 1397 if ( ! empty( $api->homepage ) ) {
995 1398 ?>
996 1399 <li><a target="_blank"
1400 + rel="noopener noreferrer"
997 1401 href="<?php echo esc_url( $api->homepage ); ?>"><?php fs_esc_html_echo_inline( 'Plugin Homepage', 'plugin-homepage', $api->slug ) ?>
998 1402 &#187;</a>
999 1403 </li>
1000 1404 <?php
@@ -1001,8 +1405,9 @@
1001 1405 }
1002 1406 if ( ! empty( $api->donate_link ) && empty( $api->contributors ) ) {
1003 1407 ?>
1004 1408 <li><a target="_blank"
1409 + rel="noopener noreferrer"
1005 1410 href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_esc_html_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1006 1411 &#187;</a>
1007 1412 </li>
1008 1413 <?php } ?>
@@ -1044,20 +1449,21 @@
1044 1449 number_format_i18n( $key )
1045 1450 );
1046 1451 ?>
1047 1452 <div class="counter-container">
1048 - <span class="counter-label"><a
1049 - href="https://wordpress.org/support/view/plugin-reviews/<?php echo $api->slug; ?>?filter=<?php echo $key; ?>"
1050 - target="_blank"
1051 - title="<?php echo esc_attr( sprintf(
1052 - /* translators: %s: # of stars (e.g. 5 stars) */
1053 - fs_text_inline( 'Click to see reviews that provided a rating of %s', 'click-to-reviews', $api->slug ),
1054 - $stars_label
1055 - ) ) ?>"><?php echo $stars_label ?></a></span>
1453 + <span class="counter-label"><a
1454 + href="https://wordpress.org/support/view/plugin-reviews/<?php echo $api->slug; ?>?filter=<?php echo $key; ?>"
1455 + target="_blank"
1456 + rel="noopener noreferrer"
1457 + title="<?php echo esc_attr( sprintf(
1458 + /* translators: %s: # of stars (e.g. 5 stars) */
1459 + fs_text_inline( 'Click to see reviews that provided a rating of %s', 'click-to-reviews', $api->slug ),
1460 + $stars_label
1461 + ) ) ?>"><?php echo $stars_label ?></a></span>
1056 1462 <span class="counter-back">
1057 - <span class="counter-bar" style="width: <?php echo 92 * $_rating; ?>px;"></span>
1058 - </span>
1059 - <span class="counter-count"><?php echo number_format_i18n( $ratecount ); ?></span>
1463 + <span class="counter-bar" style="width: <?php echo absint(92 * $_rating); ?>px;"></span>
1464 + </span>
1465 + <span class="counter-count"><?php echo number_format_i18n( $ratecount ); ?></span>
1060 1466 </div>
1061 1467 <?php
1062 1468 }
1063 1469 }
@@ -1076,9 +1482,9 @@
1076 1482 $contrib_username = sanitize_user( $contrib_username );
1077 1483 if ( empty( $contrib_profile ) ) {
1078 1484 echo "<li><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</li>";
1079 1485 } else {
1080 - echo "<li><a href='{$contrib_profile}' target='_blank'><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</a></li>";
1486 + echo "<li><a href='{$contrib_profile}' target='_blank' rel='noopener noreferrer'><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</a></li>";
1081 1487 }
1082 1488 }
1083 1489 ?>
1084 1490 </ul>
@@ -1083,8 +1489,9 @@
1083 1489 ?>
1084 1490 </ul>
1085 1491 <?php if ( ! empty( $api->donate_link ) ) { ?>
1086 1492 <a target="_blank"
1493 + rel="noopener noreferrer"
1087 1494 href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1088 1495 &#187;</a>
1089 1496 <?php } ?>
1090 1497 <?php } ?>
@@ -1090,11 +1497,45 @@
1090 1497 <?php } ?>
1091 1498 </div>
1092 1499 <div id="section-holder" class="wrap">
1093 1500 <?php
1094 - if ( ! empty( $api->tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) {
1501 + $requires_php = isset( $api->requires_php ) ? $api->requires_php : null;
1502 + $requires_wp = isset( $api->requires ) ? $api->requires : null;
1503 +
1504 + $compatible_php = empty( $requires_php ) || version_compare( PHP_VERSION, $requires_php, '>=' );
1505 +
1506 + // Strip off any -alpha, -RC, -beta, -src suffixes.
1507 + list( $wp_version ) = explode( '-', $GLOBALS['wp_version'] );
1508 +
1509 + $compatible_wp = empty( $requires_wp ) || version_compare( $wp_version, $requires_wp, '>=' );
1510 + $tested_wp = ( empty( $api->tested ) || version_compare( $wp_version, $api->tested, '<=' ) );
1511 +
1512 + if ( ! $compatible_php ) {
1513 + echo '<div class="notice notice-error notice-alt"><p><strong>' . fs_text_inline( 'Error', 'error', $api->slug ) . ':</strong> ' . fs_text_inline( 'This plugin requires a newer version of PHP.', 'newer-php-required-error', $api->slug );
1514 +
1515 + if ( current_user_can( 'update_php' ) ) {
1516 + $wp_get_update_php_url = function_exists( 'wp_get_update_php_url' ) ?
1517 + wp_get_update_php_url() :
1518 + 'https://wordpress.org/support/update-php/';
1519 +
1520 + printf(
1521 + /* translators: %s: URL to Update PHP page. */
1522 + ' ' . fs_text_inline( '<a href="%s" target="_blank">Click here to learn more about updating PHP</a>.', 'php-update-learn-more-link', $api->slug ),
1523 + esc_url( $wp_get_update_php_url )
1524 + );
1525 +
1526 + if ( function_exists( 'wp_update_php_annotation' ) ) {
1527 + wp_update_php_annotation( '</p><p><em>', '</em>' );
1528 + }
1529 + } else {
1530 + echo '</p>';
1531 + }
1532 + echo '</div>';
1533 + }
1534 +
1535 + if ( ! $tested_wp ) {
1095 1536 echo '<div class="notice notice-warning"><p>' . '<strong>' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ':</strong> ' . fs_text_inline( 'This plugin has not been tested with your current version of WordPress.', 'not-tested-warning', $api->slug ) . '</p></div>';
1096 - } else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) {
1537 + } else if ( ! $compatible_wp ) {
1097 1538 echo '<div class="notice notice-warning"><p>' . '<strong>' . fs_text_inline( 'Warning', 'warning', $api->slug ) . ':</strong> ' . fs_text_inline( 'This plugin has not been marked as compatible with your version of WordPress.', 'not-compatible-warning', $api->slug ) . '</p></div>';
1098 1539 }
1099 1540
1100 1541 foreach ( (array) $api->sections as $section_name => $content ) {
@@ -1126,18 +1567,125 @@
1126 1567 echo "</div>\n";
1127 1568 echo "</div>\n"; // #plugin-information-scrollable
1128 1569 echo "<div id='$tab-footer'>\n";
1129 1570
1130 - if ( $api->has_paid_plan && ! empty( $api->checkout_link ) ) {
1131 - echo $this->get_checkout_cta( $api );
1571 + if (
1572 + ! empty( $api->download_link ) &&
1573 + ! empty( $this->status ) &&
1574 + in_array( $this->status['status'], array( 'newer_installed', 'latest_installed' ) )
1575 + ) {
1576 + if ( 'newer_installed' === $this->status['status'] ) {
1577 + echo $this->get_cta(
1578 + ( $this->status['is_premium_installed'] ?
1579 + esc_html( sprintf( fs_text_inline( 'Newer Version (%s) Installed', 'newer-installed', $api->slug ), $this->status['version'] ) ) :
1580 + esc_html( sprintf( fs_text_inline( 'Newer Free Version (%s) Installed', 'newer-free-installed', $api->slug ), $this->status['version'] ) ) ),
1581 + false,
1582 + true
1583 + );
1584 + } else {
1585 + echo $this->get_cta(
1586 + ( $this->status['is_premium_installed'] ?
1587 + fs_esc_html_inline( 'Latest Version Installed', 'latest-installed', $api->slug ) :
1588 + fs_esc_html_inline( 'Latest Free Version Installed', 'latest-free-installed', $api->slug ) ),
1589 + false,
1590 + true
1591 + );
1592 + }
1132 1593 }
1133 1594
1134 - if ( ! empty( $api->download_link ) ) {
1135 - echo $this->get_download_cta( $api, empty( $api->checkout_link ) );
1136 - }
1595 + echo $this->get_actions_dropdown( $api, null );
1137 1596
1138 1597 echo "</div>\n";
1598 + ?>
1599 + <script type="text/javascript">
1600 + ( function( $, undef ) {
1601 + var $dropdowns = $( '.fs-dropdown' );
1139 1602
1603 + $( '#plugin-information' )
1604 + .click( function( evt ) {
1605 + var $target = $( evt.target );
1606 +
1607 + if (
1608 + $target.hasClass( 'fs-dropdown-arrow-button' ) ||
1609 + ( 0 !== $target.parents( '.fs-dropdown-arrow-button' ).length )
1610 + ) {
1611 + var $dropdown = $target.parents( '.fs-dropdown' ),
1612 + isActive = $dropdown.hasClass( 'active' );
1613 +
1614 + if ( ! isActive ) {
1615 + /**
1616 + * Close the other dropdown if it's active.
1617 + *
1618 + * @author Leo Fajardo (@leorw)
1619 + * @since 2.3.0
1620 + */
1621 + $( '.fs-dropdown.active' ).each( function() {
1622 + toggleDropdown( $( this ), false );
1623 + } );
1624 + }
1625 +
1626 + /**
1627 + * Toggle the current dropdown.
1628 + *
1629 + * @author Leo Fajardo (@leorw)
1630 + * @since 2.3.0
1631 + */
1632 + toggleDropdown( $dropdown, ! isActive );
1633 +
1634 + return true;
1635 + }
1636 +
1637 + /**
1638 + * Close all dropdowns.
1639 + *
1640 + * @author Leo Fajardo (@leorw)
1641 + * @since 2.3.0
1642 + */
1643 + toggleDropdown( $( this ).find( '.fs-dropdown' ), false );
1644 + });
1645 +
1646 + if ( 0 !== $dropdowns.length ) {
1647 + /**
1648 + * Add the `up` class so that the bottom dropdown's content will be shown above its buttons.
1649 + *
1650 + * @author Leo Fajardo (@leorw)
1651 + * @since 2.3.0
1652 + */
1653 + $( '#plugin-information-footer' ).find( '.fs-dropdown' ).addClass( 'up' );
1654 + }
1655 +
1656 + /**
1657 + * Returns the default state of the dropdown arrow button and hides the dropdown list.
1658 + *
1659 + * @author Leo Fajardo (@leorw)
1660 + * @since 2.3.0
1661 + *
1662 + * @param {Object} [$dropdown]
1663 + * @param {Boolean} [state]
1664 + */
1665 + function toggleDropdown( $dropdown, state ) {
1666 + if ( undef === $dropdown ) {
1667 + var $activeDropdown = $dropdowns.find( '.active' );
1668 + if ( 0 !== $activeDropdown.length ) {
1669 + $dropdown = $activeDropdown;
1670 + }
1671 + }
1672 +
1673 + if ( undef === $dropdown ) {
1674 + return;
1675 + }
1676 +
1677 + if ( undef === state ) {
1678 + state = false;
1679 + }
1680 +
1681 + $dropdown.toggleClass( 'active', state );
1682 + $dropdown.find( '.fs-dropdown-list' ).toggle( state );
1683 + $dropdown.find( '.fs-dropdown-arrow-button' ).toggleClass( 'active', state );
1684 + }
1685 + } )( jQuery );
1686 + </script>
1687 + <?php
1140 1688 iframe_footer();
1141 1689 exit;
1142 1690 }
1143 - }
1691 + }