=== Blue Billywig ===
Contributors: BlueBillywig
Tags: bluebillywig, blue billywig, video, billywig
Requires at least: 4.7
Tested up to: 6.9
Stable tag: 2.0.2
Requires PHP: 8.1.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

== Description ==

The Blue Billywig plugin enhances Blue Billywig's capabilities by integrating with WordPress. This plugin allows users to seamlessly upload media directly to their Blue Billywig publication.

**Documentation:** See USER-GUIDE.md in the plugin directory for a complete walkthrough of all features.

== Upgrade Notice ==

= 2.0.2 =
Fixes the dashboard video analytics widget: it read SAPI responses at the wrong path and quietly rendered the "no data" empty state even on publications with real traffic. After upgrading, the widget now shows Views / Player Loads / Play Rate, Top 5 Videos, and the device breakdown. Also adds a one-click admin notice when the plugin is installed but has no API credentials yet, and a red error panel inside the widget when a SAPI call fails (instead of the misleading empty state).

= 2.0.1 =
This is the first 2.x release shipped via wordpress.org (2.0.0 was distributed via GitHub only). Upgrading from 1.x is a MAJOR update with breaking changes: requires PHP 8.1+, replaces the upload flow with direct browser-to-storage uploads, removes the blue_billywig_progress_list filter, and REST endpoints now require authentication. Read the 2.0 migration guide in USER-GUIDE.md before updating in production. 2.0.1 itself adds a fail-closed fix to the content-gate recursion cap, observability for gate-error and JWT-pepper persistence failures, a Solr-fq validation parity fix on a library AJAX endpoint, and regression tests.

= 2.0.0 =
Major update with breaking changes. Direct browser-to-storage uploads via Uppy (no more PHP upload limits), Gutenberg blocks with SEO, oEmbed support, content protection, video analytics dashboard, folder browsing, and more. Requires PHP 8.1+. The blue_billywig_progress_list filter has been removed — see release notes if you used it in custom code.

== Changelog ==

= 2.0.2 =
* Fix: Dashboard video analytics widget was reading SAPI responses at the wrong path (`['count']` / `['facets']` instead of `['body']['total']` / `['body']['facets']`, and facet entries use `'value'`, not `'val'`). `$total_views` and `$total_inits` always stayed 0, so the widget always rendered its "No video views recorded" empty state — even on publications with plenty of traffic. After upgrade the widget shows real data.
* UX: New `admin_notices` banner warns administrators when the plugin is installed but has no API credentials yet, with a one-click link to the settings page. Gated on `manage_options`, suppressed on the settings page itself, auto-clears as soon as credentials are saved.
* UX: When a SAPI analytics call fails the widget now renders a red error panel naming the failed sections and writes a structured `error_log` entry, instead of silently falling back to the empty-state copy. Makes "API outage" visually distinct from "publication has no traffic".
* Observability: On cache skip (at least one of the four analytics calls errored) the plugin now writes a single summary `error_log` line listing which section(s) failed, so transient SAPI outages surface in `debug.log` without waiting for a dashboard render.
* Performance: Dashboard widget transient TTL reduced from 15 to 5 minutes for slightly fresher stats; cache still busts immediately on credential changes via `SapiClient::clearCache()`, and errored responses are still never cached.
* Tests: +34 assertions in `AnalyzeWidgetDataTest` locking the SAPI envelope contract against the exact response-shape drift that caused the 2.0.1 regression.

= 2.0.1 =
* Security: ContentGate::injectJwt now fails closed at the depth cap (previously returned un-gated content, so a crafted nested-base64 payload could bypass the gate).
* Security: Library AJAX endpoint now validates `bb_folder` and `bb-sourcetype` against the same regex the REST `/search_media_clips` route uses (closes a Solr-fq injection surface the REST path had already closed).
* Hardening: `bulk_action` now verifies the CSRF nonce before the capability check (defence in depth).
* Observability: Content gate logs each distinct failure reason (SAPI disconnected / SAPI error / 404 / missing shared secret) and records a rolling one-hour tally. A new admin-notices banner surfaces the tally so operators can tell "outage" from "misconfig" instead of all paths rendering the same viewer message.
* Observability: JWT pepper persistence failure now triggers a persistent `notice-error` admin banner (`manage_options`-gated); previously every gated video silently failed verification with no signal.
* UX: Library AJAX endpoints (`load_library_videos`, `load_library_playlists`) surface the SAPI `Error` message via `wp_send_json_error` 502 instead of the prior generic 200 + "No data". Auth failures are now distinguishable from legitimately-empty libraries.
* Compat: Replaced `array_filter(..., 'strlen')` in `CtaTranslator` with a closure (PHP 8.1 deprecation, removed in 9).
* Docs: Corrected inaccurate comments in `CtaTranslator` (widget types) and subtitle upload (single-POST behaviour).
* Tests: +10 assertions via `ContentGateFailsClosedTest`, `UninstallAllowlistTest`, `SapiClientInvalidateClipCppCacheTest`; updated `ContentGateInjectJwtTest::test_depth_cap_fails_closed` to lock the fail-closed invariant.

