2013-02-16 16:41:40 +00:00
< ? php if ( ! defined ( 'BASEPATH' )) exit ( 'No direct script access allowed' );
class Lotw extends CI_Controller {
2020-08-26 21:26:37 +01:00
/*
|--------------------------------------------------------------------------
| Controller : Lotw
|--------------------------------------------------------------------------
2021-05-01 19:52:13 +02:00
|
2023-07-27 09:16:55 +02:00
| This Controller handles all things LoTW , upload and download .
2020-08-26 21:26:37 +01:00
|
|
| Note :
| If you plan on using any of the code within this class please credit
2024-01-18 00:43:03 +01:00
| Peter , 2 M0SQL . A lot of hard work went into building the
2020-08-26 21:26:37 +01:00
| signing of files .
|
| Big Thanks to Rodrigo PY2RAF for all the help and information about OpenSSL
|
*/
2013-02-16 16:41:40 +00:00
/* Controls who can access the controller and its functions */
2024-09-12 13:41:16 +00:00
function __construct () {
2013-02-16 16:41:40 +00:00
parent :: __construct ();
$this -> load -> helper ( array ( 'form' , 'url' ));
2021-05-01 19:52:13 +02:00
2024-02-29 13:52:54 +01:00
if ( ENVIRONMENT == 'maintenance' && $this -> session -> userdata ( 'user_id' ) == '' ) {
2024-09-12 13:41:16 +00:00
echo __ ( " Maintenance Mode is active. Try again later. " ) . " \n " ;
2024-02-29 13:52:54 +01:00
redirect ( 'user/login' );
}
2013-02-16 16:41:40 +00:00
}
2016-01-09 23:50:42 +00:00
2020-08-13 17:28:22 +01:00
/*
|--------------------------------------------------------------------------
| Function : index
|--------------------------------------------------------------------------
2021-05-01 19:52:13 +02:00
|
2020-08-13 17:28:22 +01:00
| Default function for the controller which loads when doing / lotw
| this shows all the uploaded lotw p12 certificates the user has uploaded
|
*/
2020-08-13 17:24:07 +01:00
public function index () {
2024-01-31 10:07:53 +01:00
$this -> load -> library ( 'Permissions' );
2025-01-02 10:22:23 +01:00
if ( ! $this -> user_model -> authorize ( 2 ) || ! clubaccess_check ( 9 )) { $this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " )); redirect ( 'dashboard' ); }
2020-09-03 23:20:22 +01:00
2020-08-13 17:28:22 +01:00
// Load required models for page generation
2024-07-04 06:59:58 +02:00
$this -> load -> model ( 'Lotw_model' );
2020-08-13 17:24:07 +01:00
2025-11-20 13:00:25 +01:00
// Check for superseded certificates
$certcheck = $this -> Lotw_model -> lotw_certs ( $this -> session -> userdata ( 'user_id' ));
foreach ( $certcheck -> result () as $row ) {
2025-11-20 13:11:28 +01:00
if ( $row -> serial != null ) {
$status = $this -> lotw_cert_status ( $row -> serial );
if ( $status != 99 && $status != $row -> status ) {
$this -> Lotw_model -> update_cert_status ( $row -> lotw_cert_id , $status );
}
2025-11-20 13:00:25 +01:00
}
}
2023-07-27 09:16:55 +02:00
// Get Array of the logged in users LoTW certs.
2024-07-04 06:59:58 +02:00
$data [ 'lotw_cert_results' ] = $this -> Lotw_model -> lotw_certs ( $this -> session -> userdata ( 'user_id' ));
2020-08-13 17:24:07 +01:00
2020-08-13 17:28:22 +01:00
// Set Page Title
2024-06-08 11:01:59 +02:00
$data [ 'page_title' ] = __ ( " Logbook of the World " );
2020-08-13 17:24:07 +01:00
2024-01-31 09:53:56 +01:00
// Check folder permissions
2024-01-31 10:07:53 +01:00
$uploads_folder = $this -> permissions -> is_really_writable ( 'uploads' );
2024-01-31 09:53:56 +01:00
$data [ 'uploads_folder' ] = $uploads_folder ;
2024-10-18 09:36:35 +00:00
$this -> load -> model ( 'cron_model' );
2024-10-18 16:07:33 +02:00
$data [ 'next_run' ] = $this -> cron_model -> get_next_run ( " lotw_lotw_upload " );
2024-10-18 09:36:35 +00:00
2020-08-13 17:28:22 +01:00
// Load Views
2020-08-13 17:24:07 +01:00
$this -> load -> view ( 'interface_assets/header' , $data );
$this -> load -> view ( 'lotw_views/index' );
$this -> load -> view ( 'interface_assets/footer' );
}
2020-08-17 17:02:54 +01:00
/*
|--------------------------------------------------------------------------
| Function : cert_upload
|--------------------------------------------------------------------------
2021-05-01 19:52:13 +02:00
|
2020-08-17 17:02:54 +01:00
| Nothing fancy just shows the cert_upload form for uploading p12 files
|
*/
public function cert_upload () {
2020-12-28 13:42:26 +00:00
$this -> load -> model ( 'dxcc' );
2024-08-16 10:08:44 +02:00
if ( ! $this -> user_model -> authorize ( 2 )) { $this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " )); redirect ( 'dashboard' ); }
2020-09-02 14:48:13 +01:00
2020-12-28 13:42:26 +00:00
// Load DXCC Countrys List
$data [ 'dxcc_list' ] = $this -> dxcc -> list ();
2020-08-17 17:02:54 +01:00
// Set Page Title
2024-06-08 11:01:59 +02:00
$data [ 'page_title' ] = __ ( " Logbook of the World " );
2020-08-17 17:02:54 +01:00
// Load Views
$this -> load -> view ( 'interface_assets/header' , $data );
$this -> load -> view ( 'lotw_views/upload_cert' , array ( 'error' => ' ' ));
2021-05-01 19:52:13 +02:00
$this -> load -> view ( 'interface_assets/footer' );
2020-08-17 17:02:54 +01:00
}
/*
|--------------------------------------------------------------------------
| Function : do_cert_upload
|--------------------------------------------------------------------------
2021-05-01 19:52:13 +02:00
|
2020-08-17 17:02:54 +01:00
| do_cert_upload is called from cert_upload form submit and handles uploading
| and processing of p12 files and storing the data into mysql
|
*/
2024-11-21 06:25:26 +00:00
public function do_cert_upload () {
2020-12-28 13:42:26 +00:00
$this -> load -> model ( 'dxcc' );
2024-08-16 10:08:44 +02:00
if ( ! $this -> user_model -> authorize ( 2 )) { $this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " )); redirect ( 'dashboard' ); }
2020-09-02 14:02:58 +01:00
2024-07-04 07:09:33 +02:00
// create folder to store certs while processing
2020-09-02 14:02:58 +01:00
if ( ! file_exists ( './uploads/lotw/certs' )) {
mkdir ( './uploads/lotw/certs' , 0755 , true );
}
2020-08-17 17:02:54 +01:00
$config [ 'upload_path' ] = './uploads/lotw/certs' ;
$config [ 'allowed_types' ] = 'p12' ;
$this -> load -> library ( 'upload' , $config );
2024-11-21 06:25:26 +00:00
if ( ! $this -> upload -> do_upload ( 'userfile' )) {
2020-08-17 17:02:54 +01:00
// Upload of P12 Failed
$error = array ( 'error' => $this -> upload -> display_errors ());
2022-11-12 14:12:53 +00:00
// Load DXCC Countrys List
$data [ 'dxcc_list' ] = $this -> dxcc -> list ();
2020-12-26 12:21:23 +00:00
// Set Page Title
2024-06-08 11:01:59 +02:00
$data [ 'page_title' ] = __ ( " Logbook of the World " );
2020-08-17 17:02:54 +01:00
// Load Views
$this -> load -> view ( 'interface_assets/header' , $data );
$this -> load -> view ( 'lotw_views/upload_cert' , $error );
2021-05-01 19:52:13 +02:00
$this -> load -> view ( 'interface_assets/footer' );
2024-11-21 06:25:26 +00:00
} else {
2020-08-17 17:02:54 +01:00
// Load database queries
2024-07-04 06:59:58 +02:00
$this -> load -> model ( 'Lotw_model' );
2020-08-17 17:02:54 +01:00
//Upload of P12 successful
$data = array ( 'upload_data' => $this -> upload -> data ());
$info = $this -> decrypt_key ( $data [ 'upload_data' ][ 'full_path' ]);
2020-12-28 13:42:26 +00:00
// Check to see if certificate is already in the system
2024-07-04 06:59:58 +02:00
$new_certificate = $this -> Lotw_model -> find_cert ( $info [ 'issued_callsign' ], $info [ 'dxcc-id' ], $this -> session -> userdata ( 'user_id' ));
2020-08-17 17:02:54 +01:00
2023-03-30 12:33:09 +02:00
if ( $new_certificate == 0 ) {
2020-08-17 17:02:54 +01:00
// New Certificate Store in Database
// Store Certificate Data into MySQL
2025-11-19 12:53:41 +01:00
$this -> Lotw_model -> store_certificate ( $this -> session -> userdata ( 'user_id' ), $info [ 'issued_callsign' ], $info [ 'dxcc-id' ], $info [ 'validFrom' ], $info [ 'validTo_Date' ], $info [ 'qso-first-date' ], $info [ 'qso-end-date' ], $info [ 'pem_key' ], $info [ 'general_cert' ], $info [ 'serialNumber' ]);
2020-08-17 17:02:54 +01:00
// Cert success flash message
2024-08-19 16:36:35 +02:00
$this -> session -> set_flashdata ( 'success' , $info [ 'issued_callsign' ] . ' ' . __ ( " Certificate Imported. " ));
2020-08-17 17:02:54 +01:00
} else {
2023-03-30 12:33:09 +02:00
// Certificate is in the system time to update
2020-08-17 17:02:54 +01:00
2025-11-19 12:53:41 +01:00
$this -> Lotw_model -> update_certificate ( $this -> session -> userdata ( 'user_id' ), $info [ 'issued_callsign' ], $info [ 'dxcc-id' ], $info [ 'validFrom' ], $info [ 'validTo_Date' ], $info [ 'qso-first-date' ], $info [ 'qso-end-date' ], $info [ 'pem_key' ], $info [ 'general_cert' ], $info [ 'serialNumber' ]);
2020-08-17 17:02:54 +01:00
// Cert success flash message
2024-08-19 16:36:35 +02:00
$this -> session -> set_flashdata ( 'success' , $info [ 'issued_callsign' ] . ' ' . __ ( " Certificate Updated. " ));
2020-08-17 17:02:54 +01:00
}
// p12 certificate processed time to delete the file
unlink ( $data [ 'upload_data' ][ 'full_path' ]);
2024-02-23 22:33:45 +01:00
redirect ( 'lotw' );
2020-08-17 17:02:54 +01:00
}
}
2020-08-22 22:26:04 +01:00
/*
|--------------------------------------------------------------------------
| Function : lotw_upload
|--------------------------------------------------------------------------
2021-05-01 19:52:13 +02:00
|
2023-07-27 09:16:55 +02:00
| This function Uploads to LoTW
2020-08-22 22:26:04 +01:00
|
*/
public function lotw_upload () {
2020-09-05 21:42:07 +01:00
2026-07-24 13:16:08 +02:00
$this -> load -> helper ( 'cronauth' );
if ( ! cronauth_allowed ( 3 )) {
// return a 403
$this -> output -> set_status_header ( 403 );
exit ();
}
2023-07-12 15:56:12 +00:00
2024-05-03 11:14:52 +02:00
// set the last run in cron table for the correct cron id
2024-04-22 23:57:29 +02:00
$this -> load -> model ( 'cron_model' );
2024-04-26 15:07:54 +02:00
$this -> cron_model -> set_last_run ( $this -> router -> class . '_' . $this -> router -> method );
2024-04-22 23:57:29 +02:00
2024-02-29 13:52:54 +01:00
// Get Station Profile Data
$this -> load -> model ( 'Stations' );
2020-08-22 22:26:04 +01:00
2026-07-24 13:16:08 +02:00
if ( $this -> user_model -> authorize ( 3 )) {
2024-05-16 07:11:47 +00:00
if ( ! ( $this -> config -> item ( 'disable_manual_lotw' ))) {
2024-05-16 06:46:14 +00:00
$station_profiles = $this -> Stations -> all_of_user ( $this -> session -> userdata ( 'user_id' ));
$sync_user_id = $this -> session -> userdata ( 'user_id' );
} else {
2024-05-20 08:04:24 +02:00
echo " Manual syncing is disabled by configuration " ;
2024-05-16 06:46:14 +00:00
redirect ( 'dashboard' );
exit ();
}
2024-02-29 13:52:54 +01:00
} else {
$station_profiles = $this -> Stations -> all ();
$sync_user_id = null ;
}
2020-08-22 22:26:04 +01:00
2024-02-29 13:52:54 +01:00
// Array of QSO IDs being Uploaded
2020-08-26 21:04:17 +01:00
2024-02-29 13:52:54 +01:00
$qso_id_array = array ();
2020-08-26 21:04:17 +01:00
2024-02-29 13:52:54 +01:00
// Build TQ8 Outputs
if ( $station_profiles -> num_rows () >= 1 ) {
2020-08-26 21:04:17 +01:00
2024-02-29 13:52:54 +01:00
foreach ( $station_profiles -> result () as $station_profile ) {
2020-08-22 22:26:04 +01:00
2024-02-29 13:52:54 +01:00
// Get Certificate Data
2024-07-04 06:59:58 +02:00
$this -> load -> model ( 'Lotw_model' );
2024-02-29 13:52:54 +01:00
$data [ 'station_profile' ] = $station_profile ;
2025-10-26 11:43:40 +01:00
$cert_query = $this -> Lotw_model -> lotw_cert_details ( $station_profile -> station_callsign , $station_profile -> user_id );
if ( $cert_query -> num_rows () > 1 ) {
echo $station_profile -> station_callsign . " : Multiple matching LoTW certificates found. Skipping.<br> " ;
continue ;
}
2020-08-22 22:26:04 +01:00
2024-02-29 13:52:54 +01:00
// If Station Profile has no LoTW Cert continue on.
2025-10-26 11:43:40 +01:00
if ( $cert_query -> num_rows () == 0 ) {
2024-09-10 16:21:03 +02:00
echo $station_profile -> station_callsign . " : No LoTW certificate for station callsign found.<br> " ;
2024-02-29 13:52:54 +01:00
continue ;
}
2020-09-02 11:45:50 +01:00
2025-10-26 11:43:40 +01:00
$data [ 'lotw_cert_info' ] = $cert_query -> row ();
// Check if station profile DXCC matches cert DXCC
if ( $data [ 'lotw_cert_info' ] -> cert_dxcc_id != $station_profile -> station_dxcc ) {
echo $station_profile -> station_callsign . " : DXCC of station profile does not match DXCC of LoTW certificate.<br> " ;
continue ;
}
2025-11-21 07:22:59 +01:00
// Check LoTW cert against CRL
if ( $data [ 'lotw_cert_info' ] -> status != 0 ) {
if ( $data [ 'lotw_cert_info' ] -> status == 1 ) {
echo $station_profile -> station_callsign . " : LoTW certificate superseded.<br> " ;
continue ;
}
}
2024-02-29 13:52:54 +01:00
// Check if LoTW certificate itself is valid
// Validty of QSO dates will be checked later
$current_date = date ( 'Y-m-d H:i:s' );
2025-10-24 10:43:42 +02:00
if ( $current_date < $data [ 'lotw_cert_info' ] -> qso_start_date ) {
echo $data [ 'lotw_cert_info' ] -> callsign . " : QSO start date of LoTW certificate not reached yet!<br> " ;
continue ;
}
if ( $current_date < $data [ 'lotw_cert_info' ] -> date_created ) {
2025-07-17 17:00:30 +02:00
echo $data [ 'lotw_cert_info' ] -> callsign . " : LoTW certificate not valid yet!<br> " ;
2024-02-29 13:52:54 +01:00
continue ;
}
2025-10-24 10:43:42 +02:00
if ( $current_date > $data [ 'lotw_cert_info' ] -> date_expires ) {
2025-07-17 17:00:30 +02:00
echo $data [ 'lotw_cert_info' ] -> callsign . " : LoTW certificate expired!<br> " ;
2024-02-29 13:52:54 +01:00
continue ;
}
2022-08-24 16:04:26 +02:00
2024-02-29 13:52:54 +01:00
// Get QSOs
2020-08-25 17:50:52 +01:00
2024-02-29 13:52:54 +01:00
$this -> load -> model ( 'Logbook_model' );
2020-08-25 17:50:52 +01:00
2024-07-12 12:51:11 +02:00
// First mark QSOs with unsupported propagation modes as ignore
$this -> Logbook_model -> mark_lotw_ignore ( $data [ 'station_profile' ] -> station_id );
2024-02-29 13:52:54 +01:00
$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 );
2020-08-25 17:50:52 +01:00
2024-02-29 13:52:54 +01:00
// Nothing to upload
if ( empty ( $data [ 'qsos' ] -> result ())){
if ( $this -> user_model -> authorize ( 2 )) { // Only be verbose if we have a session
2026-08-12 07:52:58 +00:00
echo '<span class="callsign">' . $station_profile -> station_callsign . '</span> (' . $station_profile -> station_profile_name . '): No QSOs to upload.<br>' ;
2021-05-01 19:52:13 +02:00
}
2024-02-29 13:52:54 +01:00
continue ;
}
2020-09-02 12:02:29 +01:00
2024-02-29 13:52:54 +01:00
foreach ( $data [ 'qsos' ] -> result () as $temp_qso ) {
array_push ( $qso_id_array , $temp_qso -> COL_PRIMARY_KEY );
}
2020-08-26 21:04:17 +01:00
2024-02-29 13:52:54 +01:00
// Build File to save
$adif_to_save = $this -> load -> view ( 'lotw_views/adif_views/adif_export' , $data , TRUE );
if ( strpos ( $adif_to_save , '<SIGN_LOTW_V2.0:1:6>' )) {
// Signing failed
2025-07-17 17:00:30 +02:00
echo " Signing failed.<br> " ;
2024-02-29 13:52:54 +01:00
continue ;
}
2020-09-06 20:33:12 +01:00
2024-02-29 13:52:54 +01:00
// create folder to store upload file
if ( ! file_exists ( './uploads/lotw' )) {
mkdir ( './uploads/lotw' , 0775 , true );
}
2020-10-21 16:04:14 -04:00
2024-02-29 13:52:54 +01:00
// 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 " ;
2020-08-26 21:04:17 +01:00
2024-02-29 13:52:54 +01:00
$gzdata = gzencode ( $adif_to_save , 9 );
$fp = fopen ( $filename_for_saving , " w " );
fwrite ( $fp , $gzdata );
fclose ( $fp );
2020-08-26 21:04:17 +01:00
2024-02-29 13:52:54 +01:00
//The URL that accepts the file upload.
$url = 'https://lotw.arrl.org/lotw/upload' ;
2021-05-01 19:52:13 +02:00
2024-02-29 13:52:54 +01:00
//The name of the field for the uploaded file.
$uploadFieldName = 'upfile' ;
2021-05-01 19:52:13 +02:00
2024-02-29 13:52:54 +01:00
//The full path to the file that you want to upload
$filePath = realpath ( $filename_for_saving );
2021-05-01 19:52:13 +02:00
2024-02-29 13:52:54 +01:00
//Initiate cURL
$ch = curl_init ();
2021-05-01 19:52:13 +02:00
2024-02-29 13:52:54 +01:00
//Set the URL
curl_setopt ( $ch , CURLOPT_URL , $url );
2021-05-01 19:52:13 +02:00
2024-02-29 13:52:54 +01:00
//Set the HTTP request to POST
curl_setopt ( $ch , CURLOPT_POST , true );
2021-05-01 19:52:13 +02:00
2024-02-29 13:52:54 +01:00
//Tell cURL to return the output as a string.
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
2021-05-01 19:52:13 +02:00
2024-05-13 09:20:06 +02:00
//Use the recommended way, creating a CURLFile object.
$uploadfile = curl_file_create ( $filePath );
$uploadfile -> setPostFilename ( basename ( $filePath ));
2024-05-13 09:17:43 +02:00
2024-02-29 13:52:54 +01:00
//Setup our POST fields
$postFields = array (
2024-05-13 09:17:43 +02:00
$uploadFieldName => $uploadfile
2024-02-29 13:52:54 +01:00
);
2021-05-01 19:52:13 +02:00
2024-02-29 13:52:54 +01:00
curl_setopt ( $ch , CURLOPT_POSTFIELDS , $postFields );
2024-05-16 08:33:14 +02:00
curl_setopt ( $ch , CURLOPT_CONNECTTIMEOUT , 30 );
2021-05-01 19:52:13 +02:00
2024-02-29 13:52:54 +01:00
//Execute the request
$result = curl_exec ( $ch );
2021-05-01 19:52:13 +02:00
2024-02-29 13:52:54 +01:00
if ( curl_errno ( $ch )){
2024-05-20 09:52:20 +02:00
echo $station_profile -> station_callsign . " ( " . $station_profile -> station_profile_name . " ): Upload Failed - " . curl_strerror ( curl_errno ( $ch )) . " ( " . curl_errno ( $ch ) . " )<br> " ;
2024-07-04 06:59:58 +02:00
$this -> Lotw_model -> last_upload ( $data [ 'lotw_cert_info' ] -> lotw_cert_id , " Upload failed " );
2024-05-20 09:52:20 +02:00
if ( curl_errno ( $ch ) == 28 ) { // break on timeout
echo " Timeout reached. Stopping subsequent uploads.<br> " ;
break ;
} else {
continue ;
}
2024-02-29 13:52:54 +01:00
}
2021-05-01 19:52:13 +02:00
2025-07-20 11:29:13 +02:00
if ( ! preg_match ( '/<!-- \.UPL\.\s*accepted -->/' , $result )) {
2024-02-29 13:52:54 +01:00
// Upload of TQ8 Failed for unknown reason
2024-05-20 09:52:20 +02:00
echo $station_profile -> station_callsign . " ( " . $station_profile -> station_profile_name . " ): Upload Failed - " . curl_strerror ( curl_errno ( $ch )) . " ( " . curl_errno ( $ch ) . " )<br> " ;
2024-07-04 06:59:58 +02:00
$this -> Lotw_model -> last_upload ( $data [ 'lotw_cert_info' ] -> lotw_cert_id , " Upload failed " );
2024-05-20 09:52:20 +02:00
if ( curl_errno ( $ch ) == 28 ) { // break on timeout
echo " Timeout reached. Stopping subsequent uploads.<br> " ;
break ;
} else {
continue ;
}
2024-02-29 13:52:54 +01:00
} else {
// Upload of TQ8 was successfull
2020-08-26 21:04:17 +01:00
2024-05-20 08:26:53 +02:00
echo $station_profile -> station_callsign . " ( " . $station_profile -> station_profile_name . " ): Upload Successful - " . $filename_for_saving . " <br> " ;
2020-08-26 21:04:17 +01:00
2024-07-04 06:59:58 +02:00
$this -> Lotw_model -> last_upload ( $data [ 'lotw_cert_info' ] -> lotw_cert_id , " Success " );
2020-08-26 21:20:58 +01:00
2024-02-29 13:52:54 +01:00
// Mark QSOs as Sent
foreach ( $qso_id_array as $qso_number ) {
$this -> Logbook_model -> mark_lotw_sent ( $qso_number );
2020-08-26 21:04:17 +01:00
}
2024-02-27 20:58:02 +01:00
}
2020-08-22 22:26:04 +01:00
2024-02-29 13:52:54 +01:00
// Delete TQ8 File - This is done regardless of whether upload was succcessful
unlink ( realpath ( $filename_for_saving ));
2024-02-27 20:58:02 +01:00
}
2023-07-13 07:13:56 +00:00
} else {
2024-02-29 13:52:54 +01:00
echo " No Station Profiles found to upload to LoTW " ;
}
/*
| Download QSO Matches from LoTW
*/
if ( $this -> user_model -> authorize ( 2 )) {
echo " <br><br> " ;
$sync_user_id = $this -> session -> userdata ( 'user_id' );
} else {
$sync_user_id = null ;
}
echo $this -> lotw_download ( $sync_user_id );
2020-08-22 22:26:04 +01:00
}
2020-08-17 17:02:54 +01:00
/*
|--------------------------------------------------------------------------
| Function : delete_cert
|--------------------------------------------------------------------------
2021-05-01 19:52:13 +02:00
|
2023-07-27 09:16:55 +02:00
| Deletes LoTW certificate from the MySQL table
2020-08-17 17:02:54 +01:00
|
*/
public function delete_cert ( $cert_id ) {
2024-08-16 10:08:44 +02:00
if ( ! $this -> user_model -> authorize ( 2 )) { $this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " )); redirect ( 'dashboard' ); }
2020-09-02 14:48:13 +01:00
2024-07-04 06:59:58 +02:00
$this -> load -> model ( 'Lotw_model' );
2020-08-17 17:02:54 +01:00
2024-07-04 06:59:58 +02:00
$this -> Lotw_model -> delete_certificate ( $this -> session -> userdata ( 'user_id' ), $cert_id );
2020-08-17 17:02:54 +01:00
2024-08-19 16:36:35 +02:00
$this -> session -> set_flashdata ( 'success' , __ ( " Certificate Deleted. " ));
2020-08-17 17:02:54 +01:00
2024-08-19 16:36:35 +02:00
redirect ( 'lotw' );
2020-08-17 17:02:54 +01:00
}
/*
|--------------------------------------------------------------------------
| Function : decrypt_key
|--------------------------------------------------------------------------
2021-05-01 19:52:13 +02:00
|
2020-08-17 17:02:54 +01:00
| Accepts p12 file and optional password and encrypts the file returning
2023-07-27 09:16:55 +02:00
| the required fields for LoTW and the PEM Key
2020-08-17 17:02:54 +01:00
|
*/
public function decrypt_key ( $file , $password = " " ) {
2024-08-16 10:08:44 +02:00
if ( ! $this -> user_model -> authorize ( 2 )) { $this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " )); redirect ( 'dashboard' ); }
2020-09-02 14:48:13 +01:00
2020-08-13 23:03:25 +01:00
$results = array ();
2020-08-17 17:02:54 +01:00
$filename = file_get_contents ( 'file://' . $file );
2020-08-13 23:03:25 +01:00
$worked = openssl_pkcs12_read ( $filename , $results , $password );
2026-01-03 21:48:44 +01:00
$openssl_error_pkcs12_read = openssl_error_string ();
if ( ! $worked || $openssl_error_pkcs12_read ) {
log_message ( 'error' , 'OpenSSL reading LoTW cert file resulted in error: ' . $openssl_error_pkcs12_read );
2024-08-19 16:37:27 +02:00
unlink ( $file );
2026-01-03 21:48:44 +01:00
// OpenSSL error:11800071:PKCS12 routines::mac verify failure is most likely an (unknown) password set on the exported certificate
if ( str_contains ( $openssl_error_pkcs12_read , 'mac verify failure' )) {
2026-02-25 12:01:29 +01:00
$this -> session -> set_flashdata ( 'warning' , sprintf ( __ ( " The certificate found in file %s contains a password and cannot be processed. %sPlease make sure you export the LoTW certificate from tqsl application without password!%s For further information please visit the %sLoTW FAQ page%s in the Wavelog Wiki. " ), basename ( $file ), '<b>' , '</b>' , '<a target="_blank" href="https://docs.wavelog.org/user-guide/qsl/lotw/">' , '</a>' ));
2020-08-13 23:03:25 +01:00
} else {
2026-01-03 21:48:44 +01:00
$this -> session -> set_flashdata ( 'warning' , sprintf ( __ ( " Generic error extracting the certificate from file %s. If the filename contains 'key-only' this is typically a certificate request which has not been processed by LoTW yet. " ), basename ( $file )));
2020-08-13 23:03:25 +01:00
}
2024-08-19 16:36:35 +02:00
redirect ( 'lotw' );
2026-01-03 21:48:44 +01:00
} else {
if ( ! array_key_exists ( 'cert' , $results )) {
log_message ( 'error' , 'Generic error processing the certificate from file ' . $file );
unlink ( $file );
2026-01-04 08:07:50 +01:00
$this -> session -> set_flashdata ( 'warning' , sprintf ( __ ( " Generic error processing the certificate in file %s. " ), basename ( $file )));
2026-01-03 21:48:44 +01:00
redirect ( 'lotw' );
} else {
$data [ 'general_cert' ] = $results [ 'cert' ];
// Reading p12 successful
$new_password = " wavelog " ; // set default password
$result = null ;
$worked = openssl_pkey_export ( $results [ 'pkey' ], $result , $new_password );
$openssl_error_pkey_export = openssl_error_string ();
if ( ! $worked || $openssl_error_pkey_export ) {
log_message ( 'error' , 'OpenSSL reading LoTW private key resulted in error: ' . $openssl_error_pkey_export );
$this -> session -> set_flashdata ( 'warning' , sprintf ( __ ( " Generic error extracting the private key from certificate in file %s. " ), basename ( $file )));
unlink ( $file );
redirect ( 'lotw' );
} else {
// Store PEM Key in Array
$data [ 'pem_key' ] = $result ;
// Read Cert Data
2026-05-20 20:47:13 +02:00
/** @var mixed $certdata */
2026-01-03 21:48:44 +01:00
$certdata = openssl_x509_parse ( $results [ 'cert' ], 0 );
// Store Variables
$data [ 'serialNumber' ] = $certdata [ 'serialNumber' ];
$data [ 'issued_callsign' ] = $certdata [ 'subject' ][ 'undefined' ];
$data [ 'issued_name' ] = $certdata [ 'subject' ][ 'commonName' ];
$data [ 'validFrom' ] = date ( 'Y-m-d H:i:s' , $certdata [ 'validFrom_time_t' ]);
$data [ 'validTo_Date' ] = date ( 'Y-m-d H:i:s' , $certdata [ 'validTo_time_t' ]);
// https://oidref.com/1.3.6.1.4.1.12348.1
$data [ 'qso-first-date' ] = $certdata [ 'extensions' ][ '1.3.6.1.4.1.12348.1.2' ];
$data [ 'qso-end-date' ] = $certdata [ 'extensions' ][ '1.3.6.1.4.1.12348.1.3' ];
$data [ 'dxcc-id' ] = $certdata [ 'extensions' ][ '1.3.6.1.4.1.12348.1.4' ];
return $data ;
}
}
2020-08-13 23:03:25 +01:00
}
}
2021-05-01 19:52:13 +02:00
2020-09-06 20:38:45 +01:00
/*
|--------------------------------------------------------------------------
| Function : loadFromFile
|--------------------------------------------------------------------------
2021-05-01 19:52:13 +02:00
|
2020-09-06 20:38:45 +01:00
| $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
|
*/
2024-09-12 13:41:16 +00:00
private function loadFromFile ( $filepath , $station_ids , $display_view = " TRUE " ) {
2020-07-01 00:34:51 -06:00
2013-03-02 14:24:16 -06:00
// Figure out how we should be marking QSLs confirmed via LoTW
2024-12-19 08:42:53 +00:00
$query = $this -> db -> query ( 'SELECT lotw_rcvd_mark FROM config' );
2013-03-02 14:24:16 -06:00
$q = $query -> row ();
$config [ 'lotw_rcvd_mark' ] = $q -> lotw_rcvd_mark ;
2016-01-09 23:50:42 +00:00
2013-02-23 18:40:06 -06:00
ini_set ( 'memory_limit' , '-1' );
2013-02-23 16:42:18 -07:00
set_time_limit ( 0 );
2024-09-11 10:09:43 +02:00
if ( ! $this -> load -> is_loaded ( 'adif_parser' )) {
$this -> load -> library ( 'adif_parser' );
}
2013-02-23 16:42:18 -07:00
$this -> adif_parser -> load_from_file ( $filepath );
$this -> adif_parser -> initialize ();
2020-08-10 23:07:55 +01:00
$tableheaders = " <table width= \" 100% \" > " ;
2024-09-12 13:41:16 +00:00
$tableheaders .= " <tr class= \" titles \" > " ;
$tableheaders .= " <td>Station Callsign</td> " ;
$tableheaders .= " <td>QSO Date</td> " ;
$tableheaders .= " <td>Call</td> " ;
$tableheaders .= " <td>Mode</td> " ;
$tableheaders .= " <td>LoTW QSL Received</td> " ;
$tableheaders .= " <td>Date LoTW Confirmed</td> " ;
$tableheaders .= " <td>State</td> " ;
$tableheaders .= " <td>Gridsquare</td> " ;
$tableheaders .= " <td>IOTA</td> " ;
$tableheaders .= " <td>Log Status</td> " ;
$tableheaders .= " <td>LoTW Status</td> " ;
$tableheaders .= " </tr> " ;
$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 ;
}
2024-10-01 18:47:37 +00:00
if (( $record [ 'call' ] ? ? '' ) == '' ) { // Failsafe if no call is given
continue ;
}
2025-01-06 07:04:58 +00:00
if (( $record [ 'station_callsign' ] ? ? '' ) == '' ) { // Failsafe if no station_callsign is given
continue ;
}
2024-09-12 13:41:16 +00:00
$time_on = date ( 'Y-m-d' , strtotime ( $record [ 'qso_date' ])) . " " . date ( 'H:i' , strtotime ( $record [ 'time_on' ]));
2016-01-09 23:50:42 +00:00
2024-09-12 13:41:16 +00:00
$qsl_date = date ( 'Y-m-d H:i' , strtotime ( $record [ 'app_lotw_rxqsl' ]));
2013-02-23 16:42:18 -07:00
2024-09-12 13:41:16 +00:00
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' ]));
}
2016-01-09 23:50:42 +00:00
2024-09-12 13:41:16 +00:00
// 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' ];
}
2016-01-09 23:50:42 +00:00
2025-02-13 07:55:28 +00:00
// SAT-Name not given? Create array-key and fill with null
if ( ! ( array_key_exists ( 'sat_name' , $record ))) {
$record [ 'sat_name' ] = null ;
}
2025-02-12 16:29:45 +00:00
// Prop-Mode not given? Create array-key and fill with null
2025-02-12 13:54:12 +00:00
if ( ! ( array_key_exists ( 'prop_mode' , $record ))) {
$record [ 'prop_mode' ] = null ;
}
2025-02-13 07:55:28 +00:00
$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 );
2020-05-01 13:29:02 +02:00
2024-09-12 13:41:16 +00:00
if ( $status [ 0 ] == " Found " ) {
$qso_id4lotw = $status [ 1 ];
2025-05-12 10:28:48 +02:00
2026-08-12 07:52:58 +00:00
$call = $record [ 'call' ];
2025-05-12 10:28:48 +02:00
2024-09-12 13:41:16 +00:00
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' ])) {
2025-07-28 03:44:01 +00:00
if ( strlen ( $record [ 'gridsquare' ]) >= strlen ( $status [ 2 ] ? ? '' ) || substr ( strtoupper ( $status [ 2 ] ? ? '' ), 0 , 4 ) != substr ( strtoupper ( $record [ 'gridsquare' ]), 0 , 4 )) {
2024-09-12 13:41:16 +00:00
$qsl_gridsquare = $record [ 'gridsquare' ];
2023-07-12 17:30:15 +02:00
}
2024-09-12 13:41:16 +00:00
}
2023-07-12 17:30:15 +02:00
2024-11-23 08:54:16 +01:00
$ant_path = $status [ 3 ] ? ? '' ;
2024-09-12 13:41:16 +00:00
if ( isset ( $record [ 'vucc_grids' ])) {
$qsl_vucc_grids = $record [ 'vucc_grids' ];
} else {
$qsl_vucc_grids = " " ;
}
2020-08-10 23:07:55 +01:00
2024-09-12 13:41:16 +00:00
if ( isset ( $record [ 'iota' ])) {
$iota = $record [ 'iota' ];
} else {
$iota = " " ;
}
2023-01-10 00:46:02 +01:00
2024-09-12 13:41:16 +00:00
if ( isset ( $record [ 'cnty' ])) {
$cnty = $record [ 'cnty' ];
} else {
$cnty = " " ;
}
2023-05-31 20:06:27 +02:00
2024-09-12 13:41:16 +00:00
if ( isset ( $record [ 'cqz' ])) {
$cqz = $record [ 'cqz' ];
} else {
$cqz = " " ;
}
2023-05-31 20:06:27 +02:00
2024-09-12 13:41:16 +00:00
if ( isset ( $record [ 'ituz' ])) {
$ituz = $record [ 'ituz' ];
2023-07-12 11:15:14 +02:00
} else {
2024-09-12 13:41:16 +00:00
$ituz = " " ;
2023-07-12 11:15:14 +02:00
}
2024-09-12 13:41:16 +00:00
2024-12-31 17:21:36 +00:00
if ( isset ( $record [ 'dxcc' ])) {
$dxcc = $record [ 'dxcc' ];
} else {
$dxcc = " " ;
}
2025-03-07 06:36:56 +00:00
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 );
2024-09-12 13:41:16 +00:00
$table .= " <tr> " ;
$table .= " <td> " . $record [ 'station_callsign' ] . " </td> " ;
$table .= " <td> " . $time_on . " </td> " ;
2025-05-12 10:28:48 +02:00
$table .= " <td><a id= \" view_lotw_qso \" href= \" javascript:displayQso( " . $status [ 1 ] . " ) \" > " . $call . " </a></td> " ;
2024-09-12 13:41:16 +00:00
$table .= " <td> " . $record [ 'mode' ] . " </td> " ;
$table .= " <td> " . $record [ 'qsl_rcvd' ] . " </td> " ;
$table .= " <td> " . $qsl_date . " </td> " ;
$table .= " <td> " . $state . " </td> " ;
2025-09-23 15:03:22 +02:00
$table .= " <td> " . ( $qsl_gridsquare != '' ? $qsl_gridsquare : $qsl_vucc_grids ) . " </td> " ;
2024-09-12 13:41:16 +00:00
$table .= " <td> " . $iota . " </td> " ;
$table .= " <td>QSO Record: " . $status [ 0 ] . " </td> " ;
$table .= " <td>LoTW Record: " . $lotw_status . " </td> " ;
$table .= " </tr> " ;
} else {
$table .= " <tr> " ;
$table .= " <td> " . $record [ 'station_callsign' ] . " </td> " ;
$table .= " <td> " . $time_on . " </td> " ;
$table .= " <td> " . $record [ 'call' ] . " </td> " ;
$table .= " <td> " . $record [ 'mode' ] . " </td> " ;
$table .= " <td> " . $record [ 'qsl_rcvd' ] . " </td> " ;
$table .= " <td></td> " ;
$table .= " <td></td> " ;
$table .= " <td></td> " ;
$table .= " <td></td> " ;
$table .= " <td>QSO Record: " . $status [ 0 ] . " </td> " ;
$table .= " <td></td> " ;
$table .= " </tr> " ;
2013-11-15 15:28:56 -06:00
}
2024-09-12 13:41:16 +00:00
}
2016-01-09 23:50:42 +00:00
2024-09-12 13:41:16 +00:00
if ( $table != " " ) {
$table .= " </table> " ;
$data [ 'lotw_table_headers' ] = $tableheaders ;
$data [ 'lotw_table' ] = $table ;
2013-11-15 15:28:56 -06:00
}
2016-01-09 23:50:42 +00:00
2013-02-23 18:40:06 -06:00
unlink ( $filepath );
2013-02-23 16:42:18 -07:00
2023-07-12 15:56:12 +00:00
if ( $this -> user_model -> authorize ( 2 )) { // Only Output results if authorized User
if ( isset ( $data [ 'lotw_table_headers' ])) {
if ( $display_view == TRUE ) {
2024-06-08 11:01:59 +02:00
$data [ 'page_title' ] = __ ( " LoTW ADIF Information " );
2023-07-12 15:56:12 +00:00
$this -> load -> view ( 'interface_assets/header' , $data );
$this -> load -> view ( 'lotw/analysis' );
$this -> load -> view ( 'interface_assets/footer' );
} else {
return $tableheaders . $table ;
}
2020-09-06 20:26:19 +01:00
} else {
2023-07-12 15:56:12 +00:00
echo " Downloaded LoTW report contains no matches. " ;
2020-09-06 20:26:19 +01:00
}
2020-09-06 17:44:06 +01:00
}
2013-02-23 16:42:18 -07:00
}
2013-02-16 16:41:40 +00:00
2026-05-20 20:47:13 +02:00
/**
* Helper function to validate lotw url
*/
private function _validate_url ( string $url ) {
$scheme = strtolower ( parse_url ( $url , PHP_URL_SCHEME ) ? ? '' );
if ( $scheme !== 'https' ) {
log_message ( 'error' , 'LoTW URL rejected – disallowed scheme: ' . $scheme );
2026-05-20 20:55:05 +02:00
show_error ( 'LoTW download URL is invalid (only https allowed).' , 500 );
2026-05-20 20:47:13 +02:00
}
}
2020-09-06 20:38:45 +01:00
/*
|--------------------------------------------------------------------------
| Function : lotw_download
|--------------------------------------------------------------------------
2021-05-01 19:52:13 +02:00
|
| Collects users with LoTW usernames and passwords and runs through them
2020-09-06 20:38:45 +01:00
| downloading matching QSOs .
|
*/
2023-07-12 15:56:12 +00:00
function lotw_download ( $sync_user_id = null ) {
2020-09-06 16:26:15 +01:00
$this -> load -> model ( 'logbook_model' );
2024-09-12 13:41:16 +00:00
$this -> load -> model ( 'Stations' );
2020-09-06 16:26:15 +01:00
2020-09-06 16:55:30 +01:00
$query = $this -> user_model -> get_all_lotw_users ();
2020-09-06 16:32:02 +01:00
2020-09-06 16:55:30 +01:00
if ( $query -> num_rows () >= 1 ) {
2023-11-24 13:49:30 +01:00
$result = '' ;
2023-11-30 12:26:31 +01:00
// Get URL for downloading LoTW
$url_query = $this -> db -> query ( 'SELECT lotw_download_url FROM config' );
$q = $url_query -> row ();
2023-11-30 13:15:36 +01:00
$lotw_base_url = $q -> lotw_download_url ;
2026-05-20 20:47:13 +02:00
$this -> _validate_url ( $lotw_base_url ); // fails with 500 error if URL is invalid
2023-11-30 12:26:31 +01:00
2026-02-17 15:37:11 +00:00
// Single-user mode: fall back to sequential download
if ( $sync_user_id != null ) {
foreach ( $query -> result () as $user ) {
if ( $sync_user_id != $user -> user_id ) { continue ; }
$station_ids = $this -> Stations -> all_station_ids_of_user ( $user -> user_id );
if ( $station_ids == '' ) { continue ; }
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 ;
}
$lotw_last_qsl_date = date ( 'Y-m-d' , strtotime ( $this -> logbook_model -> lotw_last_qsl_date ( $user -> user_id )));
$lotw_url = $lotw_base_url . " ? " ;
$lotw_url .= " login= " . urlencode ( $user -> user_lotw_name );
$lotw_url .= " &password= " . urlencode ( $user -> 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 );
2026-05-20 20:47:13 +02:00
$http_code = curl_getinfo ( $ch , CURLINFO_HTTP_CODE );
2026-02-17 15:37:11 +00:00
if ( curl_errno ( $ch )) {
$result = " LoTW download failed for user " . $user -> user_lotw_name . " : " . curl_strerror ( curl_errno ( $ch )) . " ( " . curl_errno ( $ch ) . " ). " ;
continue ;
2026-05-20 20:47:13 +02:00
} else if ( $http_code !== 200 ) {
$result = " LoTW download failed for user " . $user -> user_lotw_name . " : unexpected HTTP status " . $http_code . " . " ;
log_message ( 'error' , 'LoTW download failed for user ' . $user -> user_name . ': unexpected HTTP status ' . $http_code );
continue ;
2026-02-17 15:37:11 +00:00
} else if ( str_contains ( substr ( $content , 0 , 2000 ), " Username/password incorrect</I> " )) {
$result = " LoTW download failed for user " . $user -> user_lotw_name . " : Username/password incorrect " ;
log_message ( 'error' , 'LoTW download failed for user ' . $user -> user_name . ': Username/password incorrect' );
if ( $this -> Lotw_model -> remove_lotw_credentials ( $user -> user_id )) {
log_message ( 'error' , 'LoTW credentials deleted for user ' . $user -> user_name );
} else {
log_message ( 'error' , 'Deleting LoTW credentials for user ' . $user -> user_name . ' failed' );
}
continue ;
} else if ( str_contains ( substr ( $content , 0 , 2000 ), " Page Request Limit!</B> " )) {
$result = " LoTW download hit a rate limit for user " . $user -> user_lotw_name ;
log_message ( 'error' , 'LoTW download hit a rate limit for user ' . $user -> user_name );
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 " . $user -> user_lotw_name . " is invalid. Check your credentials. " ;
log_message ( 'error' , 'Downloaded LoTW report is invalid for user ' . $user -> user_name );
continue ;
}
ini_set ( 'memory_limit' , '-1' );
$result = $this -> loadFromFile ( $file , $station_ids , false );
}
return $result ;
} else {
// Multi-user mode (sync_user_id == null): parallel download via curl_multi only triggered by cron - so message-return is omitted
// Pass 1: collect eligible users and prepare download queue
$max_parallel = 5 ;
$queue = array ();
2023-07-12 15:56:12 +00:00
foreach ( $query -> result () as $user ) {
2026-02-17 15:37:11 +00:00
$station_ids = $this -> Stations -> all_station_ids_of_user ( $user -> user_id );
if ( $station_ids == '' ) { continue ; }
2020-09-06 16:26:15 +01:00
2023-11-24 13:49:30 +01:00
if ( $user -> user_lotw_password == '' ) {
continue ;
}
2020-09-06 16:55:30 +01:00
$config [ 'upload_path' ] = './uploads/' ;
2024-11-28 09:21:24 +00:00
$file = $config [ 'upload_path' ] . 'lotwreport_download_' . $user -> user_id . '_auto.adi' ;
2023-04-11 22:40:53 +02:00
if ( file_exists ( $file ) && ! is_writable ( $file )) {
2026-02-17 15:37:11 +00:00
log_message ( " Error " , " LoTW Multidownload: UID: " . $user -> user_id . " - Temporary download file " . $file . " is not writable. Aborting! " );
continue ;
}
if ( ! is_writable ( dirname ( $file ))) {
log_message ( " Error " , " LoTW Multidownload: UID: " . $user -> user_id . " - Temporary download directory " . dirname ( $file ) . " is not writable. Aborting! " );
2023-11-24 13:49:30 +01:00
continue ;
2023-04-11 22:40:53 +02:00
}
2020-09-06 16:55:30 +01:00
2023-11-24 13:49:30 +01:00
$lotw_last_qsl_date = date ( 'Y-m-d' , strtotime ( $this -> logbook_model -> lotw_last_qsl_date ( $user -> user_id )));
2020-09-06 16:26:15 +01:00
2023-11-30 13:15:36 +01:00
$lotw_url = $lotw_base_url . " ? " ;
2025-11-02 15:48:55 +01:00
$lotw_url .= " login= " . urlencode ( $user -> user_lotw_name );
$lotw_url .= " &password= " . urlencode ( $user -> user_lotw_password );
2020-09-06 16:55:30 +01:00
$lotw_url .= " &qso_query=1&qso_qsl='yes'&qso_qsldetail='yes'&qso_mydetail='yes' " ;
2022-05-09 14:44:32 +02:00
$lotw_url .= " &qso_qslsince= " ;
$lotw_url .= " $lotw_last_qsl_date " ;
2020-09-06 16:26:15 +01:00
2026-02-17 15:37:11 +00:00
$queue [] = array (
'url' => $lotw_url ,
'user' => $user ,
'file' => $file ,
'station_ids' => $station_ids ,
);
}
// Download in batches of $max_parallel, process completed ones before next batch
$mh = curl_multi_init ();
$active_handles = array (); // maps curl resource id => queue entry + handle
$queue_index = 0 ;
// Seed initial batch
while ( $queue_index < count ( $queue ) && count ( $active_handles ) < $max_parallel ) {
$entry = $queue [ $queue_index ];
2024-05-16 18:29:11 +02:00
$ch = curl_init ();
2026-02-17 15:37:11 +00:00
curl_setopt ( $ch , CURLOPT_URL , $entry [ 'url' ]);
2024-05-16 18:29:11 +02:00
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
curl_setopt ( $ch , CURLOPT_CONNECTTIMEOUT , 30 );
2026-02-17 15:37:11 +00:00
curl_multi_add_handle ( $mh , $ch );
$active_handles [( int ) $ch ] = array_merge ( $entry , array ( 'ch' => $ch ));
log_message ( 'debug' , 'LoTW parallel download started for UID ' . $entry [ 'user' ] -> user_id . ' (' . $entry [ 'user' ] -> user_lotw_name . ')' );
$queue_index ++ ;
}
// Process downloads as they complete, refill slots from queue
while ( count ( $active_handles ) > 0 ) {
curl_multi_exec ( $mh , $running );
// Check for completed handles
while ( $info = curl_multi_info_read ( $mh )) {
$ch = $info [ 'handle' ];
$dl = $active_handles [( int ) $ch ];
unset ( $active_handles [( int ) $ch ]);
$user = $dl [ 'user' ];
$file = $dl [ 'file' ];
$station_ids = $dl [ 'station_ids' ];
$errno = curl_errno ( $ch );
log_message ( 'debug' , 'LoTW parallel download finished for UID ' . $user -> user_id . ' (' . $user -> user_lotw_name . ')' . ( $errno ? ' with error: ' . curl_strerror ( $errno ) : '' ));
if ( $errno ) {
log_message ( 'error' , 'LoTW download failed for user ' . $user -> user_name . ': ' . curl_strerror ( $errno ));
curl_multi_remove_handle ( $mh , $ch );
2025-11-02 17:24:19 +01:00
} else {
2026-05-20 20:47:13 +02:00
$http_code = curl_getinfo ( $ch , CURLINFO_HTTP_CODE );
2026-02-17 15:37:11 +00:00
$content = curl_multi_getcontent ( $ch );
curl_multi_remove_handle ( $mh , $ch );
2026-05-20 20:47:13 +02:00
if ( $http_code !== 200 ) {
log_message ( 'error' , 'LoTW download failed for user ' . $user -> user_name . ': unexpected HTTP status ' . $http_code );
} else if ( str_contains ( substr ( $content , 0 , 2000 ), " Username/password incorrect</I> " )) {
2026-02-17 15:37:11 +00:00
log_message ( 'error' , 'LoTW download failed for user ' . $user -> user_name . ': Username/password incorrect' );
if ( $this -> Lotw_model -> remove_lotw_credentials ( $user -> user_id )) {
log_message ( 'error' , 'LoTW credentials deleted for user ' . $user -> user_name );
} else {
log_message ( 'error' , 'Deleting LoTW credentials for user ' . $user -> user_name . ' failed' );
}
} else if ( str_contains ( substr ( $content , 0 , 2000 ), " Page Request Limit!</B> " )) {
log_message ( 'error' , 'LoTW download hit a rate limit for user ' . $user -> user_name );
} else {
file_put_contents ( $file , $content );
if ( file_get_contents ( $file , false , null , 0 , 39 ) != " ARRL Logbook of the World Status Report " ) {
log_message ( 'error' , 'Downloaded LoTW report is invalid for user ' . $user -> user_name );
} else {
ini_set ( 'memory_limit' , '-1' );
log_message ( 'debug' , 'LoTW parallel download passing to loadFromFile for UID ' . $user -> user_id . ' (' . $user -> user_lotw_name . ')' );
$this -> loadFromFile ( $file , $station_ids , false );
}
}
}
// Refill slot from queue
if ( $queue_index < count ( $queue )) {
$entry = $queue [ $queue_index ];
$new_ch = curl_init ();
curl_setopt ( $new_ch , CURLOPT_URL , $entry [ 'url' ]);
curl_setopt ( $new_ch , CURLOPT_RETURNTRANSFER , true );
curl_setopt ( $new_ch , CURLOPT_CONNECTTIMEOUT , 30 );
curl_multi_add_handle ( $mh , $new_ch );
$active_handles [( int ) $new_ch ] = array_merge ( $entry , array ( 'ch' => $new_ch ));
log_message ( 'debug' , 'LoTW parallel download started for UID ' . $entry [ 'user' ] -> user_id . ' (' . $entry [ 'user' ] -> user_lotw_name . ')' );
$queue_index ++ ;
2025-11-02 17:24:19 +01:00
}
2023-04-11 23:19:32 +02:00
}
2020-09-06 16:55:30 +01:00
2026-02-17 15:37:11 +00:00
if ( count ( $active_handles ) > 0 ) {
curl_multi_select ( $mh , 1.0 );
}
2023-11-24 13:49:30 +01:00
}
2026-02-17 15:37:11 +00:00
curl_multi_close ( $mh );
2023-11-24 13:49:30 +01:00
return $result ;
2026-02-17 15:37:11 +00:00
} // end else (multi-user parallel mode)
2020-09-06 16:55:30 +01:00
} else {
2023-07-27 09:16:55 +02:00
return " No LoTW User details found to carry out matches. " ;
2020-09-06 16:55:30 +01:00
}
2020-09-06 16:26:15 +01:00
}
2024-12-19 06:44:55 +00:00
public function check_lotw_credentials () {
if ( ! $this -> user_model -> authorize ( 2 )) {
$this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " ));
redirect ( 'dashboard' );
exit ();
}
$ret = [];
2024-12-19 07:49:00 +00:00
$ret [ 'status' ] = '' ;
$raw = file_get_contents ( " php://input " );
try {
$obj = json_decode ( $raw , true );
2024-12-19 09:59:29 +01:00
} catch ( Exception $e ) {
2024-12-19 07:49:00 +00:00
$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 ? ? '' );
}
2024-12-19 06:44:55 +00:00
2024-12-19 07:49:00 +00:00
if ((( $data [ 'user_lotw_name' ] ? ? '' ) != '' ) && (( $data [ 'user_lotw_password' ] ? ? '' ) != '' ) && ( $ret [ 'status' ] != 'failed_wrongcall' )) {
2024-12-19 06:44:55 +00:00
2024-12-19 07:49:00 +00:00
// 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' ;
}
2024-12-19 06:44:55 +00:00
2024-12-19 07:49:00 +00:00
// 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 );
2026-01-02 13:13:39 +01:00
curl_setopt ( $ch , CURLOPT_CONNECTTIMEOUT , 10 );
curl_setopt ( $ch , CURLOPT_TIMEOUT , 20 );
2024-12-19 07:49:00 +00:00
$content = curl_exec ( $ch );
2026-01-01 16:49:24 +01:00
if ( curl_errno ( $ch )) {
$ret [ 'status' ] = 'failed' ;
2026-01-01 19:39:02 +01:00
$ret [ 'details' ] = __ ( " Connection to LoTW failed. " );
2026-01-02 10:39:11 +01:00
log_message ( 'debug' , " LoTW error: Connection to LoTW failed: " . curl_strerror ( curl_errno ( $ch )) . " ( " . curl_errno ( $ch ) . " ) " );
2026-01-01 16:49:24 +01:00
} else {
if ( str_contains ( $content , " Username/password incorrect</I> " )) {
2024-12-19 07:49:00 +00:00
$ret [ 'status' ] = 'failed_wrong_creds' ;
2024-12-19 10:19:17 +01:00
$ret [ 'details' ] = sprintf ( __ ( " LoTW login failed for user %s: %s. " ), $data [ 'user_lotw_name' ], __ ( " Username/password incorrect " ));
2026-01-02 10:39:11 +01:00
} elseif ( ! $content ) {
$ret [ 'status' ] = 'failed_na' ;
$ret [ 'details' ] = __ ( " LoTW currently not available. Try again later. " );
log_message ( 'debug' , " LoTW error: Connecting LoTW gave an empty result " );
2024-12-19 07:49:00 +00:00
} else {
$ret [ 'status' ] = 'OK' ;
2024-12-19 10:25:07 +01:00
$ret [ 'details' ] = __ ( " LoTW login OK! " );
2024-12-19 07:49:00 +00:00
}
2024-12-19 06:44:55 +00:00
}
} else {
2024-12-19 07:49:00 +00:00
if (( $ret [ 'status' ] ? ? '' ) == '' ) {
$ret [ 'status' ] = 'failed_nocred' ;
2024-12-19 10:19:17 +01:00
$ret [ 'details' ] = __ ( " No LoTW credentials provided. " );
2024-12-19 07:49:00 +00:00
}
2024-12-19 06:44:55 +00:00
}
header ( " Content-type: application/json " );
echo json_encode ( $ret );
return $ret ;
}
2024-05-16 07:11:47 +00:00
public function import () { // Is only called via frontend. Cron uses "upload". within download the download is called
2024-09-12 13:41:16 +00:00
$this -> load -> model ( 'Stations' );
2024-05-20 08:04:24 +02:00
if ( ! $this -> user_model -> authorize ( 2 )) {
2024-08-16 10:08:44 +02:00
$this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " ));
2024-05-20 08:04:24 +02:00
redirect ( 'dashboard' );
exit ();
}
2013-02-16 16:41:40 +00:00
2024-09-12 13:41:16 +00:00
$station_ids = $this -> Stations -> all_station_ids_of_user ( $this -> session -> userdata [ 'user_id' ]);
2024-11-21 06:25:26 +00:00
$data [ 'page_title' ] = __ ( " LoTW ADIF Import " );
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
$config [ 'upload_path' ] = './uploads/' ;
$config [ 'allowed_types' ] = 'adi|ADI' ;
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
$this -> load -> library ( 'upload' , $config );
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
$this -> load -> model ( 'logbook_model' );
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
if (( $this -> input -> post ( 'lotwimport' ) == 'fetch' ) && ( ! ( $this -> config -> item ( 'disable_manual_lotw' )))) {
2024-11-21 12:11:10 +00:00
$file = $config [ 'upload_path' ] . 'lotwreport_download_' . $this -> session -> userdata ( 'user_id' ) . '.adi' ;
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
// 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 ? ? '' );
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
// 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 ;
2026-05-20 20:47:13 +02:00
$this -> _validate_url ( $lotw_url ); // fails with 500 error if URL is invalid
2024-11-21 06:25:26 +00:00
// 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' );
}
2020-04-25 23:11:13 +02:00
2024-11-21 06:25:26 +00:00
$customDate = $this -> input -> post ( 'from' );
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
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' ])));
}
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
// 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' " ;
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
$lotw_url .= " &qso_qslsince= " ;
$lotw_url .= " $lotw_last_qsl_date " ;
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
if ( $this -> input -> post ( 'callsign' ) != '0' ) {
$lotw_url .= " &qso_owncall= " . $this -> input -> post ( 'callsign' );
}
2023-12-10 22:27:20 +01:00
2024-11-21 06:25:26 +00:00
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 );
2026-05-20 20:47:13 +02:00
$http_code = curl_getinfo ( $ch , CURLINFO_HTTP_CODE );
2024-12-04 10:15:32 +01:00
if ( curl_errno ( $ch )) {
print " LoTW download failed for user " . $data [ 'user_lotw_name' ] . " : " . curl_strerror ( curl_errno ( $ch )) . " ( " . curl_errno ( $ch ) . " ). " ;
2026-05-20 20:47:13 +02:00
} else if ( $http_code !== 200 ) {
print " LoTW download failed for user " . $data [ 'user_lotw_name' ] . " : unexpected HTTP status " . $http_code . " . " ;
2024-12-04 10:15:32 +01:00
} else if ( str_contains ( $content , " Username/password incorrect</I> " )) {
print " LoTW download failed for user " . $data [ 'user_lotw_name' ] . " : Username/password incorrect " ;
} else {
2024-11-21 06:25:26 +00:00
file_put_contents ( $file , $content );
ini_set ( 'memory_limit' , '-1' );
$this -> loadFromFile ( $file , $station_ids );
2024-05-16 07:11:47 +00:00
}
2024-09-12 13:41:16 +00:00
} else {
2024-11-21 06:25:26 +00:00
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' ));
2024-05-16 07:11:47 +00:00
2024-11-21 06:25:26 +00:00
$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 ()) {
2024-05-16 07:11:47 +00:00
2024-11-21 06:25:26 +00:00
$data [ 'error' ] = $this -> upload -> display_errors ();
$this -> load -> model ( 'Stations' );
$data [ 'callsigns' ] = $this -> Stations -> callsigns_of_user ( $this -> session -> userdata ( 'user_id' ));
2016-01-09 23:50:42 +00:00
2024-11-21 06:25:26 +00:00
$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 );
2013-02-21 20:13:42 -06:00
}
2013-02-16 16:41:40 +00:00
}
2013-02-22 18:30:33 -06:00
} // end function
2016-01-09 23:50:42 +00:00
2019-06-16 14:31:23 +01:00
/*
2026-01-07 22:40:01 +01:00
Deprecated . To be back compatible we do the same as update / lotw_users
2024-07-11 23:22:25 +02:00
HB9HIL , July 2024
2021-05-01 19:52:13 +02:00
*/
2020-09-21 14:02:50 +01:00
public function load_users () {
2024-07-11 23:22:25 +02:00
$this -> load -> model ( 'Update_model' );
$result = $this -> Update_model -> lotw_users ();
echo $result ;
2019-06-16 14:31:23 +01:00
}
2020-08-25 17:50:52 +01:00
function signlog ( $sign_key , $string ) {
2020-08-12 23:53:24 +01:00
2020-08-25 17:50:52 +01:00
$qso_string = $string ;
2020-08-12 23:53:24 +01:00
2020-08-25 17:50:52 +01:00
$key = $sign_key ;
2020-08-12 23:53:24 +01:00
2024-01-20 23:35:19 +01:00
$pkeyid = openssl_pkey_get_private ( $key , 'wavelog' );
2024-01-20 23:45:25 +01:00
if ( $pkeyid ) {
//openssl_sign($plaintext, $signature, $pkeyid, OPENSSL_ALGO_SHA1 );
//openssl_free_key($pkeyid);
2020-08-12 23:53:24 +01:00
2024-01-20 23:45:25 +01:00
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 );
2024-05-13 08:43:31 +02:00
return $signature_b64 . " \n " ;
2024-07-10 00:21:45 +08:00
} else {
// in case of deprecation of SHA-1 in some distro
2024-07-10 18:00:56 +02:00
log_message ( 'error' , 'Error signing LoTW log: ' . openssl_error_string ());
2024-01-20 23:45:25 +01:00
}
} else {
log_message ( 'error' , 'Error signing LoTW log.' );
2024-01-20 23:59:45 +01:00
return null ;
2020-08-13 23:03:25 +01:00
}
2020-08-12 23:53:24 +01:00
}
2022-03-15 18:04:24 +00:00
/*
| Function : lotw_ca_province_map
| Requires : candian province map $ca_province
2022-03-15 13:14:03 +00:00
*/
function lotw_ca_province_map ( $ca_prov ) {
switch ( $ca_prov ) :
case " QC " :
return " PQ " ;
break ;
2024-05-16 07:50:23 +02:00
case " NL " :
return " NF " ;
break ;
2022-03-15 13:14:03 +00:00
default :
return $ca_prov ;
endswitch ;
}
2025-09-16 20:49:11 +02:00
/*
| Function : lotw_ru_oblast_map
| Requires : russian oblast map $ru_oblast
*/
function lotw_ru_oblast_map ( $ru_oblast ) {
switch ( $ru_oblast ) :
case " YR " :
return " JA " ;
break ;
case " YN " :
return " JN " ;
break ;
default :
return $ru_oblast ;
endswitch ;
}
2021-03-18 17:06:21 +00:00
/*
| Function : mode_map
2021-03-19 15:55:58 -07:00
| Requires : mode as $mode , submode as $submode
2021-03-18 17:06:21 +00:00
|
| This converts ADIF modes to the mode that LoTW expects if its non standard
*/
2021-03-19 15:55:58 -07:00
function mode_map ( $mode , $submode ) {
2021-03-18 17:06:21 +00:00
switch ( $mode ) :
case " PKT " :
2021-03-18 17:08:01 +00:00
return " PACKET " ;
2021-03-18 17:06:21 +00:00
break ;
2021-03-19 15:55:58 -07:00
case " MFSK " :
if ( $submode == " FT4 " ) {
return " FT4 " ;
break ;
2021-11-04 11:43:17 +00:00
} elseif ( $submode == " FST4 " ) {
2021-11-20 20:51:01 +08:00
return " FST4 " ;
break ;
} elseif ( $submode == " MFSK16 " ) {
return " MFSK16 " ;
break ;
} elseif ( $submode == " MFSK8 " ) {
return " MFSK8 " ;
break ;
2021-11-04 11:43:17 +00:00
} elseif ( $submode == " Q65 " ) {
2021-11-20 20:51:01 +08:00
return " Q65 " ;
break ;
2021-03-19 15:55:58 -07:00
} else {
2021-11-20 20:51:01 +08:00
return " DATA " ;
2021-03-19 15:55:58 -07:00
break ;
}
2022-01-04 17:10:32 +00:00
case " PSK " :
if ( $submode == " PSK31 " ) {
return " PSK31 " ;
break ;
} elseif ( $submode == " PSK63 " ) {
2022-01-05 22:17:45 +00:00
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 ;
2022-01-04 17:10:32 +00:00
} else {
return " DATA " ;
break ;
}
2021-03-18 17:06:21 +00:00
default :
2021-03-18 17:08:01 +00:00
return $mode ;
2021-03-18 17:06:21 +00:00
endswitch ;
}
2025-11-20 13:11:28 +01:00
function lotw_cert_status ( $serial ) {
2025-11-19 14:07:58 +01:00
if (( $serial ? ? '' ) != '' && is_numeric ( $serial )) {
$url = 'https://lotw.arrl.org/lotw/crl?serial=' . $serial ;
$ch = curl_init ();
curl_setopt ( $ch , CURLOPT_URL , $url );
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
curl_setopt ( $ch , CURLOPT_CONNECTTIMEOUT , 10 );
$result = curl_exec ( $ch );
2025-11-20 21:39:48 +01:00
if ( curl_errno ( $ch )){
log_message ( 'error' , 'Error fetch LoTW CRL results: ' . curl_strerror ( curl_errno ( $ch )));
return 99 ;
}
2025-11-19 14:07:58 +01:00
$xml = new SimpleXMLElement ( $result );
2025-11-20 21:39:48 +01:00
if ( ! isset ( $xml -> Status )) {
log_message ( 'error' , 'Error parsing LoTW CRL result: ' . $result );
return 98 ;
}
2025-11-19 14:07:58 +01:00
switch (( string ) $xml -> Status ) {
case 'Superceded' :
return 1 ;
case 'Unrevoked' :
return 0 ;
default :
log_message ( 'error' , 'Unknown LotW CRL status: ' . ( string ) $xml -> Status );
2025-11-20 21:39:48 +01:00
return 97 ;
2025-11-19 14:07:58 +01:00
}
}
return 99 ;
}
2016-01-09 23:50:42 +00:00
} // end class