Wordpress favorite system without plugin learning method

Viewed 1782

Good afternoon devs, I developed a favorite system using Wordpress and php, it works as follows, clicking on the add favorites icon I send an ajax request to php with the favorite id and the user id logged in. php processes this by saving in user_meta the information with number of favorite users and in the logged user an array with the ids of the favorites. Anyway, the question is, the system is working, but whenever I develop something I keep wondering if there is a way to improve the code, if there is a way to do it in a better way, I will leave my code here.

All this for learning ok?

HTML

<div class="stats -favorites">
    <a class="icon -click <?php echo is_favorite(get_current_user_id(), $user_ID) ? 'fa' : 'far' ?> fa-heart" data-js="favorite" data-favorite="<?php echo $user_ID; ?>" data-user="<?php echo get_current_user_id(); ?>"></a>
    <span class="label" data-js="favorite_label">
        <?php echo get_favorites_num( $user_ID ); ?>
    </span>
    <span class="value">favoritos</span>
</div>

JS

 function setFavorite(userid, favoriteid) {

    var favorite_field = $('[data-js="favorite"]');
    var favorite_label = $('[data-js="favorite_label"]');

    $.ajax({
        url        : appmeninas_ajax_params.ajaxurl,
        data       : {
                        'action'     : 'setfavorite',
                        'userid'     : userid,
                        'favoriteid' : favoriteid,
                    },
        dataType   : 'json',
        type       : 'POST',
        cache      : false,

        beforeSend : function( xhr ) {
            favorite_field.removeClass('far fa fa-heart').addClass('fas fa-circle-notch fa-spin');
        },

        success : function( data ) {

            var icon = (data.is_favorite) ? 'fa fa-heart' : 'far fa-heart';

            favorite_field.removeClass('fas fa-circle-notch fa-spin');
            favorite_field.addClass(icon);

            favorite_label.html('');
            favorite_label.append(data.favorites_num);

        }
    });

};

$('[data-js=favorite]').click(function() {
    var favoriteid = $(this).data('favorite');
    var userid = $(this).data('user');
    setFavorite(userid, favoriteid);
});

PHP

function setfavorite() {

    $userid = $_POST['userid'];
    $favoriteid = $_POST['favoriteid'];

    // user require favorite
    $favorites_list = get_field( 'favorites_list', 'user_' .$userid );
    $favorites_num = get_field( 'favorites', 'user_' .$favoriteid );

    if ( !$favorites_list ) {
        $favorites_list = [];
    }

    // profile favorite
    if ( in_array( $favoriteid, $favorites_list ) ) {

        $favorites_num--;
        $tmp = array_search( $userid, $favorites_list );
        array_splice( $favorites_list, $tmp, 1 );
        $is_favorite = false;

    } else {

        $favorites_num++;
        $favorites_list[] = $favoriteid;
        $is_favorite = true;

    }


    // set favorite counter
    update_user_meta( $favoriteid, 'favorites', $favorites_num );

    // set favorite list
    update_user_meta( $userid, 'favorites_list', $favorites_list );


    echo json_encode( array(
        'favorites_num'  => $favorites_num,
        'favorites_list' => $favorites_list,
        'is_favorite'    => $is_favorite,
    ) );

    die();

}

function is_favorite($userid, $favoriteid) {
    $favorites_list = get_field( 'favorites_list', 'user_' .$userid );
    return in_array( $favoriteid, $favorites_list );
}
function get_favorites_num( $userid ) {
    if ( get_field( 'favorites', 'user_' .$userid ) ) {
        return get_field( 'favorites', 'user_' .$userid );
    } else {
        return '0';
    }
}


add_action('wp_ajax_setfavorite', 'setfavorite');
add_action('wp_ajax_nopriv_setfavorite', 'setfavorite');
1 Answers

i maybe will implement a same function in the next 2 months. i allready started researching plugins but as your question states, it does not seems to be very hard.

I try to come back then but first i need a frontend profile page where i can list the favorites.

The first impression looks good so far but first i would give attention to the $_POST array and sanitize and maybe validate the values because sometimes not only your ajax will call.

