PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / controllers / FrmAddonsController.php

FrmAddonsController.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More trunk, at classes/controllers/FrmAddonsController.php

1,748 lines 45.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmAddonsController {
7
8 /**
9 * @var string
10 */
11 const SCRIPT_HANDLE = 'frm-addons-page';
12
13 /**
14 * @var array
15 */
16 private static $categories = array();
17
18 /**
19 * @var string
20 */
21 private static $request_addon_url;
22
23 /**
24 * @var string
25 */
26 protected static $plugin;
27
28 /**
29 * @since 6.15
30 *
31 * @return void
32 */
33 public static function load_admin_hooks() {
34 add_action( 'admin_menu', self::class . '::menu', 100 );
35 add_filter( 'pre_set_site_transient_update_plugins', self::class . '::check_update' );
36
37 if ( ! FrmAppHelper::is_admin_page( 'formidable-addons' ) ) {
38 return;
39 }
40
41 self::$request_addon_url = 'https://connect.formidableforms.com/add-on-request/';
42
43 add_action( 'admin_enqueue_scripts', self::class . '::enqueue_assets', 15 );
44 add_filter( 'frm_show_footer_links', '__return_false' );
45 }
46
47 /**
48 * Enqueues the Add-Ons page scripts and styles.
49 *
50 * @since 6.15
51 *
52 * @return void
53 */
54 public static function enqueue_assets() {
55 $plugin_url = FrmAppHelper::plugin_url();
56 $version = FrmAppHelper::plugin_version();
57 $js_dependencies = array(
58 'wp-i18n',
59 // This prevents a console error "wp.hooks is undefined" in WP versions older than 5.7.
60 'wp-hooks',
61 'formidable_dom',
62 );
63
64 // Enqueue styles that needed.
65 wp_enqueue_style( 'formidable-admin' );
66 wp_enqueue_style( 'formidable-grids' );
67
68 // Register and enqueue Add-Ons page style.
69 wp_register_style( self::SCRIPT_HANDLE, $plugin_url . '/css/admin/addons-page.css', array(), $version );
70 wp_enqueue_style( self::SCRIPT_HANDLE );
71
72 // Register and enqueue Add-Ons page script.
73 wp_register_script( self::SCRIPT_HANDLE, $plugin_url . '/js/addons-page.js', $js_dependencies, $version, true );
74 wp_localize_script( self::SCRIPT_HANDLE, 'frmAddonsVars', self::get_js_variables() );
75 wp_enqueue_script( self::SCRIPT_HANDLE );
76 wp_set_script_translations( self::SCRIPT_HANDLE, 'formidable' );
77
78 FrmAppHelper::dequeue_extra_global_scripts();
79 }
80
81 /**
82 * Get the Add-Ons page JS variables as an array.
83 *
84 * @since 6.15
85 *
86 * @return array
87 */
88 private static function get_js_variables() {
89 return array(
90 'proIsIncluded' => FrmAppHelper::pro_is_included(),
91 'addonRequestURL' => self::$request_addon_url,
92 );
93 }
94
95 /**
96 * @return void
97 */
98 public static function menu() {
99 if ( ! current_user_can( 'activate_plugins' ) ) {
100 return;
101 }
102
103 $label = __( 'Add-Ons', 'formidable' );
104 $label = '<span style="color:#3FCA89">' . esc_html( $label ) . '</span>';
105
106 add_submenu_page( 'formidable', 'Formidable | ' . __( 'Add-Ons', 'formidable' ), $label, 'frm_view_forms', 'formidable-addons', 'FrmAddonsController::list_addons' );
107
108 // Remove default created subpage, make the page with highest priority as default.
109 remove_submenu_page( 'formidable', 'formidable' );
110
111 if ( ! FrmAppHelper::pro_is_installed() ) {
112 $cta_text = FrmSalesApi::get_best_sale_value( 'menu_cta_text' );
113
114 if ( ! $cta_text ) {
115 $cta_text = __( 'Upgrade', 'formidable' );
116 }
117
118 add_submenu_page(
119 'formidable',
120 'Formidable | ' . __( 'Upgrade', 'formidable' ),
121 '<span class="frm-upgrade-submenu">' . esc_html( $cta_text ) . '</span>',
122 'frm_view_forms',
123 'formidable-pro-upgrade',
124 function () {
125 // This function doesn't need to do anything.
126 // The redirect is handled earlier to avoid issues with the headers being sent.
127 }
128 );
129 } elseif ( 'formidable-pro-upgrade' === FrmAppHelper::get_param( 'page' ) ) {
130 wp_safe_redirect( admin_url( 'admin.php?page=formidable' ) );
131 exit;
132 }//end if
133 }
134
135 /**
136 * @return void
137 */
138 public static function list_addons() {
139 FrmAppHelper::include_svg();
140
141 $view_path = FrmAppHelper::plugin_path() . '/classes/views/addons/';
142 $installed_addons = apply_filters( 'frm_installed_addons', array() );
143 $addons = self::get_api_addons();
144 $errors = array();
145 $license_type = '';
146 $request_addon_url = self::$request_addon_url;
147
148 if ( isset( $addons['error'] ) ) {
149 $api = new FrmFormApi();
150 $errors = $api->get_error_from_response( $addons );
151 $license_type = $addons['error']['type'] ?? '';
152 unset( $addons['error'] );
153 }
154
155 $pro = array(
156 'pro' => array(
157 'title' => 'Formidable Forms Pro',
158 'slug' => 'formidable-pro',
159 'released' => '2011-02-05',
160 'docs' => 'knowledgebase/what-is-the-difference-between-the-lite-free-and-pro-version/',
161 'docs_label' => __( 'Why Upgrade', 'formidable' ),
162 'categories' => array( 'basic', 'plus', 'business', 'elite' ),
163 'excerpt' => 'Create calculators, surveys, smart forms, and data-driven applications. Build directories, real estate listings, job boards, and much more.',
164 ),
165 );
166 $addons = $pro + $addons;
167 self::prepare_addons( $addons );
168
169 $pricing = FrmAppHelper::admin_upgrade_link( 'addons' );
170
171 self::organize_and_get_categories();
172 $categories = self::$categories;
173
174 include $view_path . 'index.php';
175 }
176
177 /**
178 * Organize and set categories.
179 *
180 * @since 6.15
181 *
182 * @return void
183 */
184 protected static function organize_and_get_categories() {
185 unset( self::$categories['strategy11'] );
186 ksort( self::$categories );
187
188 $bottom_categories = array();
189 $plans = FrmFormsHelper::get_license_types(
190 array(
191 'include_all' => false,
192 'case_lower' => true,
193 )
194 );
195
196 // Extract the elements to move
197 foreach ( $plans as $plan ) {
198 if ( ! isset( self::$categories[ $plan ] ) ) {
199 continue;
200 }
201
202 $bottom_categories[ $plan ] = self::$categories[ $plan ];
203 unset( self::$categories[ $plan ] );
204 }
205
206 $special_categories = array();
207
208 if ( 'elite' !== self::license_type() ) {
209 $special_categories['available-addons'] = array(
210 'name' => __( 'Available', 'formidable' ),
211 // To be assigned via JavaScript.
212 'count' => 0,
213 );
214 }
215
216 $special_categories['active-addons'] = array(
217 'name' => __( 'Active', 'formidable' ),
218 // To be assigned via JavaScript.
219 'count' => 0,
220 );
221
222 $special_categories['all-items'] = array(
223 'name' => __( 'All Add-Ons', 'formidable' ),
224 // To be assigned via JavaScript.
225 'count' => 0,
226 );
227
228 self::$categories = array_merge(
229 $special_categories,
230 self::$categories,
231 $bottom_categories
232 );
233 }
234
235 /**
236 * Organize and set categories.
237 *
238 * @since 6.15
239 *
240 * @param array $addon The addon array that will be modified by reference.
241 *
242 * @return void
243 */
244 protected static function set_categories( &$addon ) {
245 if ( ! isset( $addon['categories'] ) ) {
246 return;
247 }
248
249 $addon['category-slugs'] = array();
250
251 foreach ( $addon['categories'] as $category ) {
252 $category = FrmFormsHelper::convert_legacy_package_names( $category );
253 $category_slug = sanitize_title( $category );
254
255 // Add the slug to the new array.
256 $addon['category-slugs'][] = $category_slug;
257
258 if ( ! isset( self::$categories[ $category_slug ] ) ) {
259 self::$categories[ $category_slug ] = array(
260 'name' => $category,
261 'count' => 0,
262 );
263 }
264
265 ++self::$categories[ $category_slug ]['count'];
266 }
267 }
268
269 /**
270 * @return void
271 */
272 public static function license_settings() {
273 $plugins = apply_filters( 'frm_installed_addons', array() );
274
275 if ( ! $plugins ) {
276 esc_html_e( 'There are no plugins on your site that require a license', 'formidable' );
277 return;
278 }
279
280 ksort( $plugins );
281
282 include FrmAppHelper::plugin_path() . '/classes/views/addons/settings.php';
283 }
284
285 /**
286 * @return array
287 */
288 protected static function get_api_addons() {
289 $api = new FrmFormApi();
290 $addons = $api->get_api_info();
291
292 if ( ! $addons ) {
293 return self::fallback_plugin_list();
294 }
295
296 foreach ( $addons as $k => $addon ) {
297 if ( empty( $addon['excerpt'] ) && $k !== 'error' ) {
298 unset( $addons[ $k ] );
299 }
300 }
301
302 return $addons;
303 }
304
305 /**
306 * Retrieves the count of available addons.
307 *
308 * @since 6.9
309 *
310 * @return int Count of addons.
311 */
312 public static function get_addons_count() {
313 return count( self::get_api_addons() );
314 }
315
316 /**
317 * If the API is unable to connect, show something on the addons page
318 *
319 * @since 3.04.03
320 *
321 * @return array
322 */
323 protected static function fallback_plugin_list() {
324 $list = array(
325 'formidable-pro' => array(
326 'title' => 'Formidable Forms Pro',
327 'link' => 'pricing/',
328 'docs' => '',
329 'excerpt' => 'Enhance your basic Formidable forms with a plethora of Pro field types and features. Create advanced forms and data-driven applications in minutes.',
330 ),
331 'mailchimp' => array(
332 'title' => 'Mailchimp Forms',
333 'excerpt' => 'Get on the path to more sales and leads in a matter of minutes. Add leads to a Mailchimp mailing list when they submit forms and update their information along with the entry.', // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
334 ),
335 'registration' => array(
336 'title' => 'User Registration Forms',
337 'link' => 'downloads/user-registration/',
338 'excerpt' => 'Give new users access to your site as quickly and painlessly as possible. Allow users to register, edit and be able to login to their profiles on your site from the front end in a clean, customized registration form.', // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
339 ),
340 'paypal' => array(
341 'title' => 'PayPal Standard Forms',
342 'link' => 'downloads/paypal-standard/',
343 'excerpt' => 'Automate your business by collecting instant payments from your clients. Collect information, calculate a total, and send them on to PayPal. Require a payment before publishing content on your site.', // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
344 ),
345 'stripe' => array(
346 'title' => 'Stripe Forms',
347 'docs' => 'knowledgebase/stripe/',
348 'excerpt' => 'Any Formidable forms on your site can accept credit card payments without users ever leaving your site.',
349 ),
350 'authorize-net' => array(
351 'title' => 'Authorize.net AIM Forms',
352 'link' => 'downloads/authorize-net-aim/',
353 'docs' => 'knowledgebase/authorize-net-aim/',
354 'excerpt' => 'Accept one-time payments directly on your site, using Authorize.net AIM.',
355 ),
356 'woocommerce' => array(
357 'title' => 'WooCommerce Forms',
358 'excerpt' => 'Use a Formidable form on your WooCommerce product pages.',
359 ),
360 'autoresponder' => array(
361 'title' => 'Form Action Automation',
362 'docs' => 'knowledgebase/schedule-autoresponder/',
363 'excerpt' => 'Schedule email notifications, SMS messages, and API actions.',
364 ),
365 'modal' => array(
366 'title' => 'Bootstrap Modal Forms',
367 'link' => 'downloads/bootstrap-modal/',
368 'docs' => 'knowledgebase/bootstrap-modal/',
369 'excerpt' => 'Open a view or form in a Bootstrap popup.',
370 ),
371 'bootstrap' => array(
372 'title' => 'Bootstrap Style Forms',
373 'excerpt' => 'Instantly add Bootstrap styling to all your Formidable forms.',
374 ),
375 'zapier' => array(
376 'title' => 'Zapier Forms',
377 'excerpt' => 'Connect with hundreds of different applications through Zapier. Insert a new row in a Google docs spreadsheet, post on Twitter, or add a new Dropbox file with your form.', // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
378 ),
379 'signature' => array(
380 'title' => 'Digital Signature Forms',
381 'excerpt' => 'Add a signature field to your form. The user may write their signature with a trackpad/mouse or just type it.',
382 ),
383 'api' => array(
384 'title' => 'Formidable Forms API',
385 'link' => 'downloads/formidable-api/',
386 'excerpt' => 'Send entry results to any other site that has a Rest API. This includes the option of sending entries from one Formidable site to another.',
387 ),
388 'twilio' => array(
389 'title' => 'Twilio SMS Forms',
390 'docs' => 'knowledgebase/twilio-add-on/',
391 'excerpt' => 'Allow users to text their votes for polls created by Formidable Forms, or send SMS notifications when entries are submitted or updated.',
392 ),
393 'views' => array(
394 'title' => 'Formidable Views',
395 'excerpt' => 'Add the power of views to your Formidable Forms to display your form submissions in listings, tables, calendars, and more.',
396 ),
397 'quiz_maker' => array(
398 'title' => 'Quiz Maker',
399 'link' => 'downloads/quiz-maker/',
400 'excerpt' => 'Make quizzes, automatically score them and show user scores.',
401 ),
402 );
403
404 $defaults = array(
405 'released' => '',
406 );
407
408 foreach ( $list as $k => $info ) {
409 $info['slug'] = $k;
410 $list[ $k ] = array_merge( $defaults, $info );
411 }
412
413 return $list;
414 }
415
416 /**
417 * If Pro is missing but has been authenticated, include a download URL
418 *
419 * @since 3.04.03
420 *
421 * @return string
422 */
423 public static function get_pro_download_url() {
424 $api = new FrmFormApi( self::get_pro_license() );
425 $downloads = $api->get_api_info();
426 $pro = self::get_pro_from_addons( $downloads );
427
428 return $pro['url'] ?? '';
429 }
430
431 /**
432 * @since 4.08
433 *
434 * @return string
435 */
436 public static function get_pro_license() {
437 $pro_cred_store = 'frmpro-credentials';
438 $pro_wpmu_store = 'frmpro-wpmu-sitewide';
439
440 if ( is_multisite() && get_site_option( $pro_wpmu_store ) ) {
441 $creds = get_site_option( $pro_cred_store );
442 } else {
443 $creds = get_option( $pro_cred_store );
444 }
445
446 if ( empty( $creds ) || ! is_array( $creds ) || ! isset( $creds['license'] ) ) {
447 return '';
448 }
449
450 $license = $creds['license'];
451
452 if ( ! $license ) {
453 return '';
454 }
455
456 if ( str_contains( $license, '-' ) ) {
457 // This is a fix for licenses saved in the past
458 return strtoupper( $license );
459 }
460
461 return $license;
462 }
463
464 /**
465 * @since 4.08
466 *
467 * @param array $addons
468 *
469 * @return array
470 */
471 protected static function get_pro_from_addons( $addons ) {
472 return $addons['93790'] ?? array();
473 }
474
475 /**
476 * @since 4.06
477 * @since 6.31 Added the $force_type param.
478 *
479 * @param bool $force_type Whether to resolve grandfathered licenses to their real license type.
480 *
481 * @return string
482 */
483 public static function license_type( $force_type = false ) {
484 if ( is_callable( 'FrmProAddonsController::license_type' ) ) {
485 return FrmProAddonsController::license_type( $force_type );
486 }
487
488 return 'free';
489 }
490
491 /**
492 * Determine the license status for payment fee decisions.
493 *
494 * Mirrors determine_status_from_license_details in the Stripe Connect service,
495 * including its 2016-04-26 grandfathered cutoff. Square and PayPal Commerce use a
496 * later cutoff, so read their fees through payment_fees_apply instead.
497 *
498 * @since 6.31
499 *
500 * @return string 'active', 'expired', or 'free'.
501 */
502 public static function get_payment_license_status() {
503 $version_info = self::get_primary_license_info();
504
505 if ( ! $version_info ) {
506 return 'free';
507 }
508
509 $error = $version_info['error'] ?? array();
510
511 if ( is_array( $error ) && 'expired' === ( $error['code'] ?? '' ) ) {
512 return 'expired';
513 }
514
515 if ( self::is_grandfathered_license() ) {
516 $expires = self::get_grandfathered_expiration( $version_info );
517
518 if ( $expires && gmdate( 'Y-m-d', $expires ) < '2016-04-26' ) {
519 return 'free';
520 }
521 }
522
523 if ( in_array( self::license_type( true ), array( 'elite', 'business' ), true ) ) {
524 return 'active';
525 }
526
527 return 'free';
528 }
529
530 /**
531 * Check if a connect service charges its per-transaction fee for this license.
532 *
533 * Square and PayPal Commerce launched long after lifetime licenses stopped being
534 * sold, so their services charge every grandfathered license. Stripe's predates
535 * them and exempts most of them, which get_payment_license_status mirrors.
536 *
537 * @since 6.34
538 *
539 * @param string $service The connect service: 'stripe', 'square', or 'paypal'.
540 *
541 * @return bool
542 */
543 public static function payment_fees_apply( $service ) {
544 if ( 'active' !== self::get_payment_license_status() ) {
545 return true;
546 }
547
548 if ( in_array( $service, array( 'square', 'paypal' ), true ) ) {
549 return self::is_grandfathered_license();
550 }
551
552 return false;
553 }
554
555 /**
556 * Check if the primary license is grandfathered.
557 *
558 * The API reports this on the Pro add-on itself, the same place
559 * FrmProAddonsController::license_type reads it from. Older responses put it
560 * in the top level error instead, so check both.
561 *
562 * @since 6.34
563 *
564 * @return bool
565 */
566 public static function is_grandfathered_license() {
567 $version_info = self::get_primary_license_info();
568
569 if ( ! $version_info ) {
570 return false;
571 }
572
573 $error = $version_info['error'] ?? array();
574
575 if ( is_array( $error ) && 'grandfathered' === ( $error['code'] ?? '' ) ) {
576 return true;
577 }
578
579 $pro = self::get_pro_from_addons( $version_info );
580
581 return 'grandfathered' === ( $pro['code'] ?? '' );
582 }
583
584 /**
585 * Get the expiration timestamp reported alongside a grandfathered license.
586 *
587 * @since 6.34
588 *
589 * @param array $version_info The license info from get_primary_license_info.
590 *
591 * @return int
592 */
593 private static function get_grandfathered_expiration( $version_info ) {
594 $error = $version_info['error'] ?? array();
595
596 if ( is_array( $error ) && ! empty( $error['expires'] ) ) {
597 return (int) $error['expires'];
598 }
599
600 $pro = self::get_pro_from_addons( $version_info );
601
602 return (int) ( $pro['expires'] ?? 0 );
603 }
604
605 /**
606 * @since 4.0.01
607 *
608 * @return bool
609 */
610 public static function is_license_expired() {
611 $version_info = self::get_primary_license_info();
612
613 if ( ! isset( $version_info['error'] ) ) {
614 return false;
615 }
616
617 $expires = $version_info['error']['expires'] ?? 0;
618
619 if ( ! $expires || $expires > time() ) {
620 return false;
621 }
622
623 $rate_limited = ! empty( $version_info['response_code'] ) && 429 === (int) $version_info['response_code'];
624
625 if ( $rate_limited ) {
626 // Do not return false positives for rate limited responses.
627 return false;
628 }
629
630 return $version_info['error'];
631 }
632
633 /**
634 * @since 4.08
635 * @since 6.7 This is public.
636 *
637 * @return array|false
638 */
639 public static function get_primary_license_info() {
640 $installed_addons = apply_filters( 'frm_installed_addons', array() );
641
642 if ( ! $installed_addons || ! isset( $installed_addons['formidable_pro'] ) ) {
643 return false;
644 }
645
646 $installed_addons = array(
647 'formidable_pro' => $installed_addons['formidable_pro'],
648 );
649
650 return self::fill_update_addon_info( $installed_addons );
651 }
652
653 /**
654 * @since 3.04.03
655 *
656 * @param mixed $transient
657 *
658 * @return object
659 */
660 public static function check_update( $transient ) {
661 if ( ! FrmAppHelper::pro_is_installed() ) {
662 // Don't make any changes if only Lite is installed.
663 return $transient;
664 }
665
666 if ( ! is_object( $transient ) ) {
667 $transient = new stdClass();
668 }
669
670 $installed_addons = apply_filters( 'frm_installed_addons', array() );
671
672 if ( ! $installed_addons ) {
673 return $transient;
674 }
675
676 $version_info = self::fill_update_addon_info( $installed_addons );
677 $transient->last_checked = time();
678 $wp_plugins = self::get_plugins();
679
680 foreach ( $version_info as $plugin ) {
681 $plugin = (object) $plugin;
682
683 if ( ! isset( $plugin->new_version ) || ! isset( $plugin->package ) ) {
684 continue;
685 }
686
687 $folder = $plugin->plugin;
688
689 if ( ! $folder ) {
690 continue;
691 }
692
693 if ( ! self::is_installed( $folder ) ) {
694 // Don't show an update if the plugin isn't installed
695 continue;
696 }
697
698 $wp_plugin = $wp_plugins[ $folder ] ?? array();
699 $wp_version = $wp_plugin['Version'] ?? '1.0';
700 $plugin->slug = explode( '/', $folder )[0];
701
702 if ( version_compare( $wp_version, $plugin->new_version, '<' ) ) {
703 $transient->response[ $folder ] = $plugin;
704 } else {
705 $transient->no_update[ $folder ] = $plugin;
706 }
707
708 $transient->checked[ $folder ] = $wp_version;
709 }//end foreach
710
711 return $transient;
712 }
713
714 /**
715 * Copy of FrmAppHelper::get_plugins.
716 * Because this gets called on "pre_set_site_transient_update_plugins" an old version of FrmAppHelper may be loaded on plugin update.
717 * This means that trying to access FrmAppHelper::get_plugins when upgrading from a Lite version before v5.5 results in a one-off error.
718 *
719 * @since 5.5.2
720 *
721 * @return array
722 */
723 protected static function get_plugins() {
724 if ( ! function_exists( 'get_plugins' ) ) {
725 require_once ABSPATH . 'wp-admin/includes/plugin.php';
726 }
727 return get_plugins();
728 }
729
730 /**
731 * Check if a plugin is installed before showing an update for it
732 *
733 * @since 3.05
734 *
735 * @param string $plugin The folder/filename.php for a plugin.
736 *
737 * @return bool - True if installed
738 */
739 protected static function is_installed( $plugin ) {
740 $all_plugins = self::get_plugins();
741 return isset( $all_plugins[ $plugin ] );
742 }
743
744 /**
745 * @since 3.04.03
746 *
747 * @param array $installed_addons
748 *
749 * @return array
750 */
751 protected static function fill_update_addon_info( $installed_addons ) {
752 $checked_licenses = array();
753 $version_info = array();
754
755 foreach ( $installed_addons as $addon ) {
756 if ( $addon->store_url !== 'https://formidableforms.com' ) {
757 // Check if this is a third-party addon
758 continue;
759 }
760
761 $new_license = $addon->license;
762
763 if ( ! $new_license || in_array( $new_license, $checked_licenses, true ) ) {
764 continue;
765 }
766
767 $checked_licenses[] = $new_license;
768 $api = new FrmFormApi( $new_license );
769
770 if ( ! $version_info ) {
771 $version_info = $api->get_api_info();
772 continue;
773 }
774
775 $plugin = $api->get_addon_for_license( $addon, $version_info );
776
777 if ( ! $plugin ) {
778 continue;
779 }
780
781 $download_id = $plugin['id'] ?? 0;
782
783 if ( ! $download_id || isset( $version_info[ $download_id ]['package'] ) ) {
784 continue;
785 }
786
787 // If this addon is using its own license, get the update url
788 $addon_info = $api->get_api_info();
789
790 $version_info[ $download_id ] = $addon_info[ $download_id ];
791
792 if ( isset( $addon_info['error'] ) ) {
793 $version_info[ $download_id ]['error'] = array(
794 'message' => $addon_info['error']['message'],
795 'code' => $addon_info['error']['code'],
796 );
797 }
798 }//end foreach
799
800 return $version_info;
801 }
802
803 /**
804 * Get the action link for an addon that isn't active.
805 *
806 * @since 3.06.03
807 *
808 * @param string $plugin The plugin slug.
809 *
810 * @return array
811 */
812 public static function install_link( $plugin ) {
813 $link = array();
814 $addon = self::get_addon( $plugin );
815
816 if ( $addon ) {
817 if ( $addon['status']['type'] === 'installed' && ! empty( $addon['activate_url'] ) ) {
818 $link = array(
819 'url' => $addon['plugin'],
820 'class' => 'frm-activate-addon',
821 );
822 } elseif ( ! empty( $addon['url'] ) ) {
823 $link = array(
824 'url' => $addon['url'],
825 'class' => 'frm-install-addon',
826 );
827 } elseif ( ! empty( $addon['categories'] ) ) {
828 $link = array(
829 'categories' => $addon['categories'],
830 );
831 }
832
833 if ( $link ) {
834 $link['status'] = $addon['status']['type'];
835 }
836 } elseif ( current_user_can( 'activate_plugins' ) && self::is_installed( 'formidable-' . $plugin . '/formidable-' . $plugin . '.php' ) ) {
837 $link = array(
838 'url' => 'formidable-' . $plugin . '/formidable-' . $plugin . '.php',
839 'class' => 'frm-activate-addon',
840 );
841 }//end if
842
843 return $link;
844 }
845
846 /**
847 * Get the JSON-encoded install data for a plugin update.
848 *
849 * @since 6.29
850 *
851 * @param string $addon_slug The addon slug (e.g. 'pro', 'dates').
852 *
853 * @return string JSON-encoded install data, or empty string if no URL is available.
854 */
855 public static function get_update_install_data( $addon_slug ) {
856 $upgrading = self::install_link( $addon_slug );
857
858 if ( isset( $upgrading['class'] ) && 'frm-install-addon' === $upgrading['class'] ) {
859 return (string) json_encode( $upgrading );
860 }
861
862 if ( 'pro' === $addon_slug ) {
863 $download_url = self::get_pro_download_url();
864 $plugin_file = 'formidable-pro/formidable-pro.php';
865 } else {
866 $addon_data = self::get_addon( $addon_slug );
867 $download_url = $addon_data && ! empty( $addon_data['url'] ) ? $addon_data['url'] : '';
868 $plugin_file = $addon_data && ! empty( $addon_data['plugin'] ) ? $addon_data['plugin'] : 'formidable-' . $addon_slug . '/formidable-' . $addon_slug . '.php';
869 }
870
871 if ( ! $download_url ) {
872 $update_plugins = get_site_transient( 'update_plugins' );
873 $plugin_update = $update_plugins->response[ $plugin_file ] ?? null;
874 $download_url = $plugin_update && ! empty( $plugin_update->package ) ? $plugin_update->package : '';
875 }
876
877 return $download_url ? (string) json_encode(
878 array(
879 'url' => $download_url,
880 'class' => 'frm-install-addon',
881 )
882 ) : '';
883 }
884
885 /**
886 * @since 4.09
887 *
888 * @param string $plugin The plugin slug.
889 *
890 * @return array|false
891 */
892 public static function get_addon( $plugin ) {
893 $addons = self::get_api_addons();
894 self::prepare_addons( $addons );
895
896 foreach ( $addons as $addon ) {
897 $slug = explode( '/', $addon['plugin'] );
898
899 if ( $slug[0] === 'formidable-' . $plugin ) {
900 return $addon;
901 }
902 }
903
904 return false;
905 }
906
907 /**
908 * @since 4.09
909 *
910 * @return string
911 */
912 protected static function get_license_type() {
913 $license_type = '';
914 $addons = self::get_api_addons();
915
916 if ( isset( $addons['error'] ) && isset( $addons['error']['type'] ) ) {
917 return $addons['error']['type'];
918 }
919
920 return $license_type;
921 }
922
923 /**
924 * @since 3.04.03
925 *
926 * @param array $addons
927 * @param object $license The FrmAddon object.
928 *
929 * @return array
930 */
931 public static function get_addon_for_license( $addons, $license ) {
932 $download_id = $license->download_id;
933 $plugin = array();
934
935 if ( ! $download_id && $addons ) {
936 foreach ( $addons as $addon ) {
937 if ( 0 === strcasecmp( $license->plugin_name, $addon['title'] ) ) {
938 return $addon;
939 }
940 }
941 } elseif ( isset( $addons[ $download_id ] ) ) {
942 $plugin = $addons[ $download_id ];
943 }
944
945 return $plugin;
946 }
947
948 /**
949 * @param array $addons
950 *
951 * @return void
952 */
953 protected static function prepare_addons( &$addons ) {
954 // Reset categories to prevent count accumulation across multiple calls.
955 self::$categories = array();
956
957 $activate_url = '';
958
959 if ( current_user_can( 'activate_plugins' ) ) {
960 $activate_url = add_query_arg( array( 'action' => 'activate' ), admin_url( 'plugins.php' ) );
961 }
962
963 $loop_addons = $addons;
964
965 foreach ( $loop_addons as $id => $addon ) {
966 if ( is_numeric( $id ) ) {
967 $slug = str_replace( array( '-wordpress-plugin', '-wordpress' ), '', $addon['slug'] );
968 $file_name = $addon['plugin'];
969 } else {
970 $slug = $id;
971 $base_file = $addon['file'] ?? 'formidable-' . $slug;
972 $file_name = $base_file . '/' . $base_file . '.php';
973
974 if ( ! isset( $addon['plugin'] ) ) {
975 $addon['plugin'] = $file_name;
976 }
977 }
978
979 $addon['installed'] = self::is_installed( $file_name );
980
981 if ( 'highrise' === $slug && ! $addon['installed'] ) {
982 unset( $addons[ $id ] );
983 continue;
984 }
985
986 if ( $addon['installed'] && 'formidable-views/formidable-views.php' === $file_name ) {
987 $active_views_version = self::get_active_views_version();
988
989 if ( false !== $active_views_version && $slug !== $active_views_version ) {
990 $addon['installed'] = false;
991 }
992 }
993
994 $addon['activate_url'] = '';
995
996 if ( $addon['installed'] && $activate_url && ! self::is_plugin_active( $file_name, $slug ) ) {
997 $addon['activate_url'] = add_query_arg(
998 array(
999 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $file_name ),
1000 'plugin' => $file_name,
1001 ),
1002 $activate_url
1003 );
1004 }
1005
1006 if ( ! isset( $addon['docs'] ) ) {
1007 $addon['docs'] = 'knowledgebase/formidable-' . $slug . '/';
1008 }
1009 self::prepare_addon_link( $addon['docs'] );
1010
1011 if ( ! isset( $addon['link'] ) ) {
1012 $addon['link'] = 'downloads/' . $slug . '/';
1013 }
1014
1015 self::prepare_addon_link( $addon['link'] );
1016 self::set_addon_status( $addon );
1017 self::set_categories( $addon );
1018
1019 $addons[ $id ] = $addon;
1020 }//end foreach
1021 }
1022
1023 /**
1024 * @param string $file_name
1025 * @param string $slug
1026 *
1027 * @return bool
1028 */
1029 private static function is_plugin_active( $file_name, $slug ) {
1030 if ( 'formidable-views/formidable-views.php' === $file_name ) {
1031 return self::get_active_views_version() === $slug;
1032 }
1033 return is_plugin_active( $file_name );
1034 }
1035
1036 /**
1037 * @return false|string either 'visual-views' or 'views', false if one is not found.
1038 */
1039 private static function get_active_views_version() {
1040 if ( ! is_callable( 'FrmViewsAppHelper::plugin_version' ) ) {
1041 return false;
1042 }
1043
1044 $plugin_version = FrmViewsAppHelper::plugin_version();
1045 return version_compare( $plugin_version, '5.0', '>=' ) ? 'visual-views' : 'views';
1046 }
1047
1048 /**
1049 * @since 3.04.02
1050 *
1051 * @param string $link
1052 *
1053 * @return void
1054 */
1055 protected static function prepare_addon_link( &$link ) {
1056 $site_url = 'https://formidableforms.com/';
1057
1058 if ( ! str_starts_with( $link, 'http' ) ) {
1059 $link = $site_url . $link;
1060 }
1061
1062 $link = FrmAppHelper::make_affiliate_url( $link );
1063
1064 $utm = array(
1065 'campaign' => 'addons',
1066 );
1067 $link = FrmAppHelper::maybe_add_missing_utm( $link, $utm );
1068 }
1069
1070 /**
1071 * Add the status to the addon array. Status options are:
1072 * installed, active, not installed
1073 *
1074 * @since 3.04.02
1075 *
1076 * @param array $addon
1077 *
1078 * @return void
1079 */
1080 protected static function set_addon_status( &$addon ) {
1081 if ( ! empty( $addon['activate_url'] ) ) {
1082 $addon['status'] = array(
1083 'type' => 'installed',
1084 'label' => __( 'Installed', 'formidable' ),
1085 );
1086 } elseif ( $addon['installed'] ) {
1087 $addon['status'] = array(
1088 'type' => 'active',
1089 'label' => __( 'Active', 'formidable' ),
1090 );
1091 } else {
1092 $addon['status'] = array(
1093 'type' => 'not-installed',
1094 'label' => __( 'Not Installed', 'formidable' ),
1095 );
1096 }
1097 }
1098
1099 /**
1100 * @since 5.5.2
1101 *
1102 * @param string $plugin
1103 * @param string $redirect
1104 * @param bool $network_wide
1105 * @param bool $silent
1106 *
1107 * @return WP_Error|null Null on success, WP_Error on invalid file.
1108 */
1109 protected static function activate_plugin( $plugin, $redirect = '', $network_wide = false, $silent = false ) {
1110 if ( ! function_exists( 'activate_plugin' ) ) {
1111 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1112 }
1113 return activate_plugin( $plugin, $redirect, $network_wide, $silent );
1114 }
1115
1116 /**
1117 * Deactivate a specified plugin.
1118 *
1119 * @since 6.8
1120 *
1121 * @param string $plugin
1122 * @param bool $silent
1123 *
1124 * @return void
1125 */
1126 protected static function deactivate_plugin( $plugin, $silent = false ) {
1127 if ( ! function_exists( 'deactivate_plugins' ) ) {
1128 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1129 }
1130 deactivate_plugins( $plugin, $silent );
1131 }
1132
1133 /**
1134 * Uninstall a specified plugin.
1135 *
1136 * @since 6.8
1137 *
1138 * @param string $plugin
1139 *
1140 * @return true|WP_Error True on success, WP_Error on invalid file.
1141 */
1142 protected static function uninstall_plugin( $plugin ) {
1143 if ( ! current_user_can( 'delete_plugins' ) ) {
1144 return new WP_Error( 'uninstall_failed', __( 'Current user cannot delete plugins.', 'formidable' ) );
1145 }
1146
1147 if ( ! function_exists( 'delete_plugins' ) ) {
1148 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1149 }
1150
1151 self::deactivate_plugin( $plugin, true );
1152 $result = delete_plugins( array( $plugin ) );
1153
1154 return is_wp_error( $result ) ? $result : true;
1155 }
1156
1157 /**
1158 * @since 5.0.10
1159 *
1160 * @return string
1161 */
1162 protected static function get_current_plugin() {
1163 if ( ! self::$plugin ) {
1164 self::$plugin = FrmAppHelper::get_param( 'plugin', '', 'post', 'esc_url_raw' );
1165 }
1166 return self::$plugin;
1167 }
1168
1169 /**
1170 * @since 4.08
1171 *
1172 * @return mixed[]|null
1173 */
1174 protected static function download_and_activate() {
1175 if ( is_admin() ) {
1176 FrmAppHelper::set_current_screen_and_hook_suffix();
1177 }
1178
1179 self::maybe_show_cred_form();
1180
1181 $installed = self::install_addon();
1182
1183 if ( is_array( $installed ) && isset( $installed['message'] ) ) {
1184 return $installed;
1185 }
1186 self::handle_addon_action( $installed, 'activate' );
1187 return null;
1188 }
1189
1190 /**
1191 * @since 3.04.02
1192 *
1193 * @return void
1194 */
1195 protected static function maybe_show_cred_form() {
1196 if ( ! function_exists( 'request_filesystem_credentials' ) ) {
1197 include_once ABSPATH . 'wp-admin/includes/file.php';
1198 }
1199
1200 // Start output buffering to catch the filesystem form if credentials are needed.
1201 ob_start();
1202
1203 $show_form = false;
1204 $method = '';
1205 $url = add_query_arg( array( 'page' => 'formidable-settings' ), admin_url( 'admin.php' ) );
1206 $url = esc_url_raw( $url );
1207 $creds = request_filesystem_credentials( $url, $method, false, false, null );
1208
1209 if ( false === $creds ) {
1210 $show_form = true;
1211 } elseif ( ! WP_Filesystem( $creds ) ) {
1212 request_filesystem_credentials( $url, $method, true, false, null );
1213 $show_form = true;
1214 }
1215
1216 if ( $show_form ) {
1217 $form = ob_get_clean();
1218 $message = __( 'Sorry, your site requires FTP authentication. Please download plugins from FormidableForms.com and install them manually.', 'formidable' );
1219 $response = array(
1220 'success' => false,
1221 'message' => $message,
1222 'form' => $form,
1223 );
1224 wp_send_json( $response );
1225 }
1226
1227 ob_end_clean();
1228 }
1229
1230 /**
1231 * Checks if an addon download url is allowed.
1232 *
1233 * @since 6.2
1234 *
1235 * @param string $download_url
1236 *
1237 * @return bool
1238 */
1239 public static function url_is_allowed( $download_url ) {
1240 return FrmAppHelper::validate_url_is_in_s3_bucket( $download_url, 'zip' ) || in_array( $download_url, self::allowed_external_urls(), true );
1241 }
1242
1243 /**
1244 * We do not need any extra credentials if we have gotten this far,
1245 * so let's install the plugin.
1246 *
1247 * @since 3.04.02
1248 *
1249 * @return array|string
1250 */
1251 protected static function install_addon() {
1252 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1253
1254 $download_url = self::get_current_plugin();
1255
1256 if ( ! self::url_is_allowed( $download_url ) ) {
1257 return array(
1258 'message' => 'Plugin URL is not valid',
1259 'success' => false,
1260 );
1261 }
1262
1263 // Create the plugin upgrader with our custom skin.
1264 $installer = new Plugin_Upgrader( new FrmInstallerSkin() );
1265 $installer->install( $download_url, array( 'overwrite_package' => true ) );
1266
1267 // Flush the cache and return the newly installed plugin basename.
1268 wp_cache_flush();
1269
1270 $plugin = $installer->plugin_info();
1271
1272 if ( ! $plugin ) {
1273 return array(
1274 'message' => 'Plugin was not installed. ' . $installer->result,
1275 'success' => false,
1276 );
1277 }
1278
1279 return $plugin;
1280 }
1281
1282 /**
1283 * Handle the AJAX request to activate an add-on.
1284 *
1285 * @since 6.8
1286 *
1287 * @return void
1288 */
1289 public static function ajax_activate_addon() {
1290 self::process_addon_action(
1291 function ( $plugin ) {
1292 return self::handle_addon_action( $plugin, 'activate' );
1293 },
1294 array( 'FrmAddonsController', 'get_addon_activation_response' )
1295 );
1296 }
1297
1298 /**
1299 * @since 3.04.02
1300 *
1301 * @param string $installed The plugin folder name with file name.
1302 */
1303 protected static function maybe_activate_addon( $installed ) {
1304 self::ajax_activate_addon();
1305 }
1306
1307 /**
1308 * Handle the AJAX request to deactivate an add-on.
1309 *
1310 * @since 6.8
1311 *
1312 * @return void
1313 */
1314 public static function ajax_deactivate_addon() {
1315 self::process_addon_action(
1316 function ( $plugin ) {
1317 return self::handle_addon_action( $plugin, 'deactivate' );
1318 }
1319 );
1320 }
1321
1322 /**
1323 * Handle the AJAX request to uninstall an add-on.
1324 *
1325 * @since 6.8
1326 *
1327 * @return void
1328 */
1329 public static function ajax_uninstall_addon() {
1330 self::process_addon_action(
1331 function ( $plugin ) {
1332 return self::handle_addon_action( $plugin, 'uninstall' );
1333 }
1334 );
1335 }
1336
1337 /**
1338 * Process a specific action (activate, deactivate, uninstall) on an add-on.
1339 *
1340 * @since 6.8
1341 *
1342 * @param callable $action_callback The specific add-on action to be executed.
1343 * @param callable|null $response_callback Optional. The response handling callback. Default null.
1344 *
1345 * @return void
1346 */
1347 private static function process_addon_action( $action_callback, $response_callback = null ) {
1348 self::install_addon_permissions();
1349 FrmAppHelper::set_current_screen_and_hook_suffix();
1350
1351 $plugin = FrmAppHelper::get_param( 'plugin', '', 'post', 'sanitize_text_field' );
1352 call_user_func( $action_callback, $plugin );
1353
1354 if ( is_callable( $response_callback ) ) {
1355 wp_send_json_success( call_user_func( $response_callback ) );
1356 return;
1357 }
1358
1359 wp_send_json_success();
1360 }
1361
1362 /**
1363 * Attempt to perform a specific action (activate, deactivate, uninstall) on an add-on.
1364 *
1365 * @since 6.8
1366 *
1367 * @param string $installed The plugin folder name with file name.
1368 * @param string $action The action type ('activate', 'deactivate', 'uninstall').
1369 *
1370 * @return mixed[]|null
1371 */
1372 protected static function handle_addon_action( $installed, $action ) {
1373 if ( ! $installed || ! $action ) {
1374 return null;
1375 }
1376
1377 $result = null;
1378 switch ( $action ) {
1379 case 'activate':
1380 $result = self::activate_plugin( $installed );
1381 break;
1382 case 'deactivate':
1383 self::deactivate_plugin( $installed );
1384 break;
1385 case 'uninstall':
1386 $result = self::uninstall_plugin( $installed );
1387 break;
1388 }
1389
1390 if ( is_wp_error( $result ) ) {
1391 // Ignore the invalid header message that shows with nested plugins.
1392 if ( $result->get_error_code() !== 'no_plugin_header' ) {
1393 if ( wp_doing_ajax() ) {
1394 wp_send_json_error( array( 'error' => $result->get_error_message() ) );
1395 }
1396
1397 return array(
1398 'message' => $result->get_error_message(),
1399 'success' => false,
1400 );
1401 }
1402 }
1403
1404 return $result;
1405 }
1406
1407 /**
1408 * @return array
1409 */
1410 private static function get_addon_activation_response() {
1411 $activating_page = self::get_activating_page();
1412 $message = $activating_page ? __( 'Your plugin has been activated. Would you like to save and reload the page now?', 'formidable' ) : __( 'Your plugin has been activated.', 'formidable' ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
1413
1414 return array(
1415 'message' => $message,
1416 'saveAndReload' => $activating_page,
1417 );
1418 }
1419
1420 /**
1421 * Return a string that reflects the page from which the addon is being activated on,
1422 * if it is from settings or form builder, otherwise return empty string.
1423 *
1424 * @return string
1425 */
1426 private static function get_activating_page() {
1427 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
1428
1429 if ( str_contains( $referer, 'frm_action=settings' ) ) {
1430 return 'settings';
1431 }
1432
1433 return str_contains( $referer, 'frm_action=edit' ) ? 'form_builder' : '';
1434 }
1435
1436 /**
1437 * Run security checks before installing
1438 *
1439 * @since 3.04.02
1440 *
1441 * @return void
1442 */
1443 protected static function install_addon_permissions() {
1444 check_ajax_referer( 'frm_ajax', 'nonce' );
1445
1446 if ( current_user_can( 'activate_plugins' ) && self::get_current_plugin() ) {
1447 return;
1448 }
1449
1450 echo json_encode( true );
1451 wp_die();
1452 }
1453
1454 /**
1455 * @since 4.08
1456 *
1457 * @return string
1458 */
1459 public static function connect_link() {
1460 $auth = get_option( 'frm_connect_token' );
1461
1462 if ( ! $auth ) {
1463 $auth = hash( 'sha512', wp_rand() );
1464 update_option( 'frm_connect_token', $auth, false );
1465 }
1466
1467 $page = FrmAppHelper::simple_get( 'page', 'sanitize_title', 'formidable-settings' );
1468 $link = 'https://formidableforms.com/api-connect/';
1469 $args = array(
1470 'v' => 2,
1471 'siteurl' => FrmAppHelper::site_url(),
1472 'url' => get_rest_url(),
1473 'inst' => (int) FrmAppHelper::pro_is_included(),
1474 'return' => $page,
1475 'token' => $auth,
1476 'l' => self::get_pro_license(),
1477 );
1478
1479 return add_query_arg( $args, $link );
1480 }
1481
1482 /**
1483 * Check the auth value for install permission.
1484 *
1485 * @since 4.08
1486 *
1487 * @return bool
1488 */
1489 public static function can_install_addon_api() {
1490 // Verify params present (auth & download link).
1491 $post_auth = FrmAppHelper::get_param( 'token', '', 'request', 'sanitize_text_field' );
1492 $post_url = FrmAppHelper::get_param( 'file_url', '', 'request', 'sanitize_text_field' );
1493
1494 // The download link is not required if already installed.
1495 $is_installed = FrmAppHelper::pro_is_included();
1496 $file_missing = ! $is_installed && ! $post_url;
1497
1498 if ( ! $post_auth || $file_missing ) {
1499 return false;
1500 }
1501
1502 // Verify auth.
1503 $auth = get_option( 'frm_connect_token' );
1504 return $auth && hash_equals( $auth, $post_auth );
1505 }
1506
1507 /**
1508 * Install and/or activate the add-on file.
1509 *
1510 * @since 4.08
1511 *
1512 * @return array
1513 */
1514 public static function install_addon_api() {
1515 self::$plugin = FrmAppHelper::get_param( 'file_url', '', 'request', 'esc_url_raw' );
1516
1517 // Delete so cannot replay.
1518 delete_option( 'frm_connect_token' );
1519
1520 // It's already installed and active.
1521 $active = self::activate_plugin( 'formidable-pro/formidable-pro.php', false, false, true );
1522
1523 if ( is_wp_error( $active ) ) {
1524 $response = self::maybe_download_and_activate();
1525
1526 if ( is_array( $response ) ) {
1527 // The download failed.
1528 return $response;
1529 }
1530 }
1531
1532 // If empty license, save it now.
1533 if ( ! self::get_pro_license() && function_exists( 'load_formidable_pro' ) ) {
1534 load_formidable_pro();
1535 $license = stripslashes( FrmAppHelper::get_param( 'key', '', 'request', 'sanitize_text_field' ) );
1536
1537 if ( ! $license ) {
1538 return array(
1539 'success' => false,
1540 'error' => 'That site does not have a valid license key.',
1541 );
1542 }
1543
1544 $response = FrmAddon::activate_license_for_plugin( $license, 'formidable_pro' );
1545
1546 if ( ! $response['success'] ) {
1547 // Could not activate license.
1548 return $response;
1549 }
1550 }
1551
1552 return array(
1553 'success' => true,
1554 );
1555 }
1556
1557 /**
1558 * @since 6.8.3
1559 *
1560 * @return array|true
1561 */
1562 private static function maybe_download_and_activate() {
1563 if ( ! self::$plugin ) {
1564 return array(
1565 'success' => false,
1566 'message' => __( 'The plugin download was not found.', 'formidable' ),
1567 );
1568 }
1569
1570 // Download plugin now.
1571 $response = self::download_and_activate();
1572
1573 if ( is_array( $response ) && isset( $response['success'] ) ) {
1574 // The download failed.
1575 return $response;
1576 }
1577
1578 return true;
1579 }
1580
1581 /**
1582 * Render a conditional action button for a specified plugin
1583 *
1584 * @since 4.09
1585 *
1586 * @param string $plugin
1587 * @param array|string $upgrade_link_args
1588 *
1589 * @return void
1590 */
1591 public static function conditional_action_button( $plugin, $upgrade_link_args ) {
1592 if ( is_callable( 'FrmProAddonsController::conditional_action_button' ) ) {
1593 FrmProAddonsController::conditional_action_button( $plugin, $upgrade_link_args );
1594 return;
1595 }
1596
1597 $addon = self::get_addon( $plugin );
1598 $upgrade_link = FrmAppHelper::admin_upgrade_link( $upgrade_link_args );
1599
1600 if ( ! is_array( $upgrade_link_args ) ) {
1601 // A string $upgrade_link_args is used for the utm-medium value when calling
1602 // FrmAppHelper::admin_upgrade_link above.
1603 // For self::addon_upgrade_link, we'll pass just an empty array with the link key (set below).
1604 $upgrade_link_args = array();
1605 }
1606
1607 $upgrade_link_args['link'] = $upgrade_link;
1608
1609 self::addon_upgrade_link( $addon, $upgrade_link_args );
1610 }
1611
1612 /**
1613 * Render a conditional action button for an add on
1614 *
1615 * @since 4.09.01
1616 *
1617 * @param array $atts {
1618 * Button attributes.
1619 *
1620 * @type array $addon
1621 * @type false|string $license_type
1622 * @type string $plan_required
1623 * @type string $upgrade_link
1624 * }
1625 *
1626 * @return void
1627 */
1628 public static function show_conditional_action_button( $atts ) {
1629 if ( is_callable( 'FrmProAddonsController::show_conditional_action_button' ) ) {
1630 FrmProAddonsController::show_conditional_action_button( $atts );
1631 return;
1632 }
1633
1634 self::addon_upgrade_link( $atts['addon'], $atts['upgrade_link'] );
1635 }
1636
1637 /**
1638 * @since 4.09.01
1639 *
1640 * @param array|false $addon
1641 * @param array|string $upgrade_link
1642 *
1643 * @return void
1644 */
1645 public static function addon_upgrade_link( $addon, $upgrade_link ) {
1646 $atts = is_array( $upgrade_link ) ? $upgrade_link : array();
1647 $upgrade_link = is_array( $upgrade_link ) ? $upgrade_link['link'] : $upgrade_link;
1648 $text = ! empty( $atts['text'] ) ? $atts['text'] : __( 'Upgrade Now', 'formidable' );
1649
1650 if ( $addon ) {
1651 $upgrade_link .= '&utm_content=' . $addon['slug'];
1652 }
1653
1654 if ( $addon && isset( $addon['categories'] ) && in_array( 'Solution', $addon['categories'], true ) ) {
1655 // Solutions will go to a separate page.
1656 $upgrade_link = FrmAppHelper::admin_upgrade_link( 'addons', $addon['link'] );
1657 }
1658
1659 $class = ! empty( $atts['class'] ) ? $atts['class'] : '';
1660
1661 if ( ! str_contains( $class, 'frm-button' ) ) {
1662 $class .= ' frm-button-secondary frm-button-sm';
1663 }
1664 // phpcs:disable Generic.WhiteSpace.ScopeIndent
1665 ?>
1666 <a class="install-now button <?php echo esc_attr( $class ); ?>" href="<?php echo esc_url( $upgrade_link ); ?>" target="_blank" rel="noopener" aria-label="<?php esc_attr_e( 'Upgrade Now', 'formidable' ); ?>"><?php // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong ?>
1667 <?php echo esc_html( $text ); ?>
1668 </a>
1669 <?php
1670 // phpcs:enable Generic.WhiteSpace.ScopeIndent
1671 }
1672
1673 /**
1674 * @since 3.04.02
1675 *
1676 * @return void
1677 */
1678 public static function ajax_install_addon() {
1679 self::install_addon_permissions();
1680
1681 $result = self::download_and_activate();
1682
1683 if ( isset( $result['success'] ) && ! $result['success'] ) {
1684 echo json_encode( $result );
1685 wp_die();
1686 }
1687
1688 echo json_encode( self::get_addon_activation_response() );
1689 wp_die();
1690 }
1691
1692 /**
1693 * Allowed URLs used for internal source of plugins installation.
1694 *
1695 * @since 6.3.1
1696 *
1697 * @return array
1698 */
1699 private static function allowed_external_urls() {
1700 $allowed_url_list = array(
1701 'https://downloads.wordpress.org/plugin/formidable-gravity-forms-importer.zip',
1702 'https://downloads.wordpress.org/plugin/formidable-import-pirate-forms.zip',
1703 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip',
1704 );
1705
1706 /**
1707 * List of URLs used in plugin formidable internal installation.
1708 *
1709 * @since 6.3.1
1710 *
1711 * @param array $allowed_url_list List of URLs.
1712 */
1713 $allowed_url_list = apply_filters( 'frm_allowed_external_urls', $allowed_url_list );
1714
1715 if ( ! is_array( $allowed_url_list ) ) {
1716 _doing_it_wrong( __METHOD__, 'Only an array of URLs could be used within this filter.', '6.3.1' );
1717 return array();
1718 }
1719
1720 return $allowed_url_list;
1721 }
1722
1723 /**
1724 * Gets required plan for an addon.
1725 *
1726 * @since 6.4.2
1727 *
1728 * @param int|string $addon_id
1729 *
1730 * @return string Empty string if no plan is required for active license.
1731 */
1732 public static function get_addon_required_plan( $addon_id ) {
1733 $api = new FrmFormApi();
1734 $addons = $api->get_api_info();
1735
1736 if ( is_array( $addons ) && array_key_exists( $addon_id, $addons ) ) {
1737 $dates = $addons[ $addon_id ];
1738 $requires = FrmFormsHelper::get_plan_required( $dates );
1739 }
1740
1741 if ( ! isset( $requires ) || ! is_string( $requires ) ) {
1742 return '';
1743 }
1744
1745 return $requires;
1746 }
1747 }
1748