diff --git a/.htaccess.sample b/.htaccess.sample index 57dfd4433..ca6b92156 100644 --- a/.htaccess.sample +++ b/.htaccess.sample @@ -3,6 +3,10 @@ # $config['index_page'] = ''; RewriteEngine On + +RewriteCond %{REQUEST_URI} ^/backup/$ +RewriteRule ^(.*)$ /index.php?/$1 [L] + RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d -RewriteRule ^(.*)$ /index.php?/$1 [L] \ No newline at end of file +RewriteRule ^(.*)$ /index.php?/$1 [L] diff --git a/application/config/config.sample.php b/application/config/config.sample.php index 1d5feb3e3..062543acc 100644 --- a/application/config/config.sample.php +++ b/application/config/config.sample.php @@ -14,7 +14,7 @@ */ $config['app_name'] = "Cloudlog"; -$config['app_version'] = "2.3.3"; +$config['app_version'] = "2.4.1"; $config['directory'] = "logbook"; $config['callbook'] = "hamqth"; // Options are hamqth or qrz diff --git a/application/config/lotw.php b/application/config/lotw.php index 650b53d97..36274f405 100644 --- a/application/config/lotw.php +++ b/application/config/lotw.php @@ -9,7 +9,7 @@ /* |-------------------------------------------------------------------------- -| Folder location for storing P12 certficiate files on the system +| Folder location for storing P12 certificate files on the system |-------------------------------------------------------------------------- | | This folder must be outside of your www root for security reasons diff --git a/application/config/migration.php b/application/config/migration.php index 1c0ef7a12..595bcece7 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -21,7 +21,7 @@ | be upgraded / downgraded to. | */ -$config['migration_version'] = 114; +$config['migration_version'] = 116; /* |-------------------------------------------------------------------------- diff --git a/application/controllers/Api.php b/application/controllers/Api.php index 0d6c3e519..51d78c167 100644 --- a/application/controllers/Api.php +++ b/application/controllers/Api.php @@ -221,6 +221,8 @@ function search() $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); } + $this->api_model->update_last_used($obj['key']); + // Retrieve the arguments from the query string $data['data']['format'] = $arguments['format']; @@ -295,6 +297,8 @@ function api_search($arguments){ $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); } + $this->api_model->update_last_used($obj['key']); + // Retrieve the arguments from the query string $data['data']['format'] = $arguments['format']; @@ -432,6 +436,7 @@ function qso() { die(); } + $this->api_model->update_last_used($obj['key']); if($obj['type'] == "adif" && $obj['string'] != "") { // Load the logbook model for adding QSO records @@ -466,6 +471,152 @@ function qso() { } + // API function to check if a callsign is in the logbook already + function logbook_check_callsign() { + header('Content-type: application/json'); + + $this->load->model('api_model'); + + // Decode JSON and store + $obj = json_decode(file_get_contents("php://input"), true); + if ($obj === NULL) { + echo json_encode(['status' => 'failed', 'reason' => "wrong JSON"]); + } + + if(!isset($obj['key']) || $this->api_model->authorize($obj['key']) == 0) { + http_response_code(401); + echo json_encode(['status' => 'failed', 'reason' => "missing api key"]); + } + + if($obj['logbook_public_slug'] != "" && $obj['callsign'] != "") { + + $logbook_slug = $obj['logbook_public_slug']; + $callsign = $obj['callsign']; + + // If $obj['band'] exists + if(isset($obj['band'])) { + $band = $obj['band']; + } else { + $band = null; + } + + $this->load->model('logbooks_model'); + + if($this->logbooks_model->public_slug_exists($logbook_slug)) { + $logbook_id = $this->logbooks_model->public_slug_exists_logbook_id($logbook_slug); + if($logbook_id != false) + { + // Get associated station locations for mysql queries + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($logbook_id); + + if (!$logbooks_locations_array) { + // Logbook not found + http_response_code(404); + echo json_encode(['status' => 'failed', 'reason' => "Empty Logbook"]); + die(); + } + } else { + // Logbook not found + http_response_code(404); + echo json_encode(['status' => 'failed', 'reason' => $logbook_slug." has no associated station locations"]); + die(); + } + // Search Logbook for callsign + $this->load->model('logbook_model'); + + $result = $this->logbook_model->check_if_callsign_worked_in_logbook($callsign, $logbooks_locations_array, $band); + + http_response_code(201); + if($result > 0) + { + echo json_encode(['callsign' => $callsign, 'result' => 'Found']); + } else { + echo json_encode(['callsign' => $callsign, 'result' => 'Not Found']); + } + } else { + // Logbook not found + http_response_code(404); + echo json_encode(['status' => 'failed', 'reason' => "logbook not found"]); + die(); + } + + } + + } + + // API function to check if a grid is in the logbook already + function logbook_check_grid() { + header('Content-type: application/json'); + + $this->load->model('api_model'); + + // Decode JSON and store + $obj = json_decode(file_get_contents("php://input"), true); + if ($obj === NULL) { + echo json_encode(['status' => 'failed', 'reason' => "wrong JSON"]); + } + + if(!isset($obj['key']) || $this->api_model->authorize($obj['key']) == 0) { + http_response_code(401); + echo json_encode(['status' => 'failed', 'reason' => "missing api key"]); + } + + if($obj['logbook_public_slug'] != "" && $obj['grid'] != "") { + + $logbook_slug = $obj['logbook_public_slug']; + $grid = $obj['grid']; + + // If $obj['band'] exists + if(isset($obj['band'])) { + $band = $obj['band']; + } else { + $band = null; + } + + $this->load->model('logbooks_model'); + + if($this->logbooks_model->public_slug_exists($logbook_slug)) { + $logbook_id = $this->logbooks_model->public_slug_exists_logbook_id($logbook_slug); + if($logbook_id != false) + { + // Get associated station locations for mysql queries + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($logbook_id); + + if (!$logbooks_locations_array) { + // Logbook not found + http_response_code(404); + echo json_encode(['status' => 'failed', 'reason' => "Empty Logbook"]); + die(); + } + } else { + // Logbook not found + http_response_code(404); + echo json_encode(['status' => 'failed', 'reason' => $logbook_slug." has no associated station locations"]); + die(); + } + // Search Logbook for callsign + $this->load->model('logbook_model'); + + $result = $this->logbook_model->check_if_grid_worked_in_logbook($grid, $logbooks_locations_array, $band); + + http_response_code(201); + if($result > 0) + { + echo json_encode(['gridsquare' => strtoupper($grid), 'result' => 'Found']); + } else { + echo json_encode(['gridsquare' => strtoupper($grid), 'result' => 'Not Found']); + } + } else { + // Logbook not found + http_response_code(404); + echo json_encode(['status' => 'failed', 'reason' => "logbook not found"]); + die(); + } + + } + + } + function country_worked($dxcc_num, $band, $mode = NULL) { $this->load->model('api_model'); @@ -500,6 +651,8 @@ function radio() { die(); } + $this->api_model->update_last_used($obj['key']); + $user_id = $this->api_model->key_userid($obj['key']); // Store Result to Database @@ -688,4 +841,4 @@ function qralatlng($qra) { $latlng = $this->qra->qra2latlong($qra); return $latlng; } -} +} \ No newline at end of file diff --git a/application/controllers/Lotw.php b/application/controllers/Lotw.php index 33eee1ee3..e4a6debbe 100644 --- a/application/controllers/Lotw.php +++ b/application/controllers/Lotw.php @@ -153,23 +153,23 @@ public function do_cert_upload() } // Check to see if certificate is already in the system - $new_certficiate = $this->LotwCert->find_cert($info['issued_callsign'], $dxcc, $this->session->userdata('user_id')); + $new_certificate = $this->LotwCert->find_cert($info['issued_callsign'], $dxcc, $this->session->userdata('user_id')); - if($new_certficiate == 0) { + if($new_certificate == 0) { // New Certificate Store in Database // Store Certificate Data into MySQL $this->LotwCert->store_certificate($this->session->userdata('user_id'), $info['issued_callsign'], $dxcc, $info['validFrom'], $info['validTo_Date'], $info['qso-first-date'], $info['qso-end-date'], $info['pem_key'], $info['general_cert']); // Cert success flash message - $this->session->set_flashdata('Success', $info['issued_callsign'].' Certficiate Imported.'); + $this->session->set_flashdata('Success', $info['issued_callsign'].' Certificate Imported.'); } else { - // Certficiate is in the system time to update + // Certificate is in the system time to update - $this->LotwCert->update_certficiate($this->session->userdata('user_id'), $info['issued_callsign'], $dxcc, $info['validFrom'], $info['validTo_Date'], $info['pem_key'], $info['general_cert']); + $this->LotwCert->update_certificate($this->session->userdata('user_id'), $info['issued_callsign'], $dxcc, $info['validFrom'], $info['validTo_Date'], $info['pem_key'], $info['general_cert']); // Cert success flash message - $this->session->set_flashdata('Success', $info['issued_callsign'].' Certficiate Updated.'); + $this->session->set_flashdata('Success', $info['issued_callsign'].' Certificate Updated.'); } @@ -378,9 +378,9 @@ public function delete_cert($cert_id) { $this->load->model('LotwCert'); - $this->LotwCert->delete_certficiate($this->session->userdata('user_id'), $cert_id); + $this->LotwCert->delete_certificate($this->session->userdata('user_id'), $cert_id); - $this->session->set_flashdata('Success', 'Certficiate Deleted.'); + $this->session->set_flashdata('Success', 'Certificate Deleted.'); redirect('/lotw/'); } diff --git a/application/controllers/Timeline.php b/application/controllers/Timeline.php index 61a3fb2e4..0cc5e3fa6 100644 --- a/application/controllers/Timeline.php +++ b/application/controllers/Timeline.php @@ -76,6 +76,7 @@ public function index() } public function details() { + $this->load->model('logbook_model'); $this->load->model('timeline_model'); $querystring = str_replace('"', "", $this->input->post("Querystring")); diff --git a/application/controllers/Webadif.php b/application/controllers/Webadif.php new file mode 100644 index 000000000..d5fcb1831 --- /dev/null +++ b/application/controllers/Webadif.php @@ -0,0 +1,168 @@ +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->stations_with_webadif_api_key(); + $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')); + $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) { + $qsoIDs=[]; + foreach ($data['qsos']->result() as $qso) { + $qsoIDs[]=$qso->COL_PRIMARY_KEY; + } + $batchSize = 500; + while ($qsoIDs !== []) { + $slice = array_slice($qsoIDs, 0, $batchSize); + $qsoIDs = array_slice($qsoIDs, $batchSize); + $this->logbook_model->mark_webadif_qsos_sent($slice); + } + } + + $this->load->view('interface_assets/header', $data); + $this->load->view('webadif/mark_webadif', $data); + $this->load->view('interface_assets/footer'); + } +} diff --git a/application/language/french/account_lang.php b/application/language/french/account_lang.php index a9da9722b..aa0d7298c 100644 --- a/application/language/french/account_lang.php +++ b/application/language/french/account_lang.php @@ -1,4 +1,4 @@ -?php + public function feed($input_data) //allows the parser to be fed a string { - $this->data = $input_data; + + if (strpos($input_data, "") !== false) { + $arr=explode("",$input_data); + $newstring = $arr[1]; + $this->data = $newstring; + } else { + $this->data = $input_data; + } + $this->datasplit = preg_split("//i", mb_substr($this->data, $this->i, NULL, "UTF-8")); } diff --git a/application/migrations/102_add_version_two_trigger_to_options.php b/application/migrations/102_add_version_two_trigger_to_options.php index 3b5307d97..e0dac7383 100644 --- a/application/migrations/102_add_version_two_trigger_to_options.php +++ b/application/migrations/102_add_version_two_trigger_to_options.php @@ -13,9 +13,12 @@ public function up() { $data = array( array('option_name' => "version2_trigger", 'option_value' => "false", 'autoload' => "yes"), - ); + ); - $this->db->insert_batch('options', $data); + $query = $this->db->select('option_name')->where('option_name', 'version2_trigger')->get('options'); + if($query->num_rows() == 0) { + $this->db->insert_batch('options', $data); + } } public function down() diff --git a/application/migrations/105_create_dxcc_master_tables.php b/application/migrations/105_create_dxcc_master_tables.php index 58aae898b..f66a9f0ab 100644 --- a/application/migrations/105_create_dxcc_master_tables.php +++ b/application/migrations/105_create_dxcc_master_tables.php @@ -4,6 +4,7 @@ class Migration_create_dxcc_master_tables extends CI_Migration { public function up() { + if (!$this->db->table_exists('dxcc_master')) { $this->db->query("CREATE TABLE `dxcc_master` ( `DXCCPrefix` varchar(6) DEFAULT NULL, `DXCCSearch` varchar(6) DEFAULT NULL, @@ -1041,6 +1042,7 @@ public function up() $this->db->query("INSERT INTO `dxcc_master` (DXCCPrefix,DXCCSearch,DXCCMap,DXCCSort,CountryCode,PrefixList,DXCCName,Location,Continent,CQZone,ITUZone,IOTA,TimeZone,Latitude,Longitude,StartDate,EndDate) VALUES ('YB','YB','YB',17,327,'YB9[L-P],YB9Z[L-P],YC9[L-P],YC9Z[L-P],YD9[L-P],YD9Z[L-P],YE9[L-P],YE9Z[L-P],YF9[L-P],YF9Z[L-P],YG9[L-P],YG9Z[L-P],YH9[L-P],YH9Z[L-P]','Indonesia','East Nusatenggara','OC','28','54',NULL,-8.0,-8.67,121.5,NULL,NULL);"); $this->db->query("INSERT INTO `dxcc_master` (DXCCPrefix,DXCCSearch,DXCCMap,DXCCSort,CountryCode,PrefixList,DXCCName,Location,Continent,CQZone,ITUZone,IOTA,TimeZone,Latitude,Longitude,StartDate,EndDate) VALUES ('CE9','CE9','CE9',29,13,'RI1ANZ','Antarctica','Progress Station (Russia)','AN','39','69','AN-016',-6.0,-69.397,76.373,NULL,NULL);"); $this->db->query("INSERT INTO `dxcc_master` (DXCCPrefix,DXCCSearch,DXCCMap,DXCCSort,CountryCode,PrefixList,DXCCName,Location,Continent,CQZone,ITUZone,IOTA,TimeZone,Latitude,Longitude,StartDate,EndDate) VALUES ('UA0','UA0','UA',1,15,'UA0','Asiatic Russia','Asiatic Russia','AS',NULL,NULL,NULL,NULL,64.0,130.0,NULL,NULL);"); + } } public function down(){ 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 000000000..22e29007f --- /dev/null +++ b/application/migrations/115_add_webadif_api_export.php @@ -0,0 +1,58 @@ +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); + } + + + 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'); + } + + } + + 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_table('webadif'); + } +} diff --git a/application/migrations/116_add_timestamp_to_api.php b/application/migrations/116_add_timestamp_to_api.php new file mode 100644 index 000000000..c13a21066 --- /dev/null +++ b/application/migrations/116_add_timestamp_to_api.php @@ -0,0 +1,21 @@ +db->field_exists('last_used', 'api')) { + $this->dbforge->add_column('api', $fields); + } + } + + public function down() + { + $this->dbforge->drop_column('api', 'last_used'); + } +} diff --git a/application/models/Api_model.php b/application/models/Api_model.php index 76ede10a0..f65b55012 100644 --- a/application/models/Api_model.php +++ b/application/models/Api_model.php @@ -175,6 +175,12 @@ function authorize($key) { } } + function update_last_used($key) { + $this->db->set('last_used', 'NOW()', FALSE); + $this->db->where('key', xss_clean($key)); + $this->db->update('api'); + } + // FUNCTION: string name(string $column) // Converts a MySQL column name to a more friendly name function name($col) diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 14fdc0639..74b779bc8 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -481,6 +481,25 @@ function add_qso($data, $skipexport = false) { $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 @@ function exists_qrz_api_key($station_id) { } } + /* + * 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 @@ function push_qso_to_qrz($apikey, $adif, $replaceoption = false) { 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 @@ function mark_qrz_qsos_sent($primarykey) { return true; } + /* + * Function marks QSOs as uploaded to WebADIF. + * $qsoIDs is an arroy of unique id for the QSOs in the logbook + */ + function mark_webadif_qsos_sent(array $qsoIDs) + { + $data = []; + $now = date("Y-m-d H:i:s", strtotime("now")); + foreach ($qsoIDs as $qsoID) { + $data[] = [ + 'upload_date' => $now, + 'qso_id' => $qsoID, + ]; + } + $this->db->insert_batch('webadif', $data); + return true; + } + function upload_amsat_status($data) { $sat_name = ''; if ($data['COL_SAT_NAME'] == 'AO-7') { @@ -1038,34 +1124,34 @@ function qso_info($id) { 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); } @@ -1191,6 +1277,48 @@ function get_qrz_qsos($station_id){ 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,$from = null, $to = null){ + $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 qsos.COL_SAT_NAME = 'QO-100' + AND webadif.upload_date IS NULL + "; + $sql = sprintf( + $sql, + $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); + } + /* * Function returns all the station_id's with QRZ API Key's */ @@ -1210,6 +1338,26 @@ function get_station_id_with_qrz_api() { } } + /* + * 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) { @@ -1219,10 +1367,10 @@ function get_last_qsos($num, $StationLocationsArray = null) { } 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 +1381,67 @@ function get_last_qsos($num, $StationLocationsArray = null) { order by col_time_on desc, col_primary_key desc"; $query = $this->db->query($sql); - + return $query; } else { return null; } - + + } + + function check_if_callsign_worked_in_logbook($callsign, $StationLocationsArray = null, $band = 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')); + } else { + $logbooks_locations_array = $StationLocationsArray; + } + + $this->db->select('COL_CALL'); + $this->db->where_in('station_id', $logbooks_locations_array); + $this->db->where('COL_CALL', $callsign); + + if($band != null && $band != 'SAT') { + $this->db->where('COL_BAND', $band); + } else if($band == 'SAT') { + // Where col_sat_name is not empty + $this->db->where('COL_SAT_NAME !=', ''); + } + $this->db->limit('2'); + $query = $this->db->get($this->config->item('table_name')); + + return $query->num_rows(); + + } + + function check_if_grid_worked_in_logbook($grid, $StationLocationsArray = null, $band = 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')); + } else { + $logbooks_locations_array = $StationLocationsArray; + } + + $this->db->select('COL_GRIDSQUARE'); + $this->db->where_in('station_id', $logbooks_locations_array); + $this->db->like('COL_GRIDSQUARE', $grid); + + if($band != null && $band != 'SAT') { + $this->db->where('COL_BAND', $band); + } else if($band == 'SAT') { + // Where col_sat_name is not empty + $this->db->where('COL_SAT_NAME !=', ''); + } + $this->db->limit('2'); + + $query = $this->db->get($this->config->item('table_name')); + + return $query->num_rows(); + } /* Get all QSOs with a valid grid for use in the KML export */ @@ -1390,7 +1593,7 @@ function todays_qsos($StationLocationsArray = null) { } else { return null; } - + } /* Return QSOs over a period of days */ @@ -1465,7 +1668,7 @@ function map_day($date) { // 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 +1820,17 @@ function total_continents($searchCriteria) { 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 +1949,7 @@ function total_bands() { } function get_QSLStats($StationLocationsArray = null) { - + if($StationLocationsArray == null) { $CI =& get_instance(); $CI->load->model('logbooks_model'); @@ -1872,7 +2075,7 @@ function total_qsl_rcvd() { $query = $this->db->get($this->config->item('table_name')); $row = $query->row(); - + if($row == null) { return 0; } else { @@ -1995,7 +2198,7 @@ function total_countries() { $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 +2231,7 @@ function total_countries_current($StationLocationsArray = null) { /* 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 +2243,7 @@ function total_countries_confirmed($StationLocationsArray = null) { 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'); @@ -2341,14 +2544,24 @@ function eqsl_dupe_check($datetime, $callsign, $band, $qsl_status) { // Show all QSOs we need to send to eQSL function eqsl_not_yet_sent() { + $CI =& get_instance(); + $CI->load->model('logbooks_model'); + $logbooks_locations_array = $CI->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + $this->db->select('station_profile.*, '.$this->config->item('table_name').'.COL_PRIMARY_KEY, '.$this->config->item('table_name').'.COL_TIME_ON, '.$this->config->item('table_name').'.COL_CALL, '.$this->config->item('table_name').'.COL_MODE, '.$this->config->item('table_name').'.COL_SUBMODE, '.$this->config->item('table_name').'.COL_BAND, '.$this->config->item('table_name').'.COL_COMMENT, '.$this->config->item('table_name').'.COL_RST_SENT, '.$this->config->item('table_name').'.COL_PROP_MODE, '.$this->config->item('table_name').'.COL_SAT_NAME, '.$this->config->item('table_name').'.COL_SAT_MODE, '.$this->config->item('table_name').'.COL_QSLMSG'); $this->db->from('station_profile'); - $this->db->join($this->config->item('table_name'),'station_profile.station_id = '.$this->config->item('table_name').'.station_id AND station_profile.eqslqthnickname != ""','right'); - $this->db->where('station_profile.eqslqthnickname !=', ''); + $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->where($this->config->item('table_name').'.COL_EQSL_QSL_SENT !=', 'Y'); - $this->db->where($this->config->item('table_name').'.COL_EQSL_QSL_SENT !=', 'I'); - $this->db->or_where(array($this->config->item('table_name').'.COL_EQSL_QSL_SENT' => NULL)); + $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'); + $this->db->or_where($this->config->item('table_name').'.COL_EQSL_QSL_SENT', 'Q'); + $this->db->or_where($this->config->item('table_name').'.COL_EQSL_QSL_SENT', 'N'); + $this->db->group_end(); + $this->db->where_in('station_profile.station_id', $logbooks_locations_array); + return $this->db->get(); } @@ -2853,11 +3066,11 @@ function import($record, $station_id = "0", $skipDuplicate = false, $markLotw = '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, @@ -2966,14 +3179,14 @@ public function check_dxcc_table($call, $date){ $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; } @@ -3047,7 +3260,7 @@ public function dxcc_lookup($call, $date){ $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"; @@ -3057,14 +3270,14 @@ public function dxcc_lookup($call, $date){ $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; } @@ -3115,38 +3328,38 @@ function wpx($testcall, $i) { $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 @@ -3162,32 +3375,32 @@ function wpx($testcall, $i) { $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 @@ -3199,12 +3412,12 @@ function wpx($testcall, $i) { # 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 @@ -3236,7 +3449,7 @@ function wpx($testcall, $i) { # 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/LotwCert.php b/application/models/LotwCert.php index 238b34491..185e3fa25 100644 --- a/application/models/LotwCert.php +++ b/application/models/LotwCert.php @@ -52,7 +52,7 @@ function store_certificate($user_id, $callsign, $dxcc, $date_created, $date_expi $this->db->insert('lotw_certs', $data); } - function update_certficiate($user_id, $callsign, $dxcc, $date_created, $date_expires, $cert_key, $general_cert) { + function update_certificate($user_id, $callsign, $dxcc, $date_created, $date_expires, $cert_key, $general_cert) { $data = array( 'cert_dxcc' => $dxcc, 'date_created' => $date_created, @@ -67,7 +67,7 @@ function update_certficiate($user_id, $callsign, $dxcc, $date_created, $date_exp $this->db->update('lotw_certs', $data); } - function delete_certficiate($user_id, $lotw_cert_id) { + function delete_certificate($user_id, $lotw_cert_id) { $this->db->where('lotw_cert_id', $lotw_cert_id); $this->db->where('user_id', $user_id); $this->db->delete('lotw_certs'); diff --git a/application/models/Stations.php b/application/models/Stations.php index 1d6b9a967..4cab378be 100644 --- a/application/models/Stations.php +++ b/application/models/Stations.php @@ -81,10 +81,13 @@ function add() { '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 @@ function edit() { '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 @@ function delete($id) { $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 @@ function claim_user($id) { $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 @@ function ClaimAllStationLocations($id = NULL) { $data = array( 'user_id' => $id, ); - + $this->db->update('station_profile', $data); } @@ -192,7 +198,7 @@ function set_active($current, $new) { $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 @@ public function find_active() { 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 @@ public function reassign($id) { ); $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 @@ function profile_exists() { return 1; } else { return 0; - } + } } function stations_with_qrz_api_key() { @@ -339,6 +345,36 @@ function stations_with_qrz_api_key() { 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 AND qsos.COL_SAT_NAME = 'QO-100' + GROUP BY qsos.station_id + ) notc ON station_profile.station_id = notc.station_id + INNER JOIN ( + SELECT qsos.station_id, COUNT(qsos.COL_PRIMARY_KEY) c + FROM %s qsos + WHERE qsos.COL_SAT_NAME = 'QO-100' + GROUP BY qsos.station_id + ) 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 + "; + $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/api/help.php b/application/views/api/help.php index 5ec2aa9e5..de10141b9 100644 --- a/application/views/api/help.php +++ b/application/views/api/help.php @@ -16,6 +16,7 @@

