WooCommerce Memberships mit Discourse-Gruppen synchronisieren

Dieses Plugin ermöglicht die Synchronisierung von WooCommerce-Mitgliedschaften mit Discourse-Gruppen.

Voraussetzungen

Damit dies funktioniert, muss DiscourseConnect aktiviert sein, wobei entweder Wordpress oder Discourse als DiscourseConnect-Anbieter fungiert.

Schritte

  1. Installieren Sie dieses Wordpress-Plugin: GitHub - paviliondev/discourse-woocommerce · GitHub.

  2. Verwenden Sie den Wordpress-Theme-Editor, um diese Werte an die gewünschte WooCommerce plan_id sowie die Discourse group_id und group_name anzupassen, die Sie synchronisieren möchten:

    $member_group_map[] = (object) array('plan_id' => 51, 'group_id' => 43, 'group_name' => 'group1');
    $member_group_map[] = (object) array('plan_id' => 62, 'group_id' => 44, 'group_name' => 'group2');
    

    Sie können so viele Einträge hinzufügen, wie Sie möchten (oder vorhandene entfernen).

Optionaler Schritt

  1. Installieren Sie WP Crontrol – WordPress plugin | WordPress.org und planen Sie run_full_wc_membership_sync so ein, dass es alle 24 Stunden ausgeführt wird. Dies führt eine vollständige Synchronisierung durch, um die Konsistenz sicherzustellen.

Technische Hinweise

Der einzige zuverlässige Weg, sowohl die Erstellung als auch die Statusänderung einer Mitgliedschaft in WooCommerce zu erfassen, ist die Verwendung des Admin-Hooks wc_memberships_user_membership_saved. Sie können dies in Kombination mit dem Hook wc_memberships_user_membership_status_changed verwenden, dies sollte jedoch nicht notwendig sein.

Dieser Code verwendet das WooCommerce-Logger-System, um Informationen in /wp-content/uploads/wc-logs/{log_file} zu protokollieren. Falls es nicht funktioniert, überprüfen Sie die Ausgabe dort. Sie werden Logs sehen, die wie folgt aussehen und Ihnen die Phasen der Logik aufzeigen:

2019-05-23T07:01:57+00:00 INFO Running handle_wc_membership_saved 1, 92, 1
2019-05-23T07:01:57+00:00 INFO angus@email.com membership of VIP Membership changed to wcm-active
2019-05-23T07:01:57+00:00 INFO Sending PUT request to http://localhost:3000/groups/41/members with angus@email.com
2019-05-23T07:01:57+00:00 INFO Response from Discourse: 200 OK

Verwendungshinweise

:point_right: Die Synchronisierung funktioniert nicht, wenn Sie im Wordpress-Adminbereich auf „Benutzer-Mitgliedschaft löschen“ klicken. Es gibt keinen Hook in WooCommerce, der beim Klicken darauf ausgelöst wird. Ändern Sie stattdessen den Status der Mitgliedschaft.

19 „Gefällt mir“

Thanks, @angus. Funny that it seems we were both doing this at the same time.

You managed to figure out what was in those $args. I couldn’t find the user_id in it, I think.

1 „Gefällt mir“

It looks correct to me. Since the site is using WordPress as the SSO provider, you could use the static add_user_to_discourse_group and remove_user_from_discourse_group functions to handle changing the group status on Discourse. That would let you do something like:

	if ( in_array( $status, ACTIVE_STATUSES ) ) {
		DiscourseUtilities::add_user_to_discourse_group( $user_id, 'your_discourse_group_name' );
	} else {
		DiscourseUtilities::remove_user_from_discourse_group( $user_id, 'your_discourse_group_name' );
	}

Both of those functions take the user’s WordPress ID as the first argument, and a comma separated list of group names (with no spaces between the names) as the second argument. Those functions will take care of all the API credentials for you, so using them would simplify the code a bit. There are more details about those functions here: Manage group membership in Discourse with WP Discourse SSO.

4 „Gefällt mir“

In this case Discourse is the SSO Provider :slight_smile:

