Files
adsPRO/autoload/controls/class.CampaignTerms.php
Jacek Pyziak efbdcce08a feat: Add XML file management functionality
- Created XmlFiles control class for handling XML file views and regeneration.
- Implemented method to retrieve clients with XML feeds in the factory class.
- Added database migration to include google_merchant_account_id in clients table.
- Created migrations for products_keyword_planner_terms and products_merchant_sync_log tables.
- Added campaign_keywords table migration for managing campaign keyword data.
- Developed main view template for displaying XML files and their statuses.
- Introduced a debug script for analyzing product URLs and their statuses.
2026-02-18 21:23:53 +01:00

673 lines
20 KiB
PHP

<?php
namespace controls;
class CampaignTerms
{
static private function is_google_ads_debug_enabled()
{
return \services\GoogleAdsApi::get_setting( 'google_ads_debug_enabled' ) !== '0';
}
static private function with_optional_debug( $payload, $debug_data )
{
if ( self::is_google_ads_debug_enabled() )
{
$payload['debug'] = $debug_data;
}
return $payload;
}
static private function normalize_ai_recommendations( $raw_json, $rows_by_id )
{
$decoded = json_decode( (string) $raw_json, true );
$items = is_array( $decoded ) && isset( $decoded['items'] ) && is_array( $decoded['items'] ) ? $decoded['items'] : [];
$recommendations = [];
foreach ( $items as $item )
{
$id = (int) ( $item['id'] ?? 0 );
if ( $id <= 0 || !isset( $rows_by_id[$id] ) )
{
continue;
}
$action_raw = strtolower( trim( (string) ( $item['action'] ?? '' ) ) );
$action = $action_raw === 'exclude' ? 'exclude' : 'keep';
$reason = trim( (string) ( $item['reason'] ?? '' ) );
if ( $reason === '' )
{
$reason = $action === 'exclude' ? 'Niska trafnosc lub slabe wyniki.' : 'Fraza zostaje bez zmian.';
}
if ( function_exists( 'mb_substr' ) )
{
$reason = mb_substr( $reason, 0, 120 );
}
else
{
$reason = substr( $reason, 0, 120 );
}
$row = $rows_by_id[$id];
$recommendations[] = [
'id' => $id,
'search_term_id' => $id,
'phrase' => trim( (string) ( $row['search_term'] ?? ( $item['phrase'] ?? '' ) ) ),
'action' => $action,
'reason' => $reason,
'ad_group_name' => (string) ( $row['ad_group_name'] ?? '' ),
'clicks_all_time' => (float) ( $row['clicks_all_time'] ?? 0 ),
'cost_all_time' => (float) ( $row['cost_all_time'] ?? 0 ),
'conversions_all_time' => (float) ( $row['conversions_all_time'] ?? 0 ),
'conversion_value_all_time' => (float) ( $row['conversion_value_all_time'] ?? 0 ),
'roas_all_time' => (float) ( $row['roas_all_time'] ?? 0 )
];
}
return $recommendations;
}
static public function main_view()
{
return \Tpl::view( 'campaign_terms/main_view', [
'clients' => \factory\Campaigns::get_clients(),
] );
}
static public function get_campaigns_list()
{
$client_id = (int) \S::get( 'client_id' );
echo json_encode( [ 'campaigns' => \factory\Campaigns::get_campaigns_list( $client_id, true ) ] );
exit;
}
static public function get_campaign_ad_groups()
{
$campaign_id = (int) \S::get( 'campaign_id' );
if ( $campaign_id <= 0 )
{
echo json_encode( [ 'ad_groups' => [] ] );
exit;
}
echo json_encode( [ 'ad_groups' => \factory\Campaigns::get_campaign_ad_groups( $campaign_id ) ] );
exit;
}
static public function get_campaign_phrase_details()
{
$campaign_id = (int) \S::get( 'campaign_id' );
$ad_group_id = (int) \S::get( 'ad_group_id' );
if ( $campaign_id <= 0 )
{
echo json_encode( [ 'search_terms' => [], 'negative_keywords' => [], 'keywords' => [] ] );
exit;
}
echo json_encode( [
'search_terms' => \factory\Campaigns::get_campaign_search_terms( $campaign_id, $ad_group_id ),
'negative_keywords' => \factory\Campaigns::get_campaign_negative_keywords( $campaign_id, $ad_group_id ),
'keywords' => \factory\Campaigns::get_campaign_keywords( $campaign_id, $ad_group_id )
] );
exit;
}
static public function add_negative_keyword()
{
$search_term_id = (int) \S::get( 'search_term_id' );
$match_type = strtoupper( trim( (string) \S::get( 'match_type' ) ) );
$scope = strtolower( trim( (string) \S::get( 'scope' ) ) );
$manual_keyword_text = trim( (string) \S::get( 'keyword_text' ) );
if ( $search_term_id <= 0 )
{
echo json_encode( self::with_optional_debug( [
'success' => false,
'message' => 'Nie podano frazy do wykluczenia.',
], [
'search_term_id' => $search_term_id,
'match_type_raw' => (string) \S::get( 'match_type' ),
'scope_raw' => (string) \S::get( 'scope' ),
'manual_keyword_text' => $manual_keyword_text
] ) );
exit;
}
if ( !in_array( $match_type, [ 'PHRASE', 'EXACT', 'BROAD' ], true ) )
{
$match_type = 'PHRASE';
}
if ( !in_array( $scope, [ 'campaign', 'ad_group' ], true ) )
{
$scope = 'campaign';
}
$context = \factory\Campaigns::get_search_term_context( $search_term_id );
if ( !$context )
{
echo json_encode( self::with_optional_debug( [
'success' => false,
'message' => 'Nie znaleziono danych frazy.',
], [
'search_term_id' => $search_term_id,
'manual_keyword_text' => $manual_keyword_text,
'scope' => $scope,
'match_type' => $match_type
] ) );
exit;
}
$customer_id = trim( (string) ( $context['google_ads_customer_id'] ?? '' ) );
$campaign_external_id = trim( (string) ( $context['external_campaign_id'] ?? '' ) );
$ad_group_external_id = trim( (string) ( $context['external_ad_group_id'] ?? '' ) );
$context_keyword_text = trim( (string) ( $context['search_term'] ?? '' ) );
$keyword_text = $manual_keyword_text !== '' ? $manual_keyword_text : $context_keyword_text;
$keyword_source = $manual_keyword_text !== '' ? 'manual' : 'search_term';
$missing_data = ( $customer_id === '' || $keyword_text === '' );
if ( $scope === 'campaign' && $campaign_external_id === '' )
{
$missing_data = true;
}
if ( $scope === 'ad_group' && $ad_group_external_id === '' )
{
$missing_data = true;
}
if ( $missing_data )
{
echo json_encode( self::with_optional_debug( [
'success' => false,
'message' => 'Brak wymaganych danych Google Ads dla tej frazy.',
], [
'customer_id' => $customer_id,
'campaign_external_id' => $campaign_external_id,
'ad_group_external_id' => $ad_group_external_id,
'keyword_text' => $keyword_text,
'keyword_source' => $keyword_source,
'scope' => $scope,
'context' => $context
] ) );
exit;
}
$api = new \services\GoogleAdsApi();
if ( !$api -> is_configured() )
{
echo json_encode( self::with_optional_debug( [
'success' => false,
'message' => 'Google Ads API nie jest skonfigurowane.',
], [
'search_term_id' => $search_term_id,
'customer_id' => $customer_id,
'campaign_external_id' => $campaign_external_id,
'ad_group_external_id' => $ad_group_external_id,
'keyword_text' => $keyword_text,
'match_type' => $match_type,
'scope' => $scope
] ) );
exit;
}
if ( $scope === 'campaign' )
{
$api_result = $api -> add_negative_keyword_to_campaign( $customer_id, $campaign_external_id, $keyword_text, $match_type );
}
else
{
$api_result = $api -> add_negative_keyword_to_ad_group( $customer_id, $ad_group_external_id, $keyword_text, $match_type );
}
if ( !( $api_result['success'] ?? false ) )
{
$last_error = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
echo json_encode( self::with_optional_debug( [
'success' => false,
'message' => 'Nie udalo sie zapisac frazy wykluczajacej w Google Ads.',
'error' => $last_error
], [
'customer_id' => $customer_id,
'campaign_external_id' => $campaign_external_id,
'ad_group_external_id' => $ad_group_external_id,
'keyword_text' => $keyword_text,
'keyword_source' => $keyword_source,
'match_type' => $match_type,
'scope' => $scope,
'api_result' => $api_result
] ) );
exit;
}
$verification = $api_result['verification'] ?? null;
$verification_found = true;
if ( is_array( $verification ) && array_key_exists( 'found', $verification ) )
{
$verification_found = (bool) $verification['found'];
}
if ( !$verification_found && !( $api_result['duplicate'] ?? false ) )
{
echo json_encode( self::with_optional_debug( [
'success' => false,
'message' => 'Google Ads API nie potwierdzilo dodania frazy po operacji create.',
], [
'customer_id' => $customer_id,
'campaign_external_id' => $campaign_external_id,
'ad_group_external_id' => $ad_group_external_id,
'keyword_text' => $keyword_text,
'keyword_source' => $keyword_source,
'match_type' => $match_type,
'scope' => $scope,
'api_result' => $api_result,
'verification' => $verification
] ) );
exit;
}
\factory\Campaigns::upsert_campaign_negative_keyword(
(int) $context['db_campaign_id'],
$scope === 'campaign' ? null : (int) $context['db_ad_group_id'],
$scope,
$keyword_text,
$match_type
);
$scope_label = $scope === 'campaign' ? 'kampanii' : 'grupy reklam';
echo json_encode( self::with_optional_debug( [
'success' => true,
'message' => ( $api_result['duplicate'] ?? false ) ? 'Fraza byla juz wykluczona na poziomie ' . $scope_label . '.' : 'Fraza zostala dodana do wykluczajacych na poziomie ' . $scope_label . '.',
'duplicate' => (bool) ( $api_result['duplicate'] ?? false ),
'match_type' => $match_type,
'scope' => $scope
], [
'customer_id' => $customer_id,
'campaign_external_id' => $campaign_external_id,
'ad_group_external_id' => $ad_group_external_id,
'keyword_text' => $keyword_text,
'keyword_source' => $keyword_source,
'scope' => $scope,
'api_response' => $api_result['response'] ?? null,
'sent_operation' => $api_result['sent_operation'] ?? null,
'verification' => $api_result['verification'] ?? null
] ) );
exit;
}
static public function analyze_search_terms_with_ai()
{
$campaign_id = (int) \S::get( 'campaign_id' );
$ad_group_id = (int) \S::get( 'ad_group_id' );
$search_term_ids_raw = \S::get( 'search_term_ids' );
if ( $campaign_id <= 0 )
{
echo json_encode( [ 'success' => false, 'message' => 'Wybierz kampanie.' ] );
exit;
}
if ( \services\GoogleAdsApi::get_setting( 'openai_enabled' ) === '0' )
{
echo json_encode( [ 'success' => false, 'message' => 'OpenAI jest wylaczone. Wlacz je w Ustawieniach.' ] );
exit;
}
if ( !\services\OpenAiApi::is_configured() )
{
echo json_encode( [ 'success' => false, 'message' => 'Klucz API OpenAI nie jest skonfigurowany. Przejdz do Ustawien.' ] );
exit;
}
$rows = \factory\Campaigns::get_campaign_search_terms( $campaign_id, $ad_group_id );
$ids_filter = [];
if ( is_array( $search_term_ids_raw ) )
{
foreach ( $search_term_ids_raw as $id_raw )
{
$id = (int) $id_raw;
if ( $id > 0 )
{
$ids_filter[$id] = true;
}
}
}
elseif ( $search_term_ids_raw !== null && $search_term_ids_raw !== '' )
{
$id = (int) $search_term_ids_raw;
if ( $id > 0 )
{
$ids_filter[$id] = true;
}
}
if ( !empty( $ids_filter ) )
{
$rows = array_values( array_filter( $rows, function( $row ) use ( $ids_filter )
{
$id = (int) ( $row['id'] ?? 0 );
return $id > 0 && isset( $ids_filter[$id] );
} ) );
}
if ( empty( $rows ) )
{
echo json_encode( [ 'success' => false, 'message' => 'Brak fraz do analizy.' ] );
exit;
}
$rows = array_slice( $rows, 0, 150 );
$rows_by_id = [];
foreach ( $rows as $row )
{
$id = (int) ( $row['id'] ?? 0 );
if ( $id > 0 )
{
$rows_by_id[$id] = $row;
}
}
$campaign_name = '';
$campaign_type = '';
if ( !empty( $rows ) )
{
$campaign_name = trim( (string) ( $rows[0]['campaign_name'] ?? '' ) );
$campaign_type = trim( (string) ( $rows[0]['advertising_channel_type'] ?? '' ) );
}
$ad_group_name = '';
if ( $ad_group_id > 0 && !empty( $rows ) )
{
$ad_group_name = trim( (string) ( $rows[0]['ad_group_name'] ?? '' ) );
}
$result = \services\OpenAiApi::suggest_negative_keywords_to_exclude( $rows, [
'campaign_name' => $campaign_name,
'campaign_type' => $campaign_type,
'ad_group_name' => $ad_group_name,
'ad_group_id' => $ad_group_id
] );
if ( ( $result['status'] ?? 'error' ) !== 'ok' )
{
echo json_encode( [
'success' => false,
'message' => (string) ( $result['message'] ?? 'Blad analizy OpenAI.' )
] );
exit;
}
$raw_json = (string) ( $result['suggestion'] ?? '' );
$recommendations = self::normalize_ai_recommendations( $raw_json, $rows_by_id );
$exclude_count = count( array_filter( $recommendations, function( $item )
{
return ( $item['action'] ?? '' ) === 'exclude';
} ) );
if ( empty( $recommendations ) )
{
echo json_encode( [
'success' => false,
'message' => 'Nie udalo sie sparsowac odpowiedzi AI.',
'raw' => $raw_json
] );
exit;
}
echo json_encode( [
'success' => true,
'message' => 'Analiza zakonczona. Proponowane wykluczenia: ' . $exclude_count . '.',
'analyzed_count' => count( $rows ),
'exclude_count' => $exclude_count,
'recommendations' => $recommendations
] );
exit;
}
static private function delete_negative_keyword_row( $negative_keyword_id )
{
$negative_keyword_id = (int) $negative_keyword_id;
if ( $negative_keyword_id <= 0 )
{
return [ 'success' => false, 'message' => 'Nie podano frazy do usuniecia.' ];
}
$context = \factory\Campaigns::get_negative_keyword_context( $negative_keyword_id );
if ( !$context )
{
return [ 'success' => false, 'message' => 'Nie znaleziono danych frazy wykluczajacej.' ];
}
$customer_id = trim( (string) ( $context['google_ads_customer_id'] ?? '' ) );
$scope = strtolower( trim( (string) ( $context['scope'] ?? 'campaign' ) ) );
$match_type = strtoupper( trim( (string) ( $context['match_type'] ?? 'PHRASE' ) ) );
$keyword_text = trim( (string) ( $context['keyword_text'] ?? '' ) );
$campaign_external_id = trim( (string) ( $context['external_campaign_id'] ?? '' ) );
$ad_group_external_id = trim( (string) ( $context['external_ad_group_id'] ?? '' ) );
if ( !in_array( $scope, [ 'campaign', 'ad_group' ], true ) )
{
$scope = 'campaign';
}
$missing_data = ( $customer_id === '' || $keyword_text === '' );
if ( $scope === 'campaign' && $campaign_external_id === '' )
{
$missing_data = true;
}
if ( $scope === 'ad_group' && $ad_group_external_id === '' )
{
$missing_data = true;
}
if ( $missing_data )
{
return self::with_optional_debug( [
'success' => false,
'message' => 'Brak wymaganych danych Google Ads dla tej frazy.'
], [
'context' => $context
] );
}
$api = new \services\GoogleAdsApi();
if ( !$api -> is_configured() )
{
return self::with_optional_debug( [
'success' => false,
'message' => 'Google Ads API nie jest skonfigurowane.'
], [
'customer_id' => $customer_id,
'scope' => $scope,
'campaign_external_id' => $campaign_external_id,
'ad_group_external_id' => $ad_group_external_id,
'keyword_text' => $keyword_text,
'match_type' => $match_type
] );
}
if ( $scope === 'campaign' )
{
$api_result = $api -> remove_negative_keyword_from_campaign( $customer_id, $campaign_external_id, $keyword_text, $match_type );
}
else
{
$api_result = $api -> remove_negative_keyword_from_ad_group( $customer_id, $ad_group_external_id, $keyword_text, $match_type );
}
if ( !( $api_result['success'] ?? false ) )
{
$last_error = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
return self::with_optional_debug( [
'success' => false,
'message' => 'Nie udalo sie usunac frazy wykluczajacej w Google Ads.',
'error' => $last_error
], [
'customer_id' => $customer_id,
'scope' => $scope,
'campaign_external_id' => $campaign_external_id,
'ad_group_external_id' => $ad_group_external_id,
'keyword_text' => $keyword_text,
'match_type' => $match_type,
'api_result' => $api_result
] );
}
\factory\Campaigns::delete_campaign_negative_keyword( $negative_keyword_id );
$removed = (int) ( $api_result['removed'] ?? 0 );
$scope_label = $scope === 'campaign' ? 'kampanii' : 'grupy reklam';
$message = $removed > 0
? 'Fraza zostala usunieta z wykluczajacych na poziomie ' . $scope_label . '.'
: 'Fraza nie byla juz obecna w Google Ads. Usunieto lokalny wpis.';
return self::with_optional_debug( [
'success' => true,
'message' => $message,
'removed' => $removed,
'negative_keyword_id' => $negative_keyword_id
], [
'customer_id' => $customer_id,
'scope' => $scope,
'campaign_external_id' => $campaign_external_id,
'ad_group_external_id' => $ad_group_external_id,
'keyword_text' => $keyword_text,
'match_type' => $match_type,
'api_result' => $api_result
] );
}
static public function delete_negative_keyword()
{
$negative_keyword_id = (int) \S::get( 'negative_keyword_id' );
$result = self::delete_negative_keyword_row( $negative_keyword_id );
echo json_encode( $result );
exit;
}
static public function delete_negative_keywords()
{
$negative_keyword_ids_raw = \S::get( 'negative_keyword_ids' );
if ( !is_array( $negative_keyword_ids_raw ) )
{
$negative_keyword_ids_raw = [ $negative_keyword_ids_raw ];
}
$negative_keyword_ids = [];
foreach ( $negative_keyword_ids_raw as $id_raw )
{
$id = (int) $id_raw;
if ( $id > 0 )
{
$negative_keyword_ids[] = $id;
}
}
$negative_keyword_ids = array_values( array_unique( $negative_keyword_ids ) );
if ( empty( $negative_keyword_ids ) )
{
echo json_encode( [ 'success' => false, 'message' => 'Nie podano fraz do usuniecia.' ] );
exit;
}
$deleted_count = 0;
$failed = [];
$debug = [];
$total_count = count( $negative_keyword_ids );
foreach ( $negative_keyword_ids as $negative_keyword_id )
{
$result = self::delete_negative_keyword_row( $negative_keyword_id );
if ( $result['success'] ?? false )
{
$deleted_count++;
$debug[] = [
'id' => $negative_keyword_id,
'success' => true,
'message' => (string) ( $result['message'] ?? '' ),
'debug' => $result['debug'] ?? null
];
continue;
}
$failed[] = [
'id' => $negative_keyword_id,
'message' => (string) ( $result['message'] ?? 'Nieznany blad' ),
'error' => (string) ( $result['error'] ?? '' ),
'debug' => $result['debug'] ?? null
];
$debug[] = [
'id' => $negative_keyword_id,
'success' => false,
'message' => (string) ( $result['message'] ?? 'Nieznany blad' ),
'error' => (string) ( $result['error'] ?? '' ),
'debug' => $result['debug'] ?? null
];
}
$failed_count = count( $failed );
if ( $deleted_count === $total_count )
{
$response = [
'success' => true,
'message' => 'Usunieto zaznaczone frazy wykluczajace (' . $deleted_count . ').',
'deleted_count' => $deleted_count,
'failed_count' => 0
];
if ( self::is_google_ads_debug_enabled() )
{
$response['debug'] = $debug;
}
echo json_encode( $response );
exit;
}
if ( $deleted_count > 0 )
{
$response = [
'success' => true,
'partial' => true,
'message' => 'Usunieto ' . $deleted_count . ' z ' . $total_count . ' zaznaczonych fraz wykluczajacych.',
'deleted_count' => $deleted_count,
'failed_count' => $failed_count,
'failed' => $failed
];
if ( self::is_google_ads_debug_enabled() )
{
$response['debug'] = $debug;
}
echo json_encode( $response );
exit;
}
$response = [
'success' => false,
'message' => 'Nie udalo sie usunac zaznaczonych fraz wykluczajacych.',
'deleted_count' => 0,
'failed_count' => $failed_count,
'failed' => $failed
];
if ( self::is_google_ads_debug_enabled() )
{
$response['debug'] = $debug;
}
echo json_encode( $response );
exit;
}
}