set_default_attributes();
}
/**
* Set default form attributes
*/
private function set_default_attributes(): void {
$this->form_attrs = [
'method' => 'post',
'action' => '',
'id' => '',
'class' => 'hvac-form',
'enctype' => 'application/x-www-form-urlencoded',
];
}
/**
* Set form attributes
*
* @param array $attrs Form attributes
* @return self
*/
public function set_attributes( array $attrs ): self {
$this->form_attrs = array_merge( $this->form_attrs, $attrs );
return $this;
}
/**
* Add a field to the form
*
* @param array $field Field configuration
* @return self
*/
public function add_field( array $field ): self {
$defaults = [
'type' => 'text',
'name' => '',
'label' => '',
'value' => '',
'required' => false,
'placeholder' => '',
'class' => '',
'id' => '',
'options' => [],
'sanitize' => 'text',
'validate' => [],
'description' => '',
'wrapper_class' => 'form-row',
];
$field = wp_parse_args( $field, $defaults );
// Auto-generate ID if not provided
if ( empty( $field['id'] ) && ! empty( $field['name'] ) ) {
$field['id'] = sanitize_html_class( $field['name'] );
}
$this->fields[] = $field;
return $this;
}
/**
* Set form data
*
* @param array $data Form data
* @return self
*/
public function set_data( array $data ): self {
$this->data = $data;
return $this;
}
/**
* Set form errors
*
* @param array $errors Form errors
* @return self
*/
public function set_errors( array $errors ): self {
$this->errors = $errors;
return $this;
}
/**
* Render the form
*
* @return string
*/
public function render(): string {
ob_start();
?>
form_attrs as $key => $value ) {
if ( ! empty( $value ) ) {
$attrs[] = sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $value ) );
}
}
return implode( ' ', $attrs );
}
/**
* Render a single field
*
* @param array $field Field configuration
* @return string
*/
private function render_field( $field ) {
$output = sprintf( '', esc_attr( $field['wrapper_class'] ) );
// Label
if ( ! empty( $field['label'] ) ) {
$output .= sprintf(
'',
esc_attr( $field['id'] ),
esc_html( $field['label'] ),
$field['required'] ? ' *' : ''
);
}
// Field
switch ( $field['type'] ) {
case 'select':
$output .= $this->render_select( $field );
break;
case 'textarea':
$output .= $this->render_textarea( $field );
break;
case 'checkbox':
$output .= $this->render_checkbox( $field );
break;
case 'radio':
$output .= $this->render_radio( $field );
break;
case 'file':
$output .= $this->render_file( $field );
break;
default:
$output .= $this->render_input( $field );
}
// Description
if ( ! empty( $field['description'] ) ) {
$output .= sprintf( '%s', esc_html( $field['description'] ) );
}
// Error
if ( isset( $this->errors[ $field['name'] ] ) ) {
$output .= sprintf(
'%s',
esc_html( $this->errors[ $field['name'] ] )
);
}
$output .= '
';
return $output;
}
/**
* Render input field
*
* @param array $field Field configuration
* @return string
*/
private function render_input( $field ) {
$value = $this->get_field_value( $field['name'], $field['value'] );
return sprintf(
'',
esc_attr( $field['type'] ),
esc_attr( $field['name'] ),
esc_attr( $field['id'] ),
esc_attr( $value ),
esc_attr( $field['class'] ),
$field['required'] ? 'required' : '',
! empty( $field['placeholder'] ) ? 'placeholder="' . esc_attr( $field['placeholder'] ) . '"' : ''
);
}
/**
* Render select field
*
* @param array $field Field configuration
* @return string
*/
private function render_select( $field ) {
$value = $this->get_field_value( $field['name'], $field['value'] );
$output = sprintf(
'';
return $output;
}
/**
* Render textarea field
*
* @param array $field Field configuration
* @return string
*/
private function render_textarea( $field ) {
$value = $this->get_field_value( $field['name'], $field['value'] );
return sprintf(
'',
esc_attr( $field['name'] ),
esc_attr( $field['id'] ),
esc_attr( $field['class'] ),
$field['required'] ? 'required' : '',
! empty( $field['placeholder'] ) ? 'placeholder="' . esc_attr( $field['placeholder'] ) . '"' : '',
esc_textarea( $value )
);
}
/**
* Render checkbox field
*
* @param array $field Field configuration
* @return string
*/
private function render_checkbox( $field ) {
$value = $this->get_field_value( $field['name'], $field['value'] );
$is_checked = ! empty( $value );
return sprintf(
'',
esc_attr( $field['name'] ),
esc_attr( $field['id'] ),
esc_attr( $field['class'] ),
checked( $is_checked, true, false )
);
}
/**
* Render radio field group
*
* @param array $field Field configuration
* @return string
*/
private function render_radio( $field ) {
$value = $this->get_field_value( $field['name'], $field['value'] );
$output = '';
foreach ( $field['options'] as $option_value => $option_label ) {
$output .= sprintf(
'',
esc_attr( $field['name'] ),
esc_attr( $option_value ),
checked( $value, $option_value, false ),
esc_html( $option_label )
);
}
$output .= '
';
return $output;
}
/**
* Render file field
*
* @param array $field Field configuration
* @return string
*/
private function render_file( $field ) {
// Ensure form has proper enctype
$this->form_attrs['enctype'] = 'multipart/form-data';
return sprintf(
'',
esc_attr( $field['name'] ),
esc_attr( $field['id'] ),
esc_attr( $field['class'] ),
$field['required'] ? 'required' : ''
);
}
/**
* Get field value from data or default
*
* @param string $name Field name
* @param mixed $default Default value
* @return mixed
*/
private function get_field_value( $name, $default = '' ) {
return isset( $this->data[ $name ] ) ? $this->data[ $name ] : $default;
}
/**
* Validate form data
*
* @param array $data Form data to validate
* @return array Validation errors
*/
public function validate( array $data ): array {
$errors = [];
foreach ( $this->fields as $field ) {
$value = isset( $data[ $field['name'] ] ) ? $data[ $field['name'] ] : '';
// Required field check
if ( $field['required'] && empty( $value ) ) {
$errors[ $field['name'] ] = sprintf( '%s is required.', $field['label'] );
continue;
}
// Custom validation rules
if ( ! empty( $field['validate'] ) && ! empty( $value ) ) {
foreach ( $field['validate'] as $rule => $params ) {
$error = $this->apply_validation_rule( $value, $rule, $params, $field );
if ( $error ) {
$errors[ $field['name'] ] = $error;
break;
}
}
}
}
return $errors;
}
/**
* Apply validation rule
*
* @param mixed $value Value to validate
* @param string $rule Validation rule
* @param mixed $params Rule parameters
* @param array $field Field configuration
* @return string|false Error message or false if valid
*/
private function apply_validation_rule( $value, $rule, $params, $field ) {
switch ( $rule ) {
case 'email':
if ( ! is_email( $value ) ) {
return sprintf( '%s must be a valid email address.', $field['label'] );
}
break;
case 'url':
if ( ! filter_var( $value, FILTER_VALIDATE_URL ) ) {
return sprintf( '%s must be a valid URL.', $field['label'] );
}
break;
case 'min_length':
if ( strlen( $value ) < $params ) {
return sprintf( '%s must be at least %d characters long.', $field['label'], $params );
}
break;
case 'max_length':
if ( strlen( $value ) > $params ) {
return sprintf( '%s must not exceed %d characters.', $field['label'], $params );
}
break;
case 'pattern':
if ( ! preg_match( $params, $value ) ) {
return sprintf( '%s has an invalid format.', $field['label'] );
}
break;
}
return false;
}
/**
* Sanitize form data
*
* @param array $data Raw form data
* @return array Sanitized data
*/
public function sanitize( array $data ): array {
$sanitized = [];
foreach ( $this->fields as $field ) {
if ( ! isset( $data[ $field['name'] ] ) ) {
continue;
}
$value = $data[ $field['name'] ];
switch ( $field['sanitize'] ) {
case 'email':
$sanitized[ $field['name'] ] = sanitize_email( $value );
break;
case 'url':
$sanitized[ $field['name'] ] = esc_url_raw( $value );
break;
case 'textarea':
$sanitized[ $field['name'] ] = sanitize_textarea_field( $value );
break;
case 'int':
$sanitized[ $field['name'] ] = intval( $value );
break;
case 'float':
$sanitized[ $field['name'] ] = floatval( $value );
break;
case 'none':
$sanitized[ $field['name'] ] = $value;
break;
default:
$sanitized[ $field['name'] ] = sanitize_text_field( $value );
}
}
return $sanitized;
}
}