=== Site-Check Accessibility ===
Contributors: sitecheck
Tags: accessibility, a11y, wcag, bfsg
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 1.0.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Applies accessibility fixes reviewed in the Site-Check web app directly to your WordPress site — server-side, no manual code edits, fully reversible.

== Description ==

**Requires a Site-Check account and an active subscription.** This plugin is the
connector (client) for Site-Check, a hosted, paid accessibility service; the
scanning and fix-computation run externally on Site-Check's servers, not inside
WordPress. See "External services" below.

Site-Check Accessibility connects your WordPress site to the **Site-Check**
accessibility web app. Fixes that you review and approve in the app are applied
directly to your live pages — no manual CSS or markup editing.

Approved fixes are written **server-side, straight into the delivered HTML**;
only elements created later by your site's own JavaScript fall back to a small
client-side script. Every change is traceable and reversible, and is delivered
only while an active connection **and** a valid subscription exist — disconnect
the site or end the subscription and the fixes are removed automatically on the
next page load.

Features:

* Image alt text, ARIA labels, contrast and CSS corrections
* Structural fixes (heading levels, lists, form labels, …)
* Optional accessibility overlay (an assistive aid — not a replacement for real fixes)
* Guided site connection in the WordPress admin

**Deutsch:** Site-Check Accessibility verbindet deine WordPress-Seite mit der
**Site-Check**-Webapp. In der Webapp geprüfte und freigegebene
Barrierefreiheits-Verbesserungen landen automatisch auf der Live-Seite — ohne
manuelle Eingriffe im Code, serverseitig geschrieben und jederzeit widerrufbar.
Die Auslieferung erfolgt nur bei aktiver Verbindung und gültigem Abonnement.

This plugin is the connector (client) for the Site-Check service; the web app
and backend are a separate, proprietary service.

== Installation ==

1. Upload the plugin ZIP under **Plugins → Add New → Upload Plugin**, or unpack
   it into `wp-content/plugins/site-check-accessibility/`.
