From 5955e31b6d01f76df1b1b7f101484413da7d483d Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 24 Apr 2025 10:03:44 +0000 Subject: [PATCH 01/49] DCL 1st step --- application/controllers/Dcl.php | 1040 +++++++++++++++++ application/models/Dcl_model.php | 83 ++ .../dcl_views/adif_views/adif_export.php | 172 +++ application/views/dcl_views/index.php | 92 ++ application/views/dcl_views/upload_cert.php | 44 + 5 files changed, 1431 insertions(+) create mode 100644 application/controllers/Dcl.php create mode 100644 application/models/Dcl_model.php create mode 100644 application/views/dcl_views/adif_views/adif_export.php create mode 100644 application/views/dcl_views/index.php create mode 100644 application/views/dcl_views/upload_cert.php diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php new file mode 100644 index 000000000..515f76f23 --- /dev/null +++ b/application/controllers/Dcl.php @@ -0,0 +1,1040 @@ +load->helper(array('form', 'url')); + + if (ENVIRONMENT == 'maintenance' && $this->session->userdata('user_id') == '') { + echo __("Maintenance Mode is active. Try again later.")."\n"; + redirect('user/login'); + } + } + + public function index() { + $this->load->library('Permissions'); + $this->load->model('user_model'); + if(!$this->user_model->authorize(2) || !clubaccess_check(9)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } + + // Load required models for page generation + $this->load->model('Dcl_model'); + + // Get Array of the logged in users LoTW certs. + $dclkeys=($this->Dcl_model->dcl_keys($this->session->userdata('user_id')) ?? ''); + $i=0; + foreach ($dclkeys as $dclkey) { + $data['dcl_keys'][$i] = json_decode($dclkey->option_value ?? ''); + $data['dcl_keys'][$i]->call = $dclkey->option_key ?? ''; + $i++; + } + + // Set Page Title + $data['page_title'] = __("DCL"); + + $this->load->model('cron_model'); + $data['next_run'] = $this->cron_model->get_next_run("dcl_dcl_upload"); + + // Load Views + $this->load->view('interface_assets/header', $data); + $this->load->view('dcl_views/index'); + $this->load->view('interface_assets/footer'); + } + + /* + |-------------------------------------------------------------------------- + | Function: cert_upload + |-------------------------------------------------------------------------- + | + | Nothing fancy just shows the cert_upload form for uploading p12 files + | + */ + public function cert_upload() { + $this->load->model('user_model'); + $this->load->model('dxcc'); + if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } + + // Load DXCC Countrys List + $data['dxcc_list'] = $this->dxcc->list(); + + // Set Page Title + $data['page_title'] = __("Logbook of the World"); + + // Load Views + $this->load->view('interface_assets/header', $data); + $this->load->view('lotw_views/upload_cert', array('error' => ' ' )); + $this->load->view('interface_assets/footer'); + } + + + /* + |-------------------------------------------------------------------------- + | Function: lotw_upload + |-------------------------------------------------------------------------- + | + | This function Uploads to LoTW + | + */ + public function lotw_upload() { + + $this->load->model('user_model'); + $this->user_model->authorize(2); + + // set the last run in cron table for the correct cron id + $this->load->model('cron_model'); + $this->cron_model->set_last_run($this->router->class.'_'.$this->router->method); + + // Get Station Profile Data + $this->load->model('Stations'); + + if ($this->user_model->authorize(2)) { + if (!($this->config->item('disable_manual_lotw'))) { + $station_profiles = $this->Stations->all_of_user($this->session->userdata('user_id')); + $sync_user_id=$this->session->userdata('user_id'); + } else { + echo "Manual syncing is disabled by configuration"; + redirect('dashboard'); + exit(); + } + } else { + $station_profiles = $this->Stations->all(); + $sync_user_id=null; + } + + // Array of QSO IDs being Uploaded + + $qso_id_array = array(); + + // Build TQ8 Outputs + if ($station_profiles->num_rows() >= 1) { + + foreach ($station_profiles->result() as $station_profile) { + + // Get Certificate Data + $this->load->model('Lotw_model'); + $data['station_profile'] = $station_profile; + $data['lotw_cert_info'] = $this->Lotw_model->lotw_cert_details($station_profile->station_callsign, $station_profile->station_dxcc, $station_profile->user_id); + + // If Station Profile has no LoTW Cert continue on. + if(!isset($data['lotw_cert_info']->cert_dxcc_id)) { + echo $station_profile->station_callsign.": No LoTW certificate for station callsign found.
"; + continue; + } + + // Check if LoTW certificate itself is valid + // Validty of QSO dates will be checked later + $current_date = date('Y-m-d H:i:s'); + if ($current_date <= $data['lotw_cert_info']->date_created) { + echo $data['lotw_cert_info']->callsign.": LoTW certificate not valid yet!"; + continue; + } + if ($current_date >= $data['lotw_cert_info']->date_expires) { + echo $data['lotw_cert_info']->callsign.": LoTW certificate expired!"; + continue; + } + + // Get QSOs + + $this->load->model('Logbook_model'); + + // First mark QSOs with unsupported propagation modes as ignore + $this->Logbook_model->mark_lotw_ignore($data['station_profile']->station_id); + + $data['qsos'] = $this->Logbook_model->get_lotw_qsos_to_upload($data['station_profile']->station_id, $data['lotw_cert_info']->qso_start_date, $data['lotw_cert_info']->qso_end_date); + + // Nothing to upload + if(empty($data['qsos']->result())){ + if ($this->user_model->authorize(2)) { // Only be verbose if we have a session + echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): No QSOs to upload.
"; + } + continue; + } + + foreach ($data['qsos']->result() as $temp_qso) { + array_push($qso_id_array, $temp_qso->COL_PRIMARY_KEY); + } + + // Build File to save + $adif_to_save = $this->load->view('lotw_views/adif_views/adif_export', $data, TRUE); + if (strpos($adif_to_save, '')) { + // Signing failed + echo "Signing failed."; + continue; + } + + // create folder to store upload file + if (!file_exists('./uploads/lotw')) { + mkdir('./uploads/lotw', 0775, true); + } + + // Build Filename + $filename_for_saving = './uploads/lotw/'.preg_replace('/[^a-z0-9]+/', '-', strtolower($data['lotw_cert_info']->callsign))."-".date("Y-m-d-H-i-s")."-wavelog.tq8"; + + $gzdata = gzencode($adif_to_save, 9); + $fp = fopen($filename_for_saving, "w"); + fwrite($fp, $gzdata); + fclose($fp); + + //The URL that accepts the file upload. + $url = 'https://lotw.arrl.org/lotw/upload'; + + //The name of the field for the uploaded file. + $uploadFieldName = 'upfile'; + + //The full path to the file that you want to upload + $filePath = realpath($filename_for_saving); + + //Initiate cURL + $ch = curl_init(); + + //Set the URL + curl_setopt($ch, CURLOPT_URL, $url); + + //Set the HTTP request to POST + curl_setopt($ch, CURLOPT_POST, true); + + //Tell cURL to return the output as a string. + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + //Use the recommended way, creating a CURLFile object. + $uploadfile = curl_file_create($filePath); + $uploadfile->setPostFilename(basename($filePath)); + + //Setup our POST fields + $postFields = array( + $uploadFieldName => $uploadfile + ); + + curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); + + //Execute the request + $result = curl_exec($ch); + + if(curl_errno($ch)){ + echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Failed - ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")
"; + $this->Lotw_model->last_upload($data['lotw_cert_info']->lotw_cert_id, "Upload failed"); + if (curl_errno($ch) == 28) { // break on timeout + echo "Timeout reached. Stopping subsequent uploads.
"; + break; + } else { + continue; + } + } + + $pos = strpos($result, ""); + + if ($pos === false) { + // Upload of TQ8 Failed for unknown reason + echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Failed - ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")
"; + $this->Lotw_model->last_upload($data['lotw_cert_info']->lotw_cert_id, "Upload failed"); + if (curl_errno($ch) == 28) { // break on timeout + echo "Timeout reached. Stopping subsequent uploads.
"; + break; + } else { + continue; + } + } else { + // Upload of TQ8 was successfull + + echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Successful - ".$filename_for_saving."
"; + + $this->Lotw_model->last_upload($data['lotw_cert_info']->lotw_cert_id, "Success"); + + // Mark QSOs as Sent + foreach ($qso_id_array as $qso_number) { + $this->Logbook_model->mark_lotw_sent($qso_number); + } + } + + // Delete TQ8 File - This is done regardless of whether upload was succcessful + unlink(realpath($filename_for_saving)); + } + } else { + echo "No Station Profiles found to upload to LoTW"; + } + + /* + | Download QSO Matches from LoTW + */ + if ($this->user_model->authorize(2)) { + echo "

