PluginProbe ʕ •ᴥ•ʔ
Code Manager / 1.0.35
Code Manager v1.0.35
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 2 years ago debug 2 years ago entities 2 years ago managers 2 years ago sdk 2 years ago supplements 2 years ago class-freemius-abstract.php 2 years ago class-freemius.php 2 years ago class-fs-admin-notices.php 2 years ago class-fs-api.php 2 years ago class-fs-garbage-collector.php 2 years ago class-fs-lock.php 2 years ago class-fs-logger.php 2 years ago class-fs-options.php 2 years ago class-fs-plugin-updater.php 2 years ago class-fs-security.php 2 years ago class-fs-storage.php 2 years ago class-fs-user-lock.php 2 years ago fs-core-functions.php 2 years ago fs-essential-functions.php 2 years ago fs-html-escaping-functions.php 2 years ago fs-plugin-info-dialog.php 2 years ago index.php 2 years ago l10n.php 2 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