2 „Gefällt mir“

Just an update here.

The updated version of this integration has the following structure:

  1. Sync on every WooCommerce Membership status change

  2. Batch sync every relevant WooCommerce Member every 24 hours to ensure consistency.

  3. In terms of matching wordpress users with discourse users, the code first checks if the wordpress user has a discourse account associated with it. If so, it uses the discourse user id. If not it attempts the sync using the wordpress user’s email. The sync can fail if

    3.1 the user has never associated their wordpress and discourse accounts; and
    3.2 they have used different emails on wordpress and discourse.

We’re successfully running this in production on a client’s site. So far we’ve had only 2 users who have failed to sync, i.e. where both 3.1 and 3.2 were true.

Implementation

The updated implementation is

  1. Functions.php methods (update MEMBERSHIP_PLAN_ID and DISCOURSE_GROUP_ID)
Functions.php

use WPDiscourse\Utilities\Utilities as DiscourseUtilities;

const MEMBERSHIP_PLAN_ID = 61128;
const DISCOURSE_GROUP_ID = 62;
const ACTIVE_STATUSES = array(‘wcm-active’);

function update_discourse_group_access($user_id, $membership_plan_id, $membership_plan_name, $status) {
$options = DiscourseUtilities::get_options();
$base_url = $options[‘url’];
$api_key = $options[‘api-key’];
$api_username = $options[‘publish-username’];

if ( empty( $base_url ) || empty( $api_key ) || empty( $api_username ) ) {
  return new \WP_Error( 'discourse_configuration_error', 'The WP Discourse plugin has not been properly configured.' );
}

$discourse_user_id = get_user_meta($user_id, 'discourse_sso_user_id', true);
$user_info = get_userdata($user_id);
$user_email = $user_info->user_email;
$logger = wc_get_logger();

$logger->info( sprintf('%s membership of %s is %s' , $user_email, $membership_plan_name, $status ) );

if (in_array($status, ACTIVE_STATUSES)) {
	$action = 'PUT';
} else {
	$action = 'DELETE';
}

$external_url = esc_url_raw( $base_url . "/groups/". DISCOURSE_GROUP_ID ."/members" );

$args = array(
‘api_key’ => $api_key,
‘api_username’ => $api_username
);

if ($discourse_user_id) {
$args[‘user_id’] = $discourse_user_id;
} else {
$args[‘user_emails’] = $user_email;
}

$logger->info( sprintf(‘Sending %s request to %s with %s’, $action, $external_url, http_build_query($args)) );

$external_url = add_query_arg($args, $external_url);

$response = wp_remote_request($external_url,
 array(
	 'method' => $action
 )
);

$logger->info( sprintf( 'Response from Discourse: %s %s' ,
    wp_remote_retrieve_response_code($response),
    wp_remote_retrieve_response_message($response) ) );

if ( ! DiscourseUtilities::validate( $response ) ) {

	return new \WP_Error( 'discourse_response_error', 'There has been an error in retrieving the user data from Discourse.' );
}

};

function handle_wc_membership_saved($membership_plan, $args) {
$logger = wc_get_logger();

$logger->info( sprintf('Running handle_wc_membership_saved %s, %s, %s', $args['user_id'], $args['user_membership_id'], $args['is_update'] ) );

$user_id = $args['user_id'];
$membership = wc_memberships_get_user_membership($args['user_membership_id']);
$membership_plan_id = $membership->plan->id;

if ($membership && $membership_plan_id == MEMBERSHIP_PLAN_ID) {
	$membership_plan_name = $membership_plan->name;
	$status = $membership->status;
	update_discourse_group_access($user_id, $membership_plan_id, $membership_plan_name, $status);
}

};

add_action(‘wc_memberships_user_membership_saved’, ‘handle_wc_membership_saved’, 10, 2);

