<?php
// backend/json_handler.php
/**
* Reads and processes JSON data files securely.
*
* @param string $fileName The name of the JSON file to read (e.g., 'config.json').
* @return array|null The JSON data decoded as associative array, or error array on failure.
*/
function readJsonFile(string $fileName): ?array
{
error_log("JSON Handler: Called for file '$fileName'");
$baseDataPath = __DIR__.'/../data/';
$filePath = realpath($baseDataPath.$fileName);
// Security check: Ensure the resolved path is within the intended data directory
// Whitelist allowed JSON files
$allowedFiles = [
'key_metrics.json',
'sample_data.json',
'config.json',
'chart_data.json',
'dashboard_config.json'
];
if (!$filePath || strpos($filePath, realpath($baseDataPath)) !== 0 || !in_array(basename($filePath), $allowedFiles)) {
error_log("JSON Handler Error: Invalid, unauthorized or non-existent file path requested: $fileName");
return ['error' => "Invalid or unauthorized file path for JSON: $fileName"];
}
if (!file_exists($filePath) || !is_readable($filePath)) {
// Provide simulation data for key_metrics.json if not found
if ($fileName === 'key_metrics.json') {
error_log("JSON Handler: File '$fileName' not found, providing simulation.");
return [
// Structure expected by displayDataAsCards in script.js
['title' => 'Total Revenue', 'value' => '$'.number_format(rand(50000, 200000)), 'icon' => 'attach_money', 'description' => 'This month'],
['title' => 'New Customers', 'value' => rand(50, 200), 'icon' => 'person_add', 'description' => 'This week'],
['title' => 'Page Views', 'value' => number_format(rand(10000, 50000)), 'icon' => 'visibility', 'description' => 'Past 7 days'],
['title' => 'Bounce Rate', 'value' => rand(20, 60).'%', 'icon' => 'trending_down', 'description' => 'Session average'],
['title' => 'Load Time', 'value' => rand(100, 500).'ms', 'icon' => 'speed', 'description' => 'Average response']
];
}
error_log("JSON Handler Error: File not found or not readable: $filePath");
return ['error' => "JSON file not found or not readable: $fileName"];
}
// Read and decode JSON file
$jsonContent = file_get_contents($filePath);
if ($jsonContent === false) {
error_log("JSON Handler Error: Could not read file: $filePath");
return ['error' => "Could not read JSON file: $fileName"];
}
$data = json_decode($jsonContent, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("JSON Handler Error: Invalid JSON format in file: $filePath - ".json_last_error_msg());
return ['error' => "Invalid JSON format in file: $fileName - ".json_last_error_msg()];
}
// Validate and format data based on the file type
if ($fileName === 'key_metrics.json') {
// Ensure proper format for dashboard cards
if (!is_array($data)) {
return ['error' => "Key metrics JSON must contain an array of metric objects"];
}
$formattedData = [];
foreach ($data as $item) {
if (!is_array($item) || !isset($item['title']) || !isset($item['value'])) {
error_log("JSON Handler Warning: Skipping invalid metric item in $fileName");
continue;
}
$formattedItem = [
'title' => (string) $item['title'],
'value' => (string) $item['value'],
'icon' => $item['icon'] ?? 'info',
'description' => $item['description'] ?? ''
];
$formattedData[] = $formattedItem;
}
return $formattedData;
}
// For chart data or config files, return as-is after basic validation
if (in_array($fileName, ['chart_data.json', 'dashboard_config.json', 'config.json'])) {
// Basic structure validation can be added here if needed
return $data;
}
// For generic JSON files that might be used for tables
if (is_array($data) && !empty($data) && is_array($data[0])) {
return [
'columns' => array_keys($data[0]),
'rows' => $data
];
}
return $data; // Return decoded data
}
/**
* Validates JSON file content against expected structure
*
* @param array $data The decoded JSON data
* @param string $fileName The filename to determine validation rules
* @return bool True if valid, false otherwise
*/
function validateJsonStructure(array $data, string $fileName): bool
{
switch ($fileName) {
case 'key_metrics.json':
if (!is_array($data)) {
return false;
}
foreach ($data as $item) {
if (!is_array($item) || !isset($item['title']) || !isset($item['value'])) {
return false;
}
}
return true;
case 'chart_data.json':
// Basic validation for chart data
return is_array($data) && (isset($data['labels']) || isset($data['datasets']) || isset($data['data']));
case 'dashboard_config.json':
case 'config.json':
// Configuration files should be objects/arrays
return is_array($data);
default:
return true; // Allow other JSON structures
}
}