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 "
";
@@ -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 "
";
} 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 "
";
+ }
+ }
+
+ 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
";
+ }
+ }
+
+ 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 @@
+
+
+
+
+
+
+
DXPeditions (This Week)
@@ -13,4 +23,5 @@
echo '
';
}
?>
-
\ 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 @@