function full_wc_membership_sync() {
$allusers = get_users();
$logger = wc_get_logger();

$logger->info( sprintf('Running full_wc_membership_sync') );

foreach ( $allusers as $user ) {
	$user_id = $user->id;
	$membership = wc_memberships_get_user_membership($user_id, MEMBERSHIP_PLAN_ID);
	$membership_plan_id = $membership->plan->id;

	$logger->info( sprintf('Checking membership of %s', $user->user_login) );

	if ($membership && $membership_plan_id === MEMBERSHIP_PLAN_ID) {
		$membership_plan_name = $membership->plan->name;
		$status = $membership->status;
		$logger->info( sprintf('Updating group access of %s', $user->user_login) );

		update_discourse_group_access($user_id, $membership_plan_id, $membership_plan_name, $status);

		$logger->info( sprintf('Sleeping for 5 seconds') );
		sleep(5);
	}
}

}

add_action(‘run_full_wc_membership_sync’, ‘full_wc_membership_sync’);

  1. Install WP Crontrol – WordPress plugin | WordPress.org and schedule “run_full_wc_membership_sync” to run every 24 hours.

You can review the logs of the sync in WooCommerce > Status > Logs.

@DNSTARS @pfaffman

8 „Gefällt mir“

What a baller, sending some magical internet money your way in thanks. :+1:

6 „Gefällt mir“

Much appreciated! Thank you @DNSTARS

1 „Gefällt mir“

Welche Änderungen sollte ich in meinem Fall vornehmen? Ich habe eine etwas andere Situation. Hier sind die Unterschiede:

  1. Ich verwende WordPress als SSO-Client, und Benutzer melden sich über Discourse an.
  2. Ich habe das WP-Plugin „WooCommerce Groups

Ich habe bemerkt, dass die Funktion update_discourse_group_access die API-Authentifizierungsdaten zur URL hinzufügt. Das wird in keiner aktuellen Version von Discourse funktionieren. Hast du eine aktualisierte Version des Codes, die eine Header-basierte Authentifizierung verwendet? Falls nicht, kann ich den Code bearbeiten, werde meine Änderungen jedoch nicht testen können, was ein gewisses Risiko birgt.

Ich habe eine saubere Discourse- und WordPress-Installation und kann deinen Code ohne jegliches Risiko testen.

2 „Gefällt mir“

Hier ist Angus’ Code, aktualisiert für die Header-basierte Authentifizierung. Ich habe keine weiteren Änderungen daran vorgenommen. Ich habe den Code auch nicht getestet. Ich werde den ursprünglichen Beitrag erst aktualisieren, wenn jemand Gelegenheit hat, ihn zu testen.

use WPDiscourse\Utilities\Utilities as DiscourseUtilities;

const MEMBERSHIP_PLAN_ID = 35;
const DISCOURSE_GROUP_ID = 41;
const ACTIVE_STATUSES    = array( 'wcm-active' );

function update_discourse_group_access( $user_id, $membership_plan_id, $membership_plan_name, $status ) {
	$options      = DiscourseUtilities::get_options();
	$base_url     = $options['url'];
	$api_key      = $options['api-key'];
	$api_username = $options['publish-username'];

	if ( empty( $base_url ) || empty( $api_key ) || empty( $api_username ) ) {
		return new \WP_Error( 'discourse_configuration_error', 'Das WP Discourse-Plugin wurde nicht ordnungsgemäß konfiguriert.' );
	}

	$user_info         = get_userdata( $user_id );
	$user_email        = $user_info->user_email;
	$logger            = wc_get_logger();

	$logger->info( sprintf( '%s Mitgliedschaft von %s geändert auf %s', $user_email, $membership_plan_name, $status ) );

	if ( in_array( $status, ACTIVE_STATUSES ) ) {
		$action = 'PUT';
	} else {
		$action = 'DELETE';
	}

	$external_url = esc_url_raw( $base_url . "/groups/" . DISCOURSE_GROUP_ID . "/members" );

	$logger->info( sprintf( 'Sende %s-Anfrage an %s mit %s', $action, $external_url, $user_email ) );

	$response = wp_remote_request( $external_url,
		array(
			'method'  => $action,
			'headers' => array(
				'Api-Key'      => sanitize_key( $api_key ),
				'Api-Username' => sanitize_text_field( $api_username ),
			),
			'body'    => array( 'user_emails' => $user_email ),
		)
	);

	$logger->info( sprintf( 'Antwort von Discourse: %s %s',
		wp_remote_retrieve_response_code( $response ),
		wp_remote_retrieve_response_message( $response ) ) );

	if ( ! DiscourseUtilities::validate( $response ) ) {

		return new \WP_Error( 'discourse_response_error', 'Beim Abrufen der Benutzerdaten von Discourse ist ein Fehler aufgetreten.' );
	}
}

