Quantcast
Channel: Discourse Meta - Latest topics
Viewing all 60721 articles
Browse latest View live

Russian localization missing key


Use embed.ly while embedding new links

$
0
0

@Alex_Flom wrote:

http://embed.ly/ is a service which is automatically displaying thumbnails and rich media previews for videos when a new link is posted (in a discussion).
Is there a way to use it on discourse(or something similar)?

Thanks!

Posts: 3

Participants: 3

Read full topic

Add formulas to the editor

Private messages sharedrop

Visual distinction between reply to last comment and reply to original post

$
0
0

@noamraph wrote:

I see replying to the last comment and replying to the original post as two very different things, as I see comments as a hierarchy. Each has its own button. However, I see no visual distinction between those.

How about, for replies to the last comment, adding an arrow pointing upwards with a tooltip saying "This is a reply to the previous comment"? Something like:

(Notice the arrow).

Here's a relevant comment about the difference between the two.

Posts: 5

Participants: 3

Read full topic

Quickly tell someone why they should look at a topic

$
0
0

@watchmanmonitor wrote:

Continuing the discussion from User Invitation/Poking a user to a topic is now live!:

With this feature in place, I would love to be able to include a one-line reason why I'm pointing out a topic to someone.. is that in the cards, or would it be better to have a new "create private message" button that makes the new message with a blank "To" and a link to the topic?

Posts: 4

Participants: 2

Read full topic

Is there a way to unwatch a topic with a link?

$
0
0

@watchmanmonitor wrote:

Continuing the discussion from Is there a way to list all topics that I am tracking or watching?:

I'd like to start using Watched Topics more, but would like to be able to unwatch them easily too.

Is there a URL I could that visit that would unwatch a topic? something like:

Posts: 5

Participants: 4

Read full topic

Watching a Category, perhaps the best kind of mailing list mode?

$
0
0

@watchmanmonitor wrote:

Continuing the discussion from Setting defaults to enable mailing list mode-Temporary Solution:

Anyone following me knows I'm all about mailing list mode. I love it because it keeps community content fresh in the face of our users.

The powers that be at discourse are all about the web-UI, and rightfully so.. it's beautiful, laid out well, easy to search, navigate, bookmark, link to, etc etc etc. It's the best mailing list archive I've ever seen, period.

What occurs to me is that the behavior I want out of discourse is FAR more like auto-watching every topic. Easily able to unwatch or mute a topic which no longer has interest.

Kinda like Jeff does here:

Before I go database diving and scripting ways to have every user auto-watch a forum's important categories.. is there a way to do that in the UI already?

With this available
- auto setting email_always to true
- then allowing people to get copies of all posts they create by email

I truly believe we'll get the best of both worlds. Especially if email footers can have links to unwatch a topic.

Posts: 1

Participants: 1

Read full topic


Table of Contents and Named Anchors?

$
0
0

@Ayer31 wrote:

So previously I have just created seperate posts and then linked with a top post in a thread as a method to table of contents. I am wondering can there be some kind of implementation for wiki and anchor tags. That can be all clumped into one big post. I do create how to's frequently. If something like this exists feel free to move this thread mods and direct me to a source. It doesn't matter how hard it is I want to do it. wink
If the feature doesnt exist well I would like this to be a feature request. I think that would be cool.

Thank You so much everyone.

Posts: 3

Participants: 2

Read full topic

Add plug-in directly in the admin

Flagged/deleted posts can be viewed even for topics where I have no access

$
0
0

@TechnoBear wrote:

As a moderator, I see the "Flagged posts" and "Deleted posts" counts on a member's profile, and can use those to see the posts in question.

