=== Swirv ===
Contributors: mattsteady
Tags: links, redirects, campaigns, analytics, qr-code
Requires at least: 6.3
Tested up to: 7.0
Requires PHP: 7.4
Stable tag: 1.2.49
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Personal link manager with personalised Passes — redirect, personalise & monitor links on your own WordPress domain.

== Description ==

Swirv is a personal link manager for WordPress. Redirect, personalise, and monitor links on your own domain, so you can share a URL once and change where it points later without resending anything.

Swirv also includes **Passes**: personalised versions of a link with their own unguessable code and reference label. Send supporters, customers, press contacts, or subscribers their own private URL, then update the parent link once when the destination changes.

= Core features =

* Redirect links on your own WordPress domain, such as `yoursite.com/swirv/download`
* Editable target URLs, so links already shared can point somewhere new
* Personalised Passes tied to a parent link
* Campaigns for grouping links, including optional public campaign pages
* Monitor clicks with daily charts and privacy-focused event storage
* QR codes for links and Passes
* Health scans for checking target URLs
* JSON/ZIP import and export for backups and migrations
* Optional prefixless mode, with clash checks against existing WordPress pages

A companion plugin, Swirv Pro, is available separately for larger libraries and team workflows.

= Privacy and external services =

Swirv is designed to keep link and click data on your WordPress site.

* Click tracking does not store visitor IP addresses in any form. The raw IP is read only to derive a 2-letter country code (see below) and is never written to the database, hashed or otherwise.
* Click tracking stores a 2-letter ISO country code per click. Swirv reads the country from Cloudflare's `CF-IPCountry` request header (set on the incoming request by Cloudflare, no outbound call) when present, and otherwise looks the visitor's IP up against the local MaxMind GeoLite2-Country database if one has been installed. With neither source available, the country code is stored as empty. See **External services** below for the MaxMind download details.
* Detailed click tracking is on by default; you can switch it off under **Swirv -> Settings -> Data**.
* Redirects send `Referrer-Policy: no-referrer` and Swirv URLs are served with `noindex, nofollow` headers.
* Swirv makes server-side HTTP requests to the target URLs you have added in three situations: (1) when you save a Link, Swirv sends a HEAD request to learn the target's MIME type, used to drive per-link image-target download behaviour; (2) Health scans send HEAD requests when you run them; (3) when a visitor clicks an image-typed Link that has the force-download behaviour active, Swirv fetches the image body server-side and streams it back to the visitor. In all three cases the destination server receives a request from your WordPress site.
* The save-time HEAD probe sends a User-Agent of `Swirv Target Probe/{version} (+{your_site_url}; content-type sniffer)`. The Health-scan and inline-download paths use the default WordPress HTTP API User-Agent. No visitor data, click data, or WordPress account information is sent in the body of these requests.
* The **About ↗** and **Swirv Pro ↗** submenu items, and the **★ Swirv Pro** badges next to Pro-only features in the admin, open swirv.org URLs in a new browser tab. The connection happens in the user's browser, not on the WordPress server. See **External services** below for details.

== External services ==

Swirv reads Cloudflare's HTTP_CF_IPCOUNTRY request header when your site is fronted by Cloudflare. Swirv does not send any data to Cloudflare; the header is set by Cloudflare's edge on incoming requests as part of your site's existing Cloudflare relationship. The only data Swirv extracts is the 2-letter ISO country code, used for visitor-country analytics on this install. If your site is not on Cloudflare, this code path is inert.

