Per-tenant URL prefix sync — admin preview links match your real prefix
* The plugin’s URL prefix has been tenant-configurable since 3.1.0 (you can change /hub/ to anything you want in Settings → Oria Site Sync), but Oria’s admin dashboard had no way to know what you set, so preview links generated in Oria assumed /hub/ and broke for tenants who customized the prefix to something else (e.g. /content/ or /resources/).
* Now: every branding ping the plugin sends to Oria includes the configured url_prefix and the plugin version. Oria stores them per-tenant and uses them when building admin preview links, so the link in your Oria dashboard always points at the correct path on your WordPress site.
* No tenant action required — the plugin reports automatically on every branding refresh. Existing installs catch up the next time the 5-minute branding cache expires (or sooner if you click “Refresh now” in Settings → Oria Site Sync).
Smarter menu injection — label-aware duplicates + cleanup of disabled pages
* “Add enabled Oria pages to my menu” now detects duplicates by label, not just URL. Before this, a tenant’s existing “Blog” menu item pointing at /blog wasn’t recognized when the plugin tried to add a new “Blog” pointing at /hub/blog — different URLs, same intent, two menu entries. Now the plugin skips by label OR URL match (case-insensitive).
* Menu injection now removes menu items that point at hub URLs whose features are currently disabled. So after toggling Services Hub, Contact Form, or Chat off in Oria’s settings, the next click of “Add enabled Oria pages” cleans them out of the WordPress menu instead of leaving orphans.
* Forces a fresh branding-cache read every time the button is clicked, so tenants don’t have to wait 5 minutes after changing feature toggles in Oria for the menu inject to reflect the new state.
* Notice now reports added / skipped / removed counts after every run.
Respect Oria feature toggles on WordPress sites + one-click menu injection
* Site Sync now reads enabledFeatures from Oria’s /api/connect/branding response and only serves the approved hub pages on the tenant’s WordPress site. Disabled pages fall through to a proper HTTP 404 instead of rendering thin duplicate content.
* Added plugin-admin visibility for every Oria page: the Settings → Oria Site Sync “Your Pages” table now shows enabled / disabled state read-only and points tenants back to Oria’s Settings → Website Features panel as the source of truth.
* Added a “Refresh now” button so tenants can clear the 5-minute branding cache after changing website-feature toggles in Oria.
* Added a one-click “Add enabled Oria pages to my menu” action. Tenant picks an existing WordPress menu (Primary, Footer, etc.) and the plugin adds custom-link items only for currently enabled Oria pages, skipping duplicates safely on re-run.
* Branding cache key now includes the plugin version, so a 3.6.3 → 3.6.4 upgrade forces a fresh feature-config fetch instead of waiting for the old transient to expire.
Capture leads from your existing WordPress contact forms into Oria CRM
* Site Sync now connects your existing WordPress form (the one already on your contact page) to the Oria CRM. Every submission lands in your Oria leads dashboard automatically — without rebuilding the form, without changing your theme, without any tenant configuration. Triggers the full lead automation chain: instant SMS to you, welcome email to the homeowner from your Gmail, and the 4-stage nurture sequence.
* Hooks the top 5 WordPress form plugins via their official action APIs:
* Contact Form 7 — wpcf7_mail_sent (after successful send)
* WPForms — wpforms_process_complete
* Gravity Forms — gform_after_submission
* Elementor Pro Forms — elementor_pro/forms/new_record
* Forminator — forminator_form_after_save_entry
* Field detection uses heuristics (substring matches + shape detection — does this look like an email? a phone number?) so it works regardless of how you named your form fields. Anything that can’t be auto-mapped goes into the message body so you still see the full form context.
* Fire-and-forget API call — the form submission never blocks waiting for our server. Every adapter is wrapped in try/catch so a parse error here can never break the form for a real homeowner. If the relevant form plugin isn’t active, the hook simply never fires.
Title override on SSR pages + smart popular-service links (no more 404s)
* Title filter now uses the SSR-rendered title when an Oria page is being served. Before this, city pages like /areas/mountain-view rendered with the theme’s bare WP title (e.g. just “Alon”) instead of the proper “Alon Design and Remodeling in Mountain View” — losing all the SEO benefit of the page title we generate.
* Sibling server fix in renderServiceArea: popular-service links on city pages now check whether the city × service page actually exists for the tenant. When the page exists (e.g. Berkeley × ADU), link goes to it. When it doesn’t exist yet (Mountain View hasn’t had its service combos generated by the scan), link goes to /contact?project_type=…&city=… so the click flows into the lead funnel instead of dead-ending at a 404.
Route bare /areas/{city} city-only landing pages
* Authority Radar’s “Run full scan” generates rich city-only landing pages (Mountain View, San Mateo, Palo Alto, etc.) at /areas/{city} with homeowner_scenario, costTable, neighborhoods, FAQ, and trade-aware content. Before this release the plugin only routed the two-slug /areas/{city}/{service} pattern, so every bare city page returned 404 — content was generated and indexed in the database but no public URL reached it. Now /areas/{city} routes to the same SSR rendering layer used by /hub/areas, surfacing the full per-city content. Sibling fix in the server’s renderServiceArea ensures the actual generator field schema is rendered (was previously reading wrong field names and falling back to generic defaults).
Tenant-aware SEO meta + JSON-LD schema injection
* Site Sync now upgrades the SEO of any tenant’s WordPress homepage with the same treatment Oria-hosted sites receive: trade-aware title (e.g. “Roofer in Los Angeles, CA | Vision Roofing”), tenant-specific meta description from your About copy, trade + city keywords (no platform-keyword leak), full Open Graph + Twitter Card meta, and trade-specific JSON-LD schema (RoofingContractor / Plumber / SolarPanelInstaller / etc.) with REAL areaServed cities from your service markets — not the generic “United States” most plugins emit.
* Conflict-aware: detects Yoast, RankMath, and All-in-One SEO. When one is active we DEFER for title/description/og (let your existing SEO plugin own them) but still inject our trade-specific JSON-LD because ours has the correct @type and real city coverage that generic SEO plugins don’t generate.
* Static SEO body block on the homepage — invisible to humans (offscreen via CSS), visible to crawlers. Includes H1 with trade + city, license number, top 3 testimonials. Helps Google understand the page even before your theme’s React/JS renders.
* Image alt-text filler — when an image on your site has an empty alt attribute, the plugin fills it with “{Your Company} — {trade} project” (never overwrites alt text you’ve set yourself). Helps both SEO ranking and accessibility.
* Robots meta — emits index, follow, max-image-preview:large so Google can show large image previews in search results.
* Single API call to oriamarketing.com cached 6 hours via WordPress transient. Same architecture as the pixel injection in 3.5.0 — fast, safe, every branch try/catch wrapped so SEO injection failure can never WSOD the site.
Primary navigation menu now shows on Oria SSR pages + edge cache safety
* Themes that switch nav menus by page type (common on contractor themes with a “demo” menu and a configured primary menu) previously fell back to the demo menu on Oria SSR pages — Alon’s site showed “Pages → Portfolio → Our Work 1/2/3” on /hub/blog instead of his primary Services/About/Gallery menu. Root cause: our SSR pages did not set WP_Query flags, so is_page(), is_singular(), and is_front_page() all returned false and the theme’s menu logic fell through to defaults. Fix: oria_sync_pose_as_page() sets $wp_query->is_page = true + is_singular = true before get_header() so the theme sees Oria pages as first-class singular pages.
* Replaced nocache_headers() on SSR routes with Cache-Control: public, max-age=300, must-revalidate, stale-while-revalidate=60. Before this, if an edge CDN (Cloudflare, GoDaddy Gateway) ever cached a transient error response, the cache would stick for whatever the host’s default TTL was — Alon’s site had a stale 301→404 cached by Cloudflare for 31 days. Short explicit TTL means any incident self-heals within 5 minutes.
SEO audit fixes — article timestamps, robots directive, absolute logo/image URLs
* Added <meta name=”robots” content=”index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1″ /> on every SSR surface. Before, the plugin relied on Google’s default behavior and emitted no explicit directive; being explicit unlocks the Googlebot “large image preview” behavior in SERPs and signals intent to other crawlers (Bing, DuckDuckGo, Yandex).
* Emit <meta property=”article:published_time”> and <meta property=”article:modified_time”> on SSR pages. The server already provides these timestamps in the render fragment; before 3.5.6 the plugin dropped them. These feed Google’s Article / BlogPosting Rich Results freshness signal.
* Server-side: every logoUrl, og_image, and JSON-LD image/logo reference is now wrapped in an absolute-URL helper (toAbsoluteMediaUrl). Previously many references leaked relative paths into structured data and Open Graph tags — Google can’t resolve /api/blog-images/… against the tenant’s domain, so the image references were effectively broken. Affects BlogPosting publisher logo, Article image, Project/CreativeWork image, og_image on every surface. Deployed alongside plugin 3.5.6.
Hotfix — /hub/blog and other SSR pages rendered as visually empty in 3.5.4
* 3.5.4 enqueued embed.css on SSR pages (blog list, reviews, case-studies, projects) so the oria-card grid would get styled. But embed.css contained a rule, designed for the iframe embed pages, that hid every direct <div> child of body except the topbar and iframe wrapper. On SSR pages that rule silently hid every theme wrapper div — the blog section was in the HTML but invisible. Scoped the rule to body.oria-iframe-mode so it only applies on iframe pages, and added that class to the iframe renderer’s body tag.
Branded topbar on iframe pages + styled SSR content
* Iframe embed pages (/hub/book, /hub/chat, /hub/ai-viz, etc.) now show the tenant’s custom logo (Appearance → Customize → Site Identity → Logo) in the topbar instead of plain site-name text. Falls back to the site icon at 96px, then to the site name, so every tenant gets something rendered regardless of their theme setup.
* SSR blog, reviews, case-studies, projects, and service-area list pages now enqueue the embed stylesheet. Before this release the rendered fragments had zero CSS rules for .oria-card, .oria-grid, .oria-ssr-content etc. — the theme’s default .entry-content typography gave huge headings and images ran at native width. Now list pages render as proper card grids with consistent typography, cover-cropped images, and mobile breakpoints.
Fix iframe embed page layout (missing stylesheet)
* The iframe-based embed pages (/hub/book, /hub/chat, /hub/ai-viz, etc.) rendered unstyled — massive unconstrained SVG arrow, broken layout. Root cause: the plugin exits at the parse_request hook (priority 0) to beat WordPress routing, but that fires before wp_enqueue_scripts, so the style + script handles registered by oria_sync_register_embed_assets() didn’t exist yet when the iframe renderer tried to enqueue them. Result: wp_enqueue_style() silently no-op’d and no <link> tag was emitted.
* Fixed by self-registering the embed asset handles on-demand inside the iframe renderer if they aren’t registered yet — preserves WP.org Guideline 13 compliance (still using registered handles + wp_enqueue_style / wp_print_styles), just closes the hook-ordering gap.
Second critical stability fix — root cause of the 3.5.0 / 3.5.1 WSOD
* Fixed Call to undefined method WP_Sitemaps::add_provider() fatal that crashed every install with an API key set. The 3.4.0 sitemap hook used $sitemaps->add_provider(), which is not a method on WordPress Core’s WP_Sitemaps class — the correct API is wp_register_sitemap_provider(). This was the real cause of Alon Design & Remodeling’s WSOD on 2026-04-24 (the 3.5.1 try/catch wrappers didn’t cover this because the bug lives in the wp_sitemaps_init closure, not in the new 3.5.0 pixel code). Now wrapped in try/catch as defense-in-depth.
* Every named plugin function is now declared inside an if ( ! function_exists( … ) ) guard, preventing the “Cannot redeclare oria_sync_*” fatal that occurred when an older pre-WP.org install of the same functions (via the WPCode snippet) was still present alongside the new plugin. Tenants migrating from the snippet can now safely activate the plugin without first manually removing the snippet.
Critical stability fix — prevents WSOD on PHP 8+
* 3.5.0 crashed Alon Design & Remodeling’s site (GoDaddy Managed WordPress, PHP 8+) on 2026-04-24 when the pixel-config API call returned null. PHP 8 is stricter about accessing offsets on null, and the line empty($cfg[‘metaPixelId’]) threw a TypeError.
* Every new function from 3.5.0 is now wrapped in try/catch at the outer boundary — pixel config fetch, pixel injection, and transient cache flush. Any runtime error is logged via error_log() and silently skipped rather than bubbling into a fatal.
* Added oria_sync_safe_array_get() helper for null-safe nested array access.
* All variable types are validated explicitly (is_array, is_string) before use — no more implicit coercion surprises.
* Closures replaced with named functions (oria_sync_inject_pixel, oria_sync_flush_pixel_cache) so WordPress can cleanly unregister the hooks if the plugin deactivates mid-request.
Automatic Meta Pixel injection per tenant
* New: plugin automatically fetches the tenant’s Meta Pixel ID from the Oria API using the configured API key, and injects the standard Meta Pixel snippet into every frontend page’s <head>. PageView fires on load. No manual snippet install required.
* New: window.dispatchEvent(new CustomEvent(‘oria:lead_submitted’)) from any form on the site will fire a Lead event to the tenant’s pixel. Oria-rendered forms already dispatch this; custom forms can too.
* Pixel config is cached for 6 hours via a WordPress transient (oria_sync_pixel_config) to avoid API round-trips on every pageview. Cache is invalidated automatically when the tenant changes their API key.
* <noscript> tracking pixel fallback included for browsers without JavaScript.
City × service pages now route correctly
* New standalone URL pattern /areas/{city}/{service} (no prefix required) maps to Oria’s auto-generated city × service landing pages. Before this release these URLs returned 404 on tenant domains because only single-slug routes under the configurable prefix were matched.
* New Oria_Renderer::fetch_city_service() helper hits /api/connect/service-areas/:city/:service?render=html on the Oria API to retrieve pre-rendered SSR HTML and Schema.org markup (Service + LocalBusiness + FAQPage). 24-hour transient cache.
* New plugin render path oria_sync_render_city_service_page() wraps the fragment in the tenant’s theme via get_header() / get_footer() so Google can crawl the content on the tenant’s own domain.
WordPress.org Plugin Review compliance (round 2)
* Fixed PHP syntax error in docblock above oria_sync_register_embed_assets() — the sequence wp_register_*/wp_enqueue_* inside the comment was prematurely closing the docblock (the */ inside that token). Rewrote to wp_register_style / wp_enqueue_style so PHP parses the docblock correctly.
* Added a complete External services section documenting every outbound call to oriamarketing.com: what data is sent, when, and why, plus links to the Oria Marketing Terms of Service and Privacy Policy. Required for WordPress.org Plugin Review.
WordPress.org Plugin Review compliance
* Inline <style> and <script> output in the iframe embed render function replaced with wp_enqueue_style() and wp_enqueue_script() registering the new assets/embed.css and assets/embed.js static files (Plugin Guideline 13).
* Readme description clarified — the sentence about the configurable URL prefix now lists example values explicitly instead of comma-separating them ambiguously.
* No functional changes for end users — the embed page renders identically.
Server-side rendering for SEO surfaces (the big change)
* Blog posts, reviews, case studies, service-area pages, and project pages are now fetched from Oria as pre-rendered HTML and injected into your WordPress theme via get_header() and get_footer(). The content is part of your page on your domain, so Google indexes it as your content (iframes were not crawlable, so SEO link equity was leaking back to oriamarketing.com).
* Internal links inside Oria-generated content are rewritten to your /hub/* paths so SEO link equity flows through your domain.
* Schema.org JSON-LD emitted for each surface: BlogPosting (blog), AggregateRating + Review (reviews), Article (case studies), LocalBusiness (service areas), CreativeWork (projects). Google Rich Results eligible.
* Meta tags (title, description, canonical, og:*, twitter:*) emitted via the standard wp_head hook so SEO plugins like Yoast can observe and modify them.
* Lead-generation tools (chat, booking, estimate, photo-quote, contact, AI viz) stay as iframe widgets, that’s the right architecture for real-time JS forms with no SEO value.
Sitemap with real per-post URLs
* wp-sitemap.xml now lists every individual blog post, case study, area page, and project at its full URL with a per-post lastmod timestamp.
* Sitemap data is fetched from /api/connect/sitemap and cached for 1 hour.
Performance
* WP transient cache: 5 minutes for list pages, 24 hours for detail pages, 1 hour for sitemap. Reduces API load to roughly one request per surface per cache window.
* Soft-fail with 60-second negative cache so transient API errors don’t hammer Oria during recovery.
Plugin Website
Visit website
Share Post
Get insights into what’s happening at ChangelogWP right in your inbox. We don’t believe in spam.