0, 'timestamp' => time()]; if (file_exists($rateLimitFile) && is_readable($rateLimitFile)) { $stored = @json_decode(file_get_contents($rateLimitFile), true); if ($stored && is_array($stored)) { $rateLimitData = $stored; } } if (time() - $rateLimitData['timestamp'] > $timeWindow) { // Reset counter if time window has passed $rateLimitData = ['count' => 1, 'timestamp' => time()]; } else { $rateLimitData['count']++; if ($rateLimitData['count'] > $maxRequests) { sendErrorJSON("Rate limit exceeded ($maxRequests requests per hour). Please try again later.", 429); } } // Save rate limit data (suppress errors if can't write) @file_put_contents($rateLimitFile, json_encode($rateLimitData)); } // Basic error response function /** * @param string $message * @param int $statusCode * @param string $sourceType */ function sendErrorJSON(string $message, int $statusCode = 400, ?string $sourceType = null): void { global $config; http_response_code($statusCode); $response = ['error' => $message]; if ($sourceType) { $response['source'] = $sourceType; } // Log errors if enabled if ($config['security']['log_errors']) { error_log("Data Provider Error ($sourceType): $message (Status: $statusCode)"); } echo json_encode($response); exit; } if (!$sourceType) { sendErrorJSON('Data source type (`source`) not specified in the request.'); } $data = null; switch ($sourceType) { case 'api_revenue': // Matches value in index.html dropdown $endpoint = $params['endpoint'] ?? 'monthly_revenue'; // Default to monthly_revenue if not specified unset($params['source'], $params['endpoint']); // Clean up $data = fetchFromApi($endpoint, $params); if (isset($data['error'])) { sendErrorJSON("API Error: ".$data['error'], 500, $sourceType); } break; case 'db_sales': // Matches value in index.html dropdown $tableName = $params['table'] ?? 'sales_overview'; // Default to sales_overview $columns = isset($params['columns']) ? (is_array($params['columns']) ? $params['columns'] : [$params['columns']]) : ['*']; unset($params['source'], $params['table'], $params['columns']); // Clean up $data = queryDatabase($tableName, $columns, $params); if (isset($data['error'])) { sendErrorJSON("Database Error: ".$data['error'], 500, $sourceType); } break; case 'csv_metrics': // Matches value in index.html dropdown $fileName = $params['file'] ?? 'key_metrics.csv'; // Default to key_metrics.csv unset($params['source'], $params['file']); // Clean up $data = readCsvFile($fileName); if (isset($data['error'])) { sendErrorJSON("CSV Error: ".$data['error'], 500, $sourceType); } // Ensure the data is an array of objects/cards, not the table structure // The csv_handler for key_metrics.csv should already return it in card format. // If it was a generic CSV handler that returned columns/rows, we'd transform here. break; case 'json_metrics': // New JSON data source $fileName = $params['file'] ?? 'key_metrics.json'; // Default to key_metrics.json unset($params['source'], $params['file']); // Clean up $data = readJsonFile($fileName); if (isset($data['error'])) { sendErrorJSON("JSON Error: ".$data['error'], 500, $sourceType); } break; case 'json_chart': // JSON chart data source $fileName = $params['file'] ?? 'chart_data.json'; // Default to chart_data.json unset($params['source'], $params['file']); // Clean up $data = readJsonFile($fileName); if (isset($data['error'])) { sendErrorJSON("JSON Error: ".$data['error'], 500, $sourceType); } break; default: sendErrorJSON("Unsupported data source type: '$sourceType'. Please check the 'source' parameter.", 400, $sourceType); break; } // Final check if data is null for some other reason (should be caught by handlers) if ($data === null) { sendErrorJSON("No data could be retrieved for the specified source: '$sourceType'. The handler returned null.", 500, $sourceType); } else { // Add source type to the response for debugging or specific client-side handling if needed // $data['_source'] = $sourceType; echo json_encode($data); }