<?php declare(strict_types=1); if (!defined('DASHBOARD_ROOT')) { http_response_code(403); exit('Direct access denied.'); } class MetaAdsAPI { private const API_VERSION = 'v21.0'; private const BASE_URL = 'https://graph.facebook.com'; private static function getToken(): string { $token = ServiceSecrets::get('META_ACCESS_TOKEN'); if (!$token) { throw new RuntimeException('Meta access token not configured.'); } return $token; } public static function get(string $endpoint, array $params = []): array { $params['access_token'] = self::getToken(); $url = self::BASE_URL . '/' . self::API_VERSION . '/' . ltrim($endpoint, '/'); $url .= '?' . http_build_query($params); return self::request('GET', $url); } public static function post(string $endpoint, array $data = []): array { $data['access_token'] = self::getToken(); $url = self::BASE_URL . '/' . self::API_VERSION . '/' . ltrim($endpoint, '/'); return self::request('POST', $url, $data); } public static function getCampaigns(string $adAccountId, array $fields = []): array { $defaultFields = ['id', 'name', 'status', 'objective', 'daily_budget', 'lifetime_budget', 'created_time']; return self::get("act_{$adAccountId}/campaigns", [ 'fields' => implode(',', $fields ?: $defaultFields), 'limit' => 100, ]); } public static function getAdSets(string $campaignId, array $fields = []): array { $defaultFields = ['id', 'name', 'status', 'daily_budget', 'targeting', 'start_time', 'end_time']; return self::get("{$campaignId}/adsets", [ 'fields' => implode(',', $fields ?: $defaultFields), ]); } public static function getAds(string $adSetId, array $fields = []): array { $defaultFields = ['id', 'name', 'status', 'creative', 'created_time']; return self::get("{$adSetId}/ads", [ 'fields' => implode(',', $fields ?: $defaultFields), ]); } public static function getInsights(string $objectId, string $timeRange = 'last_7d'): array { return self::get("{$objectId}/insights", [ 'fields' => 'impressions,clicks,spend,cpc,cpm,ctr,actions,cost_per_action_type', 'date_preset' => $timeRange, ]); } public static function getAdAccounts(string $userId = 'me'): array { return self::get("{$userId}/adaccounts", [ 'fields' => 'id,name,account_status,currency,timezone_name,balance', ]); } private static function request(string $method, string $url, array $postData = []): array { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'], ]); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData)); } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($response === false) { throw new RuntimeException("Meta API request failed: {$error}"); } $decoded = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new RuntimeException('Meta API returned invalid JSON.'); } if (isset($decoded['error'])) { throw new RuntimeException('Meta API error: ' . ($decoded['error']['message'] ?? 'Unknown')); } return $decoded; } } 