Architecture Overview
Plugin Structure
buddypress-reactions/
├── admin/ # Admin functionality
│ ├── class-buddypress-reactions-admin.php
│ ├── css/ # Admin styles
│ ├── js/ # Admin scripts
│ └── inc/ # Admin includes
├── includes/ # Core functionality
│ ├── class-buddypress-reactions.php
│ ├── class-buddypress-reactions-activator.php
│ ├── class-buddypress-reactions-deactivator.php
│ └── buddypress-reactions-function.php
├── public/ # Frontend functionality
│ ├── class-buddypress-reactions-public.php
│ ├── css/ # Frontend styles
│ └── js/ # Frontend scripts
├── emojis/ # Emoji assets
│ ├── svg/ # SVG files (1-200)
│ ├── json/ # Animation files
│ └── bp-reaction-emojis.json
└── docs/ # Documentation
Core Classes
Main Plugin Class
class Buddypress_Reactions {
protected $loader;
protected $plugin_name;
protected $version;
public function __construct() {
$this->version = BUDDYPRESS_REACTIONS_VERSION;
$this->plugin_name = 'buddypress-reactions';
$this->load_dependencies();
$this->set_locale();
$this->define_admin_hooks();
$this->define_public_hooks();
}
}
Database Schema
Tables
wp_bp_reactions_shortcodes
CREATE TABLE wp_bp_reactions_shortcodes (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(150) DEFAULT NULL,
post_type varchar(100) DEFAULT NULL,
front_render int(11) DEFAULT 0,
options longtext,
PRIMARY KEY (id)
);
wp_bp_reactions_emojis
CREATE TABLE wp_bp_reactions_emojis (
id int NOT NULL AUTO_INCREMENT,
name varchar(500) DEFAULT NULL,
type enum('inbuilt','custom') NOT NULL,
format varchar(10) NOT NULL,
PRIMARY KEY (id)
);
wp_bp_reactions_reacted_emoji
CREATE TABLE wp_bp_reactions_reacted_emoji (
id int NOT NULL AUTO_INCREMENT,
post_id int NOT NULL,
post_type varchar(50) NOT NULL,
user_id int DEFAULT NULL,
emoji_id int DEFAULT NULL,
bprs_id int DEFAULT NULL,
PRIMARY KEY (id),
KEY post_id (post_id),
KEY user_id (user_id),
KEY emoji_id (emoji_id)
);
Hooks and Filters
Actions
Frontend Actions
// Fired when reactions UI should be displayed
do_action('buddypress_reactions_after_activity_entry', $activity_id);
// Fired when a reaction is added
do_action('buddypress_reactions_after_add_reaction', $post_id, $post_type, $emoji_id, $user_id);
// Fired when a reaction is removed
do_action('buddypress_reactions_after_remove_reaction', $post_id, $post_type, $emoji_id, $user_id);
Admin Actions
// Fired when emoji database is reindexed
do_action('buddypress_reactions_after_reindex', $emoji_count);
// Fired when settings are saved
do_action('buddypress_reactions_settings_saved', $settings);
Filters
Display Filters
// Filter emoji display URL
$emoji_url = apply_filters('buddypress_reactions_emoji_url', $url, $emoji_id, $type);
// Filter reaction button text
$button_text = apply_filters('buddypress_reactions_button_text', __('React', 'buddypress-reactions'), $post_type);
// Filter available emojis for a post type
$emojis = apply_filters('buddypress_reactions_available_emojis', $emojis, $post_type);
// Filter to load scripts/styles
$should_load = apply_filters('buddypress_reactions_load_scripts', $should_load);
$should_load = apply_filters('buddypress_reactions_load_styles', $should_load);
Data Filters
// Filter reaction data before saving
$reaction_data = apply_filters('buddypress_reactions_before_save', $reaction_data);
// Filter emoji name
$emoji_name = apply_filters('buddypress_reactions_emoji_name', $name, $emoji_id);
JavaScript API
Global Object
window.bpreactions = {
ajaxUrl: 'admin-ajax.php',
emojis_path: '/wp-content/plugins/buddypress-reactions/emojis/',
version: '3.0.0',
rest_url: '/wp-json/buddypress/v1/',
nonce: 'security_nonce'
};
Core Functions
Adding a Reaction
// Trigger reaction via JavaScript
function addReaction(postId, postType, emojiId, bprsId) {
jQuery.ajax({
url: bpreactions.ajaxUrl,
type: 'POST',
dataType: 'json',
data: {
action: 'bpr_create_user_react_emoji_ajax',
post_id: postId,
post_type: postType,
emoji_id: emojiId,
bprs_id: bprsId,
ajax_nonce: jQuery('#_ajax_nonce').val()
},
success: function(response) {
if (response.status === 'success') {
// Update UI
jQuery('#bp-reactions-' + postType + '-' + postId).html(response.container);
}
}
});
}
Event Listeners
// Listen for reaction clicks
jQuery(document).on('click', '.emoji-pick', function() {
var emojiId = jQuery(this).data('emoji-id');
var postId = jQuery(this).data('post-id');
var postType = jQuery(this).data('type');
var bprsId = jQuery(this).data('bprs-id');
// Handle reaction
});
// Listen for reaction removal
jQuery(document).on('click', '.bp-activity-react-button', function() {
// Handle removal
});
Animation API
// Initialize Lottie animations
var animation = bodymovin.loadAnimation({
container: element,
path: bpreactions.emojis_path + 'json/' + emojiId + '.json',
renderer: 'svg',
loop: true,
autoplay: true
});
AJAX Handlers
Add Reaction
add_action('wp_ajax_bpr_create_user_react_emoji_ajax', 'handle_create_reaction');
function handle_create_reaction() {
// Verify nonce
if (!wp_verify_nonce($_POST['ajax_nonce'], 'bp-reactions')) {
wp_send_json_error('Security check failed');
}
// Get data
$emoji_id = intval($_POST['emoji_id']);
$post_id = intval($_POST['post_id']);
$post_type = sanitize_text_field($_POST['post_type']);
$bprs_id = intval($_POST['bprs_id']);
// Process reaction
// Return response
wp_send_json_success(array(
'status' => 'success',
'container' => $html_output
));
}
Remove Reaction
add_action('wp_ajax_bpr_remove_user_react_emoji_ajax', 'handle_remove_reaction');
Display Reactions
add_action('wp_ajax_bpr_display_user_react_emoji_ajax', 'handle_display_reactions');
Creating Custom Integrations
Adding Reactions to Custom Post Types
// 1. Register post type support
function add_reactions_to_custom_post_type() {
global $wpdb;
// Insert shortcode for custom post type
$wpdb->insert(
$wpdb->prefix . 'bp_reactions_shortcodes',
array(
'name' => 'Custom Post Reaction',
'post_type' => 'my_custom_post',
'front_render' => 1,
'options' => json_encode(array(
'emojis' => array(64, 190, 36, 5, 42, 29, 26, 7),
'animation' => 'true'
))
)
);
}
add_action('init', 'add_reactions_to_custom_post_type');
// 2. Display reactions
function display_custom_post_reactions($content) {
if (get_post_type() === 'my_custom_post') {
$reactions = do_shortcode('[bp_reactions id="4"]');
$content .= $reactions;
}
return $content;
}
add_filter('the_content', 'display_custom_post_reactions');
Custom Emoji Sets
// Filter emojis for specific post type
function custom_emoji_set($emojis, $post_type) {
if ($post_type === 'forum_topic') {
// Return different emoji set for forums
return array(64, 65, 190, 187, 26, 132, 152, 194);
}
return $emojis;
}
add_filter('buddypress_reactions_available_emojis', 'custom_emoji_set', 10, 2);
Custom Reaction Notifications
// Add custom notification handling
function custom_reaction_notification($post_id, $post_type, $emoji_id, $user_id) {
if ($post_type === 'activity') {
// Send custom notification
bp_notifications_add_notification(array(
'user_id' => $author_id,
'item_id' => $post_id,
'secondary_item_id' => $user_id,
'component_name' => 'custom_reactions',
'component_action' => 'new_reaction',
'date_notified' => bp_core_current_time(),
'is_new' => 1
));
}
}
add_action('buddypress_reactions_after_add_reaction', 'custom_reaction_notification', 10, 4);
Extending Functionality
Adding New Reaction Types
class Custom_Reaction_Type {
public function __construct() {
add_filter('buddypress_reactions_post_types', array($this, 'add_post_type'));
add_action('wp_ajax_custom_reaction', array($this, 'handle_reaction'));
}
public function add_post_type($post_types) {
$post_types[] = 'custom_type';
return $post_types;
}
public function handle_reaction() {
// Custom reaction logic
}
}
new Custom_Reaction_Type();
Creating Reaction Widgets
class My_Reaction_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'my_reaction_widget',
__('My Reaction Widget', 'buddypress-reactions')
);
}
public function widget($args, $instance) {
global $wpdb;
// Get top reactions
$top_reactions = $wpdb->get_results("
SELECT emoji_id, COUNT(*) as count
FROM {$wpdb->prefix}bp_reactions_reacted_emoji
GROUP BY emoji_id
ORDER BY count DESC
LIMIT 5
");
// Display widget
echo $args['before_widget'];
foreach ($top_reactions as $reaction) {
$emoji_url = get_buddypress_reaction_emoji($reaction->emoji_id, 'svg');
echo '<img src="' . esc_url($emoji_url) . '" /> ';
echo '<span>' . $reaction->count . '</span>';
}
echo $args['after_widget'];
}
}
REST API Integration
// Register REST routes
function register_reactions_rest_routes() {
register_rest_route('buddypress-reactions/v1', '/reactions/(?P<id>\d+)', array(
'methods' => 'GET',
'callback' => 'get_post_reactions',
'permission_callback' => '__return_true'
));
register_rest_route('buddypress-reactions/v1', '/react', array(
'methods' => 'POST',
'callback' => 'add_reaction_rest',
'permission_callback' => 'is_user_logged_in'
));
}
add_action('rest_api_init', 'register_reactions_rest_routes');
Best Practices
Performance Optimization
- Use Caching
// Cache reaction counts
$cache_key = 'reactions_' . $post_id . '_' . $post_type;
$reactions = wp_cache_get($cache_key, 'bp_reactions');
if (false === $reactions) {
$reactions = $wpdb->get_results($query);
wp_cache_set($cache_key, $reactions, 'bp_reactions', HOUR_IN_SECONDS);
}
- Batch Database Operations
// Use single query for multiple operations
$wpdb->query("START TRANSACTION");
try {
// Multiple operations
$wpdb->query("COMMIT");
} catch (Exception $e) {
$wpdb->query("ROLLBACK");
}
Security Best Practices
- Always Verify Nonces
if (!wp_verify_nonce($_POST['nonce'], 'bp-reactions')) {
wp_die('Security check failed');
}
- Sanitize All Input
$post_id = absint($_POST['post_id']);
$post_type = sanitize_text_field($_POST['post_type']);
$emoji_id = absint($_POST['emoji_id']);
- Check Capabilities
if (!is_user_logged_in()) {
wp_send_json_error('Login required');
}
if (!current_user_can('read')) {
wp_send_json_error('Insufficient permissions');
}
Error Handling
try {
// Reaction operation
$result = add_reaction($post_id, $emoji_id, $user_id);
if (is_wp_error($result)) {
throw new Exception($result->get_error_message());
}
wp_send_json_success(array('message' => 'Reaction added'));
} catch (Exception $e) {
wp_send_json_error($e->getMessage());
}
Debugging
// Enable debug logging
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('BuddyPress Reactions: ' . print_r($data, true));
}
// Custom debug function
function bpr_debug($data, $label = '') {
if (!defined('WP_DEBUG') || !WP_DEBUG) {
return;
}
$backtrace = debug_backtrace();
$file = $backtrace[0]['file'];
$line = $backtrace[0]['line'];
error_log("BPR Debug [$label] at $file:$line - " . print_r($data, true));
}
Testing
Unit Testing Example
class Test_Reactions extends WP_UnitTestCase {
public function test_add_reaction() {
$user_id = $this->factory->user->create();
wp_set_current_user($user_id);
$post_id = $this->factory->post->create();
$emoji_id = 64; // Thumbs up
$result = add_reaction($post_id, 'post', $emoji_id, $user_id);
$this->assertTrue($result);
$this->assertEquals(1, get_reaction_count($post_id, 'post'));
}
}
Support and Resources
- GitHub Repository: Repository
- Developer Forums: https://wbcomdesigns.com/support/
- API Documentation: Available in
/docs/api/ - Code Examples: Available in
/examples/
