Drupal 7

How to build a "Like" button functionality

By: Jesus Larrubia

Main goal

  • Allow authenticated users to “Like” content and comments.

Requirements

  1. Authenticated users cannot ‘like’ their own content.
  2. Authenticated users can view a list of all content and comments they ‘like’ on a tab in their profile.
  3. Authenticated users can see the total number of ‘likes’ they have received across all content and comments on their profile page.

Additional requirements to add complexity!

  • Contrib modules must be production ready.
  • Two solutions are required:
    • Using contrib modules
    • Not using contrib modules

My requirement

To make the challenge more difficult and enhance the enjoyment:

  • Include a dynamic counter next to the like link.

Solutions. General aspects

  • Two completely different solutions.
  • Virtually same result.
  • Two test sites in the platform Pantheon.

A. The "community" solution

Investigation

In drupal.org we can find different contrib modules that provide a "like" functionality:

  • Flag & DLike

  • Like button

  • Like & Dislike

Flag+DLike modules

*THE CHOSEN*

Why?

  • Production ready.
  • Provide the required functionality:
    • Flag module allows users to flag entities. Typically used to bookmark content.
    • DLike module inserts a flag counter.

 

Flag+DLike

More reasons...

  • It was a known solution.
  • Light solution. It doesn´t depend on third party plugins.
  • Extensive Views integration.
  • Supported by Lullabot.

 

Like button

Advantages

  • It provides the required functionality, highly customizable, integration with Views…

 

Like button

Disadvantages

  • Depends on a third commercial plugin.
  • Limitations: 1 button per page.
  • It includes JS files and tracking pixels out of your control.

 

Like & Dislike

Disadvantages

  • Only beta version.

 

Implementation

The modules provide the necessary functionality, through their UI, to:

  • Created two "Like" flags (for comments and content), with an added counter. (Main goal, My requirement)
  • Control user access to the "like" link by :
    • Role: authenticated users. (Main goal)
    • Content authorship: Users may only flag content of others (Requirement 1).

Implementation

Implementation

Requirement 2. Liked content/comments list

Two views configured through UI:

  • Created menu user tab.
  • Contextual filter to show only comments/nodes liked by the user.
  • Mode views:
    • Teaser (content).
    • Full (comments).

Implementation

Implementation

Requirement 3. Received likes

Two blocks (views-block display):

  • Aggregation (SUM operator).
  • Contextual filter to show only comments/nodes posted by the user.
  • Only displayed in user/* pages (user account).

Implementation

B. The custom solution

Main goal

  • Added a pseudofield mylike to comment and node entities.
/**
 * Implements hook_field_extra_fields().
 */
  ...
  // Includes the pseudo field mylike for every comment/content type.
  foreach ($entities as $entity) {
    $extra[$entity][$bundle] = array(
      'display' => array(
        'mylike' => array(
          'label' => t('My like'),
          'description' => t('Custom like button.'),
          'weight' => -5,
        ),
      ),
    );
  }
  ...

Main goal

  • Displayed as link in the full view mode.
/**
 * Implements hook_entity_view().
 *
 */
  ...
  if ($view_mode == 'full') {
    $entity->content['mylike'] = array(
      '#markup' => mylike_create_link($entity, $type),
    );
  }
}

Main goal

/**
 * It creates a renderable mylike link.
 */
