Skip to content
Merged

2.8.8 #3414

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3d351a7
Stop tracking runtime-updated POTA parks CSV
magicbug Feb 2, 2026
1d51b60
Use direct lotw_users joins instead of subqueries
magicbug Feb 11, 2026
f01599e
Add indexes and batch processing for uploads
magicbug Feb 11, 2026
2cb8d62
Add gridsquare prefix index and optimizations
magicbug Feb 11, 2026
cfa24d9
Add LoTW batch updates and performance indexes
magicbug Feb 11, 2026
bcd9ded
Rename LOTW index to idx_confirmation_match
magicbug Feb 11, 2026
d4a7e4c
Batch eQSL updates and add indexes
magicbug Feb 11, 2026
d3d551c
Remove gridsquare index drop; re-enable db_debug
magicbug Feb 11, 2026
654c456
Fix migration index drop logic
magicbug Feb 11, 2026
41a8849
Cache dashboard stats and add DB indexes
magicbug Feb 11, 2026
c57c250
Use distinct() and fix station field
magicbug Feb 11, 2026
fc67bb0
Ignore application/cache but keep index and .htaccess
magicbug Feb 11, 2026
440f5b1
Skip unsaved-change warning if callsign empty
magicbug Feb 11, 2026
a4ec01a
Add Station Diary modal and quick_add endpoint
magicbug Feb 12, 2026
bef1e54
Show saved timestamp in notes views
magicbug Feb 12, 2026
680612c
Add Station Diary print view & action
magicbug Feb 12, 2026
60fe6dd
Format notes view templates
magicbug Feb 13, 2026
392a138
Map additional satellite names in Logbook_model
magicbug Feb 17, 2026
ad81bd8
Support batch updates by primary key
magicbug Feb 17, 2026
a94a875
Remove LoTW Status column; add status summary
magicbug Feb 17, 2026
9338ca4
Include LoTW status in table HTML
magicbug Feb 17, 2026
f26c112
Report found vs updated LoTW QSOs
magicbug Feb 17, 2026
7f7d2a6
Add left-positioned dropdown submenu styles
magicbug Feb 17, 2026
88e95b0
Add HTMX modal upload and cert table refresh
magicbug Feb 18, 2026
fe38b96
Add migration 250 tagging release 2.8.8
magicbug Feb 19, 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
/application/config/config.php
/application/config/managed.php
/application/config/managed.sample.php
/application/cache/*
!/application/cache/index.html
!/application/cache/.htaccess
/application/logs/*.php
/uploads/*.adi
/uploads/*.ADI
Expand Down
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'] = 244;
$config['migration_version'] = 250;

/*
|--------------------------------------------------------------------------
Expand Down
48 changes: 40 additions & 8 deletions application/controllers/Dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,35 @@ public function index()
//
$this->load->model('cat');
$this->load->model('vucc');

// Load cache driver for dashboard statistics caching (15 minutes)
$this->load->driver('cache', array('adapter' => 'file'));

$data['radio_status'] = $this->cat->recent_status();

// Store info - Use consolidated query for QSO statistics
$qso_stats = $this->logbook_model->get_qso_statistics_consolidated($logbooks_locations_array);
// Create cache key based on user and active logbook
$cache_key = 'dashboard_stats_' . $this->session->userdata('user_id') . '_' . $this->session->userdata('active_station_logbook');
$cache_ttl = 900; // 15 minutes

// Try to get QSO statistics from cache
$qso_stats = $this->cache->get($cache_key . '_qso');
if (!$qso_stats) {
// Cache miss - query database
$qso_stats = $this->logbook_model->get_qso_statistics_consolidated($logbooks_locations_array);
$this->cache->save($cache_key . '_qso', $qso_stats, $cache_ttl);
}
$data['todays_qsos'] = $qso_stats['todays_qsos'];
$data['total_qsos'] = $qso_stats['total_qsos'];
$data['month_qsos'] = $qso_stats['month_qsos'];
$data['year_qsos'] = $qso_stats['year_qsos'];

// Use consolidated countries statistics instead of separate queries
$countries_stats = $this->logbook_model->get_countries_statistics_consolidated($logbooks_locations_array);
// Try to get countries statistics from cache
$countries_stats = $this->cache->get($cache_key . '_countries');
if (!$countries_stats) {
// Cache miss - query database
$countries_stats = $this->logbook_model->get_countries_statistics_consolidated($logbooks_locations_array);
$this->cache->save($cache_key . '_countries', $countries_stats, $cache_ttl);
}

$data['total_countries'] = $countries_stats['Countries_Worked'];
$data['total_countries_confirmed_paper'] = $countries_stats['Countries_Worked_QSL'];
Expand Down Expand Up @@ -131,12 +148,27 @@ public function index()

// Only load VUCC data if the card is actually enabled
if ($data['dashboard_vuccgrids_card']) {
$data['vucc'] = $this->vucc->fetchVuccSummary();
$data['vuccSAT'] = $this->vucc->fetchVuccSummary('SAT');
// Try to get VUCC data from cache
$vucc_data = $this->cache->get($cache_key . '_vucc');
if (!$vucc_data) {
// Cache miss - query database
$vucc_data = array(
'vucc' => $this->vucc->fetchVuccSummary(),
'vuccSAT' => $this->vucc->fetchVuccSummary('SAT')
);
$this->cache->save($cache_key . '_vucc', $vucc_data, $cache_ttl);
}
$data['vucc'] = $vucc_data['vucc'];
$data['vuccSAT'] = $vucc_data['vuccSAT'];
}


$QSLStatsBreakdownArray = $this->logbook_model->get_QSLStats($logbooks_locations_array);
// Try to get QSL statistics from cache
$QSLStatsBreakdownArray = $this->cache->get($cache_key . '_qsl');
if (!$QSLStatsBreakdownArray) {
// Cache miss - query database
$QSLStatsBreakdownArray = $this->logbook_model->get_QSLStats($logbooks_locations_array);
$this->cache->save($cache_key . '_qsl', $QSLStatsBreakdownArray, $cache_ttl);
}

$data['total_qsl_sent'] = $QSLStatsBreakdownArray['QSL_Sent'];
$data['total_qsl_rcvd'] = $QSLStatsBreakdownArray['QSL_Received'];
Expand Down
28 changes: 24 additions & 4 deletions application/controllers/Eqsl.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ public function export()
}

$rows = '';
$successful_uploads = array(); // Collect successful QSO primary keys for batch update

// Grab the list of QSOs to send information about
// perform an HTTP get on each one, and grab the status back
$qslsnotsent = $this->eqslmethods_model->eqsl_not_yet_sent();
Expand All @@ -172,7 +174,7 @@ public function export()
// The station callsign is handled in the ADIF data via eqslqthnickname
$adif = $this->generateAdif($qsl, $data);

$status = $this->uploadQso($adif, $qsl);
$status = $this->uploadQso($adif, $qsl, $successful_uploads);

$timestamp = strtotime($qsl['COL_TIME_ON']);
$rows .= "<td>" . date($custom_date_format, $timestamp) . "</td>";
Expand All @@ -188,6 +190,13 @@ public function export()
$rows .= "<td>" . $status . "</td>";
}
$rows .= "</tr>";

// Batch update all successful uploads at the end for better performance
if (!empty($successful_uploads)) {
$affected_rows = $this->eqslmethods_model->eqsl_mark_sent_batch($successful_uploads);
log_message('info', 'eQSL export: Marked ' . $affected_rows . ' QSOs as sent to eQSL');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Batch eQSL marks lost when redirect fires mid-loop

High Severity

The batch update of $successful_uploads only runs after the entire foreach loop completes. However, uploadQso() calls redirect() directly on several error conditions (auth failure, 500, 400, format error). When a redirect fires mid-loop, PHP exits before reaching the batch eqsl_mark_sent_batch() call, so any QSOs already successfully uploaded in that session are never marked as sent in the database. Those QSOs will be re-uploaded on the next export, causing duplicates on eQSL. The original code called eqsl_mark_sent() immediately on each success, avoiding this problem.

Additional Locations (1)

Fix in Cursor Fix in Web


$data['eqsl_table'] = $this->generateResultTable($custom_date_format, $rows);
} else {
$qslsnotsent = $this->eqslmethods_model->eqsl_not_yet_sent();
Expand All @@ -201,7 +210,7 @@ public function export()
$this->load->view('interface_assets/footer');
}

function uploadQso($adif, $qsl)
function uploadQso($adif, $qsl, &$successful_uploads = null)
{
$this->load->model('eqslmethods_model');
$status = "";
Expand Down Expand Up @@ -232,7 +241,14 @@ function uploadQso($adif, $qsl)
if ($chi['http_code'] == "200") {
if (stristr($result, "Result: 1 out of 1 records added")) {
$status = "Sent";
$this->eqslmethods_model->eqsl_mark_sent($qsl['COL_PRIMARY_KEY']);

// If batch array is provided, add to it instead of immediate update
if ($successful_uploads !== null) {
$successful_uploads[] = $qsl['COL_PRIMARY_KEY'];
} else {
// Legacy: immediate update (for backward compatibility)
$this->eqslmethods_model->eqsl_mark_sent($qsl['COL_PRIMARY_KEY']);
}
} else {
if (stristr($result, "Error: No match on eQSL_User/eQSL_Pswd")) {
$this->session->set_flashdata('warning', 'Your eQSL username and/or password is incorrect.');
Expand All @@ -246,7 +262,11 @@ function uploadQso($adif, $qsl)
$status = "Duplicate";

# Mark the QSL as sent if this is a dupe.
$this->eqslmethods_model->eqsl_mark_sent($qsl['COL_PRIMARY_KEY']);
if ($successful_uploads !== null) {
$successful_uploads[] = $qsl['COL_PRIMARY_KEY'];
} else {
$this->eqslmethods_model->eqsl_mark_sent($qsl['COL_PRIMARY_KEY']);
}
}
}
}
Expand Down
47 changes: 33 additions & 14 deletions application/controllers/Hrdlog.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,26 @@ function setOptions() {
/*
* Function gets all QSOs from given station_id, that are not previously uploaded to hrdlog.
* Adif is build for each qso, and then uploaded, one at a time
* Uses batch fetching to prevent memory exhaustion with large datasets
*/
function mass_upload_qsos($station_id, $hrdlog_username, $hrdlog_code) {
$i = 0;
$data['qsos'] = $this->logbook_model->get_hrdlog_qsos($station_id);
$errormessages = array();
$batch_size = 1000;
$offset = 0;
$has_more_qsos = true;

$this->load->library('AdifHelper');

if ($data['qsos']) {
// Process QSOs in batches to prevent memory exhaustion
while ($has_more_qsos) {
$data['qsos'] = $this->logbook_model->get_hrdlog_qsos($station_id, $batch_size, $offset);

if (!$data['qsos'] || $data['qsos']->num_rows() == 0) {
$has_more_qsos = false;
break;
}

foreach ($data['qsos']->result() as $qso) {
$adif = $this->adifhelper->getAdifLine($qso);

Expand All @@ -76,26 +87,34 @@ function mass_upload_qsos($station_id, $hrdlog_username, $hrdlog_code) {
log_message('error', 'hrdlog upload stopped for Station_ID: ' . $station_id);
$errormessages[] = $result['message'] . 'Invalid HRDLog-Code, stopped at Call: ' . $qso->COL_CALL . ' Band: ' . $qso->COL_BAND . ' Mode: ' . $qso->COL_MODE . ' Time: ' . $qso->COL_TIME_ON;
$result['status'] = 'Error';
break; /* If key is invalid, immediate stop syncing for more QSOs of this station */
break 2; /* If key is invalid, immediate stop syncing for more QSOs of this station */
} else {
log_message('error', 'hrdlog upload failed for qso: Call: ' . $qso->COL_CALL . ' Band: ' . $qso->COL_BAND . ' Mode: ' . $qso->COL_MODE . ' Time: ' . $qso->COL_TIME_ON);
log_message('error', 'hrdlog upload failed with the following message: ' . $result['message']);
$result['status'] = 'Error';
$errormessages[] = $result['message'] . ' Call: ' . $qso->COL_CALL . ' Band: ' . $qso->COL_BAND . ' Mode: ' . $qso->COL_MODE . ' Time: ' . $qso->COL_TIME_ON;
}
}
if ($i == 0) {
$result['status']='Error';

// Check if we got a full batch - if not, we've reached the end
if ($data['qsos']->num_rows() < $batch_size) {
$has_more_qsos = false;
} else {
$offset += $batch_size;
log_message('info', 'HRDLog batch upload: Processed ' . $offset . ' QSOs so far for station_id: ' . $station_id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Offset-based batch pagination skips QSOs mid-upload

High Severity

Both mass_upload_qsos functions mark QSOs as sent inside the inner foreach loop (via markqso() in HRDLog and mark_qrz_qsos_sent_batch() every 100 records in QRZ). This removes those rows from the unprocessed result set mid-batch. After each batch finishes, $offset += $batch_size is applied, but since ~1000 rows were removed from the WHERE clause result set, OFFSET 1000 now skips the next 1000 un-processed QSOs. In a large upload, every second batch of 1000 QSOs is silently skipped. The fix is to keep $offset at 0 across all fetches, since the WHERE filter already excludes sent QSOs.

Additional Locations (1)

Fix in Cursor Fix in Web

}
$result['count'] = $i;
$result['errormessages'] = $errormessages;
return $result;
} else {
$result['status'] = 'Error';
$result['count'] = $i;
$result['errormessages'] = $errormessages;
return $result;
}
}

if ($i > 0) {
log_message('info', 'HRDLog upload completed: Total of ' . $i . ' QSOs successfully uploaded for station_id: ' . $station_id);
}

if ($i == 0) {
$result['status']='Error';
}
$result['count'] = $i;
$result['errormessages'] = $errormessages;
return $result;
}

/*
Expand Down
Loading