- 2.91.7: Single sign-on no longer bounces crawlers and other automated browsers. On a logged-out subsite visit, the SSO script now skips both the bounce to the main site and the cross-site link rewriting when the browser reports navigator.webdriver or its user agent matches a skip pattern (default: bot, crawl, spider, slurp, headless, lighthouse, preview). Crawlers such as Bing’s renderer were being sent to the main site on every direct landing, which also made LH Beacon refuse their unload beacons as cross-origin. The pattern is filterable via lh_membership_sso_skip_ua_pattern. A real visitor whose user agent matches just does not get the automatic sign-in and can log in normally.
- 2.91.6: Single sign-on to a subsite no longer triggers a Two Factor code prompt. The subsite reuses the main site’s existing, already-verified session, so Two Factor’s wp_login handler is suspended while the SSO callback fires wp_login. Other wp_login actions still run. Normal logins are still challenged.
- 2.91.5: Rejoining a former member from a group’s Former Members screen now works for group admins, instead of failing with “The link you followed has expired.” On the front end that screen sits inside BuddyPress’s own group admin form, and browsers ignore a form nested inside another: the Rejoin form’s fields were sent to the Former Members screen’s save, which refused them because its own nonce had been cut off by the Rejoin form’s closing tag, so the Rejoin handler never ran. The Rejoin button no longer has a form of its own. It submits the surrounding form to the Rejoin address instead (still a POST, so the nonce never appears in a URL), and the Rejoin nonce is printed once per list under its own field name, so it never replaces another form’s nonce. The template pack’s styling and confirm prompt still apply.
- 2.91.4: Adding members with the Add New Members form on a group’s Manage Members screen now works. The form reuses BuddyPress’s add-members field and autocomplete, whose script only copies the people picked from the suggestions into the field when a button with the id save is clicked; the form’s Add Members button had no id, so the field was always sent empty and nobody was added, with no message. The button now has that id. The form also now says so when nobody was selected (“No members were selected to add.”) or when the page has expired, instead of reloading with no message.
- 2.91.3: Saving a BuddyPress group in wp-admin (Groups > Edit) no longer fails with “The link you followed has expired.” when the group has former members with a membership type. The Former Members list printed a Rejoin form with a _wpnonce field for each of them, and on the wp-admin group edit screen that list sits inside BuddyPress’s own edit form: browsers ignore a form nested inside another, so each Rejoin nonce was added to the group save and replaced BuddyPress’s edit-group nonce, and the save was refused. The Rejoin button is no longer shown in wp-admin, where it could never work (it would have submitted the group save instead); it remains on the group’s Manage Members screen. The Rejoin nonce field is also renamed from _wpnonce to lh_membership-rejoin-nonce, and the Rejoin handler checks that field, so a Rejoin form can never overwrite another form’s nonce wherever it is shown.
- 2.91.2: Fixes a fatal error when saving a group in wp-admin (Groups > Edit). The Banned Members group extension showed a metabox there but had no save method, so BuddyPress called an empty save callback and threw a TypeError after the group’s changes had been saved. It now has a settings_screen_save() that does nothing, since the screen is a list with nothing to save.
- 2.91.1: Fixes two errors in the consent-form reading timer. On a return visit, when the start time is already stored, it looked up an element by the storage key instead of the timer’s own script tag and threw a TypeError, so the countdown never ran. When the disable message has no “seconds” in it, it referred to an undefined variable instead of the word it was given.
- 2.91.0: Membership now shows up in LH Profile Page’s search-users MCP ability (needs LH Profile Page 3.9.0 or later; without it nothing changes). Searches gain two parameters: is_member (true for members of any type, false for non-members) and member_type (member type slugs, OR-ed; an unknown slug returns an error listing the site’s member types). Both are hard restrictions whatever match_mode is. Every result gains a membership object with is_member, member_type and member_type_name, fetched with one query per page. Membership here means the member type record, not the member capability that editors and above also hold. The code is a new class, LH_Membership_profile_search_class, which fills search-users’ extension filters so neither plugin calls the other directly.
- 2.90.1: Removes code left over from moving group member management out of LH Buddypress Group Activities and Notifications, which has now been deleted. The checks that held back leave activities, the Banned Members screen and the Add New Members form while that plugin was active are gone, as is the old lh_bp_g_as_and_ns_maybe_add_users_to_group_fallback filter, which nothing uses any more. Add New Members lookups beyond username and email use lh_membership_add_members_user_fallback. No change in behaviour.
- 2.90.0: Group member management moves into LH Membership from LH Buddypress Group Activities and Notifications, which is being retired: the left-the-group activity posted when a member leaves or (once BuddyPress confirms it) is removed, the Banned Members group screen, and the Add New Members form on Manage Members for site moderators. LH Membership is now self-contained for former members: without LH Audit Trail its fallback reads the leave activities it posts itself. None of these run while LH Buddypress Group Activities and Notifications is still active, so deactivating it hands over without anything being posted twice. The Add New Members form applies a new lh_membership_add_members_user_fallback filter and still applies the old lh_bp_g_as_and_ns_maybe_add_users_to_group_fallback name, so lookup by phone keeps working.
- 2.89.0: Former members now come from LH Audit Trail when it is active, falling back to the leave activities otherwise, and the side-by-side testing list is gone. LH Membership now also owns the other former-members features, moved from LH Buddypress Group Activities and Notifications: the Former members box on the wp-admin group edit screen, the Former Members table at the end of a group report, and the Former teams and groups section under a member’s Groups tab, which is now shown only to the member and to site moderators and no longer deletes anything while it renders. Rejoin works from the wp-admin box too.
- 2.88.0: Former Members group screen: while the audit trail is being tested, group admins see the existing list (from leave activities) and a second list built from LH Audit Trail, which also shows members who were removed, who removed them, and leaves that happened automatically. Without LH Audit Trail only the existing list is shown. The leave activity type is now read through the filterable return_left_activity_type() instead of calling LH Buddypress Group Activities and Notifications directly.
- 2.87.0: The no-access (403) page now works on themes that use block templates. When a logged-in visitor is denied access, LH Membership puts its own no-access template first in the 404 template hierarchy, so WordPress uses it instead of the theme’s 404 page on any theme with block template support. The template is registered by the plugin (title No Access, editable in the Site Editor) and holds the theme’s header and footer parts, a No Access heading and a new No access message block (lh-membership/no-access) showing the reason-specific message with the site admin’s email address. Classic themes without block templates keep using their 404.php through the existing 404_the_title, 404_the_content and 404_show_search_form filters. The admin email link is now escaped, and the access-control class no longer sits inside a class_exists() guard.
- 2.86.3: Navigation block links to non-public pages now follow the same access rules as classic menus. WordPress only renders a Navigation link to a post or page when it is published, so a link to a private page (or one in another non-public status, such as restricted) was hidden even from people allowed to read it. The access-control class now adds the linked post’s status to the allowed list when that status is non-public and the current visitor can read the post, so such links show to those who can read the page and stay hidden from everyone else.
- 2.86.2: Manage Members now uses the active template pack’s own template instead of a copy bundled in LH Membership. LH Membership used to override groups/single/admin/manage-members.php with a copy of the BuddyPress Legacy template whose only change was hiding banned members from the Members list; because its template folder is put first in the template stack, that copy also hid any improvements in the theme or template pack (for example the BP LocalHero pack’s POST-form member actions). The copy is deleted, and banned members are still hidden on Manage Members through a bp_after_group_has_members_parse_args filter that sets exclude_banned on that screen only; banned members are managed on the group’s separate banned screen. Nothing visible changes on sites using BuddyPress Legacy.
- 2.86.1: Security fix for the Rejoin action on a group’s Former Members list. Rejoin previously acted on any request to its URL with no nonce check and no permission check, and it ran before BuddyPress’s group admin screen gate, so any visitor, a crawler or a link prefetch could put a former member back into any group. It now acts only on a POST from a group admin or a site moderator, checks its nonce, and redirects back to Manage Members afterwards. The Rejoin button is now a small POST form with the nonce as a hidden field instead of a link, so no nonce appears in a URL; it keeps the confirm prompt where the BP LocalHero template pack is active. Failed attempts show an error message instead of doing nothing silently.
- 2.86.0: Adds a Register link block (lh-membership/register-link) for Navigation menus. It can only be placed inside a Navigation block, links to the site’s registration page, and shows only to visitors who can register, using the same rule as the Register item from LH Dynamic Menu Items. Its markup matches a core Navigation link and the Navigation block wraps it in a list item like its own items. The link text defaults to Register and can be changed in the block settings. Nothing changes on existing menus; the block is new and only appears where it is added.
- 2.85.1: Release 2.85.1. Removes the class_exists guard that wrapped the whole LH_membership_plugin class declaration, its instantiation and its activation and deactivation hooks. On this server a class with no parent can be bound early by PHP and OPcache at compile time, so class_exists can already report true before the guarded code would logically run; the guard then skips the class body and all of the plugin’s hook registration on every request, silently and with no error. This is the same pattern that caused the lh-tasks incident, and WordPress already loads each active plugin file only once per request, so the guard never protected anything. The guard around the vendored WP_Statuses library is kept, since that is shared third-party code other plugins may also bundle. No behaviour change otherwise. This is also the first release on three-segment versioning: 2.85 becomes 2.85.1. readme.txt Stable tag and trimmed changelog updated to match.
- 2.85: Release 2.85. The plugin_version identity method now reads the Version field from the plugin file header with get_file_data, cached in a static variable for the rest of the request, instead of returning a hardcoded string. The header is now the single source of truth, so a release only needs the version bumped in one place and the two can no longer drift apart. get_file_data is used rather than get_plugin_data because it is available on the front end without loading wp-admin includes and does not translate header fields, which on WordPress 6.7 and later triggers a just in time textdomain notice if called before init. Also brings readme.txt up to date: Stable tag is now 2.85 and the trimmed changelog covers 2.83 to 2.85.
- 2.84: Release 2.84. Harden the group former members list. The former members query now runs through wpdb prepare with placeholders for the activity type, group ID and excluded member IDs instead of concatenating them into the SQL. It also selects only the user ID, display name and the latest leave date per user, using MAX of date_recorded, where the old select star with group by returned an arbitrary activity row per user, so the left date shown was not reliably the most recent. All output in the former members metabox is now escaped: display names and membership type names with esc_html, profile and rejoin links with esc_url, and translated strings with esc_html__. Adds the missing plugin_version identity method to the main plugin class.
- 2.83: Release 2.83. Fix an undefined variable warning in the former members list on the BuddyPress group admin screen. When a former member had no membership type, the pending approval check was passed an undefined user_id variable instead of the former member’s own ID, so it always failed and pending members were wrongly labelled as Non member. It now checks the former member’s ID.
- 2.82: Release 2.82. Fix restricted, confidential and logged-in posts missing from front-end archives such as the GatherPress past-events listing. The shared post-status base class appended its status in pre_get_posts at the default priority, but lh-archived-post-status also runs at priority 10 and replaces post_status wholesale with publish (plus private) on every non-singular main query. Our callback ran first, found an empty post_status with nothing to append to, and the list was then overwritten, so the status was dropped. pre_get_posts now runs at a late, filterable priority (return_pre_get_posts_priority, default 1000) so it appends after any callback that replaces the list. It also accepts a comma-separated string post_status, which previously would have fatalled on the array append, skips the any keyword, and avoids adding a duplicate. An empty post_status is still left alone, since WP_Query then includes every public status and register_status already makes the status public for a qualifying viewer.
- 2.81: Release 2.81. Removes register_scripts_and_styles() and add_wp_body_open_hooks() from the god class along with their two hook registrations, and adds LH_Membership_assets_class to the include map. maybe_enqueue() deliberately stays here: the login, recover-account and register classes call it as self::maybe_enqueue() and it only resolves while defined on the class they inherit from. $fake_page_ran and $email_sent also stay β both are set and read by those same subclasses on their own inherited instances, so neither is dead despite the only readers inside this class being the two unhooked fix_shortlink/fix_canonical methods.
- 2.80: Release 2.80. Removes the five session and login-flow methods from the god class β do_login_extension(), force_logout(), extend_cookie_expiration_to_360_days(), redirect_to_login() and maybe_upgrade_on_login() β along with their five hook registrations in plugin_init(), and adds the new LH_Membership_session_class to the include map in their place. The methods themselves were lifted verbatim into includes/lh-membership-session-class.php in the preceding change; that class extends LH_membership_plugin, so every self:: call inside them resolves as before. Grep across the network confirmed nothing outside this plugin called any of the five. The WooCommerce lostpassword_url removal stays here β it is not session behaviour.
- 2.79: Release 2.79. Replace the thirty-two hand-maintained class_exists()/include_once blocks in __construct() with a single filterable class-to-file map (return_included_classes()) and a loop that walks it (include_classes()). Load order is preserved exactly β the array is ordered as the blocks were, and PHP preserves insertion order β which matters because the post-status base class must load before its subclasses. Paths are now resolved through return_plugin_path() rather than relying on the include path, and a missing file is logged rather than silently skipped. Behaviourally identical: same classes, same order, same guard against double-inclusion. Adding or extracting a class is now one line in the map instead of an eight-line block, and the filter lets a site-specific plugin swap an implementation without editing this file.
- 2.78: Release 2.78. Restricted-content access no longer comes from a stored role: read_restricted_posts is granted at runtime via user_has_cap, from the member-type taxonomy for members plus an edit_others_posts proxy for editorial roles. The capability was stripped from every role (it had been written onto participant and onto every role with edit_posts, i.e. contributor and above rather than editor and above), and the participant role itself was retired network-wide by a batched per-site migration, assigning subscriber to members and unclaimed to anyone else who would otherwise have been left with no role. The two remaining participant assignments were removed from the onboarding class β the approval flow now falls back to subscriber, and the BuddyPress settings-page section callback that bulk-added the role on every render is now a no-op. Also fixes a regression introduced during this release: the runtime grant briefly memoised its eligibility lookup per request, and because capability checks fire before the taxonomy registers on init 9, an unanswerable check cached a denial that locked every member out of restricted content until the cache was removed.
- 2.77: Version bump to 2.77 accompanying the check-ins-restricted-status fix (see lh-membership-restricted-post-status-class.php change): bump plugin header version, bump readme.txt Stable tag, add the 2.77 changelog entry, and trim readme.txt’s changelog to the last 3 versions (2.77/2.76/2.75) per standing instructions rule 3.
- 2.76: Version bump 2.75 -> 2.76 following the browser-caching / second-code-path fix applied in proposal lhcp_5b54d6ca. Bumped the plugin header Version and readme.txt Stable tag to 2.76, added a 2.76 changelog entry summarising the outstanding-site-invite purge gap fix and the Clear-Site-Data / nocache_headers browser-caching fix, and trimmed the changelog to the last 3 versions (2.76, 2.75, 2.74) by removing the 2.73 entry, per standing instructions.
- 2.75: Fix broken LiteSpeed cache purge on the confirm-details -> terms-consent redirect. The purge call fired after a user confirms their profile details (right before redirecting them to the T&Cs/consent page, which reads the just-set user_details-confirmed option) was silently a no-op: header(‘X-LiteSpeed-Purge: ‘.$redirect_to) sent a bare URL, but LiteSpeed Web Server only treats this header as a purge instruction when the URL carries a `url=` prefix, and purging this visitor’s private/ESI cache entry additionally requires a `private` flag β a bare URL matches neither documented form. Meanwhile LiteSpeedPurge::purge_private() purged private cache broadly without targeting this specific URL. Together this meant a copy of the destination page cached before the user confirmed their details (e.g. via LiteSpeed pre-fetching the link) kept being served after the redirect, only clearing on a full manual wp-admin cache purge. Replaced both with a correctly-formed `X-LiteSpeed-Purge: private,url=β¦` header plus a call through LiteSpeed Cache for WordPress’s own `litespeed_purge_url` action hook, so the destination URL is actually purged before the redirect completes. Bumped plugin version 2.74 -> 2.75 (header comment + readme.txt stable tag) and added a changelog entry per standing conventions.
- 2.74: version bump 2.73 -> 2.74 covering two REST pixel hotfixes (REST namespace underscore rename + validate_callback closure fix)
- 2.73: version bump 2.72 -> 2.73 and changelog entry
- 2.72: Fix the member terms/consent-viewed tracking pixel (lh_membership-do_pixel): register wp_ajax_nopriv_ alongside wp_ajax_ so a request with no logged-in session doesn’t hit admin-ajax.php’s default ‘no handler’ HTTP 400 die; always emit a valid 1Γ1 PNG regardless of whether the tracking-update checks pass; and exit immediately after echoing the image so admin-ajax.php’s own trailing die(‘0’) can’t append a stray byte and corrupt the PNG. Also bump plugin version 2.71 -> 2.72 and update readme.txt changelog/stable tag per standing conventions.
- 2.71: Adopt the canonical write_log() convention: add $level parameter (default ‘info’), encode arrays/objects as single-line JSON instead of print_r(), write ‘Level: [namespace] message’. return_plugin_debugging_status() already existed, unchanged. No version bump per Peter’s instruction. Only the write_log block changes β everything else in this large file left byte-identical.
- 2.69: Added a filterable capability-based exemption from the member-onboarding BuddyPress gate: LH_Membership_private_buddypress_class::non_member_requires_access() now returns false immediately for any user with the promote_users capability (default, filterable via lh_membership_return_gate_exempt_capability), regardless of their own membership or approval status. New method return_gate_exempt_capability() added alongside the class’s other filterable whitelists.
- 2.68: Added a restrict integration into lh-buddypress-log-entry rather than lh-competitions: hooks the new lh_bp_log_entry_return_event_post_statuses filter (lh-buddypress-log-entry 1.03) so restricted events are included in the log entry form’s event lookup, the open and closed shortcodes, and the past-events listing. This integration is unconditional, not gated by current_user_can_view(), for the same reason as the standings integration: it only adds restrict to the set of statuses queried, not deciding what a particular viewer gets to see.
- 2.67: Added a fifth lh-competitions integration for restrict: hooks the new lh_comps_latest_scoresheet_event_query_args filter, so the “Latest {Night} Scoresheet” dynamic menu item can resolve to a restricted event for users who can view them (current_user_can_view(), the same gated pattern as round-nav, event-listing and game-edit-event; unlike standings, which is deliberately unconditional). The default query status (‘publish’, a string rather than an array here) is normalised to an array before appending restrict.
- 2.66: Added a fourth lh-competitions integration for restrict: hooks the new lh_comps_standings_event_query_args filter (lh-competitions 1.80) so events with status restrict are included in standings table calculations alongside publish. Unlike the other three restrict integrations (round-nav, event-listing, game-edit-event), this one is deliberately unconditional, not gated by current_user_can_view(), since the standings table is a computed aggregate (wins and points) rather than per-viewer visible content; gating which events feed into the calculation by the viewer’s access would make the numbers differ depending on who is looking.
- 2.65: Added a third lh-competitions integration for restrict, matching the existing round-nav and event-listing ones: hooks the new lh_comps_game_edit_event_post_status filter (lh-competitions 1.79 or later) so restricted events are selectable in the Event dropdown on the game edit screen’s Game Data meta box, for users who can view restricted content (self::current_user_can_view(), the same check as the other two integrations). Not extended to confidential or logged_in; this integration stays restrict-only.
- 2.64: Reworded the BuddyPress access-denied flash message. Since the person only sees it after being redirected to their own profile, “This area is only visible to current members” read as if it described the page they had just landed on. Changed to “You tried to access an area that is only visible to current members” to make clear it refers to where they came from.
- 2.63: Fixed 2.62’s BuddyPress no-access page: the title correctly changed to “No Access” but the actual screen content (groups directory, members directory, etc.) kept rendering underneath it. Root cause: BuddyPress doesn’t render through WordPress’s normal template and 404 flow at all; it has its own screen-rendering system that runs regardless of is_404() or the 404_the_content filter. LH_Membership_access_control_class::show_no_access() only ever affects the page title (a global filter, unrelated to the template system) for a BuddyPress URL; the screen content itself is entirely unaffected by it. handle_no_access() for BuddyPress now redirects the non-member to their own profile (bp_members_get_user_url()) instead, with a bp_core_add_message() flash notice explaining why. This is what the retired no_access_class’s redirect_for_buddypress() always did; 2.62 had replaced it with the in-place 403 overlay that doesn’t work for BuddyPress specifically. Every other gate (restrict, confidential, logged_in, private, private_site) is unaffected, since they render through the normal WordPress template flow, where show_no_access() works correctly. Redirecting to the user’s own profile is safe against a redirect loop, since is_own_profile() will be true on the next request.
- 2.62: Fixed BuddyPress access gating: logged-in non-members could view every BuddyPress component, not just their own profile and its subscreens as intended. Two separate root causes, both pre-existing and neither introduced by the 2.59 unification. 1. LoginRequired() never checked actual club membership at all, only whether the visitor was logged in and the component wasn’t whitelisted. The real “is this user a current member” check (LH_Membership_member_type_class::get_membership_type_details()) was only ever wired up to the own-profile exemption, never to gating the Activity feed, Groups directory, Members directory or any other component. 2. The logged-in branch of force_login_if_required() only ever ran its check inside if (is_404() && β¦), but BuddyPress component pages are normal 200 responses via BuddyPress’s own rewrite rules, not 404s, so that condition was never true in practice. Added is_member() (wired to the real membership-type check) and two new audience-decision methods, visitor_requires_login() and non_member_requires_access(), which combine it with the own-profile exemption and the whitelists. force_login_if_required() and hijack_bp_no_access() now both gate on these directly. Also added two new filterable whitelists, split out from the existing general one (lh_membership_return_white_labelled_components): lh_membership_return_visitor_accessible_components (visitors only) and lh_membership_return_non_member_accessible_components (logged-in non-members only).
- 2.61: Fixed the root cause of restricted-status content being visible to users who shouldn’t have access. A long-standing bug in create_role_and_assign_capability()’s auto-grant loop: it iterated $wp_roles->roles checking $role[‘role’], but that array is keyed by role slug with no ‘role’ sub-key, so the condition was always empty and the loop never ran, effectively dead code. Fixed to iterate correctly by role slug using get_role(), and made it self-healing: a role that has the capability but doesn’t qualify now has it actively removed. Only runs on plugin activation.
- 2.60: Fixed a fatal error on activation: LH_Membership_access_control_class defined a static redirect_to_login() method colliding with the main plugin class’s non-static instance method of the same name. Renamed to send_to_login_for_access_denied() throughout. No behavioural change.
- 2.59: Unified non-public content access handling. Previously seven classes independently implemented pieces of “decide who gets in, tell them why if not”: three post-status classes (restrict, confidential, logged_in), two content-redirect classes, and two whole-site or whole-section gates (private_site, private_buddypress), plus an eighth undocumented class (LH_Membership_no_access_class) handling logged-in-but-insufficient-access 403s. The new canonical class, LH_Membership_access_control_class, is the single home for both outcomes: not logged in redirects to login with a reason-coded query arg; logged in with insufficient access gets a 403 “No Access” page. Three filterable lookups (lh_membership_return_non_public_statuses, lh_membership_view_capability_for_status, and message filters) let any status or gate participate without lh-membership knowing about them. New LH_Membership_post_status_gate_class is the single template_redirect trigger for every post-status-based gate. New LH_Membership_post_status_base_class removes duplicated boilerplate across restrict, confidential and logged_in. LH_Membership_private_content_class is reduced to a policy class. LH_Membership_private_site_class and LH_Membership_private_buddypress_class now call the shared outcome methods. LH_Membership_no_access_class retired. Action needed: the query-arg rename means any bookmarked or cached URL using the old ?lh_membership-login_required=true flag no longer triggers the login message. This only affects single-use mid-flow redirect URLs, so no stored data is affected.
- 2.58: Brought the standalone LH Private BuddyPress plugin (v1.12) into lh-membership as LH_Membership_private_buddypress_class. It gates whole BuddyPress component pages from logged-out visitors via the wp action and BuddyPress’s bp_core_no_access filter. Faithful port; fixed a spelling typo (return_exluded_page_ids to return_excluded_page_ids), internal only. Action needed: deactivate and remove the standalone lh-private-buddypress plugin.
- 2.57: Removed LH_Membership_sso_class::return_rest_namespace(), an orphan flagged in 2.56, after confirming it had no remaining callers. No functional changes.
- 2.56: Three bug fixes. (1) private_content_class’s attachment_id branch was re-fetching the original queried object instead of the attachment specified in $_GET[‘attachment_id’]; fixed. (2) Fixed a typo in a class_exists guard (resticted to restricted); the guard was always false, harmless but dead. (3) Removed three confirmed-orphaned SSO methods.
- 2.55: Brought the standalone LH Private Content Login plugin (v1.05) into lh-membership as LH_Membership_private_content_login_class. A faithful, minimal-change port, deliberately not reconciled with the heavily overlapping LH_Membership_private_content_class; that reconciliation was planned as a later overhaul. Documented six areas of overlap and divergence between the two classes for that future work. No functional changes in this release. Action needed: deactivate and remove the standalone lh-private-content-login plugin.
- 2.54: Added a second lh-competitions integration alongside round-nav: hooked lh_comps_event_listing_post_status and included restrict for users with read_restricted_posts, so restricted events surface in the Event Listing block.
- 2.53: Brought the standalone LH Logged in Post status plugin (v1.09) into lh-membership as LH_Membership_logged_in_post_status_class, following the restricted and confidential post-status pattern. Fixed a real bug in the process: the standalone plugin’s filter name for its private_content_class integration never matched the actual filter, so it never fired on any site running both plugins.
- 2.52: Refactor: generic utilities (IsResourceLocal, url_to_relative_path, curpageurl, uri_to_array, http_parse_query, build_url, generate_post_select) moved to a new LH_Membership_generic_helpers_class. xprofile_form_output() and handle_bp_edit() moved into LH_Membership_confirm_details_class. The main plugin class is down to 47,369 bytes from 87,013 at the start of the refactor (a 46% reduction). No functional changes.
- 2.51: Refactor (step 5, final): split handle_form_post() out, with the login branch moving to LH_Membership_login_class and the password-change branch to LH_Membership_change_password_class, each self-registering on wp_loaded. Also moved simple_login_user(), do_login_setup() and add_login_fail_message() to login_class. Completes the planned 5-step refactor: the main class is down 32% across all steps. No functional changes.
- 2.50: Dead code sweep: removed 11 confirmed-unreferenced methods across the main class and three other classes, each verified individually to rule out framework-invoked methods. No functional changes.
- 2.49: Refactor (step 4, narrowed scope): removed four duplicate page-slug and URL methods from the main class in favour of the equivalents already in login_class, recover_account_class and change_password_class. Two other originally scoped items turned out not to be real duplication and were dropped. No functional changes.
- 2.48: Refactor (step 3): moved the “use email addresses” login setting UI out of the main class into LH_Membership_login_class. No functional changes.
- 2.47: Added an lh-competitions integration: hooks lh_comps_round_nav_post_status to include restrict in round navigation for users with read_restricted_posts.
- 2.46: Refactor: extracted the OTC and passkey subsystem into a new LH_Membership_otc_class, including the hourly cleanup cron. No functional changes.
- 2.45: Refactor: moved site-invitation methods into LH_Membership_user_invitations_class. Removed a dead, unimplemented shortcode registration. No functional changes.
- 2.44: Switched the get_user_by_email() fallback from a per-plugin filter to the canonical lh_user_identity_get_user_by_email_fallback.
- 2.43: Registration: ensure_user_is_added() now uses LH_membership_plugin::get_user_by_email(), so alias email addresses are recognised as existing accounts.
- 2.31: Cleanup: removed a duplicate user invitations include, replaced the authentication codes if-chain with a loop, removed the dead rest_login_user method, and removed SSO debug logging.
- 2.30: SSO: renamed the return parameter to lh_membership-sso_return.
- 2.29: SSO: renamed the return parameter to lh-membership-sso_return.
- 2.28: SSO: prefixed the return parameter with the plugin namespace.
- 2.27: SSO: renamed the return_url parameter to lh_sso_return.
- 2.26: SSO: added the missing base64url_encode and base64url_decode methods.
- 2.25: SSO: added debug logging and fixed base64url encoding.
- 2.24: SSO: replaced sanitize_text_field with a regex whitelist for token parameters.
- 2.23: SSO: capture the raw session token via the set_auth_cookie action for same-request logins.
- 2.22: SSO: converted sso-receive to template_redirect.
- 2.21: SSO: converted all three endpoints to template_redirect and removed the REST routes.
- 2.20: SSO: attempted nonce-based REST authentication (reverted).
- 2.19: SSO: fixed the plugin folder name in the zip.
- 2.18: SSO: converted the endpoints to REST routes.
- 2.17: SSO: OTC logins are now routed through the main site sync via return_sync_redirect_url().
- 2.16: Added an auth page exclusion list to the SSO logged-out script.
- 2.15: OTC security hardening: brute force protection (10 attempt limit), expiry enforcement, cron cleanup of expired OTCs, and encryption keys derived from wp_salt(). Also removed a debug email, fixed do_login_extension for all users, fixed broken string concatenation, and fixed an undefined variable in rest_login_user.
- 2.14: Bug fix.
- 2.13: Direct access check.
- 2.12: filemtime and much more.
- 2.11: Translatable strings.
- 2.10: Latest web credential standards.
- 2.09: Better option settings.
- 2.08: Web credentials support.
- 2.07: Improved settings screen, bumped to force update.
- 2.05: Added WooCommerce support.
- 2.04: Added a login link shortcode.
- 2.02: Use isset.
- 2.01: Input focus.
- 1.99: Support redirect attribute.
- 1.98: Better redirects.
- 1.96: Error fix. Better multisite support. (The legacy changelog listed 1.96 twice, dated 17 December 2015 and 12 April 2016; both notes are merged here.)
- 1.94: Add pages across the network.
- 1.92: Redirect fix. Fixed logout. (The legacy changelog listed 1.92 twice, the second with an invalid date of 31 November 2015; both notes are merged here.)
- 1.90: Support SSL.
- 1.81: Minor fix.
- 1.80: Allow redirects to be blocked.
- 1.62: Bug fix.
- 1.60: Removes the title on the login page and styles the form.
- 1.7: Added the login_form hook.
- 1.52: Fixed row meta.
- 1.51: Fixed a redirect bug.
- 1.5: Log in with email addresses or user names.
- 1.4: Automatically create the page.
- 1.3: Added filters.
- 1.21: Improved menu.
- 1.1: Settings link.
- 1.00: Initial release.
Changelog
If you like this plugin please consider making a donation to further development