=== PBJ Geo Viewer ===
Contributors: pbj
Tags: geolocation, woocommerce, restrict, view-only, country
Requires at least: 5.8
Tested up to: 6.7
Requires PHP: 7.4
Stable tag: 1.5.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Restrict purchases, forms, comments and account features to visitors from selected countries. Everyone else sees a fully browseable, SEO-friendly, view-only site.

== Description ==

PBJ Geo Viewer makes your site **view-only** for visitors outside the countries you explicitly allow. Selected countries get the full interactive experience; everyone else can still browse every page, but cannot:

* Add products to cart, see the checkout, or place an order (WooCommerce)
* Submit any form (Contact Form 7, Gravity Forms, WPForms, Ninja Forms, native)
* Leave comments
* Log in, register, or access My Account

**Search engines and admins are always exempt** so SEO and editorial workflows are unaffected.

= Geo detection =

1. Cloudflare `CF-IPCountry` header (instant, free, no API call) if the site is behind Cloudflare
2. Common reverse-proxy headers (`X-Vercel-IP-Country`, etc.)
3. Free ip-api.com fallback, cached per-IP for 24 hours by default

= Crawler safety =

Page HTML is fully rendered for everyone — only interactive controls are disabled. Crawlers and AI scrapers (Googlebot, Bingbot, GPTBot, ClaudeBot, etc.) are detected by user-agent and always treated as fully allowed so they index the canonical experience.

== Installation ==

1. Upload `pbj-geo-viewer.zip` via Plugins → Add New → Upload Plugin, or extract into `wp-content/plugins/`.
2. Activate the plugin.
3. Go to **Settings → Geo Viewer**.
4. Pick mode (Allowlist or Blocklist), pick countries, pick what to block.
5. Add your office IP to the admin allowlist so you can test as a blocked visitor.

== Upgrade Notice ==

= 1.2.0 =
Adds the built-in User Guide tab. Includes everything from 1.1.0 (login
rework, cache fix, bot lookup fix) — upgrade straight to this one.

= 1.1.0 =
Login rework + cache fix. The login page is no longer blocked — admins and
editors (plus a new username allowlist) can always sign in from blocked
regions, so a traveling admin can never be locked out; registration stays
blocked. Also stops page/edge caches (incl. pbj-seo's LiteSpeed module) from
storing the blocked view-only page and serving it to allowed visitors, and
stops geo lookups for crawler traffic. Recommended for all installs.

= 1.0.5 =
Closes a form-submission leak: the POST interceptor previously matched a hardcoded list of form-plugin action names, so first-party or unknown form plugins that submitted to admin-post.php with a different action slipped through. Now blocks by endpoint (admin-post.php, wp-comments-post.php, wp-signup.php, wp-login.php?action=register) so every form/comment/signup endpoint is covered. Strongly recommended.

= 1.0.4 =
Closes registration leaks: WooCommerce My Account / checkout registration, REST API user creation (`/wp/v2/users`), and core wp-login.php `?action=register` (POST) were not fully blocked. Adds an `X-PBJ-Geo` diagnostic HTTP header so you can verify the plugin's decision per request from the browser Network tab. Changes default unknown-country behaviour to fail-closed in BOTH allowlist and blocklist modes (was leaking in blocklist when CF-IPCountry wasn't set).

= 1.0.3 =
Critical fix: form submissions from blocked regions were leaking through because the form-integration filters were registered on the `wp` action, which does not fire for REST API or admin-ajax requests. Modern Contact Form 7, Gravity Forms, WPForms, and the PBJ Form Builder all submit via those paths. This release moves the form/comment/login hooks to `init`, adds a REST API gate, broadens the POST interception, and registers PBJ Form Builder's endpoints. Strongly recommended for any install relying on form blocking.

= 1.0.2 =
Fixes a WordPress 6.7+ "translation loaded too early" PHP notice that caused header warnings on plugin activation. Recommended upgrade for all installs.

