795 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			795 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable file
		
	
	
	
	
<?php
 | 
						|
require_once __DIR__ . "/session_bootstrap.php";
 | 
						|
 | 
						|
define("ESI_CLIENT_ID", "YOUR-EVE-CLIENT-ID");
 | 
						|
// Replace with your ESI client secret from EVE Developer Portal
 | 
						|
define("ESI_CLIENT_SECRET", "YOUR-EVE-CLIENT-SECRET");
 | 
						|
 | 
						|
function fetch_character_jobs($character_id, &$access_token)
 | 
						|
{
 | 
						|
    // Check cache first
 | 
						|
    $cache_key = "character_jobs_{$character_id}";
 | 
						|
    $cache = get_cache_data($cache_key);
 | 
						|
    
 | 
						|
    if ($cache !== null) {
 | 
						|
        return $cache;
 | 
						|
    }
 | 
						|
 | 
						|
    $esi_url = "https://esi.evetech.net/latest/characters/{$character_id}/industry/jobs/?include_completed=false";
 | 
						|
    $headers = [
 | 
						|
        "Authorization: Bearer $access_token",
 | 
						|
        "Accept: application/json",
 | 
						|
    ];
 | 
						|
 | 
						|
    $response = esi_call($esi_url, $headers);
 | 
						|
 | 
						|
    // Token expired? Try to refresh it.
 | 
						|
    if (
 | 
						|
        $response["http_code"] === 401 &&
 | 
						|
        isset($_SESSION["characters"][$character_id]["refresh_token"])
 | 
						|
    ) {
 | 
						|
        $new_tokens = refresh_token(
 | 
						|
            $_SESSION["characters"][$character_id]["refresh_token"]
 | 
						|
        );
 | 
						|
 | 
						|
        if (!empty($new_tokens["access_token"])) {
 | 
						|
            // Save refreshed token
 | 
						|
            $_SESSION["characters"][$character_id]["access_token"] =
 | 
						|
                $new_tokens["access_token"];
 | 
						|
            $access_token = $new_tokens["access_token"];
 | 
						|
            $headers[0] = "Authorization: Bearer $access_token";
 | 
						|
 | 
						|
            // Retry the API call
 | 
						|
            $response = esi_call($esi_url, $headers);
 | 
						|
        } else {
 | 
						|
            error_log(
 | 
						|
                "ERROR: Token refresh failed for character ID $character_id."
 | 
						|
            );
 | 
						|
            return []; // Avoid crashing page
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Still bad? Bail out
 | 
						|
    if ($response["http_code"] !== 200) {
 | 
						|
        error_log(
 | 
						|
            "ERROR: ESI call failed after token refresh for character ID $character_id. HTTP {$response["http_code"]}"
 | 
						|
        );
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
 | 
						|
    $char_jobs = json_decode($response["body"], true);
 | 
						|
    if (!is_array($char_jobs)) {
 | 
						|
        $char_jobs = [];
 | 
						|
    }
 | 
						|
 | 
						|
    // Skip corporation jobs if no character jobs (faster load)
 | 
						|
    if (empty($char_jobs)) {
 | 
						|
        // Cache empty results for 60 seconds (shorter time for empty results)
 | 
						|
        set_cache_data($cache_key, [], 60);
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
 | 
						|
    // Fetch corporation jobs if possible
 | 
						|
    $corp_jobs = [];
 | 
						|
    $character_info = fetch_character_info($character_id, $access_token);
 | 
						|
    if (isset($character_info["corporation_id"])) {
 | 
						|
        $corp_jobs = fetch_corporation_jobs(
 | 
						|
            $character_info["corporation_id"],
 | 
						|
            $access_token
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
    // Filter out corp jobs where character_id is not in the $_SESSION["characters"]
 | 
						|
    $filtered_corp_jobs = [];
 | 
						|
    foreach ($corp_jobs as $job) {
 | 
						|
        if (
 | 
						|
            isset($job["installer_id"]) &&
 | 
						|
            $job["installer_id"] == $character_id
 | 
						|
        ) {
 | 
						|
            $filtered_corp_jobs[] = $job;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    $all_jobs = array_merge($char_jobs, $filtered_corp_jobs);
 | 
						|
 | 
						|
    // If still no jobs after merging, return empty results
 | 
						|
    if (empty($all_jobs)) {
 | 
						|
        // Cache empty results for 60 seconds (shorter time for empty results)
 | 
						|
        set_cache_data($cache_key, [], 60);
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
 | 
						|
    // Extract IDs and fetch names in batch
 | 
						|
    $blueprint_ids = array_unique(array_column($all_jobs, "blueprint_type_id"));
 | 
						|
    $system_ids = array_unique(array_column($all_jobs, "system_id"));
 | 
						|
    
 | 
						|
    // Fetch names in parallel using promises
 | 
						|
    $blueprint_names = [];
 | 
						|
    $system_names = [];
 | 
						|
    
 | 
						|
    // Optimize by caching character name
 | 
						|
    $character_name = $_SESSION["characters"][$character_id]["name"] ?? "Unknown";
 | 
						|
 | 
						|
    // Pre-define activity mapping
 | 
						|
    $activity_map = [
 | 
						|
        1 => "Manufacturing",
 | 
						|
        3 => "TE Research",
 | 
						|
        4 => "ME Research",
 | 
						|
        5 => "Copying",
 | 
						|
        7 => "Reverse Engineering",
 | 
						|
        8 => "Invention",
 | 
						|
        9 => "Reaction",
 | 
						|
        11 => "Reaction",
 | 
						|
    ];
 | 
						|
 | 
						|
    // Process blueprint and system IDs in smaller batches for better performance
 | 
						|
    if (!empty($blueprint_ids)) {
 | 
						|
        $blueprint_names = fetch_type_names($blueprint_ids);
 | 
						|
    }
 | 
						|
    
 | 
						|
    if (!empty($system_ids)) {
 | 
						|
        $system_names = fetch_system_names($system_ids);
 | 
						|
    }
 | 
						|
 | 
						|
    $results = [];
 | 
						|
    $current_time = time();
 | 
						|
    
 | 
						|
    foreach ($all_jobs as $job) {
 | 
						|
        $start_time = format_time($job["start_date"]);
 | 
						|
        $end_time = isset($job["end_date"])
 | 
						|
            ? format_time($job["end_date"])
 | 
						|
            : "";
 | 
						|
 | 
						|
        $end_timestamp = isset($job["end_date"])
 | 
						|
            ? strtotime($job["end_date"])
 | 
						|
            : null;
 | 
						|
 | 
						|
        $time_left =
 | 
						|
            $end_timestamp && $job["status"] !== "delivered"
 | 
						|
                ? max(0, $end_timestamp - $current_time)
 | 
						|
                : "Completed";
 | 
						|
 | 
						|
        $system_name = $system_names[$job["system_id"]] ?? "Private Structure";
 | 
						|
        $blueprint_name =
 | 
						|
            $blueprint_names[$job["blueprint_type_id"]] ??
 | 
						|
            $job["blueprint_type_id"];
 | 
						|
 | 
						|
        $results[] = [
 | 
						|
            "character" =>
 | 
						|
                $character_name .
 | 
						|
                (isset($job["installer_id"]) &&
 | 
						|
                $job["installer_id"] != $character_id
 | 
						|
                    ? " (Corp)"
 | 
						|
                    : ""),
 | 
						|
            "blueprint" => $blueprint_name,
 | 
						|
            "activity" =>
 | 
						|
                $activity_map[$job["activity_id"]] ??
 | 
						|
                "Activity {$job["activity_id"]}",
 | 
						|
            "status" => $job["status"],
 | 
						|
            "location" => $system_name,
 | 
						|
            "start_time" => $start_time,
 | 
						|
            "end_time" => $end_time,
 | 
						|
            "end_time_unix" => $end_timestamp,
 | 
						|
            "time_left" => is_numeric($time_left)
 | 
						|
                ? gmdate("H:i:s", $time_left)
 | 
						|
                : $time_left,
 | 
						|
        ];
 | 
						|
    }
 | 
						|
 | 
						|
    // Cache results with dynamic TTL based on the nearest job completion
 | 
						|
    $min_time_left = PHP_INT_MAX;
 | 
						|
    foreach ($results as $job) {
 | 
						|
        if (is_numeric($job["end_time_unix"]) && $job["end_time_unix"] > $current_time) {
 | 
						|
            $time_until_completion = $job["end_time_unix"] - $current_time;
 | 
						|
            if ($time_until_completion < $min_time_left) {
 | 
						|
                $min_time_left = $time_until_completion;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Cache until the next job completes (min 60 seconds, max 300 seconds)
 | 
						|
    $cache_ttl = ($min_time_left < PHP_INT_MAX) 
 | 
						|
        ? min(max(60, $min_time_left), 300) 
 | 
						|
        : 300;
 | 
						|
    
 | 
						|
    set_cache_data($cache_key, $results, $cache_ttl);
 | 
						|
    
 | 
						|
    return $results;
 | 
						|
}
 | 
						|
 | 
						|
function fetch_corporation_jobs($corporation_id, $access_token)
 | 
						|
{
 | 
						|
    // Check cache first
 | 
						|
    $cache_key = "corporation_jobs_{$corporation_id}";
 | 
						|
    $cache = get_cache_data($cache_key);
 | 
						|
    
 | 
						|
    if ($cache !== null) {
 | 
						|
        return $cache;
 | 
						|
    }
 | 
						|
 | 
						|
    $esi_url = "https://esi.evetech.net/latest/corporations/{$corporation_id}/industry/jobs/?include_completed=false";
 | 
						|
    $headers = [
 | 
						|
        "Authorization: Bearer $access_token",
 | 
						|
        "Accept: application/json",
 | 
						|
    ];
 | 
						|
 | 
						|
    $response = esi_call($esi_url, $headers);
 | 
						|
 | 
						|
    if ($response["http_code"] !== 200) {
 | 
						|
        error_log("Corp job fetch failed: " . $response["body"]);
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
 | 
						|
    $jobs = json_decode($response["body"], true);
 | 
						|
    $results = is_array($jobs) ? $jobs : [];
 | 
						|
    
 | 
						|
    // Cache the results for 5 minutes
 | 
						|
    set_cache_data($cache_key, $results, 300);
 | 
						|
    
 | 
						|
    return $results;
 | 
						|
}
 | 
						|
 | 
						|
function fetch_character_info($character_id, $access_token)
 | 
						|
{
 | 
						|
    // Check cache first
 | 
						|
    $cache_key = "character_info_{$character_id}";
 | 
						|
    $cache = get_cache_data($cache_key);
 | 
						|
    
 | 
						|
    if ($cache !== null) {
 | 
						|
        return $cache;
 | 
						|
    }
 | 
						|
 | 
						|
    $url = "https://esi.evetech.net/latest/characters/{$character_id}/";
 | 
						|
    $headers = [
 | 
						|
        "Authorization: Bearer $access_token",
 | 
						|
        "Accept: application/json",
 | 
						|
    ];
 | 
						|
 | 
						|
    $response = esi_call($url, $headers);
 | 
						|
    $info = json_decode($response["body"], true);
 | 
						|
    
 | 
						|
    if (is_array($info)) {
 | 
						|
        // Cache character info for 1 hour (rarely changes)
 | 
						|
        set_cache_data($cache_key, $info, 3600);
 | 
						|
    }
 | 
						|
    
 | 
						|
    return $info;
 | 
						|
}
 | 
						|
 | 
						|
function format_time($iso_string)
 | 
						|
{
 | 
						|
    static $cache = [];
 | 
						|
    
 | 
						|
    // Use cached result if available
 | 
						|
    if (isset($cache[$iso_string])) {
 | 
						|
        return $cache[$iso_string];
 | 
						|
    }
 | 
						|
    
 | 
						|
    $dt = DateTime::createFromFormat(DateTime::ATOM, $iso_string);
 | 
						|
    $result = $dt ? $dt->format("M j, Y H:i") : $iso_string;
 | 
						|
    
 | 
						|
    // Cache the result
 | 
						|
    $cache[$iso_string] = $result;
 | 
						|
    
 | 
						|
    return $result;
 | 
						|
}
 | 
						|
 | 
						|
function fetch_type_names($type_ids) {
 | 
						|
    if (empty($type_ids)) {
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
 | 
						|
    // Check if cleanup is needed
 | 
						|
    $last_cleanup = get_last_cleanup_time();
 | 
						|
    $thirty_days_ago = strtotime("-30 days");
 | 
						|
    if (!$last_cleanup || strtotime($last_cleanup) < $thirty_days_ago) {
 | 
						|
        // Call populate_cache.php to refresh the cache
 | 
						|
        $populate_cache_script = __DIR__ . "/populate_cache.php";
 | 
						|
        if (file_exists($populate_cache_script)) {
 | 
						|
            exec("php " . escapeshellarg($populate_cache_script));
 | 
						|
        }
 | 
						|
        update_last_cleanup_time(); // Update the last cleanup timestamp
 | 
						|
    }
 | 
						|
 | 
						|
    // Load cache
 | 
						|
    $cache_file = __DIR__ . "/cache/blueprint_cache.json";
 | 
						|
    $cache = file_exists($cache_file) ? json_decode(file_get_contents($cache_file), true) : [];
 | 
						|
 | 
						|
    // Check cache first
 | 
						|
    $cached_names = [];
 | 
						|
    $ids_to_fetch = [];
 | 
						|
    foreach ($type_ids as $id) {
 | 
						|
        if (isset($cache[$id])) {
 | 
						|
            $cached_names[$id] = $cache[$id];
 | 
						|
        } else {
 | 
						|
            $ids_to_fetch[] = $id;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Fetch missing IDs from API
 | 
						|
    if (!empty($ids_to_fetch)) {
 | 
						|
        $url = "https://esi.evetech.net/latest/universe/names/";
 | 
						|
        $chunks = array_chunk($ids_to_fetch, 1000); // Split into chunks of 1000 IDs
 | 
						|
        foreach ($chunks as $chunk) {
 | 
						|
            $ch = curl_init($url);
 | 
						|
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 | 
						|
            curl_setopt($ch, CURLOPT_POST, true);
 | 
						|
            curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
 | 
						|
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array_values($chunk)));
 | 
						|
            $response = curl_exec($ch);
 | 
						|
            $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 | 
						|
            curl_close($ch);
 | 
						|
 | 
						|
            if ($http_code === 200) {
 | 
						|
                $data = json_decode($response, true);
 | 
						|
                foreach ($data as $entry) {
 | 
						|
                    if (isset($entry["id"], $entry["name"])) {
 | 
						|
                        $cached_names[$entry["id"]] = $entry["name"];
 | 
						|
                        $cache[$entry["id"]] = $entry["name"]; // Update cache
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                error_log("Failed to fetch type names. HTTP Code: $http_code");
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Save updated cache
 | 
						|
    file_put_contents($cache_file, json_encode($cache, JSON_PRETTY_PRINT));
 | 
						|
 | 
						|
    return $cached_names;
 | 
						|
}
 | 
						|
 | 
						|
function populate_blueprint_cache() {
 | 
						|
    echo "Fetching all type IDs...\n";
 | 
						|
    $all_type_ids = get_all_type_ids();
 | 
						|
 | 
						|
    if (empty($all_type_ids)) {
 | 
						|
        echo "No type IDs retrieved.\n";
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    echo "Filtering blueprint IDs...\n";
 | 
						|
    $blueprint_ids = filter_blueprint_ids($all_type_ids);
 | 
						|
 | 
						|
    if (empty($blueprint_ids)) {
 | 
						|
        echo "No blueprint IDs found.\n";
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    echo "Fetching blueprint names...\n";
 | 
						|
    $cached_data = [];
 | 
						|
    $chunks = array_chunk($blueprint_ids, 1000);
 | 
						|
    foreach ($chunks as $chunk) {
 | 
						|
        $batch_names = fetch_type_names($chunk);
 | 
						|
        foreach ($batch_names as $id => $name) {
 | 
						|
            $cached_data[$id] = $name;
 | 
						|
        }
 | 
						|
        usleep(250000);
 | 
						|
    }
 | 
						|
 | 
						|
    // Save to JSON cache file
 | 
						|
    $cache_dir = __DIR__ . "/cache";
 | 
						|
    if (!is_dir($cache_dir)) {
 | 
						|
        mkdir($cache_dir, 0775, true);
 | 
						|
    }
 | 
						|
    $cache_file = $cache_dir . "/blueprint_cache.json";
 | 
						|
    file_put_contents(
 | 
						|
        $cache_file,
 | 
						|
        json_encode($cached_data, JSON_PRETTY_PRINT)
 | 
						|
    );
 | 
						|
 | 
						|
    echo "Cache populated with " . count($cached_data) . " blueprint names.\n";
 | 
						|
}
 | 
						|
 | 
						|
function get_all_type_ids() {
 | 
						|
    $all_ids = [];
 | 
						|
    $page = 1;
 | 
						|
 | 
						|
    do {
 | 
						|
        $url = "https://esi.evetech.net/latest/universe/types/?page=$page";
 | 
						|
        $ch = curl_init($url);
 | 
						|
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 | 
						|
        $response = curl_exec($ch);
 | 
						|
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 | 
						|
        curl_close($ch);
 | 
						|
 | 
						|
        if ($http_code !== 200) {
 | 
						|
            echo "Failed to fetch type IDs on page $page. HTTP Code: $http_code\n";
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        $ids = json_decode($response, true);
 | 
						|
        if (empty($ids)) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        $all_ids = array_merge($all_ids, $ids);
 | 
						|
        $page++;
 | 
						|
        usleep(250000); // Avoid API rate limit
 | 
						|
    } while (true);
 | 
						|
 | 
						|
    return $all_ids;
 | 
						|
}
 | 
						|
 | 
						|
function filter_blueprint_ids($ids) {
 | 
						|
    $blueprint_ids = [];
 | 
						|
    $chunks = array_chunk($ids, 1000);
 | 
						|
 | 
						|
    foreach ($chunks as $chunk) {
 | 
						|
        $names = fetch_type_names($chunk);
 | 
						|
        foreach ($names as $id => $name) {
 | 
						|
            if (str_ends_with($name, "Blueprint")) {
 | 
						|
                $blueprint_ids[] = $id;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        usleep(250000);
 | 
						|
    }
 | 
						|
 | 
						|
    return $blueprint_ids;
 | 
						|
}
 | 
						|
 | 
						|
function fetch_system_names($system_ids)
 | 
						|
{
 | 
						|
    if (empty($system_ids)) {
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Generate a cache key based on the sorted system IDs
 | 
						|
    sort($system_ids);
 | 
						|
    $cache_key = "system_names_" . md5(json_encode($system_ids));
 | 
						|
    $cache = get_cache_data($cache_key);
 | 
						|
    
 | 
						|
    if ($cache !== null) {
 | 
						|
        return $cache;
 | 
						|
    }
 | 
						|
 | 
						|
    // Check if we already have some of these system names cached individually
 | 
						|
    $names = [];
 | 
						|
    $ids_to_fetch = [];
 | 
						|
    
 | 
						|
    foreach ($system_ids as $id) {
 | 
						|
        $individual_cache_key = "system_name_$id";
 | 
						|
        $cached_name = get_cache_data($individual_cache_key);
 | 
						|
        
 | 
						|
        if ($cached_name !== null) {
 | 
						|
            $names[$id] = $cached_name;
 | 
						|
        } else {
 | 
						|
            $ids_to_fetch[] = $id;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    // If we have all system names from individual caches, return them
 | 
						|
    if (empty($ids_to_fetch)) {
 | 
						|
        // Still cache the combined result
 | 
						|
        set_cache_data($cache_key, $names, 86400);
 | 
						|
        return $names;
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Only fetch the IDs we don't already have cached
 | 
						|
    $url = "https://esi.evetech.net/latest/universe/names/";
 | 
						|
    $ch = curl_init($url);
 | 
						|
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 | 
						|
    curl_setopt($ch, CURLOPT_POST, true);
 | 
						|
    curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
 | 
						|
    curl_setopt(
 | 
						|
        $ch,
 | 
						|
        CURLOPT_POSTFIELDS,
 | 
						|
        json_encode(array_values($ids_to_fetch))
 | 
						|
    );
 | 
						|
    $response = curl_exec($ch);
 | 
						|
    curl_close($ch);
 | 
						|
 | 
						|
    $data = json_decode($response, true);
 | 
						|
    
 | 
						|
    if (is_array($data)) {
 | 
						|
        foreach ($data as $entry) {
 | 
						|
            if (isset($entry["id"]) && isset($entry["name"])) {
 | 
						|
                $names[$entry["id"]] = $entry["name"];
 | 
						|
                
 | 
						|
                // Cache individual system names
 | 
						|
                $individual_cache_key = "system_name_{$entry["id"]}";
 | 
						|
                set_cache_data($individual_cache_key, $entry["name"], 86400 * 7); // Cache for a week
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Cache system names for 24 hours (they don't change)
 | 
						|
    set_cache_data($cache_key, $names, 86400);
 | 
						|
    
 | 
						|
    return $names;
 | 
						|
}
 | 
						|
 | 
						|
function refresh_token($refresh_token)
 | 
						|
{
 | 
						|
    // Initialize cURL
 | 
						|
    $ch = curl_init("https://login.eveonline.com/v2/oauth/token");
 | 
						|
 | 
						|
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 | 
						|
    curl_setopt($ch, CURLOPT_POST, true);
 | 
						|
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
 | 
						|
        "Authorization: Basic " . base64_encode(ESI_CLIENT_ID . ":" . ESI_CLIENT_SECRET),
 | 
						|
        "Content-Type: application/x-www-form-urlencoded",
 | 
						|
    ]);
 | 
						|
    curl_setopt(
 | 
						|
        $ch,
 | 
						|
        CURLOPT_POSTFIELDS,
 | 
						|
        http_build_query([
 | 
						|
            "grant_type" => "refresh_token",
 | 
						|
            "refresh_token" => $refresh_token,
 | 
						|
        ])
 | 
						|
    );
 | 
						|
 | 
						|
    // Execute the request
 | 
						|
    $response = curl_exec($ch);
 | 
						|
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); // Get HTTP status code
 | 
						|
    $curl_error = curl_error($ch); // Capture cURL error if any
 | 
						|
    curl_close($ch);
 | 
						|
 | 
						|
    // Handle cURL errors
 | 
						|
    if ($response === false) {
 | 
						|
        error_log("cURL error: $curl_error");
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
 | 
						|
    // Handle HTTP errors
 | 
						|
    if ($http_code !== 200) {
 | 
						|
        error_log("ERROR: Token refresh failed. HTTP $http_code. Response: $response");
 | 
						|
 | 
						|
        // Specific handling for common HTTP errors
 | 
						|
        if ($http_code === 400) {
 | 
						|
            error_log("Bad Request: Check the refresh token or request parameters.");
 | 
						|
        } elseif ($http_code === 401) {
 | 
						|
            error_log("Unauthorized: Check the client ID and secret.");
 | 
						|
        } elseif ($http_code === 429) {
 | 
						|
            error_log("Rate limit exceeded. Retry after delay.");
 | 
						|
        } elseif ($http_code >= 500) {
 | 
						|
            error_log("Server error. Retry after a delay.");
 | 
						|
        }
 | 
						|
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
 | 
						|
    // Decode the response
 | 
						|
    $data = json_decode($response, true);
 | 
						|
 | 
						|
    // Validate the response
 | 
						|
    if (!isset($data["access_token"])) {
 | 
						|
        error_log("ERROR: Token refresh failed. Invalid response: $response");
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
 | 
						|
    // Save the new refresh token if provided
 | 
						|
    if (isset($data["refresh_token"])) {
 | 
						|
        $_SESSION["characters"][$character_id]["refresh_token"] = $data["refresh_token"];
 | 
						|
    }
 | 
						|
 | 
						|
    return $data;
 | 
						|
}
 | 
						|
 | 
						|
function esi_call($url, $headers)
 | 
						|
{
 | 
						|
    $ch = curl_init($url);
 | 
						|
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 | 
						|
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 | 
						|
    curl_setopt($ch, CURLOPT_HEADER, true);
 | 
						|
    $response = curl_exec($ch);
 | 
						|
    $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
 | 
						|
    $body = substr($response, $header_size);
 | 
						|
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 | 
						|
    curl_close($ch);
 | 
						|
 | 
						|
    return [
 | 
						|
        "http_code" => $http_code,
 | 
						|
        "body" => $body,
 | 
						|
    ];
 | 
						|
}
 | 
						|
 | 
						|
function is_token_expired($access_token, $buffer_time = 300)
 | 
						|
{
 | 
						|
    // Split the token into its three parts (header, payload, signature)
 | 
						|
    $parts = explode(".", $access_token);
 | 
						|
 | 
						|
    // Validate that the token has exactly three parts
 | 
						|
    if (count($parts) !== 3) {
 | 
						|
        error_log("Invalid JWT format: $access_token");
 | 
						|
        return true; // Assume expired if the format is invalid
 | 
						|
    }
 | 
						|
 | 
						|
    // Decode the payload (second part of the JWT)
 | 
						|
    $payload = json_decode(base64_decode($parts[1]), true);
 | 
						|
 | 
						|
    // Validate that the payload is a valid JSON object
 | 
						|
    if (!is_array($payload)) {
 | 
						|
        error_log("Malformed JWT payload: " . base64_decode($parts[1]));
 | 
						|
        return true; // Assume expired if the payload is malformed
 | 
						|
    }
 | 
						|
 | 
						|
    // Check if the "exp" (expiration) claim exists
 | 
						|
    if (!isset($payload["exp"])) {
 | 
						|
        error_log("JWT missing 'exp' claim: " . json_encode($payload));
 | 
						|
        return true; // Assume expired if the expiration claim is missing
 | 
						|
    }
 | 
						|
 | 
						|
    // Compare the expiration time with the current time, considering the buffer time
 | 
						|
    // This prevents tokens from expiring during a user session by refreshing them early
 | 
						|
    return $payload["exp"] < (time() + $buffer_time);
 | 
						|
}
 | 
						|
function fetch_character_blueprints($character_id, $access_token)
 | 
						|
{
 | 
						|
    // Check cache first
 | 
						|
    $cache_key = "character_blueprints_{$character_id}";
 | 
						|
    $cache = get_cache_data($cache_key);
 | 
						|
    
 | 
						|
    if ($cache !== null) {
 | 
						|
        return $cache;
 | 
						|
    }
 | 
						|
 | 
						|
    $esi_url = "https://esi.evetech.net/latest/characters/{$character_id}/blueprints/";
 | 
						|
    $headers = [
 | 
						|
        "Authorization: Bearer $access_token",
 | 
						|
        "Accept: application/json",
 | 
						|
    ];
 | 
						|
 | 
						|
    $response = esi_call($esi_url, $headers);
 | 
						|
 | 
						|
    if ($response["http_code"] !== 200) {
 | 
						|
        error_log(
 | 
						|
            "Blueprint fetch failed for character ID $character_id: " .
 | 
						|
                $response["body"]
 | 
						|
        );
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
 | 
						|
    $blueprints = json_decode($response["body"], true);
 | 
						|
    $results = [];
 | 
						|
 | 
						|
    foreach ($blueprints as $bp) {
 | 
						|
        $results[] = [
 | 
						|
            "blueprint_type_id" => $bp["type_id"],
 | 
						|
            "blueprint_name" => $bp["type_id"], // Placeholder, replace with actual name lookup if needed
 | 
						|
            "material_efficiency" => $bp["material_efficiency"],
 | 
						|
            "time_efficiency" => $bp["time_efficiency"],
 | 
						|
            "runs" => $bp["runs"],
 | 
						|
            "quantity" => $bp["quantity"] ?? 1,
 | 
						|
        ];
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Cache the results for 10 minutes (blueprints change less frequently)
 | 
						|
    set_cache_data($cache_key, $results, 600);
 | 
						|
 | 
						|
    return $results;
 | 
						|
}
 | 
						|
function get_cached_name($id) {
 | 
						|
    $cache_file = __DIR__ . "/cache/blueprint_cache.json";
 | 
						|
    if (!file_exists($cache_file)) {
 | 
						|
        return null;
 | 
						|
    }
 | 
						|
 | 
						|
    $cache = json_decode(file_get_contents($cache_file), true);
 | 
						|
    return $cache[$id]["name"] ?? null;
 | 
						|
}
 | 
						|
 | 
						|
function cache_name($id, $name) {
 | 
						|
    $cache_file = __DIR__ . "/cache/blueprint_cache.json";
 | 
						|
    $cache = file_exists($cache_file) ? json_decode(file_get_contents($cache_file), true) : [];
 | 
						|
    $cache[$id] = [
 | 
						|
        "name" => $name,
 | 
						|
        "last_updated" => gmdate("Y-m-d\TH:i:s\Z")
 | 
						|
    ];
 | 
						|
    file_put_contents($cache_file, json_encode($cache, JSON_PRETTY_PRINT));
 | 
						|
}
 | 
						|
 | 
						|
function clean_cache($expiry_days = 30) {
 | 
						|
    $cache_file = __DIR__ . "/cache/blueprint_cache.json";
 | 
						|
    if (!file_exists($cache_file)) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    $cache = json_decode(file_get_contents($cache_file), true);
 | 
						|
    $expiry_time = strtotime("-$expiry_days days");
 | 
						|
 | 
						|
    foreach ($cache as $id => $entry) {
 | 
						|
        if (strtotime($entry["last_updated"]) < $expiry_time) {
 | 
						|
            unset($cache[$id]);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    file_put_contents($cache_file, json_encode($cache, JSON_PRETTY_PRINT));
 | 
						|
}
 | 
						|
function get_last_cleanup_time() {
 | 
						|
    $file = __DIR__ . "/cache/last_cleanup.json";
 | 
						|
    if (!file_exists($file)) {
 | 
						|
        return null;
 | 
						|
    }
 | 
						|
 | 
						|
    $data = json_decode(file_get_contents($file), true);
 | 
						|
    return $data["last_cleanup"] ?? null;
 | 
						|
}
 | 
						|
 | 
						|
function update_last_cleanup_time() {
 | 
						|
    $file = __DIR__ . "/cache/last_cleanup.json";
 | 
						|
    $data = ["last_cleanup" => gmdate("Y-m-d\TH:i:s\Z")];
 | 
						|
    file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT));
 | 
						|
}
 | 
						|
// Cache functions
 | 
						|
function get_cache_data($key) {
 | 
						|
    static $cache_data = null;
 | 
						|
    static $loaded = false;
 | 
						|
    
 | 
						|
    $cache_dir = __DIR__ . "/cache";
 | 
						|
    $cache_file = $cache_dir . "/api_cache.json";
 | 
						|
    
 | 
						|
    // Load cache data only once per request
 | 
						|
    if (!$loaded) {
 | 
						|
        if (file_exists($cache_file)) {
 | 
						|
            $cache_data = json_decode(file_get_contents($cache_file), true) ?: [];
 | 
						|
        } else {
 | 
						|
            $cache_data = [];
 | 
						|
        }
 | 
						|
        $loaded = true;
 | 
						|
    }
 | 
						|
    
 | 
						|
    if (!isset($cache_data[$key]) || $cache_data[$key]['expires'] < time()) {
 | 
						|
        return null;
 | 
						|
    }
 | 
						|
    
 | 
						|
    return $cache_data[$key]['data'];
 | 
						|
}
 | 
						|
 | 
						|
function set_cache_data($key, $data, $ttl = 300) {
 | 
						|
    static $cache_data = null;
 | 
						|
    static $cache_modified = false;
 | 
						|
    static $loaded = false;
 | 
						|
    
 | 
						|
    $cache_dir = __DIR__ . "/cache";
 | 
						|
    
 | 
						|
    if (!is_dir($cache_dir)) {
 | 
						|
        mkdir($cache_dir, 0755, true);
 | 
						|
    }
 | 
						|
    
 | 
						|
    $cache_file = $cache_dir . "/api_cache.json";
 | 
						|
    
 | 
						|
    // Load cache data only once per request
 | 
						|
    if (!$loaded) {
 | 
						|
        if (file_exists($cache_file)) {
 | 
						|
            $cache_data = json_decode(file_get_contents($cache_file), true) ?: [];
 | 
						|
        } else {
 | 
						|
            $cache_data = [];
 | 
						|
        }
 | 
						|
        $loaded = true;
 | 
						|
    }
 | 
						|
    
 | 
						|
    if (!is_array($cache_data)) {
 | 
						|
        $cache_data = [];
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Clean expired cache entries (only occasionally to improve performance)
 | 
						|
    if (rand(1, 10) === 1) {
 | 
						|
        foreach ($cache_data as $cache_key => $cache_entry) {
 | 
						|
            if ($cache_entry['expires'] < time()) {
 | 
						|
                unset($cache_data[$cache_key]);
 | 
						|
                $cache_modified = true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Update cache with new data
 | 
						|
    $cache_data[$key] = [
 | 
						|
        'data' => $data,
 | 
						|
        'expires' => time() + $ttl
 | 
						|
    ];
 | 
						|
    $cache_modified = true;
 | 
						|
    
 | 
						|
    // Use register_shutdown_function to write cache only once at the end of the request
 | 
						|
    if ($cache_modified && !isset($GLOBALS['cache_shutdown_registered'])) {
 | 
						|
        $GLOBALS['cache_shutdown_registered'] = true;
 | 
						|
        register_shutdown_function(function() use ($cache_file) {
 | 
						|
            global $cache_data;
 | 
						|
            if (is_array($cache_data)) {
 | 
						|
                file_put_contents($cache_file, json_encode($cache_data));
 | 
						|
            }
 | 
						|
        });
 | 
						|
    }
 | 
						|
}
 | 
						|
?>
 |