From c962a972a808fd95c504acb5df77775dda261081 Mon Sep 17 00:00:00 2001 From: Hugo Silva Date: Thu, 16 Mar 2023 18:58:48 +0000 Subject: [PATCH 1/6] Basic QO-100 Dx Club API integration --- application/config/migration.php | 2 +- .../migrations/115_add_webadif_api_export.php | 32 +++ application/models/Logbook_model.php | 190 +++++++++++++----- application/models/Stations.php | 24 ++- application/views/station_profile/create.php | 15 ++ application/views/station_profile/edit.php | 21 ++ 6 files changed, 222 insertions(+), 62 deletions(-) create mode 100644 application/migrations/115_add_webadif_api_export.php diff --git a/application/config/migration.php b/application/config/migration.php index 1c0ef7a1..9a64222f 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -21,7 +21,7 @@ $config['migration_enabled'] = TRUE; | be upgraded / downgraded to. | */ -$config['migration_version'] = 114; +$config['migration_version'] = 115; /* |-------------------------------------------------------------------------- diff --git a/application/migrations/115_add_webadif_api_export.php b/application/migrations/115_add_webadif_api_export.php new file mode 100644 index 00000000..cfbbcf5a --- /dev/null +++ b/application/migrations/115_add_webadif_api_export.php @@ -0,0 +1,32 @@ +dbforge->add_column('station_profile', $fields); + + $fields = array( + "webadif_upload_date datetime DEFAULT NULL", + "webadif_upload_status varchar(1) DEFAULT 'N'", + ); + $this->dbforge->add_column($this->config->item('table_name'), $fields); + + } + + public function down() + { + $this->dbforge->drop_column('station_profile', 'webadifapikey'); + $this->dbforge->drop_column('station_profile', 'webadifapiurl'); + $this->dbforge->drop_column('station_profile', 'webadifrealtime'); + $this->dbforge->drop_column($this->config->item('table_name'), 'webadif_upload_date'); + $this->dbforge->drop_column($this->config->item('table_name'), 'webadif_upload_status'); + } +} diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index eb1fa4b4..79b86b35 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -481,6 +481,25 @@ class Logbook_model extends CI_Model { $this->mark_qrz_qsos_sent($last_id); } } + + $result = $this->exists_webadif_api_key($data['station_id']); + // Push qso to webadif if apikey is set, and realtime upload is enabled, and we're not importing an adif-file + if (isset($result->webadifapikey) && $result->webadifrealtime == 1) { + $CI =& get_instance(); + $CI->load->library('AdifHelper'); + $qso = $this->get_qso($last_id)->result(); + + $adif = $CI->adifhelper->getAdifLine($qso[0]); + $result = $this->push_qso_to_webadif( + $result->webadifapiurl, + $result->webadifapikey, + $adif + ); + + if ($result) { + $this->mark_webadif_qsos_sent($last_id); + } + } } } @@ -503,6 +522,25 @@ class Logbook_model extends CI_Model { } } + /* + * Function checks if a WebADIF API Key exists in the table with the given station id + */ + function exists_webadif_api_key($station_id) { + $sql = 'select webadifapikey, webadifapiurl, webadifrealtime from station_profile + where station_id = ' . $station_id; + + $query = $this->db->query($sql); + + $result = $query->row(); + + if ($result) { + return $result; + } + else { + return false; + } + } + /* * Function uploads a QSO to QRZ with the API given. * $adif contains a line with the QSO in the ADIF format. QSO ends with an @@ -545,6 +583,36 @@ class Logbook_model extends CI_Model { curl_close($ch); } + /* + * Function uploads a QSO to WebADIF consumer with the API given. + * $adif contains a line with the QSO in the ADIF format. + */ + function push_qso_to_webadif($url, $apikey, $adif) : bool{ + + $headers = array( + 'Content-Type: text/plain', + 'X-API-Key: ' . $apikey + ); + + if (substr($url, -1) !== "/") { + $url .= "/"; + } + + $ch = curl_init( $url . "qso"); + curl_setopt( $ch, CURLOPT_POST, true); + curl_setopt( $ch, CURLOPT_POSTFIELDS, (string)$adif); + curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt( $ch, CURLOPT_HEADER, 0); + curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true); + + $content = curl_exec($ch); // TODO: better error handling + $errors = curl_error($ch); + $response = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + return $response === 200; + } + /* * Function marks QSOs as uploaded to QRZ. * $primarykey is the unique id for that QSO in the logbook @@ -562,6 +630,24 @@ class Logbook_model extends CI_Model { return true; } + /* + * Function marks QSOs as uploaded to WebADIF. + * $primarykey is the unique id for that QSO in the logbook + */ + function mark_webadif_qsos_sent($primarykey) + { + $data = array( + 'webadif_upload_date' => date("Y-m-d H:i:s", strtotime("now")), + 'webadif_upload_status' => 'Y', + ); + + $this->db->where('COL_PRIMARY_KEY', $primarykey); + + $this->db->update($this->config->item('table_name'), $data); + + return true; + } + function upload_amsat_status($data) { $sat_name = ''; if ($data['COL_SAT_NAME'] == 'AO-7') { @@ -1038,34 +1124,34 @@ class Logbook_model extends CI_Model { return $this->db->get($this->config->item('table_name')); } - - + + // Set Paper to received function paperqsl_update($qso_id, $method) { - + $data = array( 'COL_QSLRDATE' => date('Y-m-d H:i:s'), 'COL_QSL_RCVD' => 'Y', 'COL_QSL_RCVD_VIA' => $method ); - + $this->db->where('COL_PRIMARY_KEY', $qso_id); - + $this->db->update($this->config->item('table_name'), $data); } // Set Paper to sent function paperqsl_update_sent($qso_id, $method) { - + $data = array( 'COL_QSLSDATE' => date('Y-m-d H:i:s'), 'COL_QSL_SENT' => 'Y', 'COL_QSL_SENT_VIA' => $method ); - + $this->db->where('COL_PRIMARY_KEY', $qso_id); - + $this->db->update($this->config->item('table_name'), $data); } @@ -1219,10 +1305,10 @@ class Logbook_model extends CI_Model { } else { $logbooks_locations_array = $StationLocationsArray; } - + if ($logbooks_locations_array) { $location_list = "'".implode("','",$logbooks_locations_array)."'"; - + $sql = "SELECT * FROM ( select * from " . $this->config->item('table_name'). " WHERE station_id IN(". $location_list .") order by col_time_on desc, col_primary_key desc @@ -1233,12 +1319,12 @@ class Logbook_model extends CI_Model { order by col_time_on desc, col_primary_key desc"; $query = $this->db->query($sql); - + return $query; } else { return null; } - + } /* Get all QSOs with a valid grid for use in the KML export */ @@ -1390,7 +1476,7 @@ class Logbook_model extends CI_Model { } else { return null; } - + } /* Return QSOs over a period of days */ @@ -1465,7 +1551,7 @@ class Logbook_model extends CI_Model { // Return QSOs made during the current month function month_qsos($StationLocationsArray = null) { - if($StationLocationsArray == null) { + if($StationLocationsArray == null) { $CI =& get_instance(); $CI->load->model('logbooks_model'); $logbooks_locations_array = $CI->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); @@ -1617,17 +1703,17 @@ class Logbook_model extends CI_Model { if ($searchCriteria['mode'] !== '') { $this->db->group_start(); - $this->db->where('COL_MODE', $searchCriteria['mode']); + $this->db->where('COL_MODE', $searchCriteria['mode']); $this->db->or_where('COL_SUBMODE', $searchCriteria['mode']); $this->db->group_end(); } if ($searchCriteria['band'] !== '') { if($searchCriteria['band'] != "SAT") { - $this->db->where('COL_BAND', $searchCriteria['band']); - $this->db->where('COL_PROP_MODE != "SAT"'); + $this->db->where('COL_BAND', $searchCriteria['band']); + $this->db->where('COL_PROP_MODE != "SAT"'); } else { - $this->db->where('COL_PROP_MODE', 'SAT'); + $this->db->where('COL_PROP_MODE', 'SAT'); } } @@ -1746,7 +1832,7 @@ class Logbook_model extends CI_Model { } function get_QSLStats($StationLocationsArray = null) { - + if($StationLocationsArray == null) { $CI =& get_instance(); $CI->load->model('logbooks_model'); @@ -1872,7 +1958,7 @@ class Logbook_model extends CI_Model { $query = $this->db->get($this->config->item('table_name')); $row = $query->row(); - + if($row == null) { return 0; } else { @@ -1995,7 +2081,7 @@ class Logbook_model extends CI_Model { $this->db->where('COL_COUNTRY !=', 'Invalid'); $this->db->where('COL_DXCC >', '0'); $query = $this->db->get($this->config->item('table_name')); - + return $query->num_rows(); } else { return 0; @@ -2028,7 +2114,7 @@ class Logbook_model extends CI_Model { /* Return total number of countries confirmed with along with qsl types confirmed */ function total_countries_confirmed($StationLocationsArray = null) { - + if($StationLocationsArray == null) { $CI =& get_instance(); $CI->load->model('logbooks_model'); @@ -2040,7 +2126,7 @@ class Logbook_model extends CI_Model { if(!empty($logbooks_locations_array)) { $this->db->select('COUNT(DISTINCT COL_COUNTRY) as Countries_Worked, COUNT(DISTINCT IF(COL_QSL_RCVD = "Y", COL_COUNTRY, NULL)) as Countries_Worked_QSL, - COUNT(DISTINCT IF(COL_EQSL_QSL_RCVD = "Y", COL_COUNTRY, NULL)) as Countries_Worked_EQSL, + COUNT(DISTINCT IF(COL_EQSL_QSL_RCVD = "Y", COL_COUNTRY, NULL)) as Countries_Worked_EQSL, COUNT(DISTINCT IF(COL_LOTW_QSL_RCVD = "Y", COL_COUNTRY, NULL)) as Countries_Worked_LOTW'); $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_COUNTRY !=', 'Invalid'); @@ -2346,7 +2432,7 @@ class Logbook_model extends CI_Model { $this->db->join($this->config->item('table_name'),'station_profile.station_id = '.$this->config->item('table_name').'.station_id'); $this->db->where("coalesce(station_profile.eqslqthnickname, '') <> ''"); $this->db->where($this->config->item('table_name').'.COL_CALL !=', ''); - $this->db->group_start(); + $this->db->group_start(); $this->db->where($this->config->item('table_name').'.COL_EQSL_QSL_SENT is null'); $this->db->or_where($this->config->item('table_name').'.COL_EQSL_QSL_SENT', ''); $this->db->or_where($this->config->item('table_name').'.COL_EQSL_QSL_SENT', 'R'); @@ -2858,11 +2944,11 @@ class Logbook_model extends CI_Model { 'COL_WWFF_REF' => (!empty($record['wwff_ref'])) ? $record['wwff_ref'] : '', 'COL_POTA_REF' => (!empty($record['pota_ref'])) ? $record['pota_ref'] : '', 'COL_SRX' => (!empty($record['srx'])) ? (int)$record['srx'] : null, - //convert to integer to make sure no invalid entries are imported + //convert to integer to make sure no invalid entries are imported 'COL_SRX_STRING' => (!empty($record['srx_string'])) ? $record['srx_string'] : '', 'COL_STATE' => (!empty($record['state'])) ? strtoupper($record['state']) : '', 'COL_STATION_CALLSIGN' => (!empty($record['station_callsign'])) ? $record['station_callsign'] : '', - //convert to integer to make sure no invalid entries are imported + //convert to integer to make sure no invalid entries are imported 'COL_STX' => (!empty($record['stx'])) ? (int)$record['stx'] : null, 'COL_STX_STRING' => (!empty($record['stx_string'])) ? $record['stx_string'] : '', 'COL_SUBMODE' => $input_submode, @@ -2971,14 +3057,14 @@ class Logbook_model extends CI_Model { $callsign = $matches[3][0]; $suffix = $matches[5][0]; if ($prefix) { - $prefix = substr($prefix, 0, -1); # Remove the / at the end + $prefix = substr($prefix, 0, -1); # Remove the / at the end } if ($suffix) { $suffix = substr($suffix, 1); # Remove the / at the beginning }; if (preg_match($csadditions, $suffix)) { if ($prefix) { - $call = $prefix; + $call = $prefix; } else { $call = $callsign; } @@ -3052,7 +3138,7 @@ class Logbook_model extends CI_Model { $call = "3D2/C"; # will match with Conway } elseif (preg_match('/(^LZ\/)|(\/LZ[1-9]?$)/', $call)) { # LZ/ is LZ0 by DXCC but this is VP8h $call = "LZ"; - } elseif (preg_match('/(^KG4)[A-Z09]{2}/', $call)) { + } elseif (preg_match('/(^KG4)[A-Z09]{2}/', $call)) { $call = "KG4"; } elseif (preg_match('/(^KG4)[A-Z09]{1}/', $call)) { $call = "K"; @@ -3062,14 +3148,14 @@ class Logbook_model extends CI_Model { $callsign = $matches[3][0]; $suffix = $matches[5][0]; if ($prefix) { - $prefix = substr($prefix, 0, -1); # Remove the / at the end + $prefix = substr($prefix, 0, -1); # Remove the / at the end } if ($suffix) { $suffix = substr($suffix, 1); # Remove the / at the beginning }; if (preg_match($csadditions, $suffix)) { if ($prefix) { - $call = $prefix; + $call = $prefix; } else { $call = $callsign; } @@ -3120,38 +3206,38 @@ class Logbook_model extends CI_Model { $a = ''; $b = ''; $c = ''; - + $lidadditions = '/^QRP$|^LGT$/'; $csadditions = '/^P$|^R$|^A$|^M$|^LH$/'; $noneadditions = '/^MM$|^AM$/'; - + # First check if the call is in the proper format, A/B/C where A and C # are optional (prefix of guest country and P, MM, AM etc) and B is the # callsign. Only letters, figures and "/" is accepted, no further check if the # callsign "makes sense". # 23.Apr.06: Added another "/X" to the regex, for calls like RV0AL/0/P # as used by RDA-DXpeditions.... - + if (preg_match_all('/^((\d|[A-Z])+\/)?((\d|[A-Z]){3,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?$/', $testcall, $matches)) { - + # Now $1 holds A (incl /), $3 holds the callsign B and $5 has C - # We save them to $a, $b and $c respectively to ensure they won't get + # We save them to $a, $b and $c respectively to ensure they won't get # lost in further Regex evaluations. $a = $matches[1][0]; $b = $matches[3][0]; $c = $matches[5][0]; - + if ($a) { - $a = substr($a, 0, -1); # Remove the / at the end + $a = substr($a, 0, -1); # Remove the / at the end } if ($c) { $c = substr($c, 1); # Remove the / at the beginning }; - + # In some cases when there is no part A but B and C, and C is longer than 2 # letters, it happens that $a and $b get the values that $b and $c should # have. This often happens with liddish callsign-additions like /QRP and - # /LGT, but also with calls like DJ1YFK/KP5. ~/.yfklog has a line called + # /LGT, but also with calls like DJ1YFK/KP5. ~/.yfklog has a line called # "lidadditions", which has QRP and LGT as defaults. This sorts out half of # the problem, but not calls like DJ1YFK/KH5. This is tested in a second # try: $a looks like a call (.\d[A-Z]) and $b doesn't (.\d), they are @@ -3167,32 +3253,32 @@ class Logbook_model extends CI_Model { $a = $temp; } } - + # *** Added later *** The check didn't make sure that the callsign # contains a letter. there are letter-only callsigns like RAEM, but not - # figure-only calls. - + # figure-only calls. + if (preg_match('/^[0-9]+$/', $b)) { # Callsign only consists of numbers. Bad! return null; # exit, undef } - + # Depending on these values we have to determine the prefix. # Following cases are possible: # # 1. $a and $c undef --> only callsign, subcases # 1.1 $b contains a number -> everything from start to number - # 1.2 $b contains no number -> first two letters plus 0 + # 1.2 $b contains no number -> first two letters plus 0 # 2. $a undef, subcases: # 2.1 $c is only a number -> $a with changed number - # 2.2 $c is /P,/M,/MM,/AM -> 1. + # 2.2 $c is /P,/M,/MM,/AM -> 1. # 2.3 $c is something else and will be interpreted as a Prefix - # 3. $a is defined, will be taken as PFX, regardless of $c - + # 3. $a is defined, will be taken as PFX, regardless of $c + if (($a == null) && ($c == null)) { # Case 1 if (preg_match('/\d/', $b)) { # Case 1.1, contains number preg_match('/(.+\d)[A-Z]*/', $b, $matches); # Prefix is all but the last $prefix = $matches[1]; # Letters - } else { # Case 1.2, no number + } else { # Case 1.2, no number $prefix = substr($b, 0, 2) . "0"; # first two + 0 } } elseif (($a == null) && (isset($c))) { # Case 2, CALL/X @@ -3204,12 +3290,12 @@ class Logbook_model extends CI_Model { # like N66A/7 -> N7 this brings the wrong result of N67, but I # think that's rather irrelevant cos such calls rarely appear # and if they do, it's very unlikely for them to have a number - # attached. You can still edit it by hand anyway.. + # attached. You can still edit it by hand anyway.. if (preg_match('/^([A-Z]\d)\d$/', $matches[1])) { # e.g. A45 $c = 0 $prefix = $matches[1] . $c; # -> A40 } else { # Otherwise cut all numbers preg_match('/(.*[A-Z])\d+/', $matches[1], $match); # Prefix w/o number in $1 - $prefix = $match[1] . $c; # Add attached number + $prefix = $match[1] . $c; # Add attached number } } elseif (preg_match($csadditions, $c)) { preg_match('/(.+\d)[A-Z]*/', $b, $matches); # Known attachment -> like Case 1.1 @@ -3241,7 +3327,7 @@ class Logbook_model extends CI_Model { # case, the superfluous part will be cropped. Since this, however, changes the # DXCC of the prefix, this will NOT happen when invoked from with an # extra parameter $_[1]; this will happen when invoking it from &dxcc. - + if (preg_match('/(\w+\d)[A-Z]+\d/', $prefix, $matches) && $i == null) { $prefix = $matches[1][0]; } diff --git a/application/models/Stations.php b/application/models/Stations.php index 1d6b9a96..5abab0c7 100644 --- a/application/models/Stations.php +++ b/application/models/Stations.php @@ -81,10 +81,13 @@ class Stations extends CI_Model { 'oqrs' => xss_clean($this->input->post('oqrs', true)), 'oqrs_email' => xss_clean($this->input->post('oqrsemail', true)), 'oqrs_text' => xss_clean($this->input->post('oqrstext', true)), + 'webadifapikey' => xss_clean($this->input->post('webadifapikey', true)), + 'webadifapiurl' => 'https://qo100dx.club/api', + 'webadifrealtime' => xss_clean($this->input->post('webadifrealtime', true)), ); // Insert Records - $this->db->insert('station_profile', $data); + $this->db->insert('station_profile', $data); } function edit() { @@ -111,11 +114,14 @@ class Stations extends CI_Model { 'oqrs' => xss_clean($this->input->post('oqrs', true)), 'oqrs_email' => xss_clean($this->input->post('oqrsemail', true)), 'oqrs_text' => xss_clean($this->input->post('oqrstext', true)), + 'webadifapikey' => xss_clean($this->input->post('webadifapikey', true)), + 'webadifapiurl' => 'https://qo100dx.club/api', + 'webadifrealtime' => xss_clean($this->input->post('webadifrealtime', true)), ); $this->db->where('user_id', $this->session->userdata('user_id')); $this->db->where('station_id', xss_clean($this->input->post('station_id', true))); - $this->db->update('station_profile', $data); + $this->db->update('station_profile', $data); } function delete($id) { @@ -132,7 +138,7 @@ class Stations extends CI_Model { $this->db->delete($this->config->item('table_name')); // Delete Station Profile - $this->db->delete('station_profile', array('station_id' => $clean_id)); + $this->db->delete('station_profile', array('station_id' => $clean_id)); } function deletelog($id) { @@ -145,7 +151,7 @@ class Stations extends CI_Model { $data = array( 'user_id' => $this->session->userdata('user_id'), ); - + $this->db->where('station_id', $id); $this->db->update('station_profile', $data); } @@ -160,7 +166,7 @@ class Stations extends CI_Model { $data = array( 'user_id' => $id, ); - + $this->db->update('station_profile', $data); } @@ -192,7 +198,7 @@ class Stations extends CI_Model { $this->db->where('user_id', $this->session->userdata('user_id')); $this->db->update('station_profile', $current_default); - // Deselect current default + // Deselect current default $newdefault = array( 'station_active' => 1, ); @@ -215,7 +221,7 @@ class Stations extends CI_Model { return "0"; } } - + public function find_gridsquare() { $this->db->where('user_id', $this->session->userdata('user_id')); $this->db->where('station_active', 1); @@ -262,7 +268,7 @@ class Stations extends CI_Model { ); $this->db->where('COL_STATION_CALLSIGN', $row->station_callsign); - + if($row->station_iota != "") { $this->db->where('COL_MY_IOTA', $row->station_iota); } @@ -307,7 +313,7 @@ class Stations extends CI_Model { return 1; } else { return 0; - } + } } function stations_with_qrz_api_key() { diff --git a/application/views/station_profile/create.php b/application/views/station_profile/create.php index 59bee15d..6e4fcd65 100644 --- a/application/views/station_profile/create.php +++ b/application/views/station_profile/create.php @@ -241,6 +241,21 @@ +
+
+ + + Create your API key on your QO-100 Dx Club's profile page +
+
+ + +
+
+
webadifapikey; } ?>"> + Create your API key on your QO-100 Dx Club's profile page +
+
+ + +
+ + + +
From a745cbb6e8ca8722ba08fc0cbaff4719d1a04782 Mon Sep 17 00:00:00 2001 From: Hugo Silva Date: Fri, 17 Mar 2023 08:28:08 +0000 Subject: [PATCH 2/6] Check if columns exist before adding them in migration --- .../migrations/115_add_webadif_api_export.php | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/application/migrations/115_add_webadif_api_export.php b/application/migrations/115_add_webadif_api_export.php index cfbbcf5a..7997c83b 100644 --- a/application/migrations/115_add_webadif_api_export.php +++ b/application/migrations/115_add_webadif_api_export.php @@ -6,20 +6,40 @@ class Migration_add_webadif_api_export extends CI_Migration { public function up() { - $fields = array( - 'webadifapikey varchar(50) DEFAULT NULL', - 'webadifapiurl varchar(256) DEFAULT NULL', - 'webadifrealtime bool DEFAULT FALSE', - ); - $this->dbforge->add_column('station_profile', $fields); + if (!$this->db->field_exists('webadifapikey', 'station_profile')) { + $fields = array( + 'webadifapikey varchar(50) DEFAULT NULL' + ); + $this->dbforge->add_column('station_profile', $fields); + } + if (!$this->db->field_exists('webadifapiurl', 'station_profile')) { + $fields = array( + 'webadifapiurl varchar(256) DEFAULT NULL' + ); + $this->dbforge->add_column('station_profile', $fields); + } + if (!$this->db->field_exists('webadifrealtime', 'station_profile')) { + $fields = array( + 'webadifrealtime bool DEFAULT FALSE' + ); + $this->dbforge->add_column('station_profile', $fields); + } - $fields = array( - "webadif_upload_date datetime DEFAULT NULL", - "webadif_upload_status varchar(1) DEFAULT 'N'", - ); - $this->dbforge->add_column($this->config->item('table_name'), $fields); + if (!$this->db->field_exists('webadif_upload_date', $this->config->item('table_name'))) { + $fields = array( + "webadif_upload_date datetime DEFAULT NULL" + ); + $this->dbforge->add_column($this->config->item('table_name'), $fields); + } - } + if (!$this->db->field_exists('webadif_upload_status', $this->config->item('table_name'))) { + $fields = array( + "webadif_upload_status varchar(1) DEFAULT 'N'" + ); + $this->dbforge->add_column($this->config->item('table_name'), $fields); + } + + } public function down() { From d7d06c2bbc6d76912d38c73afff43c2aefdab40d Mon Sep 17 00:00:00 2001 From: Hugo Silva Date: Fri, 17 Mar 2023 13:11:04 +0000 Subject: [PATCH 3/6] Store webadif upload data in separate table --- .../migrations/115_add_webadif_api_export.php | 32 +++++++++++-------- application/models/Logbook_model.php | 8 ++--- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/application/migrations/115_add_webadif_api_export.php b/application/migrations/115_add_webadif_api_export.php index 7997c83b..22e29007 100644 --- a/application/migrations/115_add_webadif_api_export.php +++ b/application/migrations/115_add_webadif_api_export.php @@ -25,18 +25,25 @@ class Migration_add_webadif_api_export extends CI_Migration { $this->dbforge->add_column('station_profile', $fields); } - if (!$this->db->field_exists('webadif_upload_date', $this->config->item('table_name'))) { - $fields = array( - "webadif_upload_date datetime DEFAULT NULL" - ); - $this->dbforge->add_column($this->config->item('table_name'), $fields); - } - if (!$this->db->field_exists('webadif_upload_status', $this->config->item('table_name'))) { - $fields = array( - "webadif_upload_status varchar(1) DEFAULT 'N'" - ); - $this->dbforge->add_column($this->config->item('table_name'), $fields); + if (!$this->db->table_exists('webadif')) { + $this->dbforge->add_field(array( + 'id' => array( + 'type' => 'INT', + 'auto_increment' => TRUE + ), + 'qso_id' => array( + 'type' => 'int', + ), + 'upload_date' => array( + 'type' => 'datetime', + ), + )); + + $this->dbforge->add_key('id', TRUE); + $this->dbforge->add_key(array('qso_id','upload_date'), FALSE); + + $this->dbforge->create_table('webadif'); } } @@ -46,7 +53,6 @@ class Migration_add_webadif_api_export extends CI_Migration { $this->dbforge->drop_column('station_profile', 'webadifapikey'); $this->dbforge->drop_column('station_profile', 'webadifapiurl'); $this->dbforge->drop_column('station_profile', 'webadifrealtime'); - $this->dbforge->drop_column($this->config->item('table_name'), 'webadif_upload_date'); - $this->dbforge->drop_column($this->config->item('table_name'), 'webadif_upload_status'); + $this->dbforge->drop_table('webadif'); } } diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 79b86b35..53299b8a 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -637,13 +637,11 @@ class Logbook_model extends CI_Model { function mark_webadif_qsos_sent($primarykey) { $data = array( - 'webadif_upload_date' => date("Y-m-d H:i:s", strtotime("now")), - 'webadif_upload_status' => 'Y', + 'upload_date' => date("Y-m-d H:i:s", strtotime("now")), + 'qso_id' => $primarykey, ); - $this->db->where('COL_PRIMARY_KEY', $primarykey); - - $this->db->update($this->config->item('table_name'), $data); + $this->db->insert('webadif', $data); return true; } From cbf6efd19fd2c8612bc97ee1351474f678acda71 Mon Sep 17 00:00:00 2001 From: Hugo Silva Date: Fri, 17 Mar 2023 17:54:35 +0000 Subject: [PATCH 4/6] Adds export to QO-100 Dx Club menu entry and functionality --- application/controllers/Webadif.php | 156 ++++++++++++++++++ application/models/Logbook_model.php | 39 +++++ application/models/Stations.php | 29 ++++ application/views/interface_assets/footer.php | 35 ++-- application/views/interface_assets/header.php | 6 +- application/views/webadif/export.php | 93 +++++++++++ application/views/webadif/mark_webadif.php | 22 +++ assets/js/sections/webadif.js | 70 ++++++++ 8 files changed, 432 insertions(+), 18 deletions(-) create mode 100644 application/controllers/Webadif.php create mode 100644 application/views/webadif/export.php create mode 100644 application/views/webadif/mark_webadif.php create mode 100644 assets/js/sections/webadif.js diff --git a/application/controllers/Webadif.php b/application/controllers/Webadif.php new file mode 100644 index 00000000..608b67d6 --- /dev/null +++ b/application/controllers/Webadif.php @@ -0,0 +1,156 @@ +setOptions(); + + $this->load->model('logbook_model'); + + $station_ids = $this->logbook_model->get_station_id_with_webadif_api(); + + if ($station_ids) { + foreach ($station_ids as $station) { + $webadif_api_key = $station->webadifapikey; + $webadif_api_url = $station->webadifapiurl; + if ($this->mass_upload_qsos($station->station_id, $webadif_api_key, $webadif_api_url)) { + echo "QSOs have been uploaded to QO-100 Dx Club."; + log_message('info', 'QSOs have been uploaded to QO-100 Dx Club.'); + } else { + echo "No QSOs found for upload."; + log_message('info', 'No QSOs found for upload.'); + } + } + } else { + echo "No station profiles with a QO-100 Dx Club API Key found."; + log_message('error', "No station profiles with a QO-100 Dx Club API Key found."); + } + } + + function setOptions() { + $this->config->load('config'); + ini_set('memory_limit', '-1'); + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + } + + /* + * Function gets all QSOs from given station_id, that are not previously uploaded to webADIF consumer. + * Adif is build for each qso, and then uploaded, one at a time + */ + function mass_upload_qsos($station_id, $webadif_api_key, $webadif_api_url) { + $i = 0; + $data['qsos'] = $this->logbook_model->get_webadif_qsos($station_id); + $errormessages=array(); + + $CI =& get_instance(); + $CI->load->library('AdifHelper'); + + if ($data['qsos']) { + foreach ($data['qsos']->result() as $qso) { + $adif = $CI->adifhelper->getAdifLine($qso); + $result = $this->logbook_model->push_qso_to_webadif($webadif_api_url, $webadif_api_key, $adif); + + if ($result) { + $this->logbook_model->mark_webadif_qsos_sent($qso->COL_PRIMARY_KEY); + $i++; + } else { + $errorMessage = 'QO-100 Dx Club upload failed for qso: Call: ' . $qso->COL_CALL . ' Band: ' . $qso->COL_BAND . ' Mode: ' . $qso->COL_MODE . ' Time: ' . $qso->COL_TIME_ON; + log_message('error', $errorMessage); + $errormessages[] = $errorMessage; + } + } + $result=[]; + $result['status'] = 'OK'; + $result['count'] = $i; + $result['errormessages'] = $errormessages; + return $result; + } else { + $result=[]; + $result['status'] = 'Error'; + $result['count'] = $i; + $result['errormessages'] = $errormessages; + return $result; + } + } + + /* + * Used for displaying the uid for manually selecting log for upload to webADIF consumer + */ + public function export() { + $this->load->model('stations'); + + $data['page_title'] = "QO-100 Dx Club Upload"; + + $data['station_profiles'] = $this->stations->all_of_user(); + $data['station_profile'] = $this->stations->stations_with_webadif_api_key(); + + $this->load->view('interface_assets/header', $data); + $this->load->view('webadif/export'); + $this->load->view('interface_assets/footer'); + } + + /* + * Used for ajax-function when selecting log for upload to webADIF consumer + */ + public function upload_station() { + $this->setOptions(); + $this->load->model('stations'); + + $postData = $this->input->post(); + + $this->load->model('logbook_model'); + $result = $this->logbook_model->exists_webadif_api_key($postData['station_id']); + $webadif_api_key = $result->webadifapikey; + $webadif_api_url = $result->webadifapiurl; + header('Content-type: application/json'); + $result = $this->mass_upload_qsos($postData['station_id'], $webadif_api_key, $webadif_api_url); + if ($result['status'] == 'OK') { + $stationinfo = $this->stations->stations_with_webadif_api_key(); + $info = $stationinfo->result(); + + $data['status'] = 'OK'; + $data['info'] = $info; + $data['infomessage'] = $result['count'] . " QSOs are now uploaded to QO-100 Dx Club"; + $data['errormessages'] = $result['errormessages']; + echo json_encode($data); + } else { + $data['status'] = 'Error'; + $data['info'] = 'Error: No QSOs found to upload.'; + $data['errormessages'] = $result['errormessages']; + echo json_encode($data); + } + } + + public function mark_webadif() { + // Set memory limit to unlimited to allow heavy usage + ini_set('memory_limit', '-1'); + $data['page_title'] = "QO-100 Dx Club Upload"; + + $station_id = $this->security->xss_clean($this->input->post('station_profile')); + + $this->load->model('adif_data'); + + $data['qsos'] = $this->adif_data->export_custom($this->input->post('from'), $this->input->post('to'), $station_id); + + $this->load->model('logbook_model'); + if ($data['qsos']!==null) { + foreach ($data['qsos']->result() as $qso) { + $this->logbook_model->mark_webadif_qsos_sent($qso->COL_PRIMARY_KEY); + } + } + + $this->load->view('interface_assets/header', $data); + $this->load->view('webadif/mark_webadif', $data); + $this->load->view('interface_assets/footer'); + } +} diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 53299b8a..f4365922 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -1275,6 +1275,25 @@ class Logbook_model extends CI_Model { return $query; } + /* + * Function returns the QSOs from the logbook, which have not been either marked as uploaded to webADIF + */ + function get_webadif_qsos($station_id){ + $sql = " + SELECT qsos.*, station_profile.* FROM %s qsos + INNER JOIN station_profile ON qsos.station_id = station_profile.station_id + LEFT JOIN webadif ON qsos.COL_PRIMARY_KEY = webadif.qso_id + WHERE qsos.station_id = %d + AND webadif.upload_date IS NULL + "; + $sql = sprintf( + $sql, + $this->config->item('table_name'), + $station_id + ); + return $this->db->query($sql); + } + /* * Function returns all the station_id's with QRZ API Key's */ @@ -1294,6 +1313,26 @@ class Logbook_model extends CI_Model { } } + /* + * Function returns all the station_id's with QRZ API Key's + */ + function get_station_id_with_webadif_api() { + $sql = " + SELECT station_id, webadifapikey, webadifapiurl + FROM station_profile + WHERE COALESCE(webadifapikey, '') <> '' + AND COALESCE(webadifapiurl, '') <> '' + "; + + $query = $this->db->query($sql); + $result = $query->result(); + if ($result) { + return $result; + } else { + return null; + } + } + function get_last_qsos($num, $StationLocationsArray = null) { if($StationLocationsArray == null) { diff --git a/application/models/Stations.php b/application/models/Stations.php index 5abab0c7..03a689da 100644 --- a/application/models/Stations.php +++ b/application/models/Stations.php @@ -345,6 +345,35 @@ class Stations extends CI_Model { return $query; } + function stations_with_webadif_api_key() { + $sql=" + SELECT station_profile.station_id, station_profile.station_profile_name, station_profile.station_callsign, notc.c notcount, totc.c totcount + FROM station_profile + INNER JOIN ( + SELECT qsos.station_id, COUNT(qsos.COL_PRIMARY_KEY) c + FROM %s qsos + LEFT JOIN webadif ON qsos.COL_PRIMARY_KEY = webadif.qso_id + WHERE webadif.qso_id IS NULL + GROUP BY qsos.station_id + ) notc + INNER JOIN ( + SELECT qsos.station_id, COUNT(qsos.COL_PRIMARY_KEY) c + FROM %s qsos + GROUP BY qsos.station_id + ) totc + WHERE COALESCE(station_profile.webadifapikey, '') <> '' + AND COALESCE(station_profile.webadifapiurl, '') <> '' + AND station_profile.user_id = %d + "; + $sql=sprintf( + $sql, + $this->config->item('table_name'), + $this->config->item('table_name'), + $this->session->userdata('user_id') + ); + return $this->db->query($sql); + } + /* * Function: are_eqsl_nicks_defined * Description: Returns number of station profiles with eqslnicknames diff --git a/application/views/interface_assets/footer.php b/application/views/interface_assets/footer.php index c66bc8ad..cba5abaa 100644 --- a/application/views/interface_assets/footer.php +++ b/application/views/interface_assets/footer.php @@ -70,7 +70,7 @@ function load_was_map() { -uri->segment(1) == "adif" || $this->uri->segment(1) == "qrz") { ?> +uri->segment(1) == "adif" || $this->uri->segment(1) == "qrz" || $this->uri->segment(1) == "webadif") { ?> @@ -112,7 +112,7 @@ function load_was_map() { console.log("'clicked"); if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(showPosition); - } else { + } else { console.log('Geolocation is not supported by this browser.'); } } @@ -363,7 +363,7 @@ $(function () { $(".bootstrap-dialog-message").prepend('
×The stored query has been deleted!
'); $("#query_" + id).remove(); // removes query from table in dialog $("#querydropdown option[value='" + id + "']").remove(); // removes query from dropdown - if ($("#querydropdown option").length == 0) { + if ($("#querydropdown option").length == 0) { $("#btn-edit").remove(); $('.querydropdownform').remove(); }; @@ -549,7 +549,7 @@ function newpath(latlng1, latlng2, locator1, locator2) { var osmUrl='optionslib->get_option('option_map_tile_server');?>'; var osmAttrib='Map data © OpenStreetMap contributors'; - var osm = new L.TileLayer(osmUrl, {minZoom: 1, maxZoom: 9, attribution: osmAttrib}); + var osm = new L.TileLayer(osmUrl, {minZoom: 1, maxZoom: 9, attribution: osmAttrib}); var redIcon = L.icon({ iconUrl: icon_dot_url, @@ -602,7 +602,7 @@ function showActivatorsMap(call, count, grids) { var osmUrl='optionslib->get_option('option_map_tile_server');?>'; var osmAttrib='Map data © OpenStreetMap contributors'; - var osm = new L.TileLayer(osmUrl, {minZoom: 1, maxZoom: 9, attribution: osmAttrib}); + var osm = new L.TileLayer(osmUrl, {minZoom: 1, maxZoom: 9, attribution: osmAttrib}); map.addLayer(osm); } @@ -730,13 +730,13 @@ function showActivatorsMap(call, count, grids) { }); $(function () { - // hold onto the drop down menu + // hold onto the drop down menu var dropdownMenu; - // and when you show it, move it to the body + // and when you show it, move it to the body $(window).on('show.bs.dropdown', function (e) { - // grab the menu + // grab the menu dropdownMenu = $(e.target).find('.dropdown-menu'); // detach it and append it to the body @@ -753,7 +753,7 @@ function showActivatorsMap(call, count, grids) { }); }); - // and when you hide it, reattach the drop down, and hide it normally + // and when you hide it, reattach the drop down, and hide it normally $(window).on('hide.bs.dropdown', function (e) { $(e.target).append(dropdownMenu.detach()); dropdownMenu.hide(); @@ -882,7 +882,7 @@ $(document).on('keypress',function(e) { uri->segment(1) == "qso") { ?> -load->model('stations'); $active_station_id = $this->stations->find_active(); @@ -1581,6 +1581,9 @@ $(document).ready(function(){ uri->segment(1) == "qrz") { ?> + uri->segment(1) == "webadif") { ?> + + -uri->segment(1) == "qsl") { +uri->segment(1) == "qsl") { // Get Date format if($this->session->userdata('user_date_format')) { // If Logged in and session exists @@ -2050,7 +2053,7 @@ $(document).ready(function(){ case 'M d, Y': $usethisformat = 'MMM D, YYYY';break; case 'M d, y': $usethisformat = 'MMM D, YY';break; } - + ?> @@ -2066,8 +2069,8 @@ $(document).ready(function(){ "scrollX": true, "order": [ 2, 'desc' ], }); - - + + diff --git a/application/views/interface_assets/header.php b/application/views/interface_assets/header.php index 4cb69d2d..23c4fcf4 100644 --- a/application/views/interface_assets/header.php +++ b/application/views/interface_assets/header.php @@ -39,7 +39,7 @@ uri->segment(1) == "adif" || (isset($hasDatePicker) && $hasDatePicker)) { ?> - + '; } ?> @@ -214,7 +214,7 @@ session->userdata('user_id'); ?>" title="Account"> Account Station Logbooks - + Station Locations Bands @@ -256,6 +256,8 @@ $oqrs_requests = $CI->oqrs_model->oqrs_requests($location_list); QRZ Logbook + QO-100 Dx Club Upload + API Keys diff --git a/application/views/webadif/export.php b/application/views/webadif/export.php new file mode 100644 index 00000000..2c07640e --- /dev/null +++ b/application/views/webadif/export.php @@ -0,0 +1,93 @@ + +
+ +

