Working with CiviCRM 4.6 in WordPress

Publié
2015-01-19 11:49
Written by
haystack - member of the CiviCRM community - view blog guidelines

The release of CiviCRM 4.6 marks a watershed moment for integration with WordPress. Read on for a guide to what's new and what you can do with these cool new features.

CiviCRM 4.6 opens a world of new possibilities for developers and administrators of WordPress-based systems. CiviCRM administrators will be happy to hear that they can now reliably use shortcodes in both static pages and chronological posts. CiviCRM content inserted via a shortcode can even appear in blog archives now. For developers, the big news is that for the first time, multiple plugins can receive callbacks from CiviCRM's hook system. What this means is that WordPress developers can now begin building an ecosystem of plugins to rival the ecosystem of Drupal modules.

Anatomy of a plugin

This section is mostly aimed at site administrators but is also recommended for developers who want to know more about WordPress integration. The new version of the plugin clearly and explicitly handles CiviCRM in the four contexts in which it may be called:

  • In WordPress admin
  • On the wpBasePage
  • via AJAX/snippet/iCal etc
  • via a shortcode

So let's have a look at these contexts in turn…

WordPress admin

Using CiviCRM in the WordPress admin hasn't changed visibly. The biggest enhancement is that plugin now invokes CiviCRM properly, so that CiviCRM's resources are only added when the CiviCRM admin page is loaded. Previously they were added on every admin page. This should make things more reliable and speed up the non-CiviCRM sections of the WordPress admin section.

The wpBasePage

Previously, CiviCRM would default to using a website's homepage for public-facing CiviCRM content. This often led to display problems when the homepage was not a static page and no "base page" had been set in CiviCRM's little-known "Settings - WordPress Integration" admin page. From now on, fresh installs of CiviCRM will now auto-create a "base page" for CiviCRM. The new page is called (surprise, surprise!) "CiviCRM" and is where CiviCRM will present its public-facing content by default. Public links in CiviCRM admin will reference this page: so, for example, when you create an event, the public link will point to your base page.

If the plugin detects an existing page by that name (and it has the 'civicrm' slug) it will assume that this is a pre-existing base page and use that instead. To provide as much support for different themes as possible, the template for the base page is assigned to the standard 'page.php' template in the active theme. This can be overridden with the 'civicrm_basepage_template' filter for those who want to use a different one:

// override base page template
add_filter( 'civicrm_basepage_template', 'my_basepage_template' );
function my_basepage_template( $template ) {
  return 'my-template-name.php';
}

AJAX/snippet/iCal calls

Most people are unlikely to use these, but for those who do, it may be worth noting that AJAX/snippet/iCal calls exit much earlier now, which should make them more efficient.

And lastly... Shortcodes

The most noticeable change to shortcodes is the addition of a "Hijack" option in the CiviCRM Shortcode Generator dialog box. What does this do? Well, as long as you only insert a single shortcode into the content, the the shortcode will "hijack" the containing page or post - it will overwrite the HTML title, page title and page content with the relevant parts of the CiviCRM entity that the shortcode represents. This is a really nice way to present CiviCRM content on your site.

You do not have to hijack the containing page, however - you can just insert a shortcode anywhere into a page or post and choose not to hijack it. This will render the CiviCRM content in the appropriate place in thepost content. Whatever you decide to do, you can now have comments enabled on the post or not - previously, comments were automatically disabled.

Multiple shortcodes are now handled quite nicely. When the plugin encounters them (whether on an archive or within a single page) it renders the title and, optionally, the summary/description of the entity with a link to either the containing page or the base page, where the content can be viewed with full functionality.

WordPress plugin development

Let's turn now to the topic of plugin development. Be warned that this gets pretty technical - so if you're not a plugin developer you might want to head off now, make yourself a nice cup of tea and reflect on how much nicer your WordPress CiviCRM site is going to be. You may like to take a look at some of the new plugins that you can now use on your WordPress site, which are listed at the end of the article. If you're a developer, however, please read on - you may even enjoy the following...

First, a bit of background context. Prior to 4.6, in CiviCRM's hook system, there was only one callback that could be defined for a particular hook. So, for example, when CiviCRM fired 'hook_civicrm_pre' the only callback that received data from this event was the function 'wordpress_civicrm_pre()'. Clearly, this prevented multiple plugins from co-existing because of the function name collisions if they both wanted to receive callbacks from 'hook_civicrm_pre'.

With the release of 4.6, there is complete compatibility with the WordPress actions and filters system (well almost - more on that later). The rule for targeting the hook is to remove the 'hook_' prefix when you create the filter or action. So, if your plugin wants to receive callbacks from 'hook_civicrm_pre', you can do it in global scope like this:

