Data Dictionary
Audience: plugin contributors, data-protection / audit reviewers.
Every persistent piece of state the plugin owns. If a key is not in this doc and not owned by WordPress core, it does not exist.
Storage map overview
The plugin uses no custom database tables. State lives in standard WordPress storage:
| Concept | Storage primitive | Table |
|---|---|---|
| Ticket | CPT (helpdesk_ticket) | wp_posts |
| Reply / internal note | Comment (comment_type = 'helpdesk_reply') | wp_comments |
| Ticket field | Post meta | wp_postmeta |
| Reply field | Comment meta | wp_commentmeta |
| Plugin setting | Option | wp_options (autoloaded by default) |
| Cache / lock | Transient | wp_options (or object cache if available) |
| Rate-limit lock | Option swh_rl_<hash> (NOT a transient — survives cache flush) | wp_options |
| Category | Taxonomy term | wp_terms, wp_term_taxonomy, wp_term_relationships |
| Capability | Role (added at activation, removed at uninstall) | wp_options (wp_user_roles) |
See design-document.md invariant #1 for the rationale.
Post meta keys
Cited from grep -rEon "(update|get|delete)_post_meta\(...\)" on simple-wp-helpdesk/. All keys begin with _ (hidden meta).
| Key | Type | Required? | Purpose | Since |
|---|---|---|---|---|
_ticket_status | string | yes | Current status label (free string matching one of the configured statuses). Written only via swh_set_ticket_status() except for initial-create. | 1.0 |
_ticket_priority | string | yes | Priority label from swh_get_priorities(). | 1.0 |
_ticket_name | string | yes | Client display name from submission. | 1.0 |
_ticket_email | string | yes | Client email address. Compared to inbound webhook sender. Compared to wp_get_current_user()->user_email for My Tickets. | 1.0 |
_ticket_token | string | yes | Random token for portal access. Compared with hash_equals. | 1.0 |
_ticket_token_created | int (unix ts) | no (grandfathered) | When the token was generated. Absence on a pre-v1.9.0 ticket means “never expire”. | 1.9.0 |
_ticket_url | string | optional | Stored permalink of the submission page; fallback for swh_get_secure_ticket_link() when swh_ticket_page_id is unset. | 1.0 |
_ticket_uid | string | yes | Human-readable ticket UID (TKT-XXXX). Used in subjects, merge breadcrumbs, inbound webhook subject parsing. | 1.0 |
_ticket_assigned_to | int (user id) | no | Assignee user ID. 0 = unassigned. Set by swh_apply_assignment_rules() or manual admin edit. | 1.0 |
_ticket_attachments | string[] (URLs) | no | URLs of root-level attachments (uploaded with the new ticket). | 1.0 |
_ticket_attachment_url | string | legacy | Pre-v2 single-attachment field. Still referenced for migration. | 1.0 |
_ticket_attachment_id | int | legacy | Pre-v2 single-attachment field. | 1.0 |
_swh_attachment_orignames | array<string,string> | optional | Map of attachment URL → original filename for new-ticket uploads. Falls back to basename($url) if missing (pre-v2.3.0). | 2.3.0 |
_resolved_timestamp | int (unix ts) | optional | Set when ticket transitions to the resolved status; used by the auto-close cron to detect tickets eligible for auto-close. | 1.0 |
_ticket_first_response_at | int (unix ts) | optional | Unix timestamp of the first staff reply. Used by first-response-time reporting. | 3.0.0 |
_ticket_sla_status | string (warn/breach) | optional | Latest SLA evaluation; set by swh_sla_check_event. | 3.0.0 |
_ticket_cc_emails | string (comma-sep) | optional | Comma-separated CC/watcher addresses. Read via swh_get_cc_emails() which sanitizes and validates. | 3.0.0 |
_ticket_template | string | optional | Label of the ticket template selected at submission. | 3.0.0 |
_ticket_csat | int (1–5) | optional | Client satisfaction rating; not set if the client dismisses the prompt. Written by the swh_submit_csat AJAX handler. | 3.0.0 |
_swh_unread | string ('1') | optional | Set to '1' when a client posts a reply; cleared when admin opens the ticket. Pairs with the swh_unread_count transient (see invariant #17). | 3.1.0 |
_edit_lock | string | WP core | WordPress’s own concurrent-edit lock. Not owned by this plugin. | WP core |
Comment meta keys
Comments carry these meta when comment_type='helpdesk_reply':
| Key | Type | Required? | Purpose | Since |
|---|---|---|---|---|
_is_internal_note | string ('1') | optional | Staff-only note. Must be filtered out of any frontend renderer. | 2.0.0 |
_is_user_reply | string ('1') | optional | Marks a portal-side or inbound-email reply (i.e. came from the client, not staff). Used by reporting to attribute first-response time correctly. | 2.0.0 |
_swh_reply_orignames | array<string,string> | optional | Per-comment map of attachment URL → original filename for replies and reopens. Falls back to basename($url) if missing. | 2.3.0 |
_attachments | string[] (URLs) | optional | Attachment URLs on the reply comment. | 1.0 |
Options
Enumerated in simple-wp-helpdesk/includes/helpers.php:19-121 (swh_get_defaults()). See config-reference.md for the exhaustive list with defaults, types, and admin UI location.
Categories:
- General: statuses, priorities, autoclose, max upload.
- Assignment & Routing: default assignee, fallback email, helpdesk page, token TTL, technician restriction.
- Email Format: format, logo URL.
- Anti-Spam: method, reCAPTCHA keys/type, Turnstile keys.
- Data Retention & Tools: retention windows, delete-on-uninstall.
- Email Templates: 14 subject/body pairs (see
config-reference.md). - Messages: success/error strings shown to clients.
- Canned Responses: structured array.
- Ticket Templates: structured array.
- SLA: warn/breach hours, notify email.
- Assignment Rules: JSON array of
{category_term_id, assignee_user_id}. - Inbound Email: Bearer secret.
- Frontend Portal Appearance: theme preference.
Additionally:
swh_db_version— installed plugin version; updated byswh_run_upgrade_routine()atclass-installer.php:147. Not inswh_get_defaults()and excluded from bulk operations (swh_get_all_option_keys()filters it out).swh_comment_type_v2,swh_tech_caps_v2— one-time migration flags. Cleaned on uninstall.
Transients
| Key | TTL | Purpose | Set by | Cleared by |
|---|---|---|---|---|
swh_unread_count | 5 min | Cached count of tickets with _swh_unread=1. | swh_get_unread_reply_count() at helpers.php:740-764. | Any write to _swh_unread (see invariant #17). |
swh_report_avg_resolution_time | HOUR_IN_SECONDS | Reporting cache. | admin/class-reporting.php. | Hourly expiration. |
swh_report_first_response_time | HOUR_IN_SECONDS | Reporting cache. | admin/class-reporting.php. | Hourly expiration. |
swh_report_<other types> | HOUR_IN_SECONDS | Reporting cache for status_breakdown, weekly_trend, kpi. | admin/class-reporting.php. | Hourly expiration. |
swh_lock_clear_<post_id> | 3 min | Suppresses unread-row highlight after admin opens a ticket. | admin/class-ticket-editor.php:455. | 3-minute expiration. |
swh_lock_autoclose | cron-duration | Single-instance lock for the auto-close cron. | class-cron.php:56. | Cron handler on exit. |
swh_lock_retention_att | cron-duration | Lock for the attachment-retention cron. | class-cron.php:145. | Cron handler on exit. |
swh_lock_retention_tkt | cron-duration | Lock for the ticket-retention cron. | class-cron.php:280. | Cron handler on exit. |
swh_lock_sla | cron-duration | Lock for the SLA-check cron. | class-cron.php:330. | Cron handler on exit. |
swh_rl_* are NOT transients — they are stored as options with a TTL field embedded in the value. This is intentional: transients are flushed by aggressive object-cache resets that some hosts run. See swh_is_rate_limited() at helpers.php:777-794.
Taxonomy
| Slug | Type | REST | Rewrite | Show admin column | Registered at |
|---|---|---|---|---|---|
helpdesk_category | hierarchical | no (show_in_rest=false) | no (rewrite=false) | yes | class-installer.php:311-338 |
Assign with wp_set_post_terms( $ticket_id, $term_ids, 'helpdesk_category' ). Read with wp_get_post_terms( $ticket_id, 'helpdesk_category', $args ). Used by swh_apply_assignment_rules() to look up routing rules.
Capabilities
| Capability | Granted to roles | Controls |
|---|---|---|
read | Technician (added at activation) | basic WP login |
edit_posts | Technician | base post editing |
edit_others_posts | Technician | edit tickets created by others |
edit_published_posts | Technician | edit tickets after publish (all of them; v1.0 status was a meta field, not post_status) |
publish_posts | Technician | publish tickets |
delete_posts | Technician | delete tickets (admin UI) |
upload_files | Technician | attach files to replies |
The Technician role is created at class-activate() and removed at class-uninstall() (see class-installer.php:18-41, 234-239). Existing users in the role are reassigned to the site’s default role (get_option('default_role', 'subscriber')) before role removal.
manage_options (WordPress core) gates all admin / settings / merge / test-email / reporting actions.
Migration policy
- Meta keys are load-bearing and follow the same versioning as code (see
api-contract.md). - Adding a new meta key is MINOR. Document it here in the same PR.
- Renaming or removing a meta key is MAJOR. The change ships with an upgrade routine in
swh_run_upgrade_routine()atclass-installer.php:87-148that migrates old → new for existing installs. - Adding a comment meta is MINOR.
- Adding an option is MINOR. Adding to
swh_get_defaults()causesadd_option()to seed it on next admin page load (line 113). - Removing an option is MAJOR. Add a
delete_option()in the upgrade routine.
What is intentionally NOT here
- No custom tables. See
design-document.mdinvariant #1. - No foreign keys. WordPress does not enforce them.
- No application-managed sequences. Use
wp_insert_post(). - No
register_post_meta(). Showing meta in REST is gated separately and not desired for these keys. - No JSON columns. Where structured data is needed (assignment rules, canned responses, ticket templates, attachment origname maps) it is stored as a serialised PHP array via WP’s standard option/meta serialization. Read/write is via
get_*/update_*only — never rawunserialize()on the raw column value.
Update protocol
Update this doc when:
- A new post meta, comment meta, option, transient, capability, or taxonomy term is added — bump the relevant table.
- An existing key is renamed or removed — strike the old row, note the migration version, document the upgrade routine that converts existing data.
- The migration policy itself is amended.