# Changelog
All notable changes to **Radio** are documented here.
Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versioning: [SemVer](https://semver.org/).
---
## [Unreleased]
---
## [0.7.4] — 2026-05-30 — WordPress.org submission cleanup (updater out, LICENSE in)
Walks back the Update URI guard pattern from v0.7.3 and replaces it with the simpler, actually-correct answer: just remove the custom updater entirely. The WordPress.org Plugin Check tool (PCP) raised `plugin_updater_detected` on the v0.7.3 build with the message *"Including An Update Checker / Changing Updates functionality. Plugin Updater detected. Use of the Update URI header is not allowed in plugins hosted on WordPress.org."* PCP scans the source as-shipped, not as-distributed, so the build-time `sed` strip we relied on in v0.7.3 never had a chance to run before the scan — and the scanner flagged both the header and the `inc/updater.php` file itself. Two options remained: (a) keep dancing around PCP with branches and build scripts, or (b) accept that WordPress.org is the canonical update channel once a plugin is hosted there and drop the custom system. We went with (b).
This release also closes the per-file GPL declaration loop by adding a top-level LICENSE file with the full GPL v2 text and explicit GPL header blocks to the CSS and JS assets — every shipped file now declares its license unambiguously.
### Removed
- **`inc/updater.php`** — the self-hosted Gitea updater. Deleted from the working tree (still recoverable from git history at tag `v0.7.3` if ever needed for a non-wp.org distribution). With the file gone, PCP's `plugin_updater_detected` check is silent.
- **`Update URI:` header** in `radio.php`. PCP forbids the header in wp.org-hosted plugins, even when its value would legitimately defer updates to wp.org. (The v0.7.3 NOTE block in the file header documenting the strip-on-package pattern was removed along with it.)
- **`require_once RADIO_PATH . 'inc/updater.php';`** line in `radio.php`. The file no longer exists, so no require is needed.
- **Updates panel render block + `function_exists()` guard** in `inc/settings.php`. The panel function no longer exists in any build, so the call site is gone. Settings page now ends cleanly at the *Save Changes* button.
- **"Self-hosted update checker against the Gitea repo"** bullet in the top-level `README.md`. Replaced with *"Updates via WordPress.org (the canonical channel once published)"*.
- **"Self-hosted updater" bullet** in the `readme.txt` Privacy section. Replaced with a positive statement that updates come from WordPress.org through the normal core update process.
### Added
- **`LICENSE` file** at the plugin root — verbatim canonical GPL v2 text from `https://www.gnu.org/licenses/gpl-2.0.txt`, 338 lines. The plugin header has always declared GPL v2+ but wp.org reviewers like to see the full text shipped alongside.
- **GPL header block in `assets/css/radio.css`** (Copyright, GPL v2+ grant, pointer to LICENSE) so the CSS file's licensing is explicit at the file level, not inferred from the plugin header.
- **GPL header block in `assets/js/radio.js`** — same treatment as the CSS. The original `/** Radio — vanilla JS audio controller. */` opening is preserved verbatim as the "module overview" section below the license header.
- **GPL header block in `radio.php`'s docblock** — Copyright line + GPL v2+ grant + LICENSE pointer added below the `@package` tag. Sits alongside the existing `License:` / `License URI:` header fields rather than replacing them.
### Changed
- **`readme.txt` Stable tag** → `0.7.4`.
- **`inc/about.php` Version history** — v0.7.4 rotated into the "latest" slot; v0.7.3 demoted to a one-liner in the earlier-releases list with a note explaining the walkback.
- **`RADIO_GITEA_URL` constant** is kept in `radio.php`. It's no longer used by an updater, but `inc/about.php` still uses it to render the *"View the full CHANGELOG.md →"* outbound link on the About page. Linking to a self-hosted source repo as a documentation reference is not the same as running a custom updater — that's fine in wp.org-hosted plugins. ([WordPress.org allows links to external source repos](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/) — the prohibition is on the update *mechanism*, not on referring to where the source lives.)
### Migration
- **Existing wp.org installs** (post-acceptance): WordPress core handles updates automatically. Nothing to do.
- **Existing Gitea-installed copies of v0.7.3 or earlier** (e.g. M5): the `inc/updater.php` file on disk will become orphaned after this update lands on them — it's no longer required from `radio.php`, so it just sits there as a dormant file until the next reinstall. **Recommended path for those installs:** once a-radio is accepted onto WordPress.org, uninstall the Gitea-sourced copy and reinstall from WordPress.org. Future updates then flow through wp.org. Until acceptance, those installs simply stop receiving updates (the v0.7.3 updater code is gone in v0.7.4, so there's nothing to advertise newer versions back to them).
- **No data loss** in any case: station + volume + theme + history + favourites all live in `user_meta` and survive uninstall/reinstall cycles.
### Why this is the right answer (and v0.7.3 was clever but wrong)
v0.7.3's Update URI guard pattern reasoned correctly about WordPress core's behaviour — the `Update URI` header genuinely does opt a plugin out of wp.org's update channel, and the runtime guard genuinely would short-circuit the updater on stripped builds. But the whole scheme assumed the reviewer would see the *stripped* build. The Plugin Check tool runs against the *source* artifact — the same source that's in the public repository, that future contributors clone, that automated audits scan. From PCP's point of view, the plugin was "shipping" an updater regardless of what the build script did with it later. The simpler answer (just delete the updater for the wp.org branch) sidesteps that entire class of "the static scanner doesn't know about your build pipeline" problem.
---
## [0.7.3] — 2026-05-30 — WordPress.org guideline 8 compliance + Privacy section
WordPress.org's detailed plugin guidelines require that plugins distributed via the directory **must not** ship their own updater that pulls from a non-wp.org server (guideline 8). RangerHQ Radio's self-hosted Gitea updater predates the wp.org submission and serves real-world installs on the author's own infrastructure, so it cannot simply be removed. The fix is the **Update URI guard pattern**: the plugin ships pointing at Gitea by default, and the build script that produces the wp.org submission zip strips the `Update URI:` header line. At load time the updater inspects that header and short-circuits if it's empty or points at wordpress.org.
Net effect: one source tree, two build artifacts, both compliant.
### Added
- **`Update URI:` header in `radio.php`** pointing at `https://git.davidtkeane.com/ranger/a-radio`. WordPress core uses this header to decide whether wp.org should poll this plugin for updates — a non-wordpress.org URI opts the plugin out of wp.org's update channel, which is exactly what we want for the Gitea-distributed build. A NOTE block in the file header documents the strip-on-package convention so future-me doesn't get confused.
- **`radio_should_skip_custom_updater()` helper** at the top of `inc/updater.php`. Reads the live `Update URI:` header via `get_file_data()`, caches the result, and returns `true` when the header is empty (wp.org default) or contains `wordpress.org`. Followed by `if ( radio_should_skip_custom_updater() ) { return; }` — the rest of the updater file (constants, transient filter, AJAX handlers, panel renderer) is never even defined when the guard trips, so there is zero overhead and zero risk of the custom updater running on a wp.org install.
- **`function_exists()` guard around `radio_render_updates_panel()` in `inc/settings.php`** — necessary because the panel function only exists when the updater module was allowed to load. With the guard, the Settings page silently omits the panel on wp.org installs and renders it normally on Gitea installs.
- **Dedicated `== Privacy ==` section in `readme.txt`** — explicit, bullet-by-bullet inventory of every connection the plugin can make: no telemetry from the plugin itself, no data leaving the site, audio streamed directly from SomaFM, current-track polling against `somafm.com/songs/{station}.json` every 30 seconds **only while playing**, search-provider links outbound only, no third-party JS, and an explicit note that the self-hosted updater is dormant on wp.org installs. Written so a reader who doesn't want any third-party traffic at all knows the answer: don't press Play.
- **SomaFM Terms of Use link** (`https://somafm.com/legal/`) prominently in the readme's third-party-service note. Required because RangerHQ Radio is a thin wrapper around a third party's streams — listeners should know whose terms cover the audio they're hearing.
### Changed
- **`readme.txt` Stable tag** → `0.7.3`.
- **`readme.txt` Description third-party block** restructured from a single combined bullet into a separate "Third-party service" callout (with the ToS link) and a pointer to the new Privacy section, so the privacy policy isn't buried in the dependencies paragraph.
- **`inc/about.php` Version history** — v0.7.3 rotated into the "latest" slot (full description); v0.7.2 demoted to a one-liner in the earlier-releases list.
### Why
WordPress.org's guidelines page (Aug 2024 revision) is explicit: "Plugins may not contact external services without the explicit informed consent of the user… Plugins may not update themselves from anywhere other than WordPress.org once they're hosted there." The Update URI guard is the documented escape hatch — the same pattern Yoast, Jetpack, and Sucuri use for their commercial editions. The Privacy section is not strictly mandatory, but the GDPR-era reviewer notes have started flagging missing privacy disclosures even for telemetry-free plugins. Easier to ship with one than to play the comments game after submission.
### Migration
None. Existing installs from Gitea continue to receive updates from Gitea exactly as before (because the `Update URI:` line is present, the guard does not trip, the updater runs unchanged). The wp.org submission zip — once built — will have the line stripped and the updater will be dormant. No user-visible behaviour change in either distribution.
---
## [0.7.2] — 2026-05-30 — Screenshots + correct wp.org contributor handle
Two small but real submission-prep moves:
### Added
- **Five screenshots** at the plugin root, named per WordPress.org convention (`screenshot-1.png` through `screenshot-5.png`). Sizes 1087–1422 wide. Order matches the readme:
1. Dashboard widget
2. Settings page
3. History page (star + four search-provider links)
4. Pop-out mini-player window
5. About page
### Changed
- **`Contributors:`** in `readme.txt` updated from `davidtkeane` (placeholder) to **`ir240474`** — actual wp.org username confirmed (profile: ).
- **`Stable tag:`** bumped to `0.7.2`.
- **Screenshots section** in `readme.txt` rewritten to match the five actual screenshots provided.
### Notes
- Source images came from an `images/` working folder (David's working location); they've been moved/renamed to plugin root for the wp.org screenshot convention, and the working folder removed.
- After this release the only PCP residue is the `.gitignore` hidden-file warning, which is unavoidable on the dev install and won't be in the submission zip.
**Files changed:** `radio.php` (version), `readme.txt` (Contributors, Stable Tag, Screenshots section, new 0.7.2 changelog + upgrade notice), `CHANGELOG.md`, `inc/about.php` (rotate v0.7.2 → latest), **5 new files** `screenshot-1.png` through `screenshot-5.png` at plugin root, `images/` directory removed.
---
## [0.7.1] — 2026-05-30 — Plugin Check follow-up: tested-up-to + .DS_Store re-cleanup
A re-run of PCP after v0.7.0 dropped the issue count from 169 → 4. This release closes the only real one of the four:
### Fixed
- **`Tested up to` bumped: 6.7 → 7.0** in `readme.txt` (PCP `outdated_tested_upto_header`). WordPress 7.0 is current; the previous header would have been flagged as out-of-date at submission review.
- **Stable Tag bumped to 0.7.1** in `readme.txt`.
### Removed (again)
- `.DS_Store` files (root + `assets/`). macOS Finder regenerated them between PCP runs; they will not be present in the actual submission zip. `.gitignore` already covers them.
### Known PCP residue (not addressable in source)
- The `.gitignore` file itself triggers a `hidden_files` WARNING (sev 8) on PCP because it's a dot-file. Keeping `.gitignore` is needed for git; it will be excluded from the submission zip. Acceptable per WP.org packaging conventions.
**Files changed:** `radio.php` (version), `readme.txt` (Tested-up-to + Stable Tag + new 0.7.1 changelog + upgrade notice), `CHANGELOG.md`, `.DS_Store` files deleted.
---
## [0.7.0] — 2026-05-30 — WordPress.org submission prep (full Plugin Check clean)
Ran the official WordPress.org **Plugin Check (PCP)** against v0.6.3 — surfaced **169 issues**. This release closes all of them so the plugin is submission-ready for the WordPress.org plugin directory.
### Changed — branding
- **Plugin Name renamed: "Radio" → "RangerHQ Radio"** to (a) remove the trademarked term "SomaFM" from the plugin name surface (PCP `trademarked_term` warning) and (b) line up with the RangerHQ plugin family. SomaFM is still credited in the Description and on the About page as the data source. Plugin folder/slug stays `a-radio` — no install path changes; existing user_meta keys (`radio_state` / `radio_history` / `radio_favourites`) untouched.
- **`Text Domain` header renamed**: `radio` → `a-radio` (matched the slug for the first time; PCP `textdomain_mismatch`).
- **`Requires at least` bumped**: `5.0` → `5.3` (matches `wp_date()` usage in `inc/history.php`; PCP `wp_function_not_compatible_with_requires_wp`).
- **File docstring header** dropped "SomaFM" from the prominent first line.
### Changed — code (mass-mechanical)
- **134 i18n call sites** rewritten from `'radio'` text domain → `'a-radio'` across `radio.php`, `inc/admin-page.php`, `inc/dashboard-widget.php`, `inc/settings.php`, `inc/about.php`, `inc/history.php`, `inc/updater.php`. Single sed pass on the unique pattern `, 'radio' )` (the 6 menu-slug `'radio'` references in `add_menu_page` / `add_submenu_page` were left alone — they're the URL slug, not the text domain).
### Fixed — security
- **8 × `MissingUnslash` + 8 × `InputNotSanitized`** in the v0.5.0 history endpoints (`radio_ajax_log_track`, `radio_ajax_toggle_favourite`). All four `$_POST['artist|title|station|station_id']` access points are now wrapped `sanitize_text_field( wp_unslash( $_POST['…'] ) )` (or `sanitize_key()` for `station_id`) at the access point. The downstream `radio_sanitize_entry()` helper still re-sanitizes as belt+braces.
### Added — translator comments
- 6 × `printf` / `sprintf` calls with placeholders now carry `/* translators: ... */` comments: pop-out window title, updater status messages (HTTP error / available / up-to-date / download), history page "%s ago" relative time.
### Refactored — pop-out window
- Inline `` stylesheets, inline `