Database Schema (User Meta Fields)
All fields are stored as user meta (wp_usermeta), under the plugin's namespace prefix lh_profile-. There is no dedicated database table.
Registered meta (validated via register_meta())
These fields have a sanitize_callback registered, so any direct update_user_meta() write – from this plugin, the REST API, or any other plugin – is validated/normalised, not just writes that go through this plugin's own save paths.
| Meta key | Type | Format | Notes |
|---|---|---|---|
lh_profile-phone | string | E.164 (+61...) | Validated via giggsey/libphonenumber-for-php-lite (Composer). A full backfill (this session) converted ~8,300 legacy-format rows to E.164. |
lh_profile-secondary_phone | string | E.164 | Same validator as phone. |
lh_profile-gender | string | Male|Female|Organisation|Robot | Fixed allowed-value set. |
lh_profile-birthdate | string | Y-m-d | Changed from a UNIX timestamp to a plain date string in v2.13 – see Architectural Notes for why, and the full migration history. |
lh_profile-street_address | string | free text | sanitize_text_field() only – no richer format exists. |
lh_profile-town | string | free text | sanitize_text_field() only. |
lh_profile-postcode | string | free text | sanitize_text_field() only for now – real per-country format validation (AU 4-digit, US ZIP, UK alphanumeric) is a known future candidate, not yet built. |
lh_profile-state_province | string | free text | sanitize_text_field() only. |
lh_profile-country | string | free text | sanitize_text_field() only – a plain text field, not a country-code dropdown. |
Unregistered meta
| Meta key | Type | Notes |
|---|---|---|
lh_profile-birthdate_readable | string | Companion field alongside lh_profile-birthdate, written by handle_birthdate_update(). Historically held the pre-parse date string; since the v2.13 format change both fields hold the same Y-m-d value, making this field largely redundant going forward, but it's kept for backward compatibility since other code (filter_query_joins() for the "Users Insights" plugin integration) reads it directly. |
lh_profile-emails | array | Additional/secondary emails, serialized array of validated email strings. Slated for removal – superseded by lh-user-identity. Not extracted into its own field class for this reason. As of this session, 1,888 users have a non-empty value here, largely following a firstname.lastname@theaustraliatimes.com.au <-> real-email cross-reference pattern that turned out to indicate a large number of duplicate user accounts – see the exploration brief referenced in Architectural Notes. |
lh_profile-updated_time | int | UNIX timestamp of the last time any "non-native" field (see below) was updated. Set by track_last_meta_update_time(). |
lh_profile-updated_field | string | Which meta key was last updated, alongside updated_time. |
Core WordPress fields (not this plugin's own meta)
| Field | Storage | Notes |
|---|---|---|
| First name | first_name usermeta (WP core key) | No register_meta() here deliberately – registering a sanitize_callback against a core-recognised key would affect any other code touching that exact key, not just this plugin. |
| Last name | last_name usermeta (WP core key) | Same reasoning as first name. |
| Display name | wp_users.display_name column | Not usermeta at all – register_meta() doesn't apply. |
"Non-native fields" list
return_all_non_native_fields() returns: street_address, town, postcode, gender, phone, birthdate (all namespace-prefixed). This list drives several cross-cutting behaviours: vcard export tracking, notification token generation, the "updated_time"/"updated_field" tracking, and the prevent_bad_meta_values filter (rejects empty-string writes to any of these keys).