Sincronizza WooCommerce Memberships con i gruppi Discourse

Questo plugin ti permette di sincronizzare WooCommerce Memberships con i gruppi di Discourse.

Prerequisiti

Affinché funzioni, devi avere DiscourseConnect abilitato, con WordPress o Discourse come provider di DiscourseConnect.

Passaggi

  1. Installa questo plugin WordPress GitHub - paviliondev/discourse-woocommerce · GitHub.

  2. Usa l’editor dei temi di WordPress per aggiornare questi numeri con l’plan_id di WooCommerce e l’id e il group_name del gruppo di Discourse che desideri sincronizzare:

    $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');
    

    Puoi aggiungere tante voci quante ne desideri (o rimuoverne di esistenti).

Passo opzionale

  1. Installa WP Crontrol – Plugin WordPress | WordPress.org e programma run_full_wc_membership_sync per eseguire ogni 24 ore. Questo eseguirà una sincronizzazione completa per garantire la coerenza.

Note tecniche

L’unico modo affidabile per catturare sia la creazione della membership che le modifiche allo stato in WooCommerce è utilizzare il hook amministrativo wc_memberships_user_membership_saved. Puoi usarlo in combinazione con il hook wc_memberships_user_membership_status_changed, ma non dovrebbe essere necessario.

Questo codice utilizza il logger di WooCommerce per registrare le informazioni in /wp-content/uploads/wc-logs/{log_file}. Se non funziona, controlla l’output lì. Vedrai log simili a questo, che mostrano le fasi della logica:

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

Note sull’uso

:point_right: La sincronizzazione non funzionerà se fai clic su “Elimina membership utente” nel pannello di amministrazione di WordPress. Non esiste un hook in WooCommerce che si attivi quando questo viene cliccato. Modifica invece lo stato della membership.

19 Mi Piace

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 Mi Piace

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 Mi Piace

In this case Discourse is the SSO Provider :slight_smile:

2 Mi Piace

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 Mi Piace

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

6 Mi Piace

Much appreciated! Thank you @DNSTARS

1 Mi Piace

Quali modifiche dovrei apportare nel mio caso? La mia situazione è leggermente diversa. Ecco le differenze:

  1. Ho Wordpress come client SSO, gli utenti accedono tramite Discourse.
  2. Ho installato il plugin WP ‘WooCommerce Groups’: Groups WooCommerce Documentation - WooCommerce. È simile a WooCommerce Memberships ma con meno funzioni. Penso che tutte le funzioni principali siano le stesse.
  3. Ho più gruppi in Discourse (più di 3) e per questo motivo ho 3 gruppi di accesso in WP.

Potete fornirmi delle correzioni per questo codice o qualche consiglio? Grazie!
Forse potete aiutarmi? @pfaffman @simon

Ah, non ho scritto una domanda lì…
Quindi ho bisogno di attivare questa integrazione tra WP e Discourse.

Ciò che funziona:

  1. WP funziona come client SSO.
  2. Le membership vengono vendute tramite il plugin WP Groups e, quando scadono, gli utenti vengono aggiunti ai gruppi necessari.
  3. Gli utenti vengono rimossi dai gruppi alla scadenza.

Ho bisogno di:

  • quando un utente viene aggiunto a un gruppo in WP, aggiungerlo al gruppo specifico in Discourse
  • quando viene rimosso da quel gruppo in WP, rimuoverlo anche in Discourse

Più gruppi in WP e Discourse

Ho notato che la funzione update_discourse_group_access sta aggiungendo le credenziali di autenticazione API all’URL. Questo non funzionerà su nessuna versione aggiornata di Discourse. Hai una versione aggiornata del codice che utilizza l’autenticazione basata sugli header? In caso contrario, posso modificare il codice, ma non potrò testare le mie modifiche, quindi è un po’ rischioso.

Ho dei siti Discourse e WordPress puliti, posso testare il tuo codice senza alcun rischio.

2 Mi Piace