function handle_wc_membership_saved( $membership_plan, $args ) {
	$logger = wc_get_logger();

	$logger->info( sprintf( 'Führe handle_wc_membership_saved aus: %s, %s, %s', $args['user_id'], $args['user_membership_id'], $args['is_update'] ) );

	$user_id            = $args['user_id'];
	$membership         = wc_memberships_get_user_membership( $args['user_membership_id'] );
	$membership_plan_id = $membership->plan->id;

	if ( $membership && $membership_plan_id == MEMBERSHIP_PLAN_ID ) {
		$membership_plan_name = $membership_plan->name;
		$status               = $membership->status;
		update_discourse_group_access( $user_id, $membership_plan_id, $membership_plan_name, $status );
	}
}
add_action( 'wc_memberships_user_membership_saved', 'handle_wc_membership_saved', 10, 2 );

Der Code setzt eine Variable $discourse_user_id, die jedoch nirgendwo verwendet wird. Sie könnte wahrscheinlich aus dem Code entfernt werden.

Da bin ich mir nicht so sicher. Der Code hookt in den wc_memberships_user_membership_saved-Action-Hook ein. Es ist wahrscheinlich, dass dieser Hook vom WooCommerce Memberships-Plugin hinzugefügt wird. Im WooCommerce Groups-Plugin gibt es wahrscheinlich einen ähnlichen Hook, aber er hat wahrscheinlich nicht denselben Namen und dieselben Parameter.

3 „Gefällt mir“

Das Plugin „Groups for WooCommerce

Zur Vereinfachung habe ich zu WooCommerce Memberships gewechselt und den Originalcode plus die von @simon vorgenommenen Änderungen verwendet.

Ich habe die IDs an meine Daten angepasst:

const MEMBERSHIP_PLAN_ID = 347;
const DISCOURSE_GROUP_ID = 42;

Derzeit kann ich nicht synchronisieren, erhalte aber einige Informationen im Log:

> 2020-05-14T13:12:34+00:00 INFO test@gmail.com membership of PAID1 changed to wcm-active
> 2020-05-14T13:12:34+00:00 INFO Sending PUT request to https://site/groups/42/members with test@gmail.com
> 2020-05-14T13:12:34+00:00 INFO Response from Discourse: 400 Bad Request

Die ID für die Discourse-Gruppe habe ich hier gefunden:
site/groups/PAID1.json

> {"group":{"id":42,"automatic":false,"name":"PAID1","user_count":0,"mentionable_level":0,"messageable_level":0,"visibility_level":0,"automatic_membership_email_domains":"","primary_group":false,"title":"","grant_trust_level":null,"incoming_email":null,"has_messages":false,"flair_url":"","flair_bg_color":"","flair_color":"","bio_raw":"","bio_cooked":null,"bio_excerpt":null,"public_admission":false,"public_exit":false,"allow_membership_requests":false,"full_name":"France-PAID1","default_notification_level":3,"membership_request_template":"","is_group_user":false,"is_group_owner":true,"members_visibility_level":0,"can_see_members":true,"publish_read_state":false,"is_group_owner_display":false,"mentionable":true,"messageable":false},"extras":{"visible_group_names":["admins","moderators","PAID1","PAID2","PAID3","staff","trust_level_0","trust_level_1","trust_level_2","trust_level_3","trust_level_4"]}}
2 „Gefällt mir“

