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

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