Ecco il codice di Angus, aggiornato per utilizzare l’autenticazione basata sugli header. Non ho apportato altre modifiche. Inoltre, non ho testato il codice. Non aggiornerò l’OP finché qualcuno non avrà la possibilità di testarlo.

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', 'Il plugin WP Discourse non è stato configurato correttamente.' );
	}

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

	$logger->info( sprintf( '%s membership of %s changed to %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( 'Sending %s request to %s with %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( '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', 'Si è verificato un errore nel recupero dei dati utente da 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 );

Il codice imposta una variabile $discourse_user_id, ma tale variabile non viene utilizzata in nessun punto. Probabilmente potrebbe essere rimossa dal codice.

Non ne sono così sicuro. Il codice si aggancia all’hook di azione wc_memberships_user_membership_saved. Sembra probabile che tale hook sia aggiunto dal plugin WooCommerce Memberships. Probabilmente esiste un hook simile disponibile nel plugin WooCommerce Groups, ma è improbabile che abbia lo stesso nome e gli stessi parametri.

3 Mi Piace

Il plugin ‘Groups for Woocommerce’ utilizza il plugin ‘WP Group’ per gestire le funzioni dei gruppi; ecco la sua API: Actions | Itthinx Documentation

Per quanto riguarda Groups WooCommerce

Azioni

L’estensione non invoca alcuna azione specifica al momento.

Per semplificare, ho passato a WooCommerce Memberships e uso il codice originale più le modifiche apportate da @simon.

Ho modificato gli ID con i miei dati:

const MEMBERSHIP_PLAN_ID = 347;
const DISCOURSE_GROUP_ID = 42;

A questo punto non riesco a sincronizzare, ma ottengo alcune informazioni nel 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

L’ID del gruppo Discourse l’ho trovato qui:
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 Mi Piace

Quindi ci sono 2 domande aperte:

  1. Come sincronizzare senza errori?
  2. Cosa succede se ho più di 3 tipi di iscrizione in WP e gruppi in Discourse? Questo codice è valido solo per 1 tipo.

Sembra che ciò derivi da un bug nel mio aggiornamento del codice. Lo esaminerò più tardi oggi.

2 Mi Piace

Ho aggiornato l’esempio di codice qui: Sync WooCommerce Memberships with Discourse groups - #11 by simon. C’era un errore di codifica nel codice che ho pubblicato ieri. Ho verificato che la chiamata API nel codice funzioni correttamente, ma non ho testato il codice con il plugin Woocommerce Memberships.

Dovrebbe essere possibile farlo. È probabile che qualcuno possa aiutarti qui, ma potresti avere più fortuna assumendo qualcuno per svolgere il lavoro. Potresti provare a creare un argomento nella nostra categoria Marketplace per trovare uno sviluppatore che esegua il lavoro.

2 Mi Piace

@simon, ce l’ho fatta a farlo funzionare! Grazie, team! :vulcan_salute:

1 Mi Piace

@simon, nel tuo codice non ho trovato la parte utilizzata per la sincronizzazione in batch che @angus ha impiegato.

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

$logger->info( sprintf('Esecuzione di run_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('Verifica dell'iscrizione di %s', $user->user_login) );

	if ($membership && $membership_plan_id === MEMBERSHIP_PLAN_ID) {
		$membership_plan_name = $membership->plan->name;
		$status = $membership->status;
		$logger->info( sprintf('Aggiornamento dell'accesso al gruppo per %s', $user->user_login) );

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

		$logger->info( sprintf('Attesa di 5 secondi') );
		sleep(5);
	}
}

}

add_action('run_full_wc_membership_sync', 'full_wc_membership_sync');  

Dovrei aggiungere questo codice alla fine del tuo se voglio attivare una sincronizzazione quotidiana?

Quando provo ad aggiungere questo evento cron in WP tramite il plugin WordPress Crontrol con il nome dell’hook:

run_full_wc_membership_sync

ottengo un errore:

Cosa potrebbe causarlo?

Risolto. Potrebbe essere utile a qualcuno. Dovevo selezionare ‘At’ nel campo ‘Next Run’ e inserire una data come 2020-05-10 10:00:00.

Ho testato la sincronizzazione: ora funziona.

Ecco una soluzione funzionante se disponete di più piani di membership WooCommerce e gruppi in Discourse. Dovrete sostituire gli ID dei piani di membership e gli ID dei gruppi Discourse con i vostri.

  1. Gli ID dei piani di membership WooCommerce si trovano nell’URL mentre si modifica il piano in WordPress: https://site/wp-admin/post.php?post=**441**&action=edit
  2. Gli ID dei gruppi Discourse si trovano qui: https://site/groups/group_name.json

Codice funzionante da includere in functions.php:

Riepilogo

//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', 'Il plugin WP Discourse non è stato configurato correttamente.' );
}

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

$logger->info( sprintf( 'Membership %s di %s cambiata 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( 'Invio richiesta %s a %s con %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( 'Risposta da 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', 'Si è verificato un errore nel recupero dei dati utente da Discourse.' );
}

}

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

$logger->info( sprintf( 'Esecuzione 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 && 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('Esecuzione 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;

   $logger->info( sprintf('Verifica membership di %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('Aggiornamento accesso al gruppo per %s', $user->user_login) );

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

	  $logger->info( sprintf('Attesa di 5 secondi') );
      
	  sleep(5);
   }
}

}

add_action(‘run_full_wc_membership_sync’, ‘full_wc_membership_sync’);

Dovrete modificare nella prima colonna gli ID dei piani di membership e nella seconda colonna gli ID dei gruppi Discourse.

Riepilogo
                                    "347"  => "42",
                                    "357"  => "43",
                                    "441"  => "44"
7 Mi Piace

Ciao @Ed_Bobkov, speravo potessi aiutarmi con questo. Ecco alcune informazioni sulla mia situazione attuale…

  1. Ho Wordpress configurato come client SSO; gli utenti accedono tramite Discourse o Wordpress.
  2. Attualmente non utilizzo alcun plugin di “membership” su Wordpress.
  3. Avrò dozzine di gruppi in Discourse, uno per ogni tipo di membro.
  4. Avevo intenzione di creare manualmente un nuovo gruppo di membership in WP e il corrispondente gruppo in Discourse ogni volta che sarà disponibile una nuova tipologia di membership per i nostri utenti.
  5. Come hai detto, “Devo attivare questa integrazione tra WP e Discourse.”
  6. Immagino che debba procurarmi il plugin “Woocommerce Memberships”?

Quali sono i prossimi passi da compiere?