- Implemented a new PHP script to retrieve insights for the last N days (default 30). - Supports command-line options for token, account ID, days, API version, and output file. - Fetches data at campaign, adset, and ad levels, with filtering for active statuses. - Handles JSON output and optional file saving, including directory creation if necessary. - Includes error handling for cURL requests and JSON responses.
1015 lines
29 KiB
PHP
1015 lines
29 KiB
PHP
<?php
|
|
namespace factory;
|
|
|
|
class FacebookAds
|
|
{
|
|
static private function clients_has_deleted_column()
|
|
{
|
|
global $mdb;
|
|
|
|
static $has_deleted = null;
|
|
if ( $has_deleted !== null )
|
|
{
|
|
return (bool) $has_deleted;
|
|
}
|
|
|
|
try
|
|
{
|
|
$stmt = $mdb -> pdo -> prepare( 'SHOW COLUMNS FROM clients LIKE :column_name' );
|
|
if ( $stmt )
|
|
{
|
|
$stmt -> bindValue( ':column_name', 'deleted', \PDO::PARAM_STR );
|
|
$stmt -> execute();
|
|
$has_deleted = $stmt -> fetch( \PDO::FETCH_ASSOC ) ? 1 : 0;
|
|
}
|
|
else
|
|
{
|
|
$has_deleted = 0;
|
|
}
|
|
}
|
|
catch ( \Throwable $e )
|
|
{
|
|
$has_deleted = 0;
|
|
}
|
|
|
|
return (bool) $has_deleted;
|
|
}
|
|
|
|
static private function sql_clients_not_deleted( $alias = '' )
|
|
{
|
|
$alias = trim( (string) $alias );
|
|
$prefix = $alias !== '' ? $alias . '.' : '';
|
|
|
|
if ( self::clients_has_deleted_column() )
|
|
{
|
|
return 'COALESCE(' . $prefix . 'deleted, 0) = 0';
|
|
}
|
|
|
|
return '1=1';
|
|
}
|
|
|
|
static public function get_clients_for_sync( $client_id = 0 )
|
|
{
|
|
global $mdb;
|
|
|
|
$client_id = (int) $client_id;
|
|
$clients_not_deleted_sql = self::sql_clients_not_deleted();
|
|
|
|
$params = [];
|
|
$where_client = '';
|
|
if ( $client_id > 0 )
|
|
{
|
|
$where_client = ' AND id = :client_id';
|
|
$params[':client_id'] = $client_id;
|
|
}
|
|
|
|
return $mdb -> query(
|
|
'SELECT *
|
|
FROM clients
|
|
WHERE ' . $clients_not_deleted_sql . '
|
|
AND COALESCE(active, 0) = 1
|
|
AND TRIM(COALESCE(facebook_ads_account_id, \'\')) <> \'\'' . $where_client . '
|
|
ORDER BY id ASC',
|
|
$params
|
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
}
|
|
|
|
static public function sync_active_last_days_for_client( $client, $api, $days = 30, $active_only = true )
|
|
{
|
|
$client = is_array( $client ) ? $client : [];
|
|
$client_id = (int) ( $client['id'] ?? 0 );
|
|
$account_id = \services\FacebookAdsApi::normalize_ad_account_id( $client['facebook_ads_account_id'] ?? '' );
|
|
|
|
if ( $client_id <= 0 || !$account_id )
|
|
{
|
|
return [
|
|
'success' => false,
|
|
'client_id' => $client_id,
|
|
'errors' => [ 'Brak poprawnego client_id lub facebook_ads_account_id.' ]
|
|
];
|
|
}
|
|
|
|
$days = max( 1, min( 90, (int) $days ) );
|
|
$payload = $api -> fetch_active_insights_last_days( $account_id, $days, $active_only );
|
|
if ( $payload === false )
|
|
{
|
|
$last_error = trim( (string) \services\FacebookAdsApi::get_setting( 'facebook_ads_last_error' ) );
|
|
|
|
return [
|
|
'success' => false,
|
|
'client_id' => $client_id,
|
|
'account_id' => $account_id,
|
|
'errors' => [ $last_error !== '' ? $last_error : 'Nie udalo sie pobrac danych z Meta API.' ]
|
|
];
|
|
}
|
|
|
|
$save_result = self::save_snapshot( $client_id, $payload );
|
|
$save_result['client_id'] = $client_id;
|
|
$save_result['account_id'] = $account_id;
|
|
$save_result['days'] = $days;
|
|
$save_result['meta'] = $payload['meta'] ?? [];
|
|
|
|
return $save_result;
|
|
}
|
|
|
|
static public function sync_active_date_for_client( $client, $api, $sync_date, $active_only = true )
|
|
{
|
|
$client = is_array( $client ) ? $client : [];
|
|
$client_id = (int) ( $client['id'] ?? 0 );
|
|
$account_id = \services\FacebookAdsApi::normalize_ad_account_id( $client['facebook_ads_account_id'] ?? '' );
|
|
|
|
if ( $client_id <= 0 || !$account_id )
|
|
{
|
|
return [
|
|
'success' => false,
|
|
'client_id' => $client_id,
|
|
'errors' => [ 'Brak poprawnego client_id lub facebook_ads_account_id.' ]
|
|
];
|
|
}
|
|
|
|
$sync_timestamp = strtotime( (string) $sync_date );
|
|
if ( !$sync_timestamp )
|
|
{
|
|
return [
|
|
'success' => false,
|
|
'client_id' => $client_id,
|
|
'account_id' => $account_id,
|
|
'errors' => [ 'Niepoprawna data synchronizacji Facebook Ads.' ]
|
|
];
|
|
}
|
|
|
|
$sync_date = date( 'Y-m-d', $sync_timestamp );
|
|
$payload = $api -> fetch_active_insights_for_date( $account_id, $sync_date, $active_only );
|
|
if ( $payload === false )
|
|
{
|
|
$last_error = trim( (string) \services\FacebookAdsApi::get_setting( 'facebook_ads_last_error' ) );
|
|
|
|
return [
|
|
'success' => false,
|
|
'client_id' => $client_id,
|
|
'account_id' => $account_id,
|
|
'sync_date' => $sync_date,
|
|
'errors' => [ $last_error !== '' ? $last_error : 'Nie udalo sie pobrac danych z Meta API.' ]
|
|
];
|
|
}
|
|
|
|
$save_result = self::save_snapshot( $client_id, $payload );
|
|
$save_result['client_id'] = $client_id;
|
|
$save_result['account_id'] = $account_id;
|
|
$save_result['days'] = 1;
|
|
$save_result['sync_date'] = $sync_date;
|
|
$save_result['meta'] = $payload['meta'] ?? [];
|
|
|
|
return $save_result;
|
|
}
|
|
|
|
static public function sync_date_range_for_client( $client, $api, $since, $until, $active_only = true )
|
|
{
|
|
$client = is_array( $client ) ? $client : [];
|
|
$client_id = (int) ( $client['id'] ?? 0 );
|
|
$account_id = \services\FacebookAdsApi::normalize_ad_account_id( $client['facebook_ads_account_id'] ?? '' );
|
|
|
|
if ( $client_id <= 0 || !$account_id )
|
|
{
|
|
return [
|
|
'success' => false,
|
|
'client_id' => $client_id,
|
|
'errors' => [ 'Brak poprawnego client_id lub facebook_ads_account_id.' ]
|
|
];
|
|
}
|
|
|
|
$since_ts = strtotime( (string) $since );
|
|
$until_ts = strtotime( (string) $until );
|
|
if ( !$since_ts || !$until_ts || $since_ts > $until_ts )
|
|
{
|
|
return [
|
|
'success' => false,
|
|
'client_id' => $client_id,
|
|
'account_id' => $account_id,
|
|
'errors' => [ 'Niepoprawny zakres dat synchronizacji Facebook Ads.' ]
|
|
];
|
|
}
|
|
|
|
$since = date( 'Y-m-d', $since_ts );
|
|
$until = date( 'Y-m-d', $until_ts );
|
|
$days = (int) floor( ( $until_ts - $since_ts ) / 86400 ) + 1;
|
|
|
|
$payload = $api -> fetch_active_insights_for_range( $account_id, $since, $until, $active_only );
|
|
if ( $payload === false )
|
|
{
|
|
$last_error = trim( (string) \services\FacebookAdsApi::get_setting( 'facebook_ads_last_error' ) );
|
|
|
|
return [
|
|
'success' => false,
|
|
'client_id' => $client_id,
|
|
'account_id' => $account_id,
|
|
'since' => $since,
|
|
'until' => $until,
|
|
'errors' => [ $last_error !== '' ? $last_error : 'Nie udalo sie pobrac danych z Meta API.' ]
|
|
];
|
|
}
|
|
|
|
foreach ( [ 'campaign', 'adset', 'ad' ] as $level )
|
|
{
|
|
if ( is_array( $payload[ $level ] ?? null ) )
|
|
{
|
|
foreach ( $payload[ $level ] as &$row )
|
|
{
|
|
$row['date_start'] = $until;
|
|
}
|
|
unset( $row );
|
|
}
|
|
}
|
|
|
|
$all_time_raw = $api -> fetch_campaigns_all_time( $account_id, $active_only );
|
|
$all_time_error = '';
|
|
if ( $all_time_raw === false )
|
|
{
|
|
$all_time_error = (string) \services\FacebookAdsApi::get_setting( 'facebook_ads_last_error' );
|
|
}
|
|
$all_time_map = is_array( $all_time_raw ) ? $all_time_raw : [];
|
|
|
|
$save_result = self::save_snapshot( $client_id, $payload, $all_time_map );
|
|
$save_result['client_id'] = $client_id;
|
|
$save_result['account_id'] = $account_id;
|
|
$save_result['days'] = $days;
|
|
$save_result['since'] = $since;
|
|
$save_result['until'] = $until;
|
|
$save_result['meta'] = $payload['meta'] ?? [];
|
|
$save_result['all_time_debug'] = [
|
|
'raw_type' => is_array( $all_time_raw ) ? 'array' : ( $all_time_raw === false ? 'false' : gettype( $all_time_raw ) ),
|
|
'campaigns_count' => count( $all_time_map ),
|
|
'error' => $all_time_error,
|
|
'data' => array_slice( $all_time_map, 0, 5, true )
|
|
];
|
|
|
|
return $save_result;
|
|
}
|
|
|
|
static private function save_snapshot( $client_id, $payload, $all_time_map = [] )
|
|
{
|
|
global $mdb;
|
|
|
|
$stats = [
|
|
'success' => false,
|
|
'campaigns_synced' => 0,
|
|
'campaign_history_synced' => 0,
|
|
'ad_sets_synced' => 0,
|
|
'ad_set_history_synced' => 0,
|
|
'ads_synced' => 0,
|
|
'ad_history_synced' => 0,
|
|
'errors' => []
|
|
];
|
|
|
|
$campaign_map = [];
|
|
$ad_set_map = [];
|
|
$ad_map = [];
|
|
|
|
$campaign_rows = is_array( $payload['campaign'] ?? null ) ? $payload['campaign'] : [];
|
|
$ad_set_rows = is_array( $payload['adset'] ?? null ) ? $payload['adset'] : [];
|
|
$ad_rows = is_array( $payload['ad'] ?? null ) ? $payload['ad'] : [];
|
|
|
|
try
|
|
{
|
|
$started_transaction = false;
|
|
if ( method_exists( $mdb -> pdo, 'beginTransaction' ) && !( $mdb -> pdo -> inTransaction() ) )
|
|
{
|
|
$mdb -> pdo -> beginTransaction();
|
|
$started_transaction = true;
|
|
}
|
|
|
|
foreach ( $campaign_rows as $row )
|
|
{
|
|
$campaign_db_id = self::upsert_campaign( $client_id, $row );
|
|
if ( $campaign_db_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$campaign_external_id = self::normalize_external_id( $row['campaign_id'] ?? '' );
|
|
if ( $campaign_external_id !== null )
|
|
{
|
|
$campaign_map[ $campaign_external_id ] = $campaign_db_id;
|
|
}
|
|
|
|
$roas_at = (float) ( $all_time_map[ $campaign_external_id ]['roas_all_time'] ?? 0 );
|
|
if ( self::upsert_campaign_history( $campaign_db_id, $row, $roas_at ) )
|
|
{
|
|
$stats['campaign_history_synced']++;
|
|
}
|
|
}
|
|
|
|
foreach ( $ad_set_rows as $row )
|
|
{
|
|
$campaign_db_id = self::resolve_campaign_db_id( $client_id, $row, $campaign_map );
|
|
$ad_set_db_id = self::upsert_ad_set( $client_id, $campaign_db_id, $row );
|
|
if ( $ad_set_db_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$ad_set_external_id = self::normalize_external_id( $row['adset_id'] ?? '' );
|
|
if ( $ad_set_external_id !== null )
|
|
{
|
|
$ad_set_map[ $ad_set_external_id ] = $ad_set_db_id;
|
|
}
|
|
|
|
if ( self::upsert_ad_set_history( $ad_set_db_id, $row ) )
|
|
{
|
|
$stats['ad_set_history_synced']++;
|
|
}
|
|
}
|
|
|
|
foreach ( $ad_rows as $row )
|
|
{
|
|
$campaign_db_id = self::resolve_campaign_db_id( $client_id, $row, $campaign_map );
|
|
$ad_set_db_id = self::resolve_ad_set_db_id( $client_id, $campaign_db_id, $row, $ad_set_map );
|
|
$ad_db_id = self::upsert_ad( $client_id, $campaign_db_id, $ad_set_db_id, $row );
|
|
if ( $ad_db_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$ad_external_id = self::normalize_external_id( $row['ad_id'] ?? '' );
|
|
if ( $ad_external_id !== null )
|
|
{
|
|
$ad_map[ $ad_external_id ] = $ad_db_id;
|
|
}
|
|
|
|
if ( self::upsert_ad_history( $ad_db_id, $row ) )
|
|
{
|
|
$stats['ad_history_synced']++;
|
|
}
|
|
}
|
|
|
|
$stats['campaigns_synced'] = count( $campaign_map );
|
|
$stats['ad_sets_synced'] = count( $ad_set_map );
|
|
$stats['ads_synced'] = count( $ad_map );
|
|
|
|
if ( !empty( $started_transaction ) && method_exists( $mdb -> pdo, 'commit' ) && $mdb -> pdo -> inTransaction() )
|
|
{
|
|
$mdb -> pdo -> commit();
|
|
}
|
|
|
|
$stats['success'] = true;
|
|
}
|
|
catch ( \Throwable $e )
|
|
{
|
|
if ( !empty( $started_transaction ) && method_exists( $mdb -> pdo, 'rollBack' ) && $mdb -> pdo -> inTransaction() )
|
|
{
|
|
$mdb -> pdo -> rollBack();
|
|
}
|
|
|
|
$stats['errors'][] = $e -> getMessage();
|
|
$stats['success'] = false;
|
|
}
|
|
|
|
return $stats;
|
|
}
|
|
|
|
static private function upsert_campaign( $client_id, $row )
|
|
{
|
|
global $mdb;
|
|
|
|
$campaign_external_id = self::normalize_external_id( $row['campaign_id'] ?? '' );
|
|
if ( $campaign_external_id === null )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
$account_id = self::normalize_external_id( $row['account_id'] ?? '' ) ?: '0';
|
|
$campaign_name = trim( (string) ( $row['campaign_name'] ?? '' ) );
|
|
$date_sync = date( 'Y-m-d H:i:s' );
|
|
|
|
$mdb -> query(
|
|
'INSERT INTO facebook_campaigns
|
|
(client_id, account_id, campaign_id, campaign_name, status, effective_status, date_sync)
|
|
VALUES
|
|
(:client_id, :account_id, :campaign_id, :campaign_name, NULL, \'ACTIVE\', :date_sync)
|
|
ON DUPLICATE KEY UPDATE
|
|
id = LAST_INSERT_ID(id),
|
|
account_id = VALUES(account_id),
|
|
campaign_name = VALUES(campaign_name),
|
|
effective_status = VALUES(effective_status),
|
|
date_sync = VALUES(date_sync)',
|
|
[
|
|
':client_id' => (int) $client_id,
|
|
':account_id' => $account_id,
|
|
':campaign_id' => $campaign_external_id,
|
|
':campaign_name' => $campaign_name,
|
|
':date_sync' => $date_sync
|
|
]
|
|
);
|
|
|
|
$db_id = (int) $mdb -> id();
|
|
if ( $db_id > 0 )
|
|
{
|
|
return $db_id;
|
|
}
|
|
|
|
$existing = $mdb -> query(
|
|
'SELECT id
|
|
FROM facebook_campaigns
|
|
WHERE client_id = :client_id
|
|
AND campaign_id = :campaign_id
|
|
LIMIT 1',
|
|
[ ':client_id' => (int) $client_id, ':campaign_id' => $campaign_external_id ]
|
|
) -> fetchColumn();
|
|
|
|
return (int) $existing;
|
|
}
|
|
|
|
static private function upsert_campaign_history( $campaign_db_id, $row, $roas_all_time = 0.0 )
|
|
{
|
|
global $mdb;
|
|
|
|
$date_add = self::normalize_date( $row['date_start'] ?? null );
|
|
if ( !$date_add )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$spend = self::to_decimal( $row['spend'] ?? 0 );
|
|
$conversion_value = self::extract_conversion_value( $row );
|
|
$roas = self::extract_roas( $row, $conversion_value, $spend );
|
|
|
|
$mdb -> query(
|
|
'INSERT INTO facebook_campaigns_history
|
|
(facebook_campaign_id, spend, impressions, clicks, ctr, cpc, conversion_value, roas, roas_all_time, date_add)
|
|
VALUES
|
|
(:campaign_id, :spend, :impressions, :clicks, :ctr, :cpc, :conversion_value, :roas, :roas_all_time, :date_add)
|
|
ON DUPLICATE KEY UPDATE
|
|
spend = VALUES(spend),
|
|
impressions = VALUES(impressions),
|
|
clicks = VALUES(clicks),
|
|
ctr = VALUES(ctr),
|
|
cpc = VALUES(cpc),
|
|
conversion_value = VALUES(conversion_value),
|
|
roas = VALUES(roas),
|
|
roas_all_time = VALUES(roas_all_time)',
|
|
[
|
|
':campaign_id' => (int) $campaign_db_id,
|
|
':spend' => $spend,
|
|
':impressions' => self::to_int( $row['impressions'] ?? 0 ),
|
|
':clicks' => self::to_int( $row['clicks'] ?? 0 ),
|
|
':ctr' => self::to_decimal( $row['ctr'] ?? 0 ),
|
|
':cpc' => self::to_decimal( $row['cpc'] ?? 0 ),
|
|
':conversion_value' => $conversion_value,
|
|
':roas' => $roas,
|
|
':roas_all_time' => (float) $roas_all_time,
|
|
':date_add' => $date_add
|
|
]
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
static private function upsert_ad_set( $client_id, $campaign_db_id, $row )
|
|
{
|
|
global $mdb;
|
|
|
|
$ad_set_external_id = self::normalize_external_id( $row['adset_id'] ?? '' );
|
|
if ( $ad_set_external_id === null )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
$campaign_external_id = self::normalize_external_id( $row['campaign_id'] ?? '' ) ?: '0';
|
|
$ad_set_name = trim( (string) ( $row['adset_name'] ?? '' ) );
|
|
$date_sync = date( 'Y-m-d H:i:s' );
|
|
|
|
$mdb -> query(
|
|
'INSERT INTO facebook_ad_sets
|
|
(client_id, facebook_campaign_id, campaign_id, ad_set_id, ad_set_name, status, effective_status, date_sync)
|
|
VALUES
|
|
(:client_id, :facebook_campaign_id, :campaign_id, :ad_set_id, :ad_set_name, NULL, \'ACTIVE\', :date_sync)
|
|
ON DUPLICATE KEY UPDATE
|
|
id = LAST_INSERT_ID(id),
|
|
facebook_campaign_id = VALUES(facebook_campaign_id),
|
|
campaign_id = VALUES(campaign_id),
|
|
ad_set_name = VALUES(ad_set_name),
|
|
effective_status = VALUES(effective_status),
|
|
date_sync = VALUES(date_sync)',
|
|
[
|
|
':client_id' => (int) $client_id,
|
|
':facebook_campaign_id' => $campaign_db_id > 0 ? (int) $campaign_db_id : null,
|
|
':campaign_id' => $campaign_external_id,
|
|
':ad_set_id' => $ad_set_external_id,
|
|
':ad_set_name' => $ad_set_name,
|
|
':date_sync' => $date_sync
|
|
]
|
|
);
|
|
|
|
$db_id = (int) $mdb -> id();
|
|
if ( $db_id > 0 )
|
|
{
|
|
return $db_id;
|
|
}
|
|
|
|
$existing = $mdb -> query(
|
|
'SELECT id
|
|
FROM facebook_ad_sets
|
|
WHERE client_id = :client_id
|
|
AND ad_set_id = :ad_set_id
|
|
LIMIT 1',
|
|
[ ':client_id' => (int) $client_id, ':ad_set_id' => $ad_set_external_id ]
|
|
) -> fetchColumn();
|
|
|
|
return (int) $existing;
|
|
}
|
|
|
|
static private function upsert_ad_set_history( $ad_set_db_id, $row )
|
|
{
|
|
global $mdb;
|
|
|
|
$date_add = self::normalize_date( $row['date_start'] ?? null );
|
|
if ( !$date_add )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$spend = self::to_decimal( $row['spend'] ?? 0 );
|
|
$conversion_value = self::extract_conversion_value( $row );
|
|
$roas = self::extract_roas( $row, $conversion_value, $spend );
|
|
|
|
$mdb -> query(
|
|
'INSERT INTO facebook_ad_sets_history
|
|
(facebook_ad_set_id, spend, impressions, clicks, ctr, cpc, conversion_value, roas, date_add)
|
|
VALUES
|
|
(:ad_set_id, :spend, :impressions, :clicks, :ctr, :cpc, :conversion_value, :roas, :date_add)
|
|
ON DUPLICATE KEY UPDATE
|
|
spend = VALUES(spend),
|
|
impressions = VALUES(impressions),
|
|
clicks = VALUES(clicks),
|
|
ctr = VALUES(ctr),
|
|
cpc = VALUES(cpc),
|
|
conversion_value = VALUES(conversion_value),
|
|
roas = VALUES(roas)',
|
|
[
|
|
':ad_set_id' => (int) $ad_set_db_id,
|
|
':spend' => $spend,
|
|
':impressions' => self::to_int( $row['impressions'] ?? 0 ),
|
|
':clicks' => self::to_int( $row['clicks'] ?? 0 ),
|
|
':ctr' => self::to_decimal( $row['ctr'] ?? 0 ),
|
|
':cpc' => self::to_decimal( $row['cpc'] ?? 0 ),
|
|
':conversion_value' => $conversion_value,
|
|
':roas' => $roas,
|
|
':date_add' => $date_add
|
|
]
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
static private function upsert_ad( $client_id, $campaign_db_id, $ad_set_db_id, $row )
|
|
{
|
|
global $mdb;
|
|
|
|
$ad_external_id = self::normalize_external_id( $row['ad_id'] ?? '' );
|
|
if ( $ad_external_id === null )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
$campaign_external_id = self::normalize_external_id( $row['campaign_id'] ?? '' ) ?: '0';
|
|
$ad_set_external_id = self::normalize_external_id( $row['adset_id'] ?? '' ) ?: '0';
|
|
$ad_name = trim( (string) ( $row['ad_name'] ?? '' ) );
|
|
$date_sync = date( 'Y-m-d H:i:s' );
|
|
|
|
$mdb -> query(
|
|
'INSERT INTO facebook_ads
|
|
(client_id, facebook_campaign_id, facebook_ad_set_id, campaign_id, ad_set_id, ad_id, ad_name, status, effective_status, date_sync)
|
|
VALUES
|
|
(:client_id, :facebook_campaign_id, :facebook_ad_set_id, :campaign_id, :ad_set_id, :ad_id, :ad_name, NULL, \'ACTIVE\', :date_sync)
|
|
ON DUPLICATE KEY UPDATE
|
|
id = LAST_INSERT_ID(id),
|
|
facebook_campaign_id = VALUES(facebook_campaign_id),
|
|
facebook_ad_set_id = VALUES(facebook_ad_set_id),
|
|
campaign_id = VALUES(campaign_id),
|
|
ad_set_id = VALUES(ad_set_id),
|
|
ad_name = VALUES(ad_name),
|
|
effective_status = VALUES(effective_status),
|
|
date_sync = VALUES(date_sync)',
|
|
[
|
|
':client_id' => (int) $client_id,
|
|
':facebook_campaign_id' => $campaign_db_id > 0 ? (int) $campaign_db_id : null,
|
|
':facebook_ad_set_id' => $ad_set_db_id > 0 ? (int) $ad_set_db_id : null,
|
|
':campaign_id' => $campaign_external_id,
|
|
':ad_set_id' => $ad_set_external_id,
|
|
':ad_id' => $ad_external_id,
|
|
':ad_name' => $ad_name,
|
|
':date_sync' => $date_sync
|
|
]
|
|
);
|
|
|
|
$db_id = (int) $mdb -> id();
|
|
if ( $db_id > 0 )
|
|
{
|
|
return $db_id;
|
|
}
|
|
|
|
$existing = $mdb -> query(
|
|
'SELECT id
|
|
FROM facebook_ads
|
|
WHERE client_id = :client_id
|
|
AND ad_id = :ad_id
|
|
LIMIT 1',
|
|
[ ':client_id' => (int) $client_id, ':ad_id' => $ad_external_id ]
|
|
) -> fetchColumn();
|
|
|
|
return (int) $existing;
|
|
}
|
|
|
|
static private function upsert_ad_history( $ad_db_id, $row )
|
|
{
|
|
global $mdb;
|
|
|
|
$date_add = self::normalize_date( $row['date_start'] ?? null );
|
|
if ( !$date_add )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$spend = self::to_decimal( $row['spend'] ?? 0 );
|
|
$conversion_value = self::extract_conversion_value( $row );
|
|
$roas = self::extract_roas( $row, $conversion_value, $spend );
|
|
|
|
$mdb -> query(
|
|
'INSERT INTO facebook_ads_history
|
|
(facebook_ad_id, spend, impressions, clicks, ctr, cpc, conversion_value, roas, date_add)
|
|
VALUES
|
|
(:ad_id, :spend, :impressions, :clicks, :ctr, :cpc, :conversion_value, :roas, :date_add)
|
|
ON DUPLICATE KEY UPDATE
|
|
spend = VALUES(spend),
|
|
impressions = VALUES(impressions),
|
|
clicks = VALUES(clicks),
|
|
ctr = VALUES(ctr),
|
|
cpc = VALUES(cpc),
|
|
conversion_value = VALUES(conversion_value),
|
|
roas = VALUES(roas)',
|
|
[
|
|
':ad_id' => (int) $ad_db_id,
|
|
':spend' => $spend,
|
|
':impressions' => self::to_int( $row['impressions'] ?? 0 ),
|
|
':clicks' => self::to_int( $row['clicks'] ?? 0 ),
|
|
':ctr' => self::to_decimal( $row['ctr'] ?? 0 ),
|
|
':cpc' => self::to_decimal( $row['cpc'] ?? 0 ),
|
|
':conversion_value' => $conversion_value,
|
|
':roas' => $roas,
|
|
':date_add' => $date_add
|
|
]
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
static private function resolve_campaign_db_id( $client_id, $row, &$campaign_map )
|
|
{
|
|
$campaign_external_id = self::normalize_external_id( $row['campaign_id'] ?? '' );
|
|
if ( $campaign_external_id === null )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ( isset( $campaign_map[ $campaign_external_id ] ) )
|
|
{
|
|
return (int) $campaign_map[ $campaign_external_id ];
|
|
}
|
|
|
|
$campaign_db_id = self::upsert_campaign( $client_id, $row );
|
|
if ( $campaign_db_id > 0 )
|
|
{
|
|
$campaign_map[ $campaign_external_id ] = $campaign_db_id;
|
|
}
|
|
|
|
return $campaign_db_id;
|
|
}
|
|
|
|
static private function resolve_ad_set_db_id( $client_id, $campaign_db_id, $row, &$ad_set_map )
|
|
{
|
|
$ad_set_external_id = self::normalize_external_id( $row['adset_id'] ?? '' );
|
|
if ( $ad_set_external_id === null )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ( isset( $ad_set_map[ $ad_set_external_id ] ) )
|
|
{
|
|
return (int) $ad_set_map[ $ad_set_external_id ];
|
|
}
|
|
|
|
$ad_set_db_id = self::upsert_ad_set( $client_id, $campaign_db_id, $row );
|
|
if ( $ad_set_db_id > 0 )
|
|
{
|
|
$ad_set_map[ $ad_set_external_id ] = $ad_set_db_id;
|
|
}
|
|
|
|
return $ad_set_db_id;
|
|
}
|
|
|
|
static private function normalize_external_id( $value )
|
|
{
|
|
$digits = preg_replace( '/\D+/', '', (string) $value );
|
|
if ( $digits === '' )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return $digits;
|
|
}
|
|
|
|
static private function normalize_date( $value )
|
|
{
|
|
$value = trim( (string) $value );
|
|
if ( $value === '' )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$timestamp = strtotime( $value );
|
|
if ( !$timestamp )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return date( 'Y-m-d', $timestamp );
|
|
}
|
|
|
|
static private function to_decimal( $value )
|
|
{
|
|
if ( is_string( $value ) )
|
|
{
|
|
$value = str_replace( ',', '.', $value );
|
|
}
|
|
|
|
return (float) $value;
|
|
}
|
|
|
|
static private function to_int( $value )
|
|
{
|
|
return (int) round( (float) $value );
|
|
}
|
|
|
|
static private function get_purchase_action_types_priority()
|
|
{
|
|
return [
|
|
'purchase',
|
|
'omni_purchase',
|
|
'offsite_conversion.fb_pixel_purchase',
|
|
'web_in_store_purchase',
|
|
'onsite_conversion.purchase',
|
|
'app_custom_event.fb_mobile_purchase'
|
|
];
|
|
}
|
|
|
|
static private function get_metric_map( $raw )
|
|
{
|
|
$map = [];
|
|
if ( !is_array( $raw ) )
|
|
{
|
|
return $map;
|
|
}
|
|
|
|
foreach ( $raw as $row )
|
|
{
|
|
if ( !is_array( $row ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$action_type = trim( (string) ( $row['action_type'] ?? '' ) );
|
|
if ( $action_type === '' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$map[ $action_type ] = self::to_decimal( $row['value'] ?? 0 );
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
static private function extract_conversion_value( $row )
|
|
{
|
|
if ( !is_array( $row ) )
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
if ( isset( $row['conversion_value'] ) )
|
|
{
|
|
return self::to_decimal( $row['conversion_value'] );
|
|
}
|
|
|
|
$action_values_map = self::get_metric_map( $row['action_values'] ?? [] );
|
|
if ( empty( $action_values_map ) )
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
foreach ( self::get_purchase_action_types_priority() as $action_type )
|
|
{
|
|
if ( array_key_exists( $action_type, $action_values_map ) )
|
|
{
|
|
return self::to_decimal( $action_values_map[ $action_type ] );
|
|
}
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
static private function extract_roas( $row, $conversion_value, $spend )
|
|
{
|
|
if ( !is_array( $row ) )
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
$purchase_roas_map = self::get_metric_map( $row['purchase_roas'] ?? [] );
|
|
foreach ( self::get_purchase_action_types_priority() as $action_type )
|
|
{
|
|
if ( array_key_exists( $action_type, $purchase_roas_map ) )
|
|
{
|
|
return self::to_decimal( $purchase_roas_map[ $action_type ] ) * 100;
|
|
}
|
|
}
|
|
|
|
$conversion_value = self::to_decimal( $conversion_value );
|
|
$spend = self::to_decimal( $spend );
|
|
if ( $conversion_value > 0 && $spend > 0 )
|
|
{
|
|
return round( ( $conversion_value / $spend ) * 100, 6 );
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
static private function get_level_config( $level )
|
|
{
|
|
$level = strtolower( trim( (string) $level ) );
|
|
|
|
$map = [
|
|
'campaign' => [
|
|
'base_table' => 'facebook_campaigns',
|
|
'history_table' => 'facebook_campaigns_history',
|
|
'fk_column' => 'facebook_campaign_id',
|
|
'name_column' => 'campaign_name',
|
|
'external_id_column' => 'campaign_id'
|
|
],
|
|
'adset' => [
|
|
'base_table' => 'facebook_ad_sets',
|
|
'history_table' => 'facebook_ad_sets_history',
|
|
'fk_column' => 'facebook_ad_set_id',
|
|
'name_column' => 'ad_set_name',
|
|
'external_id_column' => 'ad_set_id'
|
|
],
|
|
'ad' => [
|
|
'base_table' => 'facebook_ads',
|
|
'history_table' => 'facebook_ads_history',
|
|
'fk_column' => 'facebook_ad_id',
|
|
'name_column' => 'ad_name',
|
|
'external_id_column' => 'ad_id'
|
|
]
|
|
];
|
|
|
|
return $map[ $level ] ?? null;
|
|
}
|
|
|
|
static public function get_clients_for_reports()
|
|
{
|
|
return self::get_clients_for_sync( 0 );
|
|
}
|
|
|
|
static public function get_entities_with_latest_metrics( $client_id, $level, $filters = [] )
|
|
{
|
|
global $mdb;
|
|
|
|
$cfg = self::get_level_config( $level );
|
|
if ( !$cfg )
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$client_id = (int) $client_id;
|
|
if ( $client_id <= 0 )
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$filters = is_array( $filters ) ? $filters : [];
|
|
$campaign_id = (int) ( $filters['campaign_id'] ?? 0 );
|
|
$ad_set_id = (int) ( $filters['ad_set_id'] ?? 0 );
|
|
|
|
$extra_where = '';
|
|
$params = [ ':client_id' => $client_id ];
|
|
|
|
if ( $level === 'adset' && $campaign_id > 0 )
|
|
{
|
|
$extra_where .= ' AND b.facebook_campaign_id = :campaign_id';
|
|
$params[':campaign_id'] = $campaign_id;
|
|
}
|
|
|
|
if ( $level === 'ad' )
|
|
{
|
|
if ( $campaign_id > 0 )
|
|
{
|
|
$extra_where .= ' AND b.facebook_campaign_id = :campaign_id';
|
|
$params[':campaign_id'] = $campaign_id;
|
|
}
|
|
|
|
if ( $ad_set_id > 0 )
|
|
{
|
|
$extra_where .= ' AND b.facebook_ad_set_id = :ad_set_id';
|
|
$params[':ad_set_id'] = $ad_set_id;
|
|
}
|
|
}
|
|
|
|
$sql = 'SELECT
|
|
b.id,
|
|
b.' . $cfg['external_id_column'] . ' AS external_id,
|
|
b.' . $cfg['name_column'] . ' AS entity_name,
|
|
h.date_add,
|
|
h.spend,
|
|
h.impressions,
|
|
h.clicks,
|
|
h.ctr,
|
|
h.cpc,
|
|
h.conversion_value,
|
|
h.roas
|
|
FROM ' . $cfg['base_table'] . ' b
|
|
LEFT JOIN ' . $cfg['history_table'] . ' h
|
|
ON h.' . $cfg['fk_column'] . ' = b.id
|
|
AND h.date_add = (
|
|
SELECT MAX(h2.date_add)
|
|
FROM ' . $cfg['history_table'] . ' h2
|
|
WHERE h2.' . $cfg['fk_column'] . ' = b.id
|
|
)
|
|
WHERE b.client_id = :client_id
|
|
' . $extra_where . '
|
|
ORDER BY b.' . $cfg['name_column'] . ' ASC';
|
|
|
|
return $mdb -> query( $sql, $params ) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
}
|
|
|
|
static public function get_entity_history( $level, $entity_id, $start, $length, $revert = false )
|
|
{
|
|
global $mdb;
|
|
|
|
$cfg = self::get_level_config( $level );
|
|
if ( !$cfg )
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$entity_id = (int) $entity_id;
|
|
if ( $entity_id <= 0 )
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$start = max( 0, (int) $start );
|
|
$length = max( 1, min( 2000, (int) $length ) );
|
|
$order = $revert ? 'ASC' : 'DESC';
|
|
|
|
$columns = 'date_add, spend, impressions, clicks, ctr, cpc, conversion_value, roas';
|
|
if ( $level === 'campaign' )
|
|
{
|
|
$columns .= ', roas_all_time';
|
|
}
|
|
|
|
$sql = 'SELECT ' . $columns . '
|
|
FROM ' . $cfg['history_table'] . '
|
|
WHERE ' . $cfg['fk_column'] . ' = :entity_id
|
|
ORDER BY date_add ' . $order . '
|
|
LIMIT ' . $start . ', ' . $length;
|
|
|
|
return $mdb -> query( $sql, [ ':entity_id' => $entity_id ] ) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
}
|
|
|
|
static public function get_entity_history_total( $level, $entity_id )
|
|
{
|
|
global $mdb;
|
|
|
|
$cfg = self::get_level_config( $level );
|
|
if ( !$cfg )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
$entity_id = (int) $entity_id;
|
|
if ( $entity_id <= 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
$count = $mdb -> query(
|
|
'SELECT COUNT(*)
|
|
FROM ' . $cfg['history_table'] . '
|
|
WHERE ' . $cfg['fk_column'] . ' = :entity_id',
|
|
[ ':entity_id' => $entity_id ]
|
|
) -> fetchColumn();
|
|
|
|
return (int) $count;
|
|
}
|
|
}
|