Merge pull request #2110 from AndreasK79/qso_confirmation

[QSO Confirmations] Added a new view for QSO confirmations
This commit is contained in:
Andreas Kristiansen
2025-07-06 17:32:54 +02:00
committed by GitHub
7 changed files with 252 additions and 14 deletions

View File

@@ -10,7 +10,7 @@ class Qsl extends CI_Controller {
parent::__construct();
$this->load->model('user_model');
if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); }
if(($this->config->item('disable_qsl') ?? false)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); exit; }
if(($this->config->item('disable_qsl') ?? false)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); exit; }
}
// Default view when loading controller.
@@ -25,11 +25,39 @@ class Qsl extends CI_Controller {
$data['page_title'] = __("QSL Cards");
$data['qslarray'] = $this->qsl_model->getQsoWithQslList();
$footerData = [];
$footerData['scripts'] = [
'assets/js/sections/qsl.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/qsl.js")),
];
$this->load->view('interface_assets/header', $data);
$this->load->view('qslcard/index');
$this->load->view('interface_assets/footer');
$this->load->view('interface_assets/footer', $footerData);
}
// View for filtering and showing confirmations on LoTW/QSL/eQSL/QRZ/HRDLog/Clublog
public function confirmations() {
// Render Page
$data['page_title'] = __("Confirmations");
$footerData = [];
$footerData['scripts'] = [
'assets/js/bootstrap-multiselect.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/bootstrap-multiselect.js")),
'assets/js/sections/qsl.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/qsl.js")),
];
$this->load->view('interface_assets/header', $data);
$this->load->view('qslcard/confirmations');
$this->load->view('interface_assets/footer', $footerData);
}
public function searchConfirmations() {
$this->load->model('qsl_model');
$confirmationtype = xss_clean($this->input->post('type'));
$data['result'] = $this->qsl_model->getConfirmations($confirmationtype);
$this->load->view('qslcard/confirmationresult', $data);
}
public function upload() {
// Render Page
$data['page_title'] = __("Upload QSL Cards");

View File

@@ -141,7 +141,7 @@ class Qsl_model extends CI_Model {
return $this->db->insert_id();
}
// return path of qsl file : u=url / p=real path
// return path of qsl file : u=url / p=real path
function get_imagePath($pathorurl='u', $user_id=null) {
// test if new folder directory option is enabled
@@ -178,4 +178,66 @@ class Qsl_model extends CI_Model {
return 'assets/qslcard';
}
}
function getConfirmations($confirmationtype) {
$this->load->model('logbooks_model');
$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
if (!$logbooks_locations_array) {
return null;
}
$location_list = "'".implode("','",$logbooks_locations_array)."'";
$table = $this->config->item('table_name');
$sql_parts = array();
if (in_array('qsl', $confirmationtype)) {
$sql_parts[] = "
SELECT col_primary_key, col_call, col_time_on, col_mode, col_submode, col_band, col_sat_name, col_qslrdate AS rxdate, 'QSL Card' AS type,
EXISTS (SELECT 1 FROM qsl_images WHERE qsoid = $table.COL_PRIMARY_KEY) AS qslcount
FROM $table
WHERE station_id IN ($location_list) AND col_qslrdate IS NOT NULL AND coalesce(col_qslrdate, '') <> '' AND col_qsl_rcvd = 'Y'
";
}
if (in_array('lotw', $confirmationtype)) {
$sql_parts[] = "
SELECT col_primary_key, col_call, col_time_on, col_mode, col_submode, col_band, col_sat_name, col_lotw_qslrdate AS rxdate, 'LoTW' AS type, 0 as qslcount
FROM $table
WHERE station_id IN ($location_list) AND col_lotw_qslrdate IS NOT NULL AND coalesce(col_lotw_qslrdate, '') <> '' AND col_lotw_qsl_rcvd = 'Y'
";
}
if (in_array('eqsl', $confirmationtype)) {
$sql_parts[] = "
SELECT col_primary_key, col_call, col_time_on, col_mode, col_submode, col_band, col_sat_name, col_eqsl_qslrdate AS rxdate, 'eQSL' AS type, 0 as qslcount
FROM $table
WHERE station_id IN ($location_list) AND col_eqsl_qslrdate IS NOT NULL AND coalesce(col_eqsl_qslrdate, '') <> '' AND col_eqsl_qsl_rcvd = 'Y'
";
}
if (in_array('qrz', $confirmationtype)) {
$sql_parts[] = "
SELECT col_primary_key, col_call, col_time_on, col_mode, col_submode, col_band, col_sat_name, col_qrzcom_qso_download_date AS rxdate, 'QRZ.com' AS type, 0 as qslcount
FROM $table
WHERE station_id IN ($location_list) AND col_qrzcom_qso_download_date IS NOT NULL AND coalesce(col_qrzcom_qso_download_date, '') <> '' AND col_qrzcom_qso_download_status = 'Y'
";
}
if (in_array('clublog', $confirmationtype)) {
$sql_parts[] = "
SELECT col_primary_key, col_call, col_time_on, col_mode, col_submode, col_band, col_sat_name, col_clublog_qso_download_date AS rxdate, 'Clublog' AS type, 0 as qslcount
FROM $table
WHERE station_id IN ($location_list) AND col_clublog_qso_download_date IS NOT NULL AND coalesce(col_clublog_qso_download_date, '') <> '' AND col_clublog_qso_download_status = 'Y'
";
}
if (count($sql_parts) == 0) {
return array();
}
$sql = implode(" UNION ALL ", $sql_parts);
$sql = "SELECT * FROM ( $sql ) AS unioned_results ORDER BY rxdate DESC LIMIT 1000";
$query = $this->db->query($sql);
return $query->result();
}
}