// hook into civicrm_pre
add_filter( 'civicrm_pre', 'my_plugin_pre_callback', 10, 4 );
function my_plugin_pre_callback( $op, $objectName, $objectId, &$objectRef ) {
  // your code here
}

And of course, if you write your plugins in object-oriented code, you can target your class methods as callbacks the way you normally would:

class Civi_Foo {
  function __construct() {
    add_filter( 'civicrm_pre', array( $this, 'my_pre_callback' ), 10, 4 );
  }
  public function my_pre_callback( $op, $objectName, $objectId, &$objectRef ) {
    // your code here
  }
}
new Civi_Foo;

Just remember that because the parameters are passed by reference, you can modify them in your callback if you need to - and that modification will be passed on to the next callback in its modified state. You can use WordPress hook priorities to easily determine (and modify) what the order of callbacks is.

The caveat I mentioned earlier? Ah, yes. Because of the way some CiviCRM hooks are constructed, the WordPress Plugin API cannot be used for hooks that require a return value, because they are seemingly incompatible with 'apply_filters' and 'apply_filters_ref_array' which expect the first parameter passed to the hook to be the one returned. Luckily, there are very few of them at present. The full list is:

  • 'hook_civicrm_upgrade'
  • 'hook_civicrm_caseSummary'
  • 'hook_civicrm_dashboard'

Any plugins that need to have callbacks for these hooks must register a unique code with CiviCRM. The code must be a valid part of a PHP function name, so stick to letters and underscores. This allows the plugins to use global scope functions as callbacks in the form:

{code}_{hookname}

So, for example, a plugin that registers the prefix 'my_unique_plugin_code' and wants a callback from 'hook_civicrm_dashboard' must create a function called:

function my_unique_plugin_code_civicrm_dashboard( $contactID, &$contentPlacement ) {
  // your code here
}

If a plugin needs to keep its logic inside a class, it will have to route the call to the class method from the global function. If you're used to writing Drupal modules, all of this should be quite familiar, except that Drupal does the 'module_prefix' discovery for you. To register your plugin's code, use the 'civicrm_wp_plugin_codes' filter:

add_filter( 'civicrm_wp_plugin_codes', 'register_my_civicrm_plugin_code' );
function register_my_civicrm_plugin_code( $plugin_codes ) {
  // add our unique code and send back to CiviCRM
  $plugin_codes[] = 'my_unique_civicrm_plugin_code';
  return $plugin_codes;
}

So then, because there are no more function name collisions, there are no more restrictions on the number of plugins that can be written to work simultaneously with CiviCRM. So go forth, WordPress plugin developers, and multiply those plugins!

Further Resources

Here's a list of some of my CiviCRM-related plugins. You can install some of them directly from your WordPress admin. Have a look at them to see the new hook system in operation - and, if you can, please create Pull Requests if you find any bugs. I'd love to have feedback on any of these plugins.

CiviCRM WordPress Profile Sync

This keeps the "First Name", "Last Name", "Email Address" and "Website" fields of a WordPress (and BuddyPress) user profile in continuous sync with a CiviCRM contact: https://wordpress.org/plugins/civicrm-wp-profile-sync https://github.com/christianwach/civicrm-wp-profile-sync Use this in combination with BP XProfile WordPress User Sync to enforce the use of "First Name" and "Last Name" in BuddyPress communities: https://wordpress.org/plugins/bp-xprofile-wp-user-sync/

CiviCRM WordPress Member Sync

Keeps a WordPress user's capabilities (rather than roles - since multiple roles is a Drupal thing - although the plugin supports roles too) in sync with a CiviCRM membership. Works seamlessly with the popular Members plugin: https://wordpress.org/plugins/civicrm-wp-member-sync/ https://github.com/christianwach/civicrm-wp-member-sync

CiviCRM Admin Utilities

Modifies and enhances CiviCRM's behaviour in single site and multisite WordPress installs. You can choose which post types the CiviCRM shortcode button appears on and enhance the appearance of the CiviCRM menu. In multisite, you can restrict the appearance of CiviCRM on sub-sites without disabling it - useful in combination with CiviCRM WordPress Profile Sync, for example, where you want to track profile changes on sub-sites: https://wordpress.org/plugins/civicrm-admin-utilities/ https://github.com/christianwach/civicrm-admin-utilities

BuddyPress Groups CiviCRM Sync

