diff --git a/application/config/migration.php b/application/config/migration.php index 68aae496b..da904e3f3 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -22,7 +22,7 @@ | */ -$config['migration_version'] = 240; +$config['migration_version'] = 241; /* |-------------------------------------------------------------------------- diff --git a/application/controllers/Awards.php b/application/controllers/Awards.php index 596dcc439..5458b6858 100644 --- a/application/controllers/Awards.php +++ b/application/controllers/Awards.php @@ -160,6 +160,10 @@ public function dxcc() $dxcclist = $this->dxcc->fetchdxcc($postdata); $data['dxcc_array'] = $this->dxcc->get_dxcc_array($dxcclist, $bands, $postdata); $data['dxcc_summary'] = $this->dxcc->get_dxcc_summary($bands, $postdata); + + // Calculate continent breakdown and totals + $data['continent_breakdown'] = $this->dxcc->get_continent_breakdown($dxcclist); + $data['dxcc_statistics'] = $this->dxcc->get_dxcc_statistics($data['dxcc_array'], $postdata); // Render Page $data['page_title'] = "Awards - DXCC"; @@ -1290,4 +1294,202 @@ function returnStatus($string) } } } -} + + /* + * Get QSOs for a specific DXCC entity + */ + public function get_dxcc_qsos() + { + $this->load->model('logbooks_model'); + + $dxcc_id = $this->security->xss_clean($this->input->post('dxcc_id')); + $limit = $this->security->xss_clean($this->input->post('limit')) ?: 20; + + if (!$dxcc_id || !is_numeric($dxcc_id)) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'Invalid DXCC ID')); + return; + } + + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + + if (!$logbooks_locations_array) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'No logbook data')); + return; + } + + $location_list = "'" . implode("','", $logbooks_locations_array) . "'"; + + try { + // Get QSOs for this DXCC + $query = $this->db->query(" + SELECT + col_time_on, + col_call, + col_band, + col_mode, + col_rst_sent, + col_rst_rcvd, + col_qsl_sent, + col_qsl_rcvd, + COL_LOTW_QSL_SENT, + COL_LOTW_QSL_RCVD + FROM " . $this->config->item('table_name') . " + WHERE station_id IN (" . $location_list . ") + AND col_dxcc = " . $dxcc_id . " + ORDER BY col_time_on DESC + LIMIT " . intval($limit) + ); + + if ($query->num_rows() > 0) { + $qsos = $query->result_array(); + header('Content-Type: application/json'); + echo json_encode(array('qsos' => $qsos, 'count' => $query->num_rows())); + } else { + header('Content-Type: application/json'); + echo json_encode(array('qsos' => array(), 'count' => 0)); + } + } catch (Exception $e) { + header('Content-Type: application/json'); + echo json_encode(array('error' => $e->getMessage())); + } + } + + /* + * Get QSOs for a specific DXCC entity filtered by status (Confirmed or Worked) + */ + public function get_dxcc_qsos_by_status() + { + $this->load->model('logbooks_model'); + + $dxcc_id = $this->security->xss_clean($this->input->post('dxcc_id')); + $status = $this->security->xss_clean($this->input->post('status')); + $limit = $this->security->xss_clean($this->input->post('limit')) ?: 100; + + if (!$dxcc_id || !is_numeric($dxcc_id) || !$status) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'Invalid parameters', 'count' => 0, 'qsos' => array())); + return; + } + + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + + if (!$logbooks_locations_array) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'No logbook data', 'count' => 0, 'qsos' => array())); + return; + } + + $location_list = "'" . implode("','", $logbooks_locations_array) . "'"; + + try { + // Build WHERE clause for status filter + $status_where = ''; + if ($status === 'C') { + // Confirmed: either QSL received or LoTW received + $status_where = " AND (col_qsl_rcvd = 1 OR COL_LOTW_QSL_RCVD = 'Y')"; + } elseif ($status === 'W') { + // Worked but not confirmed: neither QSL nor LoTW received + $status_where = " AND (col_qsl_rcvd IS NULL OR col_qsl_rcvd != 1) AND (COL_LOTW_QSL_RCVD IS NULL OR COL_LOTW_QSL_RCVD != 'Y')"; + } + + // Get QSOs for this DXCC filtered by status + $query = $this->db->query(" + SELECT + col_time_on, + col_call, + col_band, + col_mode, + col_rst_sent, + col_rst_rcvd, + col_qsl_sent, + col_qsl_rcvd, + COL_LOTW_QSL_SENT, + COL_LOTW_QSL_RCVD + FROM " . $this->config->item('table_name') . " + WHERE station_id IN (" . $location_list . ") + AND col_dxcc = " . $dxcc_id . $status_where . " + ORDER BY col_time_on DESC + LIMIT " . intval($limit) + ); + + if ($query->num_rows() > 0) { + $qsos = $query->result_array(); + header('Content-Type: application/json'); + echo json_encode(array('qsos' => $qsos, 'count' => $query->num_rows())); + } else { + header('Content-Type: application/json'); + echo json_encode(array('qsos' => array(), 'count' => 0)); + } + } catch (Exception $e) { + header('Content-Type: application/json'); + echo json_encode(array('error' => $e->getMessage(), 'count' => 0, 'qsos' => array())); + } + } + + /* + * Get DXCC entities for a specific continent with their status + */ + public function get_continent_qsos() + { + $this->load->model('logbooks_model'); + + $continent_code = $this->security->xss_clean($this->input->post('continent_code')); + + if (!$continent_code) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'Invalid continent code', 'count' => 0, 'entities' => array())); + return; + } + + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + + if (!$logbooks_locations_array) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'No logbook data', 'count' => 0, 'entities' => array())); + return; + } + + $location_list = "'" . implode("','", $logbooks_locations_array) . "'"; + + try { + // Get all DXCC entities for this continent with their status in a single query + $query = $this->db->query(" + SELECT + d.adif, + d.name, + d.prefix, + d.cont, + CASE + WHEN MAX(CASE WHEN (c.col_qsl_rcvd = 1 OR c.COL_LOTW_QSL_RCVD = 'Y') THEN 1 ELSE 0 END) = 1 THEN 'confirmed' + WHEN COUNT(c.col_dxcc) > 0 THEN 'worked' + ELSE 'unworked' + END as status + FROM dxcc_entities d + LEFT JOIN " . $this->config->item('table_name') . " c ON d.adif = c.col_dxcc AND c.station_id IN (" . $location_list . ") + WHERE d.cont = '" . $this->db->escape_like_str($continent_code) . "' + GROUP BY d.adif, d.name, d.prefix, d.cont + ORDER BY d.name ASC + "); + + $entities = array(); + if ($query->num_rows() > 0) { + foreach ($query->result_array() as $entity) { + $entities[] = array( + 'adif' => $entity['adif'], + 'name' => $entity['name'], + 'prefix' => $entity['prefix'], + 'status' => $entity['status'] + ); + } + } + + header('Content-Type: application/json'); + echo json_encode(array('entities' => $entities, 'count' => count($entities))); + } catch (Exception $e) { + header('Content-Type: application/json'); + echo json_encode(array('error' => $e->getMessage(), 'count' => 0, 'entities' => array())); + } + } +} \ No newline at end of file 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/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/controllers/Statistics.php b/application/controllers/Statistics.php index 1b05ad234..4ad2021c9 100644 --- a/application/controllers/Statistics.php +++ b/application/controllers/Statistics.php @@ -472,10 +472,20 @@ public function get_most_worked() { } private function get_total_countries($start_date = null, $end_date = null) { + $this->load->model('logbooks_model'); + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + $this->db->select('COUNT(DISTINCT COL_COUNTRY) as count'); $this->db->from($this->config->item('table_name')); - $this->db->where('COL_COUNTRY IS NOT NULL'); - $this->db->where('COL_COUNTRY !=', ''); + + // Filter by active station's locations + if (!empty($logbooks_locations_array)) { + $this->db->where_in('station_id', $logbooks_locations_array); + } + + // Apply the same validation filters as Dashboard + $this->db->where('COL_COUNTRY !=', 'Invalid'); + $this->db->where('COL_DXCC >', '0'); if ($start_date && $end_date) { $this->db->where('COL_TIME_ON >=', $start_date); 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/libraries/AdifHelper.php b/application/libraries/AdifHelper.php index 54f381423..bf5f5205f 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 | @@ -308,25 +308,28 @@ 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,true); + return array_search(strtoupper($satname),$arr); } } diff --git a/application/migrations/241_tag_2_8_5.php b/application/migrations/241_tag_2_8_5.php new file mode 100644 index 000000000..95c1147f7 --- /dev/null +++ b/application/migrations/241_tag_2_8_5.php @@ -0,0 +1,30 @@ +db->where('option_name', 'version'); + $this->db->update('options', array('option_value' => '2.8.5')); + + // Trigger Version Info Dialog + $this->db->where('option_type', 'version_dialog'); + $this->db->where('option_name', 'confirmed'); + $this->db->update('user_options', array('option_value' => 'false')); + + } + + public function down() + { + $this->db->where('option_name', 'version'); + $this->db->update('options', array('option_value' => '2.8.4')); + } +} \ No newline at end of file diff --git a/application/models/Distances_model.php b/application/models/Distances_model.php index 17a8514d7..8413abd65 100644 --- a/application/models/Distances_model.php +++ b/application/models/Distances_model.php @@ -5,6 +5,19 @@ class Distances_model extends CI_Model { function get_distances($postdata, $measurement_base) { + // Generate cache key based on request parameters + $cache_key = 'distances_' . md5(serialize($postdata) . $measurement_base . $this->session->userdata('active_station_logbook')); + + // Try to get from session cache (valid for 5 minutes) + $cached_data = $this->session->userdata($cache_key); + $cached_time = $this->session->userdata($cache_key . '_time'); + + if ($cached_data && $cached_time && (time() - $cached_time) < 300) { + header('Content-Type: application/json'); + echo json_encode($cached_data); + return; + } + $CI =& get_instance(); $CI->load->model('logbooks_model'); $logbooks_locations_array = $CI->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); @@ -72,6 +85,10 @@ function get_distances($postdata, $measurement_base) { } if ($result) { + // Store in session cache + $this->session->set_userdata($cache_key, $result); + $this->session->set_userdata($cache_key . '_time', time()); + header('Content-Type: application/json'); echo json_encode($result); } @@ -192,13 +209,13 @@ function plot($qsoArray, $gridsquare, $measurement_base) { foreach ($qsoArray as $qso) { $qrb['Qsos']++; // Counts up number of qsos - $bearingdistance = $this->qra->distance($stationgrid, $qso['grid'], $measurement_base); - $avg_distance += ($bearingdistance - $avg_distance) / $qrb['Qsos']; // Calculates running average of distance - if ($bearingdistance != $qso['COL_DISTANCE']) { - $data = array('COL_DISTANCE' => $bearingdistance); - $this->db->where('COL_PRIMARY_KEY', $qso['COL_PRIMARY_KEY']); - $this->db->update($this->config->item('table_name'), $data); + // Use stored distance if available, otherwise calculate + if (!empty($qso['COL_DISTANCE']) && $qso['COL_DISTANCE'] > 0) { + $bearingdistance = $qso['COL_DISTANCE']; + } else { + $bearingdistance = $this->qra->distance($stationgrid, $qso['grid'], $measurement_base); } + $avg_distance += ($bearingdistance - $avg_distance) / $qrb['Qsos']; // Calculates running average of distance $arrayplacement = (int)($bearingdistance / 50); // Resolution is 50, calculates where to put result in array if ($bearingdistance > $qrb['Distance']) { // Saves the longest QSO $qrb['Distance'] = $bearingdistance; diff --git a/application/models/Dxcc.php b/application/models/Dxcc.php index ef02fea57..3ad61cfe9 100644 --- a/application/models/Dxcc.php +++ b/application/models/Dxcc.php @@ -101,7 +101,7 @@ function get_dxcc_array($dxccArray, $bands, $postdata) { if ($postdata['worked'] != NULL) { $workedDXCC = $this->getDxccBandWorked($location_list, $band, $postdata); foreach ($workedDXCC as $wdxcc) { - $dxccMatrix[$wdxcc->dxcc][$band] = '
name).'","'. $band . '","'. $postdata['mode'] . '","DXCC", "")\'>W
'; + $dxccMatrix[$wdxcc->dxcc][$band] = '
dxcc . ', "W")\'>W
'; } } @@ -109,7 +109,7 @@ function get_dxcc_array($dxccArray, $bands, $postdata) { if ($postdata['confirmed'] != NULL) { $confirmedDXCC = $this->getDxccBandConfirmed($location_list, $band, $postdata); foreach ($confirmedDXCC as $cdxcc) { - $dxccMatrix[$cdxcc->dxcc][$band] = '
name).'","'. $band . '","'. $postdata['mode'] . '","DXCC","'.$qsl.'")\'>C
'; + $dxccMatrix[$cdxcc->dxcc][$band] = '
dxcc . ', "C")\'>C
'; } } } @@ -222,7 +222,7 @@ function fetchDxcc($postdata) { $location_list = "'".implode("','",$logbooks_locations_array)."'"; - $sql = "select adif, prefix, name, date(end) Enddate, date(start) Startdate, lat, `long` + $sql = "select adif, prefix, name, cont, date(end) Enddate, date(start) Startdate, lat, `long` from dxcc_entities"; if ($postdata['notworked'] == NULL) { @@ -507,5 +507,152 @@ function lookup_country($country) return $query->row(); } + + /* + * Get continent breakdown for DXCC statistics + */ + function get_continent_breakdown($dxccArray) { + $continents = array( + 'Africa' => 0, + 'Antarctica' => 0, + 'Asia' => 0, + 'Europe' => 0, + 'NorthAmerica' => 0, + 'SouthAmerica' => 0, + 'Oceania' => 0 + ); + + if (!$dxccArray) { + return $continents; + } + + // Map continent codes to names + $continentMap = array( + 'AF' => 'Africa', + 'AN' => 'Antarctica', + 'AS' => 'Asia', + 'EU' => 'Europe', + 'NA' => 'NorthAmerica', + 'SA' => 'SouthAmerica', + 'OC' => 'Oceania' + ); + + foreach ($dxccArray as $dxcc) { + if (isset($dxcc->cont) && isset($continentMap[$dxcc->cont])) { + $continentName = $continentMap[$dxcc->cont]; + $continents[$continentName]++; + } + } + + return $continents; + } + + /* + * Get overall DXCC statistics + */ + function get_dxcc_statistics($dxcc_array, $postdata) { + $stats = array( + 'total_worked' => 0, + 'total_confirmed' => 0, + 'worked_by_continent' => array( + 'Africa' => 0, + 'Antarctica' => 0, + 'Asia' => 0, + 'Europe' => 0, + 'NorthAmerica' => 0, + 'SouthAmerica' => 0, + 'Oceania' => 0 + ), + 'confirmed_by_continent' => array( + 'Africa' => 0, + 'Antarctica' => 0, + 'Asia' => 0, + 'Europe' => 0, + 'NorthAmerica' => 0, + 'SouthAmerica' => 0, + 'Oceania' => 0 + ), + 'milestones' => array( + 100 => false, + 200 => false, + 300 => false + ) + ); + + if (!$dxcc_array) { + return $stats; + } + + // Get DXCC entities to map continents + $dxcc_result = $this->db->get('dxcc_entities')->result_array(); + $all_dxcc = array(); + foreach ($dxcc_result as $row) { + $all_dxcc[$row['adif']] = $row; + } + + // Map continent codes to names + $continentMap = array( + 'AF' => 'Africa', + 'AN' => 'Antarctica', + 'AS' => 'Asia', + 'EU' => 'Europe', + 'NA' => 'NorthAmerica', + 'SA' => 'SouthAmerica', + 'OC' => 'Oceania' + ); + + // Count worked DXCC from array + $worked_count = 0; + $confirmed_count = 0; + + foreach ($dxcc_array as $adif => $entity) { + // Check if this DXCC has any confirmed contacts (any cell with 'bg-success') + $is_worked = false; + $is_confirmed = false; + + foreach ($entity as $key => $value) { + if (is_string($value) && strpos($value, 'bg-success') !== false) { + $is_confirmed = true; + $is_worked = true; + break; + } + if (is_string($value) && strpos($value, 'bg-danger') !== false) { + $is_worked = true; + } + } + + if ($is_worked) { + $worked_count++; + } + if ($is_confirmed) { + $confirmed_count++; + } + + // Map continent stats + if (isset($all_dxcc[$adif]) && isset($all_dxcc[$adif]['cont'])) { + $cont_code = $all_dxcc[$adif]['cont']; + $continent = isset($continentMap[$cont_code]) ? $continentMap[$cont_code] : null; + + if ($continent) { + if ($is_confirmed) { + $stats['confirmed_by_continent'][$continent]++; + } + if ($is_worked) { + $stats['worked_by_continent'][$continent]++; + } + } + } + } + + $stats['total_worked'] = $worked_count; + $stats['total_confirmed'] = $confirmed_count; + + // Check milestone achievements + $stats['milestones'][100] = $stats['total_worked'] >= 100; + $stats['milestones'][200] = $stats['total_worked'] >= 200; + $stats['milestones'][300] = $stats['total_worked'] >= 300; + + return $stats; + } } ?> diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index be018dccd..cd32d771d 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -127,7 +127,8 @@ function create_qso() } else { // if $country isn't empty and dxcc_id is 0 use the DXCC ID from the callsign lookup if (!empty($country) && $this->input->post('dxcc_id') == "0") { - $dxcc_id = $dxcc_id; + $dxcc = $this->check_dxcc_table(strtoupper(trim($callsign)), $datetime); + $dxcc_id = !empty($dxcc[0]) ? $dxcc[0] : 0; } else { $dxcc_id = $this->input->post('dxcc_id'); } @@ -684,7 +685,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') { @@ -1684,7 +1685,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 +1705,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 +1731,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 +1750,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'), @@ -2720,6 +2721,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() { @@ -5062,6 +5186,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; } diff --git a/application/views/awards/cq/index.php b/application/views/awards/cq/index.php index c24ee8cbd..03890ba91 100644 --- a/application/views/awards/cq/index.php +++ b/application/views/awards/cq/index.php @@ -29,186 +29,360 @@ margin: 0 8px 0 0; opacity: 0.7; } + +.stats-card { + border-left: 4px solid #007bff; + padding: 20px; + margin-bottom: 15px; + background-color: #f8f9fa; + border-radius: 4px; +} + +.stats-card.milestone { + border-left-color: #28a745; +} + +.stats-card.milestone.achieved { + background-color: #d4edda; +} + +.stats-card h5 { + margin: 0 0 10px 0; + color: #333; + font-weight: 600; +} + +.stats-number { + font-size: 28px; + font-weight: bold; + color: #007bff; + margin: 10px 0; +} + +.milestone.achieved .stats-number { + color: #28a745; +} + +.progress { + height: 24px; + margin-top: 10px; +} + +.progress-label { + font-size: 12px; + font-weight: 500; +} + +.filter-collapse { + cursor: pointer; +} + +.preset-filters { + margin-bottom: 15px; +} + +.preset-btn { + margin-right: 5px; + margin-bottom: 5px; + font-size: 12px; +} + +.table-search { + margin-bottom: 15px; +} + +.sortable { + cursor: pointer; + user-select: none; +} + +.sortable::after { + content: ' ↕'; + font-size: 12px; + color: #999; +} + +.sortable.asc::after { + content: ' ↑'; + color: #007bff; +} + +.sortable.desc::after { + content: ' ↓'; + color: #007bff; +} + +.cq-link { + color: #007bff; + cursor: pointer; + text-decoration: underline; +} + +.cq-link:hover { + color: #0056b3; +} + +.tab-pane { + animation: fadeIn 0.3s; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +}
+ +
+
+ +

+ +
+ - -
-
- -

- -
- -
-
- - -
-
-
-
- input->post('worked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('confirmed') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - + +
+
+ Filters + +
+
+
+ +
+ + + + + +
+ + + + +
+
Worked / Confirmed
+
+
+ input->post('worked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('confirmed') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('notworked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
-
- input->post('notworked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - + +
+
QSL Type
+
+
+ input->post('qsl') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('lotw') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('eqsl')) echo ' checked="checked"'; ?> > + +
+
-
-
-
-
-
-
- input->post('qsl') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - +
+ +
+ +
-
- input->post('lotw') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - + +
+ +
+ +
-
- input->post('eqsl')) echo ' checked="checked"'; ?> > - + +
+ +
+ + + + + +
-
+ +
+
+
-
- -
- + + +
+ +
+
+
CQ Zone Progress
+
+
+
Total Worked
+
0; })); ?>
+
+
+
Total Confirmed
+
0; })); ?>
+
+
-
- -
- -
-
- -
- -
- - - - + +
+
+
🏆 WAZ Milestone Progress
+
+
+
All Worked
+
+ 0; })) == 40; + echo $all_zones_worked ? '✓' : '✗'; + ?> +
+
+
+
All Confirmed
+
+ 0; })) == 40; + echo $all_zones_confirmed ? '✓' : '✗'; + ?> +
+
-
- +
+ +
-
- -
+
+
+
-
+ + + +
- - - # - " . lang('gen_hamradio_cq_zone') . ""; + echo ' + + + + '; foreach($bands as $band) { - echo ''; - } - echo ' - - '; + echo ''; + } + echo ' + + '; foreach ($cq_array as $cq => $value) { // Fills the table with the data - echo ' - - '; - foreach ($value as $key) { - echo ''; + echo ' + '; + foreach ($value as $name => $key) { + echo ''; } echo ''; } - echo "
CQ Zone' . $band . '
' . $band . '
' . $i++ . ''. $cq .'' . $key . '
'. $cq .'' . $key . '
-