function mylike_create_link($entity, $type) {
  
  ...

  // Creates the like/unlike link.
  $link_wrapper['content']['link'] = array(
    '#type' => 'link',
    '#title' => t('My Like (@likes_number)', array('@likes_number' => $likes_number)),
    '#href' => "mylike/$type/" . $entity_id . "/like",
  );
 ...

Main goal

mylike.install
/**
 * Implements hook_schema().
 */
function mylike_schema() {

  $schema['mylike'] = array(
    'fields' => array(
      'mlid' => array(
        'description' => 'The unique mylike ID.',
        ...
      'entity_type' => array(
        'description' => 'The liked entity type, "node" or "comment".',
        ...
      'entity_id' => array(
        'description' => 'The unique ID of the liked entity, (cid, or nid).',
        ...
      'uid' => array(
        'description' => 'The user ID who likes the entity.',
        ...

Created table to save the necessary information:

Main goal

Workflow

User "likes" x

(clic mylike link)

Send request

Handle request

(hook_menu)

Save mylike in DB

(callback function )

like/unlike action

Return page

New mylike link

(count +1)

Client side

Server side

Main goal (My requirement)

  • Added dynamic link behavior through Drupal AJAX.
  // Creates the like/unlike link with the AJAX property enabled.
  if (!$user_likes) {
    $link_wrapper['content']['link'] = array(
      '#type' => 'link',
      '#title' => t('My Like (@likes_number)', array('@likes_number' => $likes_number)),
      '#href' => "mylike/$type/" . $entity_id . "/like/nojs",
      '#ajax' => array(
        'wrapper' => "link-$type-$entity_id",
        'method' => 'html',
      ),
    );
  }
  • Source (Spanish): http://www.ladrupalera.com/drupal/desarrollo/drupal7/eventos-ajax-en-elementos-de-pagina-distintos-de-formulario

/**
 * Ajax callback. It manages actions (clics) in mylike links.
 */
function mylike_link($entity_type, $entity_id, $op, $type = 'ajax') {

  ...

  // Deliver Ajax response.
  if ($type == 'ajax') {
    $commands = array();
    $ajax_link = mylike_create_link($entity, $entity_type);
    $commands[] = ajax_command_replace("#link-$entity_type-$entity_id", $ajax_link);
    $commands[] = ajax_command_append("#link-$entity_type-$entity_id", $messaje_output);
    $page = array('#type' => 'ajax', '#commands' => $commands);
    ajax_deliver($page);
  }

Main goal (My requirement)

Main goal

  • Created permission to control user access to mylike link by role (authenticated).
/**
 * Implements hook_permission().
 */
function mylike_permission() {

  return array(
    // It allows users to like other users content/comments.
    'access mylike link' => array(
      'title' => t('Access mylike link'),
      'description' => t('Access mylike link'),
    ),

Requirement 1

  • Created function to extend access requirements. (liked content ≠ own content)
/**
 * Access function callback. Return true if the current user can like an entity.
 */
function mylike_link_access($entity_type, $entity_id) {

  ...
  // Only users with the permission 'access mylike link' (authenticated users)
  // and differents to the user author can access to the link.
  if (user_access('access mylike link') && $entity->uid != $user->uid) {
    return TRUE;
  }

Requirement 2

  • Created list of liked content and comments:
    • New menu item - User tab.
    • Retrieved by query.
    • Displayed as table (default Drupal theme).
    • Rows:
      • Content - Teaser
      • Comments - Full

Requirement 2

/**
 * Menu function callback. Display a liked entity history (content and comments).
 */
function mylike_user_content_liked($user, $type) {
  ...
  $query = db_select('mylike', 'ml')
    ->condition('ml.entity_type', $entity_type, '=')
    ...
  $entities = $query->execute()->fetchAll();

 foreach ($entities as $id_info) {
      $node_view = node_view(node_load($id_info->entity_id), $view_mode);
      $row['data'] = array(render($node_view));
  
  // Return properly styled output.
  return array(
    '#theme' => 'table',
    '#rows' => $rows,
  );
}

Requirement 3

  • Created two blocks to show received likes:
    • For comments
    • For content
/**
 * Implements hook_block_info().
 */
function mylike_block_info() {

  $blocks = array();
  // Total user received likes for content.
  $blocks['mylike_user_content_likes'] = array(
    'info'  => t('User, content received likes'),
  );
  // Total user received likes for comments.
  $blocks['mylike_user_comment_likes'] = array(
    'info' => t('User, comment received likes'),
  );

Requirement 3

  • Extended user (summary) profile to display total of received likes.
/**
 * Implements hook_user_view().
 */
function mylike_user_view($account, $view_mode) {

  // It includes the total of likes received by the user for comments/content
  // in the profile summary.
  $likes = mylike_count_user_received_likes('both', $account->uid);
  $account->content['summary']['mylike'] = array(
    '#type' => 'user_profile_item',
    '#title' => t('Your total count'),
    '#markup' => t('Received likes: @likes', array('@likes' => $likes)),
    '#attributes' => array('class' => array('blog')),
  );
}

Questions? :)

Drupal likes

By Jesus Larrubia

Drupal likes

Drupal tasks - like functionality

  • 361