From 39516070565dafa63b8c384a3218e193d01609b5 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 19 Sep 2026 13:33:56 +0200 Subject: [PATCH 1/6] docs: add versioned plugin advisories for issue 1396 --- CHANGELOG.md | 4 + docs/PLUGIN_ADVISORIES.md | 169 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 docs/PLUGIN_ADVISORIES.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 966e3df0cf..167935f285 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **Plugin Advisories Document** — Added the versioned single home for plugin advisories with replacement statuses. (#1396) + ### Removed - **Unused Workflow Stubs** — Removed 7 placeholder workflows that ran echo-only steps on every event, plus a dead reusable trigger and stale directory readme. (#3381) diff --git a/docs/PLUGIN_ADVISORIES.md b/docs/PLUGIN_ADVISORIES.md new file mode 100644 index 0000000000..231f20c86b --- /dev/null +++ b/docs/PLUGIN_ADVISORIES.md @@ -0,0 +1,169 @@ +# Plugin Advisories: Native Replacements, Gravity Forms Usage and Hosting-Stack Guidance + +- **Review owner:** LightSpeed Team +- **Last reviewed:** 2026-09-19 +- **Status:** active +- **Companion issue:** [#1396](https://github.com/lightspeedwp/.github/issues/1396) + +This document is the single versioned home for plugin advisories. Every +advisory names its replacement or alternative, never only the restriction. +Status legend used below: **shipped** means the replacement exists in the +starter codebase today with a link; **planned** means it does not exist yet +and the link points at the tracking issue (or states that none exists yet). + +Enforcement mechanism: the starter plugin ships a redundant-plugin admin +notice (`inc/class-redundant-plugins-notice.php`) backed by a registry with +`replaced`/`planned` statuses. `planned` entries never prompt for +deactivation. Add registry entries as replacements ship. + +## 1. Replaced natively in the starter theme/plugin + +| Plugin | Native replacement | Status | Caveat | +|---|---|---|---| +| Safe SVG | `upload_mimes` gated on a capability plus `enshrined/svg-sanitize` on upload | planned ([ls-starter-plugin#3](https://github.com/lightspeedwp/ls-starter-plugin/issues/3)) | SVG is executable markup: allowing the mime type without server-side sanitising re-opens stored-XSS. The filter alone is not a replacement. Sanitize with `enshrined/svg-sanitize` (used by Safe SVG itself and TYPO3 core) on `wp_handle_upload_prefilter`, and gate the mime type on an upload capability so only trusted roles can upload SVG. The starter registry already carries this as `planned` and warns without prompting deactivation. | +| WP Mail SMTP, Change Mail Sender | `phpmailer_init` from constants plus sender fields in Settings → General | partial: from-name/address shipped; constants and settings fields planned ([ls-starter-plugin#2](https://github.com/lightspeedwp/ls-starter-plugin/issues/2)) | `phpmailer_init` fires after core calls `setFrom()`, so set `From`/`FromName`/`Sender` from constants there; the `wp_mail_from` / `wp_mail_from_name` filters are the simpler path for sender identity alone. Do not store SMTP passwords in the database when constants will do. The starter registry marks Change Mail Sender `replaced` (from-name/address handled). | +| Cachebuster | `filemtime()` asset versioning helper | shipped (`inc/class-cachebusting.php`; registry status `replaced`) | Version strings must change when file contents change; `filemtime()` on the asset path does this with no build step and no stale-cache risk. | +| Disable Emails | `pre_wp_mail` short-circuit driven by `wp_get_environment_type()` | planned (no tracking issue yet) | `pre_wp_mail` (core since 5.7) short-circuits `wp_mail()` when the filter returns non-null. Gate on environment type (core since 5.5: `local`, `development`, `staging`, `production`, defaulting to `production`) so non-production never sends. Unlike the plugin, this keeps password-reset and admin mails working in production. | +| View Transitions | native CSS `@view-transition` | planned (no tracking issue yet) | `@view-transition` (cross-document opt-in) is **not** Baseline as of September 2026 — treat it as progressive enhancement with identical no-animation fallback, and verify current Baseline status before relying on it. Same-document transitions via `document.startViewTransition()` have wider support. | +| Carousel Slider Block | CSS scroll-snap with core blocks | planned (no tracking issue yet) | `scroll-snap-type` / `scroll-snap-align` are Baseline widely available (all browsers since 2022) and compose with core Group/Columns blocks, so most marketing carousels need no JS library and no extra DOM weight. | +| GTM4WP, Google Site Kit | container snippet and `dataLayer` output from the enhancement plugin | planned (no tracking issue yet) | Emit `window.dataLayer = window.dataLayer || []` before the container snippet and push named events (`dataLayer.push({ event: 'signup' })`) from theme/plugin code; configure tags and triggers in the container. This keeps markup vendor-neutral: swapping analytics vendors means editing container config, not theme code. | +| WPFront Scroll Top | CSS plus a few lines of JS in the theme | planned (no tracking issue yet) | A positioned button, `scroll-behavior: smooth` (with `prefers-reduced-motion` respect), and a scroll listener is under 30 lines. No settings UI or extra payload is justified. | +| Disable Comments RB | core discussion settings | planned (no tracking issue yet) | Settings → Discussion already disables comments and pings globally and per post type; a plugin adds nothing except another settings surface that can drift from core. | +| User Menus, Visibility Logic for Elementor | block theme templates and template parts | planned (no tracking issue yet) | Block visibility and template-part conditions belong in theme templates where they are version-controlled and reviewable, not in per-element plugin metadata. | +| Social Sharing Block | pattern with plain share URLs, no JS | planned (no tracking issue yet) | Share intents are plain links (`https://www.facebook.com/sharer/sharer.php?u=…`, `https://x.com/intent/post?url=…`, `mailto:`, WhatsApp `wa.me`). A block pattern renders them with zero JS; third-party share scripts are a tracking and performance liability. | +| JWT Authentication for WP-API | core Application Passwords | planned (no tracking issue yet) | Application Passwords (core since 5.6) give per-user, individually revocable credentials over Basic Auth on HTTPS, with usage metadata and no extra plugin surface. JWT plugins add token lifecycle, secret storage and revocation problems core already solved. | +| Yoast Duplicate Post | row action plus `wp_insert_post` clone | planned (no tracking issue yet) | Cloning is `get_post()` → `wp_insert_post()` with copied taxonomies and meta in one admin row action. The plugin's scheduling and bulk features are out of scope until a client asks for them. | + +## 2. Gravity Forms usage advisory + +**Reason:** Gravity Forms is a full form framework: conditional logic, multi-page state, file handling and its theme CSS/JS ship on every page that renders a form. That payload is proportionate for enquiry, application, quote and upload forms — it is disproportionate for site-wide elements, newsletter sign-ups and pop-ups, where it adds render-blocking weight to every page view and degrades Core Web Vitals (particularly INP and LCP on mobile). Gravity Forms' own documentation provides `gform_disable_css` but warns that disabling it breaks conditional logic, honeypot hiding and multi-page forms — evidence the payload is structural, not optional. + +**Rule:** + +- Gravity Forms is the approved platform for enquiry, application, quote and upload forms. +- It must not be used for elements rendered site-wide, newsletter sign-ups, or pop-ups. + +**Required alternatives (reference implementations, not just prohibitions):** + +### Newsletter sign-up: plain form posting to a REST route + +```html +
+ + + +
+ +``` + +```php +add_action( 'rest_api_init', function () { + register_rest_route( 'ls/v1', '/newsletter', array( + 'methods' => 'POST', + 'callback' => 'ls_newsletter_subscribe', + 'permission_callback' => '__return_true', // Public endpoint: rate-limit and validate below. + 'args' => array( + 'email' => array( + 'required' => true, + 'sanitize_callback' => 'sanitize_email', + 'validate_callback' => 'is_email', + ), + ), + ) ); +} ); + +function ls_newsletter_subscribe( WP_REST_Request $request ) { + $response = wp_remote_post( + 'https://provider.example/api/subscribe', + array( + 'timeout' => 10, + 'headers' => array( + 'Authorization' => 'Bearer ' . PROVIDER_API_KEY, // Constant, never the database. + ), + 'body' => array( 'email' => $request['email'] ), + ) + ); + if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { + return new WP_Error( 'subscribe_failed', 'Subscription failed.', array( 'status' => 502 ) ); + } + return array( 'subscribed' => true ); +} +``` + +Adapt the provider call to the newsletter vendor in use; keep the secret in a +constant, validate and rate-limit the route, and return `role="status"` / +`role="alert"` regions so assistive technology announces the outcome. + +### Pop-up: `` with minimal JS + +```html + +

Special offer

+

Offer copy goes here.

+
+ +
+
+ +``` + +`.showModal()` is Baseline widely available (all browsers since March +2022): the browser provides the top layer, `::backdrop`, focus handling and +Escape-to-close with implicit `aria-modal="true"`. No library, no extra +payload, and an explicit close control satisfies the most robust dismissal +pattern. + +## 3. Do not replace natively + +These stay as plugins, with reasons: + +- **Gravity Forms itself** — approved form platform per section 2; its entry handling, notifications, feeds and add-on ecosystem are not sensibly reimplemented. +- **Wordfence** — endpoint firewall, malware scanning and login hardening are a security product, not theme logic; incident response depends on its telemetry. +- **User Switching** — capability-checked user impersonation for support; reimplementing session handling is a security risk with no upside. +- **Yoast SEO** — schema output, sitemaps, redirects UI and content analysis are a maintained product surface; partial reimplementation drifts from SEO best practice silently. +- **All payment gateways** — money movement requires PCI-aware, vendor-maintained integrations; custom gateway code is a compliance and liability risk. +- **WooCommerce Subscriptions** — recurring billing, proration, dunning and gateway token management are commerce-critical logic no theme should own. +- **FacetWP** — indexed faceted search over large catalogues is a performance specialty; naive `WP_Query` faceting does not scale. +- **SearchWP** — relevance-ranked search with custom sources and stemming; core search has no ranking model. +- **Sequential Order Numbers** — order-number sequencing touches invoicing and accounting compliance; a maintained plugin owns the edge cases. + +## 4. Hosting-stack advisories + +**Reason:** the LightSpeed fleet already caches at two layers (nginx FastCGI page cache at origin, Cloudflare edge cache including APO HTML caching), purges both on content change, and runs CI. Each advisory below exists because a third tool would duplicate a layer the fleet already owns, move a check later than CI, or split ownership of a single event. + +- **WP Rocket:** the fleet already runs nginx FastCGI page caching and Cloudflare edge caching, so a third page-cache layer is redundant and a stale-content risk (three invalidation paths instead of one). Approve for asset optimisation only — or not at all — on LightSpeed hosting. Gravity Forms' own cache FAQ independently warns that page caching serves stale output to dynamic, conditional and AJAX-driven forms. +- **Accessibility Checker:** run accessibility checks in CI (axe-core via `axe-core` npm package or pa11y-ci in a GitHub Action, gating on serious/critical violations) rather than as a production plugin. Production checkers add runtime weight to every page view and report after users are already affected; CI reports before merge. Automated engines catch roughly half of WCAG issues — pair with periodic human audit, not with a production plugin. +- **Redirection:** prefer server-level redirects (nginx `return 301` / `rewrite`) for migrations and bulk URL changes — they execute before PHP boots and live in version-controlled fleet config. Use the Redirection plugin only where editors need to manage redirects themselves. +- **Analytics:** one tool owns each event. No duplicate purchase, checkout or form events across Site Kit, GTM, WooCommerce Google Analytics Pro and the Gravity Forms add-on. Duplicate ownership double-counts conversions and makes every report untrustworthy; map event ownership explicitly (which tool fires `purchase`, which fires `form_submit`) during analytics setup and audit it when adding tools. + +## References + +- WordPress Developer Resources: `upload_mimes`, `phpmailer_init`, `pre_wp_mail`, `wp_get_environment_type`, Application Passwords, REST API authentication. +- johnbillion/wp_mail: catalogue of every core email and its disable path. +- MDN: `@view-transition` (limited availability), CSS Scroll Snap (Baseline), `` / `showModal()` (Baseline since March 2022). +- Google Tag Platform docs: data layer initialisation and `dataLayer.push({ event })` model. +- dequelabs/axe-core, pa11y/pa11y-ci: CI accessibility scanning. +- Gravity Forms docs: `gform_disable_css` (with accessibility caveats), cache and script-optimiser FAQ. +- Cloudflare APO docs, nginx FastCGI caching guidance. +- `enshrined/svg-sanitize` (composer library; sanitizer behind Safe SVG and TYPO3 core). +- Starter code: `ls-starter-plugin/inc/class-redundant-plugins-notice.php`, `ls-starter-plugin/inc/class-cachebusting.php`; tracking issues [ls-starter-plugin#2](https://github.com/lightspeedwp/ls-starter-plugin/issues/2) (mail sender) and [ls-starter-plugin#3](https://github.com/lightspeedwp/ls-starter-plugin/issues/3) (SVG). From 64d306cb0207c974efc157dffbe8bad798ea6bc5 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 19 Sep 2026 13:39:41 +0200 Subject: [PATCH 2/6] docs: prefer Redirection plugin for administrator-managed rules --- docs/PLUGIN_ADVISORIES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/PLUGIN_ADVISORIES.md b/docs/PLUGIN_ADVISORIES.md index 231f20c86b..920769c033 100644 --- a/docs/PLUGIN_ADVISORIES.md +++ b/docs/PLUGIN_ADVISORIES.md @@ -153,7 +153,7 @@ These stay as plugins, with reasons: - **WP Rocket:** the fleet already runs nginx FastCGI page caching and Cloudflare edge caching, so a third page-cache layer is redundant and a stale-content risk (three invalidation paths instead of one). Approve for asset optimisation only — or not at all — on LightSpeed hosting. Gravity Forms' own cache FAQ independently warns that page caching serves stale output to dynamic, conditional and AJAX-driven forms. - **Accessibility Checker:** run accessibility checks in CI (axe-core via `axe-core` npm package or pa11y-ci in a GitHub Action, gating on serious/critical violations) rather than as a production plugin. Production checkers add runtime weight to every page view and report after users are already affected; CI reports before merge. Automated engines catch roughly half of WCAG issues — pair with periodic human audit, not with a production plugin. -- **Redirection:** prefer server-level redirects (nginx `return 301` / `rewrite`) for migrations and bulk URL changes — they execute before PHP boots and live in version-controlled fleet config. Use the Redirection plugin only where editors need to manage redirects themselves. +- **Redirection:** use the Redirection plugin. Web administrators need to manage rules themselves, and the plugin scales to that workload — server-level redirects would force every rule change through fleet config and the deploy pipeline. Reserve nginx-level redirects for one-off migration spikes managed by the fleet team. - **Analytics:** one tool owns each event. No duplicate purchase, checkout or form events across Site Kit, GTM, WooCommerce Google Analytics Pro and the Gravity Forms add-on. Duplicate ownership double-counts conversions and makes every report untrustworthy; map event ownership explicitly (which tool fires `purchase`, which fires `form_submit`) during analytics setup and audit it when adding tools. ## References From f923a64fbcb8c94c9c0d843a10be3313ae5efd32 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 19 Sep 2026 13:42:21 +0200 Subject: [PATCH 3/6] docs: link shipped replacements to implementing PR --- docs/PLUGIN_ADVISORIES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/PLUGIN_ADVISORIES.md b/docs/PLUGIN_ADVISORIES.md index 920769c033..18d7b9ef72 100644 --- a/docs/PLUGIN_ADVISORIES.md +++ b/docs/PLUGIN_ADVISORIES.md @@ -21,8 +21,8 @@ deactivation. Add registry entries as replacements ship. | Plugin | Native replacement | Status | Caveat | |---|---|---|---| | Safe SVG | `upload_mimes` gated on a capability plus `enshrined/svg-sanitize` on upload | planned ([ls-starter-plugin#3](https://github.com/lightspeedwp/ls-starter-plugin/issues/3)) | SVG is executable markup: allowing the mime type without server-side sanitising re-opens stored-XSS. The filter alone is not a replacement. Sanitize with `enshrined/svg-sanitize` (used by Safe SVG itself and TYPO3 core) on `wp_handle_upload_prefilter`, and gate the mime type on an upload capability so only trusted roles can upload SVG. The starter registry already carries this as `planned` and warns without prompting deactivation. | -| WP Mail SMTP, Change Mail Sender | `phpmailer_init` from constants plus sender fields in Settings → General | partial: from-name/address shipped; constants and settings fields planned ([ls-starter-plugin#2](https://github.com/lightspeedwp/ls-starter-plugin/issues/2)) | `phpmailer_init` fires after core calls `setFrom()`, so set `From`/`FromName`/`Sender` from constants there; the `wp_mail_from` / `wp_mail_from_name` filters are the simpler path for sender identity alone. Do not store SMTP passwords in the database when constants will do. The starter registry marks Change Mail Sender `replaced` (from-name/address handled). | -| Cachebuster | `filemtime()` asset versioning helper | shipped (`inc/class-cachebusting.php`; registry status `replaced`) | Version strings must change when file contents change; `filemtime()` on the asset path does this with no build step and no stale-cache risk. | +| WP Mail SMTP, Change Mail Sender | `phpmailer_init` from constants plus sender fields in Settings → General | partial: from-name/address shipped ([ls-starter-plugin#11](https://github.com/lightspeedwp/ls-starter-plugin/pull/11)); constants and settings fields planned ([ls-starter-plugin#2](https://github.com/lightspeedwp/ls-starter-plugin/issues/2)) | `phpmailer_init` fires after core calls `setFrom()`, so set `From`/`FromName`/`Sender` from constants there; the `wp_mail_from` / `wp_mail_from_name` filters are the simpler path for sender identity alone. Do not store SMTP passwords in the database when constants will do. The starter registry marks Change Mail Sender `replaced` (from-name/address handled). | +| Cachebuster | `filemtime()` asset versioning helper | shipped ([ls-starter-plugin#11](https://github.com/lightspeedwp/ls-starter-plugin/pull/11), `inc/class-cachebusting.php`; registry status `replaced`) | Version strings must change when file contents change; `filemtime()` on the asset path does this with no build step and no stale-cache risk. | | Disable Emails | `pre_wp_mail` short-circuit driven by `wp_get_environment_type()` | planned (no tracking issue yet) | `pre_wp_mail` (core since 5.7) short-circuits `wp_mail()` when the filter returns non-null. Gate on environment type (core since 5.5: `local`, `development`, `staging`, `production`, defaulting to `production`) so non-production never sends. Unlike the plugin, this keeps password-reset and admin mails working in production. | | View Transitions | native CSS `@view-transition` | planned (no tracking issue yet) | `@view-transition` (cross-document opt-in) is **not** Baseline as of September 2026 — treat it as progressive enhancement with identical no-animation fallback, and verify current Baseline status before relying on it. Same-document transitions via `document.startViewTransition()` have wider support. | | Carousel Slider Block | CSS scroll-snap with core blocks | planned (no tracking issue yet) | `scroll-snap-type` / `scroll-snap-align` are Baseline widely available (all browsers since 2022) and compose with core Group/Columns blocks, so most marketing carousels need no JS library and no extra DOM weight. | From bae6803c73acecd0db925774cb6a7c8b2c806617 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 19 Sep 2026 13:53:00 +0200 Subject: [PATCH 4/6] docs: add approved plugin register linked to advisories --- docs/PLUGIN_ADVISORIES.md | 11 +++++++---- docs/PLUGIN_REGISTER.md | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 docs/PLUGIN_REGISTER.md diff --git a/docs/PLUGIN_ADVISORIES.md b/docs/PLUGIN_ADVISORIES.md index 18d7b9ef72..d3bb86c2fb 100644 --- a/docs/PLUGIN_ADVISORIES.md +++ b/docs/PLUGIN_ADVISORIES.md @@ -6,10 +6,13 @@ - **Companion issue:** [#1396](https://github.com/lightspeedwp/.github/issues/1396) This document is the single versioned home for plugin advisories. Every -advisory names its replacement or alternative, never only the restriction. -Status legend used below: **shipped** means the replacement exists in the -starter codebase today with a link; **planned** means it does not exist yet -and the link points at the tracking issue (or states that none exists yet). +advisory names its replacement or its alternative, never only the +restriction. The canonical list of what may be installed is the companion +[Approved Plugin Register](PLUGIN_REGISTER.md); anything not on that list +is not approved. Status legend used below: **shipped** means the replacement +exists in the starter codebase today with a link; **planned** means it does +not exist yet and the link points at the tracking issue (or states that none +exists yet). Enforcement mechanism: the starter plugin ships a redundant-plugin admin notice (`inc/class-redundant-plugins-notice.php`) backed by a registry with diff --git a/docs/PLUGIN_REGISTER.md b/docs/PLUGIN_REGISTER.md new file mode 100644 index 0000000000..679737b6cf --- /dev/null +++ b/docs/PLUGIN_REGISTER.md @@ -0,0 +1,40 @@ +# Approved Plugin Register + +- **Review owner:** LightSpeed Team +- **Last reviewed:** 2026-09-19 +- **Status:** active +- **Companion issue:** [#1396](https://github.com/lightspeedwp/.github/issues/1396) + +Org-wide canonical list of plugins approved for client builds. This register +answers *what may be installed*; why each entry is a plugin rather than +native code — and what replaces the rest — lives in the companion +[Plugin Advisories](PLUGIN_ADVISORIES.md). Client-specific stacks do not +belong here; those live in project memory per the delivery skill guidance. + +Status legend: **approved** (stays), **transitional** (approved until its +native replacement ships, then must be removed — see advisories). + +## Approved + +| Plugin | Purpose | Notes | +|---|---|---| +| Gravity Forms | Enquiry, application, quote and upload forms | Approved form platform; see usage advisory (never site-wide elements, newsletters or pop-ups). | +| Wordfence | Endpoint firewall, malware scanning, login hardening | Incident response depends on its telemetry. | +| User Switching | Capability-checked user impersonation for support | Never reimplement session handling. | +| Yoast SEO | Schema output, sitemaps, redirects UI, content analysis | — | +| Payment gateways | Money movement (WooCommerce core and approved gateway extensions) | Custom gateway code is a compliance and liability risk. | +| WooCommerce Subscriptions | Recurring billing, proration, dunning, gateway tokens | Commerce-critical logic no theme should own. | +| FacetWP | Indexed faceted search over large catalogues | Naive `WP_Query` faceting does not scale. | +| SearchWP | Relevance-ranked search with custom sources and stemming | Core search has no ranking model. | +| Sequential Order Numbers | Sequential, compliant order numbering | Touches invoicing and accounting compliance. | +| Redirection | Administrator-managed redirects | Web administrators manage rules themselves and the plugin scales; reserve nginx-level redirects for fleet-managed migration spikes. | + +## Transitional (remove once the native replacement ships) + +| Plugin | Pending replacement | Tracking | +|---|---|---| +| Safe SVG | Capability-gated `upload_mimes` plus `enshrined/svg-sanitize` on upload | [ls-starter-plugin#3](https://github.com/lightspeedwp/ls-starter-plugin/issues/3) | +| WP Mail SMTP | `phpmailer_init` from constants plus sender fields in Settings → General | [ls-starter-plugin#2](https://github.com/lightspeedwp/ls-starter-plugin/issues/2) | + +Anything not on this list is not approved: either it has a native replacement +in the advisories document, or it needs a review entry here before use. From 31db631e79ec0a0fd31e494c5337bb8c43d98aaa Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 25 Sep 2026 08:28:31 +0200 Subject: [PATCH 5/6] docs: align plugin advisories with current scaffolds --- CHANGELOG.md | 4 +- docs/PLUGIN_ADVISORIES.md | 180 ++++++++++++++++++++++++++------------ docs/PLUGIN_REGISTER.md | 60 +++++++------ 3 files changed, 160 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29f9f2fabf..acea67c20c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ title: "Changelog" description: "All notable changes to this project, formatted per Keep a Changelog 1.1.0 and Semantic Versioning" file_type: "documentation" created_date: "2025-09-20" -last_updated: "2026-09-15" +last_updated: "2026-09-25" consolidation_phase: "Phase 1 (merged sections)" owners: - LightSpeed Team @@ -108,7 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- **Plugin Advisories Document** — Added the versioned single home for plugin advisories with replacement statuses. (#1396) +- **Plugin Advisories and Register** — Added current scaffold tracking, safe asset versioning, a rate-limited newsletter example and project-controlled gateway approval. ([#1396](https://github.com/lightspeedwp/.github/issues/1396)) - **SpecKit Folder Organization Refactoring & Quality Audit** — Added Spec 013 with `.github/specs/` audit, catalog, eight-dimension quality review, and maintenance procedures. ([PR #3348](https://github.com/lightspeedwp/.github/pull/3348)) - **Changelog pre-release validation tools** — Added automated validator script and release manager checklist for pre-release changelog quality audits. (#3350) - **Branch Cleanup Audit Added** — Added safe-cleanup guidance with keep, review and delete categories. (#3128) diff --git a/docs/PLUGIN_ADVISORIES.md b/docs/PLUGIN_ADVISORIES.md index d3bb86c2fb..bc2ec7a958 100644 --- a/docs/PLUGIN_ADVISORIES.md +++ b/docs/PLUGIN_ADVISORIES.md @@ -1,7 +1,7 @@ # Plugin Advisories: Native Replacements, Gravity Forms Usage and Hosting-Stack Guidance - **Review owner:** LightSpeed Team -- **Last reviewed:** 2026-09-19 +- **Last reviewed:** 2026-09-25 - **Status:** active - **Companion issue:** [#1396](https://github.com/lightspeedwp/.github/issues/1396) @@ -9,37 +9,49 @@ This document is the single versioned home for plugin advisories. Every advisory names its replacement or its alternative, never only the restriction. The canonical list of what may be installed is the companion [Approved Plugin Register](PLUGIN_REGISTER.md); anything not on that list -is not approved. Status legend used below: **shipped** means the replacement -exists in the starter codebase today with a link; **planned** means it does -not exist yet and the link points at the tracking issue (or states that none -exists yet). - -Enforcement mechanism: the starter plugin ships a redundant-plugin admin -notice (`inc/class-redundant-plugins-notice.php`) backed by a registry with -`replaced`/`planned` statuses. `planned` entries never prompt for -deactivation. Add registry entries as replacements ship. - -## 1. Replaced natively in the starter theme/plugin - -| Plugin | Native replacement | Status | Caveat | -|---|---|---|---| -| Safe SVG | `upload_mimes` gated on a capability plus `enshrined/svg-sanitize` on upload | planned ([ls-starter-plugin#3](https://github.com/lightspeedwp/ls-starter-plugin/issues/3)) | SVG is executable markup: allowing the mime type without server-side sanitising re-opens stored-XSS. The filter alone is not a replacement. Sanitize with `enshrined/svg-sanitize` (used by Safe SVG itself and TYPO3 core) on `wp_handle_upload_prefilter`, and gate the mime type on an upload capability so only trusted roles can upload SVG. The starter registry already carries this as `planned` and warns without prompting deactivation. | -| WP Mail SMTP, Change Mail Sender | `phpmailer_init` from constants plus sender fields in Settings → General | partial: from-name/address shipped ([ls-starter-plugin#11](https://github.com/lightspeedwp/ls-starter-plugin/pull/11)); constants and settings fields planned ([ls-starter-plugin#2](https://github.com/lightspeedwp/ls-starter-plugin/issues/2)) | `phpmailer_init` fires after core calls `setFrom()`, so set `From`/`FromName`/`Sender` from constants there; the `wp_mail_from` / `wp_mail_from_name` filters are the simpler path for sender identity alone. Do not store SMTP passwords in the database when constants will do. The starter registry marks Change Mail Sender `replaced` (from-name/address handled). | -| Cachebuster | `filemtime()` asset versioning helper | shipped ([ls-starter-plugin#11](https://github.com/lightspeedwp/ls-starter-plugin/pull/11), `inc/class-cachebusting.php`; registry status `replaced`) | Version strings must change when file contents change; `filemtime()` on the asset path does this with no build step and no stale-cache risk. | -| Disable Emails | `pre_wp_mail` short-circuit driven by `wp_get_environment_type()` | planned (no tracking issue yet) | `pre_wp_mail` (core since 5.7) short-circuits `wp_mail()` when the filter returns non-null. Gate on environment type (core since 5.5: `local`, `development`, `staging`, `production`, defaulting to `production`) so non-production never sends. Unlike the plugin, this keeps password-reset and admin mails working in production. | -| View Transitions | native CSS `@view-transition` | planned (no tracking issue yet) | `@view-transition` (cross-document opt-in) is **not** Baseline as of September 2026 — treat it as progressive enhancement with identical no-animation fallback, and verify current Baseline status before relying on it. Same-document transitions via `document.startViewTransition()` have wider support. | -| Carousel Slider Block | CSS scroll-snap with core blocks | planned (no tracking issue yet) | `scroll-snap-type` / `scroll-snap-align` are Baseline widely available (all browsers since 2022) and compose with core Group/Columns blocks, so most marketing carousels need no JS library and no extra DOM weight. | -| GTM4WP, Google Site Kit | container snippet and `dataLayer` output from the enhancement plugin | planned (no tracking issue yet) | Emit `window.dataLayer = window.dataLayer || []` before the container snippet and push named events (`dataLayer.push({ event: 'signup' })`) from theme/plugin code; configure tags and triggers in the container. This keeps markup vendor-neutral: swapping analytics vendors means editing container config, not theme code. | -| WPFront Scroll Top | CSS plus a few lines of JS in the theme | planned (no tracking issue yet) | A positioned button, `scroll-behavior: smooth` (with `prefers-reduced-motion` respect), and a scroll listener is under 30 lines. No settings UI or extra payload is justified. | -| Disable Comments RB | core discussion settings | planned (no tracking issue yet) | Settings → Discussion already disables comments and pings globally and per post type; a plugin adds nothing except another settings surface that can drift from core. | -| User Menus, Visibility Logic for Elementor | block theme templates and template parts | planned (no tracking issue yet) | Block visibility and template-part conditions belong in theme templates where they are version-controlled and reviewable, not in per-element plugin metadata. | -| Social Sharing Block | pattern with plain share URLs, no JS | planned (no tracking issue yet) | Share intents are plain links (`https://www.facebook.com/sharer/sharer.php?u=…`, `https://x.com/intent/post?url=…`, `mailto:`, WhatsApp `wa.me`). A block pattern renders them with zero JS; third-party share scripts are a tracking and performance liability. | -| JWT Authentication for WP-API | core Application Passwords | planned (no tracking issue yet) | Application Passwords (core since 5.6) give per-user, individually revocable credentials over Basic Auth on HTTPS, with usage metadata and no extra plugin surface. JWT plugins add token lifecycle, secret storage and revocation problems core already solved. | -| Yoast Duplicate Post | row action plus `wp_insert_post` clone | planned (no tracking issue yet) | Cloning is `get_post()` → `wp_insert_post()` with copied taxonomies and meta in one admin row action. The plugin's scheduling and bulk features are out of scope until a client asks for them. | +is not approved. + +Generated projects use these implementation sources; retired starter +repositories are not authoritative: + +| Generated artefact | Implementation source | +| ------------------ | ------------------------------------------------------------------------------------------------------- | +| Plugin | [`lightspeedwp/block-plugin-scaffold`](https://github.com/lightspeedwp/block-plugin-scaffold) `develop` | +| Theme | [`lightspeedwp/block-theme-scaffold`](https://github.com/lightspeedwp/block-theme-scaffold) `develop` | + +Status legend used below: + +- **available** — present in WordPress core or in the current `develop` branch + of a generator, with a direct source link. +- **planned** — not implemented yet, with a tracking issue in the generator + repository that will own the implementation. +- **conditional** — permitted only after project-specific approval; the + advisory does not grant that approval. + +Verify the current generator branch before changing a status. An example, +instruction or issue in a generator is not evidence that the feature ships. + +## 1. Replaced natively in generated projects + +| Plugin | Native replacement | Status | Caveat | +| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Safe SVG | Capability-gated `upload_mimes` plus `enshrined/svg-sanitize` on upload | planned ([block-plugin-scaffold#37](https://github.com/lightspeedwp/block-plugin-scaffold/issues/37)) | SVG is executable markup. The MIME filter alone is not a replacement: sanitise the uploaded file server-side and allow SVG only for roles with the required upload capability. | +| WP Mail SMTP, Change Mail Sender | Configured `PHPMailer` transport and sender values from deployment secrets, plus sender fields in generated plugin settings | planned ([block-plugin-scaffold#37](https://github.com/lightspeedwp/block-plugin-scaffold/issues/37)) | `phpmailer_init` fires after core sets defaults. Configure transport and sender values there, keep credentials outside the database, and test password-reset and admin mail in production mode. | +| Cachebuster | WordPress build-manifest version from `*.asset.php` | available ([block-theme-scaffold `functions.php`](https://github.com/lightspeedwp/block-theme-scaffold/blob/develop/functions.php)) | The generated theme uses the build asset's version and falls back to the theme version. Do not use `filemtime()` as proof of freshness: timestamps can remain unchanged when content changes. | +| Disable Emails | `pre_wp_mail` short-circuit driven by `wp_get_environment_type()` | planned ([block-plugin-scaffold#37](https://github.com/lightspeedwp/block-plugin-scaffold/issues/37)) | Return a non-null value only outside production. The default must remain production so a missing or unrecognised environment never suppresses live transactional mail. | +| View Transitions | Native CSS `@view-transition` with an identical no-animation fallback | planned ([block-theme-scaffold#9](https://github.com/lightspeedwp/block-theme-scaffold/issues/9)) | Treat cross-document transitions as progressive enhancement, respect reduced-motion preferences and verify support for the project's browser matrix before relying on them. | +| Carousel Slider Block | CSS scroll-snap with core Group and Columns blocks | planned ([block-theme-scaffold#9](https://github.com/lightspeedwp/block-theme-scaffold/issues/9)) | Use native scrolling and keyboard-operable controls. Do not add autoplay or JavaScript unless a documented project requirement needs them. | +| GTM4WP, Google Site Kit | Container snippet and vendor-neutral `dataLayer` events in the generated plugin | planned ([block-plugin-scaffold#37](https://github.com/lightspeedwp/block-plugin-scaffold/issues/37)) | Initialise `window.dataLayer` before the container and push named events from generated code. Keep vendor tags and triggers in the container so changing providers does not require theme rewrites. | +| WPFront Scroll Top | Generated `ScrollToTop` component | available ([block-plugin-scaffold component](https://github.com/lightspeedwp/block-plugin-scaffold/blob/develop/src/components/ScrollToTop/ScrollToTop.js)) | The component is exported but not automatically placed by every generated site. Verify keyboard behaviour, touch-target size and reduced-motion handling when integrating it. | +| Disable Comments RB | WordPress core discussion settings | available ([WordPress core](https://wordpress.org/documentation/article/settings-discussion-screen/)) | Settings → Discussion can disable comments and pings globally and per post type. A plugin only adds another settings surface that can drift from core. | +| User Menus, Visibility Logic for Elementor | Block-theme templates, template parts and navigation | planned ([block-theme-scaffold#9](https://github.com/lightspeedwp/block-theme-scaffold/issues/9)) | Keep conditional presentation in version-controlled theme structures. Do not carry Elementor-specific visibility metadata into a generated block theme. | +| Social Sharing Block | Pattern with plain share URLs and no third-party JavaScript | planned ([block-theme-scaffold#9](https://github.com/lightspeedwp/block-theme-scaffold/issues/9)) | Share intents can be ordinary links. Third-party share scripts add tracking, payload and consent work without helping the basic action. | +| JWT Authentication for WP-API | WordPress core Application Passwords | available ([WordPress core](https://developer.wordpress.org/advanced-administration/security/application-passwords/)) | Use HTTPS and individually issued, revocable credentials. Do not add a second token and secret lifecycle when core credentials meet the requirement. | +| Yoast Duplicate Post | Capability-checked row action plus a reviewed `wp_insert_post()` clone | planned ([block-plugin-scaffold#37](https://github.com/lightspeedwp/block-plugin-scaffold/issues/37)) | Copy only supported post fields, taxonomies and meta. Scheduling and bulk duplication remain out of scope until a project explicitly requires them. | ## 2. Gravity Forms usage advisory -**Reason:** Gravity Forms is a full form framework: conditional logic, multi-page state, file handling and its theme CSS/JS ship on every page that renders a form. That payload is proportionate for enquiry, application, quote and upload forms — it is disproportionate for site-wide elements, newsletter sign-ups and pop-ups, where it adds render-blocking weight to every page view and degrades Core Web Vitals (particularly INP and LCP on mobile). Gravity Forms' own documentation provides `gform_disable_css` but warns that disabling it breaks conditional logic, honeypot hiding and multi-page forms — evidence the payload is structural, not optional. +**Reason:** Gravity Forms is a full form framework: conditional logic, multi-page state, file handling and its theme CSS/JS ship on every page that renders a form. That payload is proportionate for enquiry, application, quote and upload forms. It is disproportionate when a newsletter form or pop-up loads the framework on every page, increasing transfer and execution cost and potentially degrading Core Web Vitals. Gravity Forms' own documentation provides `gform_disable_css` but warns that disabling it breaks conditional logic, honeypot hiding and multi-page forms — evidence the payload is structural, not optional. **Rule:** @@ -53,22 +65,22 @@ deactivation. Add registry entries as replacements ship. ```html
- +
``` @@ -77,7 +89,7 @@ add_action( 'rest_api_init', function () { register_rest_route( 'ls/v1', '/newsletter', array( 'methods' => 'POST', 'callback' => 'ls_newsletter_subscribe', - 'permission_callback' => '__return_true', // Public endpoint: rate-limit and validate below. + 'permission_callback' => '__return_true', 'args' => array( 'email' => array( 'required' => true, @@ -88,31 +100,85 @@ add_action( 'rest_api_init', function () { ) ); } ); +function ls_newsletter_rate_limited( $email ) { + $ip = isset( $_SERVER['REMOTE_ADDR'] ) + ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) + : 'unknown'; + $limits = array( + array( + 'prefix' => 'ls_newsletter_ip_', + 'value' => $ip, + 'max' => 20, + ), + array( + 'prefix' => 'ls_newsletter_email_', + 'value' => strtolower( $email ), + 'max' => 3, + ), + ); + $counts = array(); + + foreach ( $limits as $limit ) { + $key = $limit['prefix'] . wp_hash( $limit['value'], 'auth' ); + $count = (int) get_transient( $key ); + if ( $count >= $limit['max'] ) { + return true; + } + $counts[ $key ] = $count + 1; + } + + foreach ( $counts as $key => $count ) { + set_transient( $key, $count, HOUR_IN_SECONDS ); + } + + return false; +} + function ls_newsletter_subscribe( WP_REST_Request $request ) { + $email = sanitize_email( $request['email'] ); + if ( ls_newsletter_rate_limited( $email ) ) { + return new WP_Error( + 'subscribe_rate_limited', + 'Subscription rate limit exceeded.', + array( + 'status' => 429, + 'Retry-After' => HOUR_IN_SECONDS, + ) + ); + } + $response = wp_remote_post( 'https://provider.example/api/subscribe', array( 'timeout' => 10, 'headers' => array( - 'Authorization' => 'Bearer ' . PROVIDER_API_KEY, // Constant, never the database. + 'Authorization' => 'Bearer ' . PROVIDER_API_KEY, ), - 'body' => array( 'email' => $request['email'] ), + 'body' => array( 'email' => $email ), ) ); - if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { + if ( is_wp_error( $response ) ) { + return new WP_Error( 'subscribe_failed', 'Subscription failed.', array( 'status' => 502 ) ); + } + $status_code = (int) wp_remote_retrieve_response_code( $response ); + if ( $status_code < 200 || $status_code >= 300 ) { return new WP_Error( 'subscribe_failed', 'Subscription failed.', array( 'status' => 502 ) ); } return array( 'subscribed' => true ); } ``` -Adapt the provider call to the newsletter vendor in use; keep the secret in a -constant, validate and rate-limit the route, and return `role="status"` / -`role="alert"` regions so assistive technology announces the outcome. +The example allows 20 requests per IP and three per email address per hour, +storing only WordPress hashes of those values. Tune both limits to the provider's +contract, use a shared rate-limit service when traffic spans multiple application +servers, and keep the provider secret in a constant rather than the database. +Return `role="status"` / `role="alert"` regions so assistive technology announces +the outcome. ### Pop-up: `` with minimal JS ```html +

Special offer

Offer copy goes here.

@@ -121,12 +187,12 @@ constant, validate and rate-limit the route, and return `role="status"` /
``` @@ -144,7 +210,7 @@ These stay as plugins, with reasons: - **Wordfence** — endpoint firewall, malware scanning and login hardening are a security product, not theme logic; incident response depends on its telemetry. - **User Switching** — capability-checked user impersonation for support; reimplementing session handling is a security risk with no upside. - **Yoast SEO** — schema output, sitemaps, redirects UI and content analysis are a maintained product surface; partial reimplementation drifts from SEO best practice silently. -- **All payment gateways** — money movement requires PCI-aware, vendor-maintained integrations; custom gateway code is a compliance and liability risk. +- **Concrete payment gateway plugins** — money movement requires PCI-aware, vendor-maintained integrations, so gateways stay plugins rather than theme code. No concrete gateway is globally approved: require an exact plugin/version allowlist and project approval before installation. WooCommerce core does not process payments. - **WooCommerce Subscriptions** — recurring billing, proration, dunning and gateway token management are commerce-critical logic no theme should own. - **FacetWP** — indexed faceted search over large catalogues is a performance specialty; naive `WP_Query` faceting does not scale. - **SearchWP** — relevance-ranked search with custom sources and stemming; core search has no ranking model. @@ -156,7 +222,7 @@ These stay as plugins, with reasons: - **WP Rocket:** the fleet already runs nginx FastCGI page caching and Cloudflare edge caching, so a third page-cache layer is redundant and a stale-content risk (three invalidation paths instead of one). Approve for asset optimisation only — or not at all — on LightSpeed hosting. Gravity Forms' own cache FAQ independently warns that page caching serves stale output to dynamic, conditional and AJAX-driven forms. - **Accessibility Checker:** run accessibility checks in CI (axe-core via `axe-core` npm package or pa11y-ci in a GitHub Action, gating on serious/critical violations) rather than as a production plugin. Production checkers add runtime weight to every page view and report after users are already affected; CI reports before merge. Automated engines catch roughly half of WCAG issues — pair with periodic human audit, not with a production plugin. -- **Redirection:** use the Redirection plugin. Web administrators need to manage rules themselves, and the plugin scales to that workload — server-level redirects would force every rule change through fleet config and the deploy pipeline. Reserve nginx-level redirects for one-off migration spikes managed by the fleet team. +- **Redirection:** prefer fleet-managed nginx rules for migrations, bulk URL changes and other high-volume redirects because they are deterministic and part of the deployment path. Use the Redirection plugin only when editors need to manage a bounded set of day-to-day rules themselves; record the owner and review process. - **Analytics:** one tool owns each event. No duplicate purchase, checkout or form events across Site Kit, GTM, WooCommerce Google Analytics Pro and the Gravity Forms add-on. Duplicate ownership double-counts conversions and makes every report untrustworthy; map event ownership explicitly (which tool fires `purchase`, which fires `form_submit`) during analytics setup and audit it when adding tools. ## References @@ -169,4 +235,4 @@ These stay as plugins, with reasons: - Gravity Forms docs: `gform_disable_css` (with accessibility caveats), cache and script-optimiser FAQ. - Cloudflare APO docs, nginx FastCGI caching guidance. - `enshrined/svg-sanitize` (composer library; sanitizer behind Safe SVG and TYPO3 core). -- Starter code: `ls-starter-plugin/inc/class-redundant-plugins-notice.php`, `ls-starter-plugin/inc/class-cachebusting.php`; tracking issues [ls-starter-plugin#2](https://github.com/lightspeedwp/ls-starter-plugin/issues/2) (mail sender) and [ls-starter-plugin#3](https://github.com/lightspeedwp/ls-starter-plugin/issues/3) (SVG). +- Generator sources: [`block-plugin-scaffold`](https://github.com/lightspeedwp/block-plugin-scaffold) and [`block-theme-scaffold`](https://github.com/lightspeedwp/block-theme-scaffold), including [plugin replacement tracking](https://github.com/lightspeedwp/block-plugin-scaffold/issues/37) and [theme replacement tracking](https://github.com/lightspeedwp/block-theme-scaffold/issues/9). diff --git a/docs/PLUGIN_REGISTER.md b/docs/PLUGIN_REGISTER.md index 679737b6cf..095aba7bb4 100644 --- a/docs/PLUGIN_REGISTER.md +++ b/docs/PLUGIN_REGISTER.md @@ -1,40 +1,50 @@ # Approved Plugin Register - **Review owner:** LightSpeed Team -- **Last reviewed:** 2026-09-19 +- **Last reviewed:** 2026-09-25 - **Status:** active - **Companion issue:** [#1396](https://github.com/lightspeedwp/.github/issues/1396) Org-wide canonical list of plugins approved for client builds. This register -answers *what may be installed*; why each entry is a plugin rather than -native code — and what replaces the rest — lives in the companion -[Plugin Advisories](PLUGIN_ADVISORIES.md). Client-specific stacks do not -belong here; those live in project memory per the delivery skill guidance. +answers _what may be installed globally_; why each entry is a plugin rather +than native code — and what replaces the rest — lives in the companion +[Plugin Advisories](PLUGIN_ADVISORIES.md). Client-specific stacks and concrete +payment-gateway approvals do not belong here; those live in a controlled +project register. -Status legend: **approved** (stays), **transitional** (approved until its -native replacement ships, then must be removed — see advisories). +Status legend: **approved** (may be installed), **conditional** (requires +project-specific approval before installation), and **transitional** (approved +only until the tracked native replacement is available, then must be removed). ## Approved -| Plugin | Purpose | Notes | -|---|---|---| -| Gravity Forms | Enquiry, application, quote and upload forms | Approved form platform; see usage advisory (never site-wide elements, newsletters or pop-ups). | -| Wordfence | Endpoint firewall, malware scanning, login hardening | Incident response depends on its telemetry. | -| User Switching | Capability-checked user impersonation for support | Never reimplement session handling. | -| Yoast SEO | Schema output, sitemaps, redirects UI, content analysis | — | -| Payment gateways | Money movement (WooCommerce core and approved gateway extensions) | Custom gateway code is a compliance and liability risk. | -| WooCommerce Subscriptions | Recurring billing, proration, dunning, gateway tokens | Commerce-critical logic no theme should own. | -| FacetWP | Indexed faceted search over large catalogues | Naive `WP_Query` faceting does not scale. | -| SearchWP | Relevance-ranked search with custom sources and stemming | Core search has no ranking model. | -| Sequential Order Numbers | Sequential, compliant order numbering | Touches invoicing and accounting compliance. | -| Redirection | Administrator-managed redirects | Web administrators manage rules themselves and the plugin scales; reserve nginx-level redirects for fleet-managed migration spikes. | +| Plugin | Purpose | Notes | +| ------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| Gravity Forms | Enquiry, application, quote and upload forms | Approved form platform; see usage advisory (never site-wide elements, newsletters or pop-ups). | +| Wordfence | Endpoint firewall, malware scanning, login hardening | Incident response depends on its telemetry. | +| User Switching | Capability-checked user impersonation for support | Never reimplement session handling. | +| Yoast SEO | Schema output, sitemaps, redirects UI, content analysis | — | +| WooCommerce Subscriptions | Recurring billing, proration, dunning, gateway tokens | Commerce-critical logic no theme should own. | +| FacetWP | Indexed faceted search over large catalogues | Naive `WP_Query` faceting does not scale. | +| SearchWP | Relevance-ranked search with custom sources and stemming | Core search has no ranking model. | +| Sequential Order Numbers | Sequential, compliant order numbering | Touches invoicing and accounting compliance. | +| Redirection | Bounded, administrator-managed redirects | Use for day-to-day editor-owned rules; use fleet-managed nginx rules for migrations and bulk URL changes. | + +## Conditional (project approval required) + +| Plugin class | Policy | +| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Concrete payment gateway plugins | No concrete gateway is globally approved by this register. WooCommerce core does not process payments. Before installation, record the exact plugin slug and version, provider, supported currencies, owner, test plan and approval in a controlled project register. | ## Transitional (remove once the native replacement ships) -| Plugin | Pending replacement | Tracking | -|---|---|---| -| Safe SVG | Capability-gated `upload_mimes` plus `enshrined/svg-sanitize` on upload | [ls-starter-plugin#3](https://github.com/lightspeedwp/ls-starter-plugin/issues/3) | -| WP Mail SMTP | `phpmailer_init` from constants plus sender fields in Settings → General | [ls-starter-plugin#2](https://github.com/lightspeedwp/ls-starter-plugin/issues/2) | +| Plugin | Pending replacement | Tracking | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| Safe SVG | Capability-gated `upload_mimes` plus `enshrined/svg-sanitize` on upload | [block-plugin-scaffold#37](https://github.com/lightspeedwp/block-plugin-scaffold/issues/37) | +| WP Mail SMTP | Configured `PHPMailer` transport and sender values from deployment secrets, plus sender fields in generated plugin settings | [block-plugin-scaffold#37](https://github.com/lightspeedwp/block-plugin-scaffold/issues/37) | +| Change Mail Sender | `phpmailer_init` from constants plus sender fields in generated plugin settings | [block-plugin-scaffold#37](https://github.com/lightspeedwp/block-plugin-scaffold/issues/37) | -Anything not on this list is not approved: either it has a native replacement -in the advisories document, or it needs a review entry here before use. +Anything not listed as approved or transitional is not globally approved. +Conditional entries still require the project approval described above. A plugin +with a native replacement in the advisories document remains unapproved until +that replacement is available and the entry is removed from this register. From 3ffc7493465227dd1d72c1cc46d074e54cf944ea Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 26 Sep 2026 08:38:33 +0200 Subject: [PATCH 6/6] chore: adopt develop .gitattributes Adds `CHANGELOG.md merge=union` from develop so the merge below resolves the changelog automatically instead of by hand. Needed because git reads merge attributes from the working tree being merged into, so the branch must carry the attribute before develop can be merged in. --- .gitattributes | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.gitattributes b/.gitattributes index 7a5eb063c0..02accd8565 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14,6 +14,19 @@ *.yml text eol=lf *.yaml text eol=lf +# The changelog is append-only and almost every pull request adds an entry to +# the same short `[Unreleased]` list, so two open pull requests edit the same +# lines. A plain merge then conflicts, and the losing pull request has to have +# `develop` merged in by hand before it can pass the required status checks. +# +# `union` resolves that automatically by keeping both sides' additions, which +# is the correct merge for a file nobody edits in place. A genuine duplicate is +# still caught: the changelog validator's CHK_UNIQUE_CONTENT rule flags entries +# more than 90% similar, and +# `scripts/validation/__tests__/changelog-merge-strategy.test.js` fails if this +# line is removed or changed. +CHANGELOG.md merge=union + # Treat images and fonts as binary *.png binary *.jpg binary