= 2.0.0 =
* New: Direct browser-to-storage upload via Uppy (no PHP upload limits)
* New: Gutenberg blocks with server-side rendering and JSON-LD SEO
* New: Channel embed Gutenberg block with channel selector
* New: oEmbed — paste BB video URLs directly to embed
* New: Video XML sitemap integration
* New: AI transcript display below videos (SEO-indexed)
* New: Video chapters with clickable timestamps (opt-in)
* New: Interactive video overlay support
* New: Folder tree browsing in library and media picker
* New: Video analytics dashboard widget
* New: Inline metadata editing in Media Library
* New: JWT-based content protection via WordPress roles
* New: Test Connection button on settings page
* Security: All AJAX handlers verify capabilities + nonces
* Security: Output escaping throughout, query injection fixes
* Performance: SAPI client singleton with response caching
* Compat: Requires PHP 8.1+, tested up to WordPress 6.9
* Breaking: blue_billywig_progress_list filter removed; use SapiClient class

= 1.0.15 =
* Previous release.

== Third-Party Service Integration ==

This plugin relies on the Blue Billywig Online Video Platform to provide video uploading capabilities. By using this plugin, you acknowledge and agree to the following:

- The plugin sends data to [bbvms.com](https://bbvms.com/) and [bluebillywig.com](https://www.bluebillywig.com/) for video management tasks.
- Your usage of Blue Billywig is subject to their [Privacy Policy](https://www.bluebillywig.com/privacy-statement/).

== Privacy & GDPR ==

**What data is transmitted to Blue Billywig from site visitors?**

Every page that embeds a Blue Billywig video loads the player from `{publication}.bbvms.com`. When the script runs, the visitor's IP address, User-Agent, Referer and page URL are transmitted to Blue Billywig so the player can fetch the clip, apply geo/language rules, and record analytics.

**Consent integration (GDPR / ePrivacy / TTDSG).**

The plugin exposes a WordPress filter `bluebillywig_consent_granted` so consent-management plugins (Complianz, Cookiebot, Iubenda, Osano, …) can defer loading the player until the visitor opts in:

`add_filter( 'bluebillywig_consent_granted', function() { return your_cmp_has_video_consent(); } );`

When this filter returns `false`, BB embed blocks, channel blocks, Shorts blocks, shortcodes, and oEmbed all render a neutral placeholder explaining that loading will transmit data to bbvms.com. No IP/UA is shipped to Blue Billywig until consent is granted (or until the visitor reloads after accepting).

**Cookies and local storage.**

- The plugin itself does not set any cookies or write to local storage from admin pages.
- On the frontend, the CTA email-capture overlay (when enabled by the editor) writes `bbtl_{clipId}` to the visitor's `localStorage` after a successful submit, so the form does not re-ask an engaged visitor. This key only carries the submitted email and a timestamp.
- The Blue Billywig player itself may set first-party cookies on the `.bbvms.com` domain for playback/analytics. See Blue Billywig's privacy statement for details.

**CTA email capture — consent.**

The CTA editor can optionally include an email-capture form. The plugin requires an explicit consent checkbox on the form and rejects non-`https` submit endpoints. Captured emails are sent to the customer-configured endpoint (if any); Blue Billywig's own backend does not receive or store these emails.

**Content protection JWT.**

When you enable JWT-based content protection and a logged-in WordPress user requests a protected clip, a short-lived token (≤ 7200s, clamped) is minted. The user-ID claim in the token is an HMAC of the WordPress user ID (never the raw ID) using `wp_salt('auth')`. Pages carrying a JWT emit `Cache-Control: private, no-store` to keep shared caches from serving one user's token to another.

**Admin-side third-party code.**

On plugin admin pages, the Uppy uploader library is bundled locally (`admin/assets/vendor/uppy/`) — no request to `releases.transloadit.com` on every admin page load.

**Uninstall.**

Uninstalling the plugin deletes every `blue-billywig-*` option (including stored API keys and the content-protection shared secret) and every `bb_*` transient. A reinstall starts from a clean slate.

== Translators ==

The plugin ships with starter translations for German, French, Swedish, Danish, Norwegian Bokmål, Finnish, and Dutch (in `/languages/`). They were generated automatically and cover every string, but they have not been reviewed by native speakers.

**Please help us improve them.** Once the plugin is listed on wordpress.org, any translation you contribute at [translate.wordpress.org/projects/wp-plugins/blue-billywig](https://translate.wordpress.org/projects/wp-plugins/blue-billywig/) automatically rolls out to every site running that locale — no plugin release required. If you already translate other WordPress plugins, the process is the same; if you don't, it takes about five minutes to sign up and suggest a correction.

**Adding a new language.**

Once the plugin is listed on translate.wordpress.org, that is the preferred home for every language — translators suggest a locale and the site creates the project on first approval. If you want to kick-start a language ahead of that: copy `languages/blue-billywig.pot` to `languages/blue-billywig-<locale>.po` (e.g. `blue-billywig-it_IT.po` for Italian), translate the `msgstr` values, run `msgfmt blue-billywig-<locale>.po -o blue-billywig-<locale>.mo`, and send the files to [support.bluebillywig.com](https://support.bluebillywig.com/). We'll bundle them in the next release so translate.wordpress.org starts with a baseline.