First pass and getting badges to work for user messages and group threads, a=chris

Chris Pollett [2022-09-15 23:Sep:th]
First pass and getting badges to work for user messages and group threads, a=chris
Filename
src/configs/Config.php
src/controllers/components/AccountaccessComponent.php
src/controllers/components/Component.php
src/controllers/components/SocialComponent.php
src/css/search.css
src/library/WikiParser.php
src/models/GroupModel.php
src/models/ImpressionModel.php
src/views/elements/GroupfeedElement.php
src/views/elements/SocialcontrolsElement.php
src/views/elements/UsermessagesElement.php
src/views/helpers/GrouplistHelper.php
diff --git a/src/configs/Config.php b/src/configs/Config.php
index adb96f771..ab7cb262d 100755
--- a/src/configs/Config.php
+++ b/src/configs/Config.php
@@ -336,6 +336,8 @@ nsdefine('ERROR_INFO', 4);
 nsconddefine("MAINTENANCE_MODE", false);
 /** Constant used to indicate lasting an arbitrary number of seconds */
 nsdefine('FOREVER', -2);
+/** Constant used to indicate most recent occurence of an impression type */
+nsdefine('MOST_RECENT_VIEW', -4);
 /** Number of seconds in a day*/
 nsdefine('ONE_DAY', 86400);
 /** Number of seconds in a week*/
diff --git a/src/controllers/components/AccountaccessComponent.php b/src/controllers/components/AccountaccessComponent.php
index 147c67c43..e7c6ebffc 100644
--- a/src/controllers/components/AccountaccessComponent.php
+++ b/src/controllers/components/AccountaccessComponent.php
@@ -178,6 +178,9 @@ class AccountaccessComponent extends Component
             $item = $group_model->getMostRecentGroupPost($group_id);
             $data['GROUPS'][$i]['NUM_POSTS'] =
                 $group_model->getGroupPostCount($group_id);
+            $data['GROUPS'][$i]['NEW_POSTS'] =
+                $group_model->getGroupPostCount($group_id,
+                $data['GROUPS'][$i]["MOST_RECENT_VIEW"]);
             $data['GROUPS'][$i]['NUM_THREADS'] =
                 $group_model->getGroupThreadCount($group_id);
             $data['GROUPS'][$i]['NUM_PAGES'] =
diff --git a/src/controllers/components/Component.php b/src/controllers/components/Component.php
index d4ca59507..57db3862d 100644
--- a/src/controllers/components/Component.php
+++ b/src/controllers/components/Component.php
@@ -199,6 +199,26 @@ class Component
     {
         $parent = $this->parent;
         $group_model = $parent->model("group");
+        $impression_model = $parent->model("impression");
         $data['NUM_GROUPS'] = $group_model->countUserGroups($user_id);
+        $timestamp = $impression_model->mostRecentGroupView($user_id);
+        $data['UNREAD_POSTS'] = $group_model->getUserPostCount($user_id,
+            $timestamp);
+        $personal_group_id = $group_model->getPersonalGroupId($user_id);
+        $contact_ids = array_diff($group_model->getGroupUserIds(
+            $personal_group_id), [$user_id]);
+        $chat_ids = [];
+        $unread_count = 0;
+        foreach ($contact_ids as $contact_id) {
+            $chat_id = $group_model->getGroupThreadId(
+                $personal_group_id, $user_id,
+                $group_model->getMessagesThreadTitle(
+                [$user_id, $contact_id]));
+            $chat_stamp = $impression_model->mostRecentThreadView($user_id,
+                $chat_id);
+            $unread_count += floor($group_model->getThreadPostCount($chat_id,
+                $chat_stamp) / 2);
+        }
+        $data['UNREAD_MESSAGES'] = $unread_count;
     }
 }
diff --git a/src/controllers/components/SocialComponent.php b/src/controllers/components/SocialComponent.php
index cf6af1dd0..194de64c9 100644
--- a/src/controllers/components/SocialComponent.php
+++ b/src/controllers/components/SocialComponent.php
@@ -2058,9 +2058,10 @@ class SocialComponent extends Component implements CrawlConstants
             '}; doUpdate();';
         $contact_ids = array_diff($group_model->getGroupUserIds(
             $user_messages_id), [$user_id]);
