Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b368cba
Enforce write permissions for QSO modifications
magicbug Dec 23, 2025
fb5afab
Remove strict type check in satellite name search
magicbug Jan 3, 2026
6a8f8a8
Fix indentation for lotw_satellite_map comment block
magicbug Jan 3, 2026
0c7b919
Update satellite mapping in lotw_satellite_map
magicbug Jan 3, 2026
44fbfd4
Pass second parameter to getAdifLine in Logbook_model
magicbug Jan 3, 2026
e572821
Add Personal Propagation Advisor feature
magicbug Jan 5, 2026
70cc096
Fix band row display logic in propagation advisor
magicbug Jan 5, 2026
d597e2e
Add DXCC statistics and continent breakdown
magicbug Jan 6, 2026
1e7eb7d
Enhance country count filtering in statistics
magicbug Jan 6, 2026
6080db9
Improve distances UI and add caching for statistics
magicbug Jan 6, 2026
f4d4fac
Optimize continent QSOs query in Awards controller
magicbug Jan 7, 2026
218f796
Move filters section above statistics summary in DXCC view
magicbug Jan 7, 2026
1fa6da4
Enhance CQ awards page with filters and table features
magicbug Jan 7, 2026
d07e788
Add WAS statistics summary, filters, and CSV export
magicbug Jan 7, 2026
bf82b69
Remove redundant statistics update calls in footer
magicbug Jan 7, 2026
1000faf
Revert "Remove redundant statistics update calls in footer"
magicbug Jan 7, 2026
dd61dff
Fix null grid square handling for PHP 8 compatibility
magicbug Jan 7, 2026
58d7eee
Redesign IOTA awards page UI and add search
magicbug Jan 7, 2026
cc1b92c
Fix DXCC ID assignment in logbook model
magicbug Jan 8, 2026
4cf7d79
Add migration to tag Cloudlog as version 2.8.5
magicbug Jan 8, 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'] = 240;
$config['migration_version'] = 241;

/*
|--------------------------------------------------------------------------
Expand Down
204 changes: 203 additions & 1 deletion application/controllers/Awards.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ public function dxcc()
$dxcclist = $this->dxcc->fetchdxcc($postdata);
$data['dxcc_array'] = $this->dxcc->get_dxcc_array($dxcclist, $bands, $postdata);
$data['dxcc_summary'] = $this->dxcc->get_dxcc_summary($bands, $postdata);

// Calculate continent breakdown and totals
$data['continent_breakdown'] = $this->dxcc->get_continent_breakdown($dxcclist);
$data['dxcc_statistics'] = $this->dxcc->get_dxcc_statistics($data['dxcc_array'], $postdata);

// Render Page
$data['page_title'] = "Awards - DXCC";
Expand Down Expand Up @@ -1290,4 +1294,202 @@ function returnStatus($string)
}
}
}
}

/*
* Get QSOs for a specific DXCC entity
*/
public function get_dxcc_qsos()
{
$this->load->model('logbooks_model');

$dxcc_id = $this->security->xss_clean($this->input->post('dxcc_id'));
$limit = $this->security->xss_clean($this->input->post('limit')) ?: 20;

if (!$dxcc_id || !is_numeric($dxcc_id)) {
header('Content-Type: application/json');
echo json_encode(array('error' => 'Invalid DXCC ID'));
return;
}

$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));

if (!$logbooks_locations_array) {
header('Content-Type: application/json');
echo json_encode(array('error' => 'No logbook data'));
return;
}

$location_list = "'" . implode("','", $logbooks_locations_array) . "'";

try {
// Get QSOs for this DXCC
$query = $this->db->query("
SELECT
col_time_on,
col_call,
col_band,
col_mode,
col_rst_sent,
col_rst_rcvd,
col_qsl_sent,
col_qsl_rcvd,
COL_LOTW_QSL_SENT,
COL_LOTW_QSL_RCVD
FROM " . $this->config->item('table_name') . "
WHERE station_id IN (" . $location_list . ")
AND col_dxcc = " . $dxcc_id . "
ORDER BY col_time_on DESC
LIMIT " . intval($limit)
);

if ($query->num_rows() > 0) {
$qsos = $query->result_array();
header('Content-Type: application/json');
echo json_encode(array('qsos' => $qsos, 'count' => $query->num_rows()));
} else {
header('Content-Type: application/json');
echo json_encode(array('qsos' => array(), 'count' => 0));
}
} catch (Exception $e) {
header('Content-Type: application/json');
echo json_encode(array('error' => $e->getMessage()));
}
}

