PluginProbe ʕ •ᴥ•ʔ
Photo Gallery by FooGallery : Responsive Image Gallery, Masonry Gallery & Carousel / trunk
Photo Gallery by FooGallery : Responsive Image Gallery, Masonry Gallery & Carousel vtrunk
trunk 1.10.3 2.0.24 2.1.34 2.2.44 2.3.3 2.4.32 3.0.6 3.1.11 3.1.12 3.1.13 3.1.20 3.1.25 3.1.26 3.1.26.1 3.1.26.2
foogallery / freemius / includes / fs-plugin-info-dialog.php
foogallery / freemius / includes Last commit date
customizer 7 months ago debug 2 years ago entities 7 months ago managers 7 months ago sdk 2 years ago supplements 3 years ago .DS_Store 7 months ago class-freemius-abstract.php 3 years ago class-freemius.php 7 months ago class-fs-admin-notices.php 3 years ago class-fs-api.php 7 months ago class-fs-garbage-collector.php 7 months ago class-fs-hook-snapshot.php 7 months ago class-fs-lock.php 3 years ago class-fs-logger.php 7 months ago class-fs-options.php 8 years ago class-fs-plugin-updater.php 7 months ago class-fs-security.php 7 months ago class-fs-storage.php 7 months ago class-fs-user-lock.php 3 years ago fs-core-functions.php 7 months ago fs-essential-functions.php 2 years ago fs-html-escaping-functions.php 7 months ago fs-plugin-info-dialog.php 7 months ago index.php 3 years ago l10n.php 3 years ago
fs-plugin-info-dialog.php
1692 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 FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION,
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 $url_params = fs_parse_url_params( $network_status_url, true );
842
843 if ( empty( $url_params ) || ! isset( $url_params['plugin'] ) ) {
844 return $network_status_url;
845 }
846
847 $plugin = $url_params['plugin'];
848
849 return wp_nonce_url( get_admin_url( $blog_id,"update.php?action={$action}&plugin={$plugin}"), "{$action}_{$plugin}");
850 }
851
852 /**
853 * Helper method to get a CTA button HTML.
854 *
855 * @author Vova Feldman (@svovaf)
856 * @since 2.0.0
857 *
858 * @param string $label
859 * @param bool $is_primary
860 * @param bool $is_disabled
861 * @param string $href
862 * @param string $target
863 *
864 * @return string
865 */
866 private function get_cta(
867 $label,
868 $is_primary = true,
869 $is_disabled = false,
870 $href = '',
871 $target = '_blank'
872 ) {
873 $classes = array();
874
875 if ( ! $is_primary ) {
876 $classes[] = 'left';
877 } else {
878 $classes[] = 'button-primary';
879 $classes[] = 'right';
880 }
881
882 if ( $is_disabled ) {
883 $classes[] = 'disabled';
884 }
885
886 $rel = ( '_blank' === $target ) ? ' rel="noopener noreferrer"' : '';
887
888 return sprintf(
889 '<a %s class="button %s">%s</a>',
890 empty( $href ) ? '' : 'href="' . $href . '" target="' . $target . '"' . $rel,
891 implode( ' ', $classes ),
892 $label
893 );
894 }
895
896 /**
897 * @author Vova Feldman (@svovaf)
898 * @since 1.1.7
899 *
900 * @param FS_Plugin_Plan $plan
901 *
902 * @return string
903 */
904 private function get_trial_period( $plan ) {
905 $trial_period = (int) $plan->trial_period;
906
907 switch ( $trial_period ) {
908 case 30:
909 return 'month';
910 case 60:
911 return '2 months';
912 default:
913 return "{$plan->trial_period} days";
914 }
915 }
916
917 /**
918 * Display plugin information in dialog box form.
919 *
920 * Based on core install_plugin_information() function.
921 *
922 * @author Vova Feldman (@svovaf)
923 * @since 1.0.6
924 */
925 function install_plugin_information() {
926 global $tab;
927
928 if ( empty( $_REQUEST['plugin'] ) ) {
929 return;
930 }
931
932 $args = array(
933 'slug' => wp_unslash( $_REQUEST['plugin'] ),
934 'is_ssl' => is_ssl(),
935 'fields' => array(
936 'banners' => true,
937 'reviews' => true,
938 'downloaded' => false,
939 'active_installs' => true
940 )
941 );
942
943 if ( is_array( $args ) ) {
944 $args = (object) $args;
945 }
946
947 if ( ! isset( $args->per_page ) ) {
948 $args->per_page = 24;
949 }
950
951 if ( ! isset( $args->locale ) ) {
952 $args->locale = get_locale();
953 }
954
955 $api = apply_filters( 'fs_plugins_api', false, 'plugin_information', $args );
956
957 if ( is_wp_error( $api ) ) {
958 wp_die( $api );
959 }
960
961 $plugins_allowedtags = array(
962 'a' => array(
963 'href' => array(),
964 'title' => array(),
965 'target' => array(),
966 // Add image style for screenshots.
967 'class' => array()
968 ),
969 'style' => array(),
970 'abbr' => array( 'title' => array() ),
971 'acronym' => array( 'title' => array() ),
972 'code' => array(),
973 'pre' => array(),
974 'em' => array(),
975 'strong' => array(),
976 'div' => array( 'class' => array() ),
977 'span' => array( 'class' => array() ),
978 'p' => array(),
979 'ul' => array(),
980 'ol' => array(),
981 'li' => array( 'class' => array() ),
982 'i' => array( 'class' => array() ),
983 'h1' => array(),
984 'h2' => array(),
985 'h3' => array(),
986 'h4' => array(),
987 'h5' => array(),
988 'h6' => array(),
989 'img' => array( 'src' => array(), 'class' => array(), 'alt' => array() ),
990 // 'table' => array(),
991 // 'td' => array(),
992 // 'tr' => array(),
993 // 'th' => array(),
994 // 'thead' => array(),
995 // 'tbody' => array(),
996 );
997
998 $plugins_section_titles = array(
999 'description' => fs_text_x_inline( 'Description', 'Plugin installer section title', 'description', $api->slug ),
1000 'installation' => fs_text_x_inline( 'Installation', 'Plugin installer section title', 'installation', $api->slug ),
1001 'faq' => fs_text_x_inline( 'FAQ', 'Plugin installer section title', 'faq', $api->slug ),
1002 'screenshots' => fs_text_inline( 'Screenshots', 'screenshots', $api->slug ),
1003 'changelog' => fs_text_x_inline( 'Changelog', 'Plugin installer section title', 'changelog', $api->slug ),
1004 'reviews' => fs_text_x_inline( 'Reviews', 'Plugin installer section title', 'reviews', $api->slug ),
1005 'other_notes' => fs_text_x_inline( 'Other Notes', 'Plugin installer section title', 'other-notes', $api->slug ),
1006 );
1007
1008 // Sanitize HTML
1009 // foreach ( (array) $api->sections as $section_name => $content ) {
1010 // $api->sections[$section_name] = wp_kses( $content, $plugins_allowedtags );
1011 // }
1012
1013 foreach ( array( 'version', 'author', 'requires', 'tested', 'homepage', 'downloaded', 'slug' ) as $key ) {
1014 if ( isset( $api->$key ) ) {
1015 $api->$key = wp_kses( $api->$key, $plugins_allowedtags );
1016 }
1017 }
1018
1019 // Add after $api->slug is ready.
1020 $plugins_section_titles['features'] = fs_text_x_inline( 'Features & Pricing', 'Plugin installer section title', 'features-and-pricing', $api->slug );
1021
1022 $_tab = esc_attr( $tab );
1023
1024 $section = isset( $_REQUEST['section'] ) ? wp_unslash( $_REQUEST['section'] ) : 'description'; // Default to the Description tab, Do not translate, API returns English.
1025 if ( empty( $section ) || ! isset( $api->sections[ $section ] ) ) {
1026 $section_titles = array_keys( (array) $api->sections );
1027 $section = array_shift( $section_titles );
1028 }
1029
1030 iframe_header( fs_text_inline( 'Plugin Install', 'plugin-install', $api->slug ) );
1031
1032 $_with_banner = '';
1033
1034 // var_dump($api->banners);
1035 if ( ! empty( $api->banners ) && ( ! empty( $api->banners['low'] ) || ! empty( $api->banners['high'] ) ) ) {
1036 $_with_banner = 'with-banner';
1037 $low = empty( $api->banners['low'] ) ? $api->banners['high'] : $api->banners['low'];
1038 $high = empty( $api->banners['high'] ) ? $api->banners['low'] : $api->banners['high'];
1039 ?>
1040 <style type="text/css">
1041 #plugin-information-title.with-banner
1042 {
1043 background-image: url( <?php echo esc_url( $low ); ?> );
1044 }
1045
1046 @media only screen and ( -webkit-min-device-pixel-ratio: 1.5 )
1047 {
1048 #plugin-information-title.with-banner
1049 {
1050 background-image: url( <?php echo esc_url( $high ); ?> );
1051 }
1052 }
1053 </style>
1054 <?php
1055 }
1056
1057 echo '<div id="plugin-information-scrollable">';
1058 echo "<div id='{$_tab}-title' class='{$_with_banner}'><div class='vignette'></div><h2>{$api->name}</h2></div>";
1059 echo "<div id='{$_tab}-tabs' class='{$_with_banner}'>\n";
1060
1061 foreach ( (array) $api->sections as $section_name => $content ) {
1062 if ( 'reviews' === $section_name && ( empty( $api->ratings ) || 0 === array_sum( (array) $api->ratings ) ) ) {
1063 continue;
1064 }
1065
1066 if ( isset( $plugins_section_titles[ $section_name ] ) ) {
1067 $title = $plugins_section_titles[ $section_name ];
1068 } else {
1069 $title = ucwords( str_replace( '_', ' ', $section_name ) );
1070 }
1071
1072 $class = ( $section_name === $section ) ? ' class="current"' : '';
1073 $href = add_query_arg( array( 'tab' => $tab, 'section' => $section_name ) );
1074 $href = esc_url( $href );
1075 $san_section = esc_attr( $section_name );
1076 echo "\t<a name='$san_section' href='$href' $class>" . esc_html( $title ) . "</a>\n";
1077 }
1078
1079 echo "</div>\n";
1080
1081 ?>
1082 <div id="<?php echo $_tab; ?>-content" class='<?php echo $_with_banner; ?>'>
1083 <div class="fyi">
1084 <?php if ( $api->is_paid ) : ?>
1085 <?php if ( isset( $api->plans ) ) : ?>
1086 <div class="plugin-information-pricing">
1087 <?php foreach ( $api->plans as $plan ) : ?>
1088 <?php
1089 if ( empty( $plan->pricing ) ) {
1090 continue;
1091 }
1092
1093 /**
1094 * @var FS_Plugin_Plan $plan
1095 */
1096 ?>
1097 <?php $first_pricing = $plan->pricing[0] ?>
1098 <?php $is_multi_cycle = $first_pricing->is_multi_cycle() ?>
1099 <div class="fs-plan<?php if ( ! $is_multi_cycle ) {
1100 echo ' fs-single-cycle';
1101 } ?>" data-plan-id="<?php echo $plan->id ?>">
1102 <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>
1103 <?php $has_annual = $first_pricing->has_annual() ?>
1104 <?php $has_monthly = $first_pricing->has_monthly() ?>
1105 <div class="nav-tab-wrapper">
1106 <?php $billing_cycles = array( 'monthly', 'annual', 'lifetime' ) ?>
1107 <?php $i = 0;
1108 foreach ( $billing_cycles as $cycle ) : ?>
1109 <?php $prop = "{$cycle}_price";
1110 if ( isset( $first_pricing->{$prop} ) ) : ?>
1111 <?php $is_featured = ( 'annual' === $cycle && $is_multi_cycle ) ?>
1112 <?php
1113 $prices = array();
1114 foreach ( $plan->pricing as $pricing ) {
1115 if ( isset( $pricing->{$prop} ) ) {
1116 $prices[] = array(
1117 'id' => $pricing->id,
1118 'licenses' => $pricing->licenses,
1119 'price' => $pricing->{$prop}
1120 );
1121 }
1122 }
1123 ?>
1124 <a class="nav-tab" data-billing-cycle="<?php echo $cycle ?>"
1125 data-pricing="<?php echo esc_attr( json_encode( $prices ) ) ?>">
1126 <?php if ( $is_featured ) : ?>
1127 <label>
1128 &#9733; <?php fs_esc_html_echo_x_inline( 'Best', 'e.g. the best product', 'best', $api->slug ) ?>
1129 &#9733;</label>
1130 <?php endif ?>
1131 <?php
1132 switch ( $cycle ) {
1133 case 'monthly':
1134 fs_esc_html_echo_x_inline( 'Monthly', 'as every month', 'monthly', $api->slug );
1135 break;
1136 case 'annual':
1137 fs_esc_html_echo_x_inline( 'Annual', 'as once a year', 'annual', $api->slug );
1138 break;
1139 case 'lifetime':
1140 fs_esc_html_echo_inline( 'Lifetime', 'lifetime', $api->slug );
1141 break;
1142 }
1143 ?>
1144 </a>
1145 <?php endif ?>
1146 <?php $i ++; endforeach ?>
1147 <?php wp_enqueue_script( 'jquery' ) ?>
1148 <script type="text/javascript">
1149 (function ($, undef) {
1150 var
1151 _formatBillingFrequency = function (cycle) {
1152 switch (cycle) {
1153 case 'monthly':
1154 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 ) ) ?>';
1155 case 'annual':
1156 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 ) ) ?>';
1157 case 'lifetime':
1158 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 ) ) ?>';
1159 }
1160 },
1161 _formatLicensesTitle = function (pricing) {
1162 switch (pricing.licenses) {
1163 case 1:
1164 return '<?php fs_esc_attr_echo_inline( 'Single Site License', 'license-single-site', $api->slug ) ?>';
1165 case null:
1166 return '<?php fs_esc_attr_echo_inline( 'Unlimited Licenses', 'license-unlimited', $api->slug ) ?>';
1167 default:
1168 return '<?php fs_esc_attr_echo_inline( 'Up to %s Sites', 'license-x-sites', $api->slug ) ?>'.replace('%s', pricing.licenses);
1169 }
1170 },
1171 _formatPrice = function (pricing, cycle, multipleLicenses) {
1172 if (undef === multipleLicenses)
1173 multipleLicenses = true;
1174
1175 var priceCycle;
1176 switch (cycle) {
1177 case 'monthly':
1178 priceCycle = ' / <?php fs_echo_x_inline( 'mo', 'as monthly period', 'mo', $api->slug ) ?>';
1179 break;
1180 case 'lifetime':
1181 priceCycle = '';
1182 break;
1183 case 'annual':
1184 default:
1185 priceCycle = ' / <?php fs_echo_x_inline( 'year', 'as annual period', 'year', $api->slug ) ?>';
1186 break;
1187 }
1188
1189 if (!multipleLicenses && 1 == pricing.licenses) {
1190 return '$' + pricing.price + priceCycle;
1191 }
1192
1193 return _formatLicensesTitle(pricing) + ' - <var class="fs-price">$' + pricing.price + priceCycle + '</var>';
1194 },
1195 _checkoutUrl = function (plan, pricing, cycle) {
1196 return '<?php echo esc_url_raw( remove_query_arg( 'billing_cycle', add_query_arg( array( 'plugin_id' => $plan->plugin_id ), $api->checkout_link ) ) ) ?>' +
1197 '&plan_id=' + plan +
1198 '&pricing_id=' + pricing +
1199 '&billing_cycle=' + cycle<?php if ( $plan->has_trial() ) {
1200 echo " + '&trial=true'";
1201 }?>;
1202 },
1203 _updateCtaUrl = function (plan, pricing, cycle) {
1204 $('.plugin-information-pricing .fs-checkout-button, #plugin-information-footer .fs-checkout-button').attr('href', _checkoutUrl(plan, pricing, cycle));
1205 };
1206
1207 $(document).ready(function () {
1208 var $plan = $('.plugin-information-pricing .fs-plan[data-plan-id=<?php echo $plan->id ?>]');
1209 $plan.find('input[type=radio]').on('click', function () {
1210 _updateCtaUrl(
1211 $plan.attr('data-plan-id'),
1212 $(this).val(),
1213 $plan.find('.nav-tab-active').attr('data-billing-cycle')
1214 );
1215
1216 $plan.find('.fs-trial-terms .fs-price').html(
1217 $(this).parents('label').find('.fs-price').html()
1218 );
1219 });
1220
1221 $plan.find('.nav-tab').click(function () {
1222 if ($(this).hasClass('nav-tab-active'))
1223 return;
1224
1225 var $this = $(this),
1226 billingCycle = $this.attr('data-billing-cycle'),
1227 pricing = JSON.parse($this.attr('data-pricing')),
1228 $pricesList = $this.parents('.fs-plan').find('.fs-pricing-body .fs-licenses'),
1229 html = '';
1230
1231 // Un-select previously selected tab.
1232 $plan.find('.nav-tab').removeClass('nav-tab-active');
1233
1234 // Select current tab.
1235 $this.addClass('nav-tab-active');
1236
1237 // Render licenses prices.
1238 if (1 == pricing.length) {
1239 html = '<li><label><?php echo fs_esc_attr_x_inline( 'Price', 'noun', 'price', $api->slug ) ?>: ' + _formatPrice(pricing[0], billingCycle, false) + '</label></li>';
1240 } else {
1241 for (var i = 0; i < pricing.length; i++) {
1242 html += '<li><label><input name="pricing-<?php echo $plan->id ?>" type="radio" value="' + pricing[i].id + '">' + _formatPrice(pricing[i], billingCycle) + '</label></li>';
1243 }
1244 }
1245 $pricesList.html(html);
1246
1247 if (1 < pricing.length) {
1248 // Select first license option.
1249 $pricesList.find('li:first input').click();
1250 }
1251 else {
1252 _updateCtaUrl(
1253 $plan.attr('data-plan-id'),
1254 pricing[0].id,
1255 billingCycle
1256 );
1257 }
1258
1259 // Update billing frequency.
1260 $plan.find('.fs-billing-frequency').html(_formatBillingFrequency(billingCycle));
1261
1262 if ('annual' === billingCycle) {
1263 $plan.find('.fs-annual-discount').show();
1264 } else {
1265 $plan.find('.fs-annual-discount').hide();
1266 }
1267 });
1268
1269 <?php if ( $has_annual ) : ?>
1270 // Select annual by default.
1271 $plan.find('.nav-tab[data-billing-cycle=annual]').click();
1272 <?php else : ?>
1273 // Select first tab.
1274 $plan.find('.nav-tab:first').click();
1275 <?php endif ?>
1276 });
1277 }(jQuery));
1278 </script>
1279 </div>
1280 <div class="fs-pricing-body">
1281 <span class="fs-billing-frequency"></span>
1282 <?php $annual_discount = ( $has_annual && $has_monthly ) ? $plan->pricing[0]->annual_discount_percentage() : 0 ?>
1283 <?php if ( $annual_discount > 0 ) : ?>
1284 <span
1285 class="fs-annual-discount"><?php printf(
1286 /* translators: %s: Discount (e.g. discount of $5 or 10%) */
1287 fs_esc_html_inline( 'Save %s', 'save-x', $api->slug ), $annual_discount . '%' ) ?></span>
1288 <?php endif ?>
1289 <ul class="fs-licenses">
1290 </ul>
1291 <?php echo $this->get_actions_dropdown( $api, $plan ) ?>
1292 <div style="clear:both"></div>
1293 <?php if ( $plan->has_trial() ) : ?>
1294 <?php $trial_period = $this->get_trial_period( $plan ) ?>
1295 <ul class="fs-trial-terms">
1296 <li>
1297 <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 ) ) ?>
1298 </li>
1299 <li>
1300 <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>' ) ?>
1301 </li>
1302 </ul>
1303 <?php endif ?>
1304 </div>
1305 </div>
1306 <?php endforeach ?>
1307 </div>
1308 <?php endif ?>
1309 <?php endif ?>
1310 <div>
1311 <h3><?php fs_echo_inline( 'Details', 'details', $api->slug ) ?></h3>
1312 <ul>
1313 <?php if ( ! empty( $api->version ) ) { ?>
1314 <li>
1315 <strong><?php fs_esc_html_echo_x_inline( 'Version', 'product version', 'version', $api->slug ); ?>
1316 :</strong> <?php echo $api->version; ?></li>
1317 <?php
1318 }
1319 if ( ! empty( $api->author ) ) {
1320 ?>
1321 <li>
1322 <strong><?php fs_echo_x_inline( 'Author', 'as the plugin author', 'author', $api->slug ); ?>
1323 :</strong> <?php echo links_add_target( $api->author, '_blank' ); ?>
1324 </li>
1325 <?php
1326 }
1327 if ( ! empty( $api->last_updated ) ) {
1328 ?>
1329 <li><strong><?php fs_echo_inline( 'Last Updated', 'last-updated', $api->slug ); ?>
1330 :</strong> <span
1331 title="<?php echo $api->last_updated; ?>">
1332 <?php echo esc_html( sprintf(
1333 /* translators: %s: time period (e.g. "2 hours" ago) */
1334 fs_text_x_inline( '%s ago', 'x-ago', $api->slug ),
1335 human_time_diff( strtotime( $api->last_updated ) )
1336 ) ) ?>
1337 </span></li>
1338 <?php
1339 }
1340 if ( ! empty( $api->requires ) ) {
1341 ?>
1342 <li>
1343 <strong><?php fs_esc_html_echo_inline( 'Requires WordPress Version', 'requires-wordpress-version', $api->slug ) ?>
1344 :</strong> <?php echo esc_html( sprintf(
1345 /* translators: %s: Version number. */
1346 fs_text_inline( '%s or higher', 'x-or-higher', $api->slug ), $api->requires )
1347 ) ?>
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->requires_php ) ) {
1360 ?>
1361 <li>
1362 <strong><?php fs_esc_html_echo_inline( 'Requires PHP Version', 'requires-php-version', $api->slug ); ?>:</strong>
1363 <?php
1364 echo esc_html( sprintf(
1365 /* translators: %s: Version number. */
1366 fs_text_inline( '%s or higher', 'x-or-higher', $api->slug ), $api->requires_php )
1367 );
1368 ?>
1369 </li>
1370 <?php
1371 }
1372 if ( ! empty( $api->downloaded ) ) {
1373 ?>
1374 <li>
1375 <strong><?php fs_esc_html_echo_inline( 'Downloaded', 'downloaded', $api->slug ) ?>
1376 :</strong> <?php echo esc_html( sprintf(
1377 ( ( 1 == $api->downloaded ) ?
1378 /* translators: %s: 1 or One (Number of times downloaded) */
1379 fs_text_inline( '%s time', 'x-time', $api->slug ) :
1380 /* translators: %s: Number of times downloaded */
1381 fs_text_inline( '%s times', 'x-times', $api->slug )
1382 ),
1383 number_format_i18n( $api->downloaded )
1384 ) ); ?>
1385 </li>
1386 <?php
1387 }
1388 if ( ! empty( $api->slug ) && true == $api->is_wp_org_compliant ) {
1389 ?>
1390 <li><a target="_blank"
1391 rel="noopener noreferrer"
1392 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 ) ?>
1393 &#187;</a>
1394 </li>
1395 <?php
1396 }
1397 if ( ! empty( $api->homepage ) ) {
1398 ?>
1399 <li><a target="_blank"
1400 rel="noopener noreferrer"
1401 href="<?php echo esc_url( $api->homepage ); ?>"><?php fs_esc_html_echo_inline( 'Plugin Homepage', 'plugin-homepage', $api->slug ) ?>
1402 &#187;</a>
1403 </li>
1404 <?php
1405 }
1406 if ( ! empty( $api->donate_link ) && empty( $api->contributors ) ) {
1407 ?>
1408 <li><a target="_blank"
1409 rel="noopener noreferrer"
1410 href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_esc_html_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1411 &#187;</a>
1412 </li>
1413 <?php } ?>
1414 </ul>
1415 </div>
1416 <?php if ( ! empty( $api->rating ) ) { ?>
1417 <h3><?php fs_echo_inline( 'Average Rating', 'average-rating', $api->slug ); ?></h3>
1418 <?php wp_star_rating( array(
1419 'rating' => $api->rating,
1420 'type' => 'percent',
1421 'number' => $api->num_ratings
1422 ) ); ?>
1423 <small>(<?php echo esc_html( sprintf(
1424 fs_text_inline( 'based on %s', 'based-on-x', $api->slug ),
1425 sprintf(
1426 ( ( 1 == $api->num_ratings ) ?
1427 /* translators: %s: 1 or One */
1428 fs_text_inline( '%s rating', 'x-rating', $api->slug ) :
1429 /* translators: %s: Number larger than 1 */
1430 fs_text_inline( '%s ratings', 'x-ratings', $api->slug )
1431 ),
1432 number_format_i18n( $api->num_ratings )
1433 ) ) ) ?>)
1434 </small>
1435 <?php
1436 }
1437
1438 if ( ! empty( $api->ratings ) && array_sum( (array) $api->ratings ) > 0 ) {
1439 foreach ( $api->ratings as $key => $ratecount ) {
1440 // Avoid div-by-zero.
1441 $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0;
1442 $stars_label = sprintf(
1443 ( ( 1 == $key ) ?
1444 /* translators: %s: 1 or One */
1445 fs_text_inline( '%s star', 'x-star', $api->slug ) :
1446 /* translators: %s: Number larger than 1 */
1447 fs_text_inline( '%s stars', 'x-stars', $api->slug )
1448 ),
1449 number_format_i18n( $key )
1450 );
1451 ?>
1452 <div class="counter-container">
1453 <span class="counter-label"><a
1454 href="https://wordpress.org/support/view/plugin-reviews/<?php echo $api->slug; ?>?filter=<?php echo $key; ?>"
1455 target="_blank"
1456 rel="noopener noreferrer"
1457 title="<?php echo esc_attr( sprintf(
1458 /* translators: %s: # of stars (e.g. 5 stars) */
1459 fs_text_inline( 'Click to see reviews that provided a rating of %s', 'click-to-reviews', $api->slug ),
1460 $stars_label
1461 ) ) ?>"><?php echo $stars_label ?></a></span>
1462 <span class="counter-back">
1463 <span class="counter-bar" style="width: <?php echo absint(92 * $_rating); ?>px;"></span>
1464 </span>
1465 <span class="counter-count"><?php echo number_format_i18n( $ratecount ); ?></span>
1466 </div>
1467 <?php
1468 }
1469 }
1470 if ( ! empty( $api->contributors ) ) {
1471 ?>
1472 <h3><?php fs_echo_inline( 'Contributors', 'contributors', $api->slug ); ?></h3>
1473 <ul class="contributors">
1474 <?php
1475 foreach ( (array) $api->contributors as $contrib_username => $contrib_profile ) {
1476 if ( empty( $contrib_username ) && empty( $contrib_profile ) ) {
1477 continue;
1478 }
1479 if ( empty( $contrib_username ) ) {
1480 $contrib_username = preg_replace( '/^.+\/(.+)\/?$/', '\1', $contrib_profile );
1481 }
1482 $contrib_username = sanitize_user( $contrib_username );
1483 if ( empty( $contrib_profile ) ) {
1484 echo "<li><img src='https://wordpress.org/grav-redirect.php?user={$contrib_username}&amp;s=36' width='18' height='18' />{$contrib_username}</li>";
1485 } else {
1486 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>";
1487 }
1488 }
1489 ?>
1490 </ul>
1491 <?php if ( ! empty( $api->donate_link ) ) { ?>
1492 <a target="_blank"
1493 rel="noopener noreferrer"
1494 href="<?php echo esc_url( $api->donate_link ); ?>"><?php fs_echo_inline( 'Donate to this plugin', 'donate-to-plugin', $api->slug ) ?>
1495 &#187;</a>
1496 <?php } ?>
1497 <?php } ?>
1498 </div>
1499 <div id="section-holder" class="wrap">
1500 <?php
1501 $requires_php = isset( $api->requires_php ) ? $api->requires_php : null;
1502 $requires_wp = isset( $api->requires ) ? $api->requires : null;
1503
1504 $compatible_php = empty( $requires_php ) || version_compare( PHP_VERSION, $requires_php, '>=' );
1505
1506 // Strip off any -alpha, -RC, -beta, -src suffixes.
1507 list( $wp_version ) = explode( '-', $GLOBALS['wp_version'] );
1508
1509 $compatible_wp = empty( $requires_wp ) || version_compare( $wp_version, $requires_wp, '>=' );
1510 $tested_wp = ( empty( $api->tested ) || version_compare( $wp_version, $api->tested, '<=' ) );
1511
1512 if ( ! $compatible_php ) {
1513 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 );
1514
1515 if ( current_user_can( 'update_php' ) ) {
1516 $wp_get_update_php_url = function_exists( 'wp_get_update_php_url' ) ?
1517 wp_get_update_php_url() :
1518 'https://wordpress.org/support/update-php/';
1519
1520 printf(
1521 /* translators: %s: URL to Update PHP page. */
1522 ' ' . fs_text_inline( '<a href="%s" target="_blank">Click here to learn more about updating PHP</a>.', 'php-update-learn-more-link', $api->slug ),
1523 esc_url( $wp_get_update_php_url )
1524 );
1525
1526 if ( function_exists( 'wp_update_php_annotation' ) ) {
1527 wp_update_php_annotation( '</p><p><em>', '</em>' );
1528 }
1529 } else {
1530 echo '</p>';
1531 }
1532 echo '</div>';
1533 }
1534
1535 if ( ! $tested_wp ) {
1536 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>';
1537 } else if ( ! $compatible_wp ) {
1538 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>';
1539 }
1540
1541 foreach ( (array) $api->sections as $section_name => $content ) {
1542 $content = links_add_base_url( $content, 'https://wordpress.org/plugins/' . $api->slug . '/' );
1543 $content = links_add_target( $content, '_blank' );
1544
1545 $san_section = esc_attr( $section_name );
1546
1547 $display = ( $section_name === $section ) ? 'block' : 'none';
1548
1549 if ( 'description' === $section_name &&
1550 ( ( $api->is_wp_org_compliant && $api->wp_org_missing ) ||
1551 ( ! $api->is_wp_org_compliant && $api->fs_missing ) )
1552 ) {
1553 $missing_notice = array(
1554 'type' => 'error',
1555 'id' => md5( microtime() ),
1556 'message' => $api->is_paid ?
1557 fs_text_inline( 'Paid add-on must be deployed to Freemius.', 'paid-addon-not-deployed', $api->slug ) :
1558 fs_text_inline( 'Add-on must be deployed to WordPress.org or Freemius.', 'free-addon-not-deployed', $api->slug ),
1559 );
1560 fs_require_template( 'admin-notice.php', $missing_notice );
1561 }
1562 echo "\t<div id='section-{$san_section}' class='section' style='display: {$display};'>\n";
1563 echo $content;
1564 echo "\t</div>\n";
1565 }
1566 echo "</div>\n";
1567 echo "</div>\n";
1568 echo "</div>\n"; // #plugin-information-scrollable
1569 echo "<div id='$tab-footer'>\n";
1570
1571 if (
1572 ! empty( $api->download_link ) &&
1573 ! empty( $this->status ) &&
1574 in_array( $this->status['status'], array( 'newer_installed', 'latest_installed' ) )
1575 ) {
1576 if ( 'newer_installed' === $this->status['status'] ) {
1577 echo $this->get_cta(
1578 ( $this->status['is_premium_installed'] ?
1579 esc_html( sprintf( fs_text_inline( 'Newer Version (%s) Installed', 'newer-installed', $api->slug ), $this->status['version'] ) ) :
1580 esc_html( sprintf( fs_text_inline( 'Newer Free Version (%s) Installed', 'newer-free-installed', $api->slug ), $this->status['version'] ) ) ),
1581 false,
1582 true
1583 );
1584 } else {
1585 echo $this->get_cta(
1586 ( $this->status['is_premium_installed'] ?
1587 fs_esc_html_inline( 'Latest Version Installed', 'latest-installed', $api->slug ) :
1588 fs_esc_html_inline( 'Latest Free Version Installed', 'latest-free-installed', $api->slug ) ),
1589 false,
1590 true
1591 );
1592 }
1593 }
1594
1595 echo $this->get_actions_dropdown( $api, null );
1596
1597 echo "</div>\n";
1598 ?>
1599 <script type="text/javascript">
1600 ( function( $, undef ) {
1601 var $dropdowns = $( '.fs-dropdown' );
1602
1603 $( '#plugin-information' )
1604 .click( function( evt ) {
1605 var $target = $( evt.target );
1606
1607 if (
1608 $target.hasClass( 'fs-dropdown-arrow-button' ) ||
1609 ( 0 !== $target.parents( '.fs-dropdown-arrow-button' ).length )
1610 ) {
1611 var $dropdown = $target.parents( '.fs-dropdown' ),
1612 isActive = $dropdown.hasClass( 'active' );
1613
1614 if ( ! isActive ) {
1615 /**
1616 * Close the other dropdown if it's active.
1617 *
1618 * @author Leo Fajardo (@leorw)
1619 * @since 2.3.0
1620 */
1621 $( '.fs-dropdown.active' ).each( function() {
1622 toggleDropdown( $( this ), false );
1623 } );
1624 }
1625
1626 /**
1627 * Toggle the current dropdown.
1628 *
1629 * @author Leo Fajardo (@leorw)
1630 * @since 2.3.0
1631 */
1632 toggleDropdown( $dropdown, ! isActive );
1633
1634 return true;
1635 }
1636
1637 /**
1638 * Close all dropdowns.
1639 *
1640 * @author Leo Fajardo (@leorw)
1641 * @since 2.3.0
1642 */
1643 toggleDropdown( $( this ).find( '.fs-dropdown' ), false );
1644 });
1645
1646 if ( 0 !== $dropdowns.length ) {
1647 /**
1648 * Add the `up` class so that the bottom dropdown's content will be shown above its buttons.
1649 *
1650 * @author Leo Fajardo (@leorw)
1651 * @since 2.3.0
1652 */
1653 $( '#plugin-information-footer' ).find( '.fs-dropdown' ).addClass( 'up' );
1654 }
1655
1656 /**
1657 * Returns the default state of the dropdown arrow button and hides the dropdown list.
1658 *
1659 * @author Leo Fajardo (@leorw)
1660 * @since 2.3.0
1661 *
1662 * @param {Object} [$dropdown]
1663 * @param {Boolean} [state]
1664 */
1665 function toggleDropdown( $dropdown, state ) {
1666 if ( undef === $dropdown ) {
1667 var $activeDropdown = $dropdowns.find( '.active' );
1668 if ( 0 !== $activeDropdown.length ) {
1669 $dropdown = $activeDropdown;
1670 }
1671 }
1672
1673 if ( undef === $dropdown ) {
1674 return;
1675 }
1676
1677 if ( undef === state ) {
1678 state = false;
1679 }
1680
1681 $dropdown.toggleClass( 'active', state );
1682 $dropdown.find( '.fs-dropdown-list' ).toggle( state );
1683 $dropdown.find( '.fs-dropdown-arrow-button' ).toggleClass( 'active', state );
1684 }
1685 } )( jQuery );
1686 </script>
1687 <?php
1688 iframe_footer();
1689 exit;
1690 }
1691 }
1692