A port of the Drupal civicrm_og_sync module that enables two-way synchronisation between BuddyPress groups and CiviCRM groups. If you're familiar with Organic Groups in Drupal, using this should be straightforward: Coming soon to the WordPress Plugin Directory https://github.com/christianwach/bp-groups-civicrm-sync

Filed under

Comments

Thanks for all your hard work improving CiviCRM in WP!

Christian - Major kudos for your hard work and persistence moving the CiviCRM <=> WordPress integration light years forward. AND ... so wonderful that you took the time to explain the details in such a clear and complete way. FANTASTIC!!

Thanks, Dave. I'm looking forward to seeing the cool new plugins that the community's going to come up with. I'd like to ping the thanks back to all the CiviCRM devs who are making such great strides with 4.6 - roll on the release itself!

Christian - this is awesome!  I'm hoping to get a client to greenlight a port of the Ubercart-CiviCRM module to WooCommerce soon.  I also have a (non-published, bad dev) "record downloads as an activity" module for Drupal that a client wants ported to WP.  Thank you for your work making this all possible!

Hi Jon, good to hear that you've got new plugins in the pipeline. I'll publish some more advanced tutorials at some stage, showing some useful techniques. For example, plugins can override Civi's built-in templates (though not PHP files) and provide their own templates and PHP files. Here is how CiviCRM Admin Utilities registers its own template directory which contains the template file that overrides the built-in menu template. Very handy.

The work done is quite amazing and blogging about it is icing on the cake :) Thanx for taking the initiative and spearheading the change and also for blogging it

I do hope that this helps the Civi-WP community build better and more comprehensive plugins for WP and Civi

lobo

I thought it might be useful to add a comment regarding future development. The most pressing issue is that I'd like to see tackled is Civi's lack of WordPress multisite (and WordPress multi-network) compatibility but this cannot be done from within the plugin alone; it requires changes to civicrm-core, where the assumptions about Civi's context are being made. So if there are any developers who'd like to work with me on this, please give me a shout! There's a starter list of issues I've identified here.

I'd also like to thank CUNY Academic Commons, who commissioned the integration of Commons in a Box and CiviCRM for use by academic "communities of interest". It's perhaps not the usual type of organisation that CiviCRM is used for, but it seems to be a good fit in many regards.

Tim Otten reminded me (thanks Tim) that there's another hook which won't behave as expected in WordPress, which is 'hook_civicrm_managed'. Callbacks work fine but, at present, there's no logic for managing the entities in WordPress. Another one for the roadmap, logged here for reference.

When can we see the update process of Civi handled by the WordPress update engine?
And, along the same lines- when will the Civi dashboard be responsive to work on any size screen?

I don't think Civi will be available through the WordPress plugin directory anytime soon. I read some discussions about the technical and licensing issues involved, but am unable to find them right now. However, I agree with you that it would be desirable to see this happen.

The CSS governing the look of CiviCRM itself is handled by CiviCRM core. A proper responsive design should be implemented there so that it is CMS agnostic. There was talk about (and some progress towards) using Twitter Bootstrap for Civi's interface elements, but it wasn't clear to me what (if any) outcome there might be.

Anonymous (non vérifié)
2015-02-03 - 21:07

Thanks for all the hard work.  We are still in CiviCRM ver. 4.4.5 and will upgrade to 4.6 when it's available. We have BuddyPress access tied to an active/current CiviCRM membership. Everything works fine for active/current members, however, once a membership has expired, the contact still has access to BuddyPress.  Can page restriction features (similar to Paid Membership Pro or  Restrict Content Pro) be added to CiviMember?

The plugins we're using are Tadpoles CiviMember Role Sync and User Role Editor Pro. Would you recommend another plugin?

Thanks!

 

 

@HPCiviCRM Yes, page restriction features are not just possible, but one of the main reasons for using a Member Sync plugin. However, I don't know any of the premium plugins that you're using, so can't advise on whether they will work. I avoid them for a number of reasons, but mainly because of situations like this, where I can't inspect the code and give you an assessment of their suitability!

I'm not sure how helpful this is to you given that you're already using roles (it may be of use to people planning future projects) but I'd suggest that user role management - and especially multiple roles per user - is more of a Drupal concept. WordPress is largely built on user capabilities instead, which is why I built CiviCRM WordPress Member Sync (which builds on the great work that Jag Kandasamy and Tadpole did in porting the CiviMember Roles Sync module to WordPress) to include capability-syncing as well as role-syncing. The new plugin therefore works seamlessly with the (free) Members and Groups plugins, both of which have multiple methods for restricting content.