View File

@@ -2839,17 +2839,6 @@ function viewEqsl(picture, callsign) {
});
}
function searchAdditionalQsos(filename) {
$.ajax({
url: base_url + 'index.php/qsl/searchQsos',
type: 'post',
data: {'callsign': $('#callsign').val(), 'filename': filename},
success: function(html) {
$('#searchresult').empty();
$('#searchresult').append(html);
}
});
}
</script>
<?php if ($this->uri->segment(1) == "contesting" && ($this->uri->segment(2) != "add" && $this->uri->segment(2) != "edit")) { ?>
<script>

View File

@@ -109,6 +109,8 @@
<div class="dropdown-divider"></div>
<?php } ?>
<li><a class="dropdown-item" href="<?php echo site_url('eqsl'); ?>" title="eQSL"><i class="fa fa-id-card"></i> <?= __("View eQSL Cards"); ?></a></li>
<div class="dropdown-divider"></div>
<li><a class="dropdown-item" href="<?php echo site_url('qsl/confirmations'); ?>" title="eQSL"><i class="fa fa-id-card"></i> <?= __("View last confirmations"); ?></a></li>
</ul>
</li>

View File

@@ -0,0 +1,53 @@
<?php
if ($this->session->userdata('user_date_format')) {
// If Logged in and session exists
$custom_date_format = $this->session->userdata('user_date_format');
} else {
// Get Default date format from /config/wavelog.php
$custom_date_format = $this->config->item('qso_date_format');
}
if ($result) { ?>
<div class="table-responsive">
<table style="width:100%" class="confirmationtable table table-sm table-striped text-center">
<thead>
<tr>
<th><?= __("Callsign"); ?></th>
<th><?= __("QSO date"); ?></th>
<th><?= __("Mode"); ?></th>
<th><?= __("Band"); ?></th>
<th><?= __("Confirmation date"); ?></th>
<th><?= __("Type"); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($result as $qso) {
$qsotimestamp = strtotime($qso->col_time_on);
$confirmationtimestamp = strtotime($qso->rxdate);
?>
<tr>
<td style="text-align: center; vertical-align: middle;" ><a href="javascript:displayQso('<?php echo $qso->col_primary_key; ?>')"><?php echo $qso->col_call; ?></a></td>
<td style="text-align: center; vertical-align: middle;" ><?php echo date($custom_date_format, $qsotimestamp) . ' ' . date('H:i', $qsotimestamp)?></td>
<td style="text-align: center; vertical-align: middle;" ><?php echo $qso->col_submode == null ? $qso->col_mode : $qso->col_submode;?></td>
<td style="text-align: center; vertical-align: middle;" ><?php if($qso->col_sat_name != null) { echo $qso->col_sat_name; } else { echo strtolower($qso->col_band); };?></td>
<td style="text-align: center; vertical-align: middle;" ><?php echo date($custom_date_format, $confirmationtimestamp);
if (date('H:i:s', $confirmationtimestamp) !== '00:00:00') {
echo ' ' . date('H:i', $confirmationtimestamp);
}
if ($qso->qslcount ?? 0 != 0) {
echo ' <a href="javascript:displayQsl('.$qso->col_primary_key.');"><i class="fa fa-id-card"></i></a>';
}
if ($qso->type == 'eQSL') {
echo ' <a href="'.site_url('eqsl/image/'.$qso->col_primary_key).'" data-fancybox="images" data-width="528" data-height="336"><i class="fa fa-id-card"></i></a>';
}
?></td>
<td style="text-align: center; vertical-align: middle;" ><?php echo $qso->type;?></td>
</tr>
<?php } ?>
</tbody>
<table>
</div>
<?php } else {
echo __("No confirmations found.");
} ?>

