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

1,514 lines 40.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmAddonsController {
7
8 /**
9 * @var string
10 */
11 const SCRIPT_HANDLE = 'frm-addons-page';
12
13 /**
14 * @var array
15 */
16 private static $categories = array();
17
18 /**
19 * @var string
20 */
21 private static $request_addon_url;
22
23 /**
24 * @var string
25 */
26 protected static $plugin;
27
28 /**
29 * @since 6.15
30 */
31 public static function load_admin_hooks() {
32 add_action( 'admin_menu', __CLASS__ . '::menu', 100 );
33 add_filter( 'pre_set_site_transient_update_plugins', __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', __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 = isset( $addons['error']['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 isset( $pro['url'] ) ? $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 isset( $addons['93790'] ) ? $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 = isset( $version_info['error']['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 = isset( $wp_plugins[ $folder ] ) ? $wp_plugins[ $folder ] : array();
553 $wp_version = isset( $wp_plugin['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 = isset( $plugin['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 $query_args = array(
843 'utm_source' => 'WordPress',
844 'utm_medium' => 'addons',
845 'utm_campaign' => 'liteplugin',
846 );
847 $link = add_query_arg( $query_args, $link );
848 }
849
850 /**
851 * Add the status to the addon array. Status options are:
852 * installed, active, not installed
853 *
854 * @since 3.04.02
855 *
856 * @return void
857 */
858 protected static function set_addon_status( &$addon ) {
859 if ( ! empty( $addon['activate_url'] ) ) {
860 $addon['status'] = array(
861 'type' => 'installed',
862 'label' => __( 'Installed', 'formidable' ),
863 );
864 } elseif ( $addon['installed'] ) {
865 $addon['status'] = array(
866 'type' => 'active',
867 'label' => __( 'Active', 'formidable' ),
868 );
869 } else {
870 $addon['status'] = array(
871 'type' => 'not-installed',
872 'label' => __( 'Not Installed', 'formidable' ),
873 );
874 }
875 }
876
877 /**
878 * @since 5.5.2
879 *
880 * @param string $plugin
881 * @param string $redirect
882 * @param bool $network_wide
883 * @param bool $silent
884 * @return WP_Error|null Null on success, WP_Error on invalid file.
885 */
886 protected static function activate_plugin( $plugin, $redirect = '', $network_wide = false, $silent = false ) {
887 if ( ! function_exists( 'activate_plugin' ) ) {
888 require_once ABSPATH . 'wp-admin/includes/plugin.php';
889 }
890 return activate_plugin( $plugin, $redirect, $network_wide, $silent );
891 }
892
893 /**
894 * Deactivate a specified plugin.
895 *
896 * @since 6.8
897 *
898 * @param string $plugin
899 * @param bool $silent
900 * @return void
901 */
902 protected static function deactivate_plugin( $plugin, $silent = false ) {
903 if ( ! function_exists( 'deactivate_plugins' ) ) {
904 require_once ABSPATH . 'wp-admin/includes/plugin.php';
905 }
906 deactivate_plugins( $plugin, $silent );
907 }
908
909 /**
910 * Uninstall a specified plugin.
911 *
912 * @since 6.8
913 *
914 * @param string $plugin
915 * @return true|WP_Error True on success, WP_Error on invalid file.
916 */
917 protected static function uninstall_plugin( $plugin ) {
918 if ( ! current_user_can( 'delete_plugins' ) ) {
919 return new WP_Error( 'uninstall_failed', __( 'Current user cannot delete plugins.', 'formidable' ) );
920 }
921
922 if ( ! function_exists( 'delete_plugins' ) ) {
923 require_once ABSPATH . 'wp-admin/includes/plugin.php';
924 }
925
926 self::deactivate_plugin( $plugin, true );
927 $result = delete_plugins( array( $plugin ) );
928
929 if ( is_wp_error( $result ) ) {
930 return $result;
931 }
932
933 return true;
934 }
935
936 /**
937 * @since 5.0.10
938 *
939 * @return string
940 */
941 protected static function get_current_plugin() {
942 if ( empty( self::$plugin ) ) {
943 self::$plugin = FrmAppHelper::get_param( 'plugin', '', 'post', 'esc_url_raw' );
944 }
945 return self::$plugin;
946 }
947
948 /**
949 * @since 4.08
950 *
951 * @return array|void
952 */
953 protected static function download_and_activate() {
954 if ( is_admin() ) {
955 FrmAppHelper::set_current_screen_and_hook_suffix();
956 }
957
958 self::maybe_show_cred_form();
959
960 $installed = self::install_addon();
961 if ( is_array( $installed ) && isset( $installed['message'] ) ) {
962 return $installed;
963 }
964 self::handle_addon_action( $installed, 'activate' );
965 }
966
967 /**
968 * @since 3.04.02
969 *
970 * @return void
971 */
972 protected static function maybe_show_cred_form() {
973 if ( ! function_exists( 'request_filesystem_credentials' ) ) {
974 include_once ABSPATH . 'wp-admin/includes/file.php';
975 }
976
977 // Start output bufferring to catch the filesystem form if credentials are needed.
978 ob_start();
979
980 $show_form = false;
981 $method = '';
982 $url = add_query_arg( array( 'page' => 'formidable-settings' ), admin_url( 'admin.php' ) );
983 $url = esc_url_raw( $url );
984 $creds = request_filesystem_credentials( $url, $method, false, false, null );
985
986 if ( false === $creds ) {
987 $show_form = true;
988 } elseif ( ! WP_Filesystem( $creds ) ) {
989 request_filesystem_credentials( $url, $method, true, false, null );
990 $show_form = true;
991 }
992
993 if ( $show_form ) {
994 $form = ob_get_clean();
995 $message = __( 'Sorry, your site requires FTP authentication. Please download plugins from FormidableForms.com and install them manually.', 'formidable' );
996 $data = $form;
997 $response = array(
998 'success' => false,
999 'message' => $message,
1000 'form' => $form,
1001 );
1002 wp_send_json( $response );
1003 }
1004
1005 ob_end_clean();
1006 }
1007
1008 /**
1009 * Checks if an addon download url is allowed.
1010 *
1011 * @since 6.2
1012 *
1013 * @param string $download_url
1014 *
1015 * @return bool
1016 */
1017 public static function url_is_allowed( $download_url ) {
1018 return FrmAppHelper::validate_url_is_in_s3_bucket( $download_url, 'zip' ) || in_array( $download_url, self::allowed_external_urls(), true );
1019 }
1020
1021 /**
1022 * We do not need any extra credentials if we have gotten this far,
1023 * so let's install the plugin.
1024 *
1025 * @since 3.04.02
1026 */
1027 protected static function install_addon() {
1028 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1029
1030 $download_url = self::get_current_plugin();
1031
1032 if ( ! self::url_is_allowed( $download_url ) ) {
1033 return array(
1034 'message' => 'Plugin URL is not valid',
1035 'success' => false,
1036 );
1037 }
1038
1039 // Create the plugin upgrader with our custom skin.
1040 $installer = new Plugin_Upgrader( new FrmInstallerSkin() );
1041 $installer->install( $download_url );
1042
1043 // Flush the cache and return the newly installed plugin basename.
1044 wp_cache_flush();
1045
1046 $plugin = $installer->plugin_info();
1047 if ( ! $plugin ) {
1048 return array(
1049 'message' => 'Plugin was not installed. ' . $installer->result,
1050 'success' => false,
1051 );
1052 }
1053 return $plugin;
1054 }
1055
1056 /**
1057 * Handle the AJAX request to activate an add-on.
1058 *
1059 * @since 6.8
1060 *
1061 * @return void
1062 */
1063 public static function ajax_activate_addon() {
1064 self::process_addon_action(
1065 function ( $plugin ) {
1066 return self::handle_addon_action( $plugin, 'activate' );
1067 },
1068 array( 'FrmAddonsController', 'get_addon_activation_response' )
1069 );
1070 }
1071
1072 /**
1073 * @since 3.04.02
1074 *
1075 * @param string $installed The plugin folder name with file name.
1076 */
1077 protected static function maybe_activate_addon( $installed ) {
1078 self::ajax_activate_addon();
1079 }
1080
1081 /**
1082 * Handle the AJAX request to deactivate an add-on.
1083 *
1084 * @since 6.8
1085 *
1086 * @return void
1087 */
1088 public static function ajax_deactivate_addon() {
1089 self::process_addon_action(
1090 function ( $plugin ) {
1091 return self::handle_addon_action( $plugin, 'deactivate' );
1092 }
1093 );
1094 }
1095
1096 /**
1097 * Handle the AJAX request to uninstall an add-on.
1098 *
1099 * @since 6.8
1100 *
1101 * @return void
1102 */
1103 public static function ajax_uninstall_addon() {
1104 self::process_addon_action(
1105 function ( $plugin ) {
1106 return self::handle_addon_action( $plugin, 'uninstall' );
1107 }
1108 );
1109 }
1110
1111 /**
1112 * Process a specific action (activate, deactivate, uninstall) on an add-on.
1113 *
1114 * @since 6.8
1115 *
1116 * @param callable $action_callback The specific add-on action to be executed.
1117 * @param callable|null $response_callback Optional. The response handling callback. Default null.
1118 * @return void
1119 */
1120 private static function process_addon_action( $action_callback, $response_callback = null ) {
1121 self::install_addon_permissions();
1122 FrmAppHelper::set_current_screen_and_hook_suffix();
1123
1124 $plugin = FrmAppHelper::get_param( 'plugin', '', 'post', 'sanitize_text_field' );
1125 call_user_func( $action_callback, $plugin );
1126
1127 if ( is_callable( $response_callback ) ) {
1128 wp_send_json_success( call_user_func( $response_callback ) );
1129 return;
1130 }
1131
1132 wp_send_json_success();
1133 }
1134
1135 /**
1136 * Attempt to perform a specific action (activate, deactivate, uninstall) on an add-on.
1137 *
1138 * @since 6.8
1139 *
1140 * @param string $installed The plugin folder name with file name.
1141 * @param string $action The action type ('activate', 'deactivate', 'uninstall').
1142 * @return array|void
1143 */
1144 protected static function handle_addon_action( $installed, $action ) {
1145 if ( ! $installed || ! $action ) {
1146 return;
1147 }
1148
1149 $result = null;
1150 switch ( $action ) {
1151 case 'activate':
1152 $result = self::activate_plugin( $installed );
1153 break;
1154 case 'deactivate':
1155 self::deactivate_plugin( $installed );
1156 break;
1157 case 'uninstall':
1158 $result = self::uninstall_plugin( $installed );
1159 break;
1160 }
1161
1162 if ( is_wp_error( $result ) ) {
1163 // Ignore the invalid header message that shows with nested plugins.
1164 if ( $result->get_error_code() !== 'no_plugin_header' ) {
1165 if ( wp_doing_ajax() ) {
1166 wp_send_json_error( array( 'error' => $result->get_error_message() ) );
1167 }
1168 return array(
1169 'message' => $result->get_error_message(),
1170 'success' => false,
1171 );
1172 }
1173 }
1174
1175 return $result;
1176 }
1177
1178 /**
1179 * @return array
1180 */
1181 private static function get_addon_activation_response() {
1182 $activating_page = self::get_activating_page();
1183
1184 $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' );
1185
1186 $response = array(
1187 'message' => $message,
1188 'saveAndReload' => $activating_page,
1189 );
1190
1191 return $response;
1192 }
1193
1194 /**
1195 * Return a string that reflects the page from which the addon is being activated on,
1196 * if it is from settings or form builder, otherwise return empty string.
1197 *
1198 * @return string
1199 */
1200 private static function get_activating_page() {
1201 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
1202 if ( false !== strpos( $referer, 'frm_action=settings' ) ) {
1203 return 'settings';
1204 }
1205
1206 if ( false !== strpos( $referer, 'frm_action=edit' ) ) {
1207 return 'form_builder';
1208 }
1209
1210 return '';
1211 }
1212
1213 /**
1214 * Run security checks before installing
1215 *
1216 * @since 3.04.02
1217 *
1218 * @return void
1219 */
1220 protected static function install_addon_permissions() {
1221 check_ajax_referer( 'frm_ajax', 'nonce' );
1222
1223 if ( ! current_user_can( 'activate_plugins' ) || ! self::get_current_plugin() ) {
1224 echo json_encode( true );
1225 wp_die();
1226 }
1227 }
1228
1229 /**
1230 * @since 4.08
1231 *
1232 * @return string
1233 */
1234 public static function connect_link() {
1235 $auth = get_option( 'frm_connect_token' );
1236 if ( empty( $auth ) ) {
1237 $auth = hash( 'sha512', wp_rand() );
1238 update_option( 'frm_connect_token', $auth, 'no' );
1239 }
1240 $page = FrmAppHelper::simple_get( 'page', 'sanitize_title', 'formidable-settings' );
1241 $link = 'https://formidableforms.com/api-connect/';
1242 $args = array(
1243 'v' => 2,
1244 'siteurl' => FrmAppHelper::site_url(),
1245 'url' => get_rest_url(),
1246 'inst' => (int) FrmAppHelper::pro_is_included(),
1247 'return' => $page,
1248 'token' => $auth,
1249 'l' => self::get_pro_license(),
1250 );
1251
1252 return add_query_arg( $args, $link );
1253 }
1254
1255 /**
1256 * Check the auth value for install permission.
1257 *
1258 * @since 4.08
1259 *
1260 * @return bool
1261 */
1262 public static function can_install_addon_api() {
1263 // Verify params present (auth & download link).
1264 $post_auth = FrmAppHelper::get_param( 'token', '', 'request', 'sanitize_text_field' );
1265 $post_url = FrmAppHelper::get_param( 'file_url', '', 'request', 'sanitize_text_field' );
1266
1267 // The download link is not required if already installed.
1268 $is_installed = FrmAppHelper::pro_is_included();
1269 $file_missing = ! $is_installed && empty( $post_url );
1270 if ( ! $post_auth || $file_missing ) {
1271 return false;
1272 }
1273
1274 // Verify auth.
1275 $auth = get_option( 'frm_connect_token' );
1276 if ( empty( $auth ) || ! hash_equals( $auth, $post_auth ) ) {
1277 return false;
1278 }
1279
1280 return true;
1281 }
1282
1283 /**
1284 * Install and/or activate the add-on file.
1285 *
1286 * @since 4.08
1287 *
1288 * @return array
1289 */
1290 public static function install_addon_api() {
1291 self::$plugin = FrmAppHelper::get_param( 'file_url', '', 'request', 'esc_url_raw' );
1292
1293 $error = esc_html__( 'Could not install an upgrade. Please download from formidableforms.com and install manually.', 'formidable' );
1294
1295 // Delete so cannot replay.
1296 delete_option( 'frm_connect_token' );
1297
1298 // It's already installed and active.
1299 $active = self::activate_plugin( 'formidable-pro/formidable-pro.php', false, false, true );
1300 if ( is_wp_error( $active ) ) {
1301 $response = self::maybe_download_and_activate();
1302 if ( is_array( $response ) ) {
1303 // The download failed.
1304 return $response;
1305 }
1306 }
1307
1308 // If empty license, save it now.
1309 if ( empty( self::get_pro_license() ) && function_exists( 'load_formidable_pro' ) ) {
1310 load_formidable_pro();
1311 $license = stripslashes( FrmAppHelper::get_param( 'key', '', 'request', 'sanitize_text_field' ) );
1312 if ( ! $license ) {
1313 return array(
1314 'success' => false,
1315 'error' => 'That site does not have a valid license key.',
1316 );
1317 }
1318
1319 $response = FrmAddon::activate_license_for_plugin( $license, 'formidable_pro' );
1320 if ( ! $response['success'] ) {
1321 // Could not activate license.
1322 return $response;
1323 }
1324 }
1325
1326 return array(
1327 'success' => true,
1328 );
1329 }
1330
1331 /**
1332 * @since 6.8.3
1333 *
1334 * @return array|true
1335 */
1336 private static function maybe_download_and_activate() {
1337 if ( ! self::$plugin ) {
1338 return array(
1339 'success' => false,
1340 'message' => __( 'The plugin download was not found.', 'formidable' ),
1341 );
1342 }
1343
1344 // Download plugin now.
1345 $response = self::download_and_activate();
1346 if ( is_array( $response ) && isset( $response['success'] ) ) {
1347 // The download failed.
1348 return $response;
1349 }
1350
1351 return true;
1352 }
1353
1354 /**
1355 * Render a conditional action button for a specified plugin
1356 *
1357 * @since 4.09
1358 *
1359 * @param string $plugin
1360 * @param array|string $upgrade_link_args
1361 * @return void
1362 */
1363 public static function conditional_action_button( $plugin, $upgrade_link_args ) {
1364 if ( is_callable( 'FrmProAddonsController::conditional_action_button' ) ) {
1365 FrmProAddonsController::conditional_action_button( $plugin, $upgrade_link_args );
1366 return;
1367 }
1368
1369 $addon = self::get_addon( $plugin );
1370 $upgrade_link = FrmAppHelper::admin_upgrade_link( $upgrade_link_args );
1371
1372 if ( ! is_array( $upgrade_link_args ) ) {
1373 // A string $upgrade_link_args is used for the utm-medium value when calling
1374 // FrmAppHelper::admin_upgrade_link above.
1375 // For self::addon_upgrade_link, we'll pass just an empty array with the link key (set below).
1376 $upgrade_link_args = array();
1377 }
1378
1379 $upgrade_link_args['link'] = $upgrade_link;
1380
1381 self::addon_upgrade_link( $addon, $upgrade_link_args );
1382 }
1383
1384 /**
1385 * Render a conditional action button for an add on
1386 *
1387 * @since 4.09.01
1388 *
1389 * @param array $atts {
1390 * Button attributes.
1391 *
1392 * @type array $addon
1393 * @type false|string $license_type
1394 * @type string $plan_required
1395 * @type string $upgrade_link
1396 * }
1397 * @return void
1398 */
1399 public static function show_conditional_action_button( $atts ) {
1400 if ( is_callable( 'FrmProAddonsController::show_conditional_action_button' ) ) {
1401 FrmProAddonsController::show_conditional_action_button( $atts );
1402 return;
1403 }
1404
1405 self::addon_upgrade_link( $atts['addon'], $atts['upgrade_link'] );
1406 }
1407
1408 /**
1409 * @since 4.09.01
1410 *
1411 * @param array|false $addon
1412 * @param array|string $upgrade_link
1413 *
1414 * @return void
1415 */
1416 public static function addon_upgrade_link( $addon, $upgrade_link ) {
1417 $atts = is_array( $upgrade_link ) ? $upgrade_link : array();
1418 $upgrade_link = is_array( $upgrade_link ) ? $upgrade_link['link'] : $upgrade_link;
1419 $text = ! empty( $atts['text'] ) ? $atts['text'] : __( 'Upgrade Now', 'formidable' );
1420
1421 if ( $addon ) {
1422 $upgrade_link .= '&utm_content=' . $addon['slug'];
1423 }
1424
1425 if ( $addon && isset( $addon['categories'] ) && in_array( 'Solution', $addon['categories'], true ) ) {
1426 // Solutions will go to a separate page.
1427 $upgrade_link = FrmAppHelper::admin_upgrade_link( 'addons', $addon['link'] );
1428 }
1429
1430 $class = ! empty( $atts['class'] ) ? $atts['class'] : '';
1431 if ( strpos( $class, 'frm-button' ) === false ) {
1432 $class .= ' frm-button-secondary frm-button-sm';
1433 }
1434 ?>
1435 <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' ); ?>">
1436 <?php echo esc_html( $text ); ?>
1437 </a>
1438 <?php
1439 }
1440
1441 /**
1442 * @since 3.04.02
1443 *
1444 * @return void
1445 */
1446 public static function ajax_install_addon() {
1447 self::install_addon_permissions();
1448
1449 $result = self::download_and_activate();
1450 if ( isset( $result['success'] ) && ! $result['success'] ) {
1451 echo json_encode( $result );
1452 wp_die();
1453 }
1454
1455 echo json_encode( self::get_addon_activation_response() );
1456 wp_die();
1457 }
1458
1459 /**
1460 * Allowed URLs used for internal source of plugins installation.
1461 *
1462 * @since 6.3.1
1463 *
1464 * @return array
1465 */
1466 private static function allowed_external_urls() {
1467 $allowed_url_list = array(
1468 'https://downloads.wordpress.org/plugin/formidable-gravity-forms-importer.zip',
1469 'https://downloads.wordpress.org/plugin/formidable-import-pirate-forms.zip',
1470 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip',
1471 );
1472
1473 /**
1474 * List of URLs used in plugin formidable internal installation.
1475 *
1476 * @since 6.3.1
1477 *
1478 * @param array $allowed_url_list List of URLs.
1479 */
1480 $allowed_url_list = apply_filters( 'frm_allowed_external_urls', $allowed_url_list );
1481
1482 if ( ! is_array( $allowed_url_list ) ) {
1483 _doing_it_wrong( __METHOD__, 'Only an array of URLs could be used within this filter.', '6.3.1' );
1484
1485 return array();
1486 }
1487
1488 return $allowed_url_list;
1489 }
1490
1491 /**
1492 * Gets required plan for an addon.
1493 *
1494 * @since 6.4.2
1495 *
1496 * @return string Empty string if no plan is required for active license.
1497 */
1498 public static function get_addon_required_plan( $addon_id ) {
1499 $api = new FrmFormApi();
1500 $addons = $api->get_api_info();
1501
1502 if ( is_array( $addons ) && array_key_exists( $addon_id, $addons ) ) {
1503 $dates = $addons[ $addon_id ];
1504 $requires = FrmFormsHelper::get_plan_required( $dates );
1505 }
1506
1507 if ( ! isset( $requires ) || ! is_string( $requires ) ) {
1508 $requires = '';
1509 }
1510
1511 return $requires;
1512 }
1513 }
1514