=== Baseline Labs Connector ===
Contributors: baselinelabs
Tags: schema, json-ld, structured data, seo, ai search
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 2.12.3
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Injects your Baseline Labs schema.org JSON-LD into the raw HTML of your pages so AI crawlers can read it. Identical schema for every visitor.

== Description ==

AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended and others) do
not run JavaScript. They read the raw HTML your server returns and move on. So
schema added by client-side JavaScript is invisible to them.

This plugin fetches the schema.org JSON-LD that Baseline Labs has already
generated for each of your pages and injects it **server-side** into the page
`<head>`, inside a `<script type="application/ld+json">` block - exactly where a
non-JavaScript crawler will find it.

It is a thin reflector: it does not generate schema and has no schema logic of
its own. Baseline Labs generates and stores the schema; the plugin simply
reflects it into your HTML.

**It never cloaks.** The same schema is served to every visitor, human or bot.
Serving crawlers different content than people is against search-engine
guidelines and is not something this plugin will ever do.

= What it does =

* Hooks `wp_head` and injects the JSON-LD for the current URL.
* Caches each page's schema (12–24h) so the API is only called on a cache miss,
  and clears that cache within minutes when Baseline regenerates your schema.
* Corrects or supplies your page titles and descriptions, when you switch that
  on in your Baseline account.
* Fails open: if the API is unreachable or has no schema for a page, the plugin
  emits nothing and never breaks the page.
* Reports injection failures back to your Baseline account so you can see them.

= Requirements =

* A free Baseline Labs account and an API key. Schema generation and injection
  are included on the free plan for one site; paid plans raise the page limit
  and refresh your schema more often.

== Installation ==

1. Upload the plugin and activate it.
2. Go to **Settings → Baseline Connector**.
3. Paste your Baseline API key and save.
4. Click **Connect & verify this site**.
5. That's it - schema appears in your pages as Baseline generates it.

WP-CLI:

`wp baseline set-key bl_xxx && wp baseline enable && wp baseline connect`

== External services ==

This plugin talks to Baseline Labs (https://baselinelabs.ai) - its whole
purpose is to connect your site to that account. Nothing is sent until you
connect the site with your API key, and the traffic reporting in items 2, 3
and 6 stays **off until you switch it on** under Settings → Baseline
Connector. Every call it makes:

1. **Fetching your schema.** On a cache miss, one request per page asking for
   the JSON-LD Baseline generated for that URL. Sends the domain and path.
2. **Reporting AI-bot requests.** A batch, at most every few minutes via
   WP-Cron, of the AI crawler requests your site received: user agent, path,
   status, timestamp. **No full IP addresses and nothing identifying your
   human visitors.** Only requests whose user agent looks like a known AI bot
   are ever included, plus requests for paths that only automated scanners ask
   for (files like `.env`, `.git` and `wp-login.php`, whatever the user agent
   claims to be - nothing is recorded at all while you are signed in), plus
   visits that arrived from a known AI answer engine (ChatGPT, Perplexity and
   the like), for which the same fields plus the referring site are sent and
   nothing else.

   A bot request may also carry one true/false flag saying whether it came
   from an IP range that crawler publicly announces, and the **network block**
   the request came from. **Neither sends us a visitor's address.** The
   published ranges are downloaded to your server (item 10) and the address is
   compared against them inside PHP; what leaves your site is one true or
   false plus the surrounding block - the first three numbers of an IPv4
   address with the last replaced by a zero (`203.0.113.0/24`, a block of 256
   addresses), or the equivalent `/48` for IPv6. The address itself is never
   written to your database, never put in a batch, never logged, and never
   hashed either - a hashed address is still an identifier. A block identifies
   a network rather than a machine, and it is what lets your dashboard tell
   one crawler's run of visits from another's and check that a request
   claiming to be Googlebot really came from Google.

   Off by default; switch it on or off under Settings → Baseline Connector.
3. **Confirming delivery.** Once a day, the plugin asks Baseline how many bot
   hits it has actually received for this site in the last 24 hours, and warns
   you if the answer is none while the plugin believes it has been sending
   them. Sends your domain and API key; returns counts only.
4. **Setting up.** When you click **Connect & verify this site** (and only
   then), the plugin registers the domain with your account, collects its
   tokens and asks Baseline to verify ownership. Verifying means Baseline
   fetches your homepage and looks for the tag this plugin prints there. Two
   things happen alongside it, both only on that click: the plugin clears your
   site's page cache first (WP Rocket, LiteSpeed, W3 Total Cache, WP Super
   Cache, WP-Optimize, WP Fastest Cache, Cloudflare - whichever you run), so
   Baseline is not handed a copy of your homepage saved before the plugin was
   installed; and the plugin answers a small read-only address of its own,
   `/wp-json/baseline/v1/verify`, which Baseline can read when your cache is
   still serving the old page. That address returns only what the tag in your
   page already says - the same ownership token, the plugin version, and which
   features are switched on. No key, no settings, no visitor data, and nothing
   there can change anything.
