You are here

heartbeatparser.inc in Heartbeat 6.4

Same filename and directory in other branches
  1. 6.3 includes/heartbeatparser.inc
  2. 7 includes/heartbeatparser.inc

HeartbeatParser object

Parses raw activity messages into a stream.

File

includes/heartbeatparser.inc
View source
<?php

/**
 * @file
 *   HeartbeatParser object
 *
 *   Parses raw activity messages into a stream.
 *
 */

/**
 * Class heartbeatParser
 *
 */
class heartbeatParser {
  private $_candidates = array();
  private $_messages = array();
  private $_sets = array();
  private $_timespan_gap = 0;
  private $_allowed_tags = array();
  public $raw_messages = array();
  private function __construct() {
    $this->_allowed_tags = heartbeat_allowed_tags();
  }

  /**
   * Function to remove duplicate messages
   * Duplicate messages whish are exactly the same as already build messages,
   *   delete them and increment the count of the message instance
   * Duplicate reversive messages that occur at the same server request
   *   time are deleted as well
   */
  private function remove_message_duplicates($message_set) {
    global $user;

    // hash holding all unique values
    // if this would be static ...you could do it for all timespan-sets
    $holder = array();
    $duplicates = array();
    $users_relations = array();
    foreach ($message_set as $key => $message) {

      // if an exact identical message occurs > twice contextual wise,
      // then skip it with an incrementation of the frequency
      // of the message. Eg: a node or comment has been edited several times
      // in a too small timespan, then we only show the last one
      $message->template->concat_args['type'] = isset($message->template->concat_args['type']) ? $message->template->concat_args['type'] : 'single';
      if ($message->template->concat_args['type'] != 'single' && ($message->nid > 0 || $message->nid_target > 0)) {
        $id = $message->message_id . '-' . $message->uid . '-' . $message->uid_target . '-' . $message->nid . '-' . $message->nid_target;
        if (in_array($id, $holder)) {
          $message_set[array_search($id, $holder)]->count++;
          unset($message_set[$key]);
        }
        else {
          $holder[$key] = $id;

          //$message->message;
        }
      }

      // Remove duplicates that were created by duplicate user logging
      // This happens when two users were active for the same event
      if (isset($message->variables['duplicate']) && $message->variables['duplicate']) {
        $users_relations[$message->message_id][$message->uid . '-' . $message->uid_target] = array(
          'duplicate' => isset($users_relations[$message->message_id][$message->uid_target . '-' . $message->uid]) ? TRUE : FALSE,
          'uid' => $message->uid,
          'relation' => $message->uid_target,
          'message_id' => $message->message_id,
          'key' => $key,
        );
      }
    }
    if (!empty($users_relations)) {
      foreach ($users_relations as $type => $relations) {

        // Check which of the duplicates should be second
        foreach ($relations as $uid => $relationship) {
          $uid = $relationship['uid'];
          $rel_uid = $relationship['relation'];
          if ($user->uid == $uid && isset($relations[$rel_uid . '-' . $uid])) {
            $duplicates[] = $relations[$rel_uid . '-' . $uid]['key'];
          }
          elseif ($user->uid == $rel_uid) {
            $duplicates[] = $relationship['key'];
          }
          elseif (isset($relations[$rel_uid . '-' . $uid]) && $relationship['duplicate']) {
            $duplicates[] = $relations[$rel_uid . '-' . $uid]['key'];
          }
        }
      }
    }
    $duplicates = array_unique($duplicates);
    foreach ($duplicates as $duplicate_key => $duplicate) {
      unset($message_set[$duplicate]);
    }
    return $message_set;
  }

