Manage group membership in Discourse with WP Discourse SSO

WP Discourse functions

The WP Discourse plugin has a couple of functions that can be used to associate WordPress users with Discourse groups.

These functions are only enabled on sites that are using WordPress as the SSO provider for Discourse.

  • add_user_to_discourse_group( $user_id, $group_names ) View Code
  • remove_user_from_discourse_group( $user_id, $group_names ) View Code

Each of these functions take the same two parameters:

  • $user_id: the WordPress user’s id
  • $group_names: a comma separated list of Discourse group names (with no spaces between names.)

The functions have been made with the assumptions that every membership plugin will do an action when it adds or removes WordPress users from a level, and that the parameters sent with these actions will include the WordPress user’s ID and the membership level id. I have looked at a few WordPress membership plugins, and they function this way.

Using the functions with a membership plugin

  • Look through the hooks and filters documentation of whatever membership plugin you are using. Look for actions that correspond to add_membership_level and remove_membership_level.

  • At the top of your file (for example functions.php create an alias for the WPDiscourse namespace. This will make it easier to use the functions in your code:
    use WPDiscourse\Utilities\Utilities as DiscourseUtilities

  • Write a couple of functions to add and remove members from Discourse groups.

  • Hook into the functions after checking the the WP Discourse plugin is loaded.

An example using the PaidMembershipsPro plugin

I’m using this plugin for an example because I’ve been asked about it, it has a free version, and it’s easy to setup. The documentation for its hooks and filters is here. This example is incomplete. It just ties into two hooks, one that is called when a membership is created, and another that is called when a membership is cancelled. There are other actions that are called when membership levels are changed, or when all membership levels are cancelled at the same time. You’ll need to look through the docs…

<?php
// functions.php file

// Allows you to call the wp-discourse functions without having to write the namespace each time you use them.
use WPDiscourse\Utilities\Utilities as DiscourseUtilities;

// Returns the Discourse group name that is associated with a membership id. With
// PaidMembershipsPro, you can see the membership id on the Membership Levels page. With other
// membership plugins, you may need to call a function to get access to a `membership_id/membership_name` array.
function dcpmp_get_level_for_id( $id ) {
    $levels_to_discourse_groups = array(
        1 => 'walking',
        2 => 'running'
    );

    if ( empty( $levels_to_discourse_groups[ $id ] ) ) {

        return new WP_Error( 'pmpdc_group_not_set_error', 'A Discourse group has not been assigned to the level.' );
    }

    return $levels_to_discourse_groups[ $id ];
}

// Adds a user to a Discourse group if there is a group associated with the membership level.
function dcpmp_add_member_to_group( $member_order ) {
    if ( ! empty( $member_order->membership_id ) && ! empty( $member_order->user_id ) ) {
        $user_id = $member_order->user_id;
        $group_name = dcpmp_get_level_for_id( $member_order->membership_id );
        if ( is_wp_error( $group_name ) ) {

            return null;
        }

        // Adds the user to the Discourse group.
        $result = DiscourseUtilities::add_user_to_discourse_group( $user_id, $group_name );

        if ( ! empty( $result->success ) ) {

            // If the user has been added to the group, add a metadata key/value pair that can be used later.
            add_user_meta( $user_id, "dcpmp_group_{$group_name}", 1, true );
        }

        return $result;
    }

    return new WP_Error( 'dcpmp_order_not_set_error', 'There was an error with the PMP order.' );
}

// Removes a user from a Discourse group.
function dcpmp_remove_member_from_group( $level_id, $user_id, $cancel_level ) {
    if ( ! empty( $cancel_level ) ) {
        $group_name = dcpmp_get_level_for_id( $cancel_level );
        if ( is_wp_error( $group_name ) ) {

            return null;
        }

        // Removes the user.
        $result = DiscourseUtilities::remove_user_from_discourse_group( $user_id, $group_name );
        if ( ! empty( $result->success ) ) {
           // Remove the membership level metadata key.
            delete_user_meta( $user_id, "dcpmp_group_{$group_name}" );
        }

        return $result;
    }

    return null;
}

// Hook into the PaidMembershipsPro actions.
// Make sure to check that the Discourse class exists. If not, and you deactivate wp-discourse, this will crash your site.
if ( class_exists( '\WPDiscourse\Discourse\Discourse' ) ) {
    add_action( 'pmpro_added_order', 'dcpmp_add_member_to_group' );
    add_action( 'pmpro_after_change_membership_level', 'dcpmp_remove_member_from_group', 10, 3 );
}

Restricting access to Discourse when a membership doesn’t exist

There is a wpdc_sso_provider_before_sso_redirect action hook that can be used to restrict access to your forum to only WordPress users who have a membership. In the SSO login process the action occurs after the user has logged into WordPress, but before they are redirected to Discourse with their login credentials.

You will need to setup your own condition to check for the existence of a membership. Make sure you call exit after the redirect.

add_action( 'wpdc_sso_provider_before_sso_redirect', 'wpdc_custom_check_user_membership', 10, 2 );
function wpdc_custom_check_user_membership( $user_id, $user ) {
    if ( /* some condition */ ) {
	    wp_safe_redirect( home_url() );

	    exit;
    }
}
26 Likes

Thanks. I was able to use the pmpro snippet and incorporate similar logic using s2member

add_action( 'set_user_role', 'make_a_decision', 10, 2);

function make_a_decision( $user_id, $new_role ) {

        $site_url = get_bloginfo('wpurl');
        $user_info = get_userdata( $user_id );
        $to = "webmaster@domain.com";
        $subject = "Role changed: ".$site_url."";

        if ( $new_role === 's2member_level1') {
          if ( class_exists( '\WPDiscourse\Discourse\Discourse' ) ) {
            $result = DiscourseUtilities::add_user_to_discourse_group( $user_id, 'running');
            $message = "Hello Webmaster\n" .$user_info->display_name . " on ".$site_url.", now converted into " . $new_role;
            wp_mail($to, $subject, $message);
          }
        } else {
          if ( class_exists( '\WPDiscourse\Discourse\Discourse' ) ) {
            $result = DiscourseUtilities::remove_user_from_discourse_group( $user_id, 'running' );
            $message = "Hello Webmaster\n" .$user_info->display_name . " on ".$site_url.", now converted into " . $new_role;
            wp_mail($to, $subject, $message);
          }
        }

}

2 Likes

Hello @simon

can we create discourse users manually instead of sync [enabling discourse user creation on SSO login] , the setting in wp-discourse plugin

Thank you.

1 Like

You can create users manually on WordPress. You can do this both with the ‘Create or Sync Discourse Users on Login’ setting enabled, or with that setting disabled.

If you create a user manually on WordPress and deselect the WordPress option to Send the new user an email about their account, the user will have to confirm their email the first time they login to Discourse.

If you want to create a user manually and have them be automatically added to Discourse groups at the time that you create them, you will need to write some code that hooks into the WordPress user_register hook and calls the function WPDiscourse\Utilities\Utilities::add_user_to_discourse_group with the user’s ID and the names of the groups that you wish to add the user to.

5 Likes

Thank you for the reply Simon

If we uncheck “Create or Sync Discourse Users on Login” it won’t create discourse users right?

can we use discourse utility function create_discourse_user() when required with wordpress user information

1 Like

If you uncheck that option, a Discourse user will not be created until the user logs into Discourse through WordPress.

If you have WordPress users that you would like to not be able to login to Discourse with SSO, you need to hook into the wpdc_sso_provider_before_sso_redirect action hook.

You can use that function. If you are dealing with groups, it would be better to use the add_user_to_discourse_group function. That function will create the Discourse user and add them to the group names that you supply for its second parameter. That function really needs to be renamed. It can be used to add a user to multiple groups.

4 Likes

This helps Simon

I have successfully used wpdc_sso_provider_before_sso_redirect which is restricting user from login based on condition, you helped me in my previous thread.

now I need to uncheck “Create or Sync Discourse Users on Login” this and create discourse user based on my condition.

Thank you very much

2 Likes

Hi Simon,
I’m using ActiveMember360, the document for hooks and filters is here. I don’t see the action you mentioned. Am I right?
Thanks

I don’t see an action hook that is fired when a membership is added. You could try asking that plugin’s developers if there is a hook for that.

There is another approach that you can use for syncing memberships between WordPress and Discourse. You can add and remove users to Discourse groups by including the groups in the SSO parameters. I will update the topic to give an example of how that is done. For this to work, you need to know how to find out what WordPress memberships a user has. In the example code on this page - https://activemember360.com/docs/mbr_alternate_role/ - they are getting a user’s memberships from an array of ActiveCampaign tags. There should be some documentation for the ActiveMember plugin that lets you know how to access that array.

2 Likes

I add a reply from ActiveMember360 :

We would suggest that you should incorporate any Discourse code in the mbr_authenticated_login hook.

See: https://activemember360.com/docs/mbr_authenticated_login/

Using that hook you can ensure that the user on login is assigned to the correct Discourse group based upon tags/memberships.

The following PHP code will return an array of memberships for the current logged in user:

apply_filters( ‘mbr/contact/membership_levels’, false );

Based on that information, what should I put in my .php file? :joy: Sorry I’m not a talented developer…

1 Like

Without actually installing the plugin, it’s going to be hard for me to come up with the right code. There is an easier approach that would probably work for you. Can you find out from the plugin’s developers how to access a user’s membership levels? If we can figure that out, then users can be added and removed from groups during the normal SSO login process.

1 Like

Yes I can add as many membership levels as I want from here :

1 Like

Great! What you need to find out is how to access the memberships for a user from either the WordPress user object, or from the user_id for a user. With that information, it will be quite easy to have them added to, and removed from, the appropriate Discourse groups when they login through SSO.

The plugin add a tag that links to the user account of ActiveCampaign. I can change the tag for a specific user in ActiveCampaign but not from the user section in Wordpress.

Hello everyone, I come for help, I’m a little stuck with this group management

I get that the code is incomplete but it should be enough to add users into the Discourse groups, I wanted to test it a little bit before adding other hooks if needed. So far, nothing is syncing, here is my config (sorry, it’s in french in the screenshots)

Paid Membership Pro


Groups in Discourse

So, id1 in PMPro should sync with Discourse group “gratuit”, id2 with “abonnes” and id3 with “entreprises”

Part of the code that I needed to edit (the rest don’t need any change if i’m correct)

// Allows you to call the wp-discourse functions without having to write the namespace each time you use them.
use WPDiscourse\Utilities\Utilities as DiscourseUtilities;

// Returns the Discourse group name that is associated with a membership id. With
// PaidMembershipsPro, you can see the membership id on the Membership Levels page. With other
// membership plugins, you may need to call a function to get access to a `membership_id/membership_name` array.
function dcpmp_get_level_for_id( $id ) {
    $levels_to_discourse_groups = array(
        1 => 'gratuit',
        2 => 'abonnes',
        3 => 'entreprises'
    );

(...) 

I didn’t do any mistake I guess

I have a few (dumb) questions to be sure :

  1. I have a edit the functions.php file located in wp-includes\ and insert the code in the end of the file, right ?

  2. For the discourse groups, is it the name (all lowercase) or the Full Name we have to use in the functions.php ?
    I’ll use an example, for the id2 <=> abonnes, here is the Discourse group config :

image
I’m supposed to use abonnes instead of Abonnés, right ?

  1. Do I need to do something specific to trigger the syncing ? I tried to create a new member in the paid membership pro, logout/login to Discourse, without success

  2. Do I need to configure a webhook in the WP DIscourse plugin ? I was thinking of Update Userdata, but when I tried it, nothing happened

I tried to create two news groups, walking and running to follow the example, I didn’t work either so, it probably comes from one stupid mistake on my part. If anyone have a clue, I’ll take it.

Anyway, the wordpress plugin is really impressive, even with little knowledge on wordpress it’s really easy to configure :clap:

No, the file you need to edit is the functions.php file that is in the theme that is activated on your site.

Another way to do it is to create a small WordPress plugin with the code. That way it doesn’t depend on your theme.

You get the group name from the Name field, not the Full Name field.

The syncing is triggered by this part of the code:

if ( class_exists( '\WPDiscourse\Discourse\Discourse' ) ) {
    add_action( 'pmpro_added_order', 'dcpmp_add_member_to_group' );
    add_action( 'pmpro_after_change_membership_level', 'dcpmp_remove_member_from_group', 10, 3 );
}

No, this doesn’t depend on webhooks.

The code that’s supplied in this topic is intended to be a starting point for developers. If you’re not familiar with WordPress development, you may need to hire someone to do the work for you. If you’re going to do the work yourself, I would recommend setting up a local WordPress development environment, so that you can work on the code without risking breaking your website.

5 Likes

Thanks for the complete answers, I may be able to succeed with these

Hi,

We would like to integrate our LearnDash (which have several courses) with Discourse Forum (with subdomain).

So when someone purchase and have access to a Learndash course (for example Course A), it will automatically get access to Forum Course A in our Forum.

We already setup Group Permission to Access Specific Category, as we setup each course have one forum categories.

So the next challenge is, how to make integration so when a person purchase course A in Learndash, will automatically granted Discourse Group Permission course A.

Based on explanation in this forum, that every membership plugin will call functions when add or remove access level, we ask Learndash about it.

Here are their answers (we are not so sure, if it is what we are looking for) :slight_smile:


You can use the following to enroll a user into a course:

ld_update_course_access( $user_id, $course_id, $remove );

The function take two required parameters, $user_id and $course_id. The third parameter will default if set to true will remove the user from the course.

And the following action is for WHEN a user enrolls into a course:

/**
 * Run actions after a users list of courses is updated
 * 
 * @since 2.1.0
 * 
 * @param  int  	$user_id 		
 * @param  int  	$course_id
 * @param  array  	$access_list
 * @param  bool  	$remove
 */
do_action( 'learndash_update_course_access', $user_id, $course_id, $access_list, $remove );

// Some comments here
public String getFoo()
{
    return foo;
}

Let me know if that helps!


Is the answer above are the functions that we need to integrate with Discourse?

If yes, how to integrate Discourse Function and Learndash function here?

Thanks a lot.

Does anyone here know of a more OOTB solution that could be used to sync WP user groups/permissions to Discourse groups and permissions?

I’ve read a handful of posts from @simon where he talks about a plugin that he created at one point, but is no longer being supported. I’m at the stage where I’m researching which WP tool I want to use to manage memberships, but I want to consider which would be the easiest to integrate with Discourse to create this seamless permission sync between my WP and Discourse sites.

See WP Discourse plugin installation and setup. It will allow you to use your WordPress as SSO master (or the other way around).

It’s well supported and works well.

3 Likes