﻿=== Linkjet ===
Contributors: hafenstudios
Tags: redirect, affiliate, link cloaking, short url, rest api
Requires at least: 6.2
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.5.3
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Link redirection & affiliate link management with click stats, QR codes, a 404 monitor, CSV/JSON backup and a full REST API.

== Description ==

Linkjet manages redirects and affiliate links with an emphasis on safety and automation: regular-expression redirects are validated when you save and can be tested against a sample path before going live, a conflict guard warns before a redirect shadows a real page or touches critical paths such as checkout and login, a 404 monitor separates real broken links from bot and scanner noise, and the complete feature set is scriptable through a documented REST API with an OpenAPI manifest, for automation and AI-assisted workflows.

**Features**

* Pretty short links / branded links on your own domain (e.g. `your-site.com/go/product`)
* Affiliate link management: cloaking, `rel="nofollow"` / `rel="sponsored"`, query-parameter forwarding
* Redirect types 301, 302, 307, 308 and 410 (Gone)
* Regular-expression redirects with `$1`–`$9` back-references, validated when you save and testable against a sample URL before going live
* Conflict guard: warns you before a redirect shadows a real page, overlaps another rule or hits critical paths such as checkout, login or the REST API – and blocks redirect loops outright
* Click statistics with time-series charts, plus device, browser and country breakdowns
* GDPR-friendly IP anonymisation, bot filtering, optional referrer tracking
* Automatic click-log cleanup after a configurable number of days (WP-Cron)
* Categories to organise your links and bulk actions to manage many at once
* QR code per link, generated locally in the browser (optional drop-in library, no external calls)
* Gutenberg block and `[linkjet]` shortcode for clean affiliate anchors in your content
* 404 monitor that separates real broken links from bot traffic and vulnerability-scanner probes, with automatic log rotation so the table cannot grow forever
* CSV and full JSON backup (import/export) for migration and backups
* One-click import wizard for existing **Pretty Links** (v3 and the v4 custom-table data model) and **Redirection** setups, including click statistics
* Fully internationalised interface, ready for community translations on translate.wordpress.org, plus a language override in the settings
* Complete REST API under `/wp-json/linkjet/v1/`, authenticated via Application Passwords

**REST API endpoints**

* `GET/POST /wp-json/linkjet/v1/links`
* `GET/PUT/DELETE /wp-json/linkjet/v1/links/<id>`
* `GET /wp-json/linkjet/v1/links/<id>/clicks`
* `GET/POST /wp-json/linkjet/v1/categories`
* `DELETE /wp-json/linkjet/v1/categories/<id>`
* `GET /wp-json/linkjet/v1/stats`
* `GET/PUT /wp-json/linkjet/v1/settings`
* `POST /wp-json/linkjet/v1/links/test-pattern` (validate a regular expression and test it against a sample path)
* `GET /wp-json/linkjet/v1/404s` (add `?noise=0` for real 404s, `?noise=1` for scanner/bot noise) and `POST /wp-json/linkjet/v1/404s/<id>/redirect`
* `GET /wp-json/linkjet/v1/export`
* `POST /wp-json/linkjet/v1/import` (`?mode=replace` to overwrite)
* `GET /wp-json/linkjet/v1/openapi` (machine-readable API manifest)

Endpoints require the `manage_linkjet` capability, which is granted to administrators automatically and can optionally be granted to editors. A machine-readable OpenAPI manifest is available at `/wp-json/linkjet/v1/openapi`, ready to import into Postman, Insomnia or any API client.

== Installation ==

1. Upload the `linkjet` folder to `/wp-content/plugins/`, or install it through the Plugins screen.
2. Activate the plugin.
3. Open the **Linkjet** menu. On first activation the import wizard opens automatically and offers to import existing Pretty Links / Redirection data.
4. Create your first link, or import via CSV/JSON.

== Frequently Asked Questions ==

= Is any of this limited, or nagging me to upgrade? =
No. Everything described above is part of this plugin, without usage limits, feature locks or upgrade banners in your admin area. There is a separate paid add-on (Linkjet Pro) for agency-style extras such as geo/device targeting, split tests, a broken-link checker and scheduled client reports, but this plugin never asks you to buy it and works completely on its own.

