Skip to content

Commit be2edf7

Browse files
committed
Bring the docs back in line with the library
They described the library before the port: a branded header that no longer exists in that shape, an `encryption` config block that was replaced by a per-field flag, AES-256-CBC where it is now GCM, eight REST endpoints that were deleted, and an export that promised to decrypt values it now refuses to include at all. A doc describing a deleted API is worse than no doc, because it is the one thing someone trusts before reading the code. Corrected across home, reset-export-import, badges, the configuration reference, the common field options and the sidebar. The three that were already rewritten — registration, header, encryption, rest-api — are unchanged. Two things worth having written down that were not before: a badge's `disabled` reads backwards on purpose, because it answers "does this install already have the feature"; and a locked field is skipped on save entirely, because a disabled control sends nothing and a save path that iterates its field list would read that as cleared — wiping the premium settings of any install whose licence lapsed.
1 parent e0579bf commit be2edf7

6 files changed

Lines changed: 73 additions & 61 deletions

File tree

‎docs/_sidebar.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
- [Registration Options](registration.md)
88
- [Tabs](tabs.md)
99
- [Sections](sections.md)
10-
- [Branded Header](branded-header.md)
10+
- [Header](branded-header.md)
1111
- [Badges](badges.md)
1212
- [Conditional Fields](conditional-fields.md)
1313
- [Reset & Export/Import](reset-export-import.md)

‎docs/advanced/reference.md‎

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -118,31 +118,35 @@
118118
| `parent_slug` | string | `''` | Parent menu slug (empty = top-level) |
119119
| `icon` | string | `'dashicons-admin-generic'` | Top-level menu icon |
120120
| `position` | int | `null` | Top-level menu position |
121-
| `body_class` | string | `''` | Extra body class |
122121
| `option_name` | string | Settings ID | `wp_options` key |
123122
| `option_group` | string | ID + `_group` | Settings API group |
124123
| `submit_button` | bool | `true` | Show submit button |
125124
| `reset_button` | bool | `false` | Show reset-to-defaults button |
126125
| `export_import` | bool | `false` | Show export/import panel |
127126
| `logo` | string | `''` | Header logo URL |
127+
| `badge` | mixed | `''` | Header badge (string, array, or callable)|
128+
| `constant_prefix`| string | Option + `_` | Prefix for a derived constant name |
129+
| `layout` | string | `'stacked'` | Reserved for future layouts |
128130
| `header_title` | string | `''` | Header title override |
129-
| `header_badge` | mixed | `''` | Header badge (string, array, or callable)|
130131
| `tabs` | array | `[]` | Tab definitions |
131132
| `sections` | array | `[]` | Section definitions |
132133
| `fields` | array | `[]` | Field definitions |
133134
| `help_tabs` | array | `[]` | Help screen tabs |
134135
| `help_sidebar` | string | `''` | Help screen sidebar HTML |
135-
| `encryption` | array | Auto | Encryption settings |
136136

137137
## REST Endpoints
138138

