PluginProbe
Tiered Pricing Table for WooCommerce / 2.1.1
Tiered Pricing Table for WooCommerce v2.1.1
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 2.3.4 All 90 releases
tier-pricing-table / freemius / includes / fs-plugin-info-dialog.php

fs-plugin-info-dialog.php in Tiered Pricing Table for WooCommerce 2.1.1, at freemius/includes/fs-plugin-info-dialog.php

1,143 lines 58.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Freemius
4 * @copyright Copyright (c) 2015, Freemius, Inc.
5 * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6 * @since 1.0.6
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * Class FS_Plugin_Info_Dialog
15 *
16 * @author Vova Feldman (@svovaf)
17 * @since 1.1.7
18 */
19 class FS_Plugin_Info_Dialog {
20 /**
21 * @since 1.1.7
22 *
23 * @var FS_Logger
24 */
25 private $_logger;
26
27 /**
28 * @since 1.1.7
29 *
30 * @var Freemius
31 */
32 private $_fs;
33
34 function __construct( Freemius $fs ) {
35 $this->_fs = $fs;
36
37 $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
38
39 // Remove default plugin information action.
40 remove_all_actions( 'install_plugins_pre_plugin-information' );
41
42 // Override action with custom plugins function for add-ons.
43 add_action( 'install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' ) );
44
45 // Override request for plugin information for Add-ons.
46 add_filter(
47 'fs_plugins_api',
48 array( &$this, '_get_addon_info_filter' ),
49 WP_FS__DEFAULT_PRIORITY, 3 );
50 }
51
52 /**
53 * Generate add-on plugin information.
54 *
55 * @author Vova Feldman (@svovaf)
56 * @since 1.0.6
57 *
58 * @param array $data
59 * @param string $action
60 * @param object|null $args
61 *
62 * @return array|null
63 */
64 function _get_addon_info_filter( $data, $action = '', $args = null ) {
65 $this->_logger->entrance();
66
67 $parent_plugin_id = fs_request_get( 'parent_plugin_id', $this->_fs->get_id() );
68
69 if ( $this->_fs->get_id() != $parent_plugin_id ||
70 ( 'plugin_information' !== $action ) ||
71 ! isset( $args->slug )
72 ) {
73 return $data;
74 }
75
76 // Find add-on by slug.
77 $selected_addon = $this->_fs->get_addon_by_slug( $args->slug, WP_FS__DEV_MODE );
78
79 if ( false === $selected_addon ) {
80 return $data;
81 }
82
83 if ( ! isset( $selected_addon->info ) ) {
84 // Setup some default info.
85 $selected_addon->info = new stdClass();
86 $selected_addon->info->selling_point_0 = 'Selling Point 1';
87 $selected_addon->info->selling_point_1 = 'Selling Point 2';
88 $selected_addon->info->selling_point_2 = 'Selling Point 3';
89 $selected_addon->info->description = '<p>Tell your users all about your add-on</p>';
90 }
91
92 fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
93
94 $data = $args;
95
96 $has_free_plan = false;
97 $has_paid_plan = false;
98
99 // Load add-on pricing.
100 $has_pricing = false;
101 $has_features = false;
102 $plans = false;
103
104 $result = $this->_fs->get_api_plugin_scope()->get( $this->_fs->add_show_pending( "/addons/{$selected_addon->id}/pricing.json?type=visible" ) );
105
106 if ( ! isset( $result->error ) ) {
107 $plans = $result->plans;
108
109 if ( is_array( $plans ) ) {
110 for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) {
111 $pricing = isset( $plans[ $i ]->pricing ) ? $plans[ $i ]->pricing : null;
112 $features = isset( $plans[ $i ]->features ) ? $plans[ $i ]->features : null;
113
114 $plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] );
115 $plan = $plans[ $i ];
116
117 if ( 'free' == $plans[ $i ]->name ||
118 ! is_array( $pricing ) ||
119 0 == count( $pricing )
120 ) {
121 $has_free_plan = true;
122 }
123
124 if ( is_array( $pricing ) && 0 < count( $pricing ) ) {
125 $has_paid_plan = true;
126
127 foreach ( $pricing as &$prices ) {
128 $prices = new FS_Pricing( $prices );
129 }
130
131 $plan->pricing = $pricing;
132
133 $has_pricing = true;
134 }
135
136 if ( is_array( $features ) && 0 < count( $features ) ) {
137 $plan->features = $features;
138
139 $has_features = true;
140 }
141 }
142 }
143 }
144
145 $latest = null;
146
147 if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) {
148 $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository(
149 'plugin_information', (object) array(
150 'slug' => $selected_addon->slug,
151 'is_ssl' => is_ssl(),
152 'fields' => array(
153 'banners' => true,
154 'reviews' => true,
155 'downloaded' => false,
156 'active_installs' => true
157 )
158 ) );
159
160 if ( ! empty( $repo_data ) ) {
161 $data = $repo_data;
162 $data->wp_org_missing = false;
163 } else {
164 // Couldn't find plugin on .org.
165 $selected_addon->is_wp_org_compliant = false;
166
167 // Plugin is missing, not on Freemius nor WP.org.
168 $data->wp_org_missing = true;
169 }
170
171 $data->fs_missing = ( ! $has_free_plan || $data->wp_org_missing );
172 } else {
173 $data->wp_org_missing = false;
174
175 $current_addon_version = false;
176 if ( $this->_fs->is_addon_activated( $selected_addon->id ) ) {
177 $current_addon_version = $this->_fs->get_addon_instance( $selected_addon->id )->get_plugin_version();
178 } else if ( $this->_fs->is_addon_installed( $selected_addon->id ) ) {
179 $addon_plugin_data = get_plugin_data(
180 ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $selected_addon->id ) ),
181 false,
182 false
183 );
184
185 if ( ! empty( $addon_plugin_data ) ) {
186 $current_addon_version = $addon_plugin_data['Version'];
187 }
188 }
189
190 // Fetch latest version from Freemius.
191 $latest = $this->_fs->_fetch_latest_version(
192 $selected_addon->id,
193 true,
194 WP_FS__TIME_24_HOURS_IN_SEC,
195 $current_addon_version
196 );
197
198 if ( $has_paid_plan ) {
199 $data->checkout_link = $this->_fs->checkout_url();
200 }
201 if ( $has_free_plan ) {
202 $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id );
203 }
204
205 $data->fs_missing = ( false === $latest );
206
207
208 // Fetch as much as possible info from local files.
209 $plugin_local_data = $this->_fs->get_plugin_data();
210 $data->author = $plugin_local_data['Author'];
211
212 if ( ! empty( $selected_addon->info->banner_url ) ) {
213 $data->banners = array(
214 'low' => $selected_addon->info->banner_url,
215 );
216 }
217
218 if ( ! empty( $selected_addon->info->screenshots ) ) {
219 $view_vars = array(
220 'screenshots' => $selected_addon->info->screenshots,
221 'plugin' => $selected_addon,
222 );
223 $data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars );
224 }
225
226 if ( is_object( $latest ) ) {
227 $data->version = $latest->version;
228 $data->last_updated = $latest->created;
229 $data->requires = $latest->requires_platform_version;
230 $data->tested = $latest->tested_up_to_version;
231 } else {
232 // Add dummy version.
233 $data->version = '1.0.0';
234
235 // Add message to developer to deploy the plugin through Freemius.
236 }
237 }
238
239 $data->name = $selected_addon->title;
240 $view_vars = array( 'plugin' => $selected_addon );
241
242 if ( is_object( $latest ) && isset( $latest->readme ) && is_object( $latest->readme ) ) {
243 $latest_version_readme_data = $latest->readme;
244 if ( isset( $latest_version_readme_data->sections ) ) {
245 $data->sections = (array) $latest_version_readme_data->sections;
246 } else {
247 $data->sections = array();
248 }
249 }
250
251 $data->sections['description'] = fs_get_template( '/plugin-info/description.php', $view_vars );
252
253 if ( $has_pricing ) {
254 // Add plans to data.
255 $data->plans = $plans;
256
257 if ( $has_features ) {
258 $view_vars = array(
259 'plans' => $plans,
260 'plugin' => $selected_addon,
261 );
262 $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars );
263 }
264 }
265
266 $data->has_free_plan = $has_free_plan;
267 $data->has_paid_plan = $has_paid_plan;
268 $data->is_paid = $has_paid_plan;
269 $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant;
270
271 return $data;
272 }
273
274 /**
275 * @author Vova Feldman (@svovaf)
276 * @since 1.1.7
277 *
278 * @param FS_Plugin_Plan $plan
279 *
280 * @return string
281 */
282 private function get_billing_cycle( FS_Plugin_Plan $plan ) {
283 $billing_cycle = null;
284
285 if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) {
286 $pricing = $plan->pricing[0];
287 if ( isset( $pricing->annual_price ) ) {
288 $billing_cycle = 'annual';
289 } else if ( isset( $pricing->monthly_price ) ) {
290 $billing_cycle = 'monthly';
291 } else if ( isset( $pricing->lifetime_price ) ) {
292 $billing_cycle = 'lifetime';
293 }
294 } else {
295 foreach ( $plan->pricing as $pricing ) {
296 if ( isset( $pricing->annual_price ) ) {
297 $billing_cycle = 'annual';
298 } else if ( isset( $pricing->monthly_price ) ) {
299 $billing_cycle = 'monthly';
300 } else if ( isset( $pricing->lifetime_price ) ) {
301 $billing_cycle = 'lifetime';
302 }
303
304 if ( ! is_null( $billing_cycle ) ) {
305 break;
306 }
307 }
308 }
309
310 return $billing_cycle;
311 }
312
313 /**
314 * @author Vova Feldman (@svovaf)
315 * @since 2.0.0
316 *
317 * @param FS_Plugin_Plan $plan
318 * @param FS_Pricing $pricing
319 *
320 * @return float|null|string
321 */
322 private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) {
323 $price_tag = '';
324 if ( isset( $pricing->annual_price ) ) {
325 $price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' );
326 } else if ( isset( $pricing->monthly_price ) ) {
327 $price_tag = $pricing->monthly_price . ' / mo';
328 } else if ( isset( $pricing->lifetime_price ) ) {
329 $price_tag = $pricing->lifetime_price;
330 }
331
332 return '$' . $price_tag;
333 }
334
335 /**
336 * @author Vova Feldman (@svovaf)
337 * @since 1.1.7
338 *
339 * @param object $api
340 * @param FS_Plugin_Plan $plan
341 *
342 * @return string
343 */
344 private function get_checkout_cta( $api, $plan = null ) {
345 if ( empty( $api->checkout_link ) ||
346 ! isset( $api->plans ) ||
347 ! is_array( $api->plans ) ||
348 0 == count( $api->plans )
349 ) {
350 return '';
351 }
352
353 if ( is_null( $plan ) ) {
354 foreach ( $api->plans as $p ) {
355 if ( ! empty( $p->pricing ) ) {
356 $plan = $p;
357 break;
358 }
359 }
360 }
361
362 return '<a class="button button-primary fs-checkout-button right" href="' . $this->_fs->addon_checkout_url(
363 $plan->plugin_id,
364 $plan->pricing[0]->id,
365 $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 ) :
370 sprintf(
371 /* translators: %s: N-days trial */
372 fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ),
373 $this->get_trial_period( $plan )
374 )
375 ) .
376 '</a>';
377 }
378
379 /**
380 * @author Vova Feldman (@svovaf)
381 * @since 2.0.0
382 *
383 * @param object $api
384 * @param bool $is_primary
385 *
386 * @return string
387 */
388 private function get_download_cta( $api, $is_primary = true ) {
389 if ( empty( $api->download_link ) ) {
390 return '';
391 }
392
393 $status = install_plugin_install_status( $api );
394
395 $has_paid_version = $api->has_paid_plan;
396
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 );
420 }
421 }
422
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 );
443 }
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;
463 }
464
465 return '';
466 }
467
468 /**
469 * Helper method to get a CTA button HTML.
470 *
471 * @author Vova Feldman (@svovaf)
472 * @since 2.0.0
473 *
474 * @param string $label
475 * @param bool $is_primary
476 * @param bool $is_disabled
477 * @param string $href
478 * @param string $target
479 *
480 * @return string
481 */
482 private function get_cta(
483 $label,
484 $is_primary = true,
485 $is_disabled = false,
486 $href = '',
487 $target = '_blank'
488 ) {
489 $classes = array();
490
491 if ( ! $is_primary ) {
492 $classes[] = 'left';
493 } else {
494 $classes[] = 'button-primary';
495 $classes[] = 'right';
496 }
497
498 if ( $is_disabled ) {
499 $classes[] = 'disabled';
500 }
501
502 return sprintf(
503 '<a %s class="button %s">%s</a>',
504 empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"',
505 implode( ' ', $classes ),
506 $label
507 );
508 }
509
510 /**
511 * @author Vova Feldman (@svovaf)
512 * @since 1.1.7
513 *
514 * @param FS_Plugin_Plan $plan
515 *
516 * @return string
517 */
518 private function get_trial_period( $plan ) {
519 $trial_period = (int) $plan->trial_period;
520
521 switch ( $trial_period ) {
522 case 30:
523 return 'month';
524 case 60:
525 return '2 months';
526 default:
527 return "{$plan->trial_period} days";
528 }
529 }
530
531 /**
532 * Display plugin information in dialog box form.
533 *
534 * Based on core install_plugin_information() function.
535 *
536 * @author Vova Feldman (@svovaf)
537 * @since 1.0.6
538 */
539 function install_plugin_information() {
540 global $tab;
541
542 if ( empty( $_REQUEST['plugin'] ) ) {
543 return;
544 }
545
546 $args = array(
547 'slug' => wp_unslash( $_REQUEST['plugin'] ),
548 'is_ssl' => is_ssl(),
549 'fields' => array(
550 'banners' => true,
551 'reviews' => true,
552 'downloaded' => false,
553 'active_installs' => true
554 )
555 );
556
557 if ( is_array( $args ) ) {
558 $args = (object) $args;
559 }
560
561 if ( ! isset( $args->per_page ) ) {
562 $args->per_page = 24;
563 }
564
565 if ( ! isset( $args->locale ) ) {
566 $args->locale = get_locale();
567 }
568
569 $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args );
570
571 if ( is_wp_error( $api ) ) {
572 wp_die( $api );
573 }
574
575 $plugins_allowedtags = array(
576 'a' => array(
577 'href' => array(),
578 'title' => array(),
579 'target' => array(),
580 // Add image style for screenshots.
581 'class' => array()
582 ),
583 'style' => array(),
584 'abbr' => array( 'title' => array() ),
585 'acronym' => array( 'title' => array() ),
586 'code' => array(),
587 'pre' => array(),
588 'em' => array(),
589 'strong' => array(),
590 'div' => array( 'class' => array() ),
591 'span' => array( 'class' => array() ),
592 'p' => array(),
593 'ul' => array(),
594 'ol' => array(),
595 'li' => array( 'class' => array() ),
596 'i' => array( 'class' => array() ),
597 'h1' => array(),
598 'h2' => array(),
599 'h3' => array(),
600 'h4' => array(),
601 'h5' => array(),
602 'h6' => array(),
603 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ),
604 // 'table' => array(),
605 // 'td' => array(),
606 // 'tr' => array(),
607 // 'th' => array(),
608 // 'thead' => array(),
609 // 'tbody' => array(),
610 );
611
612 $plugins_section_titles = array(
613 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ),
614 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ),
615 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ),
616 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ),
617 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ),
618 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ),
619 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ),
620 );
621
622 // Sanitize HTML
623 // foreach ( (array) $api->sections as $section_name => $content ) {
624 // $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags );
625 // }
626
627 foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) {
628 if ( isset( $api->$key ) ) {
629 $api->$key = wp_kses( $api->$key, $plugins_allowedtags );
630 }
631 }
632
633 // Add after $api->slug is ready.
634 $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug );
635
636 $_tab = esc_attr( $tab );
637
638 $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English.
639 if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) {
640 $section_titles = array_keys( (array) $api->sections );
641 $section = array_shift( $section_titles );
642 }
643
644 iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) );
645
646 $_with_banner = '';
647
648 // var_dump($api->banners);
649 if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) {
650 $_with_banner = 'with-banner';
651 $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low'];
652 $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high'];
653 ?>
654 <style type="text/css">
655 #plugin-information-title.with-banner
656 {
657 background-image: url( <?php echo esc_url( $low ); ?> );
658 }
659
660 @media only screen and ( -webkit-min-device-pixel-ratio: 1.5 )
661 {
662 #plugin-information-title.with-banner
663 {
664 background-image: url( <?php echo esc_url( $high ); ?> );
665 }
666 }
667 </style>
668 <?php
669 }
670
671 echo '<div id="plugin-information-scrollable">';
672 echo "<div id='{$_tab}-title' class='{$_with_banner}'><div class='vignette'></div><h2>{$api->name}</h2></div>";
673 echo "<div id='{$_tab}-tabs' class='{$_with_banner}'>\n";
674
675 foreach ( (array) $api->sections as $section_name => $content ) {
676 if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) {
677 continue;
678 }
679
680 if ( isset( $plugins_section_titles[ $section_name ] ) ) {
681 $title = $plugins_section_titles[ $section_name ];
682 } else {
683 $title = ucwords( str_replace( '_', ' ', $section_name ) );
684 }
685
686 $class = ( $section_name === $section ) ? ' class="current"' : '';
687 $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) );
688 $href = esc_url( $href );
689 $san_section = esc_attr( $section_name );
690 echo "\t<a name='$san_section' href='$href' $class>$title</a>\n";
691 }
692
693 echo "</div>\n";
694
695 ?>
696 <div id="<?php echo $_tab; ?>-content" class='<?php echo $_with_banner; ?>'>
697 <div class="fyi">
698 <?php if ( $api->is_paid ) : ?>
699 <?php if ( isset( $api->plans ) ) : ?>
700 <div class="plugin-information-pricing">
701 <?php foreach ( $api->plans as $plan ) : ?>
702 <?php
703 if ( empty( $plan->pricing ) ) {
704 continue;
705 }
706
707 /**
708 * @var FS_Plugin_Plan $plan
709 */
710 ?>
711 <?php $first_pricing = $plan->pricing[0] ?>
712 <?php $is_multi_cycle = $first_pricing->is_multi_cycle() ?>
713 <div class="fs-plan<?php if ( ! $is_multi_cycle ) {
714 echo ' fs-single-cycle';
715 } ?>" data-plan-id="<?php echo $plan->id ?>">
716 <h3 data-plan="<?php echo $plan->id ?>"><?php echo esc_html( sprintf( fs_text_x_inline( '%s Plan', 'e.g. Professional Plan', 'x-plan', $api->slug ), $plan->title ) ) ?></h3>
717 <?php $has_annual = $first_pricing->has_annual() ?>
718 <?php $has_monthly = $first_pricing->has_monthly() ?>
719 <div class="nav-tab-wrapper">
720 <?php $billing_cycles = array( 'monthly', 'annual', 'lifetime' ) ?>
721 <?php $i = 0;
722 foreach ( $billing_cycles as $cycle ) : ?>
723 <?php $prop = "{$cycle}_price";
724 if ( isset( $first_pricing->{$prop} ) ) : ?>
725 <?php $is_featured = ( 'annual' === $cycle && $is_multi_cycle ) ?>
726 <?php
727 $prices = array();
728 foreach ( $plan->pricing as $pricing ) {
729 if ( isset( $pricing->{$prop} ) ) {
730 $prices[] = array(
731 'id' => $pricing->id,
732 'licenses' => $pricing->licenses,
733 'price' => $pricing->{$prop}
734 );
735 }
736 }
737 ?>
738 <a class="nav-tab" data-billing-cycle="<?php echo $cycle ?>"
739 data-pricing="<?php echo esc_attr( json_encode( $prices ) ) ?>">
740 <?php if ( $is_featured ) : ?>
741 <label>
742 &#9733; <?php fs_esc_html_echo_x_inline( 'Best', 'e.g. the best product', 'best', $api->slug ) ?>
743 &#9733;</label>
744 <?php endif ?>
745 <?php
746 switch ( $cycle ) {
747 case 'monthly':
748 fs_esc_html_echo_x_inline( 'Monthly', 'as every month', 'monthly', $api->slug );
749 break;
750 case 'annual':
751 fs_esc_html_echo_x_inline( 'Annual', 'as once a year', 'annual', $api->slug );
752 break;
753 case 'lifetime':
754 fs_esc_html_echo_inline( 'Lifetime', 'lifetime', $api->slug );
755 break;
756 }
757 ?>
758 </a>
759 <?php endif ?>
760 <?php $i ++; endforeach ?>
761 <?php wp_enqueue_script( 'jquery' ) ?>
762 <script type="text/javascript">
763 (function ($, undef) {
764 var
765 _formatBillingFrequency = function (cycle) {
766 switch (cycle) {
767 case 'monthly':
768 return '<?php printf( fs_text_x_inline( 'Billed %s', 'e.g. billed monthly', 'billed-x', $api->slug ), fs_text_x_inline( 'Monthly', 'as every month', 'monthly', $api->slug ) ) ?>';
769 case 'annual':
770 return '<?php printf( fs_text_x_inline( 'Billed %s', 'e.g. billed monthly', 'billed-x', $api->slug ), fs_text_x_inline( 'Annually', 'as once a year', 'annually', $api->slug ) ) ?>';
771 case 'lifetime':
772 return '<?php printf( fs_text_x_inline( 'Billed %s', 'e.g. billed monthly', 'billed-x', $api->slug ), fs_text_x_inline( 'Once', 'as once a year', 'once', $api->slug ) ) ?>';
773 }
774 },
775 _formatLicensesTitle = function (pricing) {
776 switch (pricing.licenses) {
777 case 1:
778 return '<?php fs_esc_attr_echo_inline( 'Single Site License', 'license-single-site', $api->slug ) ?>';
779 case null:
780 return '<?php fs_esc_attr_echo_inline( 'Unlimited Licenses', 'license-unlimited', $api->slug ) ?>';
781 default:
782 return '<?php fs_esc_attr_echo_inline( 'Up to %s Sites', 'license-x-sites', $api->slug ) ?>'.replace('%s', pricing.licenses);
783 }
784 },
785 _formatPrice = function (pricing, cycle, multipleLicenses) {
786 if (undef === multipleLicenses)
787 multipleLicenses = true;
788
789 var priceCycle;
790 switch (cycle) {
791 case 'monthly':
792 priceCycle = ' / <?php fs_echo_x_inline( 'mo', 'as monthly period', 'mo', $api->slug ) ?>';
793 break;
794 case 'lifetime':
795 priceCycle = '';
796 break;
797 case 'annual':
798 default:
799 priceCycle = ' / <?php fs_echo_x_inline( 'year', 'as annual period', 'year', $api->slug ) ?>';
800 break;
801 }
802
803 if (!multipleLicenses && 1 == pricing.licenses) {
804 return '$' + pricing.price + priceCycle;
805 }
806
807 return _formatLicensesTitle(pricing) + ' - <var class="fs-price">$' + pricing.price + priceCycle + '</var>';
808 },
809 _checkoutUrl = function (plan, pricing, cycle) {
810 return '<?php echo esc_url_raw( remove_query_arg( 'billing_cycle', add_query_arg( array( 'plugin_id' => $plan->plugin_id ), $api->checkout_link ) ) ) ?>' +
811 '&plan_id=' + plan +
812 '&pricing_id=' + pricing +
813 '&billing_cycle=' + cycle<?php if ( $plan->has_trial() ) {
814 echo " + '&trial=true'";
815 }?>;
816 },
817 _updateCtaUrl = function (plan, pricing, cycle) {
818 $('.plugin-information-pricing .button, #plugin-information-footer .button.fs-checkout-button').attr('href', _checkoutUrl(plan, pricing, cycle));
819 };
820
821 $(document).ready(function () {
822 var $plan = $('.plugin-information-pricing .fs-plan[data-plan-id=<?php echo $plan->id ?>]');
823 $plan.find('input[type=radio]').live('click', function () {
824 _updateCtaUrl(
825 $plan.attr('data-plan-id'),
826 $(this).val(),
827 $plan.find('.nav-tab-active').attr('data-billing-cycle')
828 );
829
830 $plan.find('.fs-trial-terms .fs-price').html(
831 $(this).parents('label').find('.fs-price').html()
832 );
833 });
834
835 $plan.find('.nav-tab').click(function () {
836 if ($(this).hasClass('nav-tab-active'))
837 return;
838
839 var $this = $(this),
840 billingCycle = $this.attr('data-billing-cycle'),
841 pricing = JSON.parse($this.attr('data-pricing')),
842 $pricesList = $this.parents('.fs-plan').find('.fs-pricing-body .fs-licenses'),
843 html = '';
844
845 // Un-select previously selected tab.
846 $plan.find('.nav-tab').removeClass('nav-tab-active');
847
848 // Select current tab.
849 $this.addClass('nav-tab-active');
850
851 // Render licenses prices.
852 if (1 == pricing.length) {
853 html = '<li><label><?php echo fs_esc_attr_x_inline( 'Price', 'noun', 'price', $api->slug ) ?>: ' + _formatPrice(pricing[0], billingCycle, false) + '</label></li>';
854 } else {
855 for (var i = 0; i < pricing.length; i++) {
856 html += '<li><label><input name="pricing-<?php echo $plan->id ?>" type="radio" value="' + pricing[i].id + '">' + _formatPrice(pricing[i], billingCycle) + '</label></li>';
857 }
858 }
859 $pricesList.html(html);
860
861 if (1 < pricing.length) {
862 // Select first license option.
863 $pricesList.find('li:first input').click();
864 }
865 else {
866 _updateCtaUrl(
867 $plan.attr('data-plan-id'),
868 pricing[0].id,
869 billingCycle
870 );
871 }
872
873 // Update billing frequency.
874 $plan.find('.fs-billing-frequency').html(_formatBillingFrequency(billingCycle));
875
876 if ('annual' === billingCycle) {
877 $plan.find('.fs-annual-discount').show();
878 } else {
879 $plan.find('.fs-annual-discount').hide();
880 }
881 });
882
883 <?php if ( $has_annual ) : ?>
884 // Select annual by default.
885 $plan.find('.nav-tab[data-billing-cycle=annual]').click();
886 <?php else : ?>
887 // Select first tab.
888 $plan.find('.nav-tab:first').click();
889 <?php endif ?>
890 });
891 }(jQuery));
892 </script>
893 </div>
894 <div class="fs-pricing-body">
895 <span class="fs-billing-frequency"></span>
896 <?php $annual_discount = ( $has_annual && $has_monthly ) ? $plan->pricing[0]->annual_discount_percentage() : 0 ?>
897 <?php if ( $annual_discount > 0 ) : ?>
898 <span
899 class="fs-annual-discount"><?php printf(
900 /* translators: %s: Discount (e.g. discount of $5 or 10%) */
901 fs_esc_html_inline( 'Save %s', 'save-x', $api->slug ), $annual_discount . '%' ) ?></span>
902 <?php endif ?>
903 <ul class="fs-licenses">
904 </ul>
905 <?php echo $this->get_checkout_cta( $api, $plan, false ) ?>
906 <div style="clear:both"></div>
907 <?php if ( $plan->has_trial() ) : ?>
908 <?php $trial_period = $this->get_trial_period( $plan ) ?>
909 <ul class="fs-trial-terms">
910 <li>
911 <i class="dashicons dashicons-yes"></i><?php echo esc_html( sprintf( fs_text_inline( 'No commitment for %s - cancel anytime', 'no-commitment-x', $api->slug ), $trial_period ) ) ?>
912 </li>
913 <li>
914 <i class="dashicons dashicons-yes"></i><?php printf( esc_html( fs_text_inline( 'After your free %s, pay as little as %s', 'after-x-pay-as-little-y', $api->slug ) ), $trial_period, '<var class="fs-price">' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '</var>' ) ?>
915 </li>
916 </ul>
917 <?php endif ?>
918 </div>
919 </div>
920 </div>
921 <?php endforeach ?>
922 <?php endif ?>
923 <?php endif ?>
924 <div>
925 <h3><?php fs_echo_inline( 'Details', 'details', $api->slug ) ?></h3>
926 <ul>
927 <?php if ( ! empty( $api->version ) ) { ?>
928 <li>
929 <strong><?php fs_esc_html_echo_x_inline( 'Version', 'product version', 'version', $api->slug ); ?>
930 :</strong> <?php echo $api->version; ?></li>
931 <?php
932 }
933 if ( ! empty( $api->author ) ) {
934 ?>
935 <li>
936 <strong><?php fs_echo_x_inline( 'Author', 'as the plugin author', 'author', $api->slug ); ?>
937 :</strong> <?php echo links_add_target( $api->author, '_blank' ); ?>
938 </li>
939 <?php
940 }
941 if ( ! empty( $api->last_updated ) ) {
942 ?>
943 <li><strong><?php fs_echo_inline( 'Last Updated', 'last-updated', $api->slug ); ?>
944 :</strong> <span
945 title="<?php echo $api->last_updated; ?>">
946 <?php echo esc_html( sprintf(
947 /* translators: %s: time period (e.g. "2 hours" ago) */
948 fs_text_x_inline( '%s ago', 'x-ago', $api->slug ),
949 human_time_diff( strtotime( $api->last_updated ) )
950 ) ) ?>
951 </span></li>
952 <?php
953 }
954 if ( ! empty( $api->requires ) ) {
955 ?>
956 <li>
957 <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 ) ) ?>
959 </li>
960 <?php
961 }
962 if ( ! empty( $api->tested ) ) {
963 ?>
964 <li>
965 <strong><?php fs_esc_html_echo_inline( 'Compatible up to', 'compatible-up-to', $api->slug ); ?>
966 :</strong> <?php echo $api->tested; ?>
967 </li>
968 <?php
969 }
970 if ( ! empty( $api->downloaded ) ) {
971 ?>
972 <li>
973 <strong><?php fs_esc_html_echo_inline( 'Downloaded', 'downloaded', $api->slug ) ?>
974 :</strong> <?php echo esc_html( sprintf(
975 ( ( 1 == $api->downloaded ) ?
976 /* translators: %s: 1 or One (Number of times downloaded) */
977 fs_text_inline( '%s time', 'x-time', $api->slug ) :
978 /* translators: %s: Number of times downloaded */
979 fs_text_inline( '%s times', 'x-times', $api->slug )
980 ),
981 number_format_i18n( $api->downloaded )
982 ) ); ?>
983 </li>
984 <?php
985 }
986 if ( ! empty( $api->slug ) && true == $api->is_wp_org_compliant ) {
987 ?>
988 <li><a target="_blank"
989 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 &#187;</a>
991 </li>
992 <?php
993 }
994 if ( ! empty( $api->homepage ) ) {
995 ?>
996 <li><a target="_blank"
997 href="<?php echo esc_url( $api->homepage ); ?>"><?php fs_esc_html_echo_inline( 'Plugin Homepage', 'plugin-homepage', $api->slug ) ?>
998 &#187;</a>
999 </li>
1000 <?php
1001 }
1002 if ( ! empty( $api->donate_link ) && empty( $api->contributors ) ) {
1003 ?>
1004 <li><a target="_blank"
1005 href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_esc_html_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1006 &#187;</a>
1007 </li>
1008 <?php } ?>
1009 </ul>
1010 </div>
1011 <?php if ( ! empty( $api->rating ) ) { ?>
1012 <h3><?php fs_echo_inline( 'Average Rating', 'average-rating', $api->slug ); ?></h3>
1013 <?php wp_star_rating( array(
1014 'rating' => $api->rating,
1015 'type' => 'percent',
1016 'number' => $api->num_ratings
1017 ) ); ?>
1018 <small>(<?php echo esc_html( sprintf(
1019 fs_text_inline( 'based on %s', 'based-on-x', $api->slug ),
1020 sprintf(
1021 ( ( 1 == $api->num_ratings ) ?
1022 /* translators: %s: 1 or One */
1023 fs_text_inline( '%s rating', 'x-rating', $api->slug ) :
1024 /* translators: %s: Number larger than 1 */
1025 fs_text_inline( '%s ratings', 'x-ratings', $api->slug )
1026 ),
1027 number_format_i18n( $api->num_ratings )
1028 ) ) ) ?>)
1029 </small>
1030 <?php
1031 }
1032
1033 if ( ! empty( $api->ratings ) && array_sum( (array) $api->ratings ) > 0 ) {
1034 foreach ( $api->ratings as $key => $ratecount ) {
1035 // Avoid div-by-zero.
1036 $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0;
1037 $stars_label = sprintf(
1038 ( ( 1 == $key ) ?
1039 /* translators: %s: 1 or One */
1040 fs_text_inline( '%s star', 'x-star', $api->slug ) :
1041 /* translators: %s: Number larger than 1 */
1042 fs_text_inline( '%s stars', 'x-stars', $api->slug )
1043 ),
1044 number_format_i18n( $key )
1045 );
1046 ?>
1047 <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>
1056 <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>
1060 </div>
1061 <?php
1062 }
1063 }
1064 if ( ! empty( $api->contributors ) ) {
1065 ?>
1066 <h3><?php fs_echo_inline( 'Contributors', 'contributors', $api->slug ); ?></h3>
1067 <ul class="contributors">
1068 <?php
1069 foreach ( (array) $api->contributors as $contrib_username => $contrib_profile ) {
1070 if ( empty( $contrib_username ) && empty( $contrib_profile ) ) {
1071 continue;
1072 }
1073 if ( empty( $contrib_username ) ) {
1074 $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile );
1075 }
1076 $contrib_username = sanitize_user( $contrib_username );
1077 if ( empty( $contrib_profile ) ) {
1078 echo "<li><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</li>";
1079 } 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>";
1081 }
1082 }
1083 ?>
1084 </ul>
1085 <?php if ( ! empty( $api->donate_link ) ) { ?>
1086 <a target="_blank"
1087 href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1088 &#187;</a>
1089 <?php } ?>
1090 <?php } ?>
1091 </div>
1092 <div id="section-holder" class="wrap">
1093 <?php
1094 if ( ! empty( $api->tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) {
1095 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, '<' ) ) {
1097 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 }
1099
1100 foreach ( (array) $api->sections as $section_name => $content ) {
1101 $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' );
1102 $content = links_add_target( $content, '_blank' );
1103
1104 $san_section = esc_attr( $section_name );
1105
1106 $display = ( $section_name === $section ) ? 'block' : 'none';
1107
1108 if ( 'description' === $section_name &&
1109 ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) ||
1110 ( ! $api->is_wp_org_compliant && $api->fs_missing ) )
1111 ) {
1112 $missing_notice = array(
1113 'type' => 'error',
1114 'id' => md5( microtime() ),
1115 'message' => $api->is_paid ?
1116 fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) :
1117 fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ),
1118 );
1119 fs_require_template( 'admin-notice.php', $missing_notice );
1120 }
1121 echo "\t<div id='section-{$san_section}' class='section' style='display: {$display};'>\n";
1122 echo $content;
1123 echo "\t</div>\n";
1124 }
1125 echo "</div>\n";
1126 echo "</div>\n";
1127 echo "</div>\n"; // #plugin-information-scrollable
1128 echo "<div id='$tab-footer'>\n";
1129
1130 if ( $api->has_paid_plan && ! empty( $api->checkout_link ) ) {
1131 echo $this->get_checkout_cta( $api );
1132 }
1133
1134 if ( ! empty( $api->download_link ) ) {
1135 echo $this->get_download_cta( $api, empty( $api->checkout_link ) );
1136 }
1137
1138 echo "</div>\n";
1139
1140 iframe_footer();
1141 exit;
1142 }
1143 }