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.
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.
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.
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.
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.
Introduce a new Call History feature: adds Callhistory controller, Callhistory_model, migration (create callhistory_files table) and view for uploading/managing call history files. Implements CSV/TXT upload handling, storage per-user, activation/priority controls, checksum, and server-side lookup that scans active files for matching callsigns. Integrates UI: header menu link, contesting and QSO views show call history results inline, and JavaScript to perform lookups and render results with copy-to-SIG functionality. Also updates .gitignore to exclude uploaded callhistory directory and bumps migration_version to 261.
Add support for comma-separated POTA references across the app: bump migration version to 259 and add migration 260 to expand COL_MY_POTA_REF and station_pota to VARCHAR(255). Introduce Pota::split_refs and collect_refs_from_rows to normalize and aggregate multiple refs; refactor Pota counts to compute unique refs per band/mode. Normalize station_pota when saving in Stations and Logbook_model (uses normalize_pota_refs), and adjust POTA search to match refs inside comma-separated values. Update views to render multiple POTA links and hashtags, and add Selectize-based autocomplete for station POTA input on create/edit forms. Also minor change to user_owns_station query (removed user_id where clause).
Add interactive QSO map support for public station diary entries. Controller: add stationdiary::get_qso_map_data() to return JSON map data for a given entry/date range/logbook with station location. Models: Note::get_public_entry_user_id() to resolve the entry owner; Logbook_model: get_qsos_for_public_map() to query QSOs for the map, set a default stn_loc fallback, and skip markers with invalid 0,0 coordinates. View: public_index.php includes Leaflet CSS/JS, a Show/Hide QSO Map toggle, map container per entry, client-side map initialization and marker/popups loaded via POST to the new endpoint. These changes ensure public diary entries can display QSO locations and the station marker while avoiding bogus markers and limiting results to the correct user/logbook context.
Update image processing for uploads to improve optimization: set quality to 70%, change master_dim to auto, and target a max dimension of 1080x1080. Add logic to only resize when an image exceeds 1080px on any side while still reprocessing (calling resize) for compression even when dimensions are within limits. Minor formatting/cleanup of image library initialization.
Introduce a reactions feature for public station diary entries. Adds a migration to create station_diary_reactions with indexes and a unique (diary_id, visitor_hash) constraint; bumps migration version to 257 and registers a new route for /entry/:id/react. Controller: implements visitor token/hash creation, a react() endpoint to accept POSTed reactions (like, love, fire), increments/updates reactions, invalidates cache and returns totals and visitor reaction. Model: new methods to get totals, fetch visitor reaction and save/update reactions. View: UI, styles and client-side JS for reaction buttons, counts, and copy/share enhancements; integrates totals and visitor state into the single-entry view.
Add handling for public QSO datetime formatting and single-entry sharing. Introduce get_public_qso_datetime_format() in Stationdiary controller (defaults and appends time if missing) and pass qso_datetime_format, is_single_entry and current_entry_permalink to the public view. Update Note model to select and return user_date_format from auth table so user preferences flow through. Update public_index view to use the public QSO datetime format for QSO rows and add social share buttons (Facebook, X, Bluesky) when viewing a single entry.
Add support for viewing individual public Station Diary entries. Adds a new route for /station-diary/:callsign/entry/:id, a Stationdiary::entry controller method with caching (per-call sign + entry + cache version, 86400s TTL), and a Note model method get_public_station_diary_entry() that loads images and optional QSO summaries/lists. Update public_index view to link entry titles to their permalink and include entry IDs in article IDs, and update RSS feed to use the entry permalink/guid. Also include small CSS tweaks for entry title links.
Load and render station diary images inline and separately, and make cache handling more robust. Notes controller: load diary_images for print view and invalidate public diary cache when an image caption is updated. Stationdiary controller: include a render-version token in the public diary cache key to force re-renders when markup changes. Note model: make touch_public_diary_cache_version more resilient (unique value, fallback to file_put_contents and cache clean), add invalidate_public_diary_cache_for_note helper, and enhance process_image_shortcodes to handle encoded brackets, tolerant shortcode spacing, modifiers (alignment and size) with deterministic styling, and return used image ids. Views: update station_diary_print and note view to process shortcodes, remove empty paragraph artifacts, show inline images via shortcodes, and display remaining images with captions and improved print CSS.
Support inline diary images via shortcodes and improve notes UI. Introduces a Note::process_image_shortcodes() method (ID/caption lookup and modifiers like left/right/center/small/medium/large), and updates station diary rendering to process shortcodes and avoid duplicate image output. Adds an AJAX endpoint notes/update_image_caption with ownership checks to save image captions, plus client-side JS to edit/save captions from the note edit view. Refactors notes add/edit views: reorganized form into sections/accordion, Quill editor min-height, image upload tips, improved buttons and layout, and QSO summary layout tweaks.
Introduce Public Station Diary feature and QSO filtering support.
- Add migrations: 255 adds public_station_diary_enabled option (default enabled), 256 adds qso_date_start, qso_date_end, qso_satellite_only columns to notes.
- Bump migration version to 256.
- Add controller actions (Options::public_diary, public_diary_save) and new options view to manage the public diary setting; add sidebar link.
- Extend Note model to persist QSO filter fields and provide new methods to fetch QSO lists/summaries for date ranges and satellite-only filtering; wire filter usage into summary generation.
- Update notes add/edit views to include QSO Summary Filters (date range + satellite-only).
- Add translations for the new options keys across multiple language files.
- Update public station diary listing view styling/path and minor content cleanup; other views updated to support feature.
- Remove obsolete cache files (.htaccess, index.html).
These changes enable admins to toggle a public station diary and let note authors control which QSOs are included in summaries via date ranges and SAT-only filtering.
Controller: pass a 'feed_url' value into the RSS view (site_url for the feed). RSS view: compute the current request URL (respecting HTTPS) and use it for the atom:link self href. Public view: load Google Fonts (Merriweather & Lora), add preconnect hints, switch typography to the new fonts, increase line-height and adjust font-weights/sizes for headings and body to improve readability.
Introduce public Station Diary functionality: add global config flag, routes, controller, model changes, views and DB migrations. New migrations (252-254) add is_public/include_qso_summary, diary_images table and logbook_id on notes. Notes model updated to handle public flags, diary images, qso summaries, caching (per-user cache version), and helper methods for listing/counting public diary entries. New Stationdiary controller serves paginated public pages and RSS feed with caching. Notes controller updated to handle image uploads, attach images to diary entries, and provide an AJAX endpoint to delete images. Views updated to support public visibility options, logbook selector, image upload UI and Quill editor assets; routes and migration version bumped accordingly. Includes file upload example added to uploads/diary.
Enable HTMX-driven certificate uploads and partial UI updates for LoTW. Updated Lotw controller to detect HTMX requests in do_cert_upload, return inline success/error alerts for modal uploads, and added cert_table_refresh endpoint to return the cert table partial. Extracted the certificate table into a new view (lotw_views/cert_table.php) and replaced the inline table in index.php with a container that loads that partial. Added a Bootstrap modal view (upload_cert_modal.php) with an HTMX multipart form, drag-and-drop file UI, JS handlers to manage upload state and refresh the certificate list, and modal reset behavior. Minor refactors to flash/success handling to support both full-page and HTMX flows.
Initialize and increment a $found_count while parsing ADIF records when import_check returns 'Found', then after batch update compute already-confirmed QSOs and expand the LoTW status message to include: Found, Updated, Already confirmed, and Gridsquares updated. Uses max(0, ...) to avoid negative already-confirmed values and updates the log output accordingly.
Append the LoTW status div to the $table variable before assigning it to $data['lotw_table'], rather than concatenating it afterward. This ensures the status is part of the table HTML output and avoids the separate post-assignment append.
Remove the per-row "LoTW Status" header and cells from the Lotw table output to avoid repeating the same status for each QSO. Instead, append a single "LoTW Status" summary div after the table using $lotw_status. This simplifies the table layout and centralizes the LoTW status display.
Allow LoTW batch updates to reference QSOs by COL_PRIMARY_KEY for reliable matching. Controller: include 'primary_key' in batch update payloads. Model: collect numeric primary keys, expand WHERE to include COL_PRIMARY_KEY IN (...) alongside the existing datetime/call/band/station match, add early exit/log when no usable keys provided, and build an id-based lookup (qso_map_by_id) to prefer primary-key matches while remaining backward-compatible with the composite key lookup. This reduces ambiguous matches and improves update accuracy.
Add a Station Diary print feature: controller Notes.php now sets a has_diary_entries flag for the main notes view and adds a station_diary() action that loads Station Diary entries and renders a print-friendly view. The notes main view conditionally shows a "Station Diary" button that opens the print page in a new tab. A new view notes/station_diary_print.php provides a print-optimized layout, entry listing with date/time handling, total entries summary, a print button, and a friendly fallback when no entries are found.
Introduce a Station Diary UI and backend endpoint to allow quick note creation via HTMX. Added Notes::quick_add in the controller with form validation, note creation, success/error responses and a small JS form reset. Updated view_log/index to show a Station Diary button (when user_show_notes=1) and include the modal form that posts to notes/quick_add and renders messages into the modal.
Add per-user/logbook file caching for dashboard statistics (15min TTL) and update Dashboard controller to use cached qso, countries, VUCC and QSL queries. Introduce clear_dashboard_cache() in Logbook_model and call it on QSO create/update/delete to invalidate affected users' cache entries. Add migration (249) to create composite indexes (idx_dxcc_stats, idx_vucc_gridsquare, idx_vucc_grids) to improve dashboard query performance, and bump migration_version to 249. Also include generated cache files under application/cache for current stats.
Add batch processing for eQSL imports/exports and introduce performance indexes. Bump migration_version to 248 and add Migration_add_eqsl_performance_indexes to create idx_eqsl_qslrdate and idx_eqsl_confirmation_match indexes to speed up eQSL queries and batch matching. Modify Eqsl controller to collect successful uploads and perform a single batch mark-as-sent update. Update EqslImporter to queue confirmations and run a single batch update at the end. Implement eqsl_update_batch() and eqsl_mark_sent_batch() in Eqslmethods_model to perform efficient batch DB operations and logging, reducing per-record queries for large eQSL operations.
Bump migration version to 247 and add a migration that creates performance indexes for LoTW workflows (idx_lotw_qslrdate, idx_lotw_qsos_to_upload, idx_lotw_confirmation_match) to speed common queries. Refactor Lotw controller to collect records in memory, build rows for rendering, and perform a single batch confirmation update via a new Logbook_model::lotw_update_batch() method instead of many individual updates. Implement lotw_update_batch() to look up matching QSOs, perform update_batch() calls, and separately update gridsquares/distances using the Qra library. Simplify LotwCert::toggle_archive_certificate() to toggle archived state with a single UPDATE and return the resulting status.
Improve query performance and memory usage by adding a migration to create several indexes and converting various upload/reporting flows to batched processing. Changes include:
- Add migration 245 to create indexes: idx_mode, idx_sat_name, idx_continent, idx_qsl_stats (composite), idx_hrdlog_upload, idx_qrz_upload to speed common queries.
- Bump migration version to 245 in config.
- Hrdlog and Qrz controllers: switch to fetching QSOs in LIMIT/OFFSET batches, process updates in chunks, add logging for batch progress, and use break 2 to stop outer loops on invalid API keys to avoid continuing work.
- Logbook_model:
- get_hrdlog_qsos and get_qrz_qsos now accept limit/offset and use ORDER BY/LIMIT/OFFSET for batching.
- Replaced a subquery with a JOIN for grid-based filtering to improve performance.
- get_modes rewritten to use query builder (distinct/select/order_by).
- Bulk DXCC updates use update_batch instead of per-row queries.
- calls_without_station_id limited to 500 results to avoid UI hangs.
Overall goal: prevent memory exhaustion with large datasets and improve database query performance.
Enhanced the WWFF awards page with band, mode, and QSL type filters, quick preset buttons, and a summary section showing worked/confirmed parks and milestone progress. Updated the controller to handle filter input and summary data, and extended the model with methods for filtered queries and statistics.
Introduced a new controller method sigexportcsv() to export SIG QSO data as a CSV file. Added a corresponding button in the SIG QSO list view to allow users to download the CSV export.
Introduces filtering by band, mode, and confirmation status to the SIG awards section in Awards.php, including a filter summary helper. Updates all supported language files to add translations for the new SIG award filters, table headers, and related UI elements.
Introduces band, mode, and confirmed-only filters to the Worked All Britain (WAB) award view and details AJAX endpoint. Updates the controller, model, and view logic to support filtering, and adds new localized strings for WAB award UI elements in multiple languages. Also ensures the correct script is loaded for the WAB section and improves the Bands model to check for field existence before filtering by award.
Introduces logic in the Debug controller to detect if the application is running inside a Docker container and displays the result on the debug view. This helps developers quickly identify the runtime environment for troubleshooting and configuration purposes.
The backup controller now checks if the PHP ZIP extension is installed and passes this information to the view. The backup page displays a warning if the extension is missing, guiding users to install it for backup and restore functionality.
Refactored the backup export process to stream JSON data directly to a file handle, significantly reducing memory usage for large datasets. QSOs and logbooks are now written in chunks, and memory is freed periodically. Increased max execution time and adjusted memory limit to optimize for streaming. This improves reliability and scalability for users with large logbooks.
Introduces a new section in the maintenance page allowing manual updates of reference data files (Clublog SCP, LoTW Users, DOK, SOTA, WWFF, POTA) via AJAX buttons with loading indicators. Adds corresponding UI-friendly controller methods with authorization checks and improves the WWFF update logic to process CSV files line by line for better memory efficiency.
Replaces the old SOTA model with a new Sota_model, adds CSV-based summit metadata caching, and introduces new controller endpoints and view components for SOTA table, stats, and map fragments. The SOTA award page now supports filtering, improved statistics, and interactive mapping using summit coordinates. Updates .gitignore to include the new SOTA CSV, and refactors SOTA data refresh logic for reliability and maintainability.
Introduces a new POTA awards dashboard with HTMX-powered filtering, statistics, progress bars, and a Leaflet map of worked parks. Adds new controller endpoints, model methods for filtered queries and park metadata, and modular view components for table, stats, progress, and map. Updates language files for new stats labels and enhances the update process to cache the full POTA parks CSV for richer features.
Introduces a migration to add a created_at timestamp to the notes table. Updates the Note model and Notes controller to support filtering notes by creation date, and enhances the notes view to include date filter fields and display the created date for each note. Migration version is incremented to 242.
Introduces note categories with support for filtering, assigning, and managing categories. Adds category selection and creation to add/edit forms, category filtering to the notes list, and modals for deleting/merging categories and confirming note deletion. Updates controller and model logic to handle categories, and improves UI for notes management.