track( $server_event ); add_action( 'wp_footer', array( __CLASS__, 'injectLeadEvent' ), 20 ); } /** * Injects lead event code into the footer. * * This method retrieves tracked events from the FacebookServerSideEvent * instance and renders them into pixel code using the PixelRenderer. * The resulting code is printed into the footer section of the page. * If the user is an internal user, the method returns without injecting * any code. * * @return void */ public static function injectLeadEvent() { if ( FacebookPluginUtils::is_internal_user() ) { return; } $events = FacebookServerSideEvent::get_instance()->get_tracked_events(); $pixel_code = PixelRenderer::render( $events, self::TRACKING_NAME ); printf( ' %s ', $pixel_code // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } /** * Add pixel code into AJAX success/redirect responses. * * @param array $response Existing AJAX response payload. * @param int $form_id Form ID (provided by WPForms). * @param mixed $extra Unused extra parameter (URL or form data). * * @return array Modified response containing fb_pxl_code when available. */ public static function injectLeadEventAjax( $response, $form_id = null, $extra = null ) { if ( FacebookPluginUtils::is_internal_user() ) { return $response; } $events = FacebookServerSideEvent::get_instance()->get_tracked_events(); if ( empty( $events ) ) { return $response; } $response['fb_pxl_code'] = PixelRenderer::render( $events, self::TRACKING_NAME, false // Return raw fbq calls; they will be eval'd on the client. ); return $response; } /** * Outputs a JS listener that evaluates fb_pxl_code from WPForms AJAX responses. * * This covers the default WPForms AJAX path where the page is not reloaded * and no wp_footer hook is executed after submission. * * @return void */ public static function injectAjaxListener() { ?> self::getEmail( $entry, $form_data ), 'first_name' => ! empty( $name ) ? $name[0] : null, 'last_name' => ! empty( $name ) ? $name[1] : null, 'phone' => self::getPhone( $entry, $form_data ), ); $event_data = array_merge( $event_data, self::getAddress( $entry, $form_data ) ); return $event_data; } /** * Retrieves the phone number from the form data. * * This method extracts the phone number field from the provided form entry * and form data. * * @param array $entry The form entry data. * @param array $form_data The form schema data. * * @return string|null The phone number, or null if no phone field is found. */ private static function getPhone( $entry, $form_data ) { $phone = self::getField( $entry, $form_data, 'phone' ); if ( ! is_null( $phone ) && '' !== $phone ) { return $phone; } return self::getTextFieldByLabel( $entry, $form_data, array( 'phone', 'tel', 'telephone', 'mobile' ) ); } /** * Retrieves the email address from the form data. * * This method extracts the email address field from the provided form entry * and form data. * * @param array $entry The form entry data. * @param array $form_data The form schema data. * * @return string|null The email address, or null * if no email field is found. */ private static function getEmail( $entry, $form_data ) { return self::getField( $entry, $form_data, 'email' ); } /** * Retrieves the address data from the form data. * * This method extracts the address data (city, state, country, and zip) * from the provided form entry * and form data. The country is sent in ISO format. * * Note that if the address scheme is 'us' and country * is not present, 'US' is used as the country. * * @param array $entry The form entry data. * @param array $form_data The form schema data. * * @return array The address data. */ private static function getAddress( $entry, $form_data ) { $address_field_data = self::getField( $entry, $form_data, 'address' ); if ( is_null( $address_field_data ) ) { // Fall back to individual text fields when the Address fancy field // is not available in WPForms Lite. return self::getAddressFromTextFields( $entry, $form_data ); } $address_data = array(); if ( isset( $address_field_data['city'] ) ) { $address_data['city'] = $address_field_data['city']; } if ( isset( $address_field_data['state'] ) ) { $address_data['state'] = $address_field_data['state']; } if ( isset( $address_field_data['country'] ) ) { $address_data['country'] = $address_field_data['country']; } else { $address_scheme = self::getAddressScheme( $form_data ); if ( 'us' === $address_scheme ) { $address_data['country'] = 'US'; } } if ( isset( $address_field_data['postal'] ) ) { $address_data['zip'] = $address_field_data['postal']; } return $address_data; } /** * Retrieves the user's name from the form data. * * This method extracts the name field from the provided form entry * and form data. It supports two formats: * - 'simple': where the name is a single string, * split into first and last name. * - 'first-last': where the name is provided as separate * 'first' and 'last' fields. * * @param array $entry The form entry data. * @param array $form_data The form schema data. * * @return array|null An array containing the first and * last name, or null if no name field is found. */ private static function getName( $entry, $form_data ) { if ( empty( $form_data['fields'] ) || empty( $entry['fields'] ) ) { return null; } $entries = $entry['fields']; foreach ( $form_data['fields'] as $field ) { if ( 'name' === $field['type'] ) { if ( 'simple' === $field['format'] ) { return ServerEventFactory::split_name( $entries[ $field['id'] ] ); } elseif ( 'first-last' === $field['format'] ) { return array( $entries[ $field['id'] ]['first'], $entries[ $field['id'] ]['last'], ); } } } return null; } /** * Retrieves the value of a specific field type from the form entry data. * * This method searches through the form schema data to find a field of * the specified type and returns the corresponding value from the form * entry data. * * @param array $entry The form entry data. * @param array $form_data The form schema data. * @param string $type The type of the field to retrieve. * * @return mixed|null The value of the field, or null if no * field of the specified type is found. */ private static function getField( $entry, $form_data, $type ) { if ( empty( $form_data['fields'] ) || empty( $entry['fields'] ) ) { return null; } foreach ( $form_data['fields'] as $field ) { if ( $field['type'] === $type ) { return $entry['fields'][ $field['id'] ]; } } return null; } /** * Retrieves a text field value by matching its label. * * WPForms Lite users often rely on generic "text" fields instead of * the premium/fancy types. This helper lets us recover values for * phone/address-like fields when their labels match expected names. * * @param array $entry The form entry data. * @param array $form_data The form schema data. * @param string[] $labels Candidate labels (case-insensitive). * @return string|null */ private static function getTextFieldByLabel( $entry, $form_data, $labels ) { if ( empty( $form_data['fields'] ) || empty( $entry['fields'] ) ) { return null; } $normalized_labels = array_map( 'self::normalizeLabel', $labels ); foreach ( $form_data['fields'] as $field ) { if ( 'text' !== $field['type'] || empty( $field['label'] ) ) { continue; } $label = self::normalizeLabel( $field['label'] ); if ( in_array( $label, $normalized_labels, true ) ) { $value = isset( $entry['fields'][ $field['id'] ] ) ? $entry['fields'][ $field['id'] ] : null; return '' !== $value ? $value : null; } } return null; } /** * Builds address data from individual text fields when the Address field * isn't present. * * @param array $entry The form entry data. * @param array $form_data The form schema data. * * @return array */ private static function getAddressFromTextFields( $entry, $form_data ) { $address_data = array(); $address_data['city'] = self::getTextFieldByLabel( $entry, $form_data, array( 'city', 'town' ) ); $address_data['state'] = self::getTextFieldByLabel( $entry, $form_data, array( 'state', 'province', 'region', 'county' ) ); $address_data['country'] = self::getTextFieldByLabel( $entry, $form_data, array( 'country', 'country/region' ) ); $address_data['zip'] = self::getTextFieldByLabel( $entry, $form_data, array( 'zip', 'postal', 'postcode', 'zip code' ) ); // Remove null/empty values so we don't send sparse keys. return array_filter( $address_data, function ( $value ) { return ! is_null( $value ) && '' !== $value; } ); } /** * Normalizes labels for case-insensitive comparison. * * @param string $label The label to normalize. * @return string */ private static function normalizeLabel( $label ) { return strtolower( trim( $label ) ); } /** * Retrieves the address scheme from the form data. * * This method searches through the form schema data to find the first * 'address' field and returns its 'scheme' value, which is either 'us' or * 'international'. If no address field is found, or if the address field * does not have a scheme, this method returns null. * * @param array $form_data The form schema data. * * @return string|null The address scheme, or * null if no address field is found. */ private static function getAddressScheme( $form_data ) { foreach ( $form_data['fields'] as $field ) { if ( 'address' === $field['type'] ) { if ( isset( $field['scheme'] ) ) { return $field['scheme']; } } } return null; } }