Skip to content

Commit b287af6

Browse files
committed
Add multi-user logbook sharing and permissions
Introduces logbook sharing with read, write, and admin permission levels via a new station_logbooks_permissions table and related migration. Updates controllers, models, and views to support managing collaborators, restricts sensitive actions to owners/admins, and adds UI for sharing management. Also adds user lookup by callsign and improves logbook/station location access logic.
1 parent edf58da commit b287af6

9 files changed

Lines changed: 717 additions & 25 deletions

File tree

application/config/migration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
|
2323
*/
2424

25-
$config['migration_version'] = 233;
25+
$config['migration_version'] = 234;
2626

2727
/*
2828
|--------------------------------------------------------------------------

application/controllers/Logbooks.php

Lines changed: 206 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,20 @@ public function edit($id)
5858

5959
$station_logbook_id = $this->security->xss_clean($id);
6060

61+
// Check if user has at least write access
62+
if (!$this->logbooks_model->check_logbook_is_accessible($station_logbook_id, 'write')) {
63+
$this->session->set_flashdata('notice', 'You don\'t have permission to edit this logbook');
64+
redirect('logbooks');
65+
}
66+
6167
$station_logbook_details_query = $this->logbooks_model->logbook($station_logbook_id);
6268
$data['station_locations_array'] = $this->logbooks_model->list_logbook_relationships($station_logbook_id);
6369

6470
$data['station_logbook_details'] = $station_logbook_details_query->row();
6571
$data['station_locations_list'] = $this->stations->all_of_user();
6672

6773
$data['station_locations_linked'] = $this->logbooks_model->list_logbooks_linked($station_logbook_id);
74+
$data['is_owner'] = $this->logbooks_model->is_logbook_owner($station_logbook_id);
6875

6976
$data['page_title'] = "Edit Station Logbook";
7077

@@ -87,7 +94,12 @@ public function edit($id)
8794
$this->logbooks_model->create_logbook_location_link($this->input->post('station_logbook_id'), $this->input->post('SelectedStationLocation'));
8895
}
8996
} else {
90-
$this->logbooks_model->edit();
97+
// Only owners can rename logbooks
98+
if ($this->logbooks_model->is_logbook_owner($this->input->post('station_logbook_id'))) {
99+
$this->logbooks_model->edit();
100+
} else {
101+
$this->session->set_flashdata('notice', 'Only the owner can rename a logbook');
102+
}
91103
}
92104

93105
redirect('logbooks/edit/'.$this->input->post('station_logbook_id'));
@@ -111,7 +123,28 @@ public function delete($id) {
111123

112124
public function delete_relationship($logbook_id, $station_id) {
113125
$this->load->model('logbooks_model');
114-
$this->logbooks_model->delete_relationship($logbook_id, $station_id);
126+
$this->load->model('stations');
127+
128+
// Check if user has at least write access to the logbook
129+
if (!$this->logbooks_model->check_logbook_is_accessible($logbook_id, 'write')) {
130+
$this->session->set_flashdata('notice', 'You don\'t have permission to modify this logbook');
131+
redirect('logbooks');
132+
}
133+
134+
// Get station location details
135+
$station = $this->stations->profile($station_id);
136+
137+
if ($station) {
138+
$is_owner = $this->logbooks_model->is_logbook_owner($logbook_id);
139+
$owns_station = ($station->user_id == $this->session->userdata('user_id'));
140+
141+
// Only allow unlinking if user is logbook owner OR owns the station location
142+
if ($is_owner || $owns_station) {
143+
$this->logbooks_model->delete_relationship($logbook_id, $station_id);
144+
} else {
145+
$this->session->set_flashdata('notice', 'You can only unlink your own station locations');
146+
}
147+
}
115148

116149
redirect('logbooks/edit/'.$logbook_id);
117150
}
@@ -131,23 +164,49 @@ public function publicslug_validate() {
131164

132165
public function save_publicsearch() {
133166
$this->load->model('logbooks_model');
167+
168+
$logbook_id = $this->input->post('logbook_id');
169+
170+
// Only owners can modify public settings
171+
if (!$this->logbooks_model->is_logbook_owner($logbook_id)) {
172+
echo "<div class=\"alert alert-danger\" role=\"alert\">Only the owner can modify public settings</div>";
173+
return;
174+
}
175+
134176
// Handle checkbox - if not checked, it won't be sent, so default to 0
135177
$public_search = $this->input->post('public_search') ? 1 : 0;
136-
$returndata = $this->logbooks_model->save_public_search($public_search, $this->input->post('logbook_id'));
178+
$returndata = $this->logbooks_model->save_public_search($public_search, $logbook_id);
137179
echo "<div class=\"alert alert-success\" role=\"alert\">Public Search Settings Saved</div>";
138180
}
139181

140182
public function save_publicradiostatus() {
141183
$this->load->model('logbooks_model');
184+
185+
$logbook_id = $this->input->post('logbook_id');
186+
187+
// Only owners can modify public settings
188+
if (!$this->logbooks_model->is_logbook_owner($logbook_id)) {
189+
echo "<div class=\"alert alert-danger\" role=\"alert\">Only the owner can modify public settings</div>";
190+
return;
191+
}
192+
142193
// Handle checkbox - if not checked, it won't be sent, so default to 0
143194
$public_radio_status = $this->input->post('public_radio_status') ? 1 : 0;
144-
$returndata = $this->logbooks_model->save_public_radio_status($public_radio_status, $this->input->post('logbook_id'));
195+
$returndata = $this->logbooks_model->save_public_radio_status($public_radio_status, $logbook_id);
145196
echo "<div class=\"alert alert-success\" role=\"alert\">Public Radio Status Settings Saved</div>";
146197
}
147198

148199
public function save_publicslug() {
149200
$this->load->model('logbooks_model');
150201

202+
$logbook_id = $this->input->post('logbook_id');
203+
204+
// Only owners can modify public settings
205+
if (!$this->logbooks_model->is_logbook_owner($logbook_id)) {
206+
echo "<div class=\"alert alert-danger\" role=\"alert\">Only the owner can modify public settings</div>";
207+
return;
208+
}
209+
151210
$this->load->library('form_validation');
152211

153212
$this->form_validation->set_rules('public_slug', 'Public Slug', 'required|alpha_numeric');
@@ -164,7 +223,7 @@ public function save_publicslug() {
164223

165224

166225
if($result == true) {
167-
$returndata = $this->logbooks_model->save_public_slug($this->input->post('public_slug'), $this->input->post('logbook_id'));
226+
$returndata = $this->logbooks_model->save_public_slug($this->input->post('public_slug'), $logbook_id);
168227
echo "<div class=\"alert alert-success\" role=\"alert\">Public Slug Saved</div>";
169228
} else {
170229
echo "<div class=\"alert alert-danger\" role=\"alert\">Oops! This Public Slug is unavailable</div>";
@@ -175,12 +234,153 @@ public function save_publicslug() {
175234
public function remove_publicslug() {
176235
$this->load->model('logbooks_model');
177236

178-
$this->logbooks_model->remove_public_slug($this->input->post('logbook_id'));
237+
$logbook_id = $this->input->post('logbook_id');
238+
239+
// Only owners can modify public settings
240+
if (!$this->logbooks_model->is_logbook_owner($logbook_id)) {
241+
echo "<div class=\"alert alert-danger\" role=\"alert\">Only the owner can modify public settings</div>";
242+
return;
243+
}
244+
245+
$this->logbooks_model->remove_public_slug($logbook_id);
179246
if ($this->db->affected_rows() > 0) {
180247
echo "<div class=\"alert alert-success\" role=\"alert\">Public Slug Removed</div>";
181248
} else {
182249
echo "<div class=\"alert alert-danger\" role=\"alert\">Oops! This Public Slug could not be removed</div>";
183250
}
184251
}
185252

253+
public function manage_sharing($logbook_id) {
254+
// Display sharing management interface
255+
$this->load->model('logbooks_model');
256+
257+
$clean_id = $this->security->xss_clean($logbook_id);
258+
259+
// Check if user has admin access or is owner
260+
if (!$this->logbooks_model->is_logbook_owner($clean_id) &&
261+
!$this->logbooks_model->check_logbook_is_accessible($clean_id, 'admin')) {
262+
$this->session->set_flashdata('notice', 'You\'re not allowed to manage sharing for this logbook!');
263+
redirect('logbooks');
264+
}
265+
266+
$data['logbook'] = $this->logbooks_model->logbook($clean_id)->row();
267+
$data['collaborators'] = $this->logbooks_model->list_logbook_collaborators($clean_id);
268+
$data['is_owner'] = $this->logbooks_model->is_logbook_owner($clean_id);
269+
270+
$data['page_title'] = "Manage Logbook Sharing";
271+
$this->load->view('interface_assets/header', $data);
272+
$this->load->view('logbooks/manage_sharing');
273+
$this->load->view('interface_assets/footer');
274+
}
275+
276+
public function add_user() {
277+
// Add a user to a logbook via AJAX/HTMX
278+
$this->load->model('logbooks_model');
279+
280+
$logbook_id = $this->security->xss_clean($this->input->post('logbook_id'));
281+
$user_identifier = $this->security->xss_clean($this->input->post('user_identifier'));
282+
$permission_level = $this->security->xss_clean($this->input->post('permission_level'));
283+
284+
// Check if current user has admin rights or is owner
285+
if (!$this->logbooks_model->is_logbook_owner($logbook_id) &&
286+
!$this->logbooks_model->check_logbook_is_accessible($logbook_id, 'admin')) {
287+
echo "<div class=\"alert alert-danger\" role=\"alert\">You don't have permission to add users to this logbook</div>";
288+
return;
289+
}
290+
291+
// Try to find user by email first, then by callsign
292+
$user_query = $this->user_model->get_by_email($user_identifier);
293+
if ($user_query->num_rows() == 0) {
294+
$user_query = $this->user_model->get_by_callsign($user_identifier);
295+
}
296+
297+
if ($user_query->num_rows() == 0) {
298+
echo "<div class=\"alert alert-danger\" role=\"alert\">User not found. Please check the email or callsign.</div>";
299+
return;
300+
}
301+
302+
$user = $user_query->row();
303+
304+
// Check if user is trying to add themselves
305+
if ($user->user_id == $this->session->userdata('user_id')) {
306+
echo "<div class=\"alert alert-danger\" role=\"alert\">You cannot add yourself to the logbook.</div>";
307+
return;
308+
}
309+
310+
// Check if user is the owner
311+
if ($this->logbooks_model->get_user_permission($logbook_id, $user->user_id) == 'owner') {
312+
echo "<div class=\"alert alert-danger\" role=\"alert\">This user is the owner of the logbook.</div>";
313+
return;
314+
}
315+
316+
// Add permission
317+
$result = $this->logbooks_model->add_logbook_permission($logbook_id, $user->user_id, $permission_level);
318+
319+
if ($result) {
320+
// Return updated collaborators list
321+
$data['collaborators'] = $this->logbooks_model->list_logbook_collaborators($logbook_id);
322+
$data['is_owner'] = $this->logbooks_model->is_logbook_owner($logbook_id);
323+
$this->load->view('logbooks/components/collaborators_table', $data);
324+
} else {
325+
echo "<div class=\"alert alert-danger\" role=\"alert\">Failed to add user. Please try again.</div>";
326+
}
327+
}
328+
329+
public function remove_user() {
330+
// Remove a user from a logbook via AJAX/HTMX
331+
$this->load->model('logbooks_model');
332+
333+
$logbook_id = $this->security->xss_clean($this->input->post('logbook_id'));
334+
$user_id = $this->security->xss_clean($this->input->post('user_id'));
335+
336+
// Check if current user has admin rights or is owner
337+
if (!$this->logbooks_model->is_logbook_owner($logbook_id) &&
338+
!$this->logbooks_model->check_logbook_is_accessible($logbook_id, 'admin')) {
339+
echo "<div class=\"alert alert-danger\" role=\"alert\">You don't have permission to remove users from this logbook</div>";
340+
return;
341+
}
342+
343+
// Remove permission
344+
$result = $this->logbooks_model->remove_logbook_permission($logbook_id, $user_id);
345+
346+
if ($result) {
347+
// Return updated collaborators list
348+
$data['collaborators'] = $this->logbooks_model->list_logbook_collaborators($logbook_id);
349+
$data['is_owner'] = $this->logbooks_model->is_logbook_owner($logbook_id);
350+
$this->load->view('logbooks/components/collaborators_table', $data);
351+
} else {
352+
echo "<div class=\"alert alert-danger\" role=\"alert\">Failed to remove user. Please try again.</div>";
353+
}
354+
}
355+
356+
public function validate_user() {
357+
// Validate user exists via AJAX/HTMX
358+
$this->load->model('user_model');
359+
360+
$user_identifier = $this->security->xss_clean($this->input->post('user_identifier'));
361+
362+
if (empty($user_identifier)) {
363+
echo '';
364+
return;
365+
}
366+
367+
// Try to find user by email first, then by callsign
368+
$user_query = $this->user_model->get_by_email($user_identifier);
369+
if ($user_query->num_rows() == 0) {
370+
$user_query = $this->user_model->get_by_callsign($user_identifier);
371+
}
372+
373+
if ($user_query->num_rows() > 0) {
374+
$user = $user_query->row();
375+
// Check if it's the current user
376+
if ($user->user_id == $this->session->userdata('user_id')) {
377+
echo '<span class="text-warning"><i class="fas fa-exclamation-triangle"></i> Cannot add yourself</span>';
378+
} else {
379+
echo '<span class="text-success"><i class="fas fa-check-circle"></i> User found: ' . htmlspecialchars($user->user_callsign) . '</span>';
380+
}
381+
} else {
382+
echo '<span class="text-danger"><i class="fas fa-times-circle"></i> User not found</span>';
383+
}
384+
}
385+
186386
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
defined('BASEPATH') OR exit('No direct script access allowed');
4+
5+
/*
6+
* Create station_logbooks_permissions table for multi-user logbook sharing
7+
*/
8+
9+
class Migration_create_logbook_permissions extends CI_Migration {
10+
11+
public function up()
12+
{
13+
// Check if table already exists
14+
if (!$this->db->table_exists('station_logbooks_permissions')) {
15+
// Create station_logbooks_permissions table
16+
$this->dbforge->add_field(array(
17+
'permission_id' => array(
18+
'type' => 'BIGINT',
19+
'constraint' => 20,
20+
'unsigned' => TRUE,
21+
'auto_increment' => TRUE
22+
),
23+
'logbook_id' => array(
24+
'type' => 'BIGINT',
25+
'constraint' => 20,
26+
'unsigned' => TRUE,
27+
),
28+
'user_id' => array(
29+
'type' => 'INT',
30+
'constraint' => 11,
31+
),
32+
'permission_level' => array(
33+
'type' => 'ENUM',
34+
'constraint' => array('read', 'write', 'admin'),
35+
'default' => 'read',
36+
),
37+
'created_at' => array(
38+
'type' => 'TIMESTAMP',
39+
'default' => 'CURRENT_TIMESTAMP',
40+
),
41+
'modified' => array(
42+
'type' => 'TIMESTAMP',
43+
'null' => TRUE,
44+
),
45+
));
46+
47+
$this->dbforge->add_key('permission_id', TRUE);
48+
$this->dbforge->create_table('station_logbooks_permissions');
49+
50+
// Add unique constraint on logbook_id + user_id combination
51+
$this->db->query('CREATE UNIQUE INDEX idx_logbook_user ON station_logbooks_permissions (logbook_id, user_id)');
52+
53+
// Add indexes for performance
54+
$this->db->query('CREATE INDEX idx_logbook_id ON station_logbooks_permissions (logbook_id)');
55+
$this->db->query('CREATE INDEX idx_user_id ON station_logbooks_permissions (user_id)');
56+
57+
// Add foreign key for logbook_id only (station_logbooks uses InnoDB)
58+
// Note: Cannot add foreign key for user_id because users table uses MyISAM engine
59+
$this->db->query('ALTER TABLE station_logbooks_permissions
60+
ADD CONSTRAINT fk_slp_logbook
61+
FOREIGN KEY (logbook_id)
62+
REFERENCES station_logbooks(logbook_id)
63+
ON DELETE CASCADE');
64+
}
65+
}
66+
67+
public function down()
68+
{
69+
$this->dbforge->drop_table('station_logbooks_permissions');
70+
}
71+
}

0 commit comments

Comments
 (0)