userdata_dir = $this->config->item('userdata'); $this->flag_file = '.migrated'; // we use this flag file to determine if the migration already run through $this->src_eqsl = 'images/eqsl_card_images'; $this->eqsl_dir = 'eqsl_card'; // make sure this is the same as in Eqsl_images.php function get_imagePath() $this->src_qsl = 'assets/qslcard'; $this->qsl_dir = 'qsl_card'; // make sure this is the same as in Qsl_model.php function get_imagePath() } function migrate_userdata() { $this->load->model('Logbook_model'); $allowed_file_extensions = ['jpg', 'jpeg', 'gif', 'png']; // ***** EQSL ***** // // Let's scan the whole folder and get necessary data for each file foreach (scandir($this->src_eqsl) as $file) { // Ignore files if they are not jpg, png or gif $file_extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); if (!in_array($file_extension, $allowed_file_extensions)) continue; if (!is_readable($this->src_eqsl . '/' . $file)) continue; if ($file != '.' && $file != '..') { // we need the qso_id from the database to get the necessary user_id $qso_id = $this->get_qsoid_from_eqsl_filename($file) ?? ''; // check if the qso_id is empty, if yes we create a folder 'not assigned' instead of 'user_id' if (!empty($qso_id)) { // get the user_id for this qso_id $get_user_id = $this->Logbook_model->get_user_id_from_qso($qso_id); // it can happen that the user_id is empty even there is a qso_id (deleted qso or deleted user) if(!empty($get_user_id)) { $user_id = $get_user_id; } else { $user_id = 'not_assigned'; } } else { $user_id = 'not_assigned'; } // make sure the target path exists $target = $this->userdata_dir . '/' . $user_id . '/' . $this->eqsl_dir; if (!file_exists(realpath(APPPATH . '../') . '/' . $target)) { mkdir(realpath(APPPATH . '../') . '/' . $target, 0755, true); } // then copy the file if (!copy($this->src_eqsl . '/' . $file, $target . '/' . $file)) { return false; // Failed to copy file } } } // ***** QSL Cards ***** // // Let's scan the whole folder and get necessary data for each file foreach (scandir($this->src_qsl) as $file) { // Ignore files if they are not jpg, png or gif $file_extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); if (!in_array($file_extension, $allowed_file_extensions)) continue; if (!is_readable($this->src_qsl . '/' . $file)) continue; if ($file != '.' && $file != '..') { // we need the qso_id from the database to get the necessary user_id $qso_id = $this->get_qsoid_from_qsl_filename($file) ?? ''; // check if the qso_id is empty, if yes we create a folder 'not assigned' instead of 'user_id' if (!empty($qso_id)) { // get the user_id for this qso_id $get_user_id = $this->Logbook_model->get_user_id_from_qso($qso_id); // it can happen that the user_id is empty even there is a qso_id (deleted qso or deleted user) if(!empty($get_user_id)) { $user_id = $get_user_id; } else { $user_id = 'not_assigned'; } } else { $user_id = 'not_assigned'; } // make sure the target path exists $target = $this->userdata_dir . '/' . $user_id . '/' . $this->qsl_dir; if (!file_exists(realpath(APPPATH . '../') . '/' . $target)) { mkdir(realpath(APPPATH . '../') . '/' . $target, 0755, true); } // then copy the file if (!copy($this->src_qsl . '/' . $file, $target . '/' . $file)) { return false; // Failed to copy file } } } // here we create the 'migrated' flag if (!file_exists(realpath(APPPATH . '../') . '/' . $this->userdata_dir . '/' . $this->flag_file)) { touch(realpath(APPPATH . '../') . '/' . $this->userdata_dir . '/' . $this->flag_file); } return true; } function check_migrated_flag() { if (!file_exists(realpath(APPPATH . '../') . '/' . $this->userdata_dir . '/' . $this->flag_file)) { return false; } else { return true; } } function get_qsoid_from_eqsl_filename($filename) { $sql = "SELECT qso_id FROM eQSL_images WHERE image_file = ?"; $result = $this->db->query($sql, $filename); $row = $result->row(); return $row->qso_id; } function get_qsoid_from_qsl_filename($filename) { $sql = "SELECT qsoid FROM qsl_images WHERE filename = ?"; $result = $this->db->query($sql, $filename); $row = $result->row(); return $row->qsoid; } // Returns the number of qso's total on this instance function count_all_qso() { $sql = 'SELECT COUNT(*) AS total FROM '. $this->config->item('table_name').' WHERE station_id IS NOT NULL;'; $query = $this->db->query($sql); return $query->row()->total; } function count_users() { $sql = 'SELECT COUNT(*) AS total FROM users;'; $query = $this->db->query($sql); return $query->row()->total; } function getMigrationVersion() { $this->db->select_max('version'); $query = $this->db->get('migrations'); $migration_version = $query->row(); if ($query->num_rows() == 1) { $migration_version = $query->row()->version; return $migration_version; } else { return null; } } public function calls_without_station_id() { $query=$this->db->query("select distinct COL_STATION_CALLSIGN from ".$this->config->item('table_name')." where station_id is null or station_id = ''"); $result = $query->result_array(); return $result; } function get_cache_info() { $response = []; $cache_path = $this->config->item('cache_path') ?? NULL; if (!$cache_path && $cache_path !== '') { $cache_path = ''; // default path is application/cache $response['config']['cache_path'] = sprintf(__(" %s not set in config, using default path: %s"), "'cache_path'", "application/cache"); } else { $response['config']['cache_path'] = $cache_path; } $cache_adapter = $this->config->item('cache_adapter') ?? NULL; if (!$cache_adapter) { $cache_adapter = 'file'; // default adapter is file $response['config']['cache_adapter'] = sprintf(__(" %s not set in config, using default adapter: %s"), "'cache_adapter'", "file"); } else { $response['config']['cache_adapter'] = $cache_adapter; } $cache_backup = $this->config->item('cache_backup') ?? NULL; if (!$cache_backup) { $cache_backup = 'file'; // default backup is file $response['config']['cache_backup'] = sprintf(__(" %s not set in config, using default backup: %s"), "'cache_backup'", "file"); } else { $response['config']['cache_backup'] = $cache_backup; } $cache_key_prefix = $this->config->item('cache_key_prefix') ?? NULL; if (!$cache_key_prefix) { $cache_key_prefix = ''; // default key prefix is empty $response['config']['cache_key_prefix'] = sprintf(__(" %s not set in config, using default: empty string %s"), "'cache_key_prefix'", "('')"); } else { $response['config']['cache_key_prefix'] = $cache_key_prefix; } // Load cache driver $this->load->driver('cache', [ 'adapter' => $cache_adapter, 'backup' => $cache_backup, 'key_prefix' => $cache_key_prefix ]); // Get cache details $cache_size = $this->get_cache_size(); $cache_keys_count = $this->get_cache_keys_count(); $response['details']['size'] = $this->format_bytes($cache_size); $response['details']['size_bytes'] = $cache_size; $response['details']['keys_count'] = $cache_keys_count; $available_adapters = ['file', 'redis', 'memcached', 'apcu']; foreach ($available_adapters as $adapter) { $response['adapters'][$adapter] = $this->cache->is_supported($adapter); } return $response; } public function clear_cache() { $cache_adapter = $this->config->item('cache_adapter') ?? 'file'; $cache_backup = $this->config->item('cache_backup') ?? 'file'; $cache_key_prefix = $this->config->item('cache_key_prefix') ?? ''; $this->load->driver('cache', [ 'adapter' => $cache_adapter, 'backup' => $cache_backup, 'key_prefix' => $cache_key_prefix ]); if ($cache_adapter === 'file') { $cache_path = $this->config->item('cache_path') ?: 'application/cache'; $cache_path = realpath(APPPATH . '../') . '/' . $cache_path; if (!is_dir($cache_path)) { return false; } $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($cache_path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($files as $file) { if ($file->isFile() && !in_array($file->getFilename(), ['index.html', '.htaccess'])) { @unlink($file->getRealPath()); } } return true; } if (method_exists($this->cache, 'clean')) { return $this->cache->clean(); } return false; } function get_cache_size($adapter = NULL) { $cache_adapter = $adapter ?? ($this->config->item('cache_adapter') ?? 'file'); switch ($cache_adapter) { case 'file': $cache_path = $this->config->item('cache_path') ?: 'application/cache'; $cache_path = realpath(APPPATH . '../') . '/' . $cache_path; if (!is_dir($cache_path)) { return 0; } $size = 0; $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($cache_path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST); foreach ($files as $file) { if ($file->isFile() && !in_array($file->getFilename(), ['index.html', '.htaccess'])) { $size += $file->getSize(); } } return $size; case 'redis': if ($this->cache->is_supported('redis')) { $redis_info = $this->cache->cache_info('redis'); // Note: This returns total Redis server memory usage, not just cache keys with prefix // used_memory_dataset excludes overhead and is more accurate for data size if (isset($redis_info['used_memory_dataset'])) { return (int)$redis_info['used_memory_dataset']; } return isset($redis_info['used_memory']) ? (int)$redis_info['used_memory'] : 0; } return 0; case 'memcached': if ($this->cache->is_supported('memcached')) { $memcached_info = $this->cache->cache_info('memcached'); log_message('debug', 'Memcached Info: ' . print_r($memcached_info, true)); // Memcached returns array of servers, each with stats if (is_array($memcached_info)) { $total_bytes = 0; foreach ($memcached_info as $server_stats) { if (is_array($server_stats)) { // bytes is the current bytes used in the cache if (isset($server_stats['bytes'])) { $total_bytes += (int) $server_stats['bytes']; } } } return $total_bytes; } // Fallback for single server format if (isset($memcached_info['bytes'])) { return (int) $memcached_info['bytes']; } return 0; } return 0; case 'apcu': if ($this->cache->is_supported('apcu')) { $apcu_info = apcu_cache_info(); return isset($apcu_info['mem_size']) ? (int)$apcu_info['mem_size'] : 0; } return 0; default: return 0; } } function get_cache_keys_count($adapter = NULL) { $cache_adapter = $adapter ?? ($this->config->item('cache_adapter') ?? 'file'); switch ($cache_adapter) { case 'file': $cache_path = $this->config->item('cache_path') ?: 'application/cache'; $cache_path = realpath(APPPATH . '../') . '/' . $cache_path; if (!is_dir($cache_path)) { return 0; } $count = 0; $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($cache_path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST); foreach ($files as $file) { if ($file->isFile() && !in_array($file->getFilename(), ['index.html', '.htaccess'])) { $count++; } } return $count; case 'redis': if ($this->cache->is_supported('redis')) { $redis_info = $this->cache->cache_info('redis'); $total_keys = 0; // Parse keyspace info (db0, db1, etc.) foreach ($redis_info as $key => $value) { if (preg_match('/^db(\d+)$/', $key) && is_string($value)) { // Parse "keys=4,expires=4,avg_ttl=43131246" format if (preg_match('/keys=(\d+)/', $value, $matches)) { $total_keys += (int)$matches[1]; } } } return $total_keys; } return 0; case 'memcached': if ($this->cache->is_supported('memcached')) { $memcached_info = $this->cache->cache_info('memcached'); if (isset($memcached_info['curr_items'])) { return (int) $memcached_info['curr_items']; } if (is_array($memcached_info)) { $total_items = 0; foreach ($memcached_info as $server_stats) { if (is_array($server_stats) && isset($server_stats['curr_items'])) { $total_items += (int) $server_stats['curr_items']; } } return $total_items; } return 0; } return 0; case 'apcu': if ($this->cache->is_supported('apcu')) { $apcu_info = apcu_cache_info(); return isset($apcu_info['num_entries']) ? (int)$apcu_info['num_entries'] : 0; } return 0; default: return 0; } } function format_bytes($bytes, $precision = 2) { $units = ['B', 'KB', 'MB', 'GB']; $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= (1 << (10 * $pow)); return round($bytes, $precision) . ' ' . $units[$pow]; } }