- 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.
		
			
				
	
	
		
			231 lines
		
	
	
		
			No EOL
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			231 lines
		
	
	
		
			No EOL
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * HVAC Community Events Security Helper
 | |
|  *
 | |
|  * Provides security utilities and validation methods
 | |
|  *
 | |
|  * @package    HVAC_Community_Events
 | |
|  * @subpackage Includes
 | |
|  * @since      1.1.0
 | |
|  */
 | |
| 
 | |
| if ( ! defined( 'ABSPATH' ) ) {
 | |
| 	exit;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Class HVAC_Security
 | |
|  */
 | |
| class HVAC_Security {
 | |
| 
 | |
| 	/**
 | |
| 	 * Verify a nonce with proper error handling
 | |
| 	 *
 | |
| 	 * @param string $nonce      The nonce to verify
 | |
| 	 * @param string $action     The nonce action
 | |
| 	 * @param bool   $die_on_fail Whether to die on failure
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	public static function verify_nonce( $nonce, $action, $die_on_fail = false ) {
 | |
| 		$is_valid = wp_verify_nonce( $nonce, $action );
 | |
| 
 | |
| 		if ( ! $is_valid ) {
 | |
| 			HVAC_Logger::warning( 'Nonce verification failed', 'Security', array(
 | |
| 				'action' => $action,
 | |
| 				'user_id' => get_current_user_id(),
 | |
| 			) );
 | |
| 
 | |
| 			if ( $die_on_fail ) {
 | |
| 				wp_die( __( 'Security check failed. Please refresh the page and try again.', 'hvac-community-events' ) );
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return $is_valid;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Check if current user has required capability
 | |
| 	 *
 | |
| 	 * @param string $capability The capability to check
 | |
| 	 * @param bool   $die_on_fail Whether to die on failure
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	public static function check_capability( $capability, $die_on_fail = false ) {
 | |
| 		$has_cap = current_user_can( $capability );
 | |
| 
 | |
| 		if ( ! $has_cap ) {
 | |
| 			HVAC_Logger::warning( 'Capability check failed', 'Security', array(
 | |
| 				'capability' => $capability,
 | |
| 				'user_id' => get_current_user_id(),
 | |
| 			) );
 | |
| 
 | |
| 			if ( $die_on_fail ) {
 | |
| 				wp_die( __( 'You do not have permission to perform this action.', 'hvac-community-events' ) );
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return $has_cap;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Check if user is logged in with optional redirect
 | |
| 	 *
 | |
| 	 * @param string $redirect_to URL to redirect if not logged in
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	public static function require_login( $redirect_to = '' ) {
 | |
| 		if ( ! is_user_logged_in() ) {
 | |
| 			if ( ! empty( $redirect_to ) ) {
 | |
| 				wp_safe_redirect( $redirect_to );
 | |
| 				exit;
 | |
| 			}
 | |
| 			return false;
 | |
| 		}
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Sanitize and validate email
 | |
| 	 *
 | |
| 	 * @param string $email Email to validate
 | |
| 	 * @return string|false Sanitized email or false if invalid
 | |
| 	 */
 | |
| 	public static function sanitize_email( $email ) {
 | |
| 		$email = sanitize_email( $email );
 | |
| 		return is_email( $email ) ? $email : false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Sanitize and validate URL
 | |
| 	 *
 | |
| 	 * @param string $url URL to validate
 | |
| 	 * @return string|false Sanitized URL or false if invalid
 | |
| 	 */
 | |
| 	public static function sanitize_url( $url ) {
 | |
| 		$url = esc_url_raw( $url );
 | |
| 		return filter_var( $url, FILTER_VALIDATE_URL ) ? $url : false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Sanitize array of values
 | |
| 	 *
 | |
| 	 * @param array  $array Array to sanitize
 | |
| 	 * @param string $type  Type of sanitization (text|email|url|int)
 | |
| 	 * @return array
 | |
| 	 */
 | |
| 	public static function sanitize_array( $array, $type = 'text' ) {
 | |
| 		if ( ! is_array( $array ) ) {
 | |
| 			return array();
 | |
| 		}
 | |
| 
 | |
| 		$sanitized = array();
 | |
| 		foreach ( $array as $key => $value ) {
 | |
| 			switch ( $type ) {
 | |
| 				case 'email':
 | |
| 					$clean = self::sanitize_email( $value );
 | |
| 					if ( $clean ) {
 | |
| 						$sanitized[ $key ] = $clean;
 | |
| 					}
 | |
| 					break;
 | |
| 				case 'url':
 | |
| 					$clean = self::sanitize_url( $value );
 | |
| 					if ( $clean ) {
 | |
| 						$sanitized[ $key ] = $clean;
 | |
| 					}
 | |
| 					break;
 | |
| 				case 'int':
 | |
| 					$sanitized[ $key ] = intval( $value );
 | |
| 					break;
 | |
| 				default:
 | |
| 					$sanitized[ $key ] = sanitize_text_field( $value );
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return $sanitized;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Escape output based on context
 | |
| 	 *
 | |
| 	 * @param mixed  $value   Value to escape
 | |
| 	 * @param string $context Context (html|attr|url|js)
 | |
| 	 * @return string
 | |
| 	 */
 | |
| 	public static function escape_output( $value, $context = 'html' ) {
 | |
| 		switch ( $context ) {
 | |
| 			case 'attr':
 | |
| 				return esc_attr( $value );
 | |
| 			case 'url':
 | |
| 				return esc_url( $value );
 | |
| 			case 'js':
 | |
| 				return esc_js( $value );
 | |
| 			case 'textarea':
 | |
| 				return esc_textarea( $value );
 | |
| 			default:
 | |
| 				return esc_html( $value );
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Check if request is AJAX
 | |
| 	 *
 | |
| 	 * @return bool
 | |
| 	 */
 | |
| 	public static function is_ajax_request() {
 | |
| 		return defined( 'DOING_AJAX' ) && DOING_AJAX;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get user IP address
 | |
| 	 *
 | |
| 	 * @return string
 | |
| 	 */
 | |
| 	public static function get_user_ip() {
 | |
| 		$ip = '';
 | |
| 
 | |
| 		if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
 | |
| 			$ip = $_SERVER['HTTP_CLIENT_IP'];
 | |
| 		} elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
 | |
| 			$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
 | |
| 		} elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
 | |
| 			$ip = $_SERVER['REMOTE_ADDR'];
 | |
| 		}
 | |
| 
 | |
| 		return sanitize_text_field( $ip );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Rate limiting check
 | |
| 	 *
 | |
| 	 * @param string $action    Action to limit
 | |
| 	 * @param int    $limit     Number of attempts allowed
 | |
| 	 * @param int    $window    Time window in seconds
 | |
| 	 * @param string $identifier User identifier (defaults to IP)
 | |
| 	 * @return bool True if within limits, false if exceeded
 | |
| 	 */
 | |
| 	public static function check_rate_limit( $action, $limit = 5, $window = 300, $identifier = null ) {
 | |
| 		if ( null === $identifier ) {
 | |
| 			$identifier = self::get_user_ip();
 | |
| 		}
 | |
| 
 | |
| 		$key = 'hvac_rate_limit_' . md5( $action . $identifier );
 | |
| 		$attempts = get_transient( $key );
 | |
| 
 | |
| 		if ( false === $attempts ) {
 | |
| 			set_transient( $key, 1, $window );
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		if ( $attempts >= $limit ) {
 | |
| 			HVAC_Logger::warning( 'Rate limit exceeded', 'Security', array(
 | |
| 				'action' => $action,
 | |
| 				'identifier' => $identifier,
 | |
| 				'attempts' => $attempts,
 | |
| 			) );
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		set_transient( $key, $attempts + 1, $window );
 | |
| 		return true;
 | |
| 	}
 | |
| } |