=== Launchmind Blog === Contributors: launchmind Tags: blog, content, ai, seo, articles Requires at least: 5.8 Tested up to: 6.9 Requires PHP: 7.4 Stable tag: 5.7.1 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Display AI-powered Launchmind blog content on your WordPress site. == Changelog == = 5.7.1 = * **New: `intro=""` attribute on `[launchmind_blog]` for a per-page custom welcome line.** Replaces the auto-generated "Welcome to {site name}'s blog…" copy with whatever the customer types, without forcing `show_intro="false"` + a separate text block above the shortcode. Plain text only (HTML is escaped) so the markup stays predictable across themes. Empty value falls back to the language-default welcome. Example: `[launchmind_blog intro="Welkom op de blog van Whoon. Hier vind je interieurinspiratie."]`. * **Settings page: Shortcode Reference card rewritten as a copy-paste cookbook.** Adds clearly-labeled examples for the 5.7.0 `wp_post_types` merge, the new `intro=""` attribute, common tweaks (columns, limit, show_excerpt / tags), multilingual usage, Q&A-only, and the single-post shortcode. Each example sits under its own H4 so customers can scan instead of decoding an attribute list. No behavior changes for the existing options. = 5.7.0 = * **New: `[launchmind_blog]` shortcode merges any post type, not just native `post`.** Adds a `wp_post_types` attribute (comma-separated) so customers whose own articles live on a custom post type (e.g. Whoon's "algemene-posts", a news site's "news", a magazine's "stories") can mix them into the Launchmind listing alongside their content. Example: `[launchmind_blog wp_post_types="post,algemene-posts" columns="3" limit="12"]`. Default stays `wp_post_types="post"` so existing installs are unaffected. Sanitized with `sanitize_key()` per slug; empty/garbled values fall back to `post`. Solves the page-builder edge case where Elementor Pro's Posts widget can't easily merge two post types in one query — drop the shortcode into an HTML/Shortcode widget instead and the merge happens server-side. * **Why this matters.** Until now, customers on a custom CPT had to either accept two separate listing widgets (one for their CPT, one for Launchmind) or set up a custom Elementor query ID with PHP. The shortcode now does the merge + chronological sort in one call, no PHP snippet required. = 5.3.6 = * **Fix: `launchmind_article` CPT now passes Elementor Pro's second visibility gate (`show_in_nav_menus`).** v5.3.5 set `show_ui=true` to make our CPT visible to page-builder Source dropdowns, but Elementor Pro filters its post-type list with `get_post_types(['public' => true, 'show_in_nav_menus' => true])` — the standard WP convention for "linkable content types". Our CPT had `show_in_nav_menus=false` so we passed the first gate (show_ui) but failed the second. Customers still saw no "Launchmind Articles" in Elementor Pro Posts widget after 5.3.5. This release flips `show_in_nav_menus` to track `page_builder_compat` so both gates open together. JetEngine was unaffected by either gate; this only matters for Elementor Pro / Loop Grid / similar. * **Side effect: "Launchmind Articles" now appears in Appearance → Menus.** Customers can technically pick a virtual post for a nav menu item, but the picker shows 0 items (we have no real wp_posts rows), so this is a UI no-op rather than a footgun. = 5.3.5 = * **Fix: `launchmind_article` CPT now visible to Elementor Pro + other page-builder Post widgets.** The CPT registration hardcoded `show_ui=false` to keep "Launchmind Articles" out of the WordPress admin sidebar. Side effect we hadn't anticipated: Elementor Pro's Posts widget and Loop Grid filter their Source dropdown by `show_ui`, so our CPT was invisible to them even with Page Builder Compatibility enabled. JetEngine used a looser filter and surfaced our CPT either way, masking the issue for sites that primarily used JetEngine. Fix: `show_ui` now tracks `page_builder_compat` (same as `public`, `publicly_queryable`, and `show_in_rest` already did). Admin sidebar suppression is independently controlled by `show_in_menu=false` so the admin UI stays clean. Customers with Page Builder Compatibility ON will see "Launchmind Articles" in every builder's Source dropdown after this update. * **No customer action needed.** Existing JetEngine / Bricks / Breakdance integrations continue to work unchanged. = 5.3.4 = * **Fix: legacy `lm__` API keys couldn't auto-resolve subscription_id.** v5.3.3's auto-heal path only matched the modern `lm__` key format. Customers who signed up before the UUID switch have keys shaped like `lm_broadwick_f7670e70...` — the slug isn't parseable into a subscription UUID locally, so the regex returned empty and tracking stayed off even after v5.3.3 shipped. Adds a server-side fallback: when the regex doesn't match, the plugin calls `/blog/customer` (already authenticated via api_key) and reads `subscription.id` from the response. Result is cached via transient (12h TTL) so it costs at most one API call per half-day. Server endpoint now returns `subscription.id` in its response shape — read by 5.3.4+ plugins, ignored by older ones. * **Backward compatible.** Existing UUID-format installs skip the network call entirely (regex hits first). Sites where neither path resolves (no api_key set) still surface the admin notice from 5.3.3. = 5.3.3 = * **Fix: tracking beacons silently dropped on installs where `subscription_id` was wiped (typically after a plugin re-install).** Pre-5.3.3, the JS beacon, server-side PHP beacon, and diagnostic meta tag all read the `subscription_id` plugin option directly and bailed out when empty — even though the api_key (which stays put across re-installs) embeds the subscription UUID in its `lm__` format. New helper `resolve_subscription_id()` returns the option when set, parses the UUID from api_key when not, and persists the derived value back into the option so subsequent calls skip the regex. Affected installs auto-heal on the next page-load — no customer action needed. * **Always-on analytics tracking — `enable_analytics` UI toggle removed.** The "Enable analytics tracking" checkbox in plugin settings is gone. Tracking is mandatory: partner dashboards exist precisely to show traffic, so a hidden off-switch was a footgun (the most common path was a customer flipping it off while testing then forgetting). Sites that have legitimate reasons to suppress server-side beacons can use the `launchmind_disable_server_pageview` filter (PHP) — that path stays. The `launchmind_blog_enable_analytics` option in the database is now ignored; cleaning it up is a no-op. * **Admin notice when subscription_id can't be resolved.** When BOTH the option is empty AND the api_key is missing/malformed (the only state where tracking is genuinely impossible), an amber warning surfaces in WP admin pointing the customer at the settings page. Previously these installs sent zero traffic with no UI signal — partners assumed their site had no visitors when actually the plugin couldn't beacon anything. * **JS beacon detection widened.** `inject_tracking_script_on_footer` no longer gates on `$GLOBALS['launchmind_blog_post_data']` alone — it now uses the same three-path detection (CPT singular | query var | post object) the PHP beacon got in 5.3.2. Sites where the global wasn't populated for the active render path (CPT-singular templates served without the legacy slug interceptor) still get JS beacons. = 5.3.2 = * **Fix: PHP server-side pageview beacon never fired on legacy slug-rendering sites.** v5.2 introduced a server-side PHP beacon that was supposed to count pageviews even when the JS beacon got stripped (cache plugins, ad-blockers). In practice it was hooked to `template_redirect` priority 20, which fires *before* the content/template filters that populate `$GLOBALS['launchmind_blog_post_data']` under the legacy slug-interception render path. Result: on every site using legacy rendering (which is most of them), the `is_singular('launchmind_post') || get_query_var('launchmind_post')` gate failed, `register_shutdown_function` was never registered, and the beacon silently never fired — observed across all 4 active WP installs over the 14 days following the v5.2 release. Hook moved to `wp_footer` priority 999 (the same hook the JS beacon already uses), and the gate now reads `$GLOBALS['launchmind_blog_post_data']` like the JS beacon does. Coverage now matches the JS beacon: any render mode that produces a Launchmind article — CPT singular, virtual query, or legacy slug interception — sends one server beacon per render. Bot UA filter, opt-out filter, and the existing 30s dedup window with the JS beacon are all unchanged. * **Plugin version added to beacon body.** Both PHP and JS beacons now ship `plugin_version` in the request body, persisted to event metadata. Lets the backend monitor plugin adoption per customer and verify that fixes like this one are landing on installed sites. = 5.3.1 = * **Fix: PHP error on ACF fields that don't return a string.** The 5.3.0 ACF compatibility hook included a catch-all `add_filter('acf/format_value', 'do_shortcode', 10);` that fired for *every* ACF field type, including ones that return arrays, integers or booleans (image, gallery, repeater, true/false, number, relationship, etc.) — and `do_shortcode()` expects a string. On sites with non-string ACF fields the result was a PHP warning per render, plus the field returning an empty string instead of its actual value. The catch-all has been replaced with a `type=text` scoped filter, matching the existing `type=textarea` and `type=wysiwyg` filters. Net effect: shortcodes still work in `text`, `textarea` and `wysiwyg` ACF fields, but other field types return their native value untouched. Reported by @pippijn on the WordPress.org support forum. = 5.3.0 = * **Q&A articles get their own filter path — three flavours.** Articles produced by the new Voice Engine cycle now arrive with `is_voice_article: true` and an auto-injected "Q&A" tag. Three independent ways for customers to put them on a separate page without writing a line of code: (1) **native taxonomy** — WordPress automatically maps the "Q&A" tag into a category/post_tag term, so `/category/q-and-a` (or whatever slug the host theme generates) lists Q&A articles out of the box; (2) **shortcode** `[launchmind_voice limit="6" columns="3"]` — drop on any WP page, behaves exactly like `[launchmind_blog]` but pre-filters to Q&A articles only; (3) **block toggle** — the existing "Launchmind Blog" Gutenberg block gains a "Show only Q&A articles" toggle in the Filter panel. All three live alongside each other; pick the one that matches your editing workflow. * **Optional "Hide Q&A from main listing" setting.** New toggle under Settings → Voice Engine. OFF by default (existing sites keep current behaviour: Q&A articles appear in the main blog listing). Enable it on sites that want a clean separation — Q&A articles only render on the dedicated `[launchmind_voice]` shortcode, the "Show only Q&A articles" block, or `/category/q-and-a`. Recommended OFF for most sites; flip it ON when you want a dedicated /qa page that stays distinct from the main blog flow. * **Backward-compatible API gate.** `is_voice_article` is read with a graceful fallback: when the upstream Launchmind API hasn't yet shipped the field on an older deploy, the plugin re-checks the article's `tags` array for "Q&A". So sites running 5.3.0 against an older API still get correct filtering. No breaking change for any existing shortcode, block, or template — adding 5.3.0 to a 5.2.x site that doesn't use Q&A articles is a silent no-op. = 5.2.0 = * **Server-side pageview beacon.** The plugin now POSTs a pageview from PHP on every actual Launchmind article render — independent of JavaScript, cache plugins, or render-mode globals. Until 5.2 every pageview was tracked client-side via `navigator.sendBeacon`, which silently dropped on sites where (a) LiteSpeed Cache / WP Rocket JS-optimization stripped or deferred the inline script, (b) the page was rendered through a CPT path where `$GLOBALS['launchmind_blog_post_data']` wasn't populated and the JS injection self-guard returned early, or (c) the visitor used an ad-blocker that flagged `/api/tracking/plugin-view` as a tracker domain. Affected sites showed zero traffic in their partner dashboards despite real visitors. The PHP-side beacon fires from `template_redirect` (deferred to `register_shutdown_function` so it never blocks the response), resolves the article slug via three independent paths (CPT global → query var → post object), filters out common bot User-Agents, and POSTs the same `{subscription_id, article_slug, platform, event_type, source: "server"}` payload the JS beacon already sends. Customers who want PHP tracking off can opt out with `add_filter('launchmind_disable_server_pageview', '__return_true');` (the JS beacon stays under the existing `enable_analytics` setting). * **Backend dedup window keeps partner counts honest.** When a v5.2 site fires both beacons for the same pageview (PHP from render + JS from the rendered page), the backend now dedups on `(subscription_id, article_slug, ip_hash)` within a 30-second window: the first beacon to arrive increments the partner counters, the second is recorded for audit but skips the increment RPCs. End result for partners: exactly one pageview per visitor, regardless of which side succeeded — sites that previously showed zero traffic because of a stripped JS beacon now count via PHP, sites with healthy JS keep counting via JS, and sites with both running don't double-count. * **Diagnostic meta tag in `wp_head`.** Adds `` on Launchmind article pages so support / debugging tools can verify a page is actually rendered by the plugin without trawling logs. Self-guards on the same `is_singular` check as the beacon — no-op on non-Launchmind pages. * **Click beacon labelled `source: "client"`** for symmetry with the new server-side flag. No behavior change — clicks remain JS-only because outbound link interception requires a browser-side DOM listener. * No PHP API, database, settings, shortcode or rendering-mode change. Drop-in upgrade for every existing 5.1.x install. Customers running aggressive cache plugins (LiteSpeed Cache, WP Rocket, Cloudflare APO) should see partner-dashboard pageviews start appearing within minutes of upgrade for the first time since installing the plugin. = 5.1.11 = * **WhatsApp / Telegram / Signal link previews now show the cover image.** Partner-uploaded cover images for Launchmind articles are typically 2-4 MB high-resolution PNGs (great for in-article rendering on desktop, problematic for chat-app preview crawlers). WhatsApp's preview crawler skips images larger than ~1 MB and fails outright above ~5 MB; LinkedIn and Facebook handle the same images fine because they re-process server-side at higher size limits. 5.1.11 detects when the cover image is hosted on Supabase Storage (the default for Launchmind partner uploads) and rewrites the og:image / twitter:image URL to use Supabase's `/render/image/public/` transformation endpoint with `width=1200&height=630&quality=80&resize=cover`. The transformed URL serves a ~150 KB JPG that all major chat-app preview crawlers accept, while the in-article body image still uses the raw high-resolution URL. Non-Supabase image hosts (Pexels, custom CDNs, theme-hosted) are left untouched — they either auto-optimize or already serve appropriately sized files. No new options. * **Settings tab now documents the unified-blog shortcode pattern.** The Shortcode and Examples sections now show the `[launchmind_blog limit="6" columns="2" include_wp_posts="true"]` pattern that merges native WordPress posts and Launchmind articles into one date-sorted grid with built-in pagination. The Shortcode Options table also documents `include_wp_posts` and `offset` parameters that were previously only discoverable via reading the source. Recommended for sites built with Breakdance / Elementor / Bricks / Oxygen where the native Post Loop widget renders Launchmind cards without images or pills (the builders fetch post meta via raw SQL that bypasses the WordPress filter API our virtual posts depend on). One shortcode line replaces the broken builder integration. = 5.1.10 = * **Two new routes for page-builder Post Loop coverage.** Cards in Breakdance Post Loop / Elementor Pro Posts / Bricks Query Loop / Oxygen Repeater were rendering with title, date, and link but missing the cover image and category pill, even though our 5.1.6+ filter chain on `get_post_metadata`, `image_downsize`, `wp_get_object_terms`, and friends should have intercepted those calls. Two additions in 5.1.10: (1) the `launchmind_article` CPT now declares `category` and `post_tag` as supported taxonomies, so page builders that gate term-fetching on the CPT's declared taxonomy support no longer skip our posts; (2) when virtual posts are injected via `the_posts`, the WordPress object cache is now prewarmed with `_thumbnail_id` meta for each post, so builders that consult `wp_cache_get` before falling back to raw SQL still find the correct sentinel ID. Strictly additive — installs that already render fine on 5.1.6/5.1.7/5.1.8/5.1.9 keep working unchanged. = 5.1.9 = * **Topbar contrast fix for builder-managed "sticky on scroll" headers.** The 5.1.5/5.1.7 sticky-offset + brand-colour backdrop only fired for headers whose CSS `position` was already `fixed` or `sticky` on first paint. Many Breakdance, Elementor Pro, and Divi sites build their topbar as `position: absolute` over a transparent hero and only switch it to `position: fixed` *after* the visitor scrolls — those builders use class names like `bde-header-builder--sticky-scroll-`, `elementor-sticky`, `et_pb_sticky_module`. Until 5.1.9 our detector skipped them, so the article-page sticky-offset was never reserved and the topbar text would clash with the article body's contrast as soon as the visitor scrolled. 5.1.9 extends the detector to also accept absolute/relative headers whose class names match these builder-sticky patterns (or that carry a `data-sticky` attribute), so the offset and the brand-colour backdrop both apply. No-op on installs that don't use any sticky-on-scroll header. = 5.1.8 = * **Page-builder pill cleanup + extra term-resolution coverage.** Two fixes for builders that join multiple category names into a single concatenated pill string (Breakdance Post Loop is the canonical example): (1) the virtual `category` / `post_tag` terms emitted for Launchmind cards are now capped at **2 tags max**, with **2 words max per tag**, so long-tail Launchmind keywords ("data science bureau", "AI oplossingen bedrijf") never collide into an unreadable run-on label; the caps are filterable via `launchmind_blog_card_max_tags` / `launchmind_blog_card_max_words` for themes that do render multi-pill cards. (2) Term injection now also hooks the lower-level `wp_get_object_terms` filter as a backup path — some builder configurations resolve term relationships via direct calls that bypass `get_the_terms`, which would leave virtual cards with empty pills despite our `get_the_terms` filter being in place. No breaking changes; existing sites that already render fine on 5.1.6/5.1.7 keep their current pill layout (just with the tighter caps applied). = 5.1.7 = * **Auto-detect brand colour for the article-page topbar backdrop.** 5.1.5 added a contrast-safe but neutral backdrop (#101418 / #f5f5f5) behind the topbar on single-article pages whenever the host theme's topbar text was about to disappear against the article body. 5.1.7 makes the backdrop **brand-aware** by sampling the topbar's actual background colour on the homepage and using that on the article page. Mechanism: the sticky-detect script loads `/` in a hidden same-origin iframe during browser idle time, runs the same topbar detector inside that document, walks the topbar's ancestor chain to find the first non-transparent background, and writes the resulting colour to localStorage with a 24-hour TTL. First visit shows the neutral fallback for ~1 second while sampling completes, then swaps to the brand colour with a smooth 220ms transition; every subsequent visit applies the cached brand colour instantly on first paint. Resolution order: theme-set `--launchmind-article-topbar-bg` CSS variable wins, then cached brand colour, then neutral contrast fallback, then async homepage sample. Customers can still override with a single CSS line if the auto-detection picks the wrong region. The whole feature degrades silently — sites that block iframe-of-self via `X-Frame-Options DENY`, themes without a sticky topbar, or homepages where the topbar genuinely is the article-body colour all stay on the neutral fallback (or no backdrop at all). No new options, no admin setup, no breaking changes for installs that don't have a topbar contrast collision. = 5.1.6 = * **Critical fix for 5.1.5 page-builder rendering.** 5.1.5 introduced cover-image and category-pill rendering for Launchmind articles inside page-builder Post Loops, but on real Breakdance / Bricks / Oxygen sites the cards still rendered without an `` and with an empty pill. Root cause: the `virtual_thumbnail_id`, `virtual_thumbnail`, `virtual_has_thumbnail`, and `virtual_terms` filters all called `get_post($virtual_id)` to resolve the article, but virtual Launchmind posts have no `wp_posts` row — so on a cold object cache the call did a DB lookup, returned null, and the filters silently returned the original (empty) value. Page builders therefore got `_thumbnail_id = 0` and an empty term list and skipped emitting the inline `