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