=== Orcalinq — AI Chat to WhatsApp ===
Contributors: truekin
Tags: whatsapp, ai chat, live chat, customer service, chatbot
Requires at least: 5.5
Tested up to: 7.0
Requires PHP: 7.4
Stable tag: 0.4.10
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Installs the Orcalinq Semantic PBX widget on every public
page. All configuration lives in the Orcalinq Admin Portal
— this plugin only installs the widget, manages the CSP allowlist, and
reports install status.

== Description ==

**5-Minute Setup | No Coding Required**

Orcalinq is a **semantic PBX** — an AI-first conversation
conductor that answers from approved knowledge, routes by intent, and
hands off to humans with full context directly to WhatsApp or Telegram.
This WordPress plugin is a thin installer designed for small businesses
that want zero-code integration.

What this plugin does:

* Injects the Orcalinq widget script on every public page.
* Manages a CSP allowlist (auto-emits a permissive policy on sites that
  don't have one yet, defers to your existing security plugin's CSP
  when one is detected).
* Lets you enter your Tenant Key once.
* Verifies that the Orcalinq platform has seen this site's
  widget heartbeat.
* Provides a one-click disconnect.

What this plugin does NOT do (by design):

* No conversation inbox.
* No AI policy / prompt settings.
* No routing rules.
* No agent management.
* No knowledge source management.
* No billing.
* No audit logs.
* No analytics dashboards.

