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