diff --git a/application/controllers/Qrz.php b/application/controllers/Qrz.php index 9ecfb72b..0d890572 100644 --- a/application/controllers/Qrz.php +++ b/application/controllers/Qrz.php @@ -195,89 +195,237 @@ class Qrz extends CI_Controller { // Query the logbook to determine when the last LoTW confirmation was $qrz_last_date = null; } - $this->download($this->session->userdata('user_id'),$qrz_last_date,true); + $this->download($this->session->userdata('user_id'),true); } // end function - function download($user_id_to_load = null, $lastqrz = null, $show_views = false) { + function download($user_id_to_load = null, $show_views = false) { // Remove $lastqrz parameter $this->load->model('user_model'); $this->load->model('logbook_model'); $api_keys = $this->logbook_model->get_qrz_apikeys(); + $total_processed_count = 0; // Initialize total count here + $data = []; // Initialize data array if ($api_keys) { foreach ($api_keys as $station) { if ((($user_id_to_load != null) && ($user_id_to_load != $station->user_id))) { // Skip User if we're called with a specific user_id continue; - } - if ($lastqrz == null) { - $lastqrz = $this->logbook_model->qrz_last_qsl_date($station->user_id); } + + // Remove the block checking for $lastqrz == null and fetching the date $qrz_api_key = $station->qrzapikey; - $result=($this->mass_download_qsos($qrz_api_key, $lastqrz)); - if (isset($result['tableheaders'])) { - $data['tableheaders']=$result['tableheaders']; - if (isset($data['table'])) { - $data['table'].=$result['table']; - } else { - $data['table']=$result['table']; + $result = $this->mass_download_qsos($qrz_api_key); // mass_download_qsos returns ['table_data' => ..., 'processed_count' => ...] or ['status' => 'error', 'message' => ...] + + + if ($result !== false && isset($result['processed_count'])) { + $total_processed_count += $result['processed_count']; // Accumulate count + $table_data = $result['table_data']; + + if (isset($table_data['tableheaders'])) { + // Ensure headers are set only once + if (!isset($data['tableheaders'])) { + $data['tableheaders'] = $table_data['tableheaders']; + } + if (isset($table_data['table']) && $table_data['table'] != '') { + if (isset($data['table'])) { + $data['table'] .= $table_data['table']; + } else { + $data['table'] = $table_data['table']; + } + } + } + } else if (is_array($result) && isset($result['status']) && $result['status'] === 'error') { + // Handle specific error structure returned by mass_download_qsos + log_message('error', "Error during QRZ download for user_id: " . $station->user_id . ". Message: " . $result['message']); + // Optionally echo error to user if $show_views is true, or add to $data['error'] + if ($show_views) { + $data['errors'][] = "Error for user ID " . $station->user_id . ": " . $result['message']; + } + } else { + // Catch-all for unexpected return values (like the old boolean false or other issues) + log_message('error', "Unexpected error or empty result returned from mass_download_qsos for API key associated with user_id: " . $station->user_id); + if ($show_views) { + $data['errors'][] = "Unexpected error during download for user ID " . $station->user_id . ". Check system logs."; } } } } else { echo "No station profiles with a QRZ API Key found."; log_message('error', "No station profiles with a QRZ API Key found."); + // If no keys, we can exit early if showing views, or just let it fall through if not. + if ($show_views) { + $data['page_title'] = "QRZ ADIF Information"; + $data['error'] = "No station profiles with a QRZ API Key found."; + $this->load->view('interface_assets/header', $data); + $this->load->view('qrz/analysis', $data); // Assuming view can show $error + $this->load->view('interface_assets/footer'); + return; // Stop further processing + } else { + return ''; // Return empty if not showing views and no keys found + } } $this->load->model('user_model'); if ($this->user_model->authorize(2)) { // Only Output results if authorized User - if(isset($data['tableheaders'])) { - if ($data['table'] != '') { - $data['table'].=''; - } - if($show_views == TRUE) { - $data['page_title'] = "QRZ ADIF Information"; - $this->load->view('interface_assets/header', $data); - $this->load->view('qrz/analysis'); + // Pass potential errors to the view + if (isset($data['errors'])) { + $view_data['errors'] = $data['errors']; + } + + $has_matches_to_display = (isset($data['tableheaders']) && isset($data['table']) && $data['table'] != ''); + $message = "Downloaded and processed " . $total_processed_count . " QSOs from QRZ."; + + if ($has_matches_to_display) { + $message .= " Matching QSOs found and updated."; + if ($show_views == TRUE) { + $view_data['tableheaders'] = $data['tableheaders']; + $view_data['table'] = $data['table'] . ''; + $view_data['page_title'] = "QRZ ADIF Information"; + $this->load->view('interface_assets/header', $view_data); + $this->load->view('qrz/analysis', $view_data); // Pass $view_data containing table headers, rows, and errors $this->load->view('interface_assets/footer'); } else { + echo $message; // Echo message when not showing views but matches were found + // Optionally echo errors if any occurred + if (isset($data['errors'])) { + echo " Errors encountered: " . implode("; ", $data['errors']); + } return ''; } } else { - echo "Downloaded QRZ report contains no matches."; + // No matches found in the logbook + $message .= " No matching QSOs found in your logbook to update."; + if ($show_views == TRUE) { + $view_data['page_title'] = "QRZ ADIF Information"; + $view_data['info_message'] = $message; // Pass the info message to the view + // Errors are already in $view_data if they exist + $this->load->view('interface_assets/header', $view_data); + $this->load->view('qrz/analysis', $view_data); // Load view, assuming it checks for $info_message and $errors + $this->load->view('interface_assets/footer'); + } else { + echo $message; // Echo message when not showing views and no matches found + // Optionally echo errors if any occurred + if (isset($data['errors'])) { + echo " Errors encountered: " . implode("; ", $data['errors']); + } + return ''; + } } - } + } // End authorize check } - function mass_download_qsos($qrz_api_key = '', $lastqrz = '1900-01-01', $trusted = false) { + function mass_download_qsos($qrz_api_key = '', $trusted = false) { // Remove $lastqrz parameter $config['upload_path'] = './uploads/'; $file = $config['upload_path'] . 'qrzcom_download_report.adi'; if (file_exists($file) && ! is_writable($file)) { - $result = "Temporary download file ".$file." is not writable. Aborting!"; - return false; + // This part is fine - checks local file writability + $error_message = "Temporary download file ".$file." is not writable. Aborting!"; + // Return the structured error array here too for consistency + return ['status' => 'error', 'message' => $error_message]; } - $url = 'http://logbook.qrz.com/api'; + $url = 'http://logbook.qrz.com/api'; // Correct URL - $post_data['KEY'] = $qrz_api_key; - $post_data['ACTION'] = 'FETCH'; - $post_data['OPTION'] = 'MODSINCE:'.$lastqrz.';STATUS:CONFIRMED;TYPE:ADIF'; + $post_data['KEY'] = $qrz_api_key; // Correct parameter + $post_data['ACTION'] = 'FETCH'; // Correct parameter + $post_data['OPTION'] = 'BAND:80m,TYPE:ADIF'; // Correct parameter for fetching all confirmed in ADIF $ch = curl_init( $url ); - curl_setopt( $ch, CURLOPT_POST, true); - curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_data); - curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt( $ch, CURLOPT_HEADER, 0); - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt( $ch, CURLOPT_POST, true); // Correct method + curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_data); // Correct data + curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1); // Okay + curl_setopt( $ch, CURLOPT_HEADER, 0); // Correct - don't need response headers + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true); // Correct - get response as string - $content = htmlspecialchars_decode(curl_exec($ch)); - file_put_contents($file, $content); - if (strlen(file_get_contents($file, false, null, 0, 100))!=100) { - $result = "QRZ downloading failed, either due to it being down or incorrect logins."; - return "false"; + $content = curl_exec($ch); // Get raw content + $curl_error = curl_error($ch); // Check for cURL errors + curl_close($ch); + + // Find the start of the ADIF data after "ADIF=" + $adif_start_pos = strpos($content, 'ADIF='); + if ($adif_start_pos !== false) { + // Extract the content starting after "ADIF=" + $content = substr($content, $adif_start_pos + 5); + } else { + // If "ADIF=" is not found, check for potential errors before assuming it's just ADIF + if (strpos($content, 'STATUS=FAIL') !== false || strpos($content, 'STATUS=AUTH') !== false) { + // Handle API errors even if ADIF= is missing + $reason = $content; + if (preg_match('/REASON=([^&]+)/', $content, $matches)) { + $reason = urldecode($matches[1]); // Decode URL encoded reason + } + $error_message = "QRZ API Error: " . $reason; + log_message('error', $error_message . ' API Key used: ' . $qrz_api_key . ' Raw Response: ' . $content); + return ['status' => 'error', 'message' => $error_message]; + } + // If no error status and no ADIF=, maybe it's just ADIF? Or an unknown error. + // Log a warning if content seems unusual but doesn't match known error patterns. + if (trim($content) === '' || strlen(trim($content)) < 10) { // Arbitrary small length check + log_message('error', 'QRZ download: Received unexpected content without ADIF= prefix or known error status. Content: ' . $content); + // Decide if this should be treated as an error or empty ADIF + // For now, let's treat it as potentially empty/invalid ADIF and let loadFromFile handle it. + } } + // Also remove the trailing metadata like &RESULT=OK&COUNT=... or just &COUNT=... + $result_pos = strpos($content, '&RESULT='); + $count_pos = strpos($content, '&COUNT='); + + $truncate_pos = false; + + if ($result_pos !== false && $count_pos !== false) { + // Both found, take the earlier one + $truncate_pos = min($result_pos, $count_pos); + } elseif ($result_pos !== false) { + // Only RESULT found + $truncate_pos = $result_pos; + } elseif ($count_pos !== false) { + // Only COUNT found + $truncate_pos = $count_pos; + } + + if ($truncate_pos !== false) { + $content = substr($content, 0, $truncate_pos); + } + + if ($curl_error) { // Check for cURL level errors first + $error_message = "QRZ download cURL error: " . $curl_error; + log_message('error', $error_message . ' API Key used: ' . $qrz_api_key); + return ['status' => 'error', 'message' => $error_message]; + } + + if ($content === false || $content === '') { // Check if curl_exec failed or returned empty + $error_message = "QRZ download failed: No content received from QRZ.com."; + log_message('error', $error_message . ' API Key used: ' . $qrz_api_key); + return ['status' => 'error', 'message' => $error_message]; + } + + // Check for QRZ API specific error messages + if (strpos($content, 'STATUS=FAIL') !== false || strpos($content, 'STATUS=AUTH') !== false) { + // Extract reason if possible, otherwise use full content + $reason = $content; + if (preg_match('/REASON=([^&]+)/', $content, $matches)) { + $reason = urldecode($matches[1]); // Decode URL encoded reason + } + $error_message = "QRZ API Error: " . $reason; + log_message('error', $error_message . ' API Key used: ' . $qrz_api_key . ' Raw Response: ' . $content); + return ['status' => 'error', 'message' => $error_message]; + } + + $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + + // Save the potentially valid content + if (file_put_contents($file, $content) === false) { + $error_message = "Failed to write downloaded QRZ data to temporary file: " . $file; + log_message('error', $error_message); + return ['status' => 'error', 'message' => $error_message]; + } else { + // echo "Downloaded QRZ data to temporary file: " . $file; + } + + // Proceed to load from the file ini_set('memory_limit', '-1'); - $result = $this->loadFromFile($file); + $result = $this->loadFromFile($file); // loadFromFile returns ['table_data' => ..., 'processed_count' => ...] return $result; } @@ -302,9 +450,17 @@ class Qrz extends CI_Controller { $this->load->library('adif_parser'); - $this->adif_parser->load_from_file($filepath); + // Load the data from the file into the parser object + $this->adif_parser->load_from_file($filepath); // <-- ADD THIS LINE + + // Now initialize the parser with the loaded data + if (!$this->adif_parser->initialize()) { // Check return value of initialize + // Handle initialization error (e.g., log it, return error structure) + log_message('error', 'ADIF Parser initialization failed for file: ' . $filepath); + // Return an error structure consistent with mass_download_qsos + return ['status' => 'error', 'message' => 'ADIF Parser initialization failed. Check logs.']; + } - $this->adif_parser->initialize(); $tableheaders = "
| Station Callsign | "; @@ -317,53 +473,51 @@ class Qrz extends CI_Controller { $tableheaders .= "||||||
| ".$record['station_callsign']." | "; - $table .= "".$time_on." | "; - $table .= "".$record['call']." | "; - $table .= "".$record['mode']." | "; - $table .= "".$record['qsl_rcvd']." | "; - $table .= "".$qsl_date." | "; - $table .= "QSO Record: ".$status[0]." | "; - $table .= "
| ".$record['station_callsign']." | "; - $table .= "".$time_on." | "; - $table .= "".$record['call']." | "; - $table .= "".$record['mode']." | "; - $table .= "".$record['qsl_rcvd']." | "; - $table .= "QSO Record: ".$status[0]." | "; - $table .= "|
| " . $record['station_callsign'] . " | "; + $table .= "" . $record['time_on'] . " | "; + $table .= "" . $record['call'] . " | "; + $table .= "" . $record['mode'] . " | "; + $table .= "" . $record['qsl_date'] . " | "; + $table .= "" . ($record['qsl_rcvd'] == 'Y' ? 'Yes' : 'No') . " | "; + $table .= "" . $log_status . " | "; + $table .= "