=== Opensolr Search === Contributors: opensolr Tags: search, solr, faceted search, WooCommerce search, autocomplete Requires at least: 6.0 Tested up to: 6.9 Requires PHP: 8.1 Stable tag: 1.0.21 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Powerful hybrid vector + keyword search powered by Opensolr. AI-powered search, faceted navigation, autocomplete, analytics, and WooCommerce support. == Description == Opensolr Search replaces WordPress's default search with a powerful, hosted Apache Solr search engine. It combines traditional keyword search with AI-powered vector search for dramatically better relevance. **Key Features:** * **Hybrid Search** — combines keyword matching (BM25) with semantic vector search (1024-dim embeddings) for the best of both worlds * **Faceted Navigation** — filter results by category, price range, date, custom fields, and hierarchical taxonomies * **AI Hints** — streaming AI-generated answers above search results, powered by RAG * **AI Reader** — full-screen AI summary of any search result * **Autocomplete** — real-time search suggestions with query history and Solr results * **WooCommerce Support** — index products with prices, categories, SKUs, and structured data * **Two Indexing Methods** — Web Crawler (automatic) and Data Ingestion API (push from WordPress) * **Search Analytics** — track queries, clicks, CTR, no-results queries, and visitor patterns * **Query Elevation** — pin or exclude specific results for any query * **Persistent Filters** — admin-defined include/exclude filters applied to every search * **Multilingual** — automatic locale filtering for WPML and Polylang sites * **Dark Theme** — built-in dark mode for the search page * **Embeddable** — use native search or embed the Opensolr hosted search widget **How It Works:** 1. Sign up at [opensolr.com](https://opensolr.com) and get your API key 2. Install the plugin and enter your credentials 3. Create a search index or connect to an existing one 4. Configure your sitemap and start the web crawler 5. Your search page is live at `/opensolr-search` The plugin generates a complete sitemap, injects meta tags for the crawler to extract, and provides a full search experience with zero load on your WordPress server — all search queries go directly to the Opensolr cloud. **Requirements:** * An Opensolr account (free tier available) * PHP 8.1 or higher * WordPress 6.0 or higher == External services == This plugin depends on several online services operated by Opensolr () to provide hosted search. You must have an Opensolr account; all indexing and search queries flow through these services. The service is required for core plugin functionality and cannot be self-hosted. **Service provider** * Opensolr — hosted Solr search cloud and AI API * Privacy policy: * Terms of service: * Account signup (free tier available): **Endpoints the plugin contacts** * `https://opensolr.com/solr_manager/api/*` — account management, index create/reload, config upload, sitemap registration, elevation toggle, crawl start/stop/stats. Called from WordPress admin (not from visitors' browsers). * `https://api.opensolr.com/solr_manager/api/ingest` — Data Ingestion API: pushes your post content + metadata to the search index. Called from the WordPress site (cron worker and the real-time sync on post save/delete), never from visitors. * `https://api.opensolr.com/solr_manager/api/embed` — generates a 1024-dim semantic embedding for the current search query (AI/hybrid search path only). Called from the WordPress site on every AI/hybrid search. * `https://api.opensolr.com/solr_manager/api/ai_summary` — streams the AI Hints / AI Reader answer. Called from the WordPress site only when the admin has enabled those features. * `https://.solrcluster.com/solr//select` — the Solr search request itself (host provided by Opensolr when you create an index). Called from the WordPress site on every search. * `https://search.opensolr.com/embed.js` — loaded ONLY if the admin switches Search Mode to "Embeddable". In Native mode (the default), this script is never loaded. **Data sent to these services** * Your Opensolr email + API key (authentication). * Your site's host name (`meta_domain`). * For ingestion: post titles, content, URL, excerpt, author display name, taxonomy terms, publish dates, WooCommerce price + category + SKU, featured-image URL. Only for the post types you explicitly enable in the plugin settings. Never includes user login data, email addresses, or passwords. * For search: the search query string, the active facet filters, pagination position. * For AI features (when enabled): the search query plus the top 4 result snippets, so the AI can compose an answer from your own content. * For click tracking (when enabled): the clicked result URL, title, position, the search query, and a SHA-256 hash of the visitor's IP (never the raw IP). Analytics can be disabled from the plugin's Search Display settings. No data is shared with any third party — all traffic goes only to `opensolr.com` / `api.opensolr.com` / `*.solrcluster.com` (and only to `search.opensolr.com` if you explicitly enable Embeddable mode). == Installation == 1. Upload the `opensolr-search` folder to `/wp-content/plugins/` 2. Activate the plugin through the Plugins menu 3. Go to Settings > Opensolr Search 4. Enter your Opensolr email and API key 5. Click "Save & Connect" to create or select a search index 6. Register your sitemap and start the crawler 7. Visit `/opensolr-search` to see your search page == Frequently Asked Questions == = Do I need an Opensolr account? = Yes. Opensolr Search uses the Opensolr hosted search infrastructure. You can sign up for free at [opensolr.com](https://opensolr.com). Keyword search works out of the box on every plan. AI features (vector search, AI Hints, AI Reader) are available on tailored plans. = Does this replace WordPress default search? = Yes. Once configured, the plugin provides a complete search experience at `/opensolr-search` with faceted navigation, autocomplete, highlighting, and more. = Does it work with WooCommerce? = Yes. The plugin automatically detects WooCommerce products and indexes them with prices, categories, SKUs, and structured product data. = What about multilingual sites? = The plugin supports WPML and Polylang. When a multilingual plugin is active, search results are automatically filtered to the current language. = How does indexing work? = Two methods: (1) The Web Crawler fetches your pages from the sitemap and indexes the content. (2) Data Ingestion pushes content directly from WordPress to Solr, with real-time sync on post save/delete and bulk async ingestion. = Where are search queries processed? = All search queries are sent directly from the visitor's browser to the Opensolr cloud infrastructure. This means zero search load on your WordPress server. = Can I opt out of click/query tracking? = Yes. The Analytics toggle in the plugin settings disables all query and click logging. With Analytics disabled, nothing is written to the plugin's local analytics tables. IP addresses are always SHA-256 hashed before storage regardless of this setting — raw IPs are never stored. == Screenshots == 1. Search results page with faceted navigation 2. Admin settings panel 3. Search analytics dashboard 4. Facet mapping configuration == Changelog == = 1.0.21 = * Fix: File / Document / Audio / Video media mappings now emit a real public URL instead of the bare attachment ID. Previous versions only resolved IMAGE attachments — mapping a PDF / DOCX / MP4 / MP3 attachment ID through Facet Mapping silently kept the integer in Solr. Now any image, video, audio, or known document MIME (PDF / DOC / DOCX / XLS / XLSX / PPT / PPTX / ODT / ODS / TXT) auto-resolves to its public URL. * Fix: ACF "Image Array" / "File Array" return format no longer pollutes Solr with a mix of id + alt + title + url + width + height. The flatten pass now detects the ACF attachment shape and emits ONE value per row — the `url` if present, otherwise the `ID` (which then resolves through the normal attachment-URL pipeline). Single-mode AND multi-mode mappings both produce clean URL values. * Fix: Values that are already URLs (`http://…` / `https://…`) pass through unchanged — covers ACF "Return Format = URL" + hand-rolled custom-field patterns. Previously the resolver only fired on numeric IDs, leaving everything else as-is, which was already correct for URLs but is now documented and explicit. = 1.0.20 = * New: Date-range quick presets on facet sidebar — every date_range facet now renders three pill buttons (Today / Last Week / Last Month) above the From/To inputs. One click fills both dates and submits the facet form — no more opening the native date picker twice for common queries. Uses LOCAL date components (not toISOString) so "Today" stays today in every timezone. * New: Data Ingestion tab — live "Total: N documents" tally and dynamic per-type counts. Check or uncheck a content type and the number next to that type flips between its real count and 0, with the grand total at the bottom of the fieldset updating instantly. Mirrors the Drupal module's ingestion UX. Include attached files toggle also contributes to the total. = 1.0.19 = First public release since 1.0.14. Rolls up every intermediate deploy. * New: Facet Mapping form now matches the Drupal module — Solr Field Name + Solr Type dropdown + Display Label. Pick a WP field and the Solr Type auto-fills from the detected suffix; stem auto-fills from the WP field name. Type hints shown next to every entry in the WordPress Field dropdown (post_tag → multi, string; _price → single, float; etc.). * New: WordPress Field dropdown shows every indexable source, not just a handful of postmeta keys. Core post columns (post_title, post_content, post_excerpt, post_date, post_status, post_type, post_author), ALL postmeta (underscore-prefixed included — _price/_sku/_stock/_thumbnail_id are now mappable), register_meta() keys, every public taxonomy, all pa_* WooCommerce attribute taxonomies, plus synthetic featured_image_url and gallery_image_urls. * New: Core post columns and WC synthetic aggregates resolve end-to-end in both ingestion and tag emission. * Fix: Facet Mapping saves no longer silently drop every row. The sanitize callback hooked via register_setting() expected the legacy flat scalar shape and called is_scalar($dst) on each value — every row in the new rich {wp_field, meta_name, field_type, label, solr_field} shape was failing the check and being stripped under sanitize_option_opensolr_settings. Callback now accepts both shapes. * Fix: Multi-valued mappings stored as serialized arrays in a single postmeta row (ACF Checkbox / Select multi / Relationship / Gallery fields, WooCommerce attributes) now emit one per value and serialize as Solr arrays. Both emission and ingestion paths flatten across both WP storage patterns (multi-row AND single-row-with-array). * Fix: Admin scripts self-invalidate on every deploy via filemtime() cache-busting. Previously OPENSOLR_VERSION-based cache-busting meant every intermediate fix shipped to disk without browsers refetching. * Fix: Facet Mapping table always renders at least one empty row so the admin has a visible slot to click into. * Fix: + Add Field Mapping button no longer collides with the placeholder row's index. * Fix: Backend stem fallback — save handler derives the Solr Field Name from the WP field name when left empty, preventing silent row drops on incomplete input. = 1.0.14 = * Fix: image-field Facet Mappings now emit a real image URL. Previously, mapping a meta key that stores an attachment ID (ACF image field returning "ID", `_thumbnail_id`, custom image_attachment_id, etc.) to a Solr field emitted the bare integer as the meta content — useless as an image source. Both ingestion and meta-tag emission now auto-resolve numeric values to `wp_get_attachment_url()` when the ID points at a valid image attachment. SKU, price, and other numeric fields are untouched (guarded by `wp_attachment_is_image()`). = 1.0.13 = * Fix: Persistent Filters "Exclude" mode was silently saved as "Include". The register_setting() sanitize callback used a `op: include/exclude` shape that didn't match the `mode: +/-` shape the admin form writes and the query builder reads, so any `-` (Exclude) selection was overwritten with the default on save. Existing stuck filters need to be re-saved after upgrading to pick up the correct mode. = 1.0.12 = * New: combobox autocomplete on "Solr field for result image" in Search Display. Focusing the input drops down the full alphabetical list of Solr fields indexed on your site (from the Luke handler); typing filters by case-insensitive substring match. Keyboard navigation (arrows + Enter) and mouse picking supported. New admin-only REST endpoint `/opensolr/v1/luke-fields` with a `manage_options` permission_callback feeds the list. JS and CSS enqueued via `wp_enqueue_script` / `wp_enqueue_style` (no inline tags); URL and nonce passed via `wp_localize_script`; all output escaped; DOM-based rendering avoids innerHTML. Plain typing still works for field names that aren't indexed yet. = 1.0.11 = * New: "Solr field for result image" input in Search Display settings. Default `og_image`. Lets admins point result thumbnails at any single-valued Solr field they've mapped through Facet Mapping (WordPress post meta, WooCommerce gallery, custom URL field, etc.). Applies to the search results page, the AI Reader modal, and the AI Reader metadata endpoint. Multi-valued sources normalized to the first element. Empty or non-alphanumeric input falls back to og_image. = 1.0.10 = * Fixed: Facet Mapping entries targeting multi-valued Solr fields (`_sm`/`_fm`/`_im`/`_dtm`) now work correctly with both the Web Crawler and Data Ingestion paths. Previously the ingest path was broken entirely for field mappings (reading config keys the admin UI never wrote), and the meta-tag emission concatenated array values into a single comma-joined string. Now: - Ingest parses the suffix out of `solr_field` to determine multi vs single, and writes arrays directly to Solr. - Meta-tag emission writes one `` tag per value so the Opensolr crawler can aggregate them back into an array (DOM order preserved). - Hardcoded emissions for `product_images_sm` and variable product attribute `*_sm` fields updated to the same repeated-tag pattern. Pairs with a crawler update that aggregates repeated `opensolr:*` tags. = 1.0.9 = * Security: register_setting() sanitize_callback now applies per-field sanitization (sanitize_text_field, sanitize_email, absint, float clamping, strict enum allowlists, recursive array sanitization for facets / filters / content type lists). Previous callback only merged input, relying on caller-side sanitization. * Security: REST /queue-stats endpoint now requires manage_options. The endpoint feeds the Data Crawler admin UI (live preview counts for arbitrary content-type overrides), so it's an admin-configuration surface and should not be public. * Compliance: JSON-LD structured-data blocks (WebSite SearchAction, Article, BreadcrumbList) now output via wp_print_inline_script_tag() instead of echoing raw