From b368cba8769dfd9732e7acbd3c9b4fab857149a7 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Tue, 23 Dec 2025 11:06:19 +0000 Subject: [PATCH 01/20] Enforce write permissions for QSO modifications Replaced checks for QSO accessibility with stricter write permission checks across controllers and models. Added a new check_qso_is_writable method to Logbook_model to ensure only authorized users can modify or delete QSOs, including shared logbook scenarios. Updated QSO, Qsl_model, and Sstv_model to use the new permission logic for all write operations. --- application/controllers/Qso.php | 38 +++++++++++++++++++++-- application/models/Logbook_model.php | 46 +++++++++++++++++++++++++--- application/models/Qsl_model.php | 8 ++--- application/models/Sstv_model.php | 8 ++--- 4 files changed, 85 insertions(+), 15 deletions(-) diff --git a/application/controllers/Qso.php b/application/controllers/Qso.php index c892f5ebd..f1d40ae4c 100755 --- a/application/controllers/Qso.php +++ b/application/controllers/Qso.php @@ -143,7 +143,16 @@ function edit() { $this->load->model('user_model'); $this->load->model('modes'); if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); } - $query = $this->logbook_model->qso_info($this->uri->segment(3)); + + $qso_id = $this->uri->segment(3); + + // Check if user has write permission to this QSO + if (!$this->logbook_model->check_qso_is_writable($qso_id)) { + $this->session->set_flashdata('notice', 'You do not have permission to edit this QSO'); + redirect('dashboard'); + } + + $query = $this->logbook_model->qso_info($qso_id); $this->load->library('form_validation'); @@ -331,6 +340,15 @@ public function edit_ajax() { } $id = str_replace('"', "", $this->input->post("id")); + + // Check if user has write permission to this QSO + if (!$this->logbook_model->check_qso_is_writable($id)) { + $this->session->set_flashdata('notice', 'You do not have permission to edit this QSO'); + header('Content-Type: application/json'); + echo json_encode(array('message' => 'not allowed')); + return; + } + $query = $this->logbook_model->qso_info($id); $data['qso'] = $query->row(); @@ -350,6 +368,15 @@ function qso_save_ajax() { $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); } + $qso_id = $this->input->post('id'); + + // Check if user has write permission to this QSO + if (!$this->logbook_model->check_qso_is_writable($qso_id)) { + header('Content-Type: application/json'); + echo json_encode(array('message' => 'not allowed')); + return; + } + $this->logbook_model->edit(); } @@ -455,12 +482,17 @@ public function qsl_ignore_ajax() { public function delete($id) { $this->load->model('logbook_model'); - if ($this->logbook_model->check_qso_is_accessible($id)) { + if ($this->logbook_model->check_qso_is_writable($id)) { $this->logbook_model->delete($id); $this->session->set_flashdata('notice', 'QSO Deleted Successfully'); $data['message_title'] = "Deleted"; $data['message_contents'] = "QSO Deleted Successfully"; $this->load->view('messages/message', $data); + } else { + $this->session->set_flashdata('notice', 'You do not have permission to delete this QSO'); + $data['message_title'] = "Permission Denied"; + $data['message_contents'] = "You do not have permission to delete this QSO"; + $this->load->view('messages/message', $data); } // If deletes from /logbook dropdown redirect @@ -474,7 +506,7 @@ public function delete_ajax() { $id = str_replace('"', "", $this->input->post("id")); $this->load->model('logbook_model'); - if ($this->logbook_model->check_qso_is_accessible($id)) { + if ($this->logbook_model->check_qso_is_writable($id)) { $this->logbook_model->delete($id); header('Content-Type: application/json'); echo json_encode(array('message' => 'OK')); diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index be018dccd..262cc73de 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -1684,7 +1684,7 @@ function qso_info($id) // Set Paper to received function paperqsl_update($qso_id, $method) { - if ($this->logbook_model->check_qso_is_accessible($qso_id)) { + if ($this->logbook_model->check_qso_is_writable($qso_id)) { $data = array( 'COL_QSLRDATE' => date('Y-m-d H:i:s'), @@ -1704,7 +1704,7 @@ function paperqsl_update($qso_id, $method) // Set Paper to sent function paperqsl_update_sent($qso_id, $method) { - if ($this->logbook_model->check_qso_is_accessible($qso_id)) { + if ($this->logbook_model->check_qso_is_writable($qso_id)) { if ($method != '') { $data = array( 'COL_QSLSDATE' => date('Y-m-d H:i:s'), @@ -1730,7 +1730,7 @@ function paperqsl_update_sent($qso_id, $method) // Set Paper to requested function paperqsl_requested($qso_id, $method) { - if ($this->logbook_model->check_qso_is_accessible($qso_id)) { + if ($this->logbook_model->check_qso_is_writable($qso_id)) { $data = array( 'COL_QSLSDATE' => date('Y-m-d H:i:s'), @@ -1749,7 +1749,7 @@ function paperqsl_requested($qso_id, $method) function paperqsl_ignore($qso_id, $method) { - if ($this->logbook_model->check_qso_is_accessible($qso_id)) { + if ($this->logbook_model->check_qso_is_writable($qso_id)) { $data = array( 'COL_QSLSDATE' => date('Y-m-d H:i:s'), @@ -5062,6 +5062,44 @@ public function check_qso_is_accessible($id) return false; } + public function check_qso_is_writable($id) + { + // Check if user has WRITE permission to modify this QSO + // Allows write if: QSO belongs to current user OR user has write/admin access to shared logbook + + // First: owner check (current behavior - owners can always write) + $this->db->select($this->config->item('table_name') . '.COL_PRIMARY_KEY'); + $this->db->join('station_profile', $this->config->item('table_name') . '.station_id = station_profile.station_id'); + $this->db->where('station_profile.user_id', $this->session->userdata('user_id')); + $this->db->where($this->config->item('table_name') . '.COL_PRIMARY_KEY', $id); + $ownerQuery = $this->db->get($this->config->item('table_name')); + if ($ownerQuery->num_rows() == 1) { + return true; + } + + // Second: shared-logbook check with write permission + $CI = &get_instance(); + $CI->load->model('logbooks_model'); + $activeLogbookId = $this->session->userdata('active_station_logbook'); + + // Ensure user has at least WRITE access to the active logbook + if ($activeLogbookId && $CI->logbooks_model->check_logbook_is_accessible($activeLogbookId, 'write')) { + $logbooks_locations_array = $CI->logbooks_model->list_logbook_relationships($activeLogbookId); + + if (!empty($logbooks_locations_array)) { + $this->db->select($this->config->item('table_name') . '.COL_PRIMARY_KEY'); + $this->db->where_in($this->config->item('table_name') . '.station_id', $logbooks_locations_array); + $this->db->where($this->config->item('table_name') . '.COL_PRIMARY_KEY', $id); + $sharedQuery = $this->db->get($this->config->item('table_name')); + if ($sharedQuery->num_rows() == 1) { + return true; + } + } + } + + return false; + } + // [JSON PLOT] return array for plot qso for map // public function get_plot_array_for_map($qsos_result, $isVisitor = false) { diff --git a/application/models/Qsl_model.php b/application/models/Qsl_model.php index d4232a174..9a299170b 100644 --- a/application/models/Qsl_model.php +++ b/application/models/Qsl_model.php @@ -44,10 +44,10 @@ function saveQsl($qsoid, $filename) // Clean ID $clean_id = $this->security->xss_clean($qsoid); - // be sure that QSO belongs to user + // be sure that QSO belongs to user and user has write permission $CI = &get_instance(); $CI->load->model('logbook_model'); - if (!$CI->logbook_model->check_qso_is_accessible($clean_id)) { + if (!$CI->logbook_model->check_qso_is_writable($clean_id)) { return; } @@ -66,14 +66,14 @@ function deleteQsl($id) // Clean ID $clean_id = $this->security->xss_clean($id); - // be sure that QSO belongs to user + // be sure that QSO belongs to user and user has write permission $CI = &get_instance(); $CI->load->model('logbook_model'); $this->db->select('qsoid'); $this->db->from('qsl_images'); $this->db->where('id', $clean_id); $qsoid = $this->db->get()->row()->qsoid; - if (!$CI->logbook_model->check_qso_is_accessible($qsoid)) { + if (!$CI->logbook_model->check_qso_is_writable($qsoid)) { return; } diff --git a/application/models/Sstv_model.php b/application/models/Sstv_model.php index 4b3dbcfac..cd8ef0a1c 100644 --- a/application/models/Sstv_model.php +++ b/application/models/Sstv_model.php @@ -7,10 +7,10 @@ function saveSstvImages($qsoid, $filename) // Clean ID $clean_id = $this->security->xss_clean($qsoid); - // be sure that QSO belongs to user + // be sure that QSO belongs to user and user has write permission $CI = &get_instance(); $CI->load->model('logbook_model'); - if (!$CI->logbook_model->check_qso_is_accessible($clean_id)) { + if (!$CI->logbook_model->check_qso_is_writable($clean_id)) { return; } @@ -53,14 +53,14 @@ function deleteSstv($id) // Clean ID $clean_id = $this->security->xss_clean($id); - // be sure that QSO belongs to user + // be sure that QSO belongs to user and user has write permission $CI = &get_instance(); $CI->load->model('logbook_model'); $this->db->select('qsoid'); $this->db->from('sstv_images'); $this->db->where('id', $clean_id); $qsoid = $this->db->get()->row()->qsoid; - if (!$CI->logbook_model->check_qso_is_accessible($qsoid)) { + if (!$CI->logbook_model->check_qso_is_writable($qsoid)) { return; } From fb5afabed9ca2bd96e9c7ec92807de8319d826f3 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 3 Jan 2026 18:28:08 +0000 Subject: [PATCH 02/20] Remove strict type check in satellite name search The third parameter in array_search was set to true, enforcing strict type comparison. This has been removed to allow non-strict comparison, which may improve matching for satellite names. --- application/libraries/AdifHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/libraries/AdifHelper.php b/application/libraries/AdifHelper.php index 54f381423..cfdb73cfe 100644 --- a/application/libraries/AdifHelper.php +++ b/application/libraries/AdifHelper.php @@ -327,6 +327,6 @@ function lotw_satellite_map($satname) { "INSPR7" => "INSPIRE-SAT 7", ); - return array_search(strtoupper($satname),$arr,true); + return array_search(strtoupper($satname),$arr); } } From 6a8f8a85f36a351f6f0de1179d6184e2fc18d756 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 3 Jan 2026 18:28:59 +0000 Subject: [PATCH 03/20] Fix indentation for lotw_satellite_map comment block Corrected the indentation of the comment block for the lotw_satellite_map function to improve code readability and maintain consistent formatting. --- application/libraries/AdifHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/libraries/AdifHelper.php b/application/libraries/AdifHelper.php index cfdb73cfe..a413e1caf 100644 --- a/application/libraries/AdifHelper.php +++ b/application/libraries/AdifHelper.php @@ -298,7 +298,7 @@ function getAdifFieldLine($adifcolumn, $dbvalue) { } } - /* + /* | Function: lotw_satellite_map | Requires: OSCAR Satellite name $satname | From 0c7b919e4ff01335b7e85f0fc4a7d71a09f0c563 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 3 Jan 2026 18:31:24 +0000 Subject: [PATCH 04/20] Update satellite mapping in lotw_satellite_map Added mappings for SONATE-2, ASRTU-1 (AO-123), and TEVEL2-3 satellites to improve satellite name resolution in the lotw_satellite_map function. --- application/libraries/AdifHelper.php | 37 +++++++++++++++------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/application/libraries/AdifHelper.php b/application/libraries/AdifHelper.php index a413e1caf..bf5f5205f 100644 --- a/application/libraries/AdifHelper.php +++ b/application/libraries/AdifHelper.php @@ -308,23 +308,26 @@ function getAdifFieldLine($adifcolumn, $dbvalue) { function lotw_satellite_map($satname) { $arr = array( "ARISS" => "ISS", - "UKUBE1" => "UKUBE-1", - "KEDR" => "ARISSAT-1", - "TO-108" => "CAS-6", - "TAURUS" => "TAURUS-1", - "AISAT1" => "AISAT-1", - 'UVSQ' => "UVSQ-SAT", - 'CAS-3H' => "LILACSAT-2", - 'IO-117' => "GREENCUBE", - "TEVEL1" => "TEVEL-1", - "TEVEL2" => "TEVEL-2", - "TEVEL3" => "TEVEL-3", - "TEVEL4" => "TEVEL-4", - "TEVEL5" => "TEVEL-5", - "TEVEL6" => "TEVEL-6", - "TEVEL7" => "TEVEL-7", - "TEVEL8" => "TEVEL-8", - "INSPR7" => "INSPIRE-SAT 7", + "UKUBE1" => "UKUBE-1", + "KEDR" => "ARISSAT-1", + "TO-108" => "CAS-6", + "TAURUS" => "TAURUS-1", + "AISAT1" => "AISAT-1", + 'UVSQ' => "UVSQ-SAT", + 'CAS-3H' => "LILACSAT-2", + 'IO-117' => "GREENCUBE", + "TEVEL1" => "TEVEL-1", + "TEVEL2" => "TEVEL-2", + "TEVEL3" => "TEVEL-3", + "TEVEL4" => "TEVEL-4", + "TEVEL5" => "TEVEL-5", + "TEVEL6" => "TEVEL-6", + "TEVEL7" => "TEVEL-7", + "TEVEL8" => "TEVEL-8", + "INSPR7" => "INSPIRE-SAT 7", + "SONATE" => "SONATE-2", + 'AO-123' => "ASRTU-1", + 'TEV2-3' => "TEVEL2-3", ); return array_search(strtoupper($satname),$arr); From 44fbfd4aabbc51aab42ffd090a8875c89735fa86 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 3 Jan 2026 21:34:27 +0000 Subject: [PATCH 05/20] Pass second parameter to getAdifLine in Logbook_model Updated the call to getAdifLine in Logbook_model to include a second parameter set to true. This may enable additional functionality or formatting in the ADIF line generation for Clublog integration. --- application/models/Logbook_model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 262cc73de..c93a496fb 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -684,7 +684,7 @@ function add_qso($data, $skipexport = false) $CI->load->library('AdifHelper'); $qso = $this->get_qso($last_id, true)->result(); - $adif = $CI->adifhelper->getAdifLine($qso[0]); + $adif = $CI->adifhelper->getAdifLine($qso[0], true); $clublog_result = $this->push_qso_to_clublog($clublog_creds->ucn, $clublog_creds->ucp, $data['COL_STATION_CALLSIGN'], $adif); if ($clublog_result['status'] == 'OK') { From e572821b96b12788bf88bb260fddb9806c3c44ad Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Mon, 5 Jan 2026 22:14:00 +0000 Subject: [PATCH 06/20] Add Personal Propagation Advisor feature Introduces the Personal Propagation Advisor, allowing users to analyze their own QSO data to determine the best times, bands, and modes for working specific DXCC entities. Adds a new controller, model methods, view, JavaScript, and language strings in multiple languages. Updates navigation and footer to support the new feature. --- .../controllers/Propagationadvisor.php | 278 ++++++++++++++++++ .../language/bulgarian/statistics_lang.php | 28 ++ .../chinese_simplified/statistics_lang.php | 28 ++ .../language/czech/statistics_lang.php | 28 ++ .../language/dutch/statistics_lang.php | 28 ++ application/language/english/menu_lang.php | 1 + .../language/english/statistics_lang.php | 28 ++ .../language/finnish/statistics_lang.php | 28 ++ .../language/french/statistics_lang.php | 28 ++ .../language/german/statistics_lang.php | 28 ++ .../language/greek/statistics_lang.php | 28 ++ .../language/italian/statistics_lang.php | 28 ++ .../language/polish/statistics_lang.php | 28 ++ .../language/portuguese/statistics_lang.php | 28 ++ .../language/russian/statistics_lang.php | 28 ++ .../language/spanish/statistics_lang.php | 28 ++ .../language/swedish/statistics_lang.php | 28 ++ .../language/turkish/statistics_lang.php | 28 ++ application/models/Logbook_model.php | 123 ++++++++ application/views/interface_assets/footer.php | 4 + application/views/interface_assets/header.php | 2 + .../views/propagationadvisor/index.php | 197 +++++++++++++ assets/js/sections/propagationadvisor.js | 277 +++++++++++++++++ 23 files changed, 1330 insertions(+) create mode 100644 application/controllers/Propagationadvisor.php create mode 100644 application/views/propagationadvisor/index.php create mode 100644 assets/js/sections/propagationadvisor.js diff --git a/application/controllers/Propagationadvisor.php b/application/controllers/Propagationadvisor.php new file mode 100644 index 000000000..85a7eab2b --- /dev/null +++ b/application/controllers/Propagationadvisor.php @@ -0,0 +1,278 @@ +load->model('user_model'); + if ($this->user_model->validate_session() == 0) { + redirect('user/login'); + } + + if (!$this->user_model->authorize(2)) { + $this->session->set_flashdata('notice', "You're not allowed to do that!"); + redirect('dashboard'); + } + + $this->load->model('logbooks_model'); + $this->load->model('logbook_model'); + $this->load->model('bands'); + $this->lang->load(array('statistics', 'general_words', 'menu')); + } + + public function index() + { + $data['page_title'] = lang('propagation_title'); + $data['dxcc_list'] = $this->logbook_model->fetchDxcc(); + $bands = $this->bands->get_worked_bands(); + $data['bands'] = array_values(array_filter($bands, function($band) { + return strtoupper($band) !== 'SAT'; + })); + $data['modes'] = $this->logbook_model->get_modes()->result(); + + $this->load->view('interface_assets/header', $data); + $this->load->view('propagationadvisor/index', $data); + $this->load->view('interface_assets/footer'); + } + + public function data() + { + $filters = $this->build_filters(); + if ($filters === null) { + return $this->respond_error(lang('propagation_required_filters')); + } + + $stationIds = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + if (!$stationIds) { + return $this->respond_error(lang('error_no_active_station_profile')); + } + + $hourlyRows = $this->logbook_model->get_propagation_hourly($filters, $stationIds); + $hourly = array_fill(0, 24, 0); + foreach ($hourlyRows as $row) { + $hour = (int)$row->hour_bucket; + if ($hour >= 0 && $hour <= 23) { + $hourly[$hour] = (int)$row->total; + } + } + + $hourlyByBandRows = $this->logbook_model->get_propagation_hourly_by_band($filters, $stationIds); + $hourlyByBand = $this->format_hourly_by_band($hourlyByBandRows); + + $bandBreakdown = $this->logbook_model->get_propagation_band_breakdown($filters, $stationIds); + $modeBreakdown = $this->logbook_model->get_propagation_mode_breakdown($filters, $stationIds); + $lastWorked = $this->logbook_model->get_propagation_last_worked($filters, $stationIds); + $trendRows = $this->logbook_model->get_propagation_activity_trend($filters, $stationIds, 90); + $trend = $this->format_trend($trendRows, 90); + + $response = array( + 'success' => true, + 'filters' => $filters, + 'hourly' => $hourly, + 'hourly_by_band' => $hourlyByBand, + 'total_qsos' => array_sum($hourly), + 'best_window' => $this->compute_best_window($hourly), + 'best_band' => $this->first_or_null($bandBreakdown, 'band'), + 'best_mode' => $this->first_or_null($modeBreakdown, 'mode'), + 'last_worked' => $lastWorked, + 'band_breakdown' => $this->format_breakdown($bandBreakdown, 'band'), + 'trend' => $trend, + ); + + return $this->output + ->set_content_type('application/json') + ->set_output(json_encode($response)); + } + + public function export() + { + $filters = $this->build_filters(); + if ($filters === null) { + show_error(lang('propagation_required_filters'), 400); + return; + } + + $stationIds = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + if (!$stationIds) { + show_error(lang('error_no_active_station_profile'), 400); + return; + } + + $hourlyRows = $this->logbook_model->get_propagation_hourly($filters, $stationIds); + $hourly = array_fill(0, 24, 0); + foreach ($hourlyRows as $row) { + $hour = (int)$row->hour_bucket; + if ($hour >= 0 && $hour <= 23) { + $hourly[$hour] = (int)$row->total; + } + } + + $filename = 'propagation_advisor_' . date('Ymd_His') . '.csv'; + header('Content-Description: File Transfer'); + header('Content-Disposition: attachment; filename=' . $filename); + header('Content-Type: application/csv;charset=iso-8859-1'); + + $fp = fopen('php://output', 'w'); + fputcsv($fp, array('Hour (UTC)', 'QSOs')); + foreach ($hourly as $hour => $count) { + fputcsv($fp, array(str_pad($hour, 2, '0', STR_PAD_LEFT), $count)); + } + fclose($fp); + } + + private function build_filters() + { + $dxcc = $this->input->get_post('dxcc', true); + if (empty($dxcc)) { + return null; + } + + $band = $this->input->get_post('band', true); + $mode = $this->input->get_post('mode', true); + + return array( + 'dxcc' => (int) $dxcc, + 'band' => $band ? trim($band) : '', + 'mode' => $mode ? trim($mode) : '', + ); + } + + private function compute_best_window(array $hourly, $window = 3) + { + if (empty($hourly)) { + return null; + } + + $bestStart = null; + $bestTotal = 0; + + for ($start = 0; $start <= 23 - $window + 1; $start++) { + $sliceTotal = 0; + for ($i = 0; $i < $window; $i++) { + $sliceTotal += $hourly[$start + $i]; + } + if ($sliceTotal > $bestTotal) { + $bestTotal = $sliceTotal; + $bestStart = $start; + } + } + + if ($bestTotal === 0 || $bestStart === null) { + return null; + } + + return array( + 'start' => $bestStart, + 'end' => $bestStart + $window - 1, + 'total' => $bestTotal, + 'label' => str_pad($bestStart, 2, '0', STR_PAD_LEFT) . '-' . str_pad($bestStart + $window - 1, 2, '0', STR_PAD_LEFT) . ' UTC', + ); + } + + private function first_or_null($collection, $key) + { + if (empty($collection)) { + return null; + } + + $first = $collection[0]; + if (!isset($first->$key)) { + return null; + } + + return array( + 'value' => $first->$key, + 'total' => isset($first->total) ? (int)$first->total : null, + ); + } + + private function format_breakdown($collection, $key) + { + $output = array(); + foreach ($collection as $item) { + if (isset($item->$key)) { + $output[] = array( + 'label' => $item->$key, + 'total' => (int)$item->total, + ); + } + } + return $output; + } + + private function format_hourly_by_band($rows) + { + $result = array(); + foreach ($rows as $row) { + $band = $row->band; + $hour = (int)$row->hour_bucket; + $total = (int)$row->total; + + if (!isset($result[$band])) { + $result[$band] = array_fill(0, 24, 0); + } + + if ($hour >= 0 && $hour <= 23) { + $result[$band][$hour] = $total; + } + } + + // Order bands by frequency (reverse order so lowest frequency at top) + $this->load->model('bands'); + $orderedBands = $this->bands->get_user_bands(); + $orderedResult = array(); + foreach ($orderedBands as $band) { + if (isset($result[$band])) { + $orderedResult[$band] = $result[$band]; + } + } + + return $orderedResult; + } + + private function format_trend($rows, $days) + { + // Build date-indexed array for last $days days (inclusive of today) + $daily = array(); + for ($i = $days - 1; $i >= 0; $i--) { + $dateKey = date('Y-m-d', strtotime("-$i days")); + $daily[$dateKey] = 0; + } + + foreach ($rows as $row) { + $day = $row->day; + if (isset($daily[$day])) { + $daily[$day] = (int)$row->total; + } + } + + $dates = array_keys($daily); + $counts = array_values($daily); + + $last7 = array_sum(array_slice($counts, -7)); + $prev7 = array_sum(array_slice($counts, -14, 7)); + $last30 = array_sum(array_slice($counts, -30)); + $prev30 = array_sum(array_slice($counts, -60, 30)); + $last90 = array_sum($counts); + + return array( + 'dates' => $dates, + 'counts' => $counts, + 'last_7' => $last7, + 'prev_7' => $prev7, + 'last_30' => $last30, + 'prev_30' => $prev30, + 'last_90' => $last90, + ); + } + + private function respond_error($message) + { + return $this->output + ->set_status_header(400) + ->set_content_type('application/json') + ->set_output(json_encode(array('success' => false, 'message' => $message))); + } +} diff --git a/application/language/bulgarian/statistics_lang.php b/application/language/bulgarian/statistics_lang.php index 8e1a6861b..44fe88210 100644 --- a/application/language/bulgarian/statistics_lang.php +++ b/application/language/bulgarian/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Explore the logbook.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Years'; $lang['statistics_modes'] = 'Mode'; $lang['statistics_bands'] = 'Bands'; diff --git a/application/language/chinese_simplified/statistics_lang.php b/application/language/chinese_simplified/statistics_lang.php index 4b4c82bed..ceaad87a8 100644 --- a/application/language/chinese_simplified/statistics_lang.php +++ b/application/language/chinese_simplified/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = '查看日志'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = '年'; $lang['statistics_modes'] = '模式'; $lang['statistics_bands'] = '波段'; diff --git a/application/language/czech/statistics_lang.php b/application/language/czech/statistics_lang.php index 2174cf484..02b6505ee 100644 --- a/application/language/czech/statistics_lang.php +++ b/application/language/czech/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Prozkoumejte logbook.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Roky'; $lang['statistics_modes'] = 'Módy'; $lang['statistics_bands'] = 'Pásma'; diff --git a/application/language/dutch/statistics_lang.php b/application/language/dutch/statistics_lang.php index 941fc5771..a1036bc74 100644 --- a/application/language/dutch/statistics_lang.php +++ b/application/language/dutch/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Explore the logbook.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Years'; $lang['statistics_modes'] = 'Mode'; $lang['statistics_bands'] = 'Bands'; diff --git a/application/language/english/menu_lang.php b/application/language/english/menu_lang.php index b5307090a..b0aeabd55 100644 --- a/application/language/english/menu_lang.php +++ b/application/language/english/menu_lang.php @@ -36,6 +36,7 @@ $lang['menu_custom_maps'] = 'Custom Maps'; $lang['menu_continents'] = 'Continents'; $lang['menu_eme_initials'] = 'EME Initials'; +$lang['menu_propagation_advisor'] = 'Propagation Advisor'; $lang['menu_awards'] = 'Awards'; $lang['menu_cq'] = 'CQ'; diff --git a/application/language/english/statistics_lang.php b/application/language/english/statistics_lang.php index a69f54bd5..5953d3209 100644 --- a/application/language/english/statistics_lang.php +++ b/application/language/english/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Explore the logbook.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Years'; $lang['statistics_modes'] = 'Mode'; $lang['statistics_bands'] = 'Bands'; diff --git a/application/language/finnish/statistics_lang.php b/application/language/finnish/statistics_lang.php index 4a00923cc..b79c61129 100644 --- a/application/language/finnish/statistics_lang.php +++ b/application/language/finnish/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Uppoudu lokiisi tarkemmin'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Vuodet'; $lang['statistics_modes'] = 'Modet'; $lang['statistics_bands'] = 'Bandit'; diff --git a/application/language/french/statistics_lang.php b/application/language/french/statistics_lang.php index dbef659e9..4e2a8f18e 100644 --- a/application/language/french/statistics_lang.php +++ b/application/language/french/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = "Informations sur le journal de trafic"; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = "Nb QSOs /An"; $lang['statistics_modes'] = "% /Modes"; $lang['statistics_bands'] = "% /Bandes"; diff --git a/application/language/german/statistics_lang.php b/application/language/german/statistics_lang.php index b01318597..9e3a8bdf8 100644 --- a/application/language/german/statistics_lang.php +++ b/application/language/german/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Logbuch untersuchen.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Jahre'; $lang['statistics_modes'] = 'Modi'; $lang['statistics_bands'] = 'Bänder'; diff --git a/application/language/greek/statistics_lang.php b/application/language/greek/statistics_lang.php index 3cb9c2804..438214258 100644 --- a/application/language/greek/statistics_lang.php +++ b/application/language/greek/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Explore the logbook.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Years'; $lang['statistics_modes'] = 'Mode'; $lang['statistics_bands'] = 'Bands'; diff --git a/application/language/italian/statistics_lang.php b/application/language/italian/statistics_lang.php index 9881dac34..a73b451b3 100644 --- a/application/language/italian/statistics_lang.php +++ b/application/language/italian/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Esplora il registro.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Anni'; $lang['statistics_modes'] = 'Modi'; $lang['statistics_bands'] = 'Bande'; diff --git a/application/language/polish/statistics_lang.php b/application/language/polish/statistics_lang.php index 2f7199284..a0431eb77 100644 --- a/application/language/polish/statistics_lang.php +++ b/application/language/polish/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Przeglądaj dziennik.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Lata'; $lang['statistics_modes'] = 'Tryb'; diff --git a/application/language/portuguese/statistics_lang.php b/application/language/portuguese/statistics_lang.php index 2c2c11135..c3dc5fef8 100644 --- a/application/language/portuguese/statistics_lang.php +++ b/application/language/portuguese/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Explore o logbook.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Anos'; $lang['statistics_modes'] = 'Modo'; $lang['statistics_bands'] = 'Bandas'; diff --git a/application/language/russian/statistics_lang.php b/application/language/russian/statistics_lang.php index b8b62afee..6b3e33c53 100644 --- a/application/language/russian/statistics_lang.php +++ b/application/language/russian/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Статистика журнала.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'по годам'; $lang['statistics_modes'] = 'по видам модуляции'; $lang['statistics_bands'] = 'по диапазонам'; diff --git a/application/language/spanish/statistics_lang.php b/application/language/spanish/statistics_lang.php index 91e440a40..044b459bf 100644 --- a/application/language/spanish/statistics_lang.php +++ b/application/language/spanish/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Explore el libro de guardia.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Años'; $lang['statistics_mode'] = 'Modo'; $lang['statistics_bands'] = 'Bandas'; diff --git a/application/language/swedish/statistics_lang.php b/application/language/swedish/statistics_lang.php index d4a6c3d43..923bc93c4 100644 --- a/application/language/swedish/statistics_lang.php +++ b/application/language/swedish/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Utforska loggboken.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'år'; $lang['statistics_modes'] = 'Mode'; $lang['statistics_bands'] = 'Bands'; diff --git a/application/language/turkish/statistics_lang.php b/application/language/turkish/statistics_lang.php index 1995afa24..89bb9e15d 100644 --- a/application/language/turkish/statistics_lang.php +++ b/application/language/turkish/statistics_lang.php @@ -6,6 +6,34 @@ $lang['statistics_explore_the_logbook'] = 'Kayıt defterini inceleyin.'; +// Personal Propagation Advisor +$lang['propagation_title'] = 'Personal Propagation Advisor'; +$lang['propagation_description'] = 'See when you most often work a DXCC entity, using your own QSOs only.'; +$lang['propagation_select_dxcc'] = 'DXCC entity'; +$lang['propagation_select_band'] = 'Band (optional)'; +$lang['propagation_select_mode'] = 'Mode (optional)'; +$lang['propagation_best_window'] = 'Best UTC window'; +$lang['propagation_best_band'] = 'Most successful band'; +$lang['propagation_best_mode'] = 'Most successful mode'; +$lang['propagation_last_worked'] = 'Last worked'; +$lang['propagation_total_qsos'] = 'Total QSOs analyzed'; +$lang['propagation_heatmap_title'] = '24-hour heatmap'; +$lang['propagation_band_breakdown'] = 'Band breakdown'; +$lang['propagation_download_csv'] = 'Download CSV'; +$lang['propagation_required_filters'] = 'Select a DXCC to start.'; +$lang['propagation_no_data'] = 'No QSOs found for this selection.'; +$lang['propagation_relative_intensity'] = 'Relative intensity:'; +$lang['propagation_intensity_none'] = 'None'; +$lang['propagation_intensity_low'] = 'Low'; +$lang['propagation_intensity_medium'] = 'Medium'; +$lang['propagation_intensity_high'] = 'High'; +$lang['propagation_intensity_very_high'] = 'Very high'; +$lang['propagation_strongest_band_label'] = 'Strongest band'; +$lang['propagation_activity_last_30'] = 'Activity last 30 days'; +$lang['propagation_trend_7d'] = '7d'; +$lang['propagation_trend_30d'] = '30d'; +$lang['propagation_trend_90d'] = '90d'; + $lang['statistics_years'] = 'Yıllar'; $lang['statistics_modes'] = 'Mod'; $lang['statistics_bands'] = 'Bantlar'; diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index c93a496fb..370615f3d 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -2720,6 +2720,129 @@ function get_modes() return $query; } + /* + * Personal Propagation Advisor helpers + */ + public function get_propagation_hourly(array $filters, array $stationIds) + { + if (empty($stationIds) || empty($filters['dxcc'])) { + return array(); + } + + $this->db->select('HOUR(COL_TIME_ON) AS hour_bucket, COUNT(*) AS total', false); + $this->db->where_in('station_id', $stationIds); + $this->db->where('COL_DXCC', $filters['dxcc']); + $this->apply_propagation_filters($filters); + $this->db->group_by('hour_bucket'); + $this->db->order_by('hour_bucket', 'asc'); + + return $this->db->get($this->config->item('table_name'))->result(); + } + + public function get_propagation_hourly_by_band(array $filters, array $stationIds) + { + if (empty($stationIds) || empty($filters['dxcc'])) { + return array(); + } + + $this->db->select('COL_BAND AS band, HOUR(COL_TIME_ON) AS hour_bucket, COUNT(*) AS total', false); + $this->db->where_in('station_id', $stationIds); + $this->db->where('COL_DXCC', $filters['dxcc']); + $this->apply_propagation_filters($filters); + $this->db->group_by('band, hour_bucket'); + $this->db->order_by('band', 'asc'); + $this->db->order_by('hour_bucket', 'asc'); + + return $this->db->get($this->config->item('table_name'))->result(); + } + + public function get_propagation_band_breakdown(array $filters, array $stationIds) + { + if (empty($stationIds) || empty($filters['dxcc'])) { + return array(); + } + + $this->db->select('COL_BAND AS band, COUNT(*) AS total', false); + $this->db->where_in('station_id', $stationIds); + $this->db->where('COL_DXCC', $filters['dxcc']); + $this->apply_propagation_filters($filters); + $this->db->group_by('band'); + $this->db->order_by('total', 'desc'); + + return $this->db->get($this->config->item('table_name'))->result(); + } + + public function get_propagation_mode_breakdown(array $filters, array $stationIds) + { + if (empty($stationIds) || empty($filters['dxcc'])) { + return array(); + } + + $this->db->select("COALESCE(NULLIF(COL_SUBMODE, ''), NULLIF(COL_MODE, ''), 'Unspecified') AS mode, COUNT(*) AS total", false); + $this->db->where_in('station_id', $stationIds); + $this->db->where('COL_DXCC', $filters['dxcc']); + $this->apply_propagation_filters($filters); + $this->db->group_by('mode'); + $this->db->order_by('total', 'desc'); + + return $this->db->get($this->config->item('table_name'))->result(); + } + + public function get_propagation_activity_trend(array $filters, array $stationIds, $days = 90) + { + if (empty($stationIds) || empty($filters['dxcc'])) { + return array(); + } + + $this->db->select('DATE(COL_TIME_ON) AS day, COUNT(*) AS total', false); + $this->db->where_in('station_id', $stationIds); + $this->db->where('COL_DXCC', $filters['dxcc']); + $this->apply_propagation_filters($filters); + $this->db->where('COL_TIME_ON >=', date('Y-m-d 00:00:00', strtotime('-' . ((int)$days - 1) . ' days'))); + $this->db->group_by('day'); + $this->db->order_by('day', 'asc'); + + return $this->db->get($this->config->item('table_name'))->result(); + } + + public function get_propagation_last_worked(array $filters, array $stationIds) + { + if (empty($stationIds) || empty($filters['dxcc'])) { + return null; + } + + $this->db->select('MAX(COL_TIME_ON) AS last_worked'); + $this->db->where_in('station_id', $stationIds); + $this->db->where('COL_DXCC', $filters['dxcc']); + $this->apply_propagation_filters($filters); + + $query = $this->db->get($this->config->item('table_name')); + $row = $query->row(); + + return $row ? $row->last_worked : null; + } + + private function apply_propagation_filters(array $filters, $includeBand = true) + { + // Exclude propagation modes that don't follow typical patterns + $excluded_prop_modes = array('SAT', 'INTERNET', 'EME', 'RPT'); + $this->db->group_start(); + $this->db->where_not_in('COL_PROP_MODE', $excluded_prop_modes); + $this->db->or_where('COL_PROP_MODE IS NULL', null, false); + $this->db->group_end(); + + if ($includeBand && !empty($filters['band'])) { + $this->db->where('COL_BAND', $filters['band']); + } + + if (!empty($filters['mode'])) { + $this->db->group_start(); + $this->db->where('COL_MODE', $filters['mode']); + $this->db->or_where('COL_SUBMODE', $filters['mode']); + $this->db->group_end(); + } + } + /* Return total number of QSOs per band */ function total_bands() { diff --git a/application/views/interface_assets/footer.php b/application/views/interface_assets/footer.php index d1003b526..b1f77366b 100644 --- a/application/views/interface_assets/footer.php +++ b/application/views/interface_assets/footer.php @@ -116,6 +116,10 @@ +uri->segment(1) == "propagationadvisor") { ?> + + + uri->segment(1) == "continents") { ?> diff --git a/application/views/interface_assets/header.php b/application/views/interface_assets/header.php index 05b2ee5da..1639186b0 100644 --- a/application/views/interface_assets/header.php +++ b/application/views/interface_assets/header.php @@ -243,6 +243,8 @@
Tools
+