=== Short Link Manager ===
Contributors: 404ops
Tags: url shortener, shortlinks, qr code, redirects, 404 error
Requires at least: 6.3
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 1.5.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Branded short links with QR codes, privacy-first click analytics, a 404 log of the dead URLs visitors really hit, and one-click redirects.

== Description ==

Short Link Manager turns any destination URL into a branded `/go/your-slug` short link, gives you a QR code for it, tracks its clicks with a privacy-respecting analytics model, and organizes everything with categories and tags. No third-party service is involved in any of that — the shortener, the QR generator and the analytics all run on your own site, and no click data leaves it.

It also **watches** your links. Every published post and page is scanned on a daily schedule, each link's HTTP status is checked, and anything that stopped answering is listed on the Broken Links screen. That is a supporting feature, not the point of the plugin — but it is the reason a short link you set up two years ago is still pointing somewhere real. Both the scanning and the redirect manager are unchanged from earlier versions, so existing 301/302 redirects and scan results keep working exactly as before.

= Short links =

* Create a branded short link (default prefix `/go/`, configurable) pointing at any destination URL, with a title, category, and any number of tags.
* Choose 301 (permanent), 302 (temporary), or 307 (temporary, method-preserving) redirects.
* Optional expiry by date or click count, with a configurable fallback destination once expired.
* Optional `nofollow` / `sponsored` link attributes — applied wherever this plugin writes a link to a short link (currently: the block editor's Short link button, which puts them on the anchor it inserts, and Pro keyword auto-linking's generated `<a>` tags; a short link redirect itself is an HTTP redirect, which has no equivalent markup to carry the attribute). Both are off by default, because `rel="sponsored"` tells search engines a link exists because of payment and that is not true of most links. The setup wizard asks once whether you mainly shorten affiliate or sponsored links and turns both on if you say yes; Settings exposes each attribute separately, and every individual link can override them.
* **Create a short link from inside the post you are writing.** Select the words you want to link, press the Short link button in the block editor's formatting toolbar, and enter a destination — the link is created and the selected text becomes it, without leaving the editor. The same panel searches your existing short links, for when the one you want already exists. Classic-editor sites are unaffected and keep using the Links screen; see the FAQ.
* A locally generated QR code (SVG, downloadable) for every short link — no third-party QR service is contacted.
* Redirects always send `Cache-Control: private, no-store` so browsers and most CDNs never cache them; see the FAQ below if this site runs behind a full-page cache plugin.

= Click analytics =

* Every redirect is logged: timestamp, referrer host, device/browser (parsed from the user agent), country (via a filter you can wire up — no external GeoIP lookups are made or bundled), and a bot flag.
* Privacy-first IP handling, configurable per site: **anonymize** (default — stores only a salted hash of a truncated IP, never the raw address), **full** (also keeps the raw IP), or **none** (no IP is read at all). Unique-visitor counts are derived from the anonymized hash either way.
* Respects the browser's Do Not Track signal when enabled.
* Configurable click-data retention, with a daily cron prune.
* Dashboard, Reports, and per-link stats with sparklines — all rendered as inline SVG, no charting library or CDN involved.

= Categories & tags =

Organize short links with a single category and any number of tags, with rollup stats (clicks, link count) per category on the Reports screen.

= Import, export, and migration =

* CSV export of all short links (and a separate export of click data).
* CSV import to bulk-create short links. Rows whose destination (or fallback) domain no longer resolves are imported too, and listed separately afterwards so you can find and fix them — so a file this plugin exported always imports back in full, dead links included.
* A one-way migrator from the Pretty Links plugin: it detects Pretty Links' own database tables via `DESCRIBE` and copies links (and an aggregate click count per link — not per-click history, which Pretty Links' own schema doesn't make reliably portable across its versions) into this plugin's short links, skipping anything already migrated. Links whose destination domain no longer resolves are migrated too, and listed separately afterwards so you can find and fix them — an expired or typo'd domain is the most common broken link, and the usual reason for migrating in the first place. Every link the migration does skip is now named, with the reason. **This migrator has not yet been run against a real, populated Pretty Links installation** — it fails safe (skips gracefully, reports what it couldn't read) when a table or column it expects isn't present, but has only been exercised against synthetic test data so far. Back up your database before running it on a production site.

