{
  "slug": "bw-ai-schema-pro",
  "name": "BW AI Schema Pro",
  "version": "2.1.5",
  "download_url": "https://plugins.bowden.works/wp-content/uploads/plugin-updates/bw-ai-schema-pro-2.1.5.zip",
  "download_hash": "sha256:675aeb64d112870937e4fda77b77658bf934fc913ff642335159df3c10215adc",
  "download_size": 350981,
  "requires": "6.0",
  "tested": "",
  "requires_php": "7.4",
  "last_updated": "2026-05-27",
  "homepage": "https://plugins.bowden.works/bw-ai-schema-pro/",
  "author": "Bowden Works",
  "description": "Advanced Schema Markup plugin optimized for AI era - Enhance your content's visibility and understanding by AI systems with comprehensive schema markup.",
  "changelog": "## [2.1.5] - 2026-05-27\n\n### Fixed (performance)\n- **CRITICAL: Removed `BW_Schema_Team_Member::remove_remaining_schemas()`** and its\n  `add_action( 'wp_head', ..., 0 )` registration in\n  `includes/class-bw-schema-team-member.php`. On team-member pages it ran every\n  `wp_head` and iterated the entire `$wp_filter['wp_head']` table using fragile\n  `strpos()` string-matching to remove \"schema-looking\" hook keys. Both expensive\n  and dangerous (could match legitimate non-schema hooks). The job it was trying\n  to do — disabling other plugins' schema output — is already handled correctly\n  by `BW_Schema_Core::disable_conflicting_schema()`, which uses named filter\n  hooks driven by the `bw_schema_disable_sources` setting. The\n  `remove_all_actions( 'wp_head', 60 )` call in `disable_conflicting_schemas()`\n  is retained — that's the Yoast-priority cleanup, not the catch-all iteration.\n\n- **HIGH: Added transient fallback to `BW_Schema_Cache`** in\n  `includes/class-bw-schema-cache.php` for hosts without a persistent object\n  cache (Flywheel, most shared hosts). Previously `get`/`set`/`delete` used only\n  `wp_cache_*`, which defaults to request-scoped on those hosts — meaning the\n  cache was a no-op and schema regenerated on every page load. The class now\n  routes through transients (DB-backed, persistent across requests) when\n  `wp_using_ext_object_cache()` returns false; sites with a real object cache\n  continue using `wp_cache_*`. `clear_all()` nukes the plugin's own transients\n  via a prefix-scoped DELETE on the options table (no other plugin's data is\n  touched). Cache key prefix is `bw_schema_` so all keys are namespaced.\n\n### Changed (performance)\n- **Consolidated 6 plugin-owned `init` hooks into one wrapper** in\n  `bw-ai-schema-pro.php`. Previously each module (textdomain, Cache, Hooks,\n  Security, Author_Override, Team_Member) registered its own\n  `add_action( 'init', ... )`; WP walks `$wp_filter['init']` on every request,\n  so cutting 6 entries down to 1 (named `init_default_priority_modules`) saves\n  per-request work. **The priority-1 (`maybe_migrate_options`) and priority-5\n  (`disable_conflicting_schema`) registrations remain separate** — they need\n  their explicit priorities.\n\n- **`BW_Schema_Renderer` is now a singleton** (via `get_instance()`). The\n  renderer is stateless — `render()` reads context from globals/options/post\n  meta each call — but was constructed fresh on every `wp_head` and every\n  schema-preview AJAX request. Both call sites in `bw-ai-schema-pro.php` now\n  reuse the shared instance.\n\n### Security\n- `maybe_redirect_to_setup()`: gate the `?skip_setup=1` self-completion of the setup\n  wizard behind `current_user_can( 'manage_options' )`. Previously any logged-in user\n  reaching wp-admin (e.g. a subscriber on their own profile page) could trip the\n  \"setup complete\" flag. Impact was minor (only suppressed the wizard redirect for\n  admins) but it was the single nonce/cap gap surfaced by the 2026-05-06 security\n  audit. See `docs/SESSION-LOG.md` for the audit summary."
}