= Can I import my Pretty Links setup after the 4.0 rewrite? =
Yes. The import wizard reads both the classic Pretty Links tables and the new v4 custom-table data model, and it reads the columns that are actually present – so half-migrated installations are imported as far as the data allows instead of failing. Entries without a target URL are reported separately rather than silently skipped.

= Does it send any data to external servers? =
No. Click tracking is first-party and stays in your own database. No external analytics, no CDN calls.

= How do I control Linkjet from outside WordPress (or from an AI/automation)? =
Use the REST API under `/wp-json/linkjet/v1/` with an Application Password (Users → Profile → Application Passwords). The OpenAPI manifest at `/wp-json/linkjet/v1/openapi` describes every endpoint and can be imported into any API client.

= Can I import my existing Pretty Links or Redirection links? =
Yes. The import wizard (Linkjet → Import wizard) detects both plugins' database tables and imports your links with one click, optionally including click statistics.

= Is it GDPR-compliant? =
IP addresses can be anonymised (last IPv4 octet / last 80 IPv6 bits removed), bots are not counted, country is only read from headers your server/CDN already provides (no external GeoIP database), and click logs can be auto-deleted after a chosen retention period.

= How do I enable the QR code feature? =
Drop an MIT-licensed `qrcode.min.js` (e.g. davidshimjs/qrcodejs) into `admin/assets/`. The plugin makes no external calls; the QR button appears only when the library is present.

= Where do I get support? =
Please use the plugin's support forum on WordPress.org. Linkjet is developed by hafenstudios (https://hafenstudios.com); you can also reach us at info@hafenstudios.com.

== Screenshots ==

1. Dashboard with click chart and top links
2. Link list with copy buttons, bulk actions and QR codes
3. Add/edit link with affiliate options
4. Regular-expression redirect with pattern validation and live test
5. Statistics with device, browser and country breakdowns
6. 404 monitor separating real 404s from bot and scanner noise
7. Tools: CSV/JSON backup and REST API reference
8. Import wizard for Pretty Links (v3 and v4) and Redirection

== Changelog ==

= 1.5.3 =
* Change: translations are no longer shipped inside the plugin. They come from translate.wordpress.org, where anyone can contribute and WordPress installs them automatically.
* Change: every table name now goes through the %i identifier placeholder of wpdb::prepare(), so no query builds its own SQL from an interpolated name. Because %i needs WordPress 6.2, that is the new minimum version.
* Change: the bundled translation loader now handles all translation loading on its own; the redundant load_plugin_textdomain() call was removed. Behaviour is unchanged, including the language override setting.
* Fix: four strings with placeholders now carry a translators comment, so translators can see what the placeholder stands for.
* Change: input is sanitised at the point of entry as well, not only in the data layer. No behavioural change; the data layer already cleaned every value.
* Change: database queries, view templates and view filters now carry the reasoning for the coding-standard exceptions they need, so reviewers and contributors can see why they are there.

= 1.5.1 =
* Change: English is now the source language of all interface strings. German, French, Spanish, Italian and Dutch were bundled with that release; as of 1.5.3 translations come from translate.wordpress.org instead. The language setting keeps working as before.
* Change: tested up to WordPress 7.1.

= 1.5.0 =
* New: regular-expression redirects. Patterns are validated when you save (an invalid pattern is refused instead of silently never matching) and can be tested against a sample path right in the editor. Back-references `$1`–`$9` are available in the target URL.
* New: conflict guard. Before saving, Linkjet warns you if a redirect would shadow an existing page, overlap another rule, or affect critical paths such as checkout, login or the REST API. Redirect loops are refused outright.
* New: the 404 monitor separates real broken links from bot traffic and vulnerability-scanner probes (`wp-config`, `phpmyadmin`, `cgi-bin` and friends). The badge counts real 404s only, noise has its own tab, and a configurable row limit rotates the log automatically so it cannot grow forever.
* New: the import wizard understands the Pretty Links 4.0 data model, including installations left half-migrated by that upgrade. Columns are detected instead of assumed, and entries without a target URL are reported rather than dropped silently.
* New: REST endpoint `POST /links/test-pattern` and a `noise` filter on `GET /404s`; both are documented in the OpenAPI manifest.
* Fix: the language setting works again. Since WordPress 6.5 loads bundled translation files by itself, the plugin's own language override was silently ignored – picking e.g. French on an English site kept showing English. An explicitly chosen language now takes precedence.
* Fix: the conflict guard's warnings and several other new texts were only available in German; all six languages are complete again.
* Fix: two placeholder texts in the admin UI were hard-coded in German and stayed German in every language.