"; + $sync_user_id=$this->session->userdata('user_id'); + } else { + $sync_user_id=null; + } + echo $this->lotw_download($sync_user_id); + } + + public function delete_key($call) { + $call=str_replace('_','/',xss_clean($call)); + $this->load->model('user_model'); + if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } + $this->load->model('Dcl_model'); + $this->Dcl_model->delete_key($call); + $this->session->set_flashdata('success', __("Key Deleted.")); + redirect('dcl'); + } + + + /* + |-------------------------------------------------------------------------- + | Function: loadFromFile + |-------------------------------------------------------------------------- + | + | $filepath is the ADIF file, $display_view is used to hide the output if its internal script + | + | Internal function that takes the LoTW ADIF and imports into the log + | + */ + private function loadFromFile($filepath, $station_ids, $display_view = "TRUE") { + + // Figure out how we should be marking QSLs confirmed via LoTW + $query = $this->db->query('SELECT lotw_rcvd_mark FROM config'); + $q = $query->row(); + $config['lotw_rcvd_mark'] = $q->lotw_rcvd_mark; + + ini_set('memory_limit', '-1'); + set_time_limit(0); + + if (!$this->load->is_loaded('adif_parser')) { + $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 .= ""; + $tableheaders .= ""; + $tableheaders .= ""; + $tableheaders .= ""; + $tableheaders .= ""; + $tableheaders .= ""; + + $table = ""; + while($record = $this->adif_parser->get_record()) { + // Check for LoTW confirmation in ADIF record and skip if not existent + if (!isset($record['app_lotw_rxqsl'])) { + continue; + } + if (($record['call'] ?? '') == '') { // Failsafe if no call is given + continue; + } + if (($record['station_callsign'] ?? '') == '') { // Failsafe if no station_callsign is given + continue; + } + $time_on = date('Y-m-d', strtotime($record['qso_date'])) ." ".date('H:i', strtotime($record['time_on'])); + + $qsl_date = date('Y-m-d H:i', strtotime($record['app_lotw_rxqsl'])); + + if (isset($record['time_off'])) { + $time_off = date('Y-m-d', strtotime($record['qso_date'])) ." ".date('H:i', strtotime($record['time_off'])); + } else { + $time_off = date('Y-m-d', strtotime($record['qso_date'])) ." ".date('H:i', strtotime($record['time_on'])); + } + + // If we have a positive match from LoTW, record it in the DB according to the user's preferences + if ($record['qsl_rcvd'] == "Y") + { + $record['qsl_rcvd'] = $config['lotw_rcvd_mark']; + } + + // SAT-Name not given? Create array-key and fill with null + if (!(array_key_exists('sat_name', $record))) { + $record['sat_name']=null; + } + + // Prop-Mode not given? Create array-key and fill with null + if (!(array_key_exists('prop_mode', $record))) { + $record['prop_mode']=null; + } + + $status = $this->logbook_model->import_check($time_on, $record['call'], $record['band'], $record['mode'], $record['prop_mode'], $record['sat_name'], $record['station_callsign'], $station_ids); + + if($status[0] == "Found") { + $qso_id4lotw=$status[1]; + if (isset($record['state'])) { + $state = $record['state']; + } else { + $state = ""; + } + // Present only if the QSLing station specified a single valid grid square value in its station location uploaded to LoTW. + $qsl_gridsquare = ""; + if (isset($record['gridsquare'])) { + if (strlen($record['gridsquare']) > strlen($status[2] ?? '') || substr(strtoupper($status[2] ?? ''), 0, 4) != substr(strtoupper($record['gridsquare']), 0, 4)) { + $qsl_gridsquare = $record['gridsquare']; + } + } + + $ant_path = $status[3] ?? ''; + + if (isset($record['vucc_grids'])) { + $qsl_vucc_grids = $record['vucc_grids']; + } else { + $qsl_vucc_grids = ""; + } + + if (isset($record['iota'])) { + $iota = $record['iota']; + } else { + $iota = ""; + } + + if (isset($record['cnty'])) { + $cnty = $record['cnty']; + } else { + $cnty = ""; + } + + if (isset($record['cqz'])) { + $cqz = $record['cqz']; + } else { + $cqz = ""; + } + + if (isset($record['ituz'])) { + $ituz = $record['ituz']; + } else { + $ituz = ""; + } + + if (isset($record['dxcc'])) { + $dxcc = $record['dxcc']; + } else { + $dxcc = ""; + } + + if (isset($record['country'])) { + $country = $record['country']; + } else { + $country = ""; + } + + $lotw_status = $this->logbook_model->lotw_update($time_on, $record['call'], $record['band'], $qsl_date, $record['qsl_rcvd'], $state, $qsl_gridsquare, $qsl_vucc_grids, $iota, $cnty, $cqz, $ituz, $record['station_callsign'],$qso_id4lotw, $station_ids, $dxcc, $country, $ant_path); + + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + } else { + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + $table .= ""; + } + } + + if ($table != "") { + $table .= "
Station CallsignQSO DateCallModeLoTW QSL ReceivedDate LoTW ConfirmedStateGridsquareIOTALog StatusLoTW Status
".$record['station_callsign']."".$time_on."".$record['call']."".$record['mode']."".$record['qsl_rcvd']."".$qsl_date."".$state."".($qsl_gridsquare != '' ? $qsl_gridsquare : $qsl_vucc_grids)."".$iota."QSO Record: ".$status[0]."LoTW Record: ".$lotw_status."
".$record['station_callsign']."".$time_on."".$record['call']."".$record['mode']."".$record['qsl_rcvd']."QSO Record: ".$status[0]."
"; + $data['lotw_table_headers'] = $tableheaders; + $data['lotw_table'] = $table; + } + + unlink($filepath); + + $this->load->model('user_model'); + if ($this->user_model->authorize(2)) { // Only Output results if authorized User + if(isset($data['lotw_table_headers'])) { + if($display_view == TRUE) { + $data['page_title'] = __("LoTW ADIF Information"); + $this->load->view('interface_assets/header', $data); + $this->load->view('lotw/analysis'); + $this->load->view('interface_assets/footer'); + } else { + return $tableheaders.$table; + } + } else { + echo "Downloaded LoTW report contains no matches."; + } + } + } + + /* + |-------------------------------------------------------------------------- + | Function: lotw_download + |-------------------------------------------------------------------------- + | + | Collects users with LoTW usernames and passwords and runs through them + | downloading matching QSOs. + | + */ + function lotw_download($sync_user_id = null) { + $this->load->model('user_model'); + $this->load->model('logbook_model'); + $this->load->model('Stations'); + + $query = $this->user_model->get_all_lotw_users(); + + if ($query->num_rows() >= 1) { + $result = ''; + + // Get URL for downloading LoTW + $url_query = $this->db->query('SELECT lotw_download_url FROM config'); + $q = $url_query->row(); + $lotw_base_url = $q->lotw_download_url; + + foreach ($query->result() as $user) { + if ( ($sync_user_id != null) && ($sync_user_id != $user->user_id) ) { continue; } + $station_ids=$this->Stations->all_station_ids_of_user($user->user_id); + if ($station_ids == '') { continue; } // User has no Station-ID! next one + + // Validate that LoTW credentials are not empty + // TODO: We don't actually see the error message + if ($user->user_lotw_password == '') { + $result = "You have not defined your ARRL LoTW credentials!"; + continue; + } + + $config['upload_path'] = './uploads/'; + $file = $config['upload_path'] . 'lotwreport_download_'.$user->user_id.'_auto.adi'; + if (file_exists($file) && ! is_writable($file)) { + $result = "Temporary download file ".$file." is not writable. Aborting!"; + continue; + } + + // Get credentials for LoTW + $data['user_lotw_name'] = urlencode($user->user_lotw_name); + $data['user_lotw_password'] = urlencode($user->user_lotw_password); + + $lotw_last_qsl_date = date('Y-m-d', strtotime($this->logbook_model->lotw_last_qsl_date($user->user_id))); + + // Build URL for LoTW report file + $lotw_url = $lotw_base_url."?"; + $lotw_url .= "login=" . $data['user_lotw_name']; + $lotw_url .= "&password=" . $data['user_lotw_password']; + $lotw_url .= "&qso_query=1&qso_qsl='yes'&qso_qsldetail='yes'&qso_mydetail='yes'"; + + $lotw_url .= "&qso_qslsince="; + $lotw_url .= "$lotw_last_qsl_date"; + + if (! is_writable(dirname($file))) { + $result = "Temporary download directory ".dirname($file)." is not writable. Aborting!"; + continue; + } + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $lotw_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); + $content = curl_exec($ch); + if(curl_errno($ch)) { + $result = "LoTW download failed for user ".$data['user_lotw_name'].": ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")."; + if (curl_errno($ch) == 28) { // break on timeout + $result .= "
Timeout reached. Stopping subsequent downloads."; + break; + } + continue; + } else if(str_contains($content,"Username/password incorrect")) { + $result = "LoTW download failed for user ".$data['user_lotw_name'].": Username/password incorrect"; + continue; + } + file_put_contents($file, $content); + if (file_get_contents($file, false, null, 0, 39) != "ARRL Logbook of the World Status Report") { + $result = "Downloaded LoTW report for user ".$data['user_lotw_name']." is invalid. Check your credentials."; + continue; + } + + ini_set('memory_limit', '-1'); + $result = $this->loadFromFile($file, $station_ids, false); + } + return $result; + } else { + return "No LoTW User details found to carry out matches."; + } + } + + public function check_lotw_credentials () { + $this->load->model('user_model'); + if(!$this->user_model->authorize(2)) { + $this->session->set_flashdata('error', __("You're not allowed to do that!")); + redirect('dashboard'); + exit(); + } + $ret=[]; + $ret['status']=''; + + + $raw = file_get_contents("php://input"); + try { + $obj = json_decode($raw,true); + } catch (Exception $e) { + $ret['status']='failed_wrongcall'; + log_message("Error",$ret['status']); + } finally { + $lotw_user=$obj['lotw_user'] ?? ''; + $lotw_pass=$obj['lotw_pass'] ?? ''; + } + $raw=''; + + $pw_placeholder = '**********'; + if ($lotw_pass == $pw_placeholder) { // User comes with unaltered credentials - take them from database + $query = $this->user_model->get_by_id($this->session->userdata('user_id')); + $q = $query->row(); + $data['user_lotw_name'] = urlencode($q->user_lotw_name ?? ''); + $data['user_lotw_password'] = urlencode($q->user_lotw_password ?? ''); + } else { + $data['user_lotw_name'] = urlencode($lotw_user ?? ''); + $data['user_lotw_password'] = urlencode($lotw_pass ?? ''); + } + + if ((($data['user_lotw_name'] ?? '') != '') && (($data['user_lotw_password'] ?? '') != '') && ($ret['status'] != 'failed_wrongcall')) { + + // Get URL for downloading LoTW + $query = $query = $this->db->query('SELECT lotw_login_url FROM config'); + $q = $query->row(); + $lotw_url = $q->lotw_login_url; + + // Validate that LoTW credentials are not empty + // TODO: We don't actually see the error message + if ($data['user_lotw_name'] == '' || $data['user_lotw_password'] == '') { + $ret='No Creds set'; + } + + // Build URL for LoTW report file + $lotw_url .= "?"; + $lotw_url .= "login=" . $data['user_lotw_name']; + $lotw_url .= "&password=" . $data['user_lotw_password']; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $lotw_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); + $content = curl_exec($ch); + if ($content) { + if(curl_errno($ch)) { + $ret['status']='failed'; + $ret['details']== sprintf(__("LoTW login failed for user %s: %s."), $data['user_lotw_name'], curl_strerror(curl_errno($ch))." (".curl_errno($ch).")"); + } else if (str_contains($content,"Username/password incorrect")) { + $ret['status']='failed_wrong_creds'; + $ret['details']= sprintf(__("LoTW login failed for user %s: %s."), $data['user_lotw_name'], __("Username/password incorrect")); + } else { + $ret['status']='OK'; + $ret['details']= __("LoTW login OK!"); + } + } else { + $ret['status']='failed_na'; + $ret['details']= __("LoTW currently not available. Try again later."); + } + } else { + if (($ret['status'] ?? '') == '') { + $ret['status']='failed_nocred'; + $ret['details']= __("No LoTW credentials provided."); + } + } + header("Content-type: application/json"); + echo json_encode($ret); + return $ret; + } + + public function import() { // Is only called via frontend. Cron uses "upload". within download the download is called + $this->load->model('user_model'); + $this->load->model('Stations'); + if(!$this->user_model->authorize(2)) { + $this->session->set_flashdata('error', __("You're not allowed to do that!")); + redirect('dashboard'); + exit(); + } + + $station_ids=$this->Stations->all_station_ids_of_user($this->session->userdata['user_id']); + $data['page_title'] = __("LoTW ADIF Import"); + + $config['upload_path'] = './uploads/'; + $config['allowed_types'] = 'adi|ADI'; + + $this->load->library('upload', $config); + + $this->load->model('logbook_model'); + + if (($this->input->post('lotwimport') == 'fetch') && (!($this->config->item('disable_manual_lotw')))) { + $file = $config['upload_path'] . 'lotwreport_download_'.$this->session->userdata('user_id').'.adi'; + + // Get credentials for LoTW + $query = $this->user_model->get_by_id($this->session->userdata('user_id')); + $q = $query->row(); + $data['user_lotw_name'] = urlencode($q->user_lotw_name ?? ''); + $data['user_lotw_password'] = urlencode($q->user_lotw_password ?? ''); + + // Get URL for downloading LoTW + $query = $query = $this->db->query('SELECT lotw_download_url FROM config'); + $q = $query->row(); + $lotw_url = $q->lotw_download_url; + + // Validate that LoTW credentials are not empty + // TODO: We don't actually see the error message + if ($data['user_lotw_name'] == '' || $data['user_lotw_password'] == '') { + $this->session->set_flashdata('warning', __("You have not defined your ARRL LoTW credentials!")); redirect('lotw/import'); + } + + $customDate = $this->input->post('from'); + + if ($customDate != NULL) { + $lotw_last_qsl_date = date($customDate); + } else { + // Query the logbook to determine when the last LoTW confirmation was + $lotw_last_qsl_date = date('Y-m-d', strtotime($this->logbook_model->lotw_last_qsl_date($this->session->userdata['user_id']))); + } + + // Build URL for LoTW report file + $lotw_url .= "?"; + $lotw_url .= "login=" . $data['user_lotw_name']; + $lotw_url .= "&password=" . $data['user_lotw_password']; + $lotw_url .= "&qso_query=1&qso_qsl='yes'&qso_qsldetail='yes'&qso_mydetail='yes'"; + + $lotw_url .= "&qso_qslsince="; + $lotw_url .= "$lotw_last_qsl_date"; + + if ($this->input->post('callsign') != '0') { + $lotw_url .= "&qso_owncall=".$this->input->post('callsign'); + } + + if (is_writable(dirname($file)) && (!file_exists($file) || is_writable($file))) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $lotw_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); + $content = curl_exec($ch); + if(curl_errno($ch)) { + print "LoTW download failed for user ".$data['user_lotw_name'].": ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")."; + } else if (str_contains($content,"Username/password incorrect")) { + print "LoTW download failed for user ".$data['user_lotw_name'].": Username/password incorrect"; + } else { + file_put_contents($file, $content); + ini_set('memory_limit', '-1'); + $this->loadFromFile($file, $station_ids); + } + } else { + if (!is_writable(dirname($file))) { + $data['errormsg'] = 'Directory '.dirname($file).' is not writable!'; + } else if (!is_writable($file)) { + $data['errormsg'] = 'File '.$file.' is not writable!'; + } + $this->load->model('Stations'); + $data['callsigns'] = $this->Stations->callsigns_of_user($this->session->userdata('user_id')); + + $this->load->view('interface_assets/header', $data); + $this->load->view('lotw/import', $data); + $this->load->view('interface_assets/footer'); + } + } else { + if (!$this->upload->do_upload()) { + + $data['error'] = $this->upload->display_errors(); + $this->load->model('Stations'); + $data['callsigns'] = $this->Stations->callsigns_of_user($this->session->userdata('user_id')); + + $this->load->view('interface_assets/header', $data); + $this->load->view('lotw/import', $data); + $this->load->view('interface_assets/footer'); + } else { + $data = array('upload_data' => $this->upload->data()); + + $this->loadFromFile('./uploads/'.$data['upload_data']['file_name'], $station_ids); + } + } + } // end function + + public function export() { + $this->load->model('user_model'); + if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } + + $data['page_title'] = __("LoTW .TQ8 Upload"); + + $config['upload_path'] = './uploads/'; + $config['allowed_types'] = 'tq8|TQ8'; + + $this->load->library('upload', $config); + + if ( ! $this->upload->do_upload()) + { + $data['error'] = $this->upload->display_errors(); + + $this->load->view('interface_assets/header', $data); + $this->load->view('lotw/export'); + $this->load->view('interface_assets/footer'); + } + else + { + $data = array('upload_data' => $this->upload->data()); + + // Figure out how we should be marking QSLs confirmed via LoTW + $query = $query = $this->db->query('SELECT lotw_login_url FROM config'); + $q = $query->row(); + $config['lotw_login_url'] = $q->lotw_login_url; + + // Set some fields that we're going to need for ARRL login + $query = $this->user_model->get_by_id($this->session->userdata('user_id')); + $q = $query->row(); + $fields['login'] = $q->user_lotw_name; + $fields['password'] = $q->user_lotw_password; + $fields['acct_sel'] = ""; + + if ($fields['login'] == '' || $fields['password'] == '') + { + $this->session->set_flashdata('warning', __("You have not defined your ARRL LoTW credentials!")); redirect('lotw/status'); + } + + // Curl stuff goes here + + // First we need to get a cookie + + // options + $cookie_file_path = "./uploads/cookies.txt"; + $agent = "Mozilla/4.0 (compatible;)"; + + // begin script + $ch = curl_init(); + + // extra headers + $headers[] = "Accept: */*"; + $headers[] = "Connection: Keep-Alive"; + + // basic curl options for all requests + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 0); + + // TODO: These SSL things should probably be set to true :) + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_USERAGENT, $agent); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file_path); + curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file_path); + + // Set login URL + curl_setopt($ch, CURLOPT_URL, $config['lotw_login_url']); + + // set postfields using what we extracted from the form + $POSTFIELDS = http_build_query($fields); + + // set post options + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $POSTFIELDS); + + // perform login + $result = curl_exec($ch); + if (stristr($result, "Username/password incorrect")) + { + $this->session->set_flashdata('warning', __("Your ARRL username and/or password is incorrect.")); redirect('lotw/status'); + } + + + // Now we need to use that cookie and upload the file + // change URL to upload destination URL + curl_setopt($ch, CURLOPT_URL, $config['lotw_login_url']); + + // Grab the file + $postfile = array( + "upfile"=>"@./uploads/".$data['upload_data']['file_name'], + ); + + //Upload it + curl_setopt($ch, CURLOPT_POSTFIELDS, $postfile); + $response = curl_exec($ch); + if (stristr($response, "accepted")) + { + $this->session->set_flashdata('lotw_status', 'accepted'); + $data['page_title'] = __("LoTW .TQ8 Sent"); + } + elseif (stristr($response, "rejected")) + { + $this->session->set_flashdata('lotw_status', 'rejected'); + $data['page_title'] = __("LoTW .TQ8 Sent"); + } + else + { + // If we're here, we didn't find what we're looking for in the ARRL response + // and LoTW is probably down or broken. + $this->session->set_flashdata('warning', 'Did not receive proper response from LoTW. Try again later.'); + $data['page_title'] = __("LoTW .TQ8 Not Sent"); + } + + // Now we need to clean up + unlink($cookie_file_path); + unlink('./uploads/'.$data['upload_data']['file_name']); + + $this->load->view('interface_assets/header', $data); + $this->load->view('lotw/status'); + $this->load->view('interface_assets/footer'); + } + } + + /* + Deprecated. To be back compatible we do the same as update/lotw_users + HB9HIL, July 2024 + */ + public function load_users() { + $this->load->model('Update_model'); + $result = $this->Update_model->lotw_users(); + echo $result; + } + + function signlog($sign_key, $string) { + + $qso_string = $string; + + $key = $sign_key; + + $pkeyid = openssl_pkey_get_private($key, 'wavelog'); + if ($pkeyid) { + //openssl_sign($plaintext, $signature, $pkeyid, OPENSSL_ALGO_SHA1 ); + //openssl_free_key($pkeyid); + + if(openssl_sign($qso_string, $signature, $pkeyid, OPENSSL_ALGO_SHA1)) { + if (defined('PHP_MAJOR_VERSION') && PHP_MAJOR_VERSION < 8) { + openssl_free_key($pkeyid); + } + $signature_b64 = base64_encode($signature); + return $signature_b64."\n"; + } else { + // in case of deprecation of SHA-1 in some distro + log_message('error', 'Error signing LoTW log: '.openssl_error_string()); + } + } else { + log_message('error', 'Error signing LoTW log.'); + return null; + } + + + } + + /* + | Function: lotw_ca_province_map + | Requires: candian province map $ca_province + */ + function lotw_ca_province_map($ca_prov) { + switch ($ca_prov): + case "QC": + return "PQ"; + break; + case "NL": + return "NF"; + break; + default: + return $ca_prov; + endswitch; + } + + /* + | Function: mode_map + | Requires: mode as $mode, submode as $submode + | + | This converts ADIF modes to the mode that LoTW expects if its non standard + */ + function mode_map($mode, $submode) { + switch ($mode): + case "PKT": + return "PACKET"; + break; + case "MFSK": + if ($submode == "FT4") { + return "FT4"; + break; + } elseif ($submode == "FST4") { + return "FST4"; + break; + } elseif ($submode == "MFSK16") { + return "MFSK16"; + break; + } elseif ($submode == "MFSK8") { + return "MFSK8"; + break; + } elseif ($submode == "Q65") { + return "Q65"; + break; + } else { + return "DATA"; + break; + } + case "PSK": + if ($submode == "PSK31") { + return "PSK31"; + break; + } elseif ($submode == "PSK63") { + return "PSK63"; + break; + } elseif ($submode == "BPSK125") { + return "PSK125"; + break; + } elseif ($submode == "BPSK31") { + return "PSK31"; + break; + } elseif ($submode == "BPSK63") { + return "PSK63"; + break; + } elseif ($submode == "FSK31") { + return "FSK31"; + break; + } elseif ($submode == "PSK10") { + return "PSK10"; + break; + } elseif ($submode == "PSK125") { + return "PSK125"; + break; + } elseif ($submode == "PSK500") { + return "PSK500"; + break; + } elseif ($submode == "PSK63F") { + return "PSK63F"; + break; + } elseif ($submode == "PSKAM10") { + return "PSKAM"; + break; + } elseif ($submode == "PSKAM31") { + return "PSKAM"; + break; + } elseif ($submode == "PSKAM50") { + return "PSKAM"; + break; + } elseif ($submode == "PSKFEC31") { + return "PSKFEC31"; + break; + } elseif ($submode == "QPSK125") { + return "PSK125"; + break; + } elseif ($submode == "QPSK31") { + return "PSK31"; + break; + } elseif ($submode == "QPSK63") { + return "PSK63"; + break; + } elseif ($submode == "PSK2K") { + return "PSK2K"; + break; + } else { + return "DATA"; + break; + } + default: + return $mode; + endswitch; + } + +} // end class diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php new file mode 100644 index 000000000..4e6e52e77 --- /dev/null +++ b/application/models/Dcl_model.php @@ -0,0 +1,83 @@ +load->model('user_options_model'); + return $this->user_options_model->get_options('dcl', array('option_name'=>'dcl_key'), $user_id)->result(); + } + + + function find_key($callsign, $user_id) { + $this->load->model('user_options_model'); + return $this->user_options_model->get_options('dcl', array('option_name'=>'dcl_key','option_key'=>$callsign), $user_id)->result(); + } + + function store_key($user_id, $callsign, $date_created, $date_expires, $qso_start_date, $qso_end_date, $cert_key, $general_cert) { + } + + function update_key($user_id, $callsign, $dxcc, $date_created, $date_expires, $qso_start_date, $qso_end_date, $cert_key, $general_cert) { + } + + function delete_key($call) { + $this->user_options_model->del_option('dcl', 'dcl_key',array('option_key' => $call)); + } + + function last_upload($key, $message) { + + if ($message == "Success") { + $data = array( + 'last_upload' => date("Y-m-d H:i:s"), + 'last_upload_status' => $message, + ); + + $this->db->where('lotw_cert_id', $certID); + $this->db->update('lotw_certs', $data); + return "Updated"; + } + else if ($message == "Upload failed") { + $data = array( + 'last_upload_fail' => date("Y-m-d H:i:s"), + 'last_upload_status' => $message, + ); + + $this->db->where('lotw_cert_id', $certID); + $this->db->update('lotw_certs', $data); + return "Updated"; + } + } + + function lotw_cert_expired($user_id, $date) { + $array = array('user_id' => $user_id, 'date_expires <' => $date); + $this->db->where($array); + $query = $this->db->get('lotw_certs'); + + if ($query->num_rows() > 0) { + return true; + } else { + return false; + } + } + + function lotw_cert_expiring($user_id, $date) { + $array = array('user_id' => $user_id, 'DATE_SUB(date_expires, INTERVAL 30 DAY) <' => $date, 'date_expires >' => $date); + $this->db->where($array); + $query = $this->db->get('lotw_certs'); + + if ($query->num_rows() > 0) { + return true; + } else { + return false; + } + } + +} +?> diff --git a/application/views/dcl_views/adif_views/adif_export.php b/application/views/dcl_views/adif_views/adif_export.php new file mode 100644 index 000000000..7be4e772e --- /dev/null +++ b/application/views/dcl_views/adif_views/adif_export.php @@ -0,0 +1,172 @@ +cert); +$cert1 = str_replace("-----BEGIN CERTIFICATE-----", "", $clean_cert); +$cert2 = str_replace("-----END CERTIFICATE-----", "", $cert1); +?> +TQSL V2.5.4 Lib: V2.5 Config: V11.12 AllowDupes: false + +tCERT +1 +> + + + +tSTATION +1 +1 +callsign); ?>>callsign; ?> + +cert_dxcc_id); ?>>cert_dxcc_id; ?> + +station_gridsquare) { ?>station_gridsquare); ?>>station_gridsquare; } ?> + +station_itu) { ?>station_itu); ?>>station_itu; } ?> + +station_cq) { ?>station_cq); ?>>station_cq; } ?> + +station_iota) { ?>station_iota); ?>>station_iota; } ?> + +state != "" && $station_profile->station_country == "CANADA") { ?>lotw_ca_province_map($station_profile->state)); ?>>lotw_ca_province_map($station_profile->state); } ?> + +state != "" && $station_profile->station_country == "UNITED STATES OF AMERICA") { ?>state); ?>>state; } ?> + +state != "" && $station_profile->station_country == "CHINA") { ?>state); ?>>state; } ?> + +station_cnty != "" && $station_profile->station_country == "UNITED STATES OF AMERICA") { ?>station_cnty); ?>>station_cnty; } ?> + + + +result() as $qso) { + unset($freq_in_mhz); + unset($freq_in_mhz_rx); +?> +tCONTACT +1 +COL_CALL); ?>>COL_CALL; ?> + +COL_BAND); ?>>COL_BAND); ?> + +mode_map($qso->COL_MODE, $qso->COL_SUBMODE)); ?>>mode_map(($qso->COL_MODE == null ? '' : strtoupper($qso->COL_MODE)), ($qso->COL_SUBMODE == null ? '' : strtoupper($qso->COL_SUBMODE)))); ?> + +COL_FREQ != "" && $qso->COL_FREQ != "0") { $freq_in_mhz = $qso->COL_FREQ / 1000000; ?>> + +COL_FREQ_RX != "" && $qso->COL_FREQ_RX != "0") { $freq_in_mhz_rx = $qso->COL_FREQ_RX / 1000000; ?>> + +COL_PROP_MODE) { ?>COL_PROP_MODE); ?>>COL_PROP_MODE); } ?> + +COL_SAT_NAME) { ?>COL_SAT_NAME); ?>>COL_SAT_NAME); } ?> + +COL_BAND_RX) { ?>COL_BAND_RX); ?>>COL_BAND_RX); } ?> + +COL_TIME_ON); $new_date = date('Y-m-d', $date_on); ?> +> + +COL_TIME_ON); $new_on = date('H:i:s', $time_on); ?> +> + +state != "" && $station_profile->station_country == "CANADA") { + $sign_string .= strtoupper($CI->lotw_ca_province_map($station_profile->state)); +} + +// Adds CN Province +if($station_profile->state != "" && $station_profile->station_country == "CHINA") { + $sign_string .= strtoupper($station_profile->state); +} + +// Add CQ Zone +if($station_profile->station_cq) { + $sign_string .= $station_profile->station_cq; +} + +// Add Gridsquare +if($station_profile->station_gridsquare) { + $sign_string .= strtoupper($station_profile->station_gridsquare); +} + +if($station_profile->station_iota) { + $sign_string .= strtoupper($station_profile->station_iota); +} + +if($station_profile->station_itu) { + $sign_string .= $station_profile->station_itu; +} + +if($station_profile->station_cnty != "" && $station_profile->station_country == "UNITED STATES OF AMERICA") { + $sign_string .= strtoupper($station_profile->station_cnty); +} + +if($station_profile->station_cnty != "" && $station_profile->station_country == "ALASKA") { + $sign_string .= strtoupper($station_profile->station_cnty); +} + +if($station_profile->station_cnty != "" && $station_profile->station_country == "HAWAII") { + $sign_string .= strtoupper($station_profile->station_cnty); +} + +if($station_profile->state != "" && $station_profile->station_country == "UNITED STATES OF AMERICA") { + $sign_string .= strtoupper($station_profile->state); +} + +if($station_profile->state != "" && $station_profile->station_country == "ALASKA") { + $sign_string .= strtoupper($station_profile->state); +} + +if($station_profile->state != "" && $station_profile->station_country == "HAWAII") { + $sign_string .= strtoupper($station_profile->state); +} + +if($qso->COL_BAND) { + $sign_string .= strtoupper($qso->COL_BAND); +} + +if($qso->COL_BAND_RX) { + $sign_string .= strtoupper($qso->COL_BAND_RX); +} + +if($qso->COL_CALL) { + $sign_string .= strtoupper($qso->COL_CALL); +} + +if(isset($freq_in_mhz)) { + $sign_string .= strtoupper($freq_in_mhz); +} + +if(isset($freq_in_mhz_rx)) { + $sign_string .= strtoupper($freq_in_mhz_rx); +} + +if($qso->COL_MODE) { + $sign_string .= strtoupper($CI->mode_map($qso->COL_MODE, $qso->COL_SUBMODE)); +} + + +if($qso->COL_PROP_MODE) { + $sign_string .= strtoupper($qso->COL_PROP_MODE); +} + +$sign_string .= $new_date; + +$sign_string .= $new_on."Z"; + +if($qso->COL_SAT_NAME) { + $sign_string .= strtoupper($qso->COL_SAT_NAME); +} + + ?> +signlog($lotw_cert_info->cert_key, $sign_string); +?> +:6> + +> + + + + diff --git a/application/views/dcl_views/index.php b/application/views/dcl_views/index.php new file mode 100644 index 000000000..a53f6eddd --- /dev/null +++ b/application/views/dcl_views/index.php @@ -0,0 +1,92 @@ +
+
+ +

+ + +
+
+ +
+ +
+ + + + + load->view('layout/messages'); ?> + + 0) { ?> + +
+ + + + + + + + + + + + + + + + + + + + + +
call; ?> + last_sync ?? '1970-01-01'); + $last_upload = date($this->config->item('qso_date_format').' H:i:s', $last_upload_ts); + if ($last_upload_ts == strtotime('1970-01-01')) { ?> + " class="badge text-bg-danger"> + + + + key; ?> + + +
+
+ + + + + +
+
+ + +
+ + + config->item('disable_manual_dcl'))) { ?> +
+
+ +
+ +
+ ".__("The next automatic sync with DCL will happen at: ").$next_run."

"; } ?> + + + +
+
+
+ + +
diff --git a/application/views/dcl_views/upload_cert.php b/application/views/dcl_views/upload_cert.php new file mode 100644 index 000000000..0466bbb12 --- /dev/null +++ b/application/views/dcl_views/upload_cert.php @@ -0,0 +1,44 @@ +
+ +

+ + +
+
+ +
+ +
+ + + + + + + +
+ + +
+ + + + +
+
+ + +
From 595f6a72219e9d1c5c3c96d1ab0c0ffe8e96c26e Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 24 Apr 2025 10:25:13 +0000 Subject: [PATCH 02/49] Downloadpage for DCL --- application/controllers/Dcl.php | 140 ++++++-------------------------- 1 file changed, 24 insertions(+), 116 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index 515f76f23..172a08f86 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -570,89 +570,6 @@ class Dcl extends CI_Controller { } } - public function check_lotw_credentials () { - $this->load->model('user_model'); - if(!$this->user_model->authorize(2)) { - $this->session->set_flashdata('error', __("You're not allowed to do that!")); - redirect('dashboard'); - exit(); - } - $ret=[]; - $ret['status']=''; - - - $raw = file_get_contents("php://input"); - try { - $obj = json_decode($raw,true); - } catch (Exception $e) { - $ret['status']='failed_wrongcall'; - log_message("Error",$ret['status']); - } finally { - $lotw_user=$obj['lotw_user'] ?? ''; - $lotw_pass=$obj['lotw_pass'] ?? ''; - } - $raw=''; - - $pw_placeholder = '**********'; - if ($lotw_pass == $pw_placeholder) { // User comes with unaltered credentials - take them from database - $query = $this->user_model->get_by_id($this->session->userdata('user_id')); - $q = $query->row(); - $data['user_lotw_name'] = urlencode($q->user_lotw_name ?? ''); - $data['user_lotw_password'] = urlencode($q->user_lotw_password ?? ''); - } else { - $data['user_lotw_name'] = urlencode($lotw_user ?? ''); - $data['user_lotw_password'] = urlencode($lotw_pass ?? ''); - } - - if ((($data['user_lotw_name'] ?? '') != '') && (($data['user_lotw_password'] ?? '') != '') && ($ret['status'] != 'failed_wrongcall')) { - - // Get URL for downloading LoTW - $query = $query = $this->db->query('SELECT lotw_login_url FROM config'); - $q = $query->row(); - $lotw_url = $q->lotw_login_url; - - // Validate that LoTW credentials are not empty - // TODO: We don't actually see the error message - if ($data['user_lotw_name'] == '' || $data['user_lotw_password'] == '') { - $ret='No Creds set'; - } - - // Build URL for LoTW report file - $lotw_url .= "?"; - $lotw_url .= "login=" . $data['user_lotw_name']; - $lotw_url .= "&password=" . $data['user_lotw_password']; - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $lotw_url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); - $content = curl_exec($ch); - if ($content) { - if(curl_errno($ch)) { - $ret['status']='failed'; - $ret['details']== sprintf(__("LoTW login failed for user %s: %s."), $data['user_lotw_name'], curl_strerror(curl_errno($ch))." (".curl_errno($ch).")"); - } else if (str_contains($content,"Username/password incorrect")) { - $ret['status']='failed_wrong_creds'; - $ret['details']= sprintf(__("LoTW login failed for user %s: %s."), $data['user_lotw_name'], __("Username/password incorrect")); - } else { - $ret['status']='OK'; - $ret['details']= __("LoTW login OK!"); - } - } else { - $ret['status']='failed_na'; - $ret['details']= __("LoTW currently not available. Try again later."); - } - } else { - if (($ret['status'] ?? '') == '') { - $ret['status']='failed_nocred'; - $ret['details']= __("No LoTW credentials provided."); - } - } - header("Content-type: application/json"); - echo json_encode($ret); - return $ret; - } - public function import() { // Is only called via frontend. Cron uses "upload". within download the download is called $this->load->model('user_model'); $this->load->model('Stations'); @@ -663,7 +580,7 @@ class Dcl extends CI_Controller { } $station_ids=$this->Stations->all_station_ids_of_user($this->session->userdata['user_id']); - $data['page_title'] = __("LoTW ADIF Import"); + $data['page_title'] = __("DCL ADIF Import"); $config['upload_path'] = './uploads/'; $config['allowed_types'] = 'adi|ADI'; @@ -672,58 +589,57 @@ class Dcl extends CI_Controller { $this->load->model('logbook_model'); - if (($this->input->post('lotwimport') == 'fetch') && (!($this->config->item('disable_manual_lotw')))) { - $file = $config['upload_path'] . 'lotwreport_download_'.$this->session->userdata('user_id').'.adi'; + if (($this->input->post('dclimport') == 'fetch') && (!($this->config->item('disable_manual_dcl')))) { + $file = $config['upload_path'] . 'dclreport_download_'.$this->session->userdata('user_id').'.adi'; // Get credentials for LoTW $query = $this->user_model->get_by_id($this->session->userdata('user_id')); $q = $query->row(); - $data['user_lotw_name'] = urlencode($q->user_lotw_name ?? ''); - $data['user_lotw_password'] = urlencode($q->user_lotw_password ?? ''); + $data['user_dcl_name'] = urlencode($q->user_dcl_name ?? ''); + $data['user_dcl_password'] = urlencode($q->user_dcl_password ?? ''); // Get URL for downloading LoTW - $query = $query = $this->db->query('SELECT lotw_download_url FROM config'); + $query = $query = $this->db->query('SELECT dcl_download_url FROM config'); $q = $query->row(); - $lotw_url = $q->lotw_download_url; + $dcl_url = $q->dcl_download_url; // Validate that LoTW credentials are not empty // TODO: We don't actually see the error message - if ($data['user_lotw_name'] == '' || $data['user_lotw_password'] == '') { - $this->session->set_flashdata('warning', __("You have not defined your ARRL LoTW credentials!")); redirect('lotw/import'); + if ($data['user_dcl_name'] == '' || $data['user_dcl_password'] == '') { + $this->session->set_flashdata('warning', __("You have not defined your ARRL LoTW credentials!")); redirect('dcl/import'); } $customDate = $this->input->post('from'); if ($customDate != NULL) { - $lotw_last_qsl_date = date($customDate); + $dcl_last_qsl_date = date($customDate); } else { // Query the logbook to determine when the last LoTW confirmation was - $lotw_last_qsl_date = date('Y-m-d', strtotime($this->logbook_model->lotw_last_qsl_date($this->session->userdata['user_id']))); + $dcl_last_qsl_date = date('Y-m-d', strtotime($this->logbook_model->dcl_last_qsl_date($this->session->userdata['user_id']))); } - // Build URL for LoTW report file - $lotw_url .= "?"; - $lotw_url .= "login=" . $data['user_lotw_name']; - $lotw_url .= "&password=" . $data['user_lotw_password']; - $lotw_url .= "&qso_query=1&qso_qsl='yes'&qso_qsldetail='yes'&qso_mydetail='yes'"; + $dcl_url .= "?"; + $dcl_url .= "login=" . $data['user_dcl_name']; + $dcl_url .= "&password=" . $data['user_dcl_password']; + $dcl_url .= "&qso_query=1&qso_qsl='yes'&qso_qsldetail='yes'&qso_mydetail='yes'"; - $lotw_url .= "&qso_qslsince="; - $lotw_url .= "$lotw_last_qsl_date"; + $dcl_url .= "&qso_qslsince="; + $dcl_url .= "$dcl_last_qsl_date"; if ($this->input->post('callsign') != '0') { - $lotw_url .= "&qso_owncall=".$this->input->post('callsign'); + $dcl_url .= "&qso_owncall=".$this->input->post('callsign'); } if (is_writable(dirname($file)) && (!file_exists($file) || is_writable($file))) { $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $lotw_url); + curl_setopt($ch, CURLOPT_URL, $dcl_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); $content = curl_exec($ch); if(curl_errno($ch)) { - print "LoTW download failed for user ".$data['user_lotw_name'].": ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")."; + print "DCL download failed for user ".$data['user_dcl_name'].": ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")."; } else if (str_contains($content,"Username/password incorrect")) { - print "LoTW download failed for user ".$data['user_lotw_name'].": Username/password incorrect"; + print "DCL download failed for user ".$data['user_dcl_name'].": key incorrect"; } else { file_put_contents($file, $content); ini_set('memory_limit', '-1'); @@ -739,26 +655,18 @@ class Dcl extends CI_Controller { $data['callsigns'] = $this->Stations->callsigns_of_user($this->session->userdata('user_id')); $this->load->view('interface_assets/header', $data); - $this->load->view('lotw/import', $data); + $this->load->view('dcl_views/import', $data); $this->load->view('interface_assets/footer'); } } else { - if (!$this->upload->do_upload()) { - - $data['error'] = $this->upload->display_errors(); $this->load->model('Stations'); $data['callsigns'] = $this->Stations->callsigns_of_user($this->session->userdata('user_id')); $this->load->view('interface_assets/header', $data); - $this->load->view('lotw/import', $data); + $this->load->view('dcl_views/import', $data); $this->load->view('interface_assets/footer'); - } else { - $data = array('upload_data' => $this->upload->data()); - - $this->loadFromFile('./uploads/'.$data['upload_data']['file_name'], $station_ids); - } } - } // end function + } public function export() { $this->load->model('user_model'); From eff0f208332316fe9166939f656ac12307af82c2 Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 24 Apr 2025 16:10:44 +0000 Subject: [PATCH 03/49] First working export --- application/controllers/Dcl.php | 110 ++++------- application/models/Dcl_model.php | 53 +----- application/models/Logbook_model.php | 28 +++ application/models/User_options_model.php | 6 +- .../dcl_views/adif_views/adif_export.php | 172 ------------------ 5 files changed, 74 insertions(+), 295 deletions(-) delete mode 100644 application/views/dcl_views/adif_views/adif_export.php diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index 172a08f86..0970583f1 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -42,41 +42,7 @@ class Dcl extends CI_Controller { $this->load->view('interface_assets/footer'); } - /* - |-------------------------------------------------------------------------- - | Function: cert_upload - |-------------------------------------------------------------------------- - | - | Nothing fancy just shows the cert_upload form for uploading p12 files - | - */ - public function cert_upload() { - $this->load->model('user_model'); - $this->load->model('dxcc'); - if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } - - // Load DXCC Countrys List - $data['dxcc_list'] = $this->dxcc->list(); - - // Set Page Title - $data['page_title'] = __("Logbook of the World"); - - // Load Views - $this->load->view('interface_assets/header', $data); - $this->load->view('lotw_views/upload_cert', array('error' => ' ' )); - $this->load->view('interface_assets/footer'); - } - - - /* - |-------------------------------------------------------------------------- - | Function: lotw_upload - |-------------------------------------------------------------------------- - | - | This function Uploads to LoTW - | - */ - public function lotw_upload() { + public function dcl_upload() { $this->load->model('user_model'); $this->user_model->authorize(2); @@ -88,6 +54,10 @@ class Dcl extends CI_Controller { // Get Station Profile Data $this->load->model('Stations'); + if (!$this->load->is_loaded('AdifHelper')) { + $this->load->library('AdifHelper'); + } + if ($this->user_model->authorize(2)) { if (!($this->config->item('disable_manual_lotw'))) { $station_profiles = $this->Stations->all_of_user($this->session->userdata('user_id')); @@ -111,37 +81,28 @@ class Dcl extends CI_Controller { foreach ($station_profiles->result() as $station_profile) { + log_message("Error",$station_profile->station_id); // Get Certificate Data - $this->load->model('Lotw_model'); + $this->load->model('Dcl_model'); $data['station_profile'] = $station_profile; - $data['lotw_cert_info'] = $this->Lotw_model->lotw_cert_details($station_profile->station_callsign, $station_profile->station_dxcc, $station_profile->user_id); - - // If Station Profile has no LoTW Cert continue on. - if(!isset($data['lotw_cert_info']->cert_dxcc_id)) { - echo $station_profile->station_callsign.": No LoTW certificate for station callsign found.
"; + $key_info = $this->Dcl_model->find_key($station_profile->station_callsign, $station_profile->user_id); + if (($key_info ?? '') == '') { continue; } - // Check if LoTW certificate itself is valid - // Validty of QSO dates will be checked later - $current_date = date('Y-m-d H:i:s'); - if ($current_date <= $data['lotw_cert_info']->date_created) { - echo $data['lotw_cert_info']->callsign.": LoTW certificate not valid yet!"; - continue; - } - if ($current_date >= $data['lotw_cert_info']->date_expires) { - echo $data['lotw_cert_info']->callsign.": LoTW certificate expired!"; - continue; - } + $data['dcl_key_info']=new stdClass(); + $data['dcl_key_info']->call = $key_info[0]->option_key ?? ''; + $data['dcl_key_info'] = json_decode($key_info[0]->option_value ?? ''); - // Get QSOs + // If Station Profile has no DCL Key continue on. + if (($data['dcl_key_info']->call ?? '') == '') { + echo $station_profile->station_callsign.": No DCL Key for station callsign found.
"; + continue; + } $this->load->model('Logbook_model'); - // First mark QSOs with unsupported propagation modes as ignore - $this->Logbook_model->mark_lotw_ignore($data['station_profile']->station_id); - - $data['qsos'] = $this->Logbook_model->get_lotw_qsos_to_upload($data['station_profile']->station_id, $data['lotw_cert_info']->qso_start_date, $data['lotw_cert_info']->qso_end_date); + $data['qsos'] = $this->Logbook_model->get_dcl_qsos_to_upload($data['station_profile']->station_id); // Nothing to upload if(empty($data['qsos']->result())){ @@ -156,28 +117,22 @@ class Dcl extends CI_Controller { } // Build File to save - $adif_to_save = $this->load->view('lotw_views/adif_views/adif_export', $data, TRUE); - if (strpos($adif_to_save, '')) { - // Signing failed - echo "Signing failed."; - continue; - } - + $adif_to_save = $this->load->view('adif/data/dcl.php', $data, TRUE); + // create folder to store upload file - if (!file_exists('./uploads/lotw')) { - mkdir('./uploads/lotw', 0775, true); + if (!file_exists('./uploads/dcl')) { + mkdir('./uploads/dcl', 0775, true); } // Build Filename - $filename_for_saving = './uploads/lotw/'.preg_replace('/[^a-z0-9]+/', '-', strtolower($data['lotw_cert_info']->callsign))."-".date("Y-m-d-H-i-s")."-wavelog.tq8"; + $filename_for_saving = './uploads/dcl/'.preg_replace('/[^a-z0-9]+/', '-', strtolower($data['dcl_key_info']->call))."-".date("Y-m-d-H-i-s")."-wavelog.adif"; - $gzdata = gzencode($adif_to_save, 9); $fp = fopen($filename_for_saving, "w"); - fwrite($fp, $gzdata); + fwrite($fp, $adif_to_save); fclose($fp); //The URL that accepts the file upload. - $url = 'https://lotw.arrl.org/lotw/upload'; + $url = 'https://dclnext.darc.de'; //The name of the field for the uploaded file. $uploadFieldName = 'upfile'; @@ -210,11 +165,11 @@ class Dcl extends CI_Controller { curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); //Execute the request - $result = curl_exec($ch); + // $result = curl_exec($ch); if(curl_errno($ch)){ echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Failed - ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")
"; - $this->Lotw_model->last_upload($data['lotw_cert_info']->lotw_cert_id, "Upload failed"); + $this->Dcl_model->last_upload($data['dcl_key_info']->call, "Upload failed", $this->session->userdata('user_id')); if (curl_errno($ch) == 28) { // break on timeout echo "Timeout reached. Stopping subsequent uploads.
"; break; @@ -223,12 +178,13 @@ class Dcl extends CI_Controller { } } - $pos = strpos($result, ""); + // $pos = strpos($result, ""); + $pos = true; if ($pos === false) { // Upload of TQ8 Failed for unknown reason echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Failed - ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")
"; - $this->Lotw_model->last_upload($data['lotw_cert_info']->lotw_cert_id, "Upload failed"); + $this->Dcl_model->last_upload($data['dcl_key_info']->call, "Upload failed", $this->session->userdata('user_id')); if (curl_errno($ch) == 28) { // break on timeout echo "Timeout reached. Stopping subsequent uploads.
"; break; @@ -240,16 +196,16 @@ class Dcl extends CI_Controller { echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Successful - ".$filename_for_saving."
"; - $this->Lotw_model->last_upload($data['lotw_cert_info']->lotw_cert_id, "Success"); + $this->Dcl_model->last_upload($data['dcl_key_info']->call, "Success", $this->session->userdata('user_id')); // Mark QSOs as Sent foreach ($qso_id_array as $qso_number) { - $this->Logbook_model->mark_lotw_sent($qso_number); + // $this->Logbook_model->mark_dcl_sent($qso_number); } } // Delete TQ8 File - This is done regardless of whether upload was succcessful - unlink(realpath($filename_for_saving)); + // unlink(realpath($filename_for_saving)); } } else { echo "No Station Profiles found to upload to LoTW"; diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php index 4e6e52e77..1f9ba7c1a 100644 --- a/application/models/Dcl_model.php +++ b/application/models/Dcl_model.php @@ -31,53 +31,18 @@ class Dcl_model extends CI_Model { $this->user_options_model->del_option('dcl', 'dcl_key',array('option_key' => $call)); } - function last_upload($key, $message) { + function last_upload($key, $message, $user_id) { + $this->load->model('user_options_model'); + $dclrkey=$this->user_options_model->get_options('dcl', array('option_name'=>'dcl_key'), $user_id)->result(); + $dclkey = json_decode($dclrkey[0]->option_value ?? ''); + $dclkey->call = $dclrkey[0]->option_key ?? ''; + $dclnewkey=$dclkey; if ($message == "Success") { - $data = array( - 'last_upload' => date("Y-m-d H:i:s"), - 'last_upload_status' => $message, - ); - - $this->db->where('lotw_cert_id', $certID); - $this->db->update('lotw_certs', $data); - return "Updated"; - } - else if ($message == "Upload failed") { - $data = array( - 'last_upload_fail' => date("Y-m-d H:i:s"), - 'last_upload_status' => $message, - ); - - $this->db->where('lotw_cert_id', $certID); - $this->db->update('lotw_certs', $data); - return "Updated"; + $dclnewkey->last_sync=date("Y-m-d H:i:s"); + $this->user_options_model->set_option('dcl', 'dcl_key', array($dclkey->call => json_encode($dclnewkey)), $user_id); } + return "Updated"; } - - function lotw_cert_expired($user_id, $date) { - $array = array('user_id' => $user_id, 'date_expires <' => $date); - $this->db->where($array); - $query = $this->db->get('lotw_certs'); - - if ($query->num_rows() > 0) { - return true; - } else { - return false; - } - } - - function lotw_cert_expiring($user_id, $date) { - $array = array('user_id' => $user_id, 'DATE_SUB(date_expires, INTERVAL 30 DAY) <' => $date, 'date_expires >' => $date); - $this->db->where($array); - $query = $this->db->get('lotw_certs'); - - if ($query->num_rows() > 0) { - return true; - } else { - return false; - } - } - } ?> diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 1b3de21fd..81641c728 100644 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -5343,6 +5343,19 @@ class Logbook_model extends CI_Model { return $query->result(); } + function get_dcl_qsos_to_upload($station_id) { + + $sql = 'select *, dxcc_entities.name as station_country from ' . $this->config->item('table_name') . ' thcv ' . + ' left join station_profile on thcv.station_id = station_profile.station_id' . + ' left outer join dxcc_entities on thcv.col_my_dxcc = dxcc_entities.adif' . + ' where thcv.station_id = ?' . + ' and (COL_DCL_QSL_SENT not in ("Y","I") OR COL_DCL_QSL_SENT is null)'; + $binding[] = $station_id; + + $query = $this->db->query($sql, $binding); + return $query; + } + function get_lotw_qsos_to_upload($station_id, $start_date, $end_date) { $this->db->select('COL_PRIMARY_KEY,COL_CALL, COL_BAND, COL_BAND_RX, COL_TIME_ON, COL_RST_RCVD, COL_RST_SENT, COL_MODE, COL_SUBMODE, COL_FREQ, COL_FREQ_RX, COL_GRIDSQUARE, COL_SAT_NAME, COL_PROP_MODE, COL_LOTW_QSL_SENT, station_id'); @@ -5362,6 +5375,21 @@ class Logbook_model extends CI_Model { return $query; } + function mark_dcl_sent($qso_id) { + + $data = array( + 'COL_DCL_QSLSDATE' => date("Y-m-d H:i:s"), + 'COL_DCL_QSL_SENT' => 'Y', + ); + + + $this->db->where('COL_PRIMARY_KEY', $qso_id); + + $this->db->update($this->config->item('table_name'), $data); + + return "Updated"; + } + function mark_lotw_sent($qso_id) { $data = array( diff --git a/application/models/User_options_model.php b/application/models/User_options_model.php index d69094d5c..a8d3e174c 100644 --- a/application/models/User_options_model.php +++ b/application/models/User_options_model.php @@ -8,8 +8,10 @@ class User_options_model extends CI_Model { return $this->db->get('user_options'); } - public function set_option($option_type, $option_name, $option_array) { - $uid=$this->session->userdata('user_id'); + public function set_option($option_type, $option_name, $option_array, $uid=null) { + if (($uid ?? '') == '') { + $uid=$this->session->userdata('user_id'); + } $sql='insert into user_options (user_id,option_type,option_name,option_key,option_value) values (?,?,?,?,?) ON DUPLICATE KEY UPDATE option_value=?'; foreach($option_array as $option_key => $option_value) { $query = $this->db->query($sql, array($uid, $option_type, $option_name, $option_key, $option_value, $option_value)); diff --git a/application/views/dcl_views/adif_views/adif_export.php b/application/views/dcl_views/adif_views/adif_export.php deleted file mode 100644 index 7be4e772e..000000000 --- a/application/views/dcl_views/adif_views/adif_export.php +++ /dev/null @@ -1,172 +0,0 @@ -cert); -$cert1 = str_replace("-----BEGIN CERTIFICATE-----", "", $clean_cert); -$cert2 = str_replace("-----END CERTIFICATE-----", "", $cert1); -?> -TQSL V2.5.4 Lib: V2.5 Config: V11.12 AllowDupes: false - -tCERT -1 -> - - - -tSTATION -1 -1 -callsign); ?>>callsign; ?> - -cert_dxcc_id); ?>>cert_dxcc_id; ?> - -station_gridsquare) { ?>station_gridsquare); ?>>station_gridsquare; } ?> - -station_itu) { ?>station_itu); ?>>station_itu; } ?> - -station_cq) { ?>station_cq); ?>>station_cq; } ?> - -station_iota) { ?>station_iota); ?>>station_iota; } ?> - -state != "" && $station_profile->station_country == "CANADA") { ?>lotw_ca_province_map($station_profile->state)); ?>>lotw_ca_province_map($station_profile->state); } ?> - -state != "" && $station_profile->station_country == "UNITED STATES OF AMERICA") { ?>state); ?>>state; } ?> - -state != "" && $station_profile->station_country == "CHINA") { ?>state); ?>>state; } ?> - -station_cnty != "" && $station_profile->station_country == "UNITED STATES OF AMERICA") { ?>station_cnty); ?>>station_cnty; } ?> - - - -result() as $qso) { - unset($freq_in_mhz); - unset($freq_in_mhz_rx); -?> -tCONTACT -1 -COL_CALL); ?>>COL_CALL; ?> - -COL_BAND); ?>>COL_BAND); ?> - -mode_map($qso->COL_MODE, $qso->COL_SUBMODE)); ?>>mode_map(($qso->COL_MODE == null ? '' : strtoupper($qso->COL_MODE)), ($qso->COL_SUBMODE == null ? '' : strtoupper($qso->COL_SUBMODE)))); ?> - -COL_FREQ != "" && $qso->COL_FREQ != "0") { $freq_in_mhz = $qso->COL_FREQ / 1000000; ?>> - -COL_FREQ_RX != "" && $qso->COL_FREQ_RX != "0") { $freq_in_mhz_rx = $qso->COL_FREQ_RX / 1000000; ?>> - -COL_PROP_MODE) { ?>COL_PROP_MODE); ?>>COL_PROP_MODE); } ?> - -COL_SAT_NAME) { ?>COL_SAT_NAME); ?>>COL_SAT_NAME); } ?> - -COL_BAND_RX) { ?>COL_BAND_RX); ?>>COL_BAND_RX); } ?> - -COL_TIME_ON); $new_date = date('Y-m-d', $date_on); ?> -> - -COL_TIME_ON); $new_on = date('H:i:s', $time_on); ?> -> - -state != "" && $station_profile->station_country == "CANADA") { - $sign_string .= strtoupper($CI->lotw_ca_province_map($station_profile->state)); -} - -// Adds CN Province -if($station_profile->state != "" && $station_profile->station_country == "CHINA") { - $sign_string .= strtoupper($station_profile->state); -} - -// Add CQ Zone -if($station_profile->station_cq) { - $sign_string .= $station_profile->station_cq; -} - -// Add Gridsquare -if($station_profile->station_gridsquare) { - $sign_string .= strtoupper($station_profile->station_gridsquare); -} - -if($station_profile->station_iota) { - $sign_string .= strtoupper($station_profile->station_iota); -} - -if($station_profile->station_itu) { - $sign_string .= $station_profile->station_itu; -} - -if($station_profile->station_cnty != "" && $station_profile->station_country == "UNITED STATES OF AMERICA") { - $sign_string .= strtoupper($station_profile->station_cnty); -} - -if($station_profile->station_cnty != "" && $station_profile->station_country == "ALASKA") { - $sign_string .= strtoupper($station_profile->station_cnty); -} - -if($station_profile->station_cnty != "" && $station_profile->station_country == "HAWAII") { - $sign_string .= strtoupper($station_profile->station_cnty); -} - -if($station_profile->state != "" && $station_profile->station_country == "UNITED STATES OF AMERICA") { - $sign_string .= strtoupper($station_profile->state); -} - -if($station_profile->state != "" && $station_profile->station_country == "ALASKA") { - $sign_string .= strtoupper($station_profile->state); -} - -if($station_profile->state != "" && $station_profile->station_country == "HAWAII") { - $sign_string .= strtoupper($station_profile->state); -} - -if($qso->COL_BAND) { - $sign_string .= strtoupper($qso->COL_BAND); -} - -if($qso->COL_BAND_RX) { - $sign_string .= strtoupper($qso->COL_BAND_RX); -} - -if($qso->COL_CALL) { - $sign_string .= strtoupper($qso->COL_CALL); -} - -if(isset($freq_in_mhz)) { - $sign_string .= strtoupper($freq_in_mhz); -} - -if(isset($freq_in_mhz_rx)) { - $sign_string .= strtoupper($freq_in_mhz_rx); -} - -if($qso->COL_MODE) { - $sign_string .= strtoupper($CI->mode_map($qso->COL_MODE, $qso->COL_SUBMODE)); -} - - -if($qso->COL_PROP_MODE) { - $sign_string .= strtoupper($qso->COL_PROP_MODE); -} - -$sign_string .= $new_date; - -$sign_string .= $new_on."Z"; - -if($qso->COL_SAT_NAME) { - $sign_string .= strtoupper($qso->COL_SAT_NAME); -} - - ?> -signlog($lotw_cert_info->cert_key, $sign_string); -?> -:6> - -> - - - - From c6d5ceaac95ee98df074f6ad04653cf64b21eccb Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 24 Apr 2025 16:10:56 +0000 Subject: [PATCH 04/49] Views --- application/views/adif/data/dcl.php | 11 +++++ application/views/dcl_views/import.php | 60 ++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 application/views/adif/data/dcl.php create mode 100644 application/views/dcl_views/import.php diff --git a/application/views/adif/data/dcl.php b/application/views/adif/data/dcl.php new file mode 100644 index 000000000..83076d77c --- /dev/null +++ b/application/views/adif/data/dcl.php @@ -0,0 +1,11 @@ +Wavelog ADIF export +3.1.5 +config->item('app_name')); ?>>config->item('app_name')."\r\n"; ?> +optionslib->get_option('version')); ?>>optionslib->get_option('version')."\r\n"; ?> + + +result() as $qso) { + echo $this->adifhelper->getAdifLine($qso); +} diff --git a/application/views/dcl_views/import.php b/application/views/dcl_views/import.php new file mode 100644 index 000000000..9b28f893c --- /dev/null +++ b/application/views/dcl_views/import.php @@ -0,0 +1,60 @@ +
+ +