2. Activate **Site-Check Accessibility** under **Plugins**.
3. Open **Site-Check Accessibility** in the admin sidebar.
4. Generate a pairing code in the Site-Check web app (https://site-check.de) and
   paste it into the plugin form to connect the site.
5. Scan your site in the web app, review and approve fixes — they appear on the
   live site automatically.

Requirements: WordPress 6.0+, PHP 8.0+ with the `dom` extension. Pretty
permalinks should be enabled.

== Frequently Asked Questions ==

= Does the plugin change my content permanently? =

No. Fixes are applied at delivery time and are fully reversible. They are served
only while the site is connected and the subscription is active; otherwise the
original markup is delivered unchanged.

= Do I need to edit code or templates? =

No. Once connected, approved fixes are applied automatically — no theme or
template edits are required.

= The connection or fixes don't work on my host. =

Some hosts strip the `Authorization` header before it reaches PHP. The plugin
also accepts the pairing token via the `X-Sw-Api-Token` header, which survives
most such configurations; if pairing still fails, make sure either that header
or `Authorization: Bearer` is passed through to PHP (typically an `.htaccess`
rule on Apache + mod_php).

= What data leaves my site? =

Only the data needed to sync fixes and connection status with the Site-Check web
app. See the Privacy section below.

== Privacy ==

The plugin communicates only with the Site-Check web app at
`https://site-check.de` to exchange accessibility fixes and connection/health
status. It transfers the page URLs and markup needed to compute and verify
fixes; it does not send data to any third party and sets no tracking cookies.
Details: https://site-check.de/datenschutz

== External services ==

This plugin is a connector to Site-Check, a hosted (paid) accessibility service
operated by site-works AG. It requires a Site-Check account and an active
subscription; scanning and fix-computation run on Site-Check's servers, not
inside WordPress. The plugin communicates with the Site-Check web app at
https://site-check.de in these cases:

* Connecting the site: the pairing code you enter and your site's base URL are
  sent to exchange them for a connection token.
* Fix sync and verification: page URLs and page markup are sent so the service
  can compute and verify accessibility fixes; connection/health status is
  reported back.
* Assistance overlay (only when enabled): visitor-facing pages load the overlay
  runtime script from https://site-check.de/api/overlay/runtime.js, and the
  overlay fetches its configuration from https://site-check.de/api/overlay/config.

No data is sent to any other third party, and the plugin sets no tracking
cookies.

* Terms of Service: https://site-check.de/agb
* Privacy Policy: https://site-check.de/datenschutz

== Changelog ==

= 1.0.2 =
* Fixed: a fix that was already written into the delivered HTML was applied a
  second time by the client runtime. Because the two run against differently
  shaped documents — the served page on one side, the page after the site's own
  JavaScript on the other — the second write could land on a different element
  than the first. On sliders that clone their slides this produced labels on the
  wrong slide. The server half now tells the runtime which fixes it already
  applied, and the runtime skips them.

= 1.0.1 =
* Front-end scripts and styles are now added through the WordPress enqueue API
  (wp_enqueue_script / wp_enqueue_style + wp_add_inline_script /
  wp_add_inline_style) instead of being written into the page HTML. The client
  apply runtime ships as a real enqueued file (assets/js/sc-acc-apply.js) with
  the per-page data attached inline. Server-side fixes are unchanged.
* REST: /connect-status and /webapp-status now require authorization
  (manage_options or the connector bearer token) instead of being public.
* Themes that do not call wp_head()/wp_footer() fall back to an output-buffer
  injection so the overlay + client runtime still work there; standards-compliant
  themes use the enqueue path only (assets injected once, never duplicated).

= 1.0.0 = (2026-07-23)
* Store release under the new name **Site-Check Accessibility** (renamed from
  "sw-acc-connector"). Relicensed to GPLv2-or-later. The plugin folder, text
  domain, classes, admin menu and upload directory were renamed; the REST wire
  (namespace, option keys, signing key) is unchanged, so existing pairings keep
  working after re-installing — the pairing token stays in wp_options and the
  upload directory is migrated automatically on first load.
* Added: fail-open output buffering — a mutation error can never blank the page;
  the original response is served instead.
* Version reset to 1.0.0 (continuation of the 2.6.0 line; same feature set).
* Requires re-installing the renamed plugin on customer sites.

= 2.6.0 = (2026-07-22)
* Fix: fixes are now removed on the frontend when the site is disconnected or
  the subscription lapses — not only when the admin plugin page is opened. The
  injector serves from the signed cache (24h grace); the only frontend
  revalidation (try_refresh_signed_cache) previously acted on HTTP 200/304 only
  and ignored 401/403, so disconnected/unsubscribed sites kept serving cached
  fixes for up to 24h. Now it reconciles like the admin healthcheck:
  - 401 (token revoked — site disconnected/deleted) -> clear_stored_api_token()
    drops the token AND the signed cache; fixes disappear on the next pageload
    and the status shows "disconnected" immediately.
  - 403 (entitlement_inactive — subscription lapsed) -> drop the signed cache
    only, keep the pairing; delivery resumes automatically on re-subscribe.
  Guarantees fixes are delivered only with an active connection + valid
  subscription. The webapp /clear-cache push on disconnect remains the immediate
  path; this is the fail-safe reconciliation.
* Requires re-deployment of the plugin on customer sites.

= 2.5.0 = (2026-07-22)
* Fix: the accessibility overlay would not load on WordPress sites. The
  hard-coded webapp base URL pointed at the `www` host
  (https://www.site-check.de) while the Grav connector uses the canonical apex
  (https://site-check.de). Post-EU-cutover `www` 301-redirects to the apex, and
  the overlay runtime's config fetch (`GET /api/overlay/config`, CORS
  origin-bound) plus the heartbeat POST (`Authorization` bearer -> preflighted)
  do not follow a cross-origin 301 cleanly -> the overlay panel never mounted.
  Base URL corrected to the apex, identical to the Grav connector.
* Requires re-deployment of the plugin on customer sites for the overlay to be
  delivered again.

= 2.4.0 = (2026-07-22)
* Added: `label_append` server-side render path (WCAG 3.3.2 required indicator).
  `label_append` overrides were persisted but never rendered — the output loop
  had no handler. `ServerHtmlMutator::applyLabelAppend` now resolves the field's
  `<label>` (explicit `label[for]`, then nearest wrapping `<label>`, then an
  unambiguous unassociated sibling `<label>` in the field's parent) and appends a
  visible " *"; `inject_label_append_bootstrap` runs it server-side first with a
  client fallback for post-JS/SPA fields. Idempotent (`data-sc-acc-required-marked`);
  a safe no-op when the field has no label (the paired `aria-required` write still
  lands). The marker span is `aria-hidden` so it reinforces visually only. New
  healthcheck feature: `label-append`.
* Powers the reworked `missing-required-indicator` fix: `aria-required="true"` +
  a visible " *" on the field's label, instead of a "Pflichtfeld" text node after
  the input. Re-deployment required for the visible marker to render.
* Fixed: `label_append` was dropped before render — the cache normaliser
  (`index_override_items_by_id`) had no branch for it, so the attribute-required
  catch-all silently discarded it. Added the branch; the leading space of " *"
  is preserved (was trimmed).
* Fixed: disconnect left fixes live. `clear_stored_api_token()` now also unlinks
  the signed cache, so the webapp-status reconciler (401 / DISCONNECTED) removes
  the fixes instead of leaving them until the 24h grace expired. Combined with
  the webapp calling `/clear-cache` on delete/disconnect, fixes are removed
  immediately from both sides.

= 2.3.3 =
* Hotfix on top of 2.3.2: `tag_replace` runtime now remembers per-item which
  items have already been applied (`appliedIndex` map inside the IIFE). Without
  this, the MutationObserver re-ran apply() after the first replacement and
  the originally-second `<h2>` became the new "first match" of a positional
  selector like `div.bp-container h2:nth-of-type(1)` — and got promoted too.
  Per-item idempotency makes apply() safe across the entire page lifecycle.

= 2.3.2 =
* Bugfix: `tag_replace` runtime applies the transformation to the **first
  eligible match only** (previously: all matches). This alone was not enough
  — see 2.3.3 for the actual fix.

= 2.3.1 =
* Schema bump to pipeline version 1.0.1 (additive, backward-compatible).
* `sibling_text_insert` carries an optional `wrapTag` (whitelisted to
  h1–h6/p/span/legend) so the runtime can render an `<h1>` (and not a
  `<span>`) where the fix is meant to add a real heading.
* `tag_replace` carries an optional `inlineStyle` — a compact CSS string the
  runtime merges onto the replaced element's `style` attribute. Prevents
  theme styling from breaking when promoting e.g. `<h2>` to `<h1>`.
* Disconnect (admin button + REST endpoint) now removes `cache.json` and the
  cached state option, so a previously paired site stops shipping fixes
  immediately after disconnect. Plugin deactivation stays a no-op.

= 2.3.0 =
* Initial WordPress release. Feature-parity with the Grav plugin v2.3.0
  on the apply pipeline (alt-text, aria-label, attribute-override,
  contrast-adjustment, css-declaration-set, sibling-text-insert,
  label-append, placeholder-append, html-insert-before, text-content-override,
  document-override, tag_replace, element_replace, element_remove).

= 2.0.0 = (2026-07-08)
* Breaking (delivery model): fixes are now written server-side into the
  delivered HTML (raw HTTP response, no client JS required); only post-JS/SPA
  elements fall back to a client bootstrap (`data-sc-acc-fallback="1"`). Same
  target elements, same result, same reversibility, same subscription binding —
  only the mutation site changes (server HTML instead of browser DOM).
* Vendored HTML5-spec serializer (`masterminds/html5`) + a CMS-agnostic
  ServerHtmlMutator engine. Adds `server-side-apply` to the healthcheck feature
  list so the webapp can verify fixes statically.
* Security: the retained client-fallback scripts keep the 1.1.1 JSON escaping
  (`JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT`) on all sinks.
* Requires re-deployment of the plugin on customer sites.

= 1.1.1 = (2026-07-06)
* Security: extends the 1.0.6 XSS-hardening (`JSON_HEX_TAG | JSON_HEX_AMP |
  JSON_HEX_APOS | JSON_HEX_QUOT`) to every inline-`<script>` JSON sink added
  since — the client-side alt injector and the Phase-0 bootstraps
  sr_only_text_insert / css_rule_inject / element_insert.
* Requires re-deployment of the plugin on customer sites.

= 1.1.0 = (2026-07-04)
* Parity with Grav plugin 1.3.0: new fix type `attribute_remove` — pure
  client-side attribute deletion via the selector-overrides bootstrap
  (`operation: "remove"`), verify anchor `data-sc-acc-managed-removed`.
  Includes the optional `removeAttributes` piggyback field on
  alt_text_override / aria_label_set / attribute_override /
  text_content_override items (removal applied after the value write).
* Three new WCAG-Phase-0 fix types (shared-decisions §1.1) — 1:1 port of
  Grav plugin 1.4.0 (identical validators, id derivation and bootstrap JS),
  all applied strictly client-side via <script> bootstraps (no DOM
  re-serialization):
  * `sr_only_text_insert` — visually-hidden screenreader text
    (`<span class="sc-acc-sr-only" data-sc-acc-sr-only-id="<item-id>">`,
    text via createTextNode, `<`/`>` rejected; the class is defined once
    via `<style data-sc-acc-sr-only-style>`).
  * `css_rule_inject` — fail-closed validated CSS rules collected into a
    single `<style data-sc-acc-css-inject>` tag (no at-rules, no
    url()/expression()/javascript:, no `{}`/`<>`/`;`/`@`/backslash in
    values; re-validated on the render path).
  * `element_insert` — whitelisted HTML (a/span/p/button/div only; no
    script/style/iframe, no event handlers, no javascript:/vbscript:/
    data:text/html URLs, no `&#`/`&colon;` entities, no comments/doctype)
    inserted via template + insertAdjacentElement, de-dup via
    `data-sc-acc-insert-id="<item-id>"`.
* Apply dispatcher hardened: unknown fix types are now rejected with
  `unsupported_fix_type: <type>` instead of being silently treated as
  alt_text_override. Every webapp-sent type has an explicit case; a
  missing/empty type still means alt_text_override (legacy alt payloads).
* Cache-store id normalization: explicit branches for attribute_remove,
  text_content_override (Grav 2.3.2 parity — id-less items were silently
  dropped), sr_only_text_insert, css_rule_inject and element_insert.
* Healthcheck features list extended: attribute-remove, sr-only-insert,
  css-rule-inject, element-insert.
* Requires re-deployment of the plugin on customer sites.

= 1.0.6 =
* Security: XSS-hardening of the inline `<script>` bootstrap injectors. All JSON
  embedded in an inline script (selector-overrides, sibling-inserts,
  document-overrides, tag_replace / element_replace / element_remove, overlay
  bootstrap) now also uses `JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS |
  JSON_HEX_QUOT`, so a fix value / overlay label containing `</script>…` can no
  longer break out of the inline script.
* Fix: default webapp base URL corrected from https://www.site-check.de to the
  canonical apex https://site-check.de — post-EU-cutover www 301-redirects and
  the plugin's POST callbacks (sync/register) must target the apex.
* Requires re-deployment of the plugin on customer sites.

= 1.0.4 =
* Bootstrap-Payload for the overlay runtime now ships `apiToken` so the
  browser-side overlay can authenticate the new `POST /api/overlay/heartbeat`
  call against the webapp. Without the token the runtime simply skips the
  heartbeat — overlay itself keeps working.
* New WP-admin toggle "Overlay-Runtime auf der Website laden" (default ON)
  acts as a local kill-switch independent of the webapp-driven enabled flag.
* `sync-status` response now reports the real `overlayRuntimeEnabled` value
  instead of a hard-coded `true`.
* Requires re-deployment of the plugin on customer sites so the heartbeat
  appears in the dashboard health view.