= 1.0.1 =
Adds proper in-place upgrade support: `Update URI` header, settings schema versioning with idempotent migrations, and a self-hosted updater. Re-upload over the previous version and your settings are preserved.

== Changelog ==

= 1.5.0 =
* Reworked: the Settings tab is now a guided five-step walkthrough, each step
  in its own numbered card with a plain-English "what this does" explanation —
  Step 1 choose your mode and countries (allowlist vs blocklist, what "unknown
  country" means, plus the optional IP blacklist), Step 2 choose what
  view-only visitors can't do (pages still render fully, so SEO is safe),
  Step 3 what blocked visitors see (banner vs inline message and where each
  appears), Step 4 exemptions (IP allowlist = full-site exemption, username
  allowlist = sign-in only, geo cache), and Step 5 test your setup (the
  diagnostics panel, how to read it, and how to test with a spoofed
  CF-IPCountry header / the X-PBJ-Geo response header).
* Unchanged: still one form with one Save button; every setting name, saved
  value, and all enforcement behaviour are identical to 1.4.0.

= 1.4.0 =
* Added a License section in the plugin's settings where you enter your PBJ license key to unlock automatic updates. The plugin is fully functional without a key; a key covers up to 3 sites, and on pbj.tech itself no key is required.

= 1.3.1 =
* Fixed the country picker layout: the checkbox list used CSS columns, which forced a horizontal scrollbar in a fixed-height box (awkward on some Windows browsers). It is now a responsive grid that reflows to the panel width and only ever scrolls vertically.

= 1.3.0 =
* Moved the admin screen to its own top-level "Geo Viewer" item in the sidebar (was buried under Settings).
* Country selection is now a filterable checkbox list with Select-all-shown / Clear-all and a live count, instead of the old ctrl-click multi-select box — much easier to pick many countries.
* New: IP blacklist. Enter exact IPs, CIDR ranges, or wildcard patterns (203.0.113.*, 10.*.*.*, 2001:db8:*); matching visitors are always view-only regardless of country. Search engines and signed-in admins/editors stay exempt. Schema db_version 3 -> 4 (new `ip_blacklist` key, backfilled on upgrade).

= 1.2.0 =
* Added: built-in User Guide — a new tab on Settings → Geo Viewer with the
  full guide (how requests are decided, every setting, recipes, verification,
  troubleshooting, quick reference). Also linked from the Plugins list row.

= 1.1.0 =
* Changed: the login page (wp-login.php) is no longer blocked. Sign-ins are
  gated at the `authenticate` filter instead: administrator/editor accounts
  may always sign in from blocked regions, plus any username/email on the new
  "Login username allowlist" setting (`pbj_geo_viewer_login_allowed` filter
  for programmatic control). Fixes the traveling-admin lockout; registration
  remains fully blocked. Schema db_version 2→3 (`admin_user_allowlist`).
* Fixed: blocked-region page variants (banner, blocking CSS, disabled buttons)
  are no longer storable by page/edge caches — blocked responses send
  `X-LiteSpeed-Cache-Control: no-cache` + `nocache_headers()` and the plugin
  hooks pbj-seo's `pbj_seo_cacheable` filter. Previously a blocked-region
  visitor could prime a public cache and every visitor worldwide got the
  view-only page for up to the cache TTL.
* Fixed: crawler requests no longer trigger ip-api.com lookups — the
  diagnostic header resolves bot/admin status before any country lookup
  (rate-limit and latency protection on crawled pages).
* Added: `pbj_geo_viewer_trust_proxy_headers` filter (default true). Set false
  on hosts NOT behind Cloudflare/a geo proxy, where `CF-IPCountry` /
  `X-Forwarded-For` are client-controlled and spoofable; detection then uses
  REMOTE_ADDR + ip-api only.
* Added: `pbj_geo_viewer_diagnostic_header` filter to disable the `X-PBJ-Geo`
  header; it now reports major.minor only.
* Fixed: uninstall now also removes `pbj_geo_viewer_db_version` and the
  updater transient.
* Removed: dead code (`force_true`/`force_false`), no-op filters
  (`gform_disable_form_theme_css`, `wpforms_process_bypass_captcha`), and
  `sslverify => false` on the plain-HTTP ip-api call.

= 1.0.5 =
* Fixed: form submissions from blocked regions could still leak through any plugin that submitted to admin-post.php with an action name not in the hardcoded allowlist (e.g. first-party PBJForms `pbjforms_submit`). The POST interceptor now blocks by endpoint, so every action on admin-post.php (forms by design), wp-comments-post.php (comments), wp-signup.php / wp-activate.php and wp-login.php?action=register (signups) is covered automatically when the matching block_* setting is on. admin-ajax.php is deliberately NOT blocked wholesale — it carries site features (search, lazy-load, heartbeat) — so a narrow action allowlist is kept there.
* Added: response helper picks JSON vs HTML based on `wp_doing_ajax()`, `X-Requested-With: XMLHttpRequest`, or `Accept: application/json`, so non-AJAX form POSTs now get a readable 451 page instead of raw JSON.

= 1.0.4 =
* Fixed: WooCommerce My Account / checkout registration was not blocked. Added `woocommerce_process_registration_errors`, `woocommerce_register_post`, and the `woocommerce_enable_*_registration` filters.
* Fixed: REST API user creation (`POST /wp/v2/users`) was not blocked. Added `rest_pre_insert_user` filter and `/wp/v2/users` to the REST gate.
* Fixed: WP core `wp-login.php?action=register` POST handling and multisite signup. Added `register_post`, `registration_errors`, and `wpmu_validate_user_signup` hooks.
* Added: BuddyPress / bbPress signup hooks for completeness.
* Added: `X-PBJ-Geo` diagnostic HTTP header on every front-end and REST response — format `status; country=XX; mode=allowlist; v=1.0.4`. Open browser DevTools → Network → response headers to verify the plugin's decision per request.
* Added: Forminator REST route to the blocklist.
* Changed: default unknown-country behaviour is now fail-closed in BOTH allowlist and blocklist modes (previously blocklist mode let unknown countries through). New `fail_open_on_unknown` setting (default 0) to opt back into the old blocklist behaviour.

= 1.0.3 =
* Fixed: form submissions from blocked regions were leaking through. Form/comment/login filters now register on `init` (priority 5) instead of `wp`, so they fire on REST API and admin-ajax requests too — not just full front-end page loads.
* Added: `rest_pre_dispatch` gate that returns HTTP 451 for known form-plugin REST routes (Contact Form 7, WPForms, Gravity Forms, Ninja Forms, Fluent Forms, Formidable, native comments, PBJ Form Builder).
* Added: `pre_comment_on_post` action handler — belt-and-braces for `comments_open` which some themes ignore.
* Added: detection of Gravity Forms standard (non-AJAX) page-direct POSTs (`gform_submit` $_POST field), and classic CF7 POSTs (`_wpcf7` field).
* Added: integrations for Formidable Forms and Fluent Forms validation pipelines.
* Added: `pbj_form_builder_submit` to the admin-post action allowlist and `/pbj-form-builder/v1/*` to the REST route blocklist.

= 1.0.2 =
* Fixed WP 6.7+ `_load_textdomain_just_in_time was called incorrectly` notice on activation.
* Moved `load_plugin_textdomain` from `plugins_loaded` to the `init` action.
* Default settings strings are now plain English (translated only at render time, on hooks that fire after `init`).

= 1.0.1 =
* Added `Update URI` header so this plugin is never auto-replaced by a same-slug WordPress.org plugin.
* Added settings schema versioning and idempotent migration runner (`pbj_geo_viewer_db_version` option).
* Added self-hosted updater hooked into the `update_plugins_<host>` filter (WP 5.8+) — point it at a JSON manifest to enable auto-updates from your own server.
* Migration hook: `do_action( 'pbj_geo_viewer_migrated', $from, $to )` for downstream consumers.

= 1.0.0 =
* Initial release.
