WooCommerce Memberships と Discourse グループの同期

このプラグインを使用すると、WooCommerce メンバーシップを Discourse グループと同期できます。

前提条件

この機能を使用するには、DiscourseConnect を有効にする必要があります。DiscourseConnect のプロバイダーとして WordPress または Discourse のいずれかを選択してください。

手順

  1. この WordPress プラグインをインストールします: GitHub - paviliondev/discourse-woocommerce · GitHub

  2. WordPress のテーマエディターを使用して、同期させたい WooCommerce の plan_id、Discourse の group_id、group_name に応じて以下の数値を更新してください。

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

    必要に応じてエントリを自由に追加(または既存のエントリを削除)できます。

オプションの手順

  1. WP Crontrol – WordPress plugin | WordPress.org をインストールし、run_full_wc_membership_sync を 24 時間ごとに実行するようにスケジュールを設定してください。これにより、整合性を確保するための完全な同期が実行されます。

技術的な注記

WooCommerce において、メンバーシップの作成とステータス変更の両方を確実に捕捉する唯一の方法は、wc_memberships_user_membership_saved という管理用フックを使用することです。wc_memberships_user_membership_status_changed フックと組み合わせて使用することも可能ですが、通常は必要ありません。

このコードは WooCommerce のログ機能を使用して、情報を /wp-content/uploads/wc-logs/{log_file} に記録します。動作しない場合は、その出力を確認してください。ロジックの各段階を示す、以下のようなログが表示されます。

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

使用上の注記

:point_right: WordPress の管理画面で「ユーザーメンバーシップを削除」をクリックしても、同期は機能しません。その操作が実行された際に発火する WooCommerce のフックが存在しないためです。代わりに、メンバーシップのステータスを変更してください。

「いいね!」 19

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

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

In this case Discourse is the SSO Provider :slight_smile:

「いいね!」 2

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

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

「いいね!」 6

Much appreciated! Thank you @DNSTARS

「いいね!」 1

私のケースでは、どのような変更を加えるべきでしょうか?私の状況は少し異なります。以下の違いがあります:

  1. WordPress を SSO クライアントとして使用しており、ユーザーは Discourse 経由でログインします。
  2. WP プラグイン「WooCommerce Groups」をインストールしました:Groups WooCommerce Documentation - WooCommerce 。これは WooCommerce Memberships と似ていますが、機能が少し少ないです。コア機能はすべて同じだと考えています。
  3. Discourse には複数のグループ(3 つ以上)があり、それに対応して WordPress 側でも 3 つのアクセスグループを設定しています。

このコードの修正やアドバイスをお願いできますか?ありがとうございます!
もしよろしければ、@pfaffman @simon さんにもご協力いただけますでしょうか。

あ、そこに質問を書いていませんでしたね…
つまり、WordPress と Discourse の間のこの統合を有効にする必要があります。

現在動作していること:

  1. WordPress が SSO クライアントとして機能しています。
  2. WP Groups プラグインを通じてメンバーシップが販売され、有効期限が切れると、必要なグループにユーザーが追加されます。
  3. 有効期限が切れると、グループからユーザーが削除されます。

必要なこと:

  • WordPress でユーザーがグループに追加されたら、Discourse の特定のグループにもそのユーザーを追加する
  • WordPress でそのグループからユーザーが削除されたら、Discourse からも削除する
    WordPress と Discourse での複数グループの対応

update_discourse_group_access 関数が API 認証情報を URL に追加していることに気づきました。これは最新の Discourse バージョンでは機能しません。ヘッダーベースの認証を使用する更新されたコードをお持ちでしょうか?もしなければ、コードを編集することはできますが、私の編集内容をテストできないため、少しリスクがあります。

クリーンな Discourse と WordPress サイトがありますので、リスクなしにコードをお試しください。

「いいね!」 2

