diff --git a/core/components/com_forum/api/controllers/likesv1_0.php b/core/components/com_forum/api/controllers/likesv1_0.php new file mode 100644 index 00000000000..3a6110997bb --- /dev/null +++ b/core/components/com_forum/api/controllers/likesv1_0.php @@ -0,0 +1,82 @@ +setQuery($query); + $rows = $database->loadObjectList(); + + // return results + $object = new stdClass(); + $object->assertions = $rows; + + $this->send($object); + } + + /** + * POST: Create a like for a forum post + */ + public function addLikeToPostTask() { + $threadId = Request::getString('threadId'); + $postId = Request::getString('postId'); + $userId = Request::getString('userId'); + $created = Date::of('now')->toSql(); + + $db = \App::get('db'); + $insertQuery = "INSERT INTO `#__forum_posts_like` (`threadId`, `postId`, `userId`, `created`) + VALUES (?,?,?,?)"; + + $insertVars = array($threadId, $postId, $userId, $created); + $db->prepare($insertQuery); + $db->bind($insertVars); + $insertResult = $db->execute(); + + $this->send($insertResult); + } + + // DELETE: Delete a like from a post + public function deleteLikeFromPostTask() { + $threadId = Request::getString('threadId'); + $postId = Request::getString('postId'); + $userId = Request::getString('userId'); + + // Open up the database tables + $db = \App::get('db'); + + $deleteQuery = "DELETE FROM `#__forum_posts_like` WHERE threadId = ? AND postId = ? AND userId = ?"; + $deleteVars = array($threadId, $postId, $userId); + $db->prepare($deleteQuery); + $db->bind($deleteVars); + $deleteResult = $db->execute(); + + $this->send($deleteResult); + } +} diff --git a/core/components/com_forum/api/router.php b/core/components/com_forum/api/router.php index 04aab652e23..69558f9254a 100644 --- a/core/components/com_forum/api/router.php +++ b/core/components/com_forum/api/router.php @@ -30,6 +30,12 @@ public function build(&$query) unset($query['controller']); } + if (!empty($query['id'])) + { + $segments[] = $query['id']; + unset($query['id']); + } + if (!empty($query['task'])) { $segments[] = $query['task']; @@ -46,32 +52,24 @@ public function build(&$query) * @return array The URL attributes to be used by the application. */ public function parse(&$segments) - { - $vars = array(); + { + $vars = array(); - $vars['controller'] = 'threads'; + if (empty($segments)) + { + return $vars; + } - if (isset($segments[0])) - { - if (is_numeric($segments[0])) - { - $vars['id'] = $segments[0]; - if (\App::get('request')->method() == 'GET') - { - $vars['task'] = 'read'; - } - } - else - { - $vars['task'] = $segments[0]; - } + if (isset($segments[0])) + { + $vars['id'] = $segments[0]; + } - if (isset($segments[1])) - { - $vars['task'] = $segments[1]; - } - } + if (isset($segments[1])) + { + $vars['task'] = $segments[1]; + } - return $vars; - } + return $vars; + } } diff --git a/core/components/com_forum/config/access.xml b/core/components/com_forum/config/access.xml index 91a57815e67..1db40b120da 100644 --- a/core/components/com_forum/config/access.xml +++ b/core/components/com_forum/config/access.xml @@ -8,6 +8,7 @@
+ @@ -68,4 +69,4 @@
-
\ No newline at end of file + diff --git a/core/components/com_forum/migrations/Migration20240815000000ComForum.php b/core/components/com_forum/migrations/Migration20240815000000ComForum.php new file mode 100644 index 00000000000..0d9807fa4dd --- /dev/null +++ b/core/components/com_forum/migrations/Migration20240815000000ComForum.php @@ -0,0 +1,34 @@ +db->tableExists('#__forum_posts_like')) { + $query = "CREATE TABLE `#__forum_posts_like` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `threadId` varchar(255) NOT NULL, + `postId` varchar(255) NOT NULL, + `userId` varchar(255) NOT NULL, + `created` datetime DEFAULT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; + + $this->db->setQuery($query); + $this->db->query(); + } + } + + public function down(){ + $query = "DROP TABLE IF EXISTS `#__forum_posts_like`"; + $this->db->setQuery($query); + $this->db->query(); + } +} diff --git a/core/components/com_forum/site/assets/css/like.css b/core/components/com_forum/site/assets/css/like.css new file mode 100644 index 00000000000..38910761dee --- /dev/null +++ b/core/components/com_forum/site/assets/css/like.css @@ -0,0 +1,60 @@ +/* Should put this in the comments.less file */ +.comment-body a.like { + color: #dadada; + margin-right: 5px; + font-size:20px; +} + +.comment-body a.like:hover { + color: #a6a6a6; +} + +.comment-body a.userLiked { + color: red; +} + +.comment-body a.userLiked:hover { + color: #ce0202; +} + +.comment-body .likesStat { + font-size: 14px; + vertical-align:middle; + margin-top: 5px; + text-decoration: underline; +} + +.comment-body .noLikes { + text-decoration: none; +} + +.comment-body .likesStat:hover { + cursor: pointer; + color: black; + text-decoration: underline; +} + +.comment-body .noLikes:hover { + cursor: auto; + text-decoration: none; +} + +.elementInline { + display: inline-flex; + float: right; + vertical-align:middle; +} + +.whoLikedPost { + height: 0; + overflow: hidden; + transition: height 0.8s ease; +} + +.names { + padding: 10px; + background-color: #eeeeee; + border-radius: 5px; + text-align: right; + font-size: 12px; +} \ No newline at end of file diff --git a/core/components/com_forum/site/assets/js/like.js b/core/components/com_forum/site/assets/js/like.js new file mode 100644 index 00000000000..12e400ea9f8 --- /dev/null +++ b/core/components/com_forum/site/assets/js/like.js @@ -0,0 +1,150 @@ +window.addEventListener('DOMContentLoaded', (domEvent) => { + // Find all the "like" / stat button + const commentSections = document.querySelectorAll('.comment-content') + if (commentSections.length) { + for(let i = 0; i < commentSections.length;i++) { + let likeButton = commentSections[i].querySelector('.like'); + let likeStatsLink = commentSections[i].querySelector('.likesStat'); + let whoLikedPostDiv = commentSections[i].querySelector('.whoLikedPost'); + + // Make sure all these HTML elements are present before assigning attributes + if (likeStatsLink && whoLikedPostDiv && likeButton) { + likeStatsLink.onclick = (e) => { + e.preventDefault(); + this.__toggle = !this.__toggle; + if(this.__toggle) { + whoLikedPostDiv.style.height = `${whoLikedPostDiv.scrollHeight}px`; + } else { + whoLikedPostDiv.style.height = 0; + } + } + + likeButton.onclick = (e) => { + e.preventDefault(); + + let hasHeart = likeButton.classList.contains("userLiked"); + + const threadId = likeButton.dataset.thread; + const postId = likeButton.dataset.post; + const userId = likeButton.dataset.user; + const userName = likeButton.dataset.userName; + const nameAndId = `${userName}#${userId}`; + + const likesList = likeButton.dataset.likesList; + const likeCount = likeButton.dataset.count; + + // console.log(threadId, postId, userId, likeCount, userName, likesList); + + const likesListArray = likesList.split("/"); + + if (hasHeart) { + removeLike(threadId, postId, userId).then((res) => { + if (res.ok) { + const newLikeCount = Number(likeCount) - 1; + const newLikesString = likesListArray.filter(e => e !== nameAndId).join('/'); + + likeButton.dataset.count = `${newLikeCount}`; + likeButton.classList.remove("userLiked"); + likeButton.dataset.likesList = newLikesString; + likeStatsLink.innerHTML = (newLikeCount === 0) ? 'No Likes' : `View Likes (${newLikeCount})`; + + if (newLikeCount > 0) { + let whoLikedArray = []; + const newLikesArray = newLikesString.split("/"); + for (let i = 0; i < newLikesArray.length; i++) { + const nameArray = newLikesArray[i].split('#') + const userName = nameArray[0]; + const userId = nameArray[1]; + const userProfileUrl = `/members/${userId}/profile`; + + whoLikedArray.push(`${userName}`); + } + + likeStatsLink.classList.remove("noLikes"); + whoLikedPostDiv.innerHTML = "
" + whoLikedArray.join(', ') + " liked this
"; + } else { + likeStatsLink.classList.add("noLikes"); + whoLikedPostDiv.innerHTML = ""; + } + + // console.warn(`Like removed for forum thread '${threadId}' of post '${postId}' for user ${userId}`); + } + }) + } else { + addLike(threadId, postId, userId).then((res) => { + if (res.ok) { + const newLikeCount = Number(likeCount) + 1; + const newLikesString = [...likesListArray, nameAndId].filter(Boolean).join('/'); + + likeButton.dataset.count = `${newLikeCount}`; + likeButton.classList.add("userLiked"); + likeButton.dataset.likesList = newLikesString; + likeStatsLink.innerHTML = `View Likes (${newLikeCount})`; + likeStatsLink.classList.remove("noLikes"); + + let whoLikedArray = []; + const newLikesArray = newLikesString.split("/"); + for (let i = 0; i < newLikesArray.length; i++) { + const nameArray = newLikesArray[i].split('#') + const userName = nameArray[0]; + const userId = nameArray[1]; + const userProfileUrl = `/members/${userId}/profile`; + + whoLikedArray.push(`${userName}`); + } + + whoLikedPostDiv.innerHTML = "
" + whoLikedArray.join(', ') + " liked this
"; + + // console.log(`Like recorded for forum thread '${threadId}' of post '${postId}' for user ${userId}`); + } + }) + } + + return false; + }; + } + } + } +}); + +const addLike = async (threadId, postId, userId) => { + const postUrl = "/api/forum/likes/addLikeToPost"; + const data = {threadId, postId, userId}; + + try { + let response = await fetch(postUrl, { + method: "POST", headers: {"Content-Type": "application/x-www-form-urlencoded"}, + body: new URLSearchParams(data) // urlencoded form body + }); + + if (!response.ok) { + window.confirm("Server Error with API"); + console.error(`Error Code: ${response.status} / Error Message: ${response.statusText}`); + } + + return response; + } catch (error) { + if (error instanceof SyntaxError) { + console.error('There was a SyntaxError', error); + } else { + console.error('There was an error', error); + } + } +}; + +const removeLike = async (threadId, postId, userId) => { + const deleteAssertionUrl = "/api/forum/likes/deleteLikeFromPost"; + const data = {threadId, postId, userId}; + + const deleteAssertionResp = await fetch(deleteAssertionUrl, { + method: "DELETE", headers: {"Content-Type": "application/x-www-form-urlencoded"}, + body: new URLSearchParams(data) + }) + + if (!deleteAssertionResp.ok) { + window.confirm("Server Error with API"); + console.error(`Error Code: ${response.status} / Error Message: ${response.statusText}`); + } + + return deleteAssertionResp; +} \ No newline at end of file diff --git a/core/components/com_forum/site/controllers/threads.php b/core/components/com_forum/site/controllers/threads.php index 051f1fa7c4a..0162956da86 100644 --- a/core/components/com_forum/site/controllers/threads.php +++ b/core/components/com_forum/site/controllers/threads.php @@ -184,6 +184,16 @@ public function displayTask() // Set the pathway $this->buildPathway($section, $category, $thread); + // Get all the likes of this thread + $db = \App::get('db'); + $queryLikes = "SELECT LIKES.threadId as 'threadId', LIKES.postId as 'postId', + LIKES.userId as 'userId', USERS.name as 'userName', USERS.email as 'userEmail' + FROM jos_forum_posts_like as LIKES, jos_users AS USERS + WHERE LIKES.userId = USERS.id AND LIKES.threadId = " . $thread->get('id'); + + $db->setQuery($queryLikes); + $initialLikesList = $db->loadObjectList(); + // Output the view $this->view ->set('config', $this->config) @@ -192,6 +202,7 @@ public function displayTask() ->set('category', $category) ->set('thread', $thread) ->set('filters', $filters) + ->set('likes', $initialLikesList) ->setErrors($this->getErrors()) ->display(); } diff --git a/core/components/com_forum/site/forum.php b/core/components/com_forum/site/forum.php index a0577d99fe2..5032dd13239 100644 --- a/core/components/com_forum/site/forum.php +++ b/core/components/com_forum/site/forum.php @@ -17,6 +17,16 @@ require_once __DIR__ . DS . 'controllers' . DS . $controllerName . '.php'; $controllerName = __NAMESPACE__ . '\\Controllers\\' . ucfirst(strtolower($controllerName)); +if (!User::authorise('core.access', 'com_forum')) +{ + $return = base64_encode(Request::getString('REQUEST_URI', '', 'server')); + //$return = base64_encode($_SERVER['REQUEST_URI']); + App::redirect( Route::url('index.php?option=com_users&view=login&return=' . $return, false), + Lang::txt('COM_FORUM_ALERTLOGIN_REQUIRED'), + 'warning' + ); +} + // Instantiate controller $controller = new $controllerName(); $controller->execute(); diff --git a/core/components/com_forum/site/views/threads/tmpl/_comment.php b/core/components/com_forum/site/views/threads/tmpl/_comment.php index 82b6231e022..23c6076d46c 100644 --- a/core/components/com_forum/site/views/threads/tmpl/_comment.php +++ b/core/components/com_forum/site/views/threads/tmpl/_comment.php @@ -7,6 +7,24 @@ defined('_HZEXEC_') or die(); +$this->css('like.css') + ->js('like.js'); + + $likeArray = $this->like; + $countLike = count($likeArray); + $currentUserId = User::get('id'); + + $userLikesComment = false; + $userNameLikesArray = ""; + foreach ( $likeArray as $likeObj ) { + if ( $currentUserId == $likeObj->userId ) { + $userLikesComment = true; + } + + $userNameLikesArray .= "/" . ($likeObj->userName) . "#" . ($likeObj->userId); + } + $userNameLikesArray = substr($userNameLikesArray,1); + $this->comment->set('section', $this->filters['section']); $this->comment->set('category', $this->category->get('alias')); @@ -54,7 +72,45 @@

+ + +
+ + "> + 0) ? "View Likes (" . $countLike . ")" : "No Likes"; ?> + +
+
+ +
+ 0) { ?> +
+ $userName"; + } + echo join(", ", $links) . " liked this"; + ?> +
+ +
+
+
comment->attachments()->whereEquals('state', Components\Forum\Models\Attachment::STATE_PUBLISHED)->rows() as $attachment) diff --git a/core/components/com_forum/site/views/threads/tmpl/_list.php b/core/components/com_forum/site/views/threads/tmpl/_list.php index f9ed7328ceb..8af8e534114 100644 --- a/core/components/com_forum/site/views/threads/tmpl/_list.php +++ b/core/components/com_forum/site/views/threads/tmpl/_list.php @@ -8,11 +8,22 @@ // No direct access defined('_HZEXEC_') or die(); +$hash_map = array(); +foreach ($this->likes as $like){ + $postId = $like->postId; + if (isset($hash_map[$postId])) { + $hash_map[$postId][] = $like; + } else { + $hash_map[$postId] = array($like); + } +} + +//print_r($hash_map); + ?>
    comments) -{ +if ($this->comments) { $cls = 'odd'; if (isset($this->cls)) { @@ -26,12 +37,17 @@ $this->depth++; - foreach ($this->comments as $comment) - { + foreach ($this->comments as $comment) { + + $postId = $comment->get('id'); + $likesByPostId = isset($hash_map[$postId]) ? $hash_map[$postId] : []; + $this->view('_comment') ->set('option', $this->option) ->set('controller', $this->controller) ->set('comment', $comment) + ->set('like', $likesByPostId) + ->set('likes', $this->likes) ->set('thread', $this->thread) ->set('config', $this->config) ->set('depth', $this->depth) diff --git a/core/components/com_forum/site/views/threads/tmpl/display.php b/core/components/com_forum/site/views/threads/tmpl/display.php index 07541aa8f66..e0b4c775ed0 100644 --- a/core/components/com_forum/site/views/threads/tmpl/display.php +++ b/core/components/com_forum/site/views/threads/tmpl/display.php @@ -60,6 +60,7 @@ ->set('controller', $this->controller) ->set('comments', $posts) ->set('thread', $this->thread) + ->set('likes', $this->likes) ->set('parent', 0) ->set('config', $this->config) ->set('depth', 0) diff --git a/core/components/com_groups/admin/language/en-GB/en-GB.com_groups.ini b/core/components/com_groups/admin/language/en-GB/en-GB.com_groups.ini index 6763e189af0..ce4449036f0 100644 --- a/core/components/com_groups/admin/language/en-GB/en-GB.com_groups.ini +++ b/core/components/com_groups/admin/language/en-GB/en-GB.com_groups.ini @@ -548,8 +548,13 @@ COM_GROUPS_CONFIG_DEFAULT_DISCOVERABILITY_VISIBLE="Visible" COM_GROUPS_CONFIG_DEFAULT_DISCOVERABILITY_HIDDEN="Hidden" COM_GROUPS_CONFIG_DISPLAY_SYSTEM_USERS="Display System Users?" COM_GROUPS_CONFIG_DISPLAY_SYSTEM_USERS_DESC="Display system users in group member lists?" -COM_GROUPS_CONFIG_EMAIL_PROCESSING="Enable discussion comments via E-mail" -COM_GROUPS_CONFIG_EMAIL_PROCESSING_DESC="Enable logic to send group discussion comments via email and process and add responses via email" + +COM_GROUPS_CONFIG_FORUM_OUTGOING_EMAIL="Allow outgoing email notifications to users for forum posts" +COM_GROUPS_CONFIG_FORUM_OUTGOING_EMAIL_DESC="Allow notification of individual users of forum posts via email" + +COM_GROUPS_CONFIG_INCOMING_EMAIL_PROCESSING="Enable incoming user discussion comments via email" +COM_GROUPS_CONFIG_INCOMING_EMAIL_PROCESSING_DESC="Allow user comments sent via email to be added to group forum posts" + COM_GROUPS_CONFIG_EMAIL_PROCESSING_AUTOSUBSCRIBE="Enable groups to auto setup new members to receive discussion email by default" COM_GROUPS_CONFIG_EMAIL_PROCESSING_AUTOSUBSCRIBE_DESC="Automatically setup new group users to get email from the group discussions" COM_GROUPS_CONFIG_EMAIL_FORUM_DIGEST="Allow forum digest?" diff --git a/core/components/com_groups/admin/views/manage/tmpl/edit.php b/core/components/com_groups/admin/views/manage/tmpl/edit.php index d5d1bcef849..23f26aaaf60 100644 --- a/core/components/com_groups/admin/views/manage/tmpl/edit.php +++ b/core/components/com_groups/admin/views/manage/tmpl/edit.php @@ -27,7 +27,7 @@ // are we using the email gateway for group forum $params = Component::params('com_groups'); -$allowEmailResponses = $params->get('email_comment_processing', 0); +$emailForumComments = $params->get('email_forum_comments', 0); $autoEmailResponses = $this->group->get('discussion_email_autosubscribe'); if (is_null($autoEmailResponses)) @@ -300,7 +300,7 @@
- +
diff --git a/core/components/com_groups/config/config.xml b/core/components/com_groups/config/config.xml index 84b68322f9e..12c02c6c650 100644 --- a/core/components/com_groups/config/config.xml +++ b/core/components/com_groups/config/config.xml @@ -53,7 +53,11 @@
- + + + + + diff --git a/core/components/com_groups/site/views/emails/tmpl/saved.php b/core/components/com_groups/site/views/emails/tmpl/saved.php index d2e3848797f..be42b34b4b4 100644 --- a/core/components/com_groups/site/views/emails/tmpl/saved.php +++ b/core/components/com_groups/site/views/emails/tmpl/saved.php @@ -326,7 +326,7 @@ - get('email_comment_processing')) :?> + get('email_forum_comments')) :?> diff --git a/core/components/com_groups/site/views/emails/tmpl/saved_plain.php b/core/components/com_groups/site/views/emails/tmpl/saved_plain.php index 096f38201e6..94e07814000 100644 --- a/core/components/com_groups/site/views/emails/tmpl/saved_plain.php +++ b/core/components/com_groups/site/views/emails/tmpl/saved_plain.php @@ -93,7 +93,7 @@ $message .= "\n"; $params = Component::params('com_groups'); -if ($params->get('email_comment_processing')) +if ($params->get('email_forum_comments')) { $message .= "\t" . Lang::txt('Discussion Group Emails Autosubscribe:') . ' ' . ($this->group->get('discussion_email_autosubscribe') ? Lang::txt('On') : Lang::txt('Off')) . "\n\n"; } diff --git a/core/components/com_groups/site/views/groups/tmpl/edit.php b/core/components/com_groups/site/views/groups/tmpl/edit.php index 8f067c9bfaa..d9702fdb486 100644 --- a/core/components/com_groups/site/views/groups/tmpl/edit.php +++ b/core/components/com_groups/site/views/groups/tmpl/edit.php @@ -17,7 +17,7 @@ //are we using the email gateway for group forum $params = Component::params('com_groups'); -$allowEmailResponses = $params->get('email_comment_processing', 0); +$emailForumComments = $params->get('email_forum_comments', 0); $autoEmailResponses = $params->get('email_member_groupsidcussionemail_autosignup', 0); //default logo @@ -263,7 +263,7 @@
- +

diff --git a/core/components/com_members/site/controllers/profiles.php b/core/components/com_members/site/controllers/profiles.php index 60b7601360e..01cec291194 100644 --- a/core/components/com_members/site/controllers/profiles.php +++ b/core/components/com_members/site/controllers/profiles.php @@ -271,7 +271,7 @@ public function displayTask() * @return void */ public function browseTask() - { + { // Get all the fields we can use on this page $fields = Field::all() ->whereIn('action_browse', User::getAuthorisedViewLevels()) diff --git a/core/components/com_members/site/views/profiles/tmpl/browse.php b/core/components/com_members/site/views/profiles/tmpl/browse.php index b13c372f61b..2d15f9705f3 100644 --- a/core/components/com_members/site/views/profiles/tmpl/browse.php +++ b/core/components/com_members/site/views/profiles/tmpl/browse.php @@ -237,7 +237,7 @@ -
+
rows->count() > 0) diff --git a/core/plugins/groups/forum/assets/css/like.css b/core/plugins/groups/forum/assets/css/like.css new file mode 100644 index 00000000000..38910761dee --- /dev/null +++ b/core/plugins/groups/forum/assets/css/like.css @@ -0,0 +1,60 @@ +/* Should put this in the comments.less file */ +.comment-body a.like { + color: #dadada; + margin-right: 5px; + font-size:20px; +} + +.comment-body a.like:hover { + color: #a6a6a6; +} + +.comment-body a.userLiked { + color: red; +} + +.comment-body a.userLiked:hover { + color: #ce0202; +} + +.comment-body .likesStat { + font-size: 14px; + vertical-align:middle; + margin-top: 5px; + text-decoration: underline; +} + +.comment-body .noLikes { + text-decoration: none; +} + +.comment-body .likesStat:hover { + cursor: pointer; + color: black; + text-decoration: underline; +} + +.comment-body .noLikes:hover { + cursor: auto; + text-decoration: none; +} + +.elementInline { + display: inline-flex; + float: right; + vertical-align:middle; +} + +.whoLikedPost { + height: 0; + overflow: hidden; + transition: height 0.8s ease; +} + +.names { + padding: 10px; + background-color: #eeeeee; + border-radius: 5px; + text-align: right; + font-size: 12px; +} \ No newline at end of file diff --git a/core/plugins/groups/forum/assets/js/like.js b/core/plugins/groups/forum/assets/js/like.js new file mode 100644 index 00000000000..2c2fbf017b9 --- /dev/null +++ b/core/plugins/groups/forum/assets/js/like.js @@ -0,0 +1,150 @@ +window.addEventListener('DOMContentLoaded', (domEvent) => { + // Find all the "like" / stat button + const commentSections = document.querySelectorAll('.comment-content') + if (commentSections.length) { + for(let i = 0; i < commentSections.length;i++) { + let likeButton = commentSections[i].querySelector('.like'); + let likeStatsLink = commentSections[i].querySelector('.likesStat'); + let whoLikedPostDiv = commentSections[i].querySelector('.whoLikedPost'); + + // Make sure all these HTML elements are present before assigning attributes + if (likeStatsLink && whoLikedPostDiv && likeButton) { + likeStatsLink.onclick = (e) => { + e.preventDefault(); + this.__toggle = !this.__toggle; + if(this.__toggle) { + whoLikedPostDiv.style.height = `${whoLikedPostDiv.scrollHeight}px`; + } else { + whoLikedPostDiv.style.height = 0; + } + } + + likeButton.onclick = (e) => { + e.preventDefault(); + + let hasHeart = likeButton.classList.contains("userLiked"); + + const threadId = likeButton.dataset.thread; + const postId = likeButton.dataset.post; + const userId = likeButton.dataset.user; + const userName = likeButton.dataset.userName; + const nameAndId = `${userName}#${userId}`; + + const likesList = likeButton.dataset.likesList; + const likeCount = likeButton.dataset.count; + + // console.log(threadId, postId, userId, likeCount, userName, likesList); + + const likesListArray = likesList.split("/"); + + if (hasHeart) { + removeLike(threadId, postId, userId).then((res) => { + if (res.ok) { + const newLikeCount = Number(likeCount) - 1; + const newLikesString = likesListArray.filter(e => e !== nameAndId).join('/'); + + likeButton.dataset.count = `${newLikeCount}`; + likeButton.classList.remove("userLiked"); + likeButton.dataset.likesList = newLikesString; + likeStatsLink.innerHTML = (newLikeCount === 0) ? 'No Likes' : `View Likes (${newLikeCount})`; + + if (newLikeCount > 0) { + let whoLikedArray = []; + const newLikesArray = newLikesString.split("/"); + for (let i = 0; i < newLikesArray.length; i++) { + const nameArray = newLikesArray[i].split('#') + const userName = nameArray[0]; + const userId = nameArray[1]; + const userProfileUrl = `/members/${userId}/profile`; + + whoLikedArray.push(`${userName}`); + } + + likeStatsLink.classList.remove("noLikes"); + whoLikedPostDiv.innerHTML = "
" + whoLikedArray.join(', ') + " liked this
"; + } else { + likeStatsLink.classList.add("noLikes"); + whoLikedPostDiv.innerHTML = ""; + } + + // console.warn(`Like removed for forum thread '${threadId}' of post '${postId}' for user ${userId}`); + } + }) + } else { + addLike(threadId, postId, userId).then((res) => { + if (res.ok) { + const newLikeCount = Number(likeCount) + 1; + const newLikesString = [...likesListArray, nameAndId].filter(Boolean).join('/'); + + likeButton.dataset.count = `${newLikeCount}`; + likeButton.classList.add("userLiked"); + likeButton.dataset.likesList = newLikesString; + likeStatsLink.innerHTML = `View Likes (${newLikeCount})`; + likeStatsLink.classList.remove("noLikes"); + + let whoLikedArray = []; + const newLikesArray = newLikesString.split("/"); + for (let i = 0; i < newLikesArray.length; i++) { + const nameArray = newLikesArray[i].split('#') + const userName = nameArray[0]; + const userId = nameArray[1]; + const userProfileUrl = `/members/${userId}/profile`; + + whoLikedArray.push(`${userName}`); + } + + whoLikedPostDiv.innerHTML = "
" + whoLikedArray.join(', ') + " liked this
"; + + // console.log(`Like recorded for forum thread '${threadId}' of post '${postId}' for user ${userId}`); + } + }) + } + + return false; + }; + } + } + } +}); + +const addLike = async (threadId, postId, userId) => { + const postUrl = "/api/forum/likes/addLikeToPost"; + const data = {threadId, postId, userId}; + + try { + let response = await fetch(postUrl, { + method: "POST", headers: {"Content-Type": "application/x-www-form-urlencoded"}, + body: new URLSearchParams(data) // urlencoded form body + }); + + if (!response.ok) { + window.confirm("Server Error with API"); + console.error(`Error Code: ${response.status} / Error Message: ${response.statusText}`); + } + + return response; + } catch (error) { + if (error instanceof SyntaxError) { + console.error('There was a SyntaxError', error); + } else { + console.error('There was an error', error); + } + } +}; + +const removeLike = async (threadId, postId, userId) => { + const deleteAssertionUrl = "/api/forum/likes/deleteLikeFromPost"; + const data = {threadId, postId, userId}; + + const deleteAssertionResp = await fetch(deleteAssertionUrl, { + method: "DELETE", headers: {"Content-Type": "application/x-www-form-urlencoded"}, + body: new URLSearchParams(data) + }) + + if (!deleteAssertionResp.ok) { + window.confirm("Server Error with API"); + console.error(`Error Code: ${response.status} / Error Message: ${response.statusText}`); + } + + return deleteAssertionResp; +} \ No newline at end of file diff --git a/core/plugins/groups/forum/forum.php b/core/plugins/groups/forum/forum.php index 9dfb97f3d89..47489389628 100644 --- a/core/plugins/groups/forum/forum.php +++ b/core/plugins/groups/forum/forum.php @@ -1246,6 +1246,15 @@ public function threads() $this->_authorize('thread', $thread->get('id')); $this->_authorize('post'); + // Get all the likes of this thread + $db = \App::get('db'); + $queryLikes = "SELECT LIKES.threadId as 'threadId', LIKES.postId as 'postId', + LIKES.userId as 'userId', USERS.name as 'userName', USERS.email as 'userEmail' + FROM jos_forum_posts_like as LIKES, jos_users AS USERS + WHERE LIKES.userId = USERS.id AND LIKES.threadId = " . $thread->get('id'); + $db->setQuery($queryLikes); + $initialLikesList = $db->loadObjectList(); + // If the access is anything beyond public, // make sure they're logged in. if (User::isGuest() && !in_array($thread->get('access'), User::getAuthorisedViewLevels())) @@ -1280,6 +1289,7 @@ public function threads() ->set('section', $section) ->set('category', $category) ->set('thread', $thread) + ->set('likes', $initialLikesList) ->set('filters', $filters) ->setErrors($this->getErrors()) ->loadTemplate(); @@ -1505,7 +1515,7 @@ public function savethread() // Email the group and insert email tokens to allow them to respond to group posts via email $params = Component::params('com_groups'); - if ($params->get('email_comment_processing') && (isset($moving) && $moving == false)) + if ($params->get('email_forum_comments') && (isset($moving) && $moving == false)) { $thread->set('section', $section->get('alias')); $thread->set('category', $category->get('alias')); @@ -1552,8 +1562,17 @@ public function savethread() $unsubscribeToken = $encryptor->buildEmailToken(1, 3, $userID, $this->group->get('gidNumber')); $unsubscribeLink = rtrim(Request::base(), '/') . '/' . ltrim(Route::url('index.php?option=com_groups&cn=' . $this->group->get('cn') .'&active=forum&action=unsubscribe&t=' . $unsubscribeToken), DS); - $from['replytoname'] = Lang::txt('PLG_GROUPS_FORUM_REPLYTO') . ' @ ' . Config::get('sitename'); - $from['replytoemail'] = 'hgm-' . $token . '@' . $_SERVER['HTTP_HOST']; + + if(Component::params('com_groups')->get('email_comment_processing')) + { + $from['replytoname'] = Lang::txt('PLG_GROUPS_FORUM_REPLYTO') . ' @ ' . Config::get('sitename'); + $from['replytoemail'] = 'hgm-' . $token . '@' . $_SERVER['HTTP_HOST']; + } + else + { + $from['replytoname'] = 'noreply'; + $from['replytoemail'] = 'noreply@' . $_SERVER['HTTP_HOST']; + } } $msg = array(); diff --git a/core/plugins/groups/forum/language/en-GB/en-GB.plg_groups_forum.ini b/core/plugins/groups/forum/language/en-GB/en-GB.plg_groups_forum.ini index 978b1cf6ec8..00ac5450a32 100644 --- a/core/plugins/groups/forum/language/en-GB/en-GB.plg_groups_forum.ini +++ b/core/plugins/groups/forum/language/en-GB/en-GB.plg_groups_forum.ini @@ -72,7 +72,7 @@ PLG_GROUPS_FORUM_EMAIL_POSTS_DIGEST="digest email" PLG_GROUPS_FORUM_EMAIL_POSTS_DAILY="Daily" PLG_GROUPS_FORUM_EMAIL_POSTS_WEEKLY="Weekly" PLG_GROUPS_FORUM_EMAIL_POSTS_MONTHLY="Monthly" -PLG_GROUPS_FORUM_EMAIL_POSTS="Email forum posts" +PLG_GROUPS_FORUM_EMAIL_POSTS="Email forum post notifications" PLG_GROUPS_FORUM_EMAIL_CATEGORIES="Posts to:" PLG_GROUPS_FORUM_BY_USER="by %s" PLG_GROUPS_FORUM_REPORT_ABUSE="Report abuse" diff --git a/core/plugins/groups/forum/views/email/tmpl/comment_html.php b/core/plugins/groups/forum/views/email/tmpl/comment_html.php index 446a00dc324..f7daca082f0 100644 --- a/core/plugins/groups/forum/views/email/tmpl/comment_html.php +++ b/core/plugins/groups/forum/views/email/tmpl/comment_html.php @@ -15,7 +15,8 @@ $bgcolor = '#f1f1f1'; $bdcolor = '#e1e1e1'; ?> -delimiter) { ?> +delimiter) { + if(Component::params('com_groups')->get('email_comment_processing')) { ?> @@ -27,6 +28,8 @@
+ + diff --git a/core/plugins/groups/forum/views/email/tmpl/comment_plain.php b/core/plugins/groups/forum/views/email/tmpl/comment_plain.php index c50642a2048..0d3eb80e011 100644 --- a/core/plugins/groups/forum/views/email/tmpl/comment_plain.php +++ b/core/plugins/groups/forum/views/email/tmpl/comment_plain.php @@ -17,7 +17,10 @@ if ($this->delimiter) { $message .= $this->delimiter . "\n"; - $message .= Lang::txt('PLG_GROUPS_FORUM_EMAIL_REPLY_ABOVE') . "\n"; + + if(Component::params('com_groups')->get('email_comment_processing')) + $message .= Lang::txt('PLG_GROUPS_FORUM_EMAIL_REPLY_ABOVE') . "\n"; + $message .= 'Message from ' . $base . ' / ' . Lang::txt('PLG_GROUPS_FORUM_DETAILS_THREAD', $this->thread->get('id')) . "\n"; } $message .= ($this->post->get('anonymous')) ? Lang::txt('JANONYMOUS') : $this->post->creator->get('name') . ' (' . $this->post->creator->get('username') . ')'; diff --git a/core/plugins/groups/forum/views/shared/tmpl/_email_settings.php b/core/plugins/groups/forum/views/shared/tmpl/_email_settings.php index b750cddc9ab..9327e31b269 100644 --- a/core/plugins/groups/forum/views/shared/tmpl/_email_settings.php +++ b/core/plugins/groups/forum/views/shared/tmpl/_email_settings.php @@ -7,7 +7,7 @@ $base = $this->base; -if (Component::params('com_groups')->get('email_comment_processing') && $this->config->get('access-view-section')) : ?> +if (Component::params('com_groups')->get('email_forum_comments') && $this->config->get('access-view-section')) : ?>

diff --git a/core/plugins/groups/forum/views/threads/tmpl/_comment.php b/core/plugins/groups/forum/views/threads/tmpl/_comment.php index 73811a2382a..7b347c1f925 100644 --- a/core/plugins/groups/forum/views/threads/tmpl/_comment.php +++ b/core/plugins/groups/forum/views/threads/tmpl/_comment.php @@ -7,7 +7,25 @@ defined('_HZEXEC_') or die(); - $this->comment->set('section', $this->filters['section']); +$this->css('like.css') + ->js('like.js'); + + $likeArray = $this->like; + $countLike = count($likeArray); + $currentUserId = User::get('id'); + + $userLikesComment = false; + $userNameLikesArray = ""; + foreach ( $likeArray as $likeObj ) { + if ( $currentUserId == $likeObj->userId ) { + $userLikesComment = true; + } + + $userNameLikesArray .= "/" . ($likeObj->userName) . "#" . ($likeObj->userId); + } + $userNameLikesArray = substr($userNameLikesArray,1); + + $this->comment->set('section', $this->filters['section']); $this->comment->set('category', $this->category->get('alias')); $this->config->set('access-edit-post', false); @@ -60,7 +78,44 @@

-
+ + + +
+ +
+ 0) { ?> +
+ $userName"; + } + echo join(", ", $links) . " liked this"; + ?> +
+ +
+
+
comment->attachments()->whereEquals('state', Components\Forum\Models\Attachment::STATE_PUBLISHED)->rows() as $attachment) @@ -150,7 +205,7 @@ thread->get('closed') && $this->config->get('threading') == 'tree' && $this->depth < $this->config->get('threading_depth', 3)) { ?>
-
+
comment->get('anonymous') ? $name : Lang::txt('JANONYMOUS'))); ?> @@ -175,10 +230,11 @@
-
@@ -217,7 +273,8 @@ ->set('option', $this->option) ->set('group', $this->group) ->set('comments', $this->comment->get('replies')) - ->set('thread', $this->thread) + ->set('thread', $this->thread) + ->set('likes', $this->likes) ->set('parent', $this->comment->get('id')) ->set('config', $this->config) ->set('depth', $this->depth) diff --git a/core/plugins/groups/forum/views/threads/tmpl/_list.php b/core/plugins/groups/forum/views/threads/tmpl/_list.php index b96f5d5873f..bc30edba6ce 100644 --- a/core/plugins/groups/forum/views/threads/tmpl/_list.php +++ b/core/plugins/groups/forum/views/threads/tmpl/_list.php @@ -8,6 +8,18 @@ // No direct access defined('_HZEXEC_') or die(); +$hash_map = array(); +if (isset($this->likes)) { + foreach ($this->likes as $like){ + $postId = $like->postId; + if (isset($hash_map[$postId])) { + $hash_map[$postId][] = $like; + } else { + $hash_map[$postId] = array($like); + } + } +} + ?>
    depth++; - foreach ($this->comments as $comment) - { + foreach ($this->comments as $comment) { + $postId = $comment->get('id'); + $likesByPostId = isset($hash_map[$postId]) ? $hash_map[$postId] : []; + $this->view('_comment') ->set('option', $this->option) ->set('group', $this->group) ->set('comment', $comment) + ->set('like', $likesByPostId) + ->set('likes', $this->likes) ->set('thread', $this->thread) ->set('config', $this->config) ->set('depth', $this->depth) diff --git a/core/plugins/groups/forum/views/threads/tmpl/display.php b/core/plugins/groups/forum/views/threads/tmpl/display.php index fa193178ad0..069128c70c3 100644 --- a/core/plugins/groups/forum/views/threads/tmpl/display.php +++ b/core/plugins/groups/forum/views/threads/tmpl/display.php @@ -70,6 +70,7 @@ ->set('group', $this->group) ->set('comments', $posts) ->set('thread', $this->thread) + ->set('likes', $this->likes) ->set('parent', 0) ->set('config', $this->config) ->set('depth', 0) diff --git a/core/plugins/groups/memberoptions/language/en-GB/en-GB.plg_groups_memberoptions.ini b/core/plugins/groups/memberoptions/language/en-GB/en-GB.plg_groups_memberoptions.ini index 1c0a9d011a1..6838dbfffdf 100644 --- a/core/plugins/groups/memberoptions/language/en-GB/en-GB.plg_groups_memberoptions.ini +++ b/core/plugins/groups/memberoptions/language/en-GB/en-GB.plg_groups_memberoptions.ini @@ -6,6 +6,6 @@ GROUP_MEMBEROPTIONS="Member Options" GROUP_MEMBEROPTIONS_DESC="These settings are used to specify group specific configuration options for your account:" -GROUP_RECEIVE_EMAILS_DISCUSSION_POSTS="Enable Email for posts" +GROUP_RECEIVE_EMAILS_DISCUSSION_POSTS="Enable outgoing Email notifications for posts" GROUP_MEMBEROPTIONS_NONE="There are currently no user configuratble options for this group." GROUP_MEMBEROPTIONS_UPDATED="You have successfully updated your email settings" diff --git a/core/plugins/groups/memberoptions/views/browse/tmpl/default.php b/core/plugins/groups/memberoptions/views/browse/tmpl/default.php index 84c6de7b16a..d49f4ba593b 100644 --- a/core/plugins/groups/memberoptions/views/browse/tmpl/default.php +++ b/core/plugins/groups/memberoptions/views/browse/tmpl/default.php @@ -10,11 +10,11 @@ $params = $params = Component::params('com_groups'); -$allowEmailResponses = $params->get('email_comment_processing'); +$forumCommentEmailNotifications = $params->get('email_forum_comments'); // Be sure to update this if you add more options $atLeastOneOption = false; -if ($allowEmailResponses) +if ($forumCommentEmailNotifications) { $atLeastOneOption = true; } @@ -32,7 +32,7 @@

    - +
    recvEmailOptionValue == 1) { echo 'checked="checked"'; } ?> />