__( 'Form Actions', 'formidable' ), 'description' => '', 'public' => false, 'show_ui' => false, 'exclude_from_search' => true, 'show_in_nav_menus' => false, 'show_in_menu' => true, 'capability_type' => 'page', 'supports' => array( 'title', 'editor', 'excerpt', 'custom-fields', 'page-attributes' ), 'has_archive' => false, ) ); self::maybe_setup_unlicensed_action_gate(); self::actions_init(); } public static function actions_init() { self::$registered_actions = new Frm_Form_Action_Factory(); self::register_actions(); do_action( 'frm_form_actions_init' ); } /** * @return void */ public static function register_actions() { $action_classes = array( 'on_submit' => 'FrmOnSubmitAction', 'email' => 'FrmEmailAction', 'wppost' => 'FrmDefPostAction', 'register' => 'FrmDefRegAction', 'stripe' => 'FrmStripeLiteAction', 'square' => 'FrmSquareAction', 'paypal' => 'FrmPayPalLiteAction', 'paypal-legacy' => 'FrmDefPayPalLegacyAction', 'payment' => 'FrmTransLiteAction', 'quiz' => 'FrmDefQuizAction', 'quiz_outcome' => 'FrmDefQuizOutcomeAction', 'api' => 'FrmDefApiAction', 'mailchimp' => 'FrmDefMlcmpAction', 'activecampaign' => 'FrmDefActiveCampaignAction', 'constantcontact' => 'FrmDefConstContactAction', 'getresponse' => 'FrmDefGetResponseAction', 'mailpoet' => 'FrmDefMailpoetAction', 'convertkit' => 'FrmDefConvertKitAction', 'aweber' => 'FrmDefAweberAction', 'twilio' => 'FrmDefTwilioAction', 'salesforce' => 'FrmDefSalesforceAction', 'hubspot' => 'FrmDefHubspotAction', 'highrise' => 'FrmDefHighriseAction', 'zapier' => 'FrmDefZapierAction', 'googlespreadsheet' => 'FrmDefGoogleSpreadsheetAction', 'n8n' => 'FrmDefN8NAction', ); $action_classes = apply_filters( 'frm_registered_form_actions', $action_classes ); $action_classes = self::maybe_unset_highrise( $action_classes ); include_once FrmAppHelper::plugin_path() . '/classes/views/frm-form-actions/email_action.php'; include_once FrmAppHelper::plugin_path() . '/classes/views/frm-form-actions/default_actions.php'; // This needs to be called after we include default_actions.php or FrmDefPayPalLegacyAction will never exist. if ( 'FrmPayPalLiteAction' === $action_classes['paypal'] || ! class_exists( 'FrmPaymentAction' ) || ! class_exists( 'FrmDefPayPalLegacyAction' ) ) { unset( $action_classes['paypal-legacy'] ); } foreach ( $action_classes as $action_class ) { self::$registered_actions->register( $action_class ); } self::apply_default_action_descriptions(); } /** * Sets default descriptions on registered actions from a central list. * * Keeps the description when an add-on replaces a base action class without its own. * * @since 6.31 * * @return void */ private static function apply_default_action_descriptions() { $descriptions = array( 'on_submit' => __( 'Success messages', 'formidable' ), 'email' => __( 'Autoresponder alerts', 'formidable' ), 'wppost' => __( 'Content publishing', 'formidable' ), 'register' => __( 'Account creation', 'formidable' ), 'payment' => __( 'Transaction alerts', 'formidable' ), 'stripe' => __( 'Payment gateway', 'formidable' ), 'square' => __( 'Payment gateway', 'formidable' ), 'paypal' => __( 'Payment gateway', 'formidable' ), 'quiz' => __( 'Automated grading', 'formidable' ), 'quiz_outcome' => __( 'Result logic', 'formidable' ), 'aweber' => __( 'List triggers', 'formidable' ), 'mailchimp' => __( 'Subscription confirmation', 'formidable' ), 'zapier' => __( 'App automation', 'formidable' ), 'n8n' => __( 'Workflow automation', 'formidable' ), 'twilio' => __( 'Text notifications', 'formidable' ), 'activecampaign' => __( 'Contact automation', 'formidable' ), 'salesforce' => __( 'Lead automation', 'formidable' ), 'constantcontact' => __( 'Content distribution', 'formidable' ), 'getresponse' => __( 'Success notifications', 'formidable' ), 'hubspot' => __( 'CRM alerts', 'formidable' ), 'mailpoet' => __( 'Plugin automation', 'formidable' ), 'api' => __( 'System integration', 'formidable' ), 'googlespreadsheet' => __( 'Spreadsheet sync', 'formidable' ), 'convertkit' => __( 'Broadcast publishing', 'formidable' ), ); foreach ( self::$registered_actions->actions as $action ) { if ( $action->action_options['description'] === '' && isset( $descriptions[ $action->id_base ] ) ) { $action->action_options['description'] = $descriptions[ $action->id_base ]; } } } /** * Remove the Highrise action if it is not registered. * * @since 6.23 * * @param array $action_classes * * @return array */ private static function maybe_unset_highrise( $action_classes ) { if ( 'FrmDefHighriseAction' === ( $action_classes['highrise'] ?? '' ) ) { unset( $action_classes['highrise'] ); } return $action_classes; } /** * @since 4.0 * * @param array $values */ public static function email_settings( $values ) { $form = FrmForm::getOne( $values['id'] ); $groups = self::form_action_groups(); $action_controls = self::get_form_actions(); self::maybe_add_action_to_group( $action_controls, $groups ); $allowed = self::active_actions( $action_controls ); include FrmAppHelper::plugin_path() . '/classes/views/frm-form-actions/settings.php'; } /** * Add unknown actions to a group. * * @since 4.0 * * @param array $action_controls * @param array $groups * * @return void */ private static function maybe_add_action_to_group( $action_controls, &$groups ) { $grouped = array(); foreach ( $groups as $group ) { if ( isset( $group['actions'] ) ) { $grouped = array_merge( $grouped, $group['actions'] ); } // Also collect actions from sections if ( ! isset( $group['sections'] ) ) { continue; } foreach ( $group['sections'] as $section ) { if ( isset( $section['actions'] ) ) { $grouped = array_merge( $grouped, $section['actions'] ); } } } foreach ( $action_controls as $action ) { if ( isset( $groups[ $action->id_base ] ) || in_array( $action->id_base, $grouped, true ) ) { continue; } $this_group = $action->action_options['group']; if ( ! isset( $groups[ $this_group ] ) ) { $this_group = 'misc'; } if ( ! isset( $groups[ $this_group ]['actions'] ) ) { $groups[ $this_group ]['actions'] = array(); } $groups[ $this_group ]['actions'][] = $action->id_base; unset( $action ); } } /** * @since 4.0 * * @return array */ public static function form_action_groups() { // Get all action controls to check which are active $action_controls = self::get_form_actions(); // Determine which actions are currently available $available_actions = self::get_available_my_actions( $action_controls ); // Featured actions are add-ons that are NOT currently available $all_addon_actions = array( 'api', 'register', 'n8n', 'quiz', 'quiz_outcome', 'googlespreadsheet', ); $featured_actions = array_diff( $all_addon_actions, $available_actions ); $groups = array( 'my_actions' => array( 'name' => __( 'My Actions', 'formidable' ), 'icon' => 'frmfont frm_shuffle_icon', 'sections' => array( 'active' => array( 'name' => '', 'actions' => $available_actions, ), 'featured' => array( 'name' => __( 'Featured', 'formidable' ), 'actions' => array_values( $featured_actions ), ), ), ), 'payment' => array( 'name' => __( 'E-Commerce', 'formidable' ), 'icon' => 'frmfont frm_credit_card_alt_icon', 'actions' => self::get_payment_actions( $action_controls ), ), 'marketing' => array( 'name' => __( 'Marketing', 'formidable' ), 'icon' => 'frmfont frm_mail_bulk_icon', 'actions' => array( 'mailchimp', 'activecampaign', 'constantcontact', 'getresponse', 'aweber', 'mailpoet', 'convertkit', 'twilio', ), ), 'crm' => array( 'name' => __( 'CRM', 'formidable' ), 'icon' => 'frmfont frm_address_card_icon', 'actions' => self::get_crm_actions(), ), 'misc' => array( 'name' => __( 'Misc', 'formidable' ), 'icon' => 'frmfont frm_shuffle_icon', 'actions' => self::get_misc_actions( $action_controls ), ), ); return apply_filters( 'frm_action_groups', $groups ); } /** * Get the actions that are currently available (active) for My Actions section. * * @since 6.31 * * @param array $action_controls The registered action controls. * * @return array */ private static function get_available_my_actions( $action_controls ) { $available = array( 'on_submit', 'email', 'stripe', 'square', 'paypal', ); // Include all actions that are marked as active, including custom actions. // This ensures custom actions appear in "My Actions" when enabled. foreach ( $action_controls as $action_id => $action_control ) { if ( ! in_array( $action_id, $available, true ) && ! empty( $action_control->action_options['active'] ) ) { $available[] = $action_id; } } return $available; } /** * Get the actions to include in the Misc section. * * @since 6.31 * * @param array $action_controls The registered action controls. * * @return array */ private static function get_misc_actions( $action_controls ) { $misc_actions = array( 'on_submit', 'email', 'wppost', 'register', 'api', 'n8n', 'quiz', 'quiz_outcome', 'googlespreadsheet', ); // Include all active actions that aren't in specific groups (payment, marketing, crm). // This ensures custom actions appear in "Misc" when enabled. $payment_actions = self::get_payment_actions( $action_controls ); $marketing_actions = array( 'mailchimp', 'activecampaign', 'constantcontact', 'getresponse', 'aweber', 'mailpoet', 'convertkit', 'twilio' ); $excluded_actions = array_merge( $payment_actions, $marketing_actions, self::get_crm_actions() ); foreach ( $action_controls as $action_id => $action_control ) { if ( ! in_array( $action_id, $misc_actions, true ) && ! in_array( $action_id, $excluded_actions, true ) && ! empty( $action_control->action_options['active'] ) ) { $misc_actions[] = $action_id; } } return $misc_actions; } /** * Get the actions to include in the E-Commerce section. * * @since 6.33 * * @param array $action_controls * * @return array */ private static function get_payment_actions( $action_controls ) { $payment_actions = array( 'paypal', 'stripe', 'square', ); if ( isset( $action_controls['paypal-legacy'] ) ) { $payment_actions[] = 'paypal-legacy'; } if ( isset( $action_controls['payment'] ) ) { $payment_actions[] = 'payment'; } return $payment_actions; } /** * Get the actions to include in the CRM section. * * @since 6.23 * * @return array */ private static function get_crm_actions() { $crm_actions = array( 'salesforce', 'hubspot', 'zapier', ); // Only include Highrise when the add-on is active. // This is because Highrise is deprecated. We don't want to show it in Lite. if ( class_exists( 'FrmHrsSettings' ) ) { $crm_actions[] = 'highrise'; } return $crm_actions; } /** * Get the number of currently active form actions. * * @since 4.0 * * @param array $action_controls * * @return array */ private static function active_actions( $action_controls ) { $allowed = array(); foreach ( $action_controls as $action_control ) { if ( ! empty( $action_control->action_options['active'] ) ) { $allowed[] = $action_control->id_base; } } return $allowed; } /** * For each add-on, add an li, class, and javascript function. If active, add an additional class. * * @since 4.0 * * @param object $action_control * @param array $allowed */ public static function show_action_icon_link( $action_control, $allowed ) { $data = array(); $classes = ' frm_' . $action_control->id_base . '_action frm_single_action'; $group_class = ' frm-group-' . $action_control->action_options['group']; /* translators: %s: Name of form action */ $upgrade_label = sprintf( esc_html__( '%s form actions', 'formidable' ), $action_control->action_options['tooltip'] ); $default_shown = array( 'wppost', 'register', 'payment', 'quiz', 'hubspot' ); $default_shown = array_values( array_diff( $default_shown, $allowed ) ); $default_position = array_search( $action_control->id_base, $default_shown, true ); $allowed_count = count( $allowed ); if ( ! empty( $action_control->action_options['active'] ) ) { $classes .= ' frm_active_action'; } else { $classes .= ' frm_inactive_action'; if ( str_contains( $action_control->action_options['classes'], 'frm_show_expired_modal' ) ) { $classes .= ' frm_show_expired_modal'; } if ( $default_position !== false && ( $allowed_count + $default_position ) < 6 ) { $group_class .= ' frm-default-show'; } $data['data-upgrade'] = $upgrade_label; $data['data-medium'] = 'settings-' . $action_control->id_base; $upgrading = FrmAddonsController::install_link( $action_control->action_options['plugin'] ); if ( isset( $upgrading['url'] ) ) { $data['data-oneclick'] = json_encode( $upgrading ); } if ( isset( $action_control->action_options['message'] ) ) { $data['data-message'] = $action_control->action_options['message']; } $requires = FrmFormsHelper::get_plan_required( $upgrading ); if ( $requires && 'free' !== $requires ) { $data['data-requires'] = $requires; } $learn_more_slug = ! empty( $action_control->action_options['learn-more'] ) ? $action_control->action_options['learn-more'] : self::get_learn_more_slug( $action_control->id_base ); if ( $learn_more_slug ) { $data['data-learn-more'] = FrmAppHelper::get_doc_url( $learn_more_slug, 'settings-' . $action_control->id_base, ! str_contains( $learn_more_slug, '/' ) ); } }//end if include FrmAppHelper::plugin_path() . '/classes/views/frm-form-actions/_action_icon.php'; } /** * Get the HTML attributes for the action icon. * * @since 6.31 * * @param object $action_control * * @return array */ public static function get_action_icon_atts( $action_control ) { if ( 'var(--primary-700)' !== $action_control->action_options['color'] ) { return array( 'style' => '--primary-700:' . $action_control->action_options['color'], ); } return array(); } /** * @param string $action * * @return array|FrmFormAction A single form action is returned when a specific $action value is requested. */ public static function get_form_actions( $action = 'all' ) { $temp_actions = self::$registered_actions; if ( $temp_actions ) { $temp_actions = $temp_actions->actions; } else { self::actions_init(); $temp_actions = self::$registered_actions->actions; } $actions = array(); foreach ( $temp_actions as $a ) { if ( 'all' !== $action && $a->id_base === $action ) { return $a; } $actions[ $a->id_base ] = $a; } return $actions; } /** * @since 2.0 * * @param object $form * @param array $values * * @return void */ public static function list_actions( $form, $values ) { if ( ! $form ) { return; } /** * Use this hook to migrate old settings into a new action * * @since 2.0 */ do_action( 'frm_before_list_actions', $form ); $filters = array( 'post_status' => 'all', ); $form_actions = FrmFormAction::get_action_for_form( $form->id, 'all', $filters ); /** * @var array */ $action_controls = self::get_form_actions(); $action_map = array(); foreach ( $action_controls as $key => $control ) { $action_map[ $control->id_base ] = $key; } self::maybe_show_limit_warning( $form->id, $form_actions ); echo '
'; foreach ( $form_actions as $action ) { if ( ! isset( $action_map[ $action->post_excerpt ] ) ) { // Don't try and show settings if action no longer exists continue; } self::action_control( $action, $form, $action->ID, $action_controls[ $action_map[ $action->post_excerpt ] ], $values ); } } /** * Show a warning before the form actions list if there are 99 actions, and the limit is set to 99. * If it is filtered, the warning is still shown when applicable, just using the new limit. * * @since 6.17 * * @param int|string $form_id * @param array $form_actions * * @return void */ private static function maybe_show_limit_warning( $form_id, $form_actions ) { $count = count( $form_actions ); if ( $count < 99 ) { return; } $limit = FrmFormAction::get_action_limit( $form_id ); if ( $limit < 99 || $count < $limit ) { return; } $documentation_url = 'https://formidableforms.com/knowledgebase/frm_form_action_limit/#kb-increase-limit-of-form-actions'; echo '