2026-01-01 13:51:49 +00:00
< ? php
use Wavelog\Dxcc\Dxcc ;
require_once APPPATH . '../src/Dxcc/Dxcc.php' ;
if ( ! defined ( 'BASEPATH' )) exit ( 'No direct script access allowed' );
2011-08-17 02:21:23 +01:00
class API extends CI_Controller {
2026-06-11 05:57:23 +00:00
public function __construct () {
parent :: __construct ();
// Web UI endpoints that don't need CORS
$web_ui_methods = [ 'index' , 'help' , 'edit' , 'generate' , 'delete' ];
$method = $this -> uri -> segment ( 2 , 'index' );
if ( ! in_array ( $method , $web_ui_methods , true )) {
// Preflight
if ( $_SERVER [ 'REQUEST_METHOD' ] === 'OPTIONS' ) {
header ( 'Access-Control-Allow-Origin: *' );
header ( 'Access-Control-Allow-Methods: POST, GET, OPTIONS' );
header ( 'Access-Control-Allow-Headers: Content-Type' );
header ( 'Access-Control-Max-Age: 86400' );
http_response_code ( 200 );
exit ( 0 );
}
header ( 'Access-Control-Allow-Origin: *' );
}
}
2024-11-20 17:33:17 +00:00
function index () {
2025-01-02 10:22:23 +01:00
if ( ! $this -> user_model -> authorize ( 3 )) { $this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " )); redirect ( 'dashboard' ); }
2011-12-09 17:34:50 +00:00
$this -> load -> model ( 'api_model' );
2026-06-10 22:43:49 +02:00
$this -> load -> model ( 'api_v2_model' );
2025-01-02 10:22:23 +01:00
$this -> load -> library ( 'form_validation' );
2011-12-09 17:34:50 +00:00
$data [ 'api_keys' ] = $this -> api_model -> keys ();
2026-06-10 22:43:49 +02:00
$data [ 'api_tokens' ] = $this -> api_v2_model -> get_tokens_for_user ();
2026-08-01 11:27:25 +02:00
$data [ 'token_scopes' ] = Api_v2_model :: grantable_scope_registry ();
2026-07-29 11:31:52 +02:00
$data [ 'token_presets' ] = Api_v2_model :: preset_registry ();
2026-06-10 22:43:49 +02:00
// One-time reveal: the plaintext token survives exactly one redirect.
$data [ 'new_api_token' ] = $this -> session -> flashdata ( 'new_api_token' );
2025-01-02 10:22:23 +01:00
$data [ 'clubmode' ] = $this -> session -> userdata ( 'clubstation' ) == 1 ? true : false ;
2024-06-08 11:01:59 +02:00
$data [ 'page_title' ] = __ ( " API " );
2011-11-04 17:32:03 +00:00
2019-01-09 15:18:46 +00:00
$this -> load -> view ( 'interface_assets/header' , $data );
2025-01-02 10:22:23 +01:00
$this -> load -> view ( 'api/index' );
2019-01-09 15:18:46 +00:00
$this -> load -> view ( 'interface_assets/footer' );
2011-09-30 16:51:35 +01:00
}
2025-01-02 10:22:23 +01:00
// legacy
function help () {
redirect ( 'api' );
}
2026-01-07 07:35:32 +00:00
/**
* Check rate limit for current endpoint
* Only enforced if api_rate_limits config is set
*
* returns True if request is allowed , false if rate limited
*/
protected function check_rate_limit ( $endpoint , $identifier = null ) {
if ( ! $this -> load -> is_loaded ( 'rate_limit' )) {
$this -> load -> library ( 'rate_limit' );
}
$result = $this -> rate_limit -> check ( $endpoint , $identifier );
if ( ! $result [ 'allowed' ]) {
2026-01-08 12:22:27 +01:00
log_message ( " Debug " , " Rate limit for endpoint " . $endpoint . " and ID: " . ( $identifier ? ? '' ) . " exceeded " );
2026-01-07 07:35:32 +00:00
$this -> rate_limit -> send_limit_exceeded_response ( $result [ 'retry_after' ]);
return false ;
}
return true ;
}
2019-07-09 17:18:19 +01:00
function edit ( $key ) {
2025-01-02 10:22:23 +01:00
if ( ! $this -> user_model -> authorize ( 3 )) { $this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " )); redirect ( 'dashboard' ); }
2019-07-09 17:18:19 +01:00
$this -> load -> model ( 'api_model' );
$this -> load -> helper ( array ( 'form' , 'url' ));
2026-01-07 07:35:32 +00:00
$this -> load -> library ( 'form_validation' );
2019-07-09 17:18:19 +01:00
2026-01-07 07:35:32 +00:00
$this -> form_validation -> set_rules ( 'api_desc' , __ ( " API Description " ), 'required' );
$this -> form_validation -> set_rules ( 'api_key' , __ ( " API Key is required. Do not change this field " ), 'required' );
2019-07-09 17:18:19 +01:00
2026-01-07 07:35:32 +00:00
$data [ 'api_info' ] = $this -> api_model -> key_description ( $key );
2019-07-09 17:18:19 +01:00
2026-01-07 07:35:32 +00:00
if ( $this -> form_validation -> run () == FALSE ) {
$data [ 'page_title' ] = __ ( " Edit API Description " );
2019-07-09 17:18:19 +01:00
$this -> load -> view ( 'interface_assets/header' , $data );
2026-07-17 08:36:01 +02:00
$this -> load -> view ( 'api/components/edit_legacy' );
2019-07-09 17:18:19 +01:00
$this -> load -> view ( 'interface_assets/footer' );
2026-01-07 07:35:32 +00:00
} else {
2019-07-09 17:18:19 +01:00
// Success!
2026-03-16 00:57:34 +01:00
$this -> api_model -> update_key_description ( $this -> input -> post ( 'api_key' , true ), $this -> input -> post ( 'api_desc' , true ));
2019-07-09 17:18:19 +01:00
2026-03-16 00:57:34 +01:00
$this -> session -> set_flashdata ( 'notice' , sprintf ( __ ( " API Key %s description has been updated. " ), " <b> " . htmlspecialchars ( $this -> input -> post ( 'api_key' , true ), ENT_QUOTES , 'UTF-8' ) . " </b> " ));
2019-07-09 17:18:19 +01:00
2025-01-02 10:22:23 +01:00
redirect ( 'api' );
2019-07-09 17:18:19 +01:00
}
}
2026-03-16 07:44:08 +00:00
function generate () {
// CSRF mitigation: reject non-POST requests
if ( $this -> input -> method () !== 'post' ) {
$this -> session -> set_flashdata ( 'error' , __ ( " Invalid request method " ));
redirect ( 'api' );
return ;
}
2025-01-02 10:22:23 +01:00
if ( ! $this -> user_model -> authorize ( 3 )) { $this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " )); redirect ( 'dashboard' ); }
2021-11-14 11:57:21 +00:00
2026-03-16 07:44:08 +00:00
$rights = $this -> input -> post ( 'rights' , TRUE );
2025-01-02 10:22:23 +01:00
if ( $rights !== " r " && $rights !== " rw " ) {
$this -> session -> set_flashdata ( 'error' , __ ( " Invalid API rights " ));
redirect ( 'api' );
2026-03-16 07:44:08 +00:00
return ;
2021-11-14 11:57:21 +00:00
}
2011-12-09 17:34:50 +00:00
$this -> load -> model ( 'api_model' );
2025-01-02 10:22:23 +01:00
if ( $this -> session -> userdata ( 'clubstation' ) == 1 && $this -> session -> userdata ( 'impersonate' ) == 1 ) {
$creator = $this -> session -> userdata ( 'source_uid' );
} else {
$creator = $this -> session -> userdata ( 'user_id' );
}
2011-12-09 17:34:50 +00:00
2025-01-02 10:22:23 +01:00
if ( $this -> api_model -> generate_key ( $rights , $creator )) {
$this -> session -> set_flashdata ( 'success' , __ ( " API Key generated " ));
} else {
$this -> session -> set_flashdata ( 'error' , __ ( " API Key could not be generated " ));
}
redirect ( 'api' );
2011-12-09 17:34:50 +00:00
}
2026-03-16 07:44:08 +00:00
function delete () {
// CSRF mitigation: reject non-POST requests
if ( $this -> input -> method () !== 'post' ) {
$this -> session -> set_flashdata ( 'error' , __ ( " Invalid request method " ));
redirect ( 'api' );
return ;
}
2025-01-02 10:22:23 +01:00
if ( ! $this -> user_model -> authorize ( 3 )) { $this -> session -> set_flashdata ( 'error' , __ ( " You're not allowed to do that! " )); redirect ( 'dashboard' ); }
2019-06-21 21:33:21 +01:00
2026-03-16 07:44:08 +00:00
$key = $this -> input -> post ( 'key' , TRUE );
if ( empty ( $key )) {
$this -> session -> set_flashdata ( 'error' , __ ( " Invalid API Key " ));
redirect ( 'api' );
return ;
}
2019-06-21 21:33:21 +01:00
$this -> load -> model ( 'api_model' );
$this -> api_model -> delete_key ( $key );
2026-03-16 00:57:34 +01:00
$this -> session -> set_flashdata ( 'notice' , sprintf ( __ ( " API Key %s has been deleted " ), " <b> " . htmlspecialchars ( $key , ENT_QUOTES , 'UTF-8' ) . " </b> " ));
2019-06-21 21:33:21 +01:00
2025-01-02 10:22:23 +01:00
redirect ( 'api' );
2019-06-21 21:33:21 +01:00
}
2011-12-09 17:34:50 +00:00
// Example of authing
2024-11-20 17:33:17 +00:00
function auth ( $key = '' ) {
2011-12-09 17:34:50 +00:00
$this -> load -> model ( 'api_model' );
2017-11-30 19:01:11 -07:00
header ( " Content-type: text/xml " );
2026-06-08 09:29:47 +02:00
if ( $this -> api_model -> authorize ( $key ) == 0 ) {
2011-12-09 17:34:50 +00:00
echo " <auth> " ;
2024-08-28 14:29:26 +02:00
echo " <message>Key Invalid - either not found or disabled</message> " ;
2011-12-09 17:34:50 +00:00
echo " </auth> " ;
} else {
echo " <auth> " ;
2024-08-28 14:29:26 +02:00
echo " <status>Valid</status> " ;
2011-12-09 17:34:50 +00:00
echo " <rights> " . $this -> api_model -> access ( $key ) . " </rights> " ;
echo " </auth> " ;
2024-01-13 18:17:35 +01:00
$this -> api_model -> update_last_used ( $key );
2011-12-09 17:35:48 +00:00
}
2011-12-09 17:34:50 +00:00
}
2025-11-16 11:06:56 +00:00
function create_station ( $key = '' ) {
2026-06-19 21:20:00 +08:00
header ( 'Content-type: application/json' );
2025-11-16 11:06:56 +00:00
$this -> load -> model ( 'api_model' );
2025-12-13 07:54:08 +00:00
2026-06-07 09:44:12 +02:00
$apiKeyResponse = $this -> api_model -> authorize ( $key ? ? '' );
if ( $apiKeyResponse == 0 ) {
http_response_code ( 401 );
2026-07-11 17:52:14 +02:00
log_message ( " Debug " , 'API Call 401. Invalid API Key: ' . ( $key ? ? 'N/A' ));
2026-06-07 09:44:12 +02:00
echo json_encode ([ 'status' => 'error' , 'reason' => " missing or wrong api key " ]);
die ();
}
if ( $apiKeyResponse == 1 ) {
http_response_code ( 403 );
log_message ( " Debug " , 'API Call 403. Insufficient permissions for API Key' );
echo json_encode ([ 'status' => 'error' , 'reason' => " API key does not have write permissions " ]);
die ();
2025-11-16 11:06:56 +00:00
}
2025-12-13 07:54:08 +00:00
$this -> load -> model ( 'club_model' );
$userid = $this -> api_model -> key_userid ( $key );
$created_by = $this -> api_model -> key_created_by ( $key );
$club_perm = $this -> club_model -> get_permission_noui ( $userid , $created_by );
if ( $userid != $created_by ) { // We're dealing with a Club Member/Member ADIF or Clubofficer
if ((( $club_perm ? ? 0 ) == 3 ) || (( $club_perm ? ? 0 ) == 6 )) { // Member or ADIF-Member? DENY
$this -> output -> set_status_header ( 401 ) -> set_content_type ( 'application/json' ) -> set_output ( json_encode ([ 'status' => 'error' , 'message' => 'Auth Error, not enough grants for this operation' ]));
return ;
}
}
2025-11-16 11:06:56 +00:00
try {
$raw = file_get_contents ( " php://input " );
if ( $raw === false ) {
throw new Exception ( " Failed to read input data " );
}
if ( empty ( $raw )) {
$this -> output -> set_status_header ( 400 ) -> set_content_type ( 'application/json' ) -> set_output ( json_encode ([ 'status' => 'error' , 'message' => 'No file uploaded' ]));
return ;
}
$raw = preg_replace ( '#<([eE][oO][rR])>[\r\n\t]+#' , '<$1>' , $raw );
if ( $raw === null ) {
throw new Exception ( " Regex processing failed " );
}
$locations = json_decode ( $raw , true );
if ( $locations === null ) {
$this -> output -> set_status_header ( 400 ) -> set_content_type ( 'application/json' ) -> set_output ( json_encode ([ 'status' => 'error' , 'message' => 'Invalid JSON file' ]));
return ;
}
2026-01-29 17:36:00 +00:00
// If a single station object is posted (not an array), wrap it in an array
if ( isset ( $locations [ 'station_callsign' ]) || isset ( $locations [ 'station_profile_name' ])) {
$locations = [ $locations ];
}
2025-11-16 11:06:56 +00:00
} catch ( Exception $e ) {
$this -> output -> set_status_header ( 500 ) -> set_content_type ( 'application/json' ) -> set_output ( json_encode ([ 'status' => 'error' , 'message' => 'Processing error: ' . $e -> getMessage ()]));
}
$this -> load -> model ( 'stationsetup_model' );
2026-07-11 17:52:14 +02:00
$imported = $this -> stationsetup_model -> import_locations_parse ( $locations ? ? [], $userid );
2025-11-16 11:06:56 +00:00
if (( $imported [ 0 ] ? ? '0' ) == 'limit' ) {
2025-11-16 11:15:03 +00:00
$this -> output -> set_status_header ( 201 ) -> set_content_type ( 'application/json' ) -> set_output ( json_encode ([ 'status' => 'success' , 'message' => ( $imported [ 1 ] ? ? '0' ) . " locations imported. Maximum limit of 1000 locations reached. " ]));
2025-11-16 11:06:56 +00:00
} else {
2025-11-16 11:15:03 +00:00
if (( $imported [ 1 ] ? ? 0 ) == 0 ) {
$this -> output -> set_status_header ( 200 ) -> set_content_type ( 'application/json' ) -> set_output ( json_encode ([ 'status' => 'dupe' , 'message' => ( $imported [ 1 ] ? ? '0' ) . " locations imported. " ]));
} else {
$this -> output -> set_status_header ( 201 ) -> set_content_type ( 'application/json' ) -> set_output ( json_encode ([ 'status' => 'success' , 'message' => ( $imported [ 1 ] ? ? '0' ) . " locations imported. " ]));
}
2025-11-16 11:06:56 +00:00
}
}
2024-11-20 17:31:02 +00:00
function station_info ( $key = '' ) {
2023-07-05 14:16:53 +00:00
$this -> load -> model ( 'api_model' );
$this -> load -> model ( 'stations' );
header ( " Content-type: application/json " );
2026-06-08 09:29:47 +02:00
if ( $this -> api_model -> authorize ( $key ) > 0 ) { /* Check permission for reading */
2023-07-05 14:16:53 +00:00
$this -> api_model -> update_last_used ( $key );
$userid = $this -> api_model -> key_userid ( $key );
2024-01-17 17:30:14 +01:00
$station_ids = array ();
2026-06-20 10:29:55 -07:00
$dkey_opt = $this -> user_options_model -> get_options ( 'stations' , array ( 'option_name' => 'active_log_only' , 'option_key' => 'boolean' ), $userid ) -> result ();
$user_stations_active_log_only = ( count ( $dkey_opt ) > 0 ) ? $dkey_opt [ 0 ] -> option_value : false ;
if ( $user_stations_active_log_only ) {
$stations = $this -> logbooks_model -> list_logbooks_linked ( $this -> logbooks_model -> find_active_station_logbook_from_userid ( $userid ));
} else {
$stations = $this -> stations -> all_of_user ( $userid );
}
2026-06-20 12:33:12 -07:00
if ( $stations !== FALSE ) {
foreach ( $stations -> result () as $row ) {
$result [ 'station_id' ] = $row -> station_id ;
$result [ 'station_profile_name' ] = $row -> station_profile_name ;
$result [ 'station_gridsquare' ] = $row -> station_gridsquare ;
$result [ 'station_callsign' ] = $row -> station_callsign ;;
$result [ 'station_active' ] = $row -> station_active ;
$result [ 'station_uuid' ] = $row -> station_uuid ;
$result [ 'station_city' ] = $row -> station_city ;
$result [ 'station_iota' ] = $row -> station_iota ;
$result [ 'station_sota' ] = $row -> station_sota ;
$result [ 'station_wwff' ] = $row -> station_wwff ;
$result [ 'station_pota' ] = $row -> station_pota ;
$result [ 'station_sig' ] = $row -> station_sig ;
$result [ 'station_sig_info' ] = $row -> station_sig_info ;
$result [ 'station_dxcc' ] = $row -> station_dxcc ;
$result [ 'station_cnty' ] = $row -> station_cnty ;
$result [ 'station_cq' ] = $row -> station_cq ;
$result [ 'station_itu' ] = $row -> station_itu ;
$result [ 'station_state' ] = $row -> state ;
$result [ 'station_country' ] = $row -> station_country ;
array_push ( $station_ids , $result );
}
2024-01-17 17:30:14 +01:00
}
2023-07-05 14:16:53 +00:00
echo json_encode ( $station_ids );
} else {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing or invalid api key " ]);
}
}
2026-01-04 15:48:44 +00:00
function check_auth ( $key = '' ) {
2025-05-29 10:09:38 +02:00
$this -> load -> model ( 'api_model' );
2026-06-08 09:29:47 +02:00
if ( $this -> api_model -> authorize ( $key ? ? '' ) == 0 ) {
2025-05-29 10:09:38 +02:00
// set the content type as json
header ( " Content-type: application/json " );
// set the http response code to 401
http_response_code ( 401 );
// return the json with the status as failed
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing or invalid api key " ]);
} else {
// set the content type as json
header ( " Content-type: application/json " );
// set the http response code to 200
http_response_code ( 200 );
// return the json
2026-01-04 15:48:44 +00:00
echo json_encode ([ 'status' => 'valid' , 'rights' => $this -> api_model -> access ( $key ? ? '' )]);
2025-05-29 10:09:38 +02:00
}
}
2011-08-17 02:21:23 +01:00
2024-01-17 17:30:14 +01:00
/*
2020-10-29 18:10:46 +00:00
*
* Function : QSO
2024-01-17 14:20:10 +00:00
* Task : allows passing of ADIF data to Wavelog
2020-10-29 18:10:46 +00:00
*/
2024-01-10 06:12:23 +00:00
function qso ( $dryrun = false ) {
2019-06-17 15:10:43 +01:00
header ( 'Content-type: application/json' );
2024-04-25 09:10:46 +00:00
set_time_limit ( 0 );
ini_set ( 'memory_limit' , '-1' );
2019-06-17 15:10:43 +01:00
2024-04-25 09:10:46 +00:00
session_write_close ();
2019-06-17 15:10:43 +01:00
$this -> load -> model ( 'api_model' );
2023-06-15 08:12:57 +02:00
$this -> load -> model ( 'stations' );
2025-12-13 07:27:04 +00:00
$this -> load -> model ( 'club_model' );
2023-06-15 08:12:57 +02:00
2024-12-30 14:16:33 +00:00
if ( ! $this -> load -> is_loaded ( 'Qra' )) {
$this -> load -> library ( 'Qra' );
}
2023-09-14 07:25:51 +02:00
$return_msg = array ();
$return_count = 0 ;
2019-06-17 15:10:43 +01:00
// Decode JSON and store
2024-04-25 09:10:46 +00:00
$raw = file_get_contents ( " php://input " );
2025-02-08 21:16:33 +01:00
$raw = $raw = preg_replace ( '#<([eE][oO][rR])>[\r\n\t]+#' , '<$1>' , $raw );
2024-04-25 09:10:46 +00:00
$obj = json_decode ( $raw , true );
2022-01-22 16:49:12 +01:00
if ( $obj === NULL ) {
2025-04-25 14:42:35 +00:00
log_message ( " Debug " , 'API Call 200. Wrong JSON provided: ' . $raw );
2022-01-22 16:49:12 +01:00
echo json_encode ([ 'status' => 'failed' , 'reason' => " wrong JSON " ]);
die ();
}
2026-01-07 07:35:32 +00:00
// Check rate limit
$identifier = isset ( $obj [ 'key' ]) ? $obj [ 'key' ] : null ;
$this -> check_rate_limit ( 'qso' , $identifier );
2025-04-25 14:42:35 +00:00
$raw = '' ;
2020-10-29 18:10:46 +00:00
2026-06-07 09:44:12 +02:00
$apiKeyResponse = $this -> api_model -> authorize ( $obj [ 'key' ] ? ? '' );
if ( ! isset ( $obj [ 'key' ]) || $apiKeyResponse == 0 ) {
2019-06-17 15:27:58 +01:00
http_response_code ( 401 );
2025-04-25 14:42:35 +00:00
log_message ( " Debug " , 'API Call 401. Invalid API Key: ' . ( $obj [ 'key' ] ? ? 'N/A' ));
2024-11-02 09:24:02 +01:00
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing or wrong api key " ]);
2019-06-17 15:10:43 +01:00
die ();
}
2026-06-07 09:44:12 +02:00
if ( $apiKeyResponse == 1 ) {
http_response_code ( 403 );
log_message ( " Debug " , 'API Call 403. Insufficient permissions for API Key' );
echo json_encode ([ 'status' => 'failed' , 'reason' => " API key does not have write permissions " ]);
die ();
}
2023-06-15 08:12:57 +02:00
$userid = $this -> api_model -> key_userid ( $obj [ 'key' ]);
2025-01-02 10:22:23 +01:00
$created_by = $this -> api_model -> key_created_by ( $obj [ 'key' ]);
2025-12-13 07:27:04 +00:00
$club_perm = $this -> club_model -> get_permission_noui ( $userid , $created_by );
2025-01-02 10:22:23 +01:00
/**
* As the API key user could use it also for clubstations we need to do an additional check here . Only if clubstations are enabled
2025-02-13 11:50:06 +01:00
*
2025-01-02 10:22:23 +01:00
* In Detail :
* If the user is not the creator of the API key , it ' s likely a clubstation . In this case the callsign of the clubstation
* can not be the same as the callsign of the user ( operator call provided by the user ) . If this is the case , we need to use the callsign of the creator of the API key
*/
2025-12-13 07:27:04 +00:00
$real_operator = null ; // real_operator is only filled if its a clubstation and the used key is created by an OP. otherwise its null
2025-01-02 10:22:23 +01:00
if ( $this -> config -> item ( 'special_callsign' )) {
if ( $userid != $created_by ) {
$real_operator = $this -> user_model -> get_by_id ( $created_by ) -> row () -> user_callsign ;
} else {
$real_operator = null ;
}
}
2024-04-25 09:10:46 +00:00
$this -> api_model -> update_last_used (( $obj [ 'key' ]));
2023-06-15 08:12:57 +02:00
2023-06-15 09:26:17 +02:00
if ( ! isset ( $obj [ 'station_profile_id' ]) || $this -> stations -> check_station_against_user ( $obj [ 'station_profile_id' ], $userid ) == false ) {
2023-06-15 08:12:57 +02:00
http_response_code ( 401 );
2025-04-25 14:42:35 +00:00
log_message ( " Debug " , 'API Call 401: Wrong station_id ' . ( $obj [ 'station_profile_id' ] ? ? 'N/A' ) . ' for User ' . $userid );
2023-06-15 08:12:57 +02:00
echo json_encode ([ 'status' => 'failed' , 'reason' => " station id does not belong to the API key owner. " ]);
die ();
}
2024-12-30 14:16:33 +00:00
$mystation = $this -> stations -> profile_clean ( $obj [ 'station_profile_id' ]);
$mygrid = ( $mystation -> station_gridsquare ? ? '' );
2023-06-15 08:12:57 +02:00
2019-06-17 15:10:43 +01:00
if ( $obj [ 'type' ] == " adif " && $obj [ 'string' ] != " " ) {
// Load the logbook model for adding QSO records
$this -> load -> model ( 'logbook_model' );
// Load ADIF Parser
2024-09-11 10:09:43 +02:00
if ( ! $this -> load -> is_loaded ( 'adif_parser' )) {
$this -> load -> library ( 'adif_parser' );
}
2019-06-17 15:10:43 +01:00
// Feed in the ADIF string
$this -> adif_parser -> feed ( $obj [ 'string' ]);
2024-04-25 09:10:46 +00:00
$obj [ 'string' ] = '' ;
$return_msg = [];
2024-10-10 16:01:46 +02:00
$adif_count = 0 ;
$adif_errors = 0 ;
2024-04-25 09:10:46 +00:00
if ( ! ( $dryrun ) && ( isset ( $obj [ 'station_profile_id' ]))) {
$custom_errors = " " ;
$alladif = [];
gc_collect_cycles ();
while ( $record = $this -> adif_parser -> get_record ()) {
2023-12-13 05:41:07 +00:00
if ( ! ( isset ( $record [ 'call' ])) || ( trim ( $record [ 'call' ]) == '' )) {
2024-04-25 09:10:46 +00:00
continue ;
2023-12-13 05:41:07 +00:00
}
2024-04-25 09:10:46 +00:00
if ( count ( $record ) == 0 ) {
break ;
2025-01-02 10:22:23 +01:00
}
2025-11-14 11:26:10 +01:00
// Handle slashed zeros
$record [ 'call' ] = str_replace ( 'Ø' , " 0 " , $record [ 'call' ]);
if (( $record [ 'operator' ] ? ? '' ) != '' ) {
$record [ 'operator' ] = str_replace ( 'Ø' , " 0 " , $record [ 'operator' ]);
}
if (( $record [ 'station_callsign' ] ? ? '' ) != '' ) {
$record [ 'station_callsign' ] = str_replace ( 'Ø' , " 0 " , $record [ 'station_callsign' ]);
}
if (( $record [ 'owner_callsign' ] ? ? '' ) != '' ) {
$record [ 'owner_callsign' ] = str_replace ( 'Ø' , " 0 " , $record [ 'owner_callsign' ]);
}
2025-01-02 10:22:23 +01:00
// in case the provided op call is the same as the clubstation callsign, we need to use the creator of the API key as the operator
$recorded_operator = $record [ 'operator' ] ? ? '' ;
2025-01-02 16:55:11 +00:00
if ( key_exists ( 'operator' , $record ) && $real_operator != null && ( $record [ 'operator' ] == $record [ 'station_callsign' ]) || ( $recorded_operator == '' )) {
2025-01-02 10:22:23 +01:00
$record [ 'operator' ] = $real_operator ;
}
2025-02-13 11:50:06 +01:00
2025-12-13 07:27:04 +00:00
// in case the caller is an OP for a clubstation (real_operator is filled - see above) and the OP only has level 3 or 6 - take the OP from real_operator!
if ( $real_operator != null && ((( $club_perm ? ? 0 ) == 3 ) || (( $club_perm ? ? 0 ) == 6 ))) {
$record [ 'operator' ] = $real_operator ;
}
2024-12-30 14:16:33 +00:00
if (( key_exists ( 'gridsquare' , $record )) && (( $mygrid ? ? '' ) != '' ) && (( $record [ 'gridsquare' ] ? ? '' ) != '' ) && ( ! ( key_exists ( 'distance' , $record )))) {
$record [ 'distance' ] = $this -> qra -> distance ( $mygrid , $record [ 'gridsquare' ], 'K' );
}
2024-04-25 09:10:46 +00:00
array_push ( $alladif , $record );
2024-10-10 16:01:46 +02:00
$adif_count ++ ;
2024-04-25 09:10:46 +00:00
};
$record = '' ; // free memory
gc_collect_cycles ();
2025-10-07 04:11:50 +00:00
$result = $this -> logbook_model -> import_bulk ( $alladif , $obj [ 'station_profile_id' ], true , false , false , false , false , false , false , false , true , false , true , false );
2025-08-22 15:04:11 +02:00
$custom_errors = $result [ 'errormessage' ];
2024-10-10 16:01:46 +02:00
if ( $custom_errors ) {
$adif_errors ++ ;
}
2024-04-25 09:10:46 +00:00
$alladif = [];
$return_msg [] = '' ;
} else {
$return_msg [] = 'Dryrun works' ;
}
2023-12-13 05:41:07 +00:00
2024-12-29 09:20:40 +00:00
if ( $adif_errors == 0 ) {
http_response_code ( 201 );
2025-04-25 14:42:35 +00:00
log_message ( " Debug " , 'API Call 201: QSO created for Station-ID: ' . ( $obj [ 'station_profile_id' ] ? ? 'N/A' ) . ' and User: ' . $userid );
2024-12-29 09:20:40 +00:00
echo json_encode ([ 'status' => 'created' , 'type' => $obj [ 'type' ], 'string' => $obj [ 'string' ], 'adif_count' => $adif_count , 'adif_errors' => $adif_errors , 'messages' => $return_msg ]);
} else {
$return_msg [] = $custom_errors ;
2025-04-25 14:42:35 +00:00
log_message ( " Debug " , 'API Call 400: QSO NOT created for Station-ID: ' . ( $obj [ 'station_profile_id' ] ? ? 'N/A' ) . ' and User: ' . $userid . ' Reason: ' . implode ( $return_msg ));
2024-12-29 09:20:40 +00:00
http_response_code ( 400 );
2024-12-29 09:22:26 +00:00
echo json_encode ([ 'status' => 'abort' , 'type' => $obj [ 'type' ], 'string' => $obj [ 'string' ], 'adif_count' => $adif_count , 'adif_errors' => $adif_errors , 'messages' => $return_msg ]);
2024-12-29 09:20:40 +00:00
}
2019-06-17 15:10:43 +01:00
}
}
2024-07-30 21:00:59 +00:00
/*
*
* Function : get_contacts_adif
* Task : allows third party software to pull ADIF QSO data from wavelog after a baseline of the last fetched QSO id
*/
2024-07-30 08:50:29 +00:00
function get_contacts_adif () {
//set header
header ( 'Content-type: application/json' );
//load API model
$this -> load -> model ( 'api_model' );
// Decode JSON and store
$obj = json_decode ( file_get_contents ( " php://input " ), true );
if ( $obj === NULL ) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " wrong JSON " ]);
return ;
}
2026-06-18 07:44:49 +00:00
$identifier = isset ( $obj [ 'key' ]) ? $obj [ 'key' ] : null ;
$this -> check_rate_limit ( 'get_contacts_adif' , $identifier );
2024-07-30 08:50:29 +00:00
//do authorization
if ( ! isset ( $obj [ 'key' ]) || $this -> api_model -> authorize ( $obj [ 'key' ]) == 0 ) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing api key " ]);
return ;
}
//check for relevant fields in JSON input
2024-07-30 21:00:59 +00:00
if ( ! isset ( $obj [ 'station_id' ]) or ! isset ( $obj [ 'fetchfromid' ]))
2024-07-30 08:50:29 +00:00
{
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " Not all required fields were present in input JSON " ]);
return ;
}
//extract relevant data to variables
$key = $obj [ 'key' ];
2024-07-30 21:00:59 +00:00
$fetchfromid = $obj [ 'fetchfromid' ];
2026-06-20 04:54:46 +00:00
$req_station_ids = is_array ( $obj [ 'station_id' ]) ? $obj [ 'station_id' ] : [ $obj [ 'station_id' ]];
if ( empty ( $req_station_ids )) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => '"station_id" must not be empty' ]);
return ;
}
$normalized_station_ids = [];
foreach ( $req_station_ids as $sid ) {
if ( ! is_numeric ( $sid )) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => '"station_id" values must be numeric' ]);
return ;
}
$normalized_station_ids [] = ( int ) $sid ;
}
$req_station_ids = array_values ( array_unique ( $normalized_station_ids ));
2024-09-18 11:10:12 +00:00
$limit = 20000 ;
2024-09-18 09:04:05 +00:00
if ( ( array_key_exists ( 'limit' , $obj )) && ( is_numeric ( $obj [ 'limit' ] * 1 )) ) {
$limit = $obj [ 'limit' ];
}
2024-07-30 08:50:29 +00:00
2026-04-01 07:55:00 +00:00
// output_format (optional, default: adif)
$output_format = 'adif' ;
if ( isset ( $obj [ 'output_format' ])) {
if ( ! in_array ( $obj [ 'output_format' ], [ 'adif' , 'json' ], true )) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => 'Invalid output_format. Use "adif" or "json"' ]);
return ;
}
$output_format = $obj [ 'output_format' ];
}
$fields = null ;
if ( isset ( $obj [ 'fields' ])) {
if ( $output_format !== 'json' ) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => '"fields" is only valid when output_format is "json"' ]);
return ;
}
if ( ! is_array ( $obj [ 'fields' ]) || empty ( $obj [ 'fields' ])) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => '"fields" must be a non-empty array' ]);
return ;
}
$requested_fields = array_map ( 'strtoupper' , $obj [ 'fields' ]);
2026-07-19 06:37:19 +00:00
$valid_adif_fields = [ 'ADDRESS' , 'AGE' , 'A_INDEX' , 'ANT_AZ' , 'ANT_EL' , 'ANT_PATH' , 'ARRL_SECT' , 'AWARD_GRANTED' , 'AWARD_SUBMITTED' , 'BAND' , 'BAND_RX' , 'BIOGRAPHY' , 'CALL' , 'CHECK' , 'CLASS' , 'CLUBLOG_QSO_UPLOAD_STATUS' , 'CNTY' , 'COMMENT' , 'CONT' , 'CONTACTED_OP' , 'CONTEST_ID' , 'COUNTRY' , 'CQZ' , 'CREDIT_GRANTED' , 'CREDIT_SUBMITTED' , 'DARC_DOK' , 'DISTANCE' , 'DXCC' , 'EMAIL' , 'EQ_CALL' , 'EQSL_QSL_RCVD' , 'EQSL_QSL_SENT' , 'EQSL_STATUS' , 'EQSL_AG' , 'FISTS' , 'FISTS_CC' , 'FORCE_INIT' , 'GRIDSQUARE' , 'HEADING' , 'IOTA' , 'ITUZ' , 'K_INDEX' , 'LAT' , 'LON' , 'LOTW_QSL_RCVD' , 'LOTW_QSL_SENT' , 'LOTW_STATUS' , 'MAX_BURSTS' , 'MODE' , 'MS_SHOWER' , 'NAME' , 'NOTES' , 'NR_BURSTS' , 'NR_PINGS' , 'OPERATOR' , 'OWNER_CALLSIGN' , 'PFX' , 'PRECEDENCE' , 'PROP_MODE' , 'PUBLIC_KEY' , 'HRDLOG_QSO_UPLOAD_STATUS' , 'QRZCOM_QSO_UPLOAD_STATUS' , 'QRZCOM_QSO_DOWNLOAD_STATUS' , 'QSLMSG' , 'QSL_RCVD' , 'QSL_RCVD_VIA' , 'QSL_SENT' , 'QSL_SENT_VIA' , 'QSL_VIA' , 'QSO_COMPLETE' , 'QSO_RANDOM' , 'QTH' , 'REGION' , 'RIG' , 'RST_RCVD' , 'RST_SENT' , 'RX_PWR' , 'SAT_MODE' , 'SAT_NAME' , 'SFI' , 'SILENT_KEY' , 'SKCC' , 'SOTA_REF' , 'WWFF_REF' , 'POTA_REF' , 'SRX' , 'SRX_STRING' , 'STATE' , 'STX' , 'STX_STRING' , 'SUBMODE' , 'SWL' , 'TEN_TEN' , 'TX_PWR' , 'UKSMG' , 'USACA_COUNTIES' , 'VUCC_GRIDS' , 'WEB' , 'CNTY_ALT' , 'MY_CNTY_ALT' , 'MY_DARC_DOK' , 'MORSE_KEY_INFO' , 'MORSE_KEY_TYPE' , 'QSLMSG_RCVD' , 'DCL_QSL_RCVD' , 'DCL_QSL_SENT' , 'EQSL_QSLRDATE' , 'EQSL_QSLSDATE' , 'LOTW_QSLRDATE' , 'LOTW_QSLSDATE' , 'QSLRDATE' , 'QSLSDATE' , 'CLUBLOG_QSO_UPLOAD_DATE' , 'HRDLOG_QSO_UPLOAD_DATE' , 'QRZCOM_QSO_UPLOAD_DATE' , 'QRZCOM_QSO_DOWNLOAD_DATE' , 'DCL_QSLRDATE' , 'DCL_QSLSDATE' , 'FREQ' , 'FREQ_RX' , 'QSO_DATE' , 'TIME_ON' , 'QSO_DATE_OFF' , 'TIME_OFF' , 'STATION_CALLSIGN' , 'MY_CITY' , 'MY_COUNTRY' , 'MY_DXCC' , 'MY_GRIDSQUARE' , 'MY_VUCC_GRIDS' , 'MY_IOTA' , 'MY_SOTA_REF' , 'MY_WWFF_REF' , 'MY_POTA_REF' , 'MY_CQ_ZONE' , 'MY_ITU_ZONE' , 'MY_STATE' , 'MY_CNTY' , 'MY_SIG' , 'MY_SIG_INFO' , 'SIG' , 'SIG_INFO' , 'MY_ANTENNA' , 'MY_ANTENNA_INTL' ];
2026-04-01 07:55:00 +00:00
$invalid_fields = array_diff ( $requested_fields , $valid_adif_fields );
if ( ! empty ( $invalid_fields )) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => 'Unknown fields: ' . implode ( ', ' , $invalid_fields )]);
return ;
}
$fields = $requested_fields ;
}
$qsl_filter = null ;
if ( isset ( $obj [ 'qsl_filter' ])) {
$allowed_qsl = [ 'lotw' , 'qsl' , 'eqsl' , 'clublog' ];
if ( ! is_array ( $obj [ 'qsl_filter' ]) || empty ( $obj [ 'qsl_filter' ])) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => '"qsl_filter" must be a non-empty array' ]);
return ;
}
$qsl_filter_input = array_map ( 'strtolower' , $obj [ 'qsl_filter' ]);
$invalid_qsl = array_diff ( $qsl_filter_input , $allowed_qsl );
if ( ! empty ( $invalid_qsl )) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => 'Invalid qsl_filter values: ' . implode ( ', ' , $invalid_qsl )]);
return ;
}
$qsl_filter = $qsl_filter_input ;
}
2026-04-01 09:04:04 +00:00
// band (optional)
$band = null ;
if ( isset ( $obj [ 'band' ])) {
$valid_bands = [ '160m' , '80m' , '60m' , '40m' , '30m' , '20m' , '17m' , '15m' , '12m' , '10m' , '6m' , '4m' , '2m' , '1.25m' , '70cm' , '33cm' , '23cm' , '13cm' , '9cm' , '6cm' , '3cm' , '1.25cm' , 'sat' ];
$band_input = strtolower ( trim ( $obj [ 'band' ]));
if ( ! in_array ( $band_input , $valid_bands , true )) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => 'Invalid band value' ]);
return ;
}
// Normalize: SAT uppercase (matches COL_PROP_MODE stored value), others lowercase (matches COL_BAND)
$band = ( $band_input === 'sat' ) ? 'SAT' : $band_input ;
}
2024-07-30 15:15:23 +00:00
//check if goalpost is numeric as an additional layer of SQL injection prevention
2024-07-30 21:00:59 +00:00
if ( ! is_numeric ( $fetchfromid ))
2024-07-30 15:15:23 +00:00
{
http_response_code ( 400 );
2024-07-30 21:00:59 +00:00
echo json_encode ([ 'status' => 'failed' , 'reason' => " Invalid fetchfromid. " ]);
2024-07-30 15:15:23 +00:00
return ;
}
//make sure the goalpost is an integer
2024-07-30 21:00:59 +00:00
$fetchfromid = ( int ) $fetchfromid ;
2024-07-30 15:15:23 +00:00
2024-07-30 08:50:29 +00:00
//load stations API
$this -> load -> model ( 'stations' );
//get all stations of user to check if station_id should be readable
$userid = $this -> api_model -> key_userid ( $key );
$station_ids = array ();
$stations = $this -> stations -> all_of_user ( $userid );
//extract to array
foreach ( $stations -> result () as $row ) {
array_push ( $station_ids , $row -> station_id );
}
2026-06-20 04:54:46 +00:00
foreach ( $req_station_ids as $station_id ) {
if ( ! in_array ( $station_id , $station_ids )) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " Station ID not accessible for this API key " ]);
return ;
}
2024-07-30 08:50:29 +00:00
}
//load adif data module
$this -> load -> model ( 'adif_data' );
2025-12-05 06:57:14 +00:00
$this -> load -> library ( 'AdifHelper' );
// Initialize tracking variables
$total_fetched = 0 ;
$all_qso_ids = [];
$lastfetchedid = $fetchfromid ;
// Process in chunks to avoid memory issues
$chunk_size = 5000 ;
$remaining_limit = $limit ;
$offset = 0 ;
2026-04-01 07:55:00 +00:00
$adif_content = ( $output_format === 'adif' ) ? $this -> adifhelper -> getAdifHeader ( $this -> config -> item ( 'app_name' ), $this -> optionslib -> get_option ( 'version' ), $this -> optionslib -> get_option ( 'adif_version' )) : '' ;
$qso_rows = [];
2025-12-05 06:57:14 +00:00
2026-04-01 09:34:11 +00:00
$seen_keys = [];
2025-12-05 06:57:14 +00:00
do {
// Calculate chunk size for this iteration
$current_chunk_size = min ( $chunk_size , $remaining_limit );
// Fetch chunk
2026-06-20 04:54:46 +00:00
$qsos = $this -> adif_data -> export_past_id_chunked ( $req_station_ids , $fetchfromid , $current_chunk_size , null , $offset , $current_chunk_size , $qsl_filter , $band );
2025-12-05 06:57:14 +00:00
if ( $qsos && $qsos -> num_rows () > 0 ) {
// Process chunk
foreach ( $qsos -> result () as $row ) {
2026-04-01 07:55:00 +00:00
if ( $output_format === 'json' ) {
2026-04-01 09:34:11 +00:00
$qso_data = $this -> _build_qso_array ( $row , $fields );
if ( $fields !== null ) {
$unique_key = '' ;
foreach ( $fields as $field ) {
$unique_key .= ( isset ( $qso_data [ $field ]) ? $qso_data [ $field ] : '' ) . '|' ;
}
if ( ! isset ( $seen_keys [ $unique_key ])) {
$seen_keys [ $unique_key ] = true ;
$qso_rows [] = $qso_data ;
}
} else {
$qso_rows [] = $qso_data ;
}
2026-04-01 07:55:00 +00:00
} else {
$adif_content .= $this -> adifhelper -> getAdifLine ( $row );
}
2025-12-05 06:57:14 +00:00
// Track data for response
$all_qso_ids [] = $row -> COL_PRIMARY_KEY ;
$lastfetchedid = max ( $lastfetchedid , $row -> COL_PRIMARY_KEY );
$total_fetched ++ ;
}
2024-07-31 13:28:18 +00:00
2025-12-05 06:57:14 +00:00
// Free memory
$qsos -> free_result ();
2025-02-13 11:50:06 +01:00
2025-12-05 06:57:14 +00:00
// Update tracking
$remaining_limit -= $qsos -> num_rows ();
$offset += $qsos -> num_rows ();
2025-02-13 11:50:06 +01:00
2025-12-05 06:57:14 +00:00
// Stop if we've hit the requested limit
if ( $total_fetched >= $limit ) {
break ;
}
}
2024-07-30 08:50:29 +00:00
2025-12-05 06:57:14 +00:00
// Continue if we got a full chunk and haven't hit the limit
} while ( $qsos && $qsos -> num_rows () > 0 && $total_fetched < $limit );
2024-07-30 08:50:29 +00:00
2026-04-01 07:55:00 +00:00
// Return response
http_response_code ( 200 );
if ( $total_fetched <= 0 ) {
2026-04-01 12:25:50 +02:00
echo json_encode ([ 'status' => 'successful' , 'message' => 'No new QSOs available.' , 'lastfetchedid' => $fetchfromid , 'exported_qsos' => 0 , 'adif' => null ]);
2026-04-01 07:55:00 +00:00
} elseif ( $output_format === 'json' ) {
2026-04-01 12:29:27 +02:00
echo json_encode ([ 'status' => 'successful' , 'message' => 'Export successful' , 'lastfetchedid' => $lastfetchedid , 'exported_records' => count ( $qso_rows ), 'qsos' => $qso_rows ]);
2025-12-05 06:57:14 +00:00
} else {
2026-04-01 12:25:50 +02:00
echo json_encode ([ 'status' => 'successful' , 'message' => 'Export successful' , 'lastfetchedid' => $lastfetchedid , 'exported_qsos' => $total_fetched , 'adif' => $adif_content ]);
2025-02-13 11:50:06 +01:00
}
2024-07-30 08:50:29 +00:00
}
2026-04-01 07:55:00 +00:00
private function _build_qso_array ( $qso , $fields = null ) {
$result = [];
2026-07-19 06:37:19 +00:00
$normalFields = [ 'ADDRESS' , 'AGE' , 'A_INDEX' , 'ANT_AZ' , 'ANT_EL' , 'ANT_PATH' , 'ARRL_SECT' , 'AWARD_GRANTED' , 'AWARD_SUBMITTED' , 'BAND' , 'BAND_RX' , 'BIOGRAPHY' , 'CALL' , 'CHECK' , 'CLASS' , 'CLUBLOG_QSO_UPLOAD_STATUS' , 'CNTY' , 'COMMENT' , 'CONT' , 'CONTACTED_OP' , 'CONTEST_ID' , 'COUNTRY' , 'CQZ' , 'CREDIT_GRANTED' , 'CREDIT_SUBMITTED' , 'DARC_DOK' , 'DISTANCE' , 'DXCC' , 'EMAIL' , 'EQ_CALL' , 'EQSL_QSL_RCVD' , 'EQSL_QSL_SENT' , 'EQSL_STATUS' , 'EQSL_AG' , 'FISTS' , 'FISTS_CC' , 'FORCE_INIT' , 'GRIDSQUARE' , 'HEADING' , 'IOTA' , 'ITUZ' , 'K_INDEX' , 'LAT' , 'LON' , 'LOTW_QSL_RCVD' , 'LOTW_QSL_SENT' , 'LOTW_STATUS' , 'MAX_BURSTS' , 'MODE' , 'MS_SHOWER' , 'NAME' , 'NOTES' , 'NR_BURSTS' , 'NR_PINGS' , 'OPERATOR' , 'OWNER_CALLSIGN' , 'PFX' , 'PRECEDENCE' , 'PROP_MODE' , 'PUBLIC_KEY' , 'HRDLOG_QSO_UPLOAD_STATUS' , 'QRZCOM_QSO_UPLOAD_STATUS' , 'QRZCOM_QSO_DOWNLOAD_STATUS' , 'QSLMSG' , 'QSL_RCVD' , 'QSL_RCVD_VIA' , 'QSL_SENT' , 'QSL_SENT_VIA' , 'QSL_VIA' , 'QSO_COMPLETE' , 'QSO_RANDOM' , 'QTH' , 'REGION' , 'RIG' , 'RST_RCVD' , 'RST_SENT' , 'RX_PWR' , 'SAT_MODE' , 'SAT_NAME' , 'SFI' , 'SILENT_KEY' , 'SKCC' , 'SOTA_REF' , 'WWFF_REF' , 'POTA_REF' , 'SRX' , 'SRX_STRING' , 'STATE' , 'STX' , 'STX_STRING' , 'SUBMODE' , 'SWL' , 'TEN_TEN' , 'TX_PWR' , 'UKSMG' , 'USACA_COUNTIES' , 'VUCC_GRIDS' , 'WEB' , 'CNTY_ALT' , 'MY_CNTY_ALT' , 'MY_DARC_DOK' , 'MORSE_KEY_INFO' , 'MORSE_KEY_TYPE' , 'QSLMSG_RCVD' , 'DCL_QSL_RCVD' , 'DCL_QSL_SENT' , 'MY_ANTENNA' , 'MY_ANTENNA_INTL' ];
$dateFields = [ 'EQSL_QSLRDATE' , 'EQSL_QSLSDATE' , 'LOTW_QSLRDATE' , 'LOTW_QSLSDATE' , 'QSLRDATE' , 'QSLSDATE' , 'CLUBLOG_QSO_UPLOAD_DATE' , 'HRDLOG_QSO_UPLOAD_DATE' , 'QRZCOM_QSO_UPLOAD_DATE' , 'QRZCOM_QSO_DOWNLOAD_DATE' , 'DCL_QSLRDATE' , 'DCL_QSLSDATE' ];
2026-04-01 07:55:00 +00:00
foreach ( $normalFields as $f ) {
$result [ $f ] = $qso -> { 'COL_' . $f };
}
foreach ( $dateFields as $f ) {
$val = $qso -> { 'COL_' . $f };
$result [ $f ] = $val ? date ( 'Ymd' , strtotime ( $val )) : null ;
}
$result [ 'FREQ' ] = $qso -> COL_FREQ ? $qso -> COL_FREQ / 1000000 : null ;
$result [ 'FREQ_RX' ] = $qso -> COL_FREQ_RX ? $qso -> COL_FREQ_RX / 1000000 : null ;
if ( isset ( $qso -> COL_TIME_ON ) && date ( 'YmdHis' , strtotime ( $qso -> COL_TIME_ON )) !== '-00011130000000' ) {
$result [ 'QSO_DATE' ] = date ( 'Ymd' , strtotime ( $qso -> COL_TIME_ON ));
$result [ 'TIME_ON' ] = date ( 'His' , strtotime ( $qso -> COL_TIME_ON ));
} else {
$result [ 'QSO_DATE' ] = '19700101' ;
$result [ 'TIME_ON' ] = '000000' ;
}
if ( isset ( $qso -> COL_TIME_OFF ) && date ( 'YmdHis' , strtotime ( $qso -> COL_TIME_OFF )) !== '-00011130000000' ) {
$result [ 'QSO_DATE_OFF' ] = date ( 'Ymd' , strtotime ( $qso -> COL_TIME_OFF ));
$result [ 'TIME_OFF' ] = date ( 'His' , strtotime ( $qso -> COL_TIME_OFF ));
} else {
$result [ 'QSO_DATE_OFF' ] = '19700101' ;
$result [ 'TIME_OFF' ] = '000000' ;
}
$result [ 'STATION_CALLSIGN' ] = $qso -> station_callsign ;
$result [ 'MY_CITY' ] = $qso -> station_city ;
$result [ 'MY_COUNTRY' ] = $qso -> station_country ;
$result [ 'MY_DXCC' ] = $qso -> station_dxcc ;
if ( strpos ( $qso -> station_gridsquare , ',' ) !== false ) {
$result [ 'MY_VUCC_GRIDS' ] = $qso -> station_gridsquare ;
$result [ 'MY_GRIDSQUARE' ] = null ;
} else {
$result [ 'MY_GRIDSQUARE' ] = $qso -> station_gridsquare ;
$result [ 'MY_VUCC_GRIDS' ] = null ;
}
$result [ 'MY_IOTA' ] = $qso -> station_iota ;
$result [ 'MY_SOTA_REF' ] = $qso -> station_sota ;
$result [ 'MY_WWFF_REF' ] = $qso -> station_wwff ;
$result [ 'MY_POTA_REF' ] = $qso -> station_pota ;
$result [ 'MY_CQ_ZONE' ] = $qso -> station_cq ;
$result [ 'MY_ITU_ZONE' ] = $qso -> station_itu ;
$result [ 'MY_STATE' ] = $qso -> state ;
if ( $qso -> station_cnty ) {
switch ( $qso -> station_dxcc ) {
case '6' : case '110' : case '291' :
$result [ 'MY_CNTY' ] = trim ( $qso -> state ) . ',' . trim ( $qso -> station_cnty );
break ;
default :
$result [ 'MY_CNTY' ] = trim ( $qso -> station_cnty );
}
} else {
$result [ 'MY_CNTY' ] = null ;
}
$result [ 'MY_SIG' ] = $qso -> station_sig ;
$result [ 'MY_SIG_INFO' ] = $qso -> station_sig_info ;
$result [ 'SIG' ] = $qso -> { 'COL_SIG' };
$result [ 'SIG_INFO' ] = $qso -> { 'COL_SIG_INFO' };
if ( $fields !== null ) {
$result = array_intersect_key ( $result , array_flip ( $fields ));
}
return $result ;
}
2023-03-28 14:38:50 +01:00
// API function to check if a callsign is in the logbook already
function logbook_check_callsign () {
header ( 'Content-type: application/json' );
$this -> load -> model ( 'api_model' );
// Decode JSON and store
$obj = json_decode ( file_get_contents ( " php://input " ), true );
if ( $obj === NULL ) {
echo json_encode ([ 'status' => 'failed' , 'reason' => " wrong JSON " ]);
2023-08-08 15:08:38 +00:00
return ;
2023-03-28 14:38:50 +01:00
}
if ( ! isset ( $obj [ 'key' ]) || $this -> api_model -> authorize ( $obj [ 'key' ]) == 0 ) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing api key " ]);
2023-08-08 15:08:38 +00:00
return ;
}
if ( ! isset ( $obj [ 'logbook_public_slug' ]) || ! isset ( $obj [ 'callsign' ])) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing fields " ]);
return ;
2023-03-28 14:38:50 +01:00
}
if ( $obj [ 'logbook_public_slug' ] != " " && $obj [ 'callsign' ] != " " ) {
$logbook_slug = $obj [ 'logbook_public_slug' ];
$callsign = $obj [ 'callsign' ];
// If $obj['band'] exists
if ( isset ( $obj [ 'band' ])) {
$band = $obj [ 'band' ];
} else {
$band = null ;
}
$this -> load -> model ( 'logbooks_model' );
if ( $this -> logbooks_model -> public_slug_exists ( $logbook_slug )) {
$logbook_id = $this -> logbooks_model -> public_slug_exists_logbook_id ( $logbook_slug );
if ( $logbook_id != false )
{
// Get associated station locations for mysql queries
$logbooks_locations_array = $this -> logbooks_model -> list_logbook_relationships ( $logbook_id );
2024-01-17 17:30:14 +01:00
2026-03-18 09:00:48 +01:00
if ( $logbooks_locations_array [ 0 ] === - 1 ) {
2023-03-28 14:38:50 +01:00
// Logbook not found
http_response_code ( 404 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " Empty Logbook " ]);
die ();
}
} else {
// Logbook not found
http_response_code ( 404 );
echo json_encode ([ 'status' => 'failed' , 'reason' => $logbook_slug . " has no associated station locations " ]);
die ();
}
// Search Logbook for callsign
$this -> load -> model ( 'logbook_model' );
2023-03-29 15:02:36 +01:00
$result = $this -> logbook_model -> check_if_callsign_worked_in_logbook ( $callsign , $logbooks_locations_array , $band );
2023-03-28 14:38:50 +01:00
http_response_code ( 201 );
if ( $result > 0 )
{
echo json_encode ([ 'callsign' => $callsign , 'result' => 'Found' ]);
} else {
echo json_encode ([ 'callsign' => $callsign , 'result' => 'Not Found' ]);
}
} else {
// Logbook not found
http_response_code ( 404 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " logbook not found " ]);
die ();
}
}
}
2023-03-28 14:57:43 +01:00
// API function to check if a grid is in the logbook already
function logbook_check_grid () {
header ( 'Content-type: application/json' );
$this -> load -> model ( 'api_model' );
// Decode JSON and store
$obj = json_decode ( file_get_contents ( " php://input " ), true );
if ( $obj === NULL ) {
echo json_encode ([ 'status' => 'failed' , 'reason' => " wrong JSON " ]);
}
if ( ! isset ( $obj [ 'key' ]) || $this -> api_model -> authorize ( $obj [ 'key' ]) == 0 ) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing api key " ]);
}
2023-08-08 15:08:38 +00:00
if ( ! isset ( $obj [ 'logbook_public_slug' ]) || ! isset ( $obj [ 'grid' ])) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing fields " ]);
return ;
}
2023-03-28 14:57:43 +01:00
if ( $obj [ 'logbook_public_slug' ] != " " && $obj [ 'grid' ] != " " ) {
$logbook_slug = $obj [ 'logbook_public_slug' ];
$grid = $obj [ 'grid' ];
// If $obj['band'] exists
if ( isset ( $obj [ 'band' ])) {
$band = $obj [ 'band' ];
} else {
$band = null ;
}
2024-04-15 18:25:19 +02:00
// If $obj['cnfm'] exists
if ( isset ( $obj [ 'cnfm' ])) {
$cnfm = $obj [ 'cnfm' ];
} else {
$cnfm = null ;
}
2023-03-28 14:57:43 +01:00
$this -> load -> model ( 'logbooks_model' );
if ( $this -> logbooks_model -> public_slug_exists ( $logbook_slug )) {
$logbook_id = $this -> logbooks_model -> public_slug_exists_logbook_id ( $logbook_slug );
if ( $logbook_id != false )
{
// Get associated station locations for mysql queries
$logbooks_locations_array = $this -> logbooks_model -> list_logbook_relationships ( $logbook_id );
2024-01-17 17:30:14 +01:00
2026-03-18 09:00:48 +01:00
if ( $logbooks_locations_array [ 0 ] === - 1 ) {
2023-03-28 14:57:43 +01:00
// Logbook not found
http_response_code ( 404 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " Empty Logbook " ]);
die ();
}
} else {
// Logbook not found
http_response_code ( 404 );
echo json_encode ([ 'status' => 'failed' , 'reason' => $logbook_slug . " has no associated station locations " ]);
die ();
}
// Search Logbook for callsign
$this -> load -> model ( 'logbook_model' );
2024-04-15 18:25:19 +02:00
$query = $this -> logbook_model -> check_if_grid_worked_in_logbook ( $grid , $logbooks_locations_array , $band , $cnfm );
2023-03-28 14:57:43 +01:00
http_response_code ( 201 );
2024-04-15 18:25:19 +02:00
if ( $query -> num_rows () == 0 ) {
echo json_encode ([ 'gridsquare' => strtoupper ( $grid ), 'result' => 'Not Found' ]);
} else if ( $cnfm == null ) {
2023-03-28 14:57:43 +01:00
echo json_encode ([ 'gridsquare' => strtoupper ( $grid ), 'result' => 'Found' ]);
} else {
2024-04-15 18:25:19 +02:00
$arr = [];
foreach ( $query -> result () as $line ) {
$arr [] = $line -> gridorcnfm ;
}
if ( in_array ( 'Y' , $arr )) {
echo json_encode ([ 'gridsquare' => strtoupper ( $grid ), 'result' => 'Confirmed' ]);
} else {
echo json_encode ([ 'gridsquare' => strtoupper ( $grid ), 'result' => 'Worked' ]);
}
2023-03-28 14:57:43 +01:00
}
2024-04-15 18:25:19 +02:00
2023-03-28 14:57:43 +01:00
} else {
// Logbook not found
http_response_code ( 404 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " logbook not found " ]);
die ();
}
}
}
2026-03-18 08:34:52 +01:00
// API function to get all worked grids for a band and confirmation method
2026-01-31 10:02:55 +01:00
function logbook_get_worked_grids () {
$arr = array ();
header ( 'Content-type: application/json' );
$this -> load -> model ( 'api_model' );
$obj = json_decode ( file_get_contents ( " php://input " ), true );
if ( $obj === NULL ) {
echo json_encode ([ 'status' => 'failed' , 'reason' => " wrong JSON " ]);
2026-02-20 11:30:36 +01:00
die ();
2026-01-31 10:02:55 +01:00
}
2026-02-20 12:23:55 +01:00
// Check rate limit
$identifier = isset ( $obj [ 'key' ]) ? $obj [ 'key' ] : null ;
$this -> check_rate_limit ( 'logbook_get_worked_grids' , $identifier );
2026-01-31 10:02:55 +01:00
if ( ! isset ( $obj [ 'key' ]) || $this -> api_model -> authorize ( $obj [ 'key' ]) == 0 ) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing api key " ]);
2026-02-20 11:30:42 +01:00
die ();
2026-01-31 10:02:55 +01:00
}
2026-02-20 13:43:13 +00:00
$api_user_id = $this -> api_model -> key_userid ( $obj [ 'key' ]);
2026-03-15 09:04:05 +01:00
if ( ! isset ( $obj [ 'logbook_id' ])) {
2026-02-20 11:30:28 +01:00
http_response_code ( 400 );
2026-01-31 10:02:55 +01:00
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing fields " ]);
return ;
}
2026-03-15 09:04:05 +01:00
if ( $obj [ 'logbook_id' ] != " " ) {
$logbook_id = $obj [ 'logbook_id' ];
2026-01-31 10:02:55 +01:00
if ( isset ( $obj [ 'band' ])) {
$band = $obj [ 'band' ];
} else {
$band = null ;
}
if ( isset ( $obj [ 'cnfm' ])) {
$cnfm = $obj [ 'cnfm' ];
} else {
$cnfm = null ;
}
$this -> load -> model ( 'logbooks_model' );
2026-03-15 09:04:05 +01:00
if ( ! $this -> logbooks_model -> logbook_id_belongs_to_user ( $logbook_id , $api_user_id )) {
2026-02-20 13:43:13 +00:00
http_response_code ( 403 );
2026-03-15 09:04:05 +01:00
echo json_encode ([ 'status' => 'failed' , 'reason' => " logbook does not belong to this API key or logbook ID not found " ]);
2026-02-20 13:43:13 +00:00
die ();
}
2026-03-15 09:04:05 +01:00
if ( $this -> logbooks_model -> exists_logbook_id ( $logbook_id ) != false ) {
$logbooks_locations_array = $this -> logbooks_model -> list_logbook_relationships ( $logbook_id );
2026-03-18 09:00:48 +01:00
if ( $logbooks_locations_array [ 0 ] === - 1 ) {
2026-01-31 10:02:55 +01:00
http_response_code ( 404 );
2026-03-15 09:04:05 +01:00
echo json_encode ([ 'status' => 'failed' , 'reason' => " logbook with ID " . $logbook_id . " has no associated station locations " ]);
2026-01-31 10:02:55 +01:00
die ();
2026-03-15 10:40:36 +01:00
} else {
$arr = $this -> api_model -> get_grids_worked_in_logbook ( $logbooks_locations_array , $band , $cnfm );
http_response_code ( 201 );
echo json_encode ( $arr );
2026-01-31 10:02:55 +01:00
}
}
}
}
2012-04-07 17:36:38 +01:00
/* ENDPOINT for Rig Control */
2017-11-30 19:01:11 -07:00
2012-04-07 17:36:38 +01:00
function radio () {
2024-01-13 11:16:31 +00:00
session_write_close ();
2025-10-06 10:56:23 +00:00
2012-04-07 17:36:38 +01:00
header ( 'Content-type: application/json' );
2017-11-30 19:01:11 -07:00
2018-12-17 22:16:06 +01:00
$this -> load -> model ( 'api_model' );
2012-04-07 17:36:38 +01:00
//$json = '{"radio":"FT-950","frequency":14075,"mode":"SSB","timestamp":"2012/04/07 16:47"}';
2017-11-30 19:01:11 -07:00
2012-04-07 17:36:38 +01:00
$this -> load -> model ( 'cat' );
2017-11-30 19:01:11 -07:00
2012-04-07 17:36:38 +01:00
//var_dump(file_get_contents("php://input"), true);
2017-11-30 19:01:11 -07:00
2012-04-07 17:36:38 +01:00
// Decode JSON and store
$obj = json_decode ( file_get_contents ( " php://input " ), true );
2026-01-07 07:35:32 +00:00
// Check rate limit
$identifier = isset ( $obj [ 'key' ]) ? $obj [ 'key' ] : null ;
$this -> check_rate_limit ( 'radio' , $identifier );
2018-12-17 22:16:06 +01:00
if ( ! isset ( $obj [ 'key' ]) || $this -> api_model -> authorize ( $obj [ 'key' ]) == 0 ) {
2023-07-26 12:23:52 +00:00
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing api key " ]);
die ();
2018-12-17 22:16:06 +01:00
}
2026-06-08 09:29:47 +02:00
if ( $this -> api_model -> authorize ( $obj [ 'key' ]) == 1 ) {
http_response_code ( 403 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " API key does not have write permissions " ]);
die ();
}
2024-01-19 05:13:15 +00:00
if ( ! isset ( $obj [ 'radio' ])) {
http_response_code ( 404 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing radio element in payload " ]);
die ();
}
2023-03-21 12:26:15 +01:00
$this -> api_model -> update_last_used ( $obj [ 'key' ]);
2021-09-28 17:18:04 +01:00
$user_id = $this -> api_model -> key_userid ( $obj [ 'key' ]);
2025-01-02 10:22:23 +01:00
$created_by = $this -> api_model -> key_created_by ( $obj [ 'key' ]);
// Clubmode needs an additional check for the operator
if ( $user_id != $created_by ) {
$operator = $created_by ;
} else {
$operator = $user_id ;
}
2021-09-28 17:18:04 +01:00
2025-09-14 20:25:25 +02:00
// Handle optional cat_url
if ( isset ( $obj [ 'cat_url' ]) && ! empty ( $obj [ 'cat_url' ])) {
2025-09-14 20:29:01 +02:00
$cat_url = $this -> sanitize_cat_url ( $obj [ 'cat_url' ]);
2025-09-14 20:25:25 +02:00
if ( $cat_url !== false ) {
$obj [ 'cat_url' ] = $cat_url ;
}
}
2025-02-18 18:54:32 +01:00
2017-11-30 19:01:11 -07:00
// Store Result to Database
2025-01-02 10:22:23 +01:00
$this -> cat -> update ( $obj , $user_id , $operator );
2012-04-07 17:36:38 +01:00
// Return Message
2017-11-30 19:01:11 -07:00
2012-04-07 17:36:38 +01:00
$arr = array ( 'status' => 'success' );
echo json_encode ( $arr );
2017-11-30 19:01:11 -07:00
2012-04-07 17:36:38 +01:00
}
2020-04-03 16:41:21 +01:00
/*
*
* Stats API function calls
*
*/
2023-04-27 21:08:09 +02:00
function statistics ( $key = null ) {
2025-03-23 14:26:29 +00:00
$this -> load -> model ( 'api_model' );
2026-06-08 09:29:47 +02:00
if ((( $key ? ? '' ) != '' ) && ( $this -> api_model -> authorize ( $key ) > 0 )) {
2025-03-23 14:26:29 +00:00
$this -> load -> model ( 'logbook_model' );
2026-02-04 05:34:33 +00:00
$qso_counts = $this -> logbook_model -> get_qso_counts ( null , $key );
$data [ 'todays_qsos' ] = $qso_counts [ 'today' ];
$data [ 'total_qsos' ] = $qso_counts [ 'total' ];
$data [ 'month_qsos' ] = $qso_counts [ 'month' ];
$data [ 'year_qsos' ] = $qso_counts [ 'year' ];
2025-03-23 14:26:29 +00:00
} else { # for Downcompat
$data [ 'todays_qsos' ] = 0 ;
$data [ 'total_qsos' ] = 0 ;
$data [ 'month_qsos' ] = 0 ;
$data [ 'year_qsos' ] = 0 ;
}
2020-04-03 16:41:21 +01:00
header ( 'Content-type: application/json' );
http_response_code ( 201 );
echo json_encode ([ 'Today' => $data [ 'todays_qsos' ], 'total_qsos' => $data [ 'total_qsos' ], 'month_qsos' => $data [ 'month_qsos' ], 'year_qsos' => $data [ 'year_qsos' ]]);
}
2020-04-06 23:38:02 +01:00
2024-09-30 08:06:04 +00:00
function private_lookup () {
2026-06-19 21:20:00 +08:00
header ( 'Content-type: application/json' );
2024-10-01 06:14:03 +00:00
// Lookup Callsign and dxcc for further informations. UseCase: e.g. external Application which checks calls like FlexRadio-Overlay
2024-09-30 08:06:04 +00:00
$raw_input = json_decode ( file_get_contents ( " php://input " ), true );
2026-01-07 07:35:32 +00:00
// Check rate limit
$identifier = isset ( $raw_input [ 'key' ]) ? $raw_input [ 'key' ] : null ;
$this -> check_rate_limit ( 'private_lookup' , $identifier );
2024-09-30 08:06:04 +00:00
$user_id = '' ;
if ( ! ( $this -> user_model -> authorize ( $this -> config -> item ( 'auth_mode' ) ))) { // User not authorized?
$no_auth = true ;
$this -> load -> model ( 'api_model' );
if ( ! ( (( isset ( $raw_input [ 'key' ])) && ( $this -> api_model -> authorize ( $raw_input [ 'key' ]) > 0 ) ))) { // Key invalid?
$no_auth = true ;
} else {
$no_auth = false ;
$user_id = $this -> api_model -> key_userid ( $raw_input [ 'key' ]);
}
if ( $no_auth ) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing api key or session " ]);
die ();
}
} else {
$user_id = $this -> session -> userdata ( 'user_id' );
}
2026-01-07 06:49:35 +00:00
if (( $raw_input [ 'callbook' ] ? ? '' == 'true' ) && (( $raw_input [ 'callsign' ] ? ? '' ) != '' )) {
2026-01-07 06:10:42 +00:00
$this -> load -> library ( 'callbook' );
$this -> load -> model ( 'logbook_model' );
$lookupcall = $this -> callbook -> get_plaincall ( $raw_input [ 'callsign' ]);
$callbook = $this -> logbook_model -> loadCallBook ( $raw_input [ 'callsign' ], $this -> config -> item ( 'use_fullname' ));
} else {
$callbook = null ;
}
2024-09-30 08:06:04 +00:00
$this -> load -> model ( 'stations' );
$all_station_ids = $this -> stations -> all_station_ids_of_user ( $user_id );
2025-02-13 11:50:06 +01:00
2024-09-30 08:06:04 +00:00
if (( array_key_exists ( 'station_ids' , $raw_input )) && ( is_array ( $raw_input [ 'station_ids' ]))) { // Special station_ids needed and it is an array?
$a_station_ids = [];
foreach ( $raw_input [ 'station_ids' ] as $stationid ) { // Check for grants to given station_id
2026-06-19 21:54:02 +08:00
$stationid = intval ( $stationid );
2024-09-30 08:06:04 +00:00
if ( $this -> stations -> check_station_against_user ( $stationid , $user_id )) {
$a_station_ids [] = $stationid ;
}
}
$station_ids = implode ( ', ' , $a_station_ids );
} else {
$station_ids = $all_station_ids ; // Take all of user if no station_ids were given
}
if ( $station_ids == '' ) { // No station_ids found for user or no station_id of given ones were granted? exit!
http_response_code ( 200 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " no station_profiles are matching the User with this API-Key " ]);
die ();
}
if ( array_key_exists ( 'band' , $raw_input )) {
$band = $raw_input [ 'band' ];
} else {
$band = 'NO_BAND' ;
}
if ( array_key_exists ( 'mode' , $raw_input )) {
$mode = $raw_input [ 'mode' ];
} else {
$mode = 'NO_MODE' ;
}
$lookup_callsign = strtoupper ( $raw_input [ 'callsign' ] ? ? '' );
if ( $lookup_callsign ? ? '' != '' ) {
$this -> load -> model ( " logbook_model " );
$date = date ( " Y-m-d " );
// Return Array
$return = [
" callsign " => " " ,
" dxcc " => false ,
" dxcc_id " => - 1 ,
" dxcc_lat " => " " ,
" dxcc_long " => " " ,
" dxcc_cqz " => " " ,
" dxcc_flag " => " " ,
" cont " => " " ,
" name " => " " ,
" gridsquare " => " " ,
" location " => " " ,
" iota_ref " => " " ,
" state " => " " ,
" us_county " => " " ,
" qsl_manager " => " " ,
" bearing " => " " ,
2024-10-01 17:05:57 +00:00
" call_worked " => false ,
" call_worked_band " => false ,
" call_worked_band_mode " => false ,
2024-09-30 08:06:04 +00:00
" lotw_member " => false ,
" dxcc_confirmed_on_band " => false ,
" dxcc_confirmed_on_band_mode " => false ,
" dxcc_confirmed " => false ,
2024-10-01 06:01:20 +00:00
" call_confirmed " => false ,
" call_confirmed_band " => false ,
" call_confirmed_band_mode " => false ,
2024-09-30 08:06:04 +00:00
" suffix_slash " => " " , // Suffix Slash aka Portable
];
$return [ 'callsign' ] = $lookup_callsign ;
2026-02-17 14:11:11 +01:00
$dxccobj = new Dxcc ();
2026-01-22 11:55:30 +01:00
$callsign_dxcc_lookup = $dxccobj -> dxcc_lookup ( $lookup_callsign , $date );
2024-09-30 08:06:04 +00:00
$last_slash_pos = strrpos ( $lookup_callsign , '/' );
if ( isset ( $last_slash_pos ) && $last_slash_pos > 4 ) {
$suffix_slash = $last_slash_pos === false ? $lookup_callsign : substr ( $lookup_callsign , $last_slash_pos + 1 );
switch ( $suffix_slash ) {
case " P " :
$suffix_slash_item = " Portable " ;
break ;
case " M " :
$suffix_slash_item = " Mobile " ;
case " MM " :
$suffix_slash_item = " Maritime Mobile " ;
break ;
default :
// If its not one of the above suffix slashes its likely dxcc
2026-01-22 11:55:30 +01:00
$ans2 = $dxccobj -> dxcc_lookup ( $suffix_slash , $date );
2024-09-30 08:06:04 +00:00
$suffix_slash_item = null ;
}
$return [ 'suffix_slash' ] = $suffix_slash_item ;
}
// If the final slash is a DXCC then find it!
if ( isset ( $ans2 [ 'call' ])) {
$return [ 'dxcc_id' ] = $ans2 [ 'adif' ];
$return [ 'dxcc' ] = $ans2 [ 'entity' ];
$return [ 'dxcc_lat' ] = $ans2 [ 'lat' ];
$return [ 'dxcc_long' ] = $ans2 [ 'long' ];
$return [ 'dxcc_cqz' ] = $ans2 [ 'cqz' ];
$return [ 'cont' ] = $ans2 [ 'cont' ];
} else {
$return [ 'dxcc_id' ] = $callsign_dxcc_lookup [ 'adif' ] ? ? '' ;
$return [ 'dxcc' ] = $callsign_dxcc_lookup [ 'entity' ] ? ? '' ;
$return [ 'dxcc_lat' ] = $callsign_dxcc_lookup [ 'lat' ] ? ? '' ;
$return [ 'dxcc_long' ] = $callsign_dxcc_lookup [ 'long' ] ? ? '' ;
$return [ 'dxcc_cqz' ] = $callsign_dxcc_lookup [ 'cqz' ] ? ? '' ;
$return [ 'cont' ] = $callsign_dxcc_lookup [ 'cont' ] ? ? '' ;
}
2026-06-13 12:59:39 +00:00
// ITU zone from the DXCC entity (same source/timing as dxcc_cqz). Only add the
// key when the entity has a known ITU zone, otherwise omit it entirely.
$entity = $this -> logbook_model -> get_entity ( $return [ 'dxcc_id' ]);
if ( is_array ( $entity ) && (( $entity [ 'ituz' ] ? ? 0 ) > 0 )) {
$return [ 'dxcc_ituz' ] = ( int ) $entity [ 'ituz' ];
}
2024-10-01 06:14:03 +00:00
// Query stations of KeyOwner for an already worked call
2024-10-01 05:38:41 +00:00
$userdata = $this -> user_model -> get_by_id ( $user_id );
$call_lookup_results = $this -> logbook_model -> call_lookup_result ( $lookup_callsign , $station_ids , $userdata -> row () -> user_default_confirmation , $band , $mode );
2024-09-30 08:06:04 +00:00
if ( $call_lookup_results != null ) {
$return [ 'name' ] = $call_lookup_results -> COL_NAME ;
$return [ 'gridsquare' ] = $call_lookup_results -> COL_GRIDSQUARE ;
$return [ 'location' ] = $call_lookup_results -> COL_QTH ;
$return [ 'iota_ref' ] = $call_lookup_results -> COL_IOTA ;
$return [ 'qsl_manager' ] = $call_lookup_results -> COL_QSL_VIA ;
$return [ 'state' ] = $call_lookup_results -> COL_STATE ;
$return [ 'us_county' ] = $call_lookup_results -> COL_CNTY ;
$return [ 'dxcc_id' ] = $call_lookup_results -> COL_DXCC ;
$return [ 'cont' ] = $call_lookup_results -> COL_CONT ;
2024-10-01 17:05:57 +00:00
$return [ 'call_worked' ] = true ;
$return [ 'call_worked_band' ] = ( $call_lookup_results -> CALL_WORKED_BAND == 1 ) ? true : false ;
$return [ 'call_worked_band_mode' ] = ( $call_lookup_results -> CALL_WORKED_BAND_MODE == 1 ) ? true : false ;
2024-10-01 05:38:41 +00:00
$return [ 'call_confirmed' ] = ( $call_lookup_results -> CALL_CNF == 1 ) ? true : false ;
$return [ 'call_confirmed_band' ] = ( $call_lookup_results -> CALL_CNF_BAND == 1 ) ? true : false ;
$return [ 'call_confirmed_band_mode' ] = ( $call_lookup_results -> CALL_CNF_BAND_MODE == 1 ) ? true : false ;
2024-09-30 08:06:04 +00:00
if ( $return [ 'gridsquare' ] != " " ) {
$return [ 'latlng' ] = $this -> qralatlng ( $return [ 'gridsquare' ]);
}
}
if ( $return [ 'dxcc' ] ? ? '' != '' ) {
$this -> load -> library ( 'DxccFlag' );
$return [ 'dxcc_flag' ] = $this -> dxccflag -> get ( $return [ 'dxcc_id' ]);
}
$lotw_days = $this -> logbook_model -> check_last_lotw ( $lookup_callsign );
2026-04-24 13:33:04 +02:00
if ( $lotw_days !== null ) {
2024-09-30 08:06:04 +00:00
$return [ 'lotw_member' ] = $lotw_days ;
}
2024-10-01 06:14:03 +00:00
if ( $return [ 'dxcc_id' ] ? ? '' != '' ) { // DXCC derivated before? if yes: check cnf-states
2024-09-30 08:06:04 +00:00
$return [ 'dxcc_confirmed' ] = ( $this -> logbook_model -> check_if_dxcc_cnfmd_in_logbook_api ( $userdata -> row () -> user_default_confirmation , $return [ 'dxcc_id' ], $station_ids , null , null ) > 0 ) ? true : false ;
$return [ 'dxcc_confirmed_on_band' ] = ( $this -> logbook_model -> check_if_dxcc_cnfmd_in_logbook_api ( $userdata -> row () -> user_default_confirmation , $return [ 'dxcc_id' ], $station_ids , $band , null ) > 0 ) ? true : false ;
$return [ 'dxcc_confirmed_on_band_mode' ] = ( $this -> logbook_model -> check_if_dxcc_cnfmd_in_logbook_api ( $userdata -> row () -> user_default_confirmation , $return [ 'dxcc_id' ], $station_ids , $band , $mode ) > 0 ) ? true : false ;
}
2026-01-07 06:10:42 +00:00
if ( $callbook ) {
$return [ 'callbook' ] = $callbook ;
}
2024-09-30 08:06:04 +00:00
echo json_encode ( $return , JSON_PRETTY_PRINT );
} else {
echo '{"error":"callsign to lookup not given"}' ;
}
return ;
}
function lookup () {
2026-06-19 21:20:00 +08:00
header ( 'Content-type: application/json' );
2024-10-01 06:14:03 +00:00
// This API provides NO information about previous QSOs. It just derivates DXCC, Lat, Long. It is used by the DXClusterAPI
2024-01-30 07:54:53 +00:00
$raw_input = json_decode ( file_get_contents ( " php://input " ), true );
2026-01-07 07:35:32 +00:00
// Check rate limit
$identifier = isset ( $raw_input [ 'key' ]) ? $raw_input [ 'key' ] : null ;
$this -> check_rate_limit ( 'lookup' , $identifier );
2025-09-14 18:44:16 +02:00
$user_id = '' ;
2024-01-30 07:54:53 +00:00
if ( ! ( $this -> user_model -> authorize ( $this -> config -> item ( 'auth_mode' ) ))) { // User not authorized?
2025-09-14 18:44:16 +02:00
$no_auth = true ;
2024-01-30 07:54:53 +00:00
$this -> load -> model ( 'api_model' );
if ( ! ( (( isset ( $raw_input [ 'key' ])) && ( $this -> api_model -> authorize ( $raw_input [ 'key' ]) > 0 ) ))) { // Key invalid?
2025-09-14 18:44:16 +02:00
$no_auth = true ;
2024-01-30 07:54:53 +00:00
} else {
2025-09-14 18:44:16 +02:00
$no_auth = false ;
$user_id = $this -> api_model -> key_userid ( $raw_input [ 'key' ]);
2024-01-30 07:54:53 +00:00
}
if ( $no_auth ) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing api key or session " ]);
die ();
}
2024-09-20 16:06:46 +00:00
} else {
2025-09-14 18:44:16 +02:00
$user_id = $this -> session -> userdata ( 'user_id' );
2024-01-30 06:30:20 +00:00
}
2020-04-06 23:38:02 +01:00
2024-09-20 16:06:46 +00:00
$this -> load -> model ( 'stations' );
2025-09-14 18:44:16 +02:00
$station_ids = $this -> stations -> all_station_ids_of_user ( $user_id );
2024-09-20 16:06:46 +00:00
2024-01-30 06:30:20 +00:00
$lookup_callsign = strtoupper ( $raw_input [ 'callsign' ] ? ? '' );
if ( $lookup_callsign ? ? '' != '' ) {
$this -> load -> model ( " logbook_model " );
$date = date ( " Y-m-d " );
// Return Array
$return = [
" callsign " => " " ,
" dxcc " => false ,
" dxcc_id " => - 1 ,
" dxcc_lat " => " " ,
" dxcc_long " => " " ,
" dxcc_cqz " => " " ,
" dxcc_flag " => " " ,
2024-01-30 09:17:18 +00:00
" cont " => " " ,
2024-01-30 06:30:20 +00:00
" name " => " " ,
" gridsquare " => " " ,
" location " => " " ,
" iota_ref " => " " ,
" state " => " " ,
" us_county " => " " ,
" qsl_manager " => " " ,
" bearing " => " " ,
" workedBefore " => false ,
" lotw_member " => false ,
" suffix_slash " => " " , // Suffix Slash aka Portable
];
2020-04-06 23:38:02 +01:00
$return [ 'callsign' ] = $lookup_callsign ;
2026-01-01 13:51:49 +00:00
// Use Wavelog\Dxcc\Dxcc for faster in-memory lookup
2026-02-17 14:11:11 +01:00
$dxccobj = new Dxcc ();
2026-01-01 13:51:49 +00:00
$callsign_dxcc_lookup = $dxccobj -> dxcc_lookup ( $lookup_callsign , $date );
2020-04-06 23:38:02 +01:00
2025-09-14 18:44:16 +02:00
$return [ 'dxcc_id' ] = $callsign_dxcc_lookup [ 'adif' ] ? ? '' ;
$return [ 'dxcc' ] = $callsign_dxcc_lookup [ 'entity' ] ? ? '' ;
$return [ 'dxcc_lat' ] = $callsign_dxcc_lookup [ 'lat' ] ? ? '' ;
$return [ 'dxcc_long' ] = $callsign_dxcc_lookup [ 'long' ] ? ? '' ;
$return [ 'dxcc_cqz' ] = $callsign_dxcc_lookup [ 'cqz' ] ? ? '' ;
$return [ 'cont' ] = $callsign_dxcc_lookup [ 'cont' ] ? ? '' ;
2020-04-06 23:38:02 +01:00
2024-01-30 06:30:20 +00:00
/*
2024-10-01 06:14:03 +00:00
* Query Data of API - Key - Owner for further informations
2024-01-30 06:30:20 +00:00
*/
2024-10-01 05:38:41 +00:00
$call_lookup_results = $this -> logbook_model -> call_lookup_result ( $lookup_callsign , $station_ids , '' , 'NO BAND' , 'NO MODE' );
2020-04-06 23:38:02 +01:00
2020-04-06 23:58:26 +01:00
if ( $call_lookup_results != null )
2020-04-06 23:38:02 +01:00
{
2020-04-06 23:58:26 +01:00
$return [ 'name' ] = $call_lookup_results -> COL_NAME ;
$return [ 'gridsquare' ] = $call_lookup_results -> COL_GRIDSQUARE ;
$return [ 'location' ] = $call_lookup_results -> COL_QTH ;
$return [ 'iota_ref' ] = $call_lookup_results -> COL_IOTA ;
$return [ 'qsl_manager' ] = $call_lookup_results -> COL_QSL_VIA ;
2020-04-07 00:07:17 +01:00
$return [ 'state' ] = $call_lookup_results -> COL_STATE ;
$return [ 'us_county' ] = $call_lookup_results -> COL_CNTY ;
2024-09-20 16:26:15 +00:00
$return [ 'workedBefore' ] = true ;
2020-04-06 23:38:02 +01:00
if ( $return [ 'gridsquare' ] != " " ) {
$return [ 'latlng' ] = $this -> qralatlng ( $return [ 'gridsquare' ]);
}
}
2024-01-30 06:30:20 +00:00
if ( $return [ 'dxcc' ] ? ? '' != '' ) {
$this -> load -> library ( 'DxccFlag' );
$return [ 'dxcc_flag' ] = $this -> dxccflag -> get ( $return [ 'dxcc_id' ]);
}
2024-01-30 15:34:45 +00:00
$lotw_days = $this -> logbook_model -> check_last_lotw ( $lookup_callsign );
2026-04-24 13:33:04 +02:00
if ( $lotw_days !== null ) {
2024-01-30 15:34:45 +00:00
$return [ 'lotw_member' ] = $lotw_days ;
}
2024-01-30 06:30:20 +00:00
echo json_encode ( $return , JSON_PRETTY_PRINT );
} else {
echo '{"error":"callsign to lookup not given"}' ;
}
2020-04-06 23:38:02 +01:00
return ;
}
function qralatlng ( $qra ) {
2024-07-15 05:59:47 +02:00
if ( ! $this -> load -> is_loaded ( 'Qra' )) {
$this -> load -> library ( 'Qra' );
}
2020-04-06 23:38:02 +01:00
$latlng = $this -> qra -> qra2latlong ( $qra );
return $latlng ;
}
2025-02-13 11:50:06 +01:00
2025-01-04 22:17:50 +01:00
function version () {
2025-01-04 21:46:09 +01:00
// This API endpoint provides the version of Wavelog if the provide key has at least read permissions
2025-01-05 11:54:50 +01:00
$data = json_decode ( file_get_contents ( 'php://input' ), true );
$valid = false ;
2025-02-13 11:50:06 +01:00
2025-01-05 11:54:50 +01:00
if ( ! empty ( $data [ 'key' ])) {
$this -> load -> model ( 'api_model' );
2026-06-08 09:29:47 +02:00
if ( $this -> api_model -> authorize ( $data [ 'key' ]) > 0 ) { /* Check permission for reading */
2025-01-05 11:54:50 +01:00
$valid = true ;
}
}
2025-01-04 21:46:09 +01:00
header ( " Content-type: application/json " );
2025-01-05 11:54:50 +01:00
if ( $valid ) {
2025-01-04 22:17:50 +01:00
echo json_encode ([ 'status' => 'ok' , 'version' => $this -> optionslib -> get_option ( 'version' )]);
2025-01-04 21:46:09 +01:00
} else {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing or invalid api key " ]);
}
}
2025-02-13 11:50:06 +01:00
/*
API call used in this WordPress plugin : https :// github . com / HochdruckHummer / wavelog - wp - qso - display
*/
function get_wp_stats () {
// Set header
header ( 'Content-type: application/json' );
// Load API model
$this -> load -> model ( 'api_model' );
// Decode JSON and store
$obj = json_decode ( file_get_contents ( " php://input " ), true );
if ( $obj === NULL ) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " wrong JSON " ]);
return ;
}
// Authorization
if ( ! isset ( $obj [ 'key' ]) || $this -> api_model -> authorize ( $obj [ 'key' ]) == 0 ) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " missing or wrong api key " ]);
return ;
}
// Validate station_id
if ( ! isset ( $obj [ 'station_id' ]) || ! is_numeric ( $obj [ 'station_id' ])) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " Invalid station_id. " ]);
return ;
}
$station_id = ( int ) $obj [ 'station_id' ];
$key = $obj [ 'key' ];
// Load stations model
$this -> load -> model ( 'stations' );
// Get user stations
$userid = $this -> api_model -> key_userid ( $key );
$stations = $this -> stations -> all_of_user ( $userid );
$station_ids = array_map ( function ( $row ) {
return $row -> station_id ;
}, $stations -> result ());
// Check station access
if ( ! in_array ( $station_id , $station_ids )) {
http_response_code ( 401 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " Station ID not accessible for this API key " ]);
return ;
}
// Load cache driver
2026-02-01 20:21:14 +01:00
$this -> load -> driver ( 'cache' , [
2026-06-07 09:44:12 +02:00
'adapter' => $this -> config -> item ( 'cache_adapter' ) ? ? 'file' ,
2026-02-01 20:21:14 +01:00
'backup' => $this -> config -> item ( 'cache_backup' ) ? ? 'file' ,
'key_prefix' => $this -> config -> item ( 'cache_key_prefix' ) ? ? ''
]);
2025-02-13 11:50:06 +01:00
// Create cache key
$cache_key = " wp_stats_ { $station_id } " ;
// Check if cached data exists
if ( $cached_data = $this -> cache -> get ( $cache_key )) {
http_response_code ( 200 );
echo json_encode ([ 'status' => 'successful' , 'message' => 'Data from cache' , 'statistics' => $cached_data ]);
return ;
}
// Get QSO data (from database)
$data [ 'totalalltime' ] = $this -> api_model -> get_qsos_total ( $station_id ) -> result ();
$data [ 'totalthisyear' ] = $this -> api_model -> get_qsos_this_year ( $station_id ) -> result ();
$data [ 'totalgroupedmodes' ] = $this -> api_model -> get_qsos_grouped_by_mode ( $station_id ) -> result ();
// Store in cache for 5 minutes
$this -> cache -> save ( $cache_key , $data , 600 ); // 10 minutes
// Return result
http_response_code ( 200 );
echo json_encode ([ 'status' => 'successful' , 'message' => 'Export successful' , 'statistics' => $data ]);
}
2025-09-14 20:25:25 +02:00
/**
* Sanitize and validate callback URL
* @ param string $url The URL to sanitize
* @ return string | false Returns sanitized URL or false if invalid
*/
2025-09-14 20:29:01 +02:00
private function sanitize_cat_url ( $url ) {
2025-09-14 20:25:25 +02:00
// Basic sanitization
$url = trim ( $url );
2026-01-22 11:55:30 +01:00
2025-09-14 20:25:25 +02:00
// Check if URL is valid and uses http or https
2026-01-22 11:55:30 +01:00
if ( ! filter_var ( $url , FILTER_VALIDATE_URL ) ||
2025-09-14 20:25:25 +02:00
( ! preg_match ( '/^https?:\/\//' , $url ))) {
return false ;
}
2026-01-22 11:55:30 +01:00
2025-09-14 20:25:25 +02:00
// Remove trailing slashes
$url = rtrim ( $url , '/' );
2026-01-22 11:55:30 +01:00
2025-09-14 20:25:25 +02:00
// Additional XSS cleaning
$url = $this -> security -> xss_clean ( $url );
2026-01-22 11:55:30 +01:00
2025-09-14 20:25:25 +02:00
return $url ;
}
2026-06-07 09:44:12 +02:00
/* **
2026-02-15 10:38:22 -07:00
* List members of a clubstation
* API key needs to be of a club officer ( permission level 9 )
* returns array of club member details
*/
2026-02-16 16:42:56 -07:00
function list_clubmembers () {
2026-02-15 10:38:22 -07:00
header ( 'Content-type: application/json' );
$this -> load -> model ( 'api_model' );
2026-02-16 16:42:56 -07:00
// Decode JSON and store
$obj = json_decode ( file_get_contents ( " php://input " ), true );
if ( $obj === NULL ) {
http_response_code ( 400 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " wrong JSON " ]);
return ;
}
2026-06-08 09:29:47 +02:00
if ( $this -> api_model -> authorize ( $obj [ 'key' ]) == 0 ) {
2026-02-15 10:38:22 -07:00
http_response_code ( 401 );
echo json_encode ([ 'status' => 'error' , 'message' => 'Auth Error, invalid key' ]);
return ;
}
$this -> load -> model ( 'club_model' );
2026-02-16 16:42:56 -07:00
$userid = $this -> api_model -> key_userid ( $obj [ 'key' ]);
$created_by = $this -> api_model -> key_created_by ( $obj [ 'key' ]);
2026-02-15 10:38:22 -07:00
$club_perm = $this -> club_model -> get_permission_noui ( $userid , $created_by );
if (( $userid == $created_by ) || (( $club_perm ? ? 0 ) != 9 )) { // not club officer
http_response_code ( 401 );
echo json_encode ([ 'status' => 'error' , 'message' => 'Auth Error, not enough permissions for this operation' ]);
return ;
}
$memberlist = $this -> club_model -> get_club_members ( $userid );
if ( ! empty ( $memberlist )) {
2026-07-11 17:52:14 +02:00
$members = [];
2026-02-15 10:38:22 -07:00
foreach ( $memberlist as $member ) {
$members [] = [
'callsign' => $member -> user_callsign ,
'user_name' => $member -> user_name ,
'p_level' => $member -> p_level
];
}
http_response_code ( 200 );
echo json_encode ([ 'status' => 'successful' , 'members' => $members ]);
} else {
http_response_code ( 204 );
echo json_encode ([ 'status' => 'failed' , 'reason' => " No club members found " , 'members' => '' ]);
return ;
}
}
2023-04-27 21:08:09 +02:00
}