Fri Feb 8 21:56:53 2019 +0600 7a04aac815 (HEAD -> history_read_limit) reroll  [Yuriy Shupen'ko]
diff --git a/core/modules/comment/comment.services.yml b/core/modules/comment/comment.services.yml
index 374a8ca9a3..91d67fa50a 100644
--- a/core/modules/comment/comment.services.yml
+++ b/core/modules/comment/comment.services.yml
@@ -7,7 +7,7 @@ services:
 
   comment.manager:
     class: Drupal\comment\CommentManager
-    arguments: ['@entity_type.manager', '@config.factory', '@string_translation', '@module_handler', '@current_user', '@entity_field.manager']
+    arguments: ['@entity_type.manager', '@config.factory', '@string_translation', '@module_handler', '@current_user', '@datetime.time', '@entity_field.manager']
 
   comment.statistics:
     class: Drupal\comment\CommentStatistics
diff --git a/core/modules/comment/src/CommentManager.php b/core/modules/comment/src/CommentManager.php
index 4f5919a333..a92862d996 100644
--- a/core/modules/comment/src/CommentManager.php
+++ b/core/modules/comment/src/CommentManager.php
@@ -3,6 +3,7 @@
 namespace Drupal\comment;
 
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
+use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
@@ -73,6 +74,20 @@ class CommentManager implements CommentManagerInterface {
    */
   protected $currentUser;
 
+  /**
+   * Config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * Time manager.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
+   */
+  protected $time;
+
   /**
    * Construct the CommentManager object.
    *
@@ -86,15 +101,19 @@ class CommentManager implements CommentManagerInterface {
    *   The module handler service.
    * @param \Drupal\Core\Session\AccountInterface $current_user
    *   The current user.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   Time manager.
    * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
    *   The entity field manager service.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, TranslationInterface $string_translation, ModuleHandlerInterface $module_handler, AccountInterface $current_user, EntityFieldManagerInterface $entity_field_manager = NULL) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, TranslationInterface $string_translation, ModuleHandlerInterface $module_handler, AccountInterface $current_user, TimeInterface $time, EntityFieldManagerInterface $entity_field_manager = NULL) {
     $this->entityTypeManager = $entity_type_manager;
     $this->userConfig = $config_factory->get('user.settings');
     $this->stringTranslation = $string_translation;
     $this->moduleHandler = $module_handler;
     $this->currentUser = $current_user;
+    $this->configFactory = $config_factory;
+    $this->time = $time;
     if (!$entity_field_manager) {
       @trigger_error('The entity_field.manager service must be passed to CommentManager::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
       $entity_field_manager = \Drupal::service('entity_field.manager');
@@ -216,7 +235,9 @@ public function getCountNewComments(EntityInterface $entity, $field_name = NULL,
           }
         }
       }
-      $timestamp = ($timestamp > HISTORY_READ_LIMIT ? $timestamp : HISTORY_READ_LIMIT);
+      $historyConfig = $this->configFactory->get('history.settings');
+      $history_read_limit = $this->time->getRequestTime() - $historyConfig->get('history_mark_read');
+      $timestamp = ($timestamp > $history_read_limit ? $timestamp : $history_read_limit);
 
       // Use the timestamp to retrieve the number of new comments.
       $query = $this->entityTypeManager->getStorage('comment')->getQuery()
diff --git a/core/modules/comment/src/Plugin/views/field/NodeNewComments.php b/core/modules/comment/src/Plugin/views/field/NodeNewComments.php
index a1efc1da10..0afe894777 100644
--- a/core/modules/comment/src/Plugin/views/field/NodeNewComments.php
+++ b/core/modules/comment/src/Plugin/views/field/NodeNewComments.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\comment\Plugin\views\field;
 
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\comment\CommentInterface;
 use Drupal\Core\Form\FormStateInterface;
@@ -35,6 +37,20 @@ public function usesGroupBy() {
    */
   protected $database;
 
+  /**
+   * Config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory
+   */
+  protected $configFactory;
+
+  /**
+   * Time manager.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
+   */
+  protected $time;
+
   /**
    * Constructs a \Drupal\comment\Plugin\views\field\NodeNewComments object.
    *
@@ -46,18 +62,24 @@ public function usesGroupBy() {
    *   The plugin implementation definition.
    * @param \Drupal\Core\Database\Connection $database
    *   Database Service Object.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   Config factory service.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   Time manager.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, ConfigFactoryInterface $configFactory, TimeInterface $time) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->database = $database;
+    $this->configFactory = $configFactory;
+    $this->time = $time;
   }
 
   /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
-    return new static($configuration, $plugin_id, $plugin_definition, $container->get('database'));
+    return new static($configuration, $plugin_id, $plugin_definition, $container->get('database'), $container->get('config.factory'), $container->get('datetime.time'));
   }
 
   /**
@@ -127,14 +149,15 @@ public function preRender(&$values) {
     }
 
     if ($nids) {
+      $history_read_limit = $this->time->getRequestTime() - $this->configFactory->get('history.settings')->get('history_mark_read');
       $result = $this->database->query("SELECT n.nid, COUNT(c.cid) as num_comments FROM {node} n INNER JOIN {comment_field_data} c ON n.nid = c.entity_id AND c.entity_type = 'node' AND c.default_langcode = 1
         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, :timestamp1), :timestamp2) AND c.status = :status GROUP BY n.nid", [
         ':status' => CommentInterface::PUBLISHED,
         ':h_uid' => $user->id(),
         ':nids[]' => $nids,
-        ':timestamp1' => HISTORY_READ_LIMIT,
-        ':timestamp2' => HISTORY_READ_LIMIT,
+        ':timestamp1' => $history_read_limit,
+        ':timestamp2' => $history_read_limit,
       ]);
       foreach ($result as $node) {
         foreach ($ids[$node->nid] as $id) {
diff --git a/core/modules/comment/tests/src/Unit/CommentManagerTest.php b/core/modules/comment/tests/src/Unit/CommentManagerTest.php
index 2b4c76a12f..c64ede8541 100644
--- a/core/modules/comment/tests/src/Unit/CommentManagerTest.php
+++ b/core/modules/comment/tests/src/Unit/CommentManagerTest.php
@@ -54,6 +54,7 @@ public function testGetFields() {
       $this->getMock('Drupal\Core\StringTranslation\TranslationInterface'),
       $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'),
       $this->createMock(AccountInterface::class),
+      $this->getMock('Drupal\Component\Datetime\TimeInterface'),
       $entity_field_manager
     );
     $comment_fields = $comment_manager->getFields('node');
diff --git a/core/modules/forum/src/ForumManager.php b/core/modules/forum/src/ForumManager.php
index 6e95ff0447..aec9097949 100644
--- a/core/modules/forum/src/ForumManager.php
+++ b/core/modules/forum/src/ForumManager.php
@@ -326,19 +326,22 @@ protected function getTopicOrder($sortby) {
    *
    * @return int
    *   The timestamp when the user last viewed this node, if the user has
-   *   previously viewed the node; otherwise HISTORY_READ_LIMIT.
+   *   previously viewed the node; otherwise - history read limit.
    */
   protected function lastVisit($nid, AccountInterface $account) {
+    $history_read_limit = $this->configFactory
+      ->get('history.settings')
+      ->get('history_mark_read');
     if (empty($this->history[$nid])) {
       $result = $this->connection->select('history', 'h')
         ->fields('h', ['nid', 'timestamp'])
         ->condition('uid', $account->id())
         ->execute();
       foreach ($result as $t) {
-        $this->history[$t->nid] = $t->timestamp > HISTORY_READ_LIMIT ? $t->timestamp : HISTORY_READ_LIMIT;
+        $this->history[$t->nid] = $t->timestamp > $history_read_limit ? $t->timestamp : $history_read_limit;
       }
     }
-    return isset($this->history[$nid]) ? $this->history[$nid] : HISTORY_READ_LIMIT;
+    return isset($this->history[$nid]) ? $this->history[$nid] : $history_read_limit;
   }
 
   /**
@@ -496,6 +499,9 @@ public function checkNodeType(NodeInterface $node) {
    * {@inheritdoc}
    */
   public function unreadTopics($term, $uid) {
+    $history_read_limit = $this->configFactory
+      ->get('history.settings')
+      ->get('history_mark_read');
     $query = $this->connection->select('node_field_data', 'n');
     $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', [':tid' => $term]);
     $query->leftJoin('history', 'h', 'n.nid = h.nid AND h.uid = :uid', [':uid' => $uid]);
@@ -505,7 +511,7 @@ public function unreadTopics($term, $uid) {
       // @todo This should be actually filtering on the desired node status
       //   field language and just fall back to the default language.
       ->condition('n.default_langcode', 1)
-      ->condition('n.created', HISTORY_READ_LIMIT, '>')
+      ->condition('n.created', $history_read_limit, '>')
       ->isNull('h.nid')
       ->addTag('node_access')
       ->execute()
diff --git a/core/modules/forum/src/ForumManager.php.orig b/core/modules/forum/src/ForumManager.php.orig
new file mode 100644
index 0000000000..6e95ff0447
--- /dev/null
+++ b/core/modules/forum/src/ForumManager.php.orig
@@ -0,0 +1,538 @@
+<?php
+
+namespace Drupal\forum;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\comment\CommentManagerInterface;
+use Drupal\node\NodeInterface;
+
+/**
+ * Provides forum manager service.
+ */
+class ForumManager implements ForumManagerInterface {
+  use StringTranslationTrait;
+  use DependencySerializationTrait {
+    __wakeup as defaultWakeup;
+    __sleep as defaultSleep;
+  }
+  use DeprecatedServicePropertyTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
+
+  /**
+   * Forum sort order, newest first.
+   */
+  const NEWEST_FIRST = 1;
+
+  /**
+   * Forum sort order, oldest first.
+   */
+  const OLDEST_FIRST = 2;
+
+  /**
+   * Forum sort order, posts with most comments first.
+   */
+  const MOST_POPULAR_FIRST = 3;
+
+  /**
+   * Forum sort order, posts with the least comments first.
+   */
+  const LEAST_POPULAR_FIRST = 4;
+
+  /**
+   * Forum settings config object.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * Entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * Entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Database connection
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * The comment manager service.
+   *
+   * @var \Drupal\comment\CommentManagerInterface
+   */
+  protected $commentManager;
+
+  /**
+   * Array of last post information keyed by forum (term) id.
+   *
+   * @var array
+   */
+  protected $lastPostData = [];
+
+  /**
+   * Array of forum statistics keyed by forum (term) id.
+   *
+   * @var array
+   */
+  protected $forumStatistics = [];
+
+  /**
+   * Array of forum children keyed by parent forum (term) id.
+   *
+   * @var array
+   */
+  protected $forumChildren = [];
+
+  /**
+   * Array of history keyed by nid.
+   *
+   * @var array
+   */
+  protected $history = [];
+
+  /**
+   * Cached forum index.
+   *
+   * @var \Drupal\taxonomy\TermInterface
+   */
+  protected $index;
+
+  /**
+   * Constructs the forum manager service.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The current database connection.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The translation manager service.
+   * @param \Drupal\comment\CommentManagerInterface $comment_manager
+   *   The comment manager service.
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   *   The entity field manager.
+   */
+  public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, Connection $connection, TranslationInterface $string_translation, CommentManagerInterface $comment_manager, EntityFieldManagerInterface $entity_field_manager = NULL) {
+    $this->configFactory = $config_factory;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->connection = $connection;
+    $this->stringTranslation = $string_translation;
+    $this->commentManager = $comment_manager;
+    if (!$entity_field_manager) {
+      @trigger_error('The entity_field.manager service must be passed to ForumManager::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
+      $entity_field_manager = \Drupal::service('entity_field.manager');
+    }
+    $this->entityFieldManager = $entity_field_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTopics($tid, AccountInterface $account) {
+    $config = $this->configFactory->get('forum.settings');
+    $forum_per_page = $config->get('topics.page_limit');
+    $sortby = $config->get('topics.order');
+
+    $header = [
+      ['data' => $this->t('Topic'), 'field' => 'f.title'],
+      ['data' => $this->t('Replies'), 'field' => 'f.comment_count'],
+      ['data' => $this->t('Last reply'), 'field' => 'f.last_comment_timestamp'],
+    ];
+
+    $order = $this->getTopicOrder($sortby);
+    for ($i = 0; $i < count($header); $i++) {
+      if ($header[$i]['field'] == $order['field']) {
+        $header[$i]['sort'] = $order['sort'];
+      }
+    }
+
+    $query = $this->connection->select('forum_index', 'f')
+      ->extend('Drupal\Core\Database\Query\PagerSelectExtender')
+      ->extend('Drupal\Core\Database\Query\TableSortExtender');
+    $query->fields('f');
+    $query
+      ->condition('f.tid', $tid)
+      ->addTag('node_access')
+      ->addMetaData('base_table', 'forum_index')
+      ->orderBy('f.sticky', 'DESC')
+      ->orderByHeader($header)
+      ->limit($forum_per_page);
+
+    $count_query = $this->connection->select('forum_index', 'f');
+    $count_query->condition('f.tid', $tid);
+    $count_query->addExpression('COUNT(*)');
+    $count_query->addTag('node_access');
+    $count_query->addMetaData('base_table', 'forum_index');
+
+    $query->setCountQuery($count_query);
+    $result = $query->execute();
+    $nids = [];
+    foreach ($result as $record) {
+      $nids[] = $record->nid;
+    }
+    if ($nids) {
+      $nodes = $this->entityTypeManager->getStorage('node')->loadMultiple($nids);
+
+      $query = $this->connection->select('node_field_data', 'n')
+        ->extend('Drupal\Core\Database\Query\TableSortExtender');
+      $query->fields('n', ['nid']);
+
+      $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_name = 'comment_forum' AND ces.entity_type = 'node'");
+      $query->fields('ces', [
+        'cid',
+        'last_comment_uid',
+        'last_comment_timestamp',
+        'comment_count',
+      ]);
+
+      $query->join('forum_index', 'f', 'f.nid = n.nid');
+      $query->addField('f', 'tid', 'forum_tid');
+
+      $query->join('users_field_data', 'u', 'n.uid = u.uid AND u.default_langcode = 1');
+      $query->addField('u', 'name');
+
+      $query->join('users_field_data', 'u2', 'ces.last_comment_uid = u2.uid AND u.default_langcode = 1');
+
+      $query->addExpression('CASE ces.last_comment_uid WHEN 0 THEN ces.last_comment_name ELSE u2.name END', 'last_comment_name');
+
+      $query
+        ->orderBy('f.sticky', 'DESC')
+        ->orderByHeader($header)
+        ->condition('n.nid', $nids, 'IN')
+        // @todo This should be actually filtering on the desired node language
+        //   and just fall back to the default language.
+        ->condition('n.default_langcode', 1);
+
+      $result = [];
+      foreach ($query->execute() as $row) {
+        $topic = $nodes[$row->nid];
+        $topic->comment_mode = $topic->comment_forum->status;
+
+        foreach ($row as $key => $value) {
+          $topic->{$key} = $value;
+        }
+        $result[] = $topic;
+      }
+    }
+    else {
+      $result = [];
+    }
+
+    $topics = [];
+    $first_new_found = FALSE;
+    foreach ($result as $topic) {
+      if ($account->isAuthenticated()) {
+        // A forum is new if the topic is new, or if there are new comments since
+        // the user's last visit.
+        if ($topic->forum_tid != $tid) {
+          $topic->new = 0;
+        }
+        else {
+          $history = $this->lastVisit($topic->id(), $account);
+          $topic->new_replies = $this->commentManager->getCountNewComments($topic, 'comment_forum', $history);
+          $topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history);
+        }
+      }
+      else {
+        // Do not track "new replies" status for topics if the user is anonymous.
+        $topic->new_replies = 0;
+        $topic->new = 0;
+      }
+
+      // Make sure only one topic is indicated as the first new topic.
+      $topic->first_new = FALSE;
+      if ($topic->new != 0 && !$first_new_found) {
+        $topic->first_new = TRUE;
+        $first_new_found = TRUE;
+      }
+
+      if ($topic->comment_count > 0) {
+        $last_reply = new \stdClass();
+        $last_reply->created = $topic->last_comment_timestamp;
+        $last_reply->name = $topic->last_comment_name;
+        $last_reply->uid = $topic->last_comment_uid;
+        $topic->last_reply = $last_reply;
+      }
+      $topics[$topic->id()] = $topic;
+    }
+
+    return ['topics' => $topics, 'header' => $header];
+
+  }
+
+  /**
+   * Gets topic sorting information based on an integer code.
+   *
+   * @param int $sortby
+   *   One of the following integers indicating the sort criteria:
+   *   - ForumManager::NEWEST_FIRST: Date - newest first.
+   *   - ForumManager::OLDEST_FIRST: Date - oldest first.
+   *   - ForumManager::MOST_POPULAR_FIRST: Posts with the most comments first.
+   *   - ForumManager::LEAST_POPULAR_FIRST: Posts with the least comments first.
+   *
+   * @return array
+   *   An array with the following values:
+   *   - field: A field for an SQL query.
+   *   - sort: 'asc' or 'desc'.
+   */
+  protected function getTopicOrder($sortby) {
+    switch ($sortby) {
+      case static::NEWEST_FIRST:
+        return ['field' => 'f.last_comment_timestamp', 'sort' => 'desc'];
+
+      case static::OLDEST_FIRST:
+        return ['field' => 'f.last_comment_timestamp', 'sort' => 'asc'];
+
+      case static::MOST_POPULAR_FIRST:
+        return ['field' => 'f.comment_count', 'sort' => 'desc'];
+
+      case static::LEAST_POPULAR_FIRST:
+        return ['field' => 'f.comment_count', 'sort' => 'asc'];
+
+    }
+  }
+
+  /**
+   * Gets the last time the user viewed a node.
+   *
+   * @param int $nid
+   *   The node ID.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   Account to fetch last time for.
+   *
+   * @return int
+   *   The timestamp when the user last viewed this node, if the user has
+   *   previously viewed the node; otherwise HISTORY_READ_LIMIT.
+   */
+  protected function lastVisit($nid, AccountInterface $account) {
+    if (empty($this->history[$nid])) {
+      $result = $this->connection->select('history', 'h')
+        ->fields('h', ['nid', 'timestamp'])
+        ->condition('uid', $account->id())
+        ->execute();
+      foreach ($result as $t) {
+        $this->history[$t->nid] = $t->timestamp > HISTORY_READ_LIMIT ? $t->timestamp : HISTORY_READ_LIMIT;
+      }
+    }
+    return isset($this->history[$nid]) ? $this->history[$nid] : HISTORY_READ_LIMIT;
+  }
+
+  /**
+   * Provides the last post information for the given forum tid.
+   *
+   * @param int $tid
+   *   The forum tid.
+   *
+   * @return \stdClass
+   *   The last post for the given forum.
+   */
+  protected function getLastPost($tid) {
+    if (!empty($this->lastPostData[$tid])) {
+      return $this->lastPostData[$tid];
+    }
+    // Query "Last Post" information for this forum.
+    $query = $this->connection->select('node_field_data', 'n');
+    $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', [':tid' => $tid]);
+    $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_name = 'comment_forum' AND ces.entity_type = 'node'");
+    $query->join('users_field_data', 'u', 'ces.last_comment_uid = u.uid AND u.default_langcode = 1');
+    $query->addExpression('CASE ces.last_comment_uid WHEN 0 THEN ces.last_comment_name ELSE u.name END', 'last_comment_name');
+
+    $topic = $query
+      ->fields('ces', ['last_comment_timestamp', 'last_comment_uid'])
+      ->condition('n.status', 1)
+      ->orderBy('last_comment_timestamp', 'DESC')
+      ->range(0, 1)
+      ->addTag('node_access')
+      ->execute()
+      ->fetchObject();
+
+    // Build the last post information.
+    $last_post = new \stdClass();
+    if (!empty($topic->last_comment_timestamp)) {
+      $last_post->created = $topic->last_comment_timestamp;
+      $last_post->name = $topic->last_comment_name;
+      $last_post->uid = $topic->last_comment_uid;
+    }
+
+    $this->lastPostData[$tid] = $last_post;
+    return $last_post;
+  }
+
+  /**
+   * Provides statistics for a forum.
+   *
+   * @param int $tid
+   *   The forum tid.
+   *
+   * @return \stdClass|null
+   *   Statistics for the given forum if statistics exist, else NULL.
+   */
+  protected function getForumStatistics($tid) {
+    if (empty($this->forumStatistics)) {
+      // Prime the statistics.
+      $query = $this->connection->select('node_field_data', 'n');
+      $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_name = 'comment_forum' AND ces.entity_type = 'node'");
+      $query->join('forum', 'f', 'n.vid = f.vid');
+      $query->addExpression('COUNT(n.nid)', 'topic_count');
+      $query->addExpression('SUM(ces.comment_count)', 'comment_count');
+      $this->forumStatistics = $query
+        ->fields('f', ['tid'])
+        ->condition('n.status', 1)
+        ->condition('n.default_langcode', 1)
+        ->groupBy('tid')
+        ->addTag('node_access')
+        ->execute()
+        ->fetchAllAssoc('tid');
+    }
+
+    if (!empty($this->forumStatistics[$tid])) {
+      return $this->forumStatistics[$tid];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChildren($vid, $tid) {
+    if (!empty($this->forumChildren[$tid])) {
+      return $this->forumChildren[$tid];
+    }
+    $forums = [];
+    $_forums = $this->entityTypeManager->getStorage('taxonomy_term')->loadTree($vid, $tid, NULL, TRUE);
+    foreach ($_forums as $forum) {
+      // Merge in the topic and post counters.
+      if (($count = $this->getForumStatistics($forum->id()))) {
+        $forum->num_topics = $count->topic_count;
+        $forum->num_posts = $count->topic_count + $count->comment_count;
+      }
+      else {
+        $forum->num_topics = 0;
+        $forum->num_posts = 0;
+      }
+
+      // Merge in last post details.
+      $forum->last_post = $this->getLastPost($forum->id());
+      $forums[$forum->id()] = $forum;
+    }
+
+    $this->forumChildren[$tid] = $forums;
+    return $forums;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIndex() {
+    if ($this->index) {
+      return $this->index;
+    }
+
+    $vid = $this->configFactory->get('forum.settings')->get('vocabulary');
+    $index = $this->entityTypeManager->getStorage('taxonomy_term')->create([
+      'tid' => 0,
+      'container' => 1,
+      'parents' => [],
+      'isIndex' => TRUE,
+      'vid' => $vid,
+    ]);
+
+    // Load the tree below.
+    $index->forums = $this->getChildren($vid, 0);
+    $this->index = $index;
+    return $index;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetCache() {
+    // Reset the index.
+    $this->index = NULL;
+    // Reset history.
+    $this->history = [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParents($tid) {
+    return $this->entityTypeManager->getStorage('taxonomy_term')->loadAllParents($tid);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function checkNodeType(NodeInterface $node) {
+    // Fetch information about the forum field.
+    $field_definitions = $this->entityFieldManager->getFieldDefinitions('node', $node->bundle());
+    return !empty($field_definitions['taxonomy_forums']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  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', [':tid' => $term]);
+    $query->leftJoin('history', 'h', 'n.nid = h.nid AND h.uid = :uid', [':uid' => $uid]);
+    $query->addExpression('COUNT(n.nid)', 'count');
+    return $query
+      ->condition('status', 1)
+      // @todo This should be actually filtering on the desired node status
+      //   field language and just fall back to the default language.
+      ->condition('n.default_langcode', 1)
+      ->condition('n.created', HISTORY_READ_LIMIT, '>')
+      ->isNull('h.nid')
+      ->addTag('node_access')
+      ->execute()
+      ->fetchField();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __sleep() {
+    $vars = $this->defaultSleep();
+    // Do not serialize static cache.
+    unset($vars['history'], $vars['index'], $vars['lastPostData'], $vars['forumChildren'], $vars['forumStatistics']);
+    return $vars;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __wakeup() {
+    $this->defaultWakeup();
+    // Initialize static cache.
+    $this->history = [];
+    $this->lastPostData = [];
+    $this->forumChildren = [];
+    $this->forumStatistics = [];
+    $this->index = NULL;
+  }
+
+}
diff --git a/core/modules/forum/src/ForumManagerInterface.php b/core/modules/forum/src/ForumManagerInterface.php
index 9e8833ebcf..4f81ae0997 100644
--- a/core/modules/forum/src/ForumManagerInterface.php
+++ b/core/modules/forum/src/ForumManagerInterface.php
@@ -79,7 +79,7 @@ public function checkNodeType(NodeInterface $node);
   /**
    * Calculates the number of new posts in a forum that the user has not yet read.
    *
-   * Nodes are new if they are newer than HISTORY_READ_LIMIT.
+   * Nodes are new if they are newer than history read limit.
    *
    * @param int $term
    *   The term ID of the forum.
diff --git a/core/modules/history/config/install/history.settings.yml b/core/modules/history/config/install/history.settings.yml
new file mode 100644
index 0000000000..3b72c3c63d
--- /dev/null
+++ b/core/modules/history/config/install/history.settings.yml
@@ -0,0 +1,5 @@
+# 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.
+history_mark_read: 2592000
diff --git a/core/modules/history/config/schema/history.settings.schema.yml b/core/modules/history/config/schema/history.settings.schema.yml
new file mode 100644
index 0000000000..530dddd111
--- /dev/null
+++ b/core/modules/history/config/schema/history.settings.schema.yml
@@ -0,0 +1,7 @@
+history.settings:
+  type: config_object
+  label: 'History settings'
+  mapping:
+    history_mark_read:
+      type: integer
+      label: 'Entities changed within this time period may be marked as new.'
diff --git a/core/modules/history/history.module b/core/modules/history/history.module
index 44dfb13924..a999376799 100644
--- a/core/modules/history/history.module
+++ b/core/modules/history/history.module
@@ -14,14 +14,6 @@
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\user\UserInterface;
 
-/**
- * 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);
-
 /**
  * Implements hook_help().
  */
@@ -125,8 +117,9 @@ function history_write($nid, $account = NULL) {
  * Implements hook_cron().
  */
 function history_cron() {
+  $history_read_limit = \Drupal::time()->getRequestTime() - \Drupal::config('history.settings')->get('history_mark_read');
   \Drupal::database()->delete('history')
-    ->condition('timestamp', HISTORY_READ_LIMIT, '<')
+    ->condition('timestamp', $history_read_limit, '<')
     ->execute();
 }
 
diff --git a/core/modules/history/history.post_update.php b/core/modules/history/history.post_update.php
new file mode 100644
index 0000000000..abe4416b72
--- /dev/null
+++ b/core/modules/history/history.post_update.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Post update functions for History module.
+ */
+
+/**
+ * Installing config for existing instances.
+ */
+function history_post_update_install_config(&$sandbox = NULL) {
+  \Drupal::service('config.installer')->installDefaultConfig('module', 'history');
+}
diff --git a/core/modules/history/src/Plugin/views/field/HistoryUserTimestamp.php b/core/modules/history/src/Plugin/views/field/HistoryUserTimestamp.php
index 617cd23e12..7efa93761b 100644
--- a/core/modules/history/src/Plugin/views/field/HistoryUserTimestamp.php
+++ b/core/modules/history/src/Plugin/views/field/HistoryUserTimestamp.php
@@ -2,11 +2,15 @@
 
 namespace Drupal\history\Plugin\views\field;
 
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\views\ResultRow;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\node\Plugin\views\field\Node;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Field handler to display the marker for new content.
@@ -18,7 +22,54 @@
  *
  * @ViewsField("history_user_timestamp")
  */
-class HistoryUserTimestamp extends Node {
+class HistoryUserTimestamp extends Node implements ContainerFactoryPluginInterface {
+
+  /**
+   * Config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * Time service.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
+   */
+  protected $time;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('config.factory'),
+      $container->get('datetime.time')
+    );
+  }
+
+  /**
+   * HistoryUserTimestamp constructor.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin ID for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   Config factory.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   Time service.
+   */
+  public function __construct($configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $configFactory, TimeInterface $time) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->configFactory = $configFactory;
+    $this->time = $time;
+  }
 
   /**
    * {@inheritdoc}
@@ -92,13 +143,14 @@ public function render(ResultRow $values) {
 
       $last_comment = \Drupal::moduleHandler()->moduleExists('comment') && !empty($this->options['comments']) ? $this->getValue($values, 'last_comment') : 0;
 
-      if (!$last_read && $changed > HISTORY_READ_LIMIT) {
+      $history_read_limit = $this->time->getRequestTime() - $this->configFactory->get('history.settings')->get('history_mark_read');
+      if (!$last_read && $changed > $history_read_limit) {
         $mark = MARK_NEW;
       }
-      elseif ($changed > $last_read && $changed > HISTORY_READ_LIMIT) {
+      elseif ($changed > $last_read && $changed > $history_read_limit) {
         $mark = MARK_UPDATED;
       }
-      elseif ($last_comment > $last_read && $last_comment > HISTORY_READ_LIMIT) {
+      elseif ($last_comment > $last_read && $last_comment > $history_read_limit) {
         $mark = MARK_UPDATED;
       }
       $build = [
diff --git a/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php b/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php
index 02288da1c4..1f9da52cdc 100644
--- a/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php
+++ b/core/modules/history/src/Plugin/views/filter/HistoryUserTimestamp.php
@@ -2,9 +2,13 @@
 
 namespace Drupal\history\Plugin\views\filter;
 
+use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Cache\UncacheableDependencyTrait;
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\views\Plugin\views\filter\FilterPluginBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Filter for new content.
@@ -16,10 +20,58 @@
  *
  * @ViewsFilter("history_user_timestamp")
  */
-class HistoryUserTimestamp extends FilterPluginBase {
+class HistoryUserTimestamp extends FilterPluginBase implements ContainerFactoryPluginInterface {
 
   use UncacheableDependencyTrait;
 
+  /**
+   * Config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * Time service.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
+   */
+  protected $time;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('config.factory'),
+      $container->get('datetime.time')
+
+    );
+  }
+
+  /**
+   * HistoryUserTimestamp constructor.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin ID for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   Config factory.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   Time service.
+   */
+  public function __construct($configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $configFactory, TimeInterface $time) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->configFactory = $configFactory;
+    $this->time = $time;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -80,8 +132,8 @@ public function query() {
     }
 
     // Hey, Drupal kills old history, so nodes that haven't been updated
-    // since HISTORY_READ_LIMIT are bzzzzzzzt outta here!
-    $limit = REQUEST_TIME - HISTORY_READ_LIMIT;
+    // since history_cron() are bzzzzzzzt outta here!
+    $limit = $this->time->getRequestTime() - $this->configFactory->get('history.settings')->get('history_mark_read');
 
     $this->ensureMyTable();
     $field = "$this->tableAlias.$this->realField";
diff --git a/core/modules/history/tests/src/Kernel/Views/HistoryTimestampTest.php b/core/modules/history/tests/src/Kernel/Views/HistoryTimestampTest.php
index f66ce52476..5622eeda8f 100644
--- a/core/modules/history/tests/src/Kernel/Views/HistoryTimestampTest.php
+++ b/core/modules/history/tests/src/Kernel/Views/HistoryTimestampTest.php
@@ -40,6 +40,7 @@ protected function setUp($import_test_views = TRUE) {
     $this->installEntitySchema('node');
     $this->installEntitySchema('user');
     $this->installSchema('history', ['history']);
+    $this->installConfig('history');
     // Use classy theme because its marker is wrapped in a span so it can be
     // easily targeted with xpath.
     \Drupal::service('theme_handler')->install(['classy']);
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 9fdabf32b4..8dc46d0e8f 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -246,6 +246,7 @@ function node_title_list(StatementInterface $result, $title = NULL) {
 function node_mark($nid, $timestamp) {
 
   $cache = &drupal_static(__FUNCTION__, []);
+  $history_read_limit = REQUEST_TIME - \Drupal::config('history.settings')->get('history_mark_read');
 
   if (\Drupal::currentUser()->isAnonymous() || !\Drupal::moduleHandler()->moduleExists('history')) {
     return MARK_READ;
@@ -253,10 +254,10 @@ function node_mark($nid, $timestamp) {
   if (!isset($cache[$nid])) {
     $cache[$nid] = history_read($nid);
   }
-  if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
+  if ($cache[$nid] == 0 && $timestamp > $history_read_limit) {
     return MARK_NEW;
   }
-  elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
+  elseif ($timestamp > $cache[$nid] && $timestamp > $history_read_limit) {
     return MARK_UPDATED;
   }
   return MARK_READ;
