=== Open Records Registry ===
Contributors: openrecordsregistry
Tags: open records, foia, public records, email
Requires at least: 6.0
Tested up to: 6.7
Requires PHP: 7.4
Stable tag: 1.9.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Send and track open records requests with agency lists, templated letters, and optional SMTP.

== Description ==

Open Records Registry helps site visitors submit records requests to configured agencies and lets staff review submissions in WordPress.

== Changelog ==

= 1.9.4 =
* Multisite: network activation/deactivation runs per-site bootstrap; new sites get bootstrap when plugin is network-active; uninstall removes synced pages and plugin options on every blog.

= 1.9.3 =
* **Admin UI:** Horizontal folder-tab navigation on every Open Records admin screen (including Agencies and Requests CPT screens).
* **Help:** Draggable Help window (how-to, shortcodes, required pages, mail/IMAP, merge tags) opened from the Help control on the right of the tab bar.

= 1.9.2 =
* **Admin menu:** Hide secondary Open Records screens from the Hub fly-out (empty menu titles when nested) so only one **Open Records** row appears.

= 1.9.1 =
* **Admin menu:** With DWD Plugins Hub active, stop registering a duplicate top-level “Open Records Registry” menu. Keep a single **Open Records** entry under the Hub; agency/request screens stay reachable from the dashboard.

= 1.9.0 =
* **Public records request generator:** Logged-in multi-step modal wizard builds modular category-based letters, then emails the agency and archives the request. Shortcode `[orr_ohio_request_generator]` and primary path on `[orr_request_form]`.
* **Multi-state modules:** `ORR_Request_Builder_Registry` registers jurisdiction builders (Ohio first). Other states can register via `orr_register_request_builders` / `ORR_Request_Builder_Registry::register()`.
* **Ohio builder:** Agency-type terminology, 12 record categories, delivery medium under ORC §149.43(B)(6), Select All / Clear All, server-side letter assembly and audit meta.
* **Replies:** Optional email to the requester when IMAP imports an agency reply (view link to My requests; body stays on-site). Setting **Reply notifications** (default on).
* **Ohio law snippet:** Clarifies electronic delivery to the site correspondence address.

= 1.8.41 =
* **Needs contact data:** **Search** field filters the list as you type (name, email, phone, address, website, records contact, jurisdiction, missing-field hint, and post ID). Shows a live count of matching agencies.

= 1.8.40 =
* **Needs contact data:** Inline form includes **Agency name** (post title); saving updates the title together with contact fields.

= 1.8.39 =
* **Agencies — public listing:** Agencies are **only** excluded from the public directory and request form when they have **no valid request email**. Manual “deactivate” / hide options and related meta are **removed** (database migration **v14** deletes leftover `_orr_agency_deactivated` rows).
* **Agencies — needs contact data:** New admin screen **Open Records → Needs contact data** lists published agencies missing request email and/or address, phone, or website. Click an agency **name** to **expand** the form; each row has its own **Save this agency** button. Same capability as editing agencies (`edit_posts`).

= 1.8.38 =
* **Agencies — public listing:** A **valid request email** is required for an agency to appear on the public directory and request form. Agencies without one are **automatically hidden** (not treated as “manually deactivated”).
* **Agencies — manual hide (administrators only):** The **manually hide** checkbox and **Deactivate / Activate** list actions require **`manage_options`**. Manual deactivation is only meaningful when a request email exists; saving an empty email clears the manual flag. Database migration **v13** removes stale manual-deactivation meta where the request email is missing.
* **Dashboard:** Statistics now distinguish **active on public site**, **hidden (no request email)**, and **manually deactivated**.

= 1.8.37 =
* **Administration dashboard:** Open Records top-level screen now shows **statistics** (requests sent, IMAP reply messages total, active/deactivated agency counts, agencies missing contact fields with emphasis on missing **request email**). **Quick links** to all agencies, “needs contact” filter, requests log, and Settings (for administrators). **Run database & page sync** and **Check mail now** (agency replies inbox) remain on this page for administrators.
* **Agencies — deactivate:** New meta **deactivated** hides an agency from the public directory and request form without deleting it. Checkbox on the agency edit screen, **Deactivate / Activate** row actions on the agency list, and public submission validates the agency is active.
* **Agencies — filter:** List table view **Needs contact info** (with count) shows published, non-deactivated agencies missing request email or any of address, phone, or website.

