mbtNewsFeed Plugin
Any feedback is welcome!
Edit
We discussed about OOP and code design, so there is now a version matching the community requirements. Same principle, same result, but a function based design. – Thanks go to Pieter Goosen and Mark Kaplun for the feedback
Step-by-step:
Thanks to @toscho for the conceptual design
Let's try to wrap it up. We need a button to un/follow a user
function mbt_news_feed_button($user_id, $follow=true) {
$class = 'unfollow';
$label = __('Unfollow','newsfeed');
if (true === $follow) {
$class = 'follow';
$label = __('Follow','newsfeed');
}
return "<a href='#mbt-news-feed' class='button {$class}' data-user_id='{$user_id}'>{$label}</a>";
}
We want to manipulate the follow status of a user. We create two functions: One to follow and one to unfollow.
function mbt_follow() {
$user_id = self::helper_check_data();
$current_user = wp_get_current_user();
$followers = get_user_meta( $current_user->ID, 'following', true);
if (!is_array($followers)){
$followers = array();
}
$followers[$user_id] = 1;
update_user_meta( $current_user->ID, 'following', $followers );
echo self::button($user_id, true);
exit;
}
function mbt_unfollow() {
$user_id = self::helper_check_data();
$current_user = wp_get_current_user();
$followers = get_user_meta( $current_user->ID, 'following', true);
if (is_array($followers) && !empty($followers)){
unset($followers[$user_id]);
} else {
$followers = array();
}
update_user_meta( $current_user->ID, 'following', $followers );
echo self::button($user_id, false);
exit;
}
To help us validating the ajax nonce and filtering the input a helper function is needed.
function mbt_news_feed_helper_check_data() {
if(!wp_verify_nonce( $_POST['_ajax_nonce'], 'mbtNewsFeed' )) {
die(-1);
}
if (!array_key_exists('user_id', $_POST)) {
die(-1);
}
$user_id = absint($_POST['user_id']);
if ($user_id < 1) {
die(-1);
}
return $user_id;
}
To check if the logged in user is currently following the user in question we need another function
function mbt_news_feed_is_following($user_id) {
$followers = get_user_meta( $user_id, 'following', true);
if (empty($followers)) {
return false;
}
if (array_key_exists($user_id, $followers) && $followers[$user_id]) {
return true;
}
return false;
}
We like to keep things clean and tidy, so we create a function that returns an array of all valid AJAX calls for this class
mbt_news_feed_function valid_actions() {
return array(
'mbt_follow',
'mbt_unfollow',
);
}
We want to make our AJAX calls work, so we register our actions outside the class
if (is_admin()){
foreach (mbt_news_feed_valid_actions() as $action) {
add_action('wp_ajax_'.$action, $action);
}
}
Then we want to call the actions too if requested via AJAX
function mbt_news_feed_init() {
if(in_array($_REQUEST['action'], mbt_news_feed_valid_actions())) {
do_action( 'wp_ajax_' . $_REQUEST['action'] );
}
}
To make the button we created work we need some JS.
jQuery(document).ready(function($) {
$('body').on('click', '#mbt-news-feed .follow, #mbt-news-feed .unfollow', function(e) {
e.preventDefault();
var $me = $(this),
action = 'mbt_follow';
if ($me.hasClass('unfollow')) action = 'mbt_unfollow';
var data = $.extend(true, $me.data(), {
action: action,
_ajax_nonce: admin_ajax.nonce
});
$.post(admin_ajax.url, data, function(response) {
if(response == '0' || response == '-1'){
} else {
$($me).parent().empty().html(response);
}
});
});
});
To get the admin AJAX URL and our nonce in the frontend, we need to enqueue and localize the script and enqueue jQuery too, if not already loaded. To save on loading time, we put the script in the footer.
function mbt_news_feed_enqueue_scripts() {
if (is_user_logged_in()) {
wp_enqueue_script('jquery');
wp_enqueue_script( 'mbt-news-feed', plugins_url('newsfeed.js', __FILE__), array('jquery'), NULL, true);
wp_localize_script('mbt-news-feed', 'admin_ajax', array(
'url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('mbtNewsFeed'),
));
wp_enqueue_style( 'mbt-news-feed-css', plugins_url('newsfeed.css', __FILE__) );
}
}
Now we need to display our button on certain pages, (single post/page templates and author archive templates)
function mbt_news_feed_wp_footer() {
if (is_user_logged_in()) {
$render_button = false;
$user_id = 0;
if (is_singular()) {
$render_button = true;
$post_id = get_queried_object_id();
$user_id = get_post_field( 'post_author', $post_id );
}
if (is_author()) {
$render_button = true;
$user_id = get_queried_object_id();
}
if (true === $render_button && $user_id > 0) {
$follow = self::is_following($user_id);
echo "<div id='mbt-news-feed'>";
echo self::button($user_id, !$follow);
echo "</div>";
}
}
}
Before we add all the action we need to filter the posts on the home page. If the user is logged in and follows at least one other author, we will show his individual feed.
static function pre_get_posts($query) {
if (is_user_logged_in()) {
if ( $query->is_main_query() && $query->is_home() && !is_admin()) {
$current_user = wp_get_current_user();
$followers = get_user_meta( $current_user->ID, 'following', true);
if (is_array($followers) && !empty($followers)) {
$followers = array_keys($followers);
$followers[] = $current_user->ID;
$query->set('author__in', $followers);
}
}
}
return $query;
}
Now adding some actions to make the plugin finally work
add_action( 'init', 'mbt_news_feed_init');
add_action( 'wp_enqueue_scripts', 'mbt_news_feed_enqueue_scripts');
add_action( 'pre_get_posts', 'mbt_news_feed_pre_get_posts');
add_action( 'wp_footer', 'mbt_news_feed_wp_footer');
And don't forget the CSS to display your button properly.
Files
newsfeed.php
/**
* mbtNewsFeed for purists
*
* @package mbtNewsFeed
* @author iantsch
* @copyright 2016 mbt.wien
* @license GPL-2.0+
*
* @wordpress-plugin
* Plugin Name: mbtNewsFeed
* Plugin URI: http://wordpress.stackexchange.com/questions/41934/
* Description: If you are logged in and follow some authors, the home feed will only show their posts.
* Version: 1.0.0
* Author: iantsch
* Author URI: http://mbt.wien
* Text Domain: newsfeed
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
*/
function mbt_news_feed_init() {
if(in_array($_REQUEST['action'], mbt_news_feed_valid_actions())) {
do_action( 'wp_ajax_' . $_REQUEST['action'] );
}
}
function mbt_news_feed_pre_get_posts($query) {
if (is_user_logged_in()) {
if ( $query->is_main_query() && $query->is_home() && !is_admin()) {
$current_user = wp_get_current_user();
$followers = get_user_meta( $current_user->ID, 'following', true);
if (is_array($followers) && !empty($followers)) {
$followers = array_keys($followers);
$followers[] = $current_user->ID;
$query->set('author__in', $followers);
}
}
}
return $query;
}
function mbt_news_feed_is_following($user_id) {
$followers = get_user_meta( $user_id, 'following', true);
if (empty($followers)) {
return false;
}
if (array_key_exists($user_id, $followers) && $followers[$user_id]) {
return true;
}
return false;
}
function mbt_news_feed_helper_check_data() {
if(!wp_verify_nonce( $_POST['_ajax_nonce'], 'mbtNewsFeed' )) {
die(-1);
}
if (!array_key_exists('user_id', $_POST)) {
die(-1);
}
$user_id = absint($_POST['user_id']);
if ($user_id < 1) {
die(-1);
}
return $user_id;
}
function mbt_follow() {
$user_id = mbt_news_feed_helper_check_data();
$current_user = wp_get_current_user();
$followers = get_user_meta( $current_user->ID, 'following', true);
if (!is_array($followers)){
$followers = array();
}
$followers[$user_id] = 1;
update_user_meta( $current_user->ID, 'following', $followers );
echo mbt_news_feed_button($user_id, false);
exit;
}
function mbt_unfollow() {
$user_id = mbt_news_feed_helper_check_data();
$current_user = wp_get_current_user();
$followers = get_user_meta( $current_user->ID, 'following', true);
if (is_array($followers) && !empty($followers)){
unset($followers[$user_id]);
} else {
$followers = array();
}
update_user_meta( $current_user->ID, 'following', $followers );
echo mbt_news_feed_button($user_id, true);
exit;
}
function mbt_news_feed_button($user_id, $follow=true) {
$class = 'unfollow';
$label = __('Unfollow','newsfeed');
if (true === $follow) {
$class = 'follow';
$label = __('Follow','newsfeed');
}
return "<a href='#mbt-news-feed' class='button {$class}' data-user_id='{$user_id}'>{$label}</a>";
}
function mbt_news_feed_valid_actions() {
return array(
'mbt_follow',
'mbt_unfollow',
);
}
function mbt_news_feed_enqueue_scripts() {
if (is_user_logged_in()) {
wp_enqueue_script('jquery');
wp_localize_script('jquery', 'admin_ajax', array(
'url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('mbtNewsFeed'),
));
wp_enqueue_script( 'mbt-news-feed', plugins_url('newsfeed.js', __FILE__), array('jquery'), NULL, true);
wp_enqueue_style( 'mbt-news-feed-css', plugins_url('newsfeed.css', __FILE__) );
}
}
function mbt_news_feed_wp_footer() {
if (is_user_logged_in()) {
$render_button = false;
$user_id = 0;
if (is_singular()) {
$render_button = true;
$post_id = get_queried_object_id();
$user_id = get_post_field( 'post_author', $post_id );
}
if (is_author()) {
$render_button = true;
$user_id = get_queried_object_id();
}
if (true === $render_button && $user_id > 0) {
$follow = mbt_news_feed_is_following($user_id);
echo "<div id='mbt-news-feed'>";
echo mbt_news_feed_button($user_id, !$follow);
echo "</div>";
}
}
}
if (is_admin()){
foreach (mbt_news_feed_valid_actions() as $action) {
add_action('wp_ajax_'.$action, $action);
}
}
add_action( 'init', 'mbt_news_feed_init');
add_action( 'wp_enqueue_scripts', 'mbt_news_feed_enqueue_scripts');
add_action( 'pre_get_posts', 'mbt_news_feed_pre_get_posts');
add_action( 'wp_footer', 'mbt_news_feed_wp_footer');
newsfeed.js
jQuery(document).ready(function($) {
$('body').on('click', '#mbt-news-feed .follow, #mbt-news-feed .unfollow', function(e) {
e.preventDefault();
var $me = $(this),
action = 'mbt_follow';
if ($me.hasClass('unfollow')) action = 'mbt_unfollow';
var data = $.extend(true, $me.data(), {
action: action,
_ajax_nonce: admin_ajax.nonce
});
$.post(admin_ajax.url, data, function(response) {
if(response == '0' || response == '-1'){
} else {
$($me).parent().empty().html(response);
}
});
});
});
newsfeed.css
#mbt-news-feed {
display: block;
position: fixed;
bottom: 20px;
bottom: 3.125vw;
right: 20px;
right: 3.125vw;
}
#mbt-news-feed a.button{
display: block;
border-radius: 2px;
border-radius: 0.625vw;
padding: 5px 10px;
padding: 1.5625vw 3.125vw;
color: #fff;
background-color: #333;
background-color: rgba(51,51,51,.8);
opacity: .5;
}
#mbt-news-feed a.button:hover{
opacity: 1;
}