Skip to content
Merged

2.8.12 #3427

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b3cad5b
Handle Markdown horizontal rules
magicbug Apr 1, 2026
868638e
Render Markdown-like formatting in version dialog
magicbug Apr 1, 2026
4ab0bff
Add logbook_worked_status API endpoint
magicbug Apr 2, 2026
41bc5a2
Add gridsquare support to worked status API
magicbug Apr 2, 2026
78209d4
Add API endpoint for logbook public slugs
magicbug Apr 3, 2026
aaf3aa8
Add API endpoint for accessible public slugs
magicbug Apr 3, 2026
5d71351
Add AJAX QSO save endpoint and client handler
magicbug Apr 5, 2026
b8b87e5
Escape QSO notice values and include band
magicbug Apr 5, 2026
85d5ad6
Reapply default RST on reset_fields
magicbug Apr 5, 2026
5edc233
Reapply mode and satellite fields after QSO save
magicbug Apr 5, 2026
e904595
Lazy-load QSO lists; add time index migration
magicbug Apr 6, 2026
fcd1b62
Remove QSO count from View QSO List summary
magicbug Apr 6, 2026
8716e6c
Clear satellite fields when not SAT and on reset
magicbug Apr 6, 2026
d4251cd
Add no-cache headers and localize HTMX polling
magicbug Apr 6, 2026
da5fa48
Update htmx target IDs in previous contacts
magicbug Apr 7, 2026
fdba5ab
Preserve band/mode/satellite on form reset
magicbug Apr 7, 2026
b96b205
Improve CAT sync and prevent stale updates
magicbug Apr 7, 2026
766a940
Suppress reset handler for programmatic resets
magicbug Apr 7, 2026
830df2d
Clear CAT value cache on UI reset
magicbug Apr 7, 2026
fc6e0a7
Manage previous contacts panel visibility
magicbug Apr 8, 2026
297ff0a
Add SSTV menu toggle for users
magicbug Apr 8, 2026
de293f6
Relocate SSTV menu toggle into Storage Options
magicbug Apr 8, 2026
e197a5c
Add toggle for QSL Cards menu item
magicbug Apr 8, 2026
929d428
Refactor user settings UI to two-column layout
magicbug Apr 8, 2026
d6ffc49
Remove redundant CSS from user edit view
magicbug Apr 10, 2026
79084da
Show QSP tab and clear additional fields
magicbug Apr 10, 2026
6e8d73e
Recalculate highlight DX using grid/DXCC
magicbug Apr 10, 2026
12f2078
Hide empty partial view and load DXX summary
magicbug Apr 10, 2026
9e18606
Safely handle partial lookup and toggle panel
magicbug Apr 10, 2026
c7b0a75
Pause auto-refresh during callsign lookup
magicbug Apr 13, 2026
9913eb0
Improve callsign lookup, caching & recent details
magicbug Apr 13, 2026
16e47ac
Preserve DXCC badge when resetting QSO fields
magicbug Apr 13, 2026
244d4c2
Use innerHTML swap for #qso-last-table
magicbug Apr 13, 2026
b1fcc8f
Stop resetting band filter to 'all' on radio none
magicbug Apr 13, 2026
e23decd
Conditional action buttons in logbooks list
magicbug Apr 13, 2026
24f7561
Add migration to bump version to 2.8.12
magicbug Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application/config/migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
|
*/

$config['migration_version'] = 264;
$config['migration_version'] = 265;

/*
|--------------------------------------------------------------------------
Expand Down
260 changes: 260 additions & 0 deletions application/controllers/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,85 @@ function station_info($key)
}
}

/**
* Get station logbooks that have public slugs for the API key owner.
* GET /api/logbook_public_slugs/{key}
*/
function logbook_public_slugs($key)
{
header('Content-type: application/json');

$this->load->model('api_model');
$this->load->model('logbooks_model');

if (!$key || $this->api_model->authorize($key) == 0) {
http_response_code(401);
echo json_encode(['status' => 'failed', 'reason' => 'missing or invalid api key']);
return;
}

$this->api_model->update_last_used($key);
$user_id = $this->api_model->key_userid($key);

$query = $this->logbooks_model->public_slugs_by_user($user_id);
$logbooks = array();

foreach ($query->result() as $row) {
$logbooks[] = array(
'logbook_id' => (int)$row->logbook_id,
'logbook_name' => $row->logbook_name,
'public_slug' => $row->public_slug,
);
}

http_response_code(200);
echo json_encode(array(
'status' => 'success',
'count' => count($logbooks),
'logbooks' => $logbooks,
), JSON_PRETTY_PRINT);
}