= Link health & redirect manager =

* Scans published posts, pages, and — turn them on under Settings → General — any other public post type (WooCommerce products, portfolio items, and so on), checking each link's HTTP status and reporting the broken ones with a one-click recheck.
* Scans the author URLs on approved comments too, and reports a broken one against the comment it came from, with a link straight to it. Fix or clear the URL from the Broken Links screen without opening the comment editor. Only the author URL field is checked, never the comment text.
* **Scheduled scanning is included, free.** A daily WP-Cron scan works through your content in batches, so link health looks after itself without anyone remembering to press a button. "Scan Now" on the Dashboard still runs one on demand.
* A separate redirect manager for plain source-path → target-URL 301/302 redirects (its own table, independent of short links above — nothing here changes how it works).

= 404 log =

The scanner above answers *"is my own content still pointing somewhere real"*. The 404 log answers the other question: **"what is actually breaking for the people arriving right now."**

Those are not the same set of URLs, and the second one is invisible to a content scanner. A visitor who typed a URL slightly wrong, followed a link in from someone else's site, or clicked a bookmark for a page you deleted last year hits a 404 that appears in none of your posts and none of your short links — so nothing on this site could previously see it at all.

* Every dead URL a visitor hits is recorded, with a hit counter, when it was first and last seen, and where the visitor came from.
* **One row per dead URL, not one per visit.** A bot hammering a single missing path a million times writes one row. The table is bounded by how many distinct URLs are broken, not by how much traffic you get.
* Sorted by hits by default, so the URL costing you the most visitors is at the top rather than buried in a chronological feed.
* **Fix it from the same row.** Type a destination next to a logged 404 and press Redirect — it becomes a 301 in the redirect manager, using the exact path that was recorded, so it cannot be mistyped. The entry then leaves the log, because it is not a 404 any more.
* Dismiss the noise you do not care about with Ignore; the entry stops cluttering the list but goes on counting, so you can still see what it is costing.
* Same privacy model as click analytics, with its own separate controls: honour Do Not Track, and store a salted hash of a truncated IP (the default), the full address, or nothing at all.
* Entries not hit within your retention window are cleared daily, and a hard ceiling on rows means a vulnerability scanner walking thousands of made-up URLs cannot fill your database.

All of it is free.

= REST API (opt-in, off by default) =

A read-only `GET /wp-json/ops404-link-sentinel/v1/links` endpoint listing your active short links (slug, destination, title, redirect type, status, click count — never anything else, and never disabled/expired links). Off by default; turn it on under Settings → Advanced if you want external tooling to read your short-link data.

= Onboarding =

A short setup wizard appears once, right after activation, to set your link prefix and defaults. You can always change everything afterward from Settings.

= Telemetry (opt-in, off by default, and not yet actually sent anywhere) =

An opt-in toggle and a "what would be sent" payload preview ship in this release so you can see exactly what usage data this plugin would report before ever turning it on. **No telemetry is actually transmitted in this version** — turning the toggle on stores your preference and nothing else; a live reporting endpoint is planned for a future release.

= Pro: Keyword auto-linking =

Automatically wrap the first N occurrences of chosen keyword phrases in your published content with a link to a short link — per-keyword case sensitivity, new-tab behavior, and a per-post cap.

The Keywords screen shows an "Applied (batch total)" column. **In this release it always reads 0.** It is not a live view-by-view counter — matching your content on every single page view would mean a database write per view, which doesn't scale — and the batch accounting pass that would populate it isn't part of this release. Auto-linking itself works normally; only that counter is inert.

Three auto-linking settings have no UI yet and can only be changed programmatically (via the `ops404_link_sentinel_settings` option): `keywords.post_types` (which post types are auto-linked; default `post`), `keywords.excluded` (post IDs and native category IDs to skip; default none), and `keywords.skip_headings` (leave headings untouched; default on). They do take effect — they're just not on a settings screen in this version.

= Pro: Deep Link Health =

The free scan answers "did this link break?". Link Health answers the harder questions.

