diff --git a/.vscode/ftp-kr.sync.cache.json b/.vscode/ftp-kr.sync.cache.json index 6255209..c759133 100644 --- a/.vscode/ftp-kr.sync.cache.json +++ b/.vscode/ftp-kr.sync.cache.json @@ -3,14 +3,14 @@ "public_html": { "ajax.php": { "type": "-", - "size": 1208, - "lmtime": 0, + "size": 1250, + "lmtime": 1778701280729, "modified": false }, "api.php": { "type": "-", - "size": 33086, - "lmtime": 1777532181358, + "size": 49083, + "lmtime": 1778702412982, "modified": false }, "autoload": { @@ -65,14 +65,14 @@ "controls": { "class.Allegro.php": { "type": "-", - "size": 6939, - "lmtime": 0, - "modified": true + "size": 6846, + "lmtime": 1778100624192, + "modified": false }, "class.Api.php": { "type": "-", - "size": 24772, - "lmtime": 1773134827335, + "size": 24100, + "lmtime": 1778100609423, "modified": false }, "class.CampaignAlerts.php": { @@ -95,14 +95,14 @@ }, "class.Clients.php": { "type": "-", - "size": 14217, + "size": 15154, "lmtime": 1777532181359, - "modified": false + "modified": true }, "class.Cron.php": { "type": "-", - "size": 185385, - "lmtime": 1777532181362, + "size": 187597, + "lmtime": 1778100596673, "modified": false }, "class.FacebookAds.php": { @@ -125,9 +125,9 @@ }, "class.Products.php": { "type": "-", - "size": 55416, + "size": 55518, "lmtime": 1777532181364, - "modified": false + "modified": true }, "class.Site.php": { "type": "-", @@ -137,9 +137,9 @@ }, "class.Users.php": { "type": "-", - "size": 21677, + "size": 21890, "lmtime": 1772899952961, - "modified": false + "modified": true }, "class.XmlFiles.php": { "type": "-", @@ -193,8 +193,8 @@ }, "class.Products.php": { "type": "-", - "size": 47877, - "lmtime": 1777532181365, + "size": 49115, + "lmtime": 1778100588302, "modified": false }, "class.Users.php": { @@ -243,14 +243,14 @@ }, "class.SupplementalFeed.php": { "type": "-", - "size": 10874, + "size": 11607, "lmtime": 1777532181367, - "modified": false + "modified": true }, "class.XmlFeedImporter.php": { "type": "-", - "size": 11131, - "lmtime": 1777532181367, + "size": 15200, + "lmtime": 1778100643530, "modified": false } }, @@ -293,6 +293,18 @@ } } }, + "backup_dup_20260506_2228.sql": { + "type": "-", + "size": 57424877, + "lmtime": 1778099323238, + "modified": false + }, + "backup_err.log": { + "type": "-", + "size": 0, + "lmtime": 1778099319166, + "modified": false + }, "config.php": { "type": "-", "size": 623, @@ -308,8 +320,8 @@ "docs": { "api-public-product-management.md": { "type": "-", - "size": 13629, - "lmtime": 1773184070257, + "size": 19243, + "lmtime": 1778533334340, "modified": false }, "class-methods.md": { @@ -326,7 +338,7 @@ }, "database.sql": { "type": "-", - "size": 9123, + "size": 9277, "lmtime": 1771440593718, "modified": true }, @@ -353,6 +365,12 @@ "size": 657, "lmtime": 1772115859276, "modified": false + }, + "unit-price.md": { + "type": "-", + "size": 7161, + "lmtime": 0, + "modified": false } }, "feeds": { @@ -749,11 +767,23 @@ "lmtime": 1777532181369, "modified": false }, + "030_products_unit_pricing.sql": { + "type": "-", + "size": 1815, + "lmtime": 0, + "modified": false + }, "demo_data.sql": { "type": "-", "size": 21146, "lmtime": 0, "modified": true + }, + "031_products_unique_client_offer.sql": { + "type": "-", + "size": 1439, + "lmtime": 1778100382587, + "modified": false } }, ".paul": { @@ -857,6 +887,12 @@ "size": 14402, "lmtime": 1777532181349, "modified": false + }, + "governance_2026-05-06.jsonl": { + "type": "-", + "size": 9940, + "lmtime": 1778100666956, + "modified": false } }, "phases": { @@ -934,9 +970,59 @@ }, "STATE.md": { "type": "-", - "size": 3487, + "size": 619, "lmtime": 1777532181345, + "modified": true + }, + "STATE.md.bak": { + "type": "-", + "size": 787, + "lmtime": 0, "modified": false + }, + "codebase": { + "architecture.md": { + "type": "-", + "size": 6130, + "lmtime": 1778098574971, + "modified": false + }, + "conventions.md": { + "type": "-", + "size": 4012, + "lmtime": 1778098638245, + "modified": false + }, + "integrations.md": { + "type": "-", + "size": 4886, + "lmtime": 1778098605982, + "modified": false + }, + "overview.md": { + "type": "-", + "size": 2598, + "lmtime": 1778098517204, + "modified": false + }, + "tech-stack.md": { + "type": "-", + "size": 2360, + "lmtime": 1778098535066, + "modified": false + }, + "testing.md": { + "type": "-", + "size": 1744, + "lmtime": 1778098656341, + "modified": false + }, + "concerns.md": { + "type": "-", + "size": 7004, + "lmtime": 1778098705977, + "modified": false + } } }, "raporty": {}, @@ -1050,6 +1136,12 @@ } }, "tmp": { + "apply_migration_031.php": { + "type": "-", + "size": 2396, + "lmtime": 1778100436659, + "modified": false + }, "campaign_alerts_debug.log": { "type": "-", "size": 15260, @@ -1104,6 +1196,12 @@ "lmtime": 1773133998372, "modified": false }, + "debug_bestseller_roas_compare.php": { + "type": "-", + "size": 1114, + "lmtime": 1773134018725, + "modified": false + }, "debug_clients.php": { "type": "-", "size": 1274, @@ -1122,22 +1220,58 @@ "lmtime": 0, "modified": false }, + "debug_dup_audit.php": { + "type": "-", + "size": 4647, + "lmtime": 1778098418555, + "modified": false + }, + "debug_dup_indexes.php": { + "type": "-", + "size": 2068, + "lmtime": 1778098470512, + "modified": false + }, + "debug_dup_products.php": { + "type": "-", + "size": 3090, + "lmtime": 1778098247782, + "modified": false + }, "debug_eligible_remote.php": { "type": "-", "size": 567, "lmtime": 0, "modified": false }, + "debug_verify_final.php": { + "type": "-", + "size": 1993, + "lmtime": 1778100338668, + "modified": false + }, + "debug_verify_test.php": { + "type": "-", + "size": 1511, + "lmtime": 1778099350638, + "modified": false + }, + "merge_duplicate_products.php": { + "type": "-", + "size": 11611, + "lmtime": 1778098721919, + "modified": false + }, "meta_active_last30d.json": { "type": "-", "size": 168510, "lmtime": 0, "modified": false }, - "debug_bestseller_roas_compare.php": { + "paul_query_client.php": { "type": "-", - "size": 1114, - "lmtime": 1773134018725, + "size": 624, + "lmtime": 0, "modified": false } }, diff --git a/ajax.php b/ajax.php index 12087bf..525d5ff 100644 --- a/ajax.php +++ b/ajax.php @@ -5,7 +5,7 @@ function __autoload_my_classes( $classname ) $q = explode( '\\' , $classname ); $c = array_pop( $q ); $f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php'; - + if ( file_exists( $f ) ) require_once( $f ); } diff --git a/api.php b/api.php index 5188259..f37c37d 100644 --- a/api.php +++ b/api.php @@ -775,7 +775,140 @@ if ( \S::get( 'action' ) == 'products_unoptimized_list' ) ] ); } -// Lista produktow bez unit pricing dla kategorii beauty/cosmetics +// Lista produktow bez zoptymalizowanego tytulu GMC +if ( \S::get( 'action' ) == 'products_get_missing_title' ) +{ + api_validate_api_key( $mdb ); + + $client_id_param = (int) \S::get( 'client_id' ); + $limit = (int) \S::get( 'limit' ); + + if ( $client_id_param <= 0 ) + { + api_json_response( [ 'result' => 'error', 'message' => 'Missing required param: client_id' ], 422 ); + } + + if ( $limit <= 0 || $limit > 200 ) + { + $limit = 50; + } + + $rows = $mdb -> query( + 'SELECT p.id, p.offer_id, p.title AS name, p.title_gmc AS title, p.google_product_category, + COALESCE( SUM( pa.clicks_all_time ), 0 ) AS clicks_all_time, + COALESCE( SUM( pa.impressions_all_time ), 0 ) AS impressions_all_time, + COALESCE( SUM( pa.cost_all_time ), 0 ) AS cost_all_time + FROM products p + LEFT JOIN products_aggregate pa ON pa.product_id = p.id + WHERE p.client_id = :client_id + AND TRIM( COALESCE( p.offer_id, \'\' ) ) <> \'\' + AND ( + p.title_gmc IS NULL OR TRIM( p.title_gmc ) = \'\' OR TRIM( p.title_gmc ) = TRIM( p.title ) + ) + GROUP BY p.id + ORDER BY clicks_all_time DESC, impressions_all_time DESC + LIMIT :limit', + [ + ':client_id' => (int) $client_id_param, + ':limit' => (int) $limit + ] + ) -> fetchAll( \PDO::FETCH_ASSOC ); + + $products = []; + foreach ( $rows as $row ) + { + $base_name = trim( (string) ( $row['name'] ?? '' ) ); + $custom_title = trim( (string) ( $row['title'] ?? '' ) ); + $google_category = trim( (string) ( $row['google_product_category'] ?? '' ) ); + + $products[] = [ + 'offer_id' => (string) ( $row['offer_id'] ?? '' ), + 'default_name' => $base_name, + 'custom_title' => $custom_title !== '' ? $custom_title : null, + 'title_changed' => false, + 'google_product_category' => $google_category !== '' ? $google_category : null, + 'needs_title' => true, + 'needs_category' => $google_category === '', + 'clicks' => (int) $row['clicks_all_time'], + 'impressions' => (int) $row['impressions_all_time'], + 'cost' => round( (float) $row['cost_all_time'], 2 ) + ]; + } + + api_json_response( [ + 'result' => 'ok', + 'client_id' => $client_id_param, + 'count' => count( $products ), + 'products' => $products + ] ); +} + +// Lista produktow bez kategorii Google +if ( \S::get( 'action' ) == 'products_get_missing_google_category' ) +{ + api_validate_api_key( $mdb ); + + $client_id_param = (int) \S::get( 'client_id' ); + $limit = (int) \S::get( 'limit' ); + + if ( $client_id_param <= 0 ) + { + api_json_response( [ 'result' => 'error', 'message' => 'Missing required param: client_id' ], 422 ); + } + + if ( $limit <= 0 || $limit > 200 ) + { + $limit = 50; + } + + $rows = $mdb -> query( + 'SELECT p.id, p.offer_id, p.title AS name, p.title_gmc AS title, p.google_product_category, + COALESCE( SUM( pa.clicks_all_time ), 0 ) AS clicks_all_time, + COALESCE( SUM( pa.impressions_all_time ), 0 ) AS impressions_all_time, + COALESCE( SUM( pa.cost_all_time ), 0 ) AS cost_all_time + FROM products p + LEFT JOIN products_aggregate pa ON pa.product_id = p.id + WHERE p.client_id = :client_id + AND TRIM( COALESCE( p.offer_id, \'\' ) ) <> \'\' + AND ( p.google_product_category IS NULL OR TRIM( p.google_product_category ) = \'\' ) + GROUP BY p.id + ORDER BY clicks_all_time DESC, impressions_all_time DESC + LIMIT :limit', + [ + ':client_id' => (int) $client_id_param, + ':limit' => (int) $limit + ] + ) -> fetchAll( \PDO::FETCH_ASSOC ); + + $products = []; + foreach ( $rows as $row ) + { + $base_name = trim( (string) ( $row['name'] ?? '' ) ); + $custom_title = trim( (string) ( $row['title'] ?? '' ) ); + + $products[] = [ + 'offer_id' => (string) ( $row['offer_id'] ?? '' ), + 'default_name' => $base_name, + 'custom_title' => $custom_title !== '' ? $custom_title : null, + 'title_changed' => $custom_title !== '' && $custom_title !== $base_name, + 'google_product_category' => null, + 'needs_title' => !($custom_title !== '' && $custom_title !== $base_name), + 'needs_category' => true, + 'clicks' => (int) $row['clicks_all_time'], + 'impressions' => (int) $row['impressions_all_time'], + 'cost' => round( (float) $row['cost_all_time'], 2 ) + ]; + } + + api_json_response( [ + 'result' => 'ok', + 'client_id' => $client_id_param, + 'count' => count( $products ), + 'products' => $products + ] ); +} + +// Lista produktow bez unit pricing; opcjonalnie filtrowana po kategorii Google if ( \S::get( 'action' ) == 'products_get_missing_unit_pricing' ) { api_validate_api_key( $mdb ); @@ -808,17 +941,6 @@ if ( \S::get( 'action' ) == 'products_get_missing_unit_pricing' ) $where_sql .= ' AND p.google_product_category LIKE :category_filter'; $params[':category_filter'] = '%' . $category_filter . '%'; } - else - { - $where_sql .= " AND ( - p.google_product_category LIKE '%Beauty%' - OR p.google_product_category LIKE '%Health%' - OR p.google_product_category LIKE '%Cosmetic%' - OR p.google_product_category LIKE '%Skin Care%' - OR p.google_product_category LIKE '%Higiena%' - OR p.google_product_category LIKE '%Pielegnacj%' - )"; - } $rows = $mdb -> query( 'SELECT p.id,