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