-

+ + + + + +
+
+
+ + + load->view('layout/messages'); ?> + + + + +config->item('disable_manual_dcl')) { ?> +
+
+ + +

+

:

+
+
+ +
+
+
+
+
+ + result() as $call) { + $options[$call->callsign] = $call->callsign; + } + ksort($options); + array_unshift($options, __("All")); + echo form_dropdown('callsign', $options, 'All'); + ?> +
+
+
+ +

+
+ + + " /> + + +
+
+ +
From 9e959506b3a274037b8eaa4941f37c30d706e1b9 Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 24 Apr 2025 16:20:12 +0000 Subject: [PATCH 05/49] Minor caption-adjustments --- application/controllers/Dcl.php | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index 0970583f1..ccf273c36 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -164,7 +164,7 @@ class Dcl extends CI_Controller { curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); - //Execute the request + // todo: uncomment when ready // $result = curl_exec($ch); if(curl_errno($ch)){ @@ -178,7 +178,6 @@ class Dcl extends CI_Controller { } } - // $pos = strpos($result, ""); $pos = true; if ($pos === false) { @@ -192,35 +191,29 @@ class Dcl extends CI_Controller { continue; } } else { - // Upload of TQ8 was successfull - echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Successful - ".$filename_for_saving."
"; - $this->Dcl_model->last_upload($data['dcl_key_info']->call, "Success", $this->session->userdata('user_id')); - // Mark QSOs as Sent foreach ($qso_id_array as $qso_number) { + // todo: uncomment when ready // $this->Logbook_model->mark_dcl_sent($qso_number); } } - // Delete TQ8 File - This is done regardless of whether upload was succcessful + // todo: uncomment when ready // unlink(realpath($filename_for_saving)); } } else { - echo "No Station Profiles found to upload to LoTW"; + echo "No Station Profiles found to upload to DCL"; } - /* - | Download QSO Matches from LoTW - */ if ($this->user_model->authorize(2)) { echo "

