=== Gather Grove Sync for Notion ===
Contributors: gturtle108
Tags: notion, forms, woocommerce, elementor, sync
Requires at least: 6.8
Tested up to: 7.1
Requires PHP: 8.2
Stable tag: 0.52.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Sync forms (Elementor, CF7, WPForms, Fluent), WooCommerce orders, and webhooks to a Notion database. Auto-extending columns, dedup, per-form routing.

== Description ==

Gather Grove Sync for Notion sends your WordPress data to a Notion database — form submissions (Elementor Pro, Contact Form 7, WPForms, Fluent Forms), WooCommerce orders, Gather Grove Booking activity, and anything else via an incoming webhook or the developer API. The plugin owns the Notion schema for you — columns auto-extend as data arrives, with smart type mapping (email → Email, tel → Phone, date → Date) and a rich_text fallback for anything unrecognized.

= Features =

* **Many sources, one Notion pipe** — Elementor Pro, Contact Form 7, WPForms (incl. Lite), Fluent Forms, and WooCommerce orders sync out of the box; any other service can POST to a secure incoming webhook. Every source funnels through the same dedup / column-auto-extend / retry / activity-log pipeline.
* **Auto-extending schema** — submit a new form, the columns appear in Notion. No manual mapping.
* **Smart type mapping** — Elementor email/tel/url/number/date/select/checkbox fields map to the matching Notion property types so your data is sortable and filterable.
* **Multi-database routing** — send different forms to different Notion databases via per-form overrides, with a global default for everything else.
* **Deduplication** — merge submissions by Email (or any column you choose) so the same person's submissions across forms collect into one row, with a chronological log appended to the page body. Original page title is preserved on update so "first contact" stays visible.
* **Backfill historical submissions** — paginate through Elementor's Submissions storage in 25-row chunks. Idempotent — re-runs skip already-synced submissions.
* **Activity log** — every sync attempt is recorded to a custom DB table, visible in the admin with success/failure badges and direct "View in Notion" links. Capped at 1000 entries with auto-prune.
* **Encrypted token storage** — Notion integration token is stored encrypted at rest using AES-256-GCM, with the key derived from your WordPress AUTH_KEY.
* **Connection retry** — one in-process retry on transient API failures (429, 5xx), then a background retry ladder for anything still failing.
* **One-click database creation** — search for a parent page, set a title, click Create. New database starts pre-configured with the plugin's system columns.
* **Connection status banner** — green/red status at the top of the settings page with per-database breakdown when multiple databases are configured.

= How it works =

1. Create a Notion integration at https://www.notion.so/profile/integrations and copy the token.
2. Install and activate the plugin, then go to Settings → Gather Grove Sync for Notion.
3. Paste the token (or use the one-click "Connect to Notion"). Either paste an existing database ID, or use the built-in **Create a Notion Database** tool to make a fresh one.
4. The integration must be invited to your Notion database (in Notion: open the database → ··· → Connections → add your integration).
5. Submit a form, place a WooCommerce order, or send a webhook — the plugin auto-creates whatever columns it needs and writes the record. See and control every source on the **Sources** tab.

= What's not included =

* The plugin does not provide a manual field-mapping UI — it owns the schema. Field labels become column names automatically. (If you need manual mapping, a different integration may suit you better.)
* The Elementor source requires Elementor **Pro** (free Elementor has no form widget). Every other source — Contact Form 7, WPForms, Fluent Forms, WooCommerce, Gather Grove Booking, the webhook — works on its own.

== Installation ==

1. Upload the `gather-grove-notion-sync` folder to `/wp-content/plugins/`, **or** install via Plugins → Add New → Upload Plugin and choose the zip.
2. Activate through the WordPress Plugins menu.
3. Go to **Settings → Gather Grove Sync for Notion** and connect your Notion workspace (one-click "Connect to Notion", or paste an integration token).
4. Either paste an existing database ID into the **Default Notion Database ID** field, or use the **Create a Notion Database** section to make a fresh one.
5. In Notion, open the database as a full page → click `•••` → **Connections** → add your integration. (If you used the in-plugin creation tool, this is automatic.)
6. Click **Test Connection & Refresh Schema** to verify everything's wired up.
7. Submit a form on your site to test.

== Frequently Asked Questions ==

= Does this work with the free version of Elementor? =

No. The plugin hooks into `elementor_pro/forms/new_record`, which is only available in Elementor Pro.

= Which sources are supported? =

