2011-06-17 13:52:00 +01:00
|
|
|
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
|
|
|
|
|
|
|
|
|
|
class Search extends CI_Controller {
|
|
|
|
|
|
2025-05-30 05:08:58 +00:00
|
|
|
function __construct() {
|
|
|
|
|
parent::__construct();
|
|
|
|
|
|
|
|
|
|
$this->load->helper(array('form', 'url'));
|
2025-12-10 17:36:06 +00:00
|
|
|
if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); }
|
2025-05-30 05:08:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function index() {
|
2024-06-08 11:01:59 +02:00
|
|
|
$data['page_title'] = __("Search");
|
2011-11-04 17:32:03 +00:00
|
|
|
|
2026-06-23 22:03:29 -07:00
|
|
|
$data['stations_active_log_only'] = !empty($this->session->userdata('user_stations_active_log_only'));
|
|
|
|
|
|
2025-05-30 05:08:58 +00:00
|
|
|
$this->load->view('interface_assets/header', $data);
|
2011-06-17 13:52:00 +01:00
|
|
|
$this->load->view('search/main');
|
2025-05-30 05:08:58 +00:00
|
|
|
$this->load->view('interface_assets/footer');
|
2011-06-17 13:52:00 +01:00
|
|
|
}
|
2019-09-04 23:12:45 +01:00
|
|
|
|
2025-05-30 05:08:58 +00:00
|
|
|
// Filter is for advanced searching and filtering of the logbook
|
|
|
|
|
public function filter() {
|
|
|
|
|
$data['page_title'] = __("Search & Filter Logbook");
|
2019-09-04 23:12:45 +01:00
|
|
|
|
2025-05-30 05:08:58 +00:00
|
|
|
$this->load->library('form_validation');
|
2019-09-04 23:12:45 +01:00
|
|
|
|
2025-05-30 05:08:58 +00:00
|
|
|
$this->load->model('Search_filter');
|
2019-09-04 23:42:11 +01:00
|
|
|
|
2025-05-30 05:08:58 +00:00
|
|
|
$data['get_table_names'] = $this->Search_filter->get_table_columns();
|
2021-09-25 17:40:15 +02:00
|
|
|
$data['stored_queries'] = $this->Search_filter->get_stored_queries();
|
2019-09-04 23:42:11 +01:00
|
|
|
|
2026-06-23 22:03:29 -07:00
|
|
|
$data['stations_active_log_only'] = !empty($this->session->userdata('user_stations_active_log_only'));
|
|
|
|
|
|
2025-05-30 05:08:58 +00:00
|
|
|
//print_r($this->Search_filter->get_table_columns());
|
|
|
|
|
|
|
|
|
|
if ($this->form_validation->run() == FALSE)
|
|
|
|
|
{
|
|
|
|
|
$this->load->view('interface_assets/header', $data);
|
|
|
|
|
$this->load->view('search/filter');
|
|
|
|
|
$this->load->view('interface_assets/footer');
|
2025-05-30 05:09:48 +00:00
|
|
|
} else {
|
2025-05-30 05:08:58 +00:00
|
|
|
$this->load->view('interface_assets/header', $data);
|
|
|
|
|
$this->load->view('search/filter');
|
|
|
|
|
$this->load->view('interface_assets/footer');
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-12 00:53:39 +01:00
|
|
|
|
2023-05-26 10:38:31 +02:00
|
|
|
// Searches for unconfirmed Lotw QSOs where QSO partner has uploaded to LoTW after the QSO date
|
|
|
|
|
public function lotw_unconfirmed() {
|
|
|
|
|
$this->load->model('stations');
|
|
|
|
|
|
2026-06-23 21:16:14 -07:00
|
|
|
if (!empty($this->session->userdata('user_stations_active_log_only'))) {
|
|
|
|
|
$data['station_profile'] = $this->logbooks_model->list_logbooks_linked($this->session->userdata('active_station_logbook'));
|
|
|
|
|
$data['stations_active_log_only'] = true;
|
|
|
|
|
} else {
|
|
|
|
|
$data['station_profile'] = $this->stations->all_of_user();
|
|
|
|
|
$data['stations_active_log_only'] = false;
|
|
|
|
|
}
|
2024-06-08 11:01:59 +02:00
|
|
|
$data['page_title'] = __("QSOs unconfirmed on LoTW, but the callsign has uploaded to LoTW after QSO date");
|
2023-05-26 10:38:31 +02:00
|
|
|
|
|
|
|
|
$this->load->view('interface_assets/header', $data);
|
|
|
|
|
$this->load->view('search/lotw_unconfirmed');
|
|
|
|
|
$this->load->view('interface_assets/footer');
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-30 05:08:58 +00:00
|
|
|
function json_result() {
|
2024-08-13 13:50:49 +02:00
|
|
|
$result = $this->fetchQueryResult(($this->input->post('search', TRUE) ?? ''), FALSE);
|
|
|
|
|
echo json_encode($result->result_array());
|
2025-05-30 05:08:58 +00:00
|
|
|
}
|
2021-09-23 20:18:47 +02:00
|
|
|
|
2021-09-25 17:40:15 +02:00
|
|
|
function get_stored_queries() {
|
|
|
|
|
$this->load->model('Search_filter');
|
|
|
|
|
$data['result'] = $this->Search_filter->get_stored_queries();
|
|
|
|
|
$this->load->view('search/stored_queries', $data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function search_result() {
|
2025-01-05 09:47:58 +01:00
|
|
|
$sstring = str_replace('Ø', "0", $this->input->post("search", TRUE) ?? '');
|
2024-12-30 12:15:28 +00:00
|
|
|
$data['results'] = $this->fetchQueryResult($sstring, FALSE);
|
2024-08-13 13:50:49 +02:00
|
|
|
$this->load->view('search/search_result_ajax', $data);
|
2021-09-25 17:40:15 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-23 20:18:47 +02:00
|
|
|
function export_to_adif() {
|
2025-01-20 22:56:35 +01:00
|
|
|
$sstring = str_replace('Ø', "0", $this->input->post("search", TRUE) ?? '');
|
|
|
|
|
$data['qsos'] = $this->fetchQueryResult($sstring, FALSE);
|
2024-08-13 13:50:49 +02:00
|
|
|
$this->load->view('adif/data/exportall', $data);
|
2021-09-23 20:18:47 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-25 17:40:15 +02:00
|
|
|
function export_stored_query_to_adif() {
|
|
|
|
|
$this->db->where('id', xss_clean($this->input->post('id')));
|
2026-06-19 22:02:55 +08:00
|
|
|
$this->db->where('userid', $this->session->userdata['user_id']);
|
2021-09-25 17:40:15 +02:00
|
|
|
$sql = $this->db->get('queries')->result();
|
|
|
|
|
|
2025-12-30 09:01:35 +00:00
|
|
|
$query = $sql[0]->query;
|
|
|
|
|
|
|
|
|
|
// Security: Validate query only accesses allowed tables
|
|
|
|
|
if (!$this->_validate_query_tables($query)) {
|
|
|
|
|
show_error("Invalid query: unauthorized table access detected", 403);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Security: Block dangerous SQL keywords to prevent SQL injection
|
|
|
|
|
// Note: 'join' is NOT blocked because legitimate queries use JOINs
|
2026-06-11 17:21:09 +02:00
|
|
|
$blocked = ['insert', 'drop', 'alter', 'create', 'exec', 'script', 'into outfile', 'load_file', 'update', 'delete', 'truncate', 'replace', 'rename', 'grant', 'revoke'];
|
2025-12-30 09:01:35 +00:00
|
|
|
foreach ($blocked as $word) {
|
|
|
|
|
if (stristr($query, $word)) {
|
|
|
|
|
show_error("Invalid query: contains blocked keyword", 403);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data['qsos'] = $this->db->query($query);
|
2021-09-25 17:40:15 +02:00
|
|
|
$this->load->view('adif/data/exportall', $data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function run_query() {
|
|
|
|
|
$this->db->where('id', xss_clean($this->input->post('id')));
|
2026-06-19 22:02:55 +08:00
|
|
|
$this->db->where('userid', $this->session->userdata['user_id']);
|
2021-09-25 17:40:15 +02:00
|
|
|
$sql = $this->db->get('queries')->result();
|
2021-09-25 19:49:28 +02:00
|
|
|
$sql = $sql[0]->query;
|
2021-09-25 17:40:15 +02:00
|
|
|
|
2025-12-30 09:01:35 +00:00
|
|
|
// Security: Only allow SELECT queries
|
2021-10-07 15:08:28 +01:00
|
|
|
if (stristr($sql, 'select') && !stristr($sql, 'delete') && !stristr($sql, 'update')) {
|
2025-12-30 09:01:35 +00:00
|
|
|
// Security: Validate query only accesses allowed tables
|
|
|
|
|
if (!$this->_validate_query_tables($sql)) {
|
|
|
|
|
show_error("Invalid query: unauthorized table access detected", 403);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Security: Block dangerous SQL keywords to prevent SQL injection
|
|
|
|
|
// Note: 'join' is NOT blocked because legitimate queries use JOINs
|
2026-06-11 17:21:09 +02:00
|
|
|
$blocked = ['insert', 'drop', 'alter', 'create', 'exec', 'script', 'into outfile', 'load_file', 'update', 'delete', 'truncate', 'replace', 'rename', 'grant', 'revoke'];
|
2025-12-30 09:01:35 +00:00
|
|
|
foreach ($blocked as $word) {
|
|
|
|
|
if (stristr($sql, $word)) {
|
|
|
|
|
show_error("Invalid query: contains blocked keyword", 403);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-08 07:50:16 +00:00
|
|
|
if (!(strpos(strtolower($sql),'limit'))) {
|
|
|
|
|
$sql.=' limit 5000';
|
|
|
|
|
}
|
2021-09-25 19:49:28 +02:00
|
|
|
$data['results'] = $this->db->query($sql);
|
2021-09-25 17:40:15 +02:00
|
|
|
|
2021-09-25 19:49:28 +02:00
|
|
|
$this->load->view('search/search_result_ajax', $data);
|
|
|
|
|
}
|
2021-09-25 17:40:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function save_query() {
|
2024-08-13 13:50:49 +02:00
|
|
|
$search_param = $this->input->post('search', TRUE);
|
|
|
|
|
$description = $this->input->post('description', TRUE);
|
|
|
|
|
|
|
|
|
|
$query = $this->fetchQueryResult($search_param, TRUE);
|
|
|
|
|
|
|
|
|
|
$data = array(
|
|
|
|
|
'userid' => xss_clean($this->session->userdata('user_id')),
|
|
|
|
|
'query' => $query,
|
|
|
|
|
'description' => $description
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->db->insert('queries', $data);
|
|
|
|
|
$last_id = $this->db->insert_id();
|
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
|
echo json_encode(array('id' => $last_id, 'description' => $description));
|
2021-09-25 17:40:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function delete_query() {
|
|
|
|
|
$id = xss_clean($this->input->post('id'));
|
|
|
|
|
$this->load->model('search_filter');
|
|
|
|
|
$this->search_filter->delete_query($id);
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 20:06:08 +02:00
|
|
|
function save_edited_query() {
|
2021-09-25 17:40:15 +02:00
|
|
|
$data = array(
|
2021-10-03 20:06:08 +02:00
|
|
|
'description' => xss_clean($this->input->post('description')),
|
2021-09-25 17:40:15 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->db->where('id', xss_clean($this->input->post('id')));
|
2021-10-03 20:06:08 +02:00
|
|
|
$this->db->where('userid', $this->session->userdata['user_id']);
|
|
|
|
|
$this->db->update('queries', $data);
|
2021-09-25 17:40:15 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-11 17:21:09 +02:00
|
|
|
/**
|
|
|
|
|
* Returns the list of valid searchable column names from the main logbook table.
|
|
|
|
|
* Cached statically so the DESCRIBE query runs only once per request.
|
|
|
|
|
*
|
|
|
|
|
* @return array<string> Column names (e.g. ['COL_CALL', 'COL_BAND', ...])
|
|
|
|
|
*/
|
|
|
|
|
private function _get_valid_search_fields(): array {
|
|
|
|
|
static $valid_fields = null;
|
|
|
|
|
if ($valid_fields === null) {
|
|
|
|
|
$columns = $this->db->query('DESCRIBE ' . $this->config->item('table_name'))->result();
|
|
|
|
|
$valid_fields = array_map(function($col) {
|
|
|
|
|
return $col->Field;
|
|
|
|
|
}, $columns);
|
|
|
|
|
}
|
|
|
|
|
return $valid_fields;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 10:16:24 +00:00
|
|
|
function buildWhere(array $object, ?string $condition = null): void {
|
2021-12-20 19:15:22 +00:00
|
|
|
/*
|
|
|
|
|
* The $object is one of the following:
|
|
|
|
|
* - a group, with 'condition' and 'rules' keys
|
|
|
|
|
* - a condition, that is either 'AND' or 'OR' depending on the parent group setting
|
|
|
|
|
*/
|
|
|
|
|
$objectIsGroup = isset($object['condition']);
|
|
|
|
|
if ($objectIsGroup) {
|
|
|
|
|
if ($condition === null || $condition === 'AND') {
|
|
|
|
|
$this->db->group_start();
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->or_group_start();
|
|
|
|
|
}
|
|
|
|
|
foreach ($object['rules'] as $rule) {
|
|
|
|
|
/*
|
|
|
|
|
* Now iterate over the children, that are either groups or conditions
|
|
|
|
|
*/
|
|
|
|
|
$this->buildWhere($rule, $object['condition']);
|
|
|
|
|
}
|
|
|
|
|
$this->db->group_end();
|
|
|
|
|
} else {
|
2026-06-11 17:21:09 +02:00
|
|
|
// Validate field name: must be alphanumeric/underscore AND exist in the table schema
|
2026-06-11 12:43:40 +00:00
|
|
|
if (!is_string($object['field'] ?? null) || !preg_match('/^[A-Za-z0-9_]+$/', $object['field'])) {
|
|
|
|
|
log_message('error', 'Search filter rejected: invalid field identifier');
|
|
|
|
|
show_error('Invalid search field', 400);
|
|
|
|
|
}
|
2026-06-11 17:21:09 +02:00
|
|
|
if (!in_array($object['field'], $this->_get_valid_search_fields(), true)) {
|
|
|
|
|
log_message('error', 'Search filter rejected: unknown field "' . $object['field'] . '"');
|
|
|
|
|
show_error('Invalid search field', 400);
|
|
|
|
|
}
|
2021-12-20 19:15:22 +00:00
|
|
|
$object['field'] = $this->config->item('table_name') . '.' . $object['field'];
|
2021-09-23 20:18:47 +02:00
|
|
|
|
2021-12-20 19:15:22 +00:00
|
|
|
if ($object['operator'] == "equal") {
|
|
|
|
|
if ($condition == "AND") {
|
|
|
|
|
$this->db->where($object['field'], $object['value']);
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->or_where($object['field'], $object['value']);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-23 20:18:47 +02:00
|
|
|
|
2021-12-20 19:15:22 +00:00
|
|
|
if ($object['operator'] == "not_equal") {
|
|
|
|
|
if ($condition == "AND") {
|
|
|
|
|
$this->db->where($object['field'] . ' !=', $object['value']);
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->or_where($object['field'] . ' !=', $object['value']);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-23 20:18:47 +02:00
|
|
|
|
2021-12-20 19:15:22 +00:00
|
|
|
if ($object['operator'] == "begins_with") {
|
|
|
|
|
if ($condition == "AND") {
|
|
|
|
|
$this->db->where($object['field'] . ' like ', $object['value'] . "%");
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->or_where($object['field'] . ' like ', $object['value'] . "%");
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-23 20:18:47 +02:00
|
|
|
|
2021-12-20 19:15:22 +00:00
|
|
|
if ($object['operator'] == "contains") {
|
|
|
|
|
if ($condition == "AND") {
|
|
|
|
|
$this->db->where($object['field'] . ' like ', "%" . $object['value'] . "%");
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->or_where($object['field'] . ' like ', "%" . $object['value'] . "%");
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-23 20:18:47 +02:00
|
|
|
|
2021-12-20 19:15:22 +00:00
|
|
|
if ($object['operator'] == "ends_with") {
|
|
|
|
|
if ($condition == "AND") {
|
|
|
|
|
$this->db->where($object['field'] . ' like ', "%" . $object['value']);
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->or_where($object['field'] . ' like ', "%" . $object['value']);
|
|
|
|
|
}
|
2021-09-23 20:18:47 +02:00
|
|
|
}
|
2021-12-20 19:15:22 +00:00
|
|
|
|
|
|
|
|
if ($object['operator'] == "is_empty") {
|
|
|
|
|
if ($condition == "AND") {
|
2022-01-23 20:52:23 +01:00
|
|
|
$this->db->where($object['field'], '');
|
2021-12-20 19:15:22 +00:00
|
|
|
} else {
|
2022-01-23 20:52:23 +01:00
|
|
|
$this->db->or_where($object['field'], '');
|
2021-12-20 19:15:22 +00:00
|
|
|
}
|
2021-09-23 20:18:47 +02:00
|
|
|
}
|
|
|
|
|
|
2021-12-20 19:15:22 +00:00
|
|
|
if ($object['operator'] == "is_not_empty") {
|
|
|
|
|
if ($condition == "AND") {
|
2022-01-23 20:52:23 +01:00
|
|
|
$this->db->where($object['field'] . ' !=', '');
|
2021-12-20 19:15:22 +00:00
|
|
|
} else {
|
2022-01-23 20:52:23 +01:00
|
|
|
$this->db->or_where($object['field'] . ' !=', '');
|
2021-12-20 19:15:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
2021-09-23 20:18:47 +02:00
|
|
|
|
2021-12-20 19:15:22 +00:00
|
|
|
if ($object['operator'] == "is_null") {
|
|
|
|
|
if ($condition == "AND") {
|
2022-01-23 20:52:23 +01:00
|
|
|
$this->db->where($object['field'] . ' IS NULL');
|
2021-12-20 19:15:22 +00:00
|
|
|
} else {
|
2022-01-23 20:52:23 +01:00
|
|
|
$this->db->or_where($object['field'] . ' IS NULL');
|
2021-12-20 19:15:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($object['operator'] == "is_not_null") {
|
|
|
|
|
if ($condition == "AND") {
|
2022-01-23 20:52:23 +01:00
|
|
|
$this->db->where($object['field'] . ' IS NOT NULL');
|
2021-12-20 19:15:22 +00:00
|
|
|
} else {
|
2022-01-23 20:52:23 +01:00
|
|
|
$this->db->or_where($object['field'] . ' IS NOT NULL');
|
2021-12-20 19:15:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ($object['operator'] == "less") {
|
|
|
|
|
if ($condition == "AND") {
|
|
|
|
|
$this->db->where($object['field'] . ' <', $object['value']);
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->or_where($object['field'] . ' <', $object['value']);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($object['operator'] == "less_or_equal") {
|
|
|
|
|
if ($condition == "AND") {
|
|
|
|
|
$this->db->where($object['field'] . ' <=', $object['value']);
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->or_where($object['field'] . ' <=', $object['value']);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($object['operator'] == "greater") {
|
|
|
|
|
if ($condition == "AND") {
|
|
|
|
|
$this->db->where($object['field'] . ' >', $object['value']);
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->or_where($object['field'] . ' >', $object['value']);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($object['operator'] == "greater_or_equal") {
|
|
|
|
|
if ($condition == "AND") {
|
|
|
|
|
$this->db->where($object['field'] . ' >=', $object['value']);
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->or_where($object['field'] . ' >=', $object['value']);
|
2021-09-23 20:18:47 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-20 19:15:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fetchQueryResult($json, $returnquery) {
|
|
|
|
|
$search_items = json_decode($json, true);
|
|
|
|
|
|
2026-06-23 22:03:29 -07:00
|
|
|
if (!empty($this->session->userdata('user_stations_active_log_only'))) {
|
|
|
|
|
$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
|
|
|
|
|
$this->db->where_in($this->config->item('table_name').'.station_id', $logbooks_locations_array);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-31 05:25:09 +00:00
|
|
|
$this->db->select($this->config->item('table_name').'.*, station_profile.station_profile_name, station_profile.station_gridsquare, station_profile.station_city, station_profile.station_iota, station_profile.station_callsign, station_profile.station_sota, station_profile.station_wwff, station_profile.station_dxcc, station_profile.station_pota, station_profile.station_cq, station_profile.station_itu, station_profile.station_sig, station_profile.station_sig_info, station_profile.station_cnty, station_profile.county, station_profile.state, dxcc_entities.name as station_country');
|
2023-09-22 09:09:01 +02:00
|
|
|
|
2022-01-09 12:09:35 +01:00
|
|
|
$this->db->group_start();
|
2021-12-20 19:15:22 +00:00
|
|
|
$this->buildWhere($search_items);
|
2022-01-09 12:09:35 +01:00
|
|
|
$this->db->group_end();
|
2021-09-25 17:40:15 +02:00
|
|
|
|
2021-09-23 20:18:47 +02:00
|
|
|
$this->db->order_by('COL_TIME_ON', 'DESC');
|
|
|
|
|
$this->db->join('station_profile', 'station_profile.station_id = '.$this->config->item('table_name').'.station_id');
|
2025-01-20 22:56:35 +01:00
|
|
|
$this->db->join('dxcc_entities', 'station_profile.station_dxcc = dxcc_entities.adif', 'left');
|
2022-02-06 09:41:11 +01:00
|
|
|
$this->db->where('station_profile.user_id', $this->session->userdata('user_id'));
|
2025-01-19 19:17:18 +01:00
|
|
|
$this->db->limit(5000);
|
2021-09-25 17:40:15 +02:00
|
|
|
|
|
|
|
|
if ($returnquery) {
|
|
|
|
|
$query = $this->db->get_compiled_select($this->config->item('table_name'));
|
|
|
|
|
} else {
|
|
|
|
|
$query = $this->db->get($this->config->item('table_name'));
|
|
|
|
|
}
|
2021-09-23 20:18:47 +02:00
|
|
|
return $query;
|
|
|
|
|
}
|
2025-12-30 09:01:35 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validates that query only accesses allowed tables
|
|
|
|
|
* Prevents SQL injection via UNION-based attacks on other tables
|
|
|
|
|
*
|
|
|
|
|
* @param string $sql The SQL query to validate
|
|
|
|
|
* @return bool TRUE if query only uses allowed tables, FALSE otherwise
|
|
|
|
|
*/
|
|
|
|
|
private function _validate_query_tables($sql) {
|
|
|
|
|
// Whitelist of allowed tables - users can only query these
|
|
|
|
|
$allowed_tables = [
|
|
|
|
|
$this->config->item('table_name'), // Main logbook table (e.g., TABLE_HRD_CONTACTS_V01)
|
|
|
|
|
'station_profile',
|
|
|
|
|
'dxcc_entities',
|
|
|
|
|
'lotw_users',
|
|
|
|
|
'queries'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Convert to lowercase for case-insensitive comparison
|
|
|
|
|
$allowed_tables_lower = array_map('strtolower', $allowed_tables);
|
|
|
|
|
$main_table_lower = strtolower($this->config->item('table_name'));
|
|
|
|
|
|
|
|
|
|
// Normalize the SQL: remove newlines and extra spaces for easier parsing
|
|
|
|
|
$normalized_sql = preg_replace('/\s+/', ' ', trim($sql));
|
|
|
|
|
|
|
|
|
|
// Pattern 1: Check for UNION/INTO OUTFILE/LOAD FILE - these are always blocked
|
|
|
|
|
if (preg_match('/\bunion\b.*?\bselect.*?\bfrom\s+(\w+)/i', $normalized_sql, $matches)) {
|
|
|
|
|
$union_table = strtolower($matches[1]);
|
|
|
|
|
if (!in_array($union_table, $allowed_tables_lower)) {
|
|
|
|
|
log_message('error', "Search query blocked: UNION with unauthorized table - '$union_table'");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pattern 2: Extract all table names after FROM and JOIN keywords
|
|
|
|
|
// This handles: FROM `table`, JOIN `table`, FROM table, JOIN table
|
|
|
|
|
preg_match_all('/\b(?:FROM|JOIN)\s+`?(\w+)`?/i', $normalized_sql, $from_join_matches);
|
|
|
|
|
$found_tables = $from_join_matches[1];
|
|
|
|
|
|
|
|
|
|
// Pattern 3: Extract table.column references that are NOT in the whitelist
|
|
|
|
|
// This catches things like "users.password" or "admin.secret"
|
|
|
|
|
preg_match_all('/(\w+)\.\w+/i', $normalized_sql, $column_refs);
|
|
|
|
|
foreach ($column_refs[1] as $potential_table) {
|
|
|
|
|
$potential_table_lower = strtolower($potential_table);
|
|
|
|
|
// Only add if it's not a SQL keyword and not the main table
|
|
|
|
|
$sql_keywords = ['select', 'where', 'order', 'group', 'having', 'limit', 'offset',
|
|
|
|
|
'and', 'or', 'not', 'null', 'like', 'in', 'between', 'exists', 'case',
|
|
|
|
|
'when', 'then', 'else', 'end', 'as', 'on', 'desc', 'asc', 'left', 'right', 'inner', 'outer'];
|
|
|
|
|
if (!in_array($potential_table_lower, $sql_keywords) && $potential_table_lower !== $main_table_lower) {
|
|
|
|
|
$found_tables[] = $potential_table;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check all found tables are in whitelist
|
|
|
|
|
$found_tables = array_unique($found_tables);
|
|
|
|
|
foreach ($found_tables as $table) {
|
|
|
|
|
if (empty($table)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$table_lower = strtolower($table);
|
|
|
|
|
|
|
|
|
|
// Skip common SQL keywords that might match
|
|
|
|
|
$sql_keywords = ['select', 'where', 'order', 'group', 'having', 'limit', 'offset',
|
|
|
|
|
'and', 'or', 'not', 'null', 'like', 'in', 'between', 'exists', 'case',
|
|
|
|
|
'when', 'then', 'else', 'end', 'as', 'on', 'desc', 'asc', 'left', 'right', 'inner', 'outer'];
|
|
|
|
|
if (in_array($table_lower, $sql_keywords)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!in_array($table_lower, $allowed_tables_lower)) {
|
|
|
|
|
log_message('error', "Search query blocked: unauthorized table access detected - '$table'");
|
|
|
|
|
log_message('error', "Full query: $sql");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2022-02-13 23:03:38 +01:00
|
|
|
}
|