json_handler.php

5.35 KB
10/07/2025 03:07
PHP
json_handler.php
<?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
    }
}