But doing this also shows deleted posts originally made in private categories to which I have no access, such as the Admins section. (Clicking on the post gives me the "You do not have permission to view that topic" message. These posts are easy to spot, as no category is shown. AFAIK, I (or any other moderator) would also be able to see deleted PMs.

Posts: 4

Participants: 4

Read full topic

Is it possible to add GA events to my discourse installation?

$
0
0

@christianjohnsen wrote:

Hi guys

I just want to know if there is any possible way to add GA events to my discourse installation?

Thanks a lot for your time!

-Best
Christian

Posts: 6

Participants: 3

Read full topic

Areas needing refactor?

$
0
0

@tylerdiaz wrote:

Discourse community,

I'd like to help refactor some parts of the codebase. Are there any specific areas that should be focused on?

If not, I'll start by focusing on of the following:

Posts: 5

Participants: 3

Read full topic

Message threading in Mail.app

$
0
0

@watchmanmonitor wrote:

Continuing the discussion from Emails are not threaded in Outlook 2013:

I'm seeing the my iPhone will thread emails I get from Meta, however my Mac OS Laptops won't. The Mail.app in 10.8 and 10.9 fail to pick up on the relation.

In looking at the headers of messages, I see

In-Reply-To: <topic/27505@meta.discourse.org>
References: <topic/27505@meta.discourse.org>

and I wonder.. could the / be throwing off the threading? How could I test it with headers showing:

In-Reply-To: <topic27505@meta.discourse.org>
References: <topic27505@meta.discourse.org>

Has that been tried already?

One way to solve this is to put a Re: at the start of every email, but it's a bit awkward when the first post is "re" I don't know if a conditional "Re: " could be added...

Posts: 7

Participants: 5

Read full topic

What makes discourse.org hosting better than discoursehosting.com, if anything?

$
0
0

@slobizman wrote:

I'm looking at hosting options at discourse.org and discoursehosting.com. Not interested in Digital Ocean as I want support.

Hosting at discourse.org is considerably more expensive than at discoursehosting.com. Can someone tell me what I gain by hosting at discourse.org? I assume there is something of value for the extra cost.

Thank you.

Posts: 5

Participants: 4

Read full topic


How could I connect to the postgresql in vagrant

Allowing multiple sites for embedding

$
0
0

@darix wrote:

First of all thank you for discourse! It is amazing. smiley I just finished my RPM packages and helped integrating it with pixls.us. Together with Pat I was looking into the multisite option.

pixls.us currently uses the rss parsing + embedding support shown in @eviltrout 's blog post. In the current design only one site can embed back. One option to fix this would be to have a mapping category->site and allow embedding based on that mapping. In the case of Pat, he thought about sharing his instance with the libre graphics community. For me personally I would love to have discourse as common commenting platform over all opensuse.org sites. The mozilla team was discussing similar things here.

Thoughts?

darix

Posts: 2

Participants: 2

Read full topic

Simple-rss is buggy and should be forked or replaced

$
0
0

@darix wrote:

While working on pixls.us, we were fighting for a quite a while to get the RSS parsing working. It turned out to be a bug on the server side (wrong Content-Type header without encoding) and a bug in simple-rss. With the first chunk from simple-rss PR#17, we finally got rss parsing working. Given the inactivity of upstream I would say either fork it or move to a different parser. I had a proof of concept implementation working with the feed_parser gem.

diff --git a/Gemfile b/Gemfile
index 71286ae..03b5efb 100644
--- a/Gemfile
+++ b/Gemfile
@@ -239,7 +239,7 @@ gem 'rbtrace', require: false, platform: :mri
 #
 gem 'ruby-readability', require: false

-gem 'simple-rss', require: false
+gem 'feed_parser', require: false

 # TODO mri_22 should be here, but bundler was real slow to pick it up
 # not even in production bundler yet, monkey patching it in feels bad
diff --git a/app/jobs/scheduled/poll_feed.rb b/app/jobs/scheduled/poll_feed.rb
index 8c0c8eb..752df2e 100644
--- a/app/jobs/scheduled/poll_feed.rb
+++ b/app/jobs/scheduled/poll_feed.rb
@@ -41,12 +41,12 @@ module Jobs
     end

     class Feed
-      require 'simple-rss'
+      require 'feed_parser'

       if SiteSetting.embed_username_key_from_feed.present?
-        SimpleRSS.item_tags << SiteSetting.embed_username_key_from_feed.to_sym
+       # TODO
+       Rails.logger.warn "SiteSetting.embed_username_key_from_feed is not implemented yet"
       end
-
       def initialize
         @feed_url = SiteSetting.feed_polling_url
         @feed_url = "http://#{@feed_url}" if @feed_url !~ /^https?\:\/\//
@@ -66,7 +66,7 @@ module Jobs
       private

       def rss
-        SimpleRSS.parse open(@feed_url)
+        FeedParser.new(:url => @feed_url).parse
       end

     end
@@ -86,7 +86,11 @@ module Jobs
       end

       def content
-        @article_rss_item.content || @article_rss_item.description
+        if @article_rss_item.content && not(@article_rss_item.content.empty?)
+          return @article_rss_item.content
+        else
+          return @article_rss_item.description
+        end
       end

       def title

The patch is not 100% complete yet as picking the author from the feed part is not implemented yet.

I picked it over feedjira because of the smaller dependency set. From the activity on the project, the best joice might actually be feedjira though.

thoughts?

p.s.: just noticed one bug is even opened by @sam

Posts: 2

Participants: 2

Read full topic

Don't load http images when using https

WordPress Integration Guide

$
0
0

@AdamCapriola wrote:

I've been offering a service to do WordPress integrations, but (a) I'm often unavailable because of other priorities and (b) freelancing this stuff hasn't been lucrative ... so I figured I might as well share my bits of code. Hopefully the community here can benefit from what I've learned working on integrations the past year.

My belief is that there isn't a one-size-fits-all solution for integrations, so what I'm going to do is guide you through creating a custom plugin file with different functions you can add and tweak to fulfill your needs.

I. Create Plugin File

I stick all of my code inside my custom WordPress theme (which you can do too), but for the purposes of this guide we'll use a plugin file to house all of our functions.

Create a file in your favorite text editor with the name discourse-wordpress-integration.php. Begin it with the following and customize the definition values:

<?php
/*
Plugin Name: Discourse WordPress Integration
Description: This plugin contains all of the functions that deal with integrating WordPress and Discourse.
Version: 1.0
Author: Your Name
Author URI: http://example.com
*/

/**
 * Discourse Definitions
 *
 */
define( 'DISCOURSE_URL', 'http://discourse.example.com' ); // No trailing slash!
define( 'DISCOURSE_API_KEY', 'abc123' ); // Your global API key created + found here: http://discourse.example.com/admin/api
define( 'DISCOURSE_API_USERNAME', 'system' ); // Username of an administrator ("system" is a safe bet).
define( 'DISCOURSE_SSO_SECRET', 'super_duper_secret' ); // Your "sso secret" value created + found here: http://discourse.example.com/admin/site_settings/category/login

Then we will add any blocks of code below to the file as needed.

II. SSO Functionality

A. Include Helper Class

We'll begin by adding @ArmedGuy's helper class:

/**
 * Discourse SSO Class
 *
 */
add_action( 'plugins_loaded', 'ac_discourse_sso_class_init' );

function ac_discourse_sso_class_init() {

	if ( ! class_exists( 'Discourse_SSO' ) ) {

		class Discourse_SSO {

			private $sso_secret;

			function __construct( $secret ) {
				$this->sso_secret = $secret;
			}

			public function validate( $payload, $sig ) {
				$payload = urldecode( $payload );
				if ( hash_hmac( "sha256", $payload, $this->sso_secret ) === $sig ) {
					return true;
				} else {
					return false;
				}
			}

			public function getNonce( $payload ) {
				$payload = urldecode( $payload );
				$query = array();
				parse_str( base64_decode( $payload ), $query );
				if ( isset( $query["nonce"] ) ) {
					return $query["nonce"];
				} else {
					throw new Exception( "Nonce not found in payload!" );
				}
			}

			public function buildLoginString( $params ) {
				if ( ! isset($params["external_id"] ) ) {
					throw new Exception( "Missing required parameter 'external_id'" );
				}
				if ( ! isset($params["nonce"] ) ) {
					throw new Exception( "Missing required parameter 'nonce'" );
				}
				if ( ! isset($params["email"] ) ) {
					throw new Exception( "Missing required parameter 'email'" );
				}
				$payload = base64_encode( http_build_query( $params ) );
				$sig = hash_hmac( "sha256", $payload, $this->sso_secret );

				return http_build_query( array( "sso" => $payload, "sig" => $sig ) );
			}

		}

	}

}

B. Process SSO Requests

This function handles requests to log in (and log out) via SSO. There are two values you will need to set in Discourse:

  1. "sso url" = http://example.com/?request=sso
  2. "logout redirect" = http://example.com/?request=logout

/**
 * Process SSO Requests
 *
 */
add_action( 'parse_request', 'ac_parse_request' );

function ac_parse_request() {

	// Check for SSO request
	if ( isset( $_GET['request'] ) && $_GET['request'] == 'sso' ) {

		// Variables
		$sso_secret = DISCOURSE_SSO_SECRET;
		$discourse_url = DISCOURSE_URL;

		//
		// Check if user is logged in to WordPress
		//

		// Not logged in to WordPress, redirect to WordPress login page with redirect back to here
		if ( ! is_user_logged_in() ) {

			// Preserve sso and sig parameters
			$redirect = add_query_arg( '', '' );

			// Change %0A to %0B so it's not stripped out in wp_sanitize_redirect
			$redirect = str_replace( '%0A', '%0B', $redirect );

			// Build login URL
			$login = wp_login_url( $redirect );

			// Redirect to login
			wp_redirect( $login );
			exit;

		}

		// Logged in to WordPress, now try to log in to Discourse with WordPress user information
		else {

			// Payload and signature
			$payload = $_GET['sso'];
			$sig = $_GET['sig'];

			// Change %0B back to %0A
			$payload = urldecode( str_replace( '%0B', '%0A', urlencode( $payload ) ) );

			// Check for helper class
			if ( ! class_exists( 'Discourse_SSO' ) ) {

				// Error message
				echo( 'Helper class is not properly included.' );

				// Terminate
				exit;

			}

			// Validate signature
			$sso = new Discourse_SSO( $sso_secret );

			if ( ! ( $sso->validate( $payload, $sig ) ) ) {

				// Error message
				echo( 'Something went wrong. An administrator has been notified and will look into the issue.' );

				// Notify administrator
				wp_mail( get_option( 'admin_email' ), 'Invalid SSO Request', $current_user->user_login . ' ' . $current_user->user_email );

				// Terminate
				exit;

			}

			// Nonce
			$nonce = $sso->getNonce( $payload );

			// Current user
			$current_user = wp_get_current_user();

			// Map information
			$params = array(
				'nonce' => $nonce,
				'name' => $current_user->display_name,
				'username' => $current_user->user_login,
				'email' => $current_user->user_email,
				'external_id' => $current_user->ID
			);

			// Build login string
			$q = $sso->buildLoginString( $params );

			// Remove fresh login
			delete_user_meta( $current_user->ID, 'fresh_login' );

			// Redirect back to Discourse
			wp_redirect( $discourse_url . '/session/sso_login?' . $q );
			exit;

		}

	}

	// Check for logout request
	if ( isset( $_GET['request'] ) && $_GET['request'] == 'logout' ) {

		wp_logout();

		wp_redirect( $discourse_url );
		exit;

	}

}

Note: In certain spots (like above) I use the wp_mail function to email myself in case of an error cropping up that I may not immediately notice otherwise. You may want to customize this reporting or remove it altogether.

Also, see here for more keys you can maps besides name, username, email, and external_id.

C. Sync WordPress Login to Discourse

When a user logs into WordPress, this code will also log them into Discourse (without the user needing to initiate the Discourse login).

/**
 * Add user meta to signify the user has just logged in
 *
 */
add_action( 'wp_login', 'ac_fresh_login', 10, 2 );

function ac_fresh_login( $user_login, $user ) {

	update_user_meta( $user->ID, 'fresh_login', 1 );

}

/**
 * One-time Discourse SSO iframe
 *
 */
add_action( 'wp_footer', 'ac_discourse_sso_iframe' );
add_action( 'admin_footer', 'ac_discourse_sso_iframe' );

function ac_discourse_sso_iframe() {

	// Only show iframe on first page the user hits after logging in

	$fresh_login = get_user_meta( get_current_user_id(), 'fresh_login', true );

	if ( $fresh_login == 1 ) {

		echo '<iframe src="' . DISCOURSE_URL . '/session/sso" width="0" height="0" tabindex="-1" title="Discourse SSO" style="display:none" hidden>' . "\n";

	}

}

Note: @Grex315 has mentioned having better luck using an embed instead of iframe. The iframe has worked for me so I've kept using it.

Another note: The fresh_login key gets cleared during a successful SSO request before the redirect in the ac_parse_request function.

Also, your WordPress install may need to be located at the root http://example.com rather than http://www.example.com for this to work. I haven't tested the www scenario. (I just have a hunch it may not work -- if someone could confirm either way that would be awesome.)

D. Sync Logouts

This logs the user out of Discourse when they log out of WordPress. The reverse scenario is covered above when setting the "logout redirect" option.

/**
 * Logout Sync
 *
 */
add_action( 'clear_auth_cookie', 'ac_discourse_logout' );

function ac_discourse_logout() {

	// Variables
	$discourse_url = DISCOURSE_URL;
	$api_key = DISCOURSE_API_KEY;
	$api_username = DISCOURSE_API_USERNAME;

	global $current_user;

	//
	// Get Discourse ID
	//

	// URL
	$url = sprintf(
		'%s/users/%s/activity.json',
		$discourse_url,
		$current_user->user_login
	);

	// cURL
	$ch = curl_init();
	curl_setopt( $ch, CURLOPT_URL, $url );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
	$body = curl_exec( $ch );
	curl_close( $ch );

	// Interpret results
	$json = json_decode( $body, true );

	// Check for ID
	if ( ! isset( $json['user']['id'] ) ) {
		return;
	}

	//
	// Log out
	//

	// URL
	$url = sprintf(
		'%s/admin/users/%s/log_out?api_key=%s&api_username=%s',
		$discourse_url,
		$json['user']['id'],
		$api_key,
		$api_username
	);

	// Parameters
	$parameters = array(
		'username_or_email' => $current_user->user_login
	);

	// cURL
	$ch = curl_init();
	curl_setopt( $ch, CURLOPT_URL, $url );
	curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( $parameters ) );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
	curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'POST' );
	$body = curl_exec( $ch );
	curl_close( $ch );

}

