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
|
|
|
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
|
|
|
|
|
|
|
|
|
|
class Stationdiary extends CI_Controller {
|
|
|
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
|
|
|
|
parent::__construct();
|
|
|
|
|
$this->load->model('note');
|
|
|
|
|
$this->load->library('pagination');
|
|
|
|
|
$this->load->driver('cache', array('adapter' => 'file'));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 11:00:21 +00:00
|
|
|
private function get_public_qso_datetime_format($userDateFormat = NULL)
|
|
|
|
|
{
|
|
|
|
|
$dateFormat = !empty($userDateFormat) ? trim((string)$userDateFormat) : trim((string)$this->config->item('qso_date_format'));
|
|
|
|
|
if ($dateFormat === '') {
|
|
|
|
|
$dateFormat = 'Y-m-d';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (preg_match('/[GgHh]/', $dateFormat) || strpos($dateFormat, 'i') !== FALSE) {
|
|
|
|
|
return $dateFormat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $dateFormat . ' H:i';
|
|
|
|
|
}
|
|
|
|
|
|
Add reactions to station diary entries
Introduce a reactions feature for public station diary entries. Adds a migration to create station_diary_reactions with indexes and a unique (diary_id, visitor_hash) constraint; bumps migration version to 257 and registers a new route for /entry/:id/react. Controller: implements visitor token/hash creation, a react() endpoint to accept POSTed reactions (like, love, fire), increments/updates reactions, invalidates cache and returns totals and visitor reaction. Model: new methods to get totals, fetch visitor reaction and save/update reactions. View: UI, styles and client-side JS for reaction buttons, counts, and copy/share enhancements; integrates totals and visitor state into the single-entry view.
2026-03-09 13:29:14 +00:00
|
|
|
private function get_or_create_public_visitor_token()
|
|
|
|
|
{
|
|
|
|
|
$cookieName = 'cloudlog_diary_visitor';
|
|
|
|
|
$token = $this->input->cookie($cookieName, TRUE);
|
|
|
|
|
|
|
|
|
|
if (!empty($token) && preg_match('/^[a-f0-9]{32,64}$/i', $token)) {
|
|
|
|
|
return $token;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$token = bin2hex(random_bytes(16));
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
$token = md5(uniqid((string)mt_rand(), TRUE));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setcookie($cookieName, $token, time() + (365 * 24 * 60 * 60), '/', '', (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'), TRUE);
|
|
|
|
|
return $token;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function build_public_visitor_hash()
|
|
|
|
|
{
|
|
|
|
|
$token = $this->get_or_create_public_visitor_token();
|
|
|
|
|
$ipAddress = (string)$this->input->ip_address();
|
|
|
|
|
$userAgent = substr((string)$this->input->user_agent(), 0, 255);
|
|
|
|
|
$secret = (string)$this->config->item('encryption_key');
|
|
|
|
|
|
|
|
|
|
return hash('sha256', $token . '|' . $ipAddress . '|' . $userAgent . '|' . $secret);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
public function index($callsign = NULL, $offset = 0)
|
|
|
|
|
{
|
|
|
|
|
if ($this->security->xss_clean($callsign, TRUE) === FALSE) {
|
|
|
|
|
show_404();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$resolution = $this->note->resolve_public_user_by_callsign($callsign);
|
|
|
|
|
if (!isset($resolution['status']) || $resolution['status'] !== 'ok') {
|
|
|
|
|
show_404();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user_id = (int)$resolution['user_id'];
|
|
|
|
|
$cleanCallsign = strtoupper($resolution['callsign']);
|
|
|
|
|
$pageOffset = is_numeric($offset) ? (int)$offset : 0;
|
|
|
|
|
$cacheVersion = $this->note->get_public_diary_cache_version($user_id);
|
2026-04-06 11:11:03 +01:00
|
|
|
$renderVersion = 'public_diary_render_v5';
|
Render diary images & improve diary cache
Load and render station diary images inline and separately, and make cache handling more robust. Notes controller: load diary_images for print view and invalidate public diary cache when an image caption is updated. Stationdiary controller: include a render-version token in the public diary cache key to force re-renders when markup changes. Note model: make touch_public_diary_cache_version more resilient (unique value, fallback to file_put_contents and cache clean), add invalidate_public_diary_cache_for_note helper, and enhance process_image_shortcodes to handle encoded brackets, tolerant shortcode spacing, modifiers (alignment and size) with deterministic styling, and return used image ids. Views: update station_diary_print and note view to process shortcodes, remove empty paragraph artifacts, show inline images via shortcodes, and display remaining images with captions and improved print CSS.
2026-03-08 15:11:33 +00:00
|
|
|
$cacheKey = 'public_station_diary_' . md5($cleanCallsign . '_' . $pageOffset . '_' . $cacheVersion . '_' . $renderVersion);
|
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
|
|
|
|
|
|
|
|
$cachedHtml = $this->cache->get($cacheKey);
|
|
|
|
|
if ($cachedHtml !== FALSE && !empty($cachedHtml)) {
|
|
|
|
|
$this->output->set_output($cachedHtml);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$perPage = 10;
|
|
|
|
|
$totalRows = $this->note->count_public_station_diary_entries($user_id);
|
|
|
|
|
|
|
|
|
|
$config['base_url'] = site_url('station-diary/' . rawurlencode($cleanCallsign));
|
|
|
|
|
$config['total_rows'] = $totalRows;
|
|
|
|
|
$config['per_page'] = $perPage;
|
|
|
|
|
$config['uri_segment'] = 3;
|
|
|
|
|
$config['num_links'] = 5;
|
|
|
|
|
$config['full_tag_open'] = '<ul class="pagination pagination-sm">';
|
|
|
|
|
$config['full_tag_close'] = '</ul>';
|
|
|
|
|
$config['attributes'] = array('class' => 'page-link');
|
|
|
|
|
$config['first_link'] = FALSE;
|
|
|
|
|
$config['last_link'] = FALSE;
|
|
|
|
|
$config['first_tag_open'] = '<li class="page-item">';
|
|
|
|
|
$config['first_tag_close'] = '</li>';
|
|
|
|
|
$config['prev_link'] = '«';
|
|
|
|
|
$config['prev_tag_open'] = '<li class="page-item">';
|
|
|
|
|
$config['prev_tag_close'] = '</li>';
|
|
|
|
|
$config['next_link'] = '»';
|
|
|
|
|
$config['next_tag_open'] = '<li class="page-item">';
|
|
|
|
|
$config['next_tag_close'] = '</li>';
|
|
|
|
|
$config['last_tag_open'] = '<li class="page-item">';
|
|
|
|
|
$config['last_tag_close'] = '</li>';
|
|
|
|
|
$config['cur_tag_open'] = '<li class="page-item active"><a href="#" class="page-link">';
|
|
|
|
|
$config['cur_tag_close'] = '<span class="visually-hidden">(current)</span></a></li>';
|
|
|
|
|
$config['num_tag_open'] = '<li class="page-item">';
|
|
|
|
|
$config['num_tag_close'] = '</li>';
|
|
|
|
|
|
|
|
|
|
$this->pagination->initialize($config);
|
|
|
|
|
|
|
|
|
|
$data['callsign'] = $cleanCallsign;
|
2026-04-06 11:11:03 +01:00
|
|
|
$data['entries'] = $this->note->get_public_station_diary_entries($user_id, $perPage, $pageOffset, FALSE);
|
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
|
|
|
$data['pagination_links'] = $this->pagination->create_links();
|
|
|
|
|
$data['page_title'] = 'Station Diary - ' . $cleanCallsign;
|
|
|
|
|
$data['rss_url'] = site_url('station-diary/' . rawurlencode($cleanCallsign) . '/rss');
|
2026-03-09 11:00:21 +00:00
|
|
|
$data['qso_datetime_format'] = $this->get_public_qso_datetime_format($resolution['user_date_format'] ?? NULL);
|
|
|
|
|
$data['is_single_entry'] = false;
|
2026-04-06 11:11:03 +01:00
|
|
|
$data['defer_qso_list'] = true;
|
2026-03-09 11:00:21 +00:00
|
|
|
$data['current_entry_permalink'] = '';
|
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
|
|
|
|
|
|
|
|
$html = $this->load->view('station_diary/public_index', $data, TRUE);
|
|
|
|
|
$this->cache->save($cacheKey, $html, 86400);
|
|
|
|
|
$this->output->set_output($html);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function rss($callsign = NULL)
|
|
|
|
|
{
|
|
|
|
|
if ($this->security->xss_clean($callsign, TRUE) === FALSE) {
|
|
|
|
|
show_404();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$resolution = $this->note->resolve_public_user_by_callsign($callsign);
|
|
|
|
|
if (!isset($resolution['status']) || $resolution['status'] !== 'ok') {
|
|
|
|
|
show_404();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user_id = (int)$resolution['user_id'];
|
|
|
|
|
$cleanCallsign = strtoupper($resolution['callsign']);
|
|
|
|
|
$entries = $this->note->get_public_station_diary_entries($user_id, 25, 0);
|
|
|
|
|
|
|
|
|
|
$this->output->set_content_type('application/rss+xml; charset=UTF-8');
|
|
|
|
|
$this->load->view('station_diary/rss', array(
|
|
|
|
|
'callsign' => $cleanCallsign,
|
2026-03-08 12:07:51 +00:00
|
|
|
'entries' => $entries, 'feed_url' => site_url('station-diary/' . rawurlencode($cleanCallsign) . '/rss'), ));
|
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
|
|
|
}
|
2026-03-09 10:57:38 +00:00
|
|
|
|
|
|
|
|
public function entry($callsign = NULL, $entry_id = 0)
|
|
|
|
|
{
|
|
|
|
|
if ($this->security->xss_clean($callsign, TRUE) === FALSE) {
|
|
|
|
|
show_404();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$resolution = $this->note->resolve_public_user_by_callsign($callsign);
|
|
|
|
|
if (!isset($resolution['status']) || $resolution['status'] !== 'ok') {
|
|
|
|
|
show_404();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user_id = (int)$resolution['user_id'];
|
|
|
|
|
$cleanCallsign = strtoupper($resolution['callsign']);
|
|
|
|
|
$entryId = (int)$entry_id;
|
|
|
|
|
if ($entryId <= 0) {
|
|
|
|
|
show_404();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$cacheVersion = $this->note->get_public_diary_cache_version($user_id);
|
Add reactions to station diary entries
Introduce a reactions feature for public station diary entries. Adds a migration to create station_diary_reactions with indexes and a unique (diary_id, visitor_hash) constraint; bumps migration version to 257 and registers a new route for /entry/:id/react. Controller: implements visitor token/hash creation, a react() endpoint to accept POSTed reactions (like, love, fire), increments/updates reactions, invalidates cache and returns totals and visitor reaction. Model: new methods to get totals, fetch visitor reaction and save/update reactions. View: UI, styles and client-side JS for reaction buttons, counts, and copy/share enhancements; integrates totals and visitor state into the single-entry view.
2026-03-09 13:29:14 +00:00
|
|
|
$renderVersion = 'public_diary_render_v4';
|
2026-03-09 10:57:38 +00:00
|
|
|
$cacheKey = 'public_station_diary_entry_' . md5($cleanCallsign . '_' . $entryId . '_' . $cacheVersion . '_' . $renderVersion);
|
|
|
|
|
|
|
|
|
|
$cachedHtml = $this->cache->get($cacheKey);
|
|
|
|
|
if ($cachedHtml !== FALSE && !empty($cachedHtml)) {
|
|
|
|
|
$this->output->set_output($cachedHtml);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$entry = $this->note->get_public_station_diary_entry($user_id, $entryId);
|
|
|
|
|
if (!$entry) {
|
|
|
|
|
show_404();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add reactions to station diary entries
Introduce a reactions feature for public station diary entries. Adds a migration to create station_diary_reactions with indexes and a unique (diary_id, visitor_hash) constraint; bumps migration version to 257 and registers a new route for /entry/:id/react. Controller: implements visitor token/hash creation, a react() endpoint to accept POSTed reactions (like, love, fire), increments/updates reactions, invalidates cache and returns totals and visitor reaction. Model: new methods to get totals, fetch visitor reaction and save/update reactions. View: UI, styles and client-side JS for reaction buttons, counts, and copy/share enhancements; integrates totals and visitor state into the single-entry view.
2026-03-09 13:29:14 +00:00
|
|
|
$entryReactionTotals = $this->note->get_station_diary_reaction_totals($entry->id);
|
|
|
|
|
|
2026-03-09 10:57:38 +00:00
|
|
|
$data['callsign'] = $cleanCallsign;
|
|
|
|
|
$data['entries'] = array($entry);
|
|
|
|
|
$data['pagination_links'] = '';
|
|
|
|
|
$data['page_title'] = $entry->title . ' - Station Diary - ' . $cleanCallsign;
|
|
|
|
|
$data['rss_url'] = site_url('station-diary/' . rawurlencode($cleanCallsign) . '/rss');
|
2026-03-09 11:00:21 +00:00
|
|
|
$data['qso_datetime_format'] = $this->get_public_qso_datetime_format($resolution['user_date_format'] ?? NULL);
|
|
|
|
|
$data['is_single_entry'] = true;
|
2026-04-06 11:11:03 +01:00
|
|
|
$data['defer_qso_list'] = false;
|
2026-03-09 11:00:21 +00:00
|
|
|
$data['current_entry_permalink'] = site_url('station-diary/' . rawurlencode($cleanCallsign) . '/entry/' . (int)$entry->id);
|
Add reactions to station diary entries
Introduce a reactions feature for public station diary entries. Adds a migration to create station_diary_reactions with indexes and a unique (diary_id, visitor_hash) constraint; bumps migration version to 257 and registers a new route for /entry/:id/react. Controller: implements visitor token/hash creation, a react() endpoint to accept POSTed reactions (like, love, fire), increments/updates reactions, invalidates cache and returns totals and visitor reaction. Model: new methods to get totals, fetch visitor reaction and save/update reactions. View: UI, styles and client-side JS for reaction buttons, counts, and copy/share enhancements; integrates totals and visitor state into the single-entry view.
2026-03-09 13:29:14 +00:00
|
|
|
$data['entry_reaction_totals'] = $entryReactionTotals;
|
|
|
|
|
$data['visitor_reaction'] = null;
|
2026-03-09 10:57:38 +00:00
|
|
|
|
|
|
|
|
$html = $this->load->view('station_diary/public_index', $data, TRUE);
|
|
|
|
|
$this->cache->save($cacheKey, $html, 86400);
|
|
|
|
|
$this->output->set_output($html);
|
|
|
|
|
}
|
Add reactions to station diary entries
Introduce a reactions feature for public station diary entries. Adds a migration to create station_diary_reactions with indexes and a unique (diary_id, visitor_hash) constraint; bumps migration version to 257 and registers a new route for /entry/:id/react. Controller: implements visitor token/hash creation, a react() endpoint to accept POSTed reactions (like, love, fire), increments/updates reactions, invalidates cache and returns totals and visitor reaction. Model: new methods to get totals, fetch visitor reaction and save/update reactions. View: UI, styles and client-side JS for reaction buttons, counts, and copy/share enhancements; integrates totals and visitor state into the single-entry view.
2026-03-09 13:29:14 +00:00
|
|
|
|
|
|
|
|
public function react($callsign = NULL, $entry_id = 0)
|
|
|
|
|
{
|
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
|
|
|
|
|
|
if (strtolower($this->input->method()) !== 'post') {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Invalid request method'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->security->xss_clean($callsign, TRUE) === FALSE) {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Invalid callsign'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$resolution = $this->note->resolve_public_user_by_callsign($callsign);
|
|
|
|
|
if (!isset($resolution['status']) || $resolution['status'] !== 'ok') {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Not found'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user_id = (int)$resolution['user_id'];
|
|
|
|
|
$entryId = (int)$entry_id;
|
|
|
|
|
if ($entryId <= 0) {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Invalid entry'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$entry = $this->note->get_public_station_diary_entry($user_id, $entryId);
|
|
|
|
|
if (!$entry) {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Entry not found'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$reaction = strtolower(trim((string)$this->input->post('reaction', TRUE)));
|
|
|
|
|
if (!in_array($reaction, array('like', 'love', 'fire'), TRUE)) {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Invalid reaction'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$visitorHash = $this->build_public_visitor_hash();
|
|
|
|
|
$saved = $this->note->save_station_diary_reaction($entryId, $reaction, $visitorHash);
|
|
|
|
|
if (!$saved) {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Unable to save reaction'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 11:11:03 +01:00
|
|
|
$cleanCallsign = strtoupper($resolution['callsign']);
|
|
|
|
|
$cacheVersion = $this->note->get_public_diary_cache_version($user_id);
|
|
|
|
|
$renderVersion = 'public_diary_render_v4';
|
|
|
|
|
$entryCacheKey = 'public_station_diary_entry_' . md5($cleanCallsign . '_' . $entryId . '_' . $cacheVersion . '_' . $renderVersion);
|
|
|
|
|
$this->cache->delete($entryCacheKey);
|
Add reactions to station diary entries
Introduce a reactions feature for public station diary entries. Adds a migration to create station_diary_reactions with indexes and a unique (diary_id, visitor_hash) constraint; bumps migration version to 257 and registers a new route for /entry/:id/react. Controller: implements visitor token/hash creation, a react() endpoint to accept POSTed reactions (like, love, fire), increments/updates reactions, invalidates cache and returns totals and visitor reaction. Model: new methods to get totals, fetch visitor reaction and save/update reactions. View: UI, styles and client-side JS for reaction buttons, counts, and copy/share enhancements; integrates totals and visitor state into the single-entry view.
2026-03-09 13:29:14 +00:00
|
|
|
|
|
|
|
|
$totals = $this->note->get_station_diary_reaction_totals($entryId);
|
|
|
|
|
$visitorReaction = $this->note->get_station_diary_visitor_reaction($entryId, $visitorHash);
|
|
|
|
|
|
|
|
|
|
echo json_encode(array(
|
|
|
|
|
'success' => true,
|
|
|
|
|
'totals' => $totals,
|
|
|
|
|
'visitor_reaction' => $visitorReaction,
|
|
|
|
|
));
|
|
|
|
|
}
|
2026-03-09 13:50:34 +00:00
|
|
|
|
2026-04-06 11:11:03 +01:00
|
|
|
public function get_filtered_qsos()
|
|
|
|
|
{
|
|
|
|
|
$this->output->set_content_type('application/json');
|
|
|
|
|
|
|
|
|
|
if (strtolower($this->input->method()) !== 'post') {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Invalid request method'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$callsign = $this->input->post('callsign', TRUE);
|
|
|
|
|
$entryId = (int)$this->input->post('entry_id', TRUE);
|
|
|
|
|
$startDate = trim((string)$this->input->post('start_date', TRUE));
|
|
|
|
|
$endDate = trim((string)$this->input->post('end_date', TRUE));
|
|
|
|
|
$logbookId = (int)$this->input->post('logbook_id', TRUE);
|
|
|
|
|
$satOnly = $this->input->post('sat_only', TRUE) === '1';
|
|
|
|
|
|
|
|
|
|
if ($this->security->xss_clean($callsign, TRUE) === FALSE || $entryId <= 0) {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Invalid request'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$resolution = $this->note->resolve_public_user_by_callsign($callsign);
|
|
|
|
|
if (!isset($resolution['status']) || $resolution['status'] !== 'ok') {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Not found'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$userId = (int)$resolution['user_id'];
|
|
|
|
|
$entry = $this->note->get_public_station_diary_entry($userId, $entryId);
|
|
|
|
|
if (!$entry) {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Entry not found'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$entryDate = date('Y-m-d', strtotime($entry->created_at));
|
|
|
|
|
if ($startDate === '') {
|
|
|
|
|
$startDate = !empty($entry->qso_date_start) ? $entry->qso_date_start : $entryDate;
|
|
|
|
|
}
|
|
|
|
|
if ($endDate === '') {
|
|
|
|
|
$endDate = !empty($entry->qso_date_end) ? $entry->qso_date_end : $entryDate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDate) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate)) {
|
|
|
|
|
echo json_encode(array('success' => false, 'message' => 'Invalid date format'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($logbookId <= 0) {
|
|
|
|
|
$logbookId = !empty($entry->logbook_id) ? (int)$entry->logbook_id : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$qsoSummary = $this->note->get_qso_summary_for_date_range($userId, $startDate, $endDate, $logbookId, $satOnly);
|
|
|
|
|
$qsoList = $this->note->get_qso_list_for_date_range($userId, $startDate, $endDate, $logbookId, $satOnly);
|
|
|
|
|
|
|
|
|
|
echo json_encode(array(
|
|
|
|
|
'success' => true,
|
|
|
|
|
'qso_list' => $qsoList,
|
|
|
|
|
'highlight_dx' => $qsoSummary['highlight_dx'] ?? null,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 13:50:34 +00:00
|
|
|
public function get_qso_map_data()
|
|
|
|
|
{
|
|
|
|
|
$this->output->set_content_type('application/json');
|
|
|
|
|
|
|
|
|
|
$dateFrom = $this->input->post('date_from', TRUE);
|
|
|
|
|
$dateTo = $this->input->post('date_to', TRUE);
|
|
|
|
|
$logbookId = (int)$this->input->post('logbook_id', TRUE);
|
|
|
|
|
$satOnly = $this->input->post('sat_only', TRUE) === '1';
|
|
|
|
|
$entryId = (int)$this->input->post('entry_id', TRUE);
|
|
|
|
|
|
|
|
|
|
if (empty($dateFrom) || empty($dateTo)) {
|
|
|
|
|
echo json_encode(array('error' => 'Date range required'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get user_id from the entry to ensure we only show their QSOs
|
|
|
|
|
$userId = null;
|
|
|
|
|
if ($entryId > 0) {
|
|
|
|
|
$userId = $this->note->get_public_entry_user_id($entryId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$userId) {
|
|
|
|
|
echo json_encode(array('error' => 'Unable to determine user context'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load necessary models
|
|
|
|
|
$this->load->model('logbook_model');
|
|
|
|
|
$this->load->model('Stations');
|
|
|
|
|
|
|
|
|
|
// Get QSOs for the date range
|
|
|
|
|
$qsos = $this->logbook_model->get_qsos_for_public_map($dateFrom, $dateTo, $logbookId, $satOnly, $userId);
|
|
|
|
|
|
|
|
|
|
if (empty($qsos) || $qsos->num_rows() === 0) {
|
|
|
|
|
echo json_encode(array('error' => 'No QSOs found', 'markers' => array()));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get plot array for map markers (uses gridsquare, VUCC, DXCC fallback)
|
|
|
|
|
$plotArray = $this->logbook_model->get_plot_array_for_map($qsos->result(), TRUE);
|
|
|
|
|
|
|
|
|
|
// Try to get station location from logbook
|
|
|
|
|
$stationArray = array();
|
|
|
|
|
if ($logbookId > 0) {
|
|
|
|
|
$this->load->model('logbooks_model');
|
|
|
|
|
$this->load->library('qra');
|
|
|
|
|
|
|
|
|
|
$logbookLocations = $this->logbooks_model->list_logbook_relationships($logbookId);
|
|
|
|
|
if (!empty($logbookLocations)) {
|
|
|
|
|
// Get the first station's location
|
|
|
|
|
$stationData = $this->Stations->profile($logbookLocations[0])->row();
|
|
|
|
|
if ($stationData && !empty($stationData->station_gridsquare)) {
|
|
|
|
|
list($stationLat, $stationLng) = $this->qra->qra2latlong($stationData->station_gridsquare);
|
|
|
|
|
if ($stationLat != 0 && $stationLng != 0) {
|
|
|
|
|
$stationArray = array(
|
|
|
|
|
'station' => array(
|
|
|
|
|
'lat' => $stationLat,
|
|
|
|
|
'lng' => $stationLng,
|
|
|
|
|
'html' => '<strong>' . htmlspecialchars($stationData->station_callsign ?? '', ENT_QUOTES) . '</strong><br>' . $stationData->station_gridsquare,
|
|
|
|
|
'label' => $stationData->station_profile_name ?? '',
|
|
|
|
|
'icon' => 'stationIcon'
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Merge and return
|
|
|
|
|
echo json_encode(array_merge($plotArray, $stationArray));
|
2026-04-06 11:11:03 +01:00
|
|
|
}
|
2026-04-15 11:38:38 +01:00
|
|
|
|
|
|
|
|
public function search($callsign = NULL)
|
|
|
|
|
{
|
|
|
|
|
if ($this->security->xss_clean($callsign, TRUE) === FALSE) {
|
|
|
|
|
show_404();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$resolution = $this->note->resolve_public_user_by_callsign($callsign);
|
|
|
|
|
if (!isset($resolution['status']) || $resolution['status'] !== 'ok') {
|
|
|
|
|
show_404();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$query = trim((string)$this->input->get('q', TRUE));
|
|
|
|
|
$cleanCallsign = strtoupper($resolution['callsign']);
|
|
|
|
|
|
|
|
|
|
if ($query === '') {
|
|
|
|
|
redirect('station-diary/' . rawurlencode($cleanCallsign));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user_id = (int)$resolution['user_id'];
|
|
|
|
|
$perPage = 10;
|
|
|
|
|
$totalRows = $this->note->count_public_station_diary_search_results($user_id, $query);
|
|
|
|
|
|
|
|
|
|
$config['base_url'] = site_url('station-diary/' . rawurlencode($cleanCallsign) . '/search');
|
|
|
|
|
$config['total_rows'] = $totalRows;
|
|
|
|
|
$config['per_page'] = $perPage;
|
|
|
|
|
$config['num_links'] = 5;
|
|
|
|
|
$config['page_query_string'] = TRUE;
|
|
|
|
|
$config['reuse_query_string'] = TRUE;
|
|
|
|
|
$config['query_string_segment'] = 'page';
|
|
|
|
|
$config['use_page_numbers'] = FALSE;
|
|
|
|
|
$config['full_tag_open'] = '<ul class="pagination pagination-sm">';
|
|
|
|
|
$config['full_tag_close'] = '</ul>';
|
|
|
|
|
$config['attributes'] = array('class' => 'page-link');
|
|
|
|
|
$config['first_link'] = FALSE;
|
|
|
|
|
$config['last_link'] = FALSE;
|
|
|
|
|
$config['first_tag_open'] = '<li class="page-item">';
|
|
|
|
|
$config['first_tag_close'] = '</li>';
|
|
|
|
|
$config['prev_link'] = '«';
|
|
|
|
|
$config['prev_tag_open'] = '<li class="page-item">';
|
|
|
|
|
$config['prev_tag_close'] = '</li>';
|
|
|
|
|
$config['next_link'] = '»';
|
|
|
|
|
$config['next_tag_open'] = '<li class="page-item">';
|
|
|
|
|
$config['next_tag_close'] = '</li>';
|
|
|
|
|
$config['last_tag_open'] = '<li class="page-item">';
|
|
|
|
|
$config['last_tag_close'] = '</li>';
|
|
|
|
|
$config['cur_tag_open'] = '<li class="page-item active"><a href="#" class="page-link">';
|
|
|
|
|
$config['cur_tag_close'] = '<span class="visually-hidden">(current)</span></a></li>';
|
|
|
|
|
$config['num_tag_open'] = '<li class="page-item">';
|
|
|
|
|
$config['num_tag_close'] = '</li>';
|
|
|
|
|
|
|
|
|
|
$this->pagination->initialize($config);
|
|
|
|
|
|
|
|
|
|
$pageOffset = (int)$this->input->get('page', TRUE);
|
|
|
|
|
if ($pageOffset < 0) {
|
|
|
|
|
$pageOffset = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data['callsign'] = $cleanCallsign;
|
|
|
|
|
$data['entries'] = $this->note->search_public_station_diary_entries($user_id, $query, $perPage, $pageOffset);
|
|
|
|
|
$data['pagination_links'] = $this->pagination->create_links();
|
|
|
|
|
$data['page_title'] = 'Search: ' . $query . ' - Station Diary - ' . $cleanCallsign;
|
|
|
|
|
$data['rss_url'] = site_url('station-diary/' . rawurlencode($cleanCallsign) . '/rss');
|
|
|
|
|
$data['qso_datetime_format'] = $this->get_public_qso_datetime_format($resolution['user_date_format'] ?? NULL);
|
|
|
|
|
$data['is_single_entry'] = false;
|
|
|
|
|
$data['defer_qso_list'] = false;
|
|
|
|
|
$data['current_entry_permalink'] = '';
|
|
|
|
|
$data['is_search_results'] = true;
|
|
|
|
|
$data['search_query'] = $query;
|
|
|
|
|
$data['search_total'] = $totalRows;
|
|
|
|
|
|
|
|
|
|
$this->load->view('station_diary/public_index', $data);
|
|
|
|
|
}
|
2026-04-06 11:11:03 +01:00
|
|
|
}
|