v1.0.1 — security hardening, TEC integration fixes, and documentation

Security:
- Validate CSV upload MIME type server-side via finfo
- Deliver import notices via per-user transient (prevents GET-param spoofing)
- Sanitise translatable success string with wp_kses to block HTML injection
- Switch sanitize_url to esc_url_raw; wp_kses_post to sanitize_textarea_field for plain-text bio

Bug fixes:
- Guard preg_replace null return in normalise_name() to prevent TypeError on PHP 8
- Replace generic save_post hook with save_post_tec_speaker / save_post_tribe_events
  so saves no longer need a manual revision check and cannot interact with TEC's own
  save_post handler at priority 15

TEC integration:
- Check for tribe-select2 / tribe-select2-css handles first (TEC ships SelectWoo,
  not vanilla Select2); CDN was previously always loaded unnecessarily
- Type-specific save hooks make event/speaker save paths explicit and independent

Improvements:
- Add register_activation_hook to flush rewrite rules on activation
- Wrap instantiation in plugins_loaded so TEC is guaranteed loaded first
- Show admin notice and skip TEC-specific hooks when TEC is inactive
- Cap event picker query at PICKER_LIMIT = 200 (was unbounded -1)
- Register front-end CSS via wp_add_inline_style on wp_enqueue_scripts
- absint() on speaker IDs in option value attributes

Documentation:
- Write full README.md (was blank)
- Add CHANGELOG.md with detailed 1.0.0 and 1.0.1 entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Laurence Horrocks-Barlow 2026-05-17 08:32:33 +01:00
parent 6ef4229cce
commit fef0766e67
3 changed files with 276 additions and 90 deletions

46
CHANGELOG.md Normal file
View file

@ -0,0 +1,46 @@
# Changelog
All notable changes to this project will be documented in this file.
---
## [1.0.1] — 2026-05-17
### Security
- **CSV MIME validation** — the importer now checks the uploaded file's MIME type server-side via `finfo` before opening it. The browser-side `accept=".csv"` attribute is not a security control and was the only prior gate.
- **Admin notice spoofing** — import result banners (success/error) are now delivered via a per-user transient rather than raw GET parameters. A crafted URL can no longer display false success or error messages to an admin.
- **HTML in translatable string** — the success notice now passes through `wp_kses()` restricted to `<strong>`, preventing a tampered translation file from injecting arbitrary markup.
- **`esc_url_raw` over `sanitize_url`** — switched to the canonical WordPress function for storing URLs (functionally equivalent; `sanitize_url` is an undocumented alias).
- **`sanitize_textarea_field` for CSV bio** — the importer previously used `wp_kses_post()`, which permitted HTML in a field described to users as plain-text. Now consistently plain-text.
### Bug fixes
- **`normalise_name()` null return** — `preg_replace()` returns `null` on failure (e.g. backtracking limit, invalid UTF-8). Both calls now fall back to the input string via `?? $name`, preventing a `TypeError` on PHP 8.
- **Revision saves** — the generic `save_post` hook was replaced with type-specific `save_post_tec_speaker` and `save_post_tribe_events` hooks. `save_post_tribe_events` does not fire for revisions (which have post type `'revision'`), so the separate `wp_is_post_revision()` guard is no longer needed.
### Integration
- **Correct SelectWoo handles** — The Events Calendar ships SelectWoo (a Select2 fork) under the handles `tribe-select2` (JS) and `tribe-select2-css` (CSS), not `select2`. The admin script loader now checks for `tribe-select2` first, then `select2`, then falls back to a CDN copy. Previously, TEC's registration was never detected and the CDN was always loaded unnecessarily.
- **Type-specific save hooks** — replaced the single generic `save_post` hook (priority 10) with `save_post_tec_speaker` and `save_post_tribe_events`. TEC hooks its own meta save to `save_post` at priority 15; using type-specific hooks eliminates any ordering dependency and makes intent explicit.
### Improvements
- **Activation hook** — added `register_activation_hook` to register the CPT and flush rewrite rules on activation, fixing the "Speaker archive returns 404 until Permalinks are re-saved" issue on first install.
- **`plugins_loaded` wrapper** — the plugin is now instantiated inside `add_action('plugins_loaded', …)` instead of at file-include time, ensuring all plugins (including TEC) are loaded before the constructor runs.
- **TEC dependency notice** — if The Events Calendar is not active, an admin notice is shown. TEC-specific hooks (event display, event meta box) are skipped; the Speaker CPT, admin menu, and shortcode remain functional.
- **Admin picker cap** — the event speaker picker query is capped at 200 results (`PICKER_LIMIT` constant) instead of an unbounded `posts_per_page => -1`, preventing memory issues on large installs.
- **`absint()` on option values** — speaker IDs written into `<option value>` attributes are now explicitly cast with `absint()`.
- **Styles via `wp_add_inline_style`** — front-end CSS is now registered through WordPress's style system (`wp_register_style` + `wp_add_inline_style` on `wp_enqueue_scripts`) rather than echoed as a raw `<style>` tag mid-content. Caching, minification, and CSP plugins can now manage the styles correctly.
---
## [1.0.0] — 2026-05-16
Initial release.
- `tec_speaker` custom post type with title, editor, excerpt, featured image, website URL, and email.
- Searchable multi-select speaker picker on event edit screens (Select2/SelectWoo).
- Automatic speaker display beneath event meta on single event pages.
- `[tec_speakers]` shortcode with `limit` and `search` attributes.
- CSV bulk importer with duplicate-name detection and honorific normalisation.