settings = $settings; $this->recorder = $recorder; } /** * Register hooks. * * @return void */ public function boot(): void { if ( ! $this->settings->load()->flag( 'track_forms' ) ) { return; } add_action( 'wpcf7_mail_sent', array( $this, 'on_contact_form_7' ), 20, 1 ); add_action( 'wpforms_process_complete', array( $this, 'on_wpforms' ), 20, 4 ); add_action( 'gform_after_submission', array( $this, 'on_gravity_forms' ), 20, 2 ); } /** * Contact Form 7. * * Hooked to mail_sent rather than before_send_mail, so a submission the plugin itself rejected as * spam never becomes a lead. * * @param mixed $contact_form WPCF7_ContactForm instance. * @return void */ public function on_contact_form_7( $contact_form ): void { if ( ! class_exists( 'WPCF7_Submission' ) ) { return; } $submission = \WPCF7_Submission::get_instance(); if ( ! $submission || ! method_exists( $submission, 'get_posted_data' ) ) { return; } $posted = $submission->get_posted_data(); $this->capture( is_array( $posted ) ? $posted : array(), 'contact-form-7', is_object( $contact_form ) && method_exists( $contact_form, 'id' ) ? (string) $contact_form->id() : '' ); } /** * WPForms. * * @param array $fields Submitted fields, keyed by field id. * @param array $entry Raw entry. * @param array $form_data Form configuration. * @param int $entry_id Stored entry id. * @return void */ public function on_wpforms( $fields, $entry, $form_data, $entry_id ): void { unset( $entry, $entry_id ); if ( ! is_array( $fields ) ) { return; } $values = array(); foreach ( $fields as $field ) { if ( ! is_array( $field ) || ! isset( $field['type'], $field['value'] ) || ! is_scalar( $field['value'] ) ) { continue; } // WPForms types the fields, so the email is the field marked email rather than whichever one // happens to look like an address. $values[ (string) $field['type'] ] = (string) $field['value']; } $this->capture( $values, 'wpforms', isset( $form_data['id'] ) && is_scalar( $form_data['id'] ) ? (string) $form_data['id'] : '' ); } /** * Gravity Forms. * * @param array $entry Submitted entry. * @param array $form Form configuration. * @return void */ public function on_gravity_forms( $entry, $form ): void { if ( ! is_array( $entry ) || ! is_array( $form ) || ! isset( $form['fields'] ) || ! is_array( $form['fields'] ) ) { return; } $values = array(); foreach ( $form['fields'] as $field ) { if ( ! is_object( $field ) || ! isset( $field->type, $field->id ) ) { continue; } $value = isset( $entry[ (string) $field->id ] ) ? $entry[ (string) $field->id ] : ''; if ( is_scalar( $value ) && '' !== (string) $value ) { $values[ (string) $field->type ] = (string) $value; } } $this->capture( $values, 'gravity-forms', isset( $form['id'] ) && is_scalar( $form['id'] ) ? (string) $form['id'] : '' ); } /** * Identify the submitter and record the lead. * * @param array $values Submitted values, keyed by field name or type. * @param string $source Which form plugin this came from. * @param string $form_id The form's own id. * @return void */ private function capture( array $values, string $source, string $form_id ): void { $identity = Identity::from( array( 'email' => $this->find( $values, array( 'email', 'your-email', 'e-mail', 'mail' ) ), 'phone' => $this->find( $values, array( 'phone', 'tel', 'your-phone', 'telephone', 'mobile' ) ), ) ); if ( $identity->is_empty() ) { // Nothing in the form identifies a person, so there is no lead to attribute — only a page // view that the browser tag has already recorded. return; } $this->recorder->identify( $identity ); // The id changes with every submission on purpose. The same person enquiring twice is two leads, // unlike an order status changing twice, which is one order. $event_id = Event::stable_id( 'lead', $source, $form_id, wp_generate_uuid4() ); $this->recorder->record( Event::create( 'generate_lead', $event_id ) ->with_identity( $identity ) ->with_properties( array( 'form_source' => $source, 'form_id' => $form_id, ) ) ->at( time() ) ); } /** * First non-empty value under any of the given keys. * * @param array $values Submitted values. * @param string[] $keys Candidate keys, in order of preference. * @return string */ private function find( array $values, array $keys ): string { foreach ( $keys as $key ) { foreach ( $values as $name => $value ) { if ( ! is_scalar( $value ) || '' === (string) $value ) { continue; } // A prefix match, because form builders name fields things like your-email-address and // phone_number and the site owner should not have to rename them to be measured. if ( 0 === strpos( strtolower( (string) $name ), $key ) ) { return (string) $value; } } } return ''; } }