@prefix sioc: <http://rdfs.org/sioc/ns#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix content: <http://purl.org/rss/1.0/modules/content/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<https://lhero.org/?post_type=lh-portfolio&#038;p=147301>
  a sioc:Post ;
  dc:title "LH User Provisioning" ;
  dcterms:identifier 147301 ;
  dc:modified "2026-09-13T14:08:53Z"^^xsd:dateTime ;
  dc:created "2026-09-13T14:08:53Z"^^xsd:dateTime ;
  sioc:link <https://lhero.org/portfolio/lh-user-provisioning/> ;
  sioc:has_creator <https://lhero.org/author/1/#account> ;
  sioc:has_container <https://lhero.org/#posts> ;
  content:encoded """
<p class="wp-block-paragraph">LH User Provisioning is the canonical, network-wide entry point for creating and cleaning up WordPress users on the LocalHero platform. Rather than calling <code>wp_insert_user()</code> directly, other LH plugins call <code>lh_user_provisioning_ensure_user()</code>, guaranteeing every account — however it was created — meets the same conventions: login matches email, nicename matches numeric user ID, BuddyPress profile scaffolding, and a provenance record of where the account came from.</p>



<h2 class="wp-block-heading">Why it exists</h2>



<p class="wp-block-paragraph">Before this plugin, user-creation logic — and the login/nicename conventions LocalHero relies on — was duplicated across several plugins (membership registration, BuddyPress group invites, etc.), each with its own drift-prone copy. Centralising it here means a single, guarded, idempotent implementation, and means the convention is enforced network-wide in real time (via <code>user_register</code>/<code>profile_update</code> hooks) for <em>every</em> account, not just ones created through this plugin&#8217;s own API — a WooCommerce checkout, a manually-added wp-admin user, or a CSV import all get the same guarantee.</p>



<h2 class="wp-block-heading">Key features</h2>



<ul class="wp-block-list">
<li><code>lh_user_provisioning_ensure_user( $email, $args )</code> — idempotent: creates the user if they don&#8217;t exist, cleans them up if they do, records provenance on first creation, and returns the <code>WP_User</code>/<code>WP_Error</code></li>
<li><code>lh_user_provisioning_resolve_user( $email )</code> — alias-aware &#8220;does this user exist&#8221; lookup with no side effects</li>
<li><code>lh_user_provisioning_cleanup( $user )</code> — idempotently brings an existing user up to lhero standard</li>
<li>Alias-aware email resolution: checks <code>wp_users</code> first, then falls back to <code>lh-user-identity</code>&#8216;s <code>lh_user_identity_get_user_by_email_fallback</code> filter (a documented no-op when that plugin isn&#8217;t active), so provisioning by a registered alias email reuses the existing account instead of creating a duplicate</li>
<li>Real-time, network-wide convention enforcement via <code>user_register</code>/<code>profile_update</code> hooks (priority 20), independent of this plugin&#8217;s own API</li>
<li>Provenance tracking (source plugin, action, optional reference ID, site ID) as usermeta, surfaced as a &#8220;Provisioned Via&#8221; column on the Network Admin → Users screen</li>
<li>Hourly integrity-check cron as a backstop for accounts predating the real-time hooks</li>
</ul>



<h2 class="wp-block-heading">Install / setup</h2>



<p class="wp-block-paragraph">Network-activate only — activating on an individual site is blocked with an explanatory message, since login/nicename conventions and the provenance column are network-wide concerns. No configuration required.</p>



<h2 class="wp-block-heading">Who depends on this</h2>



<p class="wp-block-paragraph"><code>lh-buddypress-add-user</code> delegates its actual user creation/cleanup to <code>lh_user_provisioning_ensure_user()</code> rather than duplicating that logic, and <code>lh-user-identity</code> documents this plugin as the preferred route for any plugin that creates or provisions users. This makes LH User Provisioning load-bearing infrastructure that other, independently migration-relevant plugins hard-depend on.</p>



<h2 class="wp-block-heading">Contents</h2>


<ul class="lh_portfolio-doc-toc"><li><a href="https://lhero.org/portfolio/lh-user-provisioning/hooks-filters/">Hooks &#038; Filters</a></li><li><a href="https://lhero.org/portfolio/lh-user-provisioning/architectural-notes/">Architectural Notes</a></li><li><a href="https://lhero.org/portfolio/lh-user-provisioning/changelog/">Changelog</a></li></ul>"""^^rdf:XMLLiteral ;
  sioc:content """
LH User Provisioning is the canonical, network-wide entry point for creating and cleaning up WordPress users on the LocalHero platform. Rather than calling wp_insert_user() directly, other LH plugins call lh_user_provisioning_ensure_user(), guaranteeing every account — however it was created — meets the same conventions: login matches email, nicename matches numeric user ID, BuddyPress profile scaffolding, and a provenance record of where the account came from.



Why it exists



Before this plugin, user-creation logic — and the login/nicename conventions LocalHero relies on — was duplicated across several plugins (membership registration, BuddyPress group invites, etc.), each with its own drift-prone copy. Centralising it here means a single, guarded, idempotent implementation, and means the convention is enforced network-wide in real time (via user_register/profile_update hooks) for every account, not just ones created through this plugin&#8217;s own API — a WooCommerce checkout, a manually-added wp-admin user, or a CSV import all get the same guarantee.



Key features




lh_user_provisioning_ensure_user( $email, $args ) — idempotent: creates the user if they don&#8217;t exist, cleans them up if they do, records provenance on first creation, and returns the WP_User/WP_Error
lh_user_provisioning_resolve_user( $email ) — alias-aware &#8220;does this user exist&#8221; lookup with no side effects
lh_user_provisioning_cleanup( $user ) — idempotently brings an existing user up to lhero standard
Alias-aware email resolution: checks wp_users first, then falls back to lh-user-identity&#8216;s lh_user_identity_get_user_by_email_fallback filter (a documented no-op when that plugin isn&#8217;t active), so provisioning by a registered alias email reuses the existing account instead of creating a duplicate
Real-time, network-wide convention enforcement via user_register/profile_update hooks (priority 20), independent of this plugin&#8217;s own API
Provenance tracking (source plugin, action, optional reference ID, site ID) as usermeta, surfaced as a &#8220;Provisioned Via&#8221; column on the Network Admin → Users screen
Hourly integrity-check cron as a backstop for accounts predating the real-time hooks




Install / setup



Network-activate only — activating on an individual site is blocked with an explanatory message, since login/nicename conventions and the provenance column are network-wide concerns. No configuration required.



Who depends on this



lh-buddypress-add-user delegates its actual user creation/cleanup to lh_user_provisioning_ensure_user() rather than duplicating that logic, and lh-user-identity documents this plugin as the preferred route for any plugin that creates or provisions users. This makes LH User Provisioning load-bearing infrastructure that other, independently migration-relevant plugins hard-depend on.



Contents


Hooks &#038; FiltersArchitectural NotesChangelog""" ;
  sioc:topic <https://lhero.org/lh_portfolio-type/plugin/>, <https://lhero.org/lh_portfolio-lifecycle-stage/active/>, <https://lhero.org/?taxonomy=author&term=cap-1> .

<https://lhero.org/author/1/#account> rdfs:seeAlso <https://lhero.org/author/1/?feed=lhrdf&format=turtle> .
<https://lhero.org/lh_portfolio-type/plugin/> rdfs:seeAlso <https://lhero.org/lh_portfolio-type/plugin/?feed=lhrdf&format=turtle> .
<https://lhero.org/lh_portfolio-lifecycle-stage/active/> rdfs:seeAlso <https://lhero.org/lh_portfolio-lifecycle-stage/active/?feed=lhrdf&format=turtle> .
<https://lhero.org/?taxonomy=author&term=cap-1> rdfs:seeAlso <https://lhero.org/?taxonomy=author&term=cap-1&feed=lhrdf&format=turtle> .
