diff --git a/application/libraries/Callbook.php b/application/libraries/Callbook.php index d3bbe6ab1..f58df3b2c 100644 --- a/application/libraries/Callbook.php +++ b/application/libraries/Callbook.php @@ -10,8 +10,55 @@ class Callbook { private $ci; + // Duration of session keys + + // QRZ.com + // They write that session keys have no guaranteed lifetime. We should cache it to reuse it, but also be prepared + // to get a new one if the session key is invalid. + // Older documents showed that the duration was between 12-24 hours. So we set it to 4 hours to be on the safe side. + // Ref.: https://www.qrz.com/docs/xml/current_spec.html + const QRZ_SESSION_DURATION = 14400; // 4 hours + private $qrz_session_cachekey = null; + + // QRZCQ.com + // I could not find any information about session key duration on their website. Let's cache it for at least 55 minutes. + // In code we are prepared for an invalid session key, so if the session key is invalid we will get a new one and retry the search. + // Ref.: https://www.qrzcq.com/docs/api/xml/ + const QRZCQ_SESSION_DURATION = 3300; // 55 minutes + private $qrzcq_session_cachekey = null; + + // HamQTH.com + // Session Key is valid for 1 hour according to their documentation. We set it just a few moments below that to 55 Minutes. + // Ref.: https://www.hamqth.com/developers.php + const HAMQTH_SESSION_DURATION = 3300; // 55 minutes + private $hamqth_session_cachekey = null; + + // QRZRU.com + // Session Key is valid for 1 hour according to their documentation. We set it just a few moments below that to 55 Minutes. + // Ref.: https://www.qrz.ru/help/api/xml + const QRZRU_SESSION_DURATION = 3300; // 55 minutes + private $qrzru_session_cachekey = null; + + // Some generic stuff + private $logbook_not_configured; + private $error_obtaining_sessionkey; + public function __construct() { $this->ci = & get_instance(); + + $this->ci->load->is_loaded('cache') ?: $this->ci->load->driver('cache', [ + 'adapter' => $this->ci->config->item('cache_adapter') ?? 'file', + 'backup' => $this->ci->config->item('cache_backup') ?? 'file', + 'key_prefix' => $this->ci->config->item('cache_key_prefix') ?? '' + ]); + + $this->qrz_session_cachekey = 'qrz_session_key_'.$this->ci->config->item('qrz_username'); + $this->qrzcq_session_cachekey = 'qrzcq_session_key_'.$this->ci->config->item('qrzcq_username'); + $this->hamqth_session_cachekey = 'hamqth_session_key_'.$this->ci->config->item('hamqth_username'); + $this->qrzru_session_cachekey = 'qrzru_session_key_'.$this->ci->config->item('qrzru_username'); + + $this->logbook_not_configured = __("Lookup not configured. Please review configuration."); + $this->error_obtaining_sessionkey = __("Error obtaining a session key for callbook. Error: %s"); } // TODO: @@ -66,179 +113,225 @@ class Callbook { function queryCallbook($callsign, $source) { switch ($source) { case 'qrz': - $callbook = $this->qrz($callsign, $this->ci->config->item('use_fullname')); + $callbook = $this->_qrz($callsign, $this->ci->config->item('use_fullname')); break; case 'qrzcq': - $callbook = $this->qrzcq($callsign); + $callbook = $this->_qrzcq($callsign); break; case 'hamqth': - $callbook = $this->hamqth($callsign); + $callbook = $this->_hamqth($callsign); break; case 'qrzru': - $callbook = $this->qrzru($callsign); + $callbook = $this->_qrzru($callsign); break; default: - $callbook['error'] = 'No callbook defined. Please review configuration.'; + $callbook['error'] = $this->logbook_not_configured; } log_message('debug', 'Callbook lookup for '.$callsign.' using '.$source.': '.((($callbook['error'] ?? '' ) != '') ? $callbook['error'] : 'Success')); return $callbook; } - function qrz($callsign, $fullname) { - if (!$this->ci->load->is_loaded('qrz')) { - $this->ci->load->library('qrz'); - } - if ($this->ci->config->item('qrz_username') == null || $this->ci->config->item('qrz_password') == null) { - $callbook['error'] = 'Lookup not configured. Please review configuration.'; - $callbook['source'] = $this->ci->qrz->sourcename(); - } else { - $username = $this->ci->config->item('qrz_username'); - $password = $this->ci->config->item('qrz_password'); + private function _qrz($callsign, $fullname) { + $this->ci->load->is_loaded('qrz') ?: $this->ci->load->library('qrz'); - if (!$this->ci->session->userdata('qrz_session_key')) { + $callbook['source'] = $this->ci->qrz->sourcename(); + $username = trim($this->ci->config->item('qrz_username') ?? ''); + $password = trim($this->ci->config->item('qrz_password') ?? ''); + + if ($username == '' || $password == '') { + $callbook['error'] = $this->logbook_not_configured; + } else { + + if (!$this->ci->cache->get($this->qrz_session_cachekey)) { $qrz_session_key = $this->ci->qrz->session($username, $password); - $this->ci->session->set_userdata('qrz_session_key', $qrz_session_key); + if (!$this->_validate_sessionkey($qrz_session_key)) { + $callbook['error'] = sprintf($this->error_obtaining_sessionkey, $qrz_session_key); + $this->ci->cache->delete($this->qrz_session_cachekey); + return $callbook; + } + $this->ci->cache->save($this->qrz_session_cachekey, $qrz_session_key, self::QRZ_SESSION_DURATION); } - $callbook = $this->ci->qrz->search($callsign, $this->ci->session->userdata('qrz_session_key'), $fullname); + $callbook = $this->ci->qrz->search($callsign, $this->ci->cache->get($this->qrz_session_cachekey), $fullname); if ($callbook['error'] ?? '' == 'Invalid session key') { $qrz_session_key = $this->ci->qrz->session($username, $password); - $this->ci->session->set_userdata('qrz_session_key', $qrz_session_key); - $callbook = $this->ci->qrz->search($callsign, $this->ci->session->userdata('qrz_session_key'), $fullname); + if (!$this->_validate_sessionkey($qrz_session_key)) { + $callbook['error'] = sprintf($this->error_obtaining_sessionkey, $qrz_session_key); + $this->ci->cache->delete($this->qrz_session_cachekey); + return $callbook; + } + $this->ci->cache->save($this->qrz_session_cachekey, $qrz_session_key, self::QRZ_SESSION_DURATION); + $callbook = $this->ci->qrz->search($callsign, $this->ci->cache->get($this->qrz_session_cachekey), $fullname); } if (strpos($callbook['error'] ?? '', 'Not found') !== false && strpos($callsign, "/") !== false) { $plaincall = $this->get_plaincall($callsign); // Now try again but give back reduced data, as we can't validate location and stuff (true at the end) - $callbook = $this->ci->qrz->search($plaincall, $this->ci->session->userdata('qrz_session_key'), $fullname, true); + $callbook = $this->ci->qrz->search($plaincall, $this->ci->cache->get($this->qrz_session_cachekey), $fullname, true); } } - $callbook['source'] = $this->ci->qrz->sourcename(); return $callbook; } - function qrzcq($callsign) { - if (!$this->ci->load->is_loaded('qrzcq')) { - $this->ci->load->library('qrzcq'); - } - if ($this->ci->config->item('qrzcq_username') == null || $this->ci->config->item('qrzcq_password') == null) { - $callbook['error'] = 'Lookup not configured. Please review configuration.'; - $callbook['source'] = $this->ci->qrzcq->sourcename(); - } else { - $username = $this->ci->config->item('qrzcq_username'); - $password = $this->ci->config->item('qrzcq_password'); + private function _qrzcq($callsign) { + $this->ci->load->is_loaded('qrzcq') ?: $this->ci->load->library('qrzcq'); - if (!$this->ci->session->userdata('qrzcq_session_key')) { + $callbook['source'] = $this->ci->qrzcq->sourcename(); + $username = trim($this->ci->config->item('qrzcq_username') ?? ''); + $password = trim($this->ci->config->item('qrzcq_password') ?? ''); + + if ($username == '' || $password == '') { + $callbook['error'] = $this->logbook_not_configured; + } else { + + if (!$this->ci->cache->get($this->qrzcq_session_cachekey)) { $result = $this->ci->qrzcq->session($username, $password); + if (!$this->_validate_sessionkey($result[1])) { + $callbook['error'] = sprintf($this->error_obtaining_sessionkey, $result[1]); + $this->ci->cache->delete($this->qrzcq_session_cachekey); + return $callbook; + } if ($result[0] == 0) { - $this->ci->session->set_userdata('qrzcq_session_key', $result[1]); + $this->ci->cache->save($this->qrzcq_session_cachekey, $result[1], self::QRZCQ_SESSION_DURATION); } else { - $data['error'] = __("QRZCQ Error").": ".$result[1]; - $data['source'] = $this->ci->qrzcq->sourcename(); - return $data; + $callbook['error'] = __("QRZCQ Error").": ".$result[1]; + return $callbook; } } - $callbook = $this->ci->qrzcq->search($callsign, $this->ci->session->userdata('qrzcq_session_key')); + $callbook = $this->ci->qrzcq->search($callsign, $this->ci->cache->get($this->qrzcq_session_cachekey)); if ($callbook['error'] ?? '' == 'Invalid session key') { $qrzcq_session_key = $this->ci->qrzcq->session($username, $password); - $this->ci->session->set_userdata('qrzcq_session_key', $qrzcq_session_key); - $callbook = $this->ci->qrzcq->search($callsign, $this->ci->session->userdata('qrzcq_session_key')); + if (!$this->_validate_sessionkey($qrzcq_session_key[1])) { + $callbook['error'] = sprintf($this->error_obtaining_sessionkey, $qrzcq_session_key[1]); + $this->ci->cache->delete($this->qrzcq_session_cachekey); + return $callbook; + } + $this->ci->cache->save($this->qrzcq_session_cachekey, $qrzcq_session_key[1], self::QRZCQ_SESSION_DURATION); + $callbook = $this->ci->qrzcq->search($callsign, $this->ci->cache->get($this->qrzcq_session_cachekey)); } if (strpos($callbook['error'] ?? '', 'Not found') !== false && strpos($callsign, "/") !== false) { $plaincall = $this->get_plaincall($callsign); // Now try again but give back reduced data, as we can't validate location and stuff (true at the end) - $callbook = $this->ci->qrzcq->search($plaincall, $this->ci->session->userdata('qrzcq_session_key'), true); + $callbook = $this->ci->qrzcq->search($plaincall, $this->ci->cache->get($this->qrzcq_session_cachekey), true); } } - $callbook['source'] = $this->ci->qrzcq->sourcename(); return $callbook; } - function hamqth($callsign) { - // Load the HamQTH library - if (!$this->ci->load->is_loaded('hamqth')) { - $this->ci->load->library('hamqth'); - } - if ($this->ci->config->item('hamqth_username') == null || $this->ci->config->item('hamqth_password') == null) { - $callbook['error'] = 'Lookup not configured. Please review configuration.'; - $callbook['source'] = $this->ci->hamqth->sourcename(); - } else { - $username = $this->ci->config->item('hamqth_username'); - $password = $this->ci->config->item('hamqth_password'); + private function _hamqth($callsign) { + $this->ci->load->is_loaded('hamqth') ?: $this->ci->load->library('hamqth'); - if (!$this->ci->session->userdata('hamqth_session_key')) { + $callbook['source'] = $this->ci->hamqth->sourcename(); + $username = trim($this->ci->config->item('hamqth_username') ?? ''); + $password = trim($this->ci->config->item('hamqth_password') ?? ''); + + if ($username == '' || $password == '') { + $callbook['error'] = $this->logbook_not_configured; + } else { + + if (!$this->ci->cache->get($this->hamqth_session_cachekey)) { $hamqth_session_key = $this->ci->hamqth->session($username, $password); - if ($hamqth_session_key == false) { - $callbook['error'] = __("Error obtaining a session key for HamQTH query"); - $callbook['source'] = $this->ci->hamqth->sourcename(); + if (!$this->_validate_sessionkey($hamqth_session_key)) { + $callbook['error'] = sprintf($this->error_obtaining_sessionkey, $hamqth_session_key); + $this->ci->cache->delete($this->hamqth_session_cachekey); return $callbook; } else { - $this->ci->session->set_userdata('hamqth_session_key', $hamqth_session_key); + $this->ci->cache->save($this->hamqth_session_cachekey, $hamqth_session_key, self::HAMQTH_SESSION_DURATION); } } - $callbook = $this->ci->hamqth->search($callsign, $this->ci->session->userdata('hamqth_session_key')); + $callbook = $this->ci->hamqth->search($callsign, $this->ci->cache->get($this->hamqth_session_cachekey)); // If HamQTH session has expired, start a new session and retry the search. if ($callbook['error'] == "Session does not exist or expired") { $hamqth_session_key = $this->ci->hamqth->session($username, $password); - $this->ci->session->set_userdata('hamqth_session_key', $hamqth_session_key); - $callbook = $this->ci->hamqth->search($callsign, $this->ci->session->userdata('hamqth_session_key')); + if (!$this->_validate_sessionkey($hamqth_session_key)) { + $callbook['error'] = sprintf($this->error_obtaining_sessionkey, $hamqth_session_key); + $this->ci->cache->delete($this->hamqth_session_cachekey); + return $callbook; + } + $this->ci->cache->save($this->hamqth_session_cachekey, $hamqth_session_key, self::HAMQTH_SESSION_DURATION); + $callbook = $this->ci->hamqth->search($callsign, $this->ci->cache->get($this->hamqth_session_cachekey)); } if (strpos($callbook['error'] ?? '', 'Not found') !== false && strpos($callsign, "/") !== false) { $plaincall = $this->get_plaincall($callsign); // Now try again but give back reduced data, as we can't validate location and stuff (true at the end) - $callbook = $this->ci->hamqth->search($plaincall, $this->ci->session->userdata('hamqth_session_key'), true); + $callbook = $this->ci->hamqth->search($plaincall, $this->ci->cache->get($this->hamqth_session_cachekey), true); } } - $callbook['source'] = $this->ci->hamqth->sourcename(); return $callbook; } - function qrzru($callsign) { - if (!$this->ci->load->is_loaded('qrzru')) { - $this->ci->load->library('qrzru'); - } - if ($this->ci->config->item('qrzru_username') == null || $this->ci->config->item('qrzru_password') == null) { - $callbook['error'] = 'Lookup not configured. Please review configuration.'; - $callbook['source'] = $this->ci->qrzru->sourcename(); - } else { - $username = $this->ci->config->item('qrzru_username'); - $password = $this->ci->config->item('qrzru_password'); + private function _qrzru($callsign) { + $this->ci->load->is_loaded('qrzru') ?: $this->ci->load->library('qrzru'); - if (!$this->ci->session->userdata('qrzru_session_key')) { + $callbook['source'] = $this->ci->qrzru->sourcename(); + $username = trim($this->ci->config->item('qrzru_username') ?? ''); + $password = trim($this->ci->config->item('qrzru_password') ?? ''); + + if ($username == '' || $password == '') { + $callbook['error'] = $this->logbook_not_configured; + } else { + + if (!$this->ci->cache->get($this->qrzru_session_cachekey)) { $result = $this->ci->qrzru->session($username, $password); - $this->ci->session->set_userdata('qrzru_session_key', $result); + if (!$this->_validate_sessionkey($result)) { + $callbook['error'] = sprintf($this->error_obtaining_sessionkey, $result); + $this->ci->cache->delete($this->qrzru_session_cachekey); + return $callbook; + } + $this->ci->cache->save($this->qrzru_session_cachekey, $result, self::QRZRU_SESSION_DURATION); } - $callbook = $this->ci->qrzru->search($callsign, $this->ci->session->userdata('qrzru_session_key')); + $callbook = $this->ci->qrzru->search($callsign, $this->ci->cache->get($this->qrzru_session_cachekey)); if ($callbook['error'] ?? '' == 'Session does not exist or expired') { $qrzru_session_key = $this->ci->qrzru->session($username, $password); - $this->ci->session->set_userdata('qrzru_session_key', $qrzru_session_key); - $callbook = $this->ci->qrzru->search($callsign, $this->ci->session->userdata('qrzru_session_key')); + if (!$this->_validate_sessionkey($qrzru_session_key)) { + $callbook['error'] = sprintf($this->error_obtaining_sessionkey, $qrzru_session_key); + $this->ci->cache->delete($this->qrzru_session_cachekey); + return $callbook; + } + $this->ci->cache->save($this->qrzru_session_cachekey, $qrzru_session_key, self::QRZRU_SESSION_DURATION); + $callbook = $this->ci->qrzru->search($callsign, $this->ci->cache->get($this->qrzru_session_cachekey)); } if (strpos($callbook['error'] ?? '', 'Callsign not found') !== false && strpos($callsign, "/") !== false) { $plaincall = $this->get_plaincall($callsign); // Now try again but give back reduced data, as we can't validate location and stuff (true at the end) - $callbook = $this->ci->qrzru->search($plaincall, $this->ci->session->userdata('qrzru_session_key'), true); + $callbook = $this->ci->qrzru->search($plaincall, $this->ci->cache->get($this->qrzru_session_cachekey), true); } } - $callbook['source'] = $this->ci->qrzru->sourcename(); return $callbook; } + private function _validate_sessionkey($key) { + // Session key must be a non-empty string + if ($key == false || $key == '' || !is_string($key)) { + return false; + } + + // All session keys should be at least 10 characters. Regarding to their documentation all keys have aprox. the same format + // "2331uf894c4bd29f3923f3bacf02c532d7bd9" + // Since it can differ and we want to don't overcomplicate things we simply check if the key is at least 10 characters long. + // If not, we consider it as invalid. + if (strlen($key) < 10) { + return false; + } + + return true; + } + function get_plaincall($callsign) { $split_callsign = explode('/', $callsign); if (count($split_callsign) == 1) { // case of plain callsign --> return callsign diff --git a/application/libraries/Hamqth.php b/application/libraries/Hamqth.php index 5c3bbc403..bddaca7b7 100644 --- a/application/libraries/Hamqth.php +++ b/application/libraries/Hamqth.php @@ -127,6 +127,7 @@ class Hamqth { } } finally { + $data['source'] = $this->sourcename(); return $data; } } diff --git a/application/libraries/Qrz.php b/application/libraries/Qrz.php index 161f2e00a..48d51d07c 100644 --- a/application/libraries/Qrz.php +++ b/application/libraries/Qrz.php @@ -182,6 +182,7 @@ class Qrz { $data['cqzone'] = ''; } } finally { + $data['source'] = $this->sourcename(); return $data; } } diff --git a/application/libraries/Qrzcq.php b/application/libraries/Qrzcq.php index 4518e072b..d8f99f241 100644 --- a/application/libraries/Qrzcq.php +++ b/application/libraries/Qrzcq.php @@ -143,6 +143,7 @@ class Qrzcq { } } finally { + $data['source'] = $this->sourcename(); return $data; } } diff --git a/application/libraries/Qrzru.php b/application/libraries/Qrzru.php index 63e423aef..fe659e13b 100644 --- a/application/libraries/Qrzru.php +++ b/application/libraries/Qrzru.php @@ -114,6 +114,7 @@ class Qrzru { $data['cqz'] = ''; } } finally { + $data['source'] = $this->sourcename(); return $data; } } diff --git a/application/locale/bg_BG/LC_MESSAGES/messages.po b/application/locale/bg_BG/LC_MESSAGES/messages.po index bbfb0120e..150ca8e96 100644 --- a/application/locale/bg_BG/LC_MESSAGES/messages.po +++ b/application/locale/bg_BG/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: translations@wavelog.org\n" -"POT-Creation-Date: 2026-02-20 07:12+0000\n" +"POT-Creation-Date: 2026-02-20 08:59+0000\n" "PO-Revision-Date: 2024-11-01 08:53+0000\n" "Last-Translator: Plamen Panteleev \n" "Language-Team: Bulgarian \n" "Language-Team: Bosnian \n" "Language-Team: Montenegrin \n" "Language-Team: Czech \n" "Language-Team: Greek \n" "Language-Team: Estonian \n" "Language-Team: Finnish \n" "Language-Team: Croatian \n" "Language-Team: Hungarian \n" "Language-Team: Armenian \n" "Language-Team: Italian \n" "Language-Team: Lithuanian \n" "Language-Team: Latvian \n" "Language-Team: Dutch \n" "Language-Team: Polish \n" "Language-Team: Portuguese (Portugal) \n" "Language-Team: Russian \n" "Language-Team: Slovak \n" "Language-Team: Albanian \n" "Language-Team: Serbian \n" "Language-Team: Turkish \n" "Language-Team: Chinese (Simplified Han script) \n" "Language-Team: LANGUAGE \n" @@ -3325,12 +3325,17 @@ msgstr "" msgid "Gridsquare Zone finder" msgstr "" -#: application/libraries/Callbook.php:139 -msgid "QRZCQ Error" +#: application/libraries/Callbook.php:60 +msgid "Lookup not configured. Please review configuration." msgstr "" -#: application/libraries/Callbook.php:179 -msgid "Error obtaining a session key for HamQTH query" +#: application/libraries/Callbook.php:61 +#, php-format +msgid "Error obtaining a session key for callbook. Error: %s" +msgstr "" + +#: application/libraries/Callbook.php:200 +msgid "QRZCQ Error" msgstr "" #: application/libraries/Cbr_parser.php:111