cloudlog/application/controllers/Notes.php
Peter Goodhall 5d034d0963 Add public Station Diary with images & RSS
Introduce public Station Diary functionality: add global config flag, routes, controller, model changes, views and DB migrations. New migrations (252-254) add is_public/include_qso_summary, diary_images table and logbook_id on notes. Notes model updated to handle public flags, diary images, qso summaries, caching (per-user cache version), and helper methods for listing/counting public diary entries. New Stationdiary controller serves paginated public pages and RSS feed with caching. Notes controller updated to handle image uploads, attach images to diary entries, and provide an AJAX endpoint to delete images. Views updated to support public visibility options, logbook selector, image upload UI and Quill editor assets; routes and migration version bumped accordingly. Includes file upload example added to uploads/diary.
2026-03-08 11:42:41 +00:00

359 lines
No EOL
11 KiB
PHP

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Notes extends CI_Controller {
function __construct()
{
parent::__construct();
$this->load->model('user_model');
if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); }
// Load language files
$this->lang->load('notes');
}
private function is_public_station_diary_enabled() {
$configValue = $this->config->item('public_station_diary_enabled');
if ($configValue === NULL) {
$configValue = TRUE;
}
$optionValue = $this->optionslib->get_option('public_station_diary_enabled');
if ($optionValue !== NULL && $optionValue !== '') {
return ($optionValue === '1' || $optionValue === 'true' || $optionValue === 1 || $optionValue === TRUE);
}
return (bool)$configValue;
}
private function handle_diary_images_upload($note_id) {
if (!isset($_FILES['diary_images']) || !isset($_FILES['diary_images']['name']) || !is_array($_FILES['diary_images']['name'])) {
return null;
}
$names = array_filter($_FILES['diary_images']['name']);
if (empty($names)) {
return null;
}
$noteResult = $this->note->view($note_id);
if ($noteResult->num_rows() === 0) {
return 'Unable to attach images: note not found.';
}
$noteRow = $noteResult->row();
if (strtoupper(trim((string)$noteRow->cat)) !== 'STATION DIARY') {
return 'Images are only supported for Station Diary entries.';
}
$user_id = (int)$this->session->userdata('user_id');
$uploadDir = FCPATH . 'uploads/diary/' . $user_id . '/';
if (!is_dir($uploadDir)) {
if (!@mkdir($uploadDir, 0755, TRUE)) {
return 'Unable to create upload directory.';
}
}
$this->load->library('upload');
$this->load->library('image_lib');
$errors = array();
$savedImages = array();
$totalFiles = count($_FILES['diary_images']['name']);
for ($i = 0; $i < $totalFiles; $i++) {
if (empty($_FILES['diary_images']['name'][$i])) {
continue;
}
$_FILES['single_diary_image']['name'] = $_FILES['diary_images']['name'][$i];
$_FILES['single_diary_image']['type'] = $_FILES['diary_images']['type'][$i];
$_FILES['single_diary_image']['tmp_name'] = $_FILES['diary_images']['tmp_name'][$i];
$_FILES['single_diary_image']['error'] = $_FILES['diary_images']['error'][$i];
$_FILES['single_diary_image']['size'] = $_FILES['diary_images']['size'][$i];
$uploadConfig = array(
'upload_path' => $uploadDir,
'allowed_types' => 'jpg|jpeg|png|gif|webp',
'max_size' => 2048,
'encrypt_name' => TRUE,
'detect_mime' => TRUE,
'mod_mime_fix' => TRUE,
);
$this->upload->initialize($uploadConfig);
if (!$this->upload->do_upload('single_diary_image')) {
$errors[] = trim(strip_tags($this->upload->display_errors('', '')));
continue;
}
$uploadData = $this->upload->data();
$imageConfig = array(
'image_library' => 'gd2',
'source_image' => $uploadData['full_path'],
'maintain_ratio' => TRUE,
'quality' => '80%',
'master_dim' => 'width',
'width' => 1600,
'height' => 1600,
);
$this->image_lib->clear();
$this->image_lib->initialize($imageConfig);
$this->image_lib->resize();
$savedImages[] = array(
'filename' => 'uploads/diary/' . $user_id . '/' . $uploadData['file_name'],
);
}
if (!empty($savedImages)) {
$this->note->add_diary_images($note_id, $savedImages);
}
if (!empty($errors)) {
return implode(' ', $errors);
}
return null;
}
/* Displays all notes in a list */
public function index()
{
$this->load->model('note');
$filters = array(
'search' => $this->input->get('q', TRUE),
'category' => $this->input->get('category', TRUE),
'date_from' => $this->input->get('date_from', TRUE),
'date_to' => $this->input->get('date_to', TRUE)
);
$data['filters'] = $filters;
$data['categories'] = $this->note->list_categories();
$data['notes'] = $this->note->list_all(null, $filters);
$data['public_station_diary_enabled'] = $this->is_public_station_diary_enabled();
$data['public_diary_url'] = site_url('station-diary/' . rawurlencode((string)$this->session->userdata('user_callsign')));
// Check if there are any Station Diary entries
$diary_filter = array('category' => 'Station Diary');
$diary_entries = $this->note->list_all(null, $diary_filter);
$data['has_diary_entries'] = $diary_entries->num_rows() > 0;
$data['page_title'] = "Notes";
$this->load->view('interface_assets/header', $data);
$this->load->view('notes/main');
$this->load->view('interface_assets/footer');
}
/* Provides function for adding notes to the system. */
function add() {
$this->load->model('note');
$this->load->model('logbooks_model');
$data['categories'] = $this->note->list_categories();
$data['public_station_diary_enabled'] = $this->is_public_station_diary_enabled();
$data['user_logbooks'] = $this->logbooks_model->show_all();
$this->load->library('form_validation');
$this->form_validation->set_rules('title', 'Note Title', 'required');
$this->form_validation->set_rules('content', 'Content', 'required');
if ($this->form_validation->run() == FALSE)
{
$data['page_title'] = "Add Notes";
$this->load->view('interface_assets/header', $data);
$this->load->view('notes/add');
$this->load->view('interface_assets/footer');
}
else
{
$note_id = $this->note->add();
$upload_error = $this->handle_diary_images_upload($note_id);
if (!empty($upload_error)) {
$this->session->set_flashdata('notice', $upload_error);
}
redirect('notes');
}
}
/* Quick add note via HTMX (for Station Diary modal) */
function quick_add() {
$this->load->model('note');
$this->load->library('form_validation');
$this->form_validation->set_rules('title', 'Note Title', 'required');
$this->form_validation->set_rules('content', 'Content', 'required');
$this->form_validation->set_rules('category', 'Category', 'required');
if ($this->form_validation->run() == FALSE) {
echo '<div class="alert alert-danger">' . validation_errors() . '</div>';
} else {
$note_id = $this->note->add();
$upload_error = $this->handle_diary_images_upload($note_id);
$message = 'Note saved successfully! <a href="' . site_url('notes') . '">View all notes</a>';
if (!empty($upload_error)) {
$message .= '<br><small>' . $upload_error . '</small>';
}
echo '<div class="alert alert-success">' . $message . '</div>';
// Reset form via JavaScript
echo '<script>setTimeout(function(){ document.getElementById("stationDiaryForm").reset(); if (typeof htmx !== "undefined") { htmx.trigger("#stationDiaryForm", "reset"); } }, 1500);</script>';
}
}
/* View Notes */
function view($id) {
$this->load->model('note');
$data['note'] = $this->note->view($id);
$data['diary_images'] = $this->note->get_diary_images(array((int)$id));
// Display
$data['page_title'] = "Note";
$this->load->view('interface_assets/header', $data);
$this->load->view('notes/view');
$this->load->view('interface_assets/footer');
}
/* Edit Notes */
function edit($id) {
$this->load->model('note');
$this->load->model('logbooks_model');
$data['id'] = $id;
$data['note'] = $this->note->view($id);
$data['categories'] = $this->note->list_categories();
$data['diary_images'] = $this->note->get_diary_images(array((int)$id));
$data['public_station_diary_enabled'] = $this->is_public_station_diary_enabled();
$data['user_logbooks'] = $this->logbooks_model->show_all();
$this->load->library('form_validation');
$this->form_validation->set_rules('title', 'Note Title', 'required');
$this->form_validation->set_rules('content', 'Content', 'required');
if ($this->form_validation->run() == FALSE)
{
$data['page_title'] = "Edit Note";
$this->load->view('interface_assets/header', $data);
$this->load->view('notes/edit');
$this->load->view('interface_assets/footer');
}
else
{
$note_id = $this->note->edit();
$upload_error = $this->handle_diary_images_upload($note_id);
if (!empty($upload_error)) {
$this->session->set_flashdata('notice', $upload_error);
}
redirect('notes');
}
}
/* Delete Note */
function delete() {
// Enforce POST for destructive action
if (strtolower($this->input->method()) !== 'post') {
$this->session->set_flashdata('notice', $this->lang->line('general_word_warning') . ': invalid request method.');
redirect('notes');
return;
}
$id = $this->input->post('id', TRUE);
if (empty($id)) {
$this->session->set_flashdata('notice', $this->lang->line('general_word_warning') . ': missing note id.');
redirect('notes');
return;
}
$this->load->model('note');
$this->note->delete($id);
$this->session->set_flashdata('notice', $this->lang->line('admin_delete') ?: 'Deleted');
redirect('notes');
}
/* Delete Diary Image (AJAX) */
function delete_diary_image() {
// Enforce POST for destructive action
if (strtolower($this->input->method()) !== 'post') {
header('Content-Type: application/json');
echo json_encode(array('success' => false, 'message' => 'Invalid request method'));
return;
}
$image_id = $this->input->post('image_id', TRUE);
if (empty($image_id)) {
header('Content-Type: application/json');
echo json_encode(array('success' => false, 'message' => 'Missing image ID'));
return;
}
$this->load->model('note');
$user_id = $this->session->userdata('user_id');
$result = $this->note->delete_diary_image_by_id($image_id, $user_id);
header('Content-Type: application/json');
if ($result) {
echo json_encode(array('success' => true, 'message' => 'Image deleted successfully'));
} else {
echo json_encode(array('success' => false, 'message' => 'Image not found or permission denied'));
}
}
/* Print Station Diary */
public function station_diary() {
$this->load->model('note');
$filters = array(
'category' => 'Station Diary'
);
$data['diary_entries'] = $this->note->list_all(null, $filters);
$data['page_title'] = "Station Diary";
// Load without header/footer for print formatting
$this->load->view('notes/station_diary_print', $data);
}
/* Delete/Merge Category */
function delete_category() {
if (strtolower($this->input->method()) !== 'post') {
$this->session->set_flashdata('notice', $this->lang->line('general_word_warning') . ': invalid request method.');
redirect('notes');
return;
}
$source = trim($this->input->post('source_category', TRUE));
$target = trim($this->input->post('target_category', TRUE));
if ($source === '') {
$this->session->set_flashdata('notice', $this->lang->line('general_word_warning') . ': missing source category.');
redirect('notes');
return;
}
if ($target === '') {
$target = 'General';
}
if ($source === $target) {
$this->session->set_flashdata('notice', $this->lang->line('general_word_warning') . ': choose a different target category.');
redirect('notes');
return;
}
$this->load->model('note');
$affected = $this->note->replace_category($source, $target);
$this->session->set_flashdata('notice', sprintf('%s → %s (%d)', $source, $target, $affected));
redirect('notes');
}
}