5. **Reporting its own errors.** If injection or delivery fails, a short
   diagnostic goes to your account so the failure is visible on the Baseline
   side too. No page content and no visitor data.
6. **Measuring visits from AI answer engines.** This is the one call your
   **visitors' browsers** make rather than your server. While reporting is on,
   the plugin adds a small script tag to your pages; it records the page
   visited and the referring site, so you can see when ChatGPT, Perplexity or
   Google's AI surfaces send someone to you. **No IP address is stored, no
   cookie is set, and no cross-site profile is built** - the visitor id is a
   random string kept in the browser's local storage for your domain alone.
   Controlled by the same opt-in toggle as bot reporting. Where your visitors
   must consent to analytics, you are responsible for collecting it.
7. **Checking for a cache in front of your site.** At most twice a day, the
   plugin requests your own homepage from your own server to see whether a
   page cache answers before WordPress does. It goes to your site, not to us;
   only the resulting one-word verdict is sent with the next bot batch, so we
   can tell you honestly whether your crawler counts are complete.
8. **Syncing your site profile and published pages.** Once a day, the plugin
   sends a description of the site so your dashboard can compare what you
   have published against what AI crawlers actually read: WordPress and PHP
   version, language, permalink style, whether search engines are
   discouraged, the active theme and any active SEO plugin, plus the list of
   **published, public** pages (path, title, type, published and modified
   dates). Never drafts, never private content, never post bodies - only
   facts already visible to anyone with a browser. Same toggle as bot
   reporting; `wp baseline sync-site` or the settings button runs it on
   demand.

   Publishing or editing a post also sends that **one** page's entry down the
   same route, so Baseline can refresh its schema straight away instead of
   re-reading your whole site on a timer. Same fields, one row, at most once
   every thirty seconds, and it happens after the page you were saving has
   already been handed back to you. If it does not get through, the daily
   sync above carries it.
9. **Fetching a page's title and description.** Only while metadata
   correction or generation is switched on in your Baseline account. On a
   cache miss, one request per page asking what the title and description for
   that URL should be, and it sends the same domain and path as item 1 and
   nothing else. Turn either switch off in your account and the plugin stops
   asking. The answer is cached the same way your schema is, and a page
   Baseline has nothing to say about is remembered as such so it is not asked
   about again on every load.
10. **Downloading the AI-agent list.** About once a day the plugin fetches the
   list of user agents that identify AI crawlers, so it knows what to look
   for without asking us about each request. The same file carries the IP
   ranges those crawlers publish and the answer-engine sites worth
   recognising as referrers. It is a list of names and numbers, not code -
   nothing downloaded here is ever executed. **It is anonymous** - no site
   URL, no API key, no query string. The download only ever comes to you;
   nothing about your visitors goes out on it.

Privacy policy: https://baselinelabs.ai/privacy
Terms: https://baselinelabs.ai/terms

== Frequently Asked Questions ==

= Does this slow down my site? =

On a cache miss the plugin makes one short (max 2s) request to fetch a page's
schema, then caches it for 12–24 hours. Warm page loads do no network work.

= Where is my API key stored? =

Encrypted at rest in the WordPress options table, using a key derived from your
site's own secret salts. (If the libsodium PHP extension is unavailable it falls
back to base64 obfuscation - install libsodium for real encryption.) Anyone with
both your database and your `wp-config.php` can still read it; that is inherent
to WordPress.

== Screenshots ==

1. Settings: paste your Baseline Labs API key, keep schema injection on, and opt in to AI traffic reporting if you want it.
2. This site: connect and verify the domain, re-check verification, or purge the cached schema.

== Changelog ==

= 2.12.3 =
* Traffic reporting is now opt-in: off on a new install until you switch it on
  in Settings. Sites that already had it on keep it.
* The visitor tracker no longer falls back to a cookie.
* The tracker script is enqueued through WordPress instead of printed by hand.
* Class names carry the full Baseline_Connector_ prefix.

