diff --git a/application/config/migration.php b/application/config/migration.php index d4853db42..3b95d5c63 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -22,7 +22,7 @@ | */ -$config['migration_version'] = 233; +$config['migration_version'] = 235; /* |-------------------------------------------------------------------------- diff --git a/application/controllers/Dashboard.php b/application/controllers/Dashboard.php index f133c0817..e7d182e06 100644 --- a/application/controllers/Dashboard.php +++ b/application/controllers/Dashboard.php @@ -227,11 +227,14 @@ function upcoming_dxcc_component() $data['thisWeekRecords'] = $thisWeekRecords; - usort($data['thisWeekRecords'], function ($a, $b) { - $dateA = new DateTime($a['1']); - $dateB = new DateTime($b['1']); - return $dateA <=> $dateB; - }); + // Only sort if we have valid records (not an error) + if (!isset($thisWeekRecords['error']) && !empty($thisWeekRecords)) { + usort($data['thisWeekRecords'], function ($a, $b) { + $dateA = new DateTime($a['1']); + $dateB = new DateTime($b['1']); + return $dateA <=> $dateB; + }); + } $this->load->view('components/upcoming_dxccs', $data); } diff --git a/application/controllers/Dxcluster.php b/application/controllers/Dxcluster.php index 1eb25a275..ef090ec04 100644 --- a/application/controllers/Dxcluster.php +++ b/application/controllers/Dxcluster.php @@ -14,6 +14,10 @@ function __construct() function index() { $data['page_title'] = "DX Cluster Spots"; + + // Load radio data for CAT control + $this->load->model('cat'); + $data['radios'] = $this->cat->radios(); /// Load layout @@ -124,4 +128,95 @@ public function qsy() { echo json_encode(['success' => false, 'message' => 'Failed to queue QSY command']); } } + + /* + * Check if callsigns have been worked + * POST /dxcluster/check_worked + */ + public function check_worked() { + header('Content-Type: application/json'); + + $this->load->model('logbook_model'); + $this->load->model('logbooks_model'); + + // Get JSON input + $input = json_decode(file_get_contents("php://input"), true); + + if (!isset($input['callsigns']) || !is_array($input['callsigns'])) { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Missing or invalid callsigns array']); + return; + } + + // Limit batch size to prevent excessive load + $max_batch_size = 50; + if (count($input['callsigns']) > $max_batch_size) { + $input['callsigns'] = array_slice($input['callsigns'], 0, $max_batch_size); + } + + // Get logbook locations for the active logbook + $logbook_id = $this->session->userdata('active_station_logbook'); + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($logbook_id); + + if (!$logbooks_locations_array) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'No logbook locations found']); + return; + } + + $results = []; + + foreach ($input['callsigns'] as $item) { + $callsign = $item['callsign']; + $band = isset($item['band']) ? $item['band'] : null; + + // Get DXCC entity for this callsign + $dxcc_info = $this->logbook_model->dxcc_lookup($callsign, date('Ymd')); + $dxcc = isset($dxcc_info['adif']) ? $dxcc_info['adif'] : null; + + // Check if worked on this band + $worked_on_band = false; + if ($band) { + $worked_on_band = $this->logbook_model->check_if_callsign_worked_in_logbook($callsign, $logbooks_locations_array, $band) > 0; + } + + // Check if worked on any band + $worked_overall = $this->logbook_model->check_if_callsign_worked_in_logbook($callsign, $logbooks_locations_array, null) > 0; + + // Check if DXCC entity is worked on this band + $dxcc_worked_on_band = false; + if ($dxcc && $band) { + $this->db->select('COL_DXCC'); + $this->db->where_in('station_id', $logbooks_locations_array); + $this->db->where('COL_DXCC', $dxcc); + $this->db->where('COL_BAND', $band); + $this->db->limit(1); + $query = $this->db->get($this->config->item('table_name')); + $dxcc_worked_on_band = $query->num_rows() > 0; + } + + // Check if DXCC entity is worked on any band + $dxcc_worked_overall = false; + if ($dxcc) { + $this->db->select('COL_DXCC'); + $this->db->where_in('station_id', $logbooks_locations_array); + $this->db->where('COL_DXCC', $dxcc); + $this->db->limit(1); + $query = $this->db->get($this->config->item('table_name')); + $dxcc_worked_overall = $query->num_rows() > 0; + } + + $results[$callsign] = [ + 'worked_on_band' => $worked_on_band, + 'worked_overall' => $worked_overall, + 'band' => $band, + 'dxcc' => $dxcc, + 'dxcc_worked_on_band' => $dxcc_worked_on_band, + 'dxcc_worked_overall' => $dxcc_worked_overall, + 'country' => isset($dxcc_info['entity']) ? $dxcc_info['entity'] : null + ]; + } + + echo json_encode(['success' => true, 'results' => $results]); + } } \ No newline at end of file diff --git a/application/controllers/Logbooks.php b/application/controllers/Logbooks.php index d99baa8b2..1af75f5fc 100644 --- a/application/controllers/Logbooks.php +++ b/application/controllers/Logbooks.php @@ -58,6 +58,12 @@ public function edit($id) $station_logbook_id = $this->security->xss_clean($id); + // Check if user has at least write access + if (!$this->logbooks_model->check_logbook_is_accessible($station_logbook_id, 'write')) { + $this->session->set_flashdata('notice', 'You don\'t have permission to edit this logbook'); + redirect('logbooks'); + } + $station_logbook_details_query = $this->logbooks_model->logbook($station_logbook_id); $data['station_locations_array'] = $this->logbooks_model->list_logbook_relationships($station_logbook_id); @@ -65,6 +71,7 @@ public function edit($id) $data['station_locations_list'] = $this->stations->all_of_user(); $data['station_locations_linked'] = $this->logbooks_model->list_logbooks_linked($station_logbook_id); + $data['is_owner'] = $this->logbooks_model->is_logbook_owner($station_logbook_id); $data['page_title'] = "Edit Station Logbook"; @@ -87,7 +94,12 @@ public function edit($id) $this->logbooks_model->create_logbook_location_link($this->input->post('station_logbook_id'), $this->input->post('SelectedStationLocation')); } } else { - $this->logbooks_model->edit(); + // Only owners can rename logbooks + if ($this->logbooks_model->is_logbook_owner($this->input->post('station_logbook_id'))) { + $this->logbooks_model->edit(); + } else { + $this->session->set_flashdata('notice', 'Only the owner can rename a logbook'); + } } redirect('logbooks/edit/'.$this->input->post('station_logbook_id')); @@ -111,7 +123,28 @@ public function delete($id) { public function delete_relationship($logbook_id, $station_id) { $this->load->model('logbooks_model'); - $this->logbooks_model->delete_relationship($logbook_id, $station_id); + $this->load->model('stations'); + + // Check if user has at least write access to the logbook + if (!$this->logbooks_model->check_logbook_is_accessible($logbook_id, 'write')) { + $this->session->set_flashdata('notice', 'You don\'t have permission to modify this logbook'); + redirect('logbooks'); + } + + // Get station location details + $station = $this->stations->profile($station_id); + + if ($station) { + $is_owner = $this->logbooks_model->is_logbook_owner($logbook_id); + $owns_station = ($station->user_id == $this->session->userdata('user_id')); + + // Only allow unlinking if user is logbook owner OR owns the station location + if ($is_owner || $owns_station) { + $this->logbooks_model->delete_relationship($logbook_id, $station_id); + } else { + $this->session->set_flashdata('notice', 'You can only unlink your own station locations'); + } + } redirect('logbooks/edit/'.$logbook_id); } @@ -131,23 +164,49 @@ public function publicslug_validate() { public function save_publicsearch() { $this->load->model('logbooks_model'); + + $logbook_id = $this->input->post('logbook_id'); + + // Only owners can modify public settings + if (!$this->logbooks_model->is_logbook_owner($logbook_id)) { + echo "
Only the owner can modify public settings
"; + return; + } + // Handle checkbox - if not checked, it won't be sent, so default to 0 $public_search = $this->input->post('public_search') ? 1 : 0; - $returndata = $this->logbooks_model->save_public_search($public_search, $this->input->post('logbook_id')); + $returndata = $this->logbooks_model->save_public_search($public_search, $logbook_id); echo "
Public Search Settings Saved
"; } public function save_publicradiostatus() { $this->load->model('logbooks_model'); + + $logbook_id = $this->input->post('logbook_id'); + + // Only owners can modify public settings + if (!$this->logbooks_model->is_logbook_owner($logbook_id)) { + echo "
Only the owner can modify public settings
"; + return; + } + // Handle checkbox - if not checked, it won't be sent, so default to 0 $public_radio_status = $this->input->post('public_radio_status') ? 1 : 0; - $returndata = $this->logbooks_model->save_public_radio_status($public_radio_status, $this->input->post('logbook_id')); + $returndata = $this->logbooks_model->save_public_radio_status($public_radio_status, $logbook_id); echo "
Public Radio Status Settings Saved
"; } public function save_publicslug() { $this->load->model('logbooks_model'); + $logbook_id = $this->input->post('logbook_id'); + + // Only owners can modify public settings + if (!$this->logbooks_model->is_logbook_owner($logbook_id)) { + echo "
Only the owner can modify public settings
"; + return; + } + $this->load->library('form_validation'); $this->form_validation->set_rules('public_slug', 'Public Slug', 'required|alpha_numeric'); @@ -164,7 +223,7 @@ public function save_publicslug() { if($result == true) { - $returndata = $this->logbooks_model->save_public_slug($this->input->post('public_slug'), $this->input->post('logbook_id')); + $returndata = $this->logbooks_model->save_public_slug($this->input->post('public_slug'), $logbook_id); echo "
Public Slug Saved
"; } else { echo "
Oops! This Public Slug is unavailable
"; @@ -175,7 +234,15 @@ public function save_publicslug() { public function remove_publicslug() { $this->load->model('logbooks_model'); - $this->logbooks_model->remove_public_slug($this->input->post('logbook_id')); + $logbook_id = $this->input->post('logbook_id'); + + // Only owners can modify public settings + if (!$this->logbooks_model->is_logbook_owner($logbook_id)) { + echo "
Only the owner can modify public settings
"; + return; + } + + $this->logbooks_model->remove_public_slug($logbook_id); if ($this->db->affected_rows() > 0) { echo "
Public Slug Removed
"; } else { @@ -183,4 +250,137 @@ public function remove_publicslug() { } } + public function manage_sharing($logbook_id) { + // Display sharing management interface + $this->load->model('logbooks_model'); + + $clean_id = $this->security->xss_clean($logbook_id); + + // Check if user has admin access or is owner + if (!$this->logbooks_model->is_logbook_owner($clean_id) && + !$this->logbooks_model->check_logbook_is_accessible($clean_id, 'admin')) { + $this->session->set_flashdata('notice', 'You\'re not allowed to manage sharing for this logbook!'); + redirect('logbooks'); + } + + $data['logbook'] = $this->logbooks_model->logbook($clean_id)->row(); + $data['collaborators'] = $this->logbooks_model->list_logbook_collaborators($clean_id); + $data['is_owner'] = $this->logbooks_model->is_logbook_owner($clean_id); + + $data['page_title'] = "Manage Logbook Sharing"; + $this->load->view('interface_assets/header', $data); + $this->load->view('logbooks/manage_sharing'); + $this->load->view('interface_assets/footer'); + } + + public function add_user() { + // Add a user to a logbook via AJAX/HTMX + $this->load->model('logbooks_model'); + + $logbook_id = $this->security->xss_clean($this->input->post('logbook_id')); + $user_identifier = $this->security->xss_clean($this->input->post('user_identifier')); + $permission_level = $this->security->xss_clean($this->input->post('permission_level')); + + // Check if current user has admin rights or is owner + if (!$this->logbooks_model->is_logbook_owner($logbook_id) && + !$this->logbooks_model->check_logbook_is_accessible($logbook_id, 'admin')) { + echo "
You don't have permission to add users to this logbook
"; + return; + } + + // Try to find user by email first, then by callsign + $user_query = $this->user_model->get_by_email($user_identifier); + if ($user_query->num_rows() == 0) { + $user_query = $this->user_model->get_by_callsign($user_identifier); + } + + if ($user_query->num_rows() == 0) { + echo "
User not found. Please check the email or callsign.
"; + return; + } + + $user = $user_query->row(); + + // Check if user is trying to add themselves + if ($user->user_id == $this->session->userdata('user_id')) { + echo "
You cannot add yourself to the logbook.
"; + return; + } + + // Check if user is the owner + if ($this->logbooks_model->get_user_permission($logbook_id, $user->user_id) == 'owner') { + echo "
This user is the owner of the logbook.
"; + return; + } + + // Add permission + $result = $this->logbooks_model->add_logbook_permission($logbook_id, $user->user_id, $permission_level); + + if ($result) { + // Return updated collaborators list + $data['collaborators'] = $this->logbooks_model->list_logbook_collaborators($logbook_id); + $data['is_owner'] = $this->logbooks_model->is_logbook_owner($logbook_id); + $this->load->view('logbooks/components/collaborators_table', $data); + } else { + echo "
Failed to add user. Please try again.
"; + } + } + + public function remove_user() { + // Remove a user from a logbook via AJAX/HTMX + $this->load->model('logbooks_model'); + + $logbook_id = $this->security->xss_clean($this->input->post('logbook_id')); + $user_id = $this->security->xss_clean($this->input->post('user_id')); + + // Check if current user has admin rights or is owner + if (!$this->logbooks_model->is_logbook_owner($logbook_id) && + !$this->logbooks_model->check_logbook_is_accessible($logbook_id, 'admin')) { + echo "
You don't have permission to remove users from this logbook
"; + return; + } + + // Remove permission + $result = $this->logbooks_model->remove_logbook_permission($logbook_id, $user_id); + + if ($result) { + // Return updated collaborators list + $data['collaborators'] = $this->logbooks_model->list_logbook_collaborators($logbook_id); + $data['is_owner'] = $this->logbooks_model->is_logbook_owner($logbook_id); + $this->load->view('logbooks/components/collaborators_table', $data); + } else { + echo "
Failed to remove user. Please try again.
"; + } + } + + public function validate_user() { + // Validate user exists via AJAX/HTMX + $this->load->model('user_model'); + + $user_identifier = $this->security->xss_clean($this->input->post('user_identifier')); + + if (empty($user_identifier)) { + echo ''; + return; + } + + // Try to find user by email first, then by callsign + $user_query = $this->user_model->get_by_email($user_identifier); + if ($user_query->num_rows() == 0) { + $user_query = $this->user_model->get_by_callsign($user_identifier); + } + + if ($user_query->num_rows() > 0) { + $user = $user_query->row(); + // Check if it's the current user + if ($user->user_id == $this->session->userdata('user_id')) { + echo ' Cannot add yourself'; + } else { + echo ' User found: ' . htmlspecialchars($user->user_callsign) . ''; + } + } else { + echo ' User not found'; + } + } + } diff --git a/application/controllers/Workabledxcc.php b/application/controllers/Workabledxcc.php index fda5adb86..3976fa77e 100644 --- a/application/controllers/Workabledxcc.php +++ b/application/controllers/Workabledxcc.php @@ -29,11 +29,24 @@ public function index() public function dxcclist() { - $json = file_get_contents($this->optionslib->get_option('dxped_url')); + try { + $json = @file_get_contents($this->optionslib->get_option('dxped_url')); + + if ($json === false) { + $data['dxcclist'] = array('error' => 'Failed to fetch DXpedition data'); + $this->load->view('/workabledxcc/components/dxcclist', $data); + return; + } + } catch (Exception $e) { + $data['dxcclist'] = array('error' => 'Error fetching DXpedition data: ' . $e->getMessage()); + $this->load->view('/workabledxcc/components/dxcclist', $data); + return; + } + $dataResult = json_decode($json, true); - if (empty($dataResult)) { - $data['dxcclist'] = array(); + if (empty($dataResult) || !is_array($dataResult)) { + $data['dxcclist'] = array('error' => 'Invalid DXpedition data received'); $this->load->view('/workabledxcc/components/dxcclist', $data); return; } diff --git a/application/migrations/234_create_logbook_permissions.php b/application/migrations/234_create_logbook_permissions.php new file mode 100644 index 000000000..909591641 --- /dev/null +++ b/application/migrations/234_create_logbook_permissions.php @@ -0,0 +1,71 @@ +db->table_exists('station_logbooks_permissions')) { + // Create station_logbooks_permissions table + $this->dbforge->add_field(array( + 'permission_id' => array( + 'type' => 'BIGINT', + 'constraint' => 20, + 'unsigned' => TRUE, + 'auto_increment' => TRUE + ), + 'logbook_id' => array( + 'type' => 'BIGINT', + 'constraint' => 20, + 'unsigned' => TRUE, + ), + 'user_id' => array( + 'type' => 'INT', + 'constraint' => 11, + ), + 'permission_level' => array( + 'type' => 'ENUM', + 'constraint' => array('read', 'write', 'admin'), + 'default' => 'read', + ), + 'created_at' => array( + 'type' => 'TIMESTAMP', + 'default' => 'CURRENT_TIMESTAMP', + ), + 'modified' => array( + 'type' => 'TIMESTAMP', + 'null' => TRUE, + ), + )); + + $this->dbforge->add_key('permission_id', TRUE); + $this->dbforge->create_table('station_logbooks_permissions'); + + // Add unique constraint on logbook_id + user_id combination + $this->db->query('CREATE UNIQUE INDEX idx_logbook_user ON station_logbooks_permissions (logbook_id, user_id)'); + + // Add indexes for performance + $this->db->query('CREATE INDEX idx_logbook_id ON station_logbooks_permissions (logbook_id)'); + $this->db->query('CREATE INDEX idx_user_id ON station_logbooks_permissions (user_id)'); + + // Add foreign key for logbook_id only (station_logbooks uses InnoDB) + // Note: Cannot add foreign key for user_id because users table uses MyISAM engine + $this->db->query('ALTER TABLE station_logbooks_permissions + ADD CONSTRAINT fk_slp_logbook + FOREIGN KEY (logbook_id) + REFERENCES station_logbooks(logbook_id) + ON DELETE CASCADE'); + } + } + + public function down() + { + $this->dbforge->drop_table('station_logbooks_permissions'); + } +} diff --git a/application/migrations/235_tag_2_8_0.php b/application/migrations/235_tag_2_8_0.php new file mode 100644 index 000000000..4c07a57fe --- /dev/null +++ b/application/migrations/235_tag_2_8_0.php @@ -0,0 +1,30 @@ +db->where('option_name', 'version'); + $this->db->update('options', array('option_value' => '2.8.0')); + + // 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.7.8')); + } +} \ No newline at end of file diff --git a/application/models/Logbooks_model.php b/application/models/Logbooks_model.php index 48ca149fb..8412a0873 100644 --- a/application/models/Logbooks_model.php +++ b/application/models/Logbooks_model.php @@ -3,8 +3,25 @@ class Logbooks_model extends CI_Model { function show_all() { - $this->db->where('user_id', $this->session->userdata('user_id')); - return $this->db->get('station_logbooks'); + // Get owned logbooks and shared logbooks with access level + $user_id = $this->session->userdata('user_id'); + + $this->db->select('station_logbooks.*, + CASE + WHEN station_logbooks.user_id = '.$this->db->escape($user_id).' THEN "owner" + ELSE slp.permission_level + END as access_level', FALSE); + $this->db->from('station_logbooks'); + $this->db->join('station_logbooks_permissions slp', + 'slp.logbook_id = station_logbooks.logbook_id AND slp.user_id = '.$this->db->escape($user_id), + 'left'); + $this->db->group_start(); + $this->db->where('station_logbooks.user_id', $user_id); + $this->db->or_where('slp.user_id', $user_id); + $this->db->group_end(); + $this->db->order_by('station_logbooks.logbook_name', 'ASC'); + + return $this->db->get(); } function CountAllStationLogbooks() { @@ -104,10 +121,21 @@ function set_logbook_active($id, $user_id = null) { function logbook($id) { // Clean ID $clean_id = $this->security->xss_clean($id); - - $this->db->where('user_id', $this->session->userdata('user_id')); - $this->db->where('logbook_id', $clean_id); - return $this->db->get('station_logbooks'); + $user_id = $this->session->userdata('user_id'); + + // Get logbook if user owns it OR has shared access + $this->db->select('station_logbooks.*'); + $this->db->from('station_logbooks'); + $this->db->join('station_logbooks_permissions slp', + 'slp.logbook_id = station_logbooks.logbook_id AND slp.user_id = '.$this->db->escape($user_id), + 'left'); + $this->db->where('station_logbooks.logbook_id', $clean_id); + $this->db->group_start(); + $this->db->where('station_logbooks.user_id', $user_id); + $this->db->or_where('slp.user_id', $user_id); + $this->db->group_end(); + + return $this->db->get(); } function find_name($id) { @@ -282,9 +310,16 @@ function list_logbooks_linked($logbook_id) { array_push($relationships_array, $row->station_location_id); } - $this->db->select('station_profile.*, dxcc_entities.name as station_country, dxcc_entities.end as end'); + $current_user_id = $this->session->userdata('user_id'); + + $this->db->select('station_profile.*, + dxcc_entities.name as station_country, + dxcc_entities.end as end, + users.user_callsign as owner_callsign, + CASE WHEN station_profile.user_id = '.$this->db->escape($current_user_id).' THEN 0 ELSE 1 END as is_shared', FALSE); $this->db->where_in('station_id', $relationships_array); $this->db->join('dxcc_entities','station_profile.station_dxcc = dxcc_entities.adif','left outer'); + $this->db->join('users','station_profile.user_id = users.user_id','left'); $query = $this->db->get('station_profile'); return $query; @@ -317,18 +352,176 @@ function delete_relationship($logbook_id, $station_id) { $this->db->delete('station_logbooks_relationship'); } - public function check_logbook_is_accessible($id) { - // check if logbook belongs to user + public function check_logbook_is_accessible($id, $min_level = 'read') { + // First check if user is the owner (existing behavior - highest priority) $this->db->select('logbook_id'); $this->db->where('user_id', $this->session->userdata('user_id')); $this->db->where('logbook_id', $id); $query = $this->db->get('station_logbooks'); if ($query->num_rows() == 1) { - return true; + return true; // Owner always has full access } + + // Check if user has shared access via permissions table + $this->db->select('permission_level'); + $this->db->where('logbook_id', $id); + $this->db->where('user_id', $this->session->userdata('user_id')); + $query = $this->db->get('station_logbooks_permissions'); + + if ($query->num_rows() == 1) { + $permission = $query->row()->permission_level; + + // Map permission levels to hierarchy + $levels = array('read' => 1, 'write' => 2, 'admin' => 3); + $user_level = isset($levels[$permission]) ? $levels[$permission] : 0; + $required_level = isset($levels[$min_level]) ? $levels[$min_level] : 1; + + return ($user_level >= $required_level); + } + return false; } + public function is_logbook_owner($id) { + // Check if current user is the owner of the logbook + $this->db->select('logbook_id'); + $this->db->where('user_id', $this->session->userdata('user_id')); + $this->db->where('logbook_id', $id); + $query = $this->db->get('station_logbooks'); + return ($query->num_rows() == 1); + } + + public function get_user_permission($logbook_id, $user_id) { + // Get the permission level for a specific user on a specific logbook + // Returns 'owner', permission level, or null + + // Check if user is owner + $this->db->select('logbook_id'); + $this->db->where('user_id', $user_id); + $this->db->where('logbook_id', $logbook_id); + $query = $this->db->get('station_logbooks'); + if ($query->num_rows() == 1) { + return 'owner'; + } + + // Check permissions table + $this->db->select('permission_level'); + $this->db->where('logbook_id', $logbook_id); + $this->db->where('user_id', $user_id); + $query = $this->db->get('station_logbooks_permissions'); + + if ($query->num_rows() == 1) { + return $query->row()->permission_level; + } + + return null; + } + + public function add_logbook_permission($logbook_id, $user_id, $permission_level = 'read') { + // Add a user to a logbook with specified permission level + // Only owner or admin can add users + + $clean_logbook_id = $this->security->xss_clean($logbook_id); + $clean_user_id = $this->security->xss_clean($user_id); + $clean_permission = $this->security->xss_clean($permission_level); + + // Validate permission level + $valid_permissions = array('read', 'write', 'admin'); + if (!in_array($clean_permission, $valid_permissions)) { + return false; + } + + // Check if current user has admin rights or is owner + if (!$this->is_logbook_owner($clean_logbook_id) && + !$this->check_logbook_is_accessible($clean_logbook_id, 'admin')) { + return false; + } + + // Check if permission already exists + $this->db->where('logbook_id', $clean_logbook_id); + $this->db->where('user_id', $clean_user_id); + $existing = $this->db->get('station_logbooks_permissions'); + + if ($existing->num_rows() > 0) { + // Update existing permission + $data = array( + 'permission_level' => $clean_permission, + 'modified' => date('Y-m-d H:i:s'), + ); + $this->db->where('logbook_id', $clean_logbook_id); + $this->db->where('user_id', $clean_user_id); + $this->db->update('station_logbooks_permissions', $data); + } else { + // Insert new permission + $data = array( + 'logbook_id' => $clean_logbook_id, + 'user_id' => $clean_user_id, + 'permission_level' => $clean_permission, + ); + $this->db->insert('station_logbooks_permissions', $data); + } + + return true; + } + + public function remove_logbook_permission($logbook_id, $user_id) { + // Remove a user's access to a logbook + // Only owner or admin can remove users + + $clean_logbook_id = $this->security->xss_clean($logbook_id); + $clean_user_id = $this->security->xss_clean($user_id); + + // Check if current user has admin rights or is owner + if (!$this->is_logbook_owner($clean_logbook_id) && + !$this->check_logbook_is_accessible($clean_logbook_id, 'admin')) { + return false; + } + + $this->db->where('logbook_id', $clean_logbook_id); + $this->db->where('user_id', $clean_user_id); + $this->db->delete('station_logbooks_permissions'); + + return true; + } + + public function list_logbook_collaborators($logbook_id) { + // Get all users with access to a logbook (excluding owner) + // Returns array of users with their permission levels + + $clean_logbook_id = $this->security->xss_clean($logbook_id); + + // Get owner information + $this->db->select('station_logbooks.user_id, users.user_callsign, users.user_email, "owner" as permission_level, "" as created_at'); + $this->db->from('station_logbooks'); + $this->db->join('users', 'users.user_id = station_logbooks.user_id'); + $this->db->where('station_logbooks.logbook_id', $clean_logbook_id); + $owner_query = $this->db->get(); + + // Get shared users + $this->db->select('slp.user_id, users.user_callsign, users.user_email, slp.permission_level, slp.created_at'); + $this->db->from('station_logbooks_permissions slp'); + $this->db->join('users', 'users.user_id = slp.user_id'); + $this->db->where('slp.logbook_id', $clean_logbook_id); + $this->db->order_by('slp.created_at', 'DESC'); + $shared_query = $this->db->get(); + + $results = array(); + + // Add owner first + if ($owner_query->num_rows() > 0) { + $results[] = $owner_query->row(); + } + + // Add shared users + if ($shared_query->num_rows() > 0) { + foreach ($shared_query->result() as $row) { + $results[] = $row; + } + } + + return $results; + } + public function find_active_station_logbook_from_userid($userid) { $this->db->select('active_station_logbook'); $this->db->where('user_id', $userid); diff --git a/application/models/User_model.php b/application/models/User_model.php index 0407a666f..f0a395458 100644 --- a/application/models/User_model.php +++ b/application/models/User_model.php @@ -55,6 +55,17 @@ function get_by_email($email) { return $r; } + // FUNCTION: object get_by_callsign($callsign) + // Retrieve a user by callsign (case-insensitive) + function get_by_callsign($callsign) { + + $clean_callsign = $this->security->xss_clean($callsign); + + $this->db->where('UPPER(user_callsign)', strtoupper($clean_callsign)); + $r = $this->db->get($this->config->item('auth_table')); + return $r; + } + /* * Function: check_email_address * diff --git a/application/models/Workabledxcc_model.php b/application/models/Workabledxcc_model.php index a09e54c32..13380cf41 100644 --- a/application/models/Workabledxcc_model.php +++ b/application/models/Workabledxcc_model.php @@ -342,11 +342,23 @@ private function batchConfirmedQuery($entities, $logbooks_locations_array, $conf public function GetThisWeek() { - $json = file_get_contents($this->optionslib->get_option('dxped_url')); + try { + $json = @file_get_contents($this->optionslib->get_option('dxped_url')); + + // Catch any errors from opening the file + if ($json === false) { + return array('error' => 'Failed to fetch DXpedition data'); + exit; + } + } catch (Exception $e) { + return array('error' => 'Error fetching DXpedition data: ' . $e->getMessage()); + exit; + } + $data = json_decode($json, true); - if (empty($data)) { - return array(); + if (empty($data) || !is_array($data)) { + return array('error' => 'Invalid DXpedition data received'); } $thisWeekRecords = []; diff --git a/application/views/components/upcoming_dxccs.php b/application/views/components/upcoming_dxccs.php index 81722036d..836470e39 100644 --- a/application/views/components/upcoming_dxccs.php +++ b/application/views/components/upcoming_dxccs.php @@ -1,3 +1,13 @@ + + + + + @@ -13,4 +23,5 @@ echo ''; } ?> -
DXPeditions (This Week)
\ No newline at end of file + + \ No newline at end of file diff --git a/application/views/contesting/index.php b/application/views/contesting/index.php index 544259895..85829d2ea 100644 --- a/application/views/contesting/index.php +++ b/application/views/contesting/index.php @@ -1,6 +1,9 @@
- -

LIVE" : " POST"); ?> +
+ + +
+

LIVE" : " POST"); ?>
@@ -99,6 +102,8 @@
+
+
@@ -219,5 +224,27 @@
- + + + diff --git a/application/views/dxcluster/bandmap.php b/application/views/dxcluster/bandmap.php index dc394c89b..e4a5a85eb 100644 --- a/application/views/dxcluster/bandmap.php +++ b/application/views/dxcluster/bandmap.php @@ -191,6 +191,42 @@ .spot-marker.new { animation: spotAppear 0.5s ease-out; } + + /* Worked status styling */ + .spot-marker.worked-on-band { + background: #27ae60; + border-color: #229954; + } + + .spot-marker.worked-on-band:hover { + background: #2ecc71; + border-color: #27ae60; + } + + .spot-marker.worked-other-band { + background: #3498db; + border-color: #2980b9; + } + + .spot-marker.worked-other-band:hover { + background: #5dade2; + border-color: #3498db; + } + + .spot-marker.not-worked { + background: #e74c3c; + border-color: #c0392b; + } + + .spot-marker.new-dxcc { + box-shadow: 0 0 10px #f39c12, 0 0 20px #f39c12; + border: 2px solid #f39c12; + } + + .spot-marker.new-band-dxcc { + box-shadow: 0 0 10px #e74c3c, 0 0 20px #e74c3c; + border: 2px solid #e74c3c; + } .tune-indicator { position: absolute; @@ -404,7 +440,7 @@
Band: 20m
-
Range: 14.000-14.350
+
Viewing: 14.000-14.350 MHz
Spots: 0
Total: 0
@@ -422,6 +458,8 @@