  /**
   * Prepare message candidates
   * This is an important method that handles several things
   * - Build an id for each time-gap limiting groupable messages
   * - Hold the candidates for each id (and count of messages)
   * - Logic to handle the group_by node or user setting
   */
  private function prepare_candidates($message_set, $set_count = 0) {
    static $singles = 0;

    // Remove duplicate messages in the same set,
    // incrementing the counter on the grouped message
    $message_set = $this
      ->remove_message_duplicates($message_set);
    foreach ($message_set as $key => $message) {
      if (!$message->nid_access || !$message->nid_target_access || !$message->uid_access) {
        continue;
      }
      $type = isset($message->template->concat_args['type']) ? $message->template->concat_args['type'] : 'single';

      // Create a gap id, which is a unique id per time gap
      $gap_id = $this
        ->create_gap_id($message, $type, $set_count);

      // Summaries have to be evaluated for merging
      if ($type == 'summary') {

        // Add a candidates if this one does not exist yet
        if (!isset($this->_candidates[$gap_id])) {
          $this->_candidates[$gap_id] = array(
            'count' => 0,
            'group_target' => $message->template->concat_args['group_target'],
            'variables' => array(),
          );

          // Add the message
          $this->_messages[$gap_id] = $message;
          $this->_messages[$gap_id]->additions->source = array(
            $message->message . $message
              ->delete_button(),
          );
        }
        else {
          $this->_messages[$gap_id]->timestamp = $message->timestamp;
          $this->_messages[$gap_id]->additions->source[] = $message->message . $message
            ->delete_button();
        }

        // Add the counters and variables needed for merging in groups
        $this->_messages[$gap_id]->target_count++;

        // Message occurrency, first time is 1
        $this->_candidates[$gap_id]['variables'][] = $message->variables;
        $this->_candidates[$gap_id]['count']++;

        // variable count, NOT the same as target_count
        $this->_messages[$gap_id]->uaids[] = $message->uaid;
      }
      elseif ($type == 'count') {
        $message_template = str_replace("%times%", $message->count, $message->message);
        $message->message = str_replace("%count%", $message->count, $message_template);
        $this->_messages[$gap_id] = $message;
      }
      else {

        // Autoincrement the singles to make the gap_id unique
        $gap_id .= '_' . $singles;
        $this->_messages[$gap_id] = $message;
        $singles++;
      }
    }
  }

  /**
   * Function to clean up dirty messages and xss attacks
   */
  private function clean_up() {
    $this
      ->remove_variables_duplicates();
  }

  /**
   * Function to remove duplicate messages valuating the
   * variables and count properties
   */
  private function remove_variables_duplicates() {
    $uniques = array();
    foreach ($this->_candidates as $single_id => $info) {
      $uniques[$single_id] = array();

      // Loop through variables to check if there are identical
      foreach ($info['variables'] as $rid => $value) {
        if (!in_array($value, $uniques[$single_id])) {
          $uniques[$single_id][] = $value;
        }
        else {
          unset($this->_candidates[$single_id]['variables'][$rid]);
        }
      }
    }

    // Re-evaluate the counts
    foreach ($this->_candidates as $single_id => $info) {
      $this->_candidates[$single_id]['count'] = count($this->_candidates[$single_id]['variables']);
    }
    return $uniques;
  }

  /**
   * create_gap_id
   *   Private helper function to build the gap_id
   *
   * @param Object HeartbeatActivity $message
   *   Object that holds the data of a heartbeat stream message
   * @param String $type
   *   type of the HeartbeatActivity message
   * @param integer $set_count
   *   The count id of the current timespan where
   *   data must be grouped
   *
   * @return $gap_id
   *  A unique id to summarize data within a certain timespan
   */
  private function create_gap_id($message, $type, $set_count) {

    // Variable to hold the unique grouping gap id.
    // Extending the gap id will result in summaries
    // and groups of identical and related messages.
    $gap_id = 'BEAT_' . $set_count . '_' . $message->message_id;

    // Extend the gap id with the node_type if available
    // but this is too dangerous because i don't know if people intend to
    // separate the node types from merging ...
    if (!empty($message->variables['@node_type'])) {
      $gap_id .= '_' . $message->variables['@node_type'];
    }

    // For single types, we dont need larger unique id's
    if ($type == 'single') {
      return $gap_id . '_single';
    }
    elseif ($type == 'count') {
      return $gap_id . '_count_' . $message->uid;
    }
    if (isset($message->template->concat_args['group_by'])) {
      $grouptarget = $message->template->concat_args['group_target'];
      $groupby = $message->template->concat_args['group_by'];

      // group by node will result in a summary of
      // users performing the activity
      if ($groupby == 'node') {
        $gap_id .= '_' . $grouptarget . '_node_' . $message->nid;
      }
      elseif ($groupby == 'user') {
        $gap_id .= '_' . $grouptarget . '_user_' . $message->uid;
        if ($message->nid_target > 0) {
          $gap_id .= '_' . $message->nid_target;
        }
      }
      elseif ($groupby == 'node-target') {
        $gap_id .= '_' . $grouptarget . '_user_' . $message->uid . '_node_target_' . $message->nid_target;
      }
      elseif ($groupby == 'user-user') {
        $gap_id .= '_user_relation';

        //$gap_id .= '_'. ($target_uid == 0 ? $message->uid : $target_uid);
        $group_target = $message->template->concat_args['group_by_target'];
        $string = $message->variables["@" . $group_target];
        $group_gap_string = strip_tags(drupal_strtolower($string));
        $gap_id .= '_' . $group_gap_string;
      }
      else {

        // Handle the fall-backs as unique messages
        $gap_id .= '_' . $message->uid . '_' . (int) $message->uid_target;
      }
    }
    return $gap_id;
  }