"; $sync_user_id=$this->session->userdata('user_id'); } else { $sync_user_id=null; } - echo $this->lotw_download($sync_user_id); + echo $this->dcl_download($sync_user_id); } public function delete_key($call) { @@ -442,7 +435,7 @@ class Dcl extends CI_Controller { | downloading matching QSOs. | */ - function lotw_download($sync_user_id = null) { + function dcl_download($sync_user_id = null) { $this->load->model('user_model'); $this->load->model('logbook_model'); $this->load->model('Stations'); From 480dc832aacf794ed57e1b0c2911631c854b78d9 Mon Sep 17 00:00:00 2001 From: int2001 Date: Sun, 8 Jun 2025 06:51:12 +0000 Subject: [PATCH 06/49] PoC for KeyImport --- application/controllers/Dcl.php | 19 ++++++++++++++++++ application/views/dcl_views/key_import.php | 23 ++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 application/views/dcl_views/key_import.php diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index ccf273c36..a7f12b905 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -13,6 +13,25 @@ class Dcl extends CI_Controller { } } + public function key_import() { + $this->load->library('Permissions'); + $this->load->model('user_model'); + if (!$this->user_model->authorize(2) || !clubaccess_check(9)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } + + $token=($this->input->get('token',true) ?? ''); + if ($token != '') { + log_message('Error',$token); + // todo: Token import // Show / etc. + $data['page_title'] = __("DCL Key Import"); + $data['token'] = $token; + $this->load->view('interface_assets/header', $data); + $this->load->view('dcl_views/key_import',$data); + $this->load->view('interface_assets/footer'); + } else { + redirect('https://dings.dcl.darc.de/token?wohin='.base_url().'dcl/key_import'); + } + } + public function index() { $this->load->library('Permissions'); $this->load->model('user_model'); diff --git a/application/views/dcl_views/key_import.php b/application/views/dcl_views/key_import.php new file mode 100644 index 000000000..d0ad8d7d7 --- /dev/null +++ b/application/views/dcl_views/key_import.php @@ -0,0 +1,23 @@ +
+ +