Note: I think there is a more direct endpoint to get a user's Discourse ID which might have a slight performance benefit, but this works fine for me.

E. Username Validation

WordPress doesn't place many restrictions on usernames, but Discourse does.

/**
 * Back-end username validation
 *
 */
add_filter( 'registration_errors', 'ac_registration_errors', 10, 3 );

function ac_registration_errors( $errors, $sanitized_user_login, $user_email ) {

	// Username must be 3-20 characters: letters, numbers, underscores only and can't begin with an underscore
	if ( ! preg_match('/^[A-Za-z0-9][A-Za-z0-9_]{2,19}$/', $sanitized_user_login ) ) {
		$errors->add( 'validation_error', '<strong>ERROR</strong>: Username is invalid.' );
	}

	return $errors;

}

This will catch any invalid usernames when you use the native WordPress registration. If you use a different method of registering users, this code may not do anything. Be sure to test your setup and verify that invalid usernames are caught.

As an aside, if you have any previously registered usernames that are invalid, they aren't going to be able to log in to Discourse. You'll need to change them to use legal characters and length. I use
Username Changer.

Also, I check the Discourse option "sso overrides username".

III. Integrations

A. Update Email

This will update the user's email in Discourse when they update it from their WordPress profile page (http://example.com/wp-admin/profile.php).