+ +
+
+ + +
+ +
+
+
+

Here you can see and upload all QSOs which have not been previously uploaded to QO-100 Dx Club.

+

You need to set a QO-100 Dx Club API key in your station profile. Only station profiles with an API Key are displayed.

+

WarningThis might take a while as QSO uploads are processed sequentially.

+ +result()) { + echo ' + + + + + + + + + + + '; + foreach ($station_profile->result() as $station) { // Fills the table with the data + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
Profile nameStation callsignTotal QSOs not uploadedTotal QSOs uploadedActions
' . $station->station_profile_name . '' . $station->station_callsign . '' . $station->notcount . '' . $station->totcount . '
'; + + } + else { + echo ''; + } + ?> + +
+
+ +
+ +

Warning If a date range is not selected then all QSOs will be marked!

+

From date:

+
+
+ +
+
+
+
+
+

To date:

+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
diff --git a/application/views/webadif/mark_webadif.php b/application/views/webadif/mark_webadif.php new file mode 100644 index 00000000..8cdd6c0d --- /dev/null +++ b/application/views/webadif/mark_webadif.php @@ -0,0 +1,22 @@ +
+
+ session->flashdata('message')) { ?> + +
+

session->flashdata('message'); ?>

+
+ + +
+
+ QSOs marked +
+
+

Yay, it's done!

+

The QSOs are marked as exported to QO-100 Dx Club.

+
+
+ + +
+ diff --git a/assets/js/sections/webadif.js b/assets/js/sections/webadif.js new file mode 100644 index 00000000..2687d11d --- /dev/null +++ b/assets/js/sections/webadif.js @@ -0,0 +1,70 @@ +$(function () { + $('#datetimepicker5').datetimepicker({ + format: 'DD/MM/YYYY', + }); +}); + +$(function () { + $('#datetimepicker6').datetimepicker({ + format: 'DD/MM/YYYY', + }); +}); + +$(document).ready(function(){ + $('#markWebAdifAsExported').click(function(e){ + let form = $(this).closest('form'); + let station = form.find('select[name=station_profile]'); + if (station.val() == 0) { + station.addClass('is-invalid'); + }else{ + form.submit(); + } + }) +}); + +function ExportWebADIF(station_id) { + if ($(".alert").length > 0) { + $(".alert").remove(); + } + if ($(".errormessages").length > 0) { + $(".errormessages").remove(); + } + $(".ld-ext-right").addClass('running'); + $(".ld-ext-right").prop('disabled', true); + + $.ajax({ + url: base_url + 'index.php/webadif/upload_station', + type: 'post', + data: {'station_id': station_id}, + success: function (data) { + $(".ld-ext-right").removeClass('running'); + $(".ld-ext-right").prop('disabled', false); + if (data.status == 'OK') { + $.each(data.info, function(index, value){ + $('#notcount'+value.station_id).html(value.notcount); + $('#totcount'+value.station_id).html(value.totcount); + }); + $(".card-body").append(''); + } + else { + $(".card-body").append(''); + } + + if (data.errormessages.length > 0) { + $(".card-body").append('' + + '

\n' + + ' \n' + + '

\n' + + '
\n' + + '
\n' + + '
\n' + + '
'); + $.each(data.errormessages, function(index, value) { + $(".errors").append('
  • ' + value); + }); + } + } + }); +} From 18b14171722ef4e9c8b21b60598289d8d947e7f1 Mon Sep 17 00:00:00 2001 From: Hugo Silva Date: Sun, 19 Mar 2023 10:49:58 +0000 Subject: [PATCH 5/6] Bugfix selecting stations with pending QSOs --- application/models/Stations.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/models/Stations.php b/application/models/Stations.php index 03a689da..2bf0c938 100644 --- a/application/models/Stations.php +++ b/application/models/Stations.php @@ -355,12 +355,12 @@ class Stations extends CI_Model { LEFT JOIN webadif ON qsos.COL_PRIMARY_KEY = webadif.qso_id WHERE webadif.qso_id IS NULL GROUP BY qsos.station_id - ) notc + ) notc ON station_profile.station_id = notc.station_id INNER JOIN ( SELECT qsos.station_id, COUNT(qsos.COL_PRIMARY_KEY) c FROM %s qsos GROUP BY qsos.station_id - ) totc + ) totc ON station_profile.station_id = totc.station_id WHERE COALESCE(station_profile.webadifapikey, '') <> '' AND COALESCE(station_profile.webadifapiurl, '') <> '' AND station_profile.user_id = %d From f4ea93ba89c76063941015183977903de8297fc1 Mon Sep 17 00:00:00 2001 From: Hugo Silva Date: Mon, 20 Mar 2023 13:39:35 +0000 Subject: [PATCH 6/6] Bugfix: don't duplicate 'Mark as Uploaded' records --- application/controllers/Webadif.php | 15 ++++++++++----- application/models/Logbook_model.php | 26 ++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/application/controllers/Webadif.php b/application/controllers/Webadif.php index 608b67d6..76650b0d 100644 --- a/application/controllers/Webadif.php +++ b/application/controllers/Webadif.php @@ -91,7 +91,7 @@ class Webadif extends CI_Controller { $data['page_title'] = "QO-100 Dx Club Upload"; - $data['station_profiles'] = $this->stations->all_of_user(); + $data['station_profiles'] = $this->stations->stations_with_webadif_api_key(); $data['station_profile'] = $this->stations->stations_with_webadif_api_key(); $this->load->view('interface_assets/header', $data); @@ -137,12 +137,17 @@ class Webadif extends CI_Controller { $data['page_title'] = "QO-100 Dx Club Upload"; $station_id = $this->security->xss_clean($this->input->post('station_profile')); - - $this->load->model('adif_data'); - - $data['qsos'] = $this->adif_data->export_custom($this->input->post('from'), $this->input->post('to'), $station_id); + $from = $this->security->xss_clean($this->input->post('from')); + $to = $this->security->xss_clean($this->input->post('to')); $this->load->model('logbook_model'); + + $data['qsos'] = $this->logbook_model->get_webadif_qsos( + $station_id, + $from, + $to + ); + if ($data['qsos']!==null) { foreach ($data['qsos']->result() as $qso) { $this->logbook_model->mark_webadif_qsos_sent($qso->COL_PRIMARY_KEY); diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index f4365922..226e4895 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -1278,9 +1278,10 @@ class Logbook_model extends CI_Model { /* * Function returns the QSOs from the logbook, which have not been either marked as uploaded to webADIF */ - function get_webadif_qsos($station_id){ + function get_webadif_qsos($station_id,$from, $to){ $sql = " - SELECT qsos.*, station_profile.* FROM %s qsos + SELECT qsos.*, station_profile.* + FROM %s qsos INNER JOIN station_profile ON qsos.station_id = station_profile.station_id LEFT JOIN webadif ON qsos.COL_PRIMARY_KEY = webadif.qso_id WHERE qsos.station_id = %d @@ -1291,6 +1292,27 @@ class Logbook_model extends CI_Model { $this->config->item('table_name'), $station_id ); + if ($from) { + $from = DateTime::createFromFormat('d/m/Y', $from); + $from = $from->format('Y-m-d'); + + $sql.=" AND qsos.COL_TIME_ON >= %s"; + $sql=sprintf( + $sql, + $this->db->escape($from) + ); + } + if ($to) { + $to = DateTime::createFromFormat('d/m/Y', $to); + $to = $to->format('Y-m-d'); + + $sql.=" AND qsos.COL_TIME_ON <= %s"; + $sql=sprintf( + $sql, + $this->db->escape($to) + ); + } + return $this->db->query($sql); }