# BW Dev

Bowden Works dev toolkit. One plugin replaces five — favicon override, sticky elements, admin list columns, post link blocks, and YouTube embeds — with a unified settings page and a white-label layer for client-facing block names.

## Requirements

- WordPress 6.0 or later
- PHP 7.4 or later

## Installation

1. Download the latest release zip from https://plugins.bowden.works/bw-dev/.
2. In WP Admin, go to Plugins → Add New → Upload Plugin.
3. Upload the zip and activate.

Once installed, BW Dev checks for updates automatically from `plugins.bowden.works`.

## Settings

Everything lives at **Settings → BW Dev** as a tabbed page:

- **Modules** — enable/disable any of the five modules. Disabled modules register zero hooks, zero assets.
- **Branding** — set the plugin's display name, the block category label, and the block title prefix for white-labeled client sites. The defaults are `BW Dev`, `BW Blocks`, and `BW `.
- One tab per enabled module (see below).

## Modules

### Favicon

Inject a custom PNG/ICO favicon at priority 9999 on the frontend, admin, and login head — overriding theme defaults like Kadence. Media-picker UI with live preview.

### Sticky Elements

Make any element on the page stick on scroll. Per-element top offset, margin-bottom, z-index, push-up target, mobile-disable + breakpoint. Frontend script only loads when at least one element is enabled.

### YouTube Block

A dynamic Gutenberg block `bw-dev/youtube` that pulls a YouTube URL from an ACF field on the current post and renders a responsive 16:9 iframe. Falls back to the post's featured image if no URL is set. Each block instance can override the ACF field name from its sidebar; the global default lives in the settings tab. Also registers a shortcode `[bw_dev_youtube]` with a backwards-compat alias `[bw_youtube]`.

### Post Link Block

Two server-rendered Gutenberg blocks for nicely-formatted post link lists:

- **BW Post Link List** (parent) — Simple (bulleted text) or Thumbnail (16:9 image + title) layout; configurable thumbnail width.
- **BW Post Link Item** (child) — pick from any public post type (with custom title / thumbnail overrides) or use an external URL.

REST endpoint `GET /wp-json/bw-dev/v1/post-types` lists eligible post types (requires `edit_posts`).

### Admin Columns

Configure custom columns on `edit.php` post list tables per post type:

- Featured image with click-to-change AJAX media picker.
- Taxonomy columns (any registered taxonomy).
- Custom-field columns (scanned from `wp_postmeta`, optionally including private `_*` keys). Sortable.

Sub-tab nav per post type inside the Admin Columns settings tab.

### Admin Note

Adds an internal "Admin Note" to posts and pages, visible only to logged-in users in the block editor. Notes appear:

- In a sidebar Document panel (PluginDocumentSettingPanel) for editing.
- As a prominent yellow banner above the first block when set.

Useful for editor instructions, page documentation, warnings, and notes-to-future-self. Notes are never rendered on the frontend. Per-post-type enable in the settings tab (default: `page`); a notes index table lists every post/page with a note.

The post-meta key is `_bw_admin_note` (not re-prefixed for compat with the legacy standalone plugin — existing notes survive migration).

### SVG Upload

Lets WordPress accept `.svg` and `.svgz` uploads. Two safety gates:

1. **Role-based capability check** — default "Administrators only"; toggle to "anyone with upload_files capability" (editors, authors, etc.) if needed.
2. **Sanitization on every upload** — `<script>`, `<foreignObject>`, `<iframe>`, `<embed>`, `<object>`, `<animate>`, `<set>`, and `<a>` elements are stripped; all `on*` event attributes are removed; `href`/`xlink:href` values with `javascript:` or `data:` protocols are dropped; `style` attributes with `expression()` or `javascript:` are dropped; XML processing-instructions and external DTDs (XXE) are stripped.

No Composer dependency — sanitization uses vanilla `DOMDocument`. For high-security sites a dedicated library (`enshrined/svg-sanitize`) is more comprehensive; the BW Dev sanitizer covers the well-known attack vectors only.

## White-label

The Branding tab controls three client-facing strings:

- **Plugin display name** — what shows in the WP plugins list and as the Settings submenu label.
- **Block category label** — the Gutenberg category that groups all bw-dev blocks in the inserter.
- **Block title prefix** — prepended to every bw-dev block title (e.g. `BW YouTube Embed` → `Acme YouTube Embed`).

The block category slug (`bw-dev-blocks`) is stable — only the label changes — so saved post content survives any rebrand. Block-level rewrites happen client-side at `blocks.registerBlockType` filter time; nothing is persisted into the saved markup.

The `bw_dev_brand` PHP filter lets mu-plugins force a brand without persisting it to options.

## Migration from the legacy plugins

BW Dev replaces five legacy plugins. On activation, BW Dev reads their options (`bw_favicon_url`, `bw_sticky_settings`, `bw_youtube_embed_settings`, `bw_admin_column_settings`) and copies them into the unified `bw_dev_settings` schema. The migration is idempotent and conservative — it only fills in module keys that aren't already populated.

The legacy plugins are **not auto-deactivated**. After verifying each module, deactivate the corresponding source plugin manually.

For sites with custom CSS or saved post content referencing the legacy class names / block names, see `docs/KNOWN-ISSUES.md` for the rewrites you'll need to perform.

## FAQ

**Q: Where do updates come from?**
A: Direct from Bowden Works at `plugins.bowden.works`. No WP.org involvement.

**Q: How do I report a bug?**
A: See `SECURITY.md` and `CONTRIBUTING.md` in the bw-plugins repository.

## License

GPL-2.0-or-later. See `LICENSE`.