/**
* Get all accessible station logbooks (owned + shared) that have public slugs for the API key owner.
* GET /api/logbook_public_slugs_accessible/{key}
*/
function logbook_public_slugs_accessible($key)
{
header('Content-type: application/json');

$this->load->model('api_model');
$this->load->model('logbooks_model');

if (!$key || $this->api_model->authorize($key) == 0) {
http_response_code(401);
echo json_encode(['status' => 'failed', 'reason' => 'missing or invalid api key']);
return;
}

$this->api_model->update_last_used($key);
$user_id = $this->api_model->key_userid($key);

$query = $this->logbooks_model->public_slugs_accessible_by_user($user_id);
$logbooks = array();

foreach ($query->result() as $row) {
$logbooks[] = array(
'logbook_id' => (int)$row->logbook_id,
'logbook_name' => $row->logbook_name,
'public_slug' => $row->public_slug,
'access_level' => $row->access_level,
);
}

http_response_code(200);
echo json_encode(array(
'status' => 'success',
'count' => count($logbooks),
'logbooks' => $logbooks,
), JSON_PRETTY_PRINT);
}


/*
*
Expand Down Expand Up @@ -594,6 +673,187 @@ function logbook_check_country()
}
}

/**
* Check worked and confirmation status for callsign and country.
*
* @api POST /api/logbook_worked_status
* @header Content-Type application/json
*
* Required fields:
* - key
* - logbook_public_slug
* - callsign
*
* Optional fields:
* - band
* - mode
* - gridsquare
*/
function logbook_worked_status()
{
header('Content-type: application/json');

$this->load->model('api_model');

$obj = json_decode(file_get_contents("php://input"), true);
if ($obj === NULL) {
http_response_code(400);
echo json_encode(['status' => 'failed', 'reason' => "wrong JSON"]);
return;
}

if (!isset($obj['key']) || $this->api_model->authorize($obj['key']) == 0) {
http_response_code(401);
echo json_encode(['status' => 'failed', 'reason' => "missing api key"]);
return;
}

if (!isset($obj['logbook_public_slug']) || !isset($obj['callsign'])) {
http_response_code(401);
echo json_encode(['status' => 'failed', 'reason' => "missing fields"]);
return;
}

$this->load->model('logbook_model');
$this->load->model('logbooks_model');

$logbook_slug = trim($obj['logbook_public_slug']);
$callsign = strtoupper(trim($obj['callsign']));
$band = isset($obj['band']) && trim($obj['band']) !== '' ? strtoupper(trim($obj['band'])) : null;
$mode = isset($obj['mode']) && trim($obj['mode']) !== '' ? strtoupper(trim($obj['mode'])) : null;
$gridsquare = isset($obj['gridsquare']) && trim($obj['gridsquare']) !== '' ? strtoupper(trim($obj['gridsquare'])) : null;

$date = date("Y-m-d");
$callsign_dxcc_lookup = $this->logbook_model->dxcc_lookup($callsign, $date);
$country = isset($callsign_dxcc_lookup['entity']) ? $callsign_dxcc_lookup['entity'] : '';

if (!$this->logbooks_model->public_slug_exists($logbook_slug)) {
http_response_code(404);
echo json_encode(['status' => 'failed', 'reason' => "logbook not found"]);
return;
}

$logbook_id = $this->logbooks_model->public_slug_exists_logbook_id($logbook_slug);
if ($logbook_id == false) {
http_response_code(404);
echo json_encode(['status' => 'failed', 'reason' => $logbook_slug . " has no associated station locations"]);
return;
}

$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($logbook_id);
if (!$logbooks_locations_array) {
http_response_code(404);
echo json_encode(['status' => 'failed', 'reason' => "Empty Logbook"]);
return;
}

$return = [
'callsign' => $callsign,
'country' => $country,
'band' => $band,
'mode' => $mode,
'gridsquare' => $gridsquare,
'worked' => [
'callsign_overall' => false,
'callsign_band' => $band !== null ? false : null,
'country_overall' => false,
'country_band' => $band !== null ? false : null,
'grid_overall' => $gridsquare !== null ? false : null,
'grid_band' => ($gridsquare !== null && $band !== null) ? false : null,
],
'confirmed' => [
'callsign' => [
'lotw' => false,
'qsl' => false,
'any' => false,
],
'country' => [
'lotw' => false,
'qsl' => false,
'any' => false,
],
],
];

$table_name = $this->config->item('table_name');
$main_mode = $mode !== null ? $this->logbook_model->get_main_mode_from_mode($mode) : null;

$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_CALL', $callsign);
$callsign_overall_query = $this->db->get($table_name, 1, 0);
$return['worked']['callsign_overall'] = $callsign_overall_query->num_rows() > 0;

if ($band !== null) {
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_CALL', $callsign);
$this->db->where('COL_BAND', $band);
if ($main_mode !== null) {
$this->db->where('COL_MODE', $main_mode);
}
$callsign_band_query = $this->db->get($table_name, 1, 0);
$return['worked']['callsign_band'] = $callsign_band_query->num_rows() > 0;
}

if ($country !== '') {
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_COUNTRY', urldecode($country));
$country_overall_query = $this->db->get($table_name, 1, 0);
$return['worked']['country_overall'] = $country_overall_query->num_rows() > 0;

if ($band !== null) {
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_COUNTRY', urldecode($country));
$this->db->where('COL_BAND', $band);
if ($main_mode !== null) {
$this->db->where('COL_MODE', $main_mode);
}
$country_band_query = $this->db->get($table_name, 1, 0);
$return['worked']['country_band'] = $country_band_query->num_rows() > 0;
}
}

if ($gridsquare !== null) {
$grid_overall_result = $this->logbook_model->check_if_grid_worked_in_logbook($gridsquare, $logbooks_locations_array, null);
$return['worked']['grid_overall'] = $grid_overall_result > 0;

if ($band !== null) {
$grid_band_result = $this->logbook_model->check_if_grid_worked_in_logbook($gridsquare, $logbooks_locations_array, $band);
$return['worked']['grid_band'] = $grid_band_result > 0;
}
}

$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_CALL', $callsign);
$this->db->where("COL_LOTW_QSL_RCVD='Y'");
$callsign_lotw_query = $this->db->get($table_name, 1, 0);
$return['confirmed']['callsign']['lotw'] = $callsign_lotw_query->num_rows() > 0;

$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_CALL', $callsign);
$this->db->where("COL_QSL_RCVD='Y'");
$callsign_qsl_query = $this->db->get($table_name, 1, 0);
$return['confirmed']['callsign']['qsl'] = $callsign_qsl_query->num_rows() > 0;
$return['confirmed']['callsign']['any'] = $return['confirmed']['callsign']['lotw'] || $return['confirmed']['callsign']['qsl'];

if ($country !== '') {
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_COUNTRY', urldecode($country));
$this->db->where("COL_LOTW_QSL_RCVD='Y'");
$country_lotw_query = $this->db->get($table_name, 1, 0);
$return['confirmed']['country']['lotw'] = $country_lotw_query->num_rows() > 0;

$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_COUNTRY', urldecode($country));
$this->db->where("COL_QSL_RCVD='Y'");
$country_qsl_query = $this->db->get($table_name, 1, 0);
$return['confirmed']['country']['qsl'] = $country_qsl_query->num_rows() > 0;
$return['confirmed']['country']['any'] = $return['confirmed']['country']['lotw'] || $return['confirmed']['country']['qsl'];
}

http_response_code(200);
echo json_encode($return, JSON_PRETTY_PRINT);
}

/* ENDPOINT for Rig Control */

function radio()
Expand Down
Loading
Loading