/**
 * Listen for change in email and sync it with Discourse
 *
 */
add_action( 'personal_options_update', 'ac_discourse_email_listener' );

function ac_discourse_email_listener( $user_id ) {

	$userdata = get_userdata( $user_id );

	if ( $userdata->user_email != $_POST['email'] ) {

		wp_schedule_single_event( time() + 15, 'ac_discourse_email_sync_event', array( $user_id ) ); // delay to ensure sync happens accurately

	}

}

add_action( 'ac_discourse_email_sync_event', 'ac_discourse_email_sync' );

function ac_discourse_email_sync( $user_id ) {

	// Define variables
	$discourse_url = DISCOURSE_URL;
	$api_key = DISCOUSE_API_KEY;
	$api_username = DISCOURSE_API_USERNAME;

	// Get userdata
	$userdata = get_userdata( $user_id );

	// URL
	$url = sprintf(
		'%s/users/%s/preferences/email?api_key=%s&api_username=%s',
		$discourse_url,
		$userdata->user_login,
		$api_key,
		$api_username
	);

	// Parameters
	$paramArray = array(
		'email' => $userdata->user_email
	);

	// cURL
	$ch = curl_init();
	curl_setopt( $ch, CURLOPT_URL, $url );
	curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( $paramArray ) );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
	curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'PUT' );
	$body = curl_exec( $ch );
	curl_close( $ch );

}