= 2.12.2 =
* Performance: a scanner sweeping your site for hundreds of addresses at once
  can no longer tie up your server while the plugin records it. Each request
  used to wait up to two seconds to record itself after the page was sent;
  scanner requests now never wait, and are recorded once per address per
  minute, up to 30 addresses a minute. AI crawler visits wait at most a fifth
  of a second. On a small hosting plan that is the difference between a scan
  your visitors never notice and one that uses up every worker the site has.
* During such a burst some scanner requests, and occasionally a crawler visit,
  go unrecorded rather than slow your site down.

= 2.12.1 =
* Security: a corrected page title is now escaped before WordPress prints it
  into your page's title tag, and any markup is removed from corrected titles
  and descriptions. WordPress prints a title supplied this way exactly as it
  receives it, so the plugin now does the escaping itself.
* Security: the schema written into your pages is encoded so that no text
  inside it can end the script tag it sits in, whatever the text says.
* Missing pages no longer ask Baseline for schema or metadata. A page that
  returns "not found" has none, and scanners probing hundreds of missing
  addresses were costing a lookup and a stored entry for each one.

= 2.12.0 =
* Each crawler request now tells you which network it came from, so your
  dashboard can say whether a run of visits was one crawler or several, and
  whether something calling itself Googlebot really came from Google. Your
  visitors' addresses still never leave your site: the address is shortened on
  your own server to the surrounding block of 256 addresses, which names a
  network rather than a machine, and only that block is sent.
* Scanners are reported now, so their 404s stop making your site look broken.
  Every site on the internet is swept for files like `.env`, `.git` and
  `wp-login.php` by software that never reads a page, and it usually claims to
  be an ordinary browser - so it was invisible here while its missing-page
  errors counted against you. Requests for those paths are reported whatever
  they claim to be, and your dashboard files them as suspicious traffic rather
  than as readers. Your own visits to your login page are not included: while
  you are signed in, none of this is recorded.

= 2.11.0 =
* If Baseline is unreachable, your pages stop waiting for it. Each page load
  already gave up after two seconds, but every page whose stored copy had
  expired paid those two seconds again. After three failed attempts the plugin
  now stops calling for two minutes and serves the schema it last held, so an
  outage at our end costs your visitors nothing after the first few seconds.
  It reconnects on its own.

= 2.10.0 =
* Publishing or editing a post now tells Baseline immediately, so your schema
  is refreshed for that page within minutes instead of on the next full crawl
  of your site. It is one small message about the one page, sent after your
  save has already finished, at most once every thirty seconds.
* Because Baseline now knows which pages changed, it stops re-reading the ones
  that did not. A large site was being fetched thousands of times a day to
  find a handful of edits; it is a handful of requests a day now, plus one
  full pass a week to catch theme and menu changes.
* Fixed: when Baseline regenerated your schema or corrected a title, the
  plugin cleared its own copy but not your page cache, so pages already stored
  by WP Rocket, LiteSpeed and the like kept serving the old version until they
  expired on their own. Your page cache is now cleared too, at most once every
  ten minutes.

= 2.9.1 =
* On a site running no SEO plugin, a page whose description Baseline supplies
  now carries exactly one description tag - ours. Before this, a theme that
  printed its own left the page with two.

= 2.9.0 =
* Your page titles and descriptions can now be corrected or filled in by
  Baseline. Switch it on in your Baseline account under Rolling metadata; the
  plugin asks for each page's title and description and prints what comes back,
  through Yoast, Rank Math or All in One SEO where one of those is running, and
  on its own where none is. Settings → Baseline Connector shows whether it is
  on. There is no switch here, because your account is where you chose it.
* A site that no AI crawler has visited yet now checks in with Baseline once an
  hour. Everything the server tells this plugin - that your schema was
  regenerated, that your metadata changed, which of the above is switched on -
  rides back on the batch of crawler requests it sends, so a site with no
  crawler traffic to send never heard any of it.
* The daily site sync now includes each page's own description, so Baseline
  compares its suggestions against what your pages really say rather than
  against a copy read through your page cache.

= 2.8.0 =
* Verification no longer fails on a cached site. Baseline verifies your site by
  reading a tag this plugin prints in your pages, and a page cache was handing
  it the copy of your homepage saved before the plugin was installed - so a
  correctly installed plugin reported "tag not found", repeatedly. The plugin
  now clears your page cache before verifying, offers Baseline a second address
  no cache stores, and says plainly that a cache or Cloudflare can take up to
  half an hour rather than showing a red failure.