* Scans across your short links, in-content links and comment author URLs together, on their own daily or weekly schedule — the free scan covers post content and comment author URLs on its own daily run; what Pro adds here is the shared schedule, the wider scope and the checks below.
* Flags redirect chains and suspicious-but-200 responses (parked-domain pages, "currently unavailable" pages that never actually 404, etc.) — the failures a plain status check reports as healthy.
* An email digest summarizing what a scan found, sent to the site's admin address. A scan that turns up no problems sends nothing at all — you only hear from it when there's something to act on.
* A rotating, fair-share scan cursor so no single link scope crowds out the others when there's more to scan than one run's budget allows.

= AI provider connection (Settings → AI; free) =

Settings → AI lets you choose your own OpenAI, Anthropic, or Google Gemini API key and "Test key" it — this screen and the connection test both work without a Pro licence. See "External services" below for exactly what that sends and when. On its own, this tab only stores and validates a connection; it never reads or changes your links or content. Actually using that connection to diagnose or fix a broken link is a Pro feature (below).

= Pro: AI broken-link agent (bring your own key) =

* Uses the provider/key configured on the free AI settings tab above — traffic goes straight from your server to your chosen provider, never through ours, and the key is stored encrypted, never logged, and excluded from exports.
* For each non-healthy finding from a Link Health scan, this plugin fetches the destination's own content itself (never lets the AI fetch anything) and asks the model for a proposed fix. Every proposal is re-verified live before being marked verified.
* Every applied AI fix is logged with a one-click revert, and a fix is refused if the source content has changed since the fix was proposed.
* A rough cost estimate (based on findings processed, not a live token count) is shown on the AI settings screen after each run.

Pro starts at $29/year for one site, with 5-site and unlimited tiers — see https://404ops.com/plugins/404ops-link-sentinel. It renews at the price you paid: no introductory discount that doubles in year two, no fake sales. Everything listed above this section stays free, and a lapsed Pro licence never switches a free feature off.

== External services ==

This plugin's link-health checking fetches the URLs found in your own site's content and the author URLs on approved comments (and, with the Pro add-on, your short-link destination URLs as well) to see whether each one still answers — that traffic goes to whatever site each link already points at, not to any 404ops service, and only the HTTP status code is read back. No data is sent to us.

If you enter your own API key on Settings → AI, this plugin connects directly to the provider you chose:

* **OpenAI** — terms: https://openai.com/policies/row-terms-of-use/ — privacy: https://openai.com/privacy/
* **Anthropic** — terms: https://www.anthropic.com/legal/consumer-terms — privacy: https://www.anthropic.com/legal/privacy
* **Google Gemini** — terms: https://ai.google.dev/gemini-api/terms — privacy: https://policies.google.com/privacy

Pressing "Test key" on that screen sends a minimal, fixed test message to confirm the key/model work — nothing about your site's content or links. This connection is stored and can be tested without a Pro licence. Actually asking the model to propose a fix for a broken link (Pro) additionally sends the destination URL, its HTTP status, and the fetched page's title/excerpt/content to that same provider — never your site's own content, and never anything to 404ops itself. Nothing is sent to any AI provider unless you have entered your own key for it.

No telemetry is sent (see "Telemetry" above — it is off by default and, in this release, never transmitted even when turned on), and no third-party QR code or GeoIP service is ever contacted.

== Installation ==

1. Upload the plugin files to `/wp-content/plugins/short-link-manager`, or install through the WordPress Plugins screen directly.
2. Activate the plugin. A short setup wizard will walk you through the essentials.
3. **Check Settings → Permalinks is not set to "Plain."** Short links cannot work with Plain permalinks — see the FAQ below for why. Any other option works. The plugin will tell you on its own screens if this needs changing.
4. Open **404ops → Short Link Manager** in the admin menu to create short links, review analytics, and manage categories. Every 404ops plugin on the site shares that one "404ops" menu, so this plugin no longer takes a sidebar slot of its own.
5. The plugin's other screens — Links, Broken Links, Redirects, Categories & Tags, Reports, Tools and Settings — are reached from the navigation row across the top of every one of its pages, not from the sidebar.

== Frequently Asked Questions ==

= Does this plugin make any external requests? =