-        $contacts = $this->marshallContactInfo($contact_ids, $contact_filter);
+        $contacts = $this->marshallContactInfo($user_id, $contact_ids,
+            $contact_filter);
         $requests_thread_id = $group_model->getGroupThreadId(
-            $user_messages_id, null,  $this->getMessagesThreadTitle(
+            $user_messages_id, null,  $group_model->getMessagesThreadTitle(
                 [$user_id]));
         $contact_requests = [];
         if (!empty($requests_thread_id)) {
@@ -2085,12 +2086,15 @@ class SocialComponent extends Component implements CrawlConstants
         $data['ELEMENT'] = 'usermessages';
         $data['MESSAGES'] = [];
         if (!empty($contact_id)) {
-            $messages_thread_id = $group_model->getGroupThreadId(
-                $user_messages_id, $user_id,  $this->getMessagesThreadTitle(
+            $message_thread_id = $group_model->getGroupThreadId(
+                $user_messages_id, $user_id,
+                    $group_model->getMessagesThreadTitle(
                     [$user_id, $contact_id]));
-            if ($messages_thread_id) {
+            if ($message_thread_id) {
+                $parent->model("impression")->add($user_id, $message_thread_id,
+                    C\THREAD_IMPRESSION);
                 $search_array = [
-                 ["parent_id", "=", $messages_thread_id, ""],
+                 ["parent_id", "=", $message_thread_id, ""],
                  ["group_id", "=", $user_messages_id, ""],
                  ["pubdate", "", "", "DESC"]];
                 list(, $data['MESSAGES']) = $this->initializeFeedItems($data,
@@ -2103,8 +2107,9 @@ class SocialComponent extends Component implements CrawlConstants
         return $data;
     }
     /**
-     * Gets an array of contact details including USER_NAME and ICON_URL ofr
-     * an array of contact ids, subject to a filter.
+     * Gets an array of contact details including USER_NAME, ICON_URL,
+     * NUM_UNREAD_MESSAGES for
+     * an array of contact ids for $user_id, subject to a filter.
      * @param array $contact_ids id's that contact details are desired of
      * @param string $contact_filter a substring of user_names to restrict
      *  the returned details to (i.e., only contacts whose user name's match
@@ -2113,12 +2118,15 @@ class SocialComponent extends Component implements CrawlConstants
      *  is an array with fields USER_NAME, ICON_URL, ...) for ids with
      *  user names matching the filter
      */
-    private function marshallContactInfo($contact_ids, $contact_filter = "")
+    private function marshallContactInfo($user_id, $contact_ids,
+        $contact_filter = "")
     {
         $parent = $this->parent;
         $user_model = $parent->model("user");
         $group_model = $parent->model("group");
+        $impression_model = $parent->model("impression");
         $contacts= [];
+        $personal_group_id = $group_model->getPersonalGroupId($user_id);
         foreach ($contact_ids as $contact_id) {
             $contact_username = $user_model->getUsername(
                 $contact_id);
@@ -2127,9 +2135,18 @@ class SocialComponent extends Component implements CrawlConstants
                 continue;
             }
             $icon_url = $user_model->getUserIconUrl($contact_id);
+            $chat_id = $group_model->getGroupThreadId(
+                $personal_group_id, $user_id,
+                $group_model->getMessagesThreadTitle(
+                [$user_id, $contact_id]));
+            $chat_stamp = $impression_model->mostRecentThreadView($user_id,
+                $chat_id);
             $contacts[$contact_id] = [
                 "USER_NAME" => $contact_username,
-                "ICON_URL" => $icon_url];
+                "ICON_URL" => $icon_url,
+                "NUM_UNREAD_MESSAGES" => floor($group_model->getThreadPostCount(
+                    $chat_id, $chat_stamp)/2)
+                ];
         }
         return $contacts;
     }
@@ -2149,17 +2166,17 @@ class SocialComponent extends Component implements CrawlConstants
         $group_model = $parent->model("group");
         $data["REFRESH"] = "feedstatus";
         $data['MESSAGES'] = [];
-        $messages_thread_id = $group_model->getGroupThreadId(
+        $message_thread_id = $group_model->getGroupThreadId(
             $data['USER_MESSAGES_ID'], $user_id,
-            $this->getMessagesThreadTitle(
+            $group_model->getMessagesThreadTitle(
             [$user_id, $data["CONTACT_ID"]]));
         $conversation_time = $parent->clean($_REQUEST['conversation_time'],
             "int") ?? 0;
-        if (!$messages_thread_id || !$conversation_time) {
+        if (!$message_thread_id || !$conversation_time) {
             return $data;
         }
         $search_array = [
-         ["parent_id", "=", $messages_thread_id, ""],
+         ["parent_id", "=", $message_thread_id, ""],
          ["group_id", "=", $data['USER_MESSAGES_ID'], ""],
          ["pubdate", ">", $conversation_time, "DESC"]];
         list(, $data['MESSAGES']) = $this->initializeFeedItems($data,
@@ -2199,7 +2216,7 @@ class SocialComponent extends Component implements CrawlConstants
                 tl('social_component_invalid_contact'));
         }
         $group_model->addUserGroup($contact_id, $user_messages_id);
-        $messages_thread_title = $this->getMessagesThreadTitle(
+        $messages_thread_title = $group_model->getMessagesThreadTitle(
             [$user_id, $contact_id]);
         $_REQUEST["contact_id"] = $contact_id;
         if ($group_model->isUserIdInContacts($user_id,
@@ -2211,7 +2228,7 @@ class SocialComponent extends Component implements CrawlConstants
             $group_model->addGroupItem(
                 $existing_parent_id, $user_messages_id, $user_id,
                 $messages_thread_title, "");
-            $request_thread_title = $this->getMessagesThreadTitle(
+            $request_thread_title = $group_model->getMessagesThreadTitle(
                 [$user_id]);
             $request_thread_id = $group_model->getGroupThreadId(
                 $user_messages_id, $contact_id, $request_thread_title);
@@ -2224,12 +2241,12 @@ class SocialComponent extends Component implements CrawlConstants
             $group_model->addGroupItem(0,
                 $user_messages_id, $user_id, $messages_thread_title,
                 "");
-            $blocked_thread_title = $this->getMessagesThreadTitle(
+            $blocked_thread_title = $group_model->getMessagesThreadTitle(
                 [$user_id, "blocked"]);
             $blocked_thread_id = $group_model->getGroupThreadId(
                 $user_messages_id, $contact_id, $blocked_thread_title);
             if (!$blocked_thread_id) {
-                $request_thread_title = $this->getMessagesThreadTitle(
+                $request_thread_title = $group_model->getMessagesThreadTitle(
                     [$contact_id]);
             }
             // thread used to keep tract of contact requests for $contact_id
@@ -2275,7 +2292,7 @@ class SocialComponent extends Component implements CrawlConstants
             return $parent->redirectWithMessage(
                 tl('social_component_invalid_contact'));
         }
-        $request_thread_title = $this->getMessagesThreadTitle(
+        $request_thread_title = $group_model->getMessagesThreadTitle(
             [$user_id]);
         $request_thread_id = $group_model->getGroupThreadId(
             $user_messages_id, $contact_id, $request_thread_title);
@@ -2321,13 +2338,13 @@ class SocialComponent extends Component implements CrawlConstants
                 tl('social_component_invalid_contact'));
         }
         $contact_messages_id = $group_model->getPersonalGroupId($contact_id);
-        $request_thread_title = $this->getMessagesThreadTitle(
+        $request_thread_title = $group_model->getMessagesThreadTitle(
             [$user_id]);
         $request_thread_id = $group_model->getGroupThreadId(
             $user_messages_id, $contact_id, $request_thread_title);
         if (!empty($request_thread_id)) {
             $group_model->deleteGroupItem($request_thread_id, $contact_id);
-            $block_thread_title = $this->getMessagesThreadTitle(
+            $block_thread_title = $group_model->getMessagesThreadTitle(
                 [$contact_id, "blocked"]);
             $group_model->addGroupItem(0,
                 $contact_messages_id, $user_id, $block_thread_title,
@@ -2353,12 +2370,14 @@ class SocialComponent extends Component implements CrawlConstants
         $parent = $this->parent;
         $group_model = $parent->model("group");
         $user_messages_id = $group_model->getPersonalGroupId($user_id);
-        $messages_thread_title = $this->getMessagesThreadTitle(
+        $messages_thread_title = $group_model->getMessagesThreadTitle(
             [$user_id, $data['CONTACT_ID']]);
         $description = $contact_id = $parent->clean($_REQUEST["description"]
             ?? "", 'string');
         $message_thread_id = $group_model->getGroupThreadId($user_messages_id,
             $user_id, $messages_thread_title);
+        $parent->model("impression")->add($user_id, $message_thread_id,
+            C\THREAD_IMPRESSION);
         if (empty($message_thread_id)) {
             return $parent->redirectWithMessage(
                 tl("social_component_invalid_message_thread"),
@@ -2393,19 +2412,6 @@ class SocialComponent extends Component implements CrawlConstants
         }
         return $parent->redirectWithMessage("", ['contact_id']);
     }
-    /**
-     * Messages use the same storage mechanism as group posts, so need a title,
-     * The title used is always computed as the `-` concatenation of the sorted
-     * id's of the participants in the message session. This method computes
-     * such a title string from an array of message participants.
-     * @param array $thread_follower_ids message chat participants
-     * @return string appropriate title of chat thread.
-     */
-    private function getMessagesThreadTitle($thread_follower_ids)
-    {
-        sort($thread_follower_ids);
-        return implode("-", $thread_follower_ids);
-    }
     /**
      * Determines a list of posts that might need to reply to a post in
      * a group
@@ -2925,12 +2931,20 @@ class SocialComponent extends Component implements CrawlConstants
     public function addActivityInfoToGroups(&$data)
     {
         $group_model = $this->parent->model("group");
+        $impression_model = $this->parent->model("impression");
         $num_shown = count($data['GROUPS']);
         $user_id = $_SESSION['USER_ID'] ?? C\PUBLIC_USER_ID;
         for ($i = 0; $i < $num_shown; $i++) {
             $group = $data['GROUPS'][$i];
             $group_id = $group['GROUP_ID'];
             $item = $group_model->getMostRecentGroupPost($group_id);
+            $most_recent_views = $impression_model->mostRecentGroupViews(
+                $user_id, [$group_id]);
+            $group["MOST_RECENT_VIEW"] =
+                $most_recent_views[$group_id] ?? 0;
+            $group['NEW_POSTS'] =
+                $group_model->getGroupPostCount($group_id,
+                $group["MOST_RECENT_VIEW"]);
             $group['NUM_POSTS'] = $group_model->getGroupPostCount($group_id);
             $group['NUM_THREADS'] =
                 $group_model->getGroupThreadCount($group_id);
diff --git a/src/css/search.css b/src/css/search.css
index 47d4e2cf1..62ec87709 100755
--- a/src/css/search.css
+++ b/src/css/search.css
@@ -4137,6 +4137,24 @@ td.instruct
 {
     left:125px;
 }
+.unread-messages
+{
+    background-color: darkgray;
+    border-radius: 6px;
+    border;: solid 1px black;
+    color: white;
+    display:inline-block;
+    padding:3px;
+    margin:18px 15px 18px 15px;
+}
+.html-ltr .unread-messages
+{
+    float: right;
+}
+.html-rtl .unread-messages
+{
+    float: left;
+}
 .message-filter
 {
     position: relative;
diff --git a/src/library/WikiParser.php b/src/library/WikiParser.php
index d9224bf8b..fd0fdd69b 100644
--- a/src/library/WikiParser.php
+++ b/src/library/WikiParser.php
@@ -191,6 +191,8 @@ class WikiParser implements CrawlConstants
             ['/\r/', ""],
         ];
         $braces_substitutions = [
+            ["/{{timestamp}}/si",
+                "<input type='hidden' name='username' value='" .time(). "' />"],
             ["/{{username}}/si",
                 "<input type='hidden' name='username' value='[{username}]' />"],
             ["/{{image-captcha\|(.+?)}}/si", "<div class='csv-captcha'>".
diff --git a/src/models/GroupModel.php b/src/models/GroupModel.php
index 0e000068c..ad2e949a9 100644
--- a/src/models/GroupModel.php
+++ b/src/models/GroupModel.php
@@ -1308,17 +1308,79 @@ class GroupModel extends Model implements MediaConstants
         $row = $db->fetchArray($result);
         return $row['NUM'] ?? 0;
     }
+    /**
+     * Returns the number of posts to groups that a user belongs to since
+     * a timestamp
+     *
+     * @param int $user_id id of the user to get posts for
+     * @param int $timestamp only post with value pubdate greater than
+     *   this will be counted
+     * @return int number of posts
+     */
+    public function getUserPostCount($user_id, $timestamp = 0)
+    {
+        $db = $this->db;
+        $personal_group_id = $this->getPersonalGroupId($user_id);
+        $subselect = "(SELECT GROUP_ID " .
+            "FROM USER_GROUP WHERE USER_ID = ? AND STATUS = ".
+            C\ACTIVE_STATUS . ")";
+        $sql = "SELECT COUNT(DISTINCT GI.ID) AS NUM FROM GROUP_ITEM GI ".
+            "WHERE GI.GROUP_ID IN $subselect AND GI.TITLE NOT LIKE ".
+            "'%$user_id%' AND GI.PUBDATE > ?";
+        $result = $db->execute($sql, [$user_id,  $timestamp]);
+        if (!$result) {
+            return 0;
+        }
+        $row = $db->fetchArray($result);
+        echo "yo";
+        var_dump($row['NUM']);
+        return $row['NUM'] ?? 0;
+    }
     /**
      * Returns the number of posts to a group
      * @param int $group_id id of the group to get post count for
+     * @param int $timestamp only post with value pubdate greater or
+     *  EDIT_DATE than this will be counted
      * @return int number of posts
      */
-    public function getGroupPostCount($group_id)
+    public function getGroupPostCount($group_id, $timestamp = 0)
     {
         $db = $this->db;
         $sql = "SELECT COUNT(DISTINCT GI.ID) AS NUM FROM GROUP_ITEM GI
-            WHERE GI.GROUP_ID = ?";
-        $result = $db->execute($sql, [$group_id]);
+            WHERE GI.GROUP_ID = ? AND GI.PUBDATE > ?";
+        $result = $db->execute($sql, [$group_id, $timestamp]);
+        if (!$result) {
+            return 0;
+        }
+        $row = $db->fetchArray($result);
+        return $row['NUM'] ?? 0;
+    }
+    /**
+     * Messages use the same storage mechanism as group posts, so need a title,
+     * The title used is always computed as the `-` concatenation of the sorted
+     * id's of the participants in the message session. This method computes
+     * such a title string from an array of message participants.
+     * @param array $thread_follower_ids message chat participants
+     * @return string appropriate title of chat thread.
+     */
+    public function getMessagesThreadTitle($thread_follower_ids)
+    {
+        sort($thread_follower_ids);
+        return implode("-", $thread_follower_ids);
+    }
+    /**
+     * Returns the number of posts to a thread
+     * @param int $thread_id id of the thread to get post count for
+     * @param int $timestamp only post with value pubdate greater or
+     *  EDIT_DATE than this will be counted
+     * @return int number of posts
+     */
+    public function getThreadPostCount($thread_id, $timestamp = 0)
+    {
+        $db = $this->db;
+        $sql = "SELECT COUNT(DISTINCT GI.ID) AS NUM FROM GROUP_ITEM GI
+            WHERE GI.PARENT_ID = ? AND GI.PUBDATE > ?";
+        $result = $db->execute($sql, [$thread_id, $timestamp]);
         if (!$result) {
             return 0;
         }
diff --git a/src/models/ImpressionModel.php b/src/models/ImpressionModel.php
index 558c20c52..ca5dbd8ea 100644
--- a/src/models/ImpressionModel.php
+++ b/src/models/ImpressionModel.php
@@ -160,8 +160,51 @@ class ImpressionModel extends Model
         return $rows;
     }
     /**
-     * @param int $user_id
-     * @param array $group_ids
+     * Returns the most recent timestamp of any view impression of a group a
+     * user has.
+     * @param int $user_id want most recent impression for
+     * @return int timstamp of most recent impression
+     */
+    public function mostRecentThreadView($user_id, $thread_id)
+    {
+        $db = $this->db;
+        $sql = "SELECT MAX(UPDATE_TIMESTAMP) AS MOST_RECENT FROM " .
+            "ITEM_IMPRESSION_SUMMARY WHERE USER_ID = ? AND ITEM_TYPE = " .
+            C\THREAD_IMPRESSION . " AND ITEM_ID = ? " .
+            "AND UPDATE_PERIOD = ". C\MOST_RECENT_VIEW;
+        $result = $db->execute($sql, [$user_id,
+            $thread_id]);
+        if (!$result || !($row = $db->fetchArray($result))) {
+            return 0;
+        }
+        return $row["MOST_RECENT"] ?? 0;
+    }
+    /**
+     * Returns the most recent timestamp of any view impression of a group a
+     * user has.
+     * @param int $user_id want most recent impression for
+     * @return int timstamp of most recent impression
+     */
+    public function mostRecentGroupView($user_id)
+    {
+        $db = $this->db;
+        $sql = "SELECT MAX(UPDATE_TIMESTAMP) AS MOST_RECENT FROM " .
+            "ITEM_IMPRESSION_SUMMARY WHERE USER_ID = $user_id AND ITEM_TYPE = " .
+            C\GROUP_IMPRESSION . " AND ITEM_ID IN (SELECT GROUP_ID " .
+            "FROM USER_GROUP WHERE USER_ID = $user_id AND STATUS = ".
+            C\ACTIVE_STATUS . ")  AND UPDATE_PERIOD = ".C\MOST_RECENT_VIEW;
+        $result = $db->execute($sql);
+        if (!$result || !($row = $db->fetchArray($result))) {
+            return 0;
+        }
+        return $row["MOST_RECENT"];
+    }
+    /**
+     * For a list of group_ids that $user_id may belong to, returns an
+     * array of pairs group_id => timestamp of most recent view
+     * @param int $user_id user to look up most recent views for
+     * @param array $group_ids groups to check in
+     * @return array pairs group_id => timestamp
      */
     public function mostRecentGroupViews($user_id, $group_ids)
     {
@@ -173,10 +216,11 @@ class ImpressionModel extends Model
             $comma = ",";
         }
         $in_clause .= ") ";
-        $sql = "SELECT ITEM_ID AS GROUP_ID, MAX(VIEW_DATE) AS MOST_RECENT " .
-            "FROM ITEM_IMPRESSION " .
+        $sql = "SELECT ITEM_ID AS GROUP_ID, UPDATE_TIMESTAMP AS MOST_RECENT " .
+            "FROM ITEM_IMPRESSION_SUMMARY " .
             "WHERE USER_ID = ? AND ITEM_TYPE = " . C\GROUP_IMPRESSION .
-            " AND ITEM_ID IN $in_clause GROUP BY ITEM_ID";
+            " AND ITEM_ID IN $in_clause AND UPDATE_PERIOD = ".
+            C\MOST_RECENT_VIEW . " AND NUM_VIEWS = 0 GROUP BY ITEM_ID";
         $result = $db->execute($sql, [$user_id]);
         if (!$result) {
             return [];
@@ -501,11 +545,22 @@ class ImpressionModel extends Model
             !is_numeric($user_id) || !is_numeric($item_id)) {
             return;
         }
+        $dbinfo = ["DBMS" => C\DBMS, "DB_HOST" => C\DB_HOST,
+            "DB_USER" => C\DB_USER, "DB_PASSWORD" => C\DB_PASSWORD,
+            "DB_NAME" => C\DB_NAME];
         $sql = "INSERT INTO ITEM_IMPRESSION VALUES (?, ?, ?, ?)";
         if ($user_id != C\PUBLIC_USER_ID) {
             $db->execute($sql, [$user_id, $item_id, $type_id, time()]);
         }
         $db->execute($sql, [C\PUBLIC_USER_ID, $item_id, $type_id, time()]);
+        $sql = "DELETE FROM ITEM_IMPRESSION_SUMMARY
+            WHERE USER_ID=? AND ITEM_ID=? AND ITEM_TYPE=? AND UPDATE_PERIOD = ".
+            C\MOST_RECENT_VIEW;
+        $db->execute($sql, [$user_id, $item_id, $type_id]);
+        $sql = "INSERT INTO ITEM_IMPRESSION_SUMMARY VALUES
+            (?, ?, ?, " . C\MOST_RECENT_VIEW .", ?, 0, -1, -1)";
+        $sql = $db->insertIgnore($sql, $dbinfo);
+        $db->execute($sql, [$user_id, $item_id, $type_id, time()]);
         $sql = "UPDATE ITEM_IMPRESSION_SUMMARY
             SET NUM_VIEWS = NUM_VIEWS + 1 WHERE USER_ID=? AND
             ITEM_ID=? AND ITEM_TYPE=? AND UPDATE_PERIOD = ". C\FOREVER . "
diff --git a/src/views/elements/GroupfeedElement.php b/src/views/elements/GroupfeedElement.php
index 38ed77ac8..2e9fb528c 100644
--- a/src/views/elements/GroupfeedElement.php
+++ b/src/views/elements/GroupfeedElement.php
@@ -54,7 +54,7 @@ class GroupfeedElement extends Element implements CrawlConstants
         $logged_in = !empty($data["ADMIN"]);
         $is_status = isset($data['STATUS']);
         $is_api = !empty($data['API']);
-        $token = ($logged_in) ? C\CSRF_TOKEN . "=".
+        $token = ($logged_in) ? C\CSRF_TOKEN . "=" .
             $data[C\CSRF_TOKEN] : "";
         $base_query = B\feedsUrl("", "", true, $data['CONTROLLER']) .
             $token;
diff --git a/src/views/elements/SocialcontrolsElement.php b/src/views/elements/SocialcontrolsElement.php
index abfcefdcd..976304047 100644
--- a/src/views/elements/SocialcontrolsElement.php
+++ b/src/views/elements/SocialcontrolsElement.php
@@ -64,7 +64,7 @@ class SocialcontrolsElement extends Element
         <?=$icon_helper->renderButton($messages_url, 'messages',
             $data['UNREAD_MESSAGES'] ?? "");?>
         <?=$icon_helper->renderButton($feed_url ."&amp;v=ungrouped",
-            'combined_discussions', $data['UNREAD_DISCUSSIONS'] ?? "");?>
+            'combined_discussions', $data['UNREAD_POSTS'] ?? "");?>
         <?=$icon_helper->renderButton($feed_url, 'groups',
             $data['NUM_GROUPS'] ?? "");?>
         <?=$icon_helper->renderButton($group_url ."&amp;browse=true",
diff --git a/src/views/elements/UsermessagesElement.php b/src/views/elements/UsermessagesElement.php
index bf46ae708..7a2357cdd 100644
--- a/src/views/elements/UsermessagesElement.php
+++ b/src/views/elements/UsermessagesElement.php
@@ -134,7 +134,11 @@ class UsermessagesElement extends Element implements CrawlConstants
             ?>
             <div class="contact-detail <?=$selected?>" tabindex="0"
                 onclick="javascript:elt('contact-id-<?=$contact_id
-                    ?>').click()">
+                    ?>').click()"><?php
+            if ($contact_info['NUM_UNREAD_MESSAGES'] > 0) {?>
+                <div class='unread-messages'><?=
+                    $contact_info['NUM_UNREAD_MESSAGES'] ?></div><?php
+            }?>
             <div class="contact-detail-inner">
             <img class="message-icon" src="<?=
                 $contact_info["ICON_URL"]; ?>"
diff --git a/src/views/helpers/GrouplistHelper.php b/src/views/helpers/GrouplistHelper.php
index 839a689c8..5acddaa64 100644
--- a/src/views/helpers/GrouplistHelper.php
+++ b/src/views/helpers/GrouplistHelper.php
@@ -166,8 +166,9 @@ class GrouplistHelper extends Helper
                 e(" ");
                 $icon_helper->renderButton(
                     htmlentities(B\feedsUrl("group", $group['GROUP_ID'], true,
-                    $controller)) . $token, 'group_feed', "", false,
-                    "media-buttons-container small-margin" ,
+                    $controller)) . $token, 'group_feed',
+                    $group['NEW_POSTS'] ?? 0,
+                    false, "media-buttons-container small-margin" ,
                     "media-anchor-button small-font", true);
                 e(" ");
                 $icon_helper->renderButton(htmlentities(B\wikiUrl(
ViewGit