history.module 5.86 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Records which users have read which content.
6 7 8 9 10 11
 *
 * @todo
 * - Generic helper for _forum_user_last_visit() + history_read().
 * - Generic helper for node_mark().
 */

12
use Drupal\Core\Entity\EntityInterface;
13
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
14
use Drupal\Core\Routing\RouteMatchInterface;
15 16 17 18 19 20 21 22 23

/**
 * Entities changed before this time are always shown as read.
 *
 * Entities changed within this time may be marked as new, updated, or read,
 * depending on their state for the current user. Defaults to 30 days ago.
 */
define('HISTORY_READ_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);

24 25 26
/**
 * Implements hook_help().
 */
27
function history_help($route_name, RouteMatchInterface $route_match) {
28 29
  switch ($route_name) {
    case 'help.page.history':
30
      $output  = '<h3>' . t('About') . '</h3>';
31
      $output .= '<p>'  . t('The History module keeps track of which content a user has read. It marks content as <em>new</em> or <em>updated</em> depending on the last time the user viewed it. History records that are older than one month are removed during cron, which means that content older than one month is always considered <em>read</em>. The History module does not have a user interface but it provides a filter to <a href="!views-help">Views</a> to show new or updated content. For more information, see the <a href="!url">online documentation for the History module</a>.', array('!views-help' => \Drupal::url('help.page', array ('name' => 'views')), '!url' => 'https://drupal.org/documentation/modules/history')) . '</p>';
32 33 34 35
      return $output;
  }
}

36 37 38 39 40 41 42 43 44 45 46
/**
 * Retrieves the timestamp for the current user's last view of a specified node.
 *
 * @param int $nid
 *   A node ID.
 *
 * @return int
 *   If a node has been previously viewed by the user, the timestamp in seconds
 *   of when the last view occurred; otherwise, zero.
 */
function history_read($nid) {
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
  $history = history_read_multiple(array($nid));
  return $history[$nid];
}

/**
 * Retrieves the last viewed timestamp for each of the passed node IDs.
 *
 * @param array $nids
 *   An array of node IDs.
 *
 * @return array
 *   Array of timestamps keyed by node ID. If a node has been previously viewed
 *   by the user, the timestamp in seconds of when the last view occurred;
 *   otherwise, zero.
 */
function history_read_multiple($nids) {
63 64
  $history = &drupal_static(__FUNCTION__, array());

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
  $return = array();

  $nodes_to_read = array();
  foreach ($nids as $nid) {
    if (isset($history[$nid])) {
      $return[$nid] = $history[$nid];
    }
    else {
      // Initialize value if current user has not viewed the node.
      $nodes_to_read[$nid] = 0;
    }
  }

  if (empty($nodes_to_read)) {
    return $return;
80 81
  }

82
  $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid AND nid IN ( :nids[] )', array(
83
    ':uid' => \Drupal::currentUser()->id(),
84
    ':nids[]' => array_keys($nodes_to_read),
85 86 87 88 89 90 91
  ));
  foreach ($result as $row) {
    $nodes_to_read[$row->nid] = (int) $row->timestamp;
  }
  $history += $nodes_to_read;

  return $return + $nodes_to_read;
92 93 94 95 96 97 98 99 100 101 102 103 104 105
}

/**
 * Updates 'last viewed' timestamp of the specified entity for the current user.
 *
 * @param $nid
 *   The node ID that has been read.
 * @param $account
 *   (optional) The user account to update the history for. Defaults to the
 *   current user.
 */
function history_write($nid, $account = NULL) {

  if (!isset($account)) {
106
    $account = \Drupal::currentUser();
107 108
  }

109
  if ($account->isAuthenticated()) {
110
    db_merge('history')
111
      ->keys(array(
112
        'uid' => $account->id(),
113 114 115 116
        'nid' => $nid,
      ))
      ->fields(array('timestamp' => REQUEST_TIME))
      ->execute();
117 118 119 120
    // Update static cache.
    $history = &drupal_static('history_read_multiple', array());
    $history[$nid] = REQUEST_TIME;
  }
121 122 123 124 125 126 127 128 129 130 131
}

/**
 * Implements hook_cron().
 */
function history_cron() {
  db_delete('history')
    ->condition('timestamp', HISTORY_READ_LIMIT, '<')
    ->execute();
}

132
/**
133
 * Implements hook_ENTITY_TYPE_view_alter() for node entities.
134
 */
135
function history_node_view_alter(array &$build, EntityInterface $node, EntityViewDisplayInterface $display) {
136
  // Update the history table, stating that this user viewed this node.
137
  if (($display->getOriginalMode() === 'full') && \Drupal::currentUser()->isAuthenticated()) {
138 139 140
    // When the window's "load" event is triggered, mark the node as read.
    // This still allows for Drupal behaviors (which are triggered on the
    // "DOMContentReady" event) to add "new" and "updated" indicators.
141 142
    $build['#attached']['library'][] = 'history/mark-as-read';
    $build['#attached']['drupalSettings']['history']['nodesToMarkAsRead'][$node->id()] = TRUE;
143 144 145 146
  }

}

147
/**
148
 * Implements hook_ENTITY_TYPE_delete() for node entities.
149
 */
150
function history_node_delete(EntityInterface $node) {
151
  db_delete('history')
152
    ->condition('nid', $node->id())
153 154 155 156 157 158 159 160 161 162
    ->execute();
}

/**
 * Implements hook_user_cancel().
 */
function history_user_cancel($edit, $account, $method) {
  switch ($method) {
    case 'user_cancel_reassign':
      db_delete('history')
163
        ->condition('uid', $account->id())
164 165 166 167 168 169
        ->execute();
      break;
  }
}

/**
170
 * Implements hook_ENTITY_TYPE_delete() for user entities.
171 172 173
 */
function history_user_delete($account) {
  db_delete('history')
174
    ->condition('uid', $account->id())
175 176
    ->execute();
}
177

178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
/**
 * #post_render_cache callback; attaches the last read timestamp for a node.
 *
 * @param array $element
 *  A render array with the following keys:
 *    - #markup
 *    - #attached
 * @param array $context
 *  An array with the following keys:
 *    - node_id: the node ID for which to attach the last read timestamp.
 *
 * @return array $element
 *   The updated $element.
 */
function history_attach_timestamp(array $element, array $context) {
193 194
  $node_id = $context['node_id'];
  $element['#attached']['drupalSettings']['history']['lastReadTimestamps'][$node_id] = (int) history_read($node_id);
195 196
  return $element;
}