From 429d23fef42fafa1874ca466d420484a4c5efab2 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Thu, 18 Dec 2025 17:39:42 +0000 Subject: [PATCH 1/9] Fix undefined index error for DXCC entity name Added a check to ensure the DXCC entity is an array and contains a 'name' key before assigning 'dxcc_name' to prevent undefined index errors. --- application/controllers/Logbook.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/controllers/Logbook.php b/application/controllers/Logbook.php index 2ed4684b2..66703af82 100644 --- a/application/controllers/Logbook.php +++ b/application/controllers/Logbook.php @@ -851,7 +851,9 @@ function partial($id) if (isset($callsign['callsign']['dxcc'])) { $this->load->model('logbook_model'); $entity = $this->logbook_model->get_entity($callsign['callsign']['dxcc']); - $callsign['callsign']['dxcc_name'] = $entity['name']; + if (is_array($entity) && isset($entity['name'])) { + $callsign['callsign']['dxcc_name'] = $entity['name']; + } } } elseif ($this->session->userdata('callbook_type') == "HamQTH") { // Load the HamQTH library From 3c2d8461cb233eb9c7d4bd5852347bf7a02157a8 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 20 Dec 2025 11:10:52 +0000 Subject: [PATCH 2/9] Handle missing or inaccessible QSO in logbook view Adds a check to ensure that if a QSO is not found or not accessible, the user is redirected to the dashboard with a notice. This prevents errors when attempting to access non-existent or unauthorized QSOs. --- application/controllers/Logbook.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/application/controllers/Logbook.php b/application/controllers/Logbook.php index 66703af82..45612e66b 100644 --- a/application/controllers/Logbook.php +++ b/application/controllers/Logbook.php @@ -613,7 +613,17 @@ function view($id) $this->load->library('subdivisions'); $this->load->model('logbook_model'); + $data['query'] = $this->logbook_model->get_qso($id); + + // Check if query returned null (QSO not found or not accessible) + if ($data['query'] == null || $data['query']->num_rows() == 0) { + $this->session->set_flashdata('notice', 'QSO not found or not accessible'); + redirect('dashboard'); + return; + } + + $data['dxccFlag'] = $this->dxccflag->get($data['query']->result()[0]->COL_DXCC); if ($this->session->userdata('user_measurement_base') == NULL) { From e78dedc70e1fbfdf4961eeec04325e9d8f31dd2f Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 20 Dec 2025 11:14:31 +0000 Subject: [PATCH 3/9] Add checks for entity name in DXCC lookup Wrapped assignments of 'dxcc_name' with checks to ensure the entity is an array and contains a 'name' key. This prevents potential errors if the entity lookup fails or returns unexpected data. --- application/controllers/Logbook.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/application/controllers/Logbook.php b/application/controllers/Logbook.php index 45612e66b..de5bf8e98 100644 --- a/application/controllers/Logbook.php +++ b/application/controllers/Logbook.php @@ -896,7 +896,9 @@ function partial($id) if (isset($callsign['callsign']['dxcc'])) { $this->load->model('logbook_model'); $entity = $this->logbook_model->get_entity($callsign['callsign']['dxcc']); - $callsign['callsign']['dxcc_name'] = $entity['name']; + if (is_array($entity) && isset($entity['name'])) { + $callsign['callsign']['dxcc_name'] = $entity['name']; + } } if (isset($callsign['callsign']['error'])) { $callsign['error'] = $callsign['callsign']['error']; @@ -980,7 +982,9 @@ function search_result($id = "", $id2 = "") if (isset($data['callsign']['dxcc'])) { $this->load->model('logbook_model'); $entity = $this->logbook_model->get_entity($data['callsign']['dxcc']); - $data['callsign']['dxcc_name'] = $entity['name']; + if (is_array($entity) && isset($entity['name'])) { + $data['callsign']['dxcc_name'] = $entity['name']; + } } if (isset($data['callsign']['gridsquare'])) { $this->load->model('logbook_model'); @@ -1017,7 +1021,9 @@ function search_result($id = "", $id2 = "") if (isset($data['callsign']['dxcc'])) { $this->load->model('logbook_model'); $entity = $this->logbook_model->get_entity($data['callsign']['dxcc']); - $data['callsign']['dxcc_name'] = $entity['name']; + if (is_array($entity) && isset($entity['name'])) { + $data['callsign']['dxcc_name'] = $entity['name']; + } } if (isset($data['callsign']['error'])) { $data['error'] = $data['callsign']['error']; From 1aeec6e72de8f5a58493e9b0521970b88803f511 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 20 Dec 2025 11:25:58 +0000 Subject: [PATCH 4/9] Handle missing station_callsign in QRZ import Added a check and error log for missing station_callsign during QRZ import. This helps identify records with incomplete data and prevents potential issues when processing QSOs. --- application/controllers/Qrz.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/application/controllers/Qrz.php b/application/controllers/Qrz.php index 121cb299d..384edd376 100644 --- a/application/controllers/Qrz.php +++ b/application/controllers/Qrz.php @@ -536,10 +536,15 @@ private function loadFromFile($filepath) { } $call = str_replace("_","/",$record['call']); - $station_callsign = str_replace("_","/",$record['station_callsign']); + $station_callsign = str_replace("_","/",$record['station_callsign'] ?? ''); $band = $record['band'] ?? ''; // Ensure band exists $mode = $record['mode'] ?? ''; // Ensure mode exists + // Log if station_callsign is missing + if (!isset($record['station_callsign']) || $record['station_callsign'] === '') { + log_message('error', 'QRZ import: Missing station_callsign for QSO with callsign: ' . $call . ' on ' . $time_on); + } + // Add record data to batch $batch_data[] = [ 'time_on' => $time_on, From 1583590ecf6745c87e5f0c889775015e82c45caf Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 20 Dec 2025 21:50:21 +0000 Subject: [PATCH 5/9] Improve logbook ownership checks and permissions Updated Logbooks controller to use profile_clean for station lookup and added a notice for missing stations. Enhanced Setup_model to count logbooks a user owns or has permissions for, ensuring accurate logbook statistics for users with shared access. Fixes #3389 --- application/controllers/Logbooks.php | 4 +++- application/models/Setup_model.php | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/application/controllers/Logbooks.php b/application/controllers/Logbooks.php index 48156a4ba..ca422bedd 100644 --- a/application/controllers/Logbooks.php +++ b/application/controllers/Logbooks.php @@ -132,7 +132,7 @@ public function delete_relationship($logbook_id, $station_id) { } // Get station location details - $station = $this->stations->profile($station_id); + $station = $this->stations->profile_clean($station_id); if ($station) { $is_owner = $this->logbooks_model->is_logbook_owner($logbook_id); @@ -144,6 +144,8 @@ public function delete_relationship($logbook_id, $station_id) { } else { $this->session->set_flashdata('notice', 'You can only unlink your own station locations'); } + } else { + $this->session->set_flashdata('notice', 'Station location not found'); } redirect('logbooks/edit/'.$logbook_id); diff --git a/application/models/Setup_model.php b/application/models/Setup_model.php index eae2abd90..61fd8790b 100644 --- a/application/models/Setup_model.php +++ b/application/models/Setup_model.php @@ -11,7 +11,12 @@ function getCountryCount() { function getLogbookCount() { $userid = xss_clean($this->session->userdata('user_id')); - $sql = 'select count(*) as count from station_logbooks where user_id =' . $userid; + // Count logbooks the user owns OR has been granted permissions to + $sql = "SELECT COUNT(DISTINCT sl.logbook_id) AS count + FROM station_logbooks sl + LEFT JOIN station_logbooks_permissions slp + ON slp.logbook_id = sl.logbook_id AND slp.user_id = {$userid} + WHERE sl.user_id = {$userid} OR slp.user_id = {$userid}"; $query = $this->db->query($sql); return $query->row()->count; @@ -31,7 +36,10 @@ function getAllSetupCounts() { $sql = "SELECT (SELECT COUNT(*) FROM dxcc_entities) as country_count, - (SELECT COUNT(*) FROM station_logbooks WHERE user_id = {$userid}) as logbook_count, + (SELECT COUNT(DISTINCT sl.logbook_id) FROM station_logbooks sl + LEFT JOIN station_logbooks_permissions slp + ON slp.logbook_id = sl.logbook_id AND slp.user_id = {$userid} + WHERE sl.user_id = {$userid} OR slp.user_id = {$userid}) as logbook_count, (SELECT COUNT(*) FROM station_profile WHERE user_id = {$userid}) as location_count"; $query = $this->db->query($sql); From 3d77c3d4a0b38e3d7488192f200fadaad6c16eb7 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 20 Dec 2025 21:56:30 +0000 Subject: [PATCH 6/9] Revert "Handle missing or inaccessible QSO in logbook view" This reverts commit 3c2d8461cb233eb9c7d4bd5852347bf7a02157a8. --- application/controllers/Logbook.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/application/controllers/Logbook.php b/application/controllers/Logbook.php index de5bf8e98..31b2e2611 100644 --- a/application/controllers/Logbook.php +++ b/application/controllers/Logbook.php @@ -613,17 +613,7 @@ function view($id) $this->load->library('subdivisions'); $this->load->model('logbook_model'); - $data['query'] = $this->logbook_model->get_qso($id); - - // Check if query returned null (QSO not found or not accessible) - if ($data['query'] == null || $data['query']->num_rows() == 0) { - $this->session->set_flashdata('notice', 'QSO not found or not accessible'); - redirect('dashboard'); - return; - } - - $data['dxccFlag'] = $this->dxccflag->get($data['query']->result()[0]->COL_DXCC); if ($this->session->userdata('user_measurement_base') == NULL) { From d9e0fe0900accf0d90bad9b29a0c8d49c5ffaa3f Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 20 Dec 2025 22:03:12 +0000 Subject: [PATCH 7/9] Improve QSO access checks and error handling Enhanced the QSO access logic to allow access if the QSO belongs to the user or is part of a user's active logbook, including shared permissions. Added a guard in the controller to prevent accessing missing or inaccessible QSOs, redirecting with a notice if access is denied. --- application/controllers/Logbook.php | 6 ++++++ application/models/Logbook_model.php | 30 +++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/application/controllers/Logbook.php b/application/controllers/Logbook.php index 31b2e2611..3b5c7f98c 100644 --- a/application/controllers/Logbook.php +++ b/application/controllers/Logbook.php @@ -614,6 +614,12 @@ function view($id) $this->load->model('logbook_model'); $data['query'] = $this->logbook_model->get_qso($id); + // Guard against inaccessible or missing QSO to avoid calling result() on null + if (!$data['query'] || $data['query']->num_rows() === 0) { + $this->session->set_flashdata('notice', "You're not allowed to do that!"); + redirect('dashboard'); + return; + } $data['dxccFlag'] = $this->dxccflag->get($data['query']->result()[0]->COL_DXCC); if ($this->session->userdata('user_measurement_base') == NULL) { diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 897b958fe..be018dccd 100755 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -5026,15 +5026,39 @@ function wab_qso_details($wab) public function check_qso_is_accessible($id) { - // check if qso belongs to user + // Allow access if QSO belongs to current user OR + // if QSO's station is part of the user's active logbook (including shared permissions) + + // First: owner check (current behavior) $this->db->select($this->config->item('table_name') . '.COL_PRIMARY_KEY'); $this->db->join('station_profile', $this->config->item('table_name') . '.station_id = station_profile.station_id'); $this->db->where('station_profile.user_id', $this->session->userdata('user_id')); $this->db->where($this->config->item('table_name') . '.COL_PRIMARY_KEY', $id); - $query = $this->db->get($this->config->item('table_name')); - if ($query->num_rows() == 1) { + $ownerQuery = $this->db->get($this->config->item('table_name')); + if ($ownerQuery->num_rows() == 1) { return true; } + + // Second: shared-logbook check via active logbook relationships + $CI = &get_instance(); + $CI->load->model('logbooks_model'); + $activeLogbookId = $this->session->userdata('active_station_logbook'); + + // Ensure user has at least read access to the active logbook + if ($activeLogbookId && $CI->logbooks_model->check_logbook_is_accessible($activeLogbookId, 'read')) { + $logbooks_locations_array = $CI->logbooks_model->list_logbook_relationships($activeLogbookId); + + if (!empty($logbooks_locations_array)) { + $this->db->select($this->config->item('table_name') . '.COL_PRIMARY_KEY'); + $this->db->where_in($this->config->item('table_name') . '.station_id', $logbooks_locations_array); + $this->db->where($this->config->item('table_name') . '.COL_PRIMARY_KEY', $id); + $sharedQuery = $this->db->get($this->config->item('table_name')); + if ($sharedQuery->num_rows() == 1) { + return true; + } + } + } + return false; } From 1a62e400af173a3e641db2ab25ab8c9d44d18e62 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Mon, 22 Dec 2025 15:04:03 +0000 Subject: [PATCH 8/9] Add embed widget modal and attribution for logbooks Introduced an 'Embed Code' modal in the logbooks index to allow users to easily copy iframe code for embedding their logbook widget. Also added a 'Powered by Cloudlog' attribution to the widget view for better visibility and credit. --- application/views/logbooks/index.php | 74 ++++++++++++++++++++++++++++ application/views/widgets/qsos.php | 3 ++ 2 files changed, 77 insertions(+) diff --git a/application/views/logbooks/index.php b/application/views/logbooks/index.php index 119f863fd..82fbffbc3 100644 --- a/application/views/logbooks/index.php +++ b/application/views/logbooks/index.php @@ -122,6 +122,15 @@ class="btn btn-primary btn-sm" title="logbook_name;?>"> + public_slug != '') { ?> + + user_id == $this->session->userdata('user_id') || (isset($row->access_level) && $row->access_level == 'admin')) { ?> logbook_id; ?>" class="btn btn-info btn-sm" @@ -167,3 +176,68 @@ class="btn btn-danger btn-sm" + + + + + diff --git a/application/views/widgets/qsos.php b/application/views/widgets/qsos.php index 7bb4b02f5..5377cbb0d 100644 --- a/application/views/widgets/qsos.php +++ b/application/views/widgets/qsos.php @@ -51,6 +51,9 @@ + From 30ee758cce9db46c940a8b81131ed792a65475b6 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Tue, 23 Dec 2025 10:49:18 +0000 Subject: [PATCH 9/9] Add migration for Cloudlog version 2.8.4 Introduces migration 240 to update the application version to 2.8.4 and trigger the version info dialog for users. Updates migration configuration to reflect the new version. --- application/config/migration.php | 2 +- application/migrations/240_tag_2_8_4.php | 30 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 application/migrations/240_tag_2_8_4.php diff --git a/application/config/migration.php b/application/config/migration.php index 180989269..68aae496b 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -22,7 +22,7 @@ | */ -$config['migration_version'] = 239; +$config['migration_version'] = 240; /* |-------------------------------------------------------------------------- diff --git a/application/migrations/240_tag_2_8_4.php b/application/migrations/240_tag_2_8_4.php new file mode 100644 index 000000000..580edf374 --- /dev/null +++ b/application/migrations/240_tag_2_8_4.php @@ -0,0 +1,30 @@ +db->where('option_name', 'version'); + $this->db->update('options', array('option_value' => '2.8.4')); + + // Trigger Version Info Dialog + $this->db->where('option_type', 'version_dialog'); + $this->db->where('option_name', 'confirmed'); + $this->db->update('user_options', array('option_value' => 'false')); + + } + + public function down() + { + $this->db->where('option_name', 'version'); + $this->db->update('options', array('option_value' => '2.8.3')); + } +} \ No newline at end of file