View File

@@ -0,0 +1,27 @@
<div class="container">
<br>
<h2><?php echo $page_title; ?></h2>
<?= __("A maximum of 1000 rows are shown in the table. This is for performance reasons."); ?>
<div class="d-flex mt-2">
<form class="form confirmationform">
<div class="row mb-2">
<label class="col-md-1 w-auto" for="confirmationtype"><?= __("Confirmation type") ?></label>
<div class="col-sm-4 w-auto">
<select class="form-select form-select-sm w-auto" id="confirmationtype" multiple="multiple">
<option value="lotw" selected>LoTW</option>
<option value="qsl" selected>QSL card</option>
<option value="eqsl" selected>eQSL</option>
<option value="qrz" selected>QRZ.com</option>
<option value="clublog" selected>Clublog</option>
</select>
</div>
<button id="confirmationbutton" type="button" name="plot" class="w-auto btn btn-sm btn-primary me-1 mb-1 ld-ext-right ld-ext-right-plot" onclick="getConfirmations(this.form,'false')"><?= __("Show"); ?><div class="ld ld-ring ld-spin"></div></button>
</div>
</form>
</div>
<div id="searchresult"></div>
</div>

77
assets/js/sections/qsl.js Normal file
View File

@@ -0,0 +1,77 @@
function searchAdditionalQsos(filename) {
$.ajax({
url: base_url + 'index.php/qsl/searchQsos',
type: 'post',
data: {'callsign': $('#callsign').val(), 'filename': filename},
success: function(html) {
$('#searchresult').empty();
$('#searchresult').append(html);
}
});
}
function getConfirmations() {
let selectedQslTypes = $('#confirmationtype').val();
if (Array.isArray(selectedQslTypes) && selectedQslTypes.length === 0) {
BootstrapDialog.alert({
title: 'INFO',
message: 'You need to select at least one QSL type to do a search!',
type: BootstrapDialog.TYPE_INFO,
closable: false,
draggable: false,
callback: function (result) {
}
});
return false;
}
$('#confirmationbutton').prop("disabled", true).addClass("running");
$.ajax({
url: base_url + 'index.php/qsl/searchConfirmations',
type: 'post',
data: {'type': $('#confirmationtype').val()},
success: function(html) {
$('#searchresult').empty();
$('#searchresult').append(html);
$(".confirmationtable").DataTable({
responsive: false,
scrollY: window.innerHeight - $('.confirmationform').innerHeight() - 300,
scrollCollapse: true,
paging: false,
scrollX: true,
ordering: false,
language: {
url: getDataTablesLanguageUrl(),
},
dom: "Bfrtip",
buttons: [
{
extend: 'csv'
},
{
extend: 'clear',
text: lang_admin_clear
}
]
});
$('#confirmationbutton').prop("disabled", false).removeClass("running");
}
});
}
$(document).ready(function () {
if ($('#confirmationtype').length) {
$('#confirmationtype').multiselect({
enableFiltering: false,
enableCaseInsensitiveFiltering: false,
filterPlaceholder: lang_general_word_search,
templates: {
button: '<button type="button" class="multiselect dropdown-toggle btn btn-sm btn-secondary me-2 w-auto" data-bs-toggle="dropdown" aria-expanded="false"><span class="multiselect-selected-text"></span></button>',
},
numberDisplayed: 1,
inheritClass: true,
includeSelectAllOption: true
});
}
});