Merge pull request #2339 from szporwolik/dev

HAMqsl integration & Solar/Propagation information can be displayed at the dashboard
This commit is contained in:
Florian (DF2ET)
2025-09-24 13:18:40 +02:00
committed by GitHub
12 changed files with 370 additions and 4 deletions

View File

@@ -22,7 +22,7 @@ $config['migration_enabled'] = TRUE;
|
*/
$config['migration_version'] = 256;
$config['migration_version'] = 257;
/*
|--------------------------------------------------------------------------

View File

@@ -79,6 +79,14 @@ class Dashboard extends CI_Controller {
$data['dashboard_banner'] = 'N';
}
// Check user preferrence to show Solar Data on Dashboard
// Default to not show
if (($this->session->userdata('user_dashboard_solar') ?? '') != '') {
$data['dashboard_solar'] = $this->session->userdata('user_dashboard_solar') ?? 'N';
} else {
$data['dashboard_solar'] = 'N'; // Default to not show
}
$data['user_map_custom'] = $this->optionslib->get_map_custom();
$this->load->model('cat');
@@ -179,6 +187,23 @@ class Dashboard extends CI_Controller {
$data['total_countries_needed'] = count($dxcc->result()) - $current;
// Check user preferrence to show Solar Data on Dashboard and load data if yes
// Default to not show
if($data['dashboard_solar'] == 'Y') {
$this->load->model('Hamqsl_model'); // Load HAMQSL model
if (!$this->Hamqsl_model->set_solardata()) {
// Problem getting data, set to null
$data['solar_bandconditions'] = null;
$data['solar_solardata'] = null;
} else {
// Load data into arrays
$data['solar_bandconditions'] = $this->Hamqsl_model->get_bandconditions_array();
$data['solar_solardata'] = $this->Hamqsl_model->get_solarinformation_array();
}
}
// Load the views
$this->load->view('interface_assets/header', $data);
$this->load->view('dashboard/index');
$this->load->view('interface_assets/footer', $footerData);

View File

@@ -112,6 +112,7 @@ class Debug extends CI_Controller
$data['wwff_update'] = $this->cron_model->cron('update_update_wwff')->row();
$data['tle_update'] = $this->cron_model->cron('update_update_tle')->row();
$data['hon_update'] = $this->cron_model->cron('update_update_hamsofnote')->row();
$data['hamqsl_update'] = $this->cron_model->cron('update_update_hamqsl')->row();
$data['page_title'] = __("Debug");

View File

@@ -438,6 +438,29 @@ class Update extends CI_Controller {
}
}
/*
* Pulls the solarxml.php data from hamqsl
*/
public function update_hamqsl() {
$this->load->model('Update_model');
$result = $this->Update_model->hamqsl();
if($this->session->userdata('user_type') == '99') {
if (substr($result, 0, 4) == 'DONE') {
$this->session->set_flashdata('success', __("HAMqsl Update complete. Result: ") . "'" . $result . "'");
} else {
$this->session->set_flashdata('error', __("HAMqsl Update failed. Result: ") . "'" . $result . "'");
}
$this->load->model('cron_model');
$this->cron_model->set_last_run($this->router->class.'_'.$this->router->method);
redirect('debug');
} else {
echo $result;
}
}
public function update_pota() {
$this->load->model('Update_model');

View File

@@ -206,6 +206,7 @@ class User extends CI_Controller {
$data['user_measurement_base'] = $this->input->post('user_measurement_base') ?? 'K';
$data['user_dashboard_map'] = $this->input->post('user_dashboard_map') ?? 'Y';
$data['user_dashboard_banner'] = $this->input->post('user_dashboard_banner') ?? 'Y';
$data['user_dashboard_solar'] = $this->input->post('user_dashboard_solar') ?? 'Y';
$data['user_stylesheet'] = $this->input->post('user_stylesheet');
$data['user_qth_lookup'] = $this->input->post('user_qth_lookup');
$data['user_sota_lookup'] = $this->input->post('user_sota_lookup');
@@ -299,6 +300,7 @@ class User extends CI_Controller {
$this->input->post('on_air_widget_show_only_most_recent_radio'),
$this->input->post('qso_widget_display_qso_time'),
$this->input->post('user_dashboard_banner') ?? 'Y',
$this->input->post('user_dashboard_solar') ?? 'Y',
$this->input->post('clubstation') == '1' ? true : false,
$this->input->post('global_oqrs_text') ?? '',
$this->input->post('oqrs_grouped_search') ?? 'off',
@@ -336,6 +338,7 @@ class User extends CI_Controller {
$data['user_measurement_base'] = $this->input->post('user_measurement_base');
$data['user_dashboard_map'] = $this->input->post('user_dashboard_map') ?? 'Y';
$data['user_dashboard_banner'] = $this->input->post('user_dashboard_banner') ?? 'Y';
$data['user_dashboard_solar'] = $this->input->post('user_dashboard_solar') ?? 'Y';
$data['user_stylesheet'] = $this->input->post('user_stylesheet');
$data['user_qth_lookup'] = $this->input->post('user_qth_lookup');
$data['user_sota_lookup'] = $this->input->post('user_sota_lookup');
@@ -717,6 +720,16 @@ class User extends CI_Controller {
}
}
// Dashboard solar data information widget
if($this->input->post('user_dashboard_solar')) {
$data['user_dashboard_solar'] = $this->input->post('user_dashboard_solar', false);
} else {
$dkey_opt=$this->user_options_model->get_options('dashboard',array('option_name'=>'show_dashboard_solar','option_key'=>'boolean'), $this->uri->segment(3))->result();
if (count($dkey_opt)>0) {
$data['user_dashboard_solar'] = $dkey_opt[0]->option_value;
}
}
if($this->input->post('user_hamsat_workable_only')) {
$data['user_hamsat_workable_only'] = $this->input->post('user_hamsat_workable_only', false);
} else {

View File

@@ -0,0 +1,48 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
class Migration_add_cron_hamqsl extends CI_Migration {
public function up() {
// Add cron job for HAMqsl update
if ($this->db->table_exists('cron')) {
// add cron job for HAMqsl update
$data = array(
array(
'id' => 'update_update_hamqsl',
'enabled' => '1',
'status' => 'pending',
'description' => 'Download HamQSL HF Propagation Tools and Solar Data',
'function' => 'index.php/update/update_hamqsl',
'expression' => '0 */1 * * *',
'last_run' => null,
'next_run' => null
)
);
// Check if the cron job already exists
$this->db->where('id', 'update_update_hamqsl');
$query = $this->db->get('cron');
if ($query->num_rows() == 0) {
// Insert the cron job only if it does not exist
$this->db->insert_batch('cron', $data);
}
}
}
public function down() {
// Remove cron job for HAMqsl update
if ($this->chk4cron('update_update_hamqsl') > 0) {
$this->db->query("delete from cron where id = 'update_update_hamqsl';");
}
}
function chk4cron($cronkey) {
// Check if a cron job with the given ID exists
$query = $this->db->query("select count(id) as cid from cron where id=?",$cronkey);
$row = $query->row();
return $row->cid ?? 0;
}
}

View File

@@ -0,0 +1,125 @@
<?php
class Hamqsl_model extends CI_Model {
public $solarData = null;
function set_solardata() {
// Reads solar data from local XML file and sets $this->solarData
// Returns true if data was read, false if not
// The XML file shall be updated every 60 minutes by a cron job
$xmlData = null;
if (file_exists("./updates/solarxml.xml")) {
$xmlstr = file_get_contents("./updates/solarxml.xml");
if ($xmlstr !== false) {
try {
$xmlData = new SimpleXMLElement($xmlstr);
} catch (Exception $e) {
// Do nothing
}
}
}
if($xmlData) {
$this->solarData = $xmlData;
return true;
} else {
$this->solarData = null;
return false;
}
}
function get_bandconditions(string $name, string $time) {
// Returns the band condition for a given band name and time of day from the provided XML root
// If the data is not available, returns null
// Example: get_band_condition('80m-40m', 'day')
// Returns: 'Good', 'Fair', 'Poor', or null if not found
if (!$this->solarData) {
if (!$this->set_solardata()) {
return null; // Unable to load data
}
}
// Properly escape values for XPath
$escapeForXPath = function($value) {
if (strpos($value, "'") === false) {
return "'$value'";
} elseif (strpos($value, '"') === false) {
return "\"$value\"";
} else {
return "concat('" . str_replace("'", "',\"'\",'", $value) . "')";
}
};
$nameEscaped = $escapeForXPath($name);
$timeEscaped = $escapeForXPath($time);
$xpathQuery = "/solardata/calculatedconditions/band[@name=$nameEscaped and @time=$timeEscaped]";
$result = $this->solarData->xpath($xpathQuery);
if ($result && count($result) > 0) {
return trim((string)$result[0]);
} else {
return null; // Not found
}
}
function get_bandconditions_array() {
// Returns an associative array of all band conditions from the XML data
// The array structure is: [band_name][time_of_day] = condition
// Example: $conditions['80m-40m']['day'] = 'Good'
// Returns null if data is not available
if (!$this->solarData) {
if (!$this->set_solardata()) {
return null; // Unable to load data
}
}
$conditions = [];
if (isset($this->solarData->solardata->calculatedconditions->band)) {
foreach ($this->solarData->solardata->calculatedconditions->band as $band) {
$name = (string)$band['name'];
$time = (string)$band['time'];
$condition = trim((string)$band);
if (!isset($conditions[$name])) {
$conditions[$name] = [];
}
$conditions[$name][$time] = $condition;
}
}
return $conditions; // Return the associative array
}
function get_solarinformation_array() {
// Returns an associative array of all information from the XML data,
// including band conditions, without filtering anything out.
// The 'updated' field is converted to "d M H:i \G\M\T" format.
// Returns null if data is not available.
if (!$this->solarData) {
if (!$this->set_solardata()) {
return null; // Unable to load data
}
}
// Find the <solardata> node (handle both root and nested)
$solardata = isset($this->solarData->solardata) ? $this->solarData->solardata : $this->solarData;
// Convert the entire <solardata> node to an associative array
$solarinformation = json_decode(json_encode($solardata), true);
// Format the 'updated' field if it exists
if (isset($solarinformation['updated'])) {
$timestamp = strtotime($solarinformation['updated']);
if ($timestamp !== false) {
$solarinformation['updated'] = gmdate('d M H:i \G\M\T', $timestamp);
}
}
return $solarinformation; // Return the associative array
}
}
?>

View File

@@ -177,6 +177,38 @@ class Update_model extends CI_Model {
}
}
function hamqsl(){
// This downloads and stores hamqsl propagation data XML file
$url = 'https://www.hamqsl.com/solarxml.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Wavelog Updater');
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
$contents = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($contents === FALSE || $http_code != 200) {
return "Something went wrong with fetching the solarxml.xml file from HAMqsl website.";
} else {
$file = './updates/solarxml.xml';
if (file_put_contents($file, $contents) !== FALSE) { // Save our content to the file.
$nCount = count(file($file));
if ($nCount > 0) {
return "DONE: solarxml.xml downloaded from HAMqsl website.";
} else {
return "FAILED: Empty file received from HAMqsl website.";
}
} else {
return "FAILED: Could not write solarxml.xml file from HAMqsl website.";
}
}
}
function pota() {
// set the last run in cron table for the correct cron id
$this->load->model('cron_model');
@@ -499,6 +531,7 @@ class Update_model extends CI_Model {
return;
}
function update_hams_of_note() {
if (($this->optionslib->get_option('hon_url') ?? '') == '') {
$file = 'https://api.ham2k.net/data/ham2k/hams-of-note.txt';

View File

@@ -224,7 +224,7 @@ class User_Model extends CI_Model {
$user_wwff_to_qso_tab, $user_pota_to_qso_tab, $user_sig_to_qso_tab, $user_dok_to_qso_tab,
$user_lotw_name, $user_lotw_password, $user_eqsl_name, $user_eqsl_password, $user_clublog_name, $user_clublog_password,
$user_winkey, $on_air_widget_enabled, $on_air_widget_display_last_seen, $on_air_widget_show_only_most_recent_radio,
$qso_widget_display_qso_time, $dashboard_banner, $clubstation = 0) {
$qso_widget_display_qso_time, $dashboard_banner,$dashboard_solar, $clubstation = 0) {
// Check that the user isn't already used
if(!$this->exists($username)) {
$data = array(
@@ -308,6 +308,7 @@ class User_Model extends CI_Model {
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'qso_tab','dok','show',".(xss_clean($user_dok_to_qso_tab ?? 'off') == "on" ? 1 : 0).");");
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'dashboard','show_map','boolean','".xss_clean($dashboard_map ?? 'Y')."');");
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'dashboard','show_dashboard_banner','boolean','".xss_clean($dashboard_banner ?? 'Y')."');");
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'dashboard','show_dashboard_solar','boolean','".xss_clean($dashboard_solar ?? 'Y')."');");
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'widget','on_air','enabled','".(xss_clean($on_air_widget_enabled ?? 'false'))."');");
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'widget','on_air','display_last_seen','".(xss_clean($on_air_widget_display_last_seen ?? 'false'))."');");
$this->db->query("insert into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $insert_id . ", 'widget','on_air','display_only_most_recent_radio','".(xss_clean($on_air_widget_show_only_most_recent_radio ?? 'true'))."');");
@@ -385,10 +386,12 @@ class User_Model extends CI_Model {
$this->db->query("replace into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $fields['id'] . ", 'qso_tab','last_qso_count','count','".$qso_page_last_qso_count."');");
$this->db->query("replace into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $fields['id'] . ", 'dashboard','show_map','boolean','".xss_clean($fields['user_dashboard_map'] ?? 'Y')."');");
$this->db->query("replace into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $fields['id'] . ", 'dashboard','show_dashboard_banner','boolean','".xss_clean($fields['user_dashboard_banner'] ?? 'Y')."');");
$this->db->query("replace into user_options (user_id, option_type, option_name, option_key, option_value) values (" . $fields['id'] . ", 'dashboard','show_dashboard_solar','boolean','".xss_clean($fields['user_dashboard_solar'] ?? 'N')."');");
$this->session->set_userdata('dashboard_last_qso_count', $dashboard_last_qso_count);
$this->session->set_userdata('qso_page_last_qso_count', $qso_page_last_qso_count);
$this->session->set_userdata('user_dashboard_map',xss_clean($fields['user_dashboard_map'] ?? 'Y'));
$this->session->set_userdata('user_dashboard_banner',xss_clean($fields['user_dashboard_banner'] ?? 'Y'));
$this->session->set_userdata('user_dashboard_solar',xss_clean($fields['user_dashboard_solar'] ?? 'N'));
// Check to see if the user is allowed to change user levels
if($this->session->userdata('user_type') == 99) {
@@ -547,6 +550,7 @@ class User_Model extends CI_Model {
'user_measurement_base' => $u->row()->user_measurement_base,
'user_dashboard_map' => ((($this->session->userdata('user_dashboard_map') ?? 'Y') == 'Y') ? $this->user_options_model->get_options('dashboard', array('option_name' => 'show_map', 'option_key' => 'boolean'))->row()->option_value ?? 'Y' : $this->session->userdata('user_dashboard_map')),
'user_dashboard_banner' => ((($this->session->userdata('user_dashboard_banner') ?? 'Y') == 'Y') ? $this->user_options_model->get_options('dashboard', array('option_name' => 'show_dashboard_banner', 'option_key' => 'boolean'))->row()->option_value ?? 'Y' : $this->session->userdata('user_dashboard_banner')),
'user_dashboard_solar' => ((($this->session->userdata('user_dashboard_solar') ?? 'N') == 'Y') ? $this->user_options_model->get_options('dashboard', array('option_name' => 'show_dashboard_solar', 'option_key' => 'boolean'))->row()->option_value ?? 'N' : $this->session->userdata('user_dashboard_solar')),
'user_date_format' => $u->row()->user_date_format,
'user_stylesheet' => $u->row()->user_stylesheet,
'user_qth_lookup' => isset($u->row()->user_qth_lookup) ? $u->row()->user_qth_lookup : 0,

View File

@@ -456,6 +456,85 @@ function getDistance($distance) {
</table>
<?php } ?>
<?php if ((($solar_bandconditions ?? '') != '') && (($solar_solardata ?? '') != '')){ ?>
<!-- Solar Data -->
<table class="table table-striped border-top">
<tr class="titles">
<td colspan="10" style="display: flex; justify-content: space-between; align-items: center;">
<div>
<div><i class="fas fa-sun"></i> <?= __("Solar Data & Propagation"); ?></div>
<div class="small fst-italic text-muted">
<?= sprintf(__("Last update at %s."), $solar_solardata['updated']); ?>
</div>
</div>
<a class="ms-2 text-body fas fa-info-circle float-end"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-html="true"
href="https://www.hamqsl.com/"
target="_blank"
title="<?= __("Data provided by HAMqsl."); ?>"
style="cursor: pointer;">
</a>
</td>
<tr>
<td colspan="10">
<table class="table table-sm small text-center table-striped">
<tr>
<th width="20%">&nbsp;</th>
<th width="20%">80m-40m</th>
<th width="20%">30m-20m</th>
<th width="20%">17m-15m</th>
<th width="20%">12m-10m</th>
</tr>
<tr>
<td>Day</td>
<td><?= $solar_bandconditions['80m-40m']['day'] ?></td>
<td><?= $solar_bandconditions['30m-20m']['day'] ?></td>
<td><?= $solar_bandconditions['17m-15m']['day'] ?></td>
<td><?= $solar_bandconditions['12m-10m']['day'] ?></td>
</tr>
<tr>
<td>Night</td>
<td><?= $solar_bandconditions['80m-40m']['night'] ?></td>
<td><?= $solar_bandconditions['30m-20m']['night'] ?></td>
<td><?= $solar_bandconditions['17m-15m']['night'] ?></td>
<td><?= $solar_bandconditions['12m-10m']['night'] ?></td>
</tr>
</table>
</td>
</tr>
<tr>
<td colspan="10">
<table class="table table-sm small text-center table-striped">
<tr>
<th width="5%">Kp</th>
<th width="5%">A</th>
<th width="15%">SFI</th>
<th width="15%">SW</th>
<th width="15%">SS</th>
<th width="15%">X</th>
<th width="20%">Noise</th>
<th width="20%">Aurora</th>
</tr>
<tr>
<td><?= $solar_solardata['kindex'] ?></td>
<td><?= $solar_solardata['aindex'] ?></td>
<td><?= $solar_solardata['solarflux'] ?></td>
<td><?= $solar_solardata['solarwind'] ?></td>
<td><?= $solar_solardata['signalnoise'] ?></td>
<td><?= $solar_solardata['xray'] ?></td>
<td><?= $solar_solardata['sunspots'] ?></td>
<td><?= $solar_solardata['aurora'] ?></td>
</tr>
</table>
</td>
</tr>
</table>
<?php } ?>
</div>
</div>
</div>

View File

@@ -554,15 +554,20 @@
<td><?php echo $wwff_update->last_run ?? __("never"); ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update/update_wwff'); ?>"><?= __("Update"); ?></a></td>
</tr>
<tr>
<tr>
<td><?= __("TLE update"); ?></td>
<td><?php echo $tle_update->last_run ?? __("never"); ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update/update_tle'); ?>"><?= __("Update"); ?></a></td>
</tr>
<tr>
<tr>
<td><?= __("Hams Of Note update"); ?></td>
<td><?php echo $hon_update->last_run ?? __("never"); ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update/update_hamsofnote'); ?>"><?= __("Update"); ?></a></td>
</tr>
<tr>
<td><?= __("HAMqsl"); ?></td>
<td><?php echo $hamqsl_update->last_run ?? __("never"); ?></td>
<td><a class="btn btn-sm btn-primary" href="<?php echo site_url('update/update_hamqsl'); ?>"><?= __("Update"); ?></a></td>
</tr>
</table>
</div>

View File

@@ -636,6 +636,16 @@
</select>
<small id="user_dashboard_banner_Help" class="form-text text-muted"><?= __("This allows to disable the global notification banner on the dashboard."); ?></small>
</div>
<div class="mb-3">
<label for="user_dashboard_solar"><?= __("Dashboard solar and propagation data"); ?></label>
<?php if(!isset($user_dashboard_solar)) { $user_dashboard_solar='N'; }?>
<select class="form-select" id="user_dashboard_solar" name="user_dashboard_solar" aria-describedby="user_dashboard_solar_Help" required>
<option value='Y' <?php if($user_dashboard_solar == "Y") { echo "selected=\"selected\""; } ?>><?= __("Enabled"); ?></option>
<option value='N' <?php if($user_dashboard_solar == "N") { echo "selected=\"selected\""; } ?>><?= __("Disabled"); ?></option>
</select>
<small id="user_dashboard_solar_Help" class="form-text text-muted"><?= __("This switches the display of the solar and propagation data on the dashboard."); ?></small>
</div>
</div>
</div>
</div>