Files
2026-04-28 15:13:50 +02:00

210 lines
8.5 KiB
PHP

<?php
/**
* Copyright (C) 2017-present, Meta, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* @package FacebookPixelPlugin
*/
namespace FacebookPixelPlugin\Core;
use FacebookPixelPlugin\Core\FacebookServerSideEvent;
use FacebookPixelPlugin\FacebookAds\Object\ServerSide\Event;
use FacebookPixelPlugin\FacebookAds\Object\ServerSide\UserData;
use FacebookPixelPlugin\FacebookAds\Object\ServerSide\CustomData;
use FacebookPixelPlugin\FacebookAds\Object\ServerSide\Content;
defined( 'ABSPATH' ) || die( 'Direct access not allowed' );
/**
* Class ServerEventAsyncTask
*/
class ServerEventAsyncTask extends \WP_Async_Task {
/**
* The action to be performed by this task.
*
* @var string
*/
protected $action = 'send_server_events';
/**
* Converts the normalized user data to the keys used in UserData.
*
* Normalized user data is an array of key => value pairs
* where the key is a normalized version of the user data field name
* and the value is the value of the field.
*
* This function takes that array and converts it to
* the format used by UserData.
*
* @param array $user_data_normalized The normalized user data.
*
* @return array The converted user data.
*/
private function convert_user_data( $user_data_normalized ) {
$norm_key_to_key = array(
AAMSettingsFields::EMAIL => 'emails',
AAMSettingsFields::FIRST_NAME => 'first_names',
AAMSettingsFields::LAST_NAME => 'last_names',
AAMSettingsFields::GENDER => 'genders',
AAMSettingsFields::DATE_OF_BIRTH => 'dates_of_birth',
AAMSettingsFields::EXTERNAL_ID => 'external_ids',
AAMSettingsFields::PHONE => 'phones',
AAMSettingsFields::CITY => 'cities',
AAMSettingsFields::STATE => 'states',
AAMSettingsFields::ZIP_CODE => 'zip_codes',
AAMSettingsFields::COUNTRY => 'country_codes',
);
$user_data = array();
foreach ( $user_data_normalized as $norm_key => $field ) {
if ( isset( $norm_key_to_key[ $norm_key ] ) ) {
$user_data[ $norm_key_to_key[ $norm_key ] ] = $field;
} else {
$user_data[ $norm_key ] = $field;
}
}
return $user_data;
}
/**
* Converts an array representation of an event into an Event object.
*
* This function takes an array that represents an event and
* converts it into
* an instance of the `Event` class. If the array contains `user_data`, a
* `UserData` object is created and associated with the event. Similarly, if
* `custom_data` is present, a `CustomData` object is created and
* associated.
* This includes handling nested `contents` and additional custom properties
* like `fb_integration_tracking`.
*
* @param array $event_as_array The array representing the event.
* @return Event The constructed Event object.
*/
private function convert_array_to_event( $event_as_array ) {
$event = new Event( $event_as_array );
if ( isset( $event_as_array['user_data'] ) ) {
$user_data = new UserData(
$this->convert_user_data(
$event_as_array['user_data']
)
);
$event->setUserData( $user_data );
}
if ( isset( $event_as_array['custom_data'] ) ) {
$custom_data = new CustomData( $event_as_array['custom_data'] );
if ( isset( $event_as_array['custom_data']['contents'] ) ) {
$contents = array();
foreach (
$event_as_array['custom_data']['contents'] as $contents_as_array
) {
if ( isset( $contents_as_array['id'] ) ) {
$contents_as_array['product_id'] = $contents_as_array['id'];
}
$contents[] = new Content( $contents_as_array );
}
$custom_data->setContents( $contents );
}
if ( isset(
$event_as_array['custom_data']['fb_integration_tracking']
) ) {
$custom_data->addCustomProperty(
'fb_integration_tracking',
$event_as_array['custom_data']['fb_integration_tracking']
);
}
$event->setCustomData( $custom_data );
}
return $event;
}
/**
* Prepares event data for processing.
*
* This function takes an input array of events, normalizes each event,
* and encodes the data into a base64 JSON string. If the input data
* contains only a single event, it is wrapped in an array for consistency.
*
* @param array $data The input data containing events and the number
* of events. The format is expected to be an array
* where the first element is the event(s) and the
* second element is the number of events.
*
* @return array An associative array containing 'event_data', a base64
* encoded JSON string of the events, and 'num_events',
* the number of events processed.
*
* @throws \Exception If there was an preprocessing error.
*/
protected function prepare_data( $data ) {
try {
if ( ! empty( $data ) ) {
$num_events = $data[1];
$events = $data[0];
if ( 1 === $num_events ) {
$events = array( $events );
}
$events_as_array = array();
foreach ( $events as $event ) {
$events_as_array[] = $event->normalize();
}
return array(
'event_data' =>
base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
wp_json_encode( $events_as_array )
),
'num_events' => $data[1],
);
}
} catch ( \Exception $ex ) {
throw $ex;
}
return array();
}
/**
* Process the events sent via the AJAX action.
*
* This function decodes the JSON string sent in the $_POST['event_data']
* and processes the events as an array of Event objects.
*
* @see FacebookServerSideEvent::send()
*
* @throws \Exception If there was an preprocessing error.
*/
protected function run_action() {
$num_events = isset( $_POST['num_events'] ) ? // phpcs:ignore WordPress.Security.NonceVerification.Missing
sanitize_text_field(
wp_unslash( $_POST['num_events'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
) : null;
if ( 0 === $num_events ) {
return;
}
$events_as_array = json_decode(
base64_decode( // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
isset( $_POST['event_data'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
? $_POST['event_data'] : null // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
),
true
);
if ( ! $events_as_array ) {
return;
}
$events = array();
foreach ( $events_as_array as $event_as_array ) {
$event = $this->convert_array_to_event( $event_as_array );
$events[] = $event;
}
FacebookServerSideEvent::send( $events );
}
}