Note: You can use other hooks to utilize the function depending on your setup. I personally use a Gravity Form on the front end rather than the default back end WordPress profile page for letting users update their email, so I hook the ac_discourse_email_sync function into there.

Also, you should add this to your custom CSS in Discourse:

.user-preferences .pref-email {
    display: none !important;
}

This isn't a perfect solution, but basically we want to hide the option for a user to change their email on the Discourse side to keep it synchronized. Ideally we would let users change their email in Discourse too and sync it back over to WordPress, but I don't know how to do that.

B. Update Avatar

I rely on Discourse to provide the avatar for my WordPress users because WordPress isn't packaged with an avatar uploader. The value is stored in user meta key discourse_avatar which I can then display in templates.

The way I run my site, there are only a handful of users that have their avatar displayed on the WordPress side, so I don't have this very tightly integrated. Ideally, when a user's avatar is updated in Discourse it would subsequently update in WordPress.

Instead, I hook this function into the SSO request, so each time the user logs in their avatar will be synced.

/**
 * Avatar Sync
 *
 */
add_action( 'ac_discourse_avatar_sync_event', 'ac_discourse_avatar_sync' );

function ac_discourse_avatar_sync( $user_id ) {

	// Define variables
	$discourse_url = DISCOURSE_URL;
	$userdata = get_userdata( $user_id );

	$url = sprintf(
		'%s/users/%s/activity.json',
		$discourse_url,
		$userdata->user_login
	);

	// cURL
	$ch = curl_init();
	curl_setopt( $ch, CURLOPT_URL, $url );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
	$body = curl_exec( $ch );
	curl_close( $ch );

	// Interpret results
	$json = json_decode( $body, true );

	// Update user meta
	if ( isset( $json['user']['avatar_template'] ) ) {

		update_user_meta( $user_id, 'discourse_avatar', $json['user']['avatar_template'] );

	}

}

