- Added explicit checks to prevent authentication redirects on registration page - Added ensure_registration_page_public() method with priority 1 to run before other auth checks - Included registration-pending and training-login pages in public pages list - Added fallback function in main plugin file to remove auth hooks on registration page This ensures that users can access /trainer/registration/ without being logged in, as intended for new trainer signups.
501 lines
No EOL
11 KiB
PHP
501 lines
No EOL
11 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Community Events Form Builder
|
|
*
|
|
* Helper class for building forms with proper validation and security
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @subpackage Includes
|
|
* @since 1.1.0
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Form_Builder
|
|
*/
|
|
class HVAC_Form_Builder {
|
|
|
|
/**
|
|
* Form fields configuration
|
|
*
|
|
* @var array
|
|
*/
|
|
private $fields = array();
|
|
|
|
/**
|
|
* Form attributes
|
|
*
|
|
* @var array
|
|
*/
|
|
private $form_attrs = array();
|
|
|
|
/**
|
|
* Form errors
|
|
*
|
|
* @var array
|
|
*/
|
|
private $errors = array();
|
|
|
|
/**
|
|
* Form data
|
|
*
|
|
* @var array
|
|
*/
|
|
private $data = array();
|
|
|
|
/**
|
|
* Nonce action
|
|
*
|
|
* @var string
|
|
*/
|
|
private $nonce_action;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param string $nonce_action Nonce action for the form
|
|
*/
|
|
public function __construct( $nonce_action ) {
|
|
$this->nonce_action = $nonce_action;
|
|
$this->set_default_attributes();
|
|
}
|
|
|
|
/**
|
|
* Set default form attributes
|
|
*/
|
|
private function set_default_attributes() {
|
|
$this->form_attrs = array(
|
|
'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( $attrs ) {
|
|
$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( $field ) {
|
|
$defaults = array(
|
|
'type' => 'text',
|
|
'name' => '',
|
|
'label' => '',
|
|
'value' => '',
|
|
'required' => false,
|
|
'placeholder' => '',
|
|
'class' => '',
|
|
'id' => '',
|
|
'options' => array(),
|
|
'sanitize' => 'text',
|
|
'validate' => array(),
|
|
'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( $data ) {
|
|
$this->data = $data;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set form errors
|
|
*
|
|
* @param array $errors Form errors
|
|
* @return self
|
|
*/
|
|
public function set_errors( $errors ) {
|
|
$this->errors = $errors;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Render the form
|
|
*
|
|
* @return string
|
|
*/
|
|
public function render() {
|
|
ob_start();
|
|
?>
|
|
<form <?php echo $this->get_form_attributes(); ?>>
|
|
<?php wp_nonce_field( $this->nonce_action, $this->nonce_action . '_nonce' ); ?>
|
|
|
|
<?php foreach ( $this->fields as $field ) : ?>
|
|
<?php echo $this->render_field( $field ); ?>
|
|
<?php endforeach; ?>
|
|
|
|
<div class="form-submit">
|
|
<button type="submit" class="button button-primary">Submit</button>
|
|
</div>
|
|
</form>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Get form attributes string
|
|
*
|
|
* @return string
|
|
*/
|
|
private function get_form_attributes() {
|
|
$attrs = array();
|
|
foreach ( $this->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( '<div class="%s">', esc_attr( $field['wrapper_class'] ) );
|
|
|
|
// Label
|
|
if ( ! empty( $field['label'] ) ) {
|
|
$output .= sprintf(
|
|
'<label for="%s">%s%s</label>',
|
|
esc_attr( $field['id'] ),
|
|
esc_html( $field['label'] ),
|
|
$field['required'] ? ' <span class="required">*</span>' : ''
|
|
);
|
|
}
|
|
|
|
// 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( '<small class="description">%s</small>', esc_html( $field['description'] ) );
|
|
}
|
|
|
|
// Error
|
|
if ( isset( $this->errors[ $field['name'] ] ) ) {
|
|
$output .= sprintf(
|
|
'<span class="error">%s</span>',
|
|
esc_html( $this->errors[ $field['name'] ] )
|
|
);
|
|
}
|
|
|
|
$output .= '</div>';
|
|
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(
|
|
'<input type="%s" name="%s" id="%s" value="%s" class="%s" %s %s />',
|
|
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(
|
|
'<select name="%s" id="%s" class="%s" %s>',
|
|
esc_attr( $field['name'] ),
|
|
esc_attr( $field['id'] ),
|
|
esc_attr( $field['class'] ),
|
|
$field['required'] ? 'required' : ''
|
|
);
|
|
|
|
foreach ( $field['options'] as $option_value => $option_label ) {
|
|
$output .= sprintf(
|
|
'<option value="%s" %s>%s</option>',
|
|
esc_attr( $option_value ),
|
|
selected( $value, $option_value, false ),
|
|
esc_html( $option_label )
|
|
);
|
|
}
|
|
|
|
$output .= '</select>';
|
|
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(
|
|
'<textarea name="%s" id="%s" class="%s" %s %s>%s</textarea>',
|
|
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(
|
|
'<input type="checkbox" name="%s" id="%s" value="1" class="%s" %s />',
|
|
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 = '<div class="radio-group">';
|
|
|
|
foreach ( $field['options'] as $option_value => $option_label ) {
|
|
$output .= sprintf(
|
|
'<label><input type="radio" name="%s" value="%s" %s /> %s</label>',
|
|
esc_attr( $field['name'] ),
|
|
esc_attr( $option_value ),
|
|
checked( $value, $option_value, false ),
|
|
esc_html( $option_label )
|
|
);
|
|
}
|
|
|
|
$output .= '</div>';
|
|
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(
|
|
'<input type="file" name="%s" id="%s" class="%s" %s />',
|
|
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( $data ) {
|
|
$errors = array();
|
|
|
|
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( $data ) {
|
|
$sanitized = array();
|
|
|
|
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;
|
|
}
|
|
}
|