= 1.8.36 =
* **Request form:** Replaced the agency **drop-down** with a **search field** using the same token matching as the agency directory (name, email, address, phone, contact, website, region). **Only agencies that match the current search are listed**—there is no full agency list until you type. Pick an agency from the results to continue; use **Change** to pick a different one. Filterable data is available to hosts via `orr_request_form_agencies_search_data`.

= 1.8.35 =
* **My requests (user dashboard):** New required page **My open records requests** (`open-records-my-requests`) with `[orr_my_requests]`. Logged-in users see requests they submitted while logged in, and legacy rows where the stored requester email matches their account. Each request links to the **outgoing letter** plus **agency replies** imported via IMAP (same thread as staff see, without admin edit links). The public request form shows a **View your submitted requests** link when logged in. Run **Run database & page sync** on the Open Records dashboard after updating if the page is missing.

= 1.8.34 =
* **Agency suggestions (critical):** The internal post type slug was **22 characters**; WordPress only allows **20**, so `register_post_type` failed (PHP notice). With `WP_DEBUG_DISPLAY` on, that output **before** `wp_safe_redirect` caused “headers already sent” and the form looked like it failed. The slug is now **`orr_agency_sug`** (15 characters). Database migration **v12** renames existing `orr_agency_suggestion` rows. **On production, set `WP_DEBUG_DISPLAY` to false** (use `WP_DEBUG_LOG` instead) so other plugins’ notices do not break redirects.
* Database version **12**.

= 1.8.33 =
* **Public agency suggestions:** Replaced `map_meta_cap` workaround with **`wp_set_current_user()`** for the insert and meta updates so WordPress runs as a real administrator (guests never pass `current_user_can( 'edit_posts' )`). Meta is written before restoring the previous user. Fallback author: first administrator, then user ID 1, then any user. Failures log to `debug.log` when `WP_DEBUG` is on.

= 1.8.32 =
* **Public agency suggestions:** Saving “Suggest an agency” / “Suggest a correction” failed for visitors who were not logged in because WordPress requires `create_posts` for the suggestion post type. Verified submissions now use a narrow `map_meta_cap` allowance only during `wp_insert_post`, and suggestions are attributed to the first administrator (override with filter `orr_agency_suggestion_post_author_id`).

= 1.8.31 =
* **Agency directory search:** Non-matching rows and empty regions are hidden with plugin CSS (`display: none !important`) so theme rules cannot leave all agencies visible while the count updates.

= 1.8.30 =
* **Agency directory:** Search field filters the list in the browser (name, contact fields, jurisdiction code, and region heading). Status line shows counts or a no-match message; clear button appears when the query is non-empty.

= 1.8.29 =
* **Mobile-friendly UI:** Public shortcodes use improved safe-area padding, full-width primary actions on narrow viewports, stacked agency-directory definition lists and modal buttons, `100dvh` full-screen dialogs on phones, and a scrollable compact administrator shortcut strip. **Agency suggestions** admin screen uses touch-sized Apply/Dismiss buttons and horizontally scrollable proposed-value tables on small screens.

= 1.8.28 =
* **Agency directory page:** New required page **Agency directory** (`open-records-agency-directory`) with `[orr_agency_list]`. Run **Update / repair** on the Open Records dashboard after updating so the page is created.
* **Public directory UI:** Agency name expands to show contact details; **Suggest a correction** opens a modal. **Suggest an agency** opens a separate modal. Submissions are stored for review — nothing is published until an administrator applies them under **Open Records → Agency suggestions** (`manage_options`).

