annotate core/modules/history/history.module @ 4:a9cd425dd02b

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:11:55 +0000
parents c75dbcec494b
children 12f9dff5fda9
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /**
Chris@0 4 * @file
Chris@0 5 * Records which users have read which content.
Chris@0 6 *
Chris@0 7 * @todo
Chris@0 8 * - Generic helper for _forum_user_last_visit() + history_read().
Chris@0 9 * - Generic helper for node_mark().
Chris@0 10 */
Chris@0 11
Chris@0 12 use Drupal\Core\Entity\EntityInterface;
Chris@0 13 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
Chris@0 14 use Drupal\Core\Routing\RouteMatchInterface;
Chris@4 15 use Drupal\user\UserInterface;
Chris@0 16
Chris@0 17 /**
Chris@0 18 * Entities changed before this time are always shown as read.
Chris@0 19 *
Chris@0 20 * Entities changed within this time may be marked as new, updated, or read,
Chris@0 21 * depending on their state for the current user. Defaults to 30 days ago.
Chris@0 22 */
Chris@0 23 define('HISTORY_READ_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
Chris@0 24
Chris@0 25 /**
Chris@0 26 * Implements hook_help().
Chris@0 27 */
Chris@0 28 function history_help($route_name, RouteMatchInterface $route_match) {
Chris@0 29 switch ($route_name) {
Chris@0 30 case 'help.page.history':
Chris@4 31 $output = '<h3>' . t('About') . '</h3>';
Chris@0 32 $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>.', [':views-help' => (\Drupal::moduleHandler()->moduleExists('views')) ? \Drupal::url('help.page', ['name' => 'views']) : '#', ':url' => 'https://www.drupal.org/documentation/modules/history']) . '</p>';
Chris@0 33 return $output;
Chris@0 34 }
Chris@0 35 }
Chris@0 36
Chris@0 37 /**
Chris@0 38 * Retrieves the timestamp for the current user's last view of a specified node.
Chris@0 39 *
Chris@0 40 * @param int $nid
Chris@0 41 * A node ID.
Chris@0 42 *
Chris@0 43 * @return int
Chris@0 44 * If a node has been previously viewed by the user, the timestamp in seconds
Chris@0 45 * of when the last view occurred; otherwise, zero.
Chris@0 46 */
Chris@0 47 function history_read($nid) {
Chris@0 48 $history = history_read_multiple([$nid]);
Chris@0 49 return $history[$nid];
Chris@0 50 }
Chris@0 51
Chris@0 52 /**
Chris@0 53 * Retrieves the last viewed timestamp for each of the passed node IDs.
Chris@0 54 *
Chris@0 55 * @param array $nids
Chris@0 56 * An array of node IDs.
Chris@0 57 *
Chris@0 58 * @return array
Chris@0 59 * Array of timestamps keyed by node ID. If a node has been previously viewed
Chris@0 60 * by the user, the timestamp in seconds of when the last view occurred;
Chris@0 61 * otherwise, zero.
Chris@0 62 */
Chris@0 63 function history_read_multiple($nids) {
Chris@0 64 $history = &drupal_static(__FUNCTION__, []);
Chris@0 65
Chris@0 66 $return = [];
Chris@0 67
Chris@0 68 $nodes_to_read = [];
Chris@0 69 foreach ($nids as $nid) {
Chris@0 70 if (isset($history[$nid])) {
Chris@0 71 $return[$nid] = $history[$nid];
Chris@0 72 }
Chris@0 73 else {
Chris@0 74 // Initialize value if current user has not viewed the node.
Chris@0 75 $nodes_to_read[$nid] = 0;
Chris@0 76 }
Chris@0 77 }
Chris@0 78
Chris@0 79 if (empty($nodes_to_read)) {
Chris@0 80 return $return;
Chris@0 81 }
Chris@0 82
Chris@0 83 $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid AND nid IN ( :nids[] )', [
Chris@0 84 ':uid' => \Drupal::currentUser()->id(),
Chris@0 85 ':nids[]' => array_keys($nodes_to_read),
Chris@0 86 ]);
Chris@0 87 foreach ($result as $row) {
Chris@0 88 $nodes_to_read[$row->nid] = (int) $row->timestamp;
Chris@0 89 }
Chris@0 90 $history += $nodes_to_read;
Chris@0 91
Chris@0 92 return $return + $nodes_to_read;
Chris@0 93 }
Chris@0 94
Chris@0 95 /**
Chris@0 96 * Updates 'last viewed' timestamp of the specified entity for the current user.
Chris@0 97 *
Chris@0 98 * @param $nid
Chris@0 99 * The node ID that has been read.
Chris@0 100 * @param $account
Chris@0 101 * (optional) The user account to update the history for. Defaults to the
Chris@0 102 * current user.
Chris@0 103 */
Chris@0 104 function history_write($nid, $account = NULL) {
Chris@0 105
Chris@0 106 if (!isset($account)) {
Chris@0 107 $account = \Drupal::currentUser();
Chris@0 108 }
Chris@0 109
Chris@0 110 if ($account->isAuthenticated()) {
Chris@0 111 db_merge('history')
Chris@0 112 ->keys([
Chris@0 113 'uid' => $account->id(),
Chris@0 114 'nid' => $nid,
Chris@0 115 ])
Chris@0 116 ->fields(['timestamp' => REQUEST_TIME])
Chris@0 117 ->execute();
Chris@0 118 // Update static cache.
Chris@0 119 $history = &drupal_static('history_read_multiple', []);
Chris@0 120 $history[$nid] = REQUEST_TIME;
Chris@0 121 }
Chris@0 122 }
Chris@0 123
Chris@0 124 /**
Chris@0 125 * Implements hook_cron().
Chris@0 126 */
Chris@0 127 function history_cron() {
Chris@0 128 db_delete('history')
Chris@0 129 ->condition('timestamp', HISTORY_READ_LIMIT, '<')
Chris@0 130 ->execute();
Chris@0 131 }
Chris@0 132
Chris@0 133 /**
Chris@0 134 * Implements hook_ENTITY_TYPE_view_alter() for node entities.
Chris@0 135 */
Chris@0 136 function history_node_view_alter(array &$build, EntityInterface $node, EntityViewDisplayInterface $display) {
Chris@0 137 // Update the history table, stating that this user viewed this node.
Chris@0 138 if ($display->getOriginalMode() === 'full') {
Chris@0 139 $build['#cache']['contexts'][] = 'user.roles:authenticated';
Chris@0 140 if (\Drupal::currentUser()->isAuthenticated()) {
Chris@0 141 // When the window's "load" event is triggered, mark the node as read.
Chris@0 142 // This still allows for Drupal behaviors (which are triggered on the
Chris@0 143 // "DOMContentReady" event) to add "new" and "updated" indicators.
Chris@0 144 $build['#attached']['library'][] = 'history/mark-as-read';
Chris@0 145 $build['#attached']['drupalSettings']['history']['nodesToMarkAsRead'][$node->id()] = TRUE;
Chris@0 146 }
Chris@0 147 }
Chris@0 148
Chris@0 149 }
Chris@0 150
Chris@0 151 /**
Chris@0 152 * Implements hook_ENTITY_TYPE_delete() for node entities.
Chris@0 153 */
Chris@0 154 function history_node_delete(EntityInterface $node) {
Chris@0 155 db_delete('history')
Chris@0 156 ->condition('nid', $node->id())
Chris@0 157 ->execute();
Chris@0 158 }
Chris@0 159
Chris@0 160 /**
Chris@0 161 * Implements hook_user_cancel().
Chris@0 162 */
Chris@4 163 function history_user_cancel($edit, UserInterface $account, $method) {
Chris@0 164 switch ($method) {
Chris@0 165 case 'user_cancel_reassign':
Chris@0 166 db_delete('history')
Chris@0 167 ->condition('uid', $account->id())
Chris@0 168 ->execute();
Chris@0 169 break;
Chris@0 170 }
Chris@0 171 }
Chris@0 172
Chris@0 173 /**
Chris@0 174 * Implements hook_ENTITY_TYPE_delete() for user entities.
Chris@0 175 */
Chris@0 176 function history_user_delete($account) {
Chris@0 177 db_delete('history')
Chris@0 178 ->condition('uid', $account->id())
Chris@0 179 ->execute();
Chris@0 180 }
Chris@0 181
Chris@0 182 /**
Chris@0 183 * #lazy_builder callback; attaches the last read timestamp for a node.
Chris@0 184 *
Chris@0 185 * @param int $node_id
Chris@0 186 * The node ID for which to attach the last read timestamp.
Chris@0 187 *
Chris@0 188 * @return array
Chris@0 189 * A renderable array containing the last read timestamp.
Chris@0 190 */
Chris@0 191 function history_attach_timestamp($node_id) {
Chris@0 192 $element = [];
Chris@0 193 $element['#attached']['drupalSettings']['history']['lastReadTimestamps'][$node_id] = (int) history_read($node_id);
Chris@0 194 return $element;
Chris@0 195 }