これは、ヘッダーベースの認証を使用するように更新された Angus のコードです。それ以外の修正は行っておりません。また、コードのテストも実施していません。誰かがテストする機会を得るまで、元の投稿(OP)は更新しません。

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', 'WP Discourse プラグインが正しく設定されていません。' );
	}

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

	$logger->info( sprintf( '%s の %s に対するメンバーシップが %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( '%s 要求を %s に送信します(ユーザー: %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( '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', 'Discourse からユーザーデータを取得する際にエラーが発生しました。' );
	}
}

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

	$logger->info( sprintf( 'handle_wc_membership_saved を実行中: ユーザー ID=%s, ユーザーメンバーシップ ID=%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 );

このコードでは $discourse_user_id 変数が設定されていますが、どこでも使用されていません。おそらくこの変数はコードから削除できるでしょう。

それは少し違うかもしれません。このコードは wc_memberships_user_membership_saved アクションフックにフックしています。このフックは WooCommerce Memberships プラグインによって追加されている可能性が高いです。WooCommerce Groups プラグインにも同様のフックが存在するかもしれませんが、名前やパラメータが同じである可能性は低いでしょう。

「いいね!」 3

‘Groups for Woocommerce’ プラグインは、グループ機能を実行するために ‘WP Group’ プラグインを使用しています。その API はこちらです:Actions | Itthinx Documentation

また、Groups WooCommerce については

アクション

この拡張機能は現在、特定のアクションを呼び出しません。

簡略化のため、WooCommerce Memberships に切り替え、元のコードと @simon 氏による変更を併用しています。

ID を自分のデータに合わせて変更しました:

const MEMBERSHIP_PLAN_ID = 347;
const DISCOURSE_GROUP_ID = 42;

現時点では同期できませんが、ログに以下の情報が出力されています:

> 2020-05-14T13:12:34+00:00 INFO test@gmail.com のメンバーシップが PAID1 から wcm-active に変更されました
> 2020-05-14T13:12:34+00:00 INFO https://site/groups/42/members に対して test@gmail.com で PUT リクエストを送信中
> 2020-05-14T13:12:34+00:00 INFO Discourse からのレスポンス: 400 Bad Request

Discourse グループの ID は、ここで確認しました:
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

さて、2 つの疑問が残っています:

  1. エラーなく同期するにはどうすればよいですか?
  2. WP に 3 つ以上のメンバーシップタイプと Discourse にそれ以上のグループがある場合はどうなりますか?このコードは 1 タイプのみを対象としています。

これは私のコード更新におけるバグに起因するもののようです。本日中に確認いたします。

「いいね!」 2

コード例を更新しました:https://meta.discourse.org/t/syncing-woocommerce-memberships-with-discourse-groups-when-discourse-is-the-sso-provider/118485/11。昨日投稿したコードにはエンコーディングエラーがありました。コード内の API 呼び出しは正しく機能することを確認しましたが、WooCommerce Memberships プラグインとの組み合わせでの動作はテストしていません。

それを実現することは可能です。ここで誰かがお手伝いできる可能性もありますが、作業を依頼する専門家を見つける方がスムーズかもしれません。開発者を探すために、Marketplace カテゴリでトピックを作成してみてください。

「いいね!」 2

@simon さん、これで動作しました!チームの皆さん、ありがとう!:vulcan_salute:

「いいね!」 1

@simon、あなたのコードには @angus が使用しているバッチ同期用の部分が見つかりませんでした。

/*run_full_wc_membership_sync*/
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');  

毎日同期を有効化したい場合、このコードをあなたのコードの末尾に追加すべきでしょうか?

WP Crontrol プラグインで WP にこの cron イベントを追加しようとした際、フック名として以下を入力しました:

run_full_wc_membership_sync

エラーが発生しました:

何が原因でしょうか?

解決しました。誰かの参考になれば幸いです。「次回実行」フィールドで「At」を選択し、そこに「2020-05-10 10:00:00」のような日付を入力する必要があります。

同期をテストしましたが、現在は正常に動作しています。

Discourse に複数の WooCommerce 会員プランとグループがある場合の動作する解決策を以下に示します。会員 ID と Discourse グループ ID をご自身のものに変更してください。

  1. WooCommerce 会員プランの ID は、Wordpress でプランを編集している間の URL から確認できます: https://site/wp-admin/post.php?post=**441**&action=edit
  2. Discourse グループの ID はこちらで確認できます: https://site/groups/group_name.json

functions.php に含める動作するコード:

概要

//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', 'WP Discourse プラグインが正しく設定されていません。' );
}

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

$logger->info( sprintf( '%s の %s 会員のステータスが %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( '%s の %s への %s リクエストを送信中', $user_email, $external_url, $action ) );

$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( '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', 'Discourse からユーザーデータの取得中にエラーが発生しました。' );
}

}

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

$logger->info( sprintf( '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('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('%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('%s のグループアクセスを更新中', $user->user_login) );

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

	  $logger->info( sprintf('5 秒間待機中') );
      
	  sleep(5);
   }
}

}

add_action(‘run_full_wc_membership_sync’, ‘full_wc_membership_sync’);

最初の列(会員プラン ID)と 2 番目の列(Discourse グループ ID)を変更する必要があります。

概要
                                    "347"  => "42",
                                    "357"  => "43",
                                    "441"  => "44"
「いいね!」 7

@Ed_Bobkov さん、お手伝いいただけますでしょうか。現在の状況についていくつかお伝えします。

  1. WordPress を SSO クライアントとして使用しており、ユーザーは Discourse または WordPress 経由でログインしています。
  2. 現在、WordPress 上で使用している「メンバーシップ」プラグインはありません。
  3. 今後、Discourse にはメンバーの種類ごとに数十のグループを用意する予定です。
  4. 新しいメンバーシップグループがユーザーに利用可能になるたびに、WP で新規のメンバーシップグループを手動で作成し、それに対応するグループを Discourse でも作成する予定でした。
  5. ご指摘の通り、「WP と Discourse の間のこの統合を有効化する必要があります」。
  6. 「WooCommerce Memberships」プラグインが必要なのではないかと推測しています。

次のステップは何でしょうか?