" . lang('awards_summary') . "

+ echo ''; + echo "

" . lang('awards_summary') . "

"; - + echo ' +
- "; + '; foreach($bands as $band) { echo ''; } - echo " + echo ' + - "; + '; foreach ($cq_summary['worked'] as $dxcc) { // Fills the table with the data echo ''; } - echo " - "; + echo ' + '; foreach ($cq_summary['confirmed'] as $dxcc) { // Fills the table with the data echo ''; } echo ' -
' . $band . '" . lang('awards_total') . "
Total
" . lang('awards_total_worked') . "
' . lang('awards_total_worked') . '' . $dxcc . '
" . lang('awards_total_confirmed') . "
' . lang('awards_total_confirmed') . '' . $dxcc . '
-
'; + '; } else { diff --git a/application/views/awards/dxcc/index.php b/application/views/awards/dxcc/index.php index 0ad0a0b44..2a5695113 100644 --- a/application/views/awards/dxcc/index.php +++ b/application/views/awards/dxcc/index.php @@ -30,7 +30,121 @@ margin: 0 8px 0 0; opacity: 0.7; } + +.stats-card { + border-left: 4px solid #007bff; + padding: 20px; + margin-bottom: 15px; + background-color: #f8f9fa; + border-radius: 4px; +} + +.stats-card.milestone { + border-left-color: #28a745; +} + +.stats-card.milestone.achieved { + background-color: #d4edda; +} + +.stats-card h5 { + margin: 0 0 10px 0; + color: #333; + font-weight: 600; +} + +.stats-number { + font-size: 28px; + font-weight: bold; + color: #007bff; + margin: 10px 0; +} + +.milestone.achieved .stats-number { + color: #28a745; +} + +.progress { + height: 24px; + margin-top: 10px; +} + +.progress-label { + font-size: 12px; + font-weight: 500; +} + +.filter-collapse { + cursor: pointer; +} + +.preset-filters { + margin-bottom: 15px; +} + +.preset-btn { + margin-right: 5px; + margin-bottom: 5px; + font-size: 12px; +} + +.table-search { + margin-bottom: 15px; +} + +.sortable { + cursor: pointer; + user-select: none; +} + +.sortable::after { + content: ' ↕'; + font-size: 12px; + color: #999; +} + +.sortable.asc::after { + content: ' ↑'; + color: #007bff; +} + +.sortable.desc::after { + content: ' ↓'; + color: #007bff; +} + +.dxcc-link { + color: #007bff; + cursor: pointer; + text-decoration: underline; +} + +.dxcc-link:hover { + color: #0056b3; +} + +.continent-card { + cursor: pointer; + user-select: none; + transition: all 0.2s ease; +} + +.continent-card:hover { + background-color: #e9ecef; + transform: translateY(-2px); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.tab-pane { + animation: fadeIn 0.3s; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} +

@@ -47,139 +161,257 @@
-
-
+ +
+
+ Filters + +
+
+
+ +
+ + + + + + +
-
-
Deleted DXCC
-
-
- input->post('includedeleted')) echo ' checked="checked"'; ?> > - + + +
+
Deleted DXCC
+
+
+ input->post('includedeleted')) echo ' checked="checked"'; ?> > + +
+
-
-
- -
-
Worked / Confirmed
-
-
- input->post('worked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - + +
+
Worked / Confirmed
+
+
+ input->post('worked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('confirmed') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('notworked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
-
- input->post('confirmed') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - + +
+
QSL Type
+
+
+ input->post('qsl') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('lotw') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('eqsl')) echo ' checked="checked"'; ?> > + +
+
-
- input->post('notworked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - + +
+
Continents
+
+
+ input->post('Antarctica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('Africa') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('Asia') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('Europe') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('NorthAmerica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('SouthAmerica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('Oceania') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
-
-
-
-
QSL Type
-
-
- input->post('qsl') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - +
+ +
+ +
-
- input->post('lotw') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - + +
+ +
+ +
-
- input->post('eqsl')) echo ' checked="checked"'; ?> > - + +
+ +
+ + + + + +
-
+ +
+
+
-
-
Continents
-
-
- input->post('Antarctica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('Africa') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('Asia') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('Europe') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('NorthAmerica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('SouthAmerica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - + + +
+ +
+
+
DXCC Progress
+
+
+
Total Worked
+
-
- input->post('Oceania') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - +
+
Total Confirmed
+
+
-
- -
- + +
+
+
🏆 Milestone Progress
+
+
+
100
+
+ +
+
+
+
200
+
+ +
+
+
+
300
+
+ +
+
+
+
-
- -
- -
-
- -
- -
- - - - +
+
+
+ / +
+
+
+
+
+
- -
- + + +