= 1.8.27 =
* **Jurisdiction codes:** `US-FED` and state codes are normalized case-insensitively (fixes `sanitize_key` lowercasing so FOIA/US-FED templates and law snippets apply correctly). Agency save, import compare, and email template preview use the same normalization.
* **Sample data:** **usa-phase-federal-foi-directory-part-01.csv** (361 federal components from FOI Directory export, jurisdiction **US-FED**). Rebuild: **tools/build-federal-foi-import-csvs.py** with **sample-data/_fed-raw.csv**.

= 1.8.26 =
* Import agencies: optional CSV headers **pr_representative**, **pio**, and **public_information_officer** map to the **records contact** field (same as **records_contact**).
* Sample data: phased Ohio lists **ohio-phase-01-state-agencies-ohio-gov-directory.csv** (state directory entries + websites) and **ohio-phase-02-all-88-counties-template.csv** (all counties with county seat; fill websites and contacts from official sources). Rebuild script: **tools/build-ohio-agency-csvs.py** with source JSON/text under **sample-data/**.

= 1.8.25 =
* **Required pages:** Fixed duplicate **Continue open records conversation** (and similar) pages. Sync now deduplicates by slug (including WordPress `-2` collision names), prefers the canonical page, trashes extras, and can rename `…-2` back to the clean slug when safe. Page lookup uses `post_name` search so pages under a parent are found—avoiding a second auto-created copy. **Run database & page sync** once to clean up existing duplicates.

= 1.8.24 =
* **Requests Log — Conversation modal:** Added a **Replying and follow-ups** note explaining that this dialog is read-only: visitors use the public **Continue open records conversation** page (private link), not wp-admin. Links to **Edit this request** for staff follow-up tools.
* **Request editor:** New sidebar box **Requester reply link (public)** — **Generate requester reply link** creates a fresh secret URL you can copy and email to the visitor (replaces any old bookmark for that request).

= 1.8.23 =
* **Requests Log:** **Check mail now** at the top of the log (same manual IMAP fetch as the dashboard; administrators only).
* **Requester follow-up:** After a successful submit, visitors are redirected to a new **Continue open records conversation** page (auto-created; shortcode `[orr_continue_conversation]`). A secret link lets them send additional plain-text messages to the agency with **In-Reply-To** / **References** threading (including agency replies and prior messages). Successful sends store a bookmarkable link; the conversation modal shows **Additional messages from requester**. Run **Run database & page sync** on the Open Records dashboard if the new page is missing.

= 1.8.22 =
* **Follow-up email:** On each request, staff can use **Follow-up to agency** (subject + message) to send additional plain-text mail with proper **In-Reply-To** / **References** threading against the original send, imported agency replies, and prior staff follow-ups. Requesters continue in their own mail app when copied; this control is for staff sending from WordPress.
* **Imported reply bodies:** IMAP import now prefers **text/plain** parts, converts **HTML** sensibly, avoids raw MIME dumps, and strips common **quoted** / “On … wrote:” blocks so replies are readable.

= 1.8.21 =
* **Requests Log:** **View conversation** opens a modal with the **outgoing request** (letter + sent time / agency “To”) and **agency replies** (from IMAP) in a thread layout. **Email reads** lists only the first two tracking times in the table; **See all reads** opens the full list in a modal.

= 1.8.20 =
* **Check mail now:** Administrators get a **Check mail now** button on the **Open Records** dashboard (main plugin screen) to run the same IMAP fetch as Settings. Manual fetch works whenever host, username, and password are saved—even if **Enable IMAP polling** is off (WP-Cron still only runs when polling is on).

= 1.8.19 =
* **Mail — agency replies:** Settings include **Reply-To** (default `openrecords@appleseednews.com`) so agency “Reply” goes to your open records inbox; optional display name. **Always Cc the requester** (on by default) copies the submitter on every agency message; turn off to use only the “Email me a copy” form option. Clear Reply-To email to restore the previous behavior (Reply-To = requester). Database version **11**.

= 1.8.18 =
* **Reply-To:** Requester address is set again on the PHPMailer object at send time (after SMTP), with optional display name from the request, so “Reply” in the agency’s mail client targets the requester even when a mail plugin rewrites headers.
* **Open tracking:** The tracking image now loads `admin-ajax.php` (usually not cached like the home page). Legacy `?orr_mail_open=` URLs still work.

= 1.8.17 =
* **Settings — IMAP:** Choose **check inbox interval** (5 minutes through once per day). Cron is rescheduled when you save settings so the new interval applies. Database version **10** merges the option for existing installs.
* **Email open tracking:** Serve the tracking pixel on `init` (before full-page cache), build the pixel URL without double-encoding the token, and apply HTML message body at a later `phpmailer_init` priority so SMTP add-ons do not strip the tracking image.

= 1.8.16 =
* **Mobile-friendly UI:** Public shortcodes use safe-area padding and full-width fluid layouts; form inputs use at least 16px text (reduces iOS zoom); primary submit and checkbox rows are touch-sized; agency list stays readable on narrow viewports. Admin: stacked Settings/Email template form rows under 782px; Requests Log table scrolls horizontally; IMAP/SMTP test buttons full-width on small screens; jurisdiction law preview limits height on short viewports.

= 1.8.15 =
* **Open tracking:** Each pixel load is stored with date and time (up to 500 events per request). **Requests Log** column **Email reads (tracking)** shows the count and a numbered list; the request screen lists every open. Older data may only show the first-open time plus a total load count.

= 1.8.14 =
* **Email templates (Ohio):** New structured default for **Ohio** — title line, requester and agency mailing blocks, date, “Dear Custodian of Records:”, `{{jurisdiction_records_law}}`, records section, fee summary, delay/denial/appeal closings, and signature lines. New merge tags: `{{requester_mailing_block}}`, `{{agency_mailing_block}}` (built from name/address and records contact/agency name/address). Migration **v9** replaces the Ohio row only when it still matched the global fallback letter. Edit under **Open Records → Email templates → Ohio** as needed (not legal advice).

= 1.8.13 =
* **Requester Cc is optional:** The form can show **Email me a copy of this request** (per submission). Settings control whether that option appears, and whether it is **checked by default** (uncheck the default for staff who rely on the Requests log). Database version **8**.

= 1.8.12 =
* Front-end success message after submit no longer refers to “being copied” unless **Copy requester on email** is enabled in Settings (matches sites that do not Cc the requester).

= 1.8.11 =
* **Settings:** Optional **Email signature** (multiline) appended after the jurisdiction letter on every outgoing request email; supports the same merge tags as the letter. Database version **7** merges the new option for existing installs.

= 1.8.10 =
* **Outbound request email:** Plain-text bodies were collapsed into a single paragraph because `wp_strip_all_tags()` was called with “remove breaks” enabled. Line breaks and paragraph spacing from templates are preserved again; basic HTML in a template is converted to line breaks before tags are stripped.

= 1.8.9 =
* Admin: submenu **Requests (table)** renamed to **Requests Log** (page title “Requests log”). Front-end: after submit, success notice confirms the letter was **sent by email**; if sending fails after save, visitors see an error with a mailto to **admin@appleseednews.com** (override with filter `orr_report_mail_failure_email`).

= 1.8.8 =
* **SMTP test:** WordPress-bundled PHPMailer no longer provides `smtpConnected()`; the test now closes the session with `smtpClose()` after a successful connect (fixes fatal error on newer WordPress).
* **Settings:** **Test IMAP connection** button verifies saved host, port, encryption, folder, username, and password without requiring IMAP polling to be enabled.

= 1.8.7 =
* Admin POST actions (sync, save settings, save templates, SMTP test) use **`orr_safe_admin_redirect()`**: if another plugin prints notices first (breaking redirects), you get a **Continue** button instead of a blank page. The textdomain notices in the log for `wp-plugin-hostgator` and `health-check` come from those plugins, not Open Records — use `WP_DEBUG_LOG` without `WP_DEBUG_DISPLAY` on live sites.

= 1.8.6 =
* **WordPress 6.7+ i18n:** Load translations on the `init` action (priority 1) instead of `plugins_loaded`, and run plugin bootstrap at `init` priority 5 so `_load_textdomain_just_in_time` is not triggered too early. Load text domain at activation and after plugin update before migrations when needed.

= 1.8.5 =
* Requests (table): declare `no_items()` as **public** to match `WP_List_Table` (fixes fatal error on WordPress 6.9+).

= 1.8.4 =
* **Debug (administrators):** Add `?plugin_debug=1` to any wp-admin URL, or define `ORR_DEBUG` as true in `wp-config.php` (admin only, persists until removed). Expanded panel shows WP/PHP versions, DB version, capabilities, law-snippet path, IMAP/SMTP flags, required-pages repair status, and quick links (including Requests table). Optional **front-end strip** when using `?plugin_debug=1` on the public site. New helpers `orr_is_debug_mode()` and `orr_debug_log()` (writes to PHP error log when debug is on). Filter: `orr_debug_urls`.

= 1.8.3 =
* Load `orr-public-records-law-snippets.php` via a **case-insensitive lookup** in `includes/` (Linux servers are case-sensitive; FTP uploads sometimes rename files). Fixes “failed to open stream” when the file exists under a different letter case.

= 1.8.2 =
* If `orr-public-records-law-snippets.php` is missing on the server (incomplete upload), load a bundled **fallback** file so WordPress does not fatal-error; show an **admin notice** for administrators until the full plugin package is reinstalled.

= 1.8.1 =
* **Requests (table):** fix critical error by loading admin dependencies (`WP_Screen` / `convert_to_screen`) before `WP_List_Table`, and pass the correct screen id for this submenu.
* **Record keeping:** store **email sent** date/time on each successful send; table columns **Form submitted**, **Email sent**, and **First read (tracking)** with date and time (site timezone). Older rows may show “Sent (time not recorded)” until new mail is sent from this version.

= 1.8.0 =
* Admin: **Requests (table)** lists all record requests in a sortable table (agency, requester, mail status, first open, reply count, submitted date).
* Email: optional **open tracking** (HTML message + tracking pixel) and stored **Message-ID** for each sent request; **Email activity** meta box on each request shows Message-ID, first open time, and imported replies.
* Inbox: optional **IMAP** settings to poll a mailbox for agency replies (matches `In-Reply-To` / `References` to the outbound Message-ID), append replies under the request, and cron polling about every 15 minutes when enabled (requires the PHP **imap** extension).

= 1.7.0 =
* Import agencies: new agencies from CSV are **published** by default (except conflict-review drafts) so they appear in the agency list and request form dropdown; add an email in the editor for agencies that need to receive requests.
* Shortcode `[orr_agency_list]` shows published agencies on the front end, **grouped by state/territory** (alphabetically by state name) with agencies **A–Z** under each. Optional attribute: `title="..."` for a heading above the list. Website field, when set, is linked.

= 1.6.6 =
* Import agencies: “Review import duplicates” drafts are created only when a row **conflicts** (email matches one existing agency and the name matches a different one). Rows that only match a single existing agency by email or name offer skip or create a second record—no review queue.

= 1.6.5 =
* Import agencies: fixed CSV header detection when the **name** column is the first column (index **0**). PHP treats `empty(0)` as true, so the importer incorrectly rejected every row as “no name column” even when the header was correct.

= 1.6.4 =
* Import agencies: recognize many alternate header labels for the agency name column (e.g. Legal name, Organization, Nombre, merged Excel header cells), optional ^ / ~ delimiters, and clearer errors with a short “start of file” excerpt plus instructions when no header row matches.

= 1.6.3 =
* Import agencies: CSV reader now scans up to 200 lines to find a real header row (skips title rows before column names), normalizes classic-Mac CR-only line endings, detects UTF-16 without a BOM (Excel), trims curly quotes and colons from header cells, and allows .txt uploads. Error message notes how many lines were scanned when no Name column is found.

= 1.6.2 =
* Import agencies: CSV parsing now decodes UTF-16 (Excel “CSV UTF-16”), skips blank and comment lines before the header, tries comma / semicolon / tab / pipe delimiters until a “name” column is found, and falls back from Latin-1/Windows-1252 to UTF-8 when the file is not valid UTF-8. This addresses uploads that still failed with “name column required” after 1.6.1.

= 1.6.1 =
* Import agencies: CSV header row detection now treats spaces and hyphens like “Agency Name” as `agency_name` (WordPress `sanitize_key()` used to collapse those to `agencyname`, so the required name column was not found). Semicolon-separated CSV (common from Excel in some locales) is detected so the first row parses into real columns. Optional aliases (e.g. organization name, telephone) map to the same fields as the template.

= 1.6.0 =
* Import agencies: when a row matches an existing agency by email and/or name but is not identical, it is saved as a draft for duplicate review (not skipped). Open Records → Review import duplicates compares the import draft with the existing agency side by side; edit merged fields, then apply to the chosen agency, keep as a separate record, or discard the draft. Rows that match an existing agency in every compared field after normalization are skipped automatically.

= 1.5.0 =
* Import agencies: CSV upload opens a review screen. Each row shows editable incoming values next to existing WordPress data when a duplicate email and/or name is detected. Choose per row: create new, skip, or update an existing agency (with a target selector when email and name match different posts).

= 1.4.2 =
* Sample data: expanded Richland County (Ohio) agency list (CSV) with county departments, courts, cities/villages, eighteen townships, school districts, fire/EMS, library branches, housing authority, park district, watershed district, and related offices. Documented that no static file can list every public body in a county; owners should merge in special districts and state offices as needed.

= 1.4.1 =
* Dashboard: “Run database & page sync” is always available to administrators (not only when a migration or page repair is detected). When work is needed, an “Action suggested” note still appears above the button.

= 1.4.0 =
* Agencies: CSV import under Open Records → Import agencies (UTF-8; required column name; optional email, address, phone, records contact, website, jurisdiction, notes). Rows without a valid email import as drafts until published.
* Sample data: template CSV and a Richland County (Ohio) starter list of public bodies (verify emails and contacts with each office before relying on them).

= 1.3.1 =
* Public form: requester name, phone, and mailing address are optional (email and records description remain required). Request titles use the email when no name is given.
* Settings: “Test SMTP login” runs a connection and authentication check against saved SMTP settings (save changes before testing).

= 1.3.0 =
* Dashboard: “Run database & page sync” only appears when a migration is pending or the public request page is missing or its shortcode content needs repair.
* Request form: optional fee contact threshold (USD) with “please contact me before proceeding” wording; optional fee waiver request for non-commercial purpose. Merge tags {{fee_contact_notice}}, {{fee_waiver_notice}}, {{fee_requests_summary}}; default letter includes {{fee_requests_summary}} (migration adds it to existing templates when missing).

= 1.2.1 =
* Email templates screen: read-only panel shows the paragraph inserted for {{jurisdiction_records_law}} for the jurisdiction selected in the dropdown.

= 1.2.0 =
* Why templates looked “all Ohio”: the fallback letter used {{ohio_public_records_act}} and the v3 migration copied that same body into every jurisdiction row.
* New {{jurisdiction_records_law}} merge tag: at send time this is replaced with a researched one-paragraph citation and request hook for each U.S. state, D.C., or federal FOIA (stored in code; verify citations with counsel).
* Default and migrated templates use that tag; agencies choose state/territory (aligned with mailing address) to pick the template row and law paragraph.
* Email template subjects default to “Public records request (State name) — {{site_name}}” per jurisdiction after upgrade (edit as needed).

= 1.1.0 =
* Email templates screen: separate subject and letter for U.S. federal FOIA, each state, and D.C.; preview with sample merge data; agencies store jurisdiction to choose the template.
* Agency fields: address, phone, records contact, email, website (name remains the post title); new merge tags for agency address, phone, records contact, and website.
* Settings “fallback” template used when a jurisdiction’s subject or body is left blank.

= 1.0.1 =
* Aligned activation order, maintenance capability, page repair by slug, request sanitization helpers, stored plugin version sync, front-end admin shortcuts, and duplicate-install detection with project WordPress standards.

= 1.0.0 =
* Initial release.