Only to: (a) the URLs found in your own content, to check whether they're still reachable; (b) your destination URLs, when the Pro AI agent fetches their content to reason about a fix; and (c) your own chosen AI provider's API (OpenAI, Anthropic, or Google Gemini), only if you've entered your own key for it on Settings → AI — that screen and its "Test key" connection check work without a Pro licence, though acting on a broken link with AI is Pro. See "External services" above for exactly what each of these sends. No telemetry is transmitted (see above), and no third-party QR or GeoIP service is ever contacted.

= Where is the data stored, and what happens if I uninstall? =

Everything is stored in your site's own database. **Uninstalling leaves all data in place by default.** If you want a completely clean removal, turn on Settings → Advanced → "Remove data on uninstall" *before* deleting the plugin from the Plugins screen — only then will its tables, options, and scheduled events be removed. Simply deactivating the plugin never touches your data, regardless of that setting.

= My short links return "Not Found" — what's wrong? =

Almost always Permalinks set to "Plain" (Settings → Permalinks). WordPress only installs the rewrite that hands a URL like `/go/your-slug` to WordPress when a permalink structure is set; with Plain, your web server answers those URLs with its own 404 and WordPress — and therefore this plugin — never runs at all. Choose any option other than Plain and your existing short links start working immediately, with no changes to them. The plugin shows a notice on its own screens when this is the problem, and its Health screen reports it too.

If permalinks are already pretty and links still 404, re-save Settings → Permalinks to flush WordPress's rewrite rules.

= Does this work on WordPress Multisite? =

It is built to: this plugin creates its tables per site on first load rather than only at activation, so activating it on any site in a network gives that site its own links, analytics, and settings, independent of every other site. It is not network-activated as a single shared install, and there is no network admin screen.

Two honest caveats. **It has not been exercised on a real multisite network** — the behaviour above follows from how the plugin is built, not from a test run against one. And **"Remove data on uninstall" only cleans the site it runs on**; on a network, deleting the plugin leaves the other sites' tables in place, and those would need removing by hand. If you run multisite, treat this as untested and back up first.

= Why is my 404 log empty? =

Two common causes, and the first is the same one that breaks short links:

**Permalinks are set to "Plain."** WordPress never runs for those URLs — your web server answers them with its own 404 — so there is nothing for this plugin to record. Choose any other permalink structure.

**A full-page cache or CDN is in front of your site.** A repeat visit to the same dead URL can be served from that cache without ever reaching PHP, so the hit is not counted. The *first* hit on each URL always gets through, so nothing goes missing from the list — the counts are a floor rather than an exact total. This plugin deliberately does not send cache-defeating headers on every 404 your site serves, because that would impose a real cost on every visitor to make a counter more precise.

Also worth checking: bot traffic is hidden by default (use the filter on the 404 Log to see it), and 404s from non-GET requests are not recorded at all — those are bots probing for form endpoints, not links anyone can follow.

= Does short-link click tracking work with a full-page cache or CDN? =

Short-link redirects always send `Cache-Control: private, no-store` so browsers and most CDNs won't cache them. But if this site runs behind a full-page cache plugin, exclude `/{prefix}/*` (default `/go/*`) from that plugin's caching rules — a page cache that serves a stale response before WordPress even runs can still cause clicks to under-count and destination edits to stop propagating.

= What's the difference between a 301, 302, and 307 redirect? =

301 signals a permanent move (search engines update their index); 302 and 307 both signal a temporary redirect, with 307 additionally guaranteeing the original HTTP method is preserved on the redirected request. Choose the type when creating a short link.

= I use the Classic Editor — where is the Short link button? =

There isn't one. The Short link button is a block editor formatting tool, and no classic-editor (TinyMCE) equivalent is registered, so on a site running the Classic Editor plugin the button simply does not appear and nothing else changes. Create the link on the Links screen and paste the short URL in, exactly as before — that workflow is unchanged and is not going away.

= Who is allowed to create short links from the editor? =

Administrators, by default — the same people who can already use the Links screen. A short link is a site-wide redirect rather than post content: it is live the moment it is created, it sends visitors from a URL on your domain to anywhere on the web, and it outlives the post it was made in. So it is deliberately not granted to Authors, Editors, or Contributors just because they can write posts. On a multi-author site where you do want your editors creating them, a developer can widen it with the `ops404_link_sentinel_editor_link_cap` filter; the toolbar button and the endpoint behind it both follow whatever that filter returns, so they can never disagree.

