Created
December 7, 2025 10:56
-
-
Save mehargags/7828502401f79419ab2a1c0a4d2b29bf to your computer and use it in GitHub Desktop.
Suppressions.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| // Set exit code to 0 by default | |
| $exitCode = 0; | |
| // Function to load environment variables from .env file | |
| function loadEnv($path) { | |
| if (!file_exists($path)) { | |
| fwrite(STDERR, "Error: Environment file (.env) not found at {$path}\n"); | |
| exit(1); | |
| } | |
| $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); | |
| foreach ($lines as $line) { | |
| if (strpos(trim($line), '#') === 0 || strpos($line, '=') === false) { | |
| continue; | |
| } | |
| list($name, $value) = explode('=', $line, 2); | |
| $name = trim($name); | |
| $value = trim($value, "'\" \t\n\r\0\x0B"); | |
| if (function_exists('putenv')) { | |
| putenv(sprintf('%s=%s', $name, $value)); | |
| } | |
| $_ENV[$name] = $value; | |
| $_SERVER[$name] = $value; | |
| } | |
| } | |
| // Load environment variables into the global scope | |
| loadEnv(__DIR__ . '/.env'); | |
| function run() { | |
| // --- API Key & DB Configuration --- | |
| $apiKey = getenv('ZOHO_SEND_API_IndAstro'); | |
| if (empty($apiKey)) { | |
| fwrite(STDERR, "Configuration Error: ZOHO_SEND_API_IndAstro not found in .env file.\n"); | |
| return 1; | |
| } | |
| $dbHost = 'localhost'; | |
| $dbName = getenv('DBNAME'); | |
| $dbUser = getenv('DBUSER'); | |
| $dbPass = getenv('DBPASS'); | |
| if (empty($dbName) || empty($dbUser) || empty($dbPass)) { | |
| fwrite(STDERR, "Configuration Error: Database credentials are not set.\n"); | |
| return 1; | |
| } | |
| $baseApiUrl = 'https://campaigns.zoho.com/emailapi/v2/recipients/suppression'; | |
| // --- Database Operations & Main Sync Logic --- | |
| // The script uses a try-catch block to handle potential database or API exceptions gracefully. | |
| mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); | |
| try { | |
| echo "Connecting to the database...\n"; | |
| $conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName); | |
| // A prepared statement is used to check if an email already exists in the local database. | |
| // This is efficient and prevents SQL injection. | |
| $select_stmt = $conn->prepare("SELECT email FROM suppressions WHERE email = ?"); | |
| // A prepared statement is used for inserting new records. | |
| // This is much faster than running a full INSERT query for each new record. | |
| $insert_stmt = $conn->prepare("INSERT INTO suppressions (email, zuid, suppression_type, added_time, modified_time, additional_data) VALUES (?, ?, ?, ?, ?, ?)"); | |
| echo "Fetching suppressed contacts from API in pages...\n"; | |
| // --- Initialization of variables for the sync process --- | |
| $start_index = 1; // The starting index for API pagination. | |
| $page_size = 500; // The number of records to fetch per API call. | |
| $total_records_from_api = -1; // Sentinel value to ensure we only get the total count once. | |
| $total_inserted_count = 0; // Counter for new records added in this run. | |
| $total_checked_count = 0; // Counter for total records processed from the API. | |
| // --- Main Sync Loop --- | |
| // The script loops through pages of API data until all records have been checked. | |
| // This "chunk-based" processing is memory-efficient and scalable, as it never loads the full dataset into memory. | |
| do { | |
| // Construct the API URL with pagination parameters. | |
| $apiUrl = $baseApiUrl . "?start_index={$start_index}&end_index=" . ($start_index + $page_size - 1); | |
| // --- API Call --- | |
| $ch = curl_init(); | |
| curl_setopt($ch, CURLOPT_URL, $apiUrl); | |
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
| curl_setopt($ch, CURLOPT_HTTPHEADER, [ | |
| 'Authorization: Zoho-zapikey ' . $apiKey, | |
| 'Content-Type: application/json' | |
| ]); | |
| curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
| $responseBody = curl_exec($ch); | |
| $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
| $curlError = curl_error($ch); | |
| curl_close($ch); | |
| // --- Error Handling for API Call --- | |
| if ($curlError || $httpCode !== 200) { | |
| fwrite(STDERR, "API Error: HTTP {$httpCode}. " . $curlError . "\nResponse: {$responseBody}\n"); | |
| throw new Exception("API request failed on page starting at index {$start_index}."); | |
| } | |
| $data = json_decode($responseBody, true); | |
| if (json_last_error() !== JSON_ERROR_NONE) { | |
| fwrite(STDERR, "API JSON Decode Error: " . json_last_error_msg() . "\n"); | |
| throw new Exception("Failed to decode API response for page at index {$start_index}."); | |
| } | |
| // --- Total Record Count --- | |
| // On the first API call, retrieve the total number of records from the response. | |
| // This is used as the termination condition for the main loop. | |
| if ($total_records_from_api === -1) { | |
| $total_records_from_api = $data['total_count'] ?? 0; | |
| if ($total_records_from_api === 0) { | |
| echo "API reports 0 total suppressed contacts. Nothing to do.\n"; | |
| break; // Exit do-while loop | |
| } | |
| echo "API reports a total of {$total_records_from_api} records. Starting sync...\n"; | |
| } | |
| // --- Process a Page of Records --- | |
| if (isset($data['suppression']) && !empty($data['suppression'])) { | |
| $suppressions_chunk = $data['suppression']; | |
| $chunk_page_count = count($suppressions_chunk); | |
| $chunk_inserted_count = 0; | |
| // Loop through each record in the fetched chunk. | |
| foreach ($suppressions_chunk as $report) { | |
| $total_checked_count++; | |
| $email = $report['address'] ?? null; | |
| if (!$email) continue; // Skip if address is missing, though unlikely. | |
| // Check if the email already exists in the local database. | |
| $select_stmt->bind_param("s", $email); | |
| $select_stmt->execute(); | |
| $result = $select_stmt->get_result(); | |
| // If the record does not exist locally, insert it. | |
| if ($result->num_rows === 0) { | |
| $zuid = $report['zuid'] ?? null; | |
| $suppression_type = $report['suppression_type'] ?? null; | |
| $added_time = isset($report['added_time']) ? date('Y-m-d H:i:s', $report['added_time'] / 1000) : null; | |
| $modified_time = isset($report['modified_time']) ? date('Y-m-d H:i:s', $report['modified_time'] / 1000) : null; | |
| $additional_data = isset($report['additional_data']) ? json_encode($report['additional_data']) : null; | |
| // Execute the prepared INSERT statement. | |
| $insert_stmt->bind_param("ssssss", $email, $zuid, $suppression_type, $added_time, $modified_time, $additional_data); | |
| if ($insert_stmt->execute()) { | |
| $chunk_inserted_count++; | |
| } | |
| } | |
| } | |
| $total_inserted_count += $chunk_inserted_count; | |
| echo "Page " . ceil($start_index / $page_size) . ": Checked {$chunk_page_count} records, found and inserted {$chunk_inserted_count} new suppressions.\n"; | |
| // Move to the next page for the next iteration. | |
| $start_index += $page_size; | |
| } else { | |
| // If the API returns no more records, we can safely stop. | |
| echo "No more records returned from API. Stopping.\n"; | |
| break; | |
| } | |
| // The loop continues until the number of checked records matches the total reported by the API. | |
| } while ($total_checked_count < $total_records_from_api); | |
| // --- Cleanup and Final Report --- | |
| $select_stmt->close(); | |
| $insert_stmt->close(); | |
| $conn->close(); | |
| echo "\nSync finished.\n"; | |
| echo "-------------------\n"; | |
| echo "Total records reported by API: " . ($total_records_from_api > 0 ? $total_records_from_api : 'N/A') . "\n"; | |
| echo "Total records checked: {$total_checked_count}\n"; | |
| echo "Total new records inserted: {$total_inserted_count}\n"; | |
| echo "-------------------\n"; | |
| return 0; | |
| } catch (Exception $e) { | |
| fwrite(STDERR, "An error occurred: " . $e->getMessage() . "\n"); | |
| // Ensure database connection is closed even if an error occurs. | |
| if (isset($conn) && $conn instanceof mysqli) { | |
| $conn->close(); | |
| } | |
| return 1; | |
| } | |
| } | |
| // --- Script Execution --- | |
| $exitCode = run(); | |
| exit($exitCode); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment