get( 'settings', 'setting_value', [ 'setting_key' => 'api_ingest_token' ] ) ); if ( $token !== '' ) { return $token; } return trim( (string) ( $settings['api_ingest_token'] ?? '' ) ); } static private function json_error( $http_code, $message, $context = [], $log_source = 'api_ingest', $client_id = null ) { http_response_code( (int) $http_code ); echo json_encode( [ 'status' => 'error', 'message' => (string) $message ] ); \factory\Logs::add( 'warning', $log_source, (string) $message, $context, $client_id !== null ? (int) $client_id : null ); exit; } static private function require_ingest_auth( $log_source, $client_id = null ) { $configured_token = self::get_configured_ingest_token(); if ( $configured_token === '' ) { self::json_error( 503, 'API ingest token nie jest skonfigurowany.', [ 'ip' => (string) ( $_SERVER['REMOTE_ADDR'] ?? '' ) ], $log_source, $client_id ); } $request_token = self::get_request_api_token(); if ( $request_token === '' || !hash_equals( $configured_token, $request_token ) ) { self::json_error( 401, 'Brak autoryzacji API.', [ 'ip' => (string) ( $_SERVER['REMOTE_ADDR'] ?? '' ) ], $log_source, $client_id ); } } static private function normalize_customer_id( $value ) { return preg_replace( '/\D+/', '', (string) $value ); } static private function extract_payload_customer_id( $data ) { return self::normalize_customer_id( (string) ( $data['googleAdsCustomerId'] ?? $data['google_ads_customer_id'] ?? $data['customerId'] ?? $data['customer_id'] ?? '' ) ); } static public function campaigns_data_save() { global $mdb; self::require_ingest_auth( 'api_campaigns_data_save' ); $json = file_get_contents( 'php://input' ); $data = json_decode( $json, true ); $client_id = (int) ( $data['clientId'] ?? $data['client_id'] ?? 0 ); $raw_date = trim( (string) ( $data['date'] ?? '' ) ); $date = $raw_date !== '' ? date( 'Y-m-d', strtotime( $raw_date ) ) : ''; if ( $client_id <= 0 || !$date || !isset( $data['data'] ) || !is_array( $data['data'] ) ) { self::json_error( 422, 'Nieprawidlowe dane wejsciowe. Oczekiwano: clientId/client_id, date, data[].', [ 'raw_payload' => is_array( $data ) ? array_keys( $data ) : 'invalid_json' ], 'api_campaigns_data_save' ); } $client_row = $mdb -> get( 'clients', [ 'id', 'google_ads_customer_id' ], [ 'id' => $client_id ] ); if ( !$client_row ) { self::json_error( 404, 'Nie znaleziono klienta.', [], 'api_campaigns_data_save', $client_id ); } $payload_customer_id = self::extract_payload_customer_id( $data ); $client_customer_id = self::normalize_customer_id( (string) ( $client_row['google_ads_customer_id'] ?? '' ) ); if ( $payload_customer_id === '' || $client_customer_id === '' || $payload_customer_id !== $client_customer_id ) { self::json_error( 422, 'Niezgodny customer_id dla klienta.', [ 'payload_customer_id' => $payload_customer_id, 'client_customer_id' => $client_customer_id ], 'api_campaigns_data_save', $client_id ); } $inserted_campaigns = 0; $updated_campaigns = 0; $inserted_history = 0; $updated_history = 0; foreach ( $data['data'] as $campaign ) { $campaign_id = 0; $external_campaign_id = trim( (string) ( $campaign['campaignId'] ?? '' ) ); if ( $external_campaign_id === '' ) { continue; } $incoming_campaign_name = trim( (string) ( $campaign['camapignName'] ?? ( $campaign['campaignName'] ?? '' ) ) ); if ( $incoming_campaign_name === '' ) { $incoming_campaign_name = 'Campaign #' . $external_campaign_id; } 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' => $incoming_campaign_name ] ); $campaign_id = $mdb -> id(); $inserted_campaigns++; } else { $campaign_id = $mdb -> get( 'campaigns', 'id', [ 'AND' => [ 'client_id' => $client_id, 'campaign_id' => $external_campaign_id ] ] ); if ( $campaign_id ) { $existing_campaign_name = trim( (string) $mdb -> get( 'campaigns', 'campaign_name', [ 'id' => $campaign_id ] ) ); if ( $incoming_campaign_name !== '' and $existing_campaign_name !== $incoming_campaign_name ) { $mdb -> update( 'campaigns', [ 'campaign_name' => $incoming_campaign_name ], [ 'id' => $campaign_id ] ); $updated_campaigns++; } } } if ( !$campaign_id ) { continue; } $campaign_history_data = []; $campaign_history_data['roas_30_days'] = $campaign['roas30Days']; $campaign_history_data['roas_all_time'] = $campaign['roasAllTime']; $campaign_history_data['budget'] = self::normalize_number( $campaign['budget'] ?? 0 ); $campaign_history_data['money_spent'] = floatval( preg_replace( [ '/[^0-9,]/', '/,/' ], [ '', '.' ], (string) ( $campaign['spend30Days'] ?? '' ) ) ); $campaign_history_data['conversion_value'] = preg_replace( '/[^\d,.-]/', '', (string) ( $campaign['conversionValue30Days'] ?? '' ) ); if ( isset( $campaign['biddingStrategy'] ) and $campaign['biddingStrategy'] == 'MAXIMIZE_CONVERSIONS' ) { $campaign_history_data['bidding_strategy'] = 'Maksymalizacja liczby konwersji'; } else if ( isset( $campaign['biddingStrategy'] ) and $campaign['biddingStrategy'] == 'MAXIMIZE_CONVERSION_VALUE' ) { $campaign_history_data['bidding_strategy'] = 'Maksymalizacja wartosci konwersji'; } else if ( isset( $campaign['biddingStrategy'] ) and $campaign['biddingStrategy'] == 'TARGET_ROAS' ) { $campaign_history_data['bidding_strategy'] = 'Docelowy ROAS'; } else if ( isset( $campaign['biddingStrategy'] ) ) { $campaign_history_data['bidding_strategy'] = $campaign['biddingStrategy']; } else { $campaign_history_data['bidding_strategy'] = 'brak'; } if ( isset( $campaign['targetRoas'] ) and (float) $campaign['targetRoas'] > 0 ) { $campaign_history_data['bidding_strategy'] .= ' | docelowy ROAS: ' . ( (float) $campaign['targetRoas'] * 100 ) . '%'; } if ( $mdb -> count( 'campaigns_history', [ 'AND' => [ 'campaign_id' => $campaign_id, 'date_add' => $date ] ] ) ) { $mdb -> update( 'campaigns_history', $campaign_history_data, [ 'AND' => [ 'campaign_id' => $campaign_id, 'date_add' => $date ] ] ); $updated_history++; } else { $campaign_history_data['campaign_id'] = $campaign_id; $campaign_history_data['date_add'] = $date; $mdb -> insert( 'campaigns_history', $campaign_history_data ); $inserted_history++; } } \factory\Logs::add( 'info', 'api_campaigns_data_save', 'Zapisano import kampanii z API.', [ 'client_id' => $client_id, 'date' => $date, 'source' => 'api_ingest', 'ip' => (string) ( $_SERVER['REMOTE_ADDR'] ?? '' ), 'campaigns_received' => count( (array) $data['data'] ), 'campaigns_inserted' => $inserted_campaigns, 'campaigns_updated' => $updated_campaigns, 'history_inserted' => $inserted_history, 'history_updated' => $updated_history ], $client_id ); echo json_encode( [ 'status' => 'ok', 'client_id' => $client_id, 'date' => $date, 'campaigns_received' => count( (array) $data['data'] ), 'campaigns_inserted' => $inserted_campaigns, 'campaigns_updated' => $updated_campaigns, 'history_inserted' => $inserted_history, 'history_updated' => $updated_history ] ); exit; } static public function phrases_data_save() { global $mdb; self::require_ingest_auth( 'api_phrases_data_save' ); $json = file_get_contents( 'php://input' ); $data = json_decode( $json, true ); if ( $data['client_id'] and $data['date'] ) { foreach ( $data['data'] as $phrase ) { $phrase_data = []; if ( !$mdb -> count( 'phrases', [ 'AND' => [ 'client_id' => $data['client_id'], 'phrase' => $phrase['Query'] ] ] ) ) { $phrase_data['phrase'] = $phrase['Query']; $mdb -> insert( 'phrases', [ 'client_id' => $data['client_id'], 'phrase' => $phrase['Query'] ] ); $phrase_id = $mdb -> id(); } else { $phrase_id = $mdb -> get( 'phrases', 'id', [ 'AND' => [ 'client_id' => $data['client_id'], 'phrase' => $phrase['Query'] ] ] ); } if ( $phrase_id ) { $phrase_data['impressions'] = $phrase['Impressions']; $phrase_data['clicks'] = $phrase['Clicks']; $phrase_data['cost'] = $phrase['Cost']; $phrase_data['conversions'] = $phrase['Conversions']; $phrase_data['conversions_value'] = preg_replace( '/[^\d,.-]/', '', $phrase['ConversionValue'] ); $phrase_data['updated'] = 1; if ( $mdb -> count( 'phrases_history', [ 'AND' => [ 'phrase_id' => $phrase_id, 'date_add' => $data['date'] ] ] ) ) { $phrase_data_old = []; $phrase_data_old = $mdb -> get( 'phrases_history', '*', [ 'AND' => [ 'phrase_id' => $phrase_id, 'date_add' => $data['date'] ] ] ); if ( $phrase_data_old['impressions'] == $phrase_data['impressions'] and $phrase_data_old['clicks'] == $phrase_data['clicks'] and number_format((float)str_replace(',', '.', $phrase_data_old['cost']), 5) == number_format((float)$phrase_data['cost'], 5) and $phrase_data_old['conversions'] == $phrase_data['conversions'] and number_format((float)str_replace(',', '.', $phrase_data_old['conversions_value']), 5) == number_format((float)$phrase_data['conversions_value'], 5) ) { continue; } $mdb -> update( 'phrases_history', [ 'impressions' => $phrase_data['impressions'], 'clicks' => $phrase_data['clicks'], 'cost' => $phrase_data['cost'], 'conversions' => $phrase_data['conversions'], 'conversions_value' => $phrase_data['conversions_value'], 'updated' => 1 ], [ 'AND' => [ 'phrase_id' => $phrase_id, 'date_add' => $data['date'] ] ] ); } else { $mdb -> insert( 'phrases_history', [ 'phrase_id' => $phrase_id, 'date_add' => $data['date'], 'impressions' => $phrase_data['impressions'], 'clicks' => $phrase_data['clicks'], 'cost' => $phrase_data['cost'], 'conversions' => $phrase_data['conversions'], 'conversions_value' => $phrase_data['conversions_value'], 'updated' => 1 ] ); } } } } echo json_encode( [ 'status' => 'ok' ] ); exit; } static public function products_data_save() { global $mdb; self::require_ingest_auth( 'api_products_data_save' ); $json = file_get_contents( 'php://input' ); $data = json_decode( $json, true ); // file_put_contents( 'tmp/products_data_save.txt', print_r( $data, true ) ); if ( $data['client_id'] and $data['date'] ) { foreach ( $data['data'] as $offer ) { $offer_data = []; if ( !$mdb -> count( 'products', [ 'AND' => [ 'client_id' => $data['client_id'], 'offer_id' => $offer['OfferId'] ] ] ) ) { $offer_data['client_id'] = $data['client_id']; $offer_data['offer_id'] = $offer['OfferId']; $offer_data['offer_name'] = $offer['ProductTitle']; $mdb -> insert( 'products', [ 'client_id' => $data['client_id'], 'offer_id' => $offer['OfferId'], 'name' => $offer['ProductTitle'] ] ); $offer_id = $mdb -> id(); } else { $offer_id = $mdb -> get( 'products', 'id', [ 'AND' => [ 'client_id' => $data['client_id'], 'offer_id' => $offer['OfferId'] ] ] ); $offer_current_name = $mdb -> get( 'products', 'name', [ 'AND' => [ 'client_id' => $data['client_id'], 'offer_id' => $offer['OfferId'] ] ] ); if ( $offer_current_name != $offer['ProductTitle'] and $data['date'] == date( 'Y-m-d', strtotime( '-1 days', time() ) ) ) { $mdb -> update( 'products', [ 'name' => $offer['ProductTitle'] ], [ 'AND' => [ 'client_id' => $data['client_id'], 'offer_id' => $offer['OfferId'] ] ] ); } } if ( $offer_id ) { $offer_data['impressions'] = $offer['Impressions']; $offer_data['clicks'] = $offer['Clicks']; $offer_data['ctr'] = round( $offer['Clicks'] / $offer['Impressions'], 4 ) * 100; $offer_data['cost'] = preg_replace( '/[^\d,.-]/', '', $offer['Cost'] ); $offer_data['conversions'] = $offer['Conversions']; $offer_data['conversions_value'] = preg_replace( '/[^\d,.-]/', '', $offer['ConversionValue'] ); $offer_data['updated'] = 1; if ( $mdb -> count( 'products_history', [ 'AND' => [ 'product_id' => $offer_id, 'date_add' => $data['date'] ] ] ) ) { $offer_data_old = []; $offer_data_old = $mdb -> get( 'products_history', '*', [ 'AND' => [ 'product_id' => $offer_id, 'date_add' => $data['date'] ] ] ); 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 $offer_data_old['conversions'] == $offer_data['conversions'] and number_format((float)str_replace(',', '.', $offer_data_old['conversions_value']), 5) == number_format((float)$offer_data['conversions_value'], 5) ) { continue; } $mdb -> update( 'products_history', [ 'impressions' => $offer_data['impressions'], 'clicks' => $offer_data['clicks'], 'ctr' => $offer_data['ctr'], 'cost' => $offer_data['cost'], 'conversions' => $offer_data['conversions'], 'conversions_value' => $offer_data['conversions_value'], 'updated' => 1 ], [ 'AND' => [ 'product_id' => $offer_id, 'date_add' => $data['date'] ] ] ); } else { $mdb -> insert( 'products_history', [ 'product_id' => $offer_id, 'date_add' => $data['date'], 'impressions' => $offer_data['impressions'], 'clicks' => $offer_data['clicks'], 'ctr' => $offer_data['ctr'], 'cost' => $offer_data['cost'], 'conversions' => $offer_data['conversions'], 'conversions_value' => $offer_data['conversions_value'], 'updated' => 1 ] ); } } } } echo json_encode( [ 'status' => 'ok' ] ); exit; } static public function products_data_import() { global $mdb; self::require_ingest_auth( 'api_products_data_import' ); $json = file_get_contents( 'php://input' ); $data = json_decode( $json, true ); if ( !is_array( $data ) || empty( $data['client_id'] ) || empty( $data['date'] ) || !isset( $data['data'] ) || !is_array( $data['data'] ) ) { echo json_encode( [ 'status' => 'error', 'message' => 'Nieprawidlowe dane wejsciowe. Oczekiwano: client_id, date, data[].' ] ); exit; } $client_id = (int) $data['client_id']; $date = date( 'Y-m-d', strtotime( $data['date'] ) ); if ( !$mdb -> count( 'clients', [ 'id' => $client_id ] ) ) { echo json_encode( [ 'status' => 'error', 'message' => 'Nie znaleziono klienta o podanym ID.' ] ); exit; } $processed = 0; $skipped = 0; $touched_scopes = []; foreach ( $data['data'] 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; } if ( !$mdb -> count( 'products', [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] ) ) { $mdb -> insert( 'products', [ 'client_id' => $client_id, 'offer_id' => $offer_external_id, 'name' => $product_title ] ); $product_id = $mdb -> id(); } else { $product_id = $mdb -> get( 'products', 'id', [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] ); $offer_current_name = $mdb -> get( 'products', 'name', [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] ); if ( $offer_current_name != $product_title and $date == date( 'Y-m-d', strtotime( '-1 days', time() ) ) ) { $mdb -> update( 'products', [ 'name' => $product_title ], [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] ); } } if ( !$product_id ) { $skipped++; continue; } $campaign_external_id = (int) self::normalize_number( $offer['CampaignId'] ?? ( $offer['campaign_id'] ?? 0 ) ); $campaign_name = trim( (string) ( $offer['CampaignName'] ?? ( $offer['campaign_name'] ?? '' ) ) ); $ad_group_external_id = (int) self::normalize_number( $offer['AdGroupId'] ?? ( $offer['ad_group_id'] ?? 0 ) ); $ad_group_name = trim( (string) ( $offer['AdGroupName'] ?? ( $offer['ad_group_name'] ?? '' ) ) ); $scope = \controls\Cron::resolve_products_scope_ids( $client_id, $campaign_external_id, $campaign_name, $ad_group_external_id, $ad_group_name, $date ); $db_campaign_id = (int) ( $scope['campaign_id'] ?? 0 ); $db_ad_group_id = (int) ( $scope['ad_group_id'] ?? 0 ); $impressions = (int) round( self::normalize_number( $offer['Impressions'] ?? 0 ) ); $clicks = (int) round( self::normalize_number( $offer['Clicks'] ?? 0 ) ); $cost = self::normalize_number( $offer['Cost'] ?? 0 ); $conversions = self::normalize_number( $offer['Conversions'] ?? 0 ); $conversion_value = self::normalize_number( $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 ]; if ( $mdb -> count( 'products_history', [ 'AND' => [ 'product_id' => $product_id, 'campaign_id' => $db_campaign_id, 'ad_group_id' => $db_ad_group_id, 'date_add' => $date ] ] ) ) { $offer_data_old = $mdb -> get( 'products_history', '*', [ 'AND' => [ 'product_id' => $product_id, 'campaign_id' => $db_campaign_id, 'ad_group_id' => $db_ad_group_id, 'date_add' => $date ] ] ); 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 ) ) { $scope_key = (int) $product_id . '|' . $db_campaign_id . '|' . $db_ad_group_id; $touched_scopes[ $scope_key ] = [ 'product_id' => (int) $product_id, 'campaign_id' => $db_campaign_id, 'ad_group_id' => $db_ad_group_id ]; $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 ] ] ); } else { $offer_data['product_id'] = $product_id; $offer_data['date_add'] = $date; $mdb -> insert( 'products_history', $offer_data ); } $scope_key = (int) $product_id . '|' . $db_campaign_id . '|' . $db_ad_group_id; $touched_scopes[ $scope_key ] = [ 'product_id' => (int) $product_id, 'campaign_id' => $db_campaign_id, 'ad_group_id' => $db_ad_group_id ]; $processed++; } $history_30_rows = 0; foreach ( $touched_scopes as $scope ) { $product_id = (int) ( $scope['product_id'] ?? 0 ); $campaign_id = (int) ( $scope['campaign_id'] ?? 0 ); $ad_group_id = (int) ( $scope['ad_group_id'] ?? 0 ); \controls\Cron::cron_product_history_30_save( $product_id, $date, $campaign_id, $ad_group_id ); $mdb -> update( 'products_history', [ 'updated' => 0 ], [ 'AND' => [ 'product_id' => $product_id, 'campaign_id' => $campaign_id, 'ad_group_id' => $ad_group_id, 'date_add' => $date ] ] ); $history_30_rows++; } $aggregate_rows = \controls\Cron::rebuild_products_temp_for_client( $client_id ); echo json_encode( [ 'status' => 'ok', 'client_id' => $client_id, 'date' => $date, 'processed' => $processed, 'skipped' => $skipped, 'history_30_products' => $history_30_rows, 'products_aggregate_rows' => $aggregate_rows ] ); exit; } static private function rebuild_products_temp_for_client( $client_id ) { return \controls\Cron::rebuild_products_temp_for_client( $client_id ); } static private function normalize_number( $value ) { if ( $value === null || $value === '' ) { return 0.0; } if ( is_int( $value ) || is_float( $value ) ) { return (float) $value; } $value = trim( (string) $value ); if ( $value === '' ) { return 0.0; } $value = preg_replace( '/[^\d,.\-]/', '', $value ); if ( $value === '' || $value === '-' || $value === ',' || $value === '.' ) { return 0.0; } $has_comma = strpos( $value, ',' ) !== false; $has_dot = strpos( $value, '.' ) !== false; if ( $has_comma && $has_dot ) { $last_comma = strrpos( $value, ',' ); $last_dot = strrpos( $value, '.' ); if ( $last_comma > $last_dot ) { $value = str_replace( '.', '', $value ); $value = str_replace( ',', '.', $value ); } else { $value = str_replace( ',', '', $value ); } } else { $value = str_replace( ',', '.', $value ); } return (float) $value; } }