= Can I migrate from Pretty Links? =

Yes, from the Tools screen — see the "Import, export, and migration" section above for exactly what is and isn't migrated, and the caveat about it not yet having been run against a real Pretty Links installation.

= Is the Pro AI agent going to publish changes without me reviewing them? =

No — every AI-proposed fix is written as a proposal you review and approve; nothing is applied automatically, and every applied change has a one-click revert.

== Screenshots ==

1. The dashboard with short-link and click-analytics summaries.
2. Creating a short link with categories, tags, and redirect options.
3. The Reports screen: click activity, per-link detail, and category rollups.
4. Pro: the Link Health scan results and AI fix proposals.

== Changelog ==

= 1.5.1 =
* Fix: Pro update checks select this plugin's uploaded package and remove stale update offers when licensing or release lookup fails.
* Fix: WordPress Pro updates require a compatible Free version, with update protection still registered when Free is missing or older.
* Maintenance: Keep the Free and Pro versions aligned for the updated add-on.

= 1.5.0 =
* **New: comment author URL scanning, free.** The daily scan now also checks the author URL on every approved comment and reports broken ones against the comment they came from. Previously this was part of the Pro Link Health scan only — a competitor gives it away free, and it is not a line worth holding. Comments get their own scan budget, so a long post backlog cannot starve them. Only the author URL is checked, never the comment text.
* **New: a 404 log.** The scanner has always told you whether your own content still points somewhere real. It could never tell you what was breaking for the people actually arriving — a mistyped URL, an inbound link from someone else's site, a bookmark for a page you deleted — because none of those appear in your own posts. Every dead URL a visitor hits is now recorded with a hit count, a first- and last-seen time, and the referring page. Free.
* **Fix a 404 from the row that reports it.** Type a destination beside a logged entry and press Redirect: it becomes a 301 in the redirect manager, built from the exact path that was recorded, and the entry leaves the log because it is no longer a 404. No retyping a path, no switching screens.
* The log stores **one row per dead URL, not one per visit**, so a bot hitting one missing path a million times writes a single row. Combined with a retention window and a hard row ceiling, a scanner walking thousands of invented URLs cannot fill your database.
* The 404 log has its own privacy controls under Settings → 404 Log, independent of click analytics: Do Not Track, IP handling (salted hash of a truncated address by default, full address, or nothing), bot filtering and retention.
* Internal: the IP-anonymisation and visitor-inspection helpers that click analytics has always used are now a single shared implementation rather than two copies, so a correction to how an address is truncated cannot reach one recorder and miss the other. No behaviour change to click tracking.

