- Introduced a new button to toggle the status of keywords between PAUSED and ENABLED in the keywords table. - Added corresponding styles for the toggle button to enhance user experience. - Updated the keywords table rendering logic to display the status and toggle button correctly. - Created a new migration to add a 'status' column to the 'campaign_keywords' table, defaulting to 'ENABLED'.
1028 lines
32 KiB
PHP
1028 lines
32 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;
|
|
}
|
|
|
|
static public function update_keyword_match_type()
|
|
{
|
|
$keyword_id = (int) \S::get( 'keyword_id' );
|
|
$new_match_type = strtoupper( trim( (string) \S::get( 'new_match_type' ) ) );
|
|
|
|
if ( $keyword_id <= 0 )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie podano frazy.' ] );
|
|
exit;
|
|
}
|
|
|
|
if ( !in_array( $new_match_type, [ 'PHRASE', 'EXACT', 'BROAD' ], true ) )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nieprawidlowy typ dopasowania.' ] );
|
|
exit;
|
|
}
|
|
|
|
$context = \factory\Campaigns::get_keyword_context( $keyword_id );
|
|
if ( !$context )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie znaleziono danych frazy.' ] );
|
|
exit;
|
|
}
|
|
|
|
$old_match_type = strtoupper( trim( (string) ( $context['match_type'] ?? '' ) ) );
|
|
if ( $old_match_type === $new_match_type )
|
|
{
|
|
echo json_encode( [ 'success' => true, 'message' => 'Dopasowanie jest juz ustawione na ' . $new_match_type . '.' ] );
|
|
exit;
|
|
}
|
|
|
|
$customer_id = trim( (string) ( $context['google_ads_customer_id'] ?? '' ) );
|
|
$ad_group_external_id = trim( (string) ( $context['external_ad_group_id'] ?? '' ) );
|
|
$keyword_text = trim( (string) ( $context['keyword_text'] ?? '' ) );
|
|
|
|
if ( $customer_id === '' || $ad_group_external_id === '' || $keyword_text === '' )
|
|
{
|
|
echo json_encode( self::with_optional_debug( [
|
|
'success' => false,
|
|
'message' => 'Brak wymaganych danych Google Ads dla tej frazy.',
|
|
], [ 'context' => $context ] ) );
|
|
exit;
|
|
}
|
|
|
|
$api = new \services\GoogleAdsApi();
|
|
if ( !$api -> is_configured() )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Google Ads API nie jest skonfigurowane.' ] );
|
|
exit;
|
|
}
|
|
|
|
$api_result = $api -> update_keyword_match_type( $customer_id, $ad_group_external_id, $keyword_text, $old_match_type, $new_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 zmienic dopasowania frazy w Google Ads.',
|
|
'error' => $last_error
|
|
], [
|
|
'customer_id' => $customer_id,
|
|
'ad_group_external_id' => $ad_group_external_id,
|
|
'keyword_text' => $keyword_text,
|
|
'old_match_type' => $old_match_type,
|
|
'new_match_type' => $new_match_type,
|
|
'api_result' => $api_result
|
|
] ) );
|
|
exit;
|
|
}
|
|
|
|
\factory\Campaigns::update_keyword_match_type( $keyword_id, $new_match_type );
|
|
|
|
echo json_encode( self::with_optional_debug( [
|
|
'success' => true,
|
|
'message' => 'Dopasowanie zmienione z ' . $old_match_type . ' na ' . $new_match_type . '.'
|
|
], [
|
|
'customer_id' => $customer_id,
|
|
'ad_group_external_id' => $ad_group_external_id,
|
|
'keyword_text' => $keyword_text,
|
|
'old_match_type' => $old_match_type,
|
|
'new_match_type' => $new_match_type,
|
|
'api_result' => $api_result
|
|
] ) );
|
|
exit;
|
|
}
|
|
|
|
static public function add_keyword()
|
|
{
|
|
$campaign_id = (int) \S::get( 'campaign_id' );
|
|
$ad_group_id = (int) \S::get( 'ad_group_id' );
|
|
$keyword_text = trim( (string) \S::get( 'keyword_text' ) );
|
|
$match_type = strtoupper( trim( (string) \S::get( 'match_type' ) ) );
|
|
|
|
if ( $campaign_id <= 0 || $ad_group_id <= 0 )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Wybierz kampanie i grupe reklam.' ] );
|
|
exit;
|
|
}
|
|
|
|
if ( $keyword_text === '' )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Wpisz fraze do dodania.' ] );
|
|
exit;
|
|
}
|
|
|
|
if ( !in_array( $match_type, [ 'PHRASE', 'EXACT', 'BROAD' ], true ) )
|
|
{
|
|
$match_type = 'BROAD';
|
|
}
|
|
|
|
global $mdb;
|
|
|
|
$campaign_data = $mdb -> query(
|
|
'SELECT c.campaign_id AS external_campaign_id, cl.google_ads_customer_id
|
|
FROM campaigns AS c
|
|
INNER JOIN clients AS cl ON cl.id = c.client_id
|
|
WHERE c.id = :campaign_id
|
|
LIMIT 1',
|
|
[ ':campaign_id' => $campaign_id ]
|
|
) -> fetch( \PDO::FETCH_ASSOC );
|
|
|
|
if ( !$campaign_data )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie znaleziono kampanii.' ] );
|
|
exit;
|
|
}
|
|
|
|
$ad_group_data = $mdb -> query(
|
|
'SELECT ad_group_id AS external_ad_group_id FROM campaign_ad_groups WHERE id = :ad_group_id LIMIT 1',
|
|
[ ':ad_group_id' => $ad_group_id ]
|
|
) -> fetch( \PDO::FETCH_ASSOC );
|
|
|
|
if ( !$ad_group_data )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie znaleziono grupy reklam.' ] );
|
|
exit;
|
|
}
|
|
|
|
$customer_id = trim( (string) ( $campaign_data['google_ads_customer_id'] ?? '' ) );
|
|
$ad_group_external_id = trim( (string) ( $ad_group_data['external_ad_group_id'] ?? '' ) );
|
|
|
|
if ( $customer_id === '' || $ad_group_external_id === '' )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Brak wymaganych danych Google Ads.' ] );
|
|
exit;
|
|
}
|
|
|
|
$api = new \services\GoogleAdsApi();
|
|
if ( !$api -> is_configured() )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Google Ads API nie jest skonfigurowane.' ] );
|
|
exit;
|
|
}
|
|
|
|
$api_result = $api -> add_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 dodac frazy w Google Ads.',
|
|
'error' => $last_error
|
|
], [
|
|
'customer_id' => $customer_id,
|
|
'ad_group_external_id' => $ad_group_external_id,
|
|
'keyword_text' => $keyword_text,
|
|
'match_type' => $match_type,
|
|
'api_result' => $api_result
|
|
] ) );
|
|
exit;
|
|
}
|
|
|
|
\factory\Campaigns::insert_campaign_keyword( $campaign_id, $ad_group_id, $keyword_text, $match_type );
|
|
|
|
$match_labels = [ 'PHRASE' => 'do wyrazenia', 'EXACT' => 'scisle', 'BROAD' => 'przyblizone' ];
|
|
$match_label = $match_labels[$match_type] ?? $match_type;
|
|
|
|
echo json_encode( self::with_optional_debug( [
|
|
'success' => true,
|
|
'message' => ( $api_result['duplicate'] ?? false )
|
|
? 'Fraza juz istnieje w Google Ads (dopasowanie ' . $match_label . ').'
|
|
: 'Fraza zostala dodana (dopasowanie ' . $match_label . ').',
|
|
'duplicate' => (bool) ( $api_result['duplicate'] ?? false )
|
|
], [
|
|
'customer_id' => $customer_id,
|
|
'ad_group_external_id' => $ad_group_external_id,
|
|
'keyword_text' => $keyword_text,
|
|
'match_type' => $match_type,
|
|
'api_result' => $api_result
|
|
] ) );
|
|
exit;
|
|
}
|
|
|
|
static public function delete_keyword()
|
|
{
|
|
$keyword_id = (int) \S::get( 'keyword_id' );
|
|
|
|
if ( $keyword_id <= 0 )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie podano frazy do usuniecia.' ] );
|
|
exit;
|
|
}
|
|
|
|
$context = \factory\Campaigns::get_keyword_context( $keyword_id );
|
|
if ( !$context )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie znaleziono danych frazy.' ] );
|
|
exit;
|
|
}
|
|
|
|
$customer_id = trim( (string) ( $context['google_ads_customer_id'] ?? '' ) );
|
|
$ad_group_external_id = trim( (string) ( $context['external_ad_group_id'] ?? '' ) );
|
|
$keyword_text = trim( (string) ( $context['keyword_text'] ?? '' ) );
|
|
$match_type = strtoupper( trim( (string) ( $context['match_type'] ?? 'BROAD' ) ) );
|
|
|
|
if ( $customer_id === '' || $ad_group_external_id === '' || $keyword_text === '' )
|
|
{
|
|
echo json_encode( self::with_optional_debug( [
|
|
'success' => false,
|
|
'message' => 'Brak wymaganych danych Google Ads dla tej frazy.',
|
|
], [ 'context' => $context ] ) );
|
|
exit;
|
|
}
|
|
|
|
$api = new \services\GoogleAdsApi();
|
|
if ( !$api -> is_configured() )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Google Ads API nie jest skonfigurowane.' ] );
|
|
exit;
|
|
}
|
|
|
|
$api_result = $api -> remove_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' );
|
|
echo json_encode( self::with_optional_debug( [
|
|
'success' => false,
|
|
'message' => 'Nie udalo sie usunac frazy w Google Ads.',
|
|
'error' => $last_error
|
|
], [
|
|
'customer_id' => $customer_id,
|
|
'ad_group_external_id' => $ad_group_external_id,
|
|
'keyword_text' => $keyword_text,
|
|
'match_type' => $match_type,
|
|
'api_result' => $api_result
|
|
] ) );
|
|
exit;
|
|
}
|
|
|
|
\factory\Campaigns::delete_campaign_keyword( $keyword_id );
|
|
|
|
$removed = (int) ( $api_result['removed'] ?? 0 );
|
|
$message = $removed > 0
|
|
? 'Fraza zostala usunieta z Google Ads.'
|
|
: 'Fraza nie byla juz obecna w Google Ads. Usunieto lokalny wpis.';
|
|
|
|
echo json_encode( self::with_optional_debug( [
|
|
'success' => true,
|
|
'message' => $message,
|
|
'removed' => $removed
|
|
], [
|
|
'customer_id' => $customer_id,
|
|
'ad_group_external_id' => $ad_group_external_id,
|
|
'keyword_text' => $keyword_text,
|
|
'match_type' => $match_type,
|
|
'api_result' => $api_result
|
|
] ) );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Wstrzymuje lub wznawia fraze kluczowa (ENABLED <-> PAUSED).
|
|
*/
|
|
static public function toggle_keyword_status()
|
|
{
|
|
$keyword_id = (int) \S::get( 'keyword_id' );
|
|
|
|
if ( $keyword_id <= 0 )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie podano frazy.' ] );
|
|
exit;
|
|
}
|
|
|
|
$context = \factory\Campaigns::get_keyword_context( $keyword_id );
|
|
if ( !$context )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie znaleziono danych frazy.' ] );
|
|
exit;
|
|
}
|
|
|
|
$customer_id = trim( (string) ( $context['google_ads_customer_id'] ?? '' ) );
|
|
$ad_group_external_id = trim( (string) ( $context['external_ad_group_id'] ?? '' ) );
|
|
$keyword_text = trim( (string) ( $context['keyword_text'] ?? '' ) );
|
|
$match_type = strtoupper( trim( (string) ( $context['match_type'] ?? 'BROAD' ) ) );
|
|
$current_status = strtoupper( trim( (string) ( $context['status'] ?? 'ENABLED' ) ) );
|
|
|
|
if ( $customer_id === '' || $ad_group_external_id === '' || $keyword_text === '' )
|
|
{
|
|
echo json_encode( self::with_optional_debug( [
|
|
'success' => false,
|
|
'message' => 'Brak wymaganych danych Google Ads dla tej frazy.',
|
|
], [ 'context' => $context ] ) );
|
|
exit;
|
|
}
|
|
|
|
$new_status = ( $current_status === 'PAUSED' ) ? 'ENABLED' : 'PAUSED';
|
|
|
|
$api = new \services\GoogleAdsApi();
|
|
if ( !$api -> is_configured() )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Google Ads API nie jest skonfigurowane.' ] );
|
|
exit;
|
|
}
|
|
|
|
$api_result = $api -> update_keyword_status( $customer_id, $ad_group_external_id, $keyword_text, $match_type, $new_status );
|
|
|
|
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 zmienic statusu frazy w Google Ads.',
|
|
'error' => $last_error
|
|
], [
|
|
'customer_id' => $customer_id,
|
|
'ad_group_external_id' => $ad_group_external_id,
|
|
'keyword_text' => $keyword_text,
|
|
'match_type' => $match_type,
|
|
'new_status' => $new_status,
|
|
'api_result' => $api_result
|
|
] ) );
|
|
exit;
|
|
}
|
|
|
|
\factory\Campaigns::update_campaign_keyword_status( $keyword_id, $new_status );
|
|
|
|
$status_label = ( $new_status === 'PAUSED' ) ? 'wstrzymana' : 'wznowiona';
|
|
|
|
echo json_encode( self::with_optional_debug( [
|
|
'success' => true,
|
|
'message' => 'Fraza zostala ' . $status_label . '.',
|
|
'new_status' => $new_status
|
|
], [
|
|
'customer_id' => $customer_id,
|
|
'ad_group_external_id' => $ad_group_external_id,
|
|
'keyword_text' => $keyword_text,
|
|
'match_type' => $match_type,
|
|
'api_result' => $api_result
|
|
] ) );
|
|
exit;
|
|
}
|
|
}
|