Es sind also noch 2 Fragen offen:

  1. Wie synchronisiert man fehlerfrei?
  2. Was passiert, wenn ich in WordPress mehr als 3 Mitgliedschaftstypen und in Discourse mehr als 3 Gruppen habe? Dieser Code ist nur für einen Typ ausgelegt.

Das sieht nach einem Fehler in meinem Update des Codes aus. Ich werde mich später heute darum kümmern.

2 „Gefällt mir“

Ich habe das Code-Beispiel hier aktualisiert: Sync WooCommerce Memberships with Discourse groups - #11 by simon. In dem Code, den ich gestern gepostet habe, war ein Codierungsfehler enthalten. Ich habe getestet, dass der API-Aufruf im Code korrekt funktioniert, habe den Code jedoch nicht mit dem WooCommerce-Mitgliedschafts-Plugin getestet.

Das sollte möglich sein. Es ist wahrscheinlich, dass jemand hier helfen kann, aber Sie haben vielleicht mehr Glück, wenn Sie jemanden beauftragen, die Arbeit zu erledigen. Sie könnten versuchen, ein Thema in unserer #marketplace-Kategorie zu erstellen, um einen Entwickler für die Aufgabe zu finden.

2 „Gefällt mir“

@simon, das hat funktioniert! Danke an das Team! :vulcan_salute:

1 „Gefällt mir“

[quote=“angus, Beitrag:5, Thema:118485”]
Installiere WP Crontrol – WordPress-Plugin | WordPress.org und plane „run_full_wc_membership_sync

Gelöst. Vielleicht hilft es jemandem. Ich musste im Feld „Nächster Lauf“ die Option „Bei“ wählen und dort ein Datum wie 2020-05-10 10:00:00 eingeben.

Synchronisierung getestet – jetzt funktioniert es.

Hier ist eine funktionierende Lösung, wenn Sie mehrere WooCommerce-Mitgliedschaftspläne und -gruppen in Discourse haben. Sie müssen Ihre Mitgliedschafts-IDs und Discourse-Gruppen-IDs durch Ihre eigenen ersetzen.

  1. WooCommerce-Mitgliedschaftsplan-IDs finden Sie in der URL, während Sie den Plan in WordPress bearbeiten: https://site/wp-admin/post.php?post=**441**&action=edit
  2. Discourse-Gruppen-IDs finden Sie hier: https://site/groups/group_name.json

Funktionsfähiger Code zum Einfügen in functions.php:

Zusammenfassung

//wp+discourse
use WPDiscourse\Utilities\Utilities as DiscourseUtilities;

const MEMBERSHIP_PLAN_DISCOURSE_GROUP = [
“347” => “42”,
“357” => “43”,
“441” => “44”
];

//const ACTIVE_STATUSES = array( ‘wcm-active’ );
const ACTIVE_STATUSES = array( ‘wcm-active’, ‘wcm-free_trial’ );

function update_discourse_group_access( $user_id, $membership_plan_id, $membership_plan_name, $status ) {
$options = DiscourseUtilities::get_options();
$base_url = $options[‘url’];
$api_key = $options[‘api-key’];
$api_username = $options[‘publish-username’];

if ( empty( $base_url ) || empty( $api_key ) || empty( $api_username ) ) {
	return new \WP_Error( 'discourse_configuration_error', 'Das WP Discourse-Plugin wurde nicht ordnungsgemäß konfiguriert.' );
}

$user_info         = get_userdata( $user_id );
$user_email        = $user_info->user_email;
$logger            = wc_get_logger();

$logger->info( sprintf( '%s Mitgliedschaft von %s geändert in %s', $user_email, $membership_plan_name, $status ) );

if ( in_array( $status, ACTIVE_STATUSES ) ) {
	$action = 'PUT';
} else {
	$action = 'DELETE';
}

$external_url = esc_url_raw( $base_url . "/groups/" . MEMBERSHIP_PLAN_DISCOURSE_GROUP[$membership_plan_id] . "/members" );

$logger->info( sprintf( 'Sende %s-Anfrage an %s mit %s', $action, $external_url, $user_email ) );

$response = wp_remote_request( $external_url,
	array(
		'method'  => $action,
		'headers' => array(
			'Api-Key'      => sanitize_key( $api_key ),
			'Api-Username' => sanitize_text_field( $api_username ),
		),
		'body'    => array( 'user_emails' => $user_email ),
	)
);

$logger->info( sprintf( 'Antwort von Discourse: %s %s',
	wp_remote_retrieve_response_code( $response ),
	wp_remote_retrieve_response_message( $response ) ) );

if ( ! DiscourseUtilities::validate( $response ) ) {

	return new \WP_Error( 'discourse_response_error', 'Beim Abrufen der Benutzerdaten von Discourse ist ein Fehler aufgetreten.' );
}

}

