PluginProbe
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder / 1.5.1
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder v1.5.1
3.6.0 3.5.0 trunk 1.0.10 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.6.1 1.0.7 1.0.8 1.0.9 1.1 1.1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5 1.5.1 All 110 releases
buttonizer-multifunctional-button / freemius / includes / fs-plugin-info-dialog.php

fs-plugin-info-dialog.php in Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder 1.5.1, at freemius/includes/fs-plugin-info-dialog.php

1,110 lines 56.7 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', false );
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( "/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 if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) {
146 $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository(
147 'plugin_information', (object) array(
148 'slug' => $selected_addon->slug,
149 'is_ssl' => is_ssl(),
150 'fields' => array(
151 'banners' => true,
152 'reviews' => true,
153 'downloaded' => false,
154 'active_installs' => true
155 )
156 ) );
157
158 if ( ! empty( $repo_data ) ) {
159 $data = $repo_data;
160 $data->wp_org_missing = false;
161 } else {
162 // Couldn't find plugin on .org.
163 $selected_addon->is_wp_org_compliant = false;
164
165 // Plugin is missing, not on Freemius nor WP.org.
166 $data->wp_org_missing = true;
167 }
168 } else {
169 $data->wp_org_missing = false;
170
171 // Fetch latest version from Freemius.
172 $latest = $this->_fs->_fetch_latest_version( $selected_addon->id );
173
174 if ( $has_paid_plan ) {
175 $data->checkout_link = $this->_fs->checkout_url();
176 }
177 if ( $has_free_plan ) {
178 $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id );
179 }
180
181 $data->fs_missing = ( false === $latest );
182
183
184 // Fetch as much as possible info from local files.
185 $plugin_local_data = $this->_fs->get_plugin_data();
186 $data->name = $selected_addon->title;
187 $data->author = $plugin_local_data['Author'];
188 $view_vars = array( 'plugin' => $selected_addon );
189 $data->sections = array(
190 'description' => fs_get_template( '/plugin-info/description.php', $view_vars ),
191 );
192
193 if ( ! empty( $selected_addon->info->banner_url ) ) {
194 $data->banners = array(
195 'low' => $selected_addon->info->banner_url,
196 );
197 }
198
199 if ( ! empty( $selected_addon->info->screenshots ) ) {
200 $view_vars = array(
201 'screenshots' => $selected_addon->info->screenshots,
202 'plugin' => $selected_addon,
203 );
204 $data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars );
205 }
206
207 if ( is_object( $latest ) ) {
208 $data->version = $latest->version;
209 $data->last_updated = ! is_null( $latest->updated ) ? $latest->updated : $latest->created;
210 $data->requires = $latest->requires_platform_version;
211 $data->tested = $latest->tested_up_to_version;
212 } else {
213 // Add dummy version.
214 $data->version = '1.0.0';
215
216 // Add message to developer to deploy the plugin through Freemius.
217 }
218 }
219
220 if ( $has_pricing ) {
221 // Add plans to data.
222 $data->plans = $plans;
223
224 if ( $has_features ) {
225 $view_vars = array(
226 'plans' => $plans,
227 'plugin' => $selected_addon,
228 );
229 $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars );
230 }
231 }
232
233 $data->has_free_plan = $has_free_plan;
234 $data->has_paid_plan = $has_paid_plan;
235 $data->is_paid = $has_paid_plan;
236 $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant;
237
238 return $data;
239 }
240
241 /**
242 * @author Vova Feldman (@svovaf)
243 * @since 1.1.7
244 *
245 * @param FS_Plugin_Plan $plan
246 *
247 * @return string
248 */
249 private function get_billing_cycle( FS_Plugin_Plan $plan ) {
250 $billing_cycle = null;
251
252 if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) {
253 $pricing = $plan->pricing[0];
254 if ( isset( $pricing->annual_price ) ) {
255 $billing_cycle = 'annual';
256 } else if ( isset( $pricing->monthly_price ) ) {
257 $billing_cycle = 'monthly';
258 } else if ( isset( $pricing->lifetime_price ) ) {
259 $billing_cycle = 'lifetime';
260 }
261 } else {
262 foreach ( $plan->pricing as $pricing ) {
263 if ( isset( $pricing->annual_price ) ) {
264 $billing_cycle = 'annual';
265 } else if ( isset( $pricing->monthly_price ) ) {
266 $billing_cycle = 'monthly';
267 } else if ( isset( $pricing->lifetime_price ) ) {
268 $billing_cycle = 'lifetime';
269 }
270
271 if ( ! is_null( $billing_cycle ) ) {
272 break;
273 }
274 }
275 }
276
277 return $billing_cycle;
278 }
279
280 /**
281 * @author Vova Feldman (@svovaf)
282 * @since 2.0.0
283 *
284 * @param FS_Plugin_Plan $plan
285 * @param FS_Pricing $pricing
286 *
287 * @return float|null|string
288 */
289 private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) {
290 $price_tag = '';
291 if ( isset( $pricing->annual_price ) ) {
292 $price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' );
293 } else if ( isset( $pricing->monthly_price ) ) {
294 $price_tag = $pricing->monthly_price . ' / mo';
295 } else if ( isset( $pricing->lifetime_price ) ) {
296 $price_tag = $pricing->lifetime_price;
297 }
298
299 return '$' . $price_tag;
300 }
301
302 /**
303 * @author Vova Feldman (@svovaf)
304 * @since 1.1.7
305 *
306 * @param object $api
307 * @param FS_Plugin_Plan $plan
308 *
309 * @return string
310 */
311 private function get_checkout_cta( $api, $plan = null ) {
312 if ( empty( $api->checkout_link ) ||
313 ! isset( $api->plans ) ||
314 ! is_array( $api->plans ) ||
315 0 == count( $api->plans )
316 ) {
317 return '';
318 }
319
320 if ( is_null( $plan ) ) {
321 foreach ( $api->plans as $p ) {
322 if ( ! empty( $p->pricing ) ) {
323 $plan = $p;
324 break;
325 }
326 }
327 }
328
329 return '<a class="button button-primary fs-checkout-button right" href="' . $this->_fs->addon_checkout_url(
330 $plan->plugin_id,
331 $plan->pricing[0]->id,
332 $this->get_billing_cycle( $plan ),
333 $plan->has_trial()
334 ) . '" target="_parent">' .
335 ( ! $plan->has_trial() ?
336 fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug ) :
337 sprintf(
338 /* translators: %s: N-days trial */
339 fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ),
340 $this->get_trial_period( $plan )
341 )
342 ) .
343 '</a>';
344 }
345
346 /**
347 * @author Vova Feldman (@svovaf)
348 * @since 2.0.0
349 *
350 * @param object $api
351 * @param bool $is_primary
352 *
353 * @return string
354 */
355 private function get_download_cta( $api, $is_primary = true ) {
356 if ( empty( $api->download_link ) ) {
357 return '';
358 }
359
360 $status = install_plugin_install_status( $api );
361
362 $has_paid_version = $api->has_paid_plan;
363
364 // Hosted on WordPress.org.
365 switch ( $status['status'] ) {
366 case 'install':
367 if ( $api->is_wp_org_compliant ||
368 ! $this->_fs->is_org_repo_compliant() ||
369 $this->_fs->is_premium()
370 ) {
371 /**
372 * Allow immediate installation if one of the following:
373 * 1. WordPress.org add-on.
374 * 2. The core module is NOT wp.org compliant.
375 * 3. The core module is running the premium version which is not wp.org compliant.
376 */
377 if ( $status['url'] ) {
378 return $this->get_cta(
379 ( $has_paid_version ?
380 fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) :
381 fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ),
382 $is_primary,
383 false,
384 $status['url'],
385 '_parent'
386 );
387 }
388 }
389
390 return $this->get_cta(
391 ( $has_paid_version ?
392 fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) :
393 fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ),
394 $is_primary,
395 false,
396 esc_url( $api->download_link )
397 );
398 break;
399 case 'update_available':
400 if ( $status['url'] ) {
401 return $this->get_cta(
402 ( $has_paid_version ?
403 fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) :
404 fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ),
405 $is_primary,
406 false,
407 $status['url'],
408 '_parent'
409 );
410 }
411 break;
412 case 'newer_installed':
413 return $this->get_cta(
414 ( $has_paid_version ?
415 esc_html( sprintf( fs_text_inline( 'Newer Free Version (%s) Installed', 'newer-free-installed', $api->slug ), $status['version'] ) ) :
416 esc_html( sprintf( fs_text_inline( 'Newer Version (%s) Installed', 'newer-installed', $api->slug ), $status['version'] ) ) ),
417 $is_primary,
418 true
419 );
420 break;
421 case 'latest_installed':
422 return $this->get_cta(
423 ( $has_paid_version ?
424 fs_esc_html_inline( 'Latest Free Version Installed', 'latest-free-installed', $api->slug ) :
425 fs_esc_html_inline( 'Latest Version Installed', 'latest-installed', $api->slug ) ),
426 $is_primary,
427 true
428 );
429 break;
430 }
431
432 return '';
433 }
434
435 /**
436 * Helper method to get a CTA button HTML.
437 *
438 * @author Vova Feldman (@svovaf)
439 * @since 2.0.0
440 *
441 * @param string $label
442 * @param bool $is_primary
443 * @param bool $is_disabled
444 * @param string $href
445 * @param string $target
446 *
447 * @return string
448 */
449 private function get_cta(
450 $label,
451 $is_primary = true,
452 $is_disabled = false,
453 $href = '',
454 $target = '_blank'
455 ) {
456 $classes = array();
457
458 if ( ! $is_primary ) {
459 $classes[] = 'left';
460 } else {
461 $classes[] = 'button-primary';
462 $classes[] = 'right';
463 }
464
465 if ( $is_disabled ) {
466 $classes[] = 'disabled';
467 }
468
469 return sprintf(
470 '<a %s class="button %s">%s</a>',
471 empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"',
472 implode( ' ', $classes ),
473 $label
474 );
475 }
476
477 /**
478 * @author Vova Feldman (@svovaf)
479 * @since 1.1.7
480 *
481 * @param FS_Plugin_Plan $plan
482 *
483 * @return string
484 */
485 private function get_trial_period( $plan ) {
486 $trial_period = (int) $plan->trial_period;
487
488 switch ( $trial_period ) {
489 case 30:
490 return 'month';
491 case 60:
492 return '2 months';
493 default:
494 return "{$plan->trial_period} days";
495 }
496 }
497
498 /**
499 * Display plugin information in dialog box form.
500 *
501 * Based on core install_plugin_information() function.
502 *
503 * @author Vova Feldman (@svovaf)
504 * @since 1.0.6
505 */
506 function install_plugin_information() {
507 global $tab;
508
509 if ( empty( $_REQUEST['plugin'] ) ) {
510 return;
511 }
512
513 $args = array(
514 'slug' => wp_unslash( $_REQUEST['plugin'] ),
515 'is_ssl' => is_ssl(),
516 'fields' => array(
517 'banners' => true,
518 'reviews' => true,
519 'downloaded' => false,
520 'active_installs' => true
521 )
522 );
523
524 if ( is_array( $args ) ) {
525 $args = (object) $args;
526 }
527
528 if ( ! isset( $args->per_page ) ) {
529 $args->per_page = 24;
530 }
531
532 if ( ! isset( $args->locale ) ) {
533 $args->locale = get_locale();
534 }
535
536 $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args );
537
538 if ( is_wp_error( $api ) ) {
539 wp_die( $api );
540 }
541
542 $plugins_allowedtags = array(
543 'a' => array(
544 'href' => array(),
545 'title' => array(),
546 'target' => array(),
547 // Add image style for screenshots.
548 'class' => array()
549 ),
550 'style' => array(),
551 'abbr' => array( 'title' => array() ),
552 'acronym' => array( 'title' => array() ),
553 'code' => array(),
554 'pre' => array(),
555 'em' => array(),
556 'strong' => array(),
557 'div' => array( 'class' => array() ),
558 'span' => array( 'class' => array() ),
559 'p' => array(),
560 'ul' => array(),
561 'ol' => array(),
562 'li' => array( 'class' => array() ),
563 'i' => array( 'class' => array() ),
564 'h1' => array(),
565 'h2' => array(),
566 'h3' => array(),
567 'h4' => array(),
568 'h5' => array(),
569 'h6' => array(),
570 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ),
571 // 'table' => array(),
572 // 'td' => array(),
573 // 'tr' => array(),
574 // 'th' => array(),
575 // 'thead' => array(),
576 // 'tbody' => array(),
577 );
578
579 $plugins_section_titles = array(
580 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ),
581 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ),
582 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ),
583 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ),
584 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ),
585 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ),
586 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ),
587 );
588
589 // Sanitize HTML
590 // foreach ( (array) $api->sections as $section_name => $content ) {
591 // $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags );
592 // }
593
594 foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) {
595 if ( isset( $api->$key ) ) {
596 $api->$key = wp_kses( $api->$key, $plugins_allowedtags );
597 }
598 }
599
600 // Add after $api->slug is ready.
601 $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug );
602
603 $_tab = esc_attr( $tab );
604
605 $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English.
606 if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) {
607 $section_titles = array_keys( (array) $api->sections );
608 $section = array_shift( $section_titles );
609 }
610
611 iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) );
612
613 $_with_banner = '';
614
615 // var_dump($api->banners);
616 if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) {
617 $_with_banner = 'with-banner';
618 $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low'];
619 $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high'];
620 ?>
621 <style type="text/css">
622 #plugin-information-title.with-banner
623 {
624 background-image: url( <?php echo esc_url( $low ); ?> );
625 }
626
627 @media only screen and ( -webkit-min-device-pixel-ratio: 1.5 )
628 {
629 #plugin-information-title.with-banner
630 {
631 background-image: url( <?php echo esc_url( $high ); ?> );
632 }
633 }
634 </style>
635 <?php
636 }
637
638 echo '<div id="plugin-information-scrollable">';
639 echo "<div id='{$_tab}-title' class='{$_with_banner}'><div class='vignette'></div><h2>{$api->name}</h2></div>";
640 echo "<div id='{$_tab}-tabs' class='{$_with_banner}'>\n";
641
642 foreach ( (array) $api->sections as $section_name => $content ) {
643 if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) {
644 continue;
645 }
646
647 if ( isset( $plugins_section_titles[ $section_name ] ) ) {
648 $title = $plugins_section_titles[ $section_name ];
649 } else {
650 $title = ucwords( str_replace( '_', ' ', $section_name ) );
651 }
652
653 $class = ( $section_name === $section ) ? ' class="current"' : '';
654 $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) );
655 $href = esc_url( $href );
656 $san_section = esc_attr( $section_name );
657 echo "\t<a name='$san_section' href='$href' $class>$title</a>\n";
658 }
659
660 echo "</div>\n";
661
662 ?>
663 <div id="<?php echo $_tab; ?>-content" class='<?php echo $_with_banner; ?>'>
664 <div class="fyi">
665 <?php if ( $api->is_paid ) : ?>
666 <?php if ( isset( $api->plans ) ) : ?>
667 <div class="plugin-information-pricing">
668 <?php foreach ( $api->plans as $plan ) : ?>
669 <?php
670 if ( empty( $plan->pricing ) ) {
671 continue;
672 }
673
674 /**
675 * @var FS_Plugin_Plan $plan
676 */
677 ?>
678 <?php $first_pricing = $plan->pricing[0] ?>
679 <?php $is_multi_cycle = $first_pricing->is_multi_cycle() ?>
680 <div class="fs-plan<?php if ( ! $is_multi_cycle ) {
681 echo ' fs-single-cycle';
682 } ?>" data-plan-id="<?php echo $plan->id ?>">
683 <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>
684 <?php $has_annual = $first_pricing->has_annual() ?>
685 <?php $has_monthly = $first_pricing->has_monthly() ?>
686 <div class="nav-tab-wrapper">
687 <?php $billing_cycles = array( 'monthly', 'annual', 'lifetime' ) ?>
688 <?php $i = 0;
689 foreach ( $billing_cycles as $cycle ) : ?>
690 <?php $prop = "{$cycle}_price";
691 if ( isset( $first_pricing->{$prop} ) ) : ?>
692 <?php $is_featured = ( 'annual' === $cycle && $is_multi_cycle ) ?>
693 <?php
694 $prices = array();
695 foreach ( $plan->pricing as $pricing ) {
696 if ( isset( $pricing->{$prop} ) ) {
697 $prices[] = array(
698 'id' => $pricing->id,
699 'licenses' => $pricing->licenses,
700 'price' => $pricing->{$prop}
701 );
702 }
703 }
704 ?>
705 <a class="nav-tab" data-billing-cycle="<?php echo $cycle ?>"
706 data-pricing="<?php echo esc_attr( json_encode( $prices ) ) ?>">
707 <?php if ( $is_featured ) : ?>
708 <label>
709 &#9733; <?php fs_esc_html_echo_x_inline( 'Best', 'e.g. the best product', 'best', $api->slug ) ?>
710 &#9733;</label>
711 <?php endif ?>
712 <?php
713 switch ( $cycle ) {
714 case 'monthly':
715 fs_esc_html_echo_x_inline( 'Monthly', 'as every month', 'monthly', $api->slug );
716 break;
717 case 'annual':
718 fs_esc_html_echo_x_inline( 'Annual', 'as once a year', 'annual', $api->slug );
719 break;
720 case 'lifetime':
721 fs_esc_html_echo_inline( 'Lifetime', 'lifetime', $api->slug );
722 break;
723 }
724 ?>
725 </a>
726 <?php endif ?>
727 <?php $i ++; endforeach ?>
728 <?php wp_enqueue_script( 'jquery' ) ?>
729 <script type="text/javascript">
730 (function ($, undef) {
731 var
732 _formatBillingFrequency = function (cycle) {
733 switch (cycle) {
734 case 'monthly':
735 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 ) ) ?>';
736 case 'annual':
737 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 ) ) ?>';
738 case 'lifetime':
739 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 ) ) ?>';
740 }
741 },
742 _formatLicensesTitle = function (pricing) {
743 switch (pricing.licenses) {
744 case 1:
745 return '<?php fs_esc_attr_echo_inline( 'Single Site License', 'license-single-site', $api->slug ) ?>';
746 case null:
747 return '<?php fs_esc_attr_echo_inline( 'Unlimited Licenses', 'license-unlimited', $api->slug ) ?>';
748 default:
749 return '<?php fs_esc_attr_echo_inline( 'Up to %s Sites', 'license-x-sites', $api->slug ) ?>'.replace('%s', pricing.licenses);
750 }
751 },
752 _formatPrice = function (pricing, cycle, multipleLicenses) {
753 if (undef === multipleLicenses)
754 multipleLicenses = true;
755
756 var priceCycle;
757 switch (cycle) {
758 case 'monthly':
759 priceCycle = ' / <?php fs_echo_x_inline( 'mo', 'as monthly period', 'mo', $api->slug ) ?>';
760 break;
761 case 'lifetime':
762 priceCycle = '';
763 break;
764 case 'annual':
765 default:
766 priceCycle = ' / <?php fs_echo_x_inline( 'year', 'as annual period', 'year', $api->slug ) ?>';
767 break;
768 }
769
770 if (!multipleLicenses && 1 == pricing.licenses) {
771 return '$' + pricing.price + priceCycle;
772 }
773
774 return _formatLicensesTitle(pricing) + ' - <var class="fs-price">$' + pricing.price + priceCycle + '</var>';
775 },
776 _checkoutUrl = function (plan, pricing, cycle) {
777 return '<?php echo esc_url_raw( remove_query_arg( 'billing_cycle', add_query_arg( array( 'plugin_id' => $plan->plugin_id ), $api->checkout_link ) ) ) ?>' +
778 '&plan_id=' + plan +
779 '&pricing_id=' + pricing +
780 '&billing_cycle=' + cycle<?php if ( $plan->has_trial() ) {
781 echo " + '&trial=true'";
782 }?>;
783 },
784 _updateCtaUrl = function (plan, pricing, cycle) {
785 $('.plugin-information-pricing .button, #plugin-information-footer .button.fs-checkout-button').attr('href', _checkoutUrl(plan, pricing, cycle));
786 };
787
788 $(document).ready(function () {
789 var $plan = $('.plugin-information-pricing .fs-plan[data-plan-id=<?php echo $plan->id ?>]');
790 $plan.find('input[type=radio]').live('click', function () {
791 _updateCtaUrl(
792 $plan.attr('data-plan-id'),
793 $(this).val(),
794 $plan.find('.nav-tab-active').attr('data-billing-cycle')
795 );
796
797 $plan.find('.fs-trial-terms .fs-price').html(
798 $(this).parents('label').find('.fs-price').html()
799 );
800 });
801
802 $plan.find('.nav-tab').click(function () {
803 if ($(this).hasClass('nav-tab-active'))
804 return;
805
806 var $this = $(this),
807 billingCycle = $this.attr('data-billing-cycle'),
808 pricing = JSON.parse($this.attr('data-pricing')),
809 $pricesList = $this.parents('.fs-plan').find('.fs-pricing-body .fs-licenses'),
810 html = '';
811
812 // Un-select previously selected tab.
813 $plan.find('.nav-tab').removeClass('nav-tab-active');
814
815 // Select current tab.
816 $this.addClass('nav-tab-active');
817
818 // Render licenses prices.
819 if (1 == pricing.length) {
820 html = '<li><label><?php echo fs_esc_attr_x_inline( 'Price', 'noun', 'price', $api->slug ) ?>: ' + _formatPrice(pricing[0], billingCycle, false) + '</label></li>';
821 } else {
822 for (var i = 0; i < pricing.length; i++) {
823 html += '<li><label><input name="pricing-<?php echo $plan->id ?>" type="radio" value="' + pricing[i].id + '">' + _formatPrice(pricing[i], billingCycle) + '</label></li>';
824 }
825 }
826 $pricesList.html(html);
827
828 if (1 < pricing.length) {
829 // Select first license option.
830 $pricesList.find('li:first input').click();
831 }
832 else {
833 _updateCtaUrl(
834 $plan.attr('data-plan-id'),
835 pricing[0].id,
836 billingCycle
837 );
838 }
839
840 // Update billing frequency.
841 $plan.find('.fs-billing-frequency').html(_formatBillingFrequency(billingCycle));
842
843 if ('annual' === billingCycle) {
844 $plan.find('.fs-annual-discount').show();
845 } else {
846 $plan.find('.fs-annual-discount').hide();
847 }
848 });
849
850 <?php if ( $has_annual ) : ?>
851 // Select annual by default.
852 $plan.find('.nav-tab[data-billing-cycle=annual]').click();
853 <?php else : ?>
854 // Select first tab.
855 $plan.find('.nav-tab:first').click();
856 <?php endif ?>
857 });
858 }(jQuery));
859 </script>
860 </div>
861 <div class="fs-pricing-body">
862 <span class="fs-billing-frequency"></span>
863 <?php $annual_discount = ( $has_annual && $has_monthly ) ? $plan->pricing[0]->annual_discount_percentage() : 0 ?>
864 <?php if ( $annual_discount > 0 ) : ?>
865 <span
866 class="fs-annual-discount"><?php printf(
867 /* translators: %s: Discount (e.g. discount of $5 or 10%) */
868 fs_esc_html_inline( 'Save %s', 'save-x', $api->slug ), $annual_discount . '%' ) ?></span>
869 <?php endif ?>
870 <ul class="fs-licenses">
871 </ul>
872 <?php echo $this->get_checkout_cta( $api, $plan, false ) ?>
873 <div style="clear:both"></div>
874 <?php if ( $plan->has_trial() ) : ?>
875 <?php $trial_period = $this->get_trial_period( $plan ) ?>
876 <ul class="fs-trial-terms">
877 <li>
878 <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 ) ) ?>
879 </li>
880 <li>
881 <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>' ) ?>
882 </li>
883 </ul>
884 <?php endif ?>
885 </div>
886 </div>
887 </div>
888 <?php endforeach ?>
889 <?php endif ?>
890 <?php endif ?>
891 <div>
892 <h3><?php fs_echo_inline( 'Details', 'details', $api->slug ) ?></h3>
893 <ul>
894 <?php if ( ! empty( $api->version ) ) { ?>
895 <li>
896 <strong><?php fs_esc_html_echo_x_inline( 'Version', 'product version', 'version', $api->slug ); ?>
897 :</strong> <?php echo $api->version; ?></li>
898 <?php
899 }
900 if ( ! empty( $api->author ) ) {
901 ?>
902 <li>
903 <strong><?php fs_echo_x_inline( 'Author', 'as the plugin author', 'author', $api->slug ); ?>
904 :</strong> <?php echo links_add_target( $api->author, '_blank' ); ?>
905 </li>
906 <?php
907 }
908 if ( ! empty( $api->last_updated ) ) {
909 ?>
910 <li><strong><?php fs_echo_inline( 'Last Updated', 'last-updated', $api->slug ); ?>
911 :</strong> <span
912 title="<?php echo $api->last_updated; ?>">
913 <?php echo esc_html( sprintf(
914 /* translators: %s: time period (e.g. "2 hours" ago) */
915 fs_text_x_inline( '%s ago', 'x-ago', $api->slug ),
916 human_time_diff( strtotime( $api->last_updated ) )
917 ) ) ?>
918 </span></li>
919 <?php
920 }
921 if ( ! empty( $api->requires ) ) {
922 ?>
923 <li>
924 <strong><?php fs_esc_html_echo_inline( 'Requires WordPress Version', 'requires-wordpress-version', $api->slug ) ?>
925 :</strong> <?php echo esc_html( sprintf( fs_text_inline( '%s or higher', 'x-or-higher', $api->slug ), $api->requires ) ) ?>
926 </li>
927 <?php
928 }
929 if ( ! empty( $api->tested ) ) {
930 ?>
931 <li>
932 <strong><?php fs_esc_html_echo_inline( 'Compatible up to', 'compatible-up-to', $api->slug ); ?>
933 :</strong> <?php echo $api->tested; ?>
934 </li>
935 <?php
936 }
937 if ( ! empty( $api->downloaded ) ) {
938 ?>
939 <li>
940 <strong><?php fs_esc_html_echo_inline( 'Downloaded', 'downloaded', $api->slug ) ?>
941 :</strong> <?php echo esc_html( sprintf(
942 ( ( 1 == $api->downloaded ) ?
943 /* translators: %s: 1 or One (Number of times downloaded) */
944 fs_text_inline( '%s time', 'x-time', $api->slug ) :
945 /* translators: %s: Number of times downloaded */
946 fs_text_inline( '%s times', 'x-times', $api->slug )
947 ),
948 number_format_i18n( $api->downloaded )
949 ) ); ?>
950 </li>
951 <?php
952 }
953 if ( ! empty( $api->slug ) && empty( $api->is_wp_org_compliant ) ) {
954 ?>
955 <li><a target="_blank"
956 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 ) ?>
957 &#187;</a>
958 </li>
959 <?php
960 }
961 if ( ! empty( $api->homepage ) ) {
962 ?>
963 <li><a target="_blank"
964 href="<?php echo esc_url( $api->homepage ); ?>"><?php fs_esc_html_echo_inline( 'Plugin Homepage', 'plugin-homepage', $api->slug ) ?>
965 &#187;</a>
966 </li>
967 <?php
968 }
969 if ( ! empty( $api->donate_link ) && empty( $api->contributors ) ) {
970 ?>
971 <li><a target="_blank"
972 href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_esc_html_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
973 &#187;</a>
974 </li>
975 <?php } ?>
976 </ul>
977 </div>
978 <?php if ( ! empty( $api->rating ) ) { ?>
979 <h3><?php fs_echo_inline( 'Average Rating', 'average-rating', $api->slug ); ?></h3>
980 <?php wp_star_rating( array(
981 'rating' => $api->rating,
982 'type' => 'percent',
983 'number' => $api->num_ratings
984 ) ); ?>
985 <small>(<?php echo esc_html( sprintf(
986 fs_text_inline( 'based on %s', 'based-on-x', $api->slug ),
987 sprintf(
988 ( ( 1 == $api->num_ratings ) ?
989 /* translators: %s: 1 or One */
990 fs_text_inline( '%s rating', 'x-rating', $api->slug ) :
991 /* translators: %s: Number larger than 1 */
992 fs_text_inline( '%s ratings', 'x-ratings', $api->slug )
993 ),
994 number_format_i18n( $api->num_ratings )
995 ) ) ) ?>)
996 </small>
997 <?php
998 }
999
1000 if ( ! empty( $api->ratings ) && array_sum( (array) $api->ratings ) > 0 ) {
1001 foreach ( $api->ratings as $key => $ratecount ) {
1002 // Avoid div-by-zero.
1003 $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0;
1004 $stars_label = sprintf(
1005 ( ( 1 == $key ) ?
1006 /* translators: %s: 1 or One */
1007 fs_text_inline( '%s star', 'x-star', $api->slug ) :
1008 /* translators: %s: Number larger than 1 */
1009 fs_text_inline( '%s stars', 'x-stars', $api->slug )
1010 ),
1011 number_format_i18n( $key )
1012 );
1013 ?>
1014 <div class="counter-container">
1015 <span class="counter-label"><a
1016 href="https://wordpress.org/support/view/plugin-reviews/<?php echo $api->slug; ?>?filter=<?php echo $key; ?>"
1017 target="_blank"
1018 title="<?php echo esc_attr( sprintf(
1019 /* translators: %s: # of stars (e.g. 5 stars) */
1020 fs_text_inline( 'Click to see reviews that provided a rating of %s', 'click-to-reviews', $api->slug ),
1021 $stars_label
1022 ) ) ?>"><?php echo $stars_label ?></a></span>
1023 <span class="counter-back">
1024 <span class="counter-bar" style="width: <?php echo 92 * $_rating; ?>px;"></span>
1025 </span>
1026 <span class="counter-count"><?php echo number_format_i18n( $ratecount ); ?></span>
1027 </div>
1028 <?php
1029 }
1030 }
1031 if ( ! empty( $api->contributors ) ) {
1032 ?>
1033 <h3><?php fs_echo_inline( 'Contributors', 'contributors', $api->slug ); ?></h3>
1034 <ul class="contributors">
1035 <?php
1036 foreach ( (array) $api->contributors as $contrib_username => $contrib_profile ) {
1037 if ( empty( $contrib_username ) && empty( $contrib_profile ) ) {
1038 continue;
1039 }
1040 if ( empty( $contrib_username ) ) {
1041 $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile );
1042 }
1043 $contrib_username = sanitize_user( $contrib_username );
1044 if ( empty( $contrib_profile ) ) {
1045 echo "<li><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</li>";
1046 } else {
1047 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>";
1048 }
1049 }
1050 ?>
1051 </ul>
1052 <?php if ( ! empty( $api->donate_link ) ) { ?>
1053 <a target="_blank"
1054 href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1055 &#187;</a>
1056 <?php } ?>
1057 <?php } ?>
1058 </div>
1059 <div id="section-holder" class="wrap">
1060 <?php
1061 if ( ! empty( $api->tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) {
1062 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>';
1063 } else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) {
1064 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>';
1065 }
1066
1067 foreach ( (array) $api->sections as $section_name => $content ) {
1068 $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' );
1069 $content = links_add_target( $content, '_blank' );
1070
1071 $san_section = esc_attr( $section_name );
1072
1073 $display = ( $section_name === $section ) ? 'block' : 'none';
1074
1075 if ( 'description' === $section_name &&
1076 ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) ||
1077 ( ! $api->is_wp_org_compliant && $api->fs_missing ) )
1078 ) {
1079 $missing_notice = array(
1080 'type' => 'error',
1081 'id' => md5( microtime() ),
1082 'message' => $api->is_paid ?
1083 fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) :
1084 fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ),
1085 );
1086 fs_require_template( 'admin-notice.php', $missing_notice );
1087 }
1088 echo "\t<div id='section-{$san_section}' class='section' style='display: {$display};'>\n";
1089 echo $content;
1090 echo "\t</div>\n";
1091 }
1092 echo "</div>\n";
1093 echo "</div>\n";
1094 echo "</div>\n"; // #plugin-information-scrollable
1095 echo "<div id='$tab-footer'>\n";
1096
1097 if ( ! empty( $api->checkout_link ) ) {
1098 echo $this->get_checkout_cta( $api );
1099 }
1100
1101 if ( ! empty( $api->download_link ) ) {
1102 echo $this->get_download_cta( $api, empty( $api->checkout_link ) );
1103 }
1104
1105 echo "</div>\n";
1106
1107 iframe_footer();
1108 exit;
1109 }
1110 }