<?php
/**
* AgentForms Form Renderer
*
* Generates HTML form fields from the field_config JSON array.
* Supports all AgentForms field types: text, email, tel, textarea, select,
* number, date, checkbox, radio, hidden, computed.
*
* Conditional fields are rendered with data-af-condition attributes for
* client-side show/hide. Server-side validation remains the source of truth.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Render a list of field definitions as HTML.
*
* @param array $fields List of field definitions.
* @param string $theme Theme (light/dark).
* @return string HTML markup.
*/
function agentforms_render_fields( $fields, $theme = 'light' ) {
if ( empty( $fields ) || ! is_array( $fields ) ) {
return '';
}
$html = '';
foreach ( $fields as $field ) {
$html .= agentforms_render_field( $field, $theme );
}
return $html;
}
/**
* Render a single field definition as HTML.
*
* @param array $field Field definition array.
* @param string $theme Theme (light/dark).
* @return string HTML markup for the field.
*/
function agentforms_render_field( $field, $theme = 'light' ) {
$key = isset( $field['key'] ) ? sanitize_key( $field['key'] ) : '';
$name = isset( $field['name'] ) ? sanitize_key( $field['name'] ) : $key;
$label = isset( $field['label'] ) ? esc_html( $field['label'] ) : ucfirst( $key );
$type = isset( $field['type'] ) ? sanitize_key( $field['type'] ) : 'text';
$required = isset( $field['required'] ) && $field['required'];
$placeholder = isset( $field['placeholder'] ) ? esc_attr( $field['placeholder'] ) : '';
$default = isset( $field['default'] ) ? esc_attr( $field['default'] ) : '';
// Build condition data attribute for client-side visibility
$condition_attr = '';
if ( isset( $field['condition'] ) ) {
$condition_attr = sprintf(
' data-af-condition=\'%s\'',
esc_attr( json_encode( $field['condition'] ) )
);
}
// Validation data attribute
$validation_attr = '';
$validations = array();
if ( $required ) {
$validations[] = 'required';
}
if ( isset( $field['minLength'] ) ) $validations[] = 'minLength:' . intval( $field['minLength'] );
if ( isset( $field['maxLength'] ) ) $validations[] = 'maxLength:' . intval( $field['maxLength'] );
if ( isset( $field['min'] ) ) $validations[] = 'min:' . esc_attr( $field['min'] );
if ( isset( $field['max'] ) ) $validations[] = 'max:' . esc_attr( $field['max'] );
if ( isset( $field['pattern'] ) ) $validations[] = 'pattern:' . esc_attr( $field['pattern'] );
if ( ! empty( $validations ) ) {
$validation_attr = sprintf(
' data-af-validation=\'%s\'',
esc_attr( json_encode( $validations ) )
);
}
// Error message attribute
$error_msg_attr = '';
if ( isset( $field['errorMessage'] ) ) {
$error_msg_attr = sprintf(
' data-af-error=\'%s\'',
esc_attr( $field['errorMessage'] )
);
}
$wrapper_classes = 'agentforms-field';
if ( $required ) $wrapper_classes .= ' af-required';
$html = '<div class="' . $wrapper_classes . '" data-af-field="' . esc_attr( $key ) . '">' . "\n";
// Skip label for hidden and computed fields
if ( ! in_array( $type, array( 'hidden', 'computed' ), true ) ) {
$html .= '<label for="af_' . esc_attr( $key ) . '"';
if ( $required ) $html .= ' class="af-label-required"';
$html .= '>' . "\n";
$html .= ' <span class="af-field-label">' . $label . '</span>';
if ( $required ) {
$html .= ' <span class="af-required-star">*</span>';
}
$html .= '</label>' . "\n";
}
switch ( $type ) {
case 'text':
case 'email':
case 'tel':
case 'phone':
case 'date':
case 'number':
$html .= ' <input type="' . esc_attr( $type === 'phone' ? 'tel' : $type ) . '"' . "\n";
$html .= ' id="af_' . esc_attr( $key ) . '"' . "\n";
$html .= ' name="' . esc_attr( $key ) . '"' . "\n";
$html .= ' class="agentforms-input"' . "\n";
$html .= ' value="' . $default . '"' . "\n";
if ( $placeholder ) $html .= ' placeholder="' . $placeholder . '"' . "\n";
if ( $required ) $html .= ' required' . "\n";
if ( isset( $field['step'] ) ) $html .= ' step="' . esc_attr( $field['step'] ) . '"' . "\n";
if ( isset( $field['min'] ) ) $html .= ' min="' . esc_attr( $field['min'] ) . '"' . "\n";
if ( isset( $field['max'] ) ) $html .= ' max="' . esc_attr( $field['max'] ) . '"' . "\n";
if ( isset( $field['minLength'] ) ) $html .= ' minlength="' . intval( $field['minLength'] ) . '"' . "\n";
if ( isset( $field['maxLength'] ) ) $html .= ' maxlength="' . intval( $field['maxLength'] ) . '"' . "\n";
$html .= $condition_attr . $validation_attr . $error_msg_attr . '>' . "\n";
break;
case 'textarea':
$html .= ' <textarea id="af_' . esc_attr( $key ) . '"' . "\n";
$html .= ' name="' . esc_attr( $key ) . '"' . "\n";
$html .= ' class="agentforms-textarea"' . "\n";
$html .= ' rows="4"' . "\n";
if ( $placeholder ) $html .= ' placeholder="' . $placeholder . '"' . "\n";
if ( $required ) $html .= ' required' . "\n";
if ( isset( $field['maxLength'] ) ) $html .= ' maxlength="' . intval( $field['maxLength'] ) . '"' . "\n";
$html .= $condition_attr . $validation_attr . $error_msg_attr . '>' . "\n";
$html .= ' ' . esc_html( $default ) . "\n";
$html .= ' </textarea>' . "\n";
break;
case 'select':
$html .= ' <select id="af_' . esc_attr( $key ) . '"' . "\n";
$html .= ' name="' . esc_attr( $key ) . '"' . "\n";
$html .= ' class="agentforms-select"' . "\n";
if ( $required ) $html .= ' required' . "\n";
$html .= $condition_attr . $validation_attr . $error_msg_attr . '>' . "\n";
// Empty option for first selection
$html .= ' <option value="">-- Select --</option>' . "\n";
if ( isset( $field['options'] ) && is_array( $field['options'] ) ) {
foreach ( $field['options'] as $option ) {
$opt_value = esc_attr( $option );
$opt_label = esc_html( $option );
$selected = ( $default === $option ) ? ' selected' : '';
$html .= ' <option value="' . $opt_value . '"' . $selected . '>' . $opt_label . '</option>' . "\n";
}
}
$html .= ' </select>' . "\n";
break;
case 'radio':
if ( isset( $field['options'] ) && is_array( $field['options'] ) ) {
$html .= ' <fieldset class="agentforms-radio-group">' . "\n";
foreach ( $field['options'] as $option ) {
$opt_value = esc_attr( $option );
$opt_label = esc_html( $option );
$checked = ( $default === $option ) ? ' checked' : '';
$html .= ' <label class="agentforms-radio-label">' . "\n";
$html .= ' <input type="radio" name="' . esc_attr( $key ) . '" value="' . $opt_value . '"' . $checked . '>';
$html .= ' ' . $opt_label . "\n";
$html .= ' </label>' . "\n";
}
$html .= ' </fieldset>' . "\n";
}
break;
case 'checkbox':
$checked = ( $default === '1' || $default === 'true' || $default === 'on' ) ? ' checked' : '';
$html .= ' <label class="agentforms-checkbox-label">' . "\n";
$html .= ' <input type="checkbox" id="af_' . esc_attr( $key ) . '" name="' . esc_attr( $key ) . '" value="1"' . $checked . $condition_attr . '>';
$html .= ' <span>' . $label . '</span>' . "\n";
$html .= ' </label>' . "\n";
break;
case 'computed':
// Read-only computed field (rendered for display, value set by server)
$html .= ' <input type="text" id="af_' . esc_attr( $key ) . '" name="' . esc_attr( $key ) . '" class="agentforms-input agentforms-computed" value="' . $default . '" readonly>' . "\n";
break;
case 'hidden':
$html .= ' <input type="hidden" id="af_' . esc_attr( $key ) . '" name="' . esc_attr( $key ) . '" value="' . $default . '">' . "\n";
break;
default:
// Fallback to text
$html .= ' <input type="text" id="af_' . esc_attr( $key ) . '" name="' . esc_attr( $key ) . '" class="agentforms-input" value="' . $default . '"' . $condition_attr . '>' . "\n";
break;
}
// Error container
$html .= ' <div class="agentforms-field-error" data-af-error-for="' . esc_attr( $key ) . '"></div>' . "\n";
$html .= '</div>' . "\n";
return $html;
}