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

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