Bump migration_version to 264 and add Migration_tag_2_8_11. The new migration updates the 'options' table to set version = '2.8.11' and resets users' 'version_dialog' confirmed flag to 'false' to trigger the version info dialog. The down() method reverts the stored version back to '2.8.10'.
Add an optional $display_callsign parameter to on_air and on_air_image so embedders can show a different callsign than the one used to look up the user (e.g., /widgets/on_air/M0ABC/GB2XXX). Update embed examples and use the display_callsign when provided (sanitised with xss_clean and strtoupper) while preserving the original lookup behavior.
Bump migration_version to 263 and add Migration_notes_utf8mb4_columns. The new migration converts the notes table to use utf8mb4 with utf8mb4_unicode_ci to support emoji and other 4-byte characters; down() reverts the table back to utf8 with utf8_general_ci.
Introduce Widgets::on_air_image(callsign) — an embeddable SVG "on air" status badge (usage: /widgets/on_air_image/YOURCALL). The method validates the callsign, looks up the user and their recent radio status via user_model and cat, builds a status string (including SAT, frequency+mode, or "qrt" if offline), and chooses a badge color. It computes dynamic badge widths using an approximate Verdana character width, sanitizes text for XML output, includes accessible title/aria-label, and returns the SVG with Content-Type image/svg+xml and Cache-Control: no-cache, max-age=60. Errors are shown for missing callsign or unknown user.
Introduce an embeddable "on air" status widget. Adds Widgets::on_air controller action to lookup a user by callsign, sanitize input, and render a new widgets/on_air view (iframe-friendly). Adds Cat::recent_status_by_user_id to fetch recent CAT entries (last 15 minutes) for public display. View shows ON AIR/ QRT badges, radio/satellite details, and includes auto-refresh and minimal styling; usage: /widgets/on_air/YOURCALL.
Prefer COL_GRIDSQUARE when present and fall back to COL_VUCC_GRIDS when building grid information. Added COL_VUCC_GRIDS to select lists in Note model queries and updated Logbook_model to set plot HTML to VUCC grids if gridsquare is missing. Also updated the public station diary view to display VUCC grids in the table when COL_GRIDSQUARE is empty. Files changed: Logbook_model.php, Note.php, public_index.php.
Expand highlight DX query to include QSOs with gridsquares/VUCC when stored distance is missing. Join station_profile to access station_gridsquare, select up to 50 candidate QSOs, prefer stored COL_DISTANCE but compute distances via the Qra library when needed, and pick the farthest candidate (rounded to int). Apply the same logic for daily and date-range queries and preserve the satellite-only filter. Replaces the previous strict COL_DISTANCE>0 single-row lookup to avoid excluding valid DXs derivable from grids.
Replace plain text status with a badge containing an icon and tooltip in the DX Cluster tab (increase font-size to 0.85rem and set cursor to default). Update setStatus() to drive the icon class and tooltip text via a stateMap for common states (Connected, Connecting..., Reconnecting..., Error, Disconnected), set the badge class, and update both title and data-bs-original-title for Bootstrap tooltip compatibility. Adds a fallback for unknown states.
Introduce a frontend QRA utilities module and switch QSO locator/bearing/distance logic to run client-side. Added assets/js/qra-utils.js (ported from the PHP Qra library), injected measurement_base and station_gridsquares into the QSO view, and updated application/controllers/Qso.php to provide the measurement_base for the frontend. Footer now loads qra-utils.js before qso.js. Modified assets/js/sections/qso.js to debounce locator input, place map markers from local grid→lat/lng math, and compute bearing/distance via QraUtils (removes several AJAX calls to server endpoints). This reduces server round-trips and keeps frontend behavior consistent with the PHP implementation.
Read and cast the manual mode once into $manual using $this->input->get(...) with a default of 0, and replace all direct $_GET['manual'] usages with $manual. Updates include the JavaScript qso_manual value, form action and resetTimers call, and multiple conditional checks and disabled attributes to improve input handling and consistency. Also ensures EOF newline is present.
Replace inline DB queries in Dxcluster with a new Logbook_model::check_if_dxcc_worked_in_logbook method to centralize DXCC existence checks (per-band and overall). Remove several debug log statements from Dxcluster. Add client-side logic in dxcluster and qso views to enforce a CW↔RBN rule: when mode is 'cw' the "hide RBN" option is forced off and the checkbox is disabled (and restored when leaving CW), and apply that lock on load and mode changes. Also disable track-band when the user manually changes band in the QSO cluster UI.
Add/adjust known FT8/FT4 spot frequencies and tighten band mode detection ranges. New FT8/FT4 frequencies were added across multiple bands (160m, 80m, 30m, 20m, 17m, 15m, 12m, 10m, 6m, etc.). Mode boundary logic was refined for 160m, 80m, 60m (added), 40m, 6m, 4m, 2m and 70cm to better reflect common CW/DIGI/SSB allocations and specific FT8 spots. These changes improve automatic mode detection from frequency values.
Add UI controls for mode and "New DXCC" filtering to the DX Cluster and QSO views, persist selections in localStorage, and wire them into existing spot filtering and rendering logic (including auto-unhide of RBN when CW is selected). Extract shared helper functions into a new assets/js/dxcluster-utils.js (DXCluster) providing isRbnSpot, getBandFromFrequency, detectModeFromFrequency, and detectMode, and update views to use these helpers. Also adjust badge logic to prioritize truly new DXCC, include the new script, and ensure spots are held until worked status is known for new/DXCC-band filtering.
Change DataTables draw() call to draw(false) so the table is redrawn without resetting the current page. This keeps the user's pagination position after updates in application/views/qso/index.php.
When a spot is selected on the QSO page, if a radio is chosen (radio != '0') the code now posts a QSY request to the dxcluster/qsy endpoint. The frequency is converted to MHz with 3 decimals and included as radio_id and frequency in a JSON body; if the spot is from RBN and marked CW the mode='CW' is also sent. The fetch is non-blocking and errors are silently ignored. This brings the QSO page behavior in line with the main DX Cluster page by automatically tuning a selected radio to the spotted frequency.
Restrict RBN spot detection to skimmers that end with a literal '#' after the callsign. Previously the code matched hyphen followed by '#' or digits (/\-[#\d]+$/), which could misclassify numeric SSIDs like '-1' as RBN spots. The check now matches hyphen + one or more literal '#' characters (/\-#+$/) and simplifies trimming by removing the temporary uppercase variable.
Refine RBN skimmer detection and avoid unnecessary DOM redraws when the DX Cluster tab is hidden. isRbn now requires a literal '#' suffix rather than any trailing digits to prevent false positives. renderTable skips re-rendering if the cluster pane is not active, and the tab-visibility handler now calls renderTable before syncing the band filter and adjusting column widths so spots received while hidden are shown when the tab becomes visible.
Add a 'Track Band' checkbox to the cluster controls and wire up state and persistence. Introduces a trackBand flag (default false), stores the setting in localStorage ('cloudlog_clusterTrackBand'), restores the state on init, and listens for changes to enable/disable band tracking. When disabled the band filter is reset to 'all'; when enabled it syncs the cluster band from the selected radio. Also prevents syncBandFromRadio from running when tracking is off.
Introduce a DX Cluster tab to the QSO view and user settings. This change:
- Enables a new qso_form option 'dxcluster_tab' in controllers (Qso.php, User.php) so the tab can be toggled per-user.
- Adds a user preference toggle in the user edit view to show/hide the DX Cluster tab.
- Implements the DX Cluster UI in the QSO index view: tab button, controls (band select, hide RBN), spot table and click-to-fill behavior.
- Adds client-side JS to manage a WebSocket to wss://dxc.cloudlog.org, maintain and render recent spots in a DataTable, persist filter preferences, sync band selection with the QSO form/radio, and batch-check worked status via the dxcluster/check_worked endpoint.
Files changed: application/controllers/Qso.php, application/controllers/User.php, application/views/qso/index.php, application/views/user/edit.php.
Replace jQuery $(function() {}) with document.addEventListener('DOMContentLoaded') in the QSO view and harden selectize usage in assets/js/sections/qso.js. Add existence checks (for elements and selectize instances) before accessing .selectize to avoid null/undefined dereferences, and consolidate selectize clearing logic. These changes improve robustness when selectize or jQuery readiness assumptions aren't met and prevent runtime errors.
Always render the DOK field wrapper but mark it with data-user-hidden when the field is disabled for the user, and make toggleDokField skip showing the field in that case. Add defensive checks around Selectize accesses in assets/js/sections/qso.js (ensure $select[0] exists and dok_selectize is non-null before calling methods) to avoid JS errors when the DOK input is absent or not initialized. Changes touch application/views/qso/index.php and assets/js/sections/qso.js to prevent runtime errors and correctly honor user-hidden DOK configuration.
Adjust indentation in assets/js/sections/qso.js so the $('#dxcc_id').val(...) line is properly nested within the surrounding block. This is a whitespace/readability fix only and introduces no functional change.
Call toggleDokField()/toggleUsaFields() on jQuery ready and ensure programmatic updates to #dxcc_id fire change handlers. Replaced the previous DOMContentLoaded init with a jQuery $(function()) call to initialise the toggle state, added .trigger('change') where #dxcc_id is set programmatically, and invoke the toggle functions from the dxcc change handler to keep UI fields in sync.
Introduce per-user visibility settings for QSO form fields and tabs. Load qso_form options in QSO and User controllers (with sensible defaults), apply them to the qso view to conditionally render fields/tabs, and save changes from the user edit form. Add a QSO Form accordion in user/edit.php exposing switches for each field/tab, update user save logic to persist each option, and include client-side JS in qso/index.php to toggle USA state/county and DOK fields based on DXCC selection. Files changed: application/controllers/Qso.php, application/controllers/User.php, application/views/qso/index.php, application/views/user/edit.php.
Move the small badge elements (callsign_info, locator_info_contest_dxcc, distance_contest_dxcc) out of the callsign input column and into a new row below the distance field (div#callsign-badge-row) with a min-height to preserve layout. In contesting.js, replace table.columns.adjust().draw(false) with table.draw(false) in updateTableColumns to avoid an extra column adjustment and reduce redraw overhead.
Rework contest QSO form layout and add collapsible panels for session settings and extra fields (name/comment/copy-exchange). Add prominent reset/save buttons, inline chevrons to indicate collapse state, and a logbook search input. In assets/js/sections/contesting.js ensure DataTables are destroyed before DOM updates, initialize DataTable with a compact dom, preserve initial search from the new logbook search field, add a keyup handler to drive table filtering, and fix column render targets/padding. Also remove a redundant search/order call after logging a QSO to avoid duplicate operations.
Add a contest stats card to the contesting view and implement client-side logic to compute and display session QSOs, per-band counts, and recent rate. Hide the callsign suggestion box by default and show/hide it based on lookup results; add a debounced duplicate-worked check while typing and visual success/error styling on the callsign input. Mark duplicate QSOs in the table (table-warning) by counting identical call|band|mode entries, and add updateTableColumns/updateContestStats helpers to toggle DataTable columns by exchange type and refresh contest stats when the QSO table is rebuilt. Also include small resets to clear suggestion UI and styling when fields are reset.
Bump migration version to 262 and add Migration_tag_2_8_10 which updates the stored app version to 2.8.10 and forces the version info dialog by resetting the user option. The migration includes a down() to revert the version to 2.8.9. Also update L.Terminator.js to generate three longitude-shifted polygon copies (offsets -360, 0, +360) so the night/day terminator overlay tiles correctly across world copies when the map is zoomed out.
Replace a single long combined option for ARRL VHF with two separate select options. The previous option that used the lang() helper was changed to two options with values "VHF-3-BAND" and "VHF-FM-ONLY" and labels "VHF-3-BAND (ARRL VHF)" and "VHF-FM-ONLY (ARRL VHF)" to improve clarity and better match expected Cabrillo category values.
Add a full Cabrillo export workflow and harden Cabrillo/QSO formatting. Introduces a modal UI to export contest logs (new button + modal form with fields for location, category time, operators, club, soapbox, date range and other Cabrillo categories). Controller updates pass the new fields to the export action. Cabrilloformat library extended to accept and emit LOCATION and CATEGORY-TIME, improve header field ordering and presence checks, map ADIF modes to the five Cabrillo modes (CW/PH/FM/RY/DG), fix a band label (2.4G -> 2.3G), and emit placeholders for missing received exchanges to preserve column alignment. Contesting_model: more robust date parsing with UTC fallback, ensure session QSO marker only persists when timestamp valid, and build start timestamp when LIVE mode omits start_date/start_time. Frontend JS: setSession() now returns the ajax promise so callers can await it; several callers updated to await setSession and re-fetch session data before refreshing the QSO table; restore full table search on callsign blur and when suggestions are cleared. Misc: small form/input fixes (club field type, default overlay option) and additional server-supplied data loaded into the contesting view (active station id, contest session, station profile). These changes add required Cabrillo fields for certain contests and make exports and session handling more reliable.
Adjust terminator/greyline visual style and default options. Changed colors to #4a6fa5 (stroke) and #001f3f (fill), increased stroke weight to 1.5, lowered fillOpacity to 0.18, and changed resolution from 2 to 1. These edits were applied both where the greyline is created in the footer view and in the L.Terminator default options to keep behavior and appearance consistent.
Add a day/night greyline (terminator) overlay to the dashboard map.
- New assets/js/leaflet/L.Terminator.js: a Leaflet polygon plugin that computes the solar terminator for a given time and exposes L.terminator().
- Load the terminator script in the footer and register a Greyline overlay layer with start/stop interval updates (refreshes every minute). Updates are managed when the overlay is added/removed or when the map unloads to avoid orphaned timers.
- Expose the greyline as a toggleable overlay in the map layer control and add markers to a dedicated markersLayer overlay (instead of adding them directly to the map).
- Add a user preference checkbox to application/views/user/edit.php (Enable Dashboard Map Greyline Layer) and wire saving/reading of the dashboard_map_greyline option in application/controllers/User.php and application/controllers/Dashboard.php. Default behavior is enabled when not set.
This change lets users enable/disable the visual day/night terminator on the dashboard map and ensures proper lifecycle handling for updates and marker layering.
Implement server-side pagination for previous contacts and add a DXCC Summary tab. Controller Qso now uses last_custom_paginated and exposes total_rows/total_pages/current_page/limit. Logbook_model gained last_custom_paginated and last_custom_count to support paged queries. The previous_contacts view was updated with HTMX pagination controls and wrapping for a scrollable table; qso/index was refactored to a tabbed UI (Previous Contacts / DXCC Summary). Lookup result display was improved: responsive table, sticky header, consolidation of LSB/USB into SSB and filtering out empty modes. JS and CSS updates load DXCC HTML into the new tab, hide legacy dxccsummary elements, and add form reset / Escape handling to return focus to the previous contacts tab.
Introduce a user preference to treat satellite QSOs as "worked" for DXPedition status and wire it through the UI, controller, and model. Changes: add dashboard_dxpedition_sat_worked default and POST handling in User controller, add checkbox to user settings view to toggle the option, update Workabledxcc_model to optionally include satellite results when computing workedBefore via a new shouldIncludeSatelliteWorked() helper, and tweak upcoming_dxccs view styling and row classes to visually indicate worked vs needed DXPeditions. This makes DXPedition cards respect users' preference for counting satellite contacts.
Replace dash placeholder with the string "Unknown" for DXpedition callsign display. The Workabledxcc_model::normalize method now returns "Unknown" for empty or placeholder patterns (e.g. F/H, M/S). The upcoming_dxccs view was updated to default to and check for "Unknown" when rendering callsigns and tooltips so the UI shows a clearer, consistent placeholder.
Add normalizeDxpedCallsign() to Workabledxcc_model and use it in the controller and model to standardize/replace placeholder callsigns (e.g. F/H, M/S) with '-'. Improve week filtering and date handling: set explicit times for start/end of week and records, use overlap logic to include DXpeditions that intersect the week, and compute daysLeft with proper past/last-day labeling. Revamp upcoming_dxccs view: implement client-side pagination, page size, prev/next controls, escaped tooltips, and graceful display for placeholder callsigns; add a link to the full list. These changes improve display consistency and UI usability.
Add callsign uniqueness checks and prevent demotion of the last admin. Introduces ECALLSIGNEXISTS and ELASTADMIN constants, new model methods (exists_by_callsign, count_admin_users, would_remove_last_admin) and integrates checks into add/update flows to return appropriate error codes. Update User controller to add validation callbacks (check_unique_callsign, check_last_admin_role) and wire errors into multiple user actions. Update signup/edit views to display callsign and usertype validation feedback using Bootstrap invalid-feedback and improve alert layout.
Handle compound callsigns (e.g. F/MM9SQL/P) when looking up call history and database records. Added extract_base_callsign() to normalize and extract the longest segment of a slash-separated callsign, updated controller lookup to fall back to the base callsign and to skip empty rows, and tightened matching logic to accept either the full input or its base call. Updated model WHERE clause to match normalized COL_CALL against exact, first, middle, or last slash-separated segments (with Ø→0 normalization) to ensure compound variants are found.
Validate and harden SIG handling across controllers and views. Awards: trim and validate requested SIG type, guard against empty types and wrap SIG data loading in try/catch with error logging and user notice. Callhistory: tighten input trimming and validation for proposed SIG info, reject proposals without membership numbers and enforce per-organization SIG-info rules (e.g. numeric for CWOPS). Add scan_remove action to perform reverse/bulk SIG clears for matched QSOs, and filter/validate batch changes before applying. Improve CSV/header parsing by normalizing header keys, aligning rows to headers (handles N1MM !!Order!! cases), extracting exch1 robustly, and adding helpers (looks_like_callsign, is_valid_sig_info_for_organization). View: add Reverse Bulk SIG Update UI, confirmation modal and JS helpers, and convert several forms to use JS-driven confirm dialogs. Misc: various trimming/safety fixes and UX messages.
Wrap preview table script in an IIFE and move initialization into initCallhistoryPreviewTable(). Add guards to wait/retry until jQuery and DataTables are available, prevent double-initialization, and defer startup until window load. Preserve selection helpers (select/deselect, check-all) but scope them to the DataTable instance, update selected count handling, and validate on submit (alert + prevent submit when no rows selected). Overall changes reduce race conditions and make the preview table initialization more robust.
Set DataTables default pageLength to 100 for the call history preview table, add a lengthMenu with options [25, 50, 100, 250, All], and enable full_numbers pagingType to improve navigation when viewing larger result sets.
Switch client/server to send selected row changes as a JSON payload. The view now renders checkboxes with data-* attributes (instead of multiple named hidden inputs) and includes a hidden changes_json field. JS collects checked rows into an array, serializes it to changes_json on submit, and adds helpers (getAllRowChecks, updated select/deselect logic and validation). The controller now accepts changes_json (JSON-decodes it into raw_changes) and falls back to the old POST 'changes' param for backward compatibility.
Include the number of selected QSOs in the flash notice and update feedback to show both selected and actually updated counts. Also fix the model logic to only increment the applied counter when DB affected_rows() > 0 (previously >= 0), preventing non-updates from being counted.
Add an optional start_date filter to the callhistory preview workflow. Controller: validate YYYY-MM-DD input, set flash notice and redirect on invalid format, pass the start_date to the model and include it in view data. Model: accept a start_date parameter and apply a COL_TIME_ON >= start_date 00:00:00 WHERE clause when provided. View: add a date input to the form and display the selected start date in the no-matches message to indicate the time scope.
Enforce presence of organization_label for uploaded call history files and bulk SIG updates. Controller changes: on upload, remove the stored temp file and show a notice if organization_label is empty; when applying scans, expect a posted file_id, load the file and reject bulk updates if the file lacks an organization_label; simplified a proposal-empty check to only consider proposed_sig. View changes: update introductory text to remove 'optional', mark the organization label input as required, and include a hidden file_id field in the preview/apply form so the controller can validate the selected file.
Adds a workflow to scan uploaded call history files for matching QSOs and apply SIG/SIG_INFO backfills. Controller Callhistory: loads logbooks, adds scan_preview and scan_apply actions, CSV parsing helpers, and safety checks to ensure files are readable and station ownership matches. Model Callhistory_model: adds get_station_ids_for_logbook, get_qsos_for_callsigns, and apply_sig_backfill to fetch candidate QSOs within a user's station/logbook scope and apply updates in a DB transaction. View callhistory/index.php: introduces a preview UI with selection controls, logbook scope selector, and client-side JS to manage selections and submission. Changes include normalization/heuristics for extracting callsigns/exchanges and only propose updates where existing SIG fields are blank.
Add normalization helpers and update callhistory renderers to avoid showing redundant membership numbers or names when they are contained in or equal to other fields. Introduces normalizeCallhistoryText and qsoCallhistoryNormalizeText, converts values to strings, trims and lowercases them for comparisons, and only appends #exch1 or name when they would be distinct from the organization label or each other. Applies the change to assets/js/sections/contesting.js and assets/js/sections/qso.js to clean up callhistory display logic.
Enhance Callhistory CSV parsing to handle comment/BOM lines and ambiguous columns. Removed the unused line_number logic and added is_comment_row to skip comment rows (handles leading BOM and '#' markers). Introduced extract_name to better detect operator/name when columns are swapped, and expanded guess_exch1_without_header to check the first few columns for exchange/name patterns. Added helper heuristics looks_like_name_value and looks_like_exchange_value to distinguish names from exchange values for more reliable extraction.