diff --git a/.vscode/ftp-kr.sync.cache.json b/.vscode/ftp-kr.sync.cache.json index ef4b3b5..9a1c73c 100644 --- a/.vscode/ftp-kr.sync.cache.json +++ b/.vscode/ftp-kr.sync.cache.json @@ -125,8 +125,8 @@ }, "class.Products.php": { "type": "-", - "size": 48016, - "lmtime": 1772529696252, + "size": 49998, + "lmtime": 1772612176689, "modified": false }, "class.Site.php": { @@ -193,8 +193,8 @@ }, "class.Products.php": { "type": "-", - "size": 35004, - "lmtime": 1772529689598, + "size": 41405, + "lmtime": 1772612327756, "modified": false }, "class.Users.php": { @@ -243,8 +243,8 @@ }, "class.SupplementalFeed.php": { "type": "-", - "size": 4797, - "lmtime": 1772527954403, + "size": 10194, + "lmtime": 1772612321236, "modified": false } }, @@ -707,16 +707,22 @@ "lmtime": 1771966738564, "modified": false }, + "026_cron_queue.sql": { + "type": "-", + "size": 1851, + "lmtime": 1772116290944, + "modified": false + }, "demo_data.sql": { "type": "-", "size": 21146, "lmtime": 0, "modified": true }, - "026_cron_queue.sql": { + "026_clients_bestseller_settings.sql": { "type": "-", - "size": 1851, - "lmtime": 1772116290944, + "size": 1613, + "lmtime": 1772611513545, "modified": false } }, @@ -774,8 +780,8 @@ "products": { "main_view.php": { "type": "-", - "size": 72463, - "lmtime": 1772530521923, + "size": 77412, + "lmtime": 1772612193848, "modified": false }, "product_history.php": { diff --git a/autoload/controls/class.Api.php b/autoload/controls/class.Api.php index 9e4d598..45c05e7 100644 --- a/autoload/controls/class.Api.php +++ b/autoload/controls/class.Api.php @@ -2,109 +2,305 @@ namespace controls; class Api { + static private function get_request_headers() + { + $headers = []; + + if ( function_exists( 'getallheaders' ) ) + { + $headers = (array) getallheaders(); + } + + if ( isset( $_SERVER['HTTP_X_ADSPRO_API_TOKEN'] ) ) + { + $headers['X-Adspro-Api-Token'] = (string) $_SERVER['HTTP_X_ADSPRO_API_TOKEN']; + } + + if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) + { + $headers['Authorization'] = (string) $_SERVER['HTTP_AUTHORIZATION']; + } + + return $headers; + } + + static private function get_request_api_token() + { + $headers = self::get_request_headers(); + + $token = trim( (string) ( $headers['X-Adspro-Api-Token'] ?? '' ) ); + if ( $token !== '' ) + { + return $token; + } + + $auth = trim( (string) ( $headers['Authorization'] ?? '' ) ); + if ( stripos( $auth, 'Bearer ' ) === 0 ) + { + $bearer_token = trim( substr( $auth, 7 ) ); + if ( $bearer_token !== '' ) + { + return $bearer_token; + } + } + + return trim( (string) ( $_GET['api_token'] ?? $_POST['api_token'] ?? '' ) ); + } + + static private function get_configured_ingest_token() + { + global $mdb, $settings; + + $token = trim( (string) $mdb -> 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 ); - if ( $data['clientId'] and $data['date'] ) + $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'] ) ) { - foreach ( $data['data'] as $campaign ) + 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 === '' ) { - $incoming_campaign_name = trim( (string) ( $campaign['camapignName'] ?? ( $campaign['campaignName'] ?? '' ) ) ); - if ( $incoming_campaign_name === '' ) - { - $incoming_campaign_name = 'Campaign #' . (string) ( $campaign['campaignId'] ?? '' ); - } + continue; + } - if ( !$mdb -> count( 'campaigns', [ 'AND' => [ 'client_id' => $data['clientId'], 'campaign_id' => $campaign['campaignId'] ] ] ) ) - { - $campaign_data['client_id'] = $data['clientId']; - $campaign_data['campaign_id'] = $campaign['campaignId']; - $campaign_data['campaign_name'] = $incoming_campaign_name; + $incoming_campaign_name = trim( (string) ( $campaign['camapignName'] ?? ( $campaign['campaignName'] ?? '' ) ) ); + if ( $incoming_campaign_name === '' ) + { + $incoming_campaign_name = 'Campaign #' . $external_campaign_id; + } - $mdb -> insert( 'campaigns', [ - 'client_id' => $data['clientId'], - 'campaign_id' => $campaign['campaignId'], - 'campaign_name' => $incoming_campaign_name - ] ); + 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(); - } - else - { - $campaign_id = $mdb -> get( 'campaigns', 'id', [ 'AND' => [ 'client_id' => $data['clientId'], 'campaign_id' => $campaign['campaignId'] ] ] ); - - 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 ] ); - } - } - } + $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 ) { - $campaign_history_data = []; - $campaign_history_data['roas_30_days'] = $campaign['roas30Days']; - $campaign_history_data['roas_all_time'] = $campaign['roasAllTime']; - $campaign_history_data['budget'] = str_replace( ' zł', '' , $campaign['budget'] ); - $campaign_history_data['money_spent'] = floatval( preg_replace(['/[^0-9,]/', '/,/'], ['', '.'], $campaign['spend30Days'] ) ); - $campaign_history_data['conversion_value'] = preg_replace( '/[^\d,.-]/', '', $campaign['conversionValue30Days'] ); - - if ( isset( $campaign['biddingStrategy'] ) and $campaign['biddingStrategy'] == 'MAXIMIZE_CONVERSIONS' ) + $existing_campaign_name = trim( (string) $mdb -> get( 'campaigns', 'campaign_name', [ 'id' => $campaign_id ] ) ); + if ( $incoming_campaign_name !== '' and $existing_campaign_name !== $incoming_campaign_name ) { - $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 wartości 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' => $data['date'] ] ] ) ) - { - $mdb -> update( 'campaigns_history', $campaign_history_data, [ 'AND' => [ 'campaign_id' => $campaign_id, 'date_add' => $data['date'] ] ] ); - } - else - { - $campaign_history_data['campaign_id'] = $campaign_id; - $campaign_history_data['date_add'] = $data['date']; - - $mdb -> insert( 'campaigns_history', $campaign_history_data ); + $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++; + } } - echo json_encode( [ 'status' => 'ok' ] ); + \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 ); @@ -191,6 +387,8 @@ class Api { 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 ) ); @@ -290,6 +488,8 @@ class Api { global $mdb; + self::require_ingest_auth( 'api_products_data_import' ); + $json = file_get_contents( 'php://input' ); $data = json_decode( $json, true ); @@ -532,3 +732,4 @@ class Api return (float) $value; } } + diff --git a/autoload/controls/class.Campaigns.php b/autoload/controls/class.Campaigns.php index 34f402f..0e229dc 100644 --- a/autoload/controls/class.Campaigns.php +++ b/autoload/controls/class.Campaigns.php @@ -11,7 +11,10 @@ class Campaigns static public function get_campaigns_list() { - echo json_encode( [ 'campaigns' => \factory\Campaigns::get_campaigns_list( \S::get( 'client_id' ) ) ] ); + $client_id = (int) \S::get( 'client_id' ); + $include_archived = (int) \S::get( 'include_archived' ) === 1; + + echo json_encode( [ 'campaigns' => \factory\Campaigns::get_campaigns_list( $client_id, !$include_archived ) ] ); exit; }