PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.28
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.28
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 6.28, at classes/controllers/FrmAddonsController.php

1,586 lines 41.0 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/',
161 'categories' => array( 'basic', 'plus', 'business', 'elite' ),
162 'excerpt' => 'Create calculators, surveys, smart forms, and data-driven applications. Build directories, real estate listings, job boards, and much more.',
163 ),
164 );
165 $addons = $pro + $addons;
166 self::prepare_addons( $addons );
167
168 $pricing = FrmAppHelper::admin_upgrade_link( 'addons' );
169
170 self::organize_and_get_categories();
171 $categories = self::$categories;
172
173 include $view_path . 'index.php';
174 }
175
176 /**
177 * Organize and set categories.
178 *
179 * @since 6.15
180 *
181 * @return void
182 */
183 protected static function organize_and_get_categories() {
184 unset( self::$categories['strategy11'] );
185 ksort( self::$categories );
186
187 $bottom_categories = array();
188 $plans = FrmFormsHelper::get_license_types(
189 array(
190 'include_all' => false,
191 'case_lower' => true,
192 )
193 );
194
195 // Extract the elements to move
196 foreach ( $plans as $plan ) {
197 if ( isset( self::$categories[ $plan ] ) ) {
198 $bottom_categories[ $plan ] = self::$categories[ $plan ];
199 unset( self::$categories[ $plan ] );
200 }
201 }
202
203 $special_categories = array();
204
205 if ( 'elite' !== self::license_type() ) {
206 $special_categories['available-addons'] = array(
207 'name' => __( 'Available', 'formidable' ),
208 // To be assigned via JavaScript.
209 'count' => 0,
210 );
211 }
212
213 $special_categories['active-addons'] = array(
214 'name' => __( 'Active', 'formidable' ),
215 // To be assigned via JavaScript.
216 'count' => 0,
217 );
218
219 $special_categories['all-items'] = array(
220 'name' => __( 'All Add-Ons', 'formidable' ),
221 // To be assigned via JavaScript.
222 'count' => 0,
223 );
224
225 self::$categories = array_merge(
226 $special_categories,
227 self::$categories,
228 $bottom_categories
229 );
230 }
231
232 /**
233 * Organize and set categories.
234 *
235 * @since 6.15
236 *
237 * @param array $addon The addon array that will be modified by reference.
238 *
239 * @return void
240 */
241 protected static function set_categories( &$addon ) {
242 if ( ! isset( $addon['categories'] ) ) {
243 return;
244 }
245
246 $addon['category-slugs'] = array();
247
248 foreach ( $addon['categories'] as $category ) {
249 $category = FrmFormsHelper::convert_legacy_package_names( $category );
250 $category_slug = sanitize_title( $category );
251
252 // Add the slug to the new array.
253 $addon['category-slugs'][] = $category_slug;
254
255 if ( ! isset( self::$categories[ $category_slug ] ) ) {
256 self::$categories[ $category_slug ] = array(
257 'name' => $category,
258 'count' => 0,
259 );
260 }
261
262 ++self::$categories[ $category_slug ]['count'];
263 }
264 }
265
266 /**
267 * @return void
268 */
269 public static function license_settings() {
270 $plugins = apply_filters( 'frm_installed_addons', array() );
271
272 if ( ! $plugins ) {
273 esc_html_e( 'There are no plugins on your site that require a license', 'formidable' );
274 return;
275 }
276
277 ksort( $plugins );
278
279 include FrmAppHelper::plugin_path() . '/classes/views/addons/settings.php';
280 }
281
282 /**
283 * @return array
284 */
285 protected static function get_api_addons() {
286 $api = new FrmFormApi();
287 $addons = $api->get_api_info();
288
289 if ( ! $addons ) {
290 $addons = self::fallback_plugin_list();
291 } else {
292 foreach ( $addons as $k => $addon ) {
293 if ( empty( $addon['excerpt'] ) && $k !== 'error' ) {
294 unset( $addons[ $k ] );
295 }
296 }
297 }
298
299 return $addons;
300 }
301
302 /**
303 * Retrieves the count of available addons.
304 *
305 * @since 6.9
306 *
307 * @return int Count of addons.
308 */
309 public static function get_addons_count() {
310 $addons = self::get_api_addons();
311 return count( $addons );
312 }
313
314 /**
315 * If the API is unable to connect, show something on the addons page
316 *
317 * @since 3.04.03
318 *
319 * @return array
320 */
321 protected static function fallback_plugin_list() {
322 $list = array(
323 'formidable-pro' => array(
324 'title' => 'Formidable Forms Pro',
325 'link' => 'pricing/',
326 'docs' => '',
327 'excerpt' => 'Enhance your basic Formidable forms with a plethora of Pro field types and features. Create advanced forms and data-driven applications in minutes.',
328 ),
329 'mailchimp' => array(
330 'title' => 'Mailchimp Forms',
331 '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
332 ),
333 'registration' => array(
334 'title' => 'User Registration Forms',
335 'link' => 'downloads/user-registration/',
336 '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
337 ),
338 'paypal' => array(
339 'title' => 'PayPal Standard Forms',
340 'link' => 'downloads/paypal-standard/',
341 '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
342 ),
343 'stripe' => array(
344 'title' => 'Stripe Forms',
345 'docs' => 'knowledgebase/stripe/',
346 'excerpt' => 'Any Formidable forms on your site can accept credit card payments without users ever leaving your site.',
347 ),
348 'authorize-net' => array(
349 'title' => 'Authorize.net AIM Forms',
350 'link' => 'downloads/authorize-net-aim/',
351 'docs' => 'knowledgebase/authorize-net-aim/',
352 'excerpt' => 'Accept one-time payments directly on your site, using Authorize.net AIM.',
353 ),
354 'woocommerce' => array(
355 'title' => 'WooCommerce Forms',
356 'excerpt' => 'Use a Formidable form on your WooCommerce product pages.',
357 ),
358 'autoresponder' => array(
359 'title' => 'Form Action Automation',
360 'docs' => 'knowledgebase/schedule-autoresponder/',
361 'excerpt' => 'Schedule email notifications, SMS messages, and API actions.',
362 ),
363 'modal' => array(
364 'title' => 'Bootstrap Modal Forms',
365 'link' => 'downloads/bootstrap-modal/',
366 'docs' => 'knowledgebase/bootstrap-modal/',
367 'excerpt' => 'Open a view or form in a Bootstrap popup.',
368 ),
369 'bootstrap' => array(
370 'title' => 'Bootstrap Style Forms',
371 'excerpt' => 'Instantly add Bootstrap styling to all your Formidable forms.',
372 ),
373 'zapier' => array(
374 'title' => 'Zapier Forms',
375 '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
376 ),
377 'signature' => array(
378 'title' => 'Digital Signature Forms',
379 'excerpt' => 'Add a signature field to your form. The user may write their signature with a trackpad/mouse or just type it.',
380 ),
381 'api' => array(
382 'title' => 'Formidable Forms API',
383 'link' => 'downloads/formidable-api/',
384 '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.',
385 ),
386 'twilio' => array(
387 'title' => 'Twilio SMS Forms',
388 'docs' => 'knowledgebase/twilio-add-on/',
389 'excerpt' => 'Allow users to text their votes for polls created by Formidable Forms, or send SMS notifications when entries are submitted or updated.',
390 ),
391 'views' => array(
392 'title' => 'Formidable Views',
393 'excerpt' => 'Add the power of views to your Formidable Forms to display your form submissions in listings, tables, calendars, and more.',
394 ),
395 'quiz_maker' => array(
396 'title' => 'Quiz Maker',
397 'link' => 'downloads/quiz-maker/',
398 'excerpt' => 'Make quizzes, automatically score them and show user scores.',
399 ),
400 );
401
402 $defaults = array(
403 'released' => '',
404 );
405
406 foreach ( $list as $k => $info ) {
407 $info['slug'] = $k;
408 $list[ $k ] = array_merge( $defaults, $info );
409 }
410
411 return $list;
412 }
413
414 /**
415 * If Pro is missing but has been authenticated, include a download URL
416 *
417 * @since 3.04.03
418 *
419 * @return string
420 */
421 public static function get_pro_download_url() {
422 $license = self::get_pro_license();
423 $api = new FrmFormApi( $license );
424 $downloads = $api->get_api_info();
425 $pro = self::get_pro_from_addons( $downloads );
426
427 return $pro['url'] ?? '';
428 }
429
430 /**
431 * @since 4.08
432 *
433 * @return string
434 */
435 public static function get_pro_license() {
436 $pro_cred_store = 'frmpro-credentials';
437 $pro_wpmu_store = 'frmpro-wpmu-sitewide';
438
439 if ( is_multisite() && get_site_option( $pro_wpmu_store ) ) {
440 $creds = get_site_option( $pro_cred_store );
441 } else {
442 $creds = get_option( $pro_cred_store );
443 }
444
445 if ( empty( $creds ) || ! is_array( $creds ) || ! isset( $creds['license'] ) ) {
446 return '';
447 }
448
449 $license = $creds['license'];
450
451 if ( ! $license ) {
452 return '';
453 }
454
455 if ( str_contains( $license, '-' ) ) {
456 // This is a fix for licenses saved in the past
457 return strtoupper( $license );
458 }
459
460 return $license;
461 }
462
463 /**
464 * @since 4.08
465 *
466 * @param array $addons
467 *
468 * @return array
469 */
470 protected static function get_pro_from_addons( $addons ) {
471 return $addons['93790'] ?? array();
472 }
473
474 /**
475 * @since 4.06
476 *
477 * @return string
478 */
479 public static function license_type() {
480 if ( is_callable( 'FrmProAddonsController::license_type' ) ) {
481 return FrmProAddonsController::license_type();
482 }
483
484 return 'free';
485 }
486
487 /**
488 * @since 4.0.01
489 *
490 * @return bool
491 */
492 public static function is_license_expired() {
493 $version_info = self::get_primary_license_info();
494
495 if ( ! isset( $version_info['error'] ) ) {
496 return false;
497 }
498
499 $expires = $version_info['error']['expires'] ?? 0;
500
501 if ( ! $expires || $expires > time() ) {
502 return false;
503 }
504
505 $rate_limited = ! empty( $version_info['response_code'] ) && 429 === (int) $version_info['response_code'];
506
507 if ( $rate_limited ) {
508 // Do not return false positives for rate limited responses.
509 return false;
510 }
511
512 return $version_info['error'];
513 }
514
515 /**
516 * @since 4.08
517 * @since 6.7 This is public.
518 *
519 * @return array|false
520 */
521 public static function get_primary_license_info() {
522 $installed_addons = apply_filters( 'frm_installed_addons', array() );
523
524 if ( ! $installed_addons || ! isset( $installed_addons['formidable_pro'] ) ) {
525 return false;
526 }
527
528 $installed_addons = array(
529 'formidable_pro' => $installed_addons['formidable_pro'],
530 );
531
532 return self::fill_update_addon_info( $installed_addons );
533 }
534
535 /**
536 * @since 3.04.03
537 *
538 * @param mixed $transient
539 *
540 * @return object
541 */
542 public static function check_update( $transient ) {
543 if ( ! FrmAppHelper::pro_is_installed() ) {
544 // Don't make any changes if only Lite is installed.
545 return $transient;
546 }
547
548 if ( ! is_object( $transient ) ) {
549 $transient = new stdClass();
550 }
551
552 $installed_addons = apply_filters( 'frm_installed_addons', array() );
553
554 if ( ! $installed_addons ) {
555 return $transient;
556 }
557
558 $version_info = self::fill_update_addon_info( $installed_addons );
559 $transient->last_checked = time();
560 $wp_plugins = self::get_plugins();
561
562 foreach ( $version_info as $plugin ) {
563 $plugin = (object) $plugin;
564
565 if ( ! isset( $plugin->new_version ) || ! isset( $plugin->package ) ) {
566 continue;
567 }
568
569 $folder = $plugin->plugin;
570
571 if ( ! $folder ) {
572 continue;
573 }
574
575 if ( ! self::is_installed( $folder ) ) {
576 // Don't show an update if the plugin isn't installed
577 continue;
578 }
579
580 $wp_plugin = $wp_plugins[ $folder ] ?? array();
581 $wp_version = $wp_plugin['Version'] ?? '1.0';
582 $plugin->slug = explode( '/', $folder )[0];
583
584 if ( version_compare( $wp_version, $plugin->new_version, '<' ) ) {
585 $transient->response[ $folder ] = $plugin;
586 } else {
587 $transient->no_update[ $folder ] = $plugin;
588 }
589
590 $transient->checked[ $folder ] = $wp_version;
591 }//end foreach
592
593 return $transient;
594 }
595
596 /**
597 * Copy of FrmAppHelper::get_plugins.
598 * Because this gets called on "pre_set_site_transient_update_plugins" an old version of FrmAppHelper may be loaded on plugin update.
599 * This means that trying to access FrmAppHelper::get_plugins when upgrading from a Lite version before v5.5 results in a one-off error.
600 *
601 * @since 5.5.2
602 *
603 * @return array
604 */
605 protected static function get_plugins() {
606 if ( ! function_exists( 'get_plugins' ) ) {
607 require_once ABSPATH . 'wp-admin/includes/plugin.php';
608 }
609 return get_plugins();
610 }
611
612 /**
613 * Check if a plugin is installed before showing an update for it
614 *
615 * @since 3.05
616 *
617 * @param string $plugin The folder/filename.php for a plugin.
618 *
619 * @return bool - True if installed
620 */
621 protected static function is_installed( $plugin ) {
622 $all_plugins = self::get_plugins();
623 return isset( $all_plugins[ $plugin ] );
624 }
625
626 /**
627 * @since 3.04.03
628 *
629 * @param array $installed_addons
630 *
631 * @return array
632 */
633 protected static function fill_update_addon_info( $installed_addons ) {
634 $checked_licenses = array();
635 $version_info = array();
636
637 foreach ( $installed_addons as $addon ) {
638 if ( $addon->store_url !== 'https://formidableforms.com' ) {
639 // Check if this is a third-party addon
640 continue;
641 }
642
643 $new_license = $addon->license;
644
645 if ( ! $new_license || in_array( $new_license, $checked_licenses, true ) ) {
646 continue;
647 }
648
649 $checked_licenses[] = $new_license;
650 $api = new FrmFormApi( $new_license );
651
652 if ( ! $version_info ) {
653 $version_info = $api->get_api_info();
654 continue;
655 }
656
657 $plugin = $api->get_addon_for_license( $addon, $version_info );
658
659 if ( ! $plugin ) {
660 continue;
661 }
662
663 $download_id = $plugin['id'] ?? 0;
664
665 if ( ! $download_id || isset( $version_info[ $download_id ]['package'] ) ) {
666 continue;
667 }
668
669 // If this addon is using its own license, get the update url
670 $addon_info = $api->get_api_info();
671
672 $version_info[ $download_id ] = $addon_info[ $download_id ];
673
674 if ( isset( $addon_info['error'] ) ) {
675 $version_info[ $download_id ]['error'] = array(
676 'message' => $addon_info['error']['message'],
677 'code' => $addon_info['error']['code'],
678 );
679 }
680 }//end foreach
681
682 return $version_info;
683 }
684
685 /**
686 * Get the action link for an addon that isn't active.
687 *
688 * @since 3.06.03
689 *
690 * @param string $plugin The plugin slug.
691 *
692 * @return array
693 */
694 public static function install_link( $plugin ) {
695 $link = array();
696 $addon = self::get_addon( $plugin );
697
698 if ( $addon ) {
699 if ( $addon['status']['type'] === 'installed' && ! empty( $addon['activate_url'] ) ) {
700 $link = array(
701 'url' => $addon['plugin'],
702 'class' => 'frm-activate-addon',
703 );
704 } elseif ( ! empty( $addon['url'] ) ) {
705 $link = array(
706 'url' => $addon['url'],
707 'class' => 'frm-install-addon',
708 );
709 } elseif ( ! empty( $addon['categories'] ) ) {
710 $link = array(
711 'categories' => $addon['categories'],
712 );
713 }
714
715 if ( $link ) {
716 $link['status'] = $addon['status']['type'];
717 }
718 } elseif ( current_user_can( 'activate_plugins' ) && self::is_installed( 'formidable-' . $plugin . '/formidable-' . $plugin . '.php' ) ) {
719 $link = array(
720 'url' => 'formidable-' . $plugin . '/formidable-' . $plugin . '.php',
721 'class' => 'frm-activate-addon',
722 );
723 }//end if
724
725 return $link;
726 }
727
728 /**
729 * @since 4.09
730 *
731 * @param string $plugin The plugin slug.
732 *
733 * @return array|false
734 */
735 public static function get_addon( $plugin ) {
736 $addons = self::get_api_addons();
737 self::prepare_addons( $addons );
738
739 foreach ( $addons as $addon ) {
740 $slug = explode( '/', $addon['plugin'] );
741
742 if ( $slug[0] === 'formidable-' . $plugin ) {
743 return $addon;
744 }
745 }
746
747 return false;
748 }
749
750 /**
751 * @since 4.09
752 *
753 * @return string
754 */
755 protected static function get_license_type() {
756 $license_type = '';
757 $addons = self::get_api_addons();
758
759 if ( isset( $addons['error'] ) && isset( $addons['error']['type'] ) ) {
760 return $addons['error']['type'];
761 }
762
763 return $license_type;
764 }
765
766 /**
767 * @since 3.04.03
768 *
769 * @param array $addons
770 * @param object $license The FrmAddon object.
771 *
772 * @return array
773 */
774 public static function get_addon_for_license( $addons, $license ) {
775 $download_id = $license->download_id;
776 $plugin = array();
777
778 if ( ! $download_id && $addons ) {
779 foreach ( $addons as $addon ) {
780 if ( 0 === strcasecmp( $license->plugin_name, $addon['title'] ) ) {
781 return $addon;
782 }
783 }
784 } elseif ( isset( $addons[ $download_id ] ) ) {
785 $plugin = $addons[ $download_id ];
786 }
787
788 return $plugin;
789 }
790
791 /**
792 * @param array $addons
793 *
794 * @return void
795 */
796 protected static function prepare_addons( &$addons ) {
797 // Reset categories to prevent count accumulation across multiple calls.
798 self::$categories = array();
799
800 $activate_url = '';
801
802 if ( current_user_can( 'activate_plugins' ) ) {
803 $activate_url = add_query_arg( array( 'action' => 'activate' ), admin_url( 'plugins.php' ) );
804 }
805
806 $loop_addons = $addons;
807
808 foreach ( $loop_addons as $id => $addon ) {
809 if ( is_numeric( $id ) ) {
810 $slug = str_replace( array( '-wordpress-plugin', '-wordpress' ), '', $addon['slug'] );
811 $file_name = $addon['plugin'];
812 } else {
813 $slug = $id;
814 $base_file = $addon['file'] ?? 'formidable-' . $slug;
815 $file_name = $base_file . '/' . $base_file . '.php';
816
817 if ( ! isset( $addon['plugin'] ) ) {
818 $addon['plugin'] = $file_name;
819 }
820 }
821
822 $addon['installed'] = self::is_installed( $file_name );
823
824 if ( $addon['installed'] && 'formidable-views/formidable-views.php' === $file_name ) {
825 $active_views_version = self::get_active_views_version();
826
827 if ( false !== $active_views_version && $slug !== $active_views_version ) {
828 $addon['installed'] = false;
829 }
830 }
831
832 $addon['activate_url'] = '';
833
834 if ( $addon['installed'] && $activate_url && ! self::is_plugin_active( $file_name, $slug ) ) {
835 $addon['activate_url'] = add_query_arg(
836 array(
837 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $file_name ),
838 'plugin' => $file_name,
839 ),
840 $activate_url
841 );
842 }
843
844 if ( ! isset( $addon['docs'] ) ) {
845 $addon['docs'] = 'knowledgebase/formidable-' . $slug . '/';
846 }
847 self::prepare_addon_link( $addon['docs'] );
848
849 if ( ! isset( $addon['link'] ) ) {
850 $addon['link'] = 'downloads/' . $slug . '/';
851 }
852
853 self::prepare_addon_link( $addon['link'] );
854 self::set_addon_status( $addon );
855 self::set_categories( $addon );
856
857 $addons[ $id ] = $addon;
858 }//end foreach
859 }
860
861 /**
862 * @param string $file_name
863 * @param string $slug
864 *
865 * @return bool
866 */
867 private static function is_plugin_active( $file_name, $slug ) {
868 if ( 'formidable-views/formidable-views.php' === $file_name ) {
869 return self::get_active_views_version() === $slug;
870 }
871 return is_plugin_active( $file_name );
872 }
873
874 /**
875 * @return false|string either 'visual-views' or 'views', false if one is not found.
876 */
877 private static function get_active_views_version() {
878 if ( ! is_callable( 'FrmViewsAppHelper::plugin_version' ) ) {
879 return false;
880 }
881
882 $plugin_version = FrmViewsAppHelper::plugin_version();
883 return version_compare( $plugin_version, '5.0', '>=' ) ? 'visual-views' : 'views';
884 }
885
886 /**
887 * @since 3.04.02
888 *
889 * @param string $link
890 *
891 * @return void
892 */
893 protected static function prepare_addon_link( &$link ) {
894 $site_url = 'https://formidableforms.com/';
895
896 if ( ! str_starts_with( $link, 'http' ) ) {
897 $link = $site_url . $link;
898 }
899
900 $link = FrmAppHelper::make_affiliate_url( $link );
901
902 $utm = array(
903 'campaign' => 'addons',
904 );
905 $link = FrmAppHelper::maybe_add_missing_utm( $link, $utm );
906 }
907
908 /**
909 * Add the status to the addon array. Status options are:
910 * installed, active, not installed
911 *
912 * @since 3.04.02
913 *
914 * @param array $addon
915 *
916 * @return void
917 */
918 protected static function set_addon_status( &$addon ) {
919 if ( ! empty( $addon['activate_url'] ) ) {
920 $addon['status'] = array(
921 'type' => 'installed',
922 'label' => __( 'Installed', 'formidable' ),
923 );
924 } elseif ( $addon['installed'] ) {
925 $addon['status'] = array(
926 'type' => 'active',
927 'label' => __( 'Active', 'formidable' ),
928 );
929 } else {
930 $addon['status'] = array(
931 'type' => 'not-installed',
932 'label' => __( 'Not Installed', 'formidable' ),
933 );
934 }
935 }
936
937 /**
938 * @since 5.5.2
939 *
940 * @param string $plugin
941 * @param string $redirect
942 * @param bool $network_wide
943 * @param bool $silent
944 *
945 * @return WP_Error|null Null on success, WP_Error on invalid file.
946 */
947 protected static function activate_plugin( $plugin, $redirect = '', $network_wide = false, $silent = false ) {
948 if ( ! function_exists( 'activate_plugin' ) ) {
949 require_once ABSPATH . 'wp-admin/includes/plugin.php';
950 }
951 return activate_plugin( $plugin, $redirect, $network_wide, $silent );
952 }
953
954 /**
955 * Deactivate a specified plugin.
956 *
957 * @since 6.8
958 *
959 * @param string $plugin
960 * @param bool $silent
961 *
962 * @return void
963 */
964 protected static function deactivate_plugin( $plugin, $silent = false ) {
965 if ( ! function_exists( 'deactivate_plugins' ) ) {
966 require_once ABSPATH . 'wp-admin/includes/plugin.php';
967 }
968 deactivate_plugins( $plugin, $silent );
969 }
970
971 /**
972 * Uninstall a specified plugin.
973 *
974 * @since 6.8
975 *
976 * @param string $plugin
977 *
978 * @return true|WP_Error True on success, WP_Error on invalid file.
979 */
980 protected static function uninstall_plugin( $plugin ) {
981 if ( ! current_user_can( 'delete_plugins' ) ) {
982 return new WP_Error( 'uninstall_failed', __( 'Current user cannot delete plugins.', 'formidable' ) );
983 }
984
985 if ( ! function_exists( 'delete_plugins' ) ) {
986 require_once ABSPATH . 'wp-admin/includes/plugin.php';
987 }
988
989 self::deactivate_plugin( $plugin, true );
990 $result = delete_plugins( array( $plugin ) );
991
992 return is_wp_error( $result ) ? $result : true;
993 }
994
995 /**
996 * @since 5.0.10
997 *
998 * @return string
999 */
1000 protected static function get_current_plugin() {
1001 if ( empty( self::$plugin ) ) {
1002 self::$plugin = FrmAppHelper::get_param( 'plugin', '', 'post', 'esc_url_raw' );
1003 }
1004 return self::$plugin;
1005 }
1006
1007 /**
1008 * @since 4.08
1009 *
1010 * @return mixed[]|null
1011 */
1012 protected static function download_and_activate() {
1013 if ( is_admin() ) {
1014 FrmAppHelper::set_current_screen_and_hook_suffix();
1015 }
1016
1017 self::maybe_show_cred_form();
1018
1019 $installed = self::install_addon();
1020
1021 if ( is_array( $installed ) && isset( $installed['message'] ) ) {
1022 return $installed;
1023 }
1024 self::handle_addon_action( $installed, 'activate' );
1025 return null;
1026 }
1027
1028 /**
1029 * @since 3.04.02
1030 *
1031 * @return void
1032 */
1033 protected static function maybe_show_cred_form() {
1034 if ( ! function_exists( 'request_filesystem_credentials' ) ) {
1035 include_once ABSPATH . 'wp-admin/includes/file.php';
1036 }
1037
1038 // Start output buffering to catch the filesystem form if credentials are needed.
1039 ob_start();
1040
1041 $show_form = false;
1042 $method = '';
1043 $url = add_query_arg( array( 'page' => 'formidable-settings' ), admin_url( 'admin.php' ) );
1044 $url = esc_url_raw( $url );
1045 $creds = request_filesystem_credentials( $url, $method, false, false, null );
1046
1047 if ( false === $creds ) {
1048 $show_form = true;
1049 } elseif ( ! WP_Filesystem( $creds ) ) {
1050 request_filesystem_credentials( $url, $method, true, false, null );
1051 $show_form = true;
1052 }
1053
1054 if ( $show_form ) {
1055 $form = ob_get_clean();
1056 $message = __( 'Sorry, your site requires FTP authentication. Please download plugins from FormidableForms.com and install them manually.', 'formidable' );
1057 $response = array(
1058 'success' => false,
1059 'message' => $message,
1060 'form' => $form,
1061 );
1062 wp_send_json( $response );
1063 }
1064
1065 ob_end_clean();
1066 }
1067
1068 /**
1069 * Checks if an addon download url is allowed.
1070 *
1071 * @since 6.2
1072 *
1073 * @param string $download_url
1074 *
1075 * @return bool
1076 */
1077 public static function url_is_allowed( $download_url ) {
1078 return FrmAppHelper::validate_url_is_in_s3_bucket( $download_url, 'zip' ) || in_array( $download_url, self::allowed_external_urls(), true );
1079 }
1080
1081 /**
1082 * We do not need any extra credentials if we have gotten this far,
1083 * so let's install the plugin.
1084 *
1085 * @since 3.04.02
1086 *
1087 * @return array|string
1088 */
1089 protected static function install_addon() {
1090 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1091
1092 $download_url = self::get_current_plugin();
1093
1094 if ( ! self::url_is_allowed( $download_url ) ) {
1095 return array(
1096 'message' => 'Plugin URL is not valid',
1097 'success' => false,
1098 );
1099 }
1100
1101 // Create the plugin upgrader with our custom skin.
1102 $installer = new Plugin_Upgrader( new FrmInstallerSkin() );
1103 $installer->install( $download_url );
1104
1105 // Flush the cache and return the newly installed plugin basename.
1106 wp_cache_flush();
1107
1108 $plugin = $installer->plugin_info();
1109
1110 if ( ! $plugin ) {
1111 return array(
1112 'message' => 'Plugin was not installed. ' . $installer->result,
1113 'success' => false,
1114 );
1115 }
1116
1117 return $plugin;
1118 }
1119
1120 /**
1121 * Handle the AJAX request to activate an add-on.
1122 *
1123 * @since 6.8
1124 *
1125 * @return void
1126 */
1127 public static function ajax_activate_addon() {
1128 self::process_addon_action(
1129 function ( $plugin ) {
1130 return self::handle_addon_action( $plugin, 'activate' );
1131 },
1132 array( 'FrmAddonsController', 'get_addon_activation_response' )
1133 );
1134 }
1135
1136 /**
1137 * @since 3.04.02
1138 *
1139 * @param string $installed The plugin folder name with file name.
1140 */
1141 protected static function maybe_activate_addon( $installed ) {
1142 self::ajax_activate_addon();
1143 }
1144
1145 /**
1146 * Handle the AJAX request to deactivate an add-on.
1147 *
1148 * @since 6.8
1149 *
1150 * @return void
1151 */
1152 public static function ajax_deactivate_addon() {
1153 self::process_addon_action(
1154 function ( $plugin ) {
1155 return self::handle_addon_action( $plugin, 'deactivate' );
1156 }
1157 );
1158 }
1159
1160 /**
1161 * Handle the AJAX request to uninstall an add-on.
1162 *
1163 * @since 6.8
1164 *
1165 * @return void
1166 */
1167 public static function ajax_uninstall_addon() {
1168 self::process_addon_action(
1169 function ( $plugin ) {
1170 return self::handle_addon_action( $plugin, 'uninstall' );
1171 }
1172 );
1173 }
1174
1175 /**
1176 * Process a specific action (activate, deactivate, uninstall) on an add-on.
1177 *
1178 * @since 6.8
1179 *
1180 * @param callable $action_callback The specific add-on action to be executed.
1181 * @param callable|null $response_callback Optional. The response handling callback. Default null.
1182 *
1183 * @return void
1184 */
1185 private static function process_addon_action( $action_callback, $response_callback = null ) {
1186 self::install_addon_permissions();
1187 FrmAppHelper::set_current_screen_and_hook_suffix();
1188
1189 $plugin = FrmAppHelper::get_param( 'plugin', '', 'post', 'sanitize_text_field' );
1190 call_user_func( $action_callback, $plugin );
1191
1192 if ( is_callable( $response_callback ) ) {
1193 wp_send_json_success( call_user_func( $response_callback ) );
1194 return;
1195 }
1196
1197 wp_send_json_success();
1198 }
1199
1200 /**
1201 * Attempt to perform a specific action (activate, deactivate, uninstall) on an add-on.
1202 *
1203 * @since 6.8
1204 *
1205 * @param string $installed The plugin folder name with file name.
1206 * @param string $action The action type ('activate', 'deactivate', 'uninstall').
1207 *
1208 * @return mixed[]|null
1209 */
1210 protected static function handle_addon_action( $installed, $action ) {
1211 if ( ! $installed || ! $action ) {
1212 return null;
1213 }
1214
1215 $result = null;
1216 switch ( $action ) {
1217 case 'activate':
1218 $result = self::activate_plugin( $installed );
1219 break;
1220 case 'deactivate':
1221 self::deactivate_plugin( $installed );
1222 break;
1223 case 'uninstall':
1224 $result = self::uninstall_plugin( $installed );
1225 break;
1226 }
1227
1228 if ( is_wp_error( $result ) ) {
1229 // Ignore the invalid header message that shows with nested plugins.
1230 if ( $result->get_error_code() !== 'no_plugin_header' ) {
1231 if ( wp_doing_ajax() ) {
1232 wp_send_json_error( array( 'error' => $result->get_error_message() ) );
1233 }
1234
1235 return array(
1236 'message' => $result->get_error_message(),
1237 'success' => false,
1238 );
1239 }
1240 }
1241
1242 return $result;
1243 }
1244
1245 /**
1246 * @return array
1247 */
1248 private static function get_addon_activation_response() {
1249 $activating_page = self::get_activating_page();
1250 $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
1251
1252 return array(
1253 'message' => $message,
1254 'saveAndReload' => $activating_page,
1255 );
1256 }
1257
1258 /**
1259 * Return a string that reflects the page from which the addon is being activated on,
1260 * if it is from settings or form builder, otherwise return empty string.
1261 *
1262 * @return string
1263 */
1264 private static function get_activating_page() {
1265 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
1266
1267 if ( str_contains( $referer, 'frm_action=settings' ) ) {
1268 return 'settings';
1269 }
1270
1271 return str_contains( $referer, 'frm_action=edit' ) ? 'form_builder' : '';
1272 }
1273
1274 /**
1275 * Run security checks before installing
1276 *
1277 * @since 3.04.02
1278 *
1279 * @return void
1280 */
1281 protected static function install_addon_permissions() {
1282 check_ajax_referer( 'frm_ajax', 'nonce' );
1283
1284 if ( current_user_can( 'activate_plugins' ) && self::get_current_plugin() ) {
1285 return;
1286 }
1287
1288 echo json_encode( true );
1289 wp_die();
1290 }
1291
1292 /**
1293 * @since 4.08
1294 *
1295 * @return string
1296 */
1297 public static function connect_link() {
1298 $auth = get_option( 'frm_connect_token' );
1299
1300 if ( ! $auth ) {
1301 $auth = hash( 'sha512', wp_rand() );
1302 update_option( 'frm_connect_token', $auth, false );
1303 }
1304
1305 $page = FrmAppHelper::simple_get( 'page', 'sanitize_title', 'formidable-settings' );
1306 $link = 'https://formidableforms.com/api-connect/';
1307 $args = array(
1308 'v' => 2,
1309 'siteurl' => FrmAppHelper::site_url(),
1310 'url' => get_rest_url(),
1311 'inst' => (int) FrmAppHelper::pro_is_included(),
1312 'return' => $page,
1313 'token' => $auth,
1314 'l' => self::get_pro_license(),
1315 );
1316
1317 return add_query_arg( $args, $link );
1318 }
1319
1320 /**
1321 * Check the auth value for install permission.
1322 *
1323 * @since 4.08
1324 *
1325 * @return bool
1326 */
1327 public static function can_install_addon_api() {
1328 // Verify params present (auth & download link).
1329 $post_auth = FrmAppHelper::get_param( 'token', '', 'request', 'sanitize_text_field' );
1330 $post_url = FrmAppHelper::get_param( 'file_url', '', 'request', 'sanitize_text_field' );
1331
1332 // The download link is not required if already installed.
1333 $is_installed = FrmAppHelper::pro_is_included();
1334 $file_missing = ! $is_installed && ! $post_url;
1335
1336 if ( ! $post_auth || $file_missing ) {
1337 return false;
1338 }
1339
1340 // Verify auth.
1341 $auth = get_option( 'frm_connect_token' );
1342 return $auth && hash_equals( $auth, $post_auth );
1343 }
1344
1345 /**
1346 * Install and/or activate the add-on file.
1347 *
1348 * @since 4.08
1349 *
1350 * @return array
1351 */
1352 public static function install_addon_api() {
1353 self::$plugin = FrmAppHelper::get_param( 'file_url', '', 'request', 'esc_url_raw' );
1354
1355 // Delete so cannot replay.
1356 delete_option( 'frm_connect_token' );
1357
1358 // It's already installed and active.
1359 $active = self::activate_plugin( 'formidable-pro/formidable-pro.php', false, false, true );
1360
1361 if ( is_wp_error( $active ) ) {
1362 $response = self::maybe_download_and_activate();
1363
1364 if ( is_array( $response ) ) {
1365 // The download failed.
1366 return $response;
1367 }
1368 }
1369
1370 // If empty license, save it now.
1371 if ( ! self::get_pro_license() && function_exists( 'load_formidable_pro' ) ) {
1372 load_formidable_pro();
1373 $license = stripslashes( FrmAppHelper::get_param( 'key', '', 'request', 'sanitize_text_field' ) );
1374
1375 if ( ! $license ) {
1376 return array(
1377 'success' => false,
1378 'error' => 'That site does not have a valid license key.',
1379 );
1380 }
1381
1382 $response = FrmAddon::activate_license_for_plugin( $license, 'formidable_pro' );
1383
1384 if ( ! $response['success'] ) {
1385 // Could not activate license.
1386 return $response;
1387 }
1388 }
1389
1390 return array(
1391 'success' => true,
1392 );
1393 }
1394
1395 /**
1396 * @since 6.8.3
1397 *
1398 * @return array|true
1399 */
1400 private static function maybe_download_and_activate() {
1401 if ( ! self::$plugin ) {
1402 return array(
1403 'success' => false,
1404 'message' => __( 'The plugin download was not found.', 'formidable' ),
1405 );
1406 }
1407
1408 // Download plugin now.
1409 $response = self::download_and_activate();
1410
1411 if ( is_array( $response ) && isset( $response['success'] ) ) {
1412 // The download failed.
1413 return $response;
1414 }
1415
1416 return true;
1417 }
1418
1419 /**
1420 * Render a conditional action button for a specified plugin
1421 *
1422 * @since 4.09
1423 *
1424 * @param string $plugin
1425 * @param array|string $upgrade_link_args
1426 *
1427 * @return void
1428 */
1429 public static function conditional_action_button( $plugin, $upgrade_link_args ) {
1430 if ( is_callable( 'FrmProAddonsController::conditional_action_button' ) ) {
1431 FrmProAddonsController::conditional_action_button( $plugin, $upgrade_link_args );
1432 return;
1433 }
1434
1435 $addon = self::get_addon( $plugin );
1436 $upgrade_link = FrmAppHelper::admin_upgrade_link( $upgrade_link_args );
1437
1438 if ( ! is_array( $upgrade_link_args ) ) {
1439 // A string $upgrade_link_args is used for the utm-medium value when calling
1440 // FrmAppHelper::admin_upgrade_link above.
1441 // For self::addon_upgrade_link, we'll pass just an empty array with the link key (set below).
1442 $upgrade_link_args = array();
1443 }
1444
1445 $upgrade_link_args['link'] = $upgrade_link;
1446
1447 self::addon_upgrade_link( $addon, $upgrade_link_args );
1448 }
1449
1450 /**
1451 * Render a conditional action button for an add on
1452 *
1453 * @since 4.09.01
1454 *
1455 * @param array $atts {
1456 * Button attributes.
1457 *
1458 * @type array $addon
1459 * @type false|string $license_type
1460 * @type string $plan_required
1461 * @type string $upgrade_link
1462 * }
1463 *
1464 * @return void
1465 */
1466 public static function show_conditional_action_button( $atts ) {
1467 if ( is_callable( 'FrmProAddonsController::show_conditional_action_button' ) ) {
1468 FrmProAddonsController::show_conditional_action_button( $atts );
1469 return;
1470 }
1471
1472 self::addon_upgrade_link( $atts['addon'], $atts['upgrade_link'] );
1473 }
1474
1475 /**
1476 * @since 4.09.01
1477 *
1478 * @param array|false $addon
1479 * @param array|string $upgrade_link
1480 *
1481 * @return void
1482 */
1483 public static function addon_upgrade_link( $addon, $upgrade_link ) {
1484 $atts = is_array( $upgrade_link ) ? $upgrade_link : array();
1485 $upgrade_link = is_array( $upgrade_link ) ? $upgrade_link['link'] : $upgrade_link;
1486 $text = ! empty( $atts['text'] ) ? $atts['text'] : __( 'Upgrade Now', 'formidable' );
1487
1488 if ( $addon ) {
1489 $upgrade_link .= '&utm_content=' . $addon['slug'];
1490 }
1491
1492 if ( $addon && isset( $addon['categories'] ) && in_array( 'Solution', $addon['categories'], true ) ) {
1493 // Solutions will go to a separate page.
1494 $upgrade_link = FrmAppHelper::admin_upgrade_link( 'addons', $addon['link'] );
1495 }
1496
1497 $class = ! empty( $atts['class'] ) ? $atts['class'] : '';
1498
1499 if ( ! str_contains( $class, 'frm-button' ) ) {
1500 $class .= ' frm-button-secondary frm-button-sm';
1501 }
1502 // phpcs:disable Generic.WhiteSpace.ScopeIndent
1503 ?>
1504 <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 ?>
1505 <?php echo esc_html( $text ); ?>
1506 </a>
1507 <?php
1508 // phpcs:enable Generic.WhiteSpace.ScopeIndent
1509 }
1510
1511 /**
1512 * @since 3.04.02
1513 *
1514 * @return void
1515 */
1516 public static function ajax_install_addon() {
1517 self::install_addon_permissions();
1518
1519 $result = self::download_and_activate();
1520
1521 if ( isset( $result['success'] ) && ! $result['success'] ) {
1522 echo json_encode( $result );
1523 wp_die();
1524 }
1525
1526 echo json_encode( self::get_addon_activation_response() );
1527 wp_die();
1528 }
1529
1530 /**
1531 * Allowed URLs used for internal source of plugins installation.
1532 *
1533 * @since 6.3.1
1534 *
1535 * @return array
1536 */
1537 private static function allowed_external_urls() {
1538 $allowed_url_list = array(
1539 'https://downloads.wordpress.org/plugin/formidable-gravity-forms-importer.zip',
1540 'https://downloads.wordpress.org/plugin/formidable-import-pirate-forms.zip',
1541 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip',
1542 );
1543
1544 /**
1545 * List of URLs used in plugin formidable internal installation.
1546 *
1547 * @since 6.3.1
1548 *
1549 * @param array $allowed_url_list List of URLs.
1550 */
1551 $allowed_url_list = apply_filters( 'frm_allowed_external_urls', $allowed_url_list );
1552
1553 if ( ! is_array( $allowed_url_list ) ) {
1554 _doing_it_wrong( __METHOD__, 'Only an array of URLs could be used within this filter.', '6.3.1' );
1555 return array();
1556 }
1557
1558 return $allowed_url_list;
1559 }
1560
1561 /**
1562 * Gets required plan for an addon.
1563 *
1564 * @since 6.4.2
1565 *
1566 * @param int|string $addon_id
1567 *
1568 * @return string Empty string if no plan is required for active license.
1569 */
1570 public static function get_addon_required_plan( $addon_id ) {
1571 $api = new FrmFormApi();
1572 $addons = $api->get_api_info();
1573
1574 if ( is_array( $addons ) && array_key_exists( $addon_id, $addons ) ) {
1575 $dates = $addons[ $addon_id ];
1576 $requires = FrmFormsHelper::get_plan_required( $dates );
1577 }
1578
1579 if ( ! isset( $requires ) || ! is_string( $requires ) ) {
1580 return '';
1581 }
1582
1583 return $requires;
1584 }
1585 }
1586