0

I have a WordPress blog that is all user based Authors that submit content and such. Currently in the site, you can view other authors profiles but basically just view their content from the author.php template.

Where would I even begin to build a plugin (or find a plugin) that allows authors to "follow" other authors and then on another page query the authors they follow to show certain content from their followed authors like comments they made, posts they made, etc.

I don't want to add BuddyPress or go that route but I have seen similar plugins for BuddyPress but not for just WordPress.

I imagine I need to do something like create an array of users they follow in their user meta then query those users to display the "activity". Is that sound right?. Any insights, tutorials, plugins etc that may help get my foot in the door for this?

Thanks!

4
  • 1
    Why not BuddyPress? It is made for that.
    – fuxia
    Feb 10, 2012 at 21:41
  • Because the site is too far developed to switch everything to BuddyPress. Plus, we are thinking this would be more of enhancement then "essential" so we would rather try to build it on what we already have done. I'm also not too familiar with how BuddyPress works so there is a bit of a time constraint in that aspect, as well.
    – Zach Reed
    Feb 10, 2012 at 21:47
  • I also have not seen too many good examples of BuddyPress being used on non forum style sites so I'm not sure how it would be done with the current setup we have.
    – Zach Reed
    Feb 10, 2012 at 21:48
  • I'm curious if anybody ever actually found an answer to this question. Dec 26, 2012 at 0:20

2 Answers 2

5

Just a plugin concept …

  1. Add a form with a button to each user profile, post or wherever you want it named Follow or Unfollow. Show the button only if is_user_logged_in(). You may use a widget for the form.

  2. On form submit update a user meta named follows for the user who clicked the button and another one named followers for … well … the user who just got a new follower. Use the user IDs as a serialized array (yes, ugly, I know). Or use a custom table, that might work better and faster.

  3. Create a dashboard widget or a whole page where the access is restricted to users who can follow or be followed. Query for the user meta follows to get a list of all authors the current user has subscribed to and then query for the posts from these users.

  4. Get the user meta followers and print a nice list of her followers.

5
  • That is what I was thinking. Thanks for the step by step on the concept, Toscho. Exactly what I was looking for. Thanks!
    – Zach Reed
    Feb 10, 2012 at 23:12
  • 2
    ZachReed, did you ever developed the plugin? and if so, are you willing to share? ;) and maybe also think about closing this question and give @toscho the answer... Feb 17, 2012 at 16:50
  • @AsafChertkoff willing to share... see my answer.
    – iantsch
    Mar 15, 2016 at 11:45
  • @toscho I'm wondering if user meta or a custom table would be better. Both have pitfalls (scaling with user meta and the non-standard{ness} of a custom table). If you had to choose either, which would it be and why? PS sorry for coming late to the party :) May 18, 2017 at 23:33
  • @henrywright Nowadays (five years later!) a custom table without any second thought. The meta tables are just too slow, and their values are loaded even when you might not need them.
    – fuxia
    May 19, 2017 at 7:06
1

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;
}
16
  • 1
    if all your functions are statis, then you do not write OOP code at all.All you have done here is to bloat the text of the code without any advantage that you might get from doing OOP Mar 15, 2016 at 11:07
  • that is a question of code design... e.g. mbt_news_feed_init instead of mbtNewsfeed::init, but i agree if you look at the code from strict OOP goggles… it is not OOP
    – iantsch
    Mar 15, 2016 at 11:16
  • to downvote an answer because you do not like the code design is ridiculous
    – iantsch
    Mar 15, 2016 at 11:25
  • 1
    1. As the code on this site is example for other people to how to write proper code, we should put ourself to higher standard. 2. The idea in OOP is that you have a template for a type of object usually called "class" which you can use to instantiate objects of that type when needed. If you never even instantiate an object from the class then you don't have objects at all and therefor no need for a class. 3. that SO question relates to the peculiarities of java in which it is hard or impossible to do anything without putting it in a class, something that is not true to PHP. Mar 15, 2016 at 12:04
  • 1
    @iantsch, the concept of wrapping up code in class is a bad code as it makes the code needlessly less readable. Functional programming has also its principles so if you do OOP follow OOP principals, if you do functional, follow functional, but hybrid means you just follow none, which indicates you just click on the keyboard in the hope that something good will happen instead of engineering your code. Mar 15, 2016 at 13:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.