139-
| Method | Route | Used By |
140-
|--------|------------------------------------|--------------------|
141-
| GET | `/setting-fields/v1/ajax` | `ajax`, `post`, `page`, `taxonomy`, `user` |
142-
| POST | `/setting-fields/v1/action` | `action_button` |
143-
| POST | `/setting-fields/v1/license` | `license` |
144-
| POST | `/setting-fields/v1/email/preview` | `email_editor` |
145-
| POST | `/setting-fields/v1/email/send-test` | `email_editor` |
146-
| POST | `/setting-fields/v1/reset` | `reset_button` |
147-
| GET | `/setting-fields/v1/export` | `export_import` |
148-
| POST | `/setting-fields/v1/import` | `export_import` |
139+
This library registers none. Its interactive field types talk to
140+
[wp-field-kit](https://github.com/arraypress/wp-field-kit)'s endpoints, and
141+
its own reset, export and import are forms posting to `admin-post.php`.
142+
143+
| Method | Route | Used By |
144+
|--------|-----------|------------------------------------------------------|
145+
| GET | `/search` | `ajax`, `post`, `page`, `taxonomy`, `user` |
146+
| POST | `/action` | `action_button`, `license`, `email_editor` |
147+
148+
The namespace is derived from the kit's own — `field-kit/v1` on a plain
149+
install, `{prefix}-field-kit/v1` in a Strauss-prefixed build — so two plugins
150+
shipping the same library do not answer each other's requests. Do not
151+
hardcode it: read it from the field's own markup, which carries the resolved
152+
URL. See [REST API](rest-api.md).

‎docs/badges.md‎

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ visible, the field is automatically disabled — no need to set `disabled` separ
2828
'badge' => [
2929
'text' => 'Business',
3030
'url' => 'https://example.com/upgrade',
31-
'class' => 'setting-fields-badge--gold',
32-
'icon' => 'dashicons-lock',
31+
'class' => 'my-badge--gold',
32+
'icon' => 'lock',
3333
'disabled' => fn() => is_setting_field_license_active( 'my_plugin', 'license' ),
3434
],
3535
],
@@ -56,8 +56,23 @@ Add a `badge` to a section definition. When the badge is visible, all fields in
5656
],
5757
```
5858

59-
The section title and badge remain fully visible and clickable (for upgrade links) even when the section's fields are
60-
dimmed and disabled.
59+
The section title and badge stay visible and clickable — that is the point of an upgrade link — while the fields under them are disabled.
60+
61+
## A locked field keeps its value
62+
63+
A disabled control sends nothing, and a save path that iterates its field list rather than the submission reads "nothing" as "cleared". So a locked field is **skipped on save entirely**.
64+
65+
Without that, an install whose licence lapsed would have its premium settings wiped by the next unrelated save, and get them back as blanks when the licence returned. The same applies to a field carrying `'disabled' => true` directly.
66+
67+
## The condition reads backwards on purpose
68+
69+
`disabled` **hides** the badge. It answers "does this install already have the feature?" — true means yes, so there is nothing to sell and nothing to lock:
70+
71+
```php
72+
'disabled' => fn() => is_setting_field_license_active( 'my_plugin', 'license' ),
73+
```
74+
75+
A callable is accepted so the answer can be a licence check made at render time rather than at registration.
6176

6277
## Badge Options
6378

@@ -66,7 +81,7 @@ dimmed and disabled.
6681
| `text` | string | Yes | — | Badge label |
6782
| `url` | string | No | `''` | Links badge to upgrade page (opens new tab) |
6883
| `class` | string | No | `''` | Additional CSS class for styling |
69-
| `icon` | string | No | `''` | Dashicon class (e.g., `dashicons-lock`) |
84+
| `icon` | string | No | `''` | Dashicon **suffix** — `lock`, not `dashicons-lock` |
7085
| `disabled` | bool\|callable | No | `false` | When truthy, hides badge and unlocks field |
7186

7287
If `badge` is a string (e.g., `'Pro'`), it's treated as `['text' => 'Pro']`.
@@ -143,7 +158,7 @@ $license_check = fn() => is_setting_field_license_active( 'my_plugin', 'license'
143158
'text' => 'Pro',
144159
'url' => 'https://example.com/upgrade',
145160
'class' => 'setting-fields-badge--pro',
146-
'icon' => 'dashicons-lock',
161+
'icon' => 'lock',
147162
'disabled' => $license_check,
148163
],
149164
],
@@ -155,7 +170,7 @@ $license_check = fn() => is_setting_field_license_active( 'my_plugin', 'license'
155170
'text' => 'Pro',
156171
'url' => 'https://example.com/upgrade',
157172
'class' => 'setting-fields-badge--pro',
158-
'icon' => 'dashicons-lock',
173+
'icon' => 'lock',
159174
'disabled' => $license_check,
160175
],
161176
],

‎docs/fields/common.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ All field types share these common options:
2121
'sanitize_callback' => null, // Custom sanitization function
2222
'render_callback' => null, // Custom render function
2323
'data' => [], // Custom data-* attributes on the input
24-
'encrypted' => false, // Enable encryption (see Encryption)
24+
'encrypted' => false, // Encrypt at rest (see Encryption)
25+
'show_in_rest' => false, // Expose the meta key in REST — off unless asked
26+
'badge' => '', // Upsell pill; a visible one locks the field
27+
'capability' => '', // Who may see and set it
2528
],
2629
```
2730