= 1.4.2 =
* Fix: bulk-deleting links now fires `linkjet_link_deleted` for each link so add-ons can clean up their data (previously only single deletes did).
* New: `linkjet_links_purged` action fires when a backup import replaces all links – add-ons must clear link-related data there because link IDs are reused after the reset.

= 1.4.1 =
* Fix: the per-link `nofollow` setting is now also applied to the redirect itself via an `X-Robots-Tag: nofollow` response header – previously it only affected shortcode/block HTML, so search engines still followed cloaked affiliate redirects. Links with nofollow disabled stay dofollow (no header). New filter `linkjet_redirect_robots` to customise the directives (e.g. add `noindex`).

= 1.4.0 =
* New: developer hooks and filters so add-ons can extend Linkjet cleanly (`linkjet_before_redirect`, `linkjet_redirect_target`, `linkjet_click_logged`, `linkjet_link_created`/`_updated`/`_deleted`, `linkjet_admin_menu`, `linkjet_admin_tabs`, `linkjet_link_edit_fields`, `linkjet_after_save_link`, `linkjet_settings_fields`, `linkjet_after_save_settings`, `linkjet_link_row_actions`, `linkjet_rest_prepare_link`). See HOOKS.md.

= 1.3.1 =
* New: case-insensitive redirect fallback – redirects now also catch differently-cased URLs (e.g. /Programm and /PROGRAMM), but only when the URL would otherwise return a 404, so real pages are never hijacked.
* Change: removed promotional wording from the plugin name/description and assets.

= 1.3.0 =
* New: dedicated `manage_linkjet` capability – administrators always keep access; editors can optionally be granted access without full admin rights.

= 1.2.1 =
* Fix: slugs now preserve upper/lower case (e.g. /Programm); exact redirect matching is case-sensitive so an existing lower-case page is not hijacked.

= 1.2.0 =
* New: 404 monitor – logs dead URLs and turns them into redirects, with REST support for AI-assisted maintenance.
* New: REST endpoints for settings (`/settings`) and a machine-readable API manifest (`/openapi`).
* Fix: reliable UI translation loading, independent of the WordPress translation-file parser.

= 1.1.0 =
* New: performance cache for the redirect engine (slug index, avoids DB queries on non-matches).
* New: GDPR auto-cleanup – click logs are deleted automatically after X days via WP-Cron.
* New: device, browser and country statistics (country only from server/CDN headers, no external GeoIP DB).
* New: bulk actions in the link list (activate, deactivate, delete, assign category).
* New: slug collision warning when saving.
* New: QR code per link, generated locally (optional drop-in library, no external calls).
* New: Gutenberg block "Linkjet link".
* New: import wizard for Pretty Links and Redirection (incl. click statistics).
* New: full JSON backup (admin + REST `/export` & `/import`).
* New: multilingual UI (English, German, French, Spanish, Italian, Dutch) + language override.
* Security: protection against PHP object injection in the Redirection importer.

= 1.0.0 =
* Initial release.

== Upgrade Notice ==

= 1.4.1 =
The per-link nofollow setting now also applies to the redirect (via X-Robots-Tag). Recommended for anyone cloaking affiliate links.

= 1.4.0 =
Adds developer hooks and filters for extensions (see HOOKS.md). No changes to existing behaviour.

= 1.3.1 =
Redirects now catch differently-cased URLs without hijacking real pages, plus a dedicated capability so editors can manage links without full admin rights.

= 1.1.0 =
Adds a redirect performance cache, GDPR click-log cleanup, device/browser/country stats, bulk actions, QR codes, a Gutenberg block, an import wizard, JSON backups and a fully translatable interface.
