LLMS_Form_Validator
LLMS_Form_Handler class.
Contents
Source Source
File: includes/forms/class-llms-form-validator.php
class LLMS_Form_Validator { /** * Filters a list of fields down to only the required fields. * * @since 5.0.0 * * @param array[] $fields Array of LifterLMS Form Field settings arrays. * @return array[] */ public function get_required_fields( $fields ) { return array_values( array_filter( $fields, function( $field ) { return ! empty( $field['required'] ); } ) ); } /** * Sanitize a single field according to its type * * @since 5.0.0 * * @param mixed $posted_value User-submitted (dirty) value. * @param array $field LifterLMS field settings. * @return mixed */ public function sanitize_field( $posted_value, $field ) { $map = array( 'email' => 'sanitize_email', 'number' => array( $this, 'sanitize_field_number' ), 'tel' => array( $this, 'sanitize_field_tel' ), 'textarea' => 'sanitize_textarea_field', 'url' => 'esc_url_raw', ); $func = isset( $map[ $field['type'] ] ) ? $map[ $field['type'] ] : 'sanitize_text_field'; // Turn the submitted value into array, so to unify sanitization of scalar and array posted values. $to_sanitize = is_array( $posted_value ) ? $posted_value : array( $posted_value ); $sanitized = array(); foreach ( $to_sanitize as $value ) { $sanitized[] = trim( call_user_func( $func, $value ) ); } return is_array( $posted_value ) ? $sanitized : $sanitized[0]; } /** * Sanitize a number field * * @since 5.0.0 * * @param string $posted_value User-submitted (dirty) value. * @return string */ protected function sanitize_field_number( $posted_value ) { return preg_replace( '/[^0-9.,]/', '', $posted_value ); } /** * Sanitize a telephone field * * @since 5.0.0 * * @param string $posted_value User-submitted (dirty) value. * @return string */ protected function sanitize_field_tel( $posted_value ) { return preg_replace( '/[^\s\#0-9\-\+\(\)\.]/', '', $posted_value ); } /** * Sanitize all user-submitted data according to field settings * * @since 5.0.0 * * @param array $posted_data User-submitted form data. * @param array[] $fields LifterLMS form fields settings. * @return array */ public function sanitize_fields( $posted_data, $fields ) { foreach ( $fields as $field ) { if ( empty( $field['name'] ) || ! isset( $posted_data[ $field['name'] ] ) ) { continue; } $posted_data[ $field['name'] ] = $this->sanitize_field( $posted_data[ $field['name'] ], $field ); } return $posted_data; } /** * Validate a posted value * * @since 5.0.0 * * @param mixed $posted_value Posted data. * @param array $field LifterLMS Form Field settings array. * @return WP_Error|true */ public function validate_field( $posted_value, $field ) { // Validate field by type. $type_map = array( 'email' => array( $this, 'validate_field_email' ), 'number' => array( $this, 'validate_field_number' ), 'tel' => array( $this, 'validate_field_tel' ), 'url' => array( $this, 'validate_field_url' ), ); // Turn the submitted value into array, so to unify validation of scalar and array posted values. $to_validate = is_array( $posted_value ) ? $posted_value : array( $posted_value ); foreach ( $to_validate as $value ) { $valid = isset( $type_map[ $field['type'] ] ) ? call_user_func( $type_map[ $field['type'] ], $value, $field ) : true; if ( is_wp_error( $valid ) ) { // Return as soon as a field is not valid. return $valid; } // HTML Attribute Validations. if ( ! empty( $field['attributes']['minlength'] ) ) { $valid = $this->validate_field_attribute_minlength( $value, $field['attributes']['minlength'], $field ); if ( is_wp_error( $valid ) ) { return $valid; } } } // Perform special validations for special field types (scalar by their nature). $extra_map = array( 'llms_voucher' => array( $this, 'validate_field_voucher' ), 'password_current' => array( $this, 'validate_field_current_password' ), 'user_email' => array( $this, 'validate_field_user_email' ), 'user_login' => array( $this, 'validate_field_user_login' ), ); $valid = isset( $extra_map[ $field['id'] ] ) ? call_user_func( $extra_map[ $field['id'] ], $posted_value ) : true; if ( is_wp_error( $valid ) ) { return $valid; } return true; } /** * Validates the html input minlength attribute * * Used by the User Password field. * * @since 5.0.0 * * @param string $posted_value User-submitted value. * @param int $minlength The minimum string length as parsed from the field block. * @param array $field LifterLMS Form Field settings array. * @return WP_Error|boolean Returns `true` for a valid value, otherwise an error. */ protected function validate_field_attribute_minlength( $posted_value, $minlength, $field ) { if ( strlen( $posted_value ) < $minlength ) { return new WP_Error( 'llms-form-field-invalid', sprintf( __( 'The %1$s must be at least %2$d characters in length.', 'lifterlms' ), isset( $field['label'] ) ? $field['label'] : $field['name'], $minlength ) ); } return true; } /** * Validate an email field * * @since 5.0.0 * * @param string $posted_value User-submitted (dirty) value. * @return WP_Error|boolean Returns `true` for a valid submission, otherwise an error. */ protected function validate_field_email( $posted_value ) { if ( ! is_email( $posted_value ) ) { // Translators: %s user submitted value. return new WP_Error( 'llms-form-field-invalid', sprintf( __( 'The email address "%s" is not valid.', 'lifterlms' ), $posted_value ) ); } return true; } /** * Validate a number field * * Ensures the posted valued is numeric and, where applicable, ensures that the number falls * within minimum and maximum value requirements. * * @since 5.0.0 * * @param string $posted_value User-submitted (dirty) value. * @param array $field The LLMS_Form_Field settings array. * @return WP_Error|boolean Returns `true` for a valid submission, otherwise an error. */ protected function validate_field_number( $posted_value, $field ) { $temp_value = str_replace( ',', '', $posted_value ); if ( ! is_numeric( $temp_value ) ) { // Translators: %1$s field label or name; %2$s = user submitted value. return new WP_Error( 'llms-form-field-invalid', sprintf( __( 'The %1$s "%2$s" is not valid number.', 'lifterlms' ), isset( $field['label'] ) ? $field['label'] : $field['name'], $posted_value ) ); } elseif ( isset( $field['attributes'] ) ) { if ( ( ! empty( $field['attributes']['min'] ) || ( isset( $field['attributes']['min'] ) && '0' === $field['attributes']['min'] ) ) && $temp_value < $field['attributes']['min'] ) { // Translators: %1$s = field label or name; %2$s = user submitted value; %3$d = minimum allowed number. return new WP_Error( 'llms-form-field-invalid', sprintf( __( 'The %1$s "%2$s" must be greater than or equal to %3$d.', 'lifterlms' ), isset( $field['label'] ) ? $field['label'] : $field['name'], $posted_value, $field['attributes']['min'] ) ); } elseif ( ( ! empty( $field['attributes']['max'] ) || ( isset( $field['attributes']['max'] ) && '0' === $field['attributes']['max'] ) ) && $temp_value > $field['attributes']['max'] ) { // Translators: %1$s = field label or name; %2$s = user submitted value; %3$d = maximum allowed number. return new WP_Error( 'llms-form-field-invalid', sprintf( __( 'The %1$s "%2$s" must be less than or equal to %3$d.', 'lifterlms' ), isset( $field['label'] ) ? $field['label'] : $field['name'], $posted_value, $field['attributes']['max'] ) ); } } return true; } /** * Validate a logged-in users current password * * @since 5.0.0 * * @param string $posted_value User-submitted (dirty) value. * @return WP_Error|boolean Returns `true` for a valid submission, otherwise an error. */ protected function validate_field_current_password( $posted_value ) { if ( ! is_user_logged_in() ) { return new WP_Error( 'llms-form-field-invalid-no-user', __( 'You must be logged in to update your password.', 'lifterlms' ), $posted_value ); } $user = wp_get_current_user(); if ( ! wp_check_password( $posted_value, $user->user_pass ) ) { return new WP_Error( 'llms-form-field-invalid', __( 'The submitted password was not correct.', 'lifterlms' ), $posted_value ); } return true; } /** * Validate a telephone field * * @since 5.0.0 * * @param string $posted_value User-submitted (dirty) value. * @return WP_Error|boolean Returns `true` for a valid submission, otherwise an error. */ protected function validate_field_tel( $posted_value ) { if ( 0 < strlen( trim( preg_replace( '/[\s\#0-9\-\+\(\)\.]/', '', $posted_value ) ) ) ) { // Translators: %s = user submitted value. return new WP_Error( 'llms-form-field-invalid', sprintf( __( 'The phone number "%s" is not valid.', 'lifterlms' ), $posted_value ) ); } return true; } /** * Validate a url field * * @since 5.0.0 * * @param string $posted_value User-submitted (dirty) value. * @return WP_Error|boolean Returns `true` for a valid submission, otherwise an error. */ protected function validate_field_url( $posted_value ) { if ( ! filter_var( $posted_value, FILTER_VALIDATE_URL ) ) { // Translators: %s = user submitted value. return new WP_Error( 'llms-form-field-invalid', sprintf( __( 'The URL "%s" is not valid.', 'lifterlms' ), $posted_value ) ); } return true; } /** * Validate a user-email field * * User emails must be unique. * * @since 5.0.0 * * @param string $posted_value User-submitted (dirty) value. * @return WP_Error|boolean Returns `true` for a valid submission, otherwise an error. */ protected function validate_field_user_email( $posted_value ) { if ( email_exists( $posted_value ) ) { return new WP_Error( 'llms-form-field-not-unique', sprintf( __( 'An account with the email address "%s" already exists.', 'lifterlms' ), $posted_value ) ); } return true; } /** * Validate a user-login field * * Ensures that a username isn't found in the LifterLMS username blocklist, that it meets the default * WP core username criteria and that the username doesn't already exist. * * @since 5.0.0 * * @param string $posted_value User-submitted (dirty) value. * @return WP_Error|boolean Returns `true` for a valid submission, otherwise an error. */ protected function validate_field_user_login( $posted_value ) { if ( in_array( $posted_value, llms_get_usernames_blocklist(), true ) || ! validate_username( $posted_value ) ) { return new WP_Error( 'llms-form-field-invalid', sprintf( __( 'The username "%s" is invalid, please try a different username.', 'lifterlms' ), $posted_value ), $posted_value ); } elseif ( username_exists( $posted_value ) ) { return new WP_Error( 'llms-form-field-not-unique', sprintf( __( 'An account with the username "%s" already exists.', 'lifterlms' ), $posted_value ), $posted_value ); } return true; } /** * Validate a voucher field ensuring it's a valid and usable voucher code * * @since 5.0.0 * * @param string $posted_value User-submitted (dirty) value. * @return WP_Error|boolean Returns `true` for a valid submission, otherwise an error. */ protected function validate_field_voucher( $posted_value ) { $voucher = new LLMS_Voucher(); $check = $voucher->check_voucher( $posted_value ); if ( is_wp_error( $check ) ) { return new WP_Error( 'llms-form-field-invalid', $check->get_error_message(), array( $posted_value, $check ) ); } return true; } /** * Validate submitted field values. * * @since 5.0.0 * @since 5.1.0 Don't validate form with no user input only if the form is not empty itself (e.g. contains only invisible fields). * * @param array $posted_data Array of posted data. * @param array[] $fields Array of LifterLMS Form Fields. * @return WP_Error|true */ public function validate_fields( $posted_data, $fields ) { if ( empty( $posted_data ) && ! empty( $fields ) ) { return new WP_Error( 'llms-form-no-input', __( 'Cannot validate a form with no user input.', 'lifterlms' ) ); } $err = new WP_Error(); $err_data = array(); foreach ( $fields as $field ) { if ( empty( $field['name'] ) || empty( $posted_data[ $field['name'] ] ) ) { continue; } $valid = $this->validate_field( $posted_data[ $field['name'] ], $field ); if ( is_wp_error( $valid ) ) { $err->add( $valid->get_error_code(), $valid->get_error_message() ); $err_data[ $field['name'] ] = $field; } } if ( $err->errors ) { $err->add_data( $err_data ); return $err; } return true; } /** * Ensure matching fields match one another. * * @since 5.0.0 * * @param array $posted_data Array of posted data. * @param array[] $fields Array of LifterLMS form fields. * @return WP_Error|true */ public function validate_matching_fields( $posted_data, $fields ) { $err = new WP_Error(); $err_data = array(); $matches = array(); foreach ( $fields as $field ) { // Field doesn't have a match to check or it was already checked by it's match. if ( empty( $field['match'] ) || in_array( $field['id'], $matches, true ) ) { continue; } $field_name = isset( $field['label'] ) ? $field['label'] : $field['name']; $name = $field['name']; $match_field = LLMS_Forms::instance()->get_field_by( $fields, 'id', $field['match'] ); if ( ! $match_field ) { continue; } $match = $match_field['name']; $val = isset( $posted_data[ $name ] ) ? $posted_data[ $name ] : ''; $match = isset( $posted_data[ $match ] ) ? $posted_data[ $match ] : ''; if ( $val !== $match ) { $match_name = isset( $match_field['label'] ) ? $match_field['label'] : $match_field['name']; $err->add( 'llms-form-field-not-matched', sprintf( __( '%1$s must match %2$s.', 'lifterlms' ), $field_name, $match_name ) ); $err_data[] = array( $field, $match_field ); } // Fields reference each other so we only need to check the pair one time. $matches[] = $match_field['id']; } if ( $err->errors ) { $err->add_data( $err_data, 'llms-form-field-not-matched' ); return $err; } return true; } /** * Ensure that all of the forms required fields are present in the submitted data. * * @since 5.0.0 * * @param array $posted_data User data (likely from $_POST). * @param array[] $fields Array of LifterLMS form fields. * @return WP_Error|true */ public function validate_required_fields( $posted_data, $fields ) { // Ensure all required fields have been submitted. $err = new WP_Error(); $err_data = array(); foreach ( $this->get_required_fields( $fields ) as $field ) { if ( empty( $posted_data[ $field['name'] ] ) ) { // Translators: %s = field label or name. $err->add( 'llms-form-missing-required', sprintf( __( '%s is a required field.', 'lifterlms' ), isset( $field['label'] ) ? $field['label'] : $field['name'] ) ); $err_data[ $field['name'] ] = $field; } } if ( $err->errors ) { $err->add_data( $err_data, 'llms-form-missing-required' ); return $err; } return true; } }
Expand full source code Collapse full source code View on GitHub
Methods Methods
- get_required_fields — Filters a list of fields down to only the required fields.
- sanitize_field — Sanitize a single field according to its type
- sanitize_field_number — Sanitize a number field
- sanitize_field_tel — Sanitize a telephone field
- sanitize_fields — Sanitize all user-submitted data according to field settings
- validate_field — Validate a posted value
- validate_field_attribute_minlength — Validates the html input minlength attribute
- validate_field_current_password — Validate a logged-in users current password
- validate_field_email — Validate an email field
- validate_field_number — Validate a number field
- validate_field_tel — Validate a telephone field
- validate_field_url — Validate a url field
- validate_field_user_email — Validate a user-email field
- validate_field_user_login — Validate a user-login field
- validate_field_voucher — Validate a voucher field ensuring it's a valid and usable voucher code
- validate_fields — Validate submitted field values.
- validate_matching_fields — Ensure matching fields match one another.
- validate_required_fields — Ensure that all of the forms required fields are present in the submitted data.
Changelog Changelog
Version | Description |
---|---|
5.0.0 | Introduced. |