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

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