From 27a8d235fd6611bf4535dfd2435221ebfa69d0df Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Wed, 24 Aug 2022 17:29:44 +0200 Subject: [PATCH] eQSL import from multiple QTH nicknames --- application/controllers/Eqsl.php | 255 ++++--------------------- application/libraries/Adif_parser.php | 1 + application/libraries/EqslImporter.php | 197 +++++++++++++++++++ application/models/Logbook_model.php | 12 +- application/models/Stations.php | 12 ++ application/views/eqsl/analysis.php | 50 +++-- 6 files changed, 295 insertions(+), 232 deletions(-) create mode 100644 application/libraries/EqslImporter.php diff --git a/application/controllers/Eqsl.php b/application/controllers/Eqsl.php index b83cb6bb..121d3582 100644 --- a/application/controllers/Eqsl.php +++ b/application/controllers/Eqsl.php @@ -12,257 +12,84 @@ class eqsl extends CI_Controller { if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); } } - private function loadFromFile($filepath) - { - ini_set('memory_limit', '-1'); - set_time_limit(0); - - // Figure out how we should be marking QSLs confirmed via eQSL - $query = $query = $this->db->query('SELECT eqsl_rcvd_mark FROM config'); - $q = $query->row(); - $config['eqsl_rcvd_mark'] = $q->eqsl_rcvd_mark; - - ini_set('memory_limit', '-1'); - set_time_limit(0); - - $this->load->library('adif_parser'); - - $this->adif_parser->load_from_file($filepath); - - $this->adif_parser->initialize(); - - $tableheaders = ""; - $tableheaders .= ""; - $tableheaders .= ""; - $tableheaders .= ""; - $tableheaders .= ""; - $tableheaders .= ""; - $tableheaders .= ""; - $tableheaders .= ""; - $tableheaders .= ""; - $table = ""; - while ($record = $this->adif_parser->get_record()) - { - $time_on = date('Y-m-d', strtotime($record['qso_date'])) ." ".date('H:i', strtotime($record['time_on'])); - - // The report from eQSL should only contain entries that have been confirmed via eQSL - // If there's a match for the QSO from the report in our log, it's confirmed via eQSL. - - // If we have a positive match from LoTW, record it in the DB according to the user's preferences - if ($record['qsl_sent'] == "Y") - { - $record['qsl_sent'] = $config['eqsl_rcvd_mark']; - } - - $status = $this->logbook_model->import_check($time_on, $record['call'], $record['band']); - if ($status == "Found") - { - $dupe = $this->logbook_model->eqsl_dupe_check($time_on, $record['call'], $record['band'], $config['eqsl_rcvd_mark']); - if ($dupe == false) - { - $eqsl_status = $this->logbook_model->eqsl_update($time_on, $record['call'], $record['band'], $config['eqsl_rcvd_mark']); - } - else - { - $eqsl_status = "Already received an eQSL for this QSO."; - } - } - else - { - $eqsl_status = "QSO not found"; - } - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - if (isset($record['submode'])) - { - $table .= ""; - } else { - $table .= ""; - } - $table .= ""; - $table .= ""; - $table .= ""; - } - if ($table != "") - { - $table .= "
DateCallModeSubmodeLog StatuseQSL Status
".$time_on."".str_replace("0","Ø",$record['call'])."".$record['mode']."".$record['submode']."QSO Record: ".$status."eQSL Record: ".$eqsl_status."
"; - $data['eqsl_results_table_headers'] = $tableheaders; - $data['eqsl_results_table'] = $table; - } - - unlink($filepath); - - $data['page_title'] = "eQSL Import Information"; - $this->load->view('interface_assets/header', $data); - $this->load->view('eqsl/analysis'); - $this->load->view('interface_assets/footer'); - } - public function import() { - // Check if eQSL Nicknames have been defined - $this->load->model('stations'); - if($this->stations->are_eqsl_nicks_defined() == 0) { - show_error('eQSL Nicknames in Station Profiles arent defined'); - exit; - } + $this->load->model('stations'); + $eqsl_locations = $this->stations->all_of_user_with_eqsl_nick_defined(); + if($eqsl_locations->num_rows() == 0) { + show_error("eQSL Nicknames in Station Profiles aren't defined"); + exit; + } ini_set('memory_limit', '-1'); set_time_limit(0); - - $data['page_title'] = "eQSL Import"; $config['upload_path'] = './uploads/'; $config['allowed_types'] = 'adi|ADI'; - + $this->load->library('upload', $config); - + $this->load->model('logbook_model'); - + + $eqsl_results = array(); if ($this->input->post('eqslimport') == 'fetch') - { - //echo "import from clublog ADIF
"; - $file = $config['upload_path'] . 'eqslreport_download.adi'; - //echo "
Download File: ".$file."
"; + { + $this->load->library('EqslImporter'); + // Get credentials for eQSL $query = $this->user_model->get_by_id($this->session->userdata('user_id')); $q = $query->row(); - $data['user_eqsl_name'] = $q->user_eqsl_name; - $data['user_eqsl_password'] = $q->user_eqsl_password; + $eqsl_password = $q->user_eqsl_password; - //echo "
Username".$data['user_eqsl_name']."
"; - - // Get URL for downloading the eqsl.cc inbox - $query = $query = $this->db->query('SELECT eqsl_download_url FROM config'); - $q = $query->row(); - $eqsl_url = $q->eqsl_download_url; - - // Validate that LoTW credentials are not empty - if ($data['user_eqsl_name'] == '' || $data['user_eqsl_password'] == '') + // Validate that eQSL credentials are not empty + if ($eqsl_password == '') { - $this->session->set_flashdata('warning', 'You have not defined your eQSL.cc credentials!'); redirect('eqsl/import'); + $this->session->set_flashdata('warning', 'You have not defined your eQSL.cc credentials!'); + redirect('eqsl/import'); } - $this->load->model('stations'); - $active_station_id = $this->stations->find_active(); - $station_profile = $this->stations->profile($active_station_id); - $active_station_info = $station_profile->row(); - // Query the logbook to determine when the last eQSL confirmation was - $eqsl_last_qsl_date = $this->logbook_model->eqsl_last_qsl_rcvd_date(); + foreach ($eqsl_locations->result_array() as $eqsl_location) { + $this->eqslimporter->from_callsign_and_QTH( + $eqsl_location['station_callsign'], + $eqsl_location['eqslqthnickname'], + $config['upload_path'] + ); - // Build parameters for eQSL inbox file - $eqsl_params = http_build_query(array( - 'UserName' => $data['user_eqsl_name'], - 'Password' => $data['user_eqsl_password'], - 'RcvdSince' => $eqsl_last_qsl_date, - 'QTHNickname' => $active_station_info->eqslqthnickname, - 'ConfirmedOnly' => 1 - )); - - //echo "

".$eqsl_url."
".$eqsl_params."

"; - - // At this point, what we get isn't the ADI file we need, but rather - // an HTML page, which contains a link to the generated ADI file that we want. - // Adapted from Original PHP code by Chirp Internet: www.chirp.com.au (regex) - - // Let's use cURL instead of file_get_contents - // begin script - $ch = curl_init(); - - // basic curl options for all requests - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HEADER, 1); - - // use the URL and params we built - curl_setopt($ch, CURLOPT_URL, $eqsl_url); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $eqsl_params); - - $input = curl_exec($ch); - $chi = curl_getinfo($ch); - - // "You have no log entries" -> Nothing else to do here - // "Your ADIF log file has been built" -> We've got an ADIF file we need to grab. - - if ($chi['http_code'] == "200") - { - if (stristr($input, "You have no log entries")) - { - $this->session->set_flashdata('success', 'There are no QSLs waiting for download at eQSL.cc.'); redirect('eqsl/import'); - } - else if (stristr($input, "Error: No such Username/Password found")) - { - $this->session->set_flashdata('warning', 'No such Username/Password found This could mean the wrong callsign or the wrong password, or the user does not exist.'); - redirect('eqsl/import'); - } - else - { - if (stristr($input, "Your ADIF log file has been built")) - { - // Get all the links on the page and grab the URL for the ADI file. - $regexp = "]*href=(\"??)([^\" >]*?)\\1[^>]*>(.*)<\/a>"; - if(preg_match_all("/$regexp/siU", $input, $matches)) { - foreach( $matches[2] as $match ) - { - // Look for the link that has the .adi file, and download it to $file - if (substr($match, -4, 4) == ".adi") - { - - file_put_contents($file, file_get_contents("http://eqsl.cc/qslcard/" . $match)); - ini_set('memory_limit', '-1'); - $this->loadFromFile($file); - break; - } - } - } - } - } + $eqsl_results[] = $this->eqslimporter->fetch($eqsl_password); } - else - { - if ($chi['http_code'] == "500") - { - $this->session->set_flashdata('warning', 'eQSL.cc is experiencing issues. Please try importing QSOs later.'); redirect('eqsl/import'); - } - else - { - if ($chi['http_code'] == "404") - { - $this->session->set_flashdata('warning', 'It seems that the eQSL site has changed. Please open up an issue on GitHub.'); redirect('eqsl/import'); - } - } - } - - - // Close cURL handle - curl_close($ch); - - - - } else { if ( ! $this->upload->do_upload()) { - + $data['page_title'] = "eQSL Import"; $data['error'] = $this->upload->display_errors(); $this->load->view('interface_assets/header', $data); $this->load->view('eqsl/import'); $this->load->view('interface_assets/footer'); + + return; } else { $data = array('upload_data' => $this->upload->data()); - - $this->loadFromFile('./uploads/'.$data['upload_data']['file_name']); + + $this->load->library('EqslImporter'); + $this->eqslimporter->from_file('./uploads/'.$data['upload_data']['file_name']); + + $eqsl_results[] = $this->eqslimporter->import(); } } + + $data['eqsl_results'] = $eqsl_results; + $data['page_title'] = "eQSL Import Information"; + + $this->load->view('interface_assets/header', $data); + $this->load->view('eqsl/analysis'); + $this->load->view('interface_assets/footer'); } // end function - + public function export() { // Check if eQSL Nicknames have been defined $this->load->model('stations'); diff --git a/application/libraries/Adif_parser.php b/application/libraries/Adif_parser.php index 0f920adc..968d19f4 100644 --- a/application/libraries/Adif_parser.php +++ b/application/libraries/Adif_parser.php @@ -111,6 +111,7 @@ class ADIF_Parser }; $this->datasplit = preg_split("//i", mb_substr($this->data, $this->i, NULL, "UTF-8")); + $this->currentarray = 0; return 1; } diff --git a/application/libraries/EqslImporter.php b/application/libraries/EqslImporter.php new file mode 100644 index 00000000..bfd49f8b --- /dev/null +++ b/application/libraries/EqslImporter.php @@ -0,0 +1,197 @@ +CI =& get_instance(); + + $this->CI->load->model('logbook_model'); + $this->CI->load->library('adif_parser'); + } + + private function init($name, $adif_file) { + $this->name = $name; + $this->adif_file = $adif_file; + } + + public function from_callsign_and_QTH($callsign, $qth, $upload_path) { + $this->init( + $qth . " - " . $callsign, + self::safe_filepath($callsign, $qth, $upload_path) + ); + + $this->callsign = $callsign; + $this->qth_nickname = $qth; + } + + public function from_file($adif_file) { + $this->init('ADIF upload', $adif_file); + } + + // generate a sanitized file name from a callsign and a QTH nickname + private static function safe_filepath($callsign, $qth, $upload_path) { + $eqsl_id = $callsign . '-' . $qth; + + // Replace anything which isn't a word, whitespace, number or any of the following caracters -_~,;[](). with a '.' + $eqsl_id = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '.', $eqsl_id); + $eqsl_id = mb_ereg_replace("([\.]{2,})", '', $eqsl_id); + + return $upload_path . $eqsl_id . '-eqslreport_download.adi'; + } + + // Download confirmed QSO from eQSL inbox and import them + public function fetch($password) { + if (empty($password) || empty($this->callsign)) { + $this->status = "Missing username and/or password"; + + return; + } + + // Get URL for downloading the eqsl.cc inbox + $query = $this->CI->db->query('SELECT eqsl_download_url FROM config'); + $q = $query->row(); + $eqsl_url = $q->eqsl_download_url; + + // Query the logbook to determine when the last eQSL confirmation was + $eqsl_last_qsl_date = $this->CI->logbook_model->eqsl_last_qsl_rcvd_date($this->callsign, $this->qth_nickname); + + // Build parameters for eQSL inbox file + $eqsl_params = http_build_query(array( + 'UserName' => $this->callsign, + 'Password' => $password, + 'RcvdSince' => $eqsl_last_qsl_date, + 'QTHNickname' => $this->qth_nickname, + 'ConfirmedOnly' => 1 + )); + + // At this point, what we get isn't the ADI file we need, but rather + // an HTML page, which contains a link to the generated ADI file that we want. + // Adapted from Original PHP code by Chirp Internet: www.chirp.com.au (regex) + + // Let's use cURL instead of file_get_contents + // begin script + $ch = curl_init(); + + // basic curl options for all requests + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 1); + + // use the URL and params we built + curl_setopt($ch, CURLOPT_URL, $eqsl_url); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $eqsl_params); + + $input = curl_exec($ch); + $chi = curl_getinfo($ch); + + // "You have no log entries" -> Nothing else to do here + // "Your ADIF log file has been built" -> We've got an ADIF file we need to grab. + + if ($chi['http_code'] == "200") { + if (stristr($input, "You have no log entries")) { + return $this->result('There are no QSLs waiting for download at eQSL.cc.'); // success + } else if (stristr($input, "Error: No such Username/Password found")) { + return $this->result('No such Username/Password found This could mean the wrong callsign or the wrong password, or the user does not exist.'); // warning + } else { + if (stristr($input, "Your ADIF log file has been built")) { + // Get all the links on the page and grab the URL for the ADI file. + $regexp = "]*href=(\"??)([^\" >]*?)\\1[^>]*>(.*)<\/a>"; + if (preg_match_all("/$regexp/siU", $input, $matches)) { + foreach ($matches[2] as $match) { + // Look for the link that has the .adi file, and download it to $file + if (substr($match, -4, 4) == ".adi") { + file_put_contents($this->adif_file, file_get_contents("http://eqsl.cc/qslcard/" . $match)); + return $this->import(); + } + } + } + } + } + } else { + if ($chi['http_code'] == "500") { + return $this->result('eQSL.cc is experiencing issues. Please try importing QSOs later.'); // warning + } else { + if ($chi['http_code'] == "404") { + return $this->result('It seems that the eQSL site has changed. Please open up an issue on GitHub.'); + } + } + } + + // Close cURL handle + curl_close($ch); + } + + // Read the ADIF file and set QSO confirmation status according to the settings + public function import(): array { + // Figure out how we should be marking QSLs confirmed via eQSL + $query = $this->CI->db->query('SELECT eqsl_rcvd_mark FROM config'); + $q = $query->row(); + $config['eqsl_rcvd_mark'] = $q->eqsl_rcvd_mark; + + $this->CI->adif_parser->load_from_file($this->adif_file); + $this->CI->adif_parser->initialize(); + + $qsos = array(); + $records = $updated = $not_found = $dupes = 0; + while ($record = $this->CI->adif_parser->get_record()) { + $records += 1; + $time_on = date('Y-m-d', strtotime($record['qso_date'])) . " " . date('H:i', strtotime($record['time_on'])); + + // The report from eQSL should only contain entries that have been confirmed via eQSL + // If there's a match for the QSO from the report in our log, it's confirmed via eQSL. + + // If we have a positive match from eQSL, record it in the DB according to the user's preferences + if ($record['qsl_sent'] == "Y") { + $record['qsl_sent'] = $config['eqsl_rcvd_mark']; + } + + $status = $this->CI->logbook_model->import_check($time_on, $record['call'], $record['band']); + if ($status == "Found") { + $dupe = $this->CI->logbook_model->eqsl_dupe_check($time_on, $record['call'], $record['band'], $config['eqsl_rcvd_mark']); + if ($dupe == false) { + $updated += 1; + $eqsl_status = $this->CI->logbook_model->eqsl_update($time_on, $record['call'], $record['band'], $config['eqsl_rcvd_mark']); + } else { + $dupes += 1; + $eqsl_status = "Already received an eQSL for this QSO."; + } + } else { + $not_found += 1; + $eqsl_status = "QSO not found"; + } + + $qsos[] = array( + 'date' => $time_on, + 'call' => str_replace("0", "Ø", $record['call']), + 'mode' => $record['mode'], + 'submode' => $record['submode'] ?? null, + 'status' => $status, + 'eqsl_status' => $eqsl_status, + ); + } + + unlink($this->adif_file); + + return $this->result("$records QSO: $updated updated / $dupes duplicates / $not_found not found", $qsos); + } + + private function result($status, $qsos = array()): array { + return array( + 'name' => $this->name, + 'adif_file' => $this->adif_file, + 'qsos' => $qsos, + 'status' => $status, + ); + } +} diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 94516d8d..b0a5739a 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -2109,13 +2109,21 @@ class Logbook_model extends CI_Model { } // Get the last date we received an eQSL - function eqsl_last_qsl_rcvd_date() { + function eqsl_last_qsl_rcvd_date($callsign, $nickname) { + $qso_table_name = $this->config->item('table_name'); + $this->db->from($qso_table_name); + + $this->db->join('station_profile', + 'station_profile.station_id = '.$qso_table_name.'.station_id AND station_profile.eqslqthnickname != ""'); + $this->db->where('station_profile.station_callsign', $callsign); + $this->db->where('station_profile.eqslqthnickname', $nickname); + $this->db->select("DATE_FORMAT(COL_EQSL_QSLRDATE,'%Y%m%d') AS COL_EQSL_QSLRDATE", FALSE); $this->db->where('COL_EQSL_QSLRDATE IS NOT NULL'); $this->db->order_by("COL_EQSL_QSLRDATE", "desc"); $this->db->limit(1); - $query = $this->db->get($this->config->item('table_name')); + $query = $this->db->get(); $row = $query->row(); if (isset($row->COL_EQSL_QSLRDATE)){ diff --git a/application/models/Stations.php b/application/models/Stations.php index 7dcfc8fc..f5834328 100644 --- a/application/models/Stations.php +++ b/application/models/Stations.php @@ -322,6 +322,18 @@ class Stations extends CI_Model { return $query->num_rows(); } + // Returns all the distinct callsing, eqsl nick pair for the current user + function all_of_user_with_eqsl_nick_defined() { + $this->db->where('user_id', $this->session->userdata('user_id')); + $this->db->where('eqslqthnickname IS NOT NULL'); + $this->db->where('eqslqthnickname !=', ''); + $this->db->from('station_profile'); + $this->db->select('station_callsign, eqslqthnickname'); + $this->db->distinct(TRUE); + + return $this->db->get(); + } + public function check_station_is_accessible($id) { // check if station belongs to user $this->db->select('station_id'); diff --git a/application/views/eqsl/analysis.php b/application/views/eqsl/analysis.php index 0e53941f..af5be872 100644 --- a/application/views/eqsl/analysis.php +++ b/application/views/eqsl/analysis.php @@ -16,22 +16,40 @@ -
- load->view('layout/messages'); ?> +
+ load->view('layout/messages'); ?> -The following QSLs have been received from eQSL.cc

"; - echo $eqsl_results_table_headers; - echo $eqsl_results_table; - } - else - { - echo "

There are no QSO confirmations waiting for you at eQSL.cc

"; - } -?> -
+ +

+ + 0) { ?> + + + + + + + + + + + + + + + + + + + +
DateCallModeSubmodeLog StatuseQSL Status
+ +

There are no QSO confirmations waiting for you at eQSL.cc

+ + +
- \ No newline at end of file +