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