"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)); } }); } } ?>