diff --git a/application/controllers/Bandmap.php b/application/controllers/Bandmap.php index 5dcfc9c6b..15788d870 100644 --- a/application/controllers/Bandmap.php +++ b/application/controllers/Bandmap.php @@ -1,137 +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 active bands and modes/submodes - function get_user_bands_and_modes() { - 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 - ]; - $submodes = []; // List of all enabled 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; - } - - // Build submode identifier - use submode if available, otherwise just mode - $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([ - 'bands' => $bandList, - 'modes' => $modeCategories, - 'submodes' => $submodes - ]); - } -} +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 04ff09d76..e44fabceb 100644 --- a/application/controllers/User_options.php +++ b/application/controllers/User_options.php @@ -1,136 +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')); - } - - /** - * 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 get_dxcluster_fav() { - $result = $this->user_options_model->get_options('DXClusterFavourite'); - $jsonout = []; - foreach($result->result() as $options) { - $value = $options->option_value; - // Try to decode JSON arrays - check if it looks like JSON first - if (is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) { - $decoded = json_decode($value, true); - if (json_last_error() === JSON_ERROR_NONE) { - $value = $decoded; - } - } - $jsonout[$options->option_name][$options->option_key] = $value; - } - 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); - } -} - - -?> +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/assets/js/sections/bandmap_list.js b/assets/js/sections/bandmap_list.js index bbeeb504b..e493cdbec 100644 --- a/assets/js/sections/bandmap_list.js +++ b/assets/js/sections/bandmap_list.js @@ -2106,12 +2106,18 @@ $(function() { function getDxClusterFavs() { $.ajax({ - url: base_url + 'index.php/user_options/get_dxcluster_fav', + url: base_url + 'index.php/user_options/get_dxcluster_user_favs_and_settings', method: 'GET', dataType: 'json', success: function(result) { - dxclusterFavs = result; + // Handle combined response with favorites and userConfig + dxclusterFavs = result.favorites || {}; renderDxClusterFavMenu(); + + // Process user config (bands/modes/submodes) + if (result.userConfig) { + processUserConfig(result.userConfig); + } } }); } @@ -3812,72 +3818,58 @@ $(function() { enableBandFilterControls(); // ======================================== - // CACHE USER FAVORITES ON PAGE LOAD + // PROCESS USER CONFIG (called from getDxClusterFavs) // ======================================== /** - * Fetch and cache user bands/modes on page load - * Initializes My Submodes filter with user's enabled submodes + * Process user bands/modes configuration + * Called from getDxClusterFavs() when userConfig is included in response + * @param {Object} data - User configuration object with bands, modes, submodes */ - function fetchUserBandsAndModes() { - let base_url = dxcluster_provider.replace('/dxcluster', ''); - $.ajax({ - url: base_url + '/bandmap/get_user_bands_and_modes', - method: 'GET', - dataType: 'json', - success: function(data) { - cachedUserFavorites = data; + function processUserConfig(data) { + if (!data) return; + + cachedUserFavorites = data; - // Store mode categories for button enabling/disabling - if (data.modes) { - userModeCategories = { - cw: data.modes.cw || false, - phone: data.modes.phone || false, - digi: data.modes.digi || false - }; - } + // Store mode categories for button enabling/disabling + if (data.modes) { + userModeCategories = { + cw: data.modes.cw || false, + phone: data.modes.phone || false, + digi: data.modes.digi || false + }; + } - // Store submodes for filtering - if (data.submodes && data.submodes.length > 0) { - userEnabledSubmodes = data.submodes; - isMySubmodesFilterActive = true; // Enable filter by default - updateMySubmodesButtonVisual(); - updateMySubmodesTooltip(); - updateModeButtonsForSubmodes(); - // Sync to requiredFlags select - syncMySubmodesToRequiredFlags(); - // Reapply filters to activate submode filtering - applyFilters(false); - } else { - // No submodes configured - disable button and show warning - userEnabledSubmodes = []; - isMySubmodesFilterActive = false; - $('#toggleMySubmodesFilter').prop('disabled', true).addClass('disabled'); - updateMySubmodesButtonVisual(); - updateMySubmodesTooltip(); - // Also disable the option in requiredFlags select - $('#requiredFlags option[value="mysubmodes"]').prop('disabled', true); - showToast( - lang_bandmap_my_submodes, - lang_bandmap_no_submodes_warning, - 'bg-warning text-dark', - 5000 - ); - } - }, - error: function() { - console.warn('Failed to fetch user bands and modes'); - cachedUserFavorites = null; - userEnabledSubmodes = []; - $('#toggleMySubmodesFilter').prop('disabled', true).addClass('disabled'); - $('#requiredFlags option[value="mysubmodes"]').prop('disabled', true); - updateMySubmodesTooltip(); - } - }); + // Store submodes for filtering + if (data.submodes && data.submodes.length > 0) { + userEnabledSubmodes = data.submodes; + isMySubmodesFilterActive = true; // Enable filter by default + updateMySubmodesButtonVisual(); + updateMySubmodesTooltip(); + updateModeButtonsForSubmodes(); + // Sync to requiredFlags select + syncMySubmodesToRequiredFlags(); + // Reapply filters to activate submode filtering + applyFilters(false); + } else { + // No submodes configured - disable button and show warning + userEnabledSubmodes = []; + isMySubmodesFilterActive = false; + $('#toggleMySubmodesFilter').prop('disabled', true).addClass('disabled'); + updateMySubmodesButtonVisual(); + updateMySubmodesTooltip(); + // Also disable the option in requiredFlags select + $('#requiredFlags option[value="mysubmodes"]').prop('disabled', true); + showToast( + lang_bandmap_my_submodes, + lang_bandmap_no_submodes_warning, + 'bg-warning text-dark', + 5000 + ); + } } - // Fetch user bands/modes on page load - fetchUserBandsAndModes(); + // Note: User config is now loaded via combined getDxClusterFavs() API response // ======================================== // AGE AUTO-UPDATE