get_posted_data() as $name => $value ) { $fields[] = array( 'label' => $name, 'type' => '', 'value' => $value, ); } self::submit( 'contact-form-7', is_object( $form ) ? $form->title() : '', $fields ); } /** * WPForms (Lite and Pro). * * @param array $fields Submitted fields, each with name (its label), type and value. * @param array $entry Raw submission. * @param array $form_data The form. * @param int $entry_id Entry ID (0 in Lite). */ public static function wpforms( $fields, $entry, $form_data, $entry_id ) { $list = array(); foreach ( (array) $fields as $field ) { $list[] = array( 'label' => $field['name'] ?? '', 'type' => $field['type'] ?? '', 'value' => $field['value'] ?? '', ); } self::submit( 'wpforms', $form_data['settings']['form_title'] ?? '', $list ); } /** * Gravity Forms. * * @param array $entry The entry, values keyed by field id ("1", "1.3", ...). * @param array $form The form. */ public static function gravity_forms( $entry, $form ) { if ( 'spam' === ( $entry['status'] ?? '' ) ) { return; } $list = array(); foreach ( (array) ( $form['fields'] ?? array() ) as $field ) { $type = is_object( $field ) ? (string) $field->type : ''; $label = is_object( $field ) ? (string) $field->label : ''; $id = is_object( $field ) ? (string) $field->id : ''; if ( 'name' === $type ) { // The name field keeps its parts under sub-ids: .3 first, .6 last. $value = trim( ( $entry[ $id . '.3' ] ?? '' ) . ' ' . ( $entry[ $id . '.6' ] ?? '' ) ); } else { $value = $entry[ $id ] ?? ''; } $list[] = array( 'label' => $label, 'type' => $type, 'value' => $value, ); } self::submit( 'gravity-forms', $form['title'] ?? '', $list ); } /** * Elementor Pro forms. * * @param object $record The form record. */ public static function elementor( $record ) { if ( ! is_object( $record ) || ! method_exists( $record, 'get' ) ) { return; } $list = array(); foreach ( (array) $record->get( 'fields' ) as $field ) { $list[] = array( 'label' => ( $field['title'] ?? '' ) . ' ' . ( $field['id'] ?? '' ), 'type' => $field['type'] ?? '', 'value' => $field['value'] ?? '', ); } $name = method_exists( $record, 'get_form_settings' ) ? $record->get_form_settings( 'form_name' ) : ''; self::submit( 'elementor', (string) $name, $list ); } /** * Fluent Forms. * * @param int $entry_id Entry ID. * @param array $form_data Submitted values keyed by field name (names come as an array). * @param object $form The form. */ public static function fluent_forms( $entry_id, $form_data, $form ) { $list = array(); foreach ( (array) $form_data as $key => $value ) { if ( '_' === substr( (string) $key, 0, 1 ) ) { continue; // Fluent's own hidden fields, like the nonce. } $list[] = array( 'label' => (string) $key, 'type' => 'names' === $key ? 'name' : '', 'value' => $value, ); } self::submit( 'fluent-forms', is_object( $form ) ? (string) $form->title : '', $list ); } /** * Forminator. * * @param object $entry The entry. * @param int $form_id Form ID. * @param array $data Fields, each with name ("email-1", "name-1", ...) and value. */ public static function forminator( $entry, $form_id, $data ) { $list = array(); foreach ( (array) $data as $field ) { $name = (string) ( $field['name'] ?? '' ); $list[] = array( 'label' => $name, 'type' => preg_replace( '/-\d+$/', '', $name ), 'value' => $field['value'] ?? '', ); } // Forminator keeps the form's name in its settings; the post title is a slug. $name = ''; if ( class_exists( 'Forminator_API' ) ) { $form = Forminator_API::get_form( (int) $form_id ); if ( is_object( $form ) && ! is_wp_error( $form ) && ! empty( $form->settings['formName'] ) ) { $name = (string) $form->settings['formName']; } } self::submit( 'forminator', '' !== $name ? $name : get_the_title( (int) $form_id ), $list ); } /** * Ninja Forms. * * @param array $form_data The submission, with fields (key, label, type, value) and settings. */ public static function ninja_forms( $form_data ) { $list = array(); foreach ( (array) ( $form_data['fields'] ?? array() ) as $field ) { $list[] = array( 'label' => ( $field['label'] ?? '' ) . ' ' . ( $field['key'] ?? '' ), 'type' => $field['type'] ?? '', 'value' => $field['value'] ?? '', ); } self::submit( 'ninja-forms', $form_data['settings']['title'] ?? '', $list ); } /** * Formidable Forms. * * @param int $entry_id Entry ID. * @param int $form_id Form ID. */ public static function formidable( $entry_id, $form_id ) { if ( ! class_exists( 'FrmEntry' ) || ! class_exists( 'FrmField' ) ) { return; } $entry = FrmEntry::getOne( $entry_id, true ); if ( ! $entry || ! empty( $entry->is_draft ) ) { return; } $list = array(); foreach ( (array) FrmField::get_all_for_form( $form_id ) as $field ) { $list[] = array( 'label' => (string) $field->name, 'type' => (string) $field->type, 'value' => $entry->metas[ $field->id ] ?? '', ); } $form = class_exists( 'FrmForm' ) ? FrmForm::getOne( $form_id ) : null; self::submit( 'formidable', $form ? (string) $form->name : '', $list ); } /** * Picks out the lead's details and queues the lead. * * @param string $source Which form plugin. * @param string $form_name The form's name. * @param array $fields Each with label (or key), type and value. */ public static function submit( $source, $form_name, array $fields ) { $lead = self::lead_from( $fields ); /** * Filters whether a form submission is sent to Monthwrap as a lead. * * @param bool $send Whether it's sent. * @param string $source The form plugin, e.g. "wpforms". * @param string $form_name The form's name. * @param array $lead The name, email and phone found. */ if ( ! apply_filters( 'monthwrap_send_form', true, $source, $form_name, $lead ) ) { return; } Monthwrap_Store::record( array( 'type' => 'form_submitted', 'id' => 'form:' . $source . ':' . wp_generate_uuid4(), 'occurred_at' => gmdate( 'c' ), 'form_name' => '' !== $form_name ? mb_substr( wp_strip_all_tags( $form_name ), 0, 200 ) : null, 'name' => $lead['name'], 'email' => $lead['email'], 'phone' => $lead['phone'], ) ); } /** * Finds the name, email and phone among a form's fields, by field type first and then * by label ("Your email", "Phone number", "First name", ...). * * @param array $fields Each with label, type and value. * @return array{name: string|null, email: string|null, phone: string|null} */ public static function lead_from( array $fields ) { $email = null; $phone = null; $name = null; $first = null; $last = null; foreach ( $fields as $field ) { $type = strtolower( (string) ( $field['type'] ?? '' ) ); $label = strtolower( (string) ( $field['label'] ?? '' ) ); $value = self::text( $field['value'] ?? '' ); if ( '' === $value ) { continue; } if ( null === $email && ( 'email' === $type || preg_match( '/e-?mail/', $label ) ) && is_email( $value ) ) { $email = $value; } elseif ( null === $phone && ( in_array( $type, array( 'tel', 'phone' ), true ) || preg_match( '/phone|\btel\b|mobile|cell/', $label ) ) ) { if ( preg_match_all( '/\d/', $value ) >= 7 ) { $phone = $value; } } elseif ( null === $first && preg_match( '/^first$|first[-_ ]?name|fname/', $label ) ) { $first = $value; } elseif ( null === $last && preg_match( '/^last$|last[-_ ]?name|lname|surname/', $label ) ) { $last = $value; } elseif ( null === $name && self::is_name_field( $type, $label ) ) { $name = $value; } } if ( null === $name && ( $first || $last ) ) { $name = trim( $first . ' ' . $last ); } elseif ( null !== $name && null === $first && $last ) { $name .= ' ' . $last; // A "Name" field followed by a "Last" one, as Formidable's default form has. } return array( 'name' => null === $name ? null : mb_substr( $name, 0, 200 ), 'email' => null === $email ? null : mb_substr( $email, 0, 254 ), 'phone' => null === $phone ? null : mb_substr( $phone, 0, 40 ), ); } /** * Whether a field holds the person's name. "Company name" or "Username" don't. * * @param string $type Field type, lowercase. * @param string $label Field label or key, lowercase. * @return bool */ private static function is_name_field( $type, $label ) { if ( preg_match( '/company|business|organi[sz]ation|user[-_ ]?name|site|domain|file/', $label ) ) { return false; } return in_array( $type, array( 'name', 'firstname' ), true ) || (bool) preg_match( '/(^|[\s_-])(full[-_ ]?)?name($|[\s_-])/', $label ); } /** * A field's value as one line of plain text. Name fields may arrive as arrays of parts. * * @param mixed $value The value. * @return string */ private static function text( $value ) { if ( is_array( $value ) ) { $value = implode( ' ', array_filter( array_map( array( __CLASS__, 'text' ), $value ) ) ); } return trim( preg_replace( '/\s+/', ' ', sanitize_text_field( (string) $value ) ) ); } }