Add custom filters to loops and enjoy them within your plugin
BuddyPress uses loops to display the content of its components. In this article you will focus on how to “rearrange” the content displayed in four of its major components : Members, Groups, Blogs and finally Activity. You will achieve this thanks to the type or action argument of the init functions of each of these loops :
Loops | Init functions | Locations |
---|---|---|
Members | bp_has_members( array( $arguments ) ) |
/bp-activity/bp-activity-template.php |
Groups | bp_has_groups( array( $arguments ) ) |
/bp-groups/bp-groups-template.php |
Blogs | bp_has_blogs( array( $arguments ) ) |
/bp-blogs/bp-blogs-template.php |
Activity | bp_has_activities( array( $arguments ) ) |
/bp-activity/bp-activity-template.php |
To let members of a BuddyPress powered community change the way items of loops are displayed, BuddyPress uses select boxes that includes the different types to reorder the members, groups or blogs and filter the activity stream to only keep the desired action (or activity type). These select boxes are generally available from the directory page of the component, and in the specific case of the activity component : in single group and member home page.
In the following tutorial, you will first extend the available filters of the Members, Groups and Blogs loops, then you will remember how to add custom actions (or activity types) to the Activity component for your plugin, finally you will build a great feature thanks to a very interesting Activity Meta Query.
Here’s your roadmap
- Add a custom filter to the Members, Groups and Blogs loops
- Extend the activity types to register your plugin ones
- Build a new great feature thanks to the ‘favorite an activity’ functionality
- Additional resources
In additional resources, you will find an example of plugin that includes all the codes that this tutorial contains. If you wish, you can activate the plugin in a local dev environment to follow the different steps of this article.
Add a custom filter to the Members, Groups and Blogs loops
To illustrate the process, you are going to use a built in filter type that is not activated by default in loops : the “random” order one. This screenshot shows the result of your first lines of code for the Members component.
If you dive into BuddyPress /bp-templates/bp-legacy/buddypress
folder, you will find 3 templates that are used to display the directory pages for the 3 components you’re dealing with in this first step:
Templates | Hooks | Lines |
---|---|---|
/bp-templates/bp-legacy/buddypress/members/index.php |
do_action( 'bp_members_directory_order_options' ); |
44 |
/bp-templates/bp-legacy/buddypress/groups/index.php |
do_action( 'bp_groups_directory_order_options' ); |
44 |
/bp-templates/bp-legacy/buddypress/blogs/index.php |
do_action( 'bp_blogs_directory_order_options' ); |
42 |
So all you need to do in order to set the “random” filter is to add an action to these three hooks. Let’s init your plugin’s class and write the lines that will render the above screenshot’s result.
<?php /** * Plugin Name: BP Loop Filters * Plugin URI: plugin uri * Description: Plugin example to illustrate loop filters * Version: 1.0 * Author: imath * Author URI: author uri * License: GPL-2.0+ * License URI: license uri */ // Exit if accessed directly if ( !defined( 'ABSPATH' ) ) exit; class BP_Loop_Filters { /** * Constructor */ public function __construct() { $this->setup_actions(); } /** * Actions * * @uses bp_is_active() * @uses is_multisite() */ private function setup_actions() { /** * Adds the random order to the select boxes of the Members, Groups and Blogs directory pages */ // Members component is core, so it will be available add_action( 'bp_members_directory_order_options', array( $this, 'random_order' ) ); // You need to check Groups component is available if( bp_is_active( 'groups' ) ) add_action( 'bp_groups_directory_order_options', array( $this, 'random_order' ) ); // You need to check WordPress config and that Blogs Component is available if( is_multisite() && bp_is_active( 'blogs' ) ) add_action( 'bp_blogs_directory_order_options', array( $this, 'random_order' ) ); } /** * Displays a new option in the Members/Groups & Blogs directories * * @return string html output */ public function random_order() { ?> <option value="random"><?php _e( 'Random', 'buddypress' ); ?></option> <?php } } // 1, 2, 3 go ! function bp_loop_filters() { return new BP_Loop_Filters(); } add_action( 'bp_include', 'bp_loop_filters' );
You can play with this new order option as long as you want 🙂 In a previous article of the codex (Group Meta Queries: Usage Example), we saw how to use Group Meta Queries, it’s also another interesting example to figure out how to use these hooks in your plugin to perform new great ways of displaying loops content.
Extend the activity types to register your plugin ones
Now let’s climb a new step, a bit “higher”. Your plugin should really take advantage of the activity component to “trace” its use by members. The great benefit of the Activity component is that it’s a central place where users can “taste” the dynamic of the community. You can remember how to set and use a custom activity type by reading a previous article of the codex : ‘Posting Activity from Plugins’.
Here, your goal is to avoid forgetting the Activity Administration screen select box. So the first part of the following code will ensure your plugin’s activity type is also available in the Administration screen of the Activity component, while the second part will benefit from the first one to populate the select boxes with your custom activity type in the front end.
You first need to add this hook to the setup_actions()
function of your class :
/** * Registers the Activity actions so that they are available in the Activity Administration Screen */ // You need to check Activity component is available if( bp_is_active( 'activity' ) ) { add_action( 'bp_register_activity_actions', array( $this, 'register_activity_actions' ) ); }
Then you have to actually write the register_activity_actions()
function to extend the available BuddyPress activity types:
/** * Registering the Activity actions for your component * * The registered actions will also be available in Administration * screens * * @uses bp_activity_set_action() */ public function register_activity_actions() { bp_activity_set_action( /* your component's id : same value as the "component" field you will use in the {$wpdb->prefix}bp_activity MySQL table */ 'bp_plugin', /* your component's activity type : - same value as the "type" field you will use in the {$wpdb->prefix}bp_activity MySQL table - it will be used in the value attribute of the plugin option in the activity selectbox */ 'bpplugin_action', /* your component's caption : - it will be displayed to the user in the activity selectbox */ __( 'BP Plugin Action' ) ); }
Once you’ve added these lines, when opening the Activity Administration screen, you will find your plugin’s filter inside the available choices of the select box as shown in the following screen capture.
Back to front end! You can take a new swim into the BuddyPress /bp-templates/bp-legacy/buddypress
folder to find the key actions to hook to in order to add your custom activity type(s).
Templates | Hooks | Lines |
---|---|---|
/bp-templates/bp-legacy/buddypress/activity/index.php |
do_action( 'bp_activity_filter_options' ); |
110 |
/bp-templates/bp-legacy/buddypress/members/single/activity.php |
do_action( 'bp_member_activity_filter_options' ); |
55 |
/bp-templates/bp-legacy/buddypress/groups/single/activiy.php |
do_action( 'bp_group_activity_filter_options' ); |
20 |
Before rushing into the process of using these hooks, you are going to build a transition function to get the list of activity types you’ve created. As you have only one activity type so far, you may think it’s not necessary but i personaly consider it as a good way to not forget the Administration screens of the Activity component. Moreover, you will soon add a new activity type for the final step. You need to edit your plugin’s class by adding a new function to list the activity actions.
/** * Building an array to loop in from the display function * * Using bp_activity_get_types() will list all registered activity actions * but you need to get the ones for your plugin, and this particular function * directly returns an array of key => value. As you need to filter activity * with your component id, the global buddypress()->activity->actions will be * more helpful. * * @uses buddypress() * @return array the list of your plugin actions. */ private function list_actions() { $bp_activity_actions = buddypress()->activity->actions; $bp_plugin_actions = array(); if( !empty( $bp_activity_actions->bp_plugin ) ) $bp_plugin_actions = array_values( (array) $bp_activity_actions->bp_plugin ); return $bp_plugin_actions; }
Now you can use the 3 hooks previously identified by adding some code into the setup_actions()
function of your plugin’s class, just after the code that hooks 'bp_register_activity_actions'
.
// Adds a new filter into the select boxes of the Activity directory page, // of group and member single items activity screens add_action( 'bp_activity_filter_options', array( $this, 'display_activity_actions' ) ); add_action( 'bp_member_activity_filter_options', array( $this, 'display_activity_actions' ) ); // You need to check Groups component is available if( bp_is_active( 'groups' ) ) add_action( 'bp_group_activity_filter_options', array( $this, 'display_activity_actions' ) );
You will also need to create the function display_activity_actions()
to build the different new options that will populate the select boxes. This function will use the transition function to get the activity types of your plugin.
/** * Displays new actions into the Activity select boxes * to filter activities * - Activity Directory * - Single Group and Member activity screens * * @return string html output */ public function display_activity_actions() { $bp_plugin_actions = $this->list_actions(); if( empty( $bp_plugin_actions ) ) return; foreach( $bp_plugin_actions as $type ):?> <option value="<?php echo esc_attr( $type['key'] );?>"><?php echo esc_attr( $type['value'] ); ?></option> <?php endforeach; }
Now you can check the result of your work by displaying the Activity Directory, a member’s home page and a group’s home page. Here’s a screenshot for the Activity Directory.
As you can see, the new option is available, and you can filter the activities to only get the activities that will be generated by your plugin. You only need to build the functions that will actually write new activities as explained in the codex article ‘Posting Activity from Plugins’.
Build a new great feature thanks to the favorite an activity functionality
In BuddyPress, there’s a feature to let members favorite some activities so that they can easily find the activities they are really interested in from the “My Favorites” tab of the Activity directory or from the one of their profile home page. And reading the above lines gave you a great idea! You think the community would really appreciate to quickly see what are the most favorited activities by all members. In a way and thanks to this new feature, members would be able to easily know what are the ones they should absolutely read. And you will see that displaying these most favorite activities in the different screens will have different new powerful meanings.
First, let’s extend the register_activity_actions()
function of your class to add a new very particular activity type. It actually won’t behave like a regular one. Its goal won’t be to display a particular type of activities as any type can be favorited. Using this new filter will perform two other things : only the favorited activities will be displayed and this display will be ordered regarding the number of times an activity has been favorited. So for this reason, you will not make this filter available from the Administration screen of Activities.
/* Activity Administration screen does not use bp_ajax_querystring Moreover This action type is reordering instead of filtering so you will only use it on front end */ if( !is_admin() ) bp_activity_set_action( 'bp_plugin', 'activity_mostfavs', __( 'Most Favorited' ) );
That’s all we have to do to add this new type to the select boxes as your function list_actions()
will automatically returns it to the function display_activity_actions()
. You now need to handle this new filter type by intercepting it when the init function of the Activity loop will be run. If you open the template /bp-templates/bp-legacy/buddypress/groups/single/activity-loop.php
, you’ll notice that the loop is using a function as an argument of this init function :
<?php if ( bp_has_activities( bp_ajax_querystring( 'activity' ) ) ) : ?>
bp_ajax_querystring()
plays a key role in the way that BuddyPress gets user inputs in order to render the appropriate display of the Activity loop. Just before returning these inputs, the function offers a filter so that you can get them and eventually edit them. You are going to use this filter, but not too early to let BuddyPress actually build the loop arguments, so you will choose a priority greater than 10, let’s say 12. You need to edit the constructor of your class so that it includes a new call to the function that will contain all your filters. This is the new constructor of your plugin :
class BP_Loop_Filters { /** * Constructor */ public function __construct() { $this->setup_actions(); // simply add the following line to your constructor $this->setup_filters(); } }
Once the constructor edited, you will create the setup_filters()
function and begin it by adding the 'bp_ajax_querystring'
filter at a priority of 12. You also need to inform WordPress that your function is expecting to receive the two arguments available for this filter : the querystring and the object it relates to. That’s why there is the number 2 after the priority argument.
/** * Filters */ private function setup_filters() { add_filter( 'bp_ajax_querystring', array( $this, 'activity_querystring_filter' ), 12, 2 ); }
The filter is in place, it’s now time you take care of building the function that will make eveything possible for your need. This is activity_querystring_filter()
:
/** * Builds an Activity Meta Query to retrieve the favorited activities * * @param string $query_string the front end arguments for the Activity loop * @param string $object the Component object * @uses wp_parse_args() * @uses bp_displayed_user_id() * @return array()|string $query_string new arguments or same if not needed */ public function activity_querystring_filter( $query_string = '', $object = '' ) { if( $object != 'activity' ) return $query_string; // You can easily manipulate the query string // by transforming it into an array and merging // arguments with these default ones $args = wp_parse_args( $query_string, array( 'action' => false, 'type' => false, 'user_id' => false, 'page' => 1 ) ); /* most favorited */ if( $args['action'] == 'activity_mostfavs' ) { unset( $args['action'], $args['type'] ); // on user's profile, shows the most favorited activities for displayed user if( bp_is_user() ) $args['user_id'] = bp_displayed_user_id(); // An activity meta query :) $args['meta_query'] = array( array( /* this is the meta_key you want to filter on */ 'key' => 'favorite_count', /* You need to get all values that are >= to 1 */ 'value' => 1, 'type' => 'numeric', 'compare' => '>=' ), ); $query_string = empty( $args ) ? $query_string : $args; } return apply_filters( 'bp_plugin_activity_querystring_filter', $query_string, $object ); }
Let’s take a minute to understand what happens in it. The 2 arguments are waiting for the filter values and default to an empty string. Then, the very first thing to do is to make sure the activity loop is about to be run as BuddyPress also uses this function for the Members, Groups, legacy user group forums, Blogs and private messages. In other words, if 'activity'
is not the value of the object argument, then just return the current query string without editing it.
The query string you will receive is actually a query string! so the arguments will look like 'arg1=1&arg2=2'
. I advise you to use the function wp_parse_args()
to quicky get an array of arguments : that will be a lot more easy to manipulate. Now you can check the action or type argument (which are carrying the same values) to see if it matches your most favorited activity type. If so, you can unset this values to include all types of activities.
If you are in a user’s profile, let’s only keep the activities for the user displayed to be consistent as the most favorited activity type will also be available in the select box of member’s home page. And, this is my favorite part, you will build an Activity Meta Query to include all the activities that have a meta value over 1 for the meta key 'favorite_count'
.
If you want to know more about Meta Queries, you’ll find in additional resources a link to the article dealing with Group Meta Queries and a link to the WordPress codex on the WP_Meta_Query
class. Finally you simply need to replace the value of the query string with your $args array.
So far, you are getting the favorited activities by all users when the Most Favorited filter is selected by the member, you need to bring the final touch, the one that often makes the difference for your plugin. You need to edit the order of the loop so that it will display the favorited activities from the more favorited one to the less favorited one. To achieve that step, you will need to put your ninja warrior suit on! Let’s add a new filter into the setup_filters() function of your class.
This is your new setup_filters()
function :
/** * Filters */ private function setup_filters() { add_filter( 'bp_ajax_querystring', array( $this, 'activity_querystring_filter' ), 12, 2 ); add_filter( 'bp_activity_get_user_join_filter', array( $this, 'order_by_most_favorited' ), 10, 6 ); }
This filter will get the query that is sent to the Activity loop to first edit the field it’s ordered by. Then as the bp_activity_meta
table will be joined in the query thanks to your Activity Meta Query. Let’s enjoy it to the max, by also adding a new field that will alias the meta value field for the favorite_count
meta key. Doing so the global $activities_template
will include this value and it will be very easy to get it for each entry of the loop. The function order_by_most_favorited()
will get the 6 arguments available for the 'bp_activity_get_user_join_filter'
filter. The most important is the first one as it’s the sql query. Others are part of this query to help you edit the main query.
/** * Ninja Warrior trick to reorder the Activity Loop * regarding the activities favorite count * * @param string $sql the sql query that will be run * @param string $select_sql the select part of the query * @param string $from_sql the from part of the query * @param string $where_sql the where part of the query * @param string $sort the sort order (leaving it to DESC will be helpful!) * @param string $pag_sql the offset part of the query * @return string $sql the current or edited query */ public function order_by_most_favorited( $sql = '', $select_sql = '', $from_sql = '', $where_sql = '', $sort = '', $pag_sql = '' ) { preg_match( '/\'favorite_count\' AND CAST\((.*) AS/', $where_sql, $match ); if( !empty( $match[1] ) ) { $new_order_by = 'ORDER BY '. $match[1] .' + 0'; $new_select_sql = $select_sql . ', '. $match[1] .' AS favorite_count'; $sql = str_replace( array( $select_sql, 'ORDER BY a.date_recorded' ), array( $new_select_sql, $new_order_by ), $sql ); /** * To help you build the pattern to search for * you can use the var_dump function to see the * query that will be performed * var_dump( $sql ); */ } return $sql; }
As you can see, you need to get the table name that the Activity Meta Query will build to use it to define you new ORDER BY
clause and to include your favorite_count
alias field into the SELECT
clause.
The very last step is to finally display the favorite count into the entry template of the Activity component. If you explore the BuddyPress /bp-templates/bp-legacy/buddypress
folder, in the activity/entry.php
template, you will find the 'bp_activity_entry_meta'
hook which can be a nice place to add your mention about the number of times the activity has been favorited. So to insert this mention, you simply need to edit your plugin’s setup_actions()
function to add an action to this hook and build the function that will be run.
private function setup_actions() { /** * previous steps code */ if( bp_is_active( 'activity' ) ) { /** * previous steps code */ // You're going to output the favorite count after action buttons add_action( 'bp_activity_entry_meta', array( $this, 'display_favorite_count' ) ); } } /** * Displays a mention to inform about the number of times the activity * was favorited. * * @global BP_Activity_Template $activities_template * @return string html output */ public function display_favorite_count() { global $activities_template; $fav_count = !empty( $activities_template->activity->favorite_count ) ? $activities_template->activity->favorite_count : 0; if( !empty( $fav_count ) ):?> <a name="favorite-<?php bp_activity_id();?>"><span><?php printf( _n( 'Favorited once', 'Favorited %s times', $fav_count ), $fav_count );?></span></a> <?php endif; }
If you observe the result of a “Most favorited” filter into the main Activity Directory, you will see that only the favorited activities are displayed ordered by the number of times they had been favorited.
If you do the same on the user’s profile home page, the filter will inform on his activities : the ones that has been favorited by him or other users. So it can be interested for a user to see the interest of his activities for the community.
If you display this filter into a group home page, it will list the group activities that were favorited by its own members in case of a non public group and potentially all community members for a public group.
Additional resources
- All the codes of this tutorial packaged in a plugin
- The Members Loop in BuddyPress Codex
- The Groups Loop in BuddyPress Codex
- The Blogs Loop in BuddyPress Codex
- The Activity Loop in BuddyPress Codex
- WP_Meta_Query in WordPress codex
- Group Meta Queries: Usage Example in BuddyPress Codex
- Posting Activity from Plugins in BuddyPress Codex
- bp_ajax_querystring in BuddyPress codex