PluginProbe ʕ •ᴥ•ʔ
Code Manager / 1.0.0
Code Manager v1.0.0
1.0.48 1.0.47 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 1.0.25 1.0.26 1.0.27 1.0.28 1.0.3 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 1.0.39 1.0.4 1.0.40 1.0.41 1.0.42 1.0.43 1.0.44 1.0.45 1.0.46 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9
code-manager / freemius / includes / fs-plugin-info-dialog.php
code-manager / freemius / includes Last commit date
customizer 5 years ago debug 5 years ago entities 5 years ago managers 5 years ago sdk 5 years ago supplements 5 years ago class-freemius-abstract.php 5 years ago class-freemius.php 5 years ago class-fs-admin-notices.php 5 years ago class-fs-api.php 5 years ago class-fs-logger.php 5 years ago class-fs-options.php 5 years ago class-fs-plugin-updater.php 5 years ago class-fs-security.php 5 years ago class-fs-storage.php 5 years ago class-fs-user-lock.php 5 years ago fs-core-functions.php 5 years ago fs-essential-functions.php 5 years ago fs-plugin-info-dialog.php 5 years ago i18n.php 5 years ago index.php 5 years ago l10n.php 5 years ago
fs-plugin-info-dialog.php
1631 lines
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 /**
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
57 function __construct( Freemius $fs ) {
58 $this->_fs = $fs;
59
60 $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $fs->get_slug() . '_info', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
61
62 // Remove default plugin information action.
63 remove_all_actions( 'install_plugins_pre_plugin-information' );
64
65 // Override action with custom plugins function for add-ons.
66 add_action( 'install_plugins_pre_plugin-information', array( &$this, 'install_plugin_information' ) );
67
68 // Override request for plugin information for Add-ons.
69 add_filter(
70 'fs_plugins_api',
71 array( &$this, '_get_addon_info_filter' ),
72 WP_FS__DEFAULT_PRIORITY, 3 );
73 }
74
75 /**
76 * Generate add-on plugin information.
77 *
78 * @author Vova Feldman (@svovaf)
79 * @since 1.0.6
80 *
81 * @param array $data
82 * @param string $action
83 * @param object|null $args
84 *
85 * @return array|null
86 */
87 function _get_addon_info_filter( $data, $action = '', $args = null ) {
88 $this->_logger->entrance();
89
90 $parent_plugin_id = fs_request_get( 'parent_plugin_id', $this->_fs->get_id() );
91
92 if ( $this->_fs->get_id() != $parent_plugin_id ||
93 ( 'plugin_information' !== $action ) ||
94 ! isset( $args->slug )
95 ) {
96 return $data;
97 }
98
99 // Find add-on by slug.
100 $selected_addon = $this->_fs->get_addon_by_slug( $args->slug, WP_FS__DEV_MODE );
101
102 if ( false === $selected_addon ) {
103 return $data;
104 }
105
106 if ( ! isset( $selected_addon->info ) ) {
107 // Setup some default info.
108 $selected_addon->info = new stdClass();
109 $selected_addon->info->selling_point_0 = 'Selling Point 1';
110 $selected_addon->info->selling_point_1 = 'Selling Point 2';
111 $selected_addon->info->selling_point_2 = 'Selling Point 3';
112 $selected_addon->info->description = '<p>Tell your users all about your add-on</p>';
113 }
114
115 fs_enqueue_local_style( 'fs_addons', '/admin/add-ons.css' );
116
117 $data = $args;
118
119 $has_free_plan = false;
120 $has_paid_plan = false;
121
122 // Load add-on pricing.
123 $has_pricing = false;
124 $has_features = false;
125 $plans = false;
126
127 $result = $this->_fs->get_api_plugin_scope()->get( $this->_fs->add_show_pending( "/addons/{$selected_addon->id}/pricing.json?type=visible" ) );
128
129 if ( ! isset( $result->error ) ) {
130 $plans = $result->plans;
131
132 if ( is_array( $plans ) ) {
133 for ( $i = 0, $len = count( $plans ); $i < $len; $i ++ ) {
134 $pricing = isset( $plans[ $i ]->pricing ) ? $plans[ $i ]->pricing : null;
135 $features = isset( $plans[ $i ]->features ) ? $plans[ $i ]->features : null;
136
137 $plans[ $i ] = new FS_Plugin_Plan( $plans[ $i ] );
138 $plan = $plans[ $i ];
139
140 if ( 'free' == $plans[ $i ]->name ||
141 ! is_array( $pricing ) ||
142 0 == count( $pricing )
143 ) {
144 $has_free_plan = true;
145 }
146
147 if ( is_array( $pricing ) && 0 < count( $pricing ) ) {
148 $filtered_pricing = array();
149
150 foreach ( $pricing as $prices ) {
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 }
169 }
170
171 if ( ! empty( $filtered_pricing ) ) {
172 $has_paid_plan = true;
173
174 $plan->pricing = $filtered_pricing;
175
176 $has_pricing = true;
177 }
178 }
179
180 if ( is_array( $features ) && 0 < count( $features ) ) {
181 $plan->features = $features;
182
183 $has_features = true;
184 }
185 }
186 }
187 }
188
189 $latest = null;
190
191 if ( ! $has_paid_plan && $selected_addon->is_wp_org_compliant ) {
192 $repo_data = FS_Plugin_Updater::_fetch_plugin_info_from_repository(
193 'plugin_information', (object) array(
194 'slug' => $selected_addon->slug,
195 'is_ssl' => is_ssl(),
196 'fields' => array(
197 'banners' => true,
198 'reviews' => true,
199 'downloaded' => false,
200 'active_installs' => true
201 )
202 ) );
203
204 if ( ! empty( $repo_data ) ) {
205 $data = $repo_data;
206 $data->wp_org_missing = false;
207 } else {
208 // Couldn't find plugin on .org.
209 $selected_addon->is_wp_org_compliant = false;
210
211 // Plugin is missing, not on Freemius nor WP.org.
212 $data->wp_org_missing = true;
213 }
214
215 $data->fs_missing = ( ! $has_free_plan || $data->wp_org_missing );
216 } else {
217 $data->has_purchased_license = false;
218 $data->wp_org_missing = false;
219
220 $fs_addon = null;
221 $current_addon_version = false;
222 if ( $this->_fs->is_addon_activated( $selected_addon->id ) ) {
223 $fs_addon = $this->_fs->get_addon_instance( $selected_addon->id );
224 $current_addon_version = $fs_addon->get_plugin_version();
225 } else if ( $this->_fs->is_addon_installed( $selected_addon->id ) ) {
226 $addon_plugin_data = get_plugin_data(
227 ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $selected_addon->id ) ),
228 false,
229 false
230 );
231
232 if ( ! empty( $addon_plugin_data ) ) {
233 $current_addon_version = $addon_plugin_data['Version'];
234 }
235 }
236
237 // Fetch latest version from Freemius.
238 $latest = $this->_fs->_fetch_latest_version(
239 $selected_addon->id,
240 true,
241 WP_FS__TIME_24_HOURS_IN_SEC,
242 $current_addon_version
243 );
244
245 if ( $has_paid_plan ) {
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 }
263
264 if ( is_object( $fs_addon ) ) {
265 $data->has_purchased_license = $fs_addon->has_active_valid_license();
266 } else {
267 $account_addons = $this->_fs->get_account_addons();
268 if ( ! empty( $account_addons ) && in_array( $selected_addon->id, $account_addons ) ) {
269 $data->has_purchased_license = true;
270 }
271 }
272 }
273
274 if ( $has_free_plan || $data->has_purchased_license ) {
275 $data->download_link = $this->_fs->_get_latest_download_local_url( $selected_addon->id );
276 }
277
278 $data->fs_missing = (
279 false === $latest &&
280 (
281 empty( $selected_addon->premium_releases_count ) ||
282 ! ( $selected_addon->premium_releases_count > 0 )
283 )
284 );
285
286 // Fetch as much as possible info from local files.
287 $plugin_local_data = $this->_fs->get_plugin_data();
288 $data->author = $plugin_local_data['Author'];
289
290 if ( ! empty( $selected_addon->info->banner_url ) ) {
291 $data->banners = array(
292 'low' => $selected_addon->info->banner_url,
293 );
294 }
295
296 if ( ! empty( $selected_addon->info->screenshots ) ) {
297 $view_vars = array(
298 'screenshots' => $selected_addon->info->screenshots,
299 'plugin' => $selected_addon,
300 );
301 $data->sections['screenshots'] = fs_get_template( '/plugin-info/screenshots.php', $view_vars );
302 }
303
304 if ( is_object( $latest ) ) {
305 $data->version = $latest->version;
306 $data->last_updated = $latest->created;
307 $data->requires = $latest->requires_platform_version;
308 $data->tested = $latest->tested_up_to_version;
309 } else if ( ! empty( $current_addon_version ) ) {
310 $data->version = $current_addon_version;
311 } else {
312 // Add dummy version.
313 $data->version = '1.0.0';
314
315 // Add message to developer to deploy the plugin through Freemius.
316 }
317 }
318
319 $data->name = $selected_addon->title;
320 $view_vars = array( 'plugin' => $selected_addon );
321
322 if ( is_object( $latest ) && isset( $latest->readme ) && is_object( $latest->readme ) ) {
323 $latest_version_readme_data = $latest->readme;
324 if ( isset( $latest_version_readme_data->sections ) ) {
325 $data->sections = (array) $latest_version_readme_data->sections;
326 } else {
327 $data->sections = array();
328 }
329 }
330
331 $data->sections['description'] = fs_get_template( '/plugin-info/description.php', $view_vars );
332
333 if ( $has_pricing ) {
334 // Add plans to data.
335 $data->plans = $plans;
336
337 if ( $has_features ) {
338 $view_vars = array(
339 'plans' => $plans,
340 'plugin' => $selected_addon,
341 );
342 $data->sections['features'] = fs_get_template( '/plugin-info/features.php', $view_vars );
343 }
344 }
345
346 $data->has_free_plan = $has_free_plan;
347 $data->has_paid_plan = $has_paid_plan;
348 $data->is_paid = $has_paid_plan;
349 $data->is_wp_org_compliant = $selected_addon->is_wp_org_compliant;
350 $data->premium_slug = $selected_addon->premium_slug;
351 $data->addon_id = $selected_addon->id;
352
353 if ( ! isset( $data->has_purchased_license ) ) {
354 $data->has_purchased_license = false;
355 }
356
357 return $data;
358 }
359
360 /**
361 * @author Vova Feldman (@svovaf)
362 * @since 1.1.7
363 *
364 * @param FS_Plugin_Plan $plan
365 *
366 * @return string
367 */
368 private function get_billing_cycle( FS_Plugin_Plan $plan ) {
369 $billing_cycle = null;
370
371 if ( 1 === count( $plan->pricing ) && 1 == $plan->pricing[0]->licenses ) {
372 $pricing = $plan->pricing[0];
373 if ( isset( $pricing->annual_price ) ) {
374 $billing_cycle = 'annual';
375 } else if ( isset( $pricing->monthly_price ) ) {
376 $billing_cycle = 'monthly';
377 } else if ( isset( $pricing->lifetime_price ) ) {
378 $billing_cycle = 'lifetime';
379 }
380 } else {
381 foreach ( $plan->pricing as $pricing ) {
382 if ( isset( $pricing->annual_price ) ) {
383 $billing_cycle = 'annual';
384 } else if ( isset( $pricing->monthly_price ) ) {
385 $billing_cycle = 'monthly';
386 } else if ( isset( $pricing->lifetime_price ) ) {
387 $billing_cycle = 'lifetime';
388 }
389
390 if ( ! is_null( $billing_cycle ) ) {
391 break;
392 }
393 }
394 }
395
396 return $billing_cycle;
397 }
398
399 /**
400 * @author Vova Feldman (@svovaf)
401 * @since 2.0.0
402 *
403 * @param FS_Plugin_Plan $plan
404 * @param FS_Pricing $pricing
405 *
406 * @return float|null|string
407 */
408 private function get_price_tag( FS_Plugin_Plan $plan, FS_Pricing $pricing ) {
409 $price_tag = '';
410 if ( isset( $pricing->annual_price ) ) {
411 $price_tag = $pricing->annual_price . ( $plan->is_block_features ? ' / year' : '' );
412 } else if ( isset( $pricing->monthly_price ) ) {
413 $price_tag = $pricing->monthly_price . ' / mo';
414 } else if ( isset( $pricing->lifetime_price ) ) {
415 $price_tag = $pricing->lifetime_price;
416 }
417
418 return '$' . $price_tag;
419 }
420
421 /**
422 * @author Leo Fajardo (@leorw)
423 * @since 2.3.0
424 *
425 * @param object $api
426 * @param FS_Plugin_Plan $plan
427 *
428 * @return string
429 */
430 private function get_actions_dropdown( $api, $plan = null ) {
431 $this->actions = isset( $this->actions ) ?
432 $this->actions :
433 $this->get_plugin_actions( $api );
434
435 $actions = $this->actions;
436
437 $checkout_cta = $this->get_checkout_cta( $api, $plan );
438 if ( ! empty( $checkout_cta ) ) {
439 /**
440 * If there's no license yet, make the checkout button the main CTA. Otherwise, make it the last item in
441 * the actions dropdown.
442 *
443 * @author Leo Fajardo (@leorw)
444 * @since 2.3.0
445 */
446 if ( ! $api->has_purchased_license ) {
447 array_unshift( $actions, $checkout_cta );
448 } else {
449 $actions[] = $checkout_cta;
450 }
451 }
452
453 if ( empty( $actions ) ) {
454 return '';
455 }
456
457 $total_actions = count( $actions );
458 if ( 1 === $total_actions ) {
459 return $actions[0];
460 }
461
462 ob_start();
463
464 ?>
465 <div class="fs-cta fs-dropdown">
466 <div class="button-group">
467 <?php
468 // This should NOT be sanitized as the $actions are HTML buttons already.
469 echo $actions[0] ?>
470 <div class="button button-primary fs-dropdown-arrow-button">
471 <span class="fs-dropdown-arrow"></span>
472 <ul class="fs-dropdown-list" style="display: none">
473 <?php for ( $i = 1; $i < $total_actions; $i ++ ) : ?>
474 <li><?php echo str_replace( 'button button-primary', '', $actions[ $i ] ) ?></li>
475 <?php endfor ?>
476 </ul>
477 </div>
478 </div>
479 </div>
480 <?php
481
482 return ob_get_clean();
483 }
484
485 /**
486 * @author Vova Feldman (@svovaf)
487 * @since 1.1.7
488 *
489 * @param object $api
490 * @param FS_Plugin_Plan $plan
491 *
492 * @return string
493 */
494 private function get_checkout_cta( $api, $plan = null ) {
495 if ( empty( $api->checkout_link ) ||
496 ! isset( $api->plans ) ||
497 ! is_array( $api->plans ) ||
498 0 == count( $api->plans )
499 ) {
500 return '';
501 }
502
503 if ( is_null( $plan ) ) {
504 foreach ( $api->plans as $p ) {
505 if ( ! empty( $p->pricing ) ) {
506 $plan = $p;
507 break;
508 }
509 }
510 }
511
512 $blog_id = fs_request_get( 'fs_blog_id' );
513 $has_valid_blog_id = is_numeric( $blog_id );
514
515 if ( $has_valid_blog_id ) {
516 switch_to_blog( $blog_id );
517 }
518
519 $addon_checkout_url = $this->_fs->addon_checkout_url(
520 $plan->plugin_id,
521 $plan->pricing[0]->id,
522 $this->get_billing_cycle( $plan ),
523 $plan->has_trial(),
524 ( $has_valid_blog_id ? false : null )
525 );
526
527 if ( $has_valid_blog_id ) {
528 restore_current_blog();
529 }
530
531 return '<a class="button button-primary fs-checkout-button right" href="' . $addon_checkout_url . '" target="_parent">' .
532 esc_html( ! $plan->has_trial() ?
533 (
534 $api->has_purchased_license ?
535 fs_text_inline( 'Purchase More', 'purchase-more', $api->slug ) :
536 fs_text_x_inline( 'Purchase', 'verb', 'purchase', $api->slug )
537 ) :
538 sprintf(
539 /* translators: %s: N-days trial */
540 fs_text_inline( 'Start my free %s', 'start-free-x', $api->slug ),
541 $this->get_trial_period( $plan )
542 )
543 ) .
544 '</a>';
545 }
546
547 /**
548 * @author Leo Fajardo (@leorw)
549 * @since 2.3.0
550 *
551 * @param object $api
552 *
553 * @return string[]
554 */
555 private function get_plugin_actions( $api ) {
556 $this->status = isset( $this->status ) ?
557 $this->status :
558 install_plugin_install_status( $api );
559
560 $is_update_available = ( 'update_available' === $this->status['status'] );
561
562 if ( $is_update_available && empty( $this->status['url'] ) ) {
563 return array();
564 }
565
566 $blog_id = fs_request_get( 'fs_blog_id' );
567
568 $active_plugins_directories_map = Freemius::get_active_plugins_directories_map( $blog_id );
569
570 $actions = array();
571
572 $is_addon_activated = $this->_fs->is_addon_activated( $api->slug );
573 $fs_addon = null;
574
575 $is_free_installed = null;
576 $is_premium_installed = null;
577
578 $has_installed_version = ( 'install' !== $this->status['status'] );
579
580 if ( ! $api->has_paid_plan ) {
581 /**
582 * Free-only add-on.
583 *
584 * @author Leo Fajardo (@leorw)
585 * @since 2.3.0
586 */
587 $is_free_installed = $has_installed_version;
588 $is_premium_installed = false;
589 } else if ( ! $api->has_free_plan ) {
590 /**
591 * Premium-only add-on.
592 *
593 * @author Leo Fajardo (@leorw)
594 * @since 2.3.0
595 */
596 $is_free_installed = false;
597 $is_premium_installed = $has_installed_version;
598 } else {
599 /**
600 * Freemium add-on.
601 *
602 * @author Leo Fajardo (@leorw)
603 * @since 2.3.0
604 */
605 if ( ! $has_installed_version ) {
606 $is_free_installed = false;
607 $is_premium_installed = false;
608 } else {
609 $fs_addon = $is_addon_activated ?
610 $this->_fs->get_addon_instance( $api->slug ) :
611 null;
612
613 if ( is_object( $fs_addon ) ) {
614 if ( $fs_addon->is_premium() ) {
615 $is_premium_installed = true;
616 } else {
617 $is_free_installed = true;
618 }
619 }
620
621 if ( is_null( $is_free_installed ) ) {
622 $is_free_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->slug}/{$api->slug}.php" ) );
623 if ( ! $is_free_installed ) {
624 /**
625 * Check if there's a plugin installed in a directory named `$api->slug`.
626 *
627 * @author Leo Fajardo (@leorw)
628 * @since 2.3.0
629 */
630 $installed_plugins = get_plugins( '/' . $api->slug );
631 $is_free_installed = ( ! empty( $installed_plugins ) );
632 }
633 }
634
635 if ( is_null( $is_premium_installed ) ) {
636 $is_premium_installed = file_exists( fs_normalize_path( WP_PLUGIN_DIR . "/{$api->premium_slug}/{$api->slug}.php" ) );
637 if ( ! $is_premium_installed ) {
638 /**
639 * Check if there's a plugin installed in a directory named `$api->premium_slug`.
640 *
641 * @author Leo Fajardo (@leorw)
642 * @since 2.3.0
643 */
644 $installed_plugins = get_plugins( '/' . $api->premium_slug );
645 $is_premium_installed = ( ! empty( $installed_plugins ) );
646 }
647 }
648 }
649
650 $has_installed_version = ( $is_free_installed || $is_premium_installed );
651 }
652
653 $this->status['is_free_installed'] = $is_free_installed;
654 $this->status['is_premium_installed'] = $is_premium_installed;
655
656 $can_install_free_version = false;
657 $can_install_free_version_update = false;
658 $can_download_free_version = false;
659 $can_activate_free_version = false;
660 $can_install_premium_version = false;
661 $can_install_premium_version_update = false;
662 $can_download_premium_version = false;
663 $can_activate_premium_version = false;
664
665 if ( ! $api->has_purchased_license ) {
666 if ( $api->has_free_plan ) {
667 if ( $has_installed_version ) {
668 if ( $is_update_available ) {
669 $can_install_free_version_update = true;
670 } else if ( ! $is_premium_installed && ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) {
671 $can_activate_free_version = true;
672 }
673 } else {
674 if (
675 $this->_fs->is_premium() ||
676 ! $this->_fs->is_org_repo_compliant() ||
677 $api->is_wp_org_compliant
678 ) {
679 $can_install_free_version = true;
680 } else {
681 $can_download_free_version = true;
682 }
683 }
684 }
685 } else {
686 if ( ! is_object( $fs_addon ) && $is_addon_activated ) {
687 $fs_addon = $this->_fs->get_addon_instance( $api->slug );
688 }
689
690 $can_download_premium_version = true;
691
692 if ( ! isset( $active_plugins_directories_map[ dirname( $this->status['file'] ) ] ) ) {
693 if ( $is_premium_installed ) {
694 $can_activate_premium_version = ( ! $is_addon_activated || ! $fs_addon->is_premium() );
695 } else if ( $is_free_installed ) {
696 $can_activate_free_version = ( ! $is_addon_activated );
697 }
698 }
699
700 if ( $this->_fs->is_premium() || ! $this->_fs->is_org_repo_compliant() ) {
701 if ( $is_update_available ) {
702 $can_install_premium_version_update = true;
703 } else if ( ! $is_premium_installed ) {
704 $can_install_premium_version = true;
705 }
706 }
707 }
708
709 if (
710 $can_install_premium_version ||
711 $can_install_premium_version_update
712 ) {
713 if ( is_numeric( $blog_id ) ) {
714 /**
715 * Replace the network status URL with a blog admin–based status URL if the `Add-Ons` page is loaded
716 * from a specific blog admin page (when `fs_blog_id` is valid) in order for plugin installation/update
717 * to work.
718 *
719 * @author Leo Fajardo (@leorw)
720 * @since 2.3.0
721 */
722 $this->status['url'] = self::get_blog_status_url( $blog_id, $this->status['url'], $this->status['status'] );
723 }
724
725 /**
726 * Add the `fs_allow_updater_and_dialog` param to the install/update URL so that the add-on can be
727 * installed/updated.
728 *
729 * @author Leo Fajardo (@leorw)
730 * @since 2.3.0
731 */
732 $this->status['url'] = str_replace( '?', '?fs_allow_updater_and_dialog=true&amp;', $this->status['url'] );
733 }
734
735 if ( $can_install_free_version_update || $can_install_premium_version_update ) {
736 $actions[] = $this->get_cta(
737 ( $can_install_free_version_update ?
738 fs_esc_html_inline( 'Install Free Version Update Now', 'install-free-version-update-now', $api->slug ) :
739 fs_esc_html_inline( 'Install Update Now', 'install-update-now', $api->slug ) ),
740 true,
741 false,
742 $this->status['url'],
743 '_parent'
744 );
745 } else if ( $can_install_free_version || $can_install_premium_version ) {
746 $actions[] = $this->get_cta(
747 ( $can_install_free_version ?
748 fs_esc_html_inline( 'Install Free Version Now', 'install-free-version-now', $api->slug ) :
749 fs_esc_html_inline( 'Install Now', 'install-now', $api->slug ) ),
750 true,
751 false,
752 $this->status['url'],
753 '_parent'
754 );
755 }
756
757 $download_latest_action = '';
758
759 if (
760 ! empty( $api->download_link ) &&
761 ( $can_download_free_version || $can_download_premium_version )
762 ) {
763 $download_latest_action = $this->get_cta(
764 ( $can_download_free_version ?
765 fs_esc_html_x_inline( 'Download Latest Free Version', 'as download latest version', 'download-latest-free-version', $api->slug ) :
766 fs_esc_html_x_inline( 'Download Latest', 'as download latest version', 'download-latest', $api->slug ) ),
767 true,
768 false,
769 esc_url( $api->download_link )
770 );
771 }
772
773 if ( ! $can_activate_free_version && ! $can_activate_premium_version ) {
774 if ( ! empty( $download_latest_action ) ) {
775 $actions[] = $download_latest_action;
776 }
777 } else {
778 $activate_action = sprintf(
779 '<a class="button button-primary edit" href="%s" title="%s" target="_parent">%s</a>',
780 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'] ),
781 fs_esc_attr_inline( 'Activate this add-on', 'activate-this-addon', $api->slug ),
782 $can_activate_free_version ?
783 fs_text_inline( 'Activate Free Version', 'activate-free', $api->slug ) :
784 fs_text_inline( 'Activate', 'activate', $api->slug )
785 );
786
787 if ( ! $can_download_premium_version && ! empty( $download_latest_action ) ) {
788 $actions[] = $download_latest_action;
789
790 $download_latest_action = '';
791 }
792
793 if ( $can_install_premium_version || $can_install_premium_version_update ) {
794 if ( $can_download_premium_version && ! empty( $download_latest_action ) ) {
795 $actions[] = $download_latest_action;
796
797 $download_latest_action = '';
798 }
799
800 $actions[] = $activate_action;
801 } else {
802 array_unshift( $actions, $activate_action );
803 }
804
805 if ( ! empty ($download_latest_action ) ) {
806 $actions[] = $download_latest_action;
807 }
808 }
809
810 return $actions;
811 }
812
813 /**
814 * Rebuilds the status URL based on the admin URL.
815 *
816 * @author Leo Fajardo (@leorw)
817 * @since 2.3.0
818 *
819 * @param int $blog_id
820 * @param string $network_status_url
821 * @param string $status
822 *
823 * @return string
824 */
825 private static function get_blog_status_url( $blog_id, $network_status_url, $status ) {
826 if ( ! in_array( $status, array( 'install', 'update_available' ) ) ) {
827 return $network_status_url;
828 }
829
830 $action = ( 'install' === $status ) ?
831 'install-plugin' :
832 'upgrade-plugin';
833
834 $query = parse_url( $network_status_url, PHP_URL_QUERY );
835 if ( empty( $query ) ) {
836 return $network_status_url;
837 }
838
839 parse_str( html_entity_decode( $query ), $url_params );
840 if ( empty( $url_params ) || ! isset( $url_params['plugin'] ) ) {
841 return $network_status_url;
842 }
843
844 $plugin = $url_params['plugin'];
845
846 return wp_nonce_url( get_admin_url( $blog_id,"update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}");
847 }
848
849 /**
850 * Helper method to get a CTA button HTML.
851 *
852 * @author Vova Feldman (@svovaf)
853 * @since 2.0.0
854 *
855 * @param string $label
856 * @param bool $is_primary
857 * @param bool $is_disabled
858 * @param string $href
859 * @param string $target
860 *
861 * @return string
862 */
863 private function get_cta(
864 $label,
865 $is_primary = true,
866 $is_disabled = false,
867 $href = '',
868 $target = '_blank'
869 ) {
870 $classes = array();
871
872 if ( ! $is_primary ) {
873 $classes[] = 'left';
874 } else {
875 $classes[] = 'button-primary';
876 $classes[] = 'right';
877 }
878
879 if ( $is_disabled ) {
880 $classes[] = 'disabled';
881 }
882
883 return sprintf(
884 '<a %s class="button %s">%s</a>',
885 empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"',
886 implode( ' ', $classes ),
887 $label
888 );
889 }
890
891 /**
892 * @author Vova Feldman (@svovaf)
893 * @since 1.1.7
894 *
895 * @param FS_Plugin_Plan $plan
896 *
897 * @return string
898 */
899 private function get_trial_period( $plan ) {
900 $trial_period = (int) $plan->trial_period;
901
902 switch ( $trial_period ) {
903 case 30:
904 return 'month';
905 case 60:
906 return '2 months';
907 default:
908 return "{$plan->trial_period} days";
909 }
910 }
911
912 /**
913 * Display plugin information in dialog box form.
914 *
915 * Based on core install_plugin_information() function.
916 *
917 * @author Vova Feldman (@svovaf)
918 * @since 1.0.6
919 */
920 function install_plugin_information() {
921 global $tab;
922
923 if ( empty( $_REQUEST['plugin'] ) ) {
924 return;
925 }
926
927 $args = array(
928 'slug' => wp_unslash( $_REQUEST['plugin'] ),
929 'is_ssl' => is_ssl(),
930 'fields' => array(
931 'banners' => true,
932 'reviews' => true,
933 'downloaded' => false,
934 'active_installs' => true
935 )
936 );
937
938 if ( is_array( $args ) ) {
939 $args = (object) $args;
940 }
941
942 if ( ! isset( $args->per_page ) ) {
943 $args->per_page = 24;
944 }
945
946 if ( ! isset( $args->locale ) ) {
947 $args->locale = get_locale();
948 }
949
950 $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args );
951
952 if ( is_wp_error( $api ) ) {
953 wp_die( $api );
954 }
955
956 $plugins_allowedtags = array(
957 'a' => array(
958 'href' => array(),
959 'title' => array(),
960 'target' => array(),
961 // Add image style for screenshots.
962 'class' => array()
963 ),
964 'style' => array(),
965 'abbr' => array( 'title' => array() ),
966 'acronym' => array( 'title' => array() ),
967 'code' => array(),
968 'pre' => array(),
969 'em' => array(),
970 'strong' => array(),
971 'div' => array( 'class' => array() ),
972 'span' => array( 'class' => array() ),
973 'p' => array(),
974 'ul' => array(),
975 'ol' => array(),
976 'li' => array( 'class' => array() ),
977 'i' => array( 'class' => array() ),
978 'h1' => array(),
979 'h2' => array(),
980 'h3' => array(),
981 'h4' => array(),
982 'h5' => array(),
983 'h6' => array(),
984 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ),
985 // 'table' => array(),
986 // 'td' => array(),
987 // 'tr' => array(),
988 // 'th' => array(),
989 // 'thead' => array(),
990 // 'tbody' => array(),
991 );
992
993 $plugins_section_titles = array(
994 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ),
995 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ),
996 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ),
997 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ),
998 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ),
999 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ),
1000 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ),
1001 );
1002
1003 // Sanitize HTML
1004 // foreach ( (array) $api->sections as $section_name => $content ) {
1005 // $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags );
1006 // }
1007
1008 foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) {
1009 if ( isset( $api->$key ) ) {
1010 $api->$key = wp_kses( $api->$key, $plugins_allowedtags );
1011 }
1012 }
1013
1014 // Add after $api->slug is ready.
1015 $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug );
1016
1017 $_tab = esc_attr( $tab );
1018
1019 $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English.
1020 if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) {
1021 $section_titles = array_keys( (array) $api->sections );
1022 $section = array_shift( $section_titles );
1023 }
1024
1025 iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) );
1026
1027 $_with_banner = '';
1028
1029 // var_dump($api->banners);
1030 if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) {
1031 $_with_banner = 'with-banner';
1032 $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low'];
1033 $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high'];
1034 ?>
1035 <style type="text/css">
1036 #plugin-information-title.with-banner
1037 {
1038 background-image: url( <?php echo esc_url( $low ); ?> );
1039 }
1040
1041 @media only screen and ( -webkit-min-device-pixel-ratio: 1.5 )
1042 {
1043 #plugin-information-title.with-banner
1044 {
1045 background-image: url( <?php echo esc_url( $high ); ?> );
1046 }
1047 }
1048 </style>
1049 <?php
1050 }
1051
1052 echo '<div id="plugin-information-scrollable">';
1053 echo "<div id='{$_tab}-title' class='{$_with_banner}'><div class='vignette'></div><h2>{$api->name}</h2></div>";
1054 echo "<div id='{$_tab}-tabs' class='{$_with_banner}'>\n";
1055
1056 foreach ( (array) $api->sections as $section_name => $content ) {
1057 if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) {
1058 continue;
1059 }
1060
1061 if ( isset( $plugins_section_titles[ $section_name ] ) ) {
1062 $title = $plugins_section_titles[ $section_name ];
1063 } else {
1064 $title = ucwords( str_replace( '_', ' ', $section_name ) );
1065 }
1066
1067 $class = ( $section_name === $section ) ? ' class="current"' : '';
1068 $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) );
1069 $href = esc_url( $href );
1070 $san_section = esc_attr( $section_name );
1071 echo "\t<a name='$san_section' href='$href' $class>" . esc_html( $title ) . "</a>\n";
1072 }
1073
1074 echo "</div>\n";
1075
1076 ?>
1077 <div id="<?php echo $_tab; ?>-content" class='<?php echo $_with_banner; ?>'>
1078 <div class="fyi">
1079 <?php if ( $api->is_paid ) : ?>
1080 <?php if ( isset( $api->plans ) ) : ?>
1081 <div class="plugin-information-pricing">
1082 <?php foreach ( $api->plans as $plan ) : ?>
1083 <?php
1084 if ( empty( $plan->pricing ) ) {
1085 continue;
1086 }
1087
1088 /**
1089 * @var FS_Plugin_Plan $plan
1090 */
1091 ?>
1092 <?php $first_pricing = $plan->pricing[0] ?>
1093 <?php $is_multi_cycle = $first_pricing->is_multi_cycle() ?>
1094 <div class="fs-plan<?php if ( ! $is_multi_cycle ) {
1095 echo ' fs-single-cycle';
1096 } ?>" data-plan-id="<?php echo $plan->id ?>">
1097 <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>
1098 <?php $has_annual = $first_pricing->has_annual() ?>
1099 <?php $has_monthly = $first_pricing->has_monthly() ?>
1100 <div class="nav-tab-wrapper">
1101 <?php $billing_cycles = array( 'monthly', 'annual', 'lifetime' ) ?>
1102 <?php $i = 0;
1103 foreach ( $billing_cycles as $cycle ) : ?>
1104 <?php $prop = "{$cycle}_price";
1105 if ( isset( $first_pricing->{$prop} ) ) : ?>
1106 <?php $is_featured = ( 'annual' === $cycle && $is_multi_cycle ) ?>
1107 <?php
1108 $prices = array();
1109 foreach ( $plan->pricing as $pricing ) {
1110 if ( isset( $pricing->{$prop} ) ) {
1111 $prices[] = array(
1112 'id' => $pricing->id,
1113 'licenses' => $pricing->licenses,
1114 'price' => $pricing->{$prop}
1115 );
1116 }
1117 }
1118 ?>
1119 <a class="nav-tab" data-billing-cycle="<?php echo $cycle ?>"
1120 data-pricing="<?php echo esc_attr( json_encode( $prices ) ) ?>">
1121 <?php if ( $is_featured ) : ?>
1122 <label>
1123 &#9733; <?php fs_esc_html_echo_x_inline( 'Best', 'e.g. the best product', 'best', $api->slug ) ?>
1124 &#9733;</label>
1125 <?php endif ?>
1126 <?php
1127 switch ( $cycle ) {
1128 case 'monthly':
1129 fs_esc_html_echo_x_inline( 'Monthly', 'as every month', 'monthly', $api->slug );
1130 break;
1131 case 'annual':
1132 fs_esc_html_echo_x_inline( 'Annual', 'as once a year', 'annual', $api->slug );
1133 break;
1134 case 'lifetime':
1135 fs_esc_html_echo_inline( 'Lifetime', 'lifetime', $api->slug );
1136 break;
1137 }
1138 ?>
1139 </a>
1140 <?php endif ?>
1141 <?php $i ++; endforeach ?>
1142 <?php wp_enqueue_script( 'jquery' ) ?>
1143 <script type="text/javascript">
1144 (function ($, undef) {
1145 var
1146 _formatBillingFrequency = function (cycle) {
1147 switch (cycle) {
1148 case 'monthly':
1149 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 ) ) ?>';
1150 case 'annual':
1151 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 ) ) ?>';
1152 case 'lifetime':
1153 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 ) ) ?>';
1154 }
1155 },
1156 _formatLicensesTitle = function (pricing) {
1157 switch (pricing.licenses) {
1158 case 1:
1159 return '<?php fs_esc_attr_echo_inline( 'Single Site License', 'license-single-site', $api->slug ) ?>';
1160 case null:
1161 return '<?php fs_esc_attr_echo_inline( 'Unlimited Licenses', 'license-unlimited', $api->slug ) ?>';
1162 default:
1163 return '<?php fs_esc_attr_echo_inline( 'Up to %s Sites', 'license-x-sites', $api->slug ) ?>'.replace('%s', pricing.licenses);
1164 }
1165 },
1166 _formatPrice = function (pricing, cycle, multipleLicenses) {
1167 if (undef === multipleLicenses)
1168 multipleLicenses = true;
1169
1170 var priceCycle;
1171 switch (cycle) {
1172 case 'monthly':
1173 priceCycle = ' / <?php fs_echo_x_inline( 'mo', 'as monthly period', 'mo', $api->slug ) ?>';
1174 break;
1175 case 'lifetime':
1176 priceCycle = '';
1177 break;
1178 case 'annual':
1179 default:
1180 priceCycle = ' / <?php fs_echo_x_inline( 'year', 'as annual period', 'year', $api->slug ) ?>';
1181 break;
1182 }
1183
1184 if (!multipleLicenses && 1 == pricing.licenses) {
1185 return '$' + pricing.price + priceCycle;
1186 }
1187
1188 return _formatLicensesTitle(pricing) + ' - <var class="fs-price">$' + pricing.price + priceCycle + '</var>';
1189 },
1190 _checkoutUrl = function (plan, pricing, cycle) {
1191 return '<?php echo esc_url_raw( remove_query_arg( 'billing_cycle', add_query_arg( array( 'plugin_id' => $plan->plugin_id ), $api->checkout_link ) ) ) ?>' +
1192 '&plan_id=' + plan +
1193 '&pricing_id=' + pricing +
1194 '&billing_cycle=' + cycle<?php if ( $plan->has_trial() ) {
1195 echo " + '&trial=true'";
1196 }?>;
1197 },
1198 _updateCtaUrl = function (plan, pricing, cycle) {
1199 $('.plugin-information-pricing .fs-checkout-button, #plugin-information-footer .fs-checkout-button').attr('href', _checkoutUrl(plan, pricing, cycle));
1200 };
1201
1202 $(document).ready(function () {
1203 var $plan = $('.plugin-information-pricing .fs-plan[data-plan-id=<?php echo $plan->id ?>]');
1204 $plan.find('input[type=radio]').live('click', function () {
1205 _updateCtaUrl(
1206 $plan.attr('data-plan-id'),
1207 $(this).val(),
1208 $plan.find('.nav-tab-active').attr('data-billing-cycle')
1209 );
1210
1211 $plan.find('.fs-trial-terms .fs-price').html(
1212 $(this).parents('label').find('.fs-price').html()
1213 );
1214 });
1215
1216 $plan.find('.nav-tab').click(function () {
1217 if ($(this).hasClass('nav-tab-active'))
1218 return;
1219
1220 var $this = $(this),
1221 billingCycle = $this.attr('data-billing-cycle'),
1222 pricing = JSON.parse($this.attr('data-pricing')),
1223 $pricesList = $this.parents('.fs-plan').find('.fs-pricing-body .fs-licenses'),
1224 html = '';
1225
1226 // Un-select previously selected tab.
1227 $plan.find('.nav-tab').removeClass('nav-tab-active');
1228
1229 // Select current tab.
1230 $this.addClass('nav-tab-active');
1231
1232 // Render licenses prices.
1233 if (1 == pricing.length) {
1234 html = '<li><label><?php echo fs_esc_attr_x_inline( 'Price', 'noun', 'price', $api->slug ) ?>: ' + _formatPrice(pricing[0], billingCycle, false) + '</label></li>';
1235 } else {
1236 for (var i = 0; i < pricing.length; i++) {
1237 html += '<li><label><input name="pricing-<?php echo $plan->id ?>" type="radio" value="' + pricing[i].id + '">' + _formatPrice(pricing[i], billingCycle) + '</label></li>';
1238 }
1239 }
1240 $pricesList.html(html);
1241
1242 if (1 < pricing.length) {
1243 // Select first license option.
1244 $pricesList.find('li:first input').click();
1245 }
1246 else {
1247 _updateCtaUrl(
1248 $plan.attr('data-plan-id'),
1249 pricing[0].id,
1250 billingCycle
1251 );
1252 }
1253
1254 // Update billing frequency.
1255 $plan.find('.fs-billing-frequency').html(_formatBillingFrequency(billingCycle));
1256
1257 if ('annual' === billingCycle) {
1258 $plan.find('.fs-annual-discount').show();
1259 } else {
1260 $plan.find('.fs-annual-discount').hide();
1261 }
1262 });
1263
1264 <?php if ( $has_annual ) : ?>
1265 // Select annual by default.
1266 $plan.find('.nav-tab[data-billing-cycle=annual]').click();
1267 <?php else : ?>
1268 // Select first tab.
1269 $plan.find('.nav-tab:first').click();
1270 <?php endif ?>
1271 });
1272 }(jQuery));
1273 </script>
1274 </div>
1275 <div class="fs-pricing-body">
1276 <span class="fs-billing-frequency"></span>
1277 <?php $annual_discount = ( $has_annual && $has_monthly ) ? $plan->pricing[0]->annual_discount_percentage() : 0 ?>
1278 <?php if ( $annual_discount > 0 ) : ?>
1279 <span
1280 class="fs-annual-discount"><?php printf(
1281 /* translators: %s: Discount (e.g. discount of $5 or 10%) */
1282 fs_esc_html_inline( 'Save %s', 'save-x', $api->slug ), $annual_discount . '%' ) ?></span>
1283 <?php endif ?>
1284 <ul class="fs-licenses">
1285 </ul>
1286 <?php echo $this->get_actions_dropdown( $api, $plan ) ?>
1287 <div style="clear:both"></div>
1288 <?php if ( $plan->has_trial() ) : ?>
1289 <?php $trial_period = $this->get_trial_period( $plan ) ?>
1290 <ul class="fs-trial-terms">
1291 <li>
1292 <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 ) ) ?>
1293 </li>
1294 <li>
1295 <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>' ) ?>
1296 </li>
1297 </ul>
1298 <?php endif ?>
1299 </div>
1300 </div>
1301 </div>
1302 <?php endforeach ?>
1303 <?php endif ?>
1304 <?php endif ?>
1305 <div>
1306 <h3><?php fs_echo_inline( 'Details', 'details', $api->slug ) ?></h3>
1307 <ul>
1308 <?php if ( ! empty( $api->version ) ) { ?>
1309 <li>
1310 <strong><?php fs_esc_html_echo_x_inline( 'Version', 'product version', 'version', $api->slug ); ?>
1311 :</strong> <?php echo $api->version; ?></li>
1312 <?php
1313 }
1314 if ( ! empty( $api->author ) ) {
1315 ?>
1316 <li>
1317 <strong><?php fs_echo_x_inline( 'Author', 'as the plugin author', 'author', $api->slug ); ?>
1318 :</strong> <?php echo links_add_target( $api->author, '_blank' ); ?>
1319 </li>
1320 <?php
1321 }
1322 if ( ! empty( $api->last_updated ) ) {
1323 ?>
1324 <li><strong><?php fs_echo_inline( 'Last Updated', 'last-updated', $api->slug ); ?>
1325 :</strong> <span
1326 title="<?php echo $api->last_updated; ?>">
1327 <?php echo esc_html( sprintf(
1328 /* translators: %s: time period (e.g. "2 hours" ago) */
1329 fs_text_x_inline( '%s ago', 'x-ago', $api->slug ),
1330 human_time_diff( strtotime( $api->last_updated ) )
1331 ) ) ?>
1332 </span></li>
1333 <?php
1334 }
1335 if ( ! empty( $api->requires ) ) {
1336 ?>
1337 <li>
1338 <strong><?php fs_esc_html_echo_inline( 'Requires WordPress Version', 'requires-wordpress-version', $api->slug ) ?>
1339 :</strong> <?php echo esc_html( sprintf( fs_text_inline( '%s or higher', 'x-or-higher', $api->slug ), $api->requires ) ) ?>
1340 </li>
1341 <?php
1342 }
1343 if ( ! empty( $api->tested ) ) {
1344 ?>
1345 <li>
1346 <strong><?php fs_esc_html_echo_inline( 'Compatible up to', 'compatible-up-to', $api->slug ); ?>
1347 :</strong> <?php echo $api->tested; ?>
1348 </li>
1349 <?php
1350 }
1351 if ( ! empty( $api->downloaded ) ) {
1352 ?>
1353 <li>
1354 <strong><?php fs_esc_html_echo_inline( 'Downloaded', 'downloaded', $api->slug ) ?>
1355 :</strong> <?php echo esc_html( sprintf(
1356 ( ( 1 == $api->downloaded ) ?
1357 /* translators: %s: 1 or One (Number of times downloaded) */
1358 fs_text_inline( '%s time', 'x-time', $api->slug ) :
1359 /* translators: %s: Number of times downloaded */
1360 fs_text_inline( '%s times', 'x-times', $api->slug )
1361 ),
1362 number_format_i18n( $api->downloaded )
1363 ) ); ?>
1364 </li>
1365 <?php
1366 }
1367 if ( ! empty( $api->slug ) && true == $api->is_wp_org_compliant ) {
1368 ?>
1369 <li><a target="_blank"
1370 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 ) ?>
1371 &#187;</a>
1372 </li>
1373 <?php
1374 }
1375 if ( ! empty( $api->homepage ) ) {
1376 ?>
1377 <li><a target="_blank"
1378 href="<?php echo esc_url( $api->homepage ); ?>"><?php fs_esc_html_echo_inline( 'Plugin Homepage', 'plugin-homepage', $api->slug ) ?>
1379 &#187;</a>
1380 </li>
1381 <?php
1382 }
1383 if ( ! empty( $api->donate_link ) && empty( $api->contributors ) ) {
1384 ?>
1385 <li><a target="_blank"
1386 href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_esc_html_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1387 &#187;</a>
1388 </li>
1389 <?php } ?>
1390 </ul>
1391 </div>
1392 <?php if ( ! empty( $api->rating ) ) { ?>
1393 <h3><?php fs_echo_inline( 'Average Rating', 'average-rating', $api->slug ); ?></h3>
1394 <?php wp_star_rating( array(
1395 'rating' => $api->rating,
1396 'type' => 'percent',
1397 'number' => $api->num_ratings
1398 ) ); ?>
1399 <small>(<?php echo esc_html( sprintf(
1400 fs_text_inline( 'based on %s', 'based-on-x', $api->slug ),
1401 sprintf(
1402 ( ( 1 == $api->num_ratings ) ?
1403 /* translators: %s: 1 or One */
1404 fs_text_inline( '%s rating', 'x-rating', $api->slug ) :
1405 /* translators: %s: Number larger than 1 */
1406 fs_text_inline( '%s ratings', 'x-ratings', $api->slug )
1407 ),
1408 number_format_i18n( $api->num_ratings )
1409 ) ) ) ?>)
1410 </small>
1411 <?php
1412 }
1413
1414 if ( ! empty( $api->ratings ) && array_sum( (array) $api->ratings ) > 0 ) {
1415 foreach ( $api->ratings as $key => $ratecount ) {
1416 // Avoid div-by-zero.
1417 $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0;
1418 $stars_label = sprintf(
1419 ( ( 1 == $key ) ?
1420 /* translators: %s: 1 or One */
1421 fs_text_inline( '%s star', 'x-star', $api->slug ) :
1422 /* translators: %s: Number larger than 1 */
1423 fs_text_inline( '%s stars', 'x-stars', $api->slug )
1424 ),
1425 number_format_i18n( $key )
1426 );
1427 ?>
1428 <div class="counter-container">
1429 <span class="counter-label"><a
1430 href="https://wordpress.org/support/view/plugin-reviews/<?php echo $api->slug; ?>?filter=<?php echo $key; ?>"
1431 target="_blank"
1432 title="<?php echo esc_attr( sprintf(
1433 /* translators: %s: # of stars (e.g. 5 stars) */
1434 fs_text_inline( 'Click to see reviews that provided a rating of %s', 'click-to-reviews', $api->slug ),
1435 $stars_label
1436 ) ) ?>"><?php echo $stars_label ?></a></span>
1437 <span class="counter-back">
1438 <span class="counter-bar" style="width: <?php echo absint(92 * $_rating); ?>px;"></span>
1439 </span>
1440 <span class="counter-count"><?php echo number_format_i18n( $ratecount ); ?></span>
1441 </div>
1442 <?php
1443 }
1444 }
1445 if ( ! empty( $api->contributors ) ) {
1446 ?>
1447 <h3><?php fs_echo_inline( 'Contributors', 'contributors', $api->slug ); ?></h3>
1448 <ul class="contributors">
1449 <?php
1450 foreach ( (array) $api->contributors as $contrib_username => $contrib_profile ) {
1451 if ( empty( $contrib_username ) && empty( $contrib_profile ) ) {
1452 continue;
1453 }
1454 if ( empty( $contrib_username ) ) {
1455 $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile );
1456 }
1457 $contrib_username = sanitize_user( $contrib_username );
1458 if ( empty( $contrib_profile ) ) {
1459 echo "<li><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</li>";
1460 } else {
1461 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>";
1462 }
1463 }
1464 ?>
1465 </ul>
1466 <?php if ( ! empty( $api->donate_link ) ) { ?>
1467 <a target="_blank"
1468 href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1469 &#187;</a>
1470 <?php } ?>
1471 <?php } ?>
1472 </div>
1473 <div id="section-holder" class="wrap">
1474 <?php
1475 if ( ! empty( $api->tested ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->tested ) ), $api->tested, '>' ) ) {
1476 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>';
1477 } else if ( ! empty( $api->requires ) && version_compare( substr( $GLOBALS['wp_version'], 0, strlen( $api->requires ) ), $api->requires, '<' ) ) {
1478 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>';
1479 }
1480
1481 foreach ( (array) $api->sections as $section_name => $content ) {
1482 $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' );
1483 $content = links_add_target( $content, '_blank' );
1484
1485 $san_section = esc_attr( $section_name );
1486
1487 $display = ( $section_name === $section ) ? 'block' : 'none';
1488
1489 if ( 'description' === $section_name &&
1490 ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) ||
1491 ( ! $api->is_wp_org_compliant && $api->fs_missing ) )
1492 ) {
1493 $missing_notice = array(
1494 'type' => 'error',
1495 'id' => md5( microtime() ),
1496 'message' => $api->is_paid ?
1497 fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) :
1498 fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ),
1499 );
1500 fs_require_template( 'admin-notice.php', $missing_notice );
1501 }
1502 echo "\t<div id='section-{$san_section}' class='section' style='display: {$display};'>\n";
1503 echo $content;
1504 echo "\t</div>\n";
1505 }
1506 echo "</div>\n";
1507 echo "</div>\n";
1508 echo "</div>\n"; // #plugin-information-scrollable
1509 echo "<div id='$tab-footer'>\n";
1510
1511 if (
1512 ! empty( $api->download_link ) &&
1513 ! empty( $this->status ) &&
1514 in_array( $this->status['status'], array( 'newer_installed', 'latest_installed' ) )
1515 ) {
1516 if ( 'newer_installed' === $this->status['status'] ) {
1517 echo $this->get_cta(
1518 ( $this->status['is_premium_installed'] ?
1519 esc_html( sprintf( fs_text_inline( 'Newer Version (%s) Installed', 'newer-installed', $api->slug ), $this->status['version'] ) ) :
1520 esc_html( sprintf( fs_text_inline( 'Newer Free Version (%s) Installed', 'newer-free-installed', $api->slug ), $this->status['version'] ) ) ),
1521 false,
1522 true
1523 );
1524 } else {
1525 echo $this->get_cta(
1526 ( $this->status['is_premium_installed'] ?
1527 fs_esc_html_inline( 'Latest Version Installed', 'latest-installed', $api->slug ) :
1528 fs_esc_html_inline( 'Latest Free Version Installed', 'latest-free-installed', $api->slug ) ),
1529 false,
1530 true
1531 );
1532 }
1533 }
1534
1535 echo $this->get_actions_dropdown( $api, null );
1536
1537 echo "</div>\n";
1538 ?>
1539 <script type="text/javascript">
1540 ( function( $, undef ) {
1541 var $dropdowns = $( '.fs-dropdown' );
1542
1543 $( '#plugin-information' )
1544 .click( function( evt ) {
1545 var $target = $( evt.target );
1546
1547 if (
1548 $target.hasClass( 'fs-dropdown-arrow-button' ) ||
1549 ( 0 !== $target.parents( '.fs-dropdown-arrow-button' ).length )
1550 ) {
1551 var $dropdown = $target.parents( '.fs-dropdown' ),
1552 isActive = $dropdown.hasClass( 'active' );
1553
1554 if ( ! isActive ) {
1555 /**
1556 * Close the other dropdown if it's active.
1557 *
1558 * @author Leo Fajardo (@leorw)
1559 * @since 2.3.0
1560 */
1561 $( '.fs-dropdown.active' ).each( function() {
1562 toggleDropdown( $( this ), false );
1563 } );
1564 }
1565
1566 /**
1567 * Toggle the current dropdown.
1568 *
1569 * @author Leo Fajardo (@leorw)
1570 * @since 2.3.0
1571 */
1572 toggleDropdown( $dropdown, ! isActive );
1573
1574 return true;
1575 }
1576
1577 /**
1578 * Close all dropdowns.
1579 *
1580 * @author Leo Fajardo (@leorw)
1581 * @since 2.3.0
1582 */
1583 toggleDropdown( $( this ).find( '.fs-dropdown' ), false );
1584 });
1585
1586 if ( 0 !== $dropdowns.length ) {
1587 /**
1588 * Add the `up` class so that the bottom dropdown's content will be shown above its buttons.
1589 *
1590 * @author Leo Fajardo (@leorw)
1591 * @since 2.3.0
1592 */
1593 $( '#plugin-information-footer' ).find( '.fs-dropdown' ).addClass( 'up' );
1594 }
1595
1596 /**
1597 * Returns the default state of the dropdown arrow button and hides the dropdown list.
1598 *
1599 * @author Leo Fajardo (@leorw)
1600 * @since 2.3.0
1601 *
1602 * @param {Object} [$dropdown]
1603 * @param {Boolean} [state]
1604 */
1605 function toggleDropdown( $dropdown, state ) {
1606 if ( undef === $dropdown ) {
1607 var $activeDropdown = $dropdowns.find( '.active' );
1608 if ( 0 !== $activeDropdown.length ) {
1609 $dropdown = $activeDropdown;
1610 }
1611 }
1612
1613 if ( undef === $dropdown ) {
1614 return;
1615 }
1616
1617 if ( undef === state ) {
1618 state = false;
1619 }
1620
1621 $dropdown.toggleClass( 'active', state );
1622 $dropdown.find( '.fs-dropdown-list' ).toggle( state );
1623 $dropdown.find( '.fs-dropdown-arrow-button' ).toggleClass( 'active', state );
1624 }
1625 } )( jQuery );
1626 </script>
1627 <?php
1628 iframe_footer();
1629 exit;
1630 }
1631 }