All of that lives in the Orcalinq Admin Portal at
[app.orcalinq.com](https://app.orcalinq.com). The plugin's only
job is to install the widget and report status.

== Installation ==

1. **Create a free Orcalinq account** at
   [app.orcalinq.com](https://app.orcalinq.com/signup) — passwordless,
   no credit card. Add this site's domain in **Domains → Add domain**.
2. Copy your **Tenant Key** from **Domains → Installation** (looks
   like `chat_…`).
3. In WordPress admin, go to **Plugins → Add New**, search for
   "Orcalinq — Semantic PBX", and click Install.
4. Activate the plugin.
5. Go to **Settings → Orcalinq** → paste your Tenant Key
   → Save.
6. Click **Verify installation** to confirm the platform has seen the
   widget heartbeat.

The bubble should appear on every public page on your site within seconds.

== Frequently Asked Questions ==

= Where do I find my Tenant Key? =

In the Orcalinq Admin Portal at
[app.orcalinq.com](https://app.orcalinq.com) under
**Domains → Installation**. It looks like `chat_…` and is the same
value as the `data-tenant` attribute of the install snippet shown
there.

= Don't have an account yet? =

Sign up free at [app.orcalinq.com](https://app.orcalinq.com/signup).
Sign-in is passwordless (email magic link) — no password to manage,
no credit card required for the free plan.

= How does the CSP setting work? =

By default the plugin runs in **Auto-allow** mode. It runs at PHP
shutdown so it sees the final set of `Content-Security-Policy`
headers that other plugins (Wordfence, Sucuri, Solid Security, Really
Simple SSL Pro, etc.) have set during the request, then **merges**
the Orcalinq origins into each existing CSP — never
removing any existing tokens. This is required because browsers
compute the **intersection** of multiple CSP headers, so a permissive
header emitted alongside a strict one is not enough on its own. If no
CSP exists, the plugin emits its own permissive policy. **CDN-edge
CSP** (Cloudflare Transform Rules, Apache/Nginx `mod_headers`) is set
after PHP runs and the plugin cannot merge into it — for those, add
the Orcalinq API origin to the edge rule manually. You
can also pick **Force** (replace any prior PHP-set CSP) or **Off**
(never touch headers) on the Settings → Orcalinq page.

= Will it slow my site down? =

No. The script tag uses `async`, so it loads in parallel with the rest
of your page. The widget renders only the chat bubble until the visitor
clicks it; the heavy chat UI is loaded lazily inside an iframe.

= Why doesn't this plugin have an inbox / AI settings / analytics? =

By design. Orcalinq keeps the entire control plane in the
Admin Portal so a single source of truth is maintained across web,
WhatsApp, LINE, Telegram, Instagram, Messenger, and Shopify channels.
Duplicating dashboards inside platform plugins creates configuration
drift, audit complexity, and inconsistent UX. Click **Open
Orcalinq Admin Portal** on the settings page.

= How does verification work? =

The "Verify installation" button calls a server-side AJAX endpoint
that posts to the Orcalinq install-check endpoint. It
works even on staging sites that aren't reachable from the public
internet, because the request originates from your WordPress server,
not the operator's browser.

= Can I disable the widget on certain pages? =

Use the standard WordPress filters (`wp_enqueue_scripts`) to dequeue
the widget on specific pages. Per-URL visibility rules are on the
roadmap.

= Does this work with WooCommerce? =

The widget loads on WooCommerce stores like any other public page.
Structured ecommerce data access (live order lookup, customer identity)
requires the WooCommerce data connector — coming separately. Without
the connector, AI answers from approved knowledge only and hands off
when live order data is needed.

= Where do I get help? =

* [Create a free account](https://app.orcalinq.com/signup)
* [Sign in](https://app.orcalinq.com/login)
* [WordPress install guide](https://orcalinq.com/docs/install/wordpress/)
* [What is a semantic PBX?](https://orcalinq.com)

== External Services ==

This plugin connects your site to Orcalinq, the hosted service
that actually answers the chat. The plugin itself is only an installer — it
holds no knowledge base and runs no AI locally, so the service is required
for it to do anything. This section describes every request that leaves your
server or your visitors' browsers.

**Terms of service:** https://orcalinq.com/terms
**Privacy policy:** https://orcalinq.com/privacy

= 1. The chat widget, on your public pages =

When the widget is enabled, every public page loads a script from
https://api.orcalinq.com/w.js into your visitors' browsers. Loading it means
each visitor's browser contacts Orcalinq directly, which
discloses their IP address and user agent to us as part of any normal web
request.

If a visitor then opens the chat and sends a message, the message text and
the conversation it belongs to are transmitted to https://api.orcalinq.com and
processed there to generate a reply. Do not invite visitors to send
information you would not want processed by a third-party service.

No script is loaded and no visitor data is transmitted until you enter an
API key and enable the widget.

= 2. Install verification (when you ask for it) =

Pressing "Verify" on the settings screen sends your API key, your site's
domain and home URL, and the plugin version to
`https://api.orcalinq.com/api/widget/install/check`, so the service can confirm
the widget is correctly installed and the domain is authorised. Runs only
when you click it.

= 3. Branding (settings screen, then daily) =

`https://api.orcalinq.com/api/widget/branding` returns the product name, logo and
help links shown on the settings screen. The request sends no data about
your site beyond the request itself. It runs once when an administrator
first opens the settings screen, then daily — but only after an API key has
been saved.

= 4. Install attestation (daily, only once configured) =

To confirm a licensed install, the plugin requests a signing secret from
`https://api.orcalinq.com/api/widget/plugin/provision` (sending a randomly
generated install identifier), then once a day sends
`https://api.orcalinq.com/api/widget/plugin/attest` a key identifier, a timestamp,
a random nonce, and a signature computed over your site's hostname.

Both requests are skipped entirely while no API key is set, and both stop
when the plugin is deactivated or the key is removed.

== Changelog ==

= 0.4.10 =
* Tested against WordPress 7.0.
* Added a full <strong>External Services</strong> disclosure covering
  every request the plugin makes and every script it loads, with links
  to the terms of service and privacy policy.
* The plugin no longer contacts the service at all until an API key is
  saved. Previously the daily background jobs were scheduled on
  activation, so a site that was never configured still made a request
  once a day. They now start when a key is entered and stop when it is
  removed or the plugin is deactivated.
* Directory listing rewritten for search: clearer title and tags
  describing what the plugin actually does.
* Added a one-time notice on the plugin's own settings screen
  inviting a review, shown no earlier than 14 days after the
  install is first seen configured, and dismissible for good.
  It never appears anywhere else in wp-admin.

= 0.4.9 =
* <strong>Critical hotfix</strong>: when the merged-CSP value
  is built for a Cloudflare-protected site, automatically include
  Cloudflare's own Bot Fight / Managed Challenge / Turnstile
  origins in <code>script-src</code> + <code>frame-src</code>,
  and the Web Analytics beacon origins in <code>script-src</code>
  + <code>connect-src</code>. Without these, a strict "Set static"
  CSP locks the operator out of admin pages that Cloudflare
  protects (most often <code>/wp-admin</code> and
  <code>/wp-login.php</code> on attacked WordPress sites): the
  challenge frame can't load its verification scripts, the bot
  check never completes, and the page never advances. Specific
  origins added on Cloudflare:
  - <code>https://challenges.cloudflare.com</code>
    (script-src + frame-src) — challenge UI iframe
  - <code>https://static.cloudflareinsights.com</code>
    (script-src) — Web Analytics beacon
  - <code>https://cloudflareinsights.com</code>
    (connect-src) — beacon POST endpoint
  Discovered while debugging a customer site: the v0.4.8 merged
  value blocked Cloudflare's challenge page and the operator
  couldn't reach <code>wp-admin/options.php</code>.

= 0.4.8 =
* Cloudflare path on the fix-it panel now includes a high-
  contrast warning step that calls out the two most common
  Modify-Response-Header mistakes:
    1. Setting the <strong>Header name</strong> to a CSP
       directive (<code>script-src</code>, <code>connect-src</code>,
       etc.) instead of <code>Content-Security-Policy</code>.
       Cloudflare modifies HTTP headers, not directives inside
       a header value — a header named <code>script-src</code>
       is junk that browsers ignore.
    2. Choosing <code>Add static</code> instead of
       <code>Set static</code>. There is no native "append a
       token to a single CSP directive" operation in Transform
       Rules; you must replace the whole header value.
* New <strong>merged-CSP value box</strong> on the Cloudflare
  path: a single click copies the entire canonical CSP string
  (every directive token from the operator's existing CSP, plus
  the platform origins) so they can paste it straight into the
  Value field on a "Set static" rule and be done. The box also
  shows the exact Header name + Operation values to use.
* Backend helper <code>aichat_merge_csps_for_replacement()</code>
  builds the canonical CSP: parses every observed policy, unions
  tokens per directive, dedupes case-insensitively, adds platform
  origins to the four directives we need, and re-emits with a
  stable directive ordering (default-src first, then fetch
  directives, then navigation/document, then alphabetised).
* Standalone directives like <code>upgrade-insecure-requests</code>
  and <code>block-all-mixed-content</code> are preserved verbatim
  in the merged output (no spurious empty values).

= 0.4.7 =
* Fix-it panel rewritten as a friendly numbered-step checklist
  instead of a wall of paragraph text. Each step is one focused
  action: a single sentence, a primary button or copyable code
  block, then the next step. Aimed at site owners who aren't
  developers — fewer reasons to abandon the install when CSP
  blocks the widget.
* New <strong>verdict header</strong> at the top of the panel:
  one sentence headline + one sentence "why" + a confidence
  badge (high / medium / low). Synthesises Cloudflare detection,
  server-kind detection, broken-policy fingerprint, and combined-
  header signal into a single lead the operator can act on.
* New <strong>"Copy a message for your developer"</strong>
  button. Click → plain-English email/Slack message goes to the
  clipboard, ready to send. Includes the CSP value, the four
  origins to add, and the diagnostic verdict — no jargon beyond
  what a developer would expect.
* New <strong>"Copy a prompt for ChatGPT / Claude / Gemini"</strong>
  button. Click → technical prompt to the clipboard. The model
  walks the operator through the fix in their specific dashboard
  / config.
* Origin lines (script-src / connect-src / frame-src / img-src)
  are now individually clickable — click any line and the exact
  text copies to clipboard with a visual flash. Keyboard
  accessible (Enter / Space).
* Step content is platform-aware: Cloudflare gets Cloudflare-
  specific steps with a deep link, nginx gets nginx-specific
  config-file guidance, Apache gets <code>.htaccess</code> +
  vhost guidance, and an "unknown / fallback" path covers
  everything else with the generic copy-and-paste flow.
* "Other places worth checking" is now a collapsed details
  section so it doesn't crowd the primary path on first read.
* Backwards-compatible: the legacy <code>detail_steps</code> /
  <code>primary_action</code> wall-of-text shape is still
  emitted by the worker so older plugin builds keep rendering;
  this build prefers the new <code>fix_plan</code> structure.

= 0.4.6 =
* "Find the file emitting this CSP" no longer reports the plugin's
  own files as suspects. The scanner now self-excludes the
  installed plugin directory (resolved at runtime via
  <code>plugin_dir_path(__FILE__)</code> so renamed installs are
  still excluded) — the diagnostic is for finding OTHER plugins
  fighting our CSP, not our own header() calls.
* Emit-shape detection tightened so docstrings, block comments,
  catalogue hint strings, scanner-source pattern strings, and
  <code>header_remove()</code> calls are no longer flagged. Three
  accepted shapes: <code>header('Content-Security-Policy: …')</code>,
  <code>'Content-Security-Policy' =&gt; …</code> in a wp_headers
  filter return, or a line that explicitly mentions both
  <code>wp_headers</code> and the directive name.
* Multi-line block-comment state machine: anything between
  <code>/*</code> and <code>*/</code> is treated as a comment and
  skipped, regardless of what it says about CSP.
* <code>/vendor/</code> directories now skipped alongside
  <code>/node_modules/</code> and <code>/.git/</code> — composer /
  PHAR dependencies rarely emit headers themselves and only add
  noise.
* Scanner stats now expose the self-excluded directory so the
  operator can confirm at a glance that our own plugin wasn't
  scanned.

= 0.4.5 =
* CSP diagnostic now correctly splits a single
  <code>Content-Security-Policy</code> header value containing
  multiple comma-separated policies (per RFC 7762 / CSP3 header
  combining). Previously, a site sending one combined header was
  treated as one big malformed policy, hiding the actual blocker.
* New <strong>"Find the file emitting this CSP"</strong> button on
  the Verify panel — runs a separate AJAX action that walks active
  plugin / mu-plugin / active theme PHP source for code that emits
  a <code>Content-Security-Policy</code> header (header(),
  <code>wp_headers</code> filter, <code>send_headers</code> hook).
  Returns each hit with file path + line number + 240-char snippet
  so the operator can open and patch the source file directly.
  Read-only; bounded by 8000 files / 2 MiB per file / ~25s wall
  clock.
* Pre-flight known-CSP-emitting plugin matcher: even before the
  file scan, the diagnostic lists active plugins whose slugs match
  a curated catalogue (Site Kit, Wordfence, Sucuri, Solid Security,
  Really Simple SSL Pro, All-in-One WP Security, HTTP Headers,
  CSP Manager, WPCode / Insert Headers and Footers, Code Snippets,
  Header Footer Code Manager, Advanced Ads, Ad Inserter) and shows
  exactly where each plugin's CSP setting lives.
* Each policy in the technical-details disclosure now carries a
  small <em>comma-combined header</em> tag when it was extracted
  from a multi-policy header value, so the operator can see at a
  glance that the browser is enforcing multiple policies even
  though only one Content-Security-Policy header was sent.
* Headline message rewords for the comma-combined case: explains
  that two policies in one header are still enforced independently
  and that the second policy is what's blocking the widget.

= 0.4.4 =
* Verify installation now produces a step-by-step fix plan instead
  of a raw token list when CSP blocks the widget.
* Auto-detects the web server stack from the live response
  (<code>Server</code> header + <code>cf-ray</code>) and shows
  config snippets specific to that stack — nginx
  <code>add_header</code>, Apache <code>.htaccess</code> /
  <code>mod_headers</code>, IIS <code>web.config</code>,
  Caddyfile, or Cloudflare Transform Rules. Sites no longer get
  generic ".htaccess" guidance when they're on nginx.
* Auto-detects the likely culprit of the second CSP from its
  fingerprint: AdSense / Site Kit / Auto Ads, Wordfence, Sucuri,
  Jetpack, or Cloudflare Web Analytics / Turnstile.
* Fixes a duplicate-origin spam in the advice
  by deduping origins in the human-readable join.
* Status panel now collapses raw CSP headers into a "Show
  technical details" disclosure so the headline is actionable
  while the verbatim policy is still inspectable.
* Required-origins block is rendered as a single, deduped per-
  directive list instead of one row per CSP × directive pair.

= 0.4.3 =
* Critical fix: Auto-allow CSP mode now **merges** platform origins
  into existing Content-Security-Policy headers instead of bailing
  out when one is detected. Previously, on sites where another plugin
  (Wordfence, Solid Security, etc.) emitted its own CSP, both headers
  shipped to the browser — and because browsers compute the
  intersection of multiple CSP headers, the strict policy still
  blocked the widget script silently. The merge runs from
  `header_register_callback` so we observe headers added by hooks
  running after `send_headers`. Existing tokens in the operator's
  CSP are preserved verbatim; we only add the origins our widget
  needs (`script-src`, `connect-src`, `img-src`, `frame-src`,
  `media-src`). `'unsafe-inline'` is NOT added during merge so
  nonce-based protection on strict policies is preserved.
* Verify installation now performs a **live CSP self-check**:
  fetches the site's home page from this WordPress server, parses
  every `Content-Security-Policy` header on the response, and
  confirms each one allows our origins in `script-src`,
  `connect-src`, `frame-src`, and `img-src`. When a CSP blocks the
  widget, the status panel shows an actionable fix list naming the
  exact directive and tokens to add — including the multi-CSP
  intersection caveat. Catches CDN-edge CSP (Cloudflare Transform
  Rules, `.htaccess` `mod_headers`) which the plugin cannot merge
  into because it is set after PHP runs.

= 0.4.2 =
* Critical fix: the install snippet now points at the LOADER
  (`/w.js`) instead of the chat-surface bundle (`/widget.js`).
  Previous versions injected the wrong script and the chat bubble
  silently failed to render — the chat-surface bundle has no
  top-level entry point that creates a bubble; it only runs inside
  the iframe the loader spawns.
* Migration: the plugin auto-rewrites a saved `cdn_url` of the
  legacy default shape `https://…/widget.js` to `/w.js` on next
  read. Operators who customised the URL to a non-default value
  are left alone.

= 0.4.1 =
* Fixed plugin zip layout — the package now contains a single
  top-level folder, which WordPress requires. Earlier 0.4.0
  builds were rejected with "No valid plugins were found" because
  files were nested two levels deep.
* Plugin folder name made consistent end-to-end (signup →
  settings → install zip → wp-content/plugins/ folder).

= 0.4.0 =
* Brand-keys are now fetched from the platform via
  `GET /api/widget/branding` and refreshed daily; brand name,
  tagline, signup/signin/help/marketing URLs all flow from the
  super-admin Settings page without a plugin update.
* "Don't have an account?" onboarding panel for first installs.
* Settings page H1 reads dynamic `{brand_name}` with the tagline
  underneath.

= 0.3.0 =
* License changed MIT → GPLv2-or-later (WP.org compatibility).
* Added Content-Security-Policy auto-injection (Auto-allow / Force / Off).
  On a fresh install the plugin emits a permissive CSP that includes
  the platform widget origins; on sites that already set a CSP, the
  plugin defers to the existing rule.
* Added signup / sign-in / help links throughout the settings page,
  plus a "Don't have an account?" onboarding panel for first-time
  installs.
* Added Plugins-page row links: Settings, Create account, Sign in,
  Install guide.
* Default endpoints switched from placeholders to the production hosts.
* Activation hook seeds default settings cleanly.

= 0.2.0 =
* Added "Verify installation" button with server-side check against
  the platform install-verify endpoint.
* Added live status display: tenant valid, domain allowed, widget seen,
  connector status, script version.
* Added "Disconnect" button (clears credentials without uninstalling).
* Added link to platform Admin Portal.
* Added explicit "what this plugin will not do" notice (architectural
  principle: thin installer, control plane in the Admin Portal).

= 0.1.0 =
Initial release.
