Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CMKC] Users can "Like" Forum Post (NEW FEATURE) #1734

Open
wants to merge 18 commits into
base: dev
Choose a base branch
from
Open
82 changes: 82 additions & 0 deletions core/components/com_forum/api/controllers/likesv1_0.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php
/**
* @package hubzero-cms
* @copyright Copyright (c) 2005-2020 The Regents of the University of California.
* @license http://opensource.org/licenses/MIT MIT
*/

namespace Components\Forum\Api\Controllers;

use Hubzero\Component\ApiController;
use Hubzero\Utility\Date;
use Component;
use Exception;
use stdClass;
use Request;
use Config;
use Event;
use Route;
use Lang;
use User;

/**
* API controller class for forum posts
*/
class Likesv1_0 extends ApiController {

/**
* GET: List ALL the likes from table
*/
// https://woo.aws.hubzero.org/api/forum/likes/list
public function listTask() {
$database = \App::get('db');
$query = "SELECT * FROM `#__forum_posts_like`";
$database->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);
}
}
46 changes: 22 additions & 24 deletions core/components/com_forum/api/router.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

use Hubzero\Content\Migration\Base;

// No direct access
defined('_HZEXEC_') or die();

/**
* Migration script for adding a new table for forum likes
**/
class Migration20240815000000ComForum extends Base {
public function up(){
// Create table for forum likes.
if (!$this->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();
}
}
42 changes: 42 additions & 0 deletions core/components/com_forum/site/assets/css/like.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* Should put this in the comments.less file */
.comment-body a.like {
color: gray;
margin-right: 5px;
font-size:20px;
}

.comment-body a.userLiked {
color: red;
}

.comment-body .likesStat {
font-size: 14px;
vertical-align:middle;
margin-top: 5px;
}

.comment-body .likesStat:hover {
cursor: pointer;
color: black;
text-decoration: underline;
}

.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;
}
143 changes: 143 additions & 0 deletions core/components/com_forum/site/assets/js/like.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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');

likeStatsLink.onclick = (e) => {
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) ? 'Like' : `Like (${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(`<a href=${userProfileUrl} target='_blank'>${userName}</a>`);
}

whoLikedPostDiv.innerHTML = "<div class='names'>" + whoLikedArray.join(', ') + " liked this</div>";
} else {
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].join('/');

likeButton.dataset.count = `${newLikeCount}`;
likeButton.classList.add("userLiked");
likeButton.dataset.likesList = newLikesString;
likeStatsLink.innerHTML = `Like (${newLikeCount})`;

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(`<a href=${userProfileUrl} target='_blank'>${userName}</a>`);
}

whoLikedPostDiv.innerHTML = "<div class='names'>" + whoLikedArray.join(', ') + " liked this</div>";

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;
}
11 changes: 11 additions & 0 deletions core/components/com_forum/site/controllers/threads.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -192,6 +202,7 @@ public function displayTask()
->set('category', $category)
->set('thread', $thread)
->set('filters', $filters)
->set('likes', $initialLikesList)
->setErrors($this->getErrors())
->display();
}
Expand Down
Loading