Out of the box: Elementor Pro, Contact Form 7, WPForms (including the free Lite version), Fluent Forms, WooCommerce orders, and Gather Grove Booking (bookings, attendance, and purchases — members and guests). Any other service can POST to a secure incoming webhook, and developers can push records with `gather_grove_notion_record()`. Each source auto-activates when its plugin is present — see and manage them all on the **Sources** tab.

= What happens to columns I don't configure? =

The plugin auto-extends the database schema. Field labels become column names; types are inferred from the source field type (email → Email, number → Number, etc.) with a rich_text fallback for anything unrecognized.

= Will deduplication accidentally merge two different "John Smiths" by name? =

Only if you configure name fields as shared. By default, only `Email`, `Phone`, and `Phone Number` are treated as merge keys — they're the only fields reliably unique per person. You can edit the shared field list in settings, but adding name fields is a known footgun: a duplicate row is just visible noise, but a wrong merge silently destroys data.

= Can I sync historical Elementor submissions? =

Yes, if Elementor's Submissions storage is enabled (visible at Elementor → Submissions in WP admin). Use the **Backfill Historical Submissions** section in plugin settings. Backfill is idempotent — re-runs skip already-synced submissions.

= Is my Notion integration token safe? =

The token is stored encrypted at rest using AES-256-GCM. The encryption key is derived from your WordPress `AUTH_KEY` (defined in `wp-config.php`) via SHA-256. This defends against database-only leaks (e.g. SQL injection in another plugin, leaked DB backups). It does not defend against full server compromise — if an attacker has both the database and `wp-config.php`, the token can be decrypted.

= What if my Notion database fills up? =

Notion databases cap at ~100 properties. Most sites stay well under this. If you have many forms with mostly-unique fields, the connection banner shows the property count so you can monitor it. The fix is either to share more field labels across forms (so they collapse into single columns) or to use multi-database routing to split forms across multiple Notion databases.

= What if Notion is down? =

The plugin retries once immediately, after a one-second pause — deliberately not the longer wait Notion's `Retry-After` header asks for, because this runs while your visitor waits for the form to submit. If that retry also fails, the write is handed to a background queue with exponential backoff (1 min → 5 min → 30 min → 2 hr → 12 hr), where waiting properly costs nobody anything. After 5 attempts the entry is dead-lettered; dead-lettered writes are visible on the Tools tab with a "Clear" control.

A failure that retrying cannot fix — no token, a revoked token, a deleted database — is reported rather than queued. Retrying those just fills the queue with the same error.

= Can I send the same form to multiple Notion databases? =

Not in this release. Each form has at most one destination (its override, or the default). If you need fan-out, the simplest workaround is to query the destination Notion database via Notion's own automations to forward rows.

== External Services ==

This plugin connects to two external services. Neither is contacted until you
choose to connect the plugin to Notion, and nothing is transmitted on a fresh
install that has not been configured.

**1. Notion API (api.notion.com)**

This is the service the plugin exists to talk to. It is used to read your
Notion database schema and to create and update pages in the database you
select.

What is sent, and when:

* When you connect and pick a database: a request to `api.notion.com/v1/search`
  and `api.notion.com/v1/databases/{id}` to list the databases and pages your
  integration can see, and to read the chosen database's column schema. Sends
  your Notion integration token.
* When a form is submitted, an order is placed, or a webhook arrives: a request
  to `api.notion.com/v1/pages` containing the submitted field values that are
  being synced — this can include personal data such as name, email address,
  phone number, and any other field your form collects. Sends your Notion
  integration token.
* When you create a database from inside the plugin, run a backfill, or send a
  test row: the same page/database endpoints, with the same kinds of data.
* When a sync fails and is retried: the same page data is sent again.

Data is only sent for the sources you have enabled and the forms you have
selected. The plugin does not send site analytics, usage telemetry, or
administrator details.

Service provided by Notion Labs, Inc.
Terms of service: https://www.notion.com/terms
Privacy policy: https://www.notion.so/notion/Notion-Privacy-Policy-3468d120cf614d4c9014c09f6adc9091

**2. Gather Grove OAuth broker (gathergrove.io)**

Optional. This is used only by the one-click "Connect to Notion" button, which
exists so you do not have to create your own Notion integration by hand. It
holds the Notion OAuth application credentials, which cannot be shipped inside
a public plugin, and exchanges them for a token belonging to your workspace.

What is sent, and when:

