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

1,671 lines 43.5 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 the API's determine_status_from_license_details logic.
495 *
496 * @since 6.31
497 *
498 * @return string 'active', 'expired', or 'free'.
499 */
500 public static function get_payment_license_status() {
501 $version_info = self::get_primary_license_info();
502
503 if ( ! $version_info ) {
504 return 'free';
505 }
506
507 $error = $version_info['error'] ?? array();
508
509 if ( is_array( $error ) ) {
510 $code = $error['code'] ?? '';
511
512 if ( 'expired' === $code ) {
513 return 'expired';
514 }
515
516 if ( 'grandfathered' === $code && isset( $error['expires'] ) && gmdate( 'Y-m-d', $error['expires'] ) < '2016-04-26' ) {
517 return 'free';
518 }
519 }
520
521 if ( in_array( self::license_type( true ), array( 'elite', 'business' ), true ) ) {
522 return 'active';
523 }
524
525 return 'free';
526 }
527
528 /**
529 * @since 4.0.01
530 *
531 * @return bool
532 */
533 public static function is_license_expired() {
534 $version_info = self::get_primary_license_info();
535
536 if ( ! isset( $version_info['error'] ) ) {
537 return false;
538 }
539
540 $expires = $version_info['error']['expires'] ?? 0;
541
542 if ( ! $expires || $expires > time() ) {
543 return false;
544 }
545
546 $rate_limited = ! empty( $version_info['response_code'] ) && 429 === (int) $version_info['response_code'];
547
548 if ( $rate_limited ) {
549 // Do not return false positives for rate limited responses.
550 return false;
551 }
552
553 return $version_info['error'];
554 }
555
556 /**
557 * @since 4.08
558 * @since 6.7 This is public.
559 *
560 * @return array|false
561 */
562 public static function get_primary_license_info() {
563 $installed_addons = apply_filters( 'frm_installed_addons', array() );
564
565 if ( ! $installed_addons || ! isset( $installed_addons['formidable_pro'] ) ) {
566 return false;
567 }
568
569 $installed_addons = array(
570 'formidable_pro' => $installed_addons['formidable_pro'],
571 );
572
573 return self::fill_update_addon_info( $installed_addons );
574 }
575
576 /**
577 * @since 3.04.03
578 *
579 * @param mixed $transient
580 *
581 * @return object
582 */
583 public static function check_update( $transient ) {
584 if ( ! FrmAppHelper::pro_is_installed() ) {
585 // Don't make any changes if only Lite is installed.
586 return $transient;
587 }
588
589 if ( ! is_object( $transient ) ) {
590 $transient = new stdClass();
591 }
592
593 $installed_addons = apply_filters( 'frm_installed_addons', array() );
594
595 if ( ! $installed_addons ) {
596 return $transient;
597 }
598
599 $version_info = self::fill_update_addon_info( $installed_addons );
600 $transient->last_checked = time();
601 $wp_plugins = self::get_plugins();
602
603 foreach ( $version_info as $plugin ) {
604 $plugin = (object) $plugin;
605
606 if ( ! isset( $plugin->new_version ) || ! isset( $plugin->package ) ) {
607 continue;
608 }
609
610 $folder = $plugin->plugin;
611
612 if ( ! $folder ) {
613 continue;
614 }
615
616 if ( ! self::is_installed( $folder ) ) {
617 // Don't show an update if the plugin isn't installed
618 continue;
619 }
620
621 $wp_plugin = $wp_plugins[ $folder ] ?? array();
622 $wp_version = $wp_plugin['Version'] ?? '1.0';
623 $plugin->slug = explode( '/', $folder )[0];
624
625 if ( version_compare( $wp_version, $plugin->new_version, '<' ) ) {
626 $transient->response[ $folder ] = $plugin;
627 } else {
628 $transient->no_update[ $folder ] = $plugin;
629 }
630
631 $transient->checked[ $folder ] = $wp_version;
632 }//end foreach
633
634 return $transient;
635 }
636
637 /**
638 * Copy of FrmAppHelper::get_plugins.
639 * Because this gets called on "pre_set_site_transient_update_plugins" an old version of FrmAppHelper may be loaded on plugin update.
640 * This means that trying to access FrmAppHelper::get_plugins when upgrading from a Lite version before v5.5 results in a one-off error.
641 *
642 * @since 5.5.2
643 *
644 * @return array
645 */
646 protected static function get_plugins() {
647 if ( ! function_exists( 'get_plugins' ) ) {
648 require_once ABSPATH . 'wp-admin/includes/plugin.php';
649 }
650 return get_plugins();
651 }
652
653 /**
654 * Check if a plugin is installed before showing an update for it
655 *
656 * @since 3.05
657 *
658 * @param string $plugin The folder/filename.php for a plugin.
659 *
660 * @return bool - True if installed
661 */
662 protected static function is_installed( $plugin ) {
663 $all_plugins = self::get_plugins();
664 return isset( $all_plugins[ $plugin ] );
665 }
666
667 /**
668 * @since 3.04.03
669 *
670 * @param array $installed_addons
671 *
672 * @return array
673 */
674 protected static function fill_update_addon_info( $installed_addons ) {
675 $checked_licenses = array();
676 $version_info = array();
677
678 foreach ( $installed_addons as $addon ) {
679 if ( $addon->store_url !== 'https://formidableforms.com' ) {
680 // Check if this is a third-party addon
681 continue;
682 }
683
684 $new_license = $addon->license;
685
686 if ( ! $new_license || in_array( $new_license, $checked_licenses, true ) ) {
687 continue;
688 }
689
690 $checked_licenses[] = $new_license;
691 $api = new FrmFormApi( $new_license );
692
693 if ( ! $version_info ) {
694 $version_info = $api->get_api_info();
695 continue;
696 }
697
698 $plugin = $api->get_addon_for_license( $addon, $version_info );
699
700 if ( ! $plugin ) {
701 continue;
702 }
703
704 $download_id = $plugin['id'] ?? 0;
705
706 if ( ! $download_id || isset( $version_info[ $download_id ]['package'] ) ) {
707 continue;
708 }
709
710 // If this addon is using its own license, get the update url
711 $addon_info = $api->get_api_info();
712
713 $version_info[ $download_id ] = $addon_info[ $download_id ];
714
715 if ( isset( $addon_info['error'] ) ) {
716 $version_info[ $download_id ]['error'] = array(
717 'message' => $addon_info['error']['message'],
718 'code' => $addon_info['error']['code'],
719 );
720 }
721 }//end foreach
722
723 return $version_info;
724 }
725
726 /**
727 * Get the action link for an addon that isn't active.
728 *
729 * @since 3.06.03
730 *
731 * @param string $plugin The plugin slug.
732 *
733 * @return array
734 */
735 public static function install_link( $plugin ) {
736 $link = array();
737 $addon = self::get_addon( $plugin );
738
739 if ( $addon ) {
740 if ( $addon['status']['type'] === 'installed' && ! empty( $addon['activate_url'] ) ) {
741 $link = array(
742 'url' => $addon['plugin'],
743 'class' => 'frm-activate-addon',
744 );
745 } elseif ( ! empty( $addon['url'] ) ) {
746 $link = array(
747 'url' => $addon['url'],
748 'class' => 'frm-install-addon',
749 );
750 } elseif ( ! empty( $addon['categories'] ) ) {
751 $link = array(
752 'categories' => $addon['categories'],
753 );
754 }
755
756 if ( $link ) {
757 $link['status'] = $addon['status']['type'];
758 }
759 } elseif ( current_user_can( 'activate_plugins' ) && self::is_installed( 'formidable-' . $plugin . '/formidable-' . $plugin . '.php' ) ) {
760 $link = array(
761 'url' => 'formidable-' . $plugin . '/formidable-' . $plugin . '.php',
762 'class' => 'frm-activate-addon',
763 );
764 }//end if
765
766 return $link;
767 }
768
769 /**
770 * Get the JSON-encoded install data for a plugin update.
771 *
772 * @since 6.29
773 *
774 * @param string $addon_slug The addon slug (e.g. 'pro', 'dates').
775 *
776 * @return string JSON-encoded install data, or empty string if no URL is available.
777 */
778 public static function get_update_install_data( $addon_slug ) {
779 $upgrading = self::install_link( $addon_slug );
780
781 if ( isset( $upgrading['class'] ) && 'frm-install-addon' === $upgrading['class'] ) {
782 return (string) json_encode( $upgrading );
783 }
784
785 if ( 'pro' === $addon_slug ) {
786 $download_url = self::get_pro_download_url();
787 $plugin_file = 'formidable-pro/formidable-pro.php';
788 } else {
789 $addon_data = self::get_addon( $addon_slug );
790 $download_url = $addon_data && ! empty( $addon_data['url'] ) ? $addon_data['url'] : '';
791 $plugin_file = $addon_data && ! empty( $addon_data['plugin'] ) ? $addon_data['plugin'] : 'formidable-' . $addon_slug . '/formidable-' . $addon_slug . '.php';
792 }
793
794 if ( ! $download_url ) {
795 $update_plugins = get_site_transient( 'update_plugins' );
796 $plugin_update = $update_plugins->response[ $plugin_file ] ?? null;
797 $download_url = $plugin_update && ! empty( $plugin_update->package ) ? $plugin_update->package : '';
798 }
799
800 return $download_url ? (string) json_encode(
801 array(
802 'url' => $download_url,
803 'class' => 'frm-install-addon',
804 )
805 ) : '';
806 }
807
808 /**
809 * @since 4.09
810 *
811 * @param string $plugin The plugin slug.
812 *
813 * @return array|false
814 */
815 public static function get_addon( $plugin ) {
816 $addons = self::get_api_addons();
817 self::prepare_addons( $addons );
818
819 foreach ( $addons as $addon ) {
820 $slug = explode( '/', $addon['plugin'] );
821
822 if ( $slug[0] === 'formidable-' . $plugin ) {
823 return $addon;
824 }
825 }
826
827 return false;
828 }
829
830 /**
831 * @since 4.09
832 *
833 * @return string
834 */
835 protected static function get_license_type() {
836 $license_type = '';
837 $addons = self::get_api_addons();
838
839 if ( isset( $addons['error'] ) && isset( $addons['error']['type'] ) ) {
840 return $addons['error']['type'];
841 }
842
843 return $license_type;
844 }
845
846 /**
847 * @since 3.04.03
848 *
849 * @param array $addons
850 * @param object $license The FrmAddon object.
851 *
852 * @return array
853 */
854 public static function get_addon_for_license( $addons, $license ) {
855 $download_id = $license->download_id;
856 $plugin = array();
857
858 if ( ! $download_id && $addons ) {
859 foreach ( $addons as $addon ) {
860 if ( 0 === strcasecmp( $license->plugin_name, $addon['title'] ) ) {
861 return $addon;
862 }
863 }
864 } elseif ( isset( $addons[ $download_id ] ) ) {
865 $plugin = $addons[ $download_id ];
866 }
867
868 return $plugin;
869 }
870
871 /**
872 * @param array $addons
873 *
874 * @return void
875 */
876 protected static function prepare_addons( &$addons ) {
877 // Reset categories to prevent count accumulation across multiple calls.
878 self::$categories = array();
879
880 $activate_url = '';
881
882 if ( current_user_can( 'activate_plugins' ) ) {
883 $activate_url = add_query_arg( array( 'action' => 'activate' ), admin_url( 'plugins.php' ) );
884 }
885
886 $loop_addons = $addons;
887
888 foreach ( $loop_addons as $id => $addon ) {
889 if ( is_numeric( $id ) ) {
890 $slug = str_replace( array( '-wordpress-plugin', '-wordpress' ), '', $addon['slug'] );
891 $file_name = $addon['plugin'];
892 } else {
893 $slug = $id;
894 $base_file = $addon['file'] ?? 'formidable-' . $slug;
895 $file_name = $base_file . '/' . $base_file . '.php';
896
897 if ( ! isset( $addon['plugin'] ) ) {
898 $addon['plugin'] = $file_name;
899 }
900 }
901
902 $addon['installed'] = self::is_installed( $file_name );
903
904 if ( 'highrise' === $slug && ! $addon['installed'] ) {
905 unset( $addons[ $id ] );
906 continue;
907 }
908
909 if ( $addon['installed'] && 'formidable-views/formidable-views.php' === $file_name ) {
910 $active_views_version = self::get_active_views_version();
911
912 if ( false !== $active_views_version && $slug !== $active_views_version ) {
913 $addon['installed'] = false;
914 }
915 }
916
917 $addon['activate_url'] = '';
918
919 if ( $addon['installed'] && $activate_url && ! self::is_plugin_active( $file_name, $slug ) ) {
920 $addon['activate_url'] = add_query_arg(
921 array(
922 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $file_name ),
923 'plugin' => $file_name,
924 ),
925 $activate_url
926 );
927 }
928
929 if ( ! isset( $addon['docs'] ) ) {
930 $addon['docs'] = 'knowledgebase/formidable-' . $slug . '/';
931 }
932 self::prepare_addon_link( $addon['docs'] );
933
934 if ( ! isset( $addon['link'] ) ) {
935 $addon['link'] = 'downloads/' . $slug . '/';
936 }
937
938 self::prepare_addon_link( $addon['link'] );
939 self::set_addon_status( $addon );
940 self::set_categories( $addon );
941
942 $addons[ $id ] = $addon;
943 }//end foreach
944 }
945
946 /**
947 * @param string $file_name
948 * @param string $slug
949 *
950 * @return bool
951 */
952 private static function is_plugin_active( $file_name, $slug ) {
953 if ( 'formidable-views/formidable-views.php' === $file_name ) {
954 return self::get_active_views_version() === $slug;
955 }
956 return is_plugin_active( $file_name );
957 }
958
959 /**
960 * @return false|string either 'visual-views' or 'views', false if one is not found.
961 */
962 private static function get_active_views_version() {
963 if ( ! is_callable( 'FrmViewsAppHelper::plugin_version' ) ) {
964 return false;
965 }
966
967 $plugin_version = FrmViewsAppHelper::plugin_version();
968 return version_compare( $plugin_version, '5.0', '>=' ) ? 'visual-views' : 'views';
969 }
970
971 /**
972 * @since 3.04.02
973 *
974 * @param string $link
975 *
976 * @return void
977 */
978 protected static function prepare_addon_link( &$link ) {
979 $site_url = 'https://formidableforms.com/';
980
981 if ( ! str_starts_with( $link, 'http' ) ) {
982 $link = $site_url . $link;
983 }
984
985 $link = FrmAppHelper::make_affiliate_url( $link );
986
987 $utm = array(
988 'campaign' => 'addons',
989 );
990 $link = FrmAppHelper::maybe_add_missing_utm( $link, $utm );
991 }
992
993 /**
994 * Add the status to the addon array. Status options are:
995 * installed, active, not installed
996 *
997 * @since 3.04.02
998 *
999 * @param array $addon
1000 *
1001 * @return void
1002 */
1003 protected static function set_addon_status( &$addon ) {
1004 if ( ! empty( $addon['activate_url'] ) ) {
1005 $addon['status'] = array(
1006 'type' => 'installed',
1007 'label' => __( 'Installed', 'formidable' ),
1008 );
1009 } elseif ( $addon['installed'] ) {
1010 $addon['status'] = array(
1011 'type' => 'active',
1012 'label' => __( 'Active', 'formidable' ),
1013 );
1014 } else {
1015 $addon['status'] = array(
1016 'type' => 'not-installed',
1017 'label' => __( 'Not Installed', 'formidable' ),
1018 );
1019 }
1020 }
1021
1022 /**
1023 * @since 5.5.2
1024 *
1025 * @param string $plugin
1026 * @param string $redirect
1027 * @param bool $network_wide
1028 * @param bool $silent
1029 *
1030 * @return WP_Error|null Null on success, WP_Error on invalid file.
1031 */
1032 protected static function activate_plugin( $plugin, $redirect = '', $network_wide = false, $silent = false ) {
1033 if ( ! function_exists( 'activate_plugin' ) ) {
1034 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1035 }
1036 return activate_plugin( $plugin, $redirect, $network_wide, $silent );
1037 }
1038
1039 /**
1040 * Deactivate a specified plugin.
1041 *
1042 * @since 6.8
1043 *
1044 * @param string $plugin
1045 * @param bool $silent
1046 *
1047 * @return void
1048 */
1049 protected static function deactivate_plugin( $plugin, $silent = false ) {
1050 if ( ! function_exists( 'deactivate_plugins' ) ) {
1051 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1052 }
1053 deactivate_plugins( $plugin, $silent );
1054 }
1055
1056 /**
1057 * Uninstall a specified plugin.
1058 *
1059 * @since 6.8
1060 *
1061 * @param string $plugin
1062 *
1063 * @return true|WP_Error True on success, WP_Error on invalid file.
1064 */
1065 protected static function uninstall_plugin( $plugin ) {
1066 if ( ! current_user_can( 'delete_plugins' ) ) {
1067 return new WP_Error( 'uninstall_failed', __( 'Current user cannot delete plugins.', 'formidable' ) );
1068 }
1069
1070 if ( ! function_exists( 'delete_plugins' ) ) {
1071 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1072 }
1073
1074 self::deactivate_plugin( $plugin, true );
1075 $result = delete_plugins( array( $plugin ) );
1076
1077 return is_wp_error( $result ) ? $result : true;
1078 }
1079
1080 /**
1081 * @since 5.0.10
1082 *
1083 * @return string
1084 */
1085 protected static function get_current_plugin() {
1086 if ( ! self::$plugin ) {
1087 self::$plugin = FrmAppHelper::get_param( 'plugin', '', 'post', 'esc_url_raw' );
1088 }
1089 return self::$plugin;
1090 }
1091
1092 /**
1093 * @since 4.08
1094 *
1095 * @return mixed[]|null
1096 */
1097 protected static function download_and_activate() {
1098 if ( is_admin() ) {
1099 FrmAppHelper::set_current_screen_and_hook_suffix();
1100 }
1101
1102 self::maybe_show_cred_form();
1103
1104 $installed = self::install_addon();
1105
1106 if ( is_array( $installed ) && isset( $installed['message'] ) ) {
1107 return $installed;
1108 }
1109 self::handle_addon_action( $installed, 'activate' );
1110 return null;
1111 }
1112
1113 /**
1114 * @since 3.04.02
1115 *
1116 * @return void
1117 */
1118 protected static function maybe_show_cred_form() {
1119 if ( ! function_exists( 'request_filesystem_credentials' ) ) {
1120 include_once ABSPATH . 'wp-admin/includes/file.php';
1121 }
1122
1123 // Start output buffering to catch the filesystem form if credentials are needed.
1124 ob_start();
1125
1126 $show_form = false;
1127 $method = '';
1128 $url = add_query_arg( array( 'page' => 'formidable-settings' ), admin_url( 'admin.php' ) );
1129 $url = esc_url_raw( $url );
1130 $creds = request_filesystem_credentials( $url, $method, false, false, null );
1131
1132 if ( false === $creds ) {
1133 $show_form = true;
1134 } elseif ( ! WP_Filesystem( $creds ) ) {
1135 request_filesystem_credentials( $url, $method, true, false, null );
1136 $show_form = true;
1137 }
1138
1139 if ( $show_form ) {
1140 $form = ob_get_clean();
1141 $message = __( 'Sorry, your site requires FTP authentication. Please download plugins from FormidableForms.com and install them manually.', 'formidable' );
1142 $response = array(
1143 'success' => false,
1144 'message' => $message,
1145 'form' => $form,
1146 );
1147 wp_send_json( $response );
1148 }
1149
1150 ob_end_clean();
1151 }
1152
1153 /**
1154 * Checks if an addon download url is allowed.
1155 *
1156 * @since 6.2
1157 *
1158 * @param string $download_url
1159 *
1160 * @return bool
1161 */
1162 public static function url_is_allowed( $download_url ) {
1163 return FrmAppHelper::validate_url_is_in_s3_bucket( $download_url, 'zip' ) || in_array( $download_url, self::allowed_external_urls(), true );
1164 }
1165
1166 /**
1167 * We do not need any extra credentials if we have gotten this far,
1168 * so let's install the plugin.
1169 *
1170 * @since 3.04.02
1171 *
1172 * @return array|string
1173 */
1174 protected static function install_addon() {
1175 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1176
1177 $download_url = self::get_current_plugin();
1178
1179 if ( ! self::url_is_allowed( $download_url ) ) {
1180 return array(
1181 'message' => 'Plugin URL is not valid',
1182 'success' => false,
1183 );
1184 }
1185
1186 // Create the plugin upgrader with our custom skin.
1187 $installer = new Plugin_Upgrader( new FrmInstallerSkin() );
1188 $installer->install( $download_url, array( 'overwrite_package' => true ) );
1189
1190 // Flush the cache and return the newly installed plugin basename.
1191 wp_cache_flush();
1192
1193 $plugin = $installer->plugin_info();
1194
1195 if ( ! $plugin ) {
1196 return array(
1197 'message' => 'Plugin was not installed. ' . $installer->result,
1198 'success' => false,
1199 );
1200 }
1201
1202 return $plugin;
1203 }
1204
1205 /**
1206 * Handle the AJAX request to activate an add-on.
1207 *
1208 * @since 6.8
1209 *
1210 * @return void
1211 */
1212 public static function ajax_activate_addon() {
1213 self::process_addon_action(
1214 function ( $plugin ) {
1215 return self::handle_addon_action( $plugin, 'activate' );
1216 },
1217 array( 'FrmAddonsController', 'get_addon_activation_response' )
1218 );
1219 }
1220
1221 /**
1222 * @since 3.04.02
1223 *
1224 * @param string $installed The plugin folder name with file name.
1225 */
1226 protected static function maybe_activate_addon( $installed ) {
1227 self::ajax_activate_addon();
1228 }
1229
1230 /**
1231 * Handle the AJAX request to deactivate an add-on.
1232 *
1233 * @since 6.8
1234 *
1235 * @return void
1236 */
1237 public static function ajax_deactivate_addon() {
1238 self::process_addon_action(
1239 function ( $plugin ) {
1240 return self::handle_addon_action( $plugin, 'deactivate' );
1241 }
1242 );
1243 }
1244
1245 /**
1246 * Handle the AJAX request to uninstall an add-on.
1247 *
1248 * @since 6.8
1249 *
1250 * @return void
1251 */
1252 public static function ajax_uninstall_addon() {
1253 self::process_addon_action(
1254 function ( $plugin ) {
1255 return self::handle_addon_action( $plugin, 'uninstall' );
1256 }
1257 );
1258 }
1259
1260 /**
1261 * Process a specific action (activate, deactivate, uninstall) on an add-on.
1262 *
1263 * @since 6.8
1264 *
1265 * @param callable $action_callback The specific add-on action to be executed.
1266 * @param callable|null $response_callback Optional. The response handling callback. Default null.
1267 *
1268 * @return void
1269 */
1270 private static function process_addon_action( $action_callback, $response_callback = null ) {
1271 self::install_addon_permissions();
1272 FrmAppHelper::set_current_screen_and_hook_suffix();
1273
1274 $plugin = FrmAppHelper::get_param( 'plugin', '', 'post', 'sanitize_text_field' );
1275 call_user_func( $action_callback, $plugin );
1276
1277 if ( is_callable( $response_callback ) ) {
1278 wp_send_json_success( call_user_func( $response_callback ) );
1279 return;
1280 }
1281
1282 wp_send_json_success();
1283 }
1284
1285 /**
1286 * Attempt to perform a specific action (activate, deactivate, uninstall) on an add-on.
1287 *
1288 * @since 6.8
1289 *
1290 * @param string $installed The plugin folder name with file name.
1291 * @param string $action The action type ('activate', 'deactivate', 'uninstall').
1292 *
1293 * @return mixed[]|null
1294 */
1295 protected static function handle_addon_action( $installed, $action ) {
1296 if ( ! $installed || ! $action ) {
1297 return null;
1298 }
1299
1300 $result = null;
1301 switch ( $action ) {
1302 case 'activate':
1303 $result = self::activate_plugin( $installed );
1304 break;
1305 case 'deactivate':
1306 self::deactivate_plugin( $installed );
1307 break;
1308 case 'uninstall':
1309 $result = self::uninstall_plugin( $installed );
1310 break;
1311 }
1312
1313 if ( is_wp_error( $result ) ) {
1314 // Ignore the invalid header message that shows with nested plugins.
1315 if ( $result->get_error_code() !== 'no_plugin_header' ) {
1316 if ( wp_doing_ajax() ) {
1317 wp_send_json_error( array( 'error' => $result->get_error_message() ) );
1318 }
1319
1320 return array(
1321 'message' => $result->get_error_message(),
1322 'success' => false,
1323 );
1324 }
1325 }
1326
1327 return $result;
1328 }
1329
1330 /**
1331 * @return array
1332 */
1333 private static function get_addon_activation_response() {
1334 $activating_page = self::get_activating_page();
1335 $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
1336
1337 return array(
1338 'message' => $message,
1339 'saveAndReload' => $activating_page,
1340 );
1341 }
1342
1343 /**
1344 * Return a string that reflects the page from which the addon is being activated on,
1345 * if it is from settings or form builder, otherwise return empty string.
1346 *
1347 * @return string
1348 */
1349 private static function get_activating_page() {
1350 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
1351
1352 if ( str_contains( $referer, 'frm_action=settings' ) ) {
1353 return 'settings';
1354 }
1355
1356 return str_contains( $referer, 'frm_action=edit' ) ? 'form_builder' : '';
1357 }
1358
1359 /**
1360 * Run security checks before installing
1361 *
1362 * @since 3.04.02
1363 *
1364 * @return void
1365 */
1366 protected static function install_addon_permissions() {
1367 check_ajax_referer( 'frm_ajax', 'nonce' );
1368
1369 if ( current_user_can( 'activate_plugins' ) && self::get_current_plugin() ) {
1370 return;
1371 }
1372
1373 echo json_encode( true );
1374 wp_die();
1375 }
1376
1377 /**
1378 * @since 4.08
1379 *
1380 * @return string
1381 */
1382 public static function connect_link() {
1383 $auth = get_option( 'frm_connect_token' );
1384
1385 if ( ! $auth ) {
1386 $auth = hash( 'sha512', wp_rand() );
1387 update_option( 'frm_connect_token', $auth, false );
1388 }
1389
1390 $page = FrmAppHelper::simple_get( 'page', 'sanitize_title', 'formidable-settings' );
1391 $link = 'https://formidableforms.com/api-connect/';
1392 $args = array(
1393 'v' => 2,
1394 'siteurl' => FrmAppHelper::site_url(),
1395 'url' => get_rest_url(),
1396 'inst' => (int) FrmAppHelper::pro_is_included(),
1397 'return' => $page,
1398 'token' => $auth,
1399 'l' => self::get_pro_license(),
1400 );
1401
1402 return add_query_arg( $args, $link );
1403 }
1404
1405 /**
1406 * Check the auth value for install permission.
1407 *
1408 * @since 4.08
1409 *
1410 * @return bool
1411 */
1412 public static function can_install_addon_api() {
1413 // Verify params present (auth & download link).
1414 $post_auth = FrmAppHelper::get_param( 'token', '', 'request', 'sanitize_text_field' );
1415 $post_url = FrmAppHelper::get_param( 'file_url', '', 'request', 'sanitize_text_field' );
1416
1417 // The download link is not required if already installed.
1418 $is_installed = FrmAppHelper::pro_is_included();
1419 $file_missing = ! $is_installed && ! $post_url;
1420
1421 if ( ! $post_auth || $file_missing ) {
1422 return false;
1423 }
1424
1425 // Verify auth.
1426 $auth = get_option( 'frm_connect_token' );
1427 return $auth && hash_equals( $auth, $post_auth );
1428 }
1429
1430 /**
1431 * Install and/or activate the add-on file.
1432 *
1433 * @since 4.08
1434 *
1435 * @return array
1436 */
1437 public static function install_addon_api() {
1438 self::$plugin = FrmAppHelper::get_param( 'file_url', '', 'request', 'esc_url_raw' );
1439
1440 // Delete so cannot replay.
1441 delete_option( 'frm_connect_token' );
1442
1443 // It's already installed and active.
1444 $active = self::activate_plugin( 'formidable-pro/formidable-pro.php', false, false, true );
1445
1446 if ( is_wp_error( $active ) ) {
1447 $response = self::maybe_download_and_activate();
1448
1449 if ( is_array( $response ) ) {
1450 // The download failed.
1451 return $response;
1452 }
1453 }
1454
1455 // If empty license, save it now.
1456 if ( ! self::get_pro_license() && function_exists( 'load_formidable_pro' ) ) {
1457 load_formidable_pro();
1458 $license = stripslashes( FrmAppHelper::get_param( 'key', '', 'request', 'sanitize_text_field' ) );
1459
1460 if ( ! $license ) {
1461 return array(
1462 'success' => false,
1463 'error' => 'That site does not have a valid license key.',
1464 );
1465 }
1466
1467 $response = FrmAddon::activate_license_for_plugin( $license, 'formidable_pro' );
1468
1469 if ( ! $response['success'] ) {
1470 // Could not activate license.
1471 return $response;
1472 }
1473 }
1474
1475 return array(
1476 'success' => true,
1477 );
1478 }
1479
1480 /**
1481 * @since 6.8.3
1482 *
1483 * @return array|true
1484 */
1485 private static function maybe_download_and_activate() {
1486 if ( ! self::$plugin ) {
1487 return array(
1488 'success' => false,
1489 'message' => __( 'The plugin download was not found.', 'formidable' ),
1490 );
1491 }
1492
1493 // Download plugin now.
1494 $response = self::download_and_activate();
1495
1496 if ( is_array( $response ) && isset( $response['success'] ) ) {
1497 // The download failed.
1498 return $response;
1499 }
1500
1501 return true;
1502 }
1503
1504 /**
1505 * Render a conditional action button for a specified plugin
1506 *
1507 * @since 4.09
1508 *
1509 * @param string $plugin
1510 * @param array|string $upgrade_link_args
1511 *
1512 * @return void
1513 */
1514 public static function conditional_action_button( $plugin, $upgrade_link_args ) {
1515 if ( is_callable( 'FrmProAddonsController::conditional_action_button' ) ) {
1516 FrmProAddonsController::conditional_action_button( $plugin, $upgrade_link_args );
1517 return;
1518 }
1519
1520 $addon = self::get_addon( $plugin );
1521 $upgrade_link = FrmAppHelper::admin_upgrade_link( $upgrade_link_args );
1522
1523 if ( ! is_array( $upgrade_link_args ) ) {
1524 // A string $upgrade_link_args is used for the utm-medium value when calling
1525 // FrmAppHelper::admin_upgrade_link above.
1526 // For self::addon_upgrade_link, we'll pass just an empty array with the link key (set below).
1527 $upgrade_link_args = array();
1528 }
1529
1530 $upgrade_link_args['link'] = $upgrade_link;
1531
1532 self::addon_upgrade_link( $addon, $upgrade_link_args );
1533 }
1534
1535 /**
1536 * Render a conditional action button for an add on
1537 *
1538 * @since 4.09.01
1539 *
1540 * @param array $atts {
1541 * Button attributes.
1542 *
1543 * @type array $addon
1544 * @type false|string $license_type
1545 * @type string $plan_required
1546 * @type string $upgrade_link
1547 * }
1548 *
1549 * @return void
1550 */
1551 public static function show_conditional_action_button( $atts ) {
1552 if ( is_callable( 'FrmProAddonsController::show_conditional_action_button' ) ) {
1553 FrmProAddonsController::show_conditional_action_button( $atts );
1554 return;
1555 }
1556
1557 self::addon_upgrade_link( $atts['addon'], $atts['upgrade_link'] );
1558 }
1559
1560 /**
1561 * @since 4.09.01
1562 *
1563 * @param array|false $addon
1564 * @param array|string $upgrade_link
1565 *
1566 * @return void
1567 */
1568 public static function addon_upgrade_link( $addon, $upgrade_link ) {
1569 $atts = is_array( $upgrade_link ) ? $upgrade_link : array();
1570 $upgrade_link = is_array( $upgrade_link ) ? $upgrade_link['link'] : $upgrade_link;
1571 $text = ! empty( $atts['text'] ) ? $atts['text'] : __( 'Upgrade Now', 'formidable' );
1572
1573 if ( $addon ) {
1574 $upgrade_link .= '&utm_content=' . $addon['slug'];
1575 }
1576
1577 if ( $addon && isset( $addon['categories'] ) && in_array( 'Solution', $addon['categories'], true ) ) {
1578 // Solutions will go to a separate page.
1579 $upgrade_link = FrmAppHelper::admin_upgrade_link( 'addons', $addon['link'] );
1580 }
1581
1582 $class = ! empty( $atts['class'] ) ? $atts['class'] : '';
1583
1584 if ( ! str_contains( $class, 'frm-button' ) ) {
1585 $class .= ' frm-button-secondary frm-button-sm';
1586 }
1587 // phpcs:disable Generic.WhiteSpace.ScopeIndent
1588 ?>
1589 <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 ?>
1590 <?php echo esc_html( $text ); ?>
1591 </a>
1592 <?php
1593 // phpcs:enable Generic.WhiteSpace.ScopeIndent
1594 }
1595
1596 /**
1597 * @since 3.04.02
1598 *
1599 * @return void
1600 */
1601 public static function ajax_install_addon() {
1602 self::install_addon_permissions();
1603
1604 $result = self::download_and_activate();
1605
1606 if ( isset( $result['success'] ) && ! $result['success'] ) {
1607 echo json_encode( $result );
1608 wp_die();
1609 }
1610
1611 echo json_encode( self::get_addon_activation_response() );
1612 wp_die();
1613 }
1614
1615 /**
1616 * Allowed URLs used for internal source of plugins installation.
1617 *
1618 * @since 6.3.1
1619 *
1620 * @return array
1621 */
1622 private static function allowed_external_urls() {
1623 $allowed_url_list = array(
1624 'https://downloads.wordpress.org/plugin/formidable-gravity-forms-importer.zip',
1625 'https://downloads.wordpress.org/plugin/formidable-import-pirate-forms.zip',
1626 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip',
1627 );
1628
1629 /**
1630 * List of URLs used in plugin formidable internal installation.
1631 *
1632 * @since 6.3.1
1633 *
1634 * @param array $allowed_url_list List of URLs.
1635 */
1636 $allowed_url_list = apply_filters( 'frm_allowed_external_urls', $allowed_url_list );
1637
1638 if ( ! is_array( $allowed_url_list ) ) {
1639 _doing_it_wrong( __METHOD__, 'Only an array of URLs could be used within this filter.', '6.3.1' );
1640 return array();
1641 }
1642
1643 return $allowed_url_list;
1644 }
1645
1646 /**
1647 * Gets required plan for an addon.
1648 *
1649 * @since 6.4.2
1650 *
1651 * @param int|string $addon_id
1652 *
1653 * @return string Empty string if no plan is required for active license.
1654 */
1655 public static function get_addon_required_plan( $addon_id ) {
1656 $api = new FrmFormApi();
1657 $addons = $api->get_api_info();
1658
1659 if ( is_array( $addons ) && array_key_exists( $addon_id, $addons ) ) {
1660 $dates = $addons[ $addon_id ];
1661 $requires = FrmFormsHelper::get_plan_required( $dates );
1662 }
1663
1664 if ( ! isset( $requires ) || ! is_string( $requires ) ) {
1665 return '';
1666 }
1667
1668 return $requires;
1669 }
1670 }
1671