register_front_assets(); } /** * Enqueues the front-end styles and scripts. * * @listens action:nf_display_enqueue_scripts */ public function enqueue_front_assets() : void { wp_enqueue_script( 'nf-custom-validation' ); } /** * Adds custom field settings to the pool of all field settings in Ninja Forms. * * @listens filter:ninja_forms_field_settings * * @param array> $settings The field settings. * @return array> */ public function filter_ninja_forms_field_settings( array $settings ) : array { return $settings + $this->get_validation_field_settings(); } /** * Adds custom field settings to a given field in Ninja Forms. * * @listens filter:ninja_forms_field_load_settings * * @param array> $settings The field settings. * @param string $field_type The field type. * @param string $parent_type The parent field type. * @return array> */ public function filter_ninja_forms_field_load_settings( array $settings, string $field_type, string $parent_type ) : array { if ( ! in_array( $field_type, [ 'textbox' ], true ) ) { return $settings; } return $settings + $this->get_validation_field_settings(); } /** * Adds custom labeling settings to the pool of all form settings in Ninja Forms. * * @listens filter:ninja_forms_form_display_settings * * @param array> $settings The form settings. * @return array> */ public function filter_ninja_forms_form_display_settings( array $settings ) : array { return $settings + $this->get_validation_form_settings(); } /** * Adds any custom labels to form settings in Ninja Forms. * * @listens filter:ninja_forms_display_form_settings * * @param array> $settings The form settings. * @param int|string $form_id The form ID. * @return array> */ public function filter_ninja_forms_display_form_settings( array $settings, $form_id ) : array { foreach ( $this->retrieve_error_messages() as $name => $label ) { if ( empty( $settings[ $name ] ) ) { $settings[ $name ] = $label; } } return $settings; } /** * Adds a field validation hooks for submitted data in Ninja Forms. * * Portions of this method are copied from {@see \NF_AJAX_Controllers_Submission::process()} * and {@see \NF_AJAX_Controllers_Submission::submit()} since there no convenient field hooks * nor proper handling of form validation clean-up (renewing the nonce). * * The extra hook is needed to avoid duplicating the iteration and avoid multiple * iterations of the fields. * * @listens filter:ninja_forms_submit_data * @fires filter:ninja_forms/custom_validation/submit_data/validate_field * * @param array> $form_data The form submission data. * @return array> */ public function filter_ninja_forms_submit_data( array $form_data ) : array { if ( empty( $form_data['id'] ) || ! is_numeric( $form_data['id'] ) ) { return $form_data; } // $form = Ninja_Forms()->form( $form_data['id'] )->get(); $form_fields = Ninja_Forms()->form( $form_data['id'] )->get_fields(); if ( ! is_iterable( $form_fields ) ) { return $form_data; } foreach ( $form_fields as $field_id => $field ) { if ( is_object( $field ) ) { $field_data = [ 'id' => $field->get_id(), 'settings' => $field->get_settings(), ]; } $field_data['settings']['id'] = $field_id; $field_data['settings']['value'] = ( $form_data['fields'][ $field_id ]['value'] ?? '' ); $field_data = array_merge( $field_data, $field_data['settings'] ); /** * Filters the field validation error array. * * @event filter:ninja_forms/custom_validation/submit_data/validate_field * * @param NFFieldError|null $field_errors The field errors. * @param array $field_data The field settings. * @param array $form_data The form submission data. */ $field_errors = apply_filters( 'ninja_forms/custom_validation/submit_data/validate_field', null, $field_data, $form_data ); if ( $field_errors ) { $form_data['errors']['fields'][ $field_id ] = $field_errors; } } return $form_data; } /** * Validates the submitted field value against any custom field settings in Ninja Forms. * * @listens filter:ninja_forms/custom_validation/submit_data/validate_field * * @param NFFieldError|null $field_errors The field errors. * @param array $field_data The field settings. * @param array $form_data The form submission data. * @return NFFieldError|null */ public function filter_field_validation( array|string|null $field_errors, array $field_data, array $form_data ) : array|string|null { if ( empty( $field_data['settings']['custom_validation'] ) ) { return $field_errors; } $validator = $field_data['settings']['custom_validation']; $validator = "validate_{$validator}_field"; if ( ! method_exists( $this, $validator ) ) { return $field_errors; } $options = []; $errors = $this->{$validator}( $field_data, $form_data, $options ); if ( ! $errors ) { return $field_errors; } return $errors; } /** * Returns an associative array of error strings from the given error * constants. * * @param ?(list|array) $codes The error codes to retrieve. * If NULL, return all messages. * @return array An array of messages keyed * by their error constant values. */ public function get_error_messages( ?array $codes = null ) : array { /** * Map of error codes and friendly messages. */ static $errors = null; $errors ??= $this->retrieve_error_messages(); if ( is_null( $codes ) ) { return $errors; } $messages = []; if ( array_is_list( $codes ) ) { foreach ( $codes as $code ) { if ( isset( $errors[ $code ] ) ) { $messages[ $code ] = $errors[ $code ]; } } } else { foreach ( $codes as $code => $details ) { if ( null === $details || false === $details || ! isset( $errors[ $code ] ) ) { continue; } if ( is_array( $details ) ) { $details = array_map( fn ( $detail ) => ( is_array( $detail ) ? implode( __( ', ' ), $detail ) : (string) $detail ), $details ); $messages[ $code ] = strtr( $errors[ $code ], $details ); } else { $messages[ $code ] = $errors[ $code ]; } } } return $messages; } /** * Retrieves custom validation field settings to Ninja Forms. * * @return array> */ public function get_validation_field_settings() : array { return [ 'custom_validation' => [ 'name' => 'custom_validation', 'type' => 'select', 'label' => esc_html__( 'Input Validation', 'nf-custom-validation' ), 'width' => 'one-half', 'group' => 'restrictions', 'value' => '', 'options' => [ [ 'label' => '-- ' . esc_html__( 'None', 'nf-custom-validation' ) . ' --', 'value' => '', ], [ 'label' => esc_html_x( 'Example', 'validation type', 'nf-custom-validation' ), 'value' => 'example', ], ], ], ]; } /** * Retrieves custom validation form settings to Ninja Forms. * * @return array> */ public function get_validation_form_settings() : array { $custom_settings = []; foreach ( $this->retrieve_error_messages() as $name => $label ) { $label_setting = [ 'name' => $name, 'type' => 'textbox', 'label' => esc_html( $label ), 'width' => 'full', ]; $custom_settings[] = $label_setting; } return [ 'custom_validation_messages' => [ 'name' => 'custom_validation_messages', 'type' => 'fieldset', 'label' => esc_html__( 'Custom Validation Labels', 'nf-custom-validation' ), 'width' => 'full', 'group' => 'advanced', 'settings' => $custom_settings, ], ]; } /** * Registers the front-end styles and scripts. */ public function register_front_assets() : void { wp_register_script( 'nf-custom-validation', plugin_dir_url( __FILE__ ) . 'resources/scripts/nf-validation.js', [ 'nf-front-end-deps', ] ); } /** * Returns an associative array of error strings from the given error * constants. * * @return array */ public function retrieve_error_messages() : array { $messages = [ static::VALIDATING_FIELD => _x( 'Validating…', 'form validation', 'nf-custom-validation' ), static::INVALID_FIELD => _x( 'Please enter a valid value.', 'form validation', 'nf-custom-validation' ), ]; return $messages; } /** * Performs the expensive/remote validation. * * @param array $field_data The field to validate. * @param array $form_data The form submission data. * @param array $options Validation options. * @return NFFieldError|null */ public function validate_example_field( array $field_data, array $form_data, array $options = [] ) : array|string|null { /** * EXPENSIVE/REMOTE VALIDATION DONE HERE * * THIS EXAMPLE IS INCOMPLETE */ $valid = $response = wp_remote_get( 'https://example.com/', [ 'body' => $field_data['value'], ] ); if ( ! $valid ) { if ( ! $errors ) { $messages = $this->get_error_messages(); return [ 'slug' => static::INVALID_FIELD, 'message' => $messages[ static::INVALID_FIELD ], ]; } $messages = $this->get_error_messages( array_slice( $errors, 0, 1, true ) ); return [ 'slug' => static::INVALID_FIELD, 'message' => $messages[ array_key_first( $messages ) ], ]; } return null; } }