* When you click "Connect to Notion": your browser is redirected to
  `gathergrove.io/wp-json/gg-notion-oauth/v1/start` with the WordPress admin
  URL to return to and a random one-time CSRF value. No site content, form
  data, or credentials are included.
* Immediately after you approve the connection in Notion: one server-to-server
  request from your site to
  `gathergrove.io/wp-json/gg-notion-oauth/v1/redeem` containing only the
  single-use handoff code from the previous step. The broker responds with the
  Notion access token for your workspace, which is then stored encrypted on
  your own site.

The broker is contacted only during these two steps of the connection flow. It
is never contacted during syncing, and no submitted form data ever passes
through it.

You can avoid this service entirely: create your own Notion internal
integration and paste its token into the "Connect manually instead" field on
the Setup tab. The broker is then never contacted.

Service provided by Gather Grove (Golden Turtle Sound).
Privacy policy: https://gathergrove.io/privacy/

**Bundled third-party library**

This plugin bundles Action Scheduler (https://actionscheduler.org/), a
background-processing library, which it uses to retry failed Notion writes.
Action Scheduler runs entirely on your own server and makes no external
requests. It creates four database tables (`actionscheduler_actions`,
`actionscheduler_claims`, `actionscheduler_groups`, `actionscheduler_logs`).
These tables are shared with any other plugin that bundles Action Scheduler, so
uninstalling this plugin deliberately leaves them in place.

== Screenshots ==

1. Setup — one-click Connect to Notion, the default database picker, and a manual-token option for self-hosters.
2. Sources — every installed form and store plugin syncs automatically; flip a source off or route it to a different database.
3. Form selection — sync every form or only the ones you tick, with a per-form database override and one-click database creation.
4. Per-form field mapping — how each field lands in Notion, with a rescan when a form changes.
5. Tools — test the connection, refresh the schema, and backfill historical submissions with live counts.
6. Activity — the recent sync log with created, updated and failed badges and direct "View in Notion" links.

== Changelog ==

= 0.52.0 =
* Changed: the plugin's internal settings, background jobs and admin actions now use its full `gather_grove_notion_sync_` prefix. Your connection, settings and mappings carry over automatically on update.
* Changed: the wp-config constant for the incoming-webhook secret is now `GATHER_GROVE_NOTION_SYNC_WEBHOOK_SECRET`. The previous name keeps working.
* Removed: the old submission action name deprecated in 0.28.0. Use `gather_grove/notion_sync/submission`.
* Changed: updated the bundled Gather Grove core to 0.63.2.

= 0.51.4 =
* Fixed: on a site with no other Gather Grove plugin, the settings page was missing from the admin menu. It is back under Settings.
* Fixed: opening a form on the Forms tab could stop with a fatal error instead of showing its field mapping.
* Hardening: column-rename names are sanitized before they are saved, and the Notion connect flow's return step carries a WordPress nonce.

= 0.51.3 =
* Maintenance: admin actions and settings reads are written in the shapes WordPress.org's plugin review expects, and diagnostic messages go to the site's error log in a consistent format. Nothing changes in how the plugin works.

= 0.51.2 =
* Maintenance: database queries and admin-screen markup are written in the shapes WordPress.org's plugin review expects, ahead of the plugin's listing there. Nothing changes in how the plugin works.

= 0.51.1 =
* Changed: updated the bundled Gather Grove core to 0.55.0.

= 0.51.0 =
* Added: if you use Gather Grove Pulse, each member's Notion row now stays up to date with their tags, pipeline stage and last activity — one row per member, updated in place rather than added again each time.

= 0.50.0 =
* Added: other plugins can now keep a single Notion row up to date — matched on a column you choose, updating in place rather than adding a new row each time. Groundwork for syncing member and lifecycle data to Notion.

= 0.49.0 =
* Added: Notion pages can now be mirrored into WordPress, so other Gather Grove plugins can display them — the groundwork for a Notion-backed wiki tab on Community Spaces. Content refreshes automatically, and a page that is deleted or unshared in Notion is flagged rather than disappearing silently.

= 0.48.0 =
* Housekeeping: the settings code is reorganised so each tab owns its own logic. No change to what any screen does or looks like.

= 0.47.0 =
* Fixed: submissions arriving from a webhook, the public API, or another plugin could be stored in Notion dated 31 December 1969 instead of when they were actually submitted. The timestamp is now read properly whether it arrives as a number or as a date.
* Fixed: rows already saved with that wrong date now correct themselves the next time the same person submits, and the correction is noted in the activity log. If you renamed a row in Notion, your name for it is kept.

= 0.46.0 =
* Changed: the settings screens now use the shared Gather Grove design — the same cards, badges and banners as the other Gather Grove plugins. Colours follow your site's palette instead of being fixed green, so the plugin no longer clashes with your own brand.
* Changed: tables, notices and "nothing here yet" states have been rebuilt. Wide tables scroll on their own instead of pushing the page sideways, and every empty screen now explains what will appear there and how.
* Changed: notices are announced to screen readers, which WordPress's default notices are not.
* Fixed: some help text on the Tools tab showed raw HTML tags.
* Fixed: the Export CSV button sat on its own line instead of beside Clear log.

Earlier releases: the complete version history is in CHANGELOG.md within the plugin.

== Upgrade Notice ==

= 0.34.0 =
Notion Sync now folds into the unified Gather Grove admin menu when other Gather Grove plugins are active; standalone installs are unchanged. Purely additive.

= 0.32.0 =
Gather Grove Booking guest bookings now sync to Notion (not just members). Tested up to WordPress 7.0. Purely additive.

= 0.31.0 =
Adds Gather Grove Booking sync (bookings/attendance/purchases → a Notion CRM, when Booking is active) and a friendlier database dropdown on the Sources tab. Purely additive.

= 0.30.0 =
Adds a Sources tab to see and control every connected source, one-click webhook setup, and a first-run setup checklist. Purely additive — existing syncs are unchanged.

= 0.29.0 =
Adds native sync for Contact Form 7, WPForms, Fluent Forms, and WooCommerce orders, plus a secure incoming webhook. Auto-detected — no setup for installed form plugins. Purely additive; your Elementor sync is unchanged.

= 0.28.0 =
Renames internal tables/options to the gg_notion_sync_ prefix automatically on update — your connection, settings, mappings, and history are preserved (no reconnect needed). Also drops the admin-page Google Fonts request for privacy.

= 0.14.0 =
Adds optional per-database overrides on the Column Rename Map. No action required — existing global renames keep working as-is. Visit the new "Per-database overrides" subsection only if a single column name needs different renames in different databases.

= 0.13.0 =
Adds a persistent retry queue: failed Notion writes now retry automatically via WP-Cron with exponential backoff (5 attempts over ~14 hours). Transient outages no longer lose live submissions. Installs a new DB table on activation. Strongly recommended.

= 0.12.0 =
Adds a scope picker in the Backfill section so you can target a specific form (or a few) for a one-off backfill without flipping your live sync mode. No behavior change unless you opt in.

= 0.11.0 =
Adds dedup conflict detection — surfaces silent multi-match cases in the activity log so duplicates in Notion don't go unnoticed. Recommended.

= 0.10.0 =
Multi-select Elementor fields now create proper Notion multi_select columns (one chip per selection) instead of one comma-joined value. Existing single-select columns from older versions are not re-typed; delete + re-sync in Notion to convert.

= 0.9.0 =
Fixes the within-chunk dedup race that caused duplicate rows during backfill bursts. Adds a Send Test Row diagnostic button and Export CSV for the activity log. Recommended.

= 0.8.0 =
Adds three aggregation columns auto-populated on dedup-merge: Forms Submitted (chips), Submission Count, First Submitted. See at a glance which forms a person filled out and how many times.

= 0.7.5 =
New Column Rename Map: rename auto-generated columns in Notion's UI, then add a `Old Name -> New Name` entry in settings to keep the plugin writing to the renamed column.

= 0.7.4 =
Auto-cleans pasted-from-URL database IDs (trailing `?`, query params, full URLs). Self-heals existing bad data without re-save.

= 0.7.3 =
Activity log auto-recovers if missing; backfill errors surface the actual Notion API message inline. Strongly recommended.

= 0.7.2 =
Fix for backfill silently skipping all submissions on certain Elementor Pro versions. Recommended.

= 0.7.1 =
WP.org packaging. No functional changes.

= 0.7.0 =
Notion database creation moved inside the plugin. No need to bootstrap databases manually anymore.

= 0.6.0 =
Multi-database routing. Schema cache is now per-DB. No action required for existing installs — overrides default to empty.

= 0.5.0 =
Notion token now encrypted at rest. Migration runs automatically on first load after upgrade.

= 0.4.0 =
New activity log + connection banner. Default shared fields trimmed — if you were relying on name-based merging, edit the shared fields list to add it back.