= 1.4.0 =
* **New: the daily/manual scan now covers custom post types, not just Posts and Pages.** Settings → General → "Post types to scan" lists every public post type on the site — turn on a WooCommerce product, a portfolio item, or any other custom-post-type-driven content and the scanner checks its links too. Post and Page stay checked by default, so an existing site's scan coverage is unchanged until you deliberately widen it. Developers can reach a non-public post type Settings doesn't list via the new `ops404_link_sentinel_scan_post_types` filter.
* Security: link-status checks (the "Scan Now" batch, the daily cron, and the Links screen's live reachability check) now go through `wp_safe_remote_head()`/`wp_safe_remote_get()` with an explicit response-size cap, instead of the plain, unbounded `wp_remote_head()`/`wp_remote_get()` they used before. Behaviour is unchanged for every legitimate destination; this only hardens the request itself against a hostile or misconfigured one.
* Docs: clarified exactly when this plugin talks to a third-party AI provider — see "External services" above. The Settings → AI connection (choosing a provider/key and using "Test key") has never required a Pro licence; the readme previously described it as a Pro-only capability, which undersold what the free plugin already does and, more importantly, undersold what it already sends.

= 1.3.0 =
* **New: create a short link from inside the editor.** Making a short link used to mean leaving the post, going to the Links screen, creating the link, copying the slug and pasting it back. Select some text, press the Short link button in the block editor's formatting toolbar, give it a destination, and the link is created and applied to your selection in one step. Leave the slug blank and it is built from the words you selected.
* New: the same panel searches your existing short links, so reusing one across several posts no longer means going to look it up. Only active links are offered — a disabled or expired one would put a dead URL in your post.
* Note on who can use it: the button is for administrators by default, because a short link is a site-wide redirect rather than post content — it goes live immediately and outlives the post it was made in. The `ops404_link_sentinel_editor_link_cap` filter widens it for multi-author sites, and governs the button and the endpoint behind it together.
* Note for Classic Editor sites: this is a block editor tool and there is no TinyMCE equivalent, so nothing changes for you — the Links screen workflow is untouched.
* Your `nofollow` / `sponsored` settings are written onto the link the editor inserts, which is the first place in the free plugin those attributes actually reach an `<a>` tag.

= 1.2.0 =
* **New: fix a broken link without leaving the report.** The Broken Links screen only ever offered "Recheck", so the only way to actually fix anything was to go and hand-edit the post. Tick the links you want and you can now replace the URL, unlink them (keeping the words, dropping the link), or ignore them — in bulk, across as many posts as the same dead URL appears in.
* New: ignored links are hidden from the Broken Links report and from the "Broken Links Found" count on the dashboard, and stay ignored when the daily scan runs again. They are listed on their own page, where you can un-ignore any of them, so ignoring is a decision you can take back rather than a disappearance.
* Note on how the rewrite works: the link is located by matching the exact `href`/`src` attribute value the scan recorded — including the site-relative spelling of an internal link — never by a plain search-and-replace across the post, which would also mangle a longer URL that happens to start with the same characters, a mention of the URL in your own prose, or a block's stored settings. If the URL is no longer in the post, that link is reported as skipped and nothing is written.
* Note on unlinking and blocks: a link that a block owns as part of its structure — a Button, a File download — is deliberately left alone, because deleting that link would leave the block broken in the editor. Those are reported so you can fix them in the editor instead. Every bulk run tells you how many links were changed and how many were skipped, and why.

= 1.1.0 =
* **New: scheduled link scanning is now free.** The daily WP-Cron scan used to require Pro, which left the free plugin able to check links only when someone remembered to press "Scan Now". It now runs on every install. Nothing was removed from Pro to make room: existing Pro sites keep the same single daily scan they already had, and a lapsed Pro licence no longer stops it.
* Change: the plugin now describes itself for what it mostly is — a link manager with short links, QR codes and click analytics — rather than leading with broken-link checking, which is the smaller half of it. No feature moved, was renamed, or changed behaviour as part of this.
* Change (Pro): the Link Health screen and its scan are described as the deeper checks they are — redirect chains, suspicious-but-200 pages, comment-author URLs and the email digest — rather than as "scheduled scanning", which is free now.
* Fix: the License screen quoted a Pro price with no link to buy it. It now links to the Pro page.
* Fix: the dashboard offered Pro "auto-suggested redirects for broken internal links". No such feature exists or ever shipped; the claim is gone.
* Fix: the Pretty Links migration refused to import any link whose destination domain no longer resolves — precisely the broken links most people migrate in order to find. Those links now come across and are listed separately from the failures, so you can see which ones need a new destination. URLs rejected for any other reason (a non-web scheme, or an address inside a private or loopback network) are still refused, unchanged.
* Fix: a Pretty Links link whose slug contained no letters or digits was counted as "skipped" with no message at all, so there was no way to tell which links had not come across, or why. Every skipped link is now named.
* Fix: when two different Pretty Links slugs normalized to the same short link ("My-Link" and "my link" both become "my-link"), the second was silently dropped and reported as though it had already been migrated. That is data loss, and it is now reported as a collision naming both links. Re-running the migration is still a safe, silent no-op for links that genuinely did already migrate.
* Fix: CSV import refused any row whose destination or fallback domain no longer resolves, reporting it as an invalid URL. That made it impossible to import a list of the very links a broken-link plugin exists to find — including a CSV this plugin itself exported. Those rows now import, and are listed separately from the failures so you can see which links need a new destination. URLs rejected for any other reason (a non-web scheme, credentials in the address, a port the site refuses, or an address inside a private or loopback network) are still refused, unchanged.

= 1.0.3 =
* New (Pro): a quiet, dismissible admin notice now appears once this add-on's licence hasn't revalidated successfully in a few days, before Pro features would otherwise silently lapse with no warning. Updated the shared licence client.
* Fix (Pro): a licence's cached "valid" state is now bounded to 14 days since its last successful check, so a site whose WP-Cron never runs no longer keeps a revoked or expired licence reading active indefinitely.

= 1.0.2 =
* Fix (Pro): licence activation and update checks used the wrong authentication scheme and an incorrect product filter against the licence server, which could reject valid requests. Updated the shared licence client.

= 1.0.1 =
* Fix (Pro): first-time licence activation could fail against the real licence server on a brand-new key (updated the shared licence client).

= 1.0.0 =
* Add branded short links: redirects (301/302/307), expiry, nofollow/sponsored flags (both off by default), and a locally generated QR code.
* Add privacy-respecting click analytics: anonymized/full/none IP handling, DNT support, bot flagging, retention pruning, dashboard/reports with inline-SVG sparklines.
* Add categories and tags for short links, with rollup stats.
* Add CSV import/export and a one-way Pretty Links migrator.
* Add a one-time onboarding wizard and an opt-in (currently non-transmitting) telemetry preview.
* Add an opt-in, read-only REST endpoint for short links (off by default).
* Add Pro: keyword auto-linking, scheduled link-health scans with an email digest, and a BYOK AI broken-link fixer with one-click revert.
* Fix: several admin controls had no accessible name — the per-row selection checkboxes and rename fields, every filter and bulk-action dropdown on the Links screen, the reporting-period selector, and the CSV file field. A screen reader announced them as unlabelled, with nothing to say which row or which filter. Each now carries a label naming what it does, and per-row controls name the specific link or category they act on.
* Fix: an unknown, deleted, or mistyped short link served the site's home page with a "200 OK" status instead of a 404. Search engines read that as every bad short link being a duplicate of your front page. Retired links still answer "410 Gone", which is more informative than a 404 and unchanged.
* Fix: the Health screen reported that short links "resolve correctly" on sites using Plain permalinks, where in fact none of them can work. It now reports that as a failure and says how to fix it, and the plugin's screens carry a notice while it applies.
* Fix: the redirect manager could not redirect anywhere off this site. It used `wp_safe_redirect()`, which rejects external hosts and silently substitutes the WordPress admin URL, so a rule pointing at another domain sent visitors to /wp-admin/ instead of the destination. Same-site targets were unaffected. Short links were never affected — they always used the correct call.
* Fix: serving a redirect no longer makes a blocking HTTP request to the destination first. That request warmed no cache the visitor could use, and made every visitor wait out a round trip to the destination before their browser was told where to go.
* Fix: a schema-migration defect meant a future column added to 11 of this plugin's tables would never actually reach an already-installed site; every table now upgrades correctly.
* Fix: uninstall is real (opt-in only) instead of a no-op stub.
* Change: the Reports screen's per-link and per-category click rollups now cover the trailing 365 days rather than all-time (keeps the underlying query bounded as click history grows), and the Categories tab shows at most the top 100 categories by clicks, with an on-screen note when that cap is reached.

= 0.1.2 =
* Improve dashboard header copy for clarity.

= 0.1.1 =
* Fix: reduce source_path column to VARCHAR(255) to stay under the MySQL key length limit.
* Fix: resolve PHPStan level 8 findings in the redirect and link-repository code.

= 0.1.0 =
* Initial release: link scanning, broken-links report, redirect manager, and settings.

== Upgrade Notice ==

= 1.1.0 =
Scheduled daily link scanning is now part of the free plugin instead of Pro. Pro sites are unaffected — same single daily scan, and it now survives a licence lapse. No existing feature was moved into Pro.

= 1.0.3 =
Pro now shows an early warning notice before licence-related features would lapse, and no longer keeps a revoked or expired licence active forever on a site whose WP-Cron never runs. Pro users should update.

= 1.0.2 =
Fixes Pro licence activation and update checks against the real licence server. Pro users should update.

= 1.0.1 =
Fixes first-time Pro licence activation. Pro users on a licence that has never activated before should update.

= 1.0.0 =
First public release. Short links with redirects and expiry, privacy-respecting click analytics, categories and tags, CSV import/export, a Pretty Links migrator, and a broken-link scanner with redirect manager. Pro adds keyword auto-linking, link-health scans, and a BYOK AI broken-link fixer.
