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