/*
* Get QSOs for a specific DXCC entity filtered by status (Confirmed or Worked)
*/
public function get_dxcc_qsos_by_status()
{
$this->load->model('logbooks_model');

$dxcc_id = $this->security->xss_clean($this->input->post('dxcc_id'));
$status = $this->security->xss_clean($this->input->post('status'));
$limit = $this->security->xss_clean($this->input->post('limit')) ?: 100;

if (!$dxcc_id || !is_numeric($dxcc_id) || !$status) {
header('Content-Type: application/json');
echo json_encode(array('error' => 'Invalid parameters', 'count' => 0, 'qsos' => array()));
return;
}

$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));

if (!$logbooks_locations_array) {
header('Content-Type: application/json');
echo json_encode(array('error' => 'No logbook data', 'count' => 0, 'qsos' => array()));
return;
}

$location_list = "'" . implode("','", $logbooks_locations_array) . "'";

try {
// Build WHERE clause for status filter
$status_where = '';
if ($status === 'C') {
// Confirmed: either QSL received or LoTW received
$status_where = " AND (col_qsl_rcvd = 1 OR COL_LOTW_QSL_RCVD = 'Y')";
} elseif ($status === 'W') {
// Worked but not confirmed: neither QSL nor LoTW received
$status_where = " AND (col_qsl_rcvd IS NULL OR col_qsl_rcvd != 1) AND (COL_LOTW_QSL_RCVD IS NULL OR COL_LOTW_QSL_RCVD != 'Y')";
}

// Get QSOs for this DXCC filtered by status
$query = $this->db->query("
SELECT
col_time_on,
col_call,
col_band,
col_mode,
col_rst_sent,
col_rst_rcvd,
col_qsl_sent,
col_qsl_rcvd,
COL_LOTW_QSL_SENT,
COL_LOTW_QSL_RCVD
FROM " . $this->config->item('table_name') . "
WHERE station_id IN (" . $location_list . ")
AND col_dxcc = " . $dxcc_id . $status_where . "
ORDER BY col_time_on DESC
LIMIT " . intval($limit)
);

if ($query->num_rows() > 0) {
$qsos = $query->result_array();
header('Content-Type: application/json');
echo json_encode(array('qsos' => $qsos, 'count' => $query->num_rows()));
} else {
header('Content-Type: application/json');
echo json_encode(array('qsos' => array(), 'count' => 0));
}
} catch (Exception $e) {
header('Content-Type: application/json');
echo json_encode(array('error' => $e->getMessage(), 'count' => 0, 'qsos' => array()));
}
}

/*
* Get DXCC entities for a specific continent with their status
*/
public function get_continent_qsos()
{
$this->load->model('logbooks_model');

$continent_code = $this->security->xss_clean($this->input->post('continent_code'));

if (!$continent_code) {
header('Content-Type: application/json');
echo json_encode(array('error' => 'Invalid continent code', 'count' => 0, 'entities' => array()));
return;
}

$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));

if (!$logbooks_locations_array) {
header('Content-Type: application/json');
echo json_encode(array('error' => 'No logbook data', 'count' => 0, 'entities' => array()));
return;
}

$location_list = "'" . implode("','", $logbooks_locations_array) . "'";

try {
// Get all DXCC entities for this continent with their status in a single query
$query = $this->db->query("
SELECT
d.adif,
d.name,
d.prefix,
d.cont,
CASE
WHEN MAX(CASE WHEN (c.col_qsl_rcvd = 1 OR c.COL_LOTW_QSL_RCVD = 'Y') THEN 1 ELSE 0 END) = 1 THEN 'confirmed'
WHEN COUNT(c.col_dxcc) > 0 THEN 'worked'
ELSE 'unworked'
END as status
FROM dxcc_entities d
LEFT JOIN " . $this->config->item('table_name') . " c ON d.adif = c.col_dxcc AND c.station_id IN (" . $location_list . ")
WHERE d.cont = '" . $this->db->escape_like_str($continent_code) . "'
Copy link

Choose a reason for hiding this comment

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

SQL injection via improper escaping in continent query

High Severity

The get_continent_qsos() function uses escape_like_str() to sanitize user input in a SQL equality comparison, but this function only escapes LIKE wildcards (%, _) and does not escape single quotes. This allows SQL injection via the continent_code POST parameter. An attacker could send a value like EU' OR '1'='1 to bypass the intended WHERE clause. The correct approach would be to use $this->db->escape() or query binding with placeholders.

Fix in Cursor Fix in Web

GROUP BY d.adif, d.name, d.prefix, d.cont
ORDER BY d.name ASC
");

$entities = array();
if ($query->num_rows() > 0) {
foreach ($query->result_array() as $entity) {
$entities[] = array(
'adif' => $entity['adif'],
'name' => $entity['name'],
'prefix' => $entity['prefix'],
'status' => $entity['status']
);
}
}

header('Content-Type: application/json');
echo json_encode(array('entities' => $entities, 'count' => count($entities)));
} catch (Exception $e) {
header('Content-Type: application/json');
echo json_encode(array('error' => $e->getMessage(), 'count' => 0, 'entities' => array()));
}
}
}
Loading