PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.32
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.32
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
← All changes | classes/controllers/FrmFormActionsController.php +416 -40 6.306.32 View file →
@@ -10,9 +10,9 @@
10 10 */
11 11 public static $action_post_type = 'frm_form_actions';
12 12
13 13 /**
14 - * @var array|null
14 + * @var Frm_Form_Action_Factory|null
15 15 */
16 16 public static $registered_actions;
17 17
18 18 /**
@@ -37,8 +37,9 @@
37 37 'has_archive' => false,
38 38 )
39 39 );
40 40
41 + self::maybe_setup_unlicensed_action_gate();
41 42 self::actions_init();
42 43 }
43 44
44 45 public static function actions_init() {
@@ -55,27 +56,30 @@
55 56 'on_submit' => 'FrmOnSubmitAction',
56 57 'email' => 'FrmEmailAction',
57 58 'wppost' => 'FrmDefPostAction',
58 59 'register' => 'FrmDefRegAction',
59 - 'paypal' => 'FrmDefPayPalAction',
60 + 'stripe' => 'FrmStripeLiteAction',
61 + 'square' => 'FrmSquareAction',
62 + 'paypal' => 'FrmPayPalLiteAction',
63 + 'paypal-legacy' => 'FrmDefPayPalLegacyAction',
60 64 'payment' => 'FrmTransLiteAction',
61 65 'quiz' => 'FrmDefQuizAction',
62 66 'quiz_outcome' => 'FrmDefQuizOutcomeAction',
67 + 'api' => 'FrmDefApiAction',
63 68 'mailchimp' => 'FrmDefMlcmpAction',
64 - 'api' => 'FrmDefApiAction',
65 - 'salesforce' => 'FrmDefSalesforceAction',
66 69 'activecampaign' => 'FrmDefActiveCampaignAction',
67 70 'constantcontact' => 'FrmDefConstContactAction',
68 71 'getresponse' => 'FrmDefGetResponseAction',
72 + 'mailpoet' => 'FrmDefMailpoetAction',
73 + 'convertkit' => 'FrmDefConvertKitAction',
74 + 'aweber' => 'FrmDefAweberAction',
75 + 'twilio' => 'FrmDefTwilioAction',
76 + 'salesforce' => 'FrmDefSalesforceAction',
69 77 'hubspot' => 'FrmDefHubspotAction',
78 + 'highrise' => 'FrmDefHighriseAction',
70 79 'zapier' => 'FrmDefZapierAction',
80 + 'googlespreadsheet' => 'FrmDefGoogleSpreadsheetAction',
71 81 'n8n' => 'FrmDefN8NAction',
72 - 'twilio' => 'FrmDefTwilioAction',
73 - 'highrise' => 'FrmDefHighriseAction',
74 - 'mailpoet' => 'FrmDefMailpoetAction',
75 - 'aweber' => 'FrmDefAweberAction',
76 - 'convertkit' => 'FrmDefConvertKitAction',
77 - 'googlespreadsheet' => 'FrmDefGoogleSpreadsheetAction',
78 82 );
79 83
80 84 $action_classes = apply_filters( 'frm_registered_form_actions', $action_classes );
81 85 $action_classes = self::maybe_unset_highrise( $action_classes );
@@ -82,14 +86,65 @@
82 86
83 87 include_once FrmAppHelper::plugin_path() . '/classes/views/frm-form-actions/email_action.php';
84 88 include_once FrmAppHelper::plugin_path() . '/classes/views/frm-form-actions/default_actions.php';
85 89
90 + // This needs to be called after we include default_actions.php or FrmDefPayPalLegacyAction will never exist.
91 + if ( 'FrmPayPalLiteAction' === $action_classes['paypal'] || ! class_exists( 'FrmPaymentAction' ) || ! class_exists( 'FrmDefPayPalLegacyAction' ) ) {
92 + unset( $action_classes['paypal-legacy'] );
93 + }
94 +
86 95 foreach ( $action_classes as $action_class ) {
87 96 self::$registered_actions->register( $action_class );
88 97 }
98 +
99 + self::apply_default_action_descriptions();
89 100 }
90 101
91 102 /**
103 + * Sets default descriptions on registered actions from a central list.
104 + *
105 + * Keeps the description when an add-on replaces a base action class without its own.
106 + *
107 + * @since 6.31
108 + *
109 + * @return void
110 + */
111 + private static function apply_default_action_descriptions() {
112 + $descriptions = array(
113 + 'on_submit' => __( 'Success messages', 'formidable' ),
114 + 'email' => __( 'Autoresponder alerts', 'formidable' ),
115 + 'wppost' => __( 'Content publishing', 'formidable' ),
116 + 'register' => __( 'Account creation', 'formidable' ),
117 + 'payment' => __( 'Transaction alerts', 'formidable' ),
118 + 'stripe' => __( 'Payment gateway', 'formidable' ),
119 + 'square' => __( 'Payment gateway', 'formidable' ),
120 + 'paypal' => __( 'Payment gateway', 'formidable' ),
121 + 'quiz' => __( 'Automated grading', 'formidable' ),
122 + 'quiz_outcome' => __( 'Result logic', 'formidable' ),
123 + 'aweber' => __( 'List triggers', 'formidable' ),
124 + 'mailchimp' => __( 'Subscription confirmation', 'formidable' ),
125 + 'zapier' => __( 'App automation', 'formidable' ),
126 + 'n8n' => __( 'Workflow automation', 'formidable' ),
127 + 'twilio' => __( 'Text notifications', 'formidable' ),
128 + 'activecampaign' => __( 'Contact automation', 'formidable' ),
129 + 'salesforce' => __( 'Lead automation', 'formidable' ),
130 + 'constantcontact' => __( 'Content distribution', 'formidable' ),
131 + 'getresponse' => __( 'Success notifications', 'formidable' ),
132 + 'hubspot' => __( 'CRM alerts', 'formidable' ),
133 + 'mailpoet' => __( 'Plugin automation', 'formidable' ),
134 + 'api' => __( 'System integration', 'formidable' ),
135 + 'googlespreadsheet' => __( 'Spreadsheet sync', 'formidable' ),
136 + 'convertkit' => __( 'Broadcast publishing', 'formidable' ),
137 + );
138 +
139 + foreach ( self::$registered_actions->actions as $action ) {
140 + if ( $action->action_options['description'] === '' && isset( $descriptions[ $action->id_base ] ) ) {
141 + $action->action_options['description'] = $descriptions[ $action->id_base ];
142 + }
143 + }
144 + }
145 +
146 + /**
92 147 * Remove the Highrise action if it is not registered.
93 148 *
94 149 * @since 6.23
95 150 *
@@ -136,8 +191,19 @@
136 191 foreach ( $groups as $group ) {
137 192 if ( isset( $group['actions'] ) ) {
138 193 $grouped = array_merge( $grouped, $group['actions'] );
139 194 }
195 +
196 + // Also collect actions from sections
197 + if ( ! isset( $group['sections'] ) ) {
198 + continue;
199 + }
200 +
201 + foreach ( $group['sections'] as $section ) {
202 + if ( isset( $section['actions'] ) ) {
203 + $grouped = array_merge( $grouped, $section['actions'] );
204 + }
205 + }
140 206 }
141 207
142 208 foreach ( $action_controls as $action ) {
143 209 if ( isset( $groups[ $action->id_base ] ) || in_array( $action->id_base, $grouped, true ) ) {
@@ -164,31 +230,47 @@
164 230 *
165 231 * @return array
166 232 */
167 233 public static function form_action_groups() {
234 + // Get all action controls to check which are active
235 + $action_controls = self::get_form_actions();
236 +
237 + // Determine which actions are currently available
238 + $available_actions = self::get_available_my_actions( $action_controls );
239 +
240 + // Featured actions are add-ons that are NOT currently available
241 + $all_addon_actions = array(
242 + 'api',
243 + 'register',
244 + 'n8n',
245 + 'quiz',
246 + 'quiz_outcome',
247 + 'googlespreadsheet',
248 + );
249 + $featured_actions = array_diff( $all_addon_actions, $available_actions );
250 +
168 251 $groups = array(
169 - 'misc' => array(
170 - 'name' => '',
171 - 'icon' => 'frmfont frm_shuffle_icon',
172 - 'actions' => array(
173 - 'email',
174 - 'wppost',
175 - 'register',
176 - 'quiz',
177 - 'quiz_outcome',
178 - 'twilio',
252 + 'my_actions' => array(
253 + 'name' => __( 'My Actions', 'formidable' ),
254 + 'icon' => 'frmfont frm_shuffle_icon',
255 + 'sections' => array(
256 + 'active' => array(
257 + 'name' => '',
258 + 'actions' => $available_actions,
259 + ),
260 + 'featured' => array(
261 + 'name' => __( 'Featured', 'formidable' ),
262 + 'actions' => array_values( $featured_actions ),
263 + ),
179 264 ),
180 265 ),
181 - 'payment' => array(
182 - 'name' => __( 'eCommerce', 'formidable' ),
266 + 'payment' => array(
267 + 'name' => __( 'E-Commerce', 'formidable' ),
183 268 'icon' => 'frmfont frm_credit_card_alt_icon',
184 - 'actions' => array(
185 - 'paypal',
186 - 'payment',
187 - ),
269 + 'actions' => self::get_payment_actions( $action_controls ),
188 270 ),
189 - 'marketing' => array(
190 - 'name' => __( 'Email Marketing', 'formidable' ),
271 + 'marketing' => array(
272 + 'name' => __( 'Marketing', 'formidable' ),
191 273 'icon' => 'frmfont frm_mail_bulk_icon',
192 274 'actions' => array(
193 275 'mailchimp',
194 276 'activecampaign',
@@ -196,15 +278,21 @@
196 278 'getresponse',
197 279 'aweber',
198 280 'mailpoet',
199 281 'convertkit',
282 + 'twilio',
200 283 ),
201 284 ),
202 - 'crm' => array(
285 + 'crm' => array(
203 286 'name' => __( 'CRM', 'formidable' ),
204 287 'icon' => 'frmfont frm_address_card_icon',
205 288 'actions' => self::get_crm_actions(),
206 289 ),
290 + 'misc' => array(
291 + 'name' => __( 'Misc', 'formidable' ),
292 + 'icon' => 'frmfont frm_shuffle_icon',
293 + 'actions' => self::get_misc_actions( $action_controls ),
294 + ),
207 295 );
208 296
209 297 return apply_filters( 'frm_action_groups', $groups );
210 298 }
@@ -209,8 +297,102 @@
209 297 return apply_filters( 'frm_action_groups', $groups );
210 298 }
211 299
212 300 /**
301 + * Get the actions that are currently available (active) for My Actions section.
302 + *
303 + * @since 6.31
304 + *
305 + * @param array $action_controls The registered action controls.
306 + *
307 + * @return array
308 + */
309 + private static function get_available_my_actions( $action_controls ) {
310 + $available = array(
311 + 'on_submit',
312 + 'email',
313 + 'stripe',
314 + 'square',
315 + 'paypal',
316 + );
317 +
318 + // Include all actions that are marked as active, including custom actions.
319 + // This ensures custom actions appear in "My Actions" when enabled.
320 + foreach ( $action_controls as $action_id => $action_control ) {
321 + if ( ! in_array( $action_id, $available, true ) && ! empty( $action_control->action_options['active'] ) ) {
322 + $available[] = $action_id;
323 + }
324 + }
325 +
326 + return $available;
327 + }
328 +
329 + /**
330 + * Get the actions to include in the Misc section.
331 + *
332 + * @since 6.31
333 + *
334 + * @param array $action_controls The registered action controls.
335 + *
336 + * @return array
337 + */
338 + private static function get_misc_actions( $action_controls ) {
339 + $misc_actions = array(
340 + 'on_submit',
341 + 'email',
342 + 'wppost',
343 + 'register',
344 + 'api',
345 + 'n8n',
346 + 'quiz',
347 + 'quiz_outcome',
348 + 'googlespreadsheet',
349 + );
350 +
351 + // Include all active actions that aren't in specific groups (payment, marketing, crm).
352 + // This ensures custom actions appear in "Misc" when enabled.
353 + $payment_actions = self::get_payment_actions( $action_controls );
354 + $marketing_actions = array( 'mailchimp', 'activecampaign', 'constantcontact', 'getresponse', 'aweber', 'mailpoet', 'convertkit', 'twilio' );
355 +
356 + $excluded_actions = array_merge( $payment_actions, $marketing_actions, self::get_crm_actions() );
357 +
358 + foreach ( $action_controls as $action_id => $action_control ) {
359 + if ( ! in_array( $action_id, $misc_actions, true ) && ! in_array( $action_id, $excluded_actions, true ) && ! empty( $action_control->action_options['active'] ) ) {
360 + $misc_actions[] = $action_id;
361 + }
362 + }
363 +
364 + return $misc_actions;
365 + }
366 +
367 + /**
368 + * Get the actions to include in the E-Commerce section.
369 + *
370 + * @since 6.33
371 + *
372 + * @param array $action_controls
373 + *
374 + * @return array
375 + */
376 + private static function get_payment_actions( $action_controls ) {
377 + $payment_actions = array(
378 + 'paypal',
379 + 'stripe',
380 + 'square',
381 + );
382 +
383 + if ( isset( $action_controls['paypal-legacy'] ) ) {
384 + $payment_actions[] = 'paypal-legacy';
385 + }
386 +
387 + if ( isset( $action_controls['payment'] ) ) {
388 + $payment_actions[] = 'payment';
389 + }
390 +
391 + return $payment_actions;
392 + }
393 +
394 + /**
213 395 * Get the actions to include in the CRM section.
214 396 *
215 397 * @since 6.23
216 398 *
@@ -219,8 +401,9 @@
219 401 private static function get_crm_actions() {
220 402 $crm_actions = array(
221 403 'salesforce',
222 404 'hubspot',
405 + 'zapier',
223 406 );
224 407
225 408 // Only include Highrise when the add-on is active.
226 409 // This is because Highrise is deprecated. We don't want to show it in Lite.
@@ -277,8 +460,12 @@
277 460 $classes .= ' frm_active_action';
278 461 } else {
279 462 $classes .= ' frm_inactive_action';
280 463
464 + if ( str_contains( $action_control->action_options['classes'], 'frm_show_expired_modal' ) ) {
465 + $classes .= ' frm_show_expired_modal';
466 + }
467 +
281 468 if ( $default_position !== false && ( $allowed_count + $default_position ) < 6 ) {
282 469 $group_class .= ' frm-default-show';
283 470 }
284 471
@@ -299,20 +486,42 @@
299 486
300 487 if ( $requires && 'free' !== $requires ) {
301 488 $data['data-requires'] = $requires;
302 489 }
490 +
491 + $learn_more_slug = ! empty( $action_control->action_options['learn-more'] )
492 + ? $action_control->action_options['learn-more']
493 + : self::get_learn_more_slug( $action_control->id_base );
494 +
495 + if ( $learn_more_slug ) {
496 + $data['data-learn-more'] = FrmAppHelper::get_doc_url(
497 + $learn_more_slug,
498 + 'settings-' . $action_control->id_base,
499 + ! str_contains( $learn_more_slug, '/' )
500 + );
501 + }
303 502 }//end if
304 503
305 - // HTML to include on the icon.
306 - $icon_atts = array();
504 + include FrmAppHelper::plugin_path() . '/classes/views/frm-form-actions/_action_icon.php';
505 + }
307 506
308 - if ( $action_control->action_options['color'] !== 'var(--primary-700)' ) {
309 - $icon_atts = array(
507 + /**
508 + * Get the HTML attributes for the action icon.
509 + *
510 + * @since 6.31
511 + *
512 + * @param object $action_control
513 + *
514 + * @return array
515 + */
516 + public static function get_action_icon_atts( $action_control ) {
517 + if ( 'var(--primary-700)' !== $action_control->action_options['color'] ) {
518 + return array(
310 519 'style' => '--primary-700:' . $action_control->action_options['color'],
311 520 );
312 521 }
313 522
314 - include FrmAppHelper::plugin_path() . '/classes/views/frm-form-actions/_action_icon.php';
523 + return array();
315 524 }
316 525
317 526 /**
318 527 * @param string $action
@@ -379,8 +588,12 @@
379 588 }
380 589
381 590 self::maybe_show_limit_warning( $form->id, $form_actions );
382 591
592 + echo '<p class="frm-mb-lg frm-no-actions-message' . ( $form_actions ? ' frm_hidden' : '' ) . '"> '
593 + . esc_html__( 'No actions have been added yet. Select an action above to get started.', 'formidable' )
594 + . '</p>';
595 +
383 596 foreach ( $form_actions as $action ) {
384 597 if ( ! isset( $action_map[ $action->post_excerpt ] ) ) {
385 598 // Don't try and show settings if action no longer exists
386 599 continue;
@@ -447,13 +660,18 @@
447 660 public static function add_form_action() {
448 661 FrmAppHelper::permission_check( 'frm_edit_forms' );
449 662 check_ajax_referer( 'frm_ajax', 'nonce' );
450 663
664 + $action_key = FrmAppHelper::get_param( 'list_id', '', 'post', 'absint' );
665 + $action_type = FrmAppHelper::get_param( 'type', '', 'post', 'sanitize_text_field' );
666 + $lite_actions = array_fill_keys( self::get_lite_actions(), true );
667 +
668 + if ( ! FrmAppHelper::pro_is_connected() && ! isset( $lite_actions[ $action_type ] ) ) {
669 + wp_die();
670 + }
671 +
451 672 global $frm_vars;
452 673
453 - $action_key = FrmAppHelper::get_param( 'list_id', '', 'post', 'absint' );
454 - $action_type = FrmAppHelper::get_param( 'type', '', 'post', 'sanitize_text_field' );
455 -
456 674 /**
457 675 * @var FrmFormAction
458 676 */
459 677 $action_control = self::get_form_actions( $action_type );
@@ -458,10 +676,16 @@
458 676 */
459 677 $action_control = self::get_form_actions( $action_type );
460 678 $action_control->_set( $action_key );
461 679
462 - $form_id = FrmAppHelper::get_param( 'form_id', '', 'post', 'absint' );
463 - $form_action = $action_control->prepare_new( $form_id );
680 + $form_id = FrmAppHelper::get_param( 'form_id', '', 'post', 'absint' );
681 + $form_action = $action_control->prepare_new( $form_id );
682 + $existing_titles = (array) FrmAppHelper::get_post_param( 'existing_titles', array(), 'sanitize_text_field' );
683 +
684 + if ( $existing_titles ) {
685 + $form_action->post_title = self::get_unique_action_title( $form_action->post_title, $existing_titles );
686 + }
687 +
464 688 $use_logging = self::should_show_log_message( $action_type );
465 689 $values = array();
466 690 $form = self::fields_to_values( $form_id, $values );
467 691
@@ -468,8 +692,29 @@
468 692 include FrmAppHelper::plugin_path() . '/classes/views/frm-form-actions/form_action.php';
469 693 wp_die();
470 694 }
471 695
696 + /**
697 + * Returns the first available title not in $existing_titles, appending " (2)", " (3)", etc. if needed.
698 + *
699 + * @since 6.31
700 + *
701 + * @param string $base_title Default action title from the action type.
702 + * @param string[] $existing_titles Titles currently visible in the form editor.
703 + *
704 + * @return string
705 + */
706 + private static function get_unique_action_title( $base_title, array $existing_titles ) {
707 + $taken = array_flip( $existing_titles );
708 + $title = $base_title;
709 +
710 + for ( $n = 2; isset( $taken[ $title ] ); $n++ ) {
711 + $title = $base_title . ' (' . $n . ')';
712 + }
713 +
714 + return $title;
715 + }
716 +
472 717 public static function fill_action() {
473 718 FrmAppHelper::permission_check( 'frm_edit_forms' );
474 719 check_ajax_referer( 'frm_ajax', 'nonce' );
475 720
@@ -497,9 +742,25 @@
497 742 *
498 743 * @return bool
499 744 */
500 745 private static function should_show_log_message( $action_type ) {
501 - $logging = array( 'api', 'salesforce', 'constantcontact', 'activecampaign' );
746 + $logging = array(
747 + 'activecampaign',
748 + 'api',
749 + 'aweber',
750 + 'campaignmonitor',
751 + 'constantcontact',
752 + 'convertkit',
753 + 'getresponse',
754 + 'googlespreadsheet',
755 + 'hubspot',
756 + 'mailchimp',
757 + 'mailpoet',
758 + 'n8n',
759 + 'salesforce',
760 + 'twilio',
761 + 'zapier',
762 + );
502 763 return in_array( $action_type, $logging, true ) && ! function_exists( 'frm_log_autoloader' );
503 764 }
504 765
505 766 /**
@@ -799,8 +1060,123 @@
799 1060 * @return bool|null
800 1061 */
801 1062 public static function prevent_wpml_translations( $null, $post_type ) {
802 1063 return self::$action_post_type === $post_type ? false : $null;
1064 + }
1065 +
1066 + /**
1067 + * If Pro is not connected, hook a filter that will force all non-Lite
1068 + * actions to inactive so the upgrade popup is shown instead.
1069 + *
1070 + * @since 6.31
1071 + *
1072 + * @return void
1073 + */
1074 + private static function maybe_setup_unlicensed_action_gate() {
1075 + if ( FrmAppHelper::pro_is_connected() ) {
1076 + return;
1077 + }
1078 +
1079 + add_filter( 'frm_registered_form_actions', array( self::class, 'disable_unlicensed_actions' ), 100 );
1080 + }
1081 +
1082 + /**
1083 + * For every registered action that is not a Lite action, add a per-action
1084 + * options filter that forces it to inactive with the upgrade class.
1085 + *
1086 + * Runs inside apply_filters('frm_registered_form_actions') at priority 100,
1087 + * so the per-key option filters are in place before the class constructors
1088 + * run in the foreach loop that follows.
1089 + *
1090 + * @since 6.31
1091 + *
1092 + * @param array $actions Map of action_key => class_name.
1093 + *
1094 + * @return array
1095 + */
1096 + public static function disable_unlicensed_actions( $actions ) {
1097 + $lite_actions = array_fill_keys( self::get_lite_actions(), true );
1098 +
1099 + foreach ( array_keys( $actions ) as $key ) {
1100 + if ( isset( $lite_actions[ $key ] ) ) {
1101 + continue;
1102 + }
1103 +
1104 + add_filter(
1105 + 'frm_' . $key . '_action_options',
1106 + function ( $options ) {
1107 + $options['active'] = false;
1108 +
1109 + if ( ! str_contains( $options['classes'], 'frm_show_upgrade' ) ) {
1110 + $options['classes'] .= ' frm_show_upgrade';
1111 + }
1112 +
1113 + return $options;
1114 + }
1115 + );
1116 + }//end foreach
1117 +
1118 + return $actions;
1119 + }
1120 +
1121 + /**
1122 + * Get action keys that are available in Lite without a Pro license.
1123 + *
1124 + * @since 6.31
1125 + *
1126 + * @return string[]
1127 + */
1128 + public static function get_lite_actions() {
1129 + return apply_filters( 'frm_lite_form_actions', array( 'on_submit', 'email', 'payment', 'stripe', 'square', 'paypal' ) );
1130 + }
1131 +
1132 + /**
1133 + * Single source of truth for learn-more URL slugs used in
1134 + * upgrade modals for non-Lite form actions.
1135 + *
1136 + * Slugs without '/' are KB doc slugs (knowledgebase/ prefix is added).
1137 + * Slugs with '/' are direct paths (e.g. features/) used as-is.
1138 + *
1139 + * @since 6.31
1140 + *
1141 + * @return array<string,string> Map of action_key => URL slug.
1142 + */
1143 + public static function get_action_learn_more_links() {
1144 + return array(
1145 + 'wppost' => 'features/user-submitted-posts-wordpress-forms',
1146 + 'register' => 'user-registration',
1147 + 'paypal' => 'features/paypal-wordpress-payments',
1148 + 'quiz' => 'quiz-maker-forms',
1149 + 'quiz_outcome' => 'quiz-maker-forms',
1150 + 'aweber' => 'features/aweber-addon',
1151 + 'mailchimp' => 'features/mailchimp-addon',
1152 + 'zapier' => 'features/form-entry-routing-with-zapier',
1153 + 'twilio' => 'features/twilio-sms-form-notifications',
1154 + 'activecampaign' => 'features/entries-to-activecampaign',
1155 + 'salesforce' => 'features/form-entries-to-salesforce',
1156 + 'constantcontact' => 'features/entries-to-constant-contact',
1157 + 'getresponse' => 'features/form-entries-to-getresponse',
1158 + 'hubspot' => 'features/form-entries-to-hubspot',
1159 + 'mailpoet' => 'features/mailpoet-newsletters-addon',
1160 + 'api' => 'features/wordpress-form-api',
1161 + 'googlespreadsheet' => 'features/google-sheets',
1162 + 'n8n' => 'features/connect-your-forms-to-any-app-with-n8n',
1163 + 'convertkit' => 'features/convertkit',
1164 + );
1165 + }
1166 +
1167 + /**
1168 + * Look up the learn-more doc slug for a given action key.
1169 + *
1170 + * @since 6.31
1171 + *
1172 + * @param string $action_key Action identifier (e.g. 'register').
1173 + *
1174 + * @return string Doc slug or empty string.
1175 + */
1176 + private static function get_learn_more_slug( $action_key ) {
1177 + $links = self::get_action_learn_more_links();
1178 + return $links[ $action_key ] ?? '';
803 1179 }
804 1180 }
805 1181
806 1182 class Frm_Form_Action_Factory {