diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeNewComments.php b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeNewComments.php index 6da4cb1..b4b1800 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeNewComments.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/views/field/NodeNewComments.php @@ -111,9 +111,9 @@ public function preRender(&$values) { } if ($nids) { - $result = $this->database->query('SELECT n.nid, COUNT(c.cid) as num_comments FROM {node} n INNER JOIN {comment} c ON n.nid = c.nid - LEFT JOIN {history} h ON h.nid = n.nid AND h.uid = :h_uid WHERE n.nid IN (:nids) - AND c.changed > GREATEST(COALESCE(h.timestamp, :timestamp), :timestamp) AND c.status = :status GROUP BY n.nid', array( + $result = $this->database->query("SELECT n.nid, COUNT(c.cid) as num_comments FROM {node} n INNER JOIN {comment} c ON n.nid = c.nid + LEFT JOIN {history} h ON h.entity_id = n.nid AND h.entity_type = 'node' AND h.uid = :h_uid WHERE n.nid IN (:nids) + AND c.changed > GREATEST(COALESCE(h.timestamp, :timestamp), :timestamp) AND c.status = :status GROUP BY n.nid", array( ':status' => COMMENT_PUBLISHED, ':h_uid' => $user->id(), ':nids' => $nids, diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php index bf7cdb5..2c88f9a 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php @@ -157,7 +157,7 @@ function setEnvironment(array $info) { // comment_num_new() relies on history_read(), so ensure that no one has // seen the node of this comment. - db_delete('history')->condition('nid', $this->node->id())->execute(); + $this->container->get('history.manager')->deleteByEntity('node', $this->node->id()); } else { $cids = db_query("SELECT cid FROM {comment}")->fetchCol(); diff --git a/core/modules/forum/forum.services.yml b/core/modules/forum/forum.services.yml index 8ea4dac..0c05dcd 100644 --- a/core/modules/forum/forum.services.yml +++ b/core/modules/forum/forum.services.yml @@ -1,7 +1,8 @@ services: forum_manager: class: Drupal\forum\ForumManager - arguments: ['@config.factory', '@plugin.manager.entity', '@database', '@field.info', '@string_translation'] + # The history.manager service is optional to allow upgrade path. + arguments: ['@config.factory', '@plugin.manager.entity', '@database', '@field.info', '@string_translation', '@?history.manager'] forum.breadcrumb: class: Drupal\forum\ForumBreadcrumbBuilder arguments: ['@entity.manager', '@config.factory', '@forum_manager'] diff --git a/core/modules/forum/lib/Drupal/forum/ForumManager.php b/core/modules/forum/lib/Drupal/forum/ForumManager.php index 92c2a7c..c3b9d89 100644 --- a/core/modules/forum/lib/Drupal/forum/ForumManager.php +++ b/core/modules/forum/lib/Drupal/forum/ForumManager.php @@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityManager; use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\field\FieldInfo; +use Drupal\history\HistoryManagerInterface; use Drupal\node\NodeInterface; /** @@ -110,6 +111,13 @@ class ForumManager implements ForumManagerInterface { protected $translationManager; /** + * Translation manager service. + * + * @var \Drupal\history\HistoryManagerInterface + */ + protected $historyManager; + + /** * Constructs the forum manager service. * * @param \Drupal\Core\Config\ConfigFactory $config_factory @@ -122,13 +130,16 @@ class ForumManager implements ForumManagerInterface { * The field info service. * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager * The translation manager service. + * @param \Drupal\history\HistoryManagerInterface $history_manager + * The history manager service. */ - public function __construct(ConfigFactory $config_factory, EntityManager $entity_manager, Connection $connection, FieldInfo $field_info, TranslationInterface $translation_manager) { + public function __construct(ConfigFactory $config_factory, EntityManager $entity_manager, Connection $connection, FieldInfo $field_info, TranslationInterface $translation_manager, HistoryManagerInterface $history_manager) { $this->configFactory = $config_factory; $this->entityManager = $entity_manager; $this->connection = $connection; $this->fieldInfo = $field_info; $this->translationManager = $translation_manager; + $this->historyManager = $history_manager; } /** @@ -326,18 +337,16 @@ protected function numberNew($nid, $timestamp) { * previously viewed the node; otherwise HISTORY_READ_LIMIT. */ protected function lastVisit($nid) { - global $user; + if (isset($this->history[$nid])) { + return $this->history[$nid]; + } + $this->history[$nid] = $this->historyManager->read('node', array($nid)); if (empty($this->history[$nid])) { - $result = $this->connection->select('history', 'h') - ->fields('h', array('nid', 'timestamp')) - ->condition('uid', $user->id()) - ->execute(); - foreach ($result as $t) { - $this->history[$t->nid] = $t->timestamp > HISTORY_READ_LIMIT ? $t->timestamp : HISTORY_READ_LIMIT; - } + $this->history[$nid] = HISTORY_READ_LIMIT; } - return isset($this->history[$nid]) ? $this->history[$nid] : HISTORY_READ_LIMIT; + + return $this->history[$nid]; } /** @@ -497,7 +506,7 @@ public function checkNodeType(NodeInterface $node) { public function unreadTopics($term, $uid) { $query = $this->connection->select('node_field_data', 'n'); $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term)); - $query->leftJoin('history', 'h', 'n.nid = h.nid AND h.uid = :uid', array(':uid' => $uid)); + $query->leftJoin('history', 'h', "n.nid = h.entity_id AND h.entity_type = 'node' AND h.uid = :uid", array(':uid' => $uid)); $query->addExpression('COUNT(n.nid)', 'count'); return $query ->condition('status', 1) @@ -505,7 +514,7 @@ public function unreadTopics($term, $uid) { // field language and just fall back to the default language. ->condition('n.default_langcode', 1) ->condition('n.created', HISTORY_READ_LIMIT, '>') - ->isNull('h.nid') + ->isNull('h.entity_id') ->addTag('node_access') ->execute() ->fetchField(); diff --git a/core/modules/history/history.install b/core/modules/history/history.install index dcfd871..086c8f7 100644 --- a/core/modules/history/history.install +++ b/core/modules/history/history.install @@ -13,13 +13,20 @@ function history_schema() { 'description' => 'A record of which {users} have read which {node}s.', 'fields' => array( 'uid' => array( - 'description' => 'The {users}.uid that read the {node} nid.', + 'description' => 'The {users}.uid that read the entity_id.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, ), - 'nid' => array( - 'description' => 'The {node}.nid that was read.', + 'entity_type' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'node', + 'length' => 255, + 'description' => 'The entity_type of the entity was read.', + ), + 'entity_id' => array( + 'description' => 'The entity_id that was read.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, @@ -31,11 +38,19 @@ function history_schema() { 'default' => 0, ), ), - 'primary key' => array('uid', 'nid'), + 'primary key' => array( + 'uid', + 'entity_id', + array('entity_type', 32), + ), 'indexes' => array( - 'nid' => array('nid'), + 'history_entity' => array( + 'entity_id', + array('entity_type', 32), + ), ), ); return $schema; } + diff --git a/core/modules/history/history.module b/core/modules/history/history.module index 38dd8ab..cb08ba8 100644 --- a/core/modules/history/history.module +++ b/core/modules/history/history.module @@ -10,6 +10,7 @@ */ use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; /** * Entities changed before this time are always shown as read. @@ -28,61 +29,40 @@ * @return int * If a node has been previously viewed by the user, the timestamp in seconds * of when the last view occurred; otherwise, zero. + * + * @deprecated Use \Drupal\history\HistoryManager::read() instead. */ function history_read($nid) { - global $user; - $history = &drupal_static(__FUNCTION__, array()); - - if (!isset($history[$nid])) { - $history[$nid] = db_query("SELECT timestamp FROM {history} WHERE uid = :uid AND nid = :nid", array(':uid' => $user->id(), ':nid' => $nid))->fetchObject(); - } - - return (isset($history[$nid]->timestamp) ? $history[$nid]->timestamp : 0); + return Drupal::service('history.manager')->read('node', array($nid)); } /** * Updates 'last viewed' timestamp of the specified entity for the current user. * - * @param $nid + * @param int $nid * The node ID that has been read. - * @param $account + * @param \Drupal\Core\Session\AccountInterface $account * (optional) The user account to update the history for. Defaults to the * current user. + * + * @deprecated Use \Drupal\history\HistoryManager::write() instead. */ -function history_write($nid, $account = NULL) { - global $user; - - if (!isset($account)) { - $account = $user; - } - - if ($account->isAuthenticated()) { - db_merge('history') - ->key(array( - 'uid' => $account->id(), - 'nid' => $nid, - )) - ->fields(array('timestamp' => REQUEST_TIME)) - ->execute(); - } +function history_write($nid, AccountInterface $account = NULL) { + Drupal::service('history.manager')->write('node', $nid, $account); } /** * Implements hook_cron(). */ function history_cron() { - db_delete('history') - ->condition('timestamp', HISTORY_READ_LIMIT, '<') - ->execute(); + Drupal::service('history.manager')->purge(); } /** * Implements hook_node_delete(). */ function history_node_delete(EntityInterface $node) { - db_delete('history') - ->condition('nid', $node->id()) - ->execute(); + Drupal::service('history.manager')->deleteByEntity('node', $node->id()); } /** @@ -91,9 +71,7 @@ function history_node_delete(EntityInterface $node) { function history_user_cancel($edit, $account, $method) { switch ($method) { case 'user_cancel_reassign': - db_delete('history') - ->condition('uid', $account->id()) - ->execute(); + Drupal::service('history.manager')->deleteByUid($account->id()); break; } } @@ -102,7 +80,5 @@ function history_user_cancel($edit, $account, $method) { * Implements hook_user_delete(). */ function history_user_delete($account) { - db_delete('history') - ->condition('uid', $account->id()) - ->execute(); + Drupal::service('history.manager')->deleteByUid($account->id()); } diff --git a/core/modules/history/history.services.yml b/core/modules/history/history.services.yml new file mode 100644 index 0000000..49285ec --- /dev/null +++ b/core/modules/history/history.services.yml @@ -0,0 +1,4 @@ +services: + history.manager: + class: Drupal\history\HistoryManager + arguments: ['@database'] diff --git a/core/modules/history/history.views.inc b/core/modules/history/history.views.inc index ed36820..9bf753b 100644 --- a/core/modules/history/history.views.inc +++ b/core/modules/history/history.views.inc @@ -24,8 +24,9 @@ function history_views_data() { 'node' => array( 'table' => 'history', 'left_field' => 'nid', - 'field' => 'nid', + 'field' => 'entity_id', 'extra' => array( + array('field' => 'entity_type', 'value' => 'node'), array('field' => 'uid', 'value' => '***CURRENT_USER***', 'numeric' => TRUE), ), ), diff --git a/core/modules/history/lib/Drupal/history/HistoryManager.php b/core/modules/history/lib/Drupal/history/HistoryManager.php new file mode 100644 index 0000000..7510afd --- /dev/null +++ b/core/modules/history/lib/Drupal/history/HistoryManager.php @@ -0,0 +1,135 @@ +connection = $connection; + } + + /** + * {@inheritdoc} + */ + public function read($entity_type, $entity_ids) { + $return = array(); + + $entities_to_read = array(); + foreach ($entity_ids as $entity_id) { + if (isset($this->history[$entity_type][$entity_id])) { + $return[$entity_id] = $this->history[$entity_type][$entity_id]; + } + else { + $entities_to_read[$entity_id] = 0; + } + } + + if (empty($entities_to_read)) { + return $return; + } + + $result = $this->connection->select('history', 'h') + ->fields('h', array('entity_id', 'timestamp')) + ->condition('uid', \Drupal::currentUser()->id()) + ->condition('entity_type', $entity_type) + ->condition('entity_id', array_keys($entities_to_read), 'IN') + ->execute(); + + foreach ($result as $row) { + $entities_to_read[$row->entity_id] = $row->timestamp; + } + $this->history[$entity_type] += $entities_to_read; + + return $return + $entities_to_read; + } + + /** + * {@inheritdoc} + */ + public function write($entity_type, $entity_id, AccountInterface $account = NULL) { + if (!isset($account)) { + $account = \Drupal::currentUser(); + } + + if ($account->isAuthenticated()) { + $this->connection->merge('history') + ->key(array( + 'uid' => $account->id(), + 'entity_id' => $entity_id, + 'entity_type' => $entity_type, + )) + ->fields(array('timestamp' => REQUEST_TIME)) + ->execute(); + // Reset cached value. + unset($this->history[$entity_type][$entity_id]); + } + } + + /** + * {@inheritdoc} + */ + public function purge() { + $this->connection->delete('history') + ->condition('timestamp', HISTORY_READ_LIMIT, '<') + ->execute(); + + } + + /** + * {@inheritdoc} + */ + public function deleteByUid($uid) { + $this->connection->delete('history') + ->condition('uid', $uid) + ->execute(); + } + + /** + * {@inheritdoc} + */ + public function deleteByEntity($entity_type, $entity_id) { + $this->connection->delete('history') + ->condition('entity_id', $entity_id) + ->condition('entity_type', $entity_type) + ->execute(); + } + +} diff --git a/core/modules/history/lib/Drupal/history/HistoryManagerInterface.php b/core/modules/history/lib/Drupal/history/HistoryManagerInterface.php new file mode 100644 index 0000000..7cc4bec --- /dev/null +++ b/core/modules/history/lib/Drupal/history/HistoryManagerInterface.php @@ -0,0 +1,68 @@ +fields(array( 'uid' => $account->id(), - 'nid' => $nodes[0]->id(), + 'entity_id' => $nodes[0]->id(), + 'entity_type' => 'node', 'timestamp' => REQUEST_TIME - 100, ))->execute(); db_insert('history') ->fields(array( 'uid' => $account->id(), - 'nid' => $nodes[1]->id(), + 'entity_id' => $nodes[1]->id(), + 'entity_type' => 'node', 'timestamp' => REQUEST_TIME + 100, ))->execute(); diff --git a/core/modules/node/node.install b/core/modules/node/node.install index 02cc7a0..9cee01b 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -692,9 +692,37 @@ function node_update_8011() { } /** - * Enable History module. + * Convert {history} table to new format and enable History module. */ function node_update_8012() { + // Drop all keys to properly rename constraints. + db_drop_primary_key('history'); + db_drop_index('history', 'nid'); + // Add new column. + db_add_field('history', 'entity_type', array( + 'type' => 'varchar', + 'not null' => TRUE, + 'default' => 'node', + 'length' => 255, + 'description' => 'The entity_type of the entity was read.', + )); + db_change_field('history', 'nid', 'entity_id', array( + 'description' => 'The entity_id that was read.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + )); + // Create new indexes. + db_add_primary_key('history', array( + 'uid', + 'entity_id', + array('entity_type', 32), + )); + db_add_index('history', 'history_entity', array( + 'entity_id', + array('entity_type', 32), + )); + // Enable the history module without re-installing the schema. module_enable(array('history')); }