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(); ?>
get_form_attributes(); ?>> nonce_action, $this->nonce_action . '_nonce' ); ?> fields as $field ) : ?> render_field( $field ); ?>
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; } }