  /**
   * Function to rebuild the messages in groups
   * all sets are handled already
   */
  private function rebuild_in_groups() {
    global $base_url;
    $max_items_to_group = variable_get('heartbeat_activity_grouping_how_many', 5);

    // Check the candidates and make the rebuild array
    foreach ($this->_candidates as $single_id => $candidate) {

      // if a candidate exists for this message and it has more than one count
      // Take care!! not message->count because this could be set with the sql as well
      // The purpose would be to fill %times% or its alias %count%
      if ($candidate['count'] > 1) {

        // rebuild the message using the candidate variables
        $this->_messages[$single_id]
          ->create_grouped_message($candidate, $max_items_to_group);
      }
    }
  }

  /**
   * Creates an instance of heartbeat as singleton
   */
  public static function instantiate($type = 'cached') {
    static $instances;
    if (!$instances) {
      $instances = array();
    }
    if (!isset($instances[$type])) {
      $instances[$type] = new HeartbeatParser();
    }
    return $instances[$type];
  }

  /**
   * Get the tags to allow in messages
   */
  public function get_tags() {
    return $this->_allowed_tags;
  }

  /**
   * Set the first time , start time of the gap
   */
  public function set_timespan_gap($gap) {
    $this->_timespan_gap = $gap;
  }

  /**
   * build sets of messages
   */
  public function build_sets($messages_raw) {

    // The start is the end of the current day.
    $renewal_time = $_SERVER['REQUEST_TIME'] - (3600 * date("H") + 60 * date("i") + date("s")) + 24 * 3600;

    // Retrieve variables to put on the object.
    $show_time = variable_get('heartbeat_show_message_times', 1);
    $time_info_grouped = variable_get('heartbeat_show_time_grouped_items', 1);

    // Check access on the buttons and put their state onto the object
    $delete_access = _heartbeat_activity_stream_delete_access();
    $actor_access = variable_get('heartbeat_activity_stream_actor_delete', 1) && user_access('delete own heartbeat activity logs');
    $raw_messages = array();
    foreach ($messages_raw as $key => $message) {

      // Set basic properties for all messages.
      $message->display_time = isset($message->template->variables['heartbeat_show_message_times']) ? $message->template->variables['heartbeat_show_message_times'] : $show_time;
      $message->time_info_grouped = $time_info_grouped;
      $message->delete_access = $delete_access;
      $message->actor_access = $actor_access;

      // if the time with the gap exceeds the starttime.
      if ($renewal_time >= $message->timestamp) {

        // reset the start time of the set
        $renewal_time = $message->timestamp - $this->_timespan_gap;
      }
      $this->_sets[$renewal_time][$key] = $message;
    }
  }

  /**
   * Merges sets of messages to fully formatted messages
   * regenerated at runtime to group settings
   */
  public function merge_sets() {
    $set_count = 0;
    foreach ($this->_sets as $message_set) {
      $this
        ->prepare_candidates($message_set, $set_count);
      $set_count++;
    }
    $this
      ->clean_up();
    $this
      ->rebuild_in_groups();
    return $set_count;
  }

  /**
   * Gets the messages as they exist for the time asked
   */
  public function get_messages($limit = 0) {
    if ($limit > 0) {
      return array_slice($this->_messages, 0, $limit);
    }
    return $this->_messages;
  }

}

Classes

Namesort descending Description
heartbeatParser Class heartbeatParser