'text', 'firstname' => 'text', 'lastname' => 'text', 'email' => 'email', 'phone' => 'tel', 'textarea' => 'textarea', 'listselect' => 'select', 'liststate' => 'select', 'listcountry' => 'select', 'listmultiselect' => 'select', 'listradio' => 'radio', 'listcheckbox' => 'checkbox', 'listimage' => 'other', 'terms' => 'checkbox', 'checkbox' => 'checkbox', 'date' => 'date', 'number' => 'number', 'starrating' => 'number', 'address' => 'text', 'address2' => 'text', 'city' => 'text', 'zip' => 'text', 'color' => 'text', 'hidden' => 'hidden', ); /** * {@inheritDoc} */ public function slug() { return 'ninja-forms'; } /** * {@inheritDoc} */ public function name() { return 'Ninja Forms'; } /** * {@inheritDoc} */ public function is_available() { return function_exists( 'Ninja_Forms' ) && class_exists( 'Ninja_Forms' ); } /** * {@inheritDoc} */ public function forms() { global $wpdb; // Ninja_Forms()->form()->get_forms() builds a model per form and loads each one's settings. $rows = $wpdb->get_results( $wpdb->prepare( "SELECT id, title FROM {$wpdb->prefix}nf3_forms ORDER BY title ASC LIMIT %d", self::MAX_FORMS ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $forms = array(); foreach ( (array) $rows as $row ) { $forms[] = array( 'id' => (string) $row->id, 'title' => self::plain( $row->title ), ); } return $forms; } /** * {@inheritDoc} */ public function fields( $form_id ) { if ( ! function_exists( 'Ninja_Forms' ) || ! (int) $form_id ) { return array(); } $settings = array(); foreach ( (array) Ninja_Forms()->form( (int) $form_id )->get_fields() as $model ) { if ( is_object( $model ) && method_exists( $model, 'get_settings' ) ) { $settings[] = (array) $model->get_settings(); } } usort( $settings, static function ( $a, $b ) { return ( isset( $a['order'] ) ? (int) $a['order'] : 0 ) - ( isset( $b['order'] ) ? (int) $b['order'] : 0 ); } ); $fields = array(); foreach ( $settings as $field ) { $type = isset( $field['type'] ) ? (string) $field['type'] : ''; $key = isset( $field['key'] ) ? (string) $field['key'] : ''; if ( '' === $key || ! isset( self::TYPE_MAP[ $type ] ) ) { continue; } $fields[] = array( 'name' => $key, 'label' => $this->label( $field, $key ), 'type' => self::TYPE_MAP[ $type ], ); } return $fields; } /** * {@inheritDoc} */ public function register() { if ( ! $this->claim( 'hooks' ) ) { return; } /* * `ninja_forms_after_submission` ($form_data), fired at the end of * NF_AJAX_Controllers_Submission::process(): every field validated (required, the spam question, * reCAPTCHA, hCaptcha and Turnstile fields) and every action ran without errors or a halt (a * halted action such as a payment resumes and fires it then). Ninja Forms 3 submits by AJAX only, * once per submission. A failed email only stops it when an administrator submits (the email * action reports its errors to administrators only). */ add_action( 'ninja_forms_after_submission', array( $this, 'handle' ), 10, 1 ); } /** * Relay one Ninja Forms submission. * * @param array $form_data The processed submission: `form_id`, `settings`, `fields`, `actions`. * @return void */ public function handle( $form_data ) { try { if ( ! is_array( $form_data ) || empty( $form_data['form_id'] ) || empty( $form_data['fields'] ) || ! is_array( $form_data['fields'] ) ) { return; } // The builder's preview: Ninja Forms' own Save action does not store it either. if ( ! empty( $form_data['settings']['is_preview'] ) ) { return; } $form_id = (string) $form_data['form_id']; $sub_id = isset( $form_data['actions']['save']['sub_id'] ) ? (string) $form_data['actions']['save']['sub_id'] : ''; $key = $form_id . ':' . ( '' !== $sub_id ? $sub_id : md5( serialize( $form_data['fields'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize if ( ! $this->claim( 'submission:' . $key ) ) { return; } $values = array(); foreach ( $form_data['fields'] as $field ) { $type = is_array( $field ) && isset( $field['type'] ) ? (string) $field['type'] : ''; $name = is_array( $field ) && isset( $field['key'] ) ? (string) $field['key'] : ''; if ( '' === $name || ! isset( self::TYPE_MAP[ $type ] ) ) { continue; } $value = isset( $field['value'] ) ? $field['value'] : ''; if ( 'checkbox' === $type ) { $value = $this->single_checkbox( $field, $value ); } elseif ( 'date' === $type && is_array( $value ) ) { // A date with a time arrives in parts (date, hour, minute, ampm): one string. $parts = array_map( static function ( $part ) { return is_scalar( $part ) ? trim( (string) $part ) : ''; }, $value ); $value = implode( ' ', array_filter( $parts, 'strlen' ) ); } self::push( $values, $name, $this->label( $field, $name ), self::TYPE_MAP[ $type ], $value ); } $title = isset( $form_data['settings']['title'] ) ? $form_data['settings']['title'] : ''; $this->send( $form_id, self::plain( $title ), $values ); } catch ( \Throwable $e ) { $this->fail( $e ); } } /** * A single checkbox's answer: its "checked value" setting (or `1`) when ticked, nothing when not. * * @param array $field The submitted field. * @param mixed $value Its value: 1/0, `on`, or the checked value. * @return string */ private function single_checkbox( array $field, $value ) { $checked_value = isset( $field['checked_value'] ) && is_scalar( $field['checked_value'] ) ? (string) $field['checked_value'] : ''; $value = is_scalar( $value ) ? (string) $value : ''; if ( in_array( $value, array( '1', 'on', 'true', 'checked' ), true ) || ( '' !== $checked_value && $value === $checked_value ) ) { return '' !== $checked_value ? $checked_value : '1'; } return ''; } /** * A field's label: its label, else its admin label, else its key. * * @param array $field The field settings. * @param string $key Its key. * @return string */ private function label( array $field, $key ) { foreach ( array( 'label', 'admin_label' ) as $setting ) { if ( isset( $field[ $setting ] ) ) { $label = self::plain( $field[ $setting ] ); if ( '' !== $label ) { return $label; } } } return $key; } }