Working with BuddyPress on top of WordPress is a super exciting thing, it adds a whole new dimension to the platform and really demonstrates huge potential.
BuddyPress like other plugins expands on the core functionality WordPress offers. Although it is important as a freelancer or company to recognise that BuddyPress unlike other plugins adds functionality of epic proportions.
This tutorial aims to show you how to demonstrate a proof of concept quickly and functionally without making any best practice cardinal sins.
Introduction
Over the course of this tutorial we will use a combination of PHP, jQuery and WordPress functions to extend BuddyPress far enough to demonstrate a concept.
Working on member profiles we will without any recourse add a link that allows users to visit a member bookmarks area.
The bookmarks area will be populated with a list of bookmarks a member has decided to save whilst browsing any BuddyPress enabled site.
The scope of bookmarks which can be saved will only be applied to WordPress posts for now, however you may look to build on this further and apply it to other areas of a WordPress powered web site that produces a permalink.
Step 1 The Essentials
We will be building upon the bp-default theme today and creating our own child theme. Below is the structure you should have created.
- style.css – Some additional styles for icons, buttons and lists (this will not be discussed).
- sidebar.php – We will call our widget from here.
- header.php – One modification required.
- functions.php – Register scripts and a apply a filter.
- _inc/img/ – A number of image files to be used.
- _inc/js/bookmarks.js – jQuery and AJAX.
- members/single/home.php – Some PHP logic to enable the template loader.
- members/single/bookmarks/ajax.php – Used for our AJAX calls.
- members/single/bookmarks/loop.php – Retrieval of bookmarks via member profiles.
- members/single/bookmarks/remove.php – Deletion of bookmarks via my member profile.
- members/single/bookmarks/save.php – Storage of bookmarks via my member profile.
- members/single/bookmarks/view.php – Hacky bookmark template loader.
- members/single/bookmarks/widget.php – Called into site sidebar.php.
style.css – Within style.css we need a bare minimum amount of code to allow for theme selection via wp-admin. Let's do that now.
/* Theme Name: Bookmark theme Description: Child theme from bp-default with added support for member bookmarks. Version: 1.0 Author: WPTuts Author URI: http://wp.tutsplus.org Tags: buddypress Template: bp-default */
Tags: buddypress
will notify BuddyPress that we are using a BP enabled theme.
Template: bp-default
will instruct BuddyPress that when this theme is active to inherit its functionality from the bp-default theme unless a theme file has been modified.
Within sidebar.php we need to load the widget.php.
locate_template(array('members/single/bookmarks/widget.php'), true);
Step 2 functions.php – Register Script
Let's go ahead and register the bookmarks.js file, it will be required on every page from here on out. In functions.php add the following.
function px_bookmark_scripts() { if(!is_admin()) { wp_enqueue_script( 'px-scripts-functions', get_stylesheet_directory_uri() . '/_inc/js/bookmarks.js', array('jquery'), '1.0', true ); } } add_action('wp_enqueue_scripts','px_bookmark_scripts');
wp_enqueue_script
accepts 5 parameters.
- Handle – Give your script a name.
- Source – Path to your script.
- Dependencies – Which scripts will your script need to function.
- Version – Version number of your script.
- In footer – If false your script will be loaded with
wp_head
. If set to true it will load withwp_footer
.
Browsers of our site will be able to add or remove a WordPress post to their bookmarks by clicking an anchor which reads "Add to bookmarks" or "Remove from bookmarks" located to the bottom of each post.
When either anchor is clicked we will use AJAX and make a request to a PHP script. Once executed we will update the sidebar widget.
Should the browser be logged in as a member of the site they can then save any "Lists of bookmarks" which are currently stored within the session and displayed in the widget.
functions.php...
function px_bookmark_link() { global $post; if(@in_array($post->ID, $_SESSION['bookmarks'])) : $content .= '<a href="'.get_permalink().'" class="delete-bookmark" data-post-id="'.$post->ID.'" data-post-name="'.get_the_title().'">Remove from bookmarks</a>'; else : $content .= '<a href="'.get_permalink().'" class="add-bookmark" data-post-id="'.$post->ID.'" data-post-name="'.get_the_title().'">Add to bookmarks</a>'; endif; return $content; } add_filter('the_tags', 'px_bookmark_link');
This function is called at each iteration of "the loop" by utilising add_filter
and the_tags
as our hook.
We let WordPress know that within this function we want access to items within $wp_query
and consequently $post
. This will allow us to retrieve the_id
, the_title
and the_permalink
.
Some logic is applied when this code executes to determine which link to show. If the user already has the item within their current session we will show a "Remove from bookmarks" anchor and vice versa. in_array()
allows us to check this.
in_array()
will flag notices if error_reporting
has that directive, we use the @
symbol to suppress these notices (hacky).
Using the data returned in $post
we form two anchors for adding and removing bookmarks (all data
attributes important here) to be later used with our AJAX calls in bookmarks.js.
For a full reference of available filters visit the codex.
Step 3 Widget – Proof of Concept
Now we have our link in place let's create the widget which will appear in the sidebar at all times and will be populated or emptied on demand.
The image above reflects the final states of the widget in 3 scenarios.
- No bookmarks whilst being logged in or logged out.
- Bookmarks currently saved in session whilst not logged in.
- Bookmarks currently saved in session whilst being logged in.
The next block of code is placed within widget.php and nested within HTML markup.
if($_SESSION['bookmarks']) : foreach($_SESSION['bookmarks'] as $key => $value) : $keys[] = $key; $start_count = min($keys); endforeach; endif; for($i = $start_count; $i < $start_count + count($_SESSION['bookmarks']); $i++) : if($_SESSION['bookmarks'][$i]) : $bookmark = get_post($_SESSION['bookmarks'][$i]); echo '<li id="bookmark-'.$bookmark->ID.'">'; echo '<a href="'.$bookmark->post_name.'">'.$bookmark->post_title.'</a>'; echo '</li>'; endif; endfor;
When building this project there was a problem with session data that kept cropping up upon output. Some values were being removed but the key was persisting. Setting a $start_count
later to be used when printing the session data solved this problem.
The important thing to note here is how to retrieve items from $_SESSION['bookmarks']
which will be created in the next stage.
At each iteration we use get_post()
to query the WordPress database with the stored integer values in $_SESSION['bookmarks']
. Which will return all the human readable data we need.
if(is_user_logged_in()) : // Show SAVE button else : // Show "LOGIN TO SAVE" message. endif; if($_SESSION['bookmarks']) : // Show CLEAR button endif;
This final piece of logic within widget.php determines which buttons and text to show alongside the widget depending on the state of the current session and
also if the user is logged in or out.
Step 4 Adding Bookmarks via AJAX
jQuery is awesome, here we'll use the delegate
method and listen for clicks on our important anchors. We'll check for the following items being clicked.
- Anchors with a class of
add-bookmark
- Anchors with a class of
delete-bookmark
- Anchors with a class of
clear-bookmarks
Using hasClass
we can test which item has been clicked within the delegate method and serve the desired AJAX
call.
Should you be building this into a larger project please consider using a pubsub pattern.
var $bookmark_widget = $('#px-bookmarks'), $bookmark_form = $('#px-bookmarks form'), $bookmark_widget_list = $('#px-bookmarks .current-bookmarks'), $empty_widget = $('#px-bookmarks p'), $widget_buttons = $('#px-bookmarks .widget-buttons'), $login_notify = $('#px-bookmarks .login-notify'), // This should be changed to reflect your domain. $ajax_path = 'http://yoursite.com/wp-content/themes/bookmark-theme/members/single/bookmarks/ajax.php';
First log some variables so we are not "splashing around in the DOM" too much. All DOM selectors above are located within widget.php.
$(".add-bookmark, .delete-bookmark, .clear-bookmarks").delegate(this, 'click', function(e) { e.preventDefault(); });
We tell jQuery to listen for click on all of the listed classes and via the callback function we will then tell it what to do. The next portions of code to be added will be placed directly after e.preventDefault()
.
Using preventDefault()
is a smarter way of nullifying the default action when JavaScript is present. Here is some discussion surrounding preventDefault()
on Stack Overflow.
The next portions of code to be added will be placed directly after e.preventDefault()
.
var $post_id = $(this).data('post-id'), $post_name = $(this).data('post-name'), $post_href = $(this).attr('href'), $that = $(this);
Once a user has clicked any of the "important anchors" we need to store the data attribute values which were attached to anchors in Step 2. This will allow us to send and retrieve the data we want.
The next code can become a little verbose as we will be showing and hiding elements based on which item has been clicked, with that pre-cursor the code below is the
bare minimum which will function without aesthetics in mind. However please do download the source and look to these lines.
if($that.hasClass('add-bookmark')) { $.ajax({ url: $ajax_path + '?method=add', type: 'GET', data: 'post_id=' + $post_id, success: function(returndata) { if($bookmark_widget_list.children().length === 0) { // Show / hide } $bookmark_widget_list.prepend('<li id="bookmark-'+ $post_id +'"> <a href="'+ $post_href +'">' + $post_name + '</a></li>'); } }); }
Here we use hasClass
to distinguish which item was clicked by using jQuery to search against our clicked item.
Based on the outcome we setup our AJAX
call a little bit differently each time. With the url
and data
being requested and sent each time changing slightly.
Notice ?method=add
appended to $ajax_path
. This is the equivalent of http://site.com/path/to/ajax.php?method=add
.
When adding a bookmark to the current session the only item we need to pass to our PHP code is the id of that post which was stored into the $post_id
variable.
When jQuery receives a successful response we then append that item to the current bookmark list within the widget area as a list item. Using $post_id
, $post_name
and $post_href
here.
When the page is refreshed the code added to widget.php in step 3 will kick in.
On line 7 of the last snippet there is a small subroutine within the success
method which determines if there are any list items present within the widget area. This is the previously-mentioned-slightly-verbose code which does nothing more than show and hide some DOM elements. It has been removed for readability here on Wptuts+. Moving on...
if($that.hasClass('delete-bookmark')) { $.ajax({ url: $ajax_path + '?method=delete', type: 'GET', data: 'post_id=' + $post_id, success: function(returndata) { if($bookmark_widget_list.children().length <= 1) { // Show / hide } $('#bookmark-'+ $post_id).remove(); } }); }
Much like if($that.hasClass('add-bookmark'))
here we check for items clicked that have the class of delete-bookmark
.
Once this subroutine has been entered the url
in the AJAX call is altered slightly by sending over a different query string. Namely ?method=delete
.
When a successful response is returned we remove that list item from the current bookmarks stored within the widget.
Applying some logic in the same fashion as the add-bookmark
subroutine to determine if the item removed is going to be the final item. Based on this outcome here DOM elements are again shown or hidden.
if($that.hasClass('clear-bookmarks')) { $.ajax({ url: $ajax_path + '?method=clear', success: function(returndata) { // Show / hide $('.postmetadata .delete-bookmark').each(function(index) { // Bookmark list cleared, set anchors attached to posts to default. $(this).removeClass().addClass('add-bookmark').html('Add to bookmarks'); }); } }); }
The final code snippet here is used to clear all bookmarks within the widget by setting the url
query string to a different method and resetting any anchors on the page to the default "Add to bookmarks" to reflect an empty $_SESSION
. This is done by utilising jQuery's each method
to find all occurrences of the class delete-bookmark
(anchor attached to posts using add_filter
) and switching it back to the defaultadd-bookmark
.
Step 5 PHP Requested via AJAX
Now we will create the PHP code referenced in the AJAX calls above which will be used to add, delete and clear all bookmarks from the session.
Within ajax.php we will create the following 3 functions.
add_bookmark()
delete_bookmark()
clear_bookmarks()
Let's first create add_bookmark()
function add_bookmark() { $post_id = $_GET['post_id']; if(@!in_array($post_id, $_SESSION['bookmarks'])) : $_SESSION['bookmarks'][] = $post_id; endif; }
First we store the $post_id
previously passed over in bookmarks.js via data: 'post_id=' + $post_id
.
Next we use the in_array
function again to determine if this item should be added to the bookmarks session.
function delete_bookmark() { $post_id = $_GET['post_id']; foreach($_SESSION['bookmarks'] as $key => $value) : $keys[] = $key; endforeach; $start_count = min($keys); if(@in_array($post_id, $_SESSION['bookmarks'])) : for($i = $start_count; $i < $start_count + count($_SESSION['bookmarks']); $i++) : if($_SESSION['bookmarks'][$i] === $post_id) : unset($_SESSION['bookmarks'][$i]); endif; endfor; endif; }
Within the delete_bookmark()
function we again store the $post_id
.
Using the same technique to output our bookmarks in widget.php a $start_count
is established.
Next we determine if the item passed ($post_id
) exists within the bookmarks session via in_array
, and unset any values that are matched.
function clear_bookmark() { session_start(); session_unset(); session_destroy(); }
Finally the clear_bookmark()
function destroys all session data.
We will need one more piece of code for this file to be complete. Head to the top of the file and add the following.
session_start(); $method = $_GET['method']; switch($method) { case "add" : add_bookmark(); break; case "delete" : delete_bookmark(); break; case "clear" : clear_bookmark(); break; }
We use session_start()
to resume the current session. This is crucial here.
Next we store the method which is sent over with url
in our $.ajax
calls.
Based on the current value of $method
we call the appropriate function.
Step 6 Bookmarks on Members Profiles
The files we will be dealing with for the remainder of this tutorial are listed below.
- members/single/home.php – This file is a modified version of bp-default/members/single/home.php.
- members/single/bookmarks/loop.php – Used to retrieve any previously saved member bookmark lists.
- members/single/bookmarks/remove.php – Used to delete any saved bookmark lists.
- members/single/bookmarks/save.php – Used to save any bookmark lists stored within the current session.
- members/single/bookmarks/view.php – Used as a makeshift template loader.
Inside home.php we will add a list item to the unordered list within the div with an id of item-nav
.
Using the $bp
global we can quickly form the URL required.
global $bp; echo '<li><a href="'.$bp->displayed_user->domain.'?component=bookmarks">Bookmarks</a></li>';
This is one of smaller sins we make along the road to demonstrate proof of concept. However to re-iterate proof-of-concept and speedy development is the important factor here.
Should we decide to expand this feature more we would look to using BuddyPress hooks.
if($_GET['component'] == 'bookmarks') : locate_template(array('members/single/bookmarks/view.php'), true);
Still within home.php we check against the query string which will allow us to serve custom templates.
if(!$_GET['action']) : locate_template(array( 'members/single/bookmarks/loop.php'), true); elseif($_GET['action'] == 'save' && is_user_logged_in() && bp_is_home()) : locate_template(array( 'members/single/bookmarks/save.php'), true); elseif($_GET['action'] == 'remove' && is_user_logged_in() && bp_is_home()) : locate_template(array( 'members/single/bookmarks/remove.php' ), true); endif;
Within view.php (our make-shift template loader) we check for 2 actions and if none has been defined we show the list of saved bookmarks.
Back in step 3 some logic was added to determine which anchors to show within the widget based on the current state of $_SESSION['bookmarks']
and whether or not the user was logged in.
Let's create a small table in the database which will be used to store a list of bookmarks which correspond to each member.
DROP TABLE IF EXISTS `bookmarks`; CREATE TABLE `bookmarks` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `created` date NOT NULL, `post_ids` text NOT NULL, `list_name` text NOT NULL, PRIMARY KEY (`id`) )
The MySQL above will create a new table with 5 fields for us to store bookmark data.
Once created it's time to move into save.php.
Whilst the user is accessing save.php we will present a form with a text input, here the user will be required to give a label to the list of bookmarks he or she would like to save.
Once a label has been provided we will store each bookmark list as a row within the database (for later retrieval) and clear the current session.
if(!$_POST['px-bookmark-list-name']) : // Present form asking to give list a name // Stage 1 elseif($_POST['px-bookmark-list-name']) : // Label supplied store to database. // Stage 2 endif;
Now within stage 1 of save.php...
// If form submitted but no label supplied present error. if($_POST['submit'] && !isset($_POST['px-bookmark-list-name'])) : echo '<p class="error">A label is required.</p>'; endif; // Establish the start counter if($_SESSION['bookmarks']) : foreach($_SESSION['bookmarks'] as $key => $value) : $keys[] = $key; endforeach; $start_count = min($keys); endif; // Loop over items and store in hidden form fields. for($i = $start_count; $i < $start_count + count($_SESSION['bookmarks']); $i++) : if($_SESSION['bookmarks'][$i] !== NULL) : $bookmark = get_post($_SESSION['bookmarks'][$i]); echo '<input type="hidden" name="px-post-url[]" value="'.$bookmark->post_name.'" />'; echo '<input type="hidden" name="px-post-name[]" value="'.$bookmark->post_title.'" />'; echo '<input type="hidden" name="px-post-id[]" value="'.$bookmark->ID.'" />'; echo '<input type="submit" name="submit" value="Save List">'; endif; endfor;
First we display an error if no label has been supplied.
Next we use the same technique from widget.php and ajax.php to establish a start counter and iterate over the session data.
Finally we output some form fields with the help of get_post
.
global $bp; foreach($_POST['px-post-id'] as $value) : $posts_to_save[] = $value; endforeach; $posts = serialize($posts_to_save);
During stage 2 of save.php we gain access to the $bp
global.
We loop over the $_POST
data and store posts to be saved as an array. This is then serialized and stored into the $posts
variable.
$list_name = $_POST['px-bookmark-list-name']; $query = $wpdb->insert( 'bookmarks', array( 'user_id' => $bp->loggedin_user->id, 'created' => current_time('mysql'), 'post_ids' => $posts, 'list_name' => $list_name ), array( '%d', // user_id '%s', // created '%s', // post_ids '%s' // list_name ) );
Next we store the label supplied by the user for this bookmark list into a variable and utilise WPDB
to insert the row to the database.
if($query) : echo '<div id="message" class="updated">'; echo '<p>List saved.</p>'; echo '</div>'; session_start(); session_unset(); session_destroy(); else : echo '<div id="message" class="error">'; echo '<p>There was an error.</p>'; echo '</div>'; endif;
Finally we check if the query was successful and unset session data, otherwise display an error.
Step 7 Retrieving and Deleting Bookmarks
Remember, in view.php when no particular action
is set we will load loop.php. In this file $wpdb
will be used to retrieve and output any bookmarks lists.
global $bp; $displayed_user = $bp->displayed_user->id; $bookmark_lists = $wpdb->get_results( "SELECT * FROM bookmarks WHERE user_id = $displayed_user ORDER BY id DESC");
Using the $bp
global the id of the profile being displayed is stored into the $displayed_user
variable.
Next we perform a query against the bookmarks table with the stored $displayed_user
as a where condition.
if($bookmark_lists) : foreach($bookmark_lists as $bookmark_list) : echo $bookmark_list->list_name; $post_ids = unserialize($bookmark_list->post_ids); foreach($post_ids as $post_id) : $bookmark = get_post($post_id); echo '<a href="http://yoursite.com/'.$bookmark->post_name.'" title="View bookmark">'.$bookmark->post_title.'</a>'; endforeach; endforeach; endif;
When results are returned they are displayed by looping over the data and outputting accordingly. Here we make use of unserialize
to reverse the effects ofserialize
which was used to store bookmarks previously.
We can make one more addition to the previous block of code.
if(is_user_logged_in() && bp_is_home()) : echo '<a href="'.$bp->displayed_user->domain.'?component=bookmarks&action=remove&id='.$bookmark_list->id.'" class="delete-list">Delete list</a>'; endif;
This will add an anchor to the title of each list which when clicked will pass a new action of remove
along with the bookmark list id.
Which leads us to our final stage... Deleting a bookmark list. Open up remove.php and let's finish this off.
if(isset($_GET['action']) == 'remove' && isset($_GET['id'])) : $list_id = $_GET['id']; global $bp; $user_id = $bp->loggedin_user->id; $query = $wpdb->query("DELETE FROM bookmarks WHERE id = $list_id AND user_id = $user_id"); if($query) : echo '<div id="message" class="updated">'; echo '<p>List deleted.</p>'; echo '</div>'; else : echo '<div id="message" class="error">'; echo '<p>There was an error.</p>'; echo '</div>'; endif; endif;
First we make sure the action is set to remove and there is an id to build a small query with.
Next we store some user data and run the query. Users should only be able to delete lists that belong to them, using $bp->loggedin_user->id
helps us achieve this.
Finally we serve a message depending on the outcome.
Conclusion
Over the course of this tutorial a number of techniques have been applied. Using jQuery, raw PHP, WordPress conventions and BuddyPress we have been able to illustrate a nice feature to be added to your social network site powered by WordPress and BuddyPress.
Out of the box BuddyPress does not come with a bookmarks manager attached to member profiles and there isn't a plugin out there that functions exactly like this.
A bookmarks manager is one example but this could be anything.
The main goal of this tutorial was to illustrate how quickly and effectively you can hi-jack BuddyPress to demonstrate proof of concept.
With some know-how this could be put together in an evening with little trouble at all. The time commitment is tangible and could be factored into a monthly maintenance contract.
However if a client desired more features from the bookmarks manager such as a dashboard widget and more in-depth features you would be stepping into the realms of a plugin.
Data has not been sanitised in this tutorial so please make sure if you are to place this into a "real world" environment, go through a little bit of validation before-hand.
I hope you have enjoyed this tutorial and any discrepancies you may find please do leave a comment and will do my best to help you through it.
Comments