(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