‎docs/home.md‎

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
# WordPress Register Setting Fields
22

3-
A WordPress library for creating settings pages with tabs, sections, branded headers, and 30+ field types. Handles registration, rendering, sanitization, and the Settings API — you just define your fields.
3+
A WordPress library for creating tabbed settings pages. Registration, the menu, the Settings API wiring and export/import live here; every field is rendered, sanitized and made accessible by [wp-field-kit](https://github.com/arraypress/wp-field-kit), which the term, post, user and list-table libraries share.
44

55
## Features
66

7-
- **30+ field types** — text, number, select, AJAX search, toggles, color pickers, image/file/gallery, WYSIWYG, code editors, and more
8-
- **Relational fields** — post, page, taxonomy, and user selects with Select2 and AJAX search
9-
- **Complex fields** — repeaters, groups, email editors, license keys, action buttons, sortable lists
10-
- **Tabs & sections** — organize fields into tabbed pages with collapsible sections
11-
- **Branded header** — logo, title, badge, and integrated tab navigation
12-
- **Conditional fields** — show/hide fields based on other field values with 12 operators
13-
- **Encryption** — transparent AES-256-CBC encryption for sensitive fields with constant fallback
14-
- **Email editor** — full email template editor with merge tags, preview, and send test — integrates with `wp-register-emails`
15-
- **License field** — license key activation/deactivation with REST callbacks and status persistence
16-
- **Sanitization** — built-in sanitizers for every field type, extensible via callbacks and filters
17-
- **Hooks** — filters and actions at every stage of the lifecycle
7+
- **50+ field types** — text, number, select, searchable and creatable selects, AJAX search, toggles, colour pickers, image/file/gallery, WYSIWYG, code editors, repeaters (stacked or as a table), groups, email panels, licence keys, action buttons, sortable lists
8+
- **Core's own header** — the shape `options-privacy.php` uses, so it matches the admin and follows the user's colour scheme, with an optional logo and badge
9+
- **Tabs & sections** — saving one tab leaves the others alone, which is the bug a tabbed settings page has if nobody thinks about it
10+
- **Conditional fields** — show or hide based on other fields; a hidden field is deleted on save rather than keeping a stale value
11+
- **Encryption** — AES-256-GCM for any field type, keyed from the site's own salts, with a constant fallback
12+
- **Every write sanitized** — `update_option()` runs the registered callback before it compares, so a value written by a cron job, an importer or another plugin passes through its own field type exactly as a submitted one does
13+
- **Accessible by construction** — labels, descriptions, required state and grouping are the renderer's job, so no field type can forget them
1814
- **Helper functions** — `get_setting_field_value()`, `is_setting_on()`, `is_setting_field_license_active()`, and more
1915

2016
## Installation
@@ -25,7 +21,7 @@ composer require arraypress/wp-register-setting-fields
2521

2622
## Requirements
2723

28-
- PHP 8.2+
24+
- PHP 8.3+
2925
- WordPress 5.8+
3026

3127
## License

‎docs/reset-export-import.md‎

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
# Reset & Export/Import
22

3-
## Reset Button
3+
These three live in the **Screen Options** panel, which opens next to Help. They are a screen's occasional controls, not something to read past on every visit — and Screen Options is a panel WordPress already provides rather than a row of buttons across the page title.
44

5-
Adds a "Reset to Defaults" button next to Submit. When tabs are present, only fields on the current tab are reset.
5+
Each is a form posting to `admin-post.php` with a nonce, not a link. They change state, and a link that changes state can be followed by a prefetch or a crawler.
6+
7+
## Reset
8+
9+
Restores every setting on the tab being viewed to its default.
610

711
```php
812
register_setting_fields( 'my_plugin', [
@@ -28,9 +32,9 @@ register_setting_fields( 'my_plugin', [
2832
] );
2933
```
3034

31-
When viewing the General tab, clicking reset restores only `site_name` to `'My Site'`. The Advanced tab's `cache_ttl` is untouched.
35+
When viewing the General tab, resetting restores only `site_name` to `'My Site'`. The Advanced tab's `cache_ttl` is untouched.
3236

33-
A confirm dialog is shown before any reset occurs.
37+
A reset removes the stored keys rather than writing defaults over them: a field with no stored value renders its configured default, so removing the key *is* the reset. A confirmation is shown first.
3438

3539
## Export/Import
3640

@@ -49,30 +53,26 @@ Clicking **Export Settings** downloads a JSON file named `{settings_id}-settings
4953

5054
```json
5155
{
52-
"settings_id": "my_plugin",
56+
"id": "my_plugin",
5357
"version": 1,
54-
"exported_at": "2025-06-15 10:30:00",
55-
"data": {
58+
"values": {
5659
"site_name": "My Site",
57-
"cache_ttl": 3600,
58-
"api_key": "sk-live-abc123"
60+
"cache_ttl": 3600
5961
}
6062
}
6163
```
6264

63-
Encrypted fields are exported **decrypted** so the file works across environments with different encryption keys.
64-
65-
Fields skipped during export: layout fields (`message`, `html`, `separator`, `heading`), `hidden`, `action_button`, `clipboard`, and any field whose value comes from a PHP constant.
65+
**Encrypted fields are left out entirely.** The key is derived from the site's own salts, so an encrypted value cannot be read anywhere else — exporting it would put a credential in a file for no benefit at all. See [Encryption](advanced/encryption.md).
6666

6767
### Import
6868

69-
Clicking **Import Settings** opens a file picker. After selecting a `.json` file, a confirm dialog is shown. On confirm:
69+
Core's own import form — a `.wp-upload-form` with a visible label, the upload limit beside it and a plain file input, the same shape `wp_import_upload_form()` produces on `import.php`.
7070

71-
1. The `settings_id` in the file is validated against the current settings page
72-
2. Each value is run through the standard field sanitizer
73-
3. Encrypted fields are re-encrypted with the current environment's key
74-
4. Values are merged with existing options (fields not in the file are preserved)
75-
5. The page reloads
71+
On upload:
72+
73+
1. The `id` in the file is checked against this settings page, and a file from another page is refused by name.
74+
2. The values are handed to `update_option()`, which means they pass through this page's registered sanitize callback like every other write — each value sanitized by its own field type.
75+
3. A key that is not a field on this page is dropped there rather than becoming part of the option. An uploaded file is untrusted input, and this is where that is enforced.
7676

7777
### Both Together
7878

@@ -84,12 +84,6 @@ register_setting_fields( 'my_plugin', [
8484
] );
8585
```
8686

87-
### REST Endpoints
88-
89-
| Method | Route | Description |
90-
|--------|------------------------------|------------------------------|
91-
| POST | `/setting-fields/v1/reset` | Reset fields to defaults |
92-
| GET | `/setting-fields/v1/export` | Export settings as JSON |
93-
| POST | `/setting-fields/v1/import` | Import settings from JSON |
87+
### Not REST
9488

95-
All three require `manage_options` capability (or the filtered equivalent).
89+
These are forms posting to `admin-post.php`, not REST routes. An earlier version registered `/reset`, `/export` and `/import` endpoints; a state change driven from a page that already has a form and a nonce does not need one.

0 commit comments

Comments
 (0)