• 1.5.19: Release 1.5.19. The User Experience fields from LH User Experience Taxonomy (field types lh_uet-tax and lh_uet-text) stay editable on the logged-in enquiry form even when they already have a value, because a member's experience changes regularly and the enquiry form is a reasonable place to update it. The form keeps a list of such field types, filterable as lh_crm_frontend_always_editable_field_types and defaulting to those two: they are rendered without suppress_if_populated so the current value is preselected, and they are left out of the submitted field_ids because these field types save their own input (LH User Experience Taxonomy saves it on lh_crm_http_post_after_user). Every other filled field is still shown read-only, as in 1.5.18. readme.txt stable tag 1.5.19 and trimmed changelog 1.5.19, 1.5.18, 1.5.17.
  • 1.5.18: Release 1.5.18. Fixes the logged-in frontend form showing extended profile fields as editable when they already hold a value. Fields such as Gender and Phone from LH Profile Page (stored in user meta) and User Experience from LH User Experience Taxonomy (stored as a user taxonomy term) are registered as xProfile field types but keep their data outside BuddyPress's own profile data table, so bp_field_has_data reported them empty. For a logged-in user the form now renders each field with the suppress_if_populated property these field types honour, and treats a field as already filled if BuddyPress has data for it, if its displayed value is not empty (LH Profile Page filters bp_get_the_profile_field_value to return its stored value), or if the field type printed nothing because it is populated. A filled field is shown as a greyed, disabled input; its value comes from the displayed profile value, or from the new lh_crm_frontend_field_display_value filter (value, field ID, user ID) for field types that do not expose one, and otherwise reads On your profile. Only empty fields keep their input and are submitted, so a stored value can no longer be overwritten from the enquiry form. The logged-out form is unchanged. readme.txt stable tag 1.5.18 and trimmed changelog 1.5.18, 1.5.17, 1.5.16.
  • 1.5.17: Release 1.5.17. A logged-in user now always lodges a frontend enquiry as themselves; before this, a logged-in user with promote_users was shown blank name and email fields and the submission resolved the enquirer from the posted email, which created or matched a different user. Applied in two parts at this version. This part, the submission class: when the visitor is logged in, the enquirer is always the session user and any posted email is ignored; a posted first or last name is saved only where the account has none. handle_bp_edit now only ever fills in profile fields that are empty, for every submission: the override that let a value be changed when the submitter could edit the user is removed, along with the staff Do not send email option. The second part, the frontend form partial: for a logged-in user the form shows their email, and any name or profile field that already has a value, greyed out and read-only, with inputs only for missing values; a notice says who the enquiry is being sent as, links to their profile edit screen, and says to log out first to enquire for someone else; users who can manage enquiries also get a link to add one for someone else in the admin. Staff now lodge enquiries on behalf of other people in the admin rather than the frontend. readme.txt stable tag 1.5.17 and trimmed changelog 1.5.17, 1.5.16, 1.5.15.
  • 1.5.16: Release 1.5.16. Restores the vCard and Text links on the enquiry page, missing since LH Vcard 1.06 (9 September 2026) removed LH_Vcard_plugin::return_vcard_link. The enquiry template only showed the links when that method existed, so both silently disappeared. The vCard link now uses LH_Vcard_plugin::return_signed_vcard_download_url, the method LH Vcard keeps for external callers, and is only output when the current user has LH Vcard's own view capability (LH_Vcard_plugin::return_view_vcard_capability, publish_posts by default), because the signed link can be redeemed by anyone holding it for 24 hours. The Text link no longer depends on LH Vcard: it shows whenever LH Profile Page is active and the enquirer's phone number contains digits. readme.txt stable tag 1.5.16 and trimmed changelog 1.5.16, 1.5.15, 1.5.14.
  • 1.5.15: Release 1.5.15. Removes the unused BuddyPress activity class (includes/lh-crm-activity-class.php). No lh_crm-enquiry activity items exist anywhere on the network and nothing in LH CRM creates them, but the class still ran on every activity query: for editors it made an extra bp_activity_get call per query, and for everyone else it rewrote the BuddyPress activity action list to registered types only, which could hide unregistered types such as check_in and log_entry from the activity stream. lh-crm.php no longer loads the class, and the file itself is deleted in a follow-up step at this same version. return_activity_type() stays in the main class for any external caller. readme.txt: stable tag 1.5.15, the description and FAQ no longer claim activity stream integration, and the trimmed changelog now shows 1.5.15, 1.5.14 and 1.5.13.
  • 1.5.14: Release 1.5.14. Static-analysis fixes from the first full PHPStan run on LH CRM; no intended change in behaviour. Applied in three batches at this version: this batch changes lh-crm.php and readme.txt, the second batch the contact form, notifications and MCP ability classes and the three partials, the third the enquiry post type class. lh-crm.php: register_activation_hook and register_deactivation_hook no longer pass two extra arguments they do not accept; get_post_by_meta passes posts_per_page as the integer 1 instead of the string; generate_post_select casts the post ID to a string before esc_attr. Contact form class: the dashboard messages widget now receives the form ID in an array instead of the whole post object. Notifications class, MCP ability and enquiry list column: post author IDs are cast to integers before get_the_author_meta, get_userdata and get_avatar, and the avatar size is passed as a number. Enquiry class: the enquiry post type args no longer contain a duplicate show_in_menu key (the submenu value already won, so the menu is unchanged); post_name is set from the post ID as a string; the category option value is cast to a string. Frontend form partial: the hidden form ID is cast to a string, and a check on subject_type that could never be false is removed, so the subject field and the lh_crm-form-after_frontend_subject filter always run, exactly as before. Contact form metabox partial: the category option value is cast to a string. Enquiry template partial: the author ID is read once as an integer, the author profile and vCard links are now escaped, its strings use the plugin text domain method, the Text link label is translatable, and the stray closing PHP tag and trailing newline are gone. readme.txt stable tag and changelog updated.
  • 1.5.13: Release 1.5.13. Standards fixes to LH CRM's own copies of three bundled library classes; these are shared drop-ins that other plugins may also ship, and consolidating every copy is tracked separately as task 147743. IP address taxonomy class: get_real_ip_addr() now unslashes and sanitises each server value and returns the first valid IP address from HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR and REMOTE_ADDR in that same order, or an empty string, instead of returning REMOTE_ADDR unchecked; nothing is recorded when no valid address is found; the term description is built with sprintf and a translators comment instead of concatenating the IP into the translatable string; strings use a proper text domain instead of the namespace; the discouraged load_plugin_textdomain call pointing at a folder that does not exist is removed. User agent class: the user agent is unslashed and sanitised before it is truncated, and strings now use the class's own text domain, which it defined but never used. Both classes, and the script registration class, keep their curpageurl and write_log methods for any other caller, but curpageurl now builds the URL from the sanitised host and request URI, and write_log now hands off to LH CRM's canonical write_log instead of calling error_log directly. The forwarded headers are still trusted ahead of REMOTE_ADDR, as before; whether to stop trusting them is part of task 147743. readme.txt stable tag and changelog updated.
  • 1.5.12: Release 1.5.12. Moves the reading of submitted xProfile fields out of handle_bp_edit() and into save_data(), after the form nonce has been verified. save_data() now parses the posted field IDs, runs BuddyPress's datebox formatting for each field, collects each field's unslashed value, and passes the IDs and values to handle_bp_edit() as arguments. handle_bp_edit() no longer touches the request data at all, so the two phpcs ignore annotations it carried are gone. Behaviour is unchanged: the same fields are saved in the same order, required fields are checked the same way, and values are still passed to BuddyPress raw so that checkbox and multiselect arrays and textarea line breaks survive, with BuddyPress sanitising each value by field type. The new handle_bp_edit() parameters default to empty, so any external caller that passes only a user now saves nothing instead of reading the request. readme.txt stable tag and changelog updated.
  • 1.5.11: Release 1.5.11. Standards fixes from the Plugin Check pass. The enquiry post type's read_private_posts capability was mapped to a misspelt read_private_postss, which no role has; it now maps to read_private_posts. The hourly run_processes() check no longer uses a direct database query: it finds due scheduled enquiries with get_posts() and a date_query on post_date_gmt against the current UTC time, still five at a time, now oldest first. The five enquiry status label counts gain translators comments. The Group Action select in the contact form metabox is now passed through wp_kses with a select and option allowlist before output. The Changes saved message after a form submission now uses the LH CRM text domain instead of borrowing BuddyPress's. The three select helpers in the main class no longer pass suppress_filters, which get_posts already defaults to and the BuddyPress helpers ignore, so behaviour is unchanged. curpageurl() now builds the URL from the unslashed and sanitised HTTP host and request URI with set_url_scheme(), instead of raw server variables. readme.txt Tested up to raised to 7.1, stable tag and changelog updated.
  • 1.5.10: Release 1.5.10. Tightens the lh-crm/query-enquiries MCP ability. It now declares its behaviour annotations (read-only, not destructive, idempotent) in the ability meta, and its input schema sets additionalProperties to false so unknown arguments are rejected rather than silently ignored. The schema also now states the valid values it already enforced: status is an enum of new_enq, prog_enq, pending_enq, res_enq and clos_enq, per_page must be between 1 and 100, and page and site_id must be at least 1. On a multisite network the ability now returns an error for a site_id that does not exist, instead of switching to a missing blog. readme.txt stable tag and changelog updated.
  • 1.5.9: Release 1.5.9. Fixes scheduled enquiries being released 10 to 11 hours late on sites whose timezone is ahead of UTC, such as Melbourne. The hourly run_processes() check in the enquiry post type class passed a GMT offset into the second parameter of current_time(), which is only a true or false flag for GMT, so the cut-off it compared against was the site's local time while the stored dates were in GMT. It now compares each enquiry's post_date_gmt against current_time('mysql', true), so both sides are UTC. The lh_crm_gmt_offset filter that supplied the offset has been removed because nothing needs it any more. readme.txt stable tag and changelog updated.
  • 1.5.8: Release 1.5.8. Removes the lh-relationships triple store integration, which nothing on the network reads yet. LH CRM no longer writes as:actor, as:target, as:to or schema:about triples for enquiries, whether on submission, on a status change, when a form's recipient changes, or in the hourly resync. The resync was re-inserting the same triples every hour and, because the live lh_relationships table is missing its unique index, had built up over a million duplicate rows. The relationships class is no longer loaded, and its file includes/lh-crm-relationships-class.php is deleted in a follow-up apply within this release. The lh_crm_enquiry_created action is still fired for other plugins. Triples already written by LH CRM are left in the lh_relationships table (source lh_crm) for separate cleanup. readme.txt feature list, recipient FAQ, stable tag and changelog updated.
  • 1.5.7: Release 1.5.7. Reverts the load-order change made in 1.5.6, which caused a fatal error on every REST API request across the network. In 1.5.6 the component classes were loaded on bp_loaded, so the enquiry post type added its status registration to init after the bundled wp-statuses library had added its own. Both run at init priority 1000, so the five enquiry statuses were registered after wp-statuses had converted the registered statuses to its own objects, and wp-statuses then called is_builtin() on a plain status object during REST server setup. The bundled libraries and component classes are loaded from the constructor again, as soon as the plugin file is read, so their init callbacks are added before wp-statuses' converter. A comment on load_components records why this must not be moved later. The lh_crm_init_plugin filter again only switches off the main class hooks. The plugin still does not work without BuddyPress. readme.txt stable tag, FAQ and changelog updated.
  • 1.5.6: Release 1.5.6. The plugin keeps bootstrapping on bp_loaded, since BuddyPress is a hard dependency, but now loads everything there: the four bundled library classes and the eight component classes are loaded by a new load_components method called from plugins_init, instead of being loaded by the constructor as soon as the plugin file is read. Without BuddyPress nothing loads, where previously the component classes loaded anyway and then called BuddyPress functions. The lh_crm_init_plugin filter now switches off the whole plugin rather than only the main class hooks. Component files are loaded with require_once and absolute paths; the library files keep their class_exists check because other plugins may ship the same files. No other behaviour change. readme.txt stable tag and changelog updated.
  • 1.5.5: Release 1.5.5. Removes the top-level class_exists guards that wrapped the plugin's own classes: the main LH_Crm_plugin class and all eight include classes (contact form, enquiry, form submission, notifications, relationships, activity, bbPress extender and MCP abilities). On this server a class can be bound at compile time, which makes such a guard report true before its own declaration runs and silently skip the singleton instantiation and every hook registration with it. The files are only loaded once, so the guards protected nothing. The four bundled library classes keep their guards because other plugins may ship the same files. Escaping: BuddyPress enquiry notification HTML, the contact form Message List edit link and dashboard widget profile link, the IP address and user agent list columns, the option labels built by the group action, post and field group select helpers, and the Contact Form Details metabox heading, submit label and Edit links. Contact form fixes: saving with Hide Message unticked now clears the setting, and saving with no Notification Recipients ticked now clears the saved list; the Hide Message label now points at its checkbox (its for attribute was malformed), the Response label now points at the Response select, and the metabox help link now goes to the Admin interfaces documentation page. readme.txt stable tag and changelog updated.
  • 1.5.4: Release 1.5.4. Form submission and form output fixes. The form's configured group action (add to group, email invite or silent invite) now always applies to the enquirer: previously a logged-in submitter caused the enquirer to be joined to the group outright whatever the form was set to, including when staff entered an enquiry on someone else's behalf. Submitted profile field values are now passed to xprofile_set_field_data unslashed but otherwise raw, so BuddyPress validates them against the field type and sanitises them on save; previously sanitize_text_field ran first, which blanked checkbox and multiselect answers and removed line breaks from textarea fields. The required-field check in handle_bp_edit no longer has its result wiped straight afterwards. The frontend form partial now escapes its output: the form action and profile and logout links go through esc_url, the subject input type is limited to text or hidden, the form ID is cast to an integer, the subject value, message placeholder, profile field ID list and submit label go through esc_attr, the logged-in display name through esc_html, and the translated labels and placeholders through esc_html__ and esc_attr__. Rendered form markup is otherwise unchanged. readme.txt stable tag and changelog updated.
  • 1.5.3: Release 1.5.3. Removed the lh_crm_form shortcode: the register_shortcodes method and its init hook are gone, after every use of the shortcode across the network was converted to the lh-crm/form block (checked against each site's posts table on 18 Sep 2026). The block is unchanged and still renders through form_shortcode_output, which is kept under its existing name so any external caller keeps working. readme.md is cut down to a description, installation steps and links to the portfolio documentation and changelog, with all reference tables and the hand-written changelog removed now that they live in lh-portfolio. readme.txt drops the shortcode from the feature list, updates the stable tag to 1.5.3, and trims its changelog to 1.5.3, 1.5.2 and 1.5.1 with a link to the full changelog.
  • 1.5.2: Moved debug logging to the standard LH write_log format: each line is written as the level, then the plugin namespace in square brackets, then the message, with arrays and objects encoded as single-line JSON, and a new level parameter defaulting to info. Applied to the main plugin class and to the three standalone library classes (IP address taxonomy, user agent, file registration), which each carry their own copy. The file registration library also gained the debugging status and namespace methods it was missing. The include classes inherit logging from the main class and needed no change.
  • 1.5.1: Moved the register_post_type_args and bp_register_email_tax_type filters that enable the block editor on the BuddyPress bp-email post type to LH Email, which now owns bp-email integration. LH CRM no longer changes how bp-email is registered.
  • 1.5.0: Removed the lh_crm-task_post post type and its admin screens in favour of the standalone LH Tasks plugin, now the shared task queue across the plugin suite. Existing task records were migrated by hand before this release. Also removed the stale task post type reference from the commented-out cleanup block in uninstall.php.
  • 1.4.0: Completed the refactor into separate include classes, adding LH_Crm_mcp_abilities_class and the lh-crm/query-enquiries MCP ability (filter by status, form, author, category, search and site). Added register_post_meta for every contact form and enquiry meta key, the plugin_version identity method, the lh_crm_init_plugin gate, uninstall.php, readme.md and a corrected readme.txt. Added a built-in fallback for creating users when LH User Provisioning is not active. Renamed the admin menu to CRM. Removed dead methods, an empty user flags taxonomy stub and a completed activity migration, and replaced a direct BuddyPress activity query with bp_activity_get. Coding standards fixes: 75 translation calls now use the text domain instead of the namespace, wp_unslash on POST reads, ABSPATH guards in partials and libraries, escaped admin column links, esc_html_e and esc_html__ for output, ordered placeholders with translator comments, and a corrected include path for the file registration library. Requires WordPress 6.9 for the Abilities API and adds a License header.
  • 1.32: Began splitting the single-file plugin into separate include classes for form submission, notifications, relationships and activity. Moved the drop-in utility classes (IP address taxonomy, user agent, file registration) from includes to a new libraries directory, merged the two partials directories into one, removed a dead enquiry email meta fallback from the admin column output that was never written anywhere, and added a languages directory.