* Regenerated schema appears within minutes instead of up to a day. Each page's
  schema was cached for 12 to 24 hours with nothing to tell it that Baseline
  had produced a new version, so a regeneration was invisible for half a day.
  The plugin is now told, and clears the cache when it happens - and after the
  plugin itself is updated.
* The visitor-measurement script is now excluded from JavaScript optimisation
  in WP Rocket, LiteSpeed Cache and Autoptimize, and carries its reporting
  address on the tag. A minifier that copied the script onto your own domain,
  combined with delayed JavaScript, could leave every visit unrecorded with
  nothing to show for it.

= 2.7.0 =
* Updates now come from the WordPress.org plugin directory like every other
  plugin you have installed. The self-updater this replaces fetched new
  versions from our own server, which the directory does not permit and which
  you should not have to trust us about. If you installed an earlier copy from
  a zip rather than from the directory, install it once from the directory to
  keep receiving updates.
* Request data read from the server (user agents, referrers, query strings) is
  now unslashed and sanitised at the point it is read rather than where it is
  used.

= 2.6.0 =
* Crawler hits are now checked against the IP ranges each AI company publishes,
  so "GPTBot read you 400 times" is a claim someone verified rather than a user
  agent string taken at its word. **No IP address leaves your site or is stored
  anywhere** - the check happens on your server against a list downloaded to it,
  and only a true/false result is reported.
* People who arrive from ChatGPT, Perplexity and other AI answer engines are
  now recorded by your server as well as by the tracker script, so visitors
  with JavaScript blocked stop going uncounted.
* When a page cache is in front of WordPress, the plugin now names the layer
  and tells you exactly why it hides traffic - whether the page is answered by
  your webserver, by a drop-in before plugins load, or at a CDN edge - instead
  of one generic sentence. If your cache is set to let crawlers straight
  through, it says that too, because then your bot counts are nearly complete
  even though the cache is there.

= 2.5.0 =
* Site Intelligence. Once a day the plugin now sends your site profile
  (WordPress/PHP version, language, permalink style, the "discourage search
  engines" flag, active theme and SEO plugins) and the list of published
  pages with their titles and modified dates. Your Baseline dashboard uses
  it to show which published pages AI crawlers have never read, which ones
  you have edited since they last crawled, and to warn you when a WordPress
  setting is quietly hiding you from search and AI engines. Public facts
  only - no drafts, no private content, no post bodies.

= 2.4.0 =
* One install now covers both halves of the picture. As well as recording the
  AI crawlers that read your site, the plugin measures the people AI answer
  engines send you - no second snippet to paste. It is the same toggle, and
  the same no-IP, no-cookie promise.
* Cache detection now runs by itself, twice a day. It only ever ran when
  somebody opened the settings screen, so most sites reported "we could not
  determine whether a cache sits in front of you" forever - and your crawler
  counts were caveated more heavily than they needed to be.

= 2.3.2 =
* A fresh install shows its first schema in minutes, not an hour. The
  "no schema yet" result is now cached for 5 minutes instead of 60, and
  a successful verification clears the schema cache, so pages viewed
  before connecting no longer pin the empty state.

= 2.3.1 =
* The settings screen no longer rejects every valid API key. Key validation
  now uses an endpoint that accepts API keys; it previously hit a
  session-only endpoint, so saving a correct key reported
  "validation failed: Not authenticated".

= 2.3.0 =
* Works on a free Baseline account. Schema generation and injection no longer
  need a paid plan.
* The `baseline-channel` marker is now stored during channel registration, so
  it appears even if domain setup fails afterwards.
* Detects silent delivery loss. Baseline accepts bot-hit batches without
  saying whether the token was recognised, so a wrong token used to discard
  every hit while this screen showed "sent". The plugin now asks once a day
  what Baseline actually received, and warns when the two disagree.
* Warns when bot reporting is on but the site has no token yet.

= 2.2.0 =
* The plugin can now update itself. Before this, an installed copy stayed on
  whatever version it was first given - a fix could be shipped and never
  reach anyone.
* Emits a single unified `baseline-channel` marker carrying ownership,
  channel, version and capabilities. A site running only the free bot
  reporting previously announced no channel at all, because the old marker
  was written alongside the schema and skipped when injection was off.

= 2.0.0 =
* Renamed from "MarkupSchema Schema Injector" to "Baseline Connector".
* WP-CLI namespace is now `wp baseline`. `wp markupschema` still works as a
  deprecated alias and will be removed in the next release.
* Default API host is now `https://baselinelabs.ai`. Override it with the
  `BASELINE_CONNECTOR_API_BASE` constant in `wp-config.php`.

= 1.0.0 =
* Initial release.