In the SSO request, add this before the redirect back to Discourse:

// Sync avatars
if ( function_exists( 'ac_discourse_avatar_sync' ) ) {
	ac_discourse_avatar_sync( $current_user->ID );
}

C. Group Sync

This is going to require customization based on how you manage your users in WordPress. Here is the basic code I use:

/**
 * Discourse group sync
 *
 */
add_action( 'ac_discourse_groups_sync_event', 'ac_discourse_groups_sync', 10, 2 ); // For delay requests

function ac_discourse_groups_sync( $user_id, $groups ) {

	//
	// Define variables
	//
	$discourse_url = DISCOURSE_URL;
	$api_key = DISCOURSE_API_KEY;
	$api_username = DISCOURSE_API_USERNAME;

	// Username
	$userdata = get_userdata( $user_id );
	$username = $userdata->user_login;

	// Cycle through groups array
	foreach ( $groups as $group_name ) {

		//
		// Get usernames of all group members
		//

		// URL
		$url = sprintf(
			'%s/groups/%s/members.json?api_key=%s&api_username=%s&limit=9999',
			$discourse_url,
			$group_name,
			$api_key,
			$api_username
		);

		// cURL
		$ch = curl_init();
		curl_setopt( $ch, CURLOPT_URL, $url );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
		$body = curl_exec( $ch );
		curl_close( $ch );

		// Intrepret results
		$json = json_decode( $body, true );

		// Error check for invalid group name
		if ( ! isset( $json['members'] ) ) {
			wp_mail( get_option( 'admin_email' ), 'Group Retrieval Error', $username . ' ' . $group_name );
			continue;
		}

		// Create arrays of usernames and ids
		$usernames = $ids = array();
		if ( count( $json['members'] ) > 0 ) {
			foreach ( $json['members'] as $key => $value ) {
				$usernames[] = $value['username'];
				$ids[ $value['username'] ] = $value['id'];
			}
		}

		//
		// Action determinations
		//

		switch( $group_name ) {

			// Premium
			case 'premium':
			if ( user_can( $user_id, 'premium_member' ) ) {
				$action = 'add';
			}
			else {
				$action = 'remove';
			}
			$group_number = 12; // Found by monitoring network when editing group
			break;

			// Add more cases here

		}

		//
		// Do action
		//

		// Add
		if ( $action == 'add' && ! in_array( $username, $usernames ) ) {

			// Parameters
			$parameters = array(
				'usernames' => $username
			);

			// Request
			$request = 'PUT';

		}

		// Remove
		elseif ( $action == 'remove' && in_array( $username, $usernames ) ) {

			// Parameters
			$parameters = array(
				'user_id' => $ids[ $username ]
			);

			// Request
			$request = 'DELETE';

		}

		// User is already in or not in group
		else {
			continue;
		}

		// URL
		$url = sprintf(
			'%s/admin/groups/%s/members.json?api_key=%s&api_username=%s',
			$discourse_url,
			$group_number,
			$api_key,
			$api_username
		);

		// cURL
		$ch = curl_init();
		curl_setopt( $ch, CURLOPT_URL, $url );
		curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( $parameters ) );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
		curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $request );
		$body = curl_exec( $ch );
		curl_close( $ch );

		// Intrepret results
		$json = json_decode( $body, true );

		// Error check
		if ( ( ! isset( $json['success'] ) || $json['success'] != 'OK' ) && $membership_type != 'shared' ) {
			wp_mail( get_option( 'admin_email' ), 'Group Sync Error', $username . ' ' . $group_name );
		}

	}

}