if ( isset($_POST['userid']) && isset($_POST['favoriteid']) ) { // Both $_POST values exist

  $userid = filter_var($_POST['userid'], FILTER_SANITIZE_NUMBER_INT);
  $favoriteid = filter_var($_POST['favoriteid'], FILTER_SANITIZE_NUMBER_INT);

  if ( filter_var($userid, FILTER_VALIDATE_INT) && filter_var($favoriteid, FILTER_VALIDATE_INT) ) {
    // $_POST was save, both values are Integers
  }
}

The second one is related to get_field which is a function provided by the ACF Plugin. therefor when deactivating it or it gets replaced with JCF, it may cause errors.

You can avoid this by using if ( function_exists('get_field') ) {. Then your code only stops working when ACF gets deactivated.

Otherwise it seems not neccessary to use the ACF function and you can use the WP native function get_user_meta instead:

function is_favorite($userid, $favoriteid){
  $favorites_list = get_user_meta($userid, 'favorites_list', true);
  // check the new output instead of get_field
  return in_array( $favoriteid, $favorites_list );
}

and also all calls to get_field('favorites', 'user_' .$favoriteid) seem to be wrong then. The ACF Docs say the the second parameter of get_field ist a post ID so i don't know what 'user_' means then. I would call:

function get_favorites_num($favoriteid){
  return get_user_meta($favoriteid, 'favorites', true) || 0;
}

i now have my own favorite system ready where users can favorite posts from a specific post_type

HTML

    <?php while ( have_posts() ) : the_post() ?>
      <?php 

        $customPostsMeta = get_post_custom();
        $favorite_class = 'favorite-me disabled';
        $fav_count = isset($customPostsMeta['_favorites']) ? intval($customPostsMeta['_favorites'][0]) : 0;
        $fav_count_text = $fav_count > 0 ? '(' . $fav_count . ')' : '';
        $fav_count = ' <span class="fav-count clearfix">' . $fav_count_text . '</span>';
        $favorite_title = '';

        if ( is_user_logged_in() ) {
          $user = wp_get_current_user();
          $favorites = get_user_meta($user->ID, '_favorite_posts', true);
          $fav_key = array_search($post_id, $favorites);
          $is_favorite = ( $fav_key !== false );
          if ( $is_favorite ) {
            $favorite_class .= ' is-favorite';
            $favorite_title = ' title="' . get_the_title() . ' ' . __('favorisieren', 'myTheme') . '"';
          } else {
            $favorite_title = ' title="' . get_the_title() . ' ' . __('nicht mehr favorisieren', 'myTheme') . '"';
          }
        }
    ?>
    <a class="<?php echo $favorite_class; ?>" href="#post-<?php the_ID() ?>"<?php echo $favorite_title; ?>><?php echo __('Favorit', 'myTheme')?><?php echo $fav_count; ?></a>
  <?php endwhile; ?>

JS

// i use a small self written JS module frame where this is included as module
// favorite.setup() is fired imediatly, favorite.ready() fires on document ready
// you can see a full version here: https://dev.alphabetisierung.at/wp-content/themes/sandbox_2017/js/actions.js
// line 732

    /**
     * Favorites ajax system
     * =====================
     * https://stackoverflow.com/questions/60468237
     */
    favorite: {
      options: {
        selectors: {
          link: '.favorite-me',
          fav_count: '.fav-count'
        },
        classNames: {
          disabled: 'disabled',
          is_favorite: 'is-favorite',
        }
      },
      events: function(){
        var options = this.options,
            selectors = options.selectors,
            classNames = options.classNames,
            info = this.info;

        this.$favorites.on('click', function(event){
          var post_id = this.hash.replace('#post-', ''),
              $favorite_link = $(this).addClass(classNames.disabled),
              $fav_count = $favorite_link.children(selectors.fav_count),
              $favorite = $.ajax({
                url: myTheme.page.urls.ajax,
                type: 'post',
                data: {
                  action: info.name, // derived from the module name "favorite"
                  verify: myTheme.page.verify, // https://developer.wordpress.org/reference/functions/check_ajax_referer/
                  post_id: post_id
                  // user_id of user who takes the action is not necessary
                }
              });

          $favorite.done(function(data){
            var fav_count = data.hasOwnProperty('fav_count') ? parseInt(data.fav_count) : 0,
                fav_count_text = '',
                is_favorite = data.hasOwnProperty('is_favorite') ? data.is_favorite : false;

            if ( fav_count > 0 ) {
              fav_count_text = '(' + fav_count + ')';
            }
            $fav_count.html(fav_count_text);

            if ( is_favorite && !$favorite_link.is('.' + classNames.is_favorite) ) {
              $favorite_link.addClass(classNames.is_favorite);
            } else {
              $favorite_link.removeClass(classNames.is_favorite);
            }
            $favorite_link.removeClass(classNames.disabled);
          });
          event.preventDefault();
        });
      },
      ready: function ready(){
        var selectors = this.options.selectors,
            classNames = this.options.classNames;

        this.$favorites = $(selectors.link).removeClass(classNames.disabled);
        this.events();
      },
      setup: function setup(){
        var setup = myTheme.info.is_user_logged_in && myTheme.info.post_type === 'my_custom_post_type';
        return setup; // only for my post_type
      }

PHP

add_action('wp_enqueue_scripts', 'myTheme_enqueue_scripts');
add_action('wp_ajax_favorite', 'myTheme_set_favorite');
// wp_ajax_nopriv_{action} is not necessary when feature is only for logged in users


  function myTheme_enqueue_scripts(){
    $data = array(
      'id' => get_the_ID(),
      'urls' => array(
        'ajax' => admin_url('admin-ajax.php'),
        'template' => get_stylesheet_directory_uri(),
      ),
      'verify' => wp_create_nonce('myThemeOrAction_ajax_call'), // used for check_ajax_referer()
      // ...
      'info' => array(
        // ...
        'is_user_logged_in' => is_user_logged_in(),
        'post_type' => get_post_type(),
      ),
    );
    // ...
    wp_localize_script('actions', 'myTheme_data', $data );
  }


function myTheme_set_favorite(){
  check_ajax_referer('myThemeOrAction_ajax_call', 'verify');

  if ( isset($_POST['post_id']) ) {
    $user = wp_get_current_user(); // here we get the user ID of the current user
    $post_id = filter_var($_POST['post_id'], FILTER_SANITIZE_NUMBER_INT);
    $post = get_post($post_id);
    // $fav_id = filter_var($_POST['fav_id'], FILTER_SANITIZE_NUMBER_INT);
    // $fav_user = get_userdata($fav_id); // WP_User
    $is_favorite = false;

    if ( $post instanceof WP_Post ) { // post ID is valid
    // for user favorites it would be
    // if ( $fav_user instanceof WP_User ) {
      $fav_count = intval(get_post_meta($post->ID, '_favorites', true));
      $favorites = get_user_meta($user->ID, '_favorite_posts', true);
      if ( !filter_var($fav_count, FILTER_VALIDATE_INT) ) {
        $fav_count = 0;
      }
      if ( !is_array($favorites) || empty($favorites) ) {
        $favorites = array();
      }
      $fav_key = array_search($post->ID, $favorites);

      if ( $fav_key !== false ) { // is favorite, remove it
        $fav_count--;
        unset($favorites[$fav_key]);
      } else { // is no favorite, add it
        $fav_count++;
        $favorites[] = $post->ID;
        $is_favorite = true;
      }

      // set favorite counter
      update_post_meta($post->ID, '_favorites', $fav_count);

      // set favorite list
      update_user_meta($user->ID, '_favorite_posts', $favorites);

      // Output
      $json = array(
        'fav_count'  => $fav_count,
        'favorites' => $favorites,
        'error' => false,
        'is_favorite' => $is_favorite,
      );
    } else {
      $json = array('is_favorite' => $is_favorite, 'error' => true, 'message' => 'Invalid Post ID');
    }
  } else {
    $json = array('is_favorite' => $is_favorite, 'error' => true, 'message' => 'No post_id Post ID sent');
  }
  // wp_send_json sets the http header and ends the request
  wp_send_json($json);
}

this are the things i noticed on the way:

  • verify referer with wp_create_nonce() and check_ajax_referer()
  • check JSON result data keys for existance with data.hasOwnProperty('key') to avoid possible JS errors
  • check valid WP_User or WP_Post object
  • current users ID with is not necessary to send with POST
  • wp_ajax_nopriv_{action} is not necessary when feature is only for logged in users
  • use wp_send_json() for ending the response

kind regards tom

Related