diff --git a/application/controllers/Bandmap.php b/application/controllers/Bandmap.php index d50bf1c66..15788d870 100644 --- a/application/controllers/Bandmap.php +++ b/application/controllers/Bandmap.php @@ -1,129 +1,82 @@ -load->model('user_model'); - if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } - $this->load->model('bands'); - } - - function index() { - $this->load->model('cat'); - $this->load->model('bands'); - $data['radios'] = $this->cat->radios(true); - $data['bands'] = $this->bands->get_user_bands_for_qso_entry(); - - $footerData = []; - $footerData['scripts'] = [ - 'assets/js/highcharts/highcharts.js', - 'assets/js/highcharts/timeline.js', - 'assets/js/highcharts/exporting.js', - 'assets/js/highcharts/accessibility.js', - 'assets/js/sections/bandmap.js', - ]; - - $data['page_title'] = __("DXCluster"); - $this->load->view('interface_assets/header', $data); - $this->load->view('bandmap/index'); - $this->load->view('interface_assets/footer', $footerData); - } - - function list() { - $this->load->model('cat'); - $this->load->model('bands'); - $data['radios'] = $this->cat->radios(); - $data['radio_last_updated'] = $this->cat->last_updated()->row(); - $data['bands'] = $this->bands->get_user_bands_for_qso_entry(); - - $footerData = []; - $footerData['scripts'] = [ - 'assets/js/moment.min.js', - 'assets/js/datetime-moment.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/datetime-moment.js")), - 'assets/js/cat.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/cat.js")), - 'assets/js/leaflet/leaflet.geodesic.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/leaflet/leaflet.geodesic.js")), - 'assets/js/leaflet.polylineDecorator.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/leaflet.polylineDecorator.js")), - 'assets/js/leaflet/L.Terminator.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/leaflet/L.Terminator.js")), - 'assets/js/sections/callstats.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/callstats.js")), - 'assets/js/sections/bandmap_list.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/bandmap_list.js")), - ]; - - // Get Date format - if($this->session->userdata('user_date_format')) { - // If Logged in and session exists - $pageData['custom_date_format'] = $this->session->userdata('user_date_format'); - } else { - // Get Default date format from /config/wavelog.php - $pageData['custom_date_format'] = $this->config->item('qso_date_format'); - } - - switch ($pageData['custom_date_format']) { - case "d/m/y": $pageData['custom_date_format'] = 'DD/MM/YY'; break; - case "d/m/Y": $pageData['custom_date_format'] = 'DD/MM/YYYY'; break; - case "m/d/y": $pageData['custom_date_format'] = 'MM/DD/YY'; break; - case "m/d/Y": $pageData['custom_date_format'] = 'MM/DD/YYYY'; break; - case "d.m.Y": $pageData['custom_date_format'] = 'DD.MM.YYYY'; break; - case "y/m/d": $pageData['custom_date_format'] = 'YY/MM/DD'; break; - case "Y-m-d": $pageData['custom_date_format'] = 'YYYY-MM-DD'; break; - case "M d, Y": $pageData['custom_date_format'] = 'MMM DD, YYYY'; break; - case "M d, y": $pageData['custom_date_format'] = 'MMM DD, YY'; break; - default: $pageData['custom_date_format'] = 'DD/MM/YYYY'; - } - - $data['page_title'] = __("DXCluster"); - $this->load->view('interface_assets/header', $data); - $this->load->view('bandmap/list',$pageData); - $this->load->view('interface_assets/footer', $footerData); - } - - // Get user's favorite bands and modes (active ones) - function get_user_favorites() { - session_write_close(); - - $this->load->model('bands'); - $this->load->model('usermodes'); - - // Get active bands - $activeBands = $this->bands->get_user_bands_for_qso_entry(false); // false = only active - $bandList = []; - - if (is_array($activeBands)) { - foreach ($activeBands as $group => $bands) { - if (is_array($bands)) { - foreach ($bands as $band) { - $bandList[] = $band; - } - } - } - } - - // Get active modes (user-specific) and categorize them - $activeModes = $this->usermodes->active(); - $modeCategories = [ - 'cw' => false, - 'phone' => false, - 'digi' => false - ]; - - if ($activeModes) { - foreach ($activeModes as $mode) { - $qrgmode = strtoupper($mode->qrgmode ?? ''); - if ($qrgmode === 'CW') { - $modeCategories['cw'] = true; - } elseif ($qrgmode === 'SSB') { - $modeCategories['phone'] = true; - } elseif ($qrgmode === 'DATA') { - $modeCategories['digi'] = true; - } - } - } - - header('Content-Type: application/json'); - echo json_encode([ - 'bands' => $bandList, - 'modes' => $modeCategories - ]); - } -} +load->model('user_model'); + if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } + $this->load->model('bands'); + } + + function index() { + $this->load->model('cat'); + $this->load->model('bands'); + $data['radios'] = $this->cat->radios(true); + $data['bands'] = $this->bands->get_user_bands_for_qso_entry(); + + $footerData = []; + $footerData['scripts'] = [ + 'assets/js/highcharts/highcharts.js', + 'assets/js/highcharts/timeline.js', + 'assets/js/highcharts/exporting.js', + 'assets/js/highcharts/accessibility.js', + 'assets/js/sections/bandmap.js', + ]; + + $data['page_title'] = __("DXCluster"); + $this->load->view('interface_assets/header', $data); + $this->load->view('bandmap/index'); + $this->load->view('interface_assets/footer', $footerData); + } + + function list() { + $this->load->model('cat'); + $this->load->model('bands'); + $data['radios'] = $this->cat->radios(); + $data['radio_last_updated'] = $this->cat->last_updated()->row(); + $data['bands'] = $this->bands->get_user_bands_for_qso_entry(); + + $footerData = []; + $footerData['scripts'] = [ + 'assets/js/moment.min.js', + 'assets/js/datetime-moment.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/datetime-moment.js")), + 'assets/js/cat.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/cat.js")), + 'assets/js/leaflet/leaflet.geodesic.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/leaflet/leaflet.geodesic.js")), + 'assets/js/leaflet.polylineDecorator.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/leaflet.polylineDecorator.js")), + 'assets/js/leaflet/L.Terminator.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/leaflet/L.Terminator.js")), + 'assets/js/sections/callstats.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/callstats.js")), + 'assets/js/sections/bandmap_list.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/bandmap_list.js")), + ]; + + // Get Date format + if($this->session->userdata('user_date_format')) { + // If Logged in and session exists + $pageData['custom_date_format'] = $this->session->userdata('user_date_format'); + } else { + // Get Default date format from /config/wavelog.php + $pageData['custom_date_format'] = $this->config->item('qso_date_format'); + } + + switch ($pageData['custom_date_format']) { + case "d/m/y": $pageData['custom_date_format'] = 'DD/MM/YY'; break; + case "d/m/Y": $pageData['custom_date_format'] = 'DD/MM/YYYY'; break; + case "m/d/y": $pageData['custom_date_format'] = 'MM/DD/YY'; break; + case "m/d/Y": $pageData['custom_date_format'] = 'MM/DD/YYYY'; break; + case "d.m.Y": $pageData['custom_date_format'] = 'DD.MM.YYYY'; break; + case "y/m/d": $pageData['custom_date_format'] = 'YY/MM/DD'; break; + case "Y-m-d": $pageData['custom_date_format'] = 'YYYY-MM-DD'; break; + case "M d, Y": $pageData['custom_date_format'] = 'MMM DD, YYYY'; break; + case "M d, y": $pageData['custom_date_format'] = 'MMM DD, YY'; break; + default: $pageData['custom_date_format'] = 'DD/MM/YYYY'; + } + + $data['page_title'] = __("DXCluster"); + $this->load->view('interface_assets/header', $data); + $this->load->view('bandmap/list',$pageData); + $this->load->view('interface_assets/footer', $footerData); + } + + // Get user's active bands and modes/submodes +} diff --git a/application/controllers/User_options.php b/application/controllers/User_options.php index 6e89a13c9..e44fabceb 100644 --- a/application/controllers/User_options.php +++ b/application/controllers/User_options.php @@ -1,71 +1,186 @@ -load->model('user_model'); - $this->load->model('user_options_model'); - if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } - } - - public function add_edit_fav() { - $obj = json_decode(file_get_contents("php://input"), true); - foreach($obj as $option_key => $option_value) { - $obj[$option_key]=$this->security->xss_clean($option_value); - } - if ($obj['sat_name'] ?? '' != '') { - $option_name=$obj['sat_name'].'/'.$obj['mode']; - } else { - $option_name=$obj['band'].'/'.$obj['mode']; - } - $this->user_options_model->set_option('Favourite',$option_name, $obj); - $jsonout['success']=1; - header('Content-Type: application/json'); - echo json_encode($jsonout); - } - - public function get_fav() { - $result=$this->user_options_model->get_options('Favourite'); - $jsonout=[]; - foreach($result->result() as $options) { - $jsonout[$options->option_name][$options->option_key]=$options->option_value; - } - header('Content-Type: application/json'); - echo json_encode($jsonout); - } - - public function del_fav() { - $result=$this->user_options_model->get_options('Favourite'); - $obj = json_decode(file_get_contents("php://input"), true); - if ($obj['option_name'] ?? '' != '') { - $option_name=$this->security->xss_clean($obj['option_name']); - $this->user_options_model->del_option('Favourite',$option_name); - } - $jsonout['success']=1; - header('Content-Type: application/json'); - echo json_encode($jsonout); - } - - public function dismissVersionDialog() { - $this->user_options_model->set_option('version_dialog', 'confirmed', array('boolean' => 'true')); - } - - public function get_qrg_units() { - - $qrg_units = []; - - foreach($this->session->get_userdata() as $key => $value) { - if (strpos($key, 'qrgunit_') === 0) { - $band = str_replace('qrgunit_', '', $key); - $qrg_units[$band] = $value; - } - } - - header('Content-Type: application/json'); - echo json_encode($qrg_units); - } -} - - -?> +load->model('user_model'); + $this->load->model('user_options_model'); + if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } + } + + public function add_edit_fav() { + $obj = json_decode(file_get_contents("php://input"), true); + foreach($obj as $option_key => $option_value) { + $obj[$option_key]=$this->security->xss_clean($option_value); + } + if ($obj['sat_name'] ?? '' != '') { + $option_name=$obj['sat_name'].'/'.$obj['mode']; + } else { + $option_name=$obj['band'].'/'.$obj['mode']; + } + $this->user_options_model->set_option('Favourite',$option_name, $obj); + $jsonout['success']=1; + header('Content-Type: application/json'); + echo json_encode($jsonout); + } + + public function get_fav() { + $result=$this->user_options_model->get_options('Favourite'); + $jsonout=[]; + foreach($result->result() as $options) { + $jsonout[$options->option_name][$options->option_key]=$options->option_value; + } + header('Content-Type: application/json'); + echo json_encode($jsonout); + } + + public function del_fav() { + $result=$this->user_options_model->get_options('Favourite'); + $obj = json_decode(file_get_contents("php://input"), true); + if ($obj['option_name'] ?? '' != '') { + $option_name=$this->security->xss_clean($obj['option_name']); + $this->user_options_model->del_option('Favourite',$option_name); + } + $jsonout['success']=1; + header('Content-Type: application/json'); + echo json_encode($jsonout); + } + + public function dismissVersionDialog() { + $this->user_options_model->set_option('version_dialog', 'confirmed', array('boolean' => 'true')); + } + + /** + * DX Cluster Filter Favorites + */ + public function add_edit_dxcluster_fav() { + $obj = json_decode(file_get_contents("php://input"), true); + if (!$obj || !isset($obj['fav_name']) || trim($obj['fav_name']) === '') { + header('Content-Type: application/json'); + echo json_encode(['success' => 0, 'error' => 'Invalid data']); + return; + } + + // Sanitize all input + foreach($obj as $option_key => $option_value) { + if (is_array($option_value)) { + $obj[$option_key] = array_map([$this->security, 'xss_clean'], $option_value); + } else { + $obj[$option_key] = $this->security->xss_clean($option_value); + } + } + + $option_name = $obj['fav_name']; + unset($obj['fav_name']); // Don't store the name as a value + + // Convert arrays to JSON for storage + foreach($obj as $key => $value) { + if (is_array($value)) { + $obj[$key] = json_encode($value); + } + } + + $this->user_options_model->set_option('DXClusterFavourite', $option_name, $obj); + $jsonout['success'] = 1; + header('Content-Type: application/json'); + echo json_encode($jsonout); + } + + public function del_dxcluster_fav() { + $obj = json_decode(file_get_contents("php://input"), true); + if ($obj['option_name'] ?? '' != '') { + $option_name = $this->security->xss_clean($obj['option_name']); + $this->user_options_model->del_option('DXClusterFavourite', $option_name); + } + $jsonout['success'] = 1; + header('Content-Type: application/json'); + echo json_encode($jsonout); + } + + public function get_qrg_units() { + + $qrg_units = []; + + foreach($this->session->get_userdata() as $key => $value) { + if (strpos($key, 'qrgunit_') === 0) { + $band = str_replace('qrgunit_', '', $key); + $qrg_units[$band] = $value; + } + } + + header('Content-Type: application/json'); + echo json_encode($qrg_units); + } + + /** + * Combined endpoint: DX Cluster favorites + user bands/modes settings + * Returns both favorites and user configuration in a single request + */ + public function get_dxcluster_user_favs_and_settings() { + session_write_close(); + + // Get DX Cluster favorites + $result = $this->user_options_model->get_options('DXClusterFavourite'); + $favorites = []; + foreach($result->result() as $options) { + $value = $options->option_value; + if (is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) { + $decoded = json_decode($value, true); + if (json_last_error() === JSON_ERROR_NONE) { + $value = $decoded; + } + } + $favorites[$options->option_name][$options->option_key] = $value; + } + + // Get user bands and modes + $this->load->model('bands'); + $this->load->model('usermodes'); + + $activeBands = $this->bands->get_user_bands_for_qso_entry(false); + $bandList = []; + if (is_array($activeBands)) { + foreach ($activeBands as $group => $bands) { + if (is_array($bands)) { + foreach ($bands as $band) { + $bandList[] = $band; + } + } + } + } + + $activeModes = $this->usermodes->active(); + $modeCategories = ['cw' => false, 'phone' => false, 'digi' => false]; + $submodes = []; + + if ($activeModes) { + foreach ($activeModes as $mode) { + $qrgmode = strtoupper($mode->qrgmode ?? ''); + if ($qrgmode === 'CW') { + $modeCategories['cw'] = true; + } elseif ($qrgmode === 'SSB') { + $modeCategories['phone'] = true; + } elseif ($qrgmode === 'DATA') { + $modeCategories['digi'] = true; + } + $submode = !empty($mode->submode) ? $mode->submode : $mode->mode; + if (!empty($submode) && !in_array($submode, $submodes)) { + $submodes[] = $submode; + } + } + } + + header('Content-Type: application/json'); + echo json_encode([ + 'favorites' => $favorites, + 'userConfig' => [ + 'bands' => $bandList, + 'modes' => $modeCategories, + 'submodes' => $submodes + ] + ]); + } +} + +?> diff --git a/application/views/bandmap/list.php b/application/views/bandmap/list.php index bb88466ab..5f33310c4 100644 --- a/application/views/bandmap/list.php +++ b/application/views/bandmap/list.php @@ -16,7 +16,7 @@ var lang_bandmap_cat_required = "= __("CAT Connection Required"); ?>"; var lang_bandmap_enable_cat = "= __("Enable CAT connection to tune the radio"); ?>"; var lang_bandmap_clear_filters = "= __("Clear Filters"); ?>"; - var lang_bandmap_band_preserved = "= __("Band filter preserved (CAT connection is active)"); ?>"; + var lang_bandmap_band_preserved = "= __("Band filter preserved (band lock is active)"); ?>"; var lang_bandmap_radio = "= __("Radio"); ?>"; var lang_bandmap_radio_none = "= __("Radio set to None - CAT connection disabled"); ?>"; var lang_bandmap_radio_tuned = "= __("Radio Tuned"); ?>"; @@ -28,9 +28,12 @@ var lang_bandmap_sent_to_form = "= __("sent to logging form"); ?>"; var lang_bandmap_cat_control = "= __("CAT Connection"); ?>"; var lang_bandmap_cat_off = "= __("Click to enable CAT connection"); ?>"; - var lang_bandmap_cat_on = "= __("CAT following radio | Click for frequency marker | Double-click to disable"); ?>"; - var lang_bandmap_cat_marker = "= __("Frequency marker active | Click to disable marker | Double-click to disable CAT"); ?>"; - var lang_bandmap_freq_changed = "= __("Frequency filter changed to"); ?>"; + var lang_bandmap_cat_on = "= __("CAT following radio - Click to disable"); ?>"; + var lang_bandmap_cat_lock_off = "= __("Click to enable band lock (requires CAT connection)"); ?>"; + var lang_bandmap_cat_lock_on = "= __("Band lock active - Click to disable"); ?>"; + var lang_bandmap_band_lock = "= __("Band Lock"); ?>"; + var lang_bandmap_band_lock_enabled = "= __("Band lock enabled - band filter will track radio band"); ?>"; + var lang_bandmap_freq_changed = "= __("Band filter changed to"); ?>"; var lang_bandmap_by_transceiver = "= __("by transceiver"); ?>"; var lang_bandmap_freq_filter_set = "= __("Frequency filter set to"); ?>"; var lang_bandmap_freq_outside = "= __("Frequency outside known bands - showing all bands"); ?>"; @@ -40,6 +43,30 @@ var lang_bandmap_modes_applied = "= __("Modes applied. Band filter preserved (CAT connection is active)"); ?>"; var lang_bandmap_favorites_applied = "= __("Applied your favorite bands and modes"); ?>"; + // My Submodes filter translations + var lang_bandmap_my_submodes = "= __("My Submodes"); ?>"; + var lang_bandmap_submodes_filter_enabled = "= __("Submode filter enabled"); ?>"; + var lang_bandmap_submodes_filter_disabled = "= __("Submode filter disabled - showing all"); ?>"; + var lang_bandmap_required_submodes = "= __("Required submodes"); ?>"; + var lang_bandmap_submodes_settings_hint = "= __("Configure in User Settings - Modes"); ?>"; + var lang_bandmap_no_submodes_configured = "= __("No submodes configured - configure in User Settings - Modes"); ?>"; + var lang_bandmap_no_submodes_warning = "= __("No submodes enabled in settings - showing all spots"); ?>"; + var lang_bandmap_mode_disabled_no_submode = "= __("Disabled - no submodes enabled for this mode in User Settings"); ?>"; + var lang_bandmap_toggle_cw = "= __("Toggle CW mode filter"); ?>"; + var lang_bandmap_toggle_digi = "= __("Toggle Digital mode filter"); ?>"; + var lang_bandmap_toggle_phone = "= __("Toggle Phone mode filter"); ?>"; + + // DX Cluster Filter Favorites translations + var lang_bandmap_filter_favorites = "= __("Favorites"); ?>"; + var lang_bandmap_save_filters = "= __("Save Current Filters..."); ?>"; + var lang_bandmap_filter_preset_name = "= __("Enter a name for this filter preset:"); ?>"; + var lang_bandmap_filter_preset_saved = "= __("Filter preset saved"); ?>"; + var lang_bandmap_filter_preset_loaded = "= __("Filter preset loaded"); ?>"; + var lang_bandmap_filter_preset_deleted = "= __("Filter preset deleted"); ?>"; + var lang_bandmap_delete_filter_confirm = "= __("Are you sure to delete this filter preset?"); ?>"; + var lang_bandmap_no_filter_presets = "= __("No saved filter presets"); ?>"; + var lang_bandmap_preset_limit_reached = "= __("Maximum of 20 filter presets reached. Please delete some before adding new ones."); ?>"; + // Bandmap filter status messages var lang_bandmap_loading_data = "= __("Loading data from DX Cluster"); ?>"; var lang_bandmap_last_fetched = "= __("Last fetched for"); ?>"; @@ -140,8 +167,8 @@ var lang_bandmap_mode = "= __("Mode"); ?>"; var lang_bandmap_band = "= __("Band"); ?>"; - // Enable ultra-compact radio status display for bandmap page (tooltip only) - window.CAT_COMPACT_MODE = 'ultra-compact'; + // Enable icon-only radio status display for bandmap page (just icon with tooltip) + window.CAT_COMPACT_MODE = 'icon-only'; // Map configuration (matches QSO map settings) var map_tile_server = 'optionslib->get_option('option_map_tile_server');?>'; @@ -180,6 +207,9 @@ " style="cursor: pointer; padding: 0.5rem; margin: -0.5rem; color: var(--bs-body-color); text-decoration: none; display: inline-flex; align-items: center;"> +