You will be passing a user ID and an array of group names to the function. You'll need to do some kind of a check to see whether the user is in or not in the groups.

This function can be hooked in with the SSO request, but the best way to keep groups synced is to make some kind of listener function that get fired every time the a user's meta updates. (I've probably lost you at this point ...)

Here's an abbreviated example:

/**
 * Listen for membership_type changes
 *
 */
add_action( 'added_user_meta', 'ac_membership_type_listener', 11, 4 );
add_action( 'updated_user_meta', 'ac_membership_type_listener', 11, 4 );

function ac_membership_type_listener( $meta_id, $user_id, $meta_key, $meta_value ) {

	if ( $meta_key == 'membership_type' ) {

		// Discourse group sync (short delay because they might be joining Premium on new user registration and not already have Discourse account)
		if ( function_exists( 'ac_discourse_groups_sync' ) ) {
			wp_schedule_single_event( time() + 15, 'ac_discourse_groups_sync_event', array( $user_id, array( 'premium' ) ) );
		}

	}

}

Depending on what management system you're using you might hook into actions other than added_user_meta and updated_user_meta.


The group management is the most confusing part to all of this. The rest isn't too bad.

Anyway, take each part that you need and add it to your plugin file, then upload the plugin file to your server and activate it. (Maybe add one piece at a time and verify it works.) Hopefully everything is somewhat clear. There is a lot that goes into a tight integration and everyone's setup is going to be slightly different.

Posts: 1

Participants: 1

Read full topic

Viewing all 60721 articles
Browse latest View live




Latest Images