The Cloudlog API (Application Programming Interface) lets third party systems access Cloudlog in a controlled way. Access to the API is managed via API keys.

You will need to generate an API key for each tool you wish to use (e.g. CloudlogCAT). Generate a read-write key if the application needs to send data to Cloudlog. Generate a read-only key if the application only needs to obtain data from Cloudlog.

+

API URL The API URL for this Cloudlog instance is:

Info It's good practice to delete a key if you are no longer using the associated application.

num_rows() > 0) { ?> @@ -25,6 +26,7 @@ API Key Description + Last Used Rights Status @@ -34,6 +36,7 @@ key; ?> description; ?> + last_used; ?> assets/js/bootstrap.bundle.js"> + uri->segment(1) == "activators") { ?> @@ -70,7 +71,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 +113,7 @@ function getLocation() { console.log("'clicked"); if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(showPosition); - } else { + } else { console.log('Geolocation is not supported by this browser.'); } } @@ -158,6 +159,16 @@ function copyApiKey(apiKey) { }); } +function copyApiUrl() { + var apiUrlField = $('#apiUrl'); + navigator.clipboard.writeText("").then(function() { + }); + apiUrlField.addClass('flash-copy') + .delay('1000').queue(function() { + apiUrlField.removeClass('flash-copy').dequeue(); + }); +} + $(function () { $('[data-toggle="tooltip"]').tooltip({'delay': { show: 500, hide: 0 }, 'placement': 'right'}); }); @@ -363,7 +374,7 @@ function delete_stored_query(id) { $(".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(); }; @@ -533,13 +544,29 @@ function edit_stored_query_dialog() { function newpath(latlng1, latlng2, locator1, locator2) { // If map is already initialized - var container = L.DomUtil.get('mapqrb'); + var container = L.DomUtil.get('mapqrbcontainer'); if(container != null){ container._leaflet_id = null; + container.remove(); + $("#mapqrb").append('
'); + } + + var map = new L.Map('mapqrbcontainer', { + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, + }).setView([30, 0], 1.5); + + // Need to fix so that marker is placed at same place as end of line, but this only needs to be done when longitude is < -170 + if (latlng2[1] < -170) { + latlng2[1] = parseFloat(latlng2[1])+360; + } + if (latlng1[1] < -170) { + latlng1[1] = parseFloat(latlng1[1])+360; } - const map = new L.map('mapqrb').setView([30, 0], 1.5); map.fitBounds([ [latlng1[0], latlng1[1]], [latlng2[0], latlng2[1]] @@ -549,7 +576,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, @@ -559,6 +586,7 @@ function newpath(latlng1, latlng2, locator1, locator2) { map.addLayer(osm); var marker = L.marker([latlng1[0], latlng1[1]], {closeOnClick: false, autoClose: false}).addTo(map).bindPopup(locator1); + var marker2 = L.marker([latlng2[0], latlng2[1]], {closeOnClick: false, autoClose: false}).addTo(map).bindPopup(locator2); const multiplelines = []; @@ -602,7 +630,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 +758,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 +781,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 +910,7 @@ function searchButtonPress(){ uri->segment(1) == "qso") { ?> -load->model('stations'); $active_station_id = $this->stations->find_active(); @@ -1292,7 +1320,11 @@ function update_stats(){ layers: [layer], center: [19, 0], zoom: 2, - minZoom: 1 + minZoom: 1, + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, }); var printer = L.easyPrint({ @@ -1435,7 +1467,11 @@ function onMapClick(event) { layers: [layer], center: [19, 0], zoom: 2, - minZoom: 1 + minZoom: 1, + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, }); var grid_two = ; @@ -1581,6 +1617,9 @@ function onMapClick(event) { 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 +2089,7 @@ function displayActivatorsContacts(call, band, leogeo) { case 'M d, Y': $usethisformat = 'MMM D, YYYY';break; case 'M d, y': $usethisformat = 'MMM D, YY';break; } - + ?> @@ -2066,8 +2105,8 @@ function displayActivatorsContacts(call, band, leogeo) { "scrollX": true, "order": [ 2, 'desc' ], }); - - + + diff --git a/application/views/interface_assets/header.php b/application/views/interface_assets/header.php index 4cb69d2d7..620ef3fb0 100644 --- a/application/views/interface_assets/header.php +++ b/application/views/interface_assets/header.php @@ -20,6 +20,7 @@ + uri->segment(1) == "search" && $this->uri->segment(2) == "filter") { ?> @@ -39,7 +40,7 @@ uri->segment(1) == "adif" || (isset($hasDatePicker) && $hasDatePicker)) { ?> - + '; } ?> @@ -214,7 +215,7 @@ session->userdata('user_id'); ?>" title="Account"> Account Station Logbooks - + Station Locations Bands @@ -256,6 +257,8 @@ QRZ Logbook + QO-100 Dx Club Upload + API Keys diff --git a/application/views/qrbcalc/index.php b/application/views/qrbcalc/index.php index 70c699b48..0806b98f0 100644 --- a/application/views/qrbcalc/index.php +++ b/application/views/qrbcalc/index.php @@ -22,4 +22,4 @@
-
\ No newline at end of file +
\ No newline at end of file diff --git a/application/views/station_profile/create.php b/application/views/station_profile/create.php index 59bee15d0..6e4fcd65a 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 +
+
+ + +
+ + + +
diff --git a/application/views/user/main.php b/application/views/user/main.php index da1bd291a..15d67aea7 100644 --- a/application/views/user/main.php +++ b/application/views/user/main.php @@ -1,14 +1,17 @@