Service: Cloudflare (https://www.cloudflare.com)
Terms of service: https://www.cloudflare.com/terms/
Privacy policy: https://www.cloudflare.com/privacypolicy/

Swirv can connect to MaxMind, Inc. to download the GeoLite2-Country database, used to map visitor IP addresses to a 2-letter country code for analytics. The database is stored locally on your server under `wp-content/uploads/swirv/geoip/` and every country lookup happens on your server against that local copy; Swirv never sends visitor IP addresses to MaxMind.

Swirv contacts MaxMind only after a site administrator has added their own MaxMind licence key under **Swirv -> Settings -> Analytics -> Geography** and either (a) clicked the **Download / update database now** button, or (b) the monthly refresh cron has fired since the key was saved. Without a saved licence key, the cron fires but takes no action and Swirv makes no outbound request to MaxMind.

The licence key you enter is stored on your own server in the WordPress options table (as the `swirv_maxmind_license_key` option), in the same plain-text form WordPress core uses for other site credentials and API keys; Swirv applies no separate encryption to it. It is never transmitted anywhere except to MaxMind as the `license_key` parameter of the download request described below. Anyone with read access to your WordPress database (including other plugins running on the site) can read it, so treat your MaxMind key like any other site secret. You can clear it at any time by emptying the field and saving.

When downloading the database, Swirv issues a single HTTPS GET request to `https://download.maxmind.com/app/geoip_download` with three query-string parameters: `edition_id=GeoLite2-Country`, `license_key={your key}`, and `suffix=tar.gz`. The request uses the default WordPress HTTP API User-Agent (which includes your site URL, per WordPress core). No visitor data, click data, or WordPress account information is sent by Swirv as part of this download request.

Service: MaxMind GeoLite country database (https://www.maxmind.com)
Terms of use: https://www.maxmind.com/en/terms-of-use
Privacy policy: https://www.maxmind.com/en/privacy-policy
GeoLite end-user licence agreement: https://www.maxmind.com/en/geolite/eula

Swirv links the admin to swirv.org (the Swirv project's marketing and documentation site, operated by Matt Steady) in three places:

* The **About ↗** submenu item under Swirv in the WordPress admin sidebar opens https://swirv.org in a new tab. This is the Swirv project's home page — project information, contact details, and links to documentation.
* The **Swirv Pro ↗** submenu item — visible only when the Swirv Pro companion plugin is not active — opens https://swirv.org/swirvfree/upgrade.html in a new tab. This is the page describing the Swirv Pro companion plugin.
* The **★ Swirv Pro** badges next to Pro-only features in the admin (the Settings → Analytics tab, the Passes and Analytics admin pages, and the Health Scan page) also open https://swirv.org/swirvfree/upgrade.html in a new tab.

These are user-initiated browser links, not server-side calls. Swirv does not send any visitor data, click data, site URL, or WordPress account information to swirv.org as part of these links — the connection happens in the user's browser when they click the link. Standard web-server access logs on swirv.org (the visitor's IP address, user-agent, referer) apply the same as any browser navigation to any site.

Service: swirv.org (the Swirv project's marketing and documentation site)
Terms of use: https://swirv.org/terms.html
Privacy policy: https://swirv.org/privacy

Note on uptime-monitor names: Swirv ships a default "exclude monitor traffic" list (Settings → Analytics) containing names such as Pulsetic, UptimeRobot, Better Stack, Hyperping, StatusCake, and Pingdom. These are not external services Swirv connects to. They are text substrings Swirv matches against the User-Agent header of incoming requests to your own site, so that clicks from those uptime monitors are not counted in your analytics. No data is sent to any of these services, and no network connection is made.

== Installation ==

1. Upload the `swirv` folder to `/wp-content/plugins/`, or install Swirv from the WordPress plugin directory.
2. Activate the plugin through the **Plugins** screen in WordPress.
3. Open **Swirv** in the admin sidebar and create your first link.

Optional: open **Swirv -> Settings -> General** before sharing links if you want to change the URL prefix. The default is `/swirv/your-link`. Prefixless mode is also available, but changing the prefix after sharing links will break URLs already in use.

== Frequently Asked Questions ==

= What is the difference between a Link and a Pass? =

A **Link** is a short URL anyone can use. A **Pass** is a personalised URL under a Link, usually issued to one person, subscriber, customer, or contact. Passes inherit the parent Link target, so changing the Link updates every Pass beneath it.

= Does Swirv use a third-party short-link service? =

No. Swirv links use your own WordPress domain. If your site is `yourband.com`, your links are on `yourband.com`.

= Can links live at the top level of my site? =

Yes. In **Settings -> General**, turn off the URL prefix to use links such as `yoursite.com/download`. Swirv checks for clashes with existing WordPress pages and posts before allowing this.

= Is click tracking GDPR-compliant? =

Swirv avoids storing raw IP addresses and keeps analytics data on your site. Compliance depends on your site, jurisdiction, and privacy policy, so treat this as privacy-friendly design rather than legal advice.

= Can I turn off analytics? =

Yes. Detailed click tracking is on by default. Go to **Swirv -> Settings -> Data** and switch it off; Swirv will stop writing detailed click-event rows and keep only basic counts.

= Can I move my Swirv data to another site? =

Yes. **Swirv -> Settings -> Data** includes JSON/ZIP export and import for campaigns, links, Passes, and optional analytics.

= What is the sponsored-link option? =

Flagging a Link as sponsored makes Swirv serve the redirect with `rel="sponsored"` signalled via the standard RFC 8288 `Link` HTTP header, plus `X-Robots-Tag: noindex, nofollow, noarchive` and `Referrer-Policy: no-referrer`. The visitor still gets a normal 302 to the affiliate destination — no interstitial page. For SEO disclosure, the right place for `rel="sponsored"` is on the link anchor in your source page, not on the redirect target.

== Source Code and Bundled Libraries ==

Swirv ships two minified third-party JavaScript libraries and keeps all custom Swirv JavaScript readable and unminified.

* `admin/js/chart.umd.min.js` is Chart.js 4.5.1. Readable source is available at `https://www.npmjs.com/package/chart.js/v/4.5.1` and `https://github.com/chartjs/Chart.js/tree/v4.5.1`.
* `admin/js/qrcode.min.js` is node-qrcode 1.5.4, bundled from the browser entry point as an IIFE that exposes `window.QRCode`. Readable source is available at `https://www.npmjs.com/package/qrcode/v/1.5.4` and `https://github.com/soldair/node-qrcode/tree/v1.5.4`.

== Screenshots ==

1. Your Links - main link management with target editing, click counts, and QR codes.
2. Your Passes - personalised Passes with optional access controls.
3. Privacy-first analytics - daily chart and top breakdowns.
4. Settings - configure the URL prefix or use bare `/your-link` URLs.
5. Campaign landing page - public page for grouped links.
6. Health Scan - find dead, redirecting, or slow target URLs.

== Changelog ==

= 1.2.49 =
* Fixed: opening an add or edit screen by direct URL now always returns you to the list view, including on hosts that have PHP output buffering turned off.

= 1.2.48 =
* New: Data retention - automatically delete click events older than a chosen number of days (Settings -> Analytics) so older analytics data clears down on its own over time.
* Improved: dead or not-yet-configured Swirv links now show a tidy branded "link not available" page instead of the theme's generic 404.
* Fixed: browser prefetch/prerender of a link no longer inflates its click count - speculative loads receive the redirect but are not counted, so a single visit registers a single click.
* Maintenance: Plugin URI now points to the dedicated Swirv (free) landing page, per WordPress.org listing guidelines.
* Hardening: internationalised remaining admin messages and tightened output escaping.
* Downloads: preserve non-ASCII (accented / Unicode) filenames on forced downloads via RFC 6266.
* Hardening: further input sanitisation and output escaping across admin screens.
* Hardening: the uninstall handler now blocks direct file access.
