- 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;
 | |
| 	}
 | |
| }
 |