+ + +
+
+ +
+ +
+ + +
+ :
+
+ +
+
+
From 60bb8ca8397721a391701a074ef9c79e4dd0f91a Mon Sep 17 00:00:00 2001 From: int2001 Date: Sun, 8 Jun 2025 10:14:35 +0000 Subject: [PATCH 07/49] Implemented asymetric signing/verification for genuine DCL-Key --- application/controllers/Dcl.php | 6 +++++- application/views/dcl_views/key_import.php | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index a7f12b905..c85934818 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -18,9 +18,13 @@ class Dcl extends CI_Controller { $this->load->model('user_model'); if (!$this->user_model->authorize(2) || !clubaccess_check(9)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } + $sig=($this->input->get('sig',true) ?? ''); $token=($this->input->get('token',true) ?? ''); - if ($token != '') { + if ( ($sig != '') && ($token != '')) { log_message('Error',$token); + $sig = sodium_base642bin($sig, SODIUM_BASE64_VARIANT_URLSAFE); + $public_key=sodium_base642bin('nV46R47wlLDOJAkSs5RT00wzgz3z98uZFxjo3FSkxeg=',SODIUM_BASE64_VARIANT_URLSAFE); + $data['is_valid']=sodium_crypto_sign_verify_detached($sig, $token, $public_key); // todo: Token import // Show / etc. $data['page_title'] = __("DCL Key Import"); $data['token'] = $token; diff --git a/application/views/dcl_views/key_import.php b/application/views/dcl_views/key_import.php index d0ad8d7d7..ea0ea86a9 100644 --- a/application/views/dcl_views/key_import.php +++ b/application/views/dcl_views/key_import.php @@ -16,6 +16,11 @@
:
+ + Key is valid + + Key is invalid +
From 32931dee32c77b5a6739c1c6ab49bde87a44f9a9 Mon Sep 17 00:00:00 2001 From: int2001 Date: Sun, 8 Jun 2025 12:18:24 +0000 Subject: [PATCH 08/49] Movve verification to model --- application/controllers/Dcl.php | 6 ++---- application/models/Dcl_model.php | 11 +++++++++++ application/views/dcl_views/key_import.php | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index c85934818..e4aaab015 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -16,15 +16,13 @@ class Dcl extends CI_Controller { public function key_import() { $this->load->library('Permissions'); $this->load->model('user_model'); + $this->load->model('dcl_model'); if (!$this->user_model->authorize(2) || !clubaccess_check(9)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } $sig=($this->input->get('sig',true) ?? ''); $token=($this->input->get('token',true) ?? ''); if ( ($sig != '') && ($token != '')) { - log_message('Error',$token); - $sig = sodium_base642bin($sig, SODIUM_BASE64_VARIANT_URLSAFE); - $public_key=sodium_base642bin('nV46R47wlLDOJAkSs5RT00wzgz3z98uZFxjo3FSkxeg=',SODIUM_BASE64_VARIANT_URLSAFE); - $data['is_valid']=sodium_crypto_sign_verify_detached($sig, $token, $public_key); + $data['is_valid']=$this->dcl_model->check_dcl_sig($token,$sig); // todo: Token import // Show / etc. $data['page_title'] = __("DCL Key Import"); $data['token'] = $token; diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php index 1f9ba7c1a..a888e5914 100644 --- a/application/models/Dcl_model.php +++ b/application/models/Dcl_model.php @@ -31,6 +31,17 @@ class Dcl_model extends CI_Model { $this->user_options_model->del_option('dcl', 'dcl_key',array('option_key' => $call)); } + function check_dcl_sig($string,$sig) { + try { + $sig = sodium_base642bin($sig, SODIUM_BASE64_VARIANT_URLSAFE); + $public_key=sodium_base642bin('nV46R47wlLDOJAkSs5RT00wzgz3z98uZFxjo3FSkxeg=',SODIUM_BASE64_VARIANT_URLSAFE); + return sodium_crypto_sign_verify_detached($sig, $string, $public_key); + } catch (Exception $e) { + log_message("Error","DCL: Error while checking signature: ".$e); + return false; + } + } + function last_upload($key, $message, $user_id) { $this->load->model('user_options_model'); $dclrkey=$this->user_options_model->get_options('dcl', array('option_name'=>'dcl_key'), $user_id)->result(); diff --git a/application/views/dcl_views/key_import.php b/application/views/dcl_views/key_import.php index ea0ea86a9..32667a8e5 100644 --- a/application/views/dcl_views/key_import.php +++ b/application/views/dcl_views/key_import.php @@ -1,4 +1,4 @@ -
+

From a4a62dc8db7f14067a7c28bd05411b2c8c3f92ea Mon Sep 17 00:00:00 2001 From: int2001 Date: Mon, 9 Jun 2025 06:06:58 +0000 Subject: [PATCH 09/49] Untested get_info_dcl Part --- application/models/Dcl_model.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php index a888e5914..f7668fa78 100644 --- a/application/models/Dcl_model.php +++ b/application/models/Dcl_model.php @@ -31,6 +31,31 @@ class Dcl_model extends CI_Model { $this->user_options_model->del_option('dcl', 'dcl_key',array('option_key' => $call)); } + function get_dcl_info($token) { + if (($token ?? '') != '') { + try { + $dclUrl = 'https://dings.dcl.darc.de/api/get_info'; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $dclUrl); + curl_setopt($ch, CURLOPT_HEADER, array('Content-Type: application/json' , "Authorization: Bearer ".$token)); + curl_setopt($ch, CURLOPT_USERAGENT, 'Wavelog DCL Connector'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + $rawdcldata = curl_exec($ch); + curl_close($ch); + if (strlen($dcldata)>100) { + $dcldata=json_decode($rawdcldata); + // todo: process Data from DCL (Contains valid call(s), valid date, DOK) + } + } catch (Exception $e) { + return false; + } + } else { + return false; + } + } + function check_dcl_sig($string,$sig) { try { $sig = sodium_base642bin($sig, SODIUM_BASE64_VARIANT_URLSAFE); From 5f52225fb2f0c36008ee2ac85e17d20ffd2f7cb3 Mon Sep 17 00:00:00 2001 From: int2001 Date: Fri, 20 Jun 2025 06:05:12 +0000 Subject: [PATCH 10/49] Added URL / Handler for get_info --- application/controllers/Dcl.php | 5 +++++ application/models/Dcl_model.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index e4aaab015..fdf273880 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -26,6 +26,11 @@ class Dcl extends CI_Controller { // todo: Token import // Show / etc. $data['page_title'] = __("DCL Key Import"); $data['token'] = $token; + if ($data['is_valid']) { + $data['dcl_info']=$this->dcl_model->get_info($token); + } else { + $data['dcl_info']=''; + } $this->load->view('interface_assets/header', $data); $this->load->view('dcl_views/key_import',$data); $this->load->view('interface_assets/footer'); diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php index f7668fa78..1f7b4ed8a 100644 --- a/application/models/Dcl_model.php +++ b/application/models/Dcl_model.php @@ -34,7 +34,7 @@ class Dcl_model extends CI_Model { function get_dcl_info($token) { if (($token ?? '') != '') { try { - $dclUrl = 'https://dings.dcl.darc.de/api/get_info'; + $dclUrl = 'https://dings.dcl.darc.de/api/getuserinfo/'+$token; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $dclUrl); From 0c4ee24c1dd344ec6147a652296e45f96a240049 Mon Sep 17 00:00:00 2001 From: int2001 Date: Fri, 20 Jun 2025 10:25:32 +0000 Subject: [PATCH 11/49] Prettified Output --- application/controllers/Dcl.php | 3 +- application/models/Dcl_model.php | 6 ++-- application/views/dcl_views/key_import.php | 41 ++++++++++++++++++---- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index fdf273880..7e782353d 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -17,6 +17,7 @@ class Dcl extends CI_Controller { $this->load->library('Permissions'); $this->load->model('user_model'); $this->load->model('dcl_model'); + $data['date_format']=$this->session->userdata('user_date_format') ?? $this->config->item('qso_date_format'); if (!$this->user_model->authorize(2) || !clubaccess_check(9)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } $sig=($this->input->get('sig',true) ?? ''); @@ -27,7 +28,7 @@ class Dcl extends CI_Controller { $data['page_title'] = __("DCL Key Import"); $data['token'] = $token; if ($data['is_valid']) { - $data['dcl_info']=$this->dcl_model->get_info($token); + $data['dcl_info']=$this->dcl_model->get_dcl_info($token); } else { $data['dcl_info']=''; } diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php index 1f7b4ed8a..2238167d0 100644 --- a/application/models/Dcl_model.php +++ b/application/models/Dcl_model.php @@ -34,19 +34,21 @@ class Dcl_model extends CI_Model { function get_dcl_info($token) { if (($token ?? '') != '') { try { - $dclUrl = 'https://dings.dcl.darc.de/api/getuserinfo/'+$token; + $dclUrl = 'https://dings.dcl.darc.de/api/getuserinfo/'.$token; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $dclUrl); curl_setopt($ch, CURLOPT_HEADER, array('Content-Type: application/json' , "Authorization: Bearer ".$token)); curl_setopt($ch, CURLOPT_USERAGENT, 'Wavelog DCL Connector'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); $rawdcldata = curl_exec($ch); curl_close($ch); - if (strlen($dcldata)>100) { + if (strlen($rawdcldata)>100) { $dcldata=json_decode($rawdcldata); // todo: process Data from DCL (Contains valid call(s), valid date, DOK) + return $dcldata; } } catch (Exception $e) { return false; diff --git a/application/views/dcl_views/key_import.php b/application/views/dcl_views/key_import.php index 32667a8e5..08899dc13 100644 --- a/application/views/dcl_views/key_import.php +++ b/application/views/dcl_views/key_import.php @@ -15,14 +15,43 @@
- :
- - Key is valid - - Key is invalid + + +
: + + + + + + DOKs as $key => $value) { + echo ""; + echo ""; + echo ""; + echo ""; + } + ?> +
DOK
".$value->dok."".date($date_format,strtotime($value->startDate)).' - '.(date($date_format,strtotime($value->endDate ?? '20991231')))."
+
+ : + + + + + + Callsigns as $key => $value) { + echo ""; + echo ""; + echo ""; + echo ""; + } + echo "
".$value->callsign."".date($date_format,strtotime($value->startDate)).' - '.(date($date_format,strtotime($value->endDate ?? '20991231')))."
"; + } else { ?> +
- +
From 19e0e65a0d6725d7032680b331b1577826c33857 Mon Sep 17 00:00:00 2001 From: int2001 Date: Sun, 17 Aug 2025 15:33:39 +0000 Subject: [PATCH 12/49] Store keys --- application/controllers/Dcl.php | 80 ++++++++++------------ application/models/Dcl_model.php | 33 ++++----- application/views/dcl_views/index.php | 31 ++++----- application/views/dcl_views/key_import.php | 1 - 4 files changed, 62 insertions(+), 83 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index 7e782353d..2acfc2bd4 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -7,18 +7,22 @@ class Dcl extends CI_Controller { parent::__construct(); $this->load->helper(array('form', 'url')); + $this->load->model('user_model'); + if (!$this->user_model->authorize(2) || !clubaccess_check(9)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } if (ENVIRONMENT == 'maintenance' && $this->session->userdata('user_id') == '') { echo __("Maintenance Mode is active. Try again later.")."\n"; redirect('user/login'); } } + public function save_key() { + $this->load->model('Dcl_model'); + $this->Dcl_model->store_key($call); + } public function key_import() { $this->load->library('Permissions'); - $this->load->model('user_model'); $this->load->model('dcl_model'); $data['date_format']=$this->session->userdata('user_date_format') ?? $this->config->item('qso_date_format'); - if (!$this->user_model->authorize(2) || !clubaccess_check(9)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } $sig=($this->input->get('sig',true) ?? ''); $token=($this->input->get('token',true) ?? ''); @@ -29,6 +33,7 @@ class Dcl extends CI_Controller { $data['token'] = $token; if ($data['is_valid']) { $data['dcl_info']=$this->dcl_model->get_dcl_info($token); + $this->dcl_model->store_key(json_encode($data['dcl_info'] ?? '')); } else { $data['dcl_info']=''; } @@ -47,13 +52,13 @@ class Dcl extends CI_Controller { // Load required models for page generation $this->load->model('Dcl_model'); + $data['date_format']=$this->session->userdata('user_date_format') ?? $this->config->item('qso_date_format'); // Get Array of the logged in users LoTW certs. $dclkeys=($this->Dcl_model->dcl_keys($this->session->userdata('user_id')) ?? ''); $i=0; foreach ($dclkeys as $dclkey) { $data['dcl_keys'][$i] = json_decode($dclkey->option_value ?? ''); - $data['dcl_keys'][$i]->call = $dclkey->option_key ?? ''; $i++; } @@ -113,17 +118,8 @@ class Dcl extends CI_Controller { $this->load->model('Dcl_model'); $data['station_profile'] = $station_profile; $key_info = $this->Dcl_model->find_key($station_profile->station_callsign, $station_profile->user_id); - if (($key_info ?? '') == '') { - continue; - } - - $data['dcl_key_info']=new stdClass(); - $data['dcl_key_info']->call = $key_info[0]->option_key ?? ''; - $data['dcl_key_info'] = json_decode($key_info[0]->option_value ?? ''); - // If Station Profile has no DCL Key continue on. - if (($data['dcl_key_info']->call ?? '') == '') { - echo $station_profile->station_callsign.": No DCL Key for station callsign found.
"; + if (($key_info ?? '') == '') { continue; } @@ -152,7 +148,7 @@ class Dcl extends CI_Controller { } // Build Filename - $filename_for_saving = './uploads/dcl/'.preg_replace('/[^a-z0-9]+/', '-', strtolower($data['dcl_key_info']->call))."-".date("Y-m-d-H-i-s")."-wavelog.adif"; + $filename_for_saving = './uploads/dcl/'.preg_replace('/[^a-z0-9]+/', '-', strtolower($key_info))."-".date("Y-m-d-H-i-s")."-wavelog.adif"; $fp = fopen($filename_for_saving, "w"); fwrite($fp, $adif_to_save); @@ -196,7 +192,6 @@ class Dcl extends CI_Controller { if(curl_errno($ch)){ echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Failed - ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")
"; - $this->Dcl_model->last_upload($data['dcl_key_info']->call, "Upload failed", $this->session->userdata('user_id')); if (curl_errno($ch) == 28) { // break on timeout echo "Timeout reached. Stopping subsequent uploads.
"; break; @@ -210,7 +205,6 @@ class Dcl extends CI_Controller { if ($pos === false) { // Upload of TQ8 Failed for unknown reason echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Failed - ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")
"; - $this->Dcl_model->last_upload($data['dcl_key_info']->call, "Upload failed", $this->session->userdata('user_id')); if (curl_errno($ch) == 28) { // break on timeout echo "Timeout reached. Stopping subsequent uploads.
"; break; @@ -219,7 +213,6 @@ class Dcl extends CI_Controller { } } else { echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Successful - ".$filename_for_saving."
"; - $this->Dcl_model->last_upload($data['dcl_key_info']->call, "Success", $this->session->userdata('user_id')); // Mark QSOs as Sent foreach ($qso_id_array as $qso_number) { // todo: uncomment when ready @@ -243,12 +236,11 @@ class Dcl extends CI_Controller { echo $this->dcl_download($sync_user_id); } - public function delete_key($call) { - $call=str_replace('_','/',xss_clean($call)); + public function delete_key() { $this->load->model('user_model'); if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } $this->load->model('Dcl_model'); - $this->Dcl_model->delete_key($call); + $this->Dcl_model->delete_key(); $this->session->set_flashdata('success', __("Key Deleted.")); redirect('dcl'); } @@ -467,73 +459,73 @@ class Dcl extends CI_Controller { $this->load->model('logbook_model'); $this->load->model('Stations'); - $query = $this->user_model->get_all_lotw_users(); + $query = $this->user_model->get_all_dcl_users(); if ($query->num_rows() >= 1) { $result = ''; - // Get URL for downloading LoTW - $url_query = $this->db->query('SELECT lotw_download_url FROM config'); + // Get URL for downloading DCL + $url_query = $this->db->query('SELECT dcl_download_url FROM config'); $q = $url_query->row(); - $lotw_base_url = $q->lotw_download_url; + $dcl_base_url = $q->dcl_download_url; foreach ($query->result() as $user) { if ( ($sync_user_id != null) && ($sync_user_id != $user->user_id) ) { continue; } $station_ids=$this->Stations->all_station_ids_of_user($user->user_id); if ($station_ids == '') { continue; } // User has no Station-ID! next one - // Validate that LoTW credentials are not empty + // Validate that DCL credentials are not empty // TODO: We don't actually see the error message - if ($user->user_lotw_password == '') { - $result = "You have not defined your ARRL LoTW credentials!"; + if ($user->user_dcl_password == '') { + $result = "You have not defined your ARRL DCL credentials!"; continue; } $config['upload_path'] = './uploads/'; - $file = $config['upload_path'] . 'lotwreport_download_'.$user->user_id.'_auto.adi'; + $file = $config['upload_path'] . 'dclreport_download_'.$user->user_id.'_auto.adi'; if (file_exists($file) && ! is_writable($file)) { $result = "Temporary download file ".$file." is not writable. Aborting!"; continue; } - // Get credentials for LoTW - $data['user_lotw_name'] = urlencode($user->user_lotw_name); - $data['user_lotw_password'] = urlencode($user->user_lotw_password); + // Get credentials for DCL + $data['user_dcl_name'] = urlencode($user->user_dcl_name); + $data['user_dcl_password'] = urlencode($user->user_dcl_password); - $lotw_last_qsl_date = date('Y-m-d', strtotime($this->logbook_model->lotw_last_qsl_date($user->user_id))); + $dcl_last_qsl_date = date('Y-m-d', strtotime($this->logbook_model->dcl_last_qsl_date($user->user_id))); - // Build URL for LoTW report file - $lotw_url = $lotw_base_url."?"; - $lotw_url .= "login=" . $data['user_lotw_name']; - $lotw_url .= "&password=" . $data['user_lotw_password']; - $lotw_url .= "&qso_query=1&qso_qsl='yes'&qso_qsldetail='yes'&qso_mydetail='yes'"; + // Build URL for DCL report file + $dcl_url = $dcl_base_url."?"; + $dcl_url .= "login=" . $data['user_dcl_name']; + $dcl_url .= "&password=" . $data['user_dcl_password']; + $dcl_url .= "&qso_query=1&qso_qsl='yes'&qso_qsldetail='yes'&qso_mydetail='yes'"; - $lotw_url .= "&qso_qslsince="; - $lotw_url .= "$lotw_last_qsl_date"; + $dcl_url .= "&qso_qslsince="; + $dcl_url .= "$dcl_last_qsl_date"; if (! is_writable(dirname($file))) { $result = "Temporary download directory ".dirname($file)." is not writable. Aborting!"; continue; } $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $lotw_url); + curl_setopt($ch, CURLOPT_URL, $dcl_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); $content = curl_exec($ch); if(curl_errno($ch)) { - $result = "LoTW download failed for user ".$data['user_lotw_name'].": ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")."; + $result = "DCL download failed for user ".$data['user_dcl_name'].": ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")."; if (curl_errno($ch) == 28) { // break on timeout $result .= "
Timeout reached. Stopping subsequent downloads."; break; } continue; } else if(str_contains($content,"Username/password incorrect")) { - $result = "LoTW download failed for user ".$data['user_lotw_name'].": Username/password incorrect"; + $result = "DCL download failed for user ".$data['user_dcl_name'].": Username/password incorrect"; continue; } file_put_contents($file, $content); if (file_get_contents($file, false, null, 0, 39) != "ARRL Logbook of the World Status Report") { - $result = "Downloaded LoTW report for user ".$data['user_lotw_name']." is invalid. Check your credentials."; + $result = "Downloaded DCL report for user ".$data['user_dcl_name']." is invalid. Check your credentials."; continue; } @@ -542,7 +534,7 @@ class Dcl extends CI_Controller { } return $result; } else { - return "No LoTW User details found to carry out matches."; + return "No DCL User details found to carry out matches."; } } diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php index 2238167d0..e37034690 100644 --- a/application/models/Dcl_model.php +++ b/application/models/Dcl_model.php @@ -18,17 +18,22 @@ class Dcl_model extends CI_Model { function find_key($callsign, $user_id) { $this->load->model('user_options_model'); - return $this->user_options_model->get_options('dcl', array('option_name'=>'dcl_key','option_key'=>$callsign), $user_id)->result(); + $userkeys=$this->user_options_model->get_options('dcl', array('option_name'=>'dcl_key','option_key'=>'key'), $user_id)->result(); + foreach ($userkeys->Callsigns as $item) { + if (isset($item['callsign']) && $item['callsign'] === $callsign) { + return $userkeys->UserKeys->token; + } + } + return ''; } - function store_key($user_id, $callsign, $date_created, $date_expires, $qso_start_date, $qso_end_date, $cert_key, $general_cert) { + function store_key($key) { + $this->load->model('user_options_model'); + $this->user_options_model->set_option('dcl', 'dcl_key', array('key'=>$key)); } - function update_key($user_id, $callsign, $dxcc, $date_created, $date_expires, $qso_start_date, $qso_end_date, $cert_key, $general_cert) { - } - - function delete_key($call) { - $this->user_options_model->del_option('dcl', 'dcl_key',array('option_key' => $call)); + function delete_key() { + $this->user_options_model->del_option('dcl', 'dcl_key',array('option_key' => 'key')); } function get_dcl_info($token) { @@ -68,19 +73,5 @@ class Dcl_model extends CI_Model { return false; } } - - function last_upload($key, $message, $user_id) { - $this->load->model('user_options_model'); - $dclrkey=$this->user_options_model->get_options('dcl', array('option_name'=>'dcl_key'), $user_id)->result(); - $dclkey = json_decode($dclrkey[0]->option_value ?? ''); - $dclkey->call = $dclrkey[0]->option_key ?? ''; - $dclnewkey=$dclkey; - - if ($message == "Success") { - $dclnewkey->last_sync=date("Y-m-d H:i:s"); - $this->user_options_model->set_option('dcl', 'dcl_key', array($dclkey->call => json_encode($dclnewkey)), $user_id); - } - return "Updated"; - } } ?> diff --git a/application/views/dcl_views/index.php b/application/views/dcl_views/index.php index a53f6eddd..d719872df 100644 --- a/application/views/dcl_views/index.php +++ b/application/views/dcl_views/index.php @@ -6,6 +6,7 @@
@@ -25,33 +26,29 @@ - - + + - + Callsigns as $dcl_call) { + ?> - call; ?> + callsign; ?> + startDate)); + $vt = date($date_format,strtotime($dcl_call->endDate ?? '2099-12-31')); + ?> + + - last_sync ?? '1970-01-01'); - $last_upload = date($this->config->item('qso_date_format').' H:i:s', $last_upload_ts); - if ($last_upload_ts == strtotime('1970-01-01')) { ?> - " class="badge text-bg-danger"> - - - - - key; ?> - - - + diff --git a/application/views/dcl_views/key_import.php b/application/views/dcl_views/key_import.php index 08899dc13..6be2298fa 100644 --- a/application/views/dcl_views/key_import.php +++ b/application/views/dcl_views/key_import.php @@ -51,7 +51,6 @@
- From 1b5c51ace0666073ed51b27496a54fd4950a4246 Mon Sep 17 00:00:00 2001 From: int2001 Date: Sun, 17 Aug 2025 15:34:36 +0000 Subject: [PATCH 13/49] Remove Options --- application/views/dcl_views/index.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/application/views/dcl_views/index.php b/application/views/dcl_views/index.php index d719872df..61ed2f396 100644 --- a/application/views/dcl_views/index.php +++ b/application/views/dcl_views/index.php @@ -28,7 +28,6 @@ - @@ -45,8 +44,6 @@ ?> - - From 9da642baa6928550de157e11e807d9408bfd4bcd Mon Sep 17 00:00:00 2001 From: int2001 Date: Sun, 17 Aug 2025 15:47:46 +0000 Subject: [PATCH 14/49] implemented valid_from and valid_till --- application/controllers/Dcl.php | 4 ++-- application/models/Dcl_model.php | 7 +++++-- application/models/Logbook_model.php | 7 +++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index 2acfc2bd4..54eb5bec6 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -125,7 +125,7 @@ class Dcl extends CI_Controller { $this->load->model('Logbook_model'); - $data['qsos'] = $this->Logbook_model->get_dcl_qsos_to_upload($data['station_profile']->station_id); + $data['qsos'] = $this->Logbook_model->get_dcl_qsos_to_upload($data['station_profile']->station_id,$key_info['vf'],$key_info['vt']); // Nothing to upload if(empty($data['qsos']->result())){ @@ -148,7 +148,7 @@ class Dcl extends CI_Controller { } // Build Filename - $filename_for_saving = './uploads/dcl/'.preg_replace('/[^a-z0-9]+/', '-', strtolower($key_info))."-".date("Y-m-d-H-i-s")."-wavelog.adif"; + $filename_for_saving = './uploads/dcl/'.preg_replace('/[^a-z0-9]+/', '-', strtolower($key_info['token']))."-".date("Y-m-d-H-i-s")."-wavelog.adif"; $fp = fopen($filename_for_saving, "w"); fwrite($fp, $adif_to_save); diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php index e37034690..9413a6f70 100644 --- a/application/models/Dcl_model.php +++ b/application/models/Dcl_model.php @@ -20,8 +20,11 @@ class Dcl_model extends CI_Model { $this->load->model('user_options_model'); $userkeys=$this->user_options_model->get_options('dcl', array('option_name'=>'dcl_key','option_key'=>'key'), $user_id)->result(); foreach ($userkeys->Callsigns as $item) { - if (isset($item['callsign']) && $item['callsign'] === $callsign) { - return $userkeys->UserKeys->token; + if (isset($item['callsign']) && strtoupper($item['callsign']) === strtoupper($callsign)) { + $key['token']=$userkeys->UserKeys->token; + $key['vt']=strtotime($item->startDate); + $key['vf']=strtotime($item->endDate ?? '2099-12-31'); + return $key; } } return ''; diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index fb8dc1768..0d0fe4325 100644 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -5415,14 +5415,17 @@ class Logbook_model extends CI_Model { return $query->result(); } - function get_dcl_qsos_to_upload($station_id) { + function get_dcl_qsos_to_upload($station_id, $from, $till) { $sql = 'select *, dxcc_entities.name as station_country from ' . $this->config->item('table_name') . ' thcv ' . ' left join station_profile on thcv.station_id = station_profile.station_id' . ' left outer join dxcc_entities on thcv.col_my_dxcc = dxcc_entities.adif' . ' where thcv.station_id = ?' . - ' and (COL_DCL_QSL_SENT not in ("Y","I") OR COL_DCL_QSL_SENT is null)'; + ' and (COL_DCL_QSL_SENT not in ("Y","I") OR COL_DCL_QSL_SENT is null)'. + ' and COL_QSO_DATE>? and COL_QSO_DATEdb->query($sql, $binding); return $query; From 8922b3dc67f477169f4629b6374335f890a769ec Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 06:29:52 +0000 Subject: [PATCH 15/49] Added DCL to Usersettings --- application/controllers/User.php | 10 +++++----- application/models/User_model.php | 2 +- application/views/user/edit.php | 9 +++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/application/controllers/User.php b/application/controllers/User.php index 0a14b312b..736b149b9 100644 --- a/application/controllers/User.php +++ b/application/controllers/User.php @@ -222,7 +222,7 @@ class User extends CI_Controller { $data['user_amsat_status_upload'] = $this->input->post('user_amsat_status_upload'); $data['user_mastodon_url'] = $this->input->post('user_mastodon_url'); $data['user_default_band'] = $this->input->post('user_default_band'); - $data['user_default_confirmation'] = ($this->input->post('user_default_confirmation_qsl') !== null ? 'Q' : '').($this->input->post('user_default_confirmation_lotw') !== null ? 'L' : '').($this->input->post('user_default_confirmation_eqsl') !== null ? 'E' : '').($this->input->post('user_default_confirmation_qrz') !== null ? 'Z' : '').($this->input->post('user_default_confirmation_clublog') !== null ? 'C' : ''); + $data['user_default_confirmation'] = ($this->input->post('user_default_confirmation_qsl') !== null ? 'Q' : '').($this->input->post('user_default_confirmation_lotw') !== null ? 'L' : '').($this->input->post('user_default_confirmation_eqsl') !== null ? 'E' : '').($this->input->post('user_default_confirmation_qrz') !== null ? 'Z' : '').($this->input->post('user_default_confirmation_clublog') !== null ? 'C' : '').($this->input->post('user_default_confirmation_dcl') !== null ? 'D' : ''); $data['user_qso_end_times'] = $this->input->post('user_qso_end_times'); $data['user_quicklog'] = $this->input->post('user_quicklog'); $data['user_quicklog_enter'] = $this->input->post('user_quicklog_enter'); @@ -274,7 +274,7 @@ class User extends CI_Controller { $this->input->post('user_amsat_status_upload'), $this->input->post('user_mastodon_url'), $this->input->post('user_default_band'), - ($this->input->post('user_default_confirmation_qsl') !== null ? 'Q' : '').($this->input->post('user_default_confirmation_lotw') !== null ? 'L' : '').($this->input->post('user_default_confirmation_eqsl') !== null ? 'E' : '').($this->input->post('user_default_confirmation_qrz') !== null ? 'Z' : '').($this->input->post('user_default_confirmation_clublog') !== null ? 'C' : ''), + ($this->input->post('user_default_confirmation_qsl') !== null ? 'Q' : '').($this->input->post('user_default_confirmation_lotw') !== null ? 'L' : '').($this->input->post('user_default_confirmation_eqsl') !== null ? 'E' : '').($this->input->post('user_default_confirmation_qrz') !== null ? 'Z' : '').($this->input->post('user_default_confirmation_clublog') !== null ? 'C' : '').($this->input->post('user_default_confirmation_dcl') !== null ? 'D' : ''), $this->input->post('user_qso_end_times'), $this->input->post('user_quicklog'), $this->input->post('user_quicklog_enter'), @@ -352,7 +352,7 @@ class User extends CI_Controller { $data['user_amsat_status_upload'] = $this->input->post('user_amsat_status_upload'); $data['user_mastodon_url'] = $this->input->post('user_mastodon_url'); $data['user_default_band'] = $this->input->post('user_default_band'); - $data['user_default_confirmation'] = ($this->input->post('user_default_confirmation_qsl') !== null ? 'Q' : '').($this->input->post('user_default_confirmation_lotw') !== null ? 'L' : '').($this->input->post('user_default_confirmation_eqsl') !== null ? 'E' : '').($this->input->post('user_default_confirmation_qrz') !== null ? 'Z' : '').($this->input->post('user_default_confirmation_clublog') !== null ? 'C' : ''); + $data['user_default_confirmation'] = ($this->input->post('user_default_confirmation_qsl') !== null ? 'Q' : '').($this->input->post('user_default_confirmation_lotw') !== null ? 'L' : '').($this->input->post('user_default_confirmation_eqsl') !== null ? 'E' : '').($this->input->post('user_default_confirmation_qrz') !== null ? 'Z' : '').($this->input->post('user_default_confirmation_clublog') !== null ? 'C' : '').($this->input->post('user_default_confirmation_dcl') !== null ? 'D' : ''); $data['user_qso_end_times'] = $this->input->post('user_qso_end_times'); $data['user_quicklog'] = $this->input->post('user_quicklog'); $data['user_quicklog_enter'] = $this->input->post('user_quicklog_enter'); @@ -647,7 +647,7 @@ class User extends CI_Controller { } if($this->input->post('user_default_confirmation')) { - $data['user_default_confirmation'] = ($this->input->post('user_default_confirmation_qsl') !== null ? 'Q' : '').($this->input->post('user_default_confirmation_lotw') !== null ? 'L' : '').($this->input->post('user_default_confirmation_eqsl') !== null ? 'E' : '').($this->input->post('user_default_confirmation_qrz') !== null ? 'Z' : '').($this->input->post('user_default_confirmation_clublog') !== null ? 'C' : ''); + $data['user_default_confirmation'] = ($this->input->post('user_default_confirmation_qsl') !== null ? 'Q' : '').($this->input->post('user_default_confirmation_lotw') !== null ? 'L' : '').($this->input->post('user_default_confirmation_eqsl') !== null ? 'E' : '').($this->input->post('user_default_confirmation_qrz') !== null ? 'Z' : '').($this->input->post('user_default_confirmation_clublog') !== null ? 'C' : '').($this->input->post('user_default_confirmation_dcl') !== null ? 'D' : ''); } else { $data['user_default_confirmation'] = $q->user_default_confirmation; } @@ -959,7 +959,7 @@ class User extends CI_Controller { $data['user_amsat_status_upload'] = $this->input->post('user_amsat_status_upload'); $data['user_mastodon_url'] = $this->input->post('user_mastodon_url'); $data['user_default_band'] = $this->input->post('user_default_band'); - $data['user_default_confirmation'] = ($this->input->post('user_default_confirmation_qsl') !== null ? 'Q' : '').($this->input->post('user_default_confirmation_lotw') !== null ? 'L' : '').($this->input->post('user_default_confirmation_eqsl') !== null ? 'E' : '').($this->input->post('user_default_confirmation_qrz') !== null ? 'Z' : '').($this->input->post('user_default_confirmation_clublog') !== null ? 'C' : ''); + $data['user_default_confirmation'] = ($this->input->post('user_default_confirmation_qsl') !== null ? 'Q' : '').($this->input->post('user_default_confirmation_lotw') !== null ? 'L' : '').($this->input->post('user_default_confirmation_eqsl') !== null ? 'E' : '').($this->input->post('user_default_confirmation_qrz') !== null ? 'Z' : '').($this->input->post('user_default_confirmation_clublog') !== null ? 'C' : '').($this->input->post('user_default_confirmation_dcl') !== null ? 'D' : ''); $data['user_qso_end_times'] = $this->input->post('user_qso_end_times'); $data['user_quicklog'] = $this->input->post('user_quicklog'); $data['user_quicklog_enter'] = $this->input->post('user_quicklog_enter'); diff --git a/application/models/User_model.php b/application/models/User_model.php index f08f6daef..fb2be7cbd 100644 --- a/application/models/User_model.php +++ b/application/models/User_model.php @@ -347,7 +347,7 @@ class User_Model extends CI_Model { 'user_amsat_status_upload' => xss_clean($fields['user_amsat_status_upload']), 'user_mastodon_url' => xss_clean($fields['user_mastodon_url']), 'user_default_band' => xss_clean($fields['user_default_band']), - 'user_default_confirmation' => (isset($fields['user_default_confirmation_qsl']) ? 'Q' : '').(isset($fields['user_default_confirmation_lotw']) ? 'L' : '').(isset($fields['user_default_confirmation_eqsl']) ? 'E' : '').(isset($fields['user_default_confirmation_qrz']) ? 'Z' : '').(isset($fields['user_default_confirmation_clublog']) ? 'C' : ''), + 'user_default_confirmation' => (isset($fields['user_default_confirmation_qsl']) ? 'Q' : '').(isset($fields['user_default_confirmation_lotw']) ? 'L' : '').(isset($fields['user_default_confirmation_eqsl']) ? 'E' : '').(isset($fields['user_default_confirmation_qrz']) ? 'Z' : '').(isset($fields['user_default_confirmation_clublog']) ? 'C' : '').(isset($fields['user_default_confirmation_dcl']) ? 'D' : ''), 'user_qso_end_times' => xss_clean($fields['user_qso_end_times']), 'user_quicklog' => xss_clean($fields['user_quicklog']), 'user_quicklog_enter' => xss_clean($fields['user_quicklog_enter']), diff --git a/application/views/user/edit.php b/application/views/user/edit.php index 0993c942c..9d76a2192 100644 --- a/application/views/user/edit.php +++ b/application/views/user/edit.php @@ -593,6 +593,7 @@ + @@ -806,6 +807,14 @@ echo '>'; ?> +
+ '; ?> + +
From 9b18cb98594fb0364c835589335a610ce14dfc98 Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 06:32:05 +0000 Subject: [PATCH 16/49] Added DCL to Genfunctions-Lib --- application/libraries/Genfunctions.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/application/libraries/Genfunctions.php b/application/libraries/Genfunctions.php index e6ace37fa..7dbaedb45 100644 --- a/application/libraries/Genfunctions.php +++ b/application/libraries/Genfunctions.php @@ -12,6 +12,7 @@ class Genfunctions (($postdata['qrz'] ?? '') != '') || (($postdata['lotw'] ?? '') != '') || (($postdata['qsl'] ?? '') != '') || + (($postdata['dcl'] ?? '') != '') || (($postdata['eqsl'] ?? '') != '') ) { $sql .= ' and ('; if (($postdata['qsl'] ?? '') != '') { @@ -29,6 +30,9 @@ class Genfunctions if (($postdata['clublog'] ?? '') != '') { array_push($qsl, "COL_CLUBLOG_QSO_DOWNLOAD_STATUS = 'Y'"); } + if (($postdata['dcl'] ?? '') != '') { + array_push($qsl, "COL_DCL_QSL_RCVD = 'Y'"); + } if (count($qsl) > 0) { $sql .= implode(' or ', $qsl); } else { @@ -68,6 +72,9 @@ class Genfunctions if (($postdata['eqsl'] ?? '')!= '' ) { $qsl .= "E"; } + if (($postdata['dcl'] ?? '')!= '' ) { + $qsl .= "D"; + } if (($postdata['clublog'] ?? '')!= '' ) { $qsl .= "C"; } From 368ef761b985bef1eb9a5b717da36df2a9674c67 Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 06:48:26 +0000 Subject: [PATCH 17/49] Added DCL-Status to QSO-Details-View --- application/views/view_log/qso.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/application/views/view_log/qso.php b/application/views/view_log/qso.php index 307c12507..3090954c0 100644 --- a/application/views/view_log/qso.php +++ b/application/views/view_log/qso.php @@ -566,10 +566,20 @@

COL_CLUBLOG_QSO_DOWNLOAD_DATE); echo date($custom_date_format, $timestamp); ?>.

- COL_CLUBLOG_QSO_DOWNLOAD_STATUS == "Y" && $row->COL_CLUBLOG_QSO_DOWNLOAD_DATE == null) { ?> + COL_CLUBLOG_QSO_DOWNLOAD_STATUS == "Y" && $row->COL_CLUBLOG_QSO_DOWNLOAD_DATE == null) { ?>

+ + COL_DCL_QSL_RCVD == "Y" && $row->COL_DCL_QSLRDATE != null) { ?> +

+

COL_DCL_QSLRDATE); echo date($custom_date_format, $timestamp); ?>.

+ + + COL_DCL_QSL_RCVD == "Y" && $row->COL_DCL_QSLRDATE == null) { ?> +

+

+
From 283d4419851543d6292f89c0d3d85389e10b894a Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 07:05:41 +0000 Subject: [PATCH 18/49] Added DCL-Arrows to partial-view --- .../views/view_log/partial/log_ajax.php | 39 +++++++++++++++++++ assets/css/general.css | 20 ++++++++++ 2 files changed, 59 insertions(+) diff --git a/application/views/view_log/partial/log_ajax.php b/application/views/view_log/partial/log_ajax.php index c8118c436..ca50e5794 100644 --- a/application/views/view_log/partial/log_ajax.php +++ b/application/views/view_log/partial/log_ajax.php @@ -221,6 +221,9 @@ function echoQrbCalcLink($mygrid, $grid, $vucc, $isVisitor = false) { session->userdata('user_default_confirmation'),'C') !== false ) { ?> + session->userdata('user_default_confirmation'),'D') !== false ) { ?> + + config->item('use_auth')) && ($this->session->userdata('user_type') >= 2)) { ?> @@ -464,6 +467,42 @@ function echoQrbCalcLink($mygrid, $grid, $vucc, $isVisitor = false) { } ?> class="clublog-COL_CLUBLOG_QSO_DOWNLOAD_STATUS=='Y')?'green':'red'?>">▼ + session->userdata('user_default_confirmation'),'D') !== false ) { ?> + + COL_DCL_QSL_SENT == "Y") { + echo 'title="'.__("Sent").($row->COL_DCL_QSLSDATE != null ? " ".date($custom_date_format, strtotime($row->COL_DCL_QSLSDATE)) : '').'" data-bs-toggle="tooltip"'; + } elseif ($row->COL_DCL_QSL_SENT == 'M') { + echo 'title="'.__("Modified"); + if ($row->COL_DCL_QSLSDATE != null) { + echo "
(".__("last sent")." ".date($custom_date_format, strtotime($row->COL_DCL_QSLSDATE)).")"; + } + echo '" data-bs-toggle="tooltip" data-bs-html="true"'; + } elseif ($row->COL_DCL_QSL_SENT == 'I') { + echo 'title="'.__("Invalid (Ignore)").'" data-bs-toggle="tooltip"'; + }?> class="dcl-COL_DCL_QSL_SENT == 'Y') { + echo 'green'; + } elseif ($row->COL_DCL_QSL_SENT == 'M') { + echo 'yellow'; + } elseif ($row->COL_DCL_QSL_SENT == 'I') { + echo 'grey'; + } else { + echo 'red'; + } ?>">▲
+ COL_DCL_QSL_RCVD == "Y") { + echo "title=\"".__("Received"); + if ($row->COL_DCL_QSLRDATE != null) { + $timestamp = strtotime($row->COL_DCL_QSLRDATE); + echo " ".($timestamp!=''?date($custom_date_format, $timestamp):''); + } + echo "\" data-bs-toggle=\"tooltip\""; + } ?> class="dcl-COL_DCL_QSL_RCVD=='Y')?'green':'red'?>">▼ + diff --git a/assets/css/general.css b/assets/css/general.css index 925e1be9d..94d6d99f6 100644 --- a/assets/css/general.css +++ b/assets/css/general.css @@ -365,6 +365,26 @@ TD.clublog { font-size: 1.1em; } +TD.dcl { + width: 33px; + white-space: nowrap; +} + +.dcl-green { + color: #00a000 !important; + font-size: 1.1em; +} + +.dcl-yellow { + color: #d39e00 !important; + font-size: 1.1em; +} + +.dcl-red { + color: #f00 !important; + font-size: 1.1em; +} + TD.qrz { width: 33px; white-space: nowrap; From 3090048250698654260cff6a2dae9ee3029aa243 Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 07:48:18 +0000 Subject: [PATCH 19/49] Added DCL-Arrows to Searchresults and log_ajax --- application/controllers/Logbook.php | 2 +- .../views/search/search_result_ajax.php | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/application/controllers/Logbook.php b/application/controllers/Logbook.php index b5ee58806..788751edb 100644 --- a/application/controllers/Logbook.php +++ b/application/controllers/Logbook.php @@ -938,7 +938,7 @@ class Logbook extends CI_Controller { } function querydb($id) { - $this->db->select('dxcc_entities.adif, lotw_users.callsign, COL_BAND, COL_CALL, COL_CLUBLOG_QSO_DOWNLOAD_DATE, + $this->db->select('dxcc_entities.adif, lotw_users.callsign, COL_BAND, COL_CALL, COL_CLUBLOG_QSO_DOWNLOAD_DATE, COL_DCL_QSLRDATE, COL_DCL_QSLSDATE, COL_DCL_QSL_SENT, COL_DCL_QSL_RCVD, COL_CLUBLOG_QSO_DOWNLOAD_STATUS, COL_CLUBLOG_QSO_UPLOAD_DATE, COL_CLUBLOG_QSO_UPLOAD_STATUS, COL_CONTEST_ID, COL_DISTANCE, COL_EQSL_QSL_RCVD, COL_EQSL_QSLRDATE, COL_EQSL_QSLSDATE, COL_EQSL_QSL_SENT, COL_FREQ, COL_GRIDSQUARE, COL_IOTA, COL_LOTW_QSL_RCVD, COL_LOTW_QSLRDATE, COL_LOTW_QSLSDATE, diff --git a/application/views/search/search_result_ajax.php b/application/views/search/search_result_ajax.php index 2964092c4..43e8bfde7 100644 --- a/application/views/search/search_result_ajax.php +++ b/application/views/search/search_result_ajax.php @@ -123,6 +123,9 @@ $ci =& get_instance(); session->userdata('user_clublog_name') != ''){ ?> + session->userdata('user_default_confirmation'),'D') !== false ) { ?> + + config->item('use_auth')) && ($this->session->userdata('user_type') >= 2)) { ?> @@ -397,6 +400,42 @@ $ci =& get_instance(); echo ''; } ?> + session->userdata('user_default_confirmation'),'D') !== false ) { + echo ''; + echo 'COL_DCL_QSL_SENT == "Y") { + echo "title=\"DCL ".__("Sent"); + if ($row->COL_DCL_QSLSDATE != null) { + $timestamp = strtotime($row->COL_DCL_QSLSDATE); + echo " ".($timestamp != '' ? date($custom_date_format, $timestamp) : ''); + } + echo "\" data-bs-toggle=\"tooltip\""; + } + echo ' class="dcl-'; + if ($row->COL_DCL_QSL_SENT=='Y') { + echo "green"; + } elseif ($row->COL_DCL_QSL_SENT=='M') { + echo "yellow"; + } else { + echo "red"; + } + echo '">▲'; + + echo 'COL_DCL_QSL_RCVD == "Y") { + echo "title=\"DCL ".__("Received"); + if ($row->COL_DCL_QSLRDATE != null) { + $timestamp = strtotime($row->COL_DCL_QSLRDATE); + echo " ".($timestamp != '' ? date($custom_date_format, $timestamp) : ''); + } + echo "\" data-bs-toggle=\"tooltip\""; + } + echo ' class="dcl-'; + echo ($row->COL_DCL_QSL_RCVD=='Y')?'green':'red'; + echo '">▼'; + echo ''; + } ?> + station_callsign)) { ?> From 5c71d90fa3a0baded7ca79bbd694203bf7d4cd3f Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 08:58:27 +0000 Subject: [PATCH 20/49] find_key refactor --- application/controllers/Dcl.php | 36 +++++--------------------------- application/models/Dcl_model.php | 18 +++++++++------- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index 54eb5bec6..895722747 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -91,7 +91,7 @@ class Dcl extends CI_Controller { } if ($this->user_model->authorize(2)) { - if (!($this->config->item('disable_manual_lotw'))) { + if (!($this->config->item('disable_manual_dcl'))) { $station_profiles = $this->Stations->all_of_user($this->session->userdata('user_id')); $sync_user_id=$this->session->userdata('user_id'); } else { @@ -113,7 +113,6 @@ class Dcl extends CI_Controller { foreach ($station_profiles->result() as $station_profile) { - log_message("Error",$station_profile->station_id); // Get Certificate Data $this->load->model('Dcl_model'); $data['station_profile'] = $station_profile; @@ -140,28 +139,10 @@ class Dcl extends CI_Controller { } // Build File to save - $adif_to_save = $this->load->view('adif/data/dcl.php', $data, TRUE); + $adif_to_post = $this->load->view('adif/data/dcl.php', $data, TRUE); - // create folder to store upload file - if (!file_exists('./uploads/dcl')) { - mkdir('./uploads/dcl', 0775, true); - } - - // Build Filename - $filename_for_saving = './uploads/dcl/'.preg_replace('/[^a-z0-9]+/', '-', strtolower($key_info['token']))."-".date("Y-m-d-H-i-s")."-wavelog.adif"; - - $fp = fopen($filename_for_saving, "w"); - fwrite($fp, $adif_to_save); - fclose($fp); - //The URL that accepts the file upload. - $url = 'https://dclnext.darc.de'; - - //The name of the field for the uploaded file. - $uploadFieldName = 'upfile'; - - //The full path to the file that you want to upload - $filePath = realpath($filename_for_saving); + $url = 'http://127.0.0.1:9999'; // todo: final URL //Initiate cURL $ch = curl_init(); @@ -175,16 +156,9 @@ class Dcl extends CI_Controller { //Tell cURL to return the output as a string. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - //Use the recommended way, creating a CURLFile object. - $uploadfile = curl_file_create($filePath); - $uploadfile->setPostFilename(basename($filePath)); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: text/plain']); - //Setup our POST fields - $postFields = array( - $uploadFieldName => $uploadfile - ); - - curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); + curl_setopt($ch, CURLOPT_POSTFIELDS, $adif_to_post); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); // todo: uncomment when ready diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php index 9413a6f70..b6c5633e6 100644 --- a/application/models/Dcl_model.php +++ b/application/models/Dcl_model.php @@ -19,12 +19,17 @@ class Dcl_model extends CI_Model { function find_key($callsign, $user_id) { $this->load->model('user_options_model'); $userkeys=$this->user_options_model->get_options('dcl', array('option_name'=>'dcl_key','option_key'=>'key'), $user_id)->result(); - foreach ($userkeys->Callsigns as $item) { - if (isset($item['callsign']) && strtoupper($item['callsign']) === strtoupper($callsign)) { - $key['token']=$userkeys->UserKeys->token; - $key['vt']=strtotime($item->startDate); - $key['vf']=strtotime($item->endDate ?? '2099-12-31'); - return $key; + foreach ($userkeys as $raw_key) { + $skey=json_decode($raw_key->option_value, true); + if (isset($skey['Callsigns']) && is_array($skey['Callsigns'])) { + foreach ($skey['Callsigns'] as $item) { + if (isset($item['callsign']) && strtoupper($item['callsign']) === strtoupper($callsign)) { + $key['token']=$skey['UserKeys']['token']; + $key['vt']=strtotime($item['startDate']); + $key['vf']=strtotime($item['endDate'] ?? '2099-12-31'); + return $key; + } + } } } return ''; @@ -55,7 +60,6 @@ class Dcl_model extends CI_Model { curl_close($ch); if (strlen($rawdcldata)>100) { $dcldata=json_decode($rawdcldata); - // todo: process Data from DCL (Contains valid call(s), valid date, DOK) return $dcldata; } } catch (Exception $e) { From 651778b443f4f6beb330db55cde1136b2fae8b54 Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 09:17:57 +0000 Subject: [PATCH 21/49] 1st successful generation of payload at /dcl/dcl_upload --- application/controllers/Dcl.php | 4 ++-- application/models/Dcl_model.php | 4 ++-- application/models/Logbook_model.php | 4 ++-- application/models/User_model.php | 7 +++++++ 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index 895722747..953f38a08 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -162,7 +162,7 @@ class Dcl extends CI_Controller { curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); // todo: uncomment when ready - // $result = curl_exec($ch); + $result = curl_exec($ch); if(curl_errno($ch)){ echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Failed - ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")
"; @@ -186,7 +186,7 @@ class Dcl extends CI_Controller { continue; } } else { - echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Successful - ".$filename_for_saving."
"; + echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Successful
"; // Mark QSOs as Sent foreach ($qso_id_array as $qso_number) { // todo: uncomment when ready diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php index b6c5633e6..5e4ee6df1 100644 --- a/application/models/Dcl_model.php +++ b/application/models/Dcl_model.php @@ -25,8 +25,8 @@ class Dcl_model extends CI_Model { foreach ($skey['Callsigns'] as $item) { if (isset($item['callsign']) && strtoupper($item['callsign']) === strtoupper($callsign)) { $key['token']=$skey['UserKeys']['token']; - $key['vt']=strtotime($item['startDate']); - $key['vf']=strtotime($item['endDate'] ?? '2099-12-31'); + $key['vf']=strtotime($item['startDate']); + $key['vt']=strtotime($item['endDate'] ?? '2099-12-31'); return $key; } } diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 47252185a..4cc42f13c 100644 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -5426,8 +5426,8 @@ class Logbook_model extends CI_Model { ' and (COL_DCL_QSL_SENT not in ("Y","I") OR COL_DCL_QSL_SENT is null)'. ' and COL_QSO_DATE>? and COL_QSO_DATEdb->query($sql, $binding); return $query; diff --git a/application/models/User_model.php b/application/models/User_model.php index fb2be7cbd..f41aaf5d0 100644 --- a/application/models/User_model.php +++ b/application/models/User_model.php @@ -55,6 +55,13 @@ class User_Model extends CI_Model { return $r; } + // FUNCTION: object get_all_dcl_users + // Returns all users with dcl details + function get_all_dcl_users() { + // todo Clever (non-CI-)query to fetch all users with DCL-Tokens in option_table + return null; + } + // FUNCTION: object get_by_email($email) // Retrieve a user by email address function get_by_email($email) { From e444db62e107f11b550f7aa2cb19b7f95a381c67 Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 09:54:16 +0000 Subject: [PATCH 22/49] Put token and adif in json // plus debug --- application/controllers/Dcl.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index 953f38a08..7863ad4e8 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -142,7 +142,7 @@ class Dcl extends CI_Controller { $adif_to_post = $this->load->view('adif/data/dcl.php', $data, TRUE); //The URL that accepts the file upload. - $url = 'http://127.0.0.1:9999'; // todo: final URL + $url = 'https://dings.dcl.darc.de/api/adiImport'; // todo: final URL //Initiate cURL $ch = curl_init(); @@ -156,14 +156,17 @@ class Dcl extends CI_Controller { //Tell cURL to return the output as a string. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: text/plain']); - - curl_setopt($ch, CURLOPT_POSTFIELDS, $adif_to_post); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); + + $payload=[]; + $payload['key']=$key_info['token']; + $payload['adif']=$adif_to_post; + + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, true)); // todo: uncomment when ready $result = curl_exec($ch); - + log_message('Error',$result); if(curl_errno($ch)){ echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Failed - ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")
"; if (curl_errno($ch) == 28) { // break on timeout @@ -177,7 +180,6 @@ class Dcl extends CI_Controller { $pos = true; if ($pos === false) { - // Upload of TQ8 Failed for unknown reason echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Failed - ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")
"; if (curl_errno($ch) == 28) { // break on timeout echo "Timeout reached. Stopping subsequent uploads.
"; From 08d24d1b3b66525918acd6be4f59c3b633b0d26e Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 10:46:39 +0000 Subject: [PATCH 23/49] Added to menue --- application/controllers/Dcl.php | 10 +++------- application/views/interface_assets/header.php | 1 + 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index 7863ad4e8..1af84f0a3 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -142,7 +142,7 @@ class Dcl extends CI_Controller { $adif_to_post = $this->load->view('adif/data/dcl.php', $data, TRUE); //The URL that accepts the file upload. - $url = 'https://dings.dcl.darc.de/api/adiImport'; // todo: final URL + $url = 'https://dings.dcl.darc.de/api/adiImport'; // todo: change to final URL b4 release //Initiate cURL $ch = curl_init(); @@ -164,9 +164,8 @@ class Dcl extends CI_Controller { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, true)); - // todo: uncomment when ready $result = curl_exec($ch); - log_message('Error',$result); + // todo: parse output from DCL (contains a lot of information within $result) if(curl_errno($ch)){ echo $station_profile->station_callsign." (".$station_profile->station_profile_name."): Upload Failed - ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")
"; if (curl_errno($ch) == 28) { // break on timeout @@ -195,9 +194,6 @@ class Dcl extends CI_Controller { // $this->Logbook_model->mark_dcl_sent($qso_number); } } - - // todo: uncomment when ready - // unlink(realpath($filename_for_saving)); } } else { echo "No Station Profiles found to upload to DCL"; @@ -209,7 +205,7 @@ class Dcl extends CI_Controller { } else { $sync_user_id=null; } - echo $this->dcl_download($sync_user_id); + // echo $this->dcl_download($sync_user_id); } public function delete_key() { diff --git a/application/views/interface_assets/header.php b/application/views/interface_assets/header.php index affd434da..e0b0f6b84 100644 --- a/application/views/interface_assets/header.php +++ b/application/views/interface_assets/header.php @@ -497,6 +497,7 @@
  • +
  • From b809abea67d0785fd09f843ea995c24ca9b7176d Mon Sep 17 00:00:00 2001 From: Andreas Kristiansen <6977712+AndreasK79@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:40:02 +0200 Subject: [PATCH 24/49] Add DCL to column in LBA --- application/views/logbookadvanced/index.php | 8 ++++++++ assets/js/sections/logbookadvanced.js | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/application/views/logbookadvanced/index.php b/application/views/logbookadvanced/index.php index c02800c27..3a75335c0 100644 --- a/application/views/logbookadvanced/index.php +++ b/application/views/logbookadvanced/index.php @@ -67,6 +67,7 @@ \"county\":{\"show\":\"true\"}, \"qth\":{\"show\":\"true\"}, \"frequency\":{\"show\":\"true\"}, + \"dcl\":{\"show\":\"true\"}, }"; } $current_opts = json_decode($options); @@ -171,6 +172,10 @@ echo "\nvar o_template = { frequency: {show: 'true'}};"; echo "\nuser_options={...user_options, ...o_template};"; } + if (!isset($current_opts->dcl)) { + echo "\nvar o_template = { dcl: {show: 'true'}};"; + echo "\nuser_options={...user_options, ...o_template};"; + } foreach ($mapoptions as $mo) { @@ -735,6 +740,9 @@ $options = json_decode($options); } ?> qrz->show ?? "true") == "true") { echo '' . __("QRZ") . ''; + } ?> + dcl->show ?? "true") == "true") { + echo '' . __("DCL") . ''; } ?> qslmsgs->show ?? "false") == "true") { echo '' . __("QSL Msg (S)") . ''; diff --git a/assets/js/sections/logbookadvanced.js b/assets/js/sections/logbookadvanced.js index f454538ce..081e0744d 100644 --- a/assets/js/sections/logbookadvanced.js +++ b/assets/js/sections/logbookadvanced.js @@ -89,6 +89,9 @@ function updateRow(qso) { if ((user_options.qrz.show ?? 'true') == "true"){ cells.eq(c++).html(qso.qrz); } + if ((user_options.dcl.show ?? 'true') == "true"){ + cells.eq(c++).html(qso.dcl); + } if ((user_options.qslmsgs.show ?? 'true') == "true"){ cells.eq(c++).text(qso.qslMessage); } @@ -274,6 +277,9 @@ function loadQSOTable(rows) { if ((user_options.qrz.show ?? 'true') == "true"){ data.push(qso.qrz); } + if ((user_options.dcl.show ?? 'true') == "true"){ + data.push(qso.dcl); + } if ((user_options.qslmsgs.show ?? 'true') == "true"){ data.push(qso.qslMessage); } From 43212281099b20cbc4cb21c383a9a01d2f0667eb Mon Sep 17 00:00:00 2001 From: Andreas Kristiansen <6977712+AndreasK79@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:02:10 +0200 Subject: [PATCH 25/49] Added user option to show/hide DCL column in LBA --- application/controllers/Logbookadvanced.php | 1 + application/views/logbookadvanced/useroptions.php | 4 ++++ assets/js/sections/logbookadvanced.js | 1 + 3 files changed, 6 insertions(+) diff --git a/application/controllers/Logbookadvanced.php b/application/controllers/Logbookadvanced.php index fa9950173..68dbd0e33 100644 --- a/application/controllers/Logbookadvanced.php +++ b/application/controllers/Logbookadvanced.php @@ -597,6 +597,7 @@ class Logbookadvanced extends CI_Controller { $json_string['region']['show'] = $this->def_boolean($this->input->post('region')); $json_string['qth']['show'] = $this->def_boolean($this->input->post('qth')); $json_string['frequency']['show'] = $this->def_boolean($this->input->post('frequency')); + $json_string['dcl']['show'] = $this->def_boolean($this->input->post('dcl')); $obj['column_settings']= json_encode($json_string); diff --git a/application/views/logbookadvanced/useroptions.php b/application/views/logbookadvanced/useroptions.php index 86349f997..817a3db10 100644 --- a/application/views/logbookadvanced/useroptions.php +++ b/application/views/logbookadvanced/useroptions.php @@ -86,6 +86,10 @@
    qrz->show ?? "true") == "true") { echo 'checked'; } ?>>
    + + +
    dcl->show ?? "true") == "true") { echo 'checked'; } ?>>
    +
    qslmsgs->show ?? "false") == "true") { echo 'checked'; } ?>>
    diff --git a/assets/js/sections/logbookadvanced.js b/assets/js/sections/logbookadvanced.js index 081e0744d..be6f2bff5 100644 --- a/assets/js/sections/logbookadvanced.js +++ b/assets/js/sections/logbookadvanced.js @@ -1504,6 +1504,7 @@ function saveOptions() { nightshadow_layer: $('input[name="nightshadow"]').is(':checked') ? true : false, qth: $('input[name="qth"]').is(':checked') ? true : false, frequency: $('input[name="frequency"]').is(':checked') ? true : false, + dcl: $('input[name="dcl"]').is(':checked') ? true : false, }, success: function(data) { $('#saveButton').prop("disabled", false); From 60878b0f44b71dd0a21a20c01f781e5ba10b6854 Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 12:25:19 +0000 Subject: [PATCH 26/49] Remove old lotw-functions --- application/controllers/Dcl.php | 651 +------------------------------- 1 file changed, 8 insertions(+), 643 deletions(-) diff --git a/application/controllers/Dcl.php b/application/controllers/Dcl.php index 1af84f0a3..2a67ec411 100644 --- a/application/controllers/Dcl.php +++ b/application/controllers/Dcl.php @@ -220,209 +220,10 @@ class Dcl extends CI_Controller { /* |-------------------------------------------------------------------------- - | Function: loadFromFile + | Function: dcl_download |-------------------------------------------------------------------------- | - | $filepath is the ADIF file, $display_view is used to hide the output if its internal script - | - | Internal function that takes the LoTW ADIF and imports into the log - | - */ - private function loadFromFile($filepath, $station_ids, $display_view = "TRUE") { - - // Figure out how we should be marking QSLs confirmed via LoTW - $query = $this->db->query('SELECT lotw_rcvd_mark FROM config'); - $q = $query->row(); - $config['lotw_rcvd_mark'] = $q->lotw_rcvd_mark; - - ini_set('memory_limit', '-1'); - set_time_limit(0); - - if (!$this->load->is_loaded('adif_parser')) { - $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 .= ""; - $tableheaders .= ""; - $tableheaders .= ""; - $tableheaders .= ""; - $tableheaders .= ""; - $tableheaders .= ""; - - $table = ""; - while($record = $this->adif_parser->get_record()) { - // Check for LoTW confirmation in ADIF record and skip if not existent - if (!isset($record['app_lotw_rxqsl'])) { - continue; - } - if (($record['call'] ?? '') == '') { // Failsafe if no call is given - continue; - } - if (($record['station_callsign'] ?? '') == '') { // Failsafe if no station_callsign is given - continue; - } - $time_on = date('Y-m-d', strtotime($record['qso_date'])) ." ".date('H:i', strtotime($record['time_on'])); - - $qsl_date = date('Y-m-d H:i', strtotime($record['app_lotw_rxqsl'])); - - if (isset($record['time_off'])) { - $time_off = date('Y-m-d', strtotime($record['qso_date'])) ." ".date('H:i', strtotime($record['time_off'])); - } else { - $time_off = date('Y-m-d', strtotime($record['qso_date'])) ." ".date('H:i', strtotime($record['time_on'])); - } - - // If we have a positive match from LoTW, record it in the DB according to the user's preferences - if ($record['qsl_rcvd'] == "Y") - { - $record['qsl_rcvd'] = $config['lotw_rcvd_mark']; - } - - // SAT-Name not given? Create array-key and fill with null - if (!(array_key_exists('sat_name', $record))) { - $record['sat_name']=null; - } - - // Prop-Mode not given? Create array-key and fill with null - if (!(array_key_exists('prop_mode', $record))) { - $record['prop_mode']=null; - } - - $status = $this->logbook_model->import_check($time_on, $record['call'], $record['band'], $record['mode'], $record['prop_mode'], $record['sat_name'], $record['station_callsign'], $station_ids); - - if($status[0] == "Found") { - $qso_id4lotw=$status[1]; - if (isset($record['state'])) { - $state = $record['state']; - } else { - $state = ""; - } - // Present only if the QSLing station specified a single valid grid square value in its station location uploaded to LoTW. - $qsl_gridsquare = ""; - if (isset($record['gridsquare'])) { - if (strlen($record['gridsquare']) > strlen($status[2] ?? '') || substr(strtoupper($status[2] ?? ''), 0, 4) != substr(strtoupper($record['gridsquare']), 0, 4)) { - $qsl_gridsquare = $record['gridsquare']; - } - } - - $ant_path = $status[3] ?? ''; - - if (isset($record['vucc_grids'])) { - $qsl_vucc_grids = $record['vucc_grids']; - } else { - $qsl_vucc_grids = ""; - } - - if (isset($record['iota'])) { - $iota = $record['iota']; - } else { - $iota = ""; - } - - if (isset($record['cnty'])) { - $cnty = $record['cnty']; - } else { - $cnty = ""; - } - - if (isset($record['cqz'])) { - $cqz = $record['cqz']; - } else { - $cqz = ""; - } - - if (isset($record['ituz'])) { - $ituz = $record['ituz']; - } else { - $ituz = ""; - } - - if (isset($record['dxcc'])) { - $dxcc = $record['dxcc']; - } else { - $dxcc = ""; - } - - if (isset($record['country'])) { - $country = $record['country']; - } else { - $country = ""; - } - - $lotw_status = $this->logbook_model->lotw_update($time_on, $record['call'], $record['band'], $qsl_date, $record['qsl_rcvd'], $state, $qsl_gridsquare, $qsl_vucc_grids, $iota, $cnty, $cqz, $ituz, $record['station_callsign'],$qso_id4lotw, $station_ids, $dxcc, $country, $ant_path); - - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - } else { - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - $table .= ""; - } - } - - if ($table != "") { - $table .= "
    Station CallsignQSO DateCallModeLoTW QSL ReceivedDate LoTW ConfirmedStateGridsquareIOTALog StatusLoTW Status
    ".$record['station_callsign']."".$time_on."".$record['call']."".$record['mode']."".$record['qsl_rcvd']."".$qsl_date."".$state."".($qsl_gridsquare != '' ? $qsl_gridsquare : $qsl_vucc_grids)."".$iota."QSO Record: ".$status[0]."LoTW Record: ".$lotw_status."
    ".$record['station_callsign']."".$time_on."".$record['call']."".$record['mode']."".$record['qsl_rcvd']."QSO Record: ".$status[0]."
    "; - $data['lotw_table_headers'] = $tableheaders; - $data['lotw_table'] = $table; - } - - unlink($filepath); - - $this->load->model('user_model'); - if ($this->user_model->authorize(2)) { // Only Output results if authorized User - if(isset($data['lotw_table_headers'])) { - if($display_view == TRUE) { - $data['page_title'] = __("LoTW ADIF Information"); - $this->load->view('interface_assets/header', $data); - $this->load->view('lotw/analysis'); - $this->load->view('interface_assets/footer'); - } else { - return $tableheaders.$table; - } - } else { - echo "Downloaded LoTW report contains no matches."; - } - } - } - - /* - |-------------------------------------------------------------------------- - | Function: lotw_download - |-------------------------------------------------------------------------- - | - | Collects users with LoTW usernames and passwords and runs through them + | Collects users with DCL tokens and runs through them | downloading matching QSOs. | */ @@ -431,83 +232,11 @@ class Dcl extends CI_Controller { $this->load->model('logbook_model'); $this->load->model('Stations'); - $query = $this->user_model->get_all_dcl_users(); - - if ($query->num_rows() >= 1) { - $result = ''; - - // Get URL for downloading DCL - $url_query = $this->db->query('SELECT dcl_download_url FROM config'); - $q = $url_query->row(); - $dcl_base_url = $q->dcl_download_url; - - foreach ($query->result() as $user) { - if ( ($sync_user_id != null) && ($sync_user_id != $user->user_id) ) { continue; } - $station_ids=$this->Stations->all_station_ids_of_user($user->user_id); - if ($station_ids == '') { continue; } // User has no Station-ID! next one - - // Validate that DCL credentials are not empty - // TODO: We don't actually see the error message - if ($user->user_dcl_password == '') { - $result = "You have not defined your ARRL DCL credentials!"; - continue; - } - - $config['upload_path'] = './uploads/'; - $file = $config['upload_path'] . 'dclreport_download_'.$user->user_id.'_auto.adi'; - if (file_exists($file) && ! is_writable($file)) { - $result = "Temporary download file ".$file." is not writable. Aborting!"; - continue; - } - - // Get credentials for DCL - $data['user_dcl_name'] = urlencode($user->user_dcl_name); - $data['user_dcl_password'] = urlencode($user->user_dcl_password); - - $dcl_last_qsl_date = date('Y-m-d', strtotime($this->logbook_model->dcl_last_qsl_date($user->user_id))); - - // Build URL for DCL report file - $dcl_url = $dcl_base_url."?"; - $dcl_url .= "login=" . $data['user_dcl_name']; - $dcl_url .= "&password=" . $data['user_dcl_password']; - $dcl_url .= "&qso_query=1&qso_qsl='yes'&qso_qsldetail='yes'&qso_mydetail='yes'"; - - $dcl_url .= "&qso_qslsince="; - $dcl_url .= "$dcl_last_qsl_date"; - - if (! is_writable(dirname($file))) { - $result = "Temporary download directory ".dirname($file)." is not writable. Aborting!"; - continue; - } - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $dcl_url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); - $content = curl_exec($ch); - if(curl_errno($ch)) { - $result = "DCL download failed for user ".$data['user_dcl_name'].": ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")."; - if (curl_errno($ch) == 28) { // break on timeout - $result .= "
    Timeout reached. Stopping subsequent downloads."; - break; - } - continue; - } else if(str_contains($content,"Username/password incorrect")) { - $result = "DCL download failed for user ".$data['user_dcl_name'].": Username/password incorrect"; - continue; - } - file_put_contents($file, $content); - if (file_get_contents($file, false, null, 0, 39) != "ARRL Logbook of the World Status Report") { - $result = "Downloaded DCL report for user ".$data['user_dcl_name']." is invalid. Check your credentials."; - continue; - } - - ini_set('memory_limit', '-1'); - $result = $this->loadFromFile($file, $station_ids, false); - } - return $result; - } else { - return "No DCL User details found to carry out matches."; - } + // Needs complete refactoring. Pseudocode: + // Loop through all users with token present (find_key at Dcl_model) or only single_users if sync_user_is has been provided + // Fetch data for call from DCL (todo: which URL? Where?) + // Mark as received + // all on the fly (no tempfiles) } public function import() { // Is only called via frontend. Cron uses "upload". within download the download is called @@ -518,371 +247,7 @@ class Dcl extends CI_Controller { redirect('dashboard'); exit(); } - - $station_ids=$this->Stations->all_station_ids_of_user($this->session->userdata['user_id']); - $data['page_title'] = __("DCL ADIF Import"); - - $config['upload_path'] = './uploads/'; - $config['allowed_types'] = 'adi|ADI'; - - $this->load->library('upload', $config); - - $this->load->model('logbook_model'); - - if (($this->input->post('dclimport') == 'fetch') && (!($this->config->item('disable_manual_dcl')))) { - $file = $config['upload_path'] . 'dclreport_download_'.$this->session->userdata('user_id').'.adi'; - - // Get credentials for LoTW - $query = $this->user_model->get_by_id($this->session->userdata('user_id')); - $q = $query->row(); - $data['user_dcl_name'] = urlencode($q->user_dcl_name ?? ''); - $data['user_dcl_password'] = urlencode($q->user_dcl_password ?? ''); - - // Get URL for downloading LoTW - $query = $query = $this->db->query('SELECT dcl_download_url FROM config'); - $q = $query->row(); - $dcl_url = $q->dcl_download_url; - - // Validate that LoTW credentials are not empty - // TODO: We don't actually see the error message - if ($data['user_dcl_name'] == '' || $data['user_dcl_password'] == '') { - $this->session->set_flashdata('warning', __("You have not defined your ARRL LoTW credentials!")); redirect('dcl/import'); - } - - $customDate = $this->input->post('from'); - - if ($customDate != NULL) { - $dcl_last_qsl_date = date($customDate); - } else { - // Query the logbook to determine when the last LoTW confirmation was - $dcl_last_qsl_date = date('Y-m-d', strtotime($this->logbook_model->dcl_last_qsl_date($this->session->userdata['user_id']))); - } - - $dcl_url .= "?"; - $dcl_url .= "login=" . $data['user_dcl_name']; - $dcl_url .= "&password=" . $data['user_dcl_password']; - $dcl_url .= "&qso_query=1&qso_qsl='yes'&qso_qsldetail='yes'&qso_mydetail='yes'"; - - $dcl_url .= "&qso_qslsince="; - $dcl_url .= "$dcl_last_qsl_date"; - - if ($this->input->post('callsign') != '0') { - $dcl_url .= "&qso_owncall=".$this->input->post('callsign'); - } - - if (is_writable(dirname($file)) && (!file_exists($file) || is_writable($file))) { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $dcl_url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); - $content = curl_exec($ch); - if(curl_errno($ch)) { - print "DCL download failed for user ".$data['user_dcl_name'].": ".curl_strerror(curl_errno($ch))." (".curl_errno($ch).")."; - } else if (str_contains($content,"Username/password incorrect")) { - print "DCL download failed for user ".$data['user_dcl_name'].": key incorrect"; - } else { - file_put_contents($file, $content); - ini_set('memory_limit', '-1'); - $this->loadFromFile($file, $station_ids); - } - } else { - if (!is_writable(dirname($file))) { - $data['errormsg'] = 'Directory '.dirname($file).' is not writable!'; - } else if (!is_writable($file)) { - $data['errormsg'] = 'File '.$file.' is not writable!'; - } - $this->load->model('Stations'); - $data['callsigns'] = $this->Stations->callsigns_of_user($this->session->userdata('user_id')); - - $this->load->view('interface_assets/header', $data); - $this->load->view('dcl_views/import', $data); - $this->load->view('interface_assets/footer'); - } - } else { - $this->load->model('Stations'); - $data['callsigns'] = $this->Stations->callsigns_of_user($this->session->userdata('user_id')); - - $this->load->view('interface_assets/header', $data); - $this->load->view('dcl_views/import', $data); - $this->load->view('interface_assets/footer'); - } + // Refactoring needed. This function provides manual download (via uploaded file into Wavelog) of DCL-Confirmations as well as triggering dcl_download } - public function export() { - $this->load->model('user_model'); - if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } - - $data['page_title'] = __("LoTW .TQ8 Upload"); - - $config['upload_path'] = './uploads/'; - $config['allowed_types'] = 'tq8|TQ8'; - - $this->load->library('upload', $config); - - if ( ! $this->upload->do_upload()) - { - $data['error'] = $this->upload->display_errors(); - - $this->load->view('interface_assets/header', $data); - $this->load->view('lotw/export'); - $this->load->view('interface_assets/footer'); - } - else - { - $data = array('upload_data' => $this->upload->data()); - - // Figure out how we should be marking QSLs confirmed via LoTW - $query = $query = $this->db->query('SELECT lotw_login_url FROM config'); - $q = $query->row(); - $config['lotw_login_url'] = $q->lotw_login_url; - - // Set some fields that we're going to need for ARRL login - $query = $this->user_model->get_by_id($this->session->userdata('user_id')); - $q = $query->row(); - $fields['login'] = $q->user_lotw_name; - $fields['password'] = $q->user_lotw_password; - $fields['acct_sel'] = ""; - - if ($fields['login'] == '' || $fields['password'] == '') - { - $this->session->set_flashdata('warning', __("You have not defined your ARRL LoTW credentials!")); redirect('lotw/status'); - } - - // Curl stuff goes here - - // First we need to get a cookie - - // options - $cookie_file_path = "./uploads/cookies.txt"; - $agent = "Mozilla/4.0 (compatible;)"; - - // begin script - $ch = curl_init(); - - // extra headers - $headers[] = "Accept: */*"; - $headers[] = "Connection: Keep-Alive"; - - // basic curl options for all requests - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_HEADER, 0); - - // TODO: These SSL things should probably be set to true :) - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_USERAGENT, $agent); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file_path); - curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file_path); - - // Set login URL - curl_setopt($ch, CURLOPT_URL, $config['lotw_login_url']); - - // set postfields using what we extracted from the form - $POSTFIELDS = http_build_query($fields); - - // set post options - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $POSTFIELDS); - - // perform login - $result = curl_exec($ch); - if (stristr($result, "Username/password incorrect")) - { - $this->session->set_flashdata('warning', __("Your ARRL username and/or password is incorrect.")); redirect('lotw/status'); - } - - - // Now we need to use that cookie and upload the file - // change URL to upload destination URL - curl_setopt($ch, CURLOPT_URL, $config['lotw_login_url']); - - // Grab the file - $postfile = array( - "upfile"=>"@./uploads/".$data['upload_data']['file_name'], - ); - - //Upload it - curl_setopt($ch, CURLOPT_POSTFIELDS, $postfile); - $response = curl_exec($ch); - if (stristr($response, "accepted")) - { - $this->session->set_flashdata('lotw_status', 'accepted'); - $data['page_title'] = __("LoTW .TQ8 Sent"); - } - elseif (stristr($response, "rejected")) - { - $this->session->set_flashdata('lotw_status', 'rejected'); - $data['page_title'] = __("LoTW .TQ8 Sent"); - } - else - { - // If we're here, we didn't find what we're looking for in the ARRL response - // and LoTW is probably down or broken. - $this->session->set_flashdata('warning', 'Did not receive proper response from LoTW. Try again later.'); - $data['page_title'] = __("LoTW .TQ8 Not Sent"); - } - - // Now we need to clean up - unlink($cookie_file_path); - unlink('./uploads/'.$data['upload_data']['file_name']); - - $this->load->view('interface_assets/header', $data); - $this->load->view('lotw/status'); - $this->load->view('interface_assets/footer'); - } - } - - /* - Deprecated. To be back compatible we do the same as update/lotw_users - HB9HIL, July 2024 - */ - public function load_users() { - $this->load->model('Update_model'); - $result = $this->Update_model->lotw_users(); - echo $result; - } - - function signlog($sign_key, $string) { - - $qso_string = $string; - - $key = $sign_key; - - $pkeyid = openssl_pkey_get_private($key, 'wavelog'); - if ($pkeyid) { - //openssl_sign($plaintext, $signature, $pkeyid, OPENSSL_ALGO_SHA1 ); - //openssl_free_key($pkeyid); - - if(openssl_sign($qso_string, $signature, $pkeyid, OPENSSL_ALGO_SHA1)) { - if (defined('PHP_MAJOR_VERSION') && PHP_MAJOR_VERSION < 8) { - openssl_free_key($pkeyid); - } - $signature_b64 = base64_encode($signature); - return $signature_b64."\n"; - } else { - // in case of deprecation of SHA-1 in some distro - log_message('error', 'Error signing LoTW log: '.openssl_error_string()); - } - } else { - log_message('error', 'Error signing LoTW log.'); - return null; - } - - - } - - /* - | Function: lotw_ca_province_map - | Requires: candian province map $ca_province - */ - function lotw_ca_province_map($ca_prov) { - switch ($ca_prov): - case "QC": - return "PQ"; - break; - case "NL": - return "NF"; - break; - default: - return $ca_prov; - endswitch; - } - - /* - | Function: mode_map - | Requires: mode as $mode, submode as $submode - | - | This converts ADIF modes to the mode that LoTW expects if its non standard - */ - function mode_map($mode, $submode) { - switch ($mode): - case "PKT": - return "PACKET"; - break; - case "MFSK": - if ($submode == "FT4") { - return "FT4"; - break; - } elseif ($submode == "FST4") { - return "FST4"; - break; - } elseif ($submode == "MFSK16") { - return "MFSK16"; - break; - } elseif ($submode == "MFSK8") { - return "MFSK8"; - break; - } elseif ($submode == "Q65") { - return "Q65"; - break; - } else { - return "DATA"; - break; - } - case "PSK": - if ($submode == "PSK31") { - return "PSK31"; - break; - } elseif ($submode == "PSK63") { - return "PSK63"; - break; - } elseif ($submode == "BPSK125") { - return "PSK125"; - break; - } elseif ($submode == "BPSK31") { - return "PSK31"; - break; - } elseif ($submode == "BPSK63") { - return "PSK63"; - break; - } elseif ($submode == "FSK31") { - return "FSK31"; - break; - } elseif ($submode == "PSK10") { - return "PSK10"; - break; - } elseif ($submode == "PSK125") { - return "PSK125"; - break; - } elseif ($submode == "PSK500") { - return "PSK500"; - break; - } elseif ($submode == "PSK63F") { - return "PSK63F"; - break; - } elseif ($submode == "PSKAM10") { - return "PSKAM"; - break; - } elseif ($submode == "PSKAM31") { - return "PSKAM"; - break; - } elseif ($submode == "PSKAM50") { - return "PSKAM"; - break; - } elseif ($submode == "PSKFEC31") { - return "PSKFEC31"; - break; - } elseif ($submode == "QPSK125") { - return "PSK125"; - break; - } elseif ($submode == "QPSK31") { - return "PSK31"; - break; - } elseif ($submode == "QPSK63") { - return "PSK63"; - break; - } elseif ($submode == "PSK2K") { - return "PSK2K"; - break; - } else { - return "DATA"; - break; - } - default: - return $mode; - endswitch; - } - } // end class From cae240920ce9c7731045a23acd374495fa78fba8 Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 14:22:51 +0000 Subject: [PATCH 27/49] Reduce code and captions to UPLOAD only (1st iteration) --- application/models/Dcl_model.php | 12 ++++++++++-- application/views/dcl_views/index.php | 1 - application/views/interface_assets/header.php | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/application/models/Dcl_model.php b/application/models/Dcl_model.php index 5e4ee6df1..449d5c2f1 100644 --- a/application/models/Dcl_model.php +++ b/application/models/Dcl_model.php @@ -4,10 +4,10 @@ class Dcl_model extends CI_Model { /* |-------------------------------------------------------------------------- - | Function: lotw_certs + | Function: dcl_keys |-------------------------------------------------------------------------- | - | Returns all lotw_certs for a selected user via the $user_id parameter + | Returns all dcl_keys for a selected user via the $user_id parameter | */ function dcl_keys($user_id) { @@ -15,6 +15,14 @@ class Dcl_model extends CI_Model { return $this->user_options_model->get_options('dcl', array('option_name'=>'dcl_key'), $user_id)->result(); } + /* + |-------------------------------------------------------------------------- + | Function: find_key + |-------------------------------------------------------------------------- + | + | Returns all dcl_keys for a selected user via the $user_id parameter which match also to the $callsign + | + */ function find_key($callsign, $user_id) { $this->load->model('user_options_model'); diff --git a/application/views/dcl_views/index.php b/application/views/dcl_views/index.php index 61ed2f396..89a5b5061 100644 --- a/application/views/dcl_views/index.php +++ b/application/views/dcl_views/index.php @@ -1,6 +1,5 @@

    -

    diff --git a/application/views/interface_assets/header.php b/application/views/interface_assets/header.php index e0b0f6b84..769e17b55 100644 --- a/application/views/interface_assets/header.php +++ b/application/views/interface_assets/header.php @@ -497,7 +497,7 @@
  • -
  • +
  • From 233f807c6f40b148e5bcda64a9353b04be2ee4e9 Mon Sep 17 00:00:00 2001 From: int2001 Date: Thu, 21 Aug 2025 14:44:21 +0000 Subject: [PATCH 28/49] Edit QSO // DCL-Fields --- application/models/Logbook_model.php | 48 +++++++++++++++++++++++----- application/views/qso/edit_ajax.php | 34 ++++++++++++++++++-- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 4cc42f13c..85866b8ba 100644 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -1368,53 +1368,65 @@ class Logbook_model extends CI_Model { } if ($this->input->post('qsl_sent')) { - $qsl_sent = $this->input->post('qsl_sent'); + $qsl_sent = $this->input->post('qsl_sent',true); } else { $qsl_sent = 'N'; } if ($this->input->post('qsl_rcvd')) { - $qsl_rcvd = $this->input->post('qsl_rcvd'); + $qsl_rcvd = $this->input->post('qsl_rcvd',true); } else { $qsl_rcvd = 'N'; } if ($this->input->post('eqsl_sent')) { - $eqsl_sent = $this->input->post('eqsl_sent'); + $eqsl_sent = $this->input->post('eqsl_sent',true); } else { $eqsl_sent = 'N'; } if ($this->input->post('eqsl_rcvd')) { - $eqsl_rcvd = $this->input->post('eqsl_rcvd'); + $eqsl_rcvd = $this->input->post('eqsl_rcvd',true); } else { $eqsl_rcvd = 'N'; } if ($this->input->post('qrz_sent')) { - $qrz_sent = $this->input->post('qrz_sent'); + $qrz_sent = $this->input->post('qrz_sent',true); } else { $qrz_sent = 'N'; } if ($this->input->post('qrz_rcvd')) { - $qrz_rcvd = $this->input->post('qrz_rcvd'); + $qrz_rcvd = $this->input->post('qrz_rcvd',true); } else { $qrz_rcvd = 'N'; } if ($this->input->post('clublog_sent')) { - $clublog_sent = $this->input->post('clublog_sent'); + $clublog_sent = $this->input->post('clublog_sent',true); } else { $clublog_sent = 'N'; } if ($this->input->post('clublog_rcvd')) { - $clublog_rcvd = $this->input->post('clublog_rcvd'); + $clublog_rcvd = $this->input->post('clublog_rcvd',true); } else { $clublog_rcvd = 'N'; } + if ($this->input->post('dcl_sent')) { + $dcl_sent = $this->input->post('dcl_sent',true); + } else { + $dcl_sent = 'N'; + } + + if ($this->input->post('dcl_rcvd')) { + $dcl_rcvd = $this->input->post('dcl_rcvd',true); + } else { + $dcl_rcvd = 'N'; + } + if (in_array($this->input->post('prop_mode'), $this->config->item('lotw_unsupported_prop_modes'))) { $lotw_sent = 'I'; } elseif ($this->input->post('lotw_sent')) { @@ -1516,6 +1528,22 @@ class Logbook_model extends CI_Model { $clublogrdate = $qso->COL_CLUBLOG_QSO_DOWNLOAD_DATE; } + if ($dcl_sent == 'N' && $qso->COL_CLUBLOG_QSO_UPLOAD_STATUS != $dcl_sent) { + $dclsdate = null; + } elseif (!$qso->COL_DCL_QSLSDATE || $qso->COL_DCL_QSLSDATE != $dcl_sent) { + $dclsdate = date('Y-m-d H:i:s'); + } else { + $dclsdate = $qso->COL_DCL_QSLSDATE; + } + + if ($dcl_rcvd == 'N' && $qso->COL_DCL_QSLRDATE != $dcl_rcvd) { + $dclrdate = null; + } elseif (!$qso->COL_DCL_QSLRDATE || $qso->COL_DCL_QSLRDATE != $dcl_rcvd) { + $dclrdate = date('Y-m-d H:i:s'); + } else { + $dclrdate = $qso->COL_DCL_QSLRDATE; + } + if (($this->input->post('distance')) && (is_numeric($this->input->post('distance')))) { $distance = $this->input->post('distance'); } else { @@ -1588,6 +1616,10 @@ class Logbook_model extends CI_Model { 'COL_CLUBLOG_QSO_DOWNLOAD_DATE' => $clublogrdate, 'COL_CLUBLOG_QSO_DOWNLOAD_STATUS' => $clublog_rcvd, 'COL_CLUBLOG_QSO_UPLOAD_STATUS' => $clublog_sent, + 'COL_DCL_QSLSDATE' => $dclsdate, + 'COL_DCL_QSLRDATE' => $dclrdate, + 'COL_DCL_QSL_RCVD' => $dcl_rcvd, + 'COL_DCL_QSL_SENT' => $dcl_sent, 'COL_IOTA' => $this->input->post('iota_ref'), 'COL_SOTA_REF' => strtoupper(trim($this->input->post('sota_ref'))), 'COL_WWFF_REF' => strtoupper(trim($this->input->post('wwff_ref'))), diff --git a/application/views/qso/edit_ajax.php b/application/views/qso/edit_ajax.php index b333bd082..e93cf1cb2 100644 --- a/application/views/qso/edit_ajax.php +++ b/application/views/qso/edit_ajax.php @@ -427,9 +427,12 @@ - +
    @@ -595,7 +598,33 @@
    -
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    @@ -608,7 +637,6 @@
    -
    From 9d28df17e4fb3b14817f613ec1f956e477b7e6a8 Mon Sep 17 00:00:00 2001 From: Andreas Kristiansen <6977712+AndreasK79@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:18:33 +0200 Subject: [PATCH 29/49] Added batch edit for DCL --- application/models/Logbookadvanced_model.php | 16 ++++++++++++++++ application/views/logbookadvanced/edit.php | 8 ++++++++ assets/js/sections/logbookadvanced_edit.js | 6 ++++++ 3 files changed, 30 insertions(+) diff --git a/application/models/Logbookadvanced_model.php b/application/models/Logbookadvanced_model.php index 5e33db3f2..0ced80033 100644 --- a/application/models/Logbookadvanced_model.php +++ b/application/models/Logbookadvanced_model.php @@ -811,6 +811,8 @@ class Logbookadvanced_model extends CI_Model { case "qrzreceived": $column = 'COL_QRZCOM_QSO_DOWNLOAD_STATUS'; break; case "eqslsent": $column = 'COL_EQSL_QSL_SENT'; break; case "eqslreceived": $column = 'COL_EQSL_QSL_RCVD'; break; + case "dclsent": $column = 'COL_DCL_QSL_SENT'; break; + case "dclreceived": $column = 'COL_DCL_QSL_RCVD'; break; case "stationpower": $column = 'COL_TX_PWR'; break; case "clublogsent": $column = 'COL_CLUBLOG_QSO_UPLOAD_STATUS'; break; case "clublogreceived": $column = 'COL_CLUBLOG_QSO_DOWNLOAD_STATUS'; break; @@ -983,6 +985,20 @@ class Logbookadvanced_model extends CI_Model { " SET " . $this->config->item('table_name').".COL_LOTW_QSL_RCVD = ?, " . $this->config->item('table_name').".COL_LOTW_QSLRDATE = now()" . " WHERE " . $this->config->item('table_name').".col_primary_key in ? and station_profile.user_id = ?"; + $query = $this->db->query($sql, array($value, json_decode($ids, true), $this->session->userdata('user_id'))); + } else if ($column == 'COL_DCL_QSL_SENT') { + + $sql = "UPDATE ".$this->config->item('table_name')." JOIN station_profile ON ". $this->config->item('table_name').".station_id = station_profile.station_id" . + " SET " . $this->config->item('table_name').".COL_DCL_QSL_SENT = ?, " . $this->config->item('table_name').".COL_DCL_QSLSDATE = now()" . + " WHERE " . $this->config->item('table_name').".col_primary_key in ? and station_profile.user_id = ?"; + + $query = $this->db->query($sql, array($value, json_decode($ids, true), $this->session->userdata('user_id'))); + } else if ($column == 'COL_DCL_QSL_RCVD') { + + $sql = "UPDATE ".$this->config->item('table_name')." JOIN station_profile ON ". $this->config->item('table_name').".station_id = station_profile.station_id" . + " SET " . $this->config->item('table_name').".COL_DCL_QSL_RCVD = ?, " . $this->config->item('table_name').".COL_DCL_QSLRDATE = now()" . + " WHERE " . $this->config->item('table_name').".col_primary_key in ? and station_profile.user_id = ?"; + $query = $this->db->query($sql, array($value, json_decode($ids, true), $this->session->userdata('user_id'))); } else if ($column == 'COL_QRZCOM_QSO_UPLOAD_STATUS') { diff --git a/application/views/logbookadvanced/edit.php b/application/views/logbookadvanced/edit.php index 8746d048f..e8da0753a 100644 --- a/application/views/logbookadvanced/edit.php +++ b/application/views/logbookadvanced/edit.php @@ -38,6 +38,8 @@ + +
     
    @@ -161,6 +163,12 @@ + +