However, I'm not entirely convinced that this approach is the best one to take for restricting access to BuddyPress functionality, because that is independent of role or capability and it won't be enough to restrict access to the main BuddyPress pages. There are plugins which will restrict access to BuddyPress for logged-out (and pending) users, but, as far as I know, not for users with certain roles or capabilities. BuddyPress Registration Options, for example, has all the functionality needed for what you want to achieve, but, unfortunately, has no filters to be able to hook into it and enable that functionality for use cases other than the ones it specifies.

The simplest way to achieve what you want is to mark the user as spam while their membership is not current. I know this sounds odd, but BuddyPress has all the logic needed to prevent access to spammers - and of course, your expired members wouldn't know that they've been marked as such!

tldr; There's nothing out-of-the-box that will do exactly what you're after but I hope the above gives you an idea of what's involved.

Thanks for the response. It gave me quite a bit of insight.  By the way, we're not using those premium plugins.  I just mentioned them as examples of the functionality we'd like.

Do you think a custom solution could be built that would restrict BuddyPress access to lasped and expired members?

 

 

 

 

 

 

 

 

For basic denial-of-access without membership, try something like:

/**
 * Disable BuddyPress UI components
 */
function my_bp_disable_ui() {

	// get user
	$user = wp_get_current_user();

	// replace with your capability that allows access
	if ( $user->has_cap( 'civimember_2' ) ) return;

	// cannot favourite
	add_filter( 'bp_activity_can_favorite', '__return_false' );

	// hide friend buttons
	add_filter( 'bp_get_add_friend_button', '__return_false' );
	add_filter( 'bp_get_send_public_message_button', '__return_false' );
	add_filter( 'bp_get_send_message_button', '__return_false' );

	// hide group buttons
	add_filter( 'bp_user_can_create_groups', '__return_false' );
	add_filter( 'bp_get_group_join_button', '__return_false' );

	// hide activity comment buttons
	add_filter( 'bp_activity_can_comment_reply', '__return_false' );
	add_filter( 'bp_activity_can_comment', '__return_false' );
	add_filter( 'bp_acomment_name', '__return_false' );

	// hide bbPress
	add_filter( 'bbp_current_user_can_access_create_reply_form', '__return_false' );
	add_filter( 'bbp_current_user_can_access_create_topic_form', '__return_false' );

	// hide menu
	add_filter( 'bp_use_wp_admin_bar', '__return_false' );
	remove_action( 'admin_bar_menu', 'bp_members_admin_bar_notifications_menu', 90 );
	remove_filter( 'edit_profile_url', 'bp_members_edit_profile_url', 10 );

}
add_action( 'bp_ready', 'my_bp_disable_ui' );

/**
 * Check if current user should be denied access or not
 */
function my_bp_deny_access() {

	// get user
	$user = wp_get_current_user();

	// replace with your capability that allows access
	if ( $user->has_cap( 'civimember_2' ) ) return;

	// hide BuddyPress
	if ( function_exists( 'is_buddypress' ) AND is_buddypress() ) {
		wp_redirect( get_bloginfo( 'url' ) );
		exit;
	}

	// hide bbPress
	if ( function_exists( 'is_bbpress' ) AND is_bbpress() ) {
		wp_redirect( get_bloginfo( 'url' ) );
		exit;
	}

}
add_action( 'wp', 'my_bp_deny_access' );

Of course, you'll have to adapt the line that checks for a capability-that-allows-access to suit your environment. For you, this might involve checking that the user has a particular role, though bear in mind that, if you're using bbPress, each user will have more than one role. If you've set your roles up appropriately, however, the roles-with-access should have a unique capability that you can check for, thus saving you checking for multiple roles.

Anonymous (non vérifié)
2015-02-07 - 10:11

Thank you for the code.  I am not a coder, but I'm sure if I give it some effort, I'll figure this out. (Did it with HTML and CSS....I think I can do this too...given time)

Questions:  

1) Where would I insert this code?

2) Any chance of this being a Make It Happen for Wordpress?  I am sure an extension or plugin such as this would be popular among membership non-profits.

 

 

 

1) You can paste this into your theme's 'functions.php' file, though this may be overwritten when the theme is updated if you're not using a child theme. I would highly recommend that you use a child theme. Otherwise, you'd need to create a bare-bones plugin to paste it into.

2) The problem with this is that BuddyPress installs often need to be addressed individually because they are often highly customised. A general solution will only get so far in this context and I can't imagine many developers who would want to deal with support requests for the particular circumstances of a particular install. My guess is that this is why many of the plugins that attempt this are now abandonware.

Anonymous (non vérifié)
2015-02-14 - 11:24

Okay, thanks!