function handle_wc_membership_saved( $membership_plan, $args ) {
$logger = wc_get_logger();

$logger->info( sprintf( 'Führe handle_wc_membership_saved aus %s, %s, %s', $args['user_id'], $args['user_membership_id'], $args['is_update'] ) );

$user_id            = $args['user_id'];

$membership         = wc_memberships_get_user_membership( $args['user_membership_id'] );

$membership_plan_id = $membership->plan->id;

if ( $membership && isset(MEMBERSHIP_PLAN_DISCOURSE_GROUP[$membership_plan_id])) {
	$membership_plan_name = $membership_plan->name;
	$status               = $membership->status;
	update_discourse_group_access( $user_id, $membership_plan_id, $membership_plan_name, $status );
}

}
add_action( ‘wc_memberships_user_membership_saved’, ‘handle_wc_membership_saved’, 10, 2 );

/run_full_wc_membership_sync/
function full_wc_membership_sync() {
$allusers = get_users();
$logger = wc_get_logger();

$logger->info( sprintf('Führe full_wc_membership_sync aus') );

foreach ( $allusers as $user ) {

   $user_id = $user->id;

   $membership = wc_memberships_get_user_membership($user_id);

   $membership_plan_id = $membership->plan->id;

   $logger->info( sprintf('Prüfe Mitgliedschaft von %s', $user->user_login) );

   if ($membership  && isset(MEMBERSHIP_PLAN_DISCOURSE_GROUP[$membership_plan_id])) {
       
	  $membership_plan_name = $membership->plan->name;
      
	  $status = $membership->status;
      
	  $logger->info( sprintf('Aktualisiere Gruppenzugriff von %s', $user->user_login) );

	  update_discourse_group_access($user_id, $membership_plan_id, $membership_plan_name, $status);

	  $logger->info( sprintf('Warte 5 Sekunden') );
      
	  sleep(5);
   }
}

}

add_action(‘run_full_wc_membership_sync’, ‘full_wc_membership_sync’);

Sie müssen in der ersten Spalte die Mitgliedschaftsplan-IDs und in der zweiten Spalte die Discourse-Gruppen-IDs ändern.

Zusammenfassung
                                    "347"  => "42",
                                    "357"  => "43",
                                    "441"  => "44"
7 „Gefällt mir“

Hey @Ed_Bobkov, ich habe gehofft, dass du mir dabei helfen könntest. Hier sind einige Aussagen zu meinem aktuellen Stand:

  1. Ich habe Wordpress als SSO-Client; Benutzer können sich über Discourse oder Wordpress anmelden.
  2. Ich verwende derzeit kein „Mitgliedschaften“-Plugin in Wordpress.
  3. Ich werde Dutzende von Gruppen in Discourse für jede Art von Mitglied haben.
  4. Ich plante, manuell eine neue Mitgliedschaftsgruppe in WP und eine entsprechende Gruppe in Discourse zu erstellen, sobald eine neue Mitgliedschaftsgruppe für unsere Benutzer verfügbar ist.
  5. Wie du bereits erwähnt hast: „Ich muss diese Integration zwischen WP und Discourse aktivieren.“
  6. Ich vermute, ich brauche das Plugin „WooCommerce Memberships“?

Was sind meine nächsten Schritte?