(row) Settings. Not shown in the * sidebar. Overrides that only apply to this one form; leave a field blank * to fall back to the ChargeForms > Global Settings default. * * Restructured from one long scrolling page (8 stacked `

` sections, no * navigation) into a left-nav + card layout — reuses the exact * `.chargeforms-settings-shell`/`.chargeforms-settings-nav`/`.chargeforms-card` * components the Global Settings page (class-settings-page.php) already * has, rather than inventing a second styled pattern for the same "several * settings sections" problem. * * Because only the ACTIVE tab's fields are ever posted (unlike the old * one-giant-form version, where every field was always present), * handle_save() can no longer rebuild the whole `settings` blob from * $_POST on every save — Form::update() replaces the `settings` column * wholesale, so that would silently wipe out every other tab's settings. * handle_save() now starts from the form's EXISTING settings and only * overwrites the group that was actually submitted (gated on a hidden * `chargeforms_settings_tab` field), the same "only touch what was actually * submitted" discipline `Settings::update()` already uses site-wide via * its `chargeforms_settings_fields` whitelist. */ class Form_Settings_Page { const TABS = [ 'confirmation', 'notifications', 'layout', 'integrations', 'post-fields', 'restrictions' ]; public function __construct() { add_action( 'admin_menu', [ $this, 'register' ] ); add_action( 'admin_post_chargeforms_save_form_settings', [ $this, 'handle_save' ] ); add_action( 'admin_post_chargeforms_send_test_notification', [ $this, 'handle_send_test' ] ); add_action( 'admin_post_chargeforms_add_notification', [ $this, 'handle_add_notification' ] ); add_action( 'admin_post_chargeforms_save_notification', [ $this, 'handle_save_notification' ] ); add_action( 'admin_post_chargeforms_delete_notification', [ $this, 'handle_delete_notification' ] ); add_action( 'admin_post_chargeforms_duplicate_notification', [ $this, 'handle_duplicate_notification' ] ); add_action( 'admin_post_chargeforms_toggle_notification', [ $this, 'handle_toggle_notification' ] ); add_action( 'wp_ajax_chargeforms_mailchimp_lists', [ $this, 'ajax_mailchimp_lists' ] ); } /** * Resolves what a notification row will actually do when it runs, using * only real, already-stored state (this row + the Global Settings * fallback + the form's own fields) — never a guess — so both the list * view's "incomplete" flag and the editor's plain-language summary line * describe the same real outcome `Notifications::send()` would produce. */ private static function describe_recipient( array $row, array $fields = [] ) { if ( 'field' === $row['send_to_type'] ) { foreach ( $fields as $field ) { if ( ( $field['id'] ?? '' ) === $row['send_to_field'] ) { return [ 'resolved' => true, /* translators: %s: form field label */ 'text' => sprintf( __( 'whatever address the visitor enters in "%s"', 'chargeforms' ), $field['label'] ?? $field['id'] ), ]; } } return [ 'resolved' => false, 'text' => __( 'an email field that no longer exists on this form', 'chargeforms' ), ]; } if ( 'routing' === $row['send_to_type'] ) { foreach ( $fields as $field ) { if ( ( $field['id'] ?? '' ) === $row['routing_field'] ) { return [ 'resolved' => true, /* translators: %s: form field label */ 'text' => sprintf( __( 'a different address depending on the picked option in "%s"', 'chargeforms' ), $field['label'] ?? $field['id'] ), ]; } } return [ 'resolved' => false, 'text' => __( 'a routing field that no longer exists on this form', 'chargeforms' ), ]; } $email = $row['email'] ?: Settings::get( 'notification_email', '' ); if ( $email ) { return [ 'resolved' => true, 'text' => $email ]; } return [ 'resolved' => false, 'text' => __( 'nowhere — no recipient is configured', 'chargeforms' ) ]; } /** Populates the per-form Audience/List render_layout( $s ); break; case 'integrations': $this->render_marketing_integrations( $form['settings']['integrations'] ?? [] ); $this->render_integrations( $mc, $slack, $discord, $telegram, $notion, $hubspot, $sheets ); break; case 'post-fields': $this->render_post_fields( $s ); break; case 'restrictions': $this->render_restrictions( $s, $form['fields'] ); break; default: $this->render_confirmation( $s, $form['fields'], $form['title'], $form['id'] ); } ?>

'email' === ( $f['type'] ?? '' ) && ! empty( $f['id'] ) ) ); ?>

render_conditional_confirmations( $s['confirmations'], $fields ); ?> 'is', 'is_not' => 'is not', 'contains' => 'contains', 'not_contains' => 'does not contain', 'is_empty' => 'is empty', 'is_not_empty' => 'is not empty', ]; /** * "Conditional Confirmations" (the reference product parity) — up to * MAX_CONFIRMATIONS extra confirmations, each gated by one * field/operator/value condition, checked in submission order by * Submission_Handler::resolve_conditional_confirmation(). Fixed, * always-rendered rows (same shape as the Bookings add-on's date * override table) rather than a JS repeater — this classic PHP admin * page has no repeater component of its own, and a blank row is simply * ignored on save, so this needs no "add row" interaction to be usable. */ const MAX_CONFIRMATIONS = 5; private function render_conditional_confirmations( array $rows, array $fields ) { $rows = array_values( $rows ); ?>

` rather than one giant repeater * form — simpler to keep the existing "which fields did this POST * actually submit" save discipline this whole page already uses. */ private function render_notifications( array $settings, array $fields, $form_id ) { $editing_id = isset( $_GET['notification'] ) ? sanitize_text_field( wp_unslash( $_GET['notification'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $rows = Notification_List::from_settings( $settings ); if ( $editing_id ) { $row = Notification_List::find( $settings, $editing_id ); if ( $row ) { $this->render_notification_edit( $row, $fields, $form_id ); return; } } ?>

1 ) : ?>
'email' === ( $f['type'] ?? '' ) && ! empty( $f['id'] ) ) ); $choice_fields = array_values( array_filter( $fields, fn( $f ) => in_array( $f['type'] ?? '', [ 'select', 'radio', 'multiple_choice' ], true ) && ! empty( $f['id'] ) ) ); $back_url = admin_url( 'admin.php?page=chargeforms-form-settings&form_id=' . $form_id . '§ion=notifications' ); $global_default = Settings::get( 'notification_email', '' ); $recipient = self::describe_recipient( $row, $fields ); ?>
←

' . esc_html( $recipient['text'] ) . '' ); ?>

'message', 'textarea_rows' => 8, 'media_buttons' => true, 'teeny' => true, 'quicktags' => true, ] ); ?>

{form_title} {admin_email} {all_data} {field.}

$global_default, 'noRecipientText' => __( 'nowhere — no recipient is configured', 'chargeforms' ), ] ); wp_add_inline_script( 'chargeforms-admin', ' ( function () { var recipientFieldSelect = document.getElementById( \'chargeforms_ntf_send_to_field\' ); var radios = document.querySelectorAll( \'.chargeforms-notification-sendto-radio\' ); var fixedRow = document.getElementById( \'chargeforms-notification-sendto-fixed\' ); var fieldRow = document.getElementById( \'chargeforms-notification-sendto-field\' ); var routingRow = document.getElementById( \'chargeforms-notification-sendto-routing\' ); var emailInput = document.getElementById( \'chargeforms_ntf_email\' ); var routingFieldSelect = document.getElementById( \'chargeforms_ntf_routing_field\' ); var summary = document.getElementById( \'chargeforms-notif-summary\' ); var summaryRecipient = document.getElementById( \'chargeforms-notif-summary-recipient\' ); var globalDefault = window.ChargeFormsNotificationSummary ? window.ChargeFormsNotificationSummary.globalDefault : \'\'; var noRecipientText = window.ChargeFormsNotificationSummary ? window.ChargeFormsNotificationSummary.noRecipientText : \'\'; function currentMode() { var checked = document.querySelector( \'.chargeforms-notification-sendto-radio:checked\' ); return checked ? checked.getAttribute( \'data-mode\' ) : \'fixed\'; } function updateSummary() { if ( ! summary || ! summaryRecipient ) { return; } var mode = currentMode(); var resolved = true; var text; if ( \'field\' === mode ) { var opt = recipientFieldSelect && recipientFieldSelect.selectedOptions[0]; text = opt ? opt.getAttribute( \'data-label\' ) : \'\'; resolved = !! text; } else if ( \'routing\' === mode ) { var routingOpt = routingFieldSelect && routingFieldSelect.selectedOptions[0]; text = routingOpt ? routingOpt.textContent : \'\'; resolved = !! text; } else { text = ( emailInput && emailInput.value.trim() ) || globalDefault; resolved = !! text; } summaryRecipient.textContent = resolved ? text : noRecipientText; summary.classList.toggle( \'is-incomplete\', ! resolved ); } if ( radios.length && fixedRow && fieldRow ) { Array.prototype.forEach.call( radios, function ( radio ) { radio.addEventListener( \'change\', function () { if ( ! radio.checked ) { return; } var mode = radio.getAttribute( \'data-mode\' ); fixedRow.style.display = \'field\' === mode ? \'none\' : \'\'; fieldRow.style.display = \'field\' === mode ? \'\' : \'none\'; if ( routingRow ) { routingRow.style.display = \'routing\' === mode ? \'\' : \'none\'; } radio.closest( \'.chargeforms-segmented\' ).querySelectorAll( \'label\' ).forEach( function ( l ) { l.classList.remove( \'is-active\' ); } ); radio.closest( \'label\' ).classList.add( \'is-active\' ); updateSummary(); } ); } ); } if ( emailInput ) { emailInput.addEventListener( \'input\', updateSummary ); } if ( recipientFieldSelect ) { recipientFieldSelect.addEventListener( \'change\', updateSummary ); } if ( routingFieldSelect ) { routingFieldSelect.addEventListener( \'change\', updateSummary ); } } )();' ); ?>
$settings ] ); } public function handle_add_notification() { $form_id = isset( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0; if ( ! current_user_can( 'chargeforms_manage_forms' ) || ! check_admin_referer( 'chargeforms_add_notification_' . $form_id ) ) { wp_die( esc_html__( 'Not allowed.', 'chargeforms' ), 403 ); } $form = Form::get( $form_id ); if ( ! $form ) { wp_die( esc_html__( 'Form not found.', 'chargeforms' ), 404 ); } $rows = Notification_List::from_settings( $form['settings'] ?: [] ); $new_id = Notification_List::new_id(); $new_row = wp_parse_args( [ 'id' => $new_id, 'name' => __( 'New Notification', 'chargeforms' ) ], Notification_List::defaults() ); $rows[] = $new_row; $this->save_notifications_list( $form_id, $rows ); wp_safe_redirect( admin_url( 'admin.php?page=chargeforms-form-settings&form_id=' . $form_id . '§ion=notifications¬ification=' . rawurlencode( $new_id ) ) ); exit; } public function handle_save_notification() { $form_id = isset( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0; $id = isset( $_POST['notification_id'] ) ? sanitize_text_field( wp_unslash( $_POST['notification_id'] ) ) : ''; if ( ! current_user_can( 'chargeforms_manage_forms' ) || ! check_admin_referer( 'chargeforms_save_notification_' . $form_id . '_' . $id ) ) { wp_die( esc_html__( 'Not allowed.', 'chargeforms' ), 403 ); } $form = Form::get( $form_id ); if ( ! $form ) { wp_die( esc_html__( 'Form not found.', 'chargeforms' ), 404 ); } $data = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $rows = Notification_List::from_settings( $form['settings'] ?: [] ); foreach ( $rows as &$row ) { if ( $row['id'] !== $id ) { continue; } $row['name'] = sanitize_text_field( $data['name'] ?? $row['name'] ); $row['enabled'] = ! empty( $data['enabled'] ); $row['send_to_type'] = in_array( $data['send_to_type'] ?? '', [ 'field', 'routing' ], true ) ? $data['send_to_type'] : 'fixed'; $row['send_to_field'] = sanitize_text_field( $data['send_to_field'] ?? '' ); $row['email'] = ! empty( $data['email'] ) ? sanitize_email( $data['email'] ) : ''; $row['routing_field'] = sanitize_text_field( $data['routing_field'] ?? '' ); $routing_rules = []; foreach ( (array) ( $data['routing_rules'] ?? [] ) as $rule_row ) { $rule_value = sanitize_text_field( $rule_row['value'] ?? '' ); $rule_email = ! empty( $rule_row['email'] ) ? sanitize_email( $rule_row['email'] ) : ''; if ( '' === $rule_value || '' === $rule_email ) { continue; } $routing_rules[] = [ 'value' => $rule_value, 'email' => $rule_email ]; } $row['routing_rules'] = $routing_rules; $row['cc'] = self::sanitize_email_list( $data['cc'] ?? '' ); $row['bcc'] = self::sanitize_email_list( $data['bcc'] ?? '' ); $row['subject'] = sanitize_text_field( $data['subject'] ?? '' ); $row['message'] = wp_kses_post( $data['message'] ?? '' ); $row['attach_uploads'] = ! empty( $data['attach_uploads'] ); $row['condition_enabled'] = ! empty( $data['condition_enabled'] ); $row['condition_match'] = 'any' === ( $data['condition_match'] ?? '' ) ? 'any' : 'all'; $condition_rules = []; foreach ( (array) ( $data['condition_rules'] ?? [] ) as $rule_row ) { $field_id = sanitize_text_field( $rule_row['field_id'] ?? '' ); if ( '' === $field_id ) { continue; } $condition_rules[] = [ 'field_id' => $field_id, 'operator' => array_key_exists( $rule_row['operator'] ?? '', self::VALIDATION_OPERATORS ) ? $rule_row['operator'] : 'is', 'value' => sanitize_text_field( $rule_row['value'] ?? '' ), ]; } $row['condition_rules'] = $condition_rules; break; } unset( $row ); $this->save_notifications_list( $form_id, $rows ); wp_safe_redirect( admin_url( 'admin.php?page=chargeforms-form-settings&form_id=' . $form_id . '§ion=notifications&chargeforms_notice=1' ) ); exit; } public function handle_delete_notification() { $form_id = isset( $_GET['form_id'] ) ? (int) $_GET['form_id'] : 0; $id = isset( $_GET['notification'] ) ? sanitize_text_field( wp_unslash( $_GET['notification'] ) ) : ''; if ( ! current_user_can( 'chargeforms_manage_forms' ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'chargeforms_delete_notification_' . $form_id . '_' . $id ) ) { wp_die( esc_html__( 'Not allowed.', 'chargeforms' ), 403 ); } $form = Form::get( $form_id ); if ( ! $form ) { wp_die( esc_html__( 'Form not found.', 'chargeforms' ), 404 ); } $rows = array_values( array_filter( Notification_List::from_settings( $form['settings'] ?: [] ), fn( $row ) => $row['id'] !== $id ) ); if ( $rows ) { // Never allow deleting the last remaining notification — a form must always have at least one, matching the list view's own "Delete" hide-when-only-one guard. $this->save_notifications_list( $form_id, $rows ); } wp_safe_redirect( admin_url( 'admin.php?page=chargeforms-form-settings&form_id=' . $form_id . '§ion=notifications&chargeforms_notice=1' ) ); exit; } public function handle_duplicate_notification() { $form_id = isset( $_GET['form_id'] ) ? (int) $_GET['form_id'] : 0; $id = isset( $_GET['notification'] ) ? sanitize_text_field( wp_unslash( $_GET['notification'] ) ) : ''; if ( ! current_user_can( 'chargeforms_manage_forms' ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'chargeforms_duplicate_notification_' . $form_id . '_' . $id ) ) { wp_die( esc_html__( 'Not allowed.', 'chargeforms' ), 403 ); } $form = Form::get( $form_id ); if ( ! $form ) { wp_die( esc_html__( 'Form not found.', 'chargeforms' ), 404 ); } $rows = Notification_List::from_settings( $form['settings'] ?: [] ); $original = Notification_List::find( $form['settings'] ?: [], $id ); if ( $original ) { $copy = $original; $copy['id'] = Notification_List::new_id(); /* translators: %s: original notification's name */ $copy['name'] = sprintf( __( '%s (copy)', 'chargeforms' ), $original['name'] ); $rows[] = $copy; $this->save_notifications_list( $form_id, $rows ); } wp_safe_redirect( admin_url( 'admin.php?page=chargeforms-form-settings&form_id=' . $form_id . '§ion=notifications&chargeforms_notice=1' ) ); exit; } public function handle_toggle_notification() { $form_id = isset( $_GET['form_id'] ) ? (int) $_GET['form_id'] : 0; $id = isset( $_GET['notification'] ) ? sanitize_text_field( wp_unslash( $_GET['notification'] ) ) : ''; if ( ! current_user_can( 'chargeforms_manage_forms' ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'chargeforms_toggle_notification_' . $form_id . '_' . $id ) ) { wp_die( esc_html__( 'Not allowed.', 'chargeforms' ), 403 ); } $form = Form::get( $form_id ); if ( ! $form ) { wp_die( esc_html__( 'Form not found.', 'chargeforms' ), 404 ); } $rows = Notification_List::from_settings( $form['settings'] ?: [] ); foreach ( $rows as &$row ) { if ( $row['id'] === $id ) { $row['enabled'] = ! $row['enabled']; break; } } unset( $row ); $this->save_notifications_list( $form_id, $rows ); wp_safe_redirect( admin_url( 'admin.php?page=chargeforms-form-settings&form_id=' . $form_id . '§ion=notifications&chargeforms_notice=1' ) ); exit; } /** * Real gap found live (user comparison against the reference product): these * four settings were already fully wired up on the render side * (frontend/class-shortcode.php reads label_placement/help_placement/ * error_placement/asterisk_position from $form['settings'] with a real * "fall back to Global Settings" chain) — there was just never an * admin UI anywhere to actually set them per form. 'default' is a * real fourth option each select offers, not just left blank. */ private function render_layout( array $s ) { ?>

Layout has configured.', 'chargeforms' ); ?>

'Brevo', 'activecampaign' => 'ActiveCampaign' ] as $provider => $label ) { $config = wp_parse_args( $integrations[ $provider ] ?? [], [ 'enabled' => false, 'list_id' => '', 'first_attribute' => '', 'last_attribute' => '' ] ); ?>

$field_id, 'operator' => array_key_exists( $row['operator'] ?? '', self::CONFIRMATION_OPERATORS ) ? $row['operator'] : 'is', 'value' => sanitize_text_field( $row['value'] ?? '' ), 'type' => in_array( $row['type'] ?? '', [ 'message', 'redirect' ], true ) ? $row['type'] : 'message', 'message' => sanitize_text_field( $row['message'] ?? '' ), 'redirect_url' => ! empty( $row['redirect_url'] ) ? esc_url_raw( $row['redirect_url'] ) : '', ]; } $settings['confirmations'] = $confirmations; } elseif ( 'layout' === $tab ) { $settings['label_placement'] = in_array( $data['label_placement'] ?? '', [ 'top', 'left' ], true ) ? $data['label_placement'] : 'default'; $settings['help_placement'] = in_array( $data['help_placement'] ?? '', [ 'below', 'tooltip' ], true ) ? $data['help_placement'] : 'default'; $settings['error_placement'] = in_array( $data['error_placement'] ?? '', [ 'below', 'above' ], true ) ? $data['error_placement'] : 'default'; $settings['asterisk_position'] = in_array( $data['asterisk_position'] ?? '', [ 'right', 'left' ], true ) ? $data['asterisk_position'] : 'default'; $settings['extra_form_class'] = sanitize_html_class( $data['extra_form_class'] ?? '' ); $settings['submit_button_text'] = sanitize_text_field( $data['submit_button_text'] ?? __( 'Submit', 'chargeforms' ) ); if ( '' === $settings['submit_button_text'] ) { $settings['submit_button_text'] = __( 'Submit', 'chargeforms' ); } $settings['conversational_mode'] = ! empty( $data['conversational_mode'] ); } elseif ( 'integrations' === $tab ) { // Preserves any OTHER integration's per-form config that might // exist under settings.integrations beyond these four (none do // yet — Webhook is site-wide only) rather than blindly // replacing the whole `integrations` key. $existing_integrations = is_array( $settings['integrations'] ?? null ) ? $settings['integrations'] : []; $existing_integrations['mailchimp'] = [ 'enabled' => ! empty( $data['mailchimp_enabled'] ), 'list_id' => sanitize_text_field( $data['mailchimp_list_id'] ?? '' ), 'tags' => sanitize_text_field( $data['mailchimp_tags'] ?? '' ), 'double_optin' => ! empty( $data['mailchimp_double_optin'] ), ]; $existing_integrations['slack'] = [ 'enabled' => ! empty( $data['slack_enabled'] ), 'webhook_url' => ! empty( $data['slack_webhook_url'] ) ? esc_url_raw( $data['slack_webhook_url'] ) : '', 'message_template' => sanitize_textarea_field( $data['slack_message_template'] ?? '' ), ]; $existing_integrations['discord'] = [ 'enabled' => ! empty( $data['discord_enabled'] ), 'webhook_url' => ! empty( $data['discord_webhook_url'] ) ? esc_url_raw( $data['discord_webhook_url'] ) : '', 'message_template' => sanitize_textarea_field( $data['discord_message_template'] ?? '' ), ]; $existing_integrations['telegram'] = [ 'enabled' => ! empty( $data['telegram_enabled'] ), 'bot_token' => sanitize_text_field( $data['telegram_bot_token'] ?? '' ), 'chat_id' => sanitize_text_field( $data['telegram_chat_id'] ?? '' ), 'message_template' => sanitize_textarea_field( $data['telegram_message_template'] ?? '' ), ]; $existing_integrations['notion'] = [ 'enabled' => ! empty( $data['notion_enabled'] ), 'token' => sanitize_text_field( $data['notion_token'] ?? '' ), 'database_id' => sanitize_text_field( $data['notion_database_id'] ?? '' ), 'title_property' => sanitize_text_field( $data['notion_title_property'] ?? '' ), ]; $existing_integrations['hubspot'] = [ 'enabled' => ! empty( $data['hubspot_enabled'] ), ]; $existing_integrations['sheets'] = [ 'enabled' => ! empty( $data['sheets_enabled'] ), 'spreadsheet_id' => sanitize_text_field( $data['sheets_spreadsheet_id'] ?? '' ), 'sheet_name' => sanitize_text_field( $data['sheets_sheet_name'] ?? '' ), ]; foreach ( [ 'brevo', 'activecampaign' ] as $provider ) { $existing_integrations[ $provider ] = [ 'enabled' => ! empty( $data[ $provider . '_enabled' ] ), 'list_id' => absint( $data[ $provider . '_list_id' ] ?? 0 ), 'first_attribute' => strtoupper( sanitize_key( $data[ $provider . '_first_attribute' ] ?? '' ) ), 'last_attribute' => strtoupper( sanitize_key( $data[ $provider . '_last_attribute' ] ?? '' ) ), ]; } $settings['integrations'] = $existing_integrations; } elseif ( 'post-fields' === $tab ) { $settings['create_post_enabled'] = ! empty( $data['create_post_enabled'] ); $settings['create_post_type'] = in_array( $data['create_post_type'] ?? '', [ 'post', 'page' ], true ) ? $data['create_post_type'] : 'post'; $settings['create_post_status'] = in_array( $data['create_post_status'] ?? '', [ 'draft', 'pending', 'publish' ], true ) ? $data['create_post_status'] : 'draft'; } elseif ( 'restrictions' === $tab ) { $settings['require_login'] = ! empty( $data['require_login'] ); $settings['require_login_message'] = sanitize_text_field( $data['require_login_message'] ?? '' ); $settings['deny_empty_submission'] = ! empty( $data['deny_empty_submission'] ); $settings['limit_entries_enabled'] = ! empty( $data['limit_entries_enabled'] ); $settings['limit_entries_count'] = max( 1, (int) ( $data['limit_entries_count'] ?? 100 ) ); $settings['limit_entries_message'] = sanitize_text_field( $data['limit_entries_message'] ?? '' ); $settings['schedule_enabled'] = ! empty( $data['schedule_enabled'] ); $settings['schedule_start'] = sanitize_text_field( $data['schedule_start'] ?? '' ); $settings['schedule_end'] = sanitize_text_field( $data['schedule_end'] ?? '' ); $settings['schedule_pending_message'] = sanitize_text_field( $data['schedule_pending_message'] ?? '' ); $settings['schedule_expired_message'] = sanitize_text_field( $data['schedule_expired_message'] ?? '' ); $settings['restrict_ip_enabled'] = ! empty( $data['restrict_ip_enabled'] ); $settings['restrict_ip_list'] = sanitize_textarea_field( $data['restrict_ip_list'] ?? '' ); $settings['restrict_ip_message'] = sanitize_text_field( $data['restrict_ip_message'] ?? '' ); $settings['restrict_keyword_enabled'] = ! empty( $data['restrict_keyword_enabled'] ); $settings['restrict_keyword_list'] = sanitize_textarea_field( $data['restrict_keyword_list'] ?? '' ); $settings['restrict_keyword_message'] = sanitize_text_field( $data['restrict_keyword_message'] ?? '' ); $settings['validation_enabled'] = ! empty( $data['validation_enabled'] ); $settings['validation_match'] = 'any' === ( $data['validation_match'] ?? '' ) ? 'any' : 'all'; $settings['validation_invert'] = ! empty( $data['validation_invert'] ); $settings['validation_message'] = sanitize_text_field( $data['validation_message'] ?? '' ); $validation_rules = []; foreach ( (array) ( $data['validation_rules'] ?? [] ) as $row ) { $field_id = sanitize_text_field( $row['field_id'] ?? '' ); if ( '' === $field_id ) { continue; } $validation_rules[] = [ 'field_id' => $field_id, 'operator' => array_key_exists( $row['operator'] ?? '', self::VALIDATION_OPERATORS ) ? $row['operator'] : 'is', 'value' => sanitize_text_field( $row['value'] ?? '' ), ]; } $settings['validation_rules'] = $validation_rules; $settings['delete_entry_data'] = ! empty( $data['delete_entry_data'] ); } Form::update( $form_id, [ 'settings' => $settings ] ); wp_safe_redirect( admin_url( 'admin.php?page=chargeforms-form-settings&form_id=' . $form_id . '§ion=' . $tab . '&chargeforms_notice=1' ) ); exit; } /** * Roadmap issue #20 ("no... test email"): sends a real notification * immediately, through the exact same Includes\Notifications::send() * path (and Notification_Log record — visible at Tools > Notifications * right after) a real submission would use, with placeholder values in * place of actual entry data. entry_id is 0 (no real entry exists) — * SubmissionHandler::send_entry_notification() accepts that as-is * since it never does anything with entry_id besides pass it through * to the log record. */ public function handle_send_test() { $form_id = isset( $_POST['form_id'] ) ? (int) $_POST['form_id'] : 0; if ( ! current_user_can( 'chargeforms_manage_forms' ) || ! check_admin_referer( 'chargeforms_send_test_notification_' . $form_id ) ) { wp_die( esc_html__( 'Not allowed.', 'chargeforms' ), 403 ); } $form = Form::get( $form_id ); if ( ! $form ) { wp_die( esc_html__( 'Form not found.', 'chargeforms' ), 404 ); } $sample_values = []; foreach ( $form['fields'] as $field ) { if ( ! empty( $field['id'] ) ) { $sample_values[ $field['id'] ] = __( '(sample value)', 'chargeforms' ); } } \ChargeForms\Frontend\SubmissionHandler::send_entry_notification( $form, $sample_values, 0, '', true ); wp_safe_redirect( admin_url( 'admin.php?page=chargeforms-form-settings&form_id=' . $form_id . '§ion=notifications&chargeforms_test_sent=1' ) ); exit; } }