-session->flashdata('notice')) { ?> -
- session->flashdata('notice'); ?> -
-

+session->flashdata('notice')) { ?> + + + + +
User List diff --git a/application/views/view_log/qso.php b/application/views/view_log/qso.php index 02fcab223..a837941dc 100644 --- a/application/views/view_log/qso.php +++ b/application/views/view_log/qso.php @@ -9,6 +9,12 @@ lang->line('cloudlog_station_profile'); ?> COL_NOTES != null) {?> + + + config->item('use_auth')) && ($this->session->userdata('user_type') >= 2)) { echo '
  • +
    +

    Notes

    + COL_NOTES); ?> +
    + config->item('use_auth')) && ($this->session->userdata('user_type') >= 2)) { ?> diff --git a/application/views/visitor/layout/footer.php b/application/views/visitor/layout/footer.php index fe48b51a5..23689ee26 100644 --- a/application/views/visitor/layout/footer.php +++ b/application/views/visitor/layout/footer.php @@ -69,7 +69,11 @@ var map = L.map('gridsquare_map', { layers: [layer], center: [19, 0], - zoom: 2 + zoom: 2, + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, }); var printer = L.easyPrint({ diff --git a/application/views/webadif/export.php b/application/views/webadif/export.php new file mode 100644 index 000000000..11756f215 --- /dev/null +++ b/application/views/webadif/export.php @@ -0,0 +1,116 @@ + +
    + +

    + +
    +
    + + +
    + +
    +
    +
    +

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

    + + +result()) { + ?> +

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

    +

    Warning This might take a while as QSO uploads are processed sequentially.

    + + + + Profile name + Station callsign + Total QSOs not uploaded + Total QSOs uploaded + Actions + + '; + foreach ($station_profile->result() as $station) { // Fills the table with the data + echo ''; + echo '' . $station->station_profile_name . ''; + echo '' . $station->station_callsign . ''; + echo '' . $station->notcount . ''; + echo '' . $station->totcount . ''; + echo ''; + echo ''; + } + echo ''; + + } + else { + ?> +
    + None of your Logbooks are configured to export data to the QO-100 Dx Club's API.
    + To configure this feature, go to your profile page at the QO-100 Dx Club and Create an API key. + Then, navigate to your Station Locations and configure you station with the key you have created at the club. +
    + + +
    +
    +

    Here you can mark as uploaded your QSOs which have not been previously uploaded to QO-100 Dx Club.

    + result()!==[]){ + ?> +
    + +

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

    +

    From date:

    +
    +
    + +
    +
    +
    +
    +
    +

    To date:

    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    + +
    + None of your Logbooks are configured to export data to the QO-100 Dx Club's API.
    + To configure this feature, go to your profile page at the QO-100 Dx Club and Create an API key. + Then, navigate to your Station Locations and configure you station with the key you have created at the club. +
    + +
    +
    +
    +
    +
    diff --git a/application/views/webadif/mark_webadif.php b/application/views/webadif/mark_webadif.php new file mode 100644 index 000000000..8cdd6c0da --- /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/css/general.css b/assets/css/general.css index 6d5b732c7..7bfbf8e4d 100644 --- a/assets/css/general.css +++ b/assets/css/general.css @@ -405,6 +405,10 @@ div#station_logbooks_linked_table_paginate { } } +.api-url { + font-family: Monospace; +} + .api-key { font-family: Monospace; } @@ -415,4 +419,8 @@ div#station_logbooks_linked_table_paginate { .qso_panel .dxccsummary { margin-bottom: 10px; -} \ No newline at end of file +} + +.qso_panel .dxccsummaryheader { + cursor: pointer; +} diff --git a/assets/js/leaflet/Control.FullScreen.css b/assets/js/leaflet/Control.FullScreen.css new file mode 100644 index 000000000..e07fea531 --- /dev/null +++ b/assets/js/leaflet/Control.FullScreen.css @@ -0,0 +1,10 @@ +.fullscreen-icon { background-image: url(icon-fullscreen.svg); background-size:26px 52px; } +.fullscreen-icon.leaflet-fullscreen-on { background-position:0 -26px; } +.leaflet-touch .fullscreen-icon { background-position: 2px 2px; } +.leaflet-touch .fullscreen-icon.leaflet-fullscreen-on { background-position: 2px -24px; } +/* one selector per rule as explained here : http://www.sitepoint.com/html5-full-screen-api/ */ +.leaflet-container:-webkit-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; } +.leaflet-container:-ms-fullscreen { width: 100% !important; height: 100% !important; z-index: 99999; } +.leaflet-container:full-screen { width: 100% !important; height: 100% !important; z-index: 99999; } +.leaflet-container:fullscreen { width: 100% !important; height: 100% !important; z-index: 99999; } +.leaflet-pseudo-fullscreen { position: fixed !important; width: 100% !important; height: 100% !important; top: 0px !important; left: 0px !important; z-index: 99999; } \ No newline at end of file diff --git a/assets/js/leaflet/Control.FullScreen.js b/assets/js/leaflet/Control.FullScreen.js new file mode 100644 index 000000000..fe582e793 --- /dev/null +++ b/assets/js/leaflet/Control.FullScreen.js @@ -0,0 +1,345 @@ +/*! +* Based on package 'screenfull' +* v5.2.0 - 2021-11-03 +* (c) Sindre Sorhus; MIT License +* Added definition for using screenfull as an amd module +* Must be placed before the definition of leaflet.fullscreen +* as it is required by that +*/ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define('screenfull', factory); + } else if (typeof module === 'object' && module.exports) { + module.exports.screenfull = factory(); + } else { + // Save 'screenfull' into global window variable + root.screenfull = factory(); + } +}(typeof self !== 'undefined' ? self : this, function () { + 'use strict'; + + var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {}; + + var fn = (function () { + var val; + + var fnMap = [ + [ + 'requestFullscreen', + 'exitFullscreen', + 'fullscreenElement', + 'fullscreenEnabled', + 'fullscreenchange', + 'fullscreenerror' + ], + // New WebKit + [ + 'webkitRequestFullscreen', + 'webkitExitFullscreen', + 'webkitFullscreenElement', + 'webkitFullscreenEnabled', + 'webkitfullscreenchange', + 'webkitfullscreenerror' + + ], + // Old WebKit + [ + 'webkitRequestFullScreen', + 'webkitCancelFullScreen', + 'webkitCurrentFullScreenElement', + 'webkitCancelFullScreen', + 'webkitfullscreenchange', + 'webkitfullscreenerror' + + ], + [ + 'mozRequestFullScreen', + 'mozCancelFullScreen', + 'mozFullScreenElement', + 'mozFullScreenEnabled', + 'mozfullscreenchange', + 'mozfullscreenerror' + ], + [ + 'msRequestFullscreen', + 'msExitFullscreen', + 'msFullscreenElement', + 'msFullscreenEnabled', + 'MSFullscreenChange', + 'MSFullscreenError' + ] + ]; + + var i = 0; + var l = fnMap.length; + var ret = {}; + + for (; i < l; i++) { + val = fnMap[i]; + if (val && val[1] in document) { + for (i = 0; i < val.length; i++) { + ret[fnMap[0][i]] = val[i]; + } + return ret; + } + } + + return false; + })(); + + var eventNameMap = { + change: fn.fullscreenchange, + error: fn.fullscreenerror + }; + + var screenfull = { + request: function (element, options) { + return new Promise(function (resolve, reject) { + var onFullScreenEntered = function () { + this.off('change', onFullScreenEntered); + resolve(); + }.bind(this); + + this.on('change', onFullScreenEntered); + + element = element || document.documentElement; + + var returnPromise = element[fn.requestFullscreen](options); + + if (returnPromise instanceof Promise) { + returnPromise.then(onFullScreenEntered).catch(reject); + } + }.bind(this)); + }, + exit: function () { + return new Promise(function (resolve, reject) { + if (!this.isFullscreen) { + resolve(); + return; + } + + var onFullScreenExit = function () { + this.off('change', onFullScreenExit); + resolve(); + }.bind(this); + + this.on('change', onFullScreenExit); + + var returnPromise = document[fn.exitFullscreen](); + + if (returnPromise instanceof Promise) { + returnPromise.then(onFullScreenExit).catch(reject); + } + }.bind(this)); + }, + toggle: function (element, options) { + return this.isFullscreen ? this.exit() : this.request(element, options); + }, + onchange: function (callback) { + this.on('change', callback); + }, + onerror: function (callback) { + this.on('error', callback); + }, + on: function (event, callback) { + var eventName = eventNameMap[event]; + if (eventName) { + document.addEventListener(eventName, callback, false); + } + }, + off: function (event, callback) { + var eventName = eventNameMap[event]; + if (eventName) { + document.removeEventListener(eventName, callback, false); + } + }, + raw: fn + }; + + if (!fn) { + return {isEnabled: false}; + } else { + Object.defineProperties(screenfull, { + isFullscreen: { + get: function () { + return Boolean(document[fn.fullscreenElement]); + } + }, + element: { + enumerable: true, + get: function () { + return document[fn.fullscreenElement]; + } + }, + isEnabled: { + enumerable: true, + get: function () { + // Coerce to boolean in case of old WebKit + return Boolean(document[fn.fullscreenEnabled]); + } + } + }); + return screenfull; + } +})); + +/*! +* leaflet.fullscreen +*/ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // define an AMD module that requires 'leaflet' and 'screenfull' + // and resolve to an object containing leaflet and screenfull + define('leafletFullScreen', ['leaflet', 'screenfull'], factory); + } else if (typeof module === 'object' && module.exports) { + // define a CommonJS module that requires 'leaflet' and 'screenfull' + module.exports = factory(require('leaflet'), require('screenfull')); + } else { + // Assume 'leaflet' and 'screenfull' are loaded into global variable already + factory(root.L, root.screenfull); + } +}(typeof self !== 'undefined' ? self : this, function (leaflet, screenfull) { + 'use strict'; + + leaflet.Control.FullScreen = leaflet.Control.extend({ + options: { + position: 'topleft', + title: 'Full Screen', + titleCancel: 'Exit Full Screen', + forceSeparateButton: false, + forcePseudoFullscreen: false, + fullscreenElement: false + }, + + _screenfull: screenfull, + + onAdd: function (map) { + var className = 'leaflet-control-zoom-fullscreen', container, content = ''; + + if (map.zoomControl && !this.options.forceSeparateButton) { + container = map.zoomControl._container; + } else { + container = leaflet.DomUtil.create('div', 'leaflet-bar'); + } + + if (this.options.content) { + content = this.options.content; + } else { + className += ' fullscreen-icon'; + } + + this._createButton(this.options.title, className, content, container, this.toggleFullScreen, this); + this._map.fullscreenControl = this; + + this._map.on('enterFullscreen exitFullscreen', this._toggleState, this); + + return container; + }, + + onRemove: function () { + leaflet.DomEvent + .off(this.link, 'click', leaflet.DomEvent.stop) + .off(this.link, 'click', this.toggleFullScreen, this); + + if (this._screenfull.isEnabled) { + leaflet.DomEvent + .off(this._container, this._screenfull.raw.fullscreenchange, leaflet.DomEvent.stop) + .off(this._container, this._screenfull.raw.fullscreenchange, this._handleFullscreenChange, this); + + leaflet.DomEvent + .off(document, this._screenfull.raw.fullscreenchange, leaflet.DomEvent.stop) + .off(document, this._screenfull.raw.fullscreenchange, this._handleFullscreenChange, this); + } + }, + + _createButton: function (title, className, content, container, fn, context) { + this.link = leaflet.DomUtil.create('a', className, container); + this.link.href = '#'; + this.link.title = title; + this.link.innerHTML = content; + + this.link.setAttribute('role', 'button'); + this.link.setAttribute('aria-label', title); + + L.DomEvent.disableClickPropagation(container); + + leaflet.DomEvent + .on(this.link, 'click', leaflet.DomEvent.stop) + .on(this.link, 'click', fn, context); + + if (this._screenfull.isEnabled) { + leaflet.DomEvent + .on(container, this._screenfull.raw.fullscreenchange, leaflet.DomEvent.stop) + .on(container, this._screenfull.raw.fullscreenchange, this._handleFullscreenChange, context); + + leaflet.DomEvent + .on(document, this._screenfull.raw.fullscreenchange, leaflet.DomEvent.stop) + .on(document, this._screenfull.raw.fullscreenchange, this._handleFullscreenChange, context); + } + + return this.link; + }, + + toggleFullScreen: function () { + var map = this._map; + map._exitFired = false; + if (map._isFullscreen) { + if (this._screenfull.isEnabled && !this.options.forcePseudoFullscreen) { + this._screenfull.exit(); + } else { + leaflet.DomUtil.removeClass(this.options.fullscreenElement ? this.options.fullscreenElement : map._container, 'leaflet-pseudo-fullscreen'); + map.invalidateSize(); + } + map.fire('exitFullscreen'); + map._exitFired = true; + map._isFullscreen = false; + } + else { + if (this._screenfull.isEnabled && !this.options.forcePseudoFullscreen) { + this._screenfull.request(this.options.fullscreenElement ? this.options.fullscreenElement : map._container); + } else { + leaflet.DomUtil.addClass(this.options.fullscreenElement ? this.options.fullscreenElement : map._container, 'leaflet-pseudo-fullscreen'); + map.invalidateSize(); + } + map.fire('enterFullscreen'); + map._isFullscreen = true; + } + }, + + _toggleState: function () { + this.link.title = this._map._isFullscreen ? this.options.title : this.options.titleCancel; + this._map._isFullscreen ? L.DomUtil.removeClass(this.link, 'leaflet-fullscreen-on') : L.DomUtil.addClass(this.link, 'leaflet-fullscreen-on'); + }, + + _handleFullscreenChange: function () { + var map = this._map; + map.invalidateSize(); + if (!this._screenfull.isFullscreen && !map._exitFired) { + map.fire('exitFullscreen'); + map._exitFired = true; + map._isFullscreen = false; + } + } + }); + + leaflet.Map.include({ + toggleFullscreen: function () { + this.fullscreenControl.toggleFullScreen(); + } + }); + + leaflet.Map.addInitHook(function () { + if (this.options.fullscreenControl) { + this.addControl(leaflet.control.fullscreen(this.options.fullscreenControlOptions)); + } + }); + + leaflet.control.fullscreen = function (options) { + return new leaflet.Control.FullScreen(options); + }; + + // must return an object containing also screenfull to make screenfull + // available outside of this package, if used as an amd module, + // as webpack cannot handle amd define with moduleid + return {leaflet: leaflet, screenfull: screenfull}; +})); diff --git a/assets/js/leaflet/icon-fullscreen.svg b/assets/js/leaflet/icon-fullscreen.svg new file mode 100644 index 000000000..6107d8c34 --- /dev/null +++ b/assets/js/leaflet/icon-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/js/sections/common.js b/assets/js/sections/common.js index f06c65d60..92caf191f 100644 --- a/assets/js/sections/common.js +++ b/assets/js/sections/common.js @@ -443,7 +443,7 @@ function getDxccResult(dxcc, name) { }, success: function (html) { $('.dxccsummary').remove(); - $('.qsopane').append('

    DXCC Summary for '+name+'
    '); + $('.qsopane').append('

    DXCC Summary for '+name+'
    '); $('.dxccsummarybody').append(html); } }); diff --git a/assets/js/sections/contesting.js b/assets/js/sections/contesting.js index 3199790a2..e22c9e01c 100644 --- a/assets/js/sections/contesting.js +++ b/assets/js/sections/contesting.js @@ -87,6 +87,15 @@ $(function () { }); }); +// We don't want spaces to be written in serial +$(function () { + $('#exch_serial_r').on('keypress', function (e) { + if (e.which == 32) { + return false; + } + }); +}); + // Here we capture keystrokes to execute functions document.onkeyup = function (e) { // ALT-W wipe @@ -95,8 +104,13 @@ document.onkeyup = function (e) { // CTRL-Enter logs QSO } else if ((e.keyCode == 10 || e.keyCode == 13) && (e.ctrlKey || e.metaKey)) { logQso(); - // Enter in sent exchange logs QSO - } else if ((e.which == 13) && ($(document.activeElement).attr("id") == "exch_rcvd")) { + // Enter in received exchange logs QSO + } else if ((e.which == 13) && ( + ($(document.activeElement).attr("id") == "exch_rcvd") + || ($(document.activeElement).attr("id") == "exch_gridsquare_r") + || ($(document.activeElement).attr("id") == "exch_serial_r") + ) + ) { logQso(); } else if (e.which == 27) { reset_log_fields(); diff --git a/assets/js/sections/cqmap.js b/assets/js/sections/cqmap.js index d374f71b7..1e6888300 100644 --- a/assets/js/sections/cqmap.js +++ b/assets/js/sections/cqmap.js @@ -119,7 +119,13 @@ function load_cq_map2(data) { $("#cqmaptab").append('
    '); } - var map = L.map('cqmap'); + var map = new L.Map('cqmap', { + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, + }); + L.tileLayer( osmUrl, { @@ -151,9 +157,9 @@ function load_cq_map2(data) { color: mapColor, strokeOpacity: 0.3, strokeWeight: 2, - }).addTo(map); + }).addTo(map); - var title = '' + (Number(i)+Number(1)) + ''; + var title = '' + (Number(i)+Number(1)) + ''; var myIcon = L.divIcon({className: 'my-div-icon', html: title}); L.marker( @@ -162,7 +168,7 @@ function load_cq_map2(data) { title: (Number(i)+Number(1)), zIndex: 1000, } - ).addTo(map).on('click', onClick); + ).addTo(map).on('click', onClick); } /*Legend specific*/ diff --git a/assets/js/sections/dxccmap.js b/assets/js/sections/dxccmap.js index e117ffec3..8a8a9f8fa 100644 --- a/assets/js/sections/dxccmap.js +++ b/assets/js/sections/dxccmap.js @@ -42,7 +42,13 @@ function load_dxcc_map2(data, worked, confirmed, notworked) { $("#dxccmaptab").append('
    '); } - var map = L.map('dxccmap'); + var map = new L.Map('dxccmap', { + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, + }); + L.tileLayer( osmUrl, { diff --git a/assets/js/sections/iotamap.js b/assets/js/sections/iotamap.js index 8a2b7f4d0..5c579387e 100644 --- a/assets/js/sections/iotamap.js +++ b/assets/js/sections/iotamap.js @@ -42,7 +42,13 @@ function load_iota_map2(data, worked, confirmed, notworked) { $("#iotamaptab").append('
    '); } - var map = L.map('iotamap'); + var map = new L.Map('iotamap', { + fullscreenControl: true, + fullscreenControlOptions: { + position: 'topleft' + }, + }); + L.tileLayer( osmUrl, { diff --git a/assets/js/sections/webadif.js b/assets/js/sections/webadif.js new file mode 100644 index 000000000..2687d11d1 --- /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); + }); + } + } + }); +} diff --git a/assets/json/satellite_data.json b/assets/json/satellite_data.json index f074ef0af..8f21974b6 100644 --- a/assets/json/satellite_data.json +++ b/assets/json/satellite_data.json @@ -136,7 +136,7 @@ "U/V":[ { "Uplink_Mode":"LSB", - "Uplink_Freq":"4352800000", + "Uplink_Freq":"435280000", "Downlink_Mode":"USB", "Downlink_Freq":"145925000" } diff --git a/install/config/config.php b/install/config/config.php index 47d665f49..a21ba9dc8 100644 --- a/install/config/config.php +++ b/install/config/config.php @@ -14,7 +14,7 @@ */ $config['app_name'] = "Cloudlog"; -$config['app_version'] = "2.3.3"; +$config['app_version'] = "2.4.1"; $config['directory'] = "%directory%"; $config['callbook'] = "hamqth"; // Options are hamqth or qrz