3348 lines
114 KiB
PHP
3348 lines
114 KiB
PHP
<?php
|
|
namespace controls;
|
|
class Cron
|
|
{
|
|
static public function cron_products()
|
|
{
|
|
global $mdb, $settings;
|
|
self::touch_cron_invocation( __FUNCTION__ );
|
|
|
|
$api = new \services\GoogleAdsApi();
|
|
if ( !$api -> is_configured() )
|
|
{
|
|
echo json_encode( [ 'result' => 'Google Ads API nie jest skonfigurowane. Uzupelnij dane w Ustawieniach.' ] );
|
|
exit;
|
|
}
|
|
|
|
$date = \S::get( 'date' ) ? date( 'Y-m-d', strtotime( \S::get( 'date' ) ) ) : date( 'Y-m-d', strtotime( '-1 days' ) );
|
|
$conversion_window_days = self::get_conversion_window_days();
|
|
$import_dates = self::build_backfill_dates( $date, $conversion_window_days );
|
|
$client_id = (int) \S::get( 'client_id' );
|
|
|
|
if ( $client_id > 0 )
|
|
{
|
|
$client = $mdb -> get( 'clients', '*', [ 'AND' => [
|
|
'id' => $client_id,
|
|
'google_ads_customer_id[!]' => null,
|
|
'deleted' => 0
|
|
] ] );
|
|
|
|
if ( !$client || trim( (string) $client['google_ads_customer_id'] ) === '' )
|
|
{
|
|
echo json_encode( [ 'result' => 'Nie znaleziono klienta z poprawnym Google Ads Customer ID.', 'client_id' => $client_id ] );
|
|
exit;
|
|
}
|
|
|
|
$sync = self::sync_products_fetch_for_client( $client, $api, $date );
|
|
$history_30 = self::aggregate_products_history_30_for_client( (int) $client['id'], $date );
|
|
$temp_rows = self::rebuild_products_temp_for_client( (int) $client['id'] );
|
|
|
|
echo json_encode( [
|
|
'result' => empty( $sync['errors'] ) ? 'Synchronizacja produktow zakonczona.' : 'Synchronizacja produktow zakonczona z bledami.',
|
|
'client_id' => (int) $client['id'],
|
|
'date' => $date,
|
|
'active_date' => $date,
|
|
'phase' => 'single_full',
|
|
'processed_products' => (int) $sync['processed_products'],
|
|
'skipped' => (int) $sync['skipped'],
|
|
'history_30_products' => (int) $history_30,
|
|
'products_temp_rows' => (int) $temp_rows,
|
|
'errors' => $sync['errors']
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
$client_ids = $mdb -> query( "SELECT id FROM clients WHERE deleted = 0 AND google_ads_customer_id IS NOT NULL AND google_ads_customer_id <> '' ORDER BY id ASC" ) -> fetchAll( \PDO::FETCH_COLUMN );
|
|
$client_ids = array_values( array_unique( array_map( 'intval', $client_ids ) ) );
|
|
|
|
if ( empty( $client_ids ) )
|
|
{
|
|
echo json_encode( [ 'result' => 'Brak klientow z ustawionym Google Ads Customer ID do przetworzenia.', 'processed_clients' => 0, 'errors' => [] ] );
|
|
exit;
|
|
}
|
|
|
|
$state_key = 'cron_products_pipeline_state';
|
|
$state = self::get_products_pipeline_state( $state_key, $date, $client_ids, $import_dates );
|
|
|
|
if ( $state['phase'] === 'done' )
|
|
{
|
|
echo json_encode( [
|
|
'result' => 'Pipeline cron_products jest zakonczony dla calego okna dat.',
|
|
'date' => $date,
|
|
'active_date' => $state['import_date'],
|
|
'conversion_window_days' => (int) ( $state['conversion_window_days'] ?? $conversion_window_days ),
|
|
'dates_synced' => $state['import_dates'] ?? $import_dates,
|
|
'phase' => 'done',
|
|
'total_clients' => count( $client_ids )
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
$phase_map = [
|
|
'fetch' => 'fetch_done_ids',
|
|
'aggregate_30' => 'aggregate_30_done_ids',
|
|
'aggregate_temp' => 'aggregate_temp_done_ids'
|
|
];
|
|
|
|
$done_key = $phase_map[ $state['phase'] ] ?? null;
|
|
if ( !$done_key )
|
|
{
|
|
$state = self::init_products_pipeline_state( $date, $client_ids, $import_dates );
|
|
$done_key = 'fetch_done_ids';
|
|
}
|
|
|
|
$clients_per_run_default = (int) ( $settings['cron_products_clients_per_run'] ?? 10 );
|
|
if ( $clients_per_run_default <= 0 )
|
|
{
|
|
$clients_per_run_default = 10;
|
|
}
|
|
|
|
$clients_per_run = (int) \S::get( 'clients_per_run' );
|
|
if ( $clients_per_run <= 0 )
|
|
{
|
|
$clients_per_run = $clients_per_run_default;
|
|
}
|
|
$clients_per_run = min( 100, $clients_per_run );
|
|
|
|
$next_client_id = self::pick_next_client_id( $client_ids, $state[ $done_key ] );
|
|
|
|
if ( !$next_client_id )
|
|
{
|
|
$state = self::advance_products_pipeline_phase( $state );
|
|
self::save_products_pipeline_state( $state_key, $state );
|
|
|
|
echo json_encode( [
|
|
'result' => 'Faza zakonczona. Przejdz do nastepnej fazy kolejnym wywolaniem.',
|
|
'date' => $date,
|
|
'active_date' => $state['import_date'],
|
|
'conversion_window_days' => (int) ( $state['conversion_window_days'] ?? $conversion_window_days ),
|
|
'phase' => $state['phase'],
|
|
'total_clients' => count( $client_ids )
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
$clients_processed_in_call = [];
|
|
$errors = [];
|
|
$processed_products_total = 0;
|
|
$skipped_total = 0;
|
|
$history_30_products_total = 0;
|
|
$products_temp_rows_total = 0;
|
|
|
|
$processed_now = 0;
|
|
while ( $processed_now < $clients_per_run )
|
|
{
|
|
$next_client_id = self::pick_next_client_id( $client_ids, $state[ $done_key ] );
|
|
if ( !$next_client_id )
|
|
{
|
|
break;
|
|
}
|
|
|
|
$selected_client = $mdb -> get( 'clients', '*', [ 'id' => $next_client_id ] );
|
|
if ( !$selected_client )
|
|
{
|
|
$errors[] = 'Nie udalo sie wybrac klienta do synchronizacji produktow. ID: ' . $next_client_id;
|
|
$state[ $done_key ][] = (int) $next_client_id;
|
|
$state[ $done_key ] = array_values( array_unique( array_map( 'intval', $state[ $done_key ] ) ) );
|
|
$processed_now++;
|
|
continue;
|
|
}
|
|
|
|
if ( $state['phase'] === 'fetch' )
|
|
{
|
|
$sync = self::sync_products_fetch_for_client( $selected_client, $api, $state['import_date'] );
|
|
$processed_products_total += (int) ( $sync['processed_products'] ?? 0 );
|
|
$skipped_total += (int) ( $sync['skipped'] ?? 0 );
|
|
if ( !empty( $sync['errors'] ) )
|
|
{
|
|
$errors = array_merge( $errors, (array) $sync['errors'] );
|
|
}
|
|
}
|
|
else if ( $state['phase'] === 'aggregate_30' )
|
|
{
|
|
$history_30_products_total += (int) self::aggregate_products_history_30_for_client( (int) $selected_client['id'], $state['import_date'] );
|
|
}
|
|
else if ( $state['phase'] === 'aggregate_temp' )
|
|
{
|
|
$products_temp_rows_total += (int) self::rebuild_products_temp_for_client( (int) $selected_client['id'] );
|
|
}
|
|
|
|
$clients_processed_in_call[] = (int) $next_client_id;
|
|
|
|
// Oznaczamy klienta jako przetworzonego rowniez po bledzie, aby nie zapetlac wywolan.
|
|
$state[ $done_key ][] = (int) $next_client_id;
|
|
$state[ $done_key ] = array_values( array_unique( array_map( 'intval', $state[ $done_key ] ) ) );
|
|
$processed_now++;
|
|
}
|
|
|
|
self::save_products_pipeline_state( $state_key, $state );
|
|
|
|
$processed_in_phase = count( array_intersect( $client_ids, $state[ $done_key ] ) );
|
|
$remaining_in_phase = max( 0, count( $client_ids ) - $processed_in_phase );
|
|
$estimated_calls_remaining_in_phase = (int) ceil( $remaining_in_phase / max( 1, $clients_per_run ) );
|
|
|
|
$result_text = 'Brak klientow do przetworzenia w tej fazie.';
|
|
if ( $state['phase'] === 'fetch' )
|
|
{
|
|
$result_text = empty( $errors ) ? 'Pobieranie produktow zakonczone.' : 'Pobieranie produktow zakonczone z bledami.';
|
|
}
|
|
else if ( $state['phase'] === 'aggregate_30' )
|
|
{
|
|
$result_text = 'Pierwsza agregacja (history_30) zakonczona.';
|
|
}
|
|
else if ( $state['phase'] === 'aggregate_temp' )
|
|
{
|
|
$result_text = 'Druga agregacja (products_temp) zakonczona.';
|
|
}
|
|
|
|
echo json_encode( [
|
|
'result' => $result_text,
|
|
'date' => $date,
|
|
'active_date' => $state['import_date'],
|
|
'conversion_window_days' => (int) ( $state['conversion_window_days'] ?? $conversion_window_days ),
|
|
'dates_synced' => $state['import_dates'] ?? $import_dates,
|
|
'phase' => $state['phase'],
|
|
'clients_per_run' => $clients_per_run,
|
|
'processed_clients_in_call' => count( $clients_processed_in_call ),
|
|
'client_ids_processed_in_call' => $clients_processed_in_call,
|
|
'processed_clients_in_phase' => $processed_in_phase,
|
|
'remaining_clients_in_phase' => $remaining_in_phase,
|
|
'estimated_calls_remaining_in_phase' => $estimated_calls_remaining_in_phase,
|
|
'total_clients' => count( $client_ids ),
|
|
'processed_products' => $processed_products_total,
|
|
'skipped' => $skipped_total,
|
|
'history_30_products' => $history_30_products_total,
|
|
'products_temp_rows' => $products_temp_rows_total,
|
|
'errors' => $errors
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
static public function cron_products_urls()
|
|
{
|
|
global $mdb, $settings;
|
|
self::touch_cron_invocation( __FUNCTION__ );
|
|
|
|
$api = new \services\GoogleAdsApi();
|
|
if ( !$api -> is_merchant_configured() )
|
|
{
|
|
echo json_encode( [
|
|
'result' => 'Merchant API nie jest skonfigurowane. Uzupelnij OAuth2 Client ID/Secret oraz Merchant Refresh Token w Ustawieniach.'
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
$client_id = (int) \S::get( 'client_id' );
|
|
$batch_limit = (int) \S::get( 'limit' );
|
|
$debug_mode = (int) \S::get( 'debug' ) === 1;
|
|
if ( $batch_limit <= 0 )
|
|
{
|
|
$batch_limit = (int) ( $settings['cron_products_urls_limit_per_client'] ?? 100 );
|
|
}
|
|
if ( $batch_limit <= 0 )
|
|
{
|
|
$batch_limit = 100;
|
|
}
|
|
$batch_limit = min( 1000, $batch_limit );
|
|
|
|
$clients_per_run_default = (int) ( $settings['cron_products_urls_clients_per_run'] ?? ( $settings['cron_products_clients_per_run'] ?? 1 ) );
|
|
if ( $clients_per_run_default <= 0 )
|
|
{
|
|
$clients_per_run_default = 1;
|
|
}
|
|
|
|
$clients_per_run = (int) \S::get( 'clients_per_run' );
|
|
if ( $clients_per_run <= 0 )
|
|
{
|
|
$clients_per_run = (int) self::get_setting_value( 'cron_products_urls_clients_per_run', $clients_per_run_default );
|
|
}
|
|
if ( $clients_per_run <= 0 )
|
|
{
|
|
$clients_per_run = $clients_per_run_default;
|
|
}
|
|
$clients_per_run = min( 20, $clients_per_run );
|
|
|
|
$where = "deleted = 0 AND google_merchant_account_id IS NOT NULL AND google_merchant_account_id <> ''";
|
|
if ( $client_id > 0 )
|
|
{
|
|
$where .= ' AND id = ' . $client_id;
|
|
}
|
|
|
|
$clients = $mdb -> query( 'SELECT id, name, google_merchant_account_id FROM clients WHERE ' . $where . ' ORDER BY id ASC' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
if ( !is_array( $clients ) || empty( $clients ) )
|
|
{
|
|
echo json_encode( [
|
|
'result' => 'Brak klientow z ustawionym Merchant Account ID.',
|
|
'processed_clients' => 0,
|
|
'checked_products' => 0,
|
|
'updated_urls' => 0,
|
|
'errors' => []
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
$total_clients_available = count( $clients );
|
|
if ( $client_id <= 0 )
|
|
{
|
|
$last_client_cursor = (int) self::get_setting_value( 'cron_products_urls_last_client_id', 0 );
|
|
$clients = self::pick_clients_batch_by_cursor( $clients, $clients_per_run, $last_client_cursor );
|
|
}
|
|
else
|
|
{
|
|
$clients_per_run = 1;
|
|
}
|
|
|
|
$checked_products = 0;
|
|
$updated_urls = 0;
|
|
$unresolved_products = 0;
|
|
$processed_clients = 0;
|
|
$errors = [];
|
|
$details = [];
|
|
|
|
foreach ( $clients as $client )
|
|
{
|
|
$processed_clients++;
|
|
$selected_products = self::get_products_missing_url_for_client( (int) $client['id'], $batch_limit );
|
|
$product_count = count( $selected_products );
|
|
$diag = $debug_mode ? self::get_products_url_sync_diagnostics_for_client( (int) $client['id'] ) : null;
|
|
|
|
if ( $product_count === 0 )
|
|
{
|
|
$detail_row = [
|
|
'client_id' => (int) $client['id'],
|
|
'client_name' => (string) $client['name'],
|
|
'merchant_account_id' => (string) $client['google_merchant_account_id'],
|
|
'selected_products' => 0,
|
|
'updated_urls' => 0,
|
|
'unresolved_products' => 0
|
|
];
|
|
|
|
if ( $debug_mode )
|
|
{
|
|
$detail_row['diag'] = $diag;
|
|
}
|
|
|
|
$details[] = $detail_row;
|
|
continue;
|
|
}
|
|
$checked_products += $product_count;
|
|
|
|
$offer_ids = [];
|
|
foreach ( $selected_products as $row )
|
|
{
|
|
$offer_ids[] = (string) $row['offer_id'];
|
|
}
|
|
|
|
$links_map = $api -> get_merchant_product_links_for_offer_ids( (string) $client['google_merchant_account_id'], $offer_ids );
|
|
if ( $links_map === false )
|
|
{
|
|
$last_err = (string) \services\GoogleAdsApi::get_setting( 'google_merchant_last_error' );
|
|
$errors[] = 'Blad Merchant API dla klienta ' . $client['name'] . ' (ID: ' . $client['id'] . '): ' . $last_err;
|
|
$unresolved_products += $product_count;
|
|
|
|
$detail_row = [
|
|
'client_id' => (int) $client['id'],
|
|
'client_name' => (string) $client['name'],
|
|
'merchant_account_id' => (string) $client['google_merchant_account_id'],
|
|
'selected_products' => $product_count,
|
|
'updated_urls' => 0,
|
|
'unresolved_products' => $product_count
|
|
];
|
|
|
|
if ( $debug_mode )
|
|
{
|
|
$detail_row['diag'] = $diag;
|
|
}
|
|
|
|
$details[] = $detail_row;
|
|
continue;
|
|
}
|
|
|
|
$client_updated = 0;
|
|
foreach ( $selected_products as $row )
|
|
{
|
|
$offer_id = (string) $row['offer_id'];
|
|
if ( !isset( $links_map[ $offer_id ] ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
\factory\Products::set_product_data( (int) $row['product_id'], 'product_url', (string) $links_map[ $offer_id ] );
|
|
$client_updated++;
|
|
}
|
|
|
|
$updated_urls += $client_updated;
|
|
$client_unresolved = max( 0, $product_count - $client_updated );
|
|
$unresolved_products += $client_unresolved;
|
|
|
|
$detail_row = [
|
|
'client_id' => (int) $client['id'],
|
|
'client_name' => (string) $client['name'],
|
|
'merchant_account_id' => (string) $client['google_merchant_account_id'],
|
|
'selected_products' => $product_count,
|
|
'updated_urls' => $client_updated,
|
|
'unresolved_products' => $client_unresolved
|
|
];
|
|
|
|
if ( $debug_mode )
|
|
{
|
|
$detail_row['diag'] = $diag;
|
|
}
|
|
|
|
$details[] = $detail_row;
|
|
}
|
|
|
|
if ( $client_id <= 0 && !empty( $clients ) )
|
|
{
|
|
$last_client = end( $clients );
|
|
$last_client_id = (int) ( $last_client['id'] ?? 0 );
|
|
if ( $last_client_id > 0 )
|
|
{
|
|
self::set_setting_value( 'cron_products_urls_last_client_id', (string) $last_client_id );
|
|
}
|
|
}
|
|
|
|
echo json_encode( [
|
|
'result' => empty( $errors ) ? 'Synchronizacja URL produktow zakonczona.' : 'Synchronizacja URL produktow zakonczona z bledami.',
|
|
'total_clients_available' => $total_clients_available,
|
|
'processed_clients' => $processed_clients,
|
|
'clients_per_run' => $clients_per_run,
|
|
'checked_products' => $checked_products,
|
|
'updated_urls' => $updated_urls,
|
|
'unresolved_products' => $unresolved_products,
|
|
'errors' => $errors,
|
|
'details' => $details
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
static private function get_products_url_sync_diagnostics_for_client( $client_id )
|
|
{
|
|
global $mdb;
|
|
|
|
$client_id = (int) $client_id;
|
|
if ( $client_id <= 0 )
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$diag = [];
|
|
|
|
$diag['products_total'] = (int) $mdb -> query( 'SELECT COUNT(*) FROM products WHERE client_id = ' . $client_id ) -> fetchColumn();
|
|
$diag['products_not_deleted'] = (int) $mdb -> query( 'SELECT COUNT(*) FROM products WHERE client_id = ' . $client_id ) -> fetchColumn();
|
|
$diag['products_with_offer_id'] = (int) $mdb -> query( 'SELECT COUNT(*) FROM products WHERE client_id = ' . $client_id . ' AND TRIM( COALESCE( offer_id, "" ) ) <> ""' ) -> fetchColumn();
|
|
$diag['products_with_pd_rows'] = (int) $mdb -> query( 'SELECT COUNT( DISTINCT pd.product_id ) FROM products_data pd INNER JOIN products p ON p.id = pd.product_id WHERE p.client_id = ' . $client_id ) -> fetchColumn();
|
|
$diag['products_with_real_url'] = (int) $mdb -> query( 'SELECT COUNT(*) FROM products p LEFT JOIN ( SELECT product_id, MAX( CASE WHEN TRIM( COALESCE( product_url, \"\" ) ) = \"\" THEN 0 WHEN LOWER( TRIM( product_url ) ) IN ( \"0\", \"-\", \"null\" ) THEN 0 ELSE 1 END ) AS has_real_url FROM products_data GROUP BY product_id ) pd ON pd.product_id = p.id WHERE p.client_id = ' . $client_id . ' AND TRIM( COALESCE( p.offer_id, \"\" ) ) <> \"\" AND COALESCE( pd.has_real_url, 0 ) = 1' ) -> fetchColumn();
|
|
$diag['products_missing_url'] = (int) $mdb -> query( 'SELECT COUNT(*) FROM products p LEFT JOIN ( SELECT product_id, MAX( CASE WHEN TRIM( COALESCE( product_url, \"\" ) ) = \"\" THEN 0 WHEN LOWER( TRIM( product_url ) ) IN ( \"0\", \"-\", \"null\" ) THEN 0 ELSE 1 END ) AS has_real_url FROM products_data GROUP BY product_id ) pd ON pd.product_id = p.id WHERE p.client_id = ' . $client_id . ' AND TRIM( COALESCE( p.offer_id, \"\" ) ) <> \"\" AND COALESCE( pd.has_real_url, 0 ) = 0' ) -> fetchColumn();
|
|
|
|
return $diag;
|
|
}
|
|
|
|
static private function get_products_missing_url_for_client( $client_id, $limit )
|
|
{
|
|
global $mdb;
|
|
|
|
$client_id = (int) $client_id;
|
|
$limit = max( 1, min( 1000, (int) $limit ) );
|
|
|
|
if ( $client_id <= 0 )
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$sql = 'SELECT p.id AS product_id, p.offer_id '
|
|
. 'FROM products p '
|
|
. 'LEFT JOIN ( '
|
|
. ' SELECT product_id, '
|
|
. ' MAX( CASE '
|
|
. ' WHEN TRIM( COALESCE( product_url, \'\' ) ) = \'\' THEN 0 '
|
|
. ' WHEN LOWER( TRIM( product_url ) ) IN ( \'0\', \'-\', \'null\' ) THEN 0 '
|
|
. ' ELSE 1 '
|
|
. ' END ) AS has_real_url '
|
|
. ' FROM products_data '
|
|
. ' GROUP BY product_id '
|
|
. ') pd ON pd.product_id = p.id '
|
|
. 'WHERE p.client_id = ' . $client_id . ' '
|
|
. 'AND TRIM( COALESCE( p.offer_id, \'\' ) ) <> \'\' '
|
|
. 'AND COALESCE( pd.has_real_url, 0 ) = 0 '
|
|
. 'ORDER BY p.id ASC '
|
|
. 'LIMIT ' . $limit;
|
|
|
|
$rows = $mdb -> query( $sql ) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
return is_array( $rows ) ? $rows : [];
|
|
}
|
|
|
|
static private function init_products_pipeline_state( $date, $client_ids, $import_dates )
|
|
{
|
|
$import_dates = array_values( array_unique( array_map( function( $item )
|
|
{
|
|
return date( 'Y-m-d', strtotime( $item ) );
|
|
}, (array) $import_dates ) ) );
|
|
sort( $import_dates );
|
|
|
|
$anchor_date = date( 'Y-m-d', strtotime( $date ) );
|
|
$initial_date = !empty( $import_dates ) ? $import_dates[0] : $anchor_date;
|
|
|
|
return [
|
|
'anchor_date' => $anchor_date,
|
|
'import_date' => $initial_date,
|
|
'import_dates' => $import_dates,
|
|
'current_date_index' => 0,
|
|
'conversion_window_days' => count( $import_dates ),
|
|
'clients_hash' => md5( $anchor_date . '|' . implode( ',', $client_ids ) . '|' . implode( ',', $import_dates ) ),
|
|
'phase' => 'fetch',
|
|
'fetch_done_ids' => [],
|
|
'aggregate_30_done_ids' => [],
|
|
'aggregate_temp_done_ids' => []
|
|
];
|
|
}
|
|
|
|
static private function get_products_pipeline_state( $state_key, $date, $client_ids, $import_dates )
|
|
{
|
|
$state_raw = self::get_setting_value( $state_key, '' );
|
|
$state = json_decode( (string) $state_raw, true );
|
|
|
|
$expected_date = date( 'Y-m-d', strtotime( $date ) );
|
|
$expected_dates = array_values( array_unique( array_map( function( $item )
|
|
{
|
|
return date( 'Y-m-d', strtotime( $item ) );
|
|
}, (array) $import_dates ) ) );
|
|
sort( $expected_dates );
|
|
$expected_hash = md5( $expected_date . '|' . implode( ',', $client_ids ) . '|' . implode( ',', $expected_dates ) );
|
|
|
|
if ( !is_array( $state ) )
|
|
{
|
|
return self::init_products_pipeline_state( $expected_date, $client_ids, $expected_dates );
|
|
}
|
|
|
|
if ( ( $state['anchor_date'] ?? '' ) !== $expected_date )
|
|
{
|
|
return self::init_products_pipeline_state( $expected_date, $client_ids, $expected_dates );
|
|
}
|
|
|
|
$state_dates = array_values( array_unique( array_map( function( $item )
|
|
{
|
|
return date( 'Y-m-d', strtotime( $item ) );
|
|
}, (array) ( $state['import_dates'] ?? [] ) ) ) );
|
|
sort( $state_dates );
|
|
|
|
// Gdy zmienia sie lista klientow (np. odblokowany klient), nie resetujemy calego pipeline.
|
|
// Zachowujemy postep dla juz przetworzonych i dopinamy nowych klientow do kolejki.
|
|
if ( ( $state['clients_hash'] ?? '' ) !== $expected_hash )
|
|
{
|
|
if ( $state_dates !== $expected_dates )
|
|
{
|
|
return self::init_products_pipeline_state( $expected_date, $client_ids, $expected_dates );
|
|
}
|
|
|
|
$allowed_client_ids = array_fill_keys( array_map( 'intval', $client_ids ), true );
|
|
foreach ( [ 'fetch_done_ids', 'aggregate_30_done_ids', 'aggregate_temp_done_ids' ] as $key )
|
|
{
|
|
$filtered_ids = [];
|
|
foreach ( (array) ( $state[ $key ] ?? [] ) as $id )
|
|
{
|
|
$id = (int) $id;
|
|
if ( $id > 0 && isset( $allowed_client_ids[ $id ] ) )
|
|
{
|
|
$filtered_ids[] = $id;
|
|
}
|
|
}
|
|
$state[ $key ] = array_values( array_unique( $filtered_ids ) );
|
|
}
|
|
|
|
$state['clients_hash'] = $expected_hash;
|
|
}
|
|
|
|
if ( !isset( $state['import_dates'] ) || !is_array( $state['import_dates'] ) || empty( $state['import_dates'] ) )
|
|
{
|
|
$state['import_dates'] = $expected_dates;
|
|
}
|
|
if ( !isset( $state['current_date_index'] ) || !is_numeric( $state['current_date_index'] ) )
|
|
{
|
|
$state['current_date_index'] = 0;
|
|
}
|
|
$state['current_date_index'] = max( 0, min( count( $state['import_dates'] ) - 1, (int) $state['current_date_index'] ) );
|
|
$state['import_date'] = $state['import_dates'][ $state['current_date_index'] ] ?? $expected_date;
|
|
|
|
foreach ( [ 'fetch_done_ids', 'aggregate_30_done_ids', 'aggregate_temp_done_ids' ] as $key )
|
|
{
|
|
if ( !isset( $state[ $key ] ) || !is_array( $state[ $key ] ) )
|
|
{
|
|
$state[ $key ] = [];
|
|
}
|
|
$state[ $key ] = array_values( array_unique( array_map( 'intval', $state[ $key ] ) ) );
|
|
}
|
|
|
|
if ( !isset( $state['phase'] ) )
|
|
{
|
|
$state['phase'] = 'fetch';
|
|
}
|
|
|
|
return $state;
|
|
}
|
|
|
|
static private function save_products_pipeline_state( $state_key, $state )
|
|
{
|
|
self::set_setting_value( $state_key, json_encode( $state, JSON_UNESCAPED_UNICODE ) );
|
|
}
|
|
|
|
static private function advance_products_pipeline_phase( $state )
|
|
{
|
|
if ( $state['phase'] === 'fetch' )
|
|
{
|
|
$state['phase'] = 'aggregate_30';
|
|
return $state;
|
|
}
|
|
|
|
if ( $state['phase'] === 'aggregate_30' )
|
|
{
|
|
$state['phase'] = 'aggregate_temp';
|
|
return $state;
|
|
}
|
|
|
|
$current_date_index = (int) ( $state['current_date_index'] ?? 0 );
|
|
$import_dates = is_array( $state['import_dates'] ?? null ) ? $state['import_dates'] : [];
|
|
$last_index = count( $import_dates ) - 1;
|
|
|
|
if ( $last_index >= 0 && $current_date_index < $last_index )
|
|
{
|
|
$state['current_date_index'] = $current_date_index + 1;
|
|
$state['import_date'] = $import_dates[ $state['current_date_index'] ];
|
|
$state['phase'] = 'fetch';
|
|
$state['fetch_done_ids'] = [];
|
|
$state['aggregate_30_done_ids'] = [];
|
|
$state['aggregate_temp_done_ids'] = [];
|
|
return $state;
|
|
}
|
|
|
|
$state['phase'] = 'done';
|
|
return $state;
|
|
}
|
|
|
|
static private function sync_products_fetch_for_client( $client, $api, $date )
|
|
{
|
|
global $mdb;
|
|
|
|
$client_id = (int) $client['id'];
|
|
$customer_id = trim( (string) ( $client['google_ads_customer_id'] ?? '' ) );
|
|
$date = date( 'Y-m-d', strtotime( $date ) );
|
|
|
|
$products = $api -> get_products_for_date( $customer_id, $date );
|
|
if ( $products === false )
|
|
{
|
|
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
return [
|
|
'date' => $date,
|
|
'processed_products' => 0,
|
|
'skipped' => 0,
|
|
'history_30_products' => 0,
|
|
'products_temp_rows' => 0,
|
|
'errors' => [ 'Blad API dla klienta ' . $client['name'] . ' (ID: ' . $customer_id . '): ' . $last_err ]
|
|
];
|
|
}
|
|
|
|
if ( !is_array( $products ) )
|
|
{
|
|
$products = [];
|
|
}
|
|
|
|
$existing_products_rows = $mdb -> query(
|
|
'SELECT id, offer_id, name
|
|
FROM products
|
|
WHERE client_id = :client_id
|
|
ORDER BY id ASC',
|
|
[ ':client_id' => $client_id ]
|
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
$products_by_offer_id = [];
|
|
foreach ( $existing_products_rows as $row )
|
|
{
|
|
$offer_id = trim( (string) ( $row['offer_id'] ?? '' ) );
|
|
if ( $offer_id === '' || isset( $products_by_offer_id[ $offer_id ] ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$products_by_offer_id[ $offer_id ] = [
|
|
'id' => (int) ( $row['id'] ?? 0 ),
|
|
'name' => (string) ( $row['name'] ?? '' )
|
|
];
|
|
}
|
|
|
|
$products_data_rows = $mdb -> query(
|
|
'SELECT pd.id, pd.product_id, pd.product_url
|
|
FROM products_data AS pd
|
|
INNER JOIN products AS p ON p.id = pd.product_id
|
|
WHERE p.client_id = :client_id',
|
|
[ ':client_id' => $client_id ]
|
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
$products_data_map = [];
|
|
foreach ( $products_data_rows as $row )
|
|
{
|
|
$product_id = (int) ( $row['product_id'] ?? 0 );
|
|
if ( $product_id <= 0 || isset( $products_data_map[ $product_id ] ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$products_data_map[ $product_id ] = [
|
|
'exists' => true,
|
|
'product_url' => trim( (string) ( $row['product_url'] ?? '' ) )
|
|
];
|
|
}
|
|
|
|
$existing_campaigns_rows = $mdb -> query(
|
|
'SELECT id, campaign_id, campaign_name
|
|
FROM campaigns
|
|
WHERE client_id = :client_id',
|
|
[ ':client_id' => $client_id ]
|
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
$campaigns_by_external_id = [];
|
|
$campaigns_by_db_id = [];
|
|
foreach ( $existing_campaigns_rows as $row )
|
|
{
|
|
$db_campaign_id = (int) ( $row['id'] ?? 0 );
|
|
$external_campaign_id = (int) ( $row['campaign_id'] ?? 0 );
|
|
|
|
if ( $db_campaign_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$campaign_data = [
|
|
'id' => $db_campaign_id,
|
|
'campaign_name' => (string) ( $row['campaign_name'] ?? '' )
|
|
];
|
|
|
|
if ( !isset( $campaigns_by_external_id[ $external_campaign_id ] ) )
|
|
{
|
|
$campaigns_by_external_id[ $external_campaign_id ] = $campaign_data;
|
|
}
|
|
|
|
$campaigns_by_db_id[ $db_campaign_id ] = $campaign_data;
|
|
}
|
|
|
|
$existing_campaign_histories = $mdb -> query(
|
|
'SELECT ch.campaign_id
|
|
FROM campaigns_history AS ch
|
|
INNER JOIN campaigns AS c ON c.id = ch.campaign_id
|
|
WHERE c.client_id = :client_id
|
|
AND ch.date_add = :date_add',
|
|
[
|
|
':client_id' => $client_id,
|
|
':date_add' => $date
|
|
]
|
|
) -> fetchAll( \PDO::FETCH_COLUMN );
|
|
|
|
$campaign_history_exists = [];
|
|
foreach ( (array) $existing_campaign_histories as $history_campaign_id )
|
|
{
|
|
$campaign_history_exists[ (int) $history_campaign_id ] = true;
|
|
}
|
|
|
|
$existing_ad_groups_rows = $mdb -> query(
|
|
'SELECT ag.id, ag.campaign_id, ag.ad_group_id, ag.ad_group_name
|
|
FROM campaign_ad_groups AS ag
|
|
INNER JOIN campaigns AS c ON c.id = ag.campaign_id
|
|
WHERE c.client_id = :client_id',
|
|
[ ':client_id' => $client_id ]
|
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
$ad_groups_by_scope = [];
|
|
foreach ( $existing_ad_groups_rows as $row )
|
|
{
|
|
$db_campaign_id = (int) ( $row['campaign_id'] ?? 0 );
|
|
$external_ad_group_id = (int) ( $row['ad_group_id'] ?? 0 );
|
|
$db_ad_group_id = (int) ( $row['id'] ?? 0 );
|
|
|
|
if ( $db_campaign_id <= 0 || $db_ad_group_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$scope_key = $db_campaign_id . '|' . $external_ad_group_id;
|
|
if ( isset( $ad_groups_by_scope[ $scope_key ] ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$ad_groups_by_scope[ $scope_key ] = [
|
|
'id' => $db_ad_group_id,
|
|
'ad_group_name' => (string) ( $row['ad_group_name'] ?? '' )
|
|
];
|
|
}
|
|
|
|
$existing_history_rows = $mdb -> query(
|
|
'SELECT ph.product_id, ph.campaign_id, ph.ad_group_id, ph.impressions, ph.clicks, ph.cost, ph.conversions, ph.conversions_value
|
|
FROM products_history AS ph
|
|
INNER JOIN products AS p ON p.id = ph.product_id
|
|
WHERE p.client_id = :client_id
|
|
AND ph.date_add = :date_add',
|
|
[
|
|
':client_id' => $client_id,
|
|
':date_add' => $date
|
|
]
|
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
$history_by_scope = [];
|
|
foreach ( $existing_history_rows as $row )
|
|
{
|
|
$history_key = (int) ( $row['product_id'] ?? 0 ) . '|' . (int) ( $row['campaign_id'] ?? 0 ) . '|' . (int) ( $row['ad_group_id'] ?? 0 );
|
|
$history_by_scope[ $history_key ] = [
|
|
'impressions' => (int) ( $row['impressions'] ?? 0 ),
|
|
'clicks' => (int) ( $row['clicks'] ?? 0 ),
|
|
'cost' => (float) ( $row['cost'] ?? 0 ),
|
|
'conversions' => (float) ( $row['conversions'] ?? 0 ),
|
|
'conversions_value' => (float) ( $row['conversions_value'] ?? 0 )
|
|
];
|
|
}
|
|
|
|
$resolve_scope_ids = function( $campaign_external_id, $campaign_name, $ad_group_external_id, $ad_group_name ) use ( &$campaigns_by_external_id, &$campaigns_by_db_id, &$campaign_history_exists, &$ad_groups_by_scope, $client_id, $date, $mdb )
|
|
{
|
|
$campaign_external_id = (int) $campaign_external_id;
|
|
$campaign_name = trim( (string) $campaign_name );
|
|
$ad_group_external_id = (int) $ad_group_external_id;
|
|
$ad_group_name = trim( (string) $ad_group_name );
|
|
|
|
$campaign_data = $campaigns_by_external_id[ $campaign_external_id ] ?? null;
|
|
if ( !$campaign_data )
|
|
{
|
|
$campaign_name_to_save = $campaign_name;
|
|
if ( $campaign_name_to_save === '' )
|
|
{
|
|
$campaign_name_to_save = $campaign_external_id > 0 ? 'Kampania #' . $campaign_external_id : '--- konto ---';
|
|
}
|
|
|
|
$mdb -> insert( 'campaigns', [
|
|
'client_id' => $client_id,
|
|
'campaign_id' => $campaign_external_id,
|
|
'campaign_name' => $campaign_name_to_save
|
|
] );
|
|
|
|
$db_campaign_id = (int) $mdb -> id();
|
|
|
|
$campaign_data = [
|
|
'id' => $db_campaign_id,
|
|
'campaign_name' => $campaign_name_to_save
|
|
];
|
|
|
|
$campaigns_by_external_id[ $campaign_external_id ] = $campaign_data;
|
|
$campaigns_by_db_id[ $db_campaign_id ] = $campaign_data;
|
|
}
|
|
else if ( $campaign_name !== '' && $campaign_name !== (string) ( $campaign_data['campaign_name'] ?? '' ) )
|
|
{
|
|
$mdb -> update( 'campaigns', [ 'campaign_name' => $campaign_name ], [ 'id' => (int) $campaign_data['id'] ] );
|
|
$campaign_data['campaign_name'] = $campaign_name;
|
|
$campaigns_by_external_id[ $campaign_external_id ] = $campaign_data;
|
|
$campaigns_by_db_id[ (int) $campaign_data['id'] ] = $campaign_data;
|
|
}
|
|
|
|
$db_campaign_id = (int) ( $campaign_data['id'] ?? 0 );
|
|
|
|
if ( $db_campaign_id > 0 && !isset( $campaign_history_exists[ $db_campaign_id ] ) )
|
|
{
|
|
$mdb -> insert( 'campaigns_history', [
|
|
'campaign_id' => $db_campaign_id,
|
|
'roas_30_days' => 0,
|
|
'roas_all_time' => 0,
|
|
'budget' => 0,
|
|
'money_spent' => 0,
|
|
'conversion_value' => 0,
|
|
'bidding_strategy' => '',
|
|
'date_add' => $date
|
|
] );
|
|
|
|
$campaign_history_exists[ $db_campaign_id ] = true;
|
|
}
|
|
|
|
if ( $db_campaign_id <= 0 )
|
|
{
|
|
return [ 'campaign_id' => 0, 'ad_group_id' => 0 ];
|
|
}
|
|
|
|
if ( $ad_group_external_id <= 0 )
|
|
{
|
|
$scope_key = $db_campaign_id . '|0';
|
|
if ( !isset( $ad_groups_by_scope[ $scope_key ] ) )
|
|
{
|
|
$db_ad_group_id = (int) self::ensure_campaign_level_ad_group( $db_campaign_id, $date );
|
|
$ad_groups_by_scope[ $scope_key ] = [
|
|
'id' => $db_ad_group_id,
|
|
'ad_group_name' => '--- kampania (brak grupy reklam) ---'
|
|
];
|
|
}
|
|
|
|
return [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => (int) ( $ad_groups_by_scope[ $scope_key ]['id'] ?? 0 )
|
|
];
|
|
}
|
|
|
|
$scope_key = $db_campaign_id . '|' . $ad_group_external_id;
|
|
$ad_group_data = $ad_groups_by_scope[ $scope_key ] ?? null;
|
|
|
|
if ( !$ad_group_data )
|
|
{
|
|
$ad_group_name_to_save = $ad_group_name !== '' ? $ad_group_name : 'Ad group #' . $ad_group_external_id;
|
|
|
|
$mdb -> insert( 'campaign_ad_groups', [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => $ad_group_external_id,
|
|
'ad_group_name' => $ad_group_name_to_save,
|
|
'impressions_30' => 0,
|
|
'clicks_30' => 0,
|
|
'cost_30' => 0,
|
|
'conversions_30' => 0,
|
|
'conversion_value_30' => 0,
|
|
'roas_30' => 0,
|
|
'impressions_all_time' => 0,
|
|
'clicks_all_time' => 0,
|
|
'cost_all_time' => 0,
|
|
'conversions_all_time' => 0,
|
|
'conversion_value_all_time' => 0,
|
|
'roas_all_time' => 0,
|
|
'date_sync' => $date
|
|
] );
|
|
|
|
$ad_group_data = [
|
|
'id' => (int) $mdb -> id(),
|
|
'ad_group_name' => $ad_group_name_to_save
|
|
];
|
|
|
|
$ad_groups_by_scope[ $scope_key ] = $ad_group_data;
|
|
}
|
|
else if ( $ad_group_name !== '' && $ad_group_name !== (string) ( $ad_group_data['ad_group_name'] ?? '' ) )
|
|
{
|
|
$mdb -> update( 'campaign_ad_groups', [ 'ad_group_name' => $ad_group_name ], [ 'id' => (int) $ad_group_data['id'] ] );
|
|
$ad_group_data['ad_group_name'] = $ad_group_name;
|
|
$ad_groups_by_scope[ $scope_key ] = $ad_group_data;
|
|
}
|
|
|
|
return [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => (int) ( $ad_group_data['id'] ?? 0 )
|
|
];
|
|
};
|
|
|
|
$processed = 0;
|
|
$skipped = 0;
|
|
$touched_product_ids = [];
|
|
|
|
foreach ( $products as $offer )
|
|
{
|
|
$offer_external_id = trim( (string) ( $offer['OfferId'] ?? '' ) );
|
|
if ( $offer_external_id === '' )
|
|
{
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
$product_title = trim( (string) ( $offer['ProductTitle'] ?? '' ) );
|
|
if ( $product_title === '' )
|
|
{
|
|
$product_title = $offer_external_id;
|
|
}
|
|
|
|
$existing_product = $products_by_offer_id[ $offer_external_id ] ?? null;
|
|
|
|
if ( !$existing_product )
|
|
{
|
|
$mdb -> insert( 'products', [
|
|
'client_id' => $client_id,
|
|
'offer_id' => $offer_external_id,
|
|
'name' => $product_title
|
|
] );
|
|
|
|
$product_id = $mdb -> id();
|
|
|
|
$products_by_offer_id[ $offer_external_id ] = [
|
|
'id' => (int) $product_id,
|
|
'name' => $product_title
|
|
];
|
|
}
|
|
else
|
|
{
|
|
$product_id = (int) ( $existing_product['id'] ?? 0 );
|
|
$offer_current_name = (string) ( $existing_product['name'] ?? '' );
|
|
|
|
if ( $offer_current_name != $product_title and $date == date( 'Y-m-d', strtotime( '-1 days' ) ) )
|
|
{
|
|
$mdb -> update( 'products', [ 'name' => $product_title ], [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] );
|
|
$products_by_offer_id[ $offer_external_id ]['name'] = $product_title;
|
|
}
|
|
}
|
|
|
|
if ( !$product_id )
|
|
{
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
$product_url = trim( (string) ( $offer['ProductUrl'] ?? '' ) );
|
|
$product_url_path = strtolower( (string) parse_url( $product_url, PHP_URL_PATH ) );
|
|
$is_image_url = (bool) preg_match( '/\.(jpg|jpeg|png|gif|webp|bmp|svg|avif)$/i', $product_url_path );
|
|
|
|
if ( $product_url !== '' && filter_var( $product_url, FILTER_VALIDATE_URL ) && !$is_image_url )
|
|
{
|
|
$product_data_row = $products_data_map[ $product_id ] ?? [ 'exists' => false, 'product_url' => '' ];
|
|
$existing_product_url = trim( (string) ( $product_data_row['product_url'] ?? '' ) );
|
|
|
|
if ( $existing_product_url !== $product_url )
|
|
{
|
|
if ( !empty( $product_data_row['exists'] ) )
|
|
{
|
|
$mdb -> update( 'products_data', [ 'product_url' => $product_url ], [ 'product_id' => $product_id ] );
|
|
}
|
|
else
|
|
{
|
|
$mdb -> insert( 'products_data', [
|
|
'product_id' => $product_id,
|
|
'product_url' => $product_url
|
|
] );
|
|
$product_data_row['exists'] = true;
|
|
}
|
|
|
|
$product_data_row['product_url'] = $product_url;
|
|
$products_data_map[ $product_id ] = $product_data_row;
|
|
}
|
|
}
|
|
|
|
$campaign_external_id = (int) ( $offer['CampaignId'] ?? 0 );
|
|
$campaign_name = trim( (string) ( $offer['CampaignName'] ?? '' ) );
|
|
$ad_group_external_id = (int) ( $offer['AdGroupId'] ?? 0 );
|
|
$ad_group_name = trim( (string) ( $offer['AdGroupName'] ?? '' ) );
|
|
|
|
$scope = $resolve_scope_ids( $campaign_external_id, $campaign_name, $ad_group_external_id, $ad_group_name );
|
|
|
|
$db_campaign_id = (int) ( $scope['campaign_id'] ?? 0 );
|
|
$db_ad_group_id = (int) ( $scope['ad_group_id'] ?? 0 );
|
|
|
|
$impressions = (int) round( (float) ( $offer['Impressions'] ?? 0 ) );
|
|
$clicks = (int) round( (float) ( $offer['Clicks'] ?? 0 ) );
|
|
$cost = (float) ( $offer['Cost'] ?? 0 );
|
|
$conversions = (float) ( $offer['Conversions'] ?? 0 );
|
|
$conversion_value = (float) ( $offer['ConversionValue'] ?? 0 );
|
|
$ctr = ( $impressions > 0 ) ? round( $clicks / $impressions, 4 ) * 100 : 0;
|
|
|
|
$offer_data = [
|
|
'impressions' => $impressions,
|
|
'clicks' => $clicks,
|
|
'ctr' => $ctr,
|
|
'cost' => $cost,
|
|
'conversions' => $conversions,
|
|
'conversions_value' => $conversion_value,
|
|
'updated' => 1,
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => $db_ad_group_id
|
|
];
|
|
|
|
$history_scope_key = (int) $product_id . '|' . (int) $db_campaign_id . '|' . (int) $db_ad_group_id;
|
|
$offer_data_old = $history_by_scope[ $history_scope_key ] ?? null;
|
|
|
|
if ( $offer_data_old )
|
|
{
|
|
if (
|
|
$offer_data_old['impressions'] == $offer_data['impressions']
|
|
and $offer_data_old['clicks'] == $offer_data['clicks']
|
|
and number_format( (float) str_replace( ',', '.', $offer_data_old['cost'] ), 5 ) == number_format( (float) $offer_data['cost'], 5 )
|
|
and (float) $offer_data_old['conversions'] == (float) $offer_data['conversions']
|
|
and number_format( (float) str_replace( ',', '.', $offer_data_old['conversions_value'] ), 5 ) == number_format( (float) $offer_data['conversions_value'], 5 )
|
|
)
|
|
{
|
|
$touched_product_ids[ $product_id ] = true;
|
|
$processed++;
|
|
continue;
|
|
}
|
|
|
|
$mdb -> update( 'products_history', $offer_data, [
|
|
'AND' => [
|
|
'product_id' => $product_id,
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => $db_ad_group_id,
|
|
'date_add' => $date
|
|
]
|
|
] );
|
|
|
|
$history_by_scope[ $history_scope_key ] = [
|
|
'impressions' => $offer_data['impressions'],
|
|
'clicks' => $offer_data['clicks'],
|
|
'cost' => $offer_data['cost'],
|
|
'conversions' => $offer_data['conversions'],
|
|
'conversions_value' => $offer_data['conversions_value']
|
|
];
|
|
}
|
|
else
|
|
{
|
|
$offer_data['product_id'] = $product_id;
|
|
$offer_data['date_add'] = $date;
|
|
$mdb -> insert( 'products_history', $offer_data );
|
|
|
|
$history_by_scope[ $history_scope_key ] = [
|
|
'impressions' => $offer_data['impressions'],
|
|
'clicks' => $offer_data['clicks'],
|
|
'cost' => $offer_data['cost'],
|
|
'conversions' => $offer_data['conversions'],
|
|
'conversions_value' => $offer_data['conversions_value']
|
|
];
|
|
}
|
|
|
|
$touched_product_ids[ $product_id ] = true;
|
|
$processed++;
|
|
}
|
|
|
|
return [
|
|
'date' => $date,
|
|
'processed_products' => $processed,
|
|
'skipped' => $skipped,
|
|
'touched_products' => count( $touched_product_ids ),
|
|
'errors' => []
|
|
];
|
|
}
|
|
|
|
static public function resolve_products_scope_ids( $client_id, $campaign_external_id, $campaign_name, $ad_group_external_id, $ad_group_name, $date_sync )
|
|
{
|
|
$client_id = (int) $client_id;
|
|
$campaign_external_id = (int) $campaign_external_id;
|
|
$ad_group_external_id = (int) $ad_group_external_id;
|
|
|
|
$db_campaign_id = self::ensure_products_campaign(
|
|
$client_id,
|
|
$campaign_external_id,
|
|
$campaign_name,
|
|
$date_sync
|
|
);
|
|
|
|
if ( $db_campaign_id <= 0 )
|
|
{
|
|
$db_campaign_id = self::ensure_products_campaign(
|
|
$client_id,
|
|
0,
|
|
'--- konto ---',
|
|
$date_sync
|
|
);
|
|
}
|
|
|
|
$db_ad_group_id = self::ensure_products_ad_group(
|
|
$db_campaign_id,
|
|
$ad_group_external_id,
|
|
$ad_group_name,
|
|
$date_sync
|
|
);
|
|
|
|
return [
|
|
'campaign_id' => (int) $db_campaign_id,
|
|
'ad_group_id' => (int) $db_ad_group_id
|
|
];
|
|
}
|
|
|
|
static private function ensure_products_campaign( $client_id, $campaign_external_id, $campaign_name, $date_sync )
|
|
{
|
|
global $mdb;
|
|
|
|
$client_id = (int) $client_id;
|
|
$campaign_external_id = (int) $campaign_external_id;
|
|
$campaign_name = trim( (string) $campaign_name );
|
|
|
|
if ( $client_id <= 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
$db_campaign_id = (int) $mdb -> get( 'campaigns', 'id', [ 'AND' => [
|
|
'client_id' => $client_id,
|
|
'campaign_id' => $campaign_external_id
|
|
] ] );
|
|
|
|
if ( $db_campaign_id > 0 )
|
|
{
|
|
if ( $campaign_name !== '' )
|
|
{
|
|
$mdb -> update( 'campaigns', [ 'campaign_name' => $campaign_name ], [ 'id' => $db_campaign_id ] );
|
|
}
|
|
|
|
return $db_campaign_id;
|
|
}
|
|
|
|
if ( $campaign_name === '' )
|
|
{
|
|
$campaign_name = $campaign_external_id > 0 ? 'Kampania #' . $campaign_external_id : '--- konto ---';
|
|
}
|
|
|
|
$mdb -> insert( 'campaigns', [
|
|
'client_id' => $client_id,
|
|
'campaign_id' => $campaign_external_id,
|
|
'campaign_name' => $campaign_name
|
|
] );
|
|
|
|
$db_campaign_id = (int) $mdb -> id();
|
|
|
|
if ( $db_campaign_id > 0 && $date_sync )
|
|
{
|
|
if ( !$mdb -> count( 'campaigns_history', [ 'AND' => [ 'campaign_id' => $db_campaign_id, 'date_add' => $date_sync ] ] ) )
|
|
{
|
|
$mdb -> insert( 'campaigns_history', [
|
|
'campaign_id' => $db_campaign_id,
|
|
'roas_30_days' => 0,
|
|
'roas_all_time' => 0,
|
|
'budget' => 0,
|
|
'money_spent' => 0,
|
|
'conversion_value' => 0,
|
|
'bidding_strategy' => '',
|
|
'date_add' => $date_sync
|
|
] );
|
|
}
|
|
}
|
|
|
|
return $db_campaign_id;
|
|
}
|
|
|
|
static private function ensure_products_ad_group( $db_campaign_id, $ad_group_external_id, $ad_group_name, $date_sync )
|
|
{
|
|
global $mdb;
|
|
|
|
$db_campaign_id = (int) $db_campaign_id;
|
|
$ad_group_external_id = (int) $ad_group_external_id;
|
|
$ad_group_name = trim( (string) $ad_group_name );
|
|
|
|
if ( $db_campaign_id <= 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ( $ad_group_external_id <= 0 )
|
|
{
|
|
return (int) self::ensure_campaign_level_ad_group( $db_campaign_id, $date_sync );
|
|
}
|
|
|
|
$db_ad_group_id = (int) $mdb -> get( 'campaign_ad_groups', 'id', [ 'AND' => [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => $ad_group_external_id
|
|
] ] );
|
|
|
|
if ( $db_ad_group_id > 0 )
|
|
{
|
|
if ( $ad_group_name !== '' )
|
|
{
|
|
$mdb -> update( 'campaign_ad_groups', [ 'ad_group_name' => $ad_group_name ], [ 'id' => $db_ad_group_id ] );
|
|
}
|
|
|
|
return $db_ad_group_id;
|
|
}
|
|
|
|
if ( $ad_group_name === '' )
|
|
{
|
|
$ad_group_name = 'Ad group #' . $ad_group_external_id;
|
|
}
|
|
|
|
$mdb -> insert( 'campaign_ad_groups', [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => $ad_group_external_id,
|
|
'ad_group_name' => $ad_group_name,
|
|
'impressions_30' => 0,
|
|
'clicks_30' => 0,
|
|
'cost_30' => 0,
|
|
'conversions_30' => 0,
|
|
'conversion_value_30' => 0,
|
|
'roas_30' => 0,
|
|
'impressions_all_time' => 0,
|
|
'clicks_all_time' => 0,
|
|
'cost_all_time' => 0,
|
|
'conversions_all_time' => 0,
|
|
'conversion_value_all_time' => 0,
|
|
'roas_all_time' => 0,
|
|
'date_sync' => $date_sync
|
|
] );
|
|
|
|
return (int) $mdb -> id();
|
|
}
|
|
|
|
static private function aggregate_products_history_30_for_client( $client_id, $date = null )
|
|
{
|
|
global $mdb;
|
|
|
|
$client_id = (int) $client_id;
|
|
$params = [ ':client_id' => $client_id ];
|
|
$sql = 'SELECT DISTINCT ph.product_id, ph.campaign_id, ph.ad_group_id, ph.date_add
|
|
FROM products_history AS ph
|
|
INNER JOIN products AS p ON p.id = ph.product_id
|
|
WHERE p.client_id = :client_id
|
|
AND ph.updated = 1';
|
|
|
|
if ( $date )
|
|
{
|
|
$params[':date_add'] = date( 'Y-m-d', strtotime( $date ) );
|
|
$sql .= ' AND ph.date_add = :date_add';
|
|
}
|
|
|
|
$sql .= ' ORDER BY ph.date_add ASC, ph.product_id ASC, ph.campaign_id ASC, ph.ad_group_id ASC';
|
|
|
|
$rows = $mdb -> query( $sql, $params ) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
$processed = 0;
|
|
foreach ( $rows as $row )
|
|
{
|
|
$product_id = (int) $row['product_id'];
|
|
$campaign_id = (int) ( $row['campaign_id'] ?? 0 );
|
|
$ad_group_id = (int) ( $row['ad_group_id'] ?? 0 );
|
|
|
|
self::cron_product_history_30_save( $product_id, $row['date_add'], $campaign_id, $ad_group_id );
|
|
$mdb -> query(
|
|
'UPDATE products_history AS ph
|
|
INNER JOIN products AS p ON p.id = ph.product_id
|
|
SET ph.updated = 0
|
|
WHERE ph.product_id = :product_id
|
|
AND ph.campaign_id = :campaign_id
|
|
AND ph.ad_group_id = :ad_group_id
|
|
AND ph.date_add = :date_add
|
|
AND p.client_id = :client_id',
|
|
[
|
|
':product_id' => $product_id,
|
|
':campaign_id' => $campaign_id,
|
|
':ad_group_id' => $ad_group_id,
|
|
':date_add' => $row['date_add'],
|
|
':client_id' => $client_id
|
|
]
|
|
);
|
|
$processed++;
|
|
}
|
|
|
|
return $processed;
|
|
}
|
|
|
|
static public function rebuild_products_temp_for_client( $client_id )
|
|
{
|
|
global $mdb;
|
|
|
|
$client_id = (int) $client_id;
|
|
if ( $client_id <= 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
$client_bestseller_min_roas = (int) \factory\Products::get_client_bestseller_min_roas( $client_id );
|
|
|
|
$rows = $mdb -> query(
|
|
'SELECT
|
|
p.id AS product_id,
|
|
p.name,
|
|
ph.campaign_id,
|
|
ph.ad_group_id,
|
|
COALESCE( SUM( ph.impressions ), 0 ) AS impressions,
|
|
COALESCE( SUM( ph.clicks ), 0 ) AS clicks,
|
|
COALESCE( SUM( ph.cost ), 0 ) AS cost,
|
|
COALESCE( SUM( ph.conversions ), 0 ) AS conversions,
|
|
COALESCE( SUM( ph.conversions_value ), 0 ) AS conversions_value
|
|
FROM products AS p
|
|
LEFT JOIN products_history AS ph ON p.id = ph.product_id
|
|
WHERE p.client_id = :client_id
|
|
GROUP BY p.id, p.name, ph.campaign_id, ph.ad_group_id',
|
|
[ ':client_id' => $client_id ]
|
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
$product_ids = $mdb -> select( 'products', 'id', [ 'client_id' => $client_id ] );
|
|
$product_ids = array_values( array_unique( array_map( 'intval', (array) $product_ids ) ) );
|
|
|
|
if ( !empty( $product_ids ) )
|
|
{
|
|
$mdb -> delete( 'products_temp', [ 'product_id' => $product_ids ] );
|
|
}
|
|
|
|
// products_data jest globalne per product_id, wiec klasyfikacje liczymy globalnie.
|
|
$global_totals = $mdb -> query(
|
|
'SELECT
|
|
p.id AS product_id,
|
|
COALESCE( SUM( ph.impressions ), 0 ) AS impressions,
|
|
COALESCE( SUM( ph.clicks ), 0 ) AS clicks,
|
|
COALESCE( SUM( ph.cost ), 0 ) AS cost,
|
|
COALESCE( SUM( ph.conversions ), 0 ) AS conversions,
|
|
COALESCE( SUM( ph.conversions_value ), 0 ) AS conversions_value
|
|
FROM products AS p
|
|
LEFT JOIN products_history AS ph ON p.id = ph.product_id
|
|
WHERE p.client_id = :client_id
|
|
GROUP BY p.id',
|
|
[ ':client_id' => $client_id ]
|
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
foreach ( $global_totals as $total )
|
|
{
|
|
$product_id = (int) ( $total['product_id'] ?? 0 );
|
|
if ( $product_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$total_cost = (float) ( $total['cost'] ?? 0 );
|
|
$total_conversions = (float) ( $total['conversions'] ?? 0 );
|
|
$total_conversion_value = (float) ( $total['conversions_value'] ?? 0 );
|
|
$total_roas = ( $total_conversions > 0 && $total_cost > 0 ) ? round( $total_conversion_value / $total_cost, 2 ) * 100 : 0;
|
|
|
|
$custom_label_4 = \factory\Products::get_product_data( $product_id, 'custom_label_4' );
|
|
if ( $custom_label_4 == null || ( $custom_label_4 == 'bestseller' && $client_bestseller_min_roas > 0 ) )
|
|
{
|
|
$new_custom_label_4 = ( $total_roas > $client_bestseller_min_roas && $total_conversions > 10 ) ? 'bestseller' : null;
|
|
|
|
$offers_data_tmp = $mdb -> get( 'products_data', '*', [ 'product_id' => $product_id ] );
|
|
if ( isset( $offers_data_tmp['id'] ) )
|
|
{
|
|
$old_custom_label_4 = (string) ( $offers_data_tmp['custom_label_4'] ?? '' );
|
|
|
|
if ( $new_custom_label_4 != $offers_data_tmp['custom_label_4'] )
|
|
{
|
|
$mdb -> insert( 'products_comments', [
|
|
'product_id' => $product_id,
|
|
'comment' => 'Zmiana pola "custom_label_4" na: ' . $new_custom_label_4,
|
|
'type' => 1,
|
|
'date_add' => date( 'Y-m-d' )
|
|
] );
|
|
}
|
|
|
|
$mdb -> update( 'products_data', [ 'custom_label_4' => $new_custom_label_4 ], [ 'id' => $offers_data_tmp['id'] ] );
|
|
|
|
if ( $old_custom_label_4 !== (string) $new_custom_label_4 )
|
|
{
|
|
\controls\Products::sync_product_fields_to_merchant( $product_id, [
|
|
'custom_label_4' => [
|
|
'old' => $old_custom_label_4,
|
|
'new' => (string) $new_custom_label_4
|
|
]
|
|
], 'cron_products' );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$mdb -> insert( 'products_data', [
|
|
'product_id' => $product_id,
|
|
'custom_label_4' => $new_custom_label_4
|
|
] );
|
|
|
|
if ( $new_custom_label_4 == 'bestseller' )
|
|
{
|
|
$mdb -> insert( 'products_comments', [
|
|
'product_id' => $product_id,
|
|
'comment' => 'Zmiana pola "custom_label_4" na: bestseller',
|
|
'type' => 1,
|
|
'date_add' => date( 'Y-m-d' )
|
|
] );
|
|
}
|
|
|
|
\controls\Products::sync_product_fields_to_merchant( $product_id, [
|
|
'custom_label_4' => [
|
|
'old' => '',
|
|
'new' => (string) $new_custom_label_4
|
|
]
|
|
], 'cron_products' );
|
|
}
|
|
}
|
|
}
|
|
|
|
$processed_rows = 0;
|
|
|
|
foreach ( $rows as $row )
|
|
{
|
|
$product_id = (int) ( $row['product_id'] ?? 0 );
|
|
if ( $product_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$campaign_id = (int) ( $row['campaign_id'] ?? 0 );
|
|
$ad_group_id = (int) ( $row['ad_group_id'] ?? 0 );
|
|
$impressions = (int) ( $row['impressions'] ?? 0 );
|
|
$clicks = (int) ( $row['clicks'] ?? 0 );
|
|
$cost = (float) ( $row['cost'] ?? 0 );
|
|
$conversions = (float) ( $row['conversions'] ?? 0 );
|
|
$conversions_value = (float) ( $row['conversions_value'] ?? 0 );
|
|
|
|
// Pomijamy puste scope bez danych.
|
|
if ( $impressions <= 0 && $clicks <= 0 && $cost <= 0 && $conversions <= 0 && $conversions_value <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$cpc = $clicks > 0 ? round( $cost / $clicks, 6 ) : 0;
|
|
$roas = ( $conversions > 0 && $cost > 0 ) ? round( $conversions_value / $cost, 2 ) * 100 : 0;
|
|
$impressions_30 = (int) \factory\Products::get_impressions_30( $product_id, $campaign_id, $ad_group_id );
|
|
$clicks_30 = (int) \factory\Products::get_clicks_30( $product_id, $campaign_id, $ad_group_id );
|
|
|
|
$mdb -> insert( 'products_temp', [
|
|
'product_id' => $product_id,
|
|
'campaign_id' => $campaign_id,
|
|
'ad_group_id' => $ad_group_id,
|
|
'name' => $row['name'],
|
|
'impressions' => $impressions,
|
|
'impressions_30' => $impressions_30,
|
|
'clicks' => $clicks,
|
|
'clicks_30' => $clicks_30,
|
|
'ctr' => ( $impressions > 0 ) ? round( $clicks / $impressions, 4 ) * 100 : 0,
|
|
'cost' => $cost,
|
|
'conversions' => $conversions,
|
|
'conversions_value' => $conversions_value,
|
|
'cpc' => $cpc,
|
|
'roas' => $roas,
|
|
] );
|
|
|
|
$processed_rows++;
|
|
}
|
|
|
|
return $processed_rows;
|
|
}
|
|
|
|
static public function cron_products_history_30()
|
|
{
|
|
global $mdb;
|
|
self::touch_cron_invocation( __FUNCTION__ );
|
|
|
|
$start_time = microtime(true);
|
|
|
|
$client_id = \S::get( 'client_id' );
|
|
|
|
if ( !$client_id )
|
|
{
|
|
echo json_encode( [ 'result' => "Nie podano ID klienta." ] );
|
|
exit;
|
|
}
|
|
|
|
if ( !$mdb -> count( 'clients', [ 'id' => $client_id ] ) )
|
|
{
|
|
echo json_encode( [ 'result' => "Nie znaleziono klienta o podanym ID.", "client" => "Nie istnieje" ] );
|
|
exit;
|
|
}
|
|
|
|
$products = $mdb -> select( 'products', 'id', [ 'client_id' => $client_id ] );
|
|
foreach ( $products as $product )
|
|
{
|
|
$scopes = $mdb -> query( 'SELECT DISTINCT campaign_id, ad_group_id, date_add FROM products_history WHERE product_id = ' . $product . ' AND updated = 1 ORDER BY date_add DESC' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
foreach ( $scopes as $scope )
|
|
{
|
|
$campaign_id = (int) ( $scope['campaign_id'] ?? 0 );
|
|
$ad_group_id = (int) ( $scope['ad_group_id'] ?? 0 );
|
|
$date_add = $scope['date_add'] ?? '';
|
|
|
|
self::cron_product_history_30_save( $product, $date_add, $campaign_id, $ad_group_id );
|
|
$mdb -> update( 'products_history', [ 'updated' => 0 ], [ 'AND' => [
|
|
'product_id' => $product,
|
|
'campaign_id' => $campaign_id,
|
|
'ad_group_id' => $ad_group_id,
|
|
'date_add' => $date_add
|
|
] ] );
|
|
}
|
|
}
|
|
|
|
$end_time = microtime(true);
|
|
$execution_time = $end_time - $start_time;
|
|
echo json_encode( [ 'result' => "Agregacja zakonczona, dane zapisane do offers_history_30. Czas wykonania skryptu: " . round($execution_time, 4) . " sekund." ] );
|
|
exit;
|
|
}
|
|
|
|
static public function get_roas_all_time( $product_id, $date_to, $campaign_id = 0, $ad_group_id = 0 )
|
|
{
|
|
global $mdb;
|
|
|
|
$product_id = (int) $product_id;
|
|
$campaign_id = (int) $campaign_id;
|
|
$ad_group_id = (int) $ad_group_id;
|
|
|
|
$sql = 'SELECT SUM(conversions_value) / SUM(cost) * 100 AS roas_all_time
|
|
FROM products_history
|
|
WHERE product_id = :product_id
|
|
AND date_add <= :date_to
|
|
AND campaign_id = :campaign_id
|
|
AND ad_group_id = :ad_group_id';
|
|
|
|
$roas_all_time = $mdb -> query( $sql, [
|
|
':product_id' => $product_id,
|
|
':date_to' => $date_to,
|
|
':campaign_id' => $campaign_id,
|
|
':ad_group_id' => $ad_group_id
|
|
] ) -> fetchColumn();
|
|
return round( $roas_all_time, 2 );
|
|
}
|
|
|
|
static public function cron_product_history_30_save( $product_id, $date_to, $campaign_id = 0, $ad_group_id = 0 )
|
|
{
|
|
global $mdb;
|
|
|
|
$product_id = (int) $product_id;
|
|
$campaign_id = (int) $campaign_id;
|
|
$ad_group_id = (int) $ad_group_id;
|
|
|
|
$data = $mdb -> query(
|
|
'SELECT
|
|
date_add,
|
|
SUM( impressions ) AS impressions,
|
|
SUM( clicks ) AS clicks,
|
|
SUM( cost ) AS cost,
|
|
SUM( conversions ) AS conversions,
|
|
SUM( conversions_value ) AS conversions_value
|
|
FROM products_history
|
|
WHERE product_id = :product_id
|
|
AND campaign_id = :campaign_id
|
|
AND ad_group_id = :ad_group_id
|
|
AND date_add <= :date_to
|
|
GROUP BY date_add
|
|
ORDER BY date_add DESC
|
|
LIMIT 30',
|
|
[
|
|
':product_id' => $product_id,
|
|
':campaign_id' => $campaign_id,
|
|
':ad_group_id' => $ad_group_id,
|
|
':date_to' => $date_to
|
|
]
|
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
// Inicjalizacja tablic do przechowywania danych
|
|
$offers_data = [];
|
|
|
|
// Grupowanie danych wedug produktow
|
|
foreach ( $data as $entry )
|
|
{
|
|
if ( !isset( $offers_data[$product_id] ) )
|
|
{
|
|
$offers_data[$product_id] = [
|
|
'impressions' => 0,
|
|
'clicks' => 0,
|
|
'cost' => 0.0,
|
|
'conversions' => 0,
|
|
'conversions_value' => 0.0,
|
|
'roas' => 0,
|
|
'days_counted' => []
|
|
];
|
|
}
|
|
|
|
// Sumowanie danych wedug produktu
|
|
$offers_data[$product_id]['impressions'] += $entry['impressions'];
|
|
$offers_data[$product_id]['clicks'] += $entry['clicks'];
|
|
$offers_data[$product_id]['cost'] += $entry['cost'];
|
|
$offers_data[$product_id]['conversions'] += $entry['conversions'];
|
|
$offers_data[$product_id]['conversions_value'] += $entry['conversions_value'];
|
|
$offers_data[$product_id]['days_counted'][] = $entry['date_add'];
|
|
}
|
|
|
|
foreach ( $offers_data as $offer )
|
|
{
|
|
$day_count = count( $offer['days_counted'] );
|
|
|
|
$impressions = $offer['impressions'];
|
|
$clicks = $offer['clicks'];
|
|
$ctr = ( $clicks > 0 and $impressions ) ? round( $clicks / $impressions, 4 ) * 100 : 0;
|
|
$cost = $offer['cost'];
|
|
$conversions = $offer['conversions'];
|
|
$conversions_value = $offer['conversions_value'];
|
|
$roas = ( $conversions_value > 0 and $cost ) ? round( $conversions_value / $cost, 2 ) * 100 : 0;
|
|
|
|
$days_count_for_product = (int) $mdb -> query(
|
|
'SELECT COUNT( DISTINCT date_add )
|
|
FROM products_history
|
|
WHERE product_id = :product_id
|
|
AND campaign_id = :campaign_id
|
|
AND ad_group_id = :ad_group_id
|
|
AND date_add <= :date_to',
|
|
[
|
|
':product_id' => $product_id,
|
|
':campaign_id' => $campaign_id,
|
|
':ad_group_id' => $ad_group_id,
|
|
':date_to' => $date_to
|
|
]
|
|
) -> fetchColumn();
|
|
|
|
if ( $days_count_for_product >= 14 )
|
|
{
|
|
if ( $mdb -> count( 'products_history_30', [ 'AND' => [
|
|
'product_id' => $product_id,
|
|
'campaign_id' => $campaign_id,
|
|
'ad_group_id' => $ad_group_id,
|
|
'date_add' => $date_to
|
|
] ] ) > 0 )
|
|
{
|
|
$mdb -> update( 'products_history_30', [
|
|
'impressions' => $impressions,
|
|
'clicks' => $clicks,
|
|
'ctr' => $ctr,
|
|
'cost' => $cost,
|
|
'conversions' => $conversions,
|
|
'conversions_value' => $conversions_value,
|
|
'roas' => $roas,
|
|
'roas_all_time' => self::get_roas_all_time( $product_id, $date_to, $campaign_id, $ad_group_id )
|
|
], [ 'AND' => [
|
|
'product_id' => $product_id,
|
|
'campaign_id' => $campaign_id,
|
|
'ad_group_id' => $ad_group_id,
|
|
'date_add' => $date_to
|
|
] ] );
|
|
}
|
|
else
|
|
{
|
|
$mdb -> insert( 'products_history_30', [
|
|
'product_id' => $product_id,
|
|
'campaign_id' => $campaign_id,
|
|
'ad_group_id' => $ad_group_id,
|
|
'impressions' => $impressions,
|
|
'clicks' => $clicks,
|
|
'ctr' => $ctr,
|
|
'cost' => $cost,
|
|
'conversions' => $conversions,
|
|
'conversions_value' => $conversions_value,
|
|
'roas' => $roas,
|
|
'roas_all_time' => self::get_roas_all_time( $product_id, $date_to, $campaign_id, $ad_group_id ),
|
|
'date_add' => $date_to
|
|
] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static public function cron_xml()
|
|
{
|
|
$result = self::generate_custom_feed_for_client( \S::get( 'client_id' ), true );
|
|
|
|
if ( ( $result['status'] ?? '' ) !== 'ok' )
|
|
{
|
|
$response = [ 'result' => $result['message'] ?? 'Nie udalo sie wygenerowac pliku XML.' ];
|
|
if ( !empty( $result['client'] ) )
|
|
{
|
|
$response['client'] = $result['client'];
|
|
}
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
$url = (string) ( $result['url'] ?? '' );
|
|
echo json_encode( [ 'result' => 'Plik XML zostal wygenerowany <a href="' . $url . '">' . $url . '</a>.' ] );
|
|
exit;
|
|
}
|
|
|
|
static public function generate_custom_feed_for_client( $client_id, $touch_invocation = true )
|
|
{
|
|
global $mdb;
|
|
|
|
$client_id = (int) $client_id;
|
|
|
|
if ( $touch_invocation )
|
|
{
|
|
self::touch_cron_invocation( 'cron_xml' );
|
|
}
|
|
|
|
if ( $client_id <= 0 )
|
|
{
|
|
return [ 'status' => 'error', 'message' => 'Nie podano ID klienta.' ];
|
|
}
|
|
|
|
if ( !$mdb -> count( 'clients', [ 'id' => $client_id ] ) )
|
|
{
|
|
return [ 'status' => 'error', 'message' => 'Nie znaleziono klienta o podanym ID.', 'client' => 'Nie istnieje' ];
|
|
}
|
|
|
|
$results = $mdb -> query( 'SELECT * FROM products AS p INNER JOIN products_data AS pd ON p.id = pd.product_id WHERE p.client_id = ' . $client_id ) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
if ( empty( $results ) )
|
|
{
|
|
return [ 'status' => 'error', 'message' => 'Brak produktow do wygenerowania pliku XML.' ];
|
|
}
|
|
|
|
$doc = new \DOMDocument( '1.0', 'UTF-8' );
|
|
$xmlRoot = $doc -> createElement( 'rss' );
|
|
$xmlRoot = $doc -> appendChild( $xmlRoot );
|
|
$xmlRoot -> setAttribute( 'version', '2.0' );
|
|
$xmlRoot -> setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:g', 'http://base.google.com/ns/1.0' );
|
|
|
|
$channelNode = $xmlRoot -> appendChild( $doc -> createElement( 'channel' ) );
|
|
$channelNode -> appendChild( $doc -> createElement( 'title', 'Custom Feed' ) );
|
|
$channelNode -> appendChild( $doc -> createElement( 'link', 'https://ads.pagedev.pl' ) );
|
|
|
|
$fieldMappings = [
|
|
'title' => 'g:title',
|
|
'description' => 'g:description',
|
|
'custom_label_4' => 'g:custom_label_4',
|
|
'custom_label_3' => 'g:custom_label_3',
|
|
'google_product_category' => 'g:google_product_category'
|
|
];
|
|
|
|
foreach ( $results as $row )
|
|
{
|
|
$hasValidField = false;
|
|
foreach ( $fieldMappings as $dbField => $xmlTag )
|
|
{
|
|
if ( !empty( $row[ $dbField ] ) )
|
|
{
|
|
$hasValidField = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !$hasValidField )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$itemNode = $channelNode -> appendChild( $doc -> createElement( 'item' ) );
|
|
|
|
$offer_id = $mdb -> get( 'products', 'offer_id', [ 'id' => $row['product_id'] ] );
|
|
$offer_id = str_replace( 'shopify_pl', 'shopify_PL', $offer_id );
|
|
$itemNode -> appendChild( $doc -> createElement( 'id', $offer_id ) );
|
|
|
|
foreach ( $fieldMappings as $dbField => $xmlTag )
|
|
{
|
|
if ( !empty( $row[ $dbField ] ) )
|
|
{
|
|
$itemNode -> appendChild( $doc -> createElement( $xmlTag, $row[ $dbField ] ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
$xml_dir = dirname( __DIR__, 2 ) . DIRECTORY_SEPARATOR . 'xml';
|
|
if ( !is_dir( $xml_dir ) )
|
|
{
|
|
@mkdir( $xml_dir, 0777, true );
|
|
}
|
|
|
|
$file_path = $xml_dir . DIRECTORY_SEPARATOR . 'custom-feed-' . $client_id . '.xml';
|
|
$save_result = @file_put_contents( $file_path, $doc -> saveXML() );
|
|
if ( $save_result === false )
|
|
{
|
|
return [ 'status' => 'error', 'message' => 'Nie udalo sie zapisac pliku XML na serwerze.' ];
|
|
}
|
|
|
|
$scheme = ( !empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ) ? 'https' : 'http';
|
|
$host = $_SERVER['HTTP_HOST'] ?? ( $_SERVER['SERVER_NAME'] ?? 'localhost' );
|
|
$url = $scheme . '://' . $host . '/xml/custom-feed-' . $client_id . '.xml';
|
|
|
|
return [
|
|
'status' => 'ok',
|
|
'message' => 'Plik XML zostal wygenerowany.',
|
|
'url' => $url,
|
|
'client_id' => $client_id
|
|
];
|
|
}
|
|
|
|
static public function cron_phrases()
|
|
{
|
|
global $mdb;
|
|
self::touch_cron_invocation( __FUNCTION__ );
|
|
|
|
if ( !$client_id = \S::get( 'client_id' ) )
|
|
{
|
|
echo json_encode( [ 'result' => "Nie podano ID klienta." ] );
|
|
exit;
|
|
}
|
|
|
|
if ( !$mdb -> count( 'clients', [ 'id' => $client_id ] ) )
|
|
{
|
|
echo json_encode( [ 'result' => "Nie znaleziono klienta o podanym ID.", "client" => "Nie istnieje" ] );
|
|
exit;
|
|
}
|
|
|
|
$data = $mdb -> query( 'SELECT * FROM phrases AS p INNER JOIN phrases_history AS ph ON p.id = ph.phrase_id WHERE p.client_id = ' . $client_id ) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
$aggregated_data = [];
|
|
|
|
foreach ( $data as $row )
|
|
{
|
|
$phrase_id = $row['phrase_id'];
|
|
|
|
if ( !isset( $aggregated_data[$client_id] ) )
|
|
{
|
|
$aggregated_data[$client_id] = [];
|
|
}
|
|
|
|
if ( !isset( $aggregated_data[$client_id][$phrase_id] ) )
|
|
{
|
|
$aggregated_data[$client_id][$phrase_id] = [
|
|
'phrase_id' => $phrase_id,
|
|
'phrase' => $row['phrase'],
|
|
'impressions' => 0,
|
|
'clicks' => 0,
|
|
'cost' => 0.0,
|
|
'conversions' => 0,
|
|
'conversions_value' => 0.0
|
|
];
|
|
}
|
|
|
|
$aggregated_data[$client_id][$phrase_id]['impressions'] += $row['impressions'];
|
|
$aggregated_data[$client_id][$phrase_id]['clicks'] += $row['clicks'];
|
|
$aggregated_data[$client_id][$phrase_id]['cost'] += $row['cost'];
|
|
$aggregated_data[$client_id][$phrase_id]['conversions'] += $row['conversions'];
|
|
$aggregated_data[$client_id][$phrase_id]['conversions_value'] += $row['conversions_value'];
|
|
}
|
|
|
|
$phrases_ids = $mdb -> select( 'phrases', 'id', [ 'client_id' => $client_id ] );
|
|
foreach ( $phrases_ids as $phrase_id )
|
|
{
|
|
$phrases_ids_array[] = $phrase_id -> id;
|
|
}
|
|
|
|
$mdb -> delete( 'phrases_temp', [ 'phrase_id' => $phrases_ids_array ] );
|
|
|
|
foreach ( $aggregated_data as $client_phrases )
|
|
{
|
|
foreach ( $client_phrases as $phrase_data )
|
|
{
|
|
$cpc = $phrase_data['clicks'] > 0 ? round( $phrase_data['cost'] / $phrase_data['clicks'], 6 ) : 0;
|
|
$roas = ( $phrase_data['conversions'] > 0 and $phrase_data['cost'] ) ? round( $phrase_data['conversions_value'] / $phrase_data['cost'], 2 ) * 100 : 0;
|
|
|
|
$mdb -> insert( 'phrases_temp', [
|
|
'phrase_id' => $phrase_data['phrase_id'],
|
|
'phrase' => $phrase_data['phrase'],
|
|
'impressions' => $phrase_data['impressions'],
|
|
'clicks' => $phrase_data['clicks'],
|
|
'cost' => $phrase_data['cost'],
|
|
'conversions' => $phrase_data['conversions'],
|
|
'conversions_value' => $phrase_data['conversions_value'],
|
|
'cpc' => $cpc,
|
|
'roas' => $roas,
|
|
] );
|
|
}
|
|
}
|
|
|
|
echo json_encode( [ 'result' => "Agregacja zakonczona, dane zapisane do phrases_temp." ] );
|
|
exit;
|
|
}
|
|
|
|
static public function cron_phrases_history_30()
|
|
{
|
|
global $mdb;
|
|
self::touch_cron_invocation( __FUNCTION__ );
|
|
|
|
$start_time = microtime( true ); // Rozpoczcie mierzenia czasu
|
|
|
|
$client_id = \S::get( 'client_id' ); // Pobranie ID klienta
|
|
|
|
if ( !$client_id ) // Jeli nie podano ID klienta
|
|
{
|
|
echo json_encode( [ 'result' => "Nie podano ID klienta." ] ); // Wyswietlenie komunikatu
|
|
exit; // Zakonczenie dziaania skryptu
|
|
}
|
|
|
|
if ( !$mdb -> count( 'clients', [ 'id' => $client_id ] ) ) // Sprawdzenie, czy klient istnieje
|
|
{
|
|
echo json_encode( [ 'result' => "Nie znaleziono klienta o podanym ID.", "client" => "Nie istnieje" ] ); // Wyswietlenie komunikatu
|
|
exit; // Zakonczenie dziaania skryptu
|
|
}
|
|
|
|
// Pobranie biecej daty i daty sprzed 30 dni
|
|
$phrases = $mdb -> query( 'SELECT * FROM phrases WHERE client_id = ' . $client_id ) -> fetchAll( \PDO::FETCH_ASSOC ); // Pobranie fraz dla danego klienta
|
|
|
|
foreach ( $phrases as $phrase )
|
|
{
|
|
for ( $i = 0; $i < 30; $i++ )
|
|
{
|
|
$date_to = date( 'Y-m-d', strtotime( '-' . ( 1 + $i ) . ' days' ) );
|
|
$date_from = date( 'Y-m-d', strtotime( '-' . ( 31 + $i ) . ' days' ) );
|
|
|
|
$data_updated = false;
|
|
|
|
if ( $mdb -> count( 'phrases_history', [ 'AND' => [ 'phrase_id' => $phrase['id'], 'date_add[>=]' => $date_from, 'date_add[<=]' => $date_to, 'updated' => 1 ] ] ) > 0 )
|
|
{
|
|
$data_updated = true;
|
|
}
|
|
|
|
if ( $data_updated )
|
|
{
|
|
self::cron_phrase_history_30_save( $phrase['id'], $date_from, $date_to );
|
|
}
|
|
}
|
|
|
|
$mdb -> update( 'phrases_history', [ 'updated' => 0 ], [ 'AND' => [ 'phrase_id' => $phrase['id'], 'updated' => 1 ] ] );
|
|
}
|
|
|
|
$end_time = microtime( true ); // Zakonczenie mierzenia czasu
|
|
$execution_time = $end_time - $start_time; // Obliczenie czasu wykonania
|
|
|
|
echo json_encode( [ 'result' => "Agregacja zakonczona, dane zapisane do phrases_history_30. Czas wykonania skryptu: " . round( $execution_time, 4 ) . " sekund.", 'client' => \factory\Campaigns::get_client_name( $client_id ) ] ); // Wyswietlenie komunikatu
|
|
exit;
|
|
}
|
|
|
|
// ===========================
|
|
// KAMPANIE - Google Ads API
|
|
// ===========================
|
|
|
|
static public function cron_campaigns()
|
|
{
|
|
global $mdb, $settings;
|
|
self::touch_cron_invocation( __FUNCTION__ );
|
|
|
|
$api = new \services\GoogleAdsApi();
|
|
|
|
if ( !$api -> is_configured() )
|
|
{
|
|
echo json_encode( [ 'result' => 'Google Ads API nie jest skonfigurowane. Uzupelnij dane w Ustawieniach.' ] );
|
|
exit;
|
|
}
|
|
|
|
$sync_date = \S::get( 'date' ) ? date( 'Y-m-d', strtotime( \S::get( 'date' ) ) ) : date( 'Y-m-d' );
|
|
$conversion_window_days = self::get_conversion_window_days();
|
|
$sync_dates = self::build_backfill_dates( $sync_date, $conversion_window_days );
|
|
$client_id = (int) \S::get( 'client_id' );
|
|
|
|
if ( $client_id > 0 )
|
|
{
|
|
$client = $mdb -> get( 'clients', '*', [ 'AND' => [
|
|
'id' => $client_id,
|
|
'google_ads_customer_id[!]' => null,
|
|
'deleted' => 0
|
|
] ] );
|
|
|
|
if ( !$client )
|
|
{
|
|
echo json_encode( [ 'result' => 'Nie znaleziono klienta z poprawnym Google Ads Customer ID.', 'client_id' => $client_id ] );
|
|
exit;
|
|
}
|
|
|
|
$sync = self::sync_campaigns_for_client( $client, $api, $sync_date, true );
|
|
|
|
echo json_encode( [
|
|
'result' => empty( $sync['errors'] ) ? 'Synchronizacja kampanii zakonczona.' : 'Synchronizacja kampanii zakonczona z bledami.',
|
|
'client_id' => (int) $client['id'],
|
|
'date' => $sync_date,
|
|
'active_date' => $sync_date,
|
|
'processed_records' => (int) $sync['processed_records'],
|
|
'ad_groups_synced' => (int) ( $sync['ad_groups_synced'] ?? 0 ),
|
|
'search_terms_synced' => (int) ( $sync['search_terms_synced'] ?? 0 ),
|
|
'keywords_synced' => (int) ( $sync['keywords_synced'] ?? 0 ),
|
|
'negative_keywords_synced' => (int) ( $sync['negative_keywords_synced'] ?? 0 ),
|
|
'errors' => $sync['errors']
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
$clients = $mdb -> select( 'clients', '*', [
|
|
'AND' => [
|
|
'google_ads_customer_id[!]' => null,
|
|
'deleted' => 0
|
|
],
|
|
'ORDER' => [ 'id' => 'ASC' ]
|
|
] );
|
|
|
|
if ( empty( $clients ) )
|
|
{
|
|
echo json_encode( [ 'result' => 'Brak klientow z ustawionym Google Ads Customer ID.' ] );
|
|
exit;
|
|
}
|
|
|
|
$client_ids = [];
|
|
$clients_map = [];
|
|
|
|
foreach ( $clients as $client )
|
|
{
|
|
$cid = (int) $client['id'];
|
|
if ( $cid <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$client_ids[] = $cid;
|
|
$clients_map[$cid] = $client;
|
|
}
|
|
|
|
$client_ids = array_values( array_unique( $client_ids ) );
|
|
|
|
$window_state_key = 'cron_campaigns_window_state';
|
|
$window_state = self::get_campaigns_window_state( $window_state_key, $sync_date, $sync_dates, $client_ids );
|
|
$window_state['client_ids'] = $client_ids;
|
|
self::save_campaigns_window_state( $window_state_key, $window_state );
|
|
$active_sync_date = $window_state['sync_date'];
|
|
$sync_details = ( $active_sync_date === $sync_date );
|
|
|
|
$state_key = 'cron_campaigns_state';
|
|
$state = self::get_daily_cron_state( $state_key, $active_sync_date );
|
|
|
|
$processed_ids_normalized = array_values( array_unique( array_map( 'intval', (array) ( $state['processed_ids'] ?? [] ) ) ) );
|
|
$allowed_clients_lookup = array_fill_keys( array_map( 'intval', $client_ids ), true );
|
|
$processed_ids_filtered = [];
|
|
foreach ( $processed_ids_normalized as $pid )
|
|
{
|
|
if ( $pid > 0 && isset( $allowed_clients_lookup[ $pid ] ) )
|
|
{
|
|
$processed_ids_filtered[] = $pid;
|
|
}
|
|
}
|
|
|
|
if ( count( $processed_ids_filtered ) !== count( $processed_ids_normalized ) )
|
|
{
|
|
$state['processed_ids'] = $processed_ids_filtered;
|
|
self::save_daily_cron_state( $state_key, $state, $active_sync_date );
|
|
}
|
|
else
|
|
{
|
|
$state['processed_ids'] = $processed_ids_normalized;
|
|
}
|
|
|
|
$clients_per_run_default = (int) ( $settings['cron_campaigns_clients_per_run'] ?? 2 );
|
|
if ( $clients_per_run_default <= 0 )
|
|
{
|
|
$clients_per_run_default = 2;
|
|
}
|
|
|
|
$clients_per_run = (int) \S::get( 'clients_per_run' );
|
|
if ( $clients_per_run <= 0 )
|
|
{
|
|
$clients_per_run = $clients_per_run_default;
|
|
}
|
|
$clients_per_run = min( 20, $clients_per_run );
|
|
|
|
$next_client_id = self::pick_next_client_id( $client_ids, $state['processed_ids'] );
|
|
|
|
if ( !$next_client_id )
|
|
{
|
|
$previous_index = (int) ( $window_state['current_date_index'] ?? 0 );
|
|
$window_state = self::advance_campaigns_window_state( $window_state );
|
|
$next_index = (int) ( $window_state['current_date_index'] ?? 0 );
|
|
|
|
if ( $next_index !== $previous_index )
|
|
{
|
|
self::save_campaigns_window_state( $window_state_key, $window_state );
|
|
echo json_encode( [
|
|
'result' => 'Wszyscy klienci dla aktualnego dnia zostali przetworzeni. Kolejne wywolanie przejdzie do nastepnego dnia.',
|
|
'date' => $sync_date,
|
|
'active_date' => $active_sync_date,
|
|
'next_active_date' => $window_state['sync_date'],
|
|
'conversion_window_days' => $conversion_window_days,
|
|
'dates_synced' => $window_state['sync_dates'],
|
|
'processed_clients' => count( array_intersect( $client_ids, $state['processed_ids'] ) ),
|
|
'total_clients' => count( $client_ids )
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
echo json_encode( [
|
|
'result' => 'Wszyscy klienci kampanii zostali juz przetworzeni dla calego okna dat.',
|
|
'date' => $sync_date,
|
|
'active_date' => $active_sync_date,
|
|
'conversion_window_days' => $conversion_window_days,
|
|
'dates_synced' => $window_state['sync_dates'],
|
|
'processed_clients' => count( array_intersect( $client_ids, $state['processed_ids'] ) ),
|
|
'total_clients' => count( $client_ids )
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
$clients_processed_in_call = [];
|
|
$errors = [];
|
|
$processed_records_total = 0;
|
|
$ad_groups_synced_total = 0;
|
|
$search_terms_synced_total = 0;
|
|
$keywords_synced_total = 0;
|
|
$negative_keywords_synced_total = 0;
|
|
|
|
$processed_now = 0;
|
|
while ( $processed_now < $clients_per_run )
|
|
{
|
|
$next_client_id = self::pick_next_client_id( $client_ids, $state['processed_ids'] );
|
|
if ( !$next_client_id )
|
|
{
|
|
break;
|
|
}
|
|
|
|
$selected_client = $clients_map[$next_client_id] ?? null;
|
|
if ( !$selected_client )
|
|
{
|
|
$errors[] = 'Nie udalo sie wybrac klienta do synchronizacji kampanii. ID: ' . $next_client_id;
|
|
$state['processed_ids'][] = (int) $next_client_id;
|
|
$state['processed_ids'] = array_values( array_unique( array_map( 'intval', $state['processed_ids'] ) ) );
|
|
$processed_now++;
|
|
continue;
|
|
}
|
|
|
|
$sync = self::sync_campaigns_for_client( $selected_client, $api, $active_sync_date, $sync_details );
|
|
$processed_records_total += (int) ( $sync['processed_records'] ?? 0 );
|
|
$ad_groups_synced_total += (int) ( $sync['ad_groups_synced'] ?? 0 );
|
|
$search_terms_synced_total += (int) ( $sync['search_terms_synced'] ?? 0 );
|
|
$keywords_synced_total += (int) ( $sync['keywords_synced'] ?? 0 );
|
|
$negative_keywords_synced_total += (int) ( $sync['negative_keywords_synced'] ?? 0 );
|
|
|
|
if ( !empty( $sync['errors'] ) )
|
|
{
|
|
$errors = array_merge( $errors, (array) $sync['errors'] );
|
|
}
|
|
|
|
// Oznaczamy klienta jako przetworzonego rowniez po bledzie, aby nie zapetlac wywolan.
|
|
$state['processed_ids'][] = (int) $next_client_id;
|
|
$state['processed_ids'] = array_values( array_unique( array_map( 'intval', $state['processed_ids'] ) ) );
|
|
$clients_processed_in_call[] = (int) $next_client_id;
|
|
$processed_now++;
|
|
}
|
|
|
|
self::save_daily_cron_state( $state_key, $state, $active_sync_date );
|
|
|
|
$processed_today = count( array_intersect( $client_ids, $state['processed_ids'] ) );
|
|
$remaining_today = max( 0, count( $client_ids ) - $processed_today );
|
|
$estimated_calls_remaining_today = (int) ceil( $remaining_today / max( 1, $clients_per_run ) );
|
|
|
|
echo json_encode( [
|
|
'result' => empty( $errors ) ? 'Synchronizacja kampanii zakonczona.' : 'Synchronizacja kampanii zakonczona z bledami.',
|
|
'client_id' => !empty( $clients_processed_in_call ) ? (int) $clients_processed_in_call[0] : 0,
|
|
'client_ids_processed_in_call' => $clients_processed_in_call,
|
|
'processed_clients_in_call' => count( $clients_processed_in_call ),
|
|
'clients_per_run' => $clients_per_run,
|
|
'date' => $sync_date,
|
|
'active_date' => $active_sync_date,
|
|
'conversion_window_days' => $conversion_window_days,
|
|
'dates_synced' => $window_state['sync_dates'],
|
|
'processed_records' => $processed_records_total,
|
|
'ad_groups_synced' => $ad_groups_synced_total,
|
|
'search_terms_synced' => $search_terms_synced_total,
|
|
'keywords_synced' => $keywords_synced_total,
|
|
'negative_keywords_synced' => $negative_keywords_synced_total,
|
|
'processed_clients_today' => $processed_today,
|
|
'remaining_clients_today' => $remaining_today,
|
|
'estimated_calls_remaining_today' => $estimated_calls_remaining_today,
|
|
'errors' => $errors
|
|
] );
|
|
exit;
|
|
}
|
|
|
|
static private function sync_campaigns_for_client( $client, $api, $as_of_date = null, $sync_details = true )
|
|
{
|
|
global $mdb;
|
|
|
|
$as_of_date = $as_of_date ? date( 'Y-m-d', strtotime( $as_of_date ) ) : date( 'Y-m-d' );
|
|
$sync_details = (bool) $sync_details;
|
|
$processed = 0;
|
|
$errors = [];
|
|
$customer_id = $client['google_ads_customer_id'];
|
|
$campaigns_db_map = [];
|
|
|
|
$campaigns_30 = $api -> get_campaigns_30_days( $customer_id, $as_of_date );
|
|
if ( $campaigns_30 === false )
|
|
{
|
|
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
$errors[] = 'Blad API dla klienta ' . $client['name'] . ' (ID: ' . $customer_id . '): ' . $last_err;
|
|
|
|
return [
|
|
'processed_records' => 0,
|
|
'ad_groups_synced' => 0,
|
|
'search_terms_synced' => 0,
|
|
'keywords_synced' => 0,
|
|
'negative_keywords_synced' => 0,
|
|
'errors' => $errors
|
|
];
|
|
}
|
|
|
|
if ( !is_array( $campaigns_30 ) )
|
|
{
|
|
$campaigns_30 = [];
|
|
}
|
|
|
|
$campaigns_all_time = $api -> get_campaigns_all_time( $customer_id, $as_of_date );
|
|
if ( $campaigns_all_time === false )
|
|
{
|
|
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
$errors[] = 'Blad pobierania danych all time dla klienta ' . $client['name'] . ' (ID: ' . $customer_id . '): ' . $last_err;
|
|
|
|
return [
|
|
'processed_records' => 0,
|
|
'ad_groups_synced' => 0,
|
|
'search_terms_synced' => 0,
|
|
'keywords_synced' => 0,
|
|
'negative_keywords_synced' => 0,
|
|
'errors' => $errors
|
|
];
|
|
}
|
|
|
|
$all_time_map = [];
|
|
$all_time_totals = [
|
|
'cost' => 0.0,
|
|
'conversion_value' => 0.0,
|
|
];
|
|
|
|
if ( is_array( $campaigns_all_time ) )
|
|
{
|
|
foreach ( $campaigns_all_time as $cat )
|
|
{
|
|
$all_time_map[ (string) ( $cat['campaign_id'] ?? '' ) ] = (float) ( $cat['roas_all_time'] ?? 0 );
|
|
$all_time_totals['cost'] += (float) ( $cat['cost_all_time'] ?? 0 );
|
|
$all_time_totals['conversion_value'] += (float) ( $cat['conversion_value_all_time'] ?? 0 );
|
|
}
|
|
}
|
|
|
|
$account_30_totals = [
|
|
'budget' => 0.0,
|
|
'money_spent' => 0.0,
|
|
'conversion_value' => 0.0,
|
|
];
|
|
|
|
foreach ( $campaigns_30 as $campaign )
|
|
{
|
|
$external_campaign_id = isset( $campaign['campaign_id'] ) ? (string) $campaign['campaign_id'] : '';
|
|
if ( $external_campaign_id === '' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$advertising_channel_type = strtoupper( trim( (string) ( $campaign['advertising_channel_type'] ?? '' ) ) );
|
|
|
|
$account_30_totals['budget'] += (float) ( $campaign['budget'] ?? 0 );
|
|
$account_30_totals['money_spent'] += (float) ( $campaign['money_spent'] ?? 0 );
|
|
$account_30_totals['conversion_value'] += (float) ( $campaign['conversion_value'] ?? 0 );
|
|
|
|
if ( !$mdb -> count( 'campaigns', [ 'AND' => [
|
|
'client_id' => $client['id'],
|
|
'campaign_id' => $external_campaign_id
|
|
] ] ) )
|
|
{
|
|
$mdb -> insert( 'campaigns', [
|
|
'client_id' => $client['id'],
|
|
'campaign_id' => $external_campaign_id,
|
|
'campaign_name' => $campaign['campaign_name'],
|
|
'advertising_channel_type' => $advertising_channel_type !== '' ? $advertising_channel_type : null
|
|
] );
|
|
$db_campaign_id = $mdb -> id();
|
|
}
|
|
else
|
|
{
|
|
$db_campaign_id = $mdb -> get( 'campaigns', 'id', [ 'AND' => [
|
|
'client_id' => $client['id'],
|
|
'campaign_id' => $external_campaign_id
|
|
] ] );
|
|
|
|
$mdb -> update( 'campaigns', [
|
|
'campaign_name' => $campaign['campaign_name'],
|
|
'advertising_channel_type' => $advertising_channel_type !== '' ? $advertising_channel_type : null
|
|
], [ 'id' => $db_campaign_id ] );
|
|
}
|
|
|
|
$bidding_strategy = self::format_bidding_strategy(
|
|
$campaign['bidding_strategy'],
|
|
$campaign['target_roas'] ?? 0
|
|
);
|
|
|
|
$history_data = [
|
|
'roas_30_days' => $campaign['roas_30_days'],
|
|
'roas_all_time' => $all_time_map[ $external_campaign_id ] ?? 0,
|
|
'budget' => $campaign['budget'],
|
|
'money_spent' => $campaign['money_spent'],
|
|
'conversion_value' => $campaign['conversion_value'],
|
|
'bidding_strategy' => $bidding_strategy,
|
|
];
|
|
|
|
if ( $mdb -> count( 'campaigns_history', [ 'AND' => [
|
|
'campaign_id' => $db_campaign_id,
|
|
'date_add' => $as_of_date
|
|
] ] ) )
|
|
{
|
|
$mdb -> update( 'campaigns_history', $history_data, [ 'AND' => [
|
|
'campaign_id' => $db_campaign_id,
|
|
'date_add' => $as_of_date
|
|
] ] );
|
|
}
|
|
else
|
|
{
|
|
$history_data['campaign_id'] = $db_campaign_id;
|
|
$history_data['date_add'] = $as_of_date;
|
|
$mdb -> insert( 'campaigns_history', $history_data );
|
|
}
|
|
|
|
$campaigns_db_map[ $external_campaign_id ] = (int) $db_campaign_id;
|
|
$processed++;
|
|
}
|
|
|
|
$account_roas_30 = ( $account_30_totals['money_spent'] > 0 )
|
|
? round( ( $account_30_totals['conversion_value'] / $account_30_totals['money_spent'] ) * 100, 2 )
|
|
: 0;
|
|
|
|
$account_roas_all_time = ( $all_time_totals['cost'] > 0 )
|
|
? round( ( $all_time_totals['conversion_value'] / $all_time_totals['cost'] ) * 100, 2 )
|
|
: 0;
|
|
|
|
if ( !$mdb -> count( 'campaigns', [ 'AND' => [
|
|
'client_id' => $client['id'],
|
|
'campaign_id' => 0
|
|
] ] ) )
|
|
{
|
|
$mdb -> insert( 'campaigns', [
|
|
'client_id' => $client['id'],
|
|
'campaign_id' => 0,
|
|
'campaign_name' => '--- konto ---',
|
|
'advertising_channel_type' => null
|
|
] );
|
|
$db_account_campaign_id = $mdb -> id();
|
|
}
|
|
else
|
|
{
|
|
$db_account_campaign_id = $mdb -> get( 'campaigns', 'id', [ 'AND' => [
|
|
'client_id' => $client['id'],
|
|
'campaign_id' => 0
|
|
] ] );
|
|
|
|
$mdb -> update( 'campaigns', [
|
|
'campaign_name' => '--- konto ---',
|
|
'advertising_channel_type' => null
|
|
], [ 'id' => $db_account_campaign_id ] );
|
|
}
|
|
|
|
$account_history_data = [
|
|
'roas_30_days' => $account_roas_30,
|
|
'roas_all_time' => $account_roas_all_time,
|
|
'budget' => $account_30_totals['budget'],
|
|
'money_spent' => $account_30_totals['money_spent'],
|
|
'conversion_value' => $account_30_totals['conversion_value'],
|
|
'bidding_strategy' => 'Konto (agregacja wszystkich kampanii)',
|
|
];
|
|
|
|
if ( $mdb -> count( 'campaigns_history', [ 'AND' => [
|
|
'campaign_id' => $db_account_campaign_id,
|
|
'date_add' => $as_of_date
|
|
] ] ) )
|
|
{
|
|
$mdb -> update( 'campaigns_history', $account_history_data, [ 'AND' => [
|
|
'campaign_id' => $db_account_campaign_id,
|
|
'date_add' => $as_of_date
|
|
] ] );
|
|
}
|
|
else
|
|
{
|
|
$account_history_data['campaign_id'] = $db_account_campaign_id;
|
|
$account_history_data['date_add'] = $as_of_date;
|
|
$mdb -> insert( 'campaigns_history', $account_history_data );
|
|
}
|
|
|
|
$processed++;
|
|
|
|
if ( !$sync_details )
|
|
{
|
|
return [
|
|
'processed_records' => $processed,
|
|
'ad_groups_synced' => 0,
|
|
'search_terms_synced' => 0,
|
|
'keywords_synced' => 0,
|
|
'negative_keywords_synced' => 0,
|
|
'errors' => $errors
|
|
];
|
|
}
|
|
|
|
$ad_groups_sync = self::sync_campaign_ad_groups_for_client( $campaigns_db_map, $customer_id, $api, $as_of_date );
|
|
$search_terms_sync = self::sync_campaign_search_terms_for_client( $campaigns_db_map, $ad_groups_sync['ad_group_map'], $customer_id, $api, $as_of_date );
|
|
$keywords_sync = self::sync_campaign_keywords_for_client( $campaigns_db_map, $ad_groups_sync['ad_group_map'], $customer_id, $api, $as_of_date );
|
|
$negative_keywords_sync = self::sync_campaign_negative_keywords_for_client( $campaigns_db_map, $ad_groups_sync['ad_group_map'], $customer_id, $api, $as_of_date );
|
|
|
|
$errors = array_merge( $errors, $ad_groups_sync['errors'], $search_terms_sync['errors'], $keywords_sync['errors'], $negative_keywords_sync['errors'] );
|
|
|
|
return [
|
|
'processed_records' => $processed,
|
|
'ad_groups_synced' => (int) $ad_groups_sync['count'],
|
|
'search_terms_synced' => (int) $search_terms_sync['count'],
|
|
'keywords_synced' => (int) $keywords_sync['count'],
|
|
'negative_keywords_synced' => (int) $negative_keywords_sync['count'],
|
|
'errors' => $errors
|
|
];
|
|
}
|
|
|
|
static private function sync_campaign_ad_groups_for_client( $campaigns_db_map, $customer_id, $api, $date_sync )
|
|
{
|
|
global $mdb;
|
|
|
|
$campaign_db_ids = array_values( array_unique( array_map( 'intval', array_values( $campaigns_db_map ) ) ) );
|
|
if ( empty( $campaign_db_ids ) )
|
|
{
|
|
return [ 'count' => 0, 'ad_group_map' => [], 'errors' => [] ];
|
|
}
|
|
|
|
$ad_groups_30 = $api -> get_ad_groups_30_days( $customer_id );
|
|
if ( $ad_groups_30 === false )
|
|
{
|
|
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
return [ 'count' => 0, 'ad_group_map' => [], 'errors' => [ 'Blad pobierania grup reklam (30 dni): ' . $last_err ] ];
|
|
}
|
|
|
|
$ad_groups_all_time = $api -> get_ad_groups_all_time( $customer_id );
|
|
if ( $ad_groups_all_time === false )
|
|
{
|
|
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
return [ 'count' => 0, 'ad_group_map' => [], 'errors' => [ 'Blad pobierania grup reklam (all time): ' . $last_err ] ];
|
|
}
|
|
|
|
if ( !is_array( $ad_groups_30 ) )
|
|
{
|
|
$ad_groups_30 = [];
|
|
}
|
|
|
|
if ( !is_array( $ad_groups_all_time ) )
|
|
{
|
|
$ad_groups_all_time = [];
|
|
}
|
|
|
|
$map_30 = [];
|
|
foreach ( $ad_groups_30 as $row )
|
|
{
|
|
$campaign_external_id = isset( $row['campaign_id'] ) ? (string) $row['campaign_id'] : '';
|
|
$ad_group_external_id = isset( $row['ad_group_id'] ) ? (string) $row['ad_group_id'] : '';
|
|
if ( $campaign_external_id === '' || $ad_group_external_id === '' )
|
|
{
|
|
continue;
|
|
}
|
|
$map_30[ $campaign_external_id . '|' . $ad_group_external_id ] = $row;
|
|
}
|
|
|
|
$map_all_time = [];
|
|
foreach ( $ad_groups_all_time as $row )
|
|
{
|
|
$campaign_external_id = isset( $row['campaign_id'] ) ? (string) $row['campaign_id'] : '';
|
|
$ad_group_external_id = isset( $row['ad_group_id'] ) ? (string) $row['ad_group_id'] : '';
|
|
if ( $campaign_external_id === '' || $ad_group_external_id === '' )
|
|
{
|
|
continue;
|
|
}
|
|
$map_all_time[ $campaign_external_id . '|' . $ad_group_external_id ] = $row;
|
|
}
|
|
|
|
$mdb -> delete( 'campaign_ad_groups', [ 'campaign_id' => $campaign_db_ids ] );
|
|
|
|
$keys = array_values( array_unique( array_merge( array_keys( $map_30 ), array_keys( $map_all_time ) ) ) );
|
|
$ad_group_db_map = [];
|
|
$count = 0;
|
|
|
|
foreach ( $keys as $key )
|
|
{
|
|
$parts = explode( '|', $key, 2 );
|
|
$campaign_external_id = $parts[0] ?? '';
|
|
$ad_group_external_id = $parts[1] ?? '';
|
|
|
|
$db_campaign_id = (int) ( $campaigns_db_map[ $campaign_external_id ] ?? 0 );
|
|
if ( $db_campaign_id <= 0 || $ad_group_external_id === '' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$row_30 = $map_30[ $key ] ?? [];
|
|
$row_all_time = $map_all_time[ $key ] ?? [];
|
|
|
|
$ad_group_name = trim( (string) ( $row_30['ad_group_name'] ?? ( $row_all_time['ad_group_name'] ?? '' ) ) );
|
|
if ( $ad_group_name === '' )
|
|
{
|
|
$ad_group_name = 'Ad group #' . $ad_group_external_id;
|
|
}
|
|
|
|
$mdb -> insert( 'campaign_ad_groups', [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => (int) $ad_group_external_id,
|
|
'ad_group_name' => $ad_group_name,
|
|
'impressions_30' => (int) ( $row_30['impressions'] ?? 0 ),
|
|
'clicks_30' => (int) ( $row_30['clicks'] ?? 0 ),
|
|
'cost_30' => (float) ( $row_30['cost'] ?? 0 ),
|
|
'conversions_30' => (float) ( $row_30['conversions'] ?? 0 ),
|
|
'conversion_value_30' => (float) ( $row_30['conversion_value'] ?? 0 ),
|
|
'roas_30' => (float) ( $row_30['roas'] ?? 0 ),
|
|
'impressions_all_time' => (int) ( $row_all_time['impressions'] ?? 0 ),
|
|
'clicks_all_time' => (int) ( $row_all_time['clicks'] ?? 0 ),
|
|
'cost_all_time' => (float) ( $row_all_time['cost'] ?? 0 ),
|
|
'conversions_all_time' => (float) ( $row_all_time['conversions'] ?? 0 ),
|
|
'conversion_value_all_time' => (float) ( $row_all_time['conversion_value'] ?? 0 ),
|
|
'roas_all_time' => (float) ( $row_all_time['roas'] ?? 0 ),
|
|
'date_sync' => $date_sync
|
|
] );
|
|
|
|
$db_ad_group_id = (int) $mdb -> id();
|
|
if ( $db_ad_group_id > 0 )
|
|
{
|
|
$ad_group_db_map[ $key ] = $db_ad_group_id;
|
|
$count++;
|
|
}
|
|
}
|
|
|
|
return [ 'count' => $count, 'ad_group_map' => $ad_group_db_map, 'errors' => [] ];
|
|
}
|
|
|
|
static private function sync_campaign_search_terms_for_client( $campaigns_db_map, $ad_group_db_map, $customer_id, $api, $date_sync )
|
|
{
|
|
global $mdb;
|
|
|
|
$campaign_db_ids = array_values( array_unique( array_map( 'intval', array_values( $campaigns_db_map ) ) ) );
|
|
if ( empty( $campaign_db_ids ) )
|
|
{
|
|
return [ 'count' => 0, 'errors' => [] ];
|
|
}
|
|
|
|
$search_terms_30 = $api -> get_search_terms_30_days( $customer_id );
|
|
if ( $search_terms_30 === false )
|
|
{
|
|
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
return [ 'count' => 0, 'errors' => [ 'Blad pobierania fraz wyszukiwanych (30 dni): ' . $last_err ] ];
|
|
}
|
|
|
|
$search_terms_all_time = $api -> get_search_terms_all_time( $customer_id );
|
|
if ( $search_terms_all_time === false )
|
|
{
|
|
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
return [ 'count' => 0, 'errors' => [ 'Blad pobierania fraz wyszukiwanych (all time): ' . $last_err ] ];
|
|
}
|
|
|
|
if ( !is_array( $search_terms_30 ) )
|
|
{
|
|
$search_terms_30 = [];
|
|
}
|
|
|
|
if ( !is_array( $search_terms_all_time ) )
|
|
{
|
|
$search_terms_all_time = [];
|
|
}
|
|
|
|
$map_30 = [];
|
|
foreach ( $search_terms_30 as $row )
|
|
{
|
|
$campaign_external_id = isset( $row['campaign_id'] ) ? (string) $row['campaign_id'] : '';
|
|
$ad_group_external_id = isset( $row['ad_group_id'] ) ? (string) $row['ad_group_id'] : '';
|
|
$search_term = trim( (string) ( $row['search_term'] ?? '' ) );
|
|
if ( $campaign_external_id === '' || $ad_group_external_id === '' || $search_term === '' )
|
|
{
|
|
continue;
|
|
}
|
|
$map_30[ $campaign_external_id . '|' . $ad_group_external_id . '|' . strtolower( $search_term ) ] = $row;
|
|
}
|
|
|
|
$map_all_time = [];
|
|
foreach ( $search_terms_all_time as $row )
|
|
{
|
|
$campaign_external_id = isset( $row['campaign_id'] ) ? (string) $row['campaign_id'] : '';
|
|
$ad_group_external_id = isset( $row['ad_group_id'] ) ? (string) $row['ad_group_id'] : '';
|
|
$search_term = trim( (string) ( $row['search_term'] ?? '' ) );
|
|
if ( $campaign_external_id === '' || $ad_group_external_id === '' || $search_term === '' )
|
|
{
|
|
continue;
|
|
}
|
|
$map_all_time[ $campaign_external_id . '|' . $ad_group_external_id . '|' . strtolower( $search_term ) ] = $row;
|
|
}
|
|
|
|
$mdb -> delete( 'campaign_search_terms', [ 'campaign_id' => $campaign_db_ids ] );
|
|
|
|
$keys = array_values( array_unique( array_merge( array_keys( $map_30 ), array_keys( $map_all_time ) ) ) );
|
|
$count = 0;
|
|
|
|
foreach ( $keys as $key )
|
|
{
|
|
$parts = explode( '|', $key, 3 );
|
|
$campaign_external_id = $parts[0] ?? '';
|
|
$ad_group_external_id = $parts[1] ?? '';
|
|
$db_campaign_id = (int) ( $campaigns_db_map[ $campaign_external_id ] ?? 0 );
|
|
$db_ad_group_id = (int) ( $ad_group_db_map[ $campaign_external_id . '|' . $ad_group_external_id ] ?? 0 );
|
|
|
|
if ( $db_campaign_id > 0 && $db_ad_group_id <= 0 && $ad_group_external_id === '0' )
|
|
{
|
|
$db_ad_group_id = self::ensure_campaign_level_ad_group( $db_campaign_id, $date_sync );
|
|
if ( $db_ad_group_id > 0 )
|
|
{
|
|
$ad_group_db_map[ $campaign_external_id . '|0' ] = $db_ad_group_id;
|
|
}
|
|
}
|
|
|
|
if ( $db_campaign_id <= 0 || $db_ad_group_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$row_30 = $map_30[ $key ] ?? [];
|
|
$row_all_time = $map_all_time[ $key ] ?? [];
|
|
|
|
$search_term = trim( (string) ( $row_30['search_term'] ?? ( $row_all_time['search_term'] ?? '' ) ) );
|
|
if ( $search_term === '' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$clicks_30 = (int) ( $row_30['clicks'] ?? 0 );
|
|
$clicks_all_time = (int) ( $row_all_time['clicks'] ?? 0 );
|
|
if ( $clicks_30 <= 0 && $clicks_all_time <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$mdb -> insert( 'campaign_search_terms', [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => $db_ad_group_id,
|
|
'search_term' => $search_term,
|
|
'impressions_30' => (int) ( $row_30['impressions'] ?? 0 ),
|
|
'clicks_30' => $clicks_30,
|
|
'cost_30' => (float) ( $row_30['cost'] ?? 0 ),
|
|
'conversions_30' => (float) ( $row_30['conversions'] ?? 0 ),
|
|
'conversion_value_30' => (float) ( $row_30['conversion_value'] ?? 0 ),
|
|
'roas_30' => (float) ( $row_30['roas'] ?? 0 ),
|
|
'impressions_all_time' => (int) ( $row_all_time['impressions'] ?? 0 ),
|
|
'clicks_all_time' => $clicks_all_time,
|
|
'cost_all_time' => (float) ( $row_all_time['cost'] ?? 0 ),
|
|
'conversions_all_time' => (float) ( $row_all_time['conversions'] ?? 0 ),
|
|
'conversion_value_all_time' => (float) ( $row_all_time['conversion_value'] ?? 0 ),
|
|
'roas_all_time' => (float) ( $row_all_time['roas'] ?? 0 ),
|
|
'date_sync' => $date_sync
|
|
] );
|
|
|
|
$count++;
|
|
}
|
|
|
|
return [ 'count' => $count, 'errors' => [] ];
|
|
}
|
|
|
|
static private function ensure_campaign_level_ad_group( $db_campaign_id, $date_sync )
|
|
{
|
|
global $mdb;
|
|
|
|
$db_campaign_id = (int) $db_campaign_id;
|
|
if ( $db_campaign_id <= 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
$existing_id = (int) $mdb -> get( 'campaign_ad_groups', 'id', [
|
|
'AND' => [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => 0
|
|
]
|
|
] );
|
|
|
|
if ( $existing_id > 0 )
|
|
{
|
|
return $existing_id;
|
|
}
|
|
|
|
$mdb -> insert( 'campaign_ad_groups', [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => 0,
|
|
'ad_group_name' => 'PMax (bez grup reklam)',
|
|
'impressions_30' => 0,
|
|
'clicks_30' => 0,
|
|
'cost_30' => 0,
|
|
'conversions_30' => 0,
|
|
'conversion_value_30' => 0,
|
|
'roas_30' => 0,
|
|
'impressions_all_time' => 0,
|
|
'clicks_all_time' => 0,
|
|
'cost_all_time' => 0,
|
|
'conversions_all_time' => 0,
|
|
'conversion_value_all_time' => 0,
|
|
'roas_all_time' => 0,
|
|
'date_sync' => $date_sync
|
|
] );
|
|
|
|
return (int) $mdb -> id();
|
|
}
|
|
|
|
static private function sync_campaign_keywords_for_client( $campaigns_db_map, $ad_group_db_map, $customer_id, $api, $date_sync )
|
|
{
|
|
global $mdb;
|
|
|
|
$campaign_db_ids = array_values( array_unique( array_map( 'intval', array_values( $campaigns_db_map ) ) ) );
|
|
if ( empty( $campaign_db_ids ) )
|
|
{
|
|
return [ 'count' => 0, 'errors' => [] ];
|
|
}
|
|
|
|
$keywords_30 = $api -> get_ad_keywords_30_days( $customer_id );
|
|
if ( $keywords_30 === false )
|
|
{
|
|
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
return [ 'count' => 0, 'errors' => [ 'Blad pobierania slow kluczowych (30 dni): ' . $last_err ] ];
|
|
}
|
|
|
|
$keywords_all_time = $api -> get_ad_keywords_all_time( $customer_id );
|
|
if ( $keywords_all_time === false )
|
|
{
|
|
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
return [ 'count' => 0, 'errors' => [ 'Blad pobierania slow kluczowych (all time): ' . $last_err ] ];
|
|
}
|
|
|
|
if ( !is_array( $keywords_30 ) )
|
|
{
|
|
$keywords_30 = [];
|
|
}
|
|
|
|
if ( !is_array( $keywords_all_time ) )
|
|
{
|
|
$keywords_all_time = [];
|
|
}
|
|
|
|
$map_30 = [];
|
|
foreach ( $keywords_30 as $row )
|
|
{
|
|
$campaign_external_id = isset( $row['campaign_id'] ) ? (string) $row['campaign_id'] : '';
|
|
$ad_group_external_id = isset( $row['ad_group_id'] ) ? (string) $row['ad_group_id'] : '';
|
|
$keyword_text = trim( (string) ( $row['keyword_text'] ?? '' ) );
|
|
$match_type = trim( (string) ( $row['match_type'] ?? '' ) );
|
|
|
|
if ( $campaign_external_id === '' || $ad_group_external_id === '' || $keyword_text === '' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$map_30[ $campaign_external_id . '|' . $ad_group_external_id . '|' . strtolower( $keyword_text ) . '|' . strtolower( $match_type ) ] = $row;
|
|
}
|
|
|
|
$map_all_time = [];
|
|
foreach ( $keywords_all_time as $row )
|
|
{
|
|
$campaign_external_id = isset( $row['campaign_id'] ) ? (string) $row['campaign_id'] : '';
|
|
$ad_group_external_id = isset( $row['ad_group_id'] ) ? (string) $row['ad_group_id'] : '';
|
|
$keyword_text = trim( (string) ( $row['keyword_text'] ?? '' ) );
|
|
$match_type = trim( (string) ( $row['match_type'] ?? '' ) );
|
|
|
|
if ( $campaign_external_id === '' || $ad_group_external_id === '' || $keyword_text === '' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$map_all_time[ $campaign_external_id . '|' . $ad_group_external_id . '|' . strtolower( $keyword_text ) . '|' . strtolower( $match_type ) ] = $row;
|
|
}
|
|
|
|
$mdb -> delete( 'campaign_keywords', [ 'campaign_id' => $campaign_db_ids ] );
|
|
|
|
$keys = array_values( array_unique( array_merge( array_keys( $map_30 ), array_keys( $map_all_time ) ) ) );
|
|
$count = 0;
|
|
|
|
foreach ( $keys as $key )
|
|
{
|
|
$parts = explode( '|', $key, 4 );
|
|
$campaign_external_id = $parts[0] ?? '';
|
|
$ad_group_external_id = $parts[1] ?? '';
|
|
|
|
$db_campaign_id = (int) ( $campaigns_db_map[ $campaign_external_id ] ?? 0 );
|
|
$db_ad_group_id = (int) ( $ad_group_db_map[ $campaign_external_id . '|' . $ad_group_external_id ] ?? 0 );
|
|
|
|
if ( $db_campaign_id <= 0 || $db_ad_group_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$row_30 = $map_30[ $key ] ?? [];
|
|
$row_all_time = $map_all_time[ $key ] ?? [];
|
|
|
|
$keyword_text = trim( (string) ( $row_30['keyword_text'] ?? ( $row_all_time['keyword_text'] ?? '' ) ) );
|
|
if ( $keyword_text === '' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$match_type = trim( (string) ( $row_30['match_type'] ?? ( $row_all_time['match_type'] ?? '' ) ) );
|
|
$clicks_30 = (int) ( $row_30['clicks'] ?? 0 );
|
|
$clicks_all_time = (int) ( $row_all_time['clicks'] ?? 0 );
|
|
|
|
if ( $clicks_30 <= 0 && $clicks_all_time <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$mdb -> insert( 'campaign_keywords', [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => $db_ad_group_id,
|
|
'keyword_text' => $keyword_text,
|
|
'match_type' => $match_type,
|
|
'impressions_30' => (int) ( $row_30['impressions'] ?? 0 ),
|
|
'clicks_30' => $clicks_30,
|
|
'cost_30' => (float) ( $row_30['cost'] ?? 0 ),
|
|
'conversions_30' => (float) ( $row_30['conversions'] ?? 0 ),
|
|
'conversion_value_30' => (float) ( $row_30['conversion_value'] ?? 0 ),
|
|
'roas_30' => (float) ( $row_30['roas'] ?? 0 ),
|
|
'impressions_all_time' => (int) ( $row_all_time['impressions'] ?? 0 ),
|
|
'clicks_all_time' => $clicks_all_time,
|
|
'cost_all_time' => (float) ( $row_all_time['cost'] ?? 0 ),
|
|
'conversions_all_time' => (float) ( $row_all_time['conversions'] ?? 0 ),
|
|
'conversion_value_all_time' => (float) ( $row_all_time['conversion_value'] ?? 0 ),
|
|
'roas_all_time' => (float) ( $row_all_time['roas'] ?? 0 ),
|
|
'date_sync' => $date_sync
|
|
] );
|
|
|
|
$count++;
|
|
}
|
|
|
|
return [ 'count' => $count, 'errors' => [] ];
|
|
}
|
|
|
|
static private function sync_campaign_negative_keywords_for_client( $campaigns_db_map, $ad_group_db_map, $customer_id, $api, $date_sync )
|
|
{
|
|
global $mdb;
|
|
|
|
$campaign_db_ids = array_values( array_unique( array_map( 'intval', array_values( $campaigns_db_map ) ) ) );
|
|
if ( empty( $campaign_db_ids ) )
|
|
{
|
|
return [ 'count' => 0, 'errors' => [] ];
|
|
}
|
|
|
|
$negatives = $api -> get_negative_keywords( $customer_id );
|
|
if ( $negatives === false )
|
|
{
|
|
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
return [ 'count' => 0, 'errors' => [ 'Blad pobierania fraz wykluczajacych: ' . $last_err ] ];
|
|
}
|
|
|
|
if ( !is_array( $negatives ) )
|
|
{
|
|
$negatives = [];
|
|
}
|
|
|
|
$mdb -> delete( 'campaign_negative_keywords', [ 'campaign_id' => $campaign_db_ids ] );
|
|
|
|
$count = 0;
|
|
$seen = [];
|
|
|
|
foreach ( $negatives as $row )
|
|
{
|
|
$campaign_external_id = isset( $row['campaign_id'] ) ? (string) $row['campaign_id'] : '';
|
|
$db_campaign_id = (int) ( $campaigns_db_map[ $campaign_external_id ] ?? 0 );
|
|
if ( $db_campaign_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$scope = ( $row['scope'] ?? '' ) === 'ad_group' ? 'ad_group' : 'campaign';
|
|
$db_ad_group_id = null;
|
|
|
|
if ( $scope === 'ad_group' )
|
|
{
|
|
$ad_group_external_id = isset( $row['ad_group_id'] ) ? (string) $row['ad_group_id'] : '';
|
|
$mapped_ad_group_id = (int) ( $ad_group_db_map[ $campaign_external_id . '|' . $ad_group_external_id ] ?? 0 );
|
|
if ( $mapped_ad_group_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
$db_ad_group_id = $mapped_ad_group_id;
|
|
}
|
|
|
|
$keyword_text = trim( (string) ( $row['keyword_text'] ?? '' ) );
|
|
if ( $keyword_text === '' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$match_type = trim( (string) ( $row['match_type'] ?? '' ) );
|
|
$uniq_key = $db_campaign_id . '|' . (int) $db_ad_group_id . '|' . $scope . '|' . strtolower( $keyword_text ) . '|' . strtolower( $match_type );
|
|
|
|
if ( isset( $seen[ $uniq_key ] ) )
|
|
{
|
|
continue;
|
|
}
|
|
$seen[ $uniq_key ] = true;
|
|
|
|
$mdb -> insert( 'campaign_negative_keywords', [
|
|
'campaign_id' => $db_campaign_id,
|
|
'ad_group_id' => $db_ad_group_id,
|
|
'scope' => $scope,
|
|
'keyword_text' => $keyword_text,
|
|
'match_type' => $match_type,
|
|
'date_sync' => $date_sync
|
|
] );
|
|
|
|
$count++;
|
|
}
|
|
|
|
return [ 'count' => $count, 'errors' => [] ];
|
|
}
|
|
|
|
static private function get_campaigns_window_state( $state_key, $anchor_date, $sync_dates, $client_ids = [] )
|
|
{
|
|
$anchor_date = date( 'Y-m-d', strtotime( $anchor_date ) );
|
|
$sync_dates = array_values( array_unique( array_map( function( $item )
|
|
{
|
|
return date( 'Y-m-d', strtotime( $item ) );
|
|
}, (array) $sync_dates ) ) );
|
|
sort( $sync_dates );
|
|
|
|
if ( empty( $sync_dates ) )
|
|
{
|
|
$sync_dates = [ $anchor_date ];
|
|
}
|
|
|
|
$client_ids_sorted = array_values( array_unique( array_map( 'intval', (array) $client_ids ) ) );
|
|
sort( $client_ids_sorted );
|
|
|
|
$expected_hash = md5( $anchor_date . '|' . implode( ',', $client_ids_sorted ) . '|' . implode( ',', $sync_dates ) );
|
|
$state_raw = self::get_setting_value( $state_key, '' );
|
|
$state = json_decode( (string) $state_raw, true );
|
|
|
|
if ( !is_array( $state ) || ( $state['window_hash'] ?? '' ) !== $expected_hash )
|
|
{
|
|
return [
|
|
'anchor_date' => $anchor_date,
|
|
'sync_dates' => $sync_dates,
|
|
'current_date_index' => 0,
|
|
'sync_date' => $sync_dates[0],
|
|
'window_hash' => $expected_hash
|
|
];
|
|
}
|
|
|
|
$current_date_index = (int) ( $state['current_date_index'] ?? 0 );
|
|
$current_date_index = max( 0, min( count( $sync_dates ) - 1, $current_date_index ) );
|
|
|
|
return [
|
|
'anchor_date' => $anchor_date,
|
|
'sync_dates' => $sync_dates,
|
|
'current_date_index' => $current_date_index,
|
|
'sync_date' => $sync_dates[ $current_date_index ],
|
|
'window_hash' => $expected_hash
|
|
];
|
|
}
|
|
|
|
static private function save_campaigns_window_state( $state_key, $state )
|
|
{
|
|
$sync_dates = array_values( array_unique( array_map( function( $item )
|
|
{
|
|
return date( 'Y-m-d', strtotime( $item ) );
|
|
}, (array) ( $state['sync_dates'] ?? [] ) ) ) );
|
|
sort( $sync_dates );
|
|
|
|
if ( empty( $sync_dates ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
$client_ids_sorted = array_values( array_unique( array_map( 'intval', (array) ( $state['client_ids'] ?? [] ) ) ) );
|
|
sort( $client_ids_sorted );
|
|
|
|
$anchor_date = date( 'Y-m-d', strtotime( $state['anchor_date'] ?? end( $sync_dates ) ) );
|
|
$current_date_index = max( 0, min( count( $sync_dates ) - 1, (int) ( $state['current_date_index'] ?? 0 ) ) );
|
|
$payload = [
|
|
'anchor_date' => $anchor_date,
|
|
'sync_dates' => $sync_dates,
|
|
'client_ids' => $client_ids_sorted,
|
|
'current_date_index' => $current_date_index,
|
|
'sync_date' => $sync_dates[ $current_date_index ],
|
|
'window_hash' => md5( $anchor_date . '|' . implode( ',', $client_ids_sorted ) . '|' . implode( ',', $sync_dates ) )
|
|
];
|
|
|
|
self::set_setting_value( $state_key, json_encode( $payload, JSON_UNESCAPED_UNICODE ) );
|
|
}
|
|
|
|
static private function advance_campaigns_window_state( $state )
|
|
{
|
|
$sync_dates = is_array( $state['sync_dates'] ?? null ) ? $state['sync_dates'] : [];
|
|
$current_date_index = (int) ( $state['current_date_index'] ?? 0 );
|
|
$last_index = count( $sync_dates ) - 1;
|
|
|
|
if ( $last_index < 0 )
|
|
{
|
|
return $state;
|
|
}
|
|
|
|
if ( $current_date_index < $last_index )
|
|
{
|
|
$state['current_date_index'] = $current_date_index + 1;
|
|
$state['sync_date'] = $sync_dates[ $state['current_date_index'] ];
|
|
}
|
|
|
|
return $state;
|
|
}
|
|
|
|
static private function get_daily_cron_state( $state_key, $state_date = null )
|
|
{
|
|
$state_date = $state_date ? date( 'Y-m-d', strtotime( $state_date ) ) : date( 'Y-m-d' );
|
|
|
|
$state_raw = self::get_setting_value( $state_key, '' );
|
|
$state = json_decode( (string) $state_raw, true );
|
|
|
|
if ( !is_array( $state ) || ( $state['date'] ?? '' ) !== $state_date )
|
|
{
|
|
return [
|
|
'date' => $state_date,
|
|
'processed_ids' => []
|
|
];
|
|
}
|
|
|
|
$processed_ids = [];
|
|
if ( isset( $state['processed_ids'] ) && is_array( $state['processed_ids'] ) )
|
|
{
|
|
foreach ( $state['processed_ids'] as $id )
|
|
{
|
|
$id = (int) $id;
|
|
if ( $id > 0 )
|
|
{
|
|
$processed_ids[] = $id;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'date' => $state_date,
|
|
'processed_ids' => array_values( array_unique( $processed_ids ) )
|
|
];
|
|
}
|
|
|
|
static private function save_daily_cron_state( $state_key, $state, $state_date = null )
|
|
{
|
|
$state_date = $state_date ? date( 'Y-m-d', strtotime( $state_date ) ) : date( 'Y-m-d' );
|
|
$payload = [
|
|
'date' => $state_date,
|
|
'processed_ids' => array_values( array_unique( array_map( 'intval', $state['processed_ids'] ?? [] ) ) )
|
|
];
|
|
|
|
self::set_setting_value( $state_key, json_encode( $payload, JSON_UNESCAPED_UNICODE ) );
|
|
}
|
|
|
|
static private function pick_next_client_id( $client_ids, $processed_ids )
|
|
{
|
|
$processed_lookup = [];
|
|
foreach ( $processed_ids as $pid )
|
|
{
|
|
$pid = (int) $pid;
|
|
if ( $pid > 0 )
|
|
{
|
|
$processed_lookup[$pid] = true;
|
|
}
|
|
}
|
|
|
|
foreach ( $client_ids as $cid )
|
|
{
|
|
$cid = (int) $cid;
|
|
if ( $cid > 0 && !isset( $processed_lookup[$cid] ) )
|
|
{
|
|
return $cid;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static private function pick_clients_batch_by_cursor( $clients, $limit, $cursor_client_id = 0 )
|
|
{
|
|
$clients = is_array( $clients ) ? array_values( $clients ) : [];
|
|
if ( empty( $clients ) )
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$limit = max( 1, (int) $limit );
|
|
$total = count( $clients );
|
|
if ( $limit >= $total )
|
|
{
|
|
return $clients;
|
|
}
|
|
|
|
$start_index = 0;
|
|
$cursor_client_id = (int) $cursor_client_id;
|
|
if ( $cursor_client_id > 0 )
|
|
{
|
|
$found_next = false;
|
|
foreach ( $clients as $idx => $client )
|
|
{
|
|
$current_id = (int) ( $client['id'] ?? 0 );
|
|
if ( $current_id > $cursor_client_id )
|
|
{
|
|
$start_index = $idx;
|
|
$found_next = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !$found_next )
|
|
{
|
|
$start_index = 0;
|
|
}
|
|
}
|
|
|
|
$batch = [];
|
|
for ( $i = 0; $i < $limit; $i++ )
|
|
{
|
|
$batch[] = $clients[( $start_index + $i ) % $total];
|
|
}
|
|
|
|
return $batch;
|
|
}
|
|
|
|
static private function get_conversion_window_days()
|
|
{
|
|
$request_value = (int) \S::get( 'conversion_window_days' );
|
|
if ( $request_value > 0 )
|
|
{
|
|
return min( 90, $request_value );
|
|
}
|
|
|
|
$setting_value = (int) self::get_setting_value( 'google_ads_conversion_window_days', 7 );
|
|
if ( $setting_value <= 0 )
|
|
{
|
|
return 7;
|
|
}
|
|
|
|
return min( 90, $setting_value );
|
|
}
|
|
|
|
static private function build_backfill_dates( $end_date, $window_days )
|
|
{
|
|
$end_timestamp = strtotime( $end_date );
|
|
if ( !$end_timestamp )
|
|
{
|
|
$end_timestamp = strtotime( date( 'Y-m-d' ) );
|
|
}
|
|
|
|
$window_days = max( 1, min( 90, (int) $window_days ) );
|
|
$dates = [];
|
|
|
|
for ( $i = $window_days - 1; $i >= 0; $i-- )
|
|
{
|
|
$dates[] = date( 'Y-m-d', strtotime( '-' . $i . ' days', $end_timestamp ) );
|
|
}
|
|
|
|
return $dates;
|
|
}
|
|
|
|
static private function get_setting_value( $setting_key, $default = null )
|
|
{
|
|
global $mdb;
|
|
|
|
$value = $mdb -> get( 'settings', 'setting_value', [ 'setting_key' => $setting_key ] );
|
|
|
|
if ( $value === null || $value === false )
|
|
{
|
|
return $default;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
static private function set_setting_value( $setting_key, $setting_value )
|
|
{
|
|
global $mdb;
|
|
|
|
if ( $mdb -> count( 'settings', [ 'setting_key' => $setting_key ] ) )
|
|
{
|
|
$mdb -> update( 'settings', [ 'setting_value' => $setting_value ], [ 'setting_key' => $setting_key ] );
|
|
return;
|
|
}
|
|
|
|
$mdb -> insert( 'settings', [
|
|
'setting_key' => $setting_key,
|
|
'setting_value' => $setting_value
|
|
] );
|
|
}
|
|
|
|
static private function touch_cron_invocation( $action_name )
|
|
{
|
|
$now_timestamp = time();
|
|
$now = date( 'Y-m-d H:i:s', $now_timestamp );
|
|
$last_action_invoked_at = self::get_setting_value( 'cron_last_invoked_' . $action_name . '_at', '' );
|
|
$last_action_timestamp = strtotime( (string) $last_action_invoked_at );
|
|
|
|
if ( $last_action_timestamp )
|
|
{
|
|
$interval_seconds = $now_timestamp - $last_action_timestamp;
|
|
|
|
// Pomijamy skrajne wartosci (np. pierwsze uruchomienie po dluzszej przerwie).
|
|
if ( $interval_seconds >= 1 && $interval_seconds <= 21600 )
|
|
{
|
|
$avg_key = 'cron_avg_interval_' . $action_name . '_sec';
|
|
$samples_key = 'cron_avg_interval_' . $action_name . '_samples';
|
|
|
|
$avg_interval = (float) self::get_setting_value( $avg_key, 0 );
|
|
$samples = (int) self::get_setting_value( $samples_key, 0 );
|
|
|
|
$weight = min( 99, max( 0, $samples ) );
|
|
if ( $weight <= 0 || $avg_interval <= 0 )
|
|
{
|
|
$new_avg = (float) $interval_seconds;
|
|
$new_samples = 1;
|
|
}
|
|
else
|
|
{
|
|
$new_avg = ( ( $avg_interval * $weight ) + $interval_seconds ) / ( $weight + 1 );
|
|
$new_samples = min( 100, $weight + 1 );
|
|
}
|
|
|
|
self::set_setting_value( $avg_key, (string) round( $new_avg, 2 ) );
|
|
self::set_setting_value( $samples_key, (string) $new_samples );
|
|
self::set_setting_value( 'cron_last_interval_' . $action_name . '_sec', (string) (int) $interval_seconds );
|
|
}
|
|
}
|
|
|
|
self::set_setting_value( 'cron_last_invoked_at', $now );
|
|
self::set_setting_value( 'cron_last_invoked_' . $action_name . '_at', $now );
|
|
}
|
|
|
|
static private function format_bidding_strategy( $strategy_type, $target_roas = 0 )
|
|
{
|
|
$map = [
|
|
'MAXIMIZE_CONVERSIONS' => 'Maksymalizacja liczby konwersji',
|
|
'MAXIMIZE_CONVERSION_VALUE' => 'Maksymalizacja wartosci konwersji',
|
|
'TARGET_ROAS' => 'Docelowy ROAS',
|
|
'TARGET_CPA' => 'Docelowy CPA',
|
|
'MANUAL_CPC' => 'Reczny CPC',
|
|
'MANUAL_CPM' => 'Reczny CPM',
|
|
'TARGET_IMPRESSION_SHARE' => 'Docelowy udzial w wyswietleniach',
|
|
];
|
|
|
|
$label = $map[ $strategy_type ] ?? $strategy_type ?? 'brak';
|
|
|
|
if ( $target_roas > 0 )
|
|
{
|
|
$label .= ' | docelowy ROAS: ' . round( $target_roas * 100 ) . '%';
|
|
}
|
|
|
|
return $label;
|
|
}
|
|
|
|
// ===========================
|
|
// FRAZY - history 30
|
|
// ===========================
|
|
|
|
static public function cron_phrase_history_30_save( $phrase_id, $date_from, $date_to )
|
|
{
|
|
global $mdb;
|
|
|
|
$data = $mdb -> query( 'SELECT * FROM phrases_history WHERE phrase_id = ' . $phrase_id . ' AND date_add >= \'' . $date_from . '\' AND date_add <= \'' . $date_to . '\' ORDER BY date_add ASC' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
// Inicjalizacja tablic do przechowywania danych
|
|
$phrases_data = [];
|
|
|
|
// Grupowanie danych wedug fraz
|
|
foreach ( $data as $entry )
|
|
{
|
|
$phrase_id = $entry['phrase_id'];
|
|
|
|
if ( !isset( $phrases_data[$phrase_id] ) )
|
|
{
|
|
$phrases_data[$phrase_id] = [
|
|
'impressions' => 0,
|
|
'clicks' => 0,
|
|
'cost' => 0.0,
|
|
'conversions' => 0,
|
|
'conversions_value' => 0.0,
|
|
'roas' => 0,
|
|
'days_counted' => []
|
|
];
|
|
}
|
|
|
|
// Sumowanie danych wedug fraz
|
|
$phrases_data[$phrase_id]['impressions'] += $entry['impressions'];
|
|
$phrases_data[$phrase_id]['clicks'] += $entry['clicks'];
|
|
$phrases_data[$phrase_id]['cost'] += $entry['cost'];
|
|
$phrases_data[$phrase_id]['conversions'] += $entry['conversions'];
|
|
$phrases_data[$phrase_id]['conversions_value'] += $entry['conversions_value'];
|
|
$phrases_data[$phrase_id]['days_counted'][] = $entry['date_add'];
|
|
}
|
|
|
|
foreach ( $phrases_data as $phrase )
|
|
{
|
|
$day_count = count( $phrase['days_counted'] );
|
|
|
|
$impressions = $phrase['impressions'] / $day_count;
|
|
$clicks = $phrase['clicks'] / $day_count;
|
|
$cost = $phrase['cost'] / $day_count;
|
|
$conversions = $phrase['conversions'] / $day_count;
|
|
$conversions_value = $phrase['conversions_value'] / $day_count;
|
|
$roas = ( $conversions_value > 0 and $cost ) ? round( $conversions_value / $cost, 2 ) * 100 : 0;
|
|
|
|
if ( $day_count > 14 )
|
|
{
|
|
if ( $mdb -> count( 'phrases_history_30', [ 'AND' => [ 'phrase_id' => $phrase_id, 'date_add' => $date_to ] ] ) > 0 )
|
|
{
|
|
$mdb -> update( 'phrases_history_30', [
|
|
'impressions' => $impressions,
|
|
'clicks' => $clicks,
|
|
'cost' => $cost,
|
|
'conversions' => $conversions,
|
|
'conversions_value' => $conversions_value,
|
|
'roas' => $roas
|
|
], [ 'AND' => [ 'phrase_id' => $phrase_id, 'date_add' => $date_to ] ] );
|
|
}
|
|
else
|
|
{
|
|
$mdb -> insert( 'phrases_history_30', [
|
|
'phrase_id' => $phrase_id,
|
|
'impressions' => $impressions,
|
|
'clicks' => $clicks,
|
|
'cost' => $cost,
|
|
'conversions' => $conversions,
|
|
'conversions_value' => $conversions_value,
|
|
